/** * The same rule the server applies to every name in the Generator's lists (server/src/services/nameThemes.ts), so the * editor can point out a bad name while it's being typed. The server checks again on save and is the one that decides. */ export function normalizeNameInput(raw: string): string | null { const folded = raw .normalize("NFD") .replace(/[̀-ͯ]/g, "") .trim() .toLowerCase(); return /^[a-z0-9](?:[a-z0-9-]{0,28}[a-z0-9])?$/.test(folded) ? folded : null; } /** Names are separated by line breaks, commas, semicolons or spaces. */ export function splitNames(text: string): string[] { return text.split(/[\s,;]+/).filter(Boolean); } /** The usable names in `text` (folded and de-duplicated, in the order written) and whatever couldn't be used. */ export function parseNames(text: string): { names: string[]; invalid: string[] } { const names: string[] = []; const invalid: string[] = []; const seen = new Set(); for (const token of splitNames(text)) { const clean = normalizeNameInput(token); if (clean === null) invalid.push(token); else if (!seen.has(clean)) { seen.add(clean); names.push(clean); } } return { names, invalid }; }