import { Marked } from 'marked'; import DOMPurify from 'dompurify'; import hljs from 'highlight.js/lib/core'; import javascript from 'highlight.js/lib/languages/javascript'; import bash from 'highlight.js/lib/languages/bash'; import typescript from 'highlight.js/lib/languages/typescript'; import json from 'highlight.js/lib/languages/json'; hljs.registerLanguage('javascript', javascript); hljs.registerLanguage('js', javascript); hljs.registerLanguage('typescript', typescript); hljs.registerLanguage('ts', typescript); hljs.registerLanguage('bash', bash); hljs.registerLanguage('sh', bash); hljs.registerLanguage('json', json); /** * Lokaler Marked-Instance, damit die globale `marked`-Singleton nicht * mutiert wird — andere Module können `marked` unbeeinflusst weiterverwenden. * (Spec §3: lokale Ersetzbarkeit der Engine.) */ const markedInstance = new Marked({ breaks: true, gfm: true, renderer: { code({ text, lang }) { const language = lang && hljs.getLanguage(lang) ? lang : undefined; const highlighted = language ? hljs.highlight(text, { language }).value : hljs.highlightAuto(text).value; const cls = language ? ` language-${language}` : ''; return `
${highlighted}
`; } } }); /** * Rendert einen Markdown-String zu sanitized HTML. * Einziger Export des Moduls — so bleibt Austausch der Engine lokal. * * Nur im Browser/jsdom aufrufen: DOMPurify braucht ein DOM. Die SPA * hat SSR global ausgeschaltet (`+layout.ts: ssr = false`), Vitest läuft * in jsdom — beide Szenarien sind abgedeckt. Ein Aufruf in reiner * Node-Umgebung würde hier laut fehlschlagen statt stumm unsicher * durchzulaufen. */ export function renderMarkdown(md: string): string { if (typeof window === 'undefined') { throw new Error('renderMarkdown: DOM-Kontext erforderlich (Browser oder jsdom).'); } const raw = markedInstance.parse(md, { async: false }) as string; return DOMPurify.sanitize(raw); }