1
1
'use client' ;
2
- import { useEffect } from 'react' ;
2
+ import { useEffect , useState } from 'react' ;
3
3
import { useTheme } from 'next-themes' ;
4
4
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
+ */
5
11
export default function Mermaid ( ) {
6
- const theme = useTheme ( ) ;
12
+ const [ isDoneRendering , setDoneRendering ] = useState ( false ) ;
13
+ const { resolvedTheme : theme } = useTheme ( ) ;
7
14
useEffect ( ( ) => {
8
15
const renderMermaid = async ( ) => {
9
16
const escapeHTML = ( str : string ) => {
@@ -24,24 +31,64 @@ export default function Mermaid() {
24
31
return ;
25
32
}
26
33
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 => {
32
36
// 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 ) ;
35
39
// 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 ;
40
47
}
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 ) ;
41
56
} ) ;
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 ) ) ;
43
63
} ;
44
64
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 ;
47
94
}
0 commit comments