Fix mermaid diagram arrowheads not rendering
The `@boneskull/typedoc-plugin-mermaid` creates both dark and light theme variants of each diagram, each with identical marker IDs (e.g., `#arrowhead`). When mermaid renders SVGs at runtime, lines reference `url(#arrowhead)`, but the browser resolves this to the first match in document order — the hidden dark-theme SVG — causing arrows to be invisible. The fix removes dark-theme `<div class="mermaid dark">` elements at build time, leaving only one SVG per diagram with unique marker IDs. A CSS filter (`filter: invert(1) hue-rotate(180deg)`) handles dark mode styling instead. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0058ad85bc
commit
4546a686c3
1 changed files with 54 additions and 6 deletions
|
|
@ -1,12 +1,15 @@
|
||||||
/**
|
/**
|
||||||
* TypeDoc plugin that properly decodes HTML entities in mermaid code blocks.
|
* TypeDoc plugin that fixes mermaid diagram rendering issues.
|
||||||
*
|
*
|
||||||
* This runs BEFORE the mermaid plugin and converts HTML entities back to raw
|
* 1. Decodes HTML entities in mermaid code blocks before the mermaid plugin
|
||||||
* characters, allowing mermaid to parse them correctly.
|
* processes them. The @boneskull/typedoc-plugin-mermaid converts < to #lt;
|
||||||
|
* and > to #gt;, but those aren't valid mermaid entities.
|
||||||
*
|
*
|
||||||
* The @boneskull/typedoc-plugin-mermaid converts < to #lt; and > to #gt;,
|
* 2. Removes dark-theme mermaid divs to fix duplicate marker IDs. The mermaid
|
||||||
* but those aren't valid mermaid entities (mermaid uses numeric codes like #60;).
|
* plugin creates both dark and light variants with identical marker IDs
|
||||||
* This plugin sidesteps the issue by decoding entities before mermaid sees them.
|
* (e.g., `#arrowhead`), causing the browser to resolve references to the
|
||||||
|
* wrong SVG. By keeping only light-theme divs, we avoid duplicate IDs.
|
||||||
|
* CSS filters handle dark mode styling.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Renderer } from "typedoc";
|
import { Renderer } from "typedoc";
|
||||||
|
|
@ -29,6 +32,18 @@ function decodeHtmlEntities(html) {
|
||||||
.replace(/&/g, "&"); // Must be last
|
.replace(/&/g, "&"); // Must be last
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS to invert mermaid diagrams in dark mode.
|
||||||
|
* Since we only render light-theme diagrams, we use CSS filters for dark mode.
|
||||||
|
*/
|
||||||
|
const darkModeStyles = `
|
||||||
|
<style>
|
||||||
|
:root[data-theme="dark"] .mermaid-block .mermaid svg {
|
||||||
|
filter: invert(1) hue-rotate(180deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TypeDoc plugin entry point.
|
* TypeDoc plugin entry point.
|
||||||
* @param {import('typedoc').Application} app
|
* @param {import('typedoc').Application} app
|
||||||
|
|
@ -51,4 +66,37 @@ export function load(app) {
|
||||||
},
|
},
|
||||||
200,
|
200,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Use low priority (-100) to run after the mermaid plugin injects its content
|
||||||
|
app.renderer.on(
|
||||||
|
Renderer.EVENT_END_PAGE,
|
||||||
|
(page) => {
|
||||||
|
if (!page.contents) return;
|
||||||
|
if (!page.contents.includes('class="mermaid-block"')) return;
|
||||||
|
|
||||||
|
// Remove dark-theme mermaid divs to avoid duplicate marker IDs
|
||||||
|
page.contents = page.contents.replace(
|
||||||
|
/<div class="mermaid dark">[\s\S]*?<\/div>/g,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Also remove the CSS that hides light-theme divs by default
|
||||||
|
// The mermaid plugin adds visibility:hidden until JS sets display:block
|
||||||
|
// Since we only have light divs now, make them visible immediately
|
||||||
|
page.contents = page.contents.replace(
|
||||||
|
/<div class="mermaid light">/g,
|
||||||
|
'<div class="mermaid" style="display: block">',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add dark mode CSS filter before </head>
|
||||||
|
const headEndIndex = page.contents.indexOf("</head>");
|
||||||
|
if (headEndIndex !== -1) {
|
||||||
|
page.contents =
|
||||||
|
page.contents.slice(0, headEndIndex) +
|
||||||
|
darkModeStyles +
|
||||||
|
page.contents.slice(headEndIndex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
-100,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue