11'use client' ;
2- import { useEffect } from 'react' ;
2+ import { useEffect , useState } from 'react' ;
33import { useTheme } from 'next-themes' ;
44
5+ /**
6+ * we target ```mermaid``` code blocks after they have been highlighted (not ideal),
7+ * then we strip the code from the html elements used for highlighting
8+ * then we render the mermaid chart both in light and dark modes
9+ * CSS takes care of showing the right one depending on the theme
10+ */
511export default function Mermaid ( ) {
6- const theme = useTheme ( ) ;
12+ const [ isDoneRendering , setDoneRendering ] = useState ( false ) ;
13+ const { resolvedTheme : theme } = useTheme ( ) ;
714 useEffect ( ( ) => {
815 const renderMermaid = async ( ) => {
916 const escapeHTML = ( str : string ) => {
@@ -23,25 +30,66 @@ export default function Mermaid() {
2330 if ( mermaidBlocks . length === 0 ) {
2431 return ;
2532 }
33+ // @ts -ignore
2634 const { default : mermaid } = await import ( 'mermaid' ) ;
27- mermaid . initialize ( {
28- startOnLoad : false ,
29- theme : theme . resolvedTheme === 'light' ? 'default' : 'dark' ,
30- } ) ;
31- mermaidBlocks . forEach ( block => {
35+ mermaid . initialize ( { startOnLoad : false , theme : 'light' } ) ;
36+ mermaidBlocks . forEach ( lightModeblock => {
3237 // get rid of code highlighting
33- const code = block . textContent ?? '' ;
34- block . innerHTML = escapeHTML ( code ) ;
38+ const code = lightModeblock . textContent ?? '' ;
39+ lightModeblock . innerHTML = escapeHTML ( code ) ;
3540 // force transparent background
36- block . style . backgroundColor = 'transparent' ;
37- const parentCodeTabs = block . closest ( '.code-tabs-wrapper' ) ;
38- if ( parentCodeTabs ) {
39- parentCodeTabs . innerHTML = block . outerHTML ;
41+ lightModeblock . style . backgroundColor = 'transparent' ;
42+ lightModeblock . classList . add ( 'light' ) ;
43+ const parentCodeTabs = lightModeblock . closest ( '.code-tabs-wrapper' ) ;
44+ if ( ! parentCodeTabs ) {
45+ // eslint-disable-next-line no-console
46+ console . error ( 'Mermaid code block was not wrapped in a code tab' ) ;
47+ return ;
4048 }
49+ // empty the container
50+ parentCodeTabs . innerHTML = '' ;
51+ parentCodeTabs . appendChild ( lightModeblock . cloneNode ( true ) ) ;
52+
53+ const darkModeBlock = lightModeblock . cloneNode ( true ) as HTMLPreElement ;
54+ darkModeBlock . classList . add ( 'dark' ) ;
55+ darkModeBlock . classList . remove ( 'light' ) ;
56+ parentCodeTabs ?. appendChild ( darkModeBlock ) ;
4157 } ) ;
42- await mermaid . run ( { nodes : document . querySelectorAll ( '.language-mermaid' ) } ) ;
58+ await mermaid . run ( { nodes : document . querySelectorAll ( '.language-mermaid.light' ) } ) ;
59+
60+ mermaid . initialize ( { startOnLoad : false , theme : 'dark' } ) ;
61+ await mermaid
62+ . run ( { nodes : document . querySelectorAll ( '.language-mermaid.dark' ) } )
63+ . then ( ( ) => setDoneRendering ( true ) ) ;
4364 } ;
4465 renderMermaid ( ) ;
45- } , [ theme ] ) ;
46- return null ;
66+ } , [ ] ) ;
67+ // we have to wait for mermaid.js to finish rendering both light and dark charts
68+ // before we hide one of them depending on the theme
69+ // this is necessary because mermaid.js relies on the DOM for calculations
70+ return isDoneRendering ? (
71+ theme === 'dark' ? (
72+ < style >
73+ { `
74+ .dark .language-mermaid {
75+ display: none;
76+ }
77+ .dark .language-mermaid.dark {
78+ display: block;
79+ }
80+ ` }
81+ </ style >
82+ ) : (
83+ < style >
84+ { `
85+ .language-mermaid.light {
86+ display: block;
87+ }
88+ .language-mermaid.dark {
89+ display: none;
90+ }
91+ ` }
92+ </ style >
93+ )
94+ ) : null ;
4795}
0 commit comments