import hljs from 'highlight.js' const LANGUAGE_ALIASES: Record = { shellscript: 'bash', sh: 'bash', zsh: 'bash', yml: 'yaml', vue: 'xml', } function escapeHtml(value: string): string { return value .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", ''') } function sanitizeLanguageClass(value: string): string { return value.replace(/[^a-z0-9_-]/gi, '-') || 'plain' } export function normalizeHighlightLanguage(lang?: string): string { const normalized = lang?.trim().toLowerCase() || '' return LANGUAGE_ALIASES[normalized] || normalized } export function inferStructuredLanguage(content: string): string | undefined { try { JSON.parse(content) return 'json' } catch { return undefined } } type RenderHighlightedCodeBlockOptions = { maxHighlightLength?: number } export function renderHighlightedCodeBlock( content: string, lang: string | undefined, copyLabel: string, options: RenderHighlightedCodeBlockOptions = {}, ): string { const requestedLanguage = lang?.trim().toLowerCase() || '' const normalizedLanguage = normalizeHighlightLanguage(requestedLanguage) const highlightLimit = options.maxHighlightLength ?? Number.POSITIVE_INFINITY let highlighted = '' let codeClassLanguage = normalizedLanguage || requestedLanguage || 'plain' let labelLanguage = requestedLanguage try { if (normalizedLanguage && hljs.getLanguage(normalizedLanguage) && content.length <= highlightLimit) { highlighted = hljs.highlight(content, { language: normalizedLanguage, ignoreIllegals: true, }).value codeClassLanguage = normalizedLanguage } else { highlighted = escapeHtml(content) if (!labelLanguage) { labelLanguage = 'text' } } } catch { highlighted = escapeHtml(content) if (!labelLanguage) { labelLanguage = 'text' } } const languageLabelHtml = labelLanguage ? `${escapeHtml(labelLanguage)}` : '' return `
${languageLabelHtml}
${highlighted}
` } export async function copyTextToClipboard(text: string): Promise { try { await navigator.clipboard?.writeText?.(text) } catch { // Ignore clipboard failures; the code block still renders safely. } } export async function handleCodeBlockCopyClick(event: MouseEvent): Promise { const target = event.target if (!(target instanceof HTMLElement)) return const button = target.closest('[data-copy-code="true"]') if (!button) return event.preventDefault() const block = button.closest('.hljs-code-block') const code = block?.querySelector('code') const text = code?.textContent ?? '' if (!text) return await copyTextToClipboard(text) }