|  | 
|  | 1 | +import {isDarkTheme} from '../utils.ts'; | 
|  | 2 | +import {makeCodeCopyButton} from './codecopy.ts'; | 
|  | 3 | +import {displayError} from './common.ts'; | 
|  | 4 | +import {queryElems} from '../utils/dom.ts'; | 
|  | 5 | +import {html, htmlRaw} from '../utils/html.ts'; | 
|  | 6 | + | 
|  | 7 | +const {excalidrawMaxSourceCharacters} = window.config; | 
|  | 8 | + | 
|  | 9 | +const iframeCss = `body { margin: 0; } svg { max-width: 100%; height: auto; }`; | 
|  | 10 | + | 
|  | 11 | +export async function initMarkupCodeExcalidraw(elMarkup: HTMLElement): Promise<void> { | 
|  | 12 | +  queryElems(elMarkup, 'code.language-excalidraw', async (el) => { | 
|  | 13 | +    const {exportToSvg} = await import(/* webpackChunkName: "excalidraw/utils" */ '@excalidraw/utils'); | 
|  | 14 | + | 
|  | 15 | +    const pre = el.closest('pre'); | 
|  | 16 | +    if (pre.hasAttribute('data-render-done')) return; | 
|  | 17 | + | 
|  | 18 | +    const source = el.textContent; | 
|  | 19 | +    if (excalidrawMaxSourceCharacters >= 0 && source.length > excalidrawMaxSourceCharacters) { | 
|  | 20 | +      displayError(pre, new Error(`Excalidraw source of ${source.length} characters exceeds the maximum allowed length of ${excalidrawMaxSourceCharacters}.`)); | 
|  | 21 | +      return; | 
|  | 22 | +    } | 
|  | 23 | + | 
|  | 24 | +    let excalidrawJson; | 
|  | 25 | +    try { | 
|  | 26 | +      excalidrawJson = JSON.parse(source); | 
|  | 27 | +    } catch (err) { | 
|  | 28 | +      displayError(pre, new Error(`Invalid Excalidraw JSON: ${err}`)); | 
|  | 29 | +      return; | 
|  | 30 | +    } | 
|  | 31 | + | 
|  | 32 | +    try { | 
|  | 33 | +      const svg = await exportToSvg({ | 
|  | 34 | +        elements: excalidrawJson.elements, | 
|  | 35 | +        appState: { | 
|  | 36 | +          ...excalidrawJson.appState, | 
|  | 37 | +          exportWithDarkMode: isDarkTheme(), | 
|  | 38 | +        }, | 
|  | 39 | +        files: excalidrawJson.files, | 
|  | 40 | +        skipInliningFonts: true, | 
|  | 41 | +      }); | 
|  | 42 | +      const iframe = document.createElement('iframe'); | 
|  | 43 | +      iframe.classList.add('markup-content-iframe', 'tw-invisible'); | 
|  | 44 | +      iframe.srcdoc = html`<html><head><style>${htmlRaw(iframeCss)}</style></head><body>${htmlRaw(svg.outerHTML)}</body></html>`; | 
|  | 45 | + | 
|  | 46 | +      const excalidrawBlock = document.createElement('div'); | 
|  | 47 | +      excalidrawBlock.classList.add('excalidraw-block', 'is-loading', 'tw-hidden'); | 
|  | 48 | +      excalidrawBlock.append(iframe); | 
|  | 49 | + | 
|  | 50 | +      const btn = makeCodeCopyButton(); | 
|  | 51 | +      btn.setAttribute('data-clipboard-text', source); | 
|  | 52 | +      excalidrawBlock.append(btn); | 
|  | 53 | + | 
|  | 54 | +      const updateIframeHeight = () => { | 
|  | 55 | +        const body = iframe.contentWindow?.document?.body; | 
|  | 56 | +        if (body) { | 
|  | 57 | +          iframe.style.height = `${body.clientHeight}px`; | 
|  | 58 | +        } | 
|  | 59 | +      }; | 
|  | 60 | +      iframe.addEventListener('load', () => { | 
|  | 61 | +        pre.replaceWith(excalidrawBlock); | 
|  | 62 | +        excalidrawBlock.classList.remove('tw-hidden'); | 
|  | 63 | +        updateIframeHeight(); | 
|  | 64 | +        setTimeout(() => { // avoid flash of iframe background | 
|  | 65 | +          excalidrawBlock.classList.remove('is-loading'); | 
|  | 66 | +          iframe.classList.remove('tw-invisible'); | 
|  | 67 | +        }, 0); | 
|  | 68 | + | 
|  | 69 | +        (new IntersectionObserver(() => { | 
|  | 70 | +          updateIframeHeight(); | 
|  | 71 | +        }, {root: document.documentElement})).observe(iframe); | 
|  | 72 | +      }); | 
|  | 73 | + | 
|  | 74 | +      document.body.append(excalidrawBlock); | 
|  | 75 | +    } catch (err) { | 
|  | 76 | +      displayError(pre, err); | 
|  | 77 | +    } | 
|  | 78 | +  }); | 
|  | 79 | +} | 
0 commit comments