// Mentions "@Pseudo" dans le texte d'une question du Char — repère qui est // visé par une question sans ajouter de colonne : le pseudo mentionné est // retrouvé par simple correspondance de texte contre la liste des membres // (pas de state séparé à synchroniser avec une édition du texte). export type MentionRange = { start: number; end: number; pseudo: string }; function isWordChar(char: string | undefined): boolean { return !!char && /[\p{L}\p{N}_]/u.test(char); } // Les pseudos les plus longs sont recherchés en premier pour qu'un pseudo // qui en préfixe un autre (ex. "Priape" / "Priapee") ne vole pas le match // du plus long à l'endroit où il apparaît vraiment. export function findMentionRanges(text: string, pseudos: string[]): MentionRange[] { const candidates = [...new Set(pseudos)].filter(Boolean).sort((a, b) => b.length - a.length); const lowerText = text.toLowerCase(); const claimed = new Array(text.length).fill(false); const ranges: MentionRange[] = []; for (const pseudo of candidates) { const needle = `@${pseudo.toLowerCase()}`; let fromIndex = 0; while (fromIndex <= lowerText.length) { const idx = lowerText.indexOf(needle, fromIndex); if (idx === -1) break; fromIndex = idx + 1; const end = idx + needle.length; if (isWordChar(text[end])) continue; if (claimed.slice(idx, end).some(Boolean)) continue; for (let i = idx; i < end; i++) claimed[i] = true; ranges.push({ start: idx, end, pseudo }); } } return ranges.sort((a, b) => a.start - b.start); } export function extractMentionedPseudos(text: string, pseudos: string[]): string[] { return findMentionRanges(text, pseudos).map((range) => range.pseudo); } // Retire les "@Pseudo" du texte affiché (ils vivent à part, en métadonnée à // côté de la question — un peu comme une réponse — plutôt qu'au milieu de // la phrase) et renvoie les pseudos visés, dans leur ordre d'apparition et // sans doublon si une même personne est taguée plusieurs fois. export function stripMentions(text: string, pseudos: string[]): { cleanText: string; mentioned: string[] } { const ranges = findMentionRanges(text, pseudos); if (ranges.length === 0) return { cleanText: text, mentioned: [] }; let cleanText = ""; let cursor = 0; const seen = new Set(); const mentioned: string[] = []; for (const range of ranges) { cleanText += text.slice(cursor, range.start); if (!seen.has(range.pseudo)) { seen.add(range.pseudo); mentioned.push(range.pseudo); } cursor = range.end; } cleanText += text.slice(cursor); // Nettoie les espaces laissés par les mentions retirées (double espace // au milieu de la phrase, espace parasite avant une ponctuation, bords). cleanText = cleanText .replace(/[ \t]{2,}/g, " ") .replace(/\s+([,.!?;:])/g, "$1") .trim(); return { cleanText, mentioned }; }