export interface TextPart { text: string; isLink: boolean; href?: string; } function escapeRegExp(string: string): string { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } /** * Разбивает текст на части, помечая вхождения ключевых слов как ссылки. Ключевые слова * обрабатываются от самого длинного к короткому (чтобы длинные совпадения имели приоритет), * сравнение регистронезависимое. Уже размеченные ссылки повторно не разбиваются. * @param text - исходный текст * @param links - карта `ключевое слово → значение ссылки` * @param resolveHref - функция, превращающая значение ссылки в href */ export function splitTextByKeywordLinks( text: string, links: Record, resolveHref: (linkValue: string) => string, ): TextPart[] { if (!text) return []; const sortedKeywords = Object.keys(links).sort((a, b) => b.length - a.length); const parts: TextPart[] = []; for (const keyword of sortedKeywords) { const href = resolveHref(links[keyword]); const regex = new RegExp(`(${escapeRegExp(keyword)})`, 'gi'); const newParts: TextPart[] = []; for (const part of parts.length ? parts : [{ text, isLink: false }]) { if (!part.isLink) { const segments = part.text.split(regex); for (let i = 0; i < segments.length; i++) { if (segments[i]) { newParts.push({ text: segments[i], isLink: i % 2 === 1, href: i % 2 === 1 ? href : undefined, }); } } } else { newParts.push(part); } } parts.length = 0; parts.push(...newParts); } if (parts.length === 0) { return [{ text, isLink: false }]; } return parts; }