fix(frontend): clean up failed mermaid renders

Signed-off-by: dongmucat <1127093059@qq.com>
This commit is contained in:
dongmucat 2026-09-21 09:59:17 +08:00
parent 804991fe89
commit 5e5fedf22c
3 changed files with 22 additions and 6 deletions

View file

@ -55,9 +55,11 @@ test.describe('Markdown Mermaid rendering (Real API)', () => {
await page.goto(`/space/${encodeURIComponent(namespace.slug)}/${encodeURIComponent(skill.slug)}`)
await expect(page.locator('[data-mermaid-error]')).toBeVisible()
const source = page.locator('pre code.language-mermaid')
await expect(source).toContainText('this is not a Mermaid diagram')
await expect(page.locator('[data-mermaid-diagram]')).toHaveCount(0)
await expect(page.locator('body > div[id^="dmermaid-"]')).toHaveCount(0)
} finally {
await builder.cleanup()
}

View file

@ -92,7 +92,11 @@ describe('MarkdownRenderer Mermaid blocks', () => {
await waitFor(() => expect(container.querySelector('[data-testid="mermaid-svg"]')).toBeTruthy())
expect(mermaidInitialize).toHaveBeenCalledWith({ startOnLoad: false, securityLevel: 'strict' })
expect(mermaidInitialize).toHaveBeenCalledWith({
startOnLoad: false,
securityLevel: 'strict',
suppressErrorRendering: true,
})
expect(container.querySelector('[data-testid="mermaid-svg"]')?.closest('pre')).toBeNull()
})
@ -103,7 +107,7 @@ describe('MarkdownRenderer Mermaid blocks', () => {
<MarkdownRenderer content={'```mermaid\nnot a valid diagram\n```'} />,
)
await waitFor(() => expect(mermaidRender).toHaveBeenCalled())
await waitFor(() => expect(container.querySelector('[data-mermaid-error]')).toBeTruthy())
expect(container.querySelector('pre code')?.textContent).toContain('not a valid diagram')
})

View file

@ -28,7 +28,11 @@ let mermaidBlockSequence = 0
function loadMermaid(): Promise<MermaidApi> {
mermaidPromise ??= import('mermaid').then(({ default: mermaid }) => {
mermaid.initialize({ startOnLoad: false, securityLevel: 'strict' })
mermaid.initialize({
startOnLoad: false,
securityLevel: 'strict',
suppressErrorRendering: true,
})
return mermaid
})
@ -60,9 +64,12 @@ function getTextContent(node: ReactNode): string {
.join('')
}
function CodeBlock({ children }: { children: ReactNode }) {
function CodeBlock({ children, mermaidError }: { children: ReactNode; mermaidError?: boolean }) {
return (
<div className="my-4 rounded-lg border border-border/60 bg-secondary/30">
<div
className="my-4 rounded-lg border border-border/60 bg-secondary/30"
data-mermaid-error={mermaidError || undefined}
>
<div className="max-w-full overflow-x-auto rounded-lg bg-background px-4 py-3">
<pre className="m-0 min-w-max bg-transparent p-0 text-[13px] leading-6">{children}</pre>
</div>
@ -79,10 +86,12 @@ const MermaidBlock = memo(function MermaidBlock({ children }: MermaidBlockProps)
const renderId = useRef(`mermaid-${++mermaidBlockSequence}`).current
const diagramRef = useRef<HTMLDivElement>(null)
const [svg, setSvg] = useState<string | null>(null)
const [failed, setFailed] = useState(false)
useEffect(() => {
let mounted = true
setSvg(null)
setFailed(false)
enqueueMermaidRender(async () => {
const mermaid = await loadMermaid()
@ -96,6 +105,7 @@ const MermaidBlock = memo(function MermaidBlock({ children }: MermaidBlockProps)
.catch(() => {
if (mounted) {
setSvg(null)
setFailed(true)
}
})
@ -111,7 +121,7 @@ const MermaidBlock = memo(function MermaidBlock({ children }: MermaidBlockProps)
}, [svg])
if (!svg) {
return <CodeBlock>{children}</CodeBlock>
return <CodeBlock mermaidError={failed}>{children}</CodeBlock>
}
return (