import { useEffect, useState, useRef, useCallback } from "react"; import { codeToHtml, type BundledLanguage } from "shiki"; import { cn } from "../lib/utils"; import { IconCopy, IconCheck } from "./icons"; // Lazily loaded, cached across renders const highlightCache = new Map(); function cacheKey(code: string, lang: string) { return `${lang}::${code.length}::${code.slice(0, 64)}`; } function detectLanguage(code: string, hint?: string): BundledLanguage { if (hint) return hint as BundledLanguage; const trimmed = code.trimStart(); if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json"; if (trimmed.startsWith("<")) return "xml"; if (trimmed.startsWith("---")) return "yaml"; return "json"; } export function CodeBlock(props: { code: string; lang?: string; className?: string; }) { const { code, lang: langHint, className } = props; const [html, setHtml] = useState(null); const [copied, setCopied] = useState(false); const mountedRef = useRef(true); const language = detectLanguage(code, langHint); const key = cacheKey(code, language); useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); useEffect(() => { const cached = highlightCache.get(key); if (cached) { setHtml(cached); return; } let cancelled = false; codeToHtml(code, { lang: language, theme: "vitesse-dark", }).then((result) => { if (cancelled) return; highlightCache.set(key, result); if (mountedRef.current) setHtml(result); }); return () => { cancelled = true; }; }, [code, key, language]); const handleCopy = useCallback(() => { void navigator.clipboard.writeText(code).then(() => { setCopied(true); setTimeout(() => setCopied(false), 1500); }); }, [code]); return (
{/* Copy button */} {html ? (
) : ( // Fallback while shiki loads
          {code}
        
)}
); }