diff --git a/web/src/components/MemoContent/MermaidBlock.tsx b/web/src/components/MemoContent/MermaidBlock.tsx index d7511e65e..c4edc195a 100644 --- a/web/src/components/MemoContent/MermaidBlock.tsx +++ b/web/src/components/MemoContent/MermaidBlock.tsx @@ -1,5 +1,5 @@ import mermaid from "mermaid"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useAuth } from "@/contexts/AuthContext"; import { cn } from "@/lib/utils"; import { getThemeWithFallback, resolveTheme, setupSystemThemeListener } from "@/utils/theme"; @@ -10,82 +10,77 @@ interface MermaidBlockProps { className?: string; } -const getMermaidTheme = (appTheme: string): "default" | "dark" => { - return appTheme === "default-dark" ? "dark" : "default"; +type MermaidTheme = "default" | "dark"; + +const toMermaidTheme = (appTheme: string): MermaidTheme => (appTheme === "default-dark" ? "dark" : "default"); + +const formatErrorMessage = (err: unknown): string => { + const msg = err instanceof Error ? err.message : "Failed to render diagram"; + if (/no diagram type detected/i.test(msg)) { + return `${msg} — check that the diagram type is valid (e.g. sequenceDiagram, classDiagram, erDiagram)`; + } + return msg; }; export const MermaidBlock = ({ children, className }: MermaidBlockProps) => { const { userGeneralSetting } = useAuth(); - const containerRef = useRef(null); const [svg, setSvg] = useState(""); const [error, setError] = useState(""); const [systemThemeChange, setSystemThemeChange] = useState(0); const codeContent = extractCodeContent(children); - - // Get theme preference (reactive via AuthContext) - // Falls back to localStorage or system preference if no user setting const themePreference = getThemeWithFallback(userGeneralSetting?.theme); - - // Resolve theme to actual value (handles "system" theme + system theme changes) const currentTheme = useMemo(() => resolveTheme(themePreference), [themePreference, systemThemeChange]); - // Listen for OS theme changes when using "system" theme preference + // Re-resolve theme when OS preference changes (only relevant when using "system" theme) useEffect(() => { - if (themePreference !== "system") { - return; - } - - return setupSystemThemeListener(() => { - setSystemThemeChange((prev) => prev + 1); - }); + if (themePreference !== "system") return; + return setupSystemThemeListener(() => setSystemThemeChange((n) => n + 1)); }, [themePreference]); - // Render Mermaid diagram when content or theme changes + // Initialize Mermaid when theme changes useEffect(() => { - if (!codeContent || !containerRef.current) { - return; - } + mermaid.initialize({ + startOnLoad: false, + theme: toMermaidTheme(currentTheme), + securityLevel: "strict", + fontFamily: "inherit", + suppressErrorRendering: true, + }); + }, [currentTheme]); - const renderDiagram = async () => { - try { - const id = `mermaid-${Math.random().toString(36).substring(7)}`; - const mermaidTheme = getMermaidTheme(currentTheme); + // Render diagram when content or theme changes + useEffect(() => { + if (!codeContent) return; - mermaid.initialize({ - startOnLoad: false, - theme: mermaidTheme, - securityLevel: "strict", - fontFamily: "inherit", - }); + const id = `mermaid-${Math.random().toString(36).substring(7)}`; - const { svg: renderedSvg } = await mermaid.render(id, codeContent); + mermaid + .render(id, codeContent) + .then(({ svg: renderedSvg }) => { setSvg(renderedSvg); setError(""); - } catch (err) { + }) + .catch((err) => { console.error("Failed to render mermaid diagram:", err); - setError(err instanceof Error ? err.message : "Failed to render diagram"); - } - }; - - renderDiagram(); + setSvg(""); + setError(formatErrorMessage(err)); + }); }, [codeContent, currentTheme]); - // If there's an error, fall back to showing the code if (error) { return (
-
Mermaid Error: {error}
-
-          {codeContent}
-        
+
Mermaid Error: {error}
+ {codeContent}
); } + if (!svg) return null; + return (
diff --git a/web/src/components/MemoContent/index.tsx b/web/src/components/MemoContent/index.tsx index bcacd67f1..e00d773e3 100644 --- a/web/src/components/MemoContent/index.tsx +++ b/web/src/components/MemoContent/index.tsx @@ -51,7 +51,7 @@ const MemoContent = (props: MemoContentProps) => { > & { node?: Element }) => { diff --git a/web/src/index.css b/web/src/index.css index 680b91673..5bef77ee6 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -25,6 +25,13 @@ border: 1px solid var(--border); } + /* KaTeX math rendering */ + .katex-display { + overflow-x: auto; + overflow-y: hidden; + max-width: 100%; + } + /* Leaflet Popup Overrides */ .leaflet-popup-content-wrapper { border-radius: 0.5rem !important;