@@ -5,206 +5,206 @@ import { registerEscapeHandler } from "./util"
55import { mouseEnterHandler , clearActivePopover } from './popover.inline'
66
77const externalIcon = `
8- <svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em; margin-left:0.2em;" viewBox="0 0 512 512">
9- <path d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"/>
10- </svg>` . trim ( )
8+ <svg aria-hidden="true" class="external-icon" style="max-width:0.8em;max-height:0.8em; margin-left:0.2em;" viewBox="0 0 512 512">
9+ <path
10+ d="M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z"/>
11+ </svg>`
1112
1213const fullIcon = `
13- <svg xmlns="http://www.w3.org/2000/svg" height="15" viewBox="0 -960 960 960" width="15" aria-hidden="true">
14- <path stroke="none" fill="currentColor" fill-rule="evenodd"
15- d="M120-120v-320h80v184l504-504H520v-80h320v320h-80v-184L256-200h184v80H120Z"/>
16- </svg>`
14+ <svg xmlns="http://www.w3.org/2000/svg" height="15" viewBox="0 -960 960 960" width="15" aria-hidden="true">
15+ <path stroke="none" fill="currentColor" fill-rule="evenodd"
16+ d="M120-120v-320h80v184l504-504H520v-80h320v320h-80v-184L256-200h184v80H120Z"/>
17+ </svg>`
1718
1819const closeIcon = `
1920<svg xmlns="http://www.w3.org/2000/svg" height="15" viewBox="0 -960 960 960" width="15" aria-hidden="true">
20- <path stroke="none" fill="currentColor" fill-rule="evenodd"
21+ <path stroke="none" fill="currentColor" fill-rule="evenodd"
2122 d="m136-80-56-56 264-264H160v-80h320v320h-80v-184L136-80Zm344-400v-320h80v184l264-264 56 56-264 264h184v80H480Z"/>
2223</svg>`
2324
2425const exitIcon = `
2526<svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 -960 960 960" width="18" >
26- <path stroke="none" fill="currentColor" fill-rule="evenodd"
27- d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/>
28- </svg>
29- `
27+ <path stroke="none" fill="currentColor" fill-rule="evenodd"
28+ d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/>
29+ </svg>`
3030
3131function renderGlobalMarkmap ( ) {
32- const transformer = new Transformer ( )
33- const { styles } = transformer . getAssets ( ) ;
34- if ( styles )
35- loadCSS ( styles )
36-
37- const jsonOptions = {
38- colorFreezeLevel : 2 ,
39- spacingHorizontal : 100 ,
40- spacingVertical : 10 ,
41- }
42- const markmapOptions = deriveOptions ( jsonOptions ) ;
43- markmapOptions . scrollForPan = false
32+ const transformer = new Transformer ( )
33+ const { styles } = transformer . getAssets ( ) ;
34+ if ( styles )
35+ loadCSS ( styles )
36+
37+ const jsonOptions = {
38+ colorFreezeLevel : 2 ,
39+ spacingHorizontal : 100 ,
40+ spacingVertical : 10 ,
41+ }
42+ const markmapOptions = deriveOptions ( jsonOptions ) ;
43+ markmapOptions . scrollForPan = false
44+
45+ const container = document . querySelector ( ".global-markmap-outer" ) as HTMLElement
46+ const containerInner = container . querySelector ( '.global-markmap-container' ) as HTMLElement
47+ const svg = container . querySelector ( "#global-markmap" ) as SVGSVGElement
48+ const toolbarEl = container . querySelector ( "#global-markmap-toolbar" ) as HTMLElement
49+
50+ if ( ! container || ! svg || ! toolbarEl ) {
51+ console . warn ( "❌ Markmap container or elements not found." )
52+ return
53+ }
54+
55+ svg . innerHTML = ""
56+ toolbarEl . innerHTML = ""
57+
58+ const raw = container . dataset . markmap
59+ if ( ! raw ) {
60+ return
61+ }
62+
63+ const data = JSON . parse ( decodeURIComponent ( raw ) )
64+ const mm = Markmap . create ( svg , markmapOptions , data )
65+ const toolbar = Toolbar . create ( mm )
66+ const mmToolbar = toolbar . render ( )
67+ toolbarEl . append ( mmToolbar )
68+ mm . fit ( )
69+
70+ registerEscapeHandler ( container , hideGlobalMarkmap )
71+ setupMarkmapPopoverSupport ( )
72+
73+ svg . querySelectorAll < HTMLAnchorElement > ( 'a[href^="http"]' ) . forEach ( a => {
74+ a . classList . add ( 'external' )
75+ } )
76+
77+ svg . querySelectorAll < HTMLAnchorElement > ( 'a.external' ) . forEach ( a => {
78+ a . insertAdjacentHTML ( 'beforeend' , externalIcon )
79+ } )
80+
81+ let isToggled = containerInner . classList . contains ( 'fullscreen' ) ? true : false ;
82+ const customToolbar = document . createElement ( "div" ) ;
83+ customToolbar . className = "mm-toolbar-item" ;
84+ customToolbar . title = "Toggle fullscreen" ;
85+ customToolbar . innerHTML = isToggled ? closeIcon : fullIcon
86+
87+ const customExit = document . createElement ( "div" ) ;
88+ customExit . className = "mm-toolbar-item" ;
89+ customExit . title = "Exit" ;
90+ customExit . innerHTML = exitIcon
91+
92+ mmToolbar . append ( customToolbar ) ;
93+ mmToolbar . append ( customExit ) ;
94+
95+ container . classList . add ( "active" )
96+
97+ customToolbar . addEventListener ( 'click' , ( ) => {
98+ containerInner . classList . toggle ( 'fullscreen' )
99+ isToggled = ! isToggled ;
100+ customToolbar . innerHTML = isToggled ? closeIcon : fullIcon ;
101+ } )
102+
103+ customExit . addEventListener ( "click" , ( ) => {
104+ const escEvent = new KeyboardEvent ( "keydown" , {
105+ key : "Escape" ,
106+ code : "Escape" ,
107+ keyCode : 27 ,
108+ which : 27 ,
109+ bubbles : true ,
110+ } ) ;
111+ document . dispatchEvent ( escEvent ) ;
112+ } ) ;
44113
45- const container = document . querySelector ( ".global-markmap-outer" ) as HTMLElement
46- const containerInner = container . querySelector ( '.global-markmap-container' ) as HTMLElement
47- const svg = container . querySelector ( "#global-markmap" ) as SVGSVGElement
48- const toolbarEl = container . querySelector ( "#global-markmap-toolbar" ) as HTMLElement
114+ renderMermaidInMarkmap ( svg )
115+ }
49116
50- if ( ! container || ! svg || ! toolbarEl ) {
51- console . warn ( "❌ Markmap container or elements not found." )
52- return
53- }
117+ function hideGlobalMarkmap ( ) {
118+ const container = document . querySelector ( ".global-markmap-outer" ) as HTMLElement
119+ container ?. classList . remove ( "active" )
120+ }
54121
55- svg . innerHTML = ""
56- toolbarEl . innerHTML = ""
122+ function toggleGlobalMarkmap ( ) {
123+ const container = document . querySelector ( ".global-markmap-outer" ) as HTMLElement
124+ if ( container ?. classList . contains ( "active" ) ) {
125+ hideGlobalMarkmap ( )
126+ } else {
127+ renderGlobalMarkmap ( )
128+ }
129+ }
57130
58- const raw = container . dataset . markmap
59- if ( ! raw ) {
60- return
61- }
131+ function setupMarkmapPopoverSupport ( ) {
132+ const markmapLinks = document . querySelectorAll ( ".markmap .markmap-foreign a.internal" ) as NodeListOf < HTMLAnchorElement >
133+ for ( const link of markmapLinks ) {
134+ if ( link . dataset . noPopover === "true" ) continue
135+
136+ link . addEventListener ( "mouseenter" , mouseEnterHandler )
137+ link . addEventListener ( "mouseleave" , clearActivePopover )
138+ window . addCleanup ?.( ( ) => {
139+ link . removeEventListener ( "mouseenter" , mouseEnterHandler )
140+ link . removeEventListener ( "mouseleave" , clearActivePopover )
141+ } )
142+ }
143+ }
62144
63- const data = JSON . parse ( decodeURIComponent ( raw ) )
64- const mm = Markmap . create ( svg , markmapOptions , data )
65- const toolbar = Toolbar . create ( mm )
66- const mmToolbar = toolbar . render ( )
67- toolbarEl . append ( mmToolbar )
68- mm . fit ( )
145+ function renderMermaidInMarkmap ( svg : SVGSVGElement ) {
146+ requestAnimationFrame ( async ( ) => {
147+ const codeBlocks = svg . querySelectorAll ( "foreignObject code.language-mermaid" )
69148
70- registerEscapeHandler ( container , hideGlobalMarkmap )
71- setupMarkmapPopoverSupport ( )
149+ if ( codeBlocks . length === 0 ) return
72150
73- svg . querySelectorAll < HTMLAnchorElement > ( 'a[href^="http"]' ) . forEach ( a => {
74- a . classList . add ( 'external' )
75- } )
151+ const { default : mermaid } = await import (
152+ "https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.4.0/mermaid.esm.min.mjs"
153+ )
76154
77- svg . querySelectorAll < HTMLAnchorElement > ( 'a.external' ) . forEach ( a => {
78- a . insertAdjacentHTML ( 'beforeend' , externalIcon )
155+ mermaid . initialize ( {
156+ startOnLoad : false ,
157+ theme : document . documentElement . getAttribute ( "saved-theme" ) === "dark" ? "dark" : "base" ,
158+ securityLevel : "loose" ,
79159 } )
80160
81- let isToggled = containerInner . classList . contains ( 'fullscreen' ) ? true : false ;
82- const customToolbar = document . createElement ( "div" ) ;
83- customToolbar . className = "mm-toolbar-item" ;
84- customToolbar . title = "Toggle fullscreen" ;
85- customToolbar . innerHTML = isToggled ? closeIcon : fullIcon
161+ for ( const code of codeBlocks ) {
162+ const graph = code . textContent ?. trim ( )
163+ if ( ! graph ) continue
86164
87- const customExit = document . createElement ( "div" ) ;
88- customExit . className = "mm-toolbar-item" ;
89- customExit . title = "Exit" ;
90- customExit . innerHTML = exitIcon
165+ const pre = code . closest ( "pre" )
166+ const div = code . closest ( "div" )
167+ if ( ! div || ! pre ) continue
91168
92- mmToolbar . append ( customToolbar ) ;
93- mmToolbar . append ( customExit ) ;
169+ pre . remove ( )
94170
95- container . classList . add ( "active" )
171+ const tempDiv = document . createElement ( "div" )
172+ tempDiv . style . visibility = "hidden"
173+ tempDiv . style . position = "absolute"
174+ tempDiv . style . top = "-9999px"
175+ document . body . appendChild ( tempDiv )
96176
97- customToolbar . addEventListener ( 'click' , ( ) => {
98- containerInner . classList . toggle ( 'fullscreen' )
99- isToggled = ! isToggled ;
100- customToolbar . innerHTML = isToggled ? closeIcon : fullIcon ;
101- } )
177+ const mermaidDiv = document . createElement ( "div" )
178+ mermaidDiv . className = "mermaid"
179+ mermaidDiv . textContent = graph
180+ tempDiv . appendChild ( mermaidDiv )
102181
103- customExit . addEventListener ( "click" , ( ) => {
104- const escEvent = new KeyboardEvent ( "keydown" , {
105- key : "Escape" ,
106- code : "Escape" ,
107- keyCode : 27 ,
108- which : 27 ,
109- bubbles : true ,
110- } ) ;
111- document . dispatchEvent ( escEvent ) ;
112- } ) ;
182+ try {
183+ await mermaid . run ( { nodes : [ mermaidDiv ] } )
184+ const renderedSvg = mermaidDiv . querySelector ( "svg" )
185+ if ( ! renderedSvg ) continue
113186
114- renderMermaidInMarkmap ( svg )
115- }
187+ const importedSvg = renderedSvg . cloneNode ( true ) as SVGSVGElement
188+ div . appendChild ( importedSvg )
116189
117- function hideGlobalMarkmap ( ) {
118- const container = document . querySelector ( ".global-markmap-outer" ) as HTMLElement
119- container ?. classList . remove ( "active" )
120- }
121-
122- function toggleGlobalMarkmap ( ) {
123- const container = document . querySelector ( ".global-markmap-outer" ) as HTMLElement
124- if ( container ?. classList . contains ( "active" ) ) {
125- hideGlobalMarkmap ( )
126- } else {
127- renderGlobalMarkmap ( )
190+ tempDiv . remove ( )
191+ } catch ( err ) {
192+ console . error ( "❌ Mermaid render failed:" , err )
193+ }
128194 }
129- }
130-
131- function setupMarkmapPopoverSupport ( ) {
132- const markmapLinks = document . querySelectorAll ( ".markmap .markmap-foreign a.internal" ) as NodeListOf < HTMLAnchorElement >
133- for ( const link of markmapLinks ) {
134- if ( link . dataset . noPopover === "true" ) continue
135-
136- link . addEventListener ( "mouseenter" , mouseEnterHandler )
137- link . addEventListener ( "mouseleave" , clearActivePopover )
138- window . addCleanup ?.( ( ) => {
139- link . removeEventListener ( "mouseenter" , mouseEnterHandler )
140- link . removeEventListener ( "mouseleave" , clearActivePopover )
141- } )
142- }
143- }
144-
145- function renderMermaidInMarkmap ( svg : SVGSVGElement ) {
146- requestAnimationFrame ( async ( ) => {
147- const codeBlocks = svg . querySelectorAll ( "foreignObject code.language-mermaid" )
148-
149- if ( codeBlocks . length === 0 ) return
150-
151- const { default : mermaid } = await import (
152- "https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.4.0/mermaid.esm.min.mjs"
153- )
154-
155- mermaid . initialize ( {
156- startOnLoad : false ,
157- theme : document . documentElement . getAttribute ( "saved-theme" ) === "dark" ? "dark" : "base" ,
158- securityLevel : "loose" ,
159- } )
160-
161- for ( const code of codeBlocks ) {
162- const graph = code . textContent ?. trim ( )
163- if ( ! graph ) continue
164-
165- const pre = code . closest ( "pre" )
166- const div = code . closest ( "div" )
167- if ( ! div || ! pre ) continue
168-
169- pre . remove ( )
170-
171- const tempDiv = document . createElement ( "div" )
172- tempDiv . style . visibility = "hidden"
173- tempDiv . style . position = "absolute"
174- tempDiv . style . top = "-9999px"
175- document . body . appendChild ( tempDiv )
176-
177- const mermaidDiv = document . createElement ( "div" )
178- mermaidDiv . className = "mermaid"
179- mermaidDiv . textContent = graph
180- tempDiv . appendChild ( mermaidDiv )
181-
182- try {
183- await mermaid . run ( { nodes : [ mermaidDiv ] } )
184- const renderedSvg = mermaidDiv . querySelector ( "svg" )
185- if ( ! renderedSvg ) continue
186-
187- const importedSvg = renderedSvg . cloneNode ( true ) as SVGSVGElement
188- div . appendChild ( importedSvg )
189-
190- tempDiv . remove ( )
191- } catch ( err ) {
192- console . error ( "❌ Mermaid render failed:" , err )
193- }
194- }
195- } )
195+ } )
196196}
197197
198198window . addEventListener ( "DOMContentLoaded" , ( ) => {
199- const btn = document . getElementById ( "show-markmap" )
200- if ( btn ) {
201- btn . addEventListener ( "click" , renderGlobalMarkmap )
199+ const btn = document . getElementById ( "show-markmap" )
200+ if ( btn ) {
201+ btn . addEventListener ( "click" , renderGlobalMarkmap )
202+ }
203+
204+ document . addEventListener ( "keydown" , ( e ) => {
205+ if ( ( e . ctrlKey || e . metaKey ) && e . key . toLowerCase ( ) === "m" && ! e . shiftKey ) {
206+ e . preventDefault ( )
207+ toggleGlobalMarkmap ( )
202208 }
203-
204- document . addEventListener ( "keydown" , ( e ) => {
205- if ( ( e . ctrlKey || e . metaKey ) && e . key . toLowerCase ( ) === "m" && ! e . shiftKey ) {
206- e . preventDefault ( )
207- toggleGlobalMarkmap ( )
208- }
209- } )
209+ } )
210210} )
0 commit comments