2024-07-07 18:32:30 +03:00
import { isDarkTheme } from '../utils.ts' ;
import { makeCodeCopyButton } from './codecopy.ts' ;
import { displayError } from './common.ts' ;
2022-12-25 20:17:48 +03:00
2021-10-21 10:37:43 +03:00
const { mermaidMaxSourceCharacters } = window . config ;
2020-08-04 22:56:37 +03:00
2023-10-08 06:20:12 +03:00
// margin removal is for https://github.com/mermaid-js/mermaid/issues/4907
2023-04-17 13:10:22 +03:00
const iframeCss = ` :root {color-scheme: normal}
body { margin : 0 ; padding : 0 ; overflow : hidden }
2023-10-08 06:20:12 +03:00
# mermaid { display : block ; margin : 0 auto }
blockquote , dd , dl , figure , h1 , h2 , h3 , h4 , h5 , h6 , hr , p , pre { margin : 0 } ` ;
2020-07-27 09:24:09 +03:00
2024-11-11 14:13:57 +03:00
export async function renderMermaid ( ) : Promise < void > {
2021-11-16 11:16:05 +03:00
const els = document . querySelectorAll ( '.markup code.language-mermaid' ) ;
if ( ! els . length ) return ;
2020-07-27 09:24:09 +03:00
2021-10-19 10:23:58 +03:00
const { default : mermaid } = await import ( /* webpackChunkName: "mermaid" */ 'mermaid' ) ;
2020-07-27 09:24:09 +03:00
2020-08-04 22:56:37 +03:00
mermaid . initialize ( {
2022-02-16 06:28:29 +03:00
startOnLoad : false ,
theme : isDarkTheme ( ) ? 'dark' : 'neutral' ,
2020-07-27 09:24:09 +03:00
securityLevel : 'strict' ,
2024-08-25 20:23:13 +03:00
suppressErrorRendering : true ,
2020-07-27 09:24:09 +03:00
} ) ;
for ( const el of els ) {
2023-04-17 13:10:22 +03:00
const pre = el . closest ( 'pre' ) ;
if ( pre . hasAttribute ( 'data-render-done' ) ) continue ;
2022-02-16 06:28:29 +03:00
2023-04-17 13:10:22 +03:00
const source = el . textContent ;
2022-02-16 06:28:29 +03:00
if ( mermaidMaxSourceCharacters >= 0 && source . length > mermaidMaxSourceCharacters ) {
2023-04-17 13:10:22 +03:00
displayError ( pre , new Error ( ` Mermaid source of ${ source . length } characters exceeds the maximum allowed length of ${ mermaidMaxSourceCharacters } . ` ) ) ;
2020-08-04 22:56:37 +03:00
continue ;
}
try {
2023-03-04 08:39:07 +03:00
await mermaid . parse ( source ) ;
2020-08-04 22:56:37 +03:00
} catch ( err ) {
2023-04-17 13:10:22 +03:00
displayError ( pre , err ) ;
2020-08-04 22:56:37 +03:00
continue ;
}
try {
2022-02-16 06:28:29 +03:00
// can't use bindFunctions here because we can't cross the iframe boundary. This
// means js-based interactions won't work but they aren't intended to work either
2023-03-04 08:39:07 +03:00
const { svg } = await mermaid . render ( 'mermaid' , source ) ;
const iframe = document . createElement ( 'iframe' ) ;
2024-03-05 08:29:32 +03:00
iframe . classList . add ( 'markup-render' , 'tw-invisible' ) ;
2023-03-04 08:39:07 +03:00
iframe . srcdoc = ` <html><head><style> ${ iframeCss } </style></head><body> ${ svg } </body></html> ` ;
const mermaidBlock = document . createElement ( 'div' ) ;
2024-03-24 21:23:38 +03:00
mermaidBlock . classList . add ( 'mermaid-block' , 'is-loading' , 'tw-hidden' ) ;
2023-03-04 08:39:07 +03:00
mermaidBlock . append ( iframe ) ;
const btn = makeCodeCopyButton ( ) ;
btn . setAttribute ( 'data-clipboard-text' , source ) ;
mermaidBlock . append ( btn ) ;
2023-04-17 13:10:22 +03:00
2024-11-09 21:03:55 +03:00
const updateIframeHeight = ( ) = > {
2024-11-26 09:37:24 +03:00
const body = iframe . contentWindow ? . document ? . body ;
if ( body ) {
iframe . style . height = ` ${ body . clientHeight } px ` ;
}
2024-11-09 21:03:55 +03:00
} ;
2023-04-17 13:10:22 +03:00
iframe . addEventListener ( 'load' , ( ) = > {
pre . replaceWith ( mermaidBlock ) ;
2024-03-24 21:23:38 +03:00
mermaidBlock . classList . remove ( 'tw-hidden' ) ;
2024-11-09 21:03:55 +03:00
updateIframeHeight ( ) ;
2023-04-17 13:10:22 +03:00
setTimeout ( ( ) = > { // avoid flash of iframe background
mermaidBlock . classList . remove ( 'is-loading' ) ;
2024-03-05 08:29:32 +03:00
iframe . classList . remove ( 'tw-invisible' ) ;
2023-04-17 13:10:22 +03:00
} , 0 ) ;
2024-11-26 09:37:24 +03:00
// update height when element's visibility state changes, for example when the diagram is inside
// a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
// would initially set a incorrect height and the correct height is set during this callback.
( new IntersectionObserver ( ( ) = > {
updateIframeHeight ( ) ;
} , { root : document.documentElement } ) ) . observe ( iframe ) ;
2023-04-17 13:10:22 +03:00
} ) ;
document . body . append ( mermaidBlock ) ;
2020-08-04 22:56:37 +03:00
} catch ( err ) {
2023-04-17 13:10:22 +03:00
displayError ( pre , err ) ;
2020-08-04 22:56:37 +03:00
}
2020-07-27 09:24:09 +03:00
}
}