Ajoute glisser-déposer, randomisation et tags @pseudo sur /char/questions
Build and deploy / deploy (push) Successful in 37s
Build and deploy / deploy (push) Successful in 37s
Réordonnancement par glisser-déposer (@dnd-kit, support tactile natif pour une page pilotée depuis un téléphone) en complément des flèches existantes, bouton de randomisation de l'ordre (confirmation à deux temps), et tags @pseudo dans le texte des questions — détectés par correspondance de texte (aucune colonne dédiée), avec autocomplétion à la frappe, mise en évidence dans la liste et sur /char, et un panneau de répartition des tags par membre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// 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<boolean>(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);
|
||||
}
|
||||
Reference in New Issue
Block a user