11import React , { useEffect , useRef } from 'react' ;
22import { createRoot } from 'react-dom/client' ;
3- import ReactDOMServer from 'react-dom/server' ;
4- import showdown from 'showdown' ;
5- import mermaid from 'mermaid' ;
6- import katex from 'katex' ;
7- // @ts -ignore
8- import * as vega from 'vega' ;
9- // @ts -ignore
10- import * as vegaLite from 'vega-lite' ;
11- import { Previewer } from 'pagedjs' ;
123import { ArrowGramStatic } from '../ArrowGramStatic' ;
134import './styles.css' ;
145
@@ -55,6 +46,8 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
5546 const pagedInstance = useRef < any > ( null ) ;
5647
5748 useEffect ( ( ) => {
49+ if ( import . meta. env . SSR ) return ;
50+
5851 let isMounted = true ;
5952 // Use a simple ID to track the latest render request
6053 const renderId = Date . now ( ) ;
@@ -66,6 +59,32 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
6659 const processAndRender = async ( ) => {
6760 if ( ! containerRef . current ) return ;
6861
62+ const [
63+ ReactDOMServer ,
64+ showdownMod ,
65+ mermaidMod ,
66+ katexMod ,
67+ vega ,
68+ vegaLite ,
69+ pagedjs ,
70+ ] = await Promise . all ( [
71+ import ( 'react-dom/server.browser' ) ,
72+ import ( 'showdown' ) ,
73+ import ( 'mermaid' ) ,
74+ import ( 'katex' ) ,
75+ // These modules are browser-safe. Keeping them dynamic avoids SSR bundling issues (e.g. node-canvas).
76+ // @ts -ignore
77+ import ( 'vega' ) ,
78+ // @ts -ignore
79+ import ( 'vega-lite' ) ,
80+ import ( 'pagedjs' ) ,
81+ ] ) ;
82+
83+ const showdown = ( showdownMod as any ) . default ?? showdownMod ;
84+ const mermaid = ( mermaidMod as any ) . default ?? mermaidMod ;
85+ const katex = ( katexMod as any ) . default ?? katexMod ;
86+ const Previewer = ( pagedjs as any ) . Previewer ?? ( pagedjs as any ) . default ?. Previewer ;
87+
6988 // 1. Pre-process Vega Lite
7089 let processedText = markdown ;
7190 const vegaRegex = / < d i v c l a s s = " v e g a - l i t e " ( [ ^ > ] * ) > ( [ \s \S ] * ?) < \/ d i v > / g;
@@ -76,8 +95,8 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
7695 if ( ! isMounted ) return { original : '' , replacement : '' } ;
7796 try {
7897 const spec = JSON . parse ( match [ 2 ] . trim ( ) ) ;
79- const vegaSpec = vegaLite . compile ( spec ) . spec ;
80- const view = new vega . View ( vega . parse ( vegaSpec ) , { renderer : 'svg' } ) ;
98+ const vegaSpec = ( vegaLite as any ) . compile ( spec ) . spec ;
99+ const view = new ( vega as any ) . View ( ( vega as any ) . parse ( vegaSpec ) , { renderer : 'svg' } ) ;
81100 const svg = await view . toSVG ( ) ;
82101 return { original : match [ 0 ] , replacement : `<div class="vega-container"${ match [ 1 ] } >${ svg } </div>` } ;
83102 } catch ( e : any ) {
@@ -95,14 +114,14 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
95114 }
96115
97116 // 2. Pre-process Mermaid
98- mermaid . initialize ( { startOnLoad : false , theme : 'base' } ) ;
117+ ( mermaid as any ) . initialize ( { startOnLoad : false , theme : 'base' } ) ;
99118 const mermaidRegex = / < d i v c l a s s = " m e r m a i d " ( [ ^ > ] * ) > ( [ \s \S ] * ?) < \/ d i v > / g;
100119 const mermaidMatches = Array . from ( processedText . matchAll ( mermaidRegex ) ) ;
101120 const mermaidResults = await Promise . all (
102121 mermaidMatches . map ( async ( match , i ) => {
103122 if ( ! isMounted ) return { original : '' , replacement : '' } ;
104123 try {
105- const { svg } = await mermaid . render ( `mermaid-svg-${ Date . now ( ) } -${ i } ` , match [ 2 ] . trim ( ) ) ;
124+ const { svg } = await ( mermaid as any ) . render ( `mermaid-svg-${ Date . now ( ) } -${ i } ` , match [ 2 ] . trim ( ) ) ;
106125 return { original : match [ 0 ] , replacement : `<div class="mermaid-container"${ match [ 1 ] } >${ svg } </div>` } ;
107126 } catch ( e ) { return { original : match [ 0 ] , replacement : `<div class="mermaid-error">Diagram Error</div>` } ; }
108127 } )
@@ -129,7 +148,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
129148 try {
130149 // Render the static SVG markup to ensure Paged.js sees the correct dimensions.
131150 // onEdit is undefined here, so no button, but sizing is correct.
132- const staticHtml = ReactDOMServer . renderToStaticMarkup (
151+ const staticHtml = ( ReactDOMServer as any ) . renderToStaticMarkup (
133152 < ArrowGramStatic spec = { spec } />
134153 ) ;
135154 return `<div id="${ id } " class="arrowgram-hydrate-target">${ staticHtml } </div>` ;
@@ -158,7 +177,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
158177 processedText = processedText . replace ( / \$ (? ! \$ ) (?: \\ .| [ ^ $ \\ \n ] ) + \$ / g, protectMath ) ;
159178
160179 // 5. Markdown -> HTML
161- const converter = new showdown . Converter ( {
180+ const converter = new ( showdown as any ) . Converter ( {
162181 metadata : true ,
163182 noHeaderId : true ,
164183 literalMidWordUnderscores : true
@@ -189,7 +208,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
189208 . replace ( / \\ \\ ( [ A - Z a - z _ ] ) / g, '\\$1' )
190209 . replace ( / \\ \\ ( [ , ; : . ! ] ) / g, '\\$1' ) ;
191210 try {
192- return `<span class="katex-display">${ katex . renderToString ( cleaned , { displayMode : true , throwOnError : false } ) } </span>` ;
211+ return `<span class="katex-display">${ ( katex as any ) . renderToString ( cleaned , { displayMode : true , throwOnError : false } ) } </span>` ;
193212 } catch ( e : any ) { return `<span class="katex-error">${ e . message } </span>` ; }
194213 } ) ;
195214 // Inline mode: $...$ (non-greedy)
@@ -199,7 +218,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
199218 . replace ( / \\ \\ ( [ A - Z a - z _ ] ) / g, '\\$1' )
200219 . replace ( / \\ \\ ( [ , ; : . ! ] ) / g, '\\$1' ) ;
201220 try {
202- return katex . renderToString ( cleaned , { displayMode : false , throwOnError : false } ) ;
221+ return ( katex as any ) . renderToString ( cleaned , { displayMode : false , throwOnError : false } ) ;
203222 } catch ( e : any ) { return `<span class="katex-error">${ e . message } </span>` ; }
204223 } ) ;
205224
@@ -229,7 +248,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
229248 const katexCss = document . querySelector ( 'link[href*="katex"]' ) ?. getAttribute ( 'href' ) || "https://cdn.jsdelivr.net/npm/[email protected] /dist/katex.min.css" ; 230249 stylesheets . push ( katexCss ) ;
231250
232- const paged = new Previewer ( ) ;
251+ const paged = new ( Previewer as any ) ( ) ;
233252 pagedInstance . current = paged ;
234253
235254 paged . preview ( finalHtml , stylesheets , containerRef . current ) . then ( ( ) => {
0 commit comments