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 ) => {
@@ -24,24 +31,64 @@ export default function Mermaid() {
2431 return ;
2532 }
2633 const { default : mermaid } = await import ( 'mermaid' ) ;
27- mermaid . initialize ( {
28- startOnLoad : false ,
29- theme : theme . resolvedTheme === 'light' ? 'default' : 'dark' ,
30- } ) ;
31- mermaidBlocks . forEach ( block => {
34+ mermaid . initialize ( { startOnLoad : false } ) ;
35+ mermaidBlocks . forEach ( lightModeblock => {
3236 // get rid of code highlighting
33- const code = block . textContent ?? '' ;
34- block . innerHTML = escapeHTML ( code ) ;
37+ const code = lightModeblock . textContent ?? '' ;
38+ lightModeblock . innerHTML = escapeHTML ( code ) ;
3539 // force transparent background
36- block . style . backgroundColor = 'transparent' ;
37- const parentCodeTabs = block . closest ( '.code-tabs-wrapper' ) ;
38- if ( parentCodeTabs ) {
39- parentCodeTabs . innerHTML = block . outerHTML ;
40+ lightModeblock . style . backgroundColor = 'transparent' ;
41+ lightModeblock . classList . add ( 'light' ) ;
42+ const parentCodeTabs = lightModeblock . closest ( '.code-tabs-wrapper' ) ;
43+ if ( ! parentCodeTabs ) {
44+ // eslint-disable-next-line no-console
45+ console . error ( 'Mermaid code block was not wrapped in a code tab' ) ;
46+ return ;
4047 }
48+ // empty the container
49+ parentCodeTabs . innerHTML = '' ;
50+ parentCodeTabs . appendChild ( lightModeblock . cloneNode ( true ) ) ;
51+
52+ const darkModeBlock = lightModeblock . cloneNode ( true ) as HTMLPreElement ;
53+ darkModeBlock . classList . add ( 'dark' ) ;
54+ darkModeBlock . classList . remove ( 'light' ) ;
55+ parentCodeTabs ?. appendChild ( darkModeBlock ) ;
4156 } ) ;
42- await mermaid . run ( { nodes : document . querySelectorAll ( '.language-mermaid' ) } ) ;
57+ await mermaid . run ( { nodes : document . querySelectorAll ( '.language-mermaid.light' ) } ) ;
58+
59+ mermaid . initialize ( { startOnLoad : false , theme : 'dark' } ) ;
60+ await mermaid
61+ . run ( { nodes : document . querySelectorAll ( '.language-mermaid.dark' ) } )
62+ . then ( ( ) => setDoneRendering ( true ) ) ;
4363 } ;
4464 renderMermaid ( ) ;
45- } , [ theme ] ) ;
46- return null ;
65+ } , [ ] ) ;
66+ // we have to wait for mermaid.js to finish rendering both light and dark charts
67+ // before we hide one of them depending on the theme
68+ // this is necessary because mermaid.js relies on the DOM for calculations
69+ return isDoneRendering ? (
70+ theme === 'dark' ? (
71+ < style >
72+ { `
73+ .dark .language-mermaid {
74+ display: none;
75+ }
76+ .dark .language-mermaid.dark {
77+ display: block;
78+ }
79+ ` }
80+ </ style >
81+ ) : (
82+ < style >
83+ { `
84+ .language-mermaid.light {
85+ display: block;
86+ }
87+ .language-mermaid.dark {
88+ display: none;
89+ }
90+ ` }
91+ </ style >
92+ )
93+ ) : null ;
4794}
0 commit comments