11import React , { useState , useEffect , useRef , useCallback } from 'react' ;
2+ import LZString from 'lz-string' ;
3+
4+ const ResetIcon = ( ) => (
5+ < svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" >
6+ < path d = "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
7+ < path d = "M3 3v5h5" />
8+ </ svg >
9+ ) ;
10+
11+ const OverviewIcon = ( ) => (
12+ < svg width = "16" height = "16" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" >
13+ < rect width = "7" height = "7" x = "3" y = "3" rx = "1" />
14+ < rect width = "7" height = "7" x = "14" y = "3" rx = "1" />
15+ < rect width = "7" height = "7" x = "14" y = "14" rx = "1" />
16+ < rect width = "7" height = "7" x = "3" y = "14" rx = "1" />
17+ </ svg >
18+ ) ;
219
320const ExcalidrawIcon = ( props : React . SVGProps < SVGSVGElement > ) => (
4- < svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" { ...props } >
5- < path d = "M12 19l7-7 3 3-7 7-3-3z" > </ path >
6- < path d = "M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z" > </ path >
7- < path d = "M2 2l7.586 7.586" > </ path >
8- < circle cx = "11" cy = "11" r = "2" > </ circle >
21+ < svg xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 32 32" { ...props } >
22+ < path fill = "#6965db" d = "M29.937 25.078a.19.19 0 0 0-.185-.042c-1.464-2.162-3.325-4.213-5.128-6.193l-.297-.325a.17.17 0 0 0-.042-.105a.2.2 0 0 0-.118-.07l-.06-.063l-.042-.031c-.052-.112-.185-.196-.332-.122c-.551.283-1.047.688-1.536 1.062c-.654.5-1.293 1.02-1.894 1.579a6 6 0 0 0-.688.73c-.098.129-.024.251.095.303q-.64.63-1.286 1.307a.2.2 0 0 0-.056.154a.2.2 0 0 0 .077.143l.755.576s.003.01.01.014c1.08 1.065 2.973 2.543 4.978 4.108q.446.351.897.702q.204.247.392.49a.2.2 0 0 0 .279.038c.045.034.09.073.136.108a.2.2 0 0 0 .28-.035a.2.2 0 0 0 .038-.108c.014 0 .025.01.035.01a.2.2 0 0 0 .147-.063l3.556-3.884a.196.196 0 0 0-.014-.28zm-10.21-1.345q.037.047.073.088c.406.342.839.712 1.279 1.09l-1.789-1.366l-.181-.126a2 2 0 0 1-.108-.084l-.133-.112s.024-.024.035-.038l.122-.123c.6-.607 1.631-1.62 2.162-2.116c-.562.566-1.7 2.225-1.456 2.787zm6.123 4.824l-1.474-1.125a37 37 0 0 0-1.83-1.757c.796.615 1.477 1.135 1.579 1.226c.772.689.737.563 1.268 1.017l.639.464c-.063.056-.126.116-.185.172zm.37.283l-.027-.02l.17-.134l-.139.154zM2.843 6.031l.14.737c.24 1.292.464 2.456.89 3.34l.168.67c.066.255.16.573.248.64c.995.88 2.522 2.193 4.153 3.43a.2.2 0 0 0 .245-.004q.006.01.014.014a.2.2 0 0 0 .132.052a.2.2 0 0 0 .147-.066c2.089-2.323 3.643-4.234 4.75-5.834a.44.44 0 0 0 .102-.293c.07-.084.143-.168.21-.237a.195.195 0 0 0-.035-.3a.2.2 0 0 0-.06-.127a95 95 0 0 0-1.208-1.145a104 104 0 0 1-2.715-2.624L10 4.264a.2.2 0 0 0-.077-.05c-.388-.136-1.184-.272-2.186-.447c-1.475-.251-3.493-.6-5.31-1.142h-.014v-.003s-.007 0-.01.007h-.004l.014-.007s-.108.003-.13.014a.2.2 0 0 0-.065.052c-.018.021-.032.042-.165.07c-.132.028.028 0 .039 0h-.039v.01c.025.12.018.203.056.34c-.007.034.074.356.084.387l.64 2.536zm10.81 2.284l-.013.018l-.224-.248q.114.107.238.23zm-2.476 3.28l-.035.042l-.007-.007q.02-.017.045-.034zm-1.415-7.02c.123.122.608.576.72.688c-.507-.231-1.768-.818-2.354-1.006c.576.1 1.372.23 1.634.317zm-6.7-.968c.294.503.525 2.267.755 3.982c-.13-.552-.24-1.09-.346-1.607c-.181-.894-.349-1.694-.583-2.403q.075.006.171.017q-.002.006.007.01zm-.1-.423q-.122-.012-.217-.017q-.01-.021-.014-.042l.23.063zm-.776.157v-.007zm27.434-.412c.014-.08-.384-.433-.259-.44c.297-.014.3-.471 0-.458c-.394.021-.793.112-1.177.186q-1.036.195-2.068.422a85 85 0 0 0-4.576 1.087c-.475.129-.999.244-1.435.475c-.147.076-.14.234-.06.331a.3.3 0 0 1-.097.032q-.195.035-.388.066a.198.198 0 0 0-.136.3c-.81 1.084-1.733 2.25-2.732 3.476a351 351 0 0 0-3.046 3.543c-3.287 3.863-7.014 8.243-11.143 12.1a.2.2 0 0 0-.01.279a.2.2 0 0 0 .066.045l-.168.154a.18.18 0 0 0-.056.112l-.08.087a.2.2 0 0 0 .01.28a.2.2 0 0 0 .28-.01l.042-.046a.293.293 0 0 1 .426 0l.681.73l-.482-.402a.2.2 0 0 0-.28.024a.2.2 0 0 0 .025.28l5.177 4.342a.2.2 0 0 0 .269-.014l.126-.126a.2.2 0 0 0 .22-.042c7.017-7.049 12.669-12.376 19.142-17.137a.2.2 0 0 0 .08-.178a.2.2 0 0 0 .168-.136c1.194-3.654 1.425-6.889 1.495-8.478l.007-.024q.01-.027.014-.05l.017-.065a.95.95 0 0 0-.052-.751zM17.072 8.647q.471-.54.933-1.055C15.993 10.24 12.66 14.32 7.942 19.168c3.213-3.555 6.451-7.24 9.13-10.52zM5.702 27.094l-.01-.01l.07.014a.2.2 0 0 0-.06 0zm2.41 2.243l-.017-.014l.01-.01c.007 0 .01.006.014.006c0 .007-.007.01-.01.018zm2.92-2.519l.482-.503l.01.018c-.163.16-.328.325-.495.485zm.783-.772l.304-.356q.004-.006.014-.014a201 201 0 0 1 3.555-3.58l.025-.021l.95-.727a520 520 0 0 0-4.848 4.702zm7.562-19.519c-.65.846-1.362 1.942-1.966 2.82c-1.908 2.762-8.048 9.528-8.185 9.657c-.946.916-3.8 3.654-5.62 5.37a1 1 0 0 0-.119.122a.277.277 0 0 1 .01-.395c8.67-8.174 13.93-14.982 16.069-17.947c-.046.115-.084.24-.192.38zm5.767 2.48l-.003.007c0-.007-.007-.024.003-.007m-2.375 1.51c-.79-.458-1.16-1.143-.947-1.834l.067-.23q.041-.1.098-.2c.206-.342.52-.646.88-.824q.026-.01.052-.014a.3.3 0 0 1-.014-.15c.018-.109.088-.203.23-.203c.235 0 .961.216 1.237.454q.127.1.238.22c.105.122.258.321.335.465c.046.02.08.216.133.317q.073.242.063.49c0 .006 0 .006.003.01c-.01.024 0 .13-.014.14c-.035.251-.126.5-.262.716l-.038.056c0 .003-.007.007-.01.014a1.6 1.6 0 0 1-.378.394c-.44.311-.953.406-1.467.276a2 2 0 0 1-.2-.087zm5.683-.57c-.171.716-.38 1.464-.629 2.229c-.01.028-.01.055-.01.08a.2.2 0 0 0-.094.038a106 106 0 0 0-4.535 3.532a250 250 0 0 1 3.923-3.476a2.24 2.24 0 0 0 .737-1.306l.196-1.178l.01-.035c.088-.248.468-.14.409.116z" />
923 </ svg >
1024) ;
1125
@@ -15,9 +29,16 @@ interface ExcalidrawSlidesProps {
1529 width ?: string | number ;
1630 title ?: string ;
1731 subtitle ?: string ;
32+ fontFamily ?: string ;
1833}
1934
20- export function ExcalidrawSlides ( { snapshotUrl, title, subtitle, height = 500 , width = "100%" } : ExcalidrawSlidesProps ) {
35+ export function ExcalidrawSlides ( {
36+ snapshotUrl,
37+ title,
38+ height = 500 ,
39+ width = "100%" ,
40+ fontFamily = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif'
41+ } : ExcalidrawSlidesProps ) {
2142 const containerRef = useRef < HTMLDivElement > ( null ) ;
2243 const svgContainerRef = useRef < HTMLDivElement > ( null ) ;
2344 const svgRef = useRef < SVGSVGElement | null > ( null ) ;
@@ -51,7 +72,31 @@ export function ExcalidrawSlides({ snapshotUrl, title, subtitle, height = 500, w
5172 try {
5273 const res = await fetch ( `${ snapshotUrl } ?t=${ Date . now ( ) } ` ) ;
5374 if ( ! res . ok ) throw new Error ( `Failed to load: ${ res . status } ` ) ;
54- const json = await res . json ( ) ;
75+
76+ const textContent = await res . text ( ) ;
77+ let json ;
78+
79+ // Try parsing as standard JSON first
80+ try {
81+ json = JSON . parse ( textContent ) ;
82+ } catch ( e ) {
83+ // If not JSON, try parsing as Obsidian-Excalidraw Markdown
84+ // Look for ```compressed-json ... ``` block
85+ const match = textContent . match ( / ` ` ` c o m p r e s s e d - j s o n \s * ( [ \s \S ] * ?) ` ` ` / ) ;
86+ if ( match ) {
87+ // Remove all whitespace (newlines, spaces) as LZString expects a continuous string
88+ const compressed = match [ 1 ] . replace ( / \s / g, '' ) ;
89+ const decompressed = LZString . decompressFromBase64 ( compressed ) ;
90+ if ( decompressed ) {
91+ json = JSON . parse ( decompressed ) ;
92+ } else {
93+ throw new Error ( "Failed to decompress Excalidraw data" ) ;
94+ }
95+ } else {
96+ throw new Error ( "Invalid Excalidraw file format" ) ;
97+ }
98+ }
99+
55100 const elements = json . elements || [ ] ;
56101
57102 const frameElements = elements . filter ( ( el : any ) => el . type === "frame" )
@@ -88,28 +133,43 @@ export function ExcalidrawSlides({ snapshotUrl, title, subtitle, height = 500, w
88133 try {
89134 const { exportToSvg } = await import ( "@excalidraw/excalidraw" ) ;
90135
91- const renderElements = data . elements . map ( ( el : any ) => {
92- if ( el . type === 'frame' ) {
93- return { ...el , strokeColor : '#00000000' , backgroundColor : 'transparent' , name : '' } ;
94- }
95- return el ;
96- } ) ;
136+ // Filter out deleted elements to ensure accurate bounds and rendering
137+ const activeElements = data . elements . filter ( ( el : any ) => ! el . isDeleted ) ;
97138
98- // Calculate JSON Min Bounds
139+ // Calculate raw bounding box of active elements (including frames)
99140 let minX = Infinity , minY = Infinity ;
100- renderElements . forEach ( ( el : any ) => {
141+ activeElements . forEach ( ( el : any ) => {
101142 if ( el . x < minX ) minX = el . x ;
102143 if ( el . y < minY ) minY = el . y ;
103144 } ) ;
104145 if ( minX === Infinity ) { minX = 0 ; minY = 0 ; }
105146
147+ // Dynamic Frame Rendering Options
148+ const frameRendering = viewMode === 'overview'
149+ ? { enabled : true , name : true , outline : true , clip : true }
150+ : { enabled : false , name : false , outline : false , clip : true } ;
151+
106152 const svg = await exportToSvg ( {
107- elements : renderElements ,
108- appState : { ...data . appState , exportBackground : true , viewBackgroundColor : "#ffffff" } ,
153+ elements : activeElements ,
154+ appState : {
155+ ...data . appState ,
156+ exportBackground : true ,
157+ viewBackgroundColor : "#ffffff" ,
158+ frameRendering
159+ } ,
109160 files : data . files || { } ,
110- exportPadding : 10 , // Restored padding to look nice, we will account for offset
161+ exportPadding : 10 ,
111162 } ) ;
112163
164+ // Inject Custom Font
165+ const style = document . createElementNS ( "http://www.w3.org/2000/svg" , "style" ) ;
166+ style . textContent = `
167+ text {
168+ font-family: ${ fontFamily } , "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", sans-serif !important;
169+ }
170+ ` ;
171+ svg . prepend ( style ) ;
172+
113173 svg . removeAttribute ( 'width' ) ;
114174 svg . removeAttribute ( 'height' ) ;
115175 svg . style . width = "100%" ;
@@ -122,8 +182,6 @@ export function ExcalidrawSlides({ snapshotUrl, title, subtitle, height = 500, w
122182 setRawViewBox ( vb ) ;
123183 currentViewBoxRef . current = vb ;
124184
125- // Calculate Offset: SVG ViewBox Origin - JSON Element Origin
126- // This accounts for padding or any normalization Excalidraw did.
127185 setCoordinateOffset ( {
128186 x : vb [ 0 ] - minX ,
129187 y : vb [ 1 ] - minY
@@ -137,7 +195,7 @@ export function ExcalidrawSlides({ snapshotUrl, title, subtitle, height = 500, w
137195 } catch ( e ) { console . error ( e ) ; }
138196 }
139197 renderSvg ( ) ;
140- } , [ data ] ) ;
198+ } , [ data , viewMode ] ) ;
141199
142200 const animate = useCallback ( ( time : number ) => {
143201 if ( ! transitionRef . current ) return ;
@@ -267,12 +325,13 @@ export function ExcalidrawSlides({ snapshotUrl, title, subtitle, height = 500, w
267325
268326 if ( ! isClient ) return < div style = { containerStyle } className = "bg-gray-50 flex items-center justify-center border-2 border-gray-200 rounded-xl" > Initializing...</ div > ;
269327
328+ const hasFrames = frames . length > 0 ;
329+
270330 return (
271331 < div className = "flex flex-col w-full my-6 bg-white border border-gray-200 rounded-2xl overflow-hidden shadow-sm" >
272332 < div className = "bg-gray-50 border-b border-gray-100 px-6 py-2.5 flex items-center justify-between" >
273333 < div className = "flex flex-col" >
274334 < h3 className = "text-sm font-bold text-gray-800 m-0 leading-tight" > { title || "Excalidraw" } </ h3 >
275- { subtitle && < span className = "text-[10px] text-gray-400 mt-0.5 font-medium" > { subtitle } </ span > }
276335 </ div >
277336 < div className = "opacity-60 hover:opacity-100 transition-opacity" >
278337 < ExcalidrawIcon className = "w-5 h-5 text-purple-600" />
@@ -294,7 +353,7 @@ export function ExcalidrawSlides({ snapshotUrl, title, subtitle, height = 500, w
294353 { loading && < div className = "absolute inset-0 z-20 flex items-center justify-center bg-white/80 backdrop-blur-sm" > < div className = "animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600" > </ div > </ div > }
295354
296355 { /* Frame Label (Slide Mode) */ }
297- { viewMode === 'slide' && (
356+ { hasFrames && viewMode === 'slide' && (
298357 < div className = "absolute bottom-4 left-4 z-50 pointer-events-none" >
299358 < span className = "px-2 py-1 bg-white/90 backdrop-blur text-gray-600 text-[10px] font-bold uppercase tracking-wider rounded border border-gray-200 shadow-sm" >
300359 { frames [ currentSlide ] ?. name || `Slide ${ currentSlide + 1 } ` }
@@ -305,45 +364,63 @@ export function ExcalidrawSlides({ snapshotUrl, title, subtitle, height = 500, w
305364
306365 < div className = "bg-gray-50 border-t border-gray-100 px-4 py-2 flex items-center justify-between h-10" >
307366 < div className = "w-24" >
308- < button
309- onClick = { ( ) => setViewMode ( prev => prev === 'overview' ? 'slide' : 'overview' ) }
310- className = { `text-[10px] font-bold uppercase tracking-widest transition-colors px-2 py-1 rounded ${ viewMode === 'overview' ? 'text-blue-600 bg-blue-50' : 'text-gray-400 hover:text-gray-600' } ` }
311- >
312- { viewMode === 'overview' ? 'Slides' : 'Overview' }
313- </ button >
367+ { hasFrames ? (
368+ < button
369+ onClick = { ( ) => setViewMode ( prev => prev === 'overview' ? 'slide' : 'overview' ) }
370+ className = { `text-[10px] font-bold uppercase tracking-widest transition-colors px-2 py-1 rounded ${
371+ viewMode === 'overview'
372+ ? 'text-red-600 bg-red-50'
373+ : 'text-blue-600 bg-blue-50'
374+ } `}
375+ >
376+ { viewMode === 'overview' ? 'Overview' : 'Slides' }
377+ </ button >
378+ ) : (
379+ < span className = "text-[10px] font-bold uppercase tracking-widest text-gray-400 bg-gray-50 px-2 py-1 rounded cursor-not-allowed" >
380+ Overview
381+ </ span >
382+ ) }
314383 </ div >
315384
316- < div className = "flex items-center bg-white border border-gray-200 rounded-lg px-1.5 py-0.5 shadow-sm gap-1" >
317- < button onClick = { ( ) => goToSlide ( ( currentSlide - 1 + frames . length ) % frames . length ) } className = "p-1 hover:bg-gray-50 rounded text-gray-500 transition-colors" > < svg width = "14" height = "14" viewBox = "0 0 15 15" fill = "none" > < path d = "M8.84182 3.13514C9.04327 3.32401 9.05348 3.64042 8.86462 3.84188L5.43521 7.49991L8.86462 11.1579C9.05348 11.3594 9.04327 11.6758 8.84182 11.8647C8.64036 12.0535 8.32394 12.0433 8.13508 11.8419L4.38508 7.84188C4.20477 7.64955 4.20477 7.35027 4.38508 7.15794L8.13508 3.15794C8.32394 2.95648 8.64036 2.94628 8.84182 3.13514Z" fill = "currentColor" fillRule = "evenodd" > </ path > </ svg > </ button >
318-
319- < div className = "flex items-center gap-1 px-1" >
320- < input
321- type = "text"
322- value = { currentSlide + 1 }
323- onChange = { ( e ) => {
324- const val = parseInt ( e . target . value ) ;
325- if ( ! isNaN ( val ) ) goToSlide ( val - 1 ) ;
326- } }
327- className = "w-6 bg-transparent text-[10px] font-black text-center text-gray-800 border-none outline-none focus:ring-0 p-0"
328- />
329- < span className = "text-[10px] font-black text-gray-300" > /</ span >
330- < span className = "text-[10px] font-black text-gray-400 w-6 text-center" > { frames . length } </ span >
331- </ div >
332-
333- < button onClick = { ( ) => goToSlide ( ( currentSlide + 1 ) % frames . length ) } className = "p-1 hover:bg-gray-50 rounded text-gray-500 transition-colors" > < svg width = "14" height = "14" viewBox = "0 0 15 15" fill = "none" > < path d = "M6.1584 3.13523C5.95694 3.32411 5.94673 3.64053 6.1356 3.84199L9.565 7.50003L6.1356 11.1581C5.94673 11.3596 5.95694 11.676 6.1584 11.8648C6.35986 12.0537 6.67628 12.0435 6.86514 11.842L10.6151 7.84201C10.7954 7.64968 10.7954 7.35038 10.6151 7.15805L6.86514 3.15805C6.67628 2.95659 6.35986 2.94638 6.1584 3.13523Z" fill = "currentColor" fillRule = "evenodd" > </ path > </ svg > </ button >
385+ < div className = "flex-1 flex justify-center" >
386+ { hasFrames && (
387+ < div className = "flex items-center bg-white border border-gray-200 rounded-lg px-1.5 py-0.5 shadow-sm gap-1" >
388+ < button onClick = { ( ) => goToSlide ( ( currentSlide - 1 + frames . length ) % frames . length ) } className = "p-1 hover:bg-gray-50 rounded text-gray-500 transition-colors" > < svg width = "14" height = "14" viewBox = "0 0 15 15" fill = "none" > < path d = "M8.84182 3.13514C9.04327 3.32401 9.05348 3.64042 8.86462 3.84188L5.43521 7.49991L8.86462 11.1579C9.05348 11.3594 9.04327 11.6758 8.84182 11.8647C8.64036 12.0535 8.32394 12.0433 8.13508 11.8419L4.38508 7.84188C4.20477 7.64955 4.20477 7.35027 4.38508 7.15794L8.13508 3.15794C8.32394 2.95648 8.64036 2.94628 8.84182 3.13514Z" fill = "currentColor" fillRule = "evenodd" > </ path > </ svg > </ button >
389+
390+ < div className = "flex items-center gap-1 px-1" >
391+ < input
392+ type = "text"
393+ value = { currentSlide + 1 }
394+ onChange = { ( e ) => {
395+ const val = parseInt ( e . target . value ) ;
396+ if ( ! isNaN ( val ) ) goToSlide ( val - 1 ) ;
397+ } }
398+ className = "w-6 bg-transparent text-[10px] font-black text-center text-gray-800 border-none outline-none focus:ring-0 p-0"
399+ />
400+ < span className = "text-[10px] font-black text-gray-300" > /</ span >
401+ < span className = "text-[10px] font-black text-gray-400 w-6 text-center" > { frames . length } </ span >
402+ </ div >
403+
404+ < button onClick = { ( ) => goToSlide ( ( currentSlide + 1 ) % frames . length ) } className = "p-1 hover:bg-gray-50 rounded text-gray-500 transition-colors" > < svg width = "14" height = "14" viewBox = "0 0 15 15" fill = "none" > < path d = "M6.1584 3.13523C5.95694 3.32411 5.94673 3.64053 6.1356 3.84199L9.565 7.50003L6.1356 11.1581C5.94673 11.3596 5.95694 11.676 6.1584 11.8648C6.35986 12.0537 6.67628 12.0435 6.86514 11.842L10.6151 7.84201C10.7954 7.64968 10.7954 7.35038 10.6151 7.15805L6.86514 3.15805C6.67628 2.95659 6.35986 2.94638 6.1584 3.13523Z" fill = "currentColor" fillRule = "evenodd" > </ path > </ svg > </ button >
405+ </ div >
406+ ) }
334407 </ div >
335408
336409 < div className = "flex items-center gap-1 w-24 justify-end" >
337410 < button
338411 onClick = { ( ) => {
339412 syncView ( ) ;
340413 } }
341- className = "w-7 h-7 flex items-center justify-center hover:bg-gray-200 rounded transition-colors text-xs "
414+ className = "w-7 h-7 flex items-center justify-center hover:bg-gray-200 rounded transition-colors text-gray-500 "
342415 title = "Reset View"
343416 >
344- 🔄
417+ < ResetIcon />
345418 </ button >
346- < button onClick = { ( ) => setViewMode ( 'overview' ) } className = "w-7 h-7 flex items-center justify-center hover:bg-red-100 rounded transition-colors text-[10px]" title = "Overview" > ⏹️</ button >
419+ { hasFrames && (
420+ < button onClick = { ( ) => setViewMode ( 'overview' ) } className = "w-7 h-7 flex items-center justify-center hover:bg-red-100 rounded transition-colors text-gray-500 hover:text-red-500" title = "Overview" >
421+ < OverviewIcon />
422+ </ button >
423+ ) }
347424 </ div >
348425 </ div >
349426 </ div >
0 commit comments