@@ -22,33 +22,6 @@ interface Diagram {
2222 category ?: string
2323}
2424
25- function sanitizeMermaidSvg ( svg : string ) {
26- const parser = new DOMParser ( )
27- const doc = parser . parseFromString ( svg , "image/svg+xml" )
28- const disallowedTags = new Set ( [ "script" , "foreignObject" ] )
29- const walker = doc . createTreeWalker ( doc , NodeFilter . SHOW_ELEMENT )
30- const toRemove : Element [ ] = [ ]
31-
32- while ( walker . nextNode ( ) ) {
33- const element = walker . currentNode as Element
34- if ( disallowedTags . has ( element . tagName ) ) {
35- toRemove . push ( element )
36- continue
37- }
38-
39- for ( const attr of Array . from ( element . attributes ) ) {
40- const name = attr . name . toLowerCase ( )
41- const value = attr . value . trim ( ) . toLowerCase ( )
42- if ( name . startsWith ( "on" ) || value . startsWith ( "javascript:" ) ) {
43- element . removeAttribute ( attr . name )
44- }
45- }
46- }
47-
48- toRemove . forEach ( ( element ) => element . remove ( ) )
49- return doc . documentElement
50- }
51-
5225function toViewDiagram ( row : DBDiagram ) : Diagram {
5326 const payload = row . data && typeof row . data === "object" ? row . data : { }
5427 return {
@@ -75,9 +48,27 @@ export default function TextDiagramEditorPage() {
7548 const [ isPreviewMode , setIsPreviewMode ] = useState ( false )
7649 const [ savedMessage , setSavedMessage ] = useState ( false )
7750 const [ error , setError ] = useState < string | null > ( null )
78- const previewRef = useRef < HTMLDivElement > ( null )
51+ const [ previewUrl , setPreviewUrl ] = useState < string | null > ( null )
52+ const previewUrlRef = useRef < string | null > ( null )
7953 const [ showDocumentation , setShowDocumentation ] = useState ( false )
8054
55+ const replacePreviewUrl = useCallback ( ( url : string | null ) => {
56+ if ( previewUrlRef . current ) {
57+ URL . revokeObjectURL ( previewUrlRef . current )
58+ }
59+
60+ previewUrlRef . current = url
61+ setPreviewUrl ( url )
62+ } , [ ] )
63+
64+ useEffect ( ( ) => {
65+ return ( ) => {
66+ if ( previewUrlRef . current ) {
67+ URL . revokeObjectURL ( previewUrlRef . current )
68+ }
69+ }
70+ } , [ ] )
71+
8172 useEffect ( ( ) => {
8273 const initMermaid = async ( ) => {
8374 const { default : mermaid } = await import ( "mermaid" )
@@ -132,24 +123,25 @@ export default function TextDiagramEditorPage() {
132123 } , [ diagramId , router ] )
133124
134125 const renderDiagram = useCallback ( async ( ) => {
135- if ( ! previewRef . current || ! textContent ) return
126+ if ( ! textContent ) return
136127
137128 try {
138129 setError ( null )
139- previewRef . current . replaceChildren ( )
140130
141131 const { default : mermaid } = await import ( "mermaid" )
142132 const id = `mermaid-${ Date . now ( ) } `
143133 const { svg } = await mermaid . render ( id , textContent )
144- previewRef . current . appendChild ( sanitizeMermaidSvg ( svg ) )
134+ const blob = new Blob ( [ svg ] , { type : "image/svg+xml" } )
135+ replacePreviewUrl ( URL . createObjectURL ( blob ) )
145136 } catch ( err : any ) {
137+ replacePreviewUrl ( null )
146138 setError ( err . message || "Failed to render diagram" )
147139 console . error ( "Mermaid render error:" , err )
148140 }
149- } , [ textContent ] )
141+ } , [ replacePreviewUrl , textContent ] )
150142
151143 useEffect ( ( ) => {
152- if ( isPreviewMode && textContent && previewRef . current ) {
144+ if ( isPreviewMode && textContent ) {
153145 void renderDiagram ( )
154146 }
155147 } , [ isPreviewMode , textContent , renderDiagram ] )
@@ -409,7 +401,19 @@ export default function TextDiagramEditorPage() {
409401 < pre className = "text-sm whitespace-pre-wrap" > { error } </ pre >
410402 </ div >
411403 ) : (
412- < div ref = { previewRef } className = "flex items-center justify-center min-h-full" />
404+ < div className = "flex min-h-full items-center justify-center" >
405+ { previewUrl ? (
406+ // Mermaid preview uses a generated object URL, so Next Image optimization does not apply.
407+ // eslint-disable-next-line @next/next/no-img-element
408+ < img
409+ src = { previewUrl }
410+ alt = { diagram ?. name ? `${ diagram . name } preview` : "Diagram preview" }
411+ className = "max-h-full max-w-full object-contain"
412+ />
413+ ) : (
414+ < p className = "text-sm text-muted-foreground" > Render the diagram to preview it.</ p >
415+ ) }
416+ </ div >
413417 ) }
414418 </ div >
415419 </ div >
0 commit comments