/** * The runtime inside a CLI-exported page. * * `seemore export` renders the article in node, where diagrams cannot render — that would * mean a headless browser as an install dependency, which SPEC §16 rules out. This entry, * bundled to an IIFE and inlined into the file, finishes the job when the page is opened: * each diagram is rendered from the source `pre` the site's own components leave in * prerendered output. It also binds the behaviors the export keeps — theme toggle, code * copy, tabs, click-to-zoom — so the file behaves like the browser-exported one. * * The browser export ships a hand-written twin of the behavior half (the `RUNTIME` string * in `exportPage.ts`): it needs no diagram half, because its diagrams are already SVG when * the export runs. Keep the two in sync — adding, removing or changing a behavior in one * `bindBehaviors`/`RUNTIME` requires the identical change in the other. */ const isDark = (): boolean => document.documentElement.classList.contains('dark'); async function renderMermaid(container: HTMLElement, chart: string, index: number): Promise { const { default: mermaid } = await import('mermaid'); mermaid.initialize({ startOnLoad: false, theme: isDark() ? 'dark' : 'default', securityLevel: 'strict' }); const { svg } = await mermaid.render(`seemore-export-mermaid-${index}`, chart); container.innerHTML = svg; } async function renderD2(container: HTMLElement, chart: string, index: number): Promise { const { D2: D2Compiler } = await import('@terrastruct/d2'); const compiler = new D2Compiler(); const { diagram, renderOptions } = await compiler.compile(chart, { options: { themeID: isDark() ? 300 : 0 }, }); container.innerHTML = await compiler.render(diagram, { ...renderOptions, salt: String(index) }); } /** The site's own failure UI for a diagram that will not render. */ function fail(container: HTMLElement, className: string, cause: unknown): void { const pre = document.createElement('pre'); pre.className = className; pre.setAttribute('role', 'note'); pre.textContent = `Could not render this diagram: ${cause instanceof Error ? cause.message : String(cause)}`; container.replaceChildren(pre); } async function renderDiagrams(): Promise { const kinds = [ { selector: '.seemore-mermaid', source: '.seemore-mermaid-source', error: 'seemore-mermaid-error', render: renderMermaid }, { selector: '.seemore-d2', source: '.seemore-d2-source', error: 'seemore-d2-error', render: renderD2 }, ] as const; for (const kind of kinds) { const containers = Array.from(document.querySelectorAll(kind.selector)); for (const [index, container] of containers.entries()) { if (container.querySelector('svg') !== null) continue; const chart = container.querySelector(kind.source)?.textContent ?? ''; if (chart === '') continue; try { await kind.render(container, chart, index); } catch (cause) { fail(container, kind.error, cause); } } } } /** * A button's accessible name: fumadocs-ui 16.15 set `aria-label`, 16.16 puts the text in a * visually hidden span instead. */ function labelOf(button: Element): string { return (button.getAttribute('aria-label') ?? button.textContent ?? '').trim(); } function bindBehaviors(): void { const root = document.documentElement; document.querySelector('.seemore-export-theme-toggle')?.addEventListener('click', () => { root.classList.toggle('dark'); // Set by THEME_INIT in the head: pins the file to this choice against the OS. (window as { __seemoreRememberTheme?: () => void }).__seemoreRememberTheme?.(); }); for (const figure of document.querySelectorAll('figure')) { const button = [...figure.querySelectorAll('button')].find((b) => /copy/i.test(labelOf(b))); if (button === undefined) continue; button.addEventListener('click', () => { const code = figure.querySelector('pre, code'); if (code !== null) void navigator.clipboard?.writeText(code.textContent ?? ''); }); } for (const button of [...document.querySelectorAll('h1 button, h2 button, h3 button, h4 button, h5 button, h6 button')].filter( (b) => labelOf(b) === 'Copy Anchor Link', )) { const heading = button.closest('h1, h2, h3, h4, h5, h6'); const svg = button.querySelector('svg'); if (heading === null || heading.id === '' || navigator.clipboard === undefined) continue; let revert: ReturnType | undefined; button.addEventListener('click', () => { const url = new URL(window.location.href); url.hash = heading.id; void navigator.clipboard.writeText(url.href); if (svg === null) return; clearTimeout(revert); const original = svg.innerHTML; svg.innerHTML = ''; revert = setTimeout(() => { svg.innerHTML = original; }, 1500); }); } // Tabs are exported as the static Radix markup the live page hydrated; the file wires // them back up by hand — a click selects within its tablist and swaps the panels. for (const tablist of document.querySelectorAll('[role="tablist"]')) { const triggers = Array.from(tablist.querySelectorAll('button[role="tab"]')); for (const trigger of triggers) { trigger.addEventListener('click', () => { for (const other of triggers) { const active = other === trigger; other.setAttribute('aria-selected', active ? 'true' : 'false'); other.setAttribute('data-state', active ? 'active' : 'inactive'); other.setAttribute('tabindex', active ? '0' : '-1'); const panel = document.getElementById(other.getAttribute('aria-controls') ?? ''); if (panel !== null) { panel.hidden = !active; panel.setAttribute('data-state', active ? 'active' : 'inactive'); } } }); } } // The live site's TOC follows the reader with fumadocs' own scroll tracking; in the file, // the plainest version of the same behaviour — last heading above the fold wins. const tocLinks = Array.from(document.querySelectorAll('.seemore-export-toc a')); if (tocLinks.length > 0) { const byId = new Map(); for (const a of tocLinks) byId.set((a.getAttribute('href') ?? '').slice(1), a); const points = Array.from(byId.keys()) .map((id) => document.getElementById(id)) .filter((el): el is HTMLElement => el !== null); const sync = (): void => { let current: HTMLElement | undefined; for (const point of points) { // Headings carry `scroll-margin-top` (room for the live site's fixed header), and an // anchor jump parks them exactly there — so "reached" means at or above their own // margin line, not the raw viewport top. const margin = Number.parseFloat(getComputedStyle(point).scrollMarginTop) || 0; if (point.getBoundingClientRect().top - margin <= 48) current = point; else break; } for (const a of tocLinks) a.removeAttribute('data-active'); if (current !== undefined) byId.get(current.id)?.setAttribute('data-active', 'true'); }; window.addEventListener('scroll', sync, { passive: true }); sync(); } // Wide enough for the rail: open the collapsible so it reads as a list, not a disclosure. const tocDetails = document.querySelector('.seemore-export-toc details'); if (tocDetails && window.matchMedia('(min-width: 1024px)').matches) tocDetails.open = true; const overlay = document.createElement('div'); overlay.className = 'seemore-export-overlay'; overlay.hidden = true; const zoomed = document.createElement('img'); overlay.append(zoomed); document.body.append(overlay); const close = (): void => { overlay.hidden = true; zoomed.removeAttribute('src'); }; overlay.addEventListener('click', close); document.addEventListener('keydown', (event) => { if (event.key === 'Escape') close(); }); for (const img of document.querySelectorAll('main img')) { if (img.closest('a') !== null) continue; img.addEventListener('click', () => { zoomed.setAttribute('src', img.currentSrc || img.src); overlay.hidden = false; }); } } function main(): void { bindBehaviors(); void renderDiagrams(); } main();