// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at https://mozilla.org/MPL/2.0/. // SPDX-License-Identifier: MPL-2.0 import type { ParserDiagnostic } from '../model/diagnostics.js'; import type { OneNoteColor, OneNoteContentBlock, OneNoteInkBlock, OneNoteTextBlock, OneNoteTextRun, } from '../one/content-model.js'; import { createOneNoteExportPlan, enforceSerializedTextOutput, exportResourceKey, type OneNoteExportPlan, type PlannedOneNoteResource, } from './plan.js'; import type { OneNoteExportOptions, OneNoteExportSection, OneNoteExportSnapshot, } from './types.js'; interface RenderContext { linkResources: boolean; resources: ReadonlyMap; } export function serializeOneNoteStructuredJson( snapshot: OneNoteExportSnapshot, options: OneNoteExportOptions = {} ): string { const plan = createOneNoteExportPlan(snapshot, options); return serializeStructuredJsonWithPlan(snapshot, plan); } export function serializeOneNotePlainText( snapshot: OneNoteExportSnapshot, options: OneNoteExportOptions = {} ): string { const plan = createOneNoteExportPlan(snapshot, options); return serializePlainTextWithPlan(snapshot, plan); } export function serializeOneNoteMarkdown( snapshot: OneNoteExportSnapshot, options: OneNoteExportOptions = {} ): string { const plan = createOneNoteExportPlan(snapshot, options); return serializeMarkdownWithPlan(snapshot, plan, false); } export function serializeOneNoteSemanticHtml( snapshot: OneNoteExportSnapshot, options: OneNoteExportOptions = {} ): string { const plan = createOneNoteExportPlan(snapshot, options); return serializeHtmlWithPlan(snapshot, plan, false); } export function serializeStructuredJsonWithPlan( snapshot: OneNoteExportSnapshot, plan: OneNoteExportPlan ): string { const resources = plan.resources.map((resource) => ({ sectionId: resource.sectionId, id: resource.id, kind: resource.kind, path: resource.path, filename: resource.filename, extension: resource.extension, mediaType: resource.mediaType, browserRenderable: resource.browserRenderable, size: resource.size, })); const value = { schemaVersion: snapshot.schemaVersion, exportKind: 'onenote-tools-structured-export', app: snapshot.app, parser: snapshot.parser, supportLevel: snapshot.supportLevel, supportNotes: snapshot.supportNotes, source: snapshot.source, notebookName: snapshot.notebookName, ...(snapshot.tree ? { tree: snapshot.tree } : {}), sections: snapshot.sections, resources, diagnostics: plan.diagnostics, warnings: plan.warnings, unsupportedContent: plan.unsupportedContent, }; return enforceSerializedTextOutput( `${JSON.stringify(value, null, 2)}\n`, plan, 'Structured JSON export' ); } export function serializePlainTextWithPlan( snapshot: OneNoteExportSnapshot, plan: OneNoteExportPlan ): string { const lines = [ snapshot.notebookName, '='.repeat(Math.max(1, snapshot.notebookName.length)), '', `Source: ${oneLine(snapshot.source.name)}`, `Source format: ${snapshot.source.format}`, `Support level: ${snapshot.supportLevel}`, `Application: ${oneLine(snapshot.app.name)} ${oneLine(snapshot.app.version)}`, `Parser: ${oneLine(snapshot.parser.name)} ${oneLine(snapshot.parser.version)}`, ]; if (snapshot.parser.supportedVariants.length > 0) { lines.push( `Parser variants: ${snapshot.parser.supportedVariants.map(oneLine).join(', ')}` ); } if (snapshot.parser.referenceRevision) { lines.push( `Parser reference: ${oneLine(snapshot.parser.referenceRevision)}` ); } if (snapshot.supportNotes.length > 0) { lines.push( ...snapshot.supportNotes.map((note) => `Support note: ${oneLine(note)}`) ); } for (const [sectionIndex, section] of snapshot.sections.entries()) { lines.push('', '', `SECTION ${sectionIndex + 1}: ${oneLine(section.name)}`); lines.push('-'.repeat(Math.max(1, section.name.length + 11))); for (const [pageIndex, page] of section.pages.entries()) { lines.push('', `PAGE ${pageIndex + 1}: ${oneLine(page.title)}`); if (page.createdAt) lines.push(`Created: ${oneLine(page.createdAt)}`); if (page.updatedAt) lines.push(`Updated: ${oneLine(page.updatedAt)}`); if (page.level !== undefined) lines.push(`Level: ${page.level}`); const rendered = plainBlocks(page.blocks).trim(); const body = rendered || page.text.trim(); lines.push('', body || '[Empty page]'); } } appendPlainWarnings(lines, plan); return enforceSerializedTextOutput( `${lines .join('\n') .replace(/\n{4,}/gu, '\n\n\n') .trimEnd()}\n`, plan, 'Plain-text export' ); } export function serializeMarkdownWithPlan( snapshot: OneNoteExportSnapshot, plan: OneNoteExportPlan, linkResources: boolean ): string { const context = createRenderContext(plan, linkResources); const lines = [ `# ${markdownText(snapshot.notebookName)}`, '', `- Source: ${markdownText(snapshot.source.name)}`, `- Source format: \`${snapshot.source.format}\``, `- Support level: \`${snapshot.supportLevel}\``, `- Application: ${markdownText(snapshot.app.name)} \`${markdownText(snapshot.app.version)}\``, `- Parser: ${markdownText(snapshot.parser.name)} \`${markdownText(snapshot.parser.version)}\``, ]; if (snapshot.parser.supportedVariants.length > 0) { lines.push( `- Parser variants: ${snapshot.parser.supportedVariants.map((value) => `\`${markdownText(value)}\``).join(', ')}` ); } if (snapshot.parser.referenceRevision) { lines.push( `- Parser reference: \`${markdownText(snapshot.parser.referenceRevision)}\`` ); } if (snapshot.supportNotes.length > 0) { lines.push('', '## Support notes', ''); lines.push( ...snapshot.supportNotes.map((note) => `- ${markdownText(note)}`) ); } for (const section of snapshot.sections) { lines.push('', `## ${markdownText(section.name)}`, ''); for (const page of section.pages) { lines.push(`### ${markdownText(page.title)}`, ''); const metadata = [ page.createdAt ? `Created: ${markdownText(page.createdAt)}` : undefined, page.updatedAt ? `Updated: ${markdownText(page.updatedAt)}` : undefined, page.level === undefined ? undefined : `Level: ${page.level}`, ].filter((value): value is string => value !== undefined); if (metadata.length > 0) lines.push(...metadata.map((line) => `_${line}_`), ''); const rendered = markdownBlocks(page.blocks, context, section.id).trim(); const body = rendered || markdownText(page.text).trim(); lines.push(body || '*Empty page*', ''); } } appendMarkdownWarnings(lines, plan); return enforceSerializedTextOutput( `${lines .join('\n') .replace(/\n{4,}/gu, '\n\n\n') .trimEnd()}\n`, plan, 'Markdown export' ); } export function serializeHtmlWithPlan( snapshot: OneNoteExportSnapshot, plan: OneNoteExportPlan, linkResources: boolean ): string { const context = createRenderContext(plan, linkResources); const variants = snapshot.parser.supportedVariants .map((value) => `
  • ${escapeHtml(value)}
  • `) .join(''); const supportNotes = snapshot.supportNotes .map((note) => `
  • ${escapeHtml(note)}
  • `) .join(''); const sections = snapshot.sections .map((section) => htmlSection(section, context)) .join('\n'); const warnings = htmlWarnings(plan); return enforceSerializedTextOutput( ` ${escapeHtml(snapshot.notebookName)}

    ${escapeHtml(snapshot.notebookName)}

    Source
    ${escapeHtml(snapshot.source.name)}
    Source format
    ${snapshot.source.format}
    Support level
    ${snapshot.supportLevel}
    Application
    ${escapeHtml(snapshot.app.name)} ${escapeHtml(snapshot.app.version)}
    Parser
    ${escapeHtml(snapshot.parser.name)} ${escapeHtml(snapshot.parser.version)}
    ${snapshot.parser.referenceRevision ? `
    Parser reference
    ${escapeHtml(snapshot.parser.referenceRevision)}
    ` : ''}
    ${variants ? `

    Parser variants

    ` : ''} ${supportNotes ? `

    Support notes

    ` : ''}
    ${sections}
    ${warnings} `, plan, 'Semantic HTML export' ); } function htmlSection( section: OneNoteExportSection, context: RenderContext ): string { const pages = section.pages .map((page) => { const metadata = [ page.createdAt ? `
    Created
    ${escapeHtml(page.createdAt)}
    ` : '', page.updatedAt ? `
    Updated
    ${escapeHtml(page.updatedAt)}
    ` : '', page.level === undefined ? '' : `
    Level
    ${page.level}
    `, ].join(''); const rendered = htmlBlocks(page.blocks, context, section.id); const body = rendered || (page.text ? `

    ${escapeHtmlWithBreaks(page.text)}

    ` : '

    Empty page

    '); return `

    ${escapeHtml(page.title)}

    ${metadata ? `` : ''}
    ${body}
    `; }) .join('\n'); return `

    ${escapeHtml(section.name)}

    ${pages}
    `; } function plainBlocks(blocks: readonly OneNoteContentBlock[]): string { return blocks.map(plainBlock).filter(Boolean).join('\n'); } function plainBlock(block: OneNoteContentBlock): string { switch (block.kind) { case 'text': return block.text.split('\u000b').join('\n'); case 'image': return `[Image: ${oneLine(block.altText ?? block.filename ?? 'unnamed')}]`; case 'attachment': return `[Attachment: ${oneLine(block.filename)}]`; case 'table': return block.rows .map((row) => row.cells .map((cell) => compactPlain(plainBlocks(cell.blocks))) .join('\t') ) .join('\n'); case 'outline': case 'outline-group': return plainBlocks(block.children); case 'outline-element': return [plainBlocks(block.contents), plainBlocks(block.children)] .filter(Boolean) .join('\n'); case 'ink': { const stats = inkStats(block); const children = block.children .map(plainBlock) .filter(Boolean) .join('\n'); return [ `[Ink drawing: ${stats.strokes} stroke${stats.strokes === 1 ? '' : 's'}, ${stats.points} point${stats.points === 1 ? '' : 's'}]`, children, ] .filter(Boolean) .join('\n'); } case 'unsupported': return `[Unsupported content: ${oneLine(block.description)}]`; } } function markdownBlocks( blocks: readonly OneNoteContentBlock[], context: RenderContext, sectionId: string ): string { return blocks .map((block) => markdownBlock(block, context, sectionId)) .filter(Boolean) .join('\n\n'); } function markdownBlock( block: OneNoteContentBlock, context: RenderContext, sectionId: string ): string { switch (block.kind) { case 'text': return markdownTextBlock(block); case 'image': { const label = markdownText(block.altText ?? block.filename ?? 'Image'); const path = resourcePath(context, sectionId, block.resourceId); const image = path ? `![${label}](${markdownUrl(path)})` : `**[Image: ${label}]**`; const href = safeExternalHref(block.href); return href ? `[${image}](${markdownUrl(href)})` : image; } case 'attachment': { const label = markdownText(block.filename); const path = resourcePath(context, sectionId, block.resourceId); return path ? `[Attachment: ${label}](${markdownUrl(path)})` : `**[Attachment: ${label}]**`; } case 'table': return markdownTable(block, context, sectionId); case 'outline': case 'outline-group': return markdownBlocks(block.children, context, sectionId); case 'outline-element': return [ markdownBlocks(block.contents, context, sectionId), markdownBlocks(block.children, context, sectionId), ] .filter(Boolean) .join('\n\n'); case 'ink': { const stats = inkStats(block); return `**[Ink drawing: ${stats.strokes} stroke${stats.strokes === 1 ? '' : 's'}, ${stats.points} point${stats.points === 1 ? '' : 's'}]**`; } case 'unsupported': return `> Unsupported content: ${markdownText(block.description)}`; } } function markdownTextBlock(block: OneNoteTextBlock): string { const visible = block.runs.filter((run) => run.style.hidden !== true); if (visible.length === 0) return markdownText(block.text); return visible.map(markdownRun).join('').split('\u000b').join(' \n'); } function markdownRun(run: OneNoteTextRun): string { let value = markdownText(run.text); if (!value) return ''; if (run.style.bold) value = `**${value}**`; if (run.style.italic) value = `*${value}*`; if (run.style.strikethrough) value = `~~${value}~~`; const href = safeExternalHref(run.href); return href ? `[${value}](${markdownUrl(href)})` : value; } function markdownTable( block: Extract, context: RenderContext, sectionId: string ): string { let columnCount = Math.max(0, block.declaredColumnCount ?? 0); for (const row of block.rows) { columnCount = Math.max(columnCount, row.cells.length); } if (columnCount === 0) return '**[Empty table]**'; const header = Array.from( { length: columnCount }, (_, index) => `Column ${index + 1}` ); const rows = block.rows.map((row) => Array.from({ length: columnCount }, (_, index) => markdownCell( row.cells[index] ? markdownBlocks(row.cells[index]!.blocks, context, sectionId) : '' ) ) ); return [ `| ${header.join(' | ')} |`, `| ${header.map(() => '---').join(' | ')} |`, ...rows.map((row) => `| ${row.join(' | ')} |`), ].join('\n'); } function htmlBlocks( blocks: readonly OneNoteContentBlock[], context: RenderContext, sectionId: string ): string { return blocks.map((block) => htmlBlock(block, context, sectionId)).join(''); } function htmlBlock( block: OneNoteContentBlock, context: RenderContext, sectionId: string ): string { switch (block.kind) { case 'text': { const runs = block.runs.filter((run) => run.style.hidden !== true); const contents = runs.length > 0 ? runs.map(htmlRun).join('') : escapeHtmlWithBreaks(block.text); const alignment = block.paragraphAlignment === 'center' || block.paragraphAlignment === 'right' ? ` align-${block.paragraphAlignment}` : ''; return `

    ${contents}

    `; } case 'image': { const label = block.altText ?? block.filename ?? 'OneNote image'; const path = resourcePath(context, sectionId, block.resourceId); if (!path) { return `
    Image: ${escapeHtml(label)}
    `; } const image = `${escapeAttribute(label)}`; const href = safeExternalHref(block.href); return `
    ${href ? `${image}` : image}
    ${escapeHtml(label)}
    `; } case 'attachment': { const path = resourcePath(context, sectionId, block.resourceId); return `

    ${path ? `Attachment: ${escapeHtml(block.filename)}` : `Attachment: ${escapeHtml(block.filename)} (unavailable)`}

    `; } case 'table': return `${block.rows .map( (row) => `${row.cells .map( (cell) => `` ) .join('')}` ) .join('')}
    ${htmlBlocks(cell.blocks, context, sectionId)}
    `; case 'outline': return `
    ${htmlBlocks(block.children, context, sectionId)}
    `; case 'outline-group': return `
    ${htmlBlocks(block.children, context, sectionId)}
    `; case 'outline-element': return `
    ${htmlBlocks(block.contents, context, sectionId)}${block.children.length > 0 ? `
    ${htmlBlocks(block.children, context, sectionId)}
    ` : ''}
    `; case 'ink': return htmlInk(block, context, sectionId); case 'unsupported': return ``; } } function htmlRun(run: OneNoteTextRun): string { let value = escapeHtmlWithBreaks(run.text); if (!value) return ''; if (run.style.bold) value = `${value}`; if (run.style.italic) value = `${value}`; if (run.style.underline) value = `${value}`; if (run.style.strikethrough) value = `${value}`; if (run.style.superscript) value = `${value}`; if (run.style.subscript) value = `${value}`; const styles: string[] = []; const color = cssColor(run.style.fontColor); const highlight = cssColor(run.style.highlight); if (color) styles.push(`color:${color}`); if (highlight) styles.push(`background-color:${highlight}`); if ( run.style.fontSize !== undefined && Number.isFinite(run.style.fontSize) && run.style.fontSize >= 2 && run.style.fontSize <= 800 ) { styles.push(`font-size:${formatNumber(run.style.fontSize / 2)}pt`); } if (styles.length > 0) value = `${value}`; const href = safeExternalHref(run.href); return href ? `${value}` : value; } function htmlInk( block: OneNoteInkBlock, context: RenderContext, sectionId: string ): string { const stats = inkStats(block); const bounds = validBounds(block.boundingBox) ?? boundsFromStrokes(block.strokes); let drawing = ''; if (bounds) { let maxWidth = 1; for (const stroke of block.strokes) { maxWidth = Math.max( maxWidth, Number.isFinite(stroke.width) && stroke.width! > 0 ? stroke.width! : 35 ); } const padding = maxWidth / 2; const viewBox = [ bounds.x - padding, bounds.y - padding, Math.max(1, bounds.width + padding * 2), Math.max(1, bounds.height + padding * 2), ] .map(formatNumber) .join(' '); const strokes = block.strokes.map(svgStroke).join(''); drawing = `${strokes}`; } const children = block.children .map((child) => htmlInk(child, context, sectionId)) .join(''); return `
    ${drawing || '
    Ink drawing unavailable
    '}
    Ink drawing: ${stats.strokes} stroke${stats.strokes === 1 ? '' : 's'}, ${stats.points} point${stats.points === 1 ? '' : 's'}
    ${children}
    `; } function svgStroke(stroke: OneNoteInkBlock['strokes'][number]): string { const points = stroke.points.filter( (point) => Number.isFinite(point.x) && Number.isFinite(point.y) ); if (points.length === 0) return ''; const color = inkColor(stroke.color); const width = Number.isFinite(stroke.width) && stroke.width! > 0 ? stroke.width! : 35; const transparency = stroke.transparency !== undefined && Number.isFinite(stroke.transparency) ? Math.max(0, Math.min(255, stroke.transparency)) : undefined; const opacity = transparency === undefined ? 1 : 1 - transparency / 255; if (points.length === 1) { return ``; } const path = points .map((point) => `${formatNumber(point.x)},${formatNumber(point.y)}`) .join(' '); return ``; } function appendPlainWarnings(lines: string[], plan: OneNoteExportPlan): void { lines.push('', '', 'WARNINGS AND DIAGNOSTICS', '------------------------'); if ( plan.diagnostics.length === 0 && plan.warnings.length === 0 && plan.unsupportedContent.length === 0 ) { lines.push('None.'); return; } for (const diagnostic of plan.diagnostics) { lines.push(diagnosticLine(diagnostic)); } for (const warning of plan.warnings) { lines.push(`[export warning] ${warning.code}: ${oneLine(warning.message)}`); } for (const item of plan.unsupportedContent) { lines.push( `[unsupported] JCID 0x${item.sourceJcid.toString(16)}: ${oneLine(item.description)}` ); } } function appendMarkdownWarnings( lines: string[], plan: OneNoteExportPlan ): void { lines.push('', '## Warnings and diagnostics', ''); if ( plan.diagnostics.length === 0 && plan.warnings.length === 0 && plan.unsupportedContent.length === 0 ) { lines.push('None.'); return; } for (const diagnostic of plan.diagnostics) { lines.push(`- ${markdownText(diagnosticLine(diagnostic))}`); } for (const warning of plan.warnings) { lines.push( `- Export warning \`${markdownText(warning.code)}\`: ${markdownText(warning.message)}` ); } for (const item of plan.unsupportedContent) { lines.push( `- Unsupported JCID \`0x${item.sourceJcid.toString(16)}\`: ${markdownText(item.description)}` ); } } function htmlWarnings(plan: OneNoteExportPlan): string { const rows = [ ...plan.diagnostics.map( (diagnostic) => `
  • ${escapeHtml(diagnostic.severity)} ${escapeHtml(diagnostic.code)}: ${escapeHtml(diagnostic.message)}
  • ` ), ...plan.warnings.map( (warning) => `
  • export warning ${escapeHtml(warning.code)}: ${escapeHtml(warning.message)}
  • ` ), ...plan.unsupportedContent.map( (item) => `
  • unsupported JCID 0x${item.sourceJcid.toString(16)}: ${escapeHtml(item.description)}
  • ` ), ]; return ``; } function createRenderContext( plan: OneNoteExportPlan, linkResources: boolean ): RenderContext { return { linkResources, resources: new Map( plan.resources.map((resource) => [ exportResourceKey(resource.sectionId, resource.id), resource, ]) ), }; } function resourcePath( context: RenderContext, sectionId: string, resourceId: string | undefined ): string | undefined { if (!context.linkResources || !resourceId) return undefined; return context.resources.get(exportResourceKey(sectionId, resourceId))?.path; } function safeExternalHref(value: string | undefined): string | undefined { if (!value || containsControl(value)) return undefined; try { const url = new URL(value); return url.protocol === 'http:' || url.protocol === 'https:' || url.protocol === 'mailto:' ? value : undefined; } catch { return undefined; } } function containsControl(value: string): boolean { for (let index = 0; index < value.length; index += 1) { const code = value.charCodeAt(index); if (code <= 0x1f || code === 0x7f) return true; } return false; } function markdownText(value: string): string { return value .split('\0') .join('') .replace(/&/gu, '&') .replace(//gu, '>') .replace(/([\\`*_{}[\]()#+.!|~-])/gu, '\\$1'); } function markdownUrl(value: string): string { if (/^[a-z][a-z0-9+.-]*:/iu.test(value)) { return encodeURI(value).replace(/[()]/gu, (character) => encodeURIComponent(character) ); } return pathUrl(value); } function markdownCell(value: string): string { return value.replace(/\s+/gu, ' ').replace(/\|/gu, '\\|').trim(); } function pathUrl(path: string): string { return path.split('/').map(encodeURIComponent).join('/'); } function escapeHtml(value: string): string { return value .split('\0') .join('') .replace(/&/gu, '&') .replace(//gu, '>') .replace(/"/gu, '"') .replace(/'/gu, '''); } function escapeAttribute(value: string): string { return escapeHtml(value).replace(/\r|\n/gu, ' '); } function escapeHtmlWithBreaks(value: string): string { return escapeHtml(value) .split('\u000b') .join('
    ') .replace(/\r\n|\r|\n/gu, '
    '); } function cssColor(color: OneNoteColor | undefined): string | undefined { if (!color || color.kind === 'auto') return undefined; const red = colorByte(color.red); const green = colorByte(color.green); const blue = colorByte(color.blue); if (red === undefined || green === undefined || blue === undefined) { return undefined; } if (color.kind === 'rgba') { const alpha = colorByte(color.alpha); if (alpha === undefined) return undefined; return `rgba(${red},${green},${blue},${formatNumber(alpha / 255)})`; } return `rgb(${red},${green},${blue})`; } function colorByte(value: number | undefined): number | undefined { return value !== undefined && Number.isInteger(value) && value >= 0 && value <= 255 ? value : undefined; } function inkColor(value: number | undefined): string { if (value === undefined || !Number.isSafeInteger(value)) return '#222222'; const red = value & 0xff; const green = (value >>> 8) & 0xff; const blue = (value >>> 16) & 0xff; return `rgb(${red},${green},${blue})`; } function validBounds( value: OneNoteInkBlock['boundingBox'] ): NonNullable | undefined { return value && Number.isFinite(value.x) && Number.isFinite(value.y) && Number.isFinite(value.width) && Number.isFinite(value.height) && value.width >= 0 && value.height >= 0 ? value : undefined; } function boundsFromStrokes( strokes: readonly OneNoteInkBlock['strokes'][number][] ): NonNullable | undefined { let x = Number.POSITIVE_INFINITY; let y = Number.POSITIVE_INFINITY; let x2 = Number.NEGATIVE_INFINITY; let y2 = Number.NEGATIVE_INFINITY; for (const stroke of strokes) { for (const point of stroke.points) { if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) continue; x = Math.min(x, point.x); y = Math.min(y, point.y); x2 = Math.max(x2, point.x); y2 = Math.max(y2, point.y); } } if (!Number.isFinite(x)) return undefined; return { x, y, width: x2 - x, height: y2 - y }; } function inkStats(block: OneNoteInkBlock): { strokes: number; points: number } { let strokes = block.strokes.length; let points = block.strokes.reduce( (total, stroke) => total + stroke.points.length, 0 ); for (const child of block.children) { const childStats = inkStats(child); strokes += childStats.strokes; points += childStats.points; } return { strokes, points }; } function diagnosticLine(diagnostic: ParserDiagnostic): string { return `[${diagnostic.severity}] ${oneLine(diagnostic.code)}: ${oneLine(diagnostic.message)}`; } function oneLine(value: string): string { return value.split('\0').join('').replace(/\s+/gu, ' ').trim(); } function compactPlain(value: string): string { return value.replace(/\s+/gu, ' ').trim(); } function formatNumber(value: number): string { return String(Math.round(value * 1000) / 1000); } const STATIC_EXPORT_CSS = ` :root{color-scheme:light dark;font-family:system-ui,sans-serif;line-height:1.5} body{max-width:72rem;margin:0 auto;padding:2rem} .export-header,.section,.warnings{margin-block:2rem} dl{display:grid;grid-template-columns:max-content 1fr;gap:.25rem 1rem}dt{font-weight:700} .page{border-top:1px solid #8886;padding-block:1.5rem}.page-metadata{font-size:.875rem} .page-content{overflow-wrap:anywhere}.text{white-space:normal}.align-center{text-align:center}.align-right{text-align:right} .outline-children{margin-inline-start:2rem}table{border-collapse:collapse;max-width:100%}td{border:1px solid #888;padding:.4rem;vertical-align:top} figure{margin:1rem 0}img{display:block;max-width:100%;height:auto}.ink{display:block;max-width:100%;height:auto;max-height:32rem} .attachment,.resource-placeholder,.unsupported,.warnings{border:1px solid #8886;padding:.75rem}.unsupported,.warnings{background:#f3b33d22} code{font-family:ui-monospace,monospace} `;