11/**
2- * TypeDoc plugin that properly decodes HTML entities in mermaid code blocks .
2+ * TypeDoc plugin that fixes mermaid diagram rendering issues .
33 *
4- * This runs BEFORE the mermaid plugin and converts HTML entities back to raw
5- * characters, allowing mermaid to parse them correctly.
4+ * 1. Decodes HTML entities in mermaid code blocks before the mermaid plugin
5+ * processes them. The @boneskull/typedoc-plugin-mermaid converts < to #lt;
6+ * and > to #gt;, but those aren't valid mermaid entities.
67 *
7- * The @boneskull/typedoc-plugin-mermaid converts < to #lt; and > to #gt;,
8- * but those aren't valid mermaid entities (mermaid uses numeric codes like #60;).
9- * This plugin sidesteps the issue by decoding entities before mermaid sees them.
8+ * 2. Removes dark-theme mermaid divs to fix duplicate marker IDs. The mermaid
9+ * plugin creates both dark and light variants with identical marker IDs
10+ * (e.g., `#arrowhead`), causing the browser to resolve references to the
11+ * wrong SVG. By keeping only light-theme divs, we avoid duplicate IDs.
12+ * CSS filters handle dark mode styling.
1013 */
1114
1215import { Renderer } from "typedoc" ;
@@ -29,6 +32,18 @@ function decodeHtmlEntities(html) {
2932 . replace ( / & a m p ; / g, "&" ) ; // Must be last
3033}
3134
35+ /**
36+ * CSS to invert mermaid diagrams in dark mode.
37+ * Since we only render light-theme diagrams, we use CSS filters for dark mode.
38+ */
39+ const darkModeStyles = `
40+ <style>
41+ :root[data-theme="dark"] .mermaid-block .mermaid svg {
42+ filter: invert(1) hue-rotate(180deg);
43+ }
44+ </style>
45+ ` ;
46+
3247/**
3348 * TypeDoc plugin entry point.
3449 * @param {import('typedoc').Application } app
@@ -51,4 +66,37 @@ export function load(app) {
5166 } ,
5267 200 ,
5368 ) ;
69+
70+ // Use low priority (-100) to run after the mermaid plugin injects its content
71+ app . renderer . on (
72+ Renderer . EVENT_END_PAGE ,
73+ ( page ) => {
74+ if ( ! page . contents ) return ;
75+ if ( ! page . contents . includes ( 'class="mermaid-block"' ) ) return ;
76+
77+ // Remove dark-theme mermaid divs to avoid duplicate marker IDs
78+ page . contents = page . contents . replace (
79+ / < d i v c l a s s = " m e r m a i d d a r k " > [ \s \S ] * ?< \/ d i v > / g,
80+ "" ,
81+ ) ;
82+
83+ // Also remove the CSS that hides light-theme divs by default
84+ // The mermaid plugin adds visibility:hidden until JS sets display:block
85+ // Since we only have light divs now, make them visible immediately
86+ page . contents = page . contents . replace (
87+ / < d i v c l a s s = " m e r m a i d l i g h t " > / g,
88+ '<div class="mermaid" style="display: block">' ,
89+ ) ;
90+
91+ // Add dark mode CSS filter before </head>
92+ const headEndIndex = page . contents . indexOf ( "</head>" ) ;
93+ if ( headEndIndex !== - 1 ) {
94+ page . contents =
95+ page . contents . slice ( 0 , headEndIndex ) +
96+ darkModeStyles +
97+ page . contents . slice ( headEndIndex ) ;
98+ }
99+ } ,
100+ - 100 ,
101+ ) ;
54102}
0 commit comments