/** * Tiny, dependency-free TS highlighter for the teaching showcase. Escapes HTML, * then wraps line-comments, strings, and a fixed keyword set in `.c`/`.s`/`.k` spans (the * classes `concepts.page` styles). Deliberately naive — good enough for the short, curated * snippets shown here; not a real tokenizer. Input is always our OWN source (extracted by * `scripts/gen-snippets.mjs` or authored inline), so the `[innerHTML]` sink is safe once * the HTML metacharacters are escaped first. Pure. */ const KEYWORDS = [ 'interface', 'type', 'export', 'import', 'from', 'const', 'let', 'return', 'function', 'switch', 'case', 'default', 'if', 'else', 'new', 'readonly', 'extends', 'as', 'void', ]; const escapeHtml = (s: string): string => s.replace(/&/g, '&').replace(//g, '>'); export function highlightTs(code: string): string { const kw = new RegExp(`\\b(${KEYWORDS.join('|')})\\b`, 'g'); return escapeHtml(code) .split('\n') .map((line) => { // Line comment: everything from // to EOL is one comment span (skip the rest). const c = line.indexOf('//'); const head = c === -1 ? line : line.slice(0, c); const tail = c === -1 ? '' : `${line.slice(c)}`; const lit = head .replace(/(['"`])(?:\\.|(?!\1).)*\1/g, (m) => `${m}`) // strings .replace(kw, '$1'); // keywords return lit + tail; }) .join('\n'); }