/** * Renders a Mermaid diagram from code string to SVG. * * Lazy-initializes mermaid once with dark theme. * Each render call uses a unique ID to avoid collisions. * SVG output is sanitized with DOMPurify before DOM insertion. * Falls back to raw code display on parse errors. */ import React, { useEffect, useRef, useState } from 'react'; import { PROSE_PRE_BG, PROSE_PRE_BORDER } from '@renderer/constants/cssVariables'; import DOMPurify from 'dompurify'; import mermaid from 'mermaid'; // ============================================================================= // Mermaid initialization (once per app lifecycle) // ============================================================================= let initialized = false; function ensureMermaidInit(): void { if (initialized) return; mermaid.initialize({ startOnLoad: false, theme: 'dark', themeVariables: { darkMode: true, background: 'transparent', primaryColor: '#3b82f6', primaryTextColor: '#fafafa', primaryBorderColor: '#3b82f6', lineColor: '#71717a', secondaryColor: '#27272a', tertiaryColor: '#1f1f23', }, }); initialized = true; } // Monotonic counter for unique diagram IDs let idCounter = 0; // ============================================================================= // Component // ============================================================================= interface MermaidDiagramProps { code: string; } export const MermaidDiagram = React.memo(function MermaidDiagram({ code, }: MermaidDiagramProps): React.ReactElement { const containerRef = useRef(null); const [error, setError] = useState(null); useEffect(() => { if (!code.trim()) return; ensureMermaidInit(); let cancelled = false; const diagramId = `mermaid-${++idCounter}`; mermaid .render(diagramId, code.trim()) .then(({ svg }) => { if (!cancelled && containerRef.current) { const sanitized = DOMPurify.sanitize(svg, { USE_PROFILES: { svg: true, svgFilters: true }, ADD_TAGS: ['foreignObject'], }); containerRef.current.replaceChildren(); containerRef.current.insertAdjacentHTML('afterbegin', sanitized); setError(null); } }) .catch((err: unknown) => { if (!cancelled) { const msg = err instanceof Error ? err.message : String(err); setError(msg); } }); return () => { cancelled = true; }; }, [code]); if (error) { return (
Mermaid syntax error
{code}
); } return (
); });