import { useEffect, useRef, useState } from 'react'; import { Check, Copy } from 'lucide-react'; import { codeToHtml } from 'shiki'; // NOTE: spec calls for syntax-highlighted code blocks. Added Shiki in v1.1. // Shiki output is compiler-generated and does not contain user input; setting // it via a ref is safe here. interface Props { code: string; lang?: string; } const LANG_MAP: Record = { ts: 'typescript', tsx: 'tsx', typescript: 'typescript', js: 'javascript', jsx: 'jsx', javascript: 'javascript', py: 'python', python: 'python', go: 'go', rs: 'rust', rust: 'rust', rb: 'ruby', ruby: 'ruby', java: 'java', c: 'c', cpp: 'cpp', cs: 'csharp', csharp: 'csharp', php: 'php', sh: 'bash', bash: 'bash', shell: 'bash', yaml: 'yaml', yml: 'yaml', json: 'json', toml: 'toml', md: 'markdown', markdown: 'markdown', sql: 'sql', dockerfile: 'dockerfile', html: 'html', css: 'css', }; const SHIKI_THEME = 'github-dark'; export function CodeBlock({ code, lang }: Props) { const [copied, setCopied] = useState(false); const [html, setHtml] = useState(null); const highlightRef = useRef(null); useEffect(() => { let cancelled = false; const mappedLang = (lang && LANG_MAP[lang.toLowerCase()]) ?? null; if (!mappedLang) { setHtml(null); return; } (async () => { try { const result = await codeToHtml(code, { lang: mappedLang, theme: SHIKI_THEME }); if (!cancelled) setHtml(result); } catch (err) { console.warn('shiki failed', err); if (!cancelled) setHtml(null); } })(); return () => { cancelled = true; }; }, [code, lang]); // Inject Shiki HTML via ref; output is compiler-generated, not user input. useEffect(() => { if (highlightRef.current) { // Shiki generates sanitized HTML spans — not user-supplied content. // eslint-disable-next-line no-unsanitized/property highlightRef.current.innerHTML = html ?? ''; } }, [html]); async function copy() { try { await navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 1200); } catch { /* ignore */ } } return (
{lang || 'code'}
{html !== null ? (
) : (
          {code}
        
)}
); }