From 5708fb472bd79dacd282e8e4e6da5aa7cbb359f4 Mon Sep 17 00:00:00 2001 From: tianma-if Date: Mon, 3 Aug 2026 19:33:25 +0800 Subject: [PATCH] restore compact Mermaid presentation --- apps/web/src/components/MermaidCodeBlock.tsx | 19 ++----- apps/web/src/components/ThemeProvider.tsx | 6 +-- apps/web/src/lib/mermaid-renderer.test.mjs | 5 ++ apps/web/src/lib/mermaid-theme.test.mjs | 32 ++++++++++++ apps/web/src/lib/mermaid-theme.ts | 51 +++++++++++++++++++ apps/web/src/styles/globals.css | 4 +- .../web/src/styles/mobile-markdown-editor.css | 4 +- 7 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 apps/web/src/lib/mermaid-theme.test.mjs create mode 100644 apps/web/src/lib/mermaid-theme.ts diff --git a/apps/web/src/components/MermaidCodeBlock.tsx b/apps/web/src/components/MermaidCodeBlock.tsx index 4e26eeb7..80edf2a1 100644 --- a/apps/web/src/components/MermaidCodeBlock.tsx +++ b/apps/web/src/components/MermaidCodeBlock.tsx @@ -6,6 +6,7 @@ import { MERMAID_THEME_PALETTES, useTheme } from "./ThemeProvider"; import { MermaidViewer } from "./MermaidViewer"; import { copyTextToClipboard } from "@/lib/clipboard"; import { renderMermaidWithFallback } from "@/lib/mermaid-renderer"; +import { getOfficialMermaidThemeVariables } from "@/lib/mermaid-theme"; type MermaidModule = typeof import("mermaid")["default"]; type BeautifulMermaidModule = typeof import("beautiful-mermaid"); @@ -79,22 +80,7 @@ export const MermaidCodeBlock = ({ editor, node }: NodeViewProps) => { securityLevel: "strict", suppressErrorRendering: true, theme: "base", - themeVariables: { - background: palette.bg, - primaryColor: palette.surface ?? palette.bg, - primaryTextColor: palette.fg, - primaryBorderColor: palette.border ?? palette.fg, - lineColor: palette.line ?? palette.fg, - textColor: palette.fg, - mainBkg: palette.bg, - nodeBorder: palette.border ?? palette.fg, - edgeLabelBackground: palette.bg, - actorBkg: palette.surface ?? palette.bg, - actorBorder: palette.border ?? palette.fg, - actorTextColor: palette.fg, - signalColor: palette.line ?? palette.fg, - signalTextColor: palette.fg, - }, + themeVariables: getOfficialMermaidThemeVariables(palette), }); const valid = await mermaid.parse(source, { suppressErrors: true }); if (!valid) throw new Error("Invalid Mermaid diagram"); @@ -237,6 +223,7 @@ export const MermaidCodeBlock = ({ editor, node }: NodeViewProps) => { className="edgeever-mermaid-preview" contentEditable={false} aria-label={t("editorToolbar.mermaidPreview")} + style={{ backgroundColor: MERMAID_THEME_PALETTES[mermaidTheme].bg }} > {!source &&

{t("editorToolbar.mermaidEmpty")}

} {source && renderState === "loading" && !svg && ( diff --git a/apps/web/src/components/ThemeProvider.tsx b/apps/web/src/components/ThemeProvider.tsx index 65fc21f7..326f6334 100644 --- a/apps/web/src/components/ThemeProvider.tsx +++ b/apps/web/src/components/ThemeProvider.tsx @@ -21,7 +21,7 @@ export const MERMAID_THEME_NAMES = [ ] as const; export type MermaidThemeName = (typeof MERMAID_THEME_NAMES)[number]; -interface MermaidThemePalette { +export interface MermaidThemePalette { bg: string; fg: string; line?: string; @@ -157,9 +157,9 @@ export const getStoredMermaidTheme = (): MermaidThemeName => { }; export const getStoredMermaidRenderer = (): MermaidRenderer => { - if (typeof window === "undefined") return "mermaid"; + if (typeof window === "undefined") return "beautiful"; const stored = window.localStorage.getItem(MERMAID_RENDERER_STORAGE_KEY); - return MERMAID_RENDERERS.includes(stored as MermaidRenderer) ? stored as MermaidRenderer : "mermaid"; + return MERMAID_RENDERERS.includes(stored as MermaidRenderer) ? stored as MermaidRenderer : "beautiful"; }; export const getStoredEditorTheme = (): string => { diff --git a/apps/web/src/lib/mermaid-renderer.test.mjs b/apps/web/src/lib/mermaid-renderer.test.mjs index 5c821f00..d364cfff 100644 --- a/apps/web/src/lib/mermaid-renderer.test.mjs +++ b/apps/web/src/lib/mermaid-renderer.test.mjs @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; import { renderMermaidSVG } from "beautiful-mermaid"; import mermaid from "mermaid"; +import { getStoredMermaidRenderer } from "../components/ThemeProvider"; import { renderMermaidWithFallback } from "./mermaid-renderer"; const GANTT_SOURCE = `gantt @@ -9,6 +10,10 @@ const GANTT_SOURCE = `gantt Compatibility fallback :fallback, 2026-07-30, 1d`; describe("Mermaid renderer fallback", () => { + test("defaults to the compact renderer when no browser preference exists", () => { + expect(getStoredMermaidRenderer()).toBe("beautiful"); + }); + test("uses the official renderer for a valid Gantt diagram unsupported by beautiful-mermaid", async () => { expect(await mermaid.parse(GANTT_SOURCE, { suppressErrors: true })).toBeTruthy(); diff --git a/apps/web/src/lib/mermaid-theme.test.mjs b/apps/web/src/lib/mermaid-theme.test.mjs new file mode 100644 index 00000000..c4985fb0 --- /dev/null +++ b/apps/web/src/lib/mermaid-theme.test.mjs @@ -0,0 +1,32 @@ +import { describe, expect, test } from "bun:test"; +import { MERMAID_THEME_PALETTES } from "../components/ThemeProvider"; +import { getOfficialMermaidThemeVariables } from "./mermaid-theme"; + +describe("official Mermaid theme variables", () => { + test("maps every built-in theme across flowchart, sequence, and state diagrams", () => { + for (const palette of Object.values(MERMAID_THEME_PALETTES)) { + const variables = getOfficialMermaidThemeVariables(palette); + const expectedAccent = palette.accent ?? palette.line ?? palette.fg; + const expectedLine = palette.line ?? palette.muted ?? palette.fg; + const expectedBorder = palette.border ?? palette.muted ?? expectedAccent; + + expect(variables.background).toBe(palette.bg); + expect(variables.primaryTextColor).toBe(palette.fg); + expect(variables.clusterBorder).toBe(expectedBorder); + expect(variables.actorBorder).toBe(expectedBorder); + expect(variables.signalColor).toBe(expectedAccent); + expect(variables.stateBorder).toBe(expectedBorder); + expect(variables.transitionColor).toBe(expectedLine); + } + }); + + test("keeps minimal two-color themes coherent", () => { + const palette = MERMAID_THEME_PALETTES["zinc-light"]; + const variables = getOfficialMermaidThemeVariables(palette); + + expect(variables.background).toBe(palette.bg); + expect(variables.primaryColor).toBe(palette.bg); + expect(variables.primaryBorderColor).toBe(palette.fg); + expect(variables.signalColor).toBe(palette.fg); + }); +}); diff --git a/apps/web/src/lib/mermaid-theme.ts b/apps/web/src/lib/mermaid-theme.ts new file mode 100644 index 00000000..2c310e5c --- /dev/null +++ b/apps/web/src/lib/mermaid-theme.ts @@ -0,0 +1,51 @@ +import type { MermaidThemePalette } from "@/components/ThemeProvider"; + +export const getOfficialMermaidThemeVariables = (palette: MermaidThemePalette) => { + const surface = palette.surface ?? palette.bg; + const accent = palette.accent ?? palette.line ?? palette.fg; + const line = palette.line ?? palette.muted ?? palette.fg; + const border = palette.border ?? palette.muted ?? accent; + + return { + background: palette.bg, + primaryColor: surface, + primaryTextColor: palette.fg, + primaryBorderColor: border, + secondaryColor: surface, + secondaryTextColor: palette.fg, + secondaryBorderColor: border, + tertiaryColor: surface, + tertiaryTextColor: palette.fg, + tertiaryBorderColor: border, + textColor: palette.fg, + lineColor: line, + mainBkg: surface, + nodeBorder: border, + nodeTextColor: palette.fg, + edgeLabelBackground: palette.bg, + clusterBkg: palette.bg, + clusterBorder: border, + titleColor: palette.fg, + actorBkg: surface, + actorBorder: border, + actorTextColor: palette.fg, + actorLineColor: line, + signalColor: accent, + signalTextColor: palette.fg, + labelBoxBkgColor: surface, + labelBoxBorderColor: border, + labelTextColor: palette.fg, + loopTextColor: palette.fg, + noteBkgColor: surface, + noteBorderColor: border, + noteTextColor: palette.fg, + activationBkgColor: surface, + activationBorderColor: accent, + sequenceNumberColor: accent, + stateBkg: surface, + stateBorder: border, + stateLabelColor: palette.fg, + transitionColor: line, + labelBackgroundColor: palette.bg, + }; +}; diff --git a/apps/web/src/styles/globals.css b/apps/web/src/styles/globals.css index 0440941b..97ceb85b 100644 --- a/apps/web/src/styles/globals.css +++ b/apps/web/src/styles/globals.css @@ -576,10 +576,10 @@ textarea { .ProseMirror .edgeever-mermaid-svg svg { display: block; - width: 100%; + width: auto; max-width: 100%; height: auto; - max-height: none; + max-height: 30rem; } .ProseMirror .edgeever-mermaid-source { diff --git a/apps/web/src/styles/mobile-markdown-editor.css b/apps/web/src/styles/mobile-markdown-editor.css index ef12d500..300853df 100644 --- a/apps/web/src/styles/mobile-markdown-editor.css +++ b/apps/web/src/styles/mobile-markdown-editor.css @@ -537,10 +537,10 @@ input { .edgeever-mobile-tiptap-content .edgeever-mermaid-svg svg { display: block; - width: 100%; + width: auto; max-width: 100%; height: auto; - max-height: none; + max-height: 440px; } .edgeever-mobile-tiptap-content .edgeever-mermaid-source {