@bendyline/squisq 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/dist/{ContentContainer-DNx460y8.d.ts → ContentContainer-BXUlIf18.d.ts} +6 -2
- package/dist/{Doc-BsMkmzH8.d.ts → Doc-BBVGq1_9.d.ts} +94 -15
- package/dist/{ImageEditDoc-DvoeHMP9.d.ts → ImageEditDoc-FuyGtt6o.d.ts} +1 -1
- package/dist/{chunk-4ZFRZBU2.js → chunk-5TYS5V3G.js} +866 -192
- package/dist/chunk-5TYS5V3G.js.map +1 -0
- package/dist/{chunk-7MQGNR76.js → chunk-A4O7GIWE.js} +2 -27
- package/dist/chunk-A4O7GIWE.js.map +1 -0
- package/dist/{chunk-OH43RPTP.js → chunk-BEQODJWV.js} +20 -3
- package/dist/chunk-BEQODJWV.js.map +1 -0
- package/dist/chunk-ET53IIEP.js +1 -0
- package/dist/chunk-ET53IIEP.js.map +1 -0
- package/dist/{chunk-KZNXZ5SM.js → chunk-GPLTCSXO.js} +18 -7
- package/dist/chunk-GPLTCSXO.js.map +1 -0
- package/dist/{chunk-OKJOQXK4.js → chunk-HWVFJAAH.js} +38 -2
- package/dist/chunk-HWVFJAAH.js.map +1 -0
- package/dist/{chunk-2TMAWB4Y.js → chunk-IMSCRLLV.js} +1 -1
- package/dist/chunk-IMSCRLLV.js.map +1 -0
- package/dist/{chunk-4AKUQPEE.js → chunk-KKNUBQ6Y.js} +1 -1
- package/dist/chunk-KKNUBQ6Y.js.map +1 -0
- package/dist/{chunk-TVSGLUID.js → chunk-LDQ2HJIX.js} +3 -3
- package/dist/{chunk-RUDYOTA6.js → chunk-LTN6I7QW.js} +56 -1
- package/dist/chunk-LTN6I7QW.js.map +1 -0
- package/dist/{chunk-2OIBZYKP.js → chunk-P3K7NLHF.js} +2 -2
- package/dist/{chunk-K32VJONL.js → chunk-T5UK6YOB.js} +41 -2
- package/dist/chunk-T5UK6YOB.js.map +1 -0
- package/dist/{chunk-FR2RBTKO.js → chunk-TQWLI6S2.js} +93 -27
- package/dist/chunk-TQWLI6S2.js.map +1 -0
- package/dist/{chunk-LRCV7N7F.js → chunk-UY7KGQ5R.js} +2 -2
- package/dist/{chunk-3E5F2XMR.js → chunk-VWUFZ6ZG.js} +29 -2
- package/dist/chunk-VWUFZ6ZG.js.map +1 -0
- package/dist/doc/index.d.ts +4 -4
- package/dist/doc/index.js +24 -9
- package/dist/generate/index.d.ts +2 -80
- package/dist/generate/index.js +2 -7
- package/dist/imageEdit/index.d.ts +4 -4
- package/dist/imageEdit/index.js +2 -2
- package/dist/index.d.ts +8 -8
- package/dist/index.js +41 -26
- package/dist/jsonForm/index.d.ts +42 -1
- package/dist/jsonForm/index.js +9 -1
- package/dist/markdown/index.d.ts +18 -2
- package/dist/markdown/index.js +5 -3
- package/dist/recommend/index.js +2 -2
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +3 -5
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.js +1 -1
- package/dist/story/index.d.ts +151 -13
- package/dist/story/index.js +24 -9
- package/dist/{themeLibrary-DQQKuRMx.d.ts → themeLibrary-CehcJhzz.d.ts} +1 -1
- package/dist/timing/index.js +3 -5
- package/dist/transform/index.d.ts +2 -2
- package/dist/transform/index.js +3 -3
- package/dist/versions/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/contentExtractor.test.ts +60 -0
- package/src/__tests__/coverBlock.test.ts +79 -3
- package/src/__tests__/customTemplateSample.test.ts +63 -2
- package/src/__tests__/customTemplatesFrontmatter.test.ts +31 -0
- package/src/__tests__/customThemesFrontmatter.test.ts +27 -0
- package/src/__tests__/inputDescriptors.test.ts +162 -0
- package/src/__tests__/jsonForm.tokens.test.ts +84 -0
- package/src/__tests__/markdown.test.ts +64 -0
- package/src/__tests__/markdownSanitize.test.ts +15 -0
- package/src/__tests__/markdownToDoc.test.ts +173 -2
- package/src/__tests__/mediaAnnotations.test.ts +27 -0
- package/src/__tests__/shapeGeometry.test.ts +37 -0
- package/src/__tests__/structuredData.test.ts +73 -3
- package/src/__tests__/templateMetadata.test.ts +11 -1
- package/src/__tests__/templates.test.ts +112 -0
- package/src/__tests__/validateDoc.test.ts +160 -0
- package/src/doc/annotationBlocks.ts +117 -0
- package/src/doc/customTemplatesFrontmatter.ts +7 -1
- package/src/doc/customThemesFrontmatter.ts +6 -1
- package/src/doc/docToMarkdown.ts +78 -29
- package/src/doc/getLayers.ts +11 -2
- package/src/doc/index.ts +12 -0
- package/src/doc/markdownToDoc.ts +80 -3
- package/src/doc/mediaAnnotations.ts +15 -33
- package/src/doc/standaloneAnnotation.ts +58 -0
- package/src/doc/structuredData.ts +92 -21
- package/src/doc/templates/__tests__/customTemplate.test.ts +21 -2
- package/src/doc/templates/comparisonBar.ts +8 -1
- package/src/doc/templates/coverBlock.ts +78 -15
- package/src/doc/templates/customTemplate.ts +10 -4
- package/src/doc/templates/diagramBlock.ts +2 -2
- package/src/doc/templates/drawingBlock.ts +2 -2
- package/src/doc/templates/factCard.ts +4 -2
- package/src/doc/templates/imageWithCaption.ts +10 -8
- package/src/doc/templates/index.ts +12 -2
- package/src/doc/templates/inputDescriptors.ts +432 -0
- package/src/doc/templates/photoGrid.ts +16 -39
- package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +267 -1
- package/src/doc/templates/tokens/resolveTokens.ts +0 -0
- package/src/doc/templates/videoWithCaption.ts +15 -14
- package/src/doc/utils/nearest.ts +59 -0
- package/src/doc/utils/shapeGeometry.ts +90 -2
- package/src/doc/validate.ts +153 -54
- package/src/generate/contentExtractor.ts +148 -35
- package/src/generate/index.ts +0 -3
- package/src/jsonForm/index.ts +2 -0
- package/src/jsonForm/tokens.ts +78 -0
- package/src/markdown/annotationCoercion.ts +1 -1
- package/src/markdown/attrTokens.ts +19 -0
- package/src/markdown/convert.ts +11 -1
- package/src/markdown/index.ts +2 -1
- package/src/markdown/sanitize.ts +22 -2
- package/src/markdown/stringify.ts +12 -3
- package/src/markdown/utils.ts +62 -1
- package/src/schemas/CustomTemplates.ts +56 -4
- package/src/schemas/Doc.ts +39 -41
- package/src/schemas/themeCompile.ts +1 -1
- package/src/storage/ContentContainer.ts +6 -2
- package/src/transform/types.ts +1 -1
- package/dist/chunk-2TMAWB4Y.js.map +0 -1
- package/dist/chunk-3E5F2XMR.js.map +0 -1
- package/dist/chunk-4AKUQPEE.js.map +0 -1
- package/dist/chunk-4X3JQXNM.js +0 -35
- package/dist/chunk-4X3JQXNM.js.map +0 -1
- package/dist/chunk-4ZFRZBU2.js.map +0 -1
- package/dist/chunk-7MQGNR76.js.map +0 -1
- package/dist/chunk-FBKP5CAO.js +0 -145
- package/dist/chunk-FBKP5CAO.js.map +0 -1
- package/dist/chunk-FR2RBTKO.js.map +0 -1
- package/dist/chunk-K32VJONL.js.map +0 -1
- package/dist/chunk-KZNXZ5SM.js.map +0 -1
- package/dist/chunk-OH43RPTP.js.map +0 -1
- package/dist/chunk-OKJOQXK4.js.map +0 -1
- package/dist/chunk-RUDYOTA6.js.map +0 -1
- package/src/__tests__/slideshowGenerator.test.ts +0 -128
- package/src/generate/slideshowGenerator.ts +0 -280
- /package/dist/{chunk-TVSGLUID.js.map → chunk-LDQ2HJIX.js.map} +0 -0
- /package/dist/{chunk-2OIBZYKP.js.map → chunk-P3K7NLHF.js.map} +0 -0
- /package/dist/{chunk-LRCV7N7F.js.map → chunk-UY7KGQ5R.js.map} +0 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
oklchSetChroma,
|
|
8
8
|
pickContrastingText,
|
|
9
9
|
relativeLuminance
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-KKNUBQ6Y.js";
|
|
11
11
|
|
|
12
12
|
// src/schemas/Doc.ts
|
|
13
13
|
function calculateDuration(audio) {
|
|
@@ -39,30 +39,6 @@ function getCaptionAtTime(captions, time) {
|
|
|
39
39
|
}
|
|
40
40
|
return null;
|
|
41
41
|
}
|
|
42
|
-
function validateDoc(script) {
|
|
43
|
-
const errors = [];
|
|
44
|
-
if (!script.articleId) {
|
|
45
|
-
errors.push("Missing articleId");
|
|
46
|
-
}
|
|
47
|
-
if (!script.blocks || script.blocks.length === 0) {
|
|
48
|
-
errors.push("No blocks defined");
|
|
49
|
-
}
|
|
50
|
-
if (!script.audio || !script.audio.segments || script.audio.segments.length === 0) {
|
|
51
|
-
errors.push("No audio segments defined");
|
|
52
|
-
}
|
|
53
|
-
const totalDuration = script.duration;
|
|
54
|
-
let covered = 0;
|
|
55
|
-
for (const block of script.blocks) {
|
|
56
|
-
if (block.startTime > covered + 0.1) {
|
|
57
|
-
errors.push(`Gap in blocks: ${covered}s to ${block.startTime}s`);
|
|
58
|
-
}
|
|
59
|
-
covered = Math.max(covered, block.startTime + block.duration);
|
|
60
|
-
}
|
|
61
|
-
if (covered < totalDuration - 0.1) {
|
|
62
|
-
errors.push(`Blocks end at ${covered}s but audio is ${totalDuration}s`);
|
|
63
|
-
}
|
|
64
|
-
return errors;
|
|
65
|
-
}
|
|
66
42
|
|
|
67
43
|
// src/schemas/Media.ts
|
|
68
44
|
function flatten(blocks, out = []) {
|
|
@@ -257,7 +233,6 @@ export {
|
|
|
257
233
|
getSegmentAtTime,
|
|
258
234
|
getBlockAtTime,
|
|
259
235
|
getCaptionAtTime,
|
|
260
|
-
validateDoc,
|
|
261
236
|
resolveMediaSchedule,
|
|
262
237
|
getDocPlaybackDuration,
|
|
263
238
|
STARTER_THEME,
|
|
@@ -266,4 +241,4 @@ export {
|
|
|
266
241
|
parseTheme,
|
|
267
242
|
serializeTheme
|
|
268
243
|
};
|
|
269
|
-
//# sourceMappingURL=chunk-
|
|
244
|
+
//# sourceMappingURL=chunk-A4O7GIWE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schemas/Doc.ts","../src/schemas/Media.ts","../src/schemas/themeCompile.ts"],"sourcesContent":["/**\n * Doc Schema\n *\n * Defines the JSON format for AI-scriptable visual docs that accompany\n * audio narration. A Doc describes a sequence of animated blocks\n * with text overlays, images, and transitions synchronized to audio.\n *\n * The format is designed to be:\n * 1. AI-generatable - Claude can create scripts from article content\n * 2. Browser-renderable - SVG + CSS animations (ES2015 compatible)\n * 3. Video-exportable - Frame-by-frame capture via Playwright\n *\n * Timing: Blocks are timed to match narration audio segments. Each article\n * has 3-6 MP3 files (intro + sections), and blocks are grouped by segment.\n */\n\nimport type { Transition } from './Transitions.js';\nexport type { Transition, TransitionType, TransitionDirection } from './Transitions.js';\n\n// ============================================\n// Core Types\n// ============================================\n\n/**\n * A directed connection from one block to another.\n *\n * Populated from a heading's Pandoc-style `{connectsTo=…}` attribute\n * (e.g. `## Step 1 {#step1 connectsTo=foo:flow,bar}`). Each item is\n * `target` or `target:type`. Used by diagram-style layouts that draw\n * edges between blocks.\n */\nexport interface BlockConnection {\n /** Target block id (matches another block's `id`). */\n target: string;\n /** Optional connection type/label (e.g., \"flow\", \"requires\"). */\n type?: string;\n}\n\n/**\n * Configuration for the Start/resting block shown before playback begins.\n */\nexport interface StartBlockConfig {\n /** Hero image path (relative to article media). When omitted, a theme-driven background is used. */\n heroSrc?: string;\n /** Alt text for the hero image */\n heroAlt?: string;\n /** Title to display over the hero */\n title: string;\n /** Optional subtitle */\n subtitle?: string;\n /** Ambient motion for the hero image */\n ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';\n /** Photo credit / artist name for the hero image */\n heroCredit?: string;\n /** License identifier for the hero image */\n heroLicense?: string;\n}\n\n/**\n * A structural problem detected while converting or validating a document.\n *\n * Diagnostics ride on `Doc.diagnostics` so any consumer — the editor, the\n * CLI `validate` command, or an agent re-parsing its own output — gets the\n * same feedback without watching the console. Conversion never throws for\n * content problems; it degrades gracefully and records a diagnostic.\n */\nexport interface DocDiagnostic {\n /** `error` = the author's intent could not be honored (e.g. unparseable\n * data fence); `warning` = something looks wrong but rendering proceeds\n * with a fallback (e.g. unknown template name); `info` = nothing is\n * broken, but the author may want to know (e.g. a redundant annotation). */\n severity: 'error' | 'warning' | 'info';\n /** Stable machine-readable code (e.g. `unknown-template`, `duplicate-id`,\n * `unresolved-connection`, `data-fence-parse`, `missing-asset`). */\n code: string;\n /** Human-readable description, including a suggestion when one exists. */\n message: string;\n /** Id of the block the problem belongs to, when attributable. */\n blockId?: string;\n /** 1-based line number in the markdown source, when known. */\n line?: number;\n}\n\n/**\n * A complete visual doc for an article.\n */\nexport interface Doc {\n /** Article ID this doc belongs to */\n articleId: string;\n\n /** Total duration in seconds (sum of all audio segments) */\n duration: number;\n\n /** Ordered list of blocks */\n blocks: Block[];\n\n /** Audio track configuration */\n audio: AudioTrack;\n\n /** Caption track for closed captions (generated by postprocess-doc) */\n captions?: CaptionTrack;\n\n /**\n * Start/resting block shown before playback begins.\n * Displays hero image with title - acts as a \"poster\" for the doc.\n * The player shows this until the user starts playback.\n */\n startBlock?: StartBlockConfig;\n\n /**\n * Persistent layers rendered on every block (behind and/or on top of content).\n * Used for consistent branding overlays, background gradients, etc.\n */\n persistentLayers?: import('./BlockTemplates.js').PersistentLayerConfig;\n\n /**\n * Optional theme identifier. Resolved at render time via `resolveTheme()`.\n * When omitted, the renderer uses the default theme ('documentary').\n */\n themeId?: string;\n\n /** Optional metadata */\n meta?: {\n generatedAt?: string;\n generatedBy?: string;\n version?: number;\n };\n\n /**\n * YAML frontmatter from the source markdown.\n * Carries rendering hints like `document-render-as` and custom metadata.\n */\n frontmatter?: Record<string, unknown>;\n\n /**\n * User-defined block templates inlined into this doc.\n *\n * Populated from the markdown frontmatter key\n * `squisq-custom-templates`. The template-expansion pipeline\n * (`expandDocBlocks` in `doc/templates/index.ts`) merges these into\n * the registry before walking blocks, so a heading annotated\n * `{[myhero]}` resolves against a doc-defined template named `myhero`.\n *\n * Library templates (stored in localStorage on the editor side) are\n * NOT auto-loaded here — applying a library template to a block\n * copies its definition into this list so the doc remains\n * self-sufficient for SSR and export.\n */\n customTemplates?: import('./CustomTemplates.js').CustomTemplateDefinition[];\n\n /**\n * User-defined themes inlined into this doc.\n *\n * The theme analog of {@link Doc.customTemplates}. Populated from the\n * markdown frontmatter key `squisq-custom-themes`. Exactly one is active\n * at a time — selected by id via `squisq-theme` / {@link Doc.themeId},\n * the doc-level counterpart of a block's `{[name]}` template annotation —\n * but the payload is a list so a doc can carry a small catalog and stay\n * self-sufficient for SSR / export. `resolveThemeForDoc(doc, id)` resolves\n * against this list first (pure, doc-scoped) before built-ins, mirroring\n * how `buildRegistry` resolves custom templates.\n */\n customThemes?: import('./Theme.js').Theme[];\n\n /**\n * Structural problems found while building this doc (unknown templates,\n * unparseable data fences, duplicate ids, unresolved connections, …).\n * Populated by `markdownToDoc()`; extended by `validateMarkdownDoc()`.\n * Absent when the document is clean.\n */\n diagnostics?: DocDiagnostic[];\n\n /**\n * Document-spanning timed media (e.g. a full-length narration MP3/MP4),\n * authored as a media annotation in the preamble (before the first\n * heading) with `anchor=document`. Each clip is timed from the document\n * start and may play across every block. See `resolveMediaSchedule`.\n */\n documentMedia?: import('./Media.js').MediaClip[];\n}\n\n/**\n * A single block in the doc.\n *\n * Blocks can be flat (audio-synced timeline) or nested (markdown-driven hierarchy).\n * When derived from markdown, a block corresponds to a heading section:\n * - `sourceHeading` references the original MarkdownHeading node\n * - `contents` holds the body markdown between this heading and the next\n * - `children` holds sub-heading blocks (e.g., H2s under an H1)\n *\n * Four adjacent fields carry template information — do not confuse them:\n * - `template` — the *resolved* template id this block renders with.\n * - `templateOverrides` — raw `{[…]}` inline params (always strings).\n * - `templateData` — structured inputs from a data fence / GFM table\n * (parsed types, not strings).\n * - `sourceAnnotation.template` — the raw *requested* template name for a\n * standalone-annotation block (before alias resolution).\n * Merge order at render time: template defaults → `templateData` →\n * `templateOverrides`.\n */\nexport interface Block {\n /** Unique identifier for this block.\n * Defaults to a slug derived from the heading text. May be overridden by\n * a Pandoc-style `{#custom-id}` attribute on the heading. */\n id: string;\n\n /** When this block appears (seconds from start).\n * Overridden by a heading attribute `startTime=…` when present. */\n startTime: number;\n\n /** How long this block is visible (seconds).\n * Overridden by a heading attribute `duration=…` when present. */\n duration: number;\n\n /** Which audio segment this block belongs to (0-indexed) */\n audioSegment: number;\n\n /**\n * Pre-computed visual layers, rendered back-to-front.\n *\n * Optional — template-derived blocks typically omit this and compute\n * layers on demand via `getLayers()` from `@bendyline/squisq/doc`.\n * Raw blocks (e.g., hand-crafted by AI) may provide layers directly.\n */\n layers?: Layer[];\n\n /** Entry transition from previous block */\n transition?: Transition;\n\n /** Resolved template id this block renders with (see the quartet note above). */\n template?: string;\n\n /**\n * True when `template` was chosen by content-aware auto-picking\n * (`markdownToDoc`'s `autoTemplates`) rather than authored. Ephemeral:\n * `docToMarkdown` does not materialize auto-picked templates as heading\n * annotations, keeping the markdown round-trip lossless.\n */\n autoTemplate?: boolean;\n\n /**\n * True when this block was produced from a *standalone* `{[templateName …]}`\n * annotation paragraph in another block's body (rather than from a heading).\n * Such blocks have no `sourceHeading`; `docToMarkdown` re-emits their\n * annotation as a synthesized paragraph before their `contents` so the\n * markdown round-trips. See `annotationBlocks.ts`.\n *\n * Presence-marker: only ever `true` (never `false`). Test with\n * `block.standaloneAnnotation` presence, not as a boolean toggle.\n */\n standaloneAnnotation?: true;\n\n /**\n * The annotation a standalone block was built from — the raw (un-resolved)\n * template name and its params, in author order. Round-tripped by\n * `docToMarkdown` (via `serializeAnnotation`) and read by validation as the\n * raw requested template name (symmetric with\n * `sourceHeading.templateAnnotation`). Present only when\n * `standaloneAnnotation` is true.\n */\n sourceAnnotation?: { template?: string; params?: Record<string, string> };\n\n /**\n * Display title for template rendering.\n * Extracted from the sourceHeading when the block is created by markdownToDoc().\n * Templates like sectionHeader and titleBlock read this for their title layer.\n */\n title?: string;\n\n // ── Markdown-driven hierarchy (optional) ──\n\n /**\n * Nested sub-blocks (heading hierarchy).\n * When a doc is derived from markdown, sub-headings within this\n * heading's section become child blocks. For example, H2 blocks\n * under an H1 block appear here.\n */\n children?: Block[];\n\n /**\n * Markdown body content for this block's section.\n * Contains the block-level markdown nodes (paragraphs, lists, code,\n * blockquotes, etc.) that appear between this heading and the next\n * heading or sub-heading. Empty for pure structural blocks.\n */\n contents?: import('../markdown/types.js').MarkdownBlockNode[];\n\n /**\n * Reference to the MarkdownHeading node that created this block.\n * Present when the block was derived from markdown via markdownToDoc().\n * Enables round-tripping back to markdown and provides heading depth.\n * Absent for the preamble block (content before the first heading).\n */\n sourceHeading?: import('../markdown/types.js').MarkdownHeading;\n\n /**\n * Template overrides extracted from a heading's `{[templateName key=value]}`\n * annotation. Only carries template-specific params (e.g. `colorScheme=blue`).\n * Block-level metadata lives in `x` / `y` / `connectsTo` / `metadata` and is\n * sourced from the Pandoc-style `{#id .class key=value}` attribute block instead.\n */\n templateOverrides?: Record<string, string>;\n\n /**\n * Structured template inputs sourced from the block's body content:\n * a ```json data / ```yaml data fence under the heading, or — for the\n * `dataTable` template — the first GFM table in the section. Unlike\n * `templateOverrides` (always strings), values here keep their parsed\n * types (arrays, numbers, nested objects). Merge order at render time:\n * template defaults → `templateData` → `templateOverrides`.\n */\n templateData?: Record<string, unknown>;\n\n // ── Block-level metadata from Pandoc-style `{#id .class key=value}` ──\n\n /**\n * X coordinate for diagram-style positioning (in author-defined units).\n * Sourced from a heading attribute `x=…`.\n */\n x?: number;\n\n /**\n * Y coordinate for diagram-style positioning (in author-defined units).\n * Sourced from a heading attribute `y=…`.\n */\n y?: number;\n\n /**\n * Outgoing connections to other blocks, by id. Sourced from a heading\n * attribute `connectsTo=target1,target2:type,target3`.\n */\n connectsTo?: BlockConnection[];\n\n /**\n * CSS-style class tokens from a heading's Pandoc attribute (e.g. `{.important .v2}`).\n * Parsed and stored; downstream consumers may use them for styling or filtering.\n */\n classes?: string[];\n\n /**\n * Free-form metadata for heading attribute keys outside the typed registry\n * (anything that isn't `x` / `y` / `connectsTo` / `startTime` / `duration`).\n * Values are raw strings exactly as authored.\n */\n metadata?: Record<string, string>;\n\n /**\n * Timed media clips attached to this block, authored as body-level\n * `{[audio …]}` / `{[video …]}` annotations. Each clip is timed relative\n * to this block's `startTime` (via `startAt`) and optionally spills past\n * the block's end. See `MediaClip` / `resolveMediaSchedule`.\n */\n media?: import('./Media.js').MediaClip[];\n}\n\n// ============================================\n// Layer Types\n// ============================================\n\n/**\n * A visual element within a block.\n * Layers are composited back-to-front (first layer is background).\n */\nexport type Layer =\n | ImageLayer\n | TextLayer\n | ShapeLayer\n | PathLayer\n | MapLayer\n | VideoLayer\n | TableLayer;\n\ninterface BaseLayer {\n /** Unique identifier for this layer */\n id: string;\n\n /** Position and size */\n position: Position;\n\n /** Animation to apply */\n animation?: Animation;\n}\n\n/**\n * Photographic treatment applied to an image layer — a theme-level \"grade\"\n * so the same photo looks native in every theme. Rendered as CSS filter\n * functions, which behave identically in the browser player and headless\n * frame capture (video/still export).\n */\nexport interface ImageTreatment {\n /**\n * mono: desaturate toward archival black & white.\n * duotone: single-hue tint (hue taken from `color`).\n * warm / cool: gentle temperature grade.\n */\n type: 'none' | 'mono' | 'duotone' | 'warm' | 'cool';\n /** Blend strength 0..1. Default 0.6. */\n strength?: number;\n /** Duotone tint color (themes default this to their primary). */\n color?: string;\n}\n\n/**\n * Image layer - displays an image with optional Ken Burns effect.\n */\nexport interface ImageLayer extends BaseLayer {\n type: 'image';\n content: {\n /** Path to image file (relative to article media dir) */\n src: string;\n /** Alt text for accessibility */\n alt: string;\n /** How to fit image in bounds */\n fit?: 'cover' | 'contain' | 'fill';\n /** Photo credit / artist name */\n credit?: string;\n /** License identifier (e.g., 'CC BY-SA 4.0') */\n license?: string;\n /** Theme-derived photographic grade (see ImageTreatment). */\n treatment?: ImageTreatment;\n /** Gaussian blur radius in px (background/atmosphere imagery). */\n blur?: number;\n };\n}\n\n/**\n * Text layer - displays text with styling.\n */\nexport interface TextLayer extends BaseLayer {\n type: 'text';\n content: {\n /**\n * Plain text (supports \\n for line breaks). Source of truth for plain\n * consumers — PDF/markdown export, search, accessibility — and the SVG\n * `<text>` fallback. When `html` is set, this is its plain-text\n * projection and must be kept in sync.\n */\n text: string;\n /**\n * Optional sanitized **inline** HTML for rich formatting (bold/italic/\n * links, and — for layout textboxes — headings/lists). When present the\n * renderer draws it via `<foreignObject>` instead of SVG `<text>`. Treated\n * as untrusted and re-sanitized at render time. See `RichTextLayer` in\n * `@bendyline/squisq-react`.\n */\n html?: string;\n /** Text styling */\n style: TextStyle;\n };\n}\n\n/**\n * A two-stop linear gradient fill, shared by shape/path fills and text\n * backgrounds. When present it overrides the solid `fill` color.\n *\n * `angle` is in degrees: 0 = top→bottom, 90 = left→right, increasing\n * clockwise. Defaults to 0. Rendered as an SVG `<linearGradient>` in\n * objectBoundingBox units so it scales with the layer's box.\n */\nexport interface LinearGradient {\n from: string;\n to: string;\n angle?: number;\n}\n\n/** Border line style, mapped to an SVG stroke-dasharray by the renderer. */\nexport type BorderStyle = 'solid' | 'dashed' | 'dotted';\n\n/**\n * Shape layer - simple geometric shapes for visual accents.\n */\n/**\n * Repeating SVG pattern fill for a shape (dots, grid, diagonal lines).\n * Rendered as a native `<pattern>` def — fully vector and export-safe.\n */\nexport interface ShapePattern {\n kind: 'dots' | 'grid' | 'diagonal';\n /** Pattern ink color. */\n color: string;\n /** Tile size in px (default 24). */\n size?: number;\n /** Pattern opacity 0–1 (default 1; tint via a translucent color or this). */\n opacity?: number;\n}\n\n/**\n * Procedural filter applied to a shape. `noise` renders static film grain\n * via SVG feTurbulence — deliberately not animated so frame capture and\n * the live player agree.\n */\nexport interface ShapeFilter {\n type: 'noise';\n /** feTurbulence base frequency (default 0.8 — fine grain). */\n baseFrequency?: number;\n /** Grain opacity 0–1 (default 0.05). */\n opacity?: number;\n}\n\nexport interface ShapeLayer extends BaseLayer {\n type: 'shape';\n content: {\n /** Shape type */\n shape: 'rect' | 'circle' | 'line';\n /** Fill color (CSS color or 'none') */\n fill?: string;\n /** Fill opacity 0–1 (applies to solid and gradient fills). */\n fillOpacity?: number;\n /** Gradient fill; overrides `fill` when set. */\n gradient?: LinearGradient;\n /** Repeating pattern fill; overrides `fill`/`gradient` when set. */\n pattern?: ShapePattern;\n /** Procedural filter (film grain). */\n filter?: ShapeFilter;\n /** Stroke color */\n stroke?: string;\n /** Stroke width in pixels */\n strokeWidth?: number;\n /** Border line style (solid/dashed/dotted). Default solid. */\n borderStyle?: BorderStyle;\n /** Corner radius for rect */\n borderRadius?: number;\n };\n}\n\n/**\n * End-of-line marker style for a path/connector endpoint.\n * `arrow` is a filled triangle (the classic arrowhead); `open` is a stroked\n * V; `diamond`/`circle`/`square` are filled glyphs; `none` draws nothing.\n */\nexport type MarkerStyle = 'none' | 'arrow' | 'open' | 'diamond' | 'circle' | 'square';\n\n/**\n * Path layer - renders an SVG `<path>` for arbitrary curves, connectors,\n * arrows, or filled regions. Used by the diagram template for edges\n * between nodes, the drawing template for non-rect/circle/line shapes, and\n * available to any template that needs a custom geometry.\n *\n * The `position` field carries the layer's bounding box (so animations\n * and clipping work the same as for other layers), but the actual\n * geometry is encoded in the `d` attribute using absolute SVG path\n * coordinates relative to the block viewport.\n */\nexport interface PathLayer extends BaseLayer {\n type: 'path';\n content: {\n /** SVG path `d` attribute (e.g. \"M 10 10 L 90 90\"). Absolute viewBox coords. */\n d: string;\n /**\n * Named shape kind (e.g. `'diamond'`, `'star'`, `'arrow-right'`) for\n * path layers that represent one of Squisq's standard shapes. When\n * set, the renderer re-derives `d` from the layer's `position` box\n * (resolved against the viewport) instead of using the stored `d` —\n * so the shape moves, resizes, and adapts to any aspect ratio like\n * the native rect/circle/line layers do. Plain paths (connectors,\n * freehand) leave this unset and keep their absolute `d`.\n */\n shapeKind?: string;\n /** Stroke color (default: theme text color). */\n stroke?: string;\n /** Stroke width in pixels (default: 2). */\n strokeWidth?: number;\n /** Optional fill color (default: 'none' — pure connectors). */\n fill?: string;\n /** Fill opacity 0–1 (applies to solid and gradient fills). */\n fillOpacity?: number;\n /** Gradient fill; overrides `fill` when set. */\n gradient?: LinearGradient;\n /**\n * Border line style (solid/dashed/dotted). A convenience over\n * `dasharray` for named shapes; when set the renderer derives the\n * dash pattern from it (scaled by stroke width).\n */\n borderStyle?: BorderStyle;\n /** Optional stroke dash pattern (SVG `stroke-dasharray` syntax). */\n dasharray?: string;\n /**\n * Legacy arrowhead flag. Prefer `startMarker`/`endMarker`. When those are\n * unset, `'end'`/`'start'`/`'both'` render a filled-triangle arrowhead.\n */\n arrow?: 'none' | 'end' | 'start' | 'both';\n /** Marker at the path start (overrides `arrow`). Default: derived from `arrow`. */\n startMarker?: MarkerStyle;\n /** Marker at the path end (overrides `arrow`). Default: derived from `arrow`. */\n endMarker?: MarkerStyle;\n };\n}\n\n/**\n * Map layer - displays a geographic map with optional markers.\n *\n * Maps are rendered as composite tile images from free tile providers.\n * Supported styles use open-source/free tiles requiring attribution.\n */\nexport interface MapLayer extends BaseLayer {\n type: 'map';\n content: {\n /** Center coordinates for the map */\n center: {\n lat: number;\n lng: number;\n };\n /** Zoom level (0-18, typically 8-14 for doc blocks) */\n zoom: number;\n /** Map tile style */\n style: MapTileStyle;\n /** Optional markers to display on the map */\n markers?: MapMarker[];\n /** Pre-rendered static image path (for video export reliability) */\n staticSrc?: string;\n /** Show attribution text (default: true) */\n showAttribution?: boolean;\n };\n}\n\n/**\n * Video layer - displays a short video clip, always muted (narration provides audio).\n *\n * Rendered via an HTML5 <video> element inside <foreignObject>, similar to how\n * ImageLayer uses <img> for Ken Burns effects. clipStart/clipEnd define the window\n * within the source video to play. During Playwright frame capture (seekTo mode),\n * the video is seeked programmatically rather than playing in real time.\n */\nexport interface VideoLayer extends BaseLayer {\n type: 'video';\n content: {\n /** Path to video file (relative to article media dir) */\n src: string;\n /** Path to poster frame (shown before video loads / during seek) */\n posterSrc?: string;\n /** Alt text for accessibility */\n alt: string;\n /** How to fit video in bounds */\n fit?: 'cover' | 'contain' | 'fill';\n /** Start time within the source video (seconds) */\n clipStart: number;\n /** End time within the source video (seconds) */\n clipEnd: number;\n /** Total source video duration (for validation) */\n sourceDuration?: number;\n /**\n * Seconds into the owning block before this video begins playing\n * (the block-relative `startAt`). Default 0 — plays from block start.\n */\n startAt?: number;\n /**\n * When true, the video keeps playing past the block's end (the\n * playback scheduler re-homes it to the player level). Default false.\n */\n spillover?: boolean;\n /** Video credit / artist name */\n credit?: string;\n /** License identifier (e.g., 'CC BY-SA 4.0') */\n license?: string;\n };\n}\n\n/**\n * Table layer - renders a data table with themed styling.\n * Uses foreignObject inside SVG for HTML-based table layout.\n */\nexport interface TableLayer extends BaseLayer {\n type: 'table';\n content: {\n /** Header cell values */\n headers: string[];\n /** Data rows (array of cell value arrays) */\n rows: string[][];\n /** Per-column alignment */\n align?: (('left' | 'right' | 'center') | null)[];\n /** Visual styling */\n style: TableLayerStyle;\n };\n}\n\n/**\n * Styling options for a TableLayer.\n */\nexport interface TableLayerStyle {\n /** Header row background color */\n headerBackground: string;\n /** Header row text color */\n headerColor: string;\n /** Body cell background color */\n cellBackground: string;\n /** Body cell text color */\n cellColor: string;\n /** Border/divider color */\n borderColor: string;\n /** Font size in pixels */\n fontSize: number;\n /** Font family */\n fontFamily?: string;\n /** Header font family (falls back to fontFamily) */\n headerFontFamily?: string;\n /** Corner radius for the table container */\n borderRadius?: number;\n}\n\n/**\n * Available map tile styles from free/open-source providers.\n */\nexport type MapTileStyle =\n | 'terrain' // OpenTopoMap - topographic/terrain (default)\n | 'satellite' // ESRI World Imagery\n | 'road' // OpenStreetMap standard\n | 'toner' // Stadia Toner (high contrast B&W)\n | 'watercolor'; // Stadia Watercolor (artistic)\n\n/**\n * A marker to display on the map.\n */\nexport interface MapMarker {\n /** Marker latitude */\n lat: number;\n /** Marker longitude */\n lng: number;\n /** Optional label text */\n label?: string;\n /** Marker color (CSS color, default: #ef4444) */\n color?: string;\n /** Marker icon type */\n icon?: 'pin' | 'circle' | 'star';\n}\n\n// ============================================\n// Position & Styling\n// ============================================\n\n/**\n * Position and dimensions for a layer.\n * Values can be pixels (number) or percentages (string like \"50%\").\n */\nexport interface Position {\n /** X position (pixels or percentage) */\n x: number | string;\n /** Y position (pixels or percentage) */\n y: number | string;\n /** Width (pixels or percentage) */\n width?: number | string;\n /** Height (pixels or percentage) */\n height?: number | string;\n /** Anchor point for positioning */\n anchor?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n}\n\n/**\n * Text styling options.\n */\nexport interface TextStyle {\n /** Font size in pixels */\n fontSize: number;\n /** Font family (defaults to system sans-serif) */\n fontFamily?: string;\n /** Font weight */\n fontWeight?: 'normal' | 'bold';\n /** Font style. Also the inherited baseline for rich `content.html`. */\n fontStyle?: 'normal' | 'italic';\n /** Text color (CSS color) */\n color: string;\n /** Horizontal text alignment within the layer's position box. */\n textAlign?: 'left' | 'center' | 'right';\n /**\n * Vertical alignment of the text within the layer's position box.\n * Requires a `position.height` to have an effect. When omitted, the\n * vertical baseline is derived from `position.anchor` (legacy behavior).\n */\n verticalAlign?: 'top' | 'middle' | 'bottom';\n /** Line height multiplier */\n lineHeight?: number;\n /** Add drop shadow for readability over images */\n shadow?: boolean;\n /** Background color for text box */\n background?: string;\n /** Background opacity 0–1 (applies to solid and gradient backgrounds). */\n backgroundOpacity?: number;\n /** Gradient background; overrides `background` when set. */\n backgroundGradient?: LinearGradient;\n /** Border (stroke) color for the text box. */\n borderColor?: string;\n /** Border width in pixels. A border renders when > 0 and a color is set. */\n borderWidth?: number;\n /** Border line style (solid/dashed/dotted). Default solid. */\n borderStyle?: BorderStyle;\n /** Padding around text (pixels) */\n padding?: number;\n /** Maximum number of lines before truncation (adds \"...\" to last line) */\n maxLines?: number;\n}\n\n// ============================================\n// Animation & Transitions\n// ============================================\n\n/**\n * Animation to apply to a layer.\n */\nexport interface Animation {\n /** Animation type */\n type: AnimationType;\n /** Duration in seconds (defaults to layer's block duration) */\n duration?: number;\n /** Delay before animation starts (seconds) */\n delay?: number;\n /** CSS easing function */\n easing?: string;\n /** For Ken Burns: zoom direction */\n direction?: 'in' | 'out';\n /** For pan animations: pan direction */\n panDirection?: 'left' | 'right' | 'up' | 'down';\n}\n\n/**\n * Available animation types.\n */\nexport type AnimationType =\n | 'none'\n | 'fadeIn'\n | 'fadeOut'\n | 'slowZoom' // Slow zoom with optional pan\n | 'zoomIn'\n | 'zoomOut'\n | 'panLeft'\n | 'panRight'\n | 'typewriter'; // Text appears letter by letter\n\n// ============================================\n// Audio Configuration\n// ============================================\n\n/**\n * Audio track configuration.\n * Articles have multiple MP3 segments (intro + sections).\n */\nexport interface AudioTrack {\n /** Audio segments in playback order */\n segments: AudioSegment[];\n}\n\n// ============================================\n// Captions\n// ============================================\n\n/**\n * A single word with precise timing, used for word-level highlighting\n * in social-style captions. Populated from TTS timing data when available.\n */\nexport interface CaptionWord {\n /** The word text */\n text: string;\n /** Start time in seconds (relative to doc start) */\n startTime: number;\n /** End time in seconds */\n endTime: number;\n}\n\n/**\n * A single caption phrase to display during playback.\n */\nexport interface CaptionPhrase {\n /** The text to display */\n text: string;\n /** Start time in seconds (relative to doc start) */\n startTime: number;\n /** End time in seconds */\n endTime: number;\n /** Which audio segment this caption belongs to (0-indexed) */\n audioSegment: number;\n /**\n * Optional per-word timing from TTS timing data.\n * When present, enables precise word-level highlighting in social\n * caption style. When absent, word timing is interpolated evenly\n * across the phrase duration.\n */\n words?: CaptionWord[];\n}\n\n/**\n * Caption track for a doc.\n */\nexport interface CaptionTrack {\n /** Caption phrases in chronological order */\n phrases: CaptionPhrase[];\n /** When the captions were generated. Optional so that conversion stays\n * deterministic — `markdownToDoc()` only sets it when the caller\n * supplies a timestamp via `captionsGeneratedAt`. */\n generatedAt?: string;\n /** Algorithm version for regeneration detection */\n version: number;\n}\n\n/**\n * A single audio segment (one MP3 file).\n */\nexport interface AudioSegment {\n /** Path to MP3 file (relative to article media dir) */\n src: string;\n /** Segment name (e.g., \"intro\", \"history\") */\n name: string;\n /** Duration in seconds */\n duration: number;\n /** Start time in overall timeline (calculated) */\n startTime: number;\n}\n\n// ============================================\n// Audio Timing Data (from TTS)\n// ============================================\n\n/**\n * A single word-level timing bookmark from TTS synthesis.\n * Used for precise audio-to-content mapping and word-level caption sync.\n */\nexport interface AudioBookmark {\n /** Unique identifier (e.g., \"word-0\", \"word-1\") */\n id: string;\n /** Timestamp in seconds from audio start */\n time: number;\n /** Character offset in the source text */\n charOffset: number;\n /** The word text at this position */\n textFragment?: string;\n}\n\n/**\n * Timing data for an audio segment, typically from a `.timing.json` file\n * generated alongside TTS audio. Contains the source text and per-word\n * timing bookmarks for precise content matching and caption sync.\n */\nexport interface AudioTimingData {\n /** The normalized plain text that was synthesized */\n sourceText: string;\n /** Word-level timing bookmarks */\n bookmarks: AudioBookmark[];\n /** Total audio duration in seconds */\n duration: number;\n}\n\n// ============================================\n// Helper Functions\n// ============================================\n\n/**\n * Calculate total duration from audio segments.\n */\nexport function calculateDuration(audio: AudioTrack): number {\n return audio.segments.reduce((sum, seg) => sum + seg.duration, 0);\n}\n\n/**\n * Find which audio segment is playing at a given time.\n */\nexport function getSegmentAtTime(audio: AudioTrack, time: number): number {\n let elapsed = 0;\n for (let i = 0; i < audio.segments.length; i++) {\n elapsed += audio.segments[i].duration;\n if (time < elapsed) return i;\n }\n return audio.segments.length - 1;\n}\n\n/**\n * Find which block should be visible at a given time.\n */\nexport function getBlockAtTime(blocks: Block[], time: number): Block | null {\n for (let i = blocks.length - 1; i >= 0; i--) {\n const block = blocks[i];\n if (time >= block.startTime && time < block.startTime + block.duration) {\n return block;\n }\n }\n return blocks[0] || null;\n}\n\n/**\n * Find the caption phrase that should be displayed at a given time.\n */\nexport function getCaptionAtTime(\n captions: CaptionTrack | undefined,\n time: number,\n): CaptionPhrase | null {\n if (!captions || !captions.phrases.length) return null;\n\n for (const phrase of captions.phrases) {\n if (time >= phrase.startTime && time < phrase.endTime) {\n return phrase;\n }\n }\n return null;\n}\n","/**\n * Media-clip timing model.\n *\n * An additive layer on top of the legacy sequential `Doc.audio.segments[]`\n * narration track. A {@link MediaClip} is a piece of audio or video whose\n * timing is expressed *relative to its parent block* (or the whole document),\n * with an optional `startAt` offset and an optional `spillover` past the\n * block's end. {@link resolveMediaSchedule} flattens these into absolute,\n * doc-timeline `ScheduledClip`s that playback and export consume.\n *\n * Authoring: a body-level `{[audio …]}` / `{[video …]}` annotation inside a\n * block becomes a `block.media` clip; the same annotation in the preamble\n * (before the first heading) with `anchor=document` becomes a\n * `doc.documentMedia` clip that spans the whole timeline.\n */\n\nimport type { Block, Doc } from './Doc.js';\n\n/**\n * A timed piece of media attached to a block (or the document).\n */\nexport interface MediaClip {\n /** Stable id (for React keys and timeline selection). */\n id: string;\n /** Source path (mp3/mp4/…), relative to the article media dir. */\n src: string;\n kind: 'audio' | 'video';\n /**\n * Seconds from the parent block's start when this clip begins. For an\n * `anchor='document'` clip, seconds from the document start. Default 0.\n */\n startAt: number;\n /** Source in-point within the file (seconds). Default 0. */\n clipStart?: number;\n /** Source out-point within the file (seconds). Default: file/block end. */\n clipEnd?: number;\n /**\n * When false (default) the clip stops at its block's end. When true it\n * keeps playing past the block boundary until the clip itself finishes.\n * Ignored for `anchor='document'` clips (they already span the timeline).\n */\n spillover?: boolean;\n /**\n * `'block'` (default) — timed relative to the parent block. `'document'` —\n * timed relative to the document start and able to span every block.\n */\n anchor: 'block' | 'document';\n /**\n * 1-based source line of the authoring annotation, when derived from\n * markdown. Enables round-tripping edits (e.g. the timeline editor rewriting\n * `startAt`) back to the exact line. Absent for programmatically built clips.\n */\n sourceLine?: number;\n}\n\n/**\n * A {@link MediaClip} resolved to absolute document-timeline coordinates.\n * Produced by {@link resolveMediaSchedule}; consumed by the playback\n * scheduler and the MP4 export.\n */\nexport interface ScheduledClip {\n id: string;\n src: string;\n kind: 'audio' | 'video';\n /** Absolute doc-timeline second the clip starts. */\n absoluteStart: number;\n /** Absolute doc-timeline second the clip ends (exclusive). */\n absoluteEnd: number;\n /** Source in-point to seek to at `absoluteStart`. */\n sourceIn: number;\n anchor: 'block' | 'document';\n /** Owning block id for block-anchored clips (for video re-homing/export). */\n blockId?: string;\n /** 1-based source line of the authoring annotation, when known. */\n sourceLine?: number;\n}\n\n/** Depth-first flatten of the block tree (local copy to avoid a doc-layer dep). */\nfunction flatten(blocks: Block[], out: Block[] = []): Block[] {\n for (const b of blocks) {\n out.push(b);\n if (b.children && b.children.length > 0) flatten(b.children, out);\n }\n return out;\n}\n\n/** Played length of a clip when known from its in/out points, else null. */\nfunction clipLength(clip: MediaClip): number | null {\n if (clip.clipEnd == null) return null;\n return Math.max(0, clip.clipEnd - (clip.clipStart ?? 0));\n}\n\n/**\n * Base playback span ignoring media — the larger of the audio-segment sum\n * and the last block's end. Document clips that don't pin a length run to\n * this point.\n */\nfunction baseTimelineEnd(doc: Doc): number {\n const blockEnd = flatten(doc.blocks).reduce(\n (max, b) => Math.max(max, b.startTime + b.duration),\n 0,\n );\n return Math.max(doc.duration ?? 0, blockEnd);\n}\n\n/**\n * Flatten every block clip and document clip into absolute-timed\n * `ScheduledClip`s. Pure — depends only on `doc`.\n */\nexport function resolveMediaSchedule(doc: Doc): ScheduledClip[] {\n const out: ScheduledClip[] = [];\n const docEnd = baseTimelineEnd(doc);\n\n for (const block of flatten(doc.blocks)) {\n const blockEnd = block.startTime + block.duration;\n for (const clip of block.media ?? []) {\n const start = block.startTime + clip.startAt;\n const len = clipLength(clip);\n let end: number;\n if (clip.spillover) {\n // Continue past the block; fall back to the block end when the\n // played length is unknown (no clipEnd authored).\n end = len != null ? start + len : blockEnd;\n } else {\n end = len != null ? Math.min(start + len, blockEnd) : blockEnd;\n }\n out.push({\n id: clip.id,\n src: clip.src,\n kind: clip.kind,\n absoluteStart: start,\n absoluteEnd: Math.max(start, end),\n sourceIn: clip.clipStart ?? 0,\n anchor: 'block',\n blockId: block.id,\n ...(clip.sourceLine != null ? { sourceLine: clip.sourceLine } : {}),\n });\n }\n }\n\n for (const clip of doc.documentMedia ?? []) {\n const start = clip.startAt;\n const len = clipLength(clip);\n const end = len != null ? start + len : docEnd;\n out.push({\n id: clip.id,\n src: clip.src,\n kind: clip.kind,\n absoluteStart: start,\n absoluteEnd: Math.max(start, end),\n sourceIn: clip.clipStart ?? 0,\n anchor: 'document',\n ...(clip.sourceLine != null ? { sourceLine: clip.sourceLine } : {}),\n });\n }\n\n return out;\n}\n\n/**\n * Total playback duration including any media that spills past the last\n * block (or audio segment). Export uses this for the frame count and the\n * player for its effective timeline length.\n */\nexport function getDocPlaybackDuration(doc: Doc): number {\n const base = baseTimelineEnd(doc);\n const mediaEnd = resolveMediaSchedule(doc).reduce((max, c) => Math.max(max, c.absoluteEnd), 0);\n return Math.max(base, mediaEnd);\n}\n","/**\n * Theme Compilation\n *\n * Turns a partial Theme (typically authored by the customizer panel from\n * a few seed colors and preset choices) into a complete, validated Theme\n * by:\n *\n * 1. Filling unspecified fields from a hardcoded `STARTER_THEME`.\n * 2. Deriving missing color slots from `seedColors` via OKLCh math.\n * 3. Validating the result.\n *\n * Built-in themes ship as fully-specified Theme JSON and skip this step.\n */\n\nimport type {\n Theme,\n DeepPartial,\n ThemeColorPalette,\n ThemeColorScheme,\n ThemeSeedColors,\n FontFamily,\n} from './Theme.js';\nimport { THEME_SCHEMA_VERSION, createTheme } from './Theme.js';\nimport { assertTheme } from './themeValidator.js';\nimport {\n oklchLighten,\n oklchDarken,\n oklchSetChroma,\n pickContrastingText,\n relativeLuminance,\n} from './colorUtils.js';\n\n/** Internal default theme used to fill in fields the customizer doesn't expose. */\nconst STARTER_BODY_FONT: FontFamily = { stackId: 'system-sans' };\nconst STARTER_TITLE_FONT: FontFamily = { stackId: 'system-serif' };\nconst STARTER_MONO_FONT: FontFamily = { stackId: 'system-mono' };\n\nconst STARTER_COLOR_SCHEMES: Record<string, ThemeColorScheme> = {\n blue: { bg: '#1a365d', text: '#63b3ed', accent: '#90cdf4' },\n green: { bg: '#22543d', text: '#9ae6b4', accent: '#68d391' },\n purple: { bg: '#44337a', text: '#d6bcfa', accent: '#b794f4' },\n red: { bg: '#742a2a', text: '#fc8181', accent: '#feb2b2' },\n orange: { bg: '#744210', text: '#fbd38d', accent: '#f6ad55' },\n teal: { bg: '#234e52', text: '#81e6d9', accent: '#4fd1c5' },\n};\n\nexport const STARTER_THEME: Theme = {\n schemaVersion: THEME_SCHEMA_VERSION,\n id: 'custom',\n name: 'Custom Theme',\n description: 'Customizer starter — gets overridden by user choices.',\n colors: {\n primary: '#3182ce',\n secondary: '#4a5568',\n background: '#1a202c',\n backgroundLight: '#2d3748',\n text: '#f7fafc',\n textMuted: '#a0aec0',\n highlight: '#4299e1',\n warning: '#fc8181',\n },\n typography: {\n bodyFont: STARTER_BODY_FONT,\n titleFont: STARTER_TITLE_FONT,\n monoFont: STARTER_MONO_FONT,\n titleWeight: 'bold',\n },\n style: {\n textShadow: true,\n overlayOpacity: 0.45,\n animationSpeed: 1.0,\n borderRadius: 6,\n },\n renderStyle: {\n name: 'standard',\n defaultTextAnimation: 'fadeIn',\n defaultImageAnimation: 'slowZoom',\n ambientMotion: true,\n defaultTransition: { type: 'fade', duration: 0.7 },\n },\n colorSchemes: STARTER_COLOR_SCHEMES,\n};\n\n/**\n * Derive a full `ThemeColorPalette` from `seedColors`, with any explicit\n * `partialColors` taking precedence over derived values. Background and\n * text are guessed from luminance when the user gave only a primary.\n */\nexport function deriveColorPalette(\n seeds: ThemeSeedColors,\n partialColors: Partial<ThemeColorPalette> = {},\n opts: { contrast?: 'subtle' | 'balanced' | 'high' } = {},\n): ThemeColorPalette {\n const spread = opts.contrast === 'high' ? 0.22 : opts.contrast === 'subtle' ? 0.08 : 0.15;\n\n const primary = seeds.primary;\n const secondary = seeds.secondary ?? oklchSetChroma(oklchLighten(primary, 0.05), 0.5);\n const accent = seeds.accent ?? oklchLighten(primary, spread);\n\n // Decide light vs dark surface from the explicit background, the seed background,\n // or default to dark.\n const bgSeed = partialColors.background ?? seeds.background;\n let background: string;\n if (bgSeed) {\n background = bgSeed;\n } else {\n // Pick a dark or light background that contrasts with primary\n background = relativeLuminance(primary) > 0.5 ? '#0a0a0a' : '#1a202c';\n }\n const isLightSurface = relativeLuminance(background) > 0.5;\n\n const backgroundLight =\n partialColors.backgroundLight ??\n (isLightSurface ? oklchDarken(background, 0.04) : oklchLighten(background, 0.04));\n\n const text =\n partialColors.text ?? seeds.text ?? pickContrastingText(background, '#f7fafc', '#1a202c');\n const textMuted =\n partialColors.textMuted ??\n (isLightSurface ? oklchLighten(text, 0.25) : oklchDarken(text, 0.25));\n\n const highlight = partialColors.highlight ?? accent;\n const warning = partialColors.warning ?? '#fc8181';\n\n return {\n primary: partialColors.primary ?? primary,\n secondary: partialColors.secondary ?? secondary,\n background,\n backgroundLight,\n text,\n textMuted,\n highlight,\n warning,\n };\n}\n\n/** Map a contrast preset to numeric spread used by `deriveColorPalette`. */\nexport type ContrastPreset = 'subtle' | 'balanced' | 'high';\n\nexport interface CompileOptions {\n /** Contrast level for OKLCh derivation (default 'balanced'). */\n contrast?: ContrastPreset;\n /**\n * Base theme to inherit from. When provided, the partial is deep-merged\n * over this theme instead of the neutral `STARTER_THEME`, so the compiled\n * theme inherits the base's render style, color schemes, typography, and\n * persistent layers — and only the fields the user changed are overridden.\n * The base's id is recorded on the result as `basedOn`.\n */\n base?: Theme;\n}\n\n/**\n * Compile a partial Theme into a complete one. Fills missing fields from\n * the base (`opts.base` when given, else `STARTER_THEME`), derives missing\n * color slots from `seedColors` (when present), and validates the result.\n */\nexport function compileTheme(partial: DeepPartial<Theme>, opts: CompileOptions = {}): Theme {\n // Step 1: deep-merge over the base (a chosen theme, or the neutral starter)\n const base = opts.base ?? STARTER_THEME;\n const merged = createTheme(base, partial);\n merged.schemaVersion = THEME_SCHEMA_VERSION;\n // Record which theme this was derived from so a customizer can re-inherit.\n if (opts.base && !merged.basedOn) merged.basedOn = opts.base.id;\n\n // Step 1b: typography fonts are discriminated unions ({stackId} | {custom}),\n // so deep-merge would leave stale keys from the starter when the partial\n // switches form. Replace each font wholesale when explicitly provided.\n const partialTypography = partial.typography;\n if (partialTypography) {\n if (partialTypography.titleFont !== undefined) {\n merged.typography.titleFont = partialTypography.titleFont as FontFamily;\n }\n if (partialTypography.bodyFont !== undefined) {\n merged.typography.bodyFont = partialTypography.bodyFont as FontFamily;\n }\n if (partialTypography.monoFont !== undefined) {\n merged.typography.monoFont = partialTypography.monoFont as FontFamily;\n }\n }\n\n // Step 1c: colorSchemes is a map the user edits wholesale (add / remove\n // accents). Deep-merge can't delete keys, so replace it outright when the\n // partial provides it — mirroring the font replacement above. This lets a\n // removed accent actually disappear rather than lingering from the base.\n if (partial.colorSchemes !== undefined) {\n merged.colorSchemes = partial.colorSchemes as Record<string, ThemeColorScheme>;\n }\n\n // Step 2: derive missing color slots from seeds, if present\n if (merged.seedColors) {\n const partialColors = (partial.colors ?? {}) as Partial<ThemeColorPalette>;\n merged.colors = deriveColorPalette(merged.seedColors, partialColors, {\n contrast: opts.contrast,\n });\n }\n\n // Step 3: validate\n return assertTheme(merged, `compiled theme \"${merged.id}\"`);\n}\n\n/**\n * Parse a JSON string into a validated Theme. Throws on invalid input.\n */\nexport function parseTheme(json: string): Theme {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid theme JSON: ${msg}`);\n }\n return assertTheme(parsed, 'parsed theme');\n}\n\n/**\n * Serialize a Theme to a stable, pretty-printed JSON string. Round-trips\n * cleanly through `parseTheme`.\n */\nexport function serializeTheme(theme: Theme): string {\n return JSON.stringify(theme, null, 2);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseFrontmatter,
|
|
3
3
|
parseHtmlToNodes
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HWVFJAAH.js";
|
|
5
5
|
import {
|
|
6
6
|
resolveIcon
|
|
7
7
|
} from "./chunk-DBS6RIFL.js";
|
|
@@ -272,6 +272,14 @@ function quoteAttrValue(value) {
|
|
|
272
272
|
}
|
|
273
273
|
return `"${value.replace(DQ_ESCAPE_RE, (ch) => `\\${ch}`)}"`;
|
|
274
274
|
}
|
|
275
|
+
function serializeAnnotation(template, params) {
|
|
276
|
+
const parts = [];
|
|
277
|
+
if (template) parts.push(template);
|
|
278
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
279
|
+
parts.push(`${key}=${quoteAttrValue(value)}`);
|
|
280
|
+
}
|
|
281
|
+
return `{[${parts.join(" ")}]}`;
|
|
282
|
+
}
|
|
275
283
|
|
|
276
284
|
// src/markdown/convert.ts
|
|
277
285
|
function convertPosition(pos) {
|
|
@@ -448,6 +456,11 @@ function serializePandocAttributes(attrs) {
|
|
|
448
456
|
if (parts.length === 0) return "{}";
|
|
449
457
|
return `{${parts.join(" ")}}`;
|
|
450
458
|
}
|
|
459
|
+
function hasPrintablePandocAttributes(attrs) {
|
|
460
|
+
return Boolean(
|
|
461
|
+
attrs.id || attrs.classes && attrs.classes.length > 0 || attrs.params && Object.keys(attrs.params).length > 0
|
|
462
|
+
);
|
|
463
|
+
}
|
|
451
464
|
function serializeTemplateAnnotation(annotation) {
|
|
452
465
|
const parts = [];
|
|
453
466
|
if (annotation.template) {
|
|
@@ -892,7 +905,9 @@ function blockToMdast(node) {
|
|
|
892
905
|
const suffixes = [];
|
|
893
906
|
if (node.attributes) {
|
|
894
907
|
const pandoc = serializePandocAttributes(node.attributes);
|
|
895
|
-
if (pandoc != null
|
|
908
|
+
if (pandoc != null && (pandoc !== "{}" || hasPrintablePandocAttributes(node.attributes))) {
|
|
909
|
+
suffixes.push(pandoc);
|
|
910
|
+
}
|
|
896
911
|
}
|
|
897
912
|
if (node.templateAnnotation) {
|
|
898
913
|
suffixes.push(serializeTemplateAnnotation(node.templateAnnotation));
|
|
@@ -1242,6 +1257,7 @@ export {
|
|
|
1242
1257
|
KNOWN_BLOCK_META_KEYS,
|
|
1243
1258
|
BLOCK_META_KEY_DESCRIPTORS,
|
|
1244
1259
|
coerceAnnotationValues,
|
|
1260
|
+
parseNumber,
|
|
1245
1261
|
parseTimeSeconds,
|
|
1246
1262
|
matchTrailingTemplateAnnotation,
|
|
1247
1263
|
matchTrailingPandocAttr,
|
|
@@ -1250,10 +1266,11 @@ export {
|
|
|
1250
1266
|
splitKeyValueToken,
|
|
1251
1267
|
needsQuoting,
|
|
1252
1268
|
quoteAttrValue,
|
|
1269
|
+
serializeAnnotation,
|
|
1253
1270
|
parsePandocAttrTokens,
|
|
1254
1271
|
serializePandocAttributes,
|
|
1255
1272
|
fromMdast,
|
|
1256
1273
|
toMdast,
|
|
1257
1274
|
parseMarkdown
|
|
1258
1275
|
};
|
|
1259
|
-
//# sourceMappingURL=chunk-
|
|
1276
|
+
//# sourceMappingURL=chunk-BEQODJWV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/markdown/annotationCoercion.ts","../src/markdown/attrTokens.ts","../src/markdown/convert.ts","../src/markdown/parse.ts"],"sourcesContent":["/**\n * Heading-attribute value coercion.\n *\n * The Pandoc-style heading attribute block `{#id .class key=value}` parses\n * to a flat `Record<string, string>` of `key → raw value`. This module\n * coerces a known subset of keys to typed values (numbers, time-in-seconds,\n * `BlockConnection[]`) so downstream consumers don't have to re-parse them.\n *\n * Unknown keys are returned in `metadata` as raw strings. Malformed known-key\n * values are dropped from the typed result and reported via `warnings` (the\n * raw string is preserved in `params` upstream for lossless round-tripping —\n * coercion failures do not corrupt the source).\n */\n\nimport type { BlockConnection } from './types.js';\nimport type { TransitionDirection, TransitionType } from '../schemas/Transitions.js';\nimport {\n normalizeTransitionDirection,\n normalizeTransitionType,\n TRANSITION_TYPES,\n TRANSITION_DIRECTIONS,\n} from '../schemas/Transitions.js';\n\n/**\n * Registry of known block-meta keys and their coercion strategies.\n *\n * Adding a new typed key: add an entry here, add the typed field to\n * `HeadingAttributes.blockMeta` in `types.ts`, and apply the coerced value\n * in `markdownToDoc.ts`.\n */\nexport const KNOWN_BLOCK_META_KEYS = {\n x: 'number',\n y: 'number',\n startTime: 'time',\n duration: 'time',\n connectsTo: 'connectionList',\n transition: 'transition',\n transitionDuration: 'time',\n transitionDirection: 'transitionDirection',\n} as const;\n\nexport type KnownBlockMetaKey = keyof typeof KNOWN_BLOCK_META_KEYS;\n\n/**\n * Editor-facing description of a known block-meta key: what it does, and\n * either its closed set of valid values or a free-form format hint. This is\n * the single source the markdown editor's `{[name key=value]}` attribute\n * autocomplete reads from, so the suggestion list stays in lockstep with\n * `KNOWN_BLOCK_META_KEYS` and the transition vocabulary rather than drifting\n * in a hand-maintained copy. `values`/`valueHint` are mutually exclusive: a\n * key has a closed enum (`values`) or a format hint (`valueHint`), not both.\n */\nexport interface BlockMetaKeyDescriptor {\n key: KnownBlockMetaKey;\n /** One-line description, shown as the completion item's detail. */\n description: string;\n /** Closed set of valid values, for keys backed by an enum. */\n values?: readonly string[];\n /** Format hint shown when the value is free-form (no closed set). */\n valueHint?: string;\n}\n\n/**\n * Ordered descriptors for every key in `KNOWN_BLOCK_META_KEYS`. Order is the\n * suggestion display order: the transition family first (the most common\n * authoring case), then timeline timing, then diagram geometry/links.\n */\nexport const BLOCK_META_KEY_DESCRIPTORS: readonly BlockMetaKeyDescriptor[] = [\n {\n key: 'transition',\n description: 'Block-to-block transition effect',\n values: TRANSITION_TYPES,\n },\n {\n key: 'transitionDuration',\n description: 'How long the transition lasts',\n valueHint: 'seconds — e.g. 0.7, 700ms',\n },\n {\n key: 'transitionDirection',\n description: 'Directional variant for the transition',\n values: TRANSITION_DIRECTIONS,\n },\n {\n key: 'startTime',\n description: 'Timeline start time of this block',\n valueHint: 'mm:ss or seconds — e.g. 01:30, 5, 1500ms',\n },\n {\n key: 'duration',\n description: 'How long this block lasts',\n valueHint: 'mm:ss or seconds — e.g. 45, 1500ms',\n },\n {\n key: 'x',\n description: 'Horizontal position on the diagram canvas',\n valueHint: 'number — e.g. 600',\n },\n {\n key: 'y',\n description: 'Vertical position on the diagram canvas',\n valueHint: 'number — e.g. 300',\n },\n {\n key: 'connectsTo',\n description: 'Diagram connections to other blocks',\n valueHint: 'comma-separated target or target:type — e.g. foo,bar:flow',\n },\n];\n\n/**\n * The shape of the typed values produced by coercion. Mirrors\n * `HeadingAttributes.blockMeta` in `types.ts`.\n */\nexport interface CoercedBlockMeta {\n x?: number;\n y?: number;\n startTime?: number;\n duration?: number;\n connectsTo?: BlockConnection[];\n transition?: {\n type: TransitionType;\n duration?: number;\n direction?: TransitionDirection;\n };\n}\n\nexport interface CoerceResult {\n /** Typed values for known keys whose coercion succeeded. */\n blockMeta: CoercedBlockMeta;\n /** Raw string values for keys not in the known-key registry. */\n metadata: Record<string, string>;\n /** Human-readable diagnostics — currently emitted for coercion failures. */\n warnings: string[];\n}\n\n/**\n * Coerce a flat `key → raw string` map into typed block-meta + an\n * untyped metadata bag, per the `KNOWN_BLOCK_META_KEYS` registry.\n */\nexport function coerceAnnotationValues(params: Record<string, string>): CoerceResult {\n const blockMeta: CoercedBlockMeta = {};\n const metadata: Record<string, string> = {};\n const warnings: string[] = [];\n let transitionType: TransitionType | null = null;\n let transitionDuration: number | undefined;\n let transitionDirection: TransitionDirection | undefined;\n\n for (const [key, raw] of Object.entries(params)) {\n const kind = (KNOWN_BLOCK_META_KEYS as Record<string, string>)[key];\n if (!kind) {\n metadata[key] = raw;\n continue;\n }\n if (kind === 'number') {\n const n = parseNumber(raw);\n if (n == null) {\n warnings.push(`Invalid number for \"${key}\": ${JSON.stringify(raw)}`);\n } else {\n (blockMeta as Record<string, unknown>)[key] = n;\n }\n } else if (kind === 'time') {\n const s = parseTimeSeconds(raw);\n if (s == null) {\n warnings.push(`Invalid time for \"${key}\": ${JSON.stringify(raw)}`);\n } else if (key === 'transitionDuration') {\n transitionDuration = s;\n } else {\n (blockMeta as Record<string, unknown>)[key] = s;\n }\n } else if (kind === 'connectionList') {\n const { list, warning } = parseConnectionList(raw);\n if (warning) warnings.push(`\"${key}\": ${warning}`);\n blockMeta.connectsTo = list;\n } else if (kind === 'transition') {\n const transition = normalizeTransitionType(raw);\n if (transition == null) {\n warnings.push(`Invalid transition for \"${key}\": ${JSON.stringify(raw)}`);\n } else {\n transitionType = transition;\n }\n } else if (kind === 'transitionDirection') {\n const direction = normalizeTransitionDirection(raw);\n if (direction == null) {\n warnings.push(`Invalid transition direction for \"${key}\": ${JSON.stringify(raw)}`);\n } else {\n transitionDirection = direction;\n }\n }\n }\n\n if (transitionType) {\n blockMeta.transition = {\n type: transitionType,\n ...(transitionDuration !== undefined ? { duration: transitionDuration } : {}),\n ...(transitionDirection !== undefined ? { direction: transitionDirection } : {}),\n };\n }\n\n return { blockMeta, metadata, warnings };\n}\n\n// ============================================\n// Coercers\n// ============================================\n\n/** Parse a finite number from a string. Returns null on failure. */\nexport function parseNumber(raw: string): number | null {\n if (raw.trim() === '') return null;\n const n = Number(raw);\n return Number.isFinite(n) ? n : null;\n}\n\nconst TIME_BARE_RE = /^\\d+(?:\\.\\d+)?$/;\nconst TIME_MS_RE = /^(\\d+(?:\\.\\d+)?)ms$/;\nconst TIME_MMSS_RE = /^(\\d+):(\\d{1,2})(?:\\.(\\d+))?$/;\n\n/**\n * Parse a time value to seconds. Accepts:\n * - bare number: `5` → 5, `5.5` → 5.5\n * - `mm:ss`: `01:30` → 90\n * - `mm:ss.ms`: `01:30.500` → 90.5\n * - `1500ms` → 1.5\n *\n * Returns null on malformed input.\n */\nexport function parseTimeSeconds(raw: string): number | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n\n if (TIME_BARE_RE.test(trimmed)) {\n const n = Number(trimmed);\n return Number.isFinite(n) ? n : null;\n }\n\n const msMatch = trimmed.match(TIME_MS_RE);\n if (msMatch) {\n const n = Number(msMatch[1]);\n return Number.isFinite(n) ? n / 1000 : null;\n }\n\n const mmssMatch = trimmed.match(TIME_MMSS_RE);\n if (mmssMatch) {\n const mins = Number(mmssMatch[1]);\n const secs = Number(mmssMatch[2]);\n const frac = mmssMatch[3] ? Number(`0.${mmssMatch[3]}`) : 0;\n if (!Number.isFinite(mins) || !Number.isFinite(secs) || !Number.isFinite(frac)) {\n return null;\n }\n if (secs >= 60) return null;\n return mins * 60 + secs + frac;\n }\n\n return null;\n}\n\n/**\n * Parse a comma-separated connection list. Each entry is `target` or\n * `target:type`. Empty entries (`a,,b`) are filtered with a warning; an\n * entirely empty input yields an empty list with no warning.\n */\nexport function parseConnectionList(raw: string): {\n list: BlockConnection[];\n warning: string | null;\n} {\n const trimmed = raw.trim();\n if (!trimmed) return { list: [], warning: null };\n\n const list: BlockConnection[] = [];\n let droppedEmpty = false;\n for (const part of trimmed.split(',')) {\n const entry = part.trim();\n if (!entry) {\n droppedEmpty = true;\n continue;\n }\n const colonIdx = entry.indexOf(':');\n if (colonIdx < 0) {\n list.push({ target: entry });\n } else {\n const target = entry.slice(0, colonIdx).trim();\n const type = entry.slice(colonIdx + 1).trim();\n if (!target) {\n droppedEmpty = true;\n continue;\n }\n list.push(type ? { target, type } : { target });\n }\n }\n\n return {\n list,\n warning: droppedEmpty ? 'dropped empty connection entries' : null,\n };\n}\n","/**\n * Shared key=value attribute machinery for the two heading annotation forms:\n * the squisq-native `{[templateName key=value …]}` template annotation and\n * the Pandoc-style `{#id .class key=value}` attribute block.\n *\n * Both forms share one interior grammar — whitespace-separated tokens whose\n * values may be quoted (double or single) to include spaces, brackets, or\n * quote characters. This module is the single implementation of that\n * grammar: one tokenizer, one value un/escaper, one quoting rule. The\n * trailing-annotation recognizers live here too so the editor bridge\n * (packages/editor-react/src/tiptapBridge.ts) stays in sync by importing\n * them rather than copying regexes.\n *\n * Value syntax:\n * - Unquoted: no whitespace, quotes, or `[ ] { }` (those would collide with\n * the surrounding annotation delimiters), e.g. `columns=3`, `name=O'Brien`\n * (a quote *inside* an unquoted value is literal).\n * - Quoted: `key=\"value with spaces\"` or `key='…'`. Inside a quoted run,\n * `\\` escapes the active quote character and itself; any other `\\` is\n * literal. Quoted values may contain `]` and `}`.\n * - Serialization emits double quotes, switching to single quotes when the\n * value contains a double quote (markdown character-escape resolution\n * makes backslash-escaped quotes fragile in `.md` source, so picking the\n * other quote character is preferred over escaping). A value containing\n * BOTH quote characters falls back to double quotes with `\\\"` escapes —\n * that survives the API path but is best avoided in markdown source.\n */\n\n// ============================================\n// Trailing-annotation recognizers\n// ============================================\n\n/** Regex source for a double-quoted run with `\\\"` / `\\\\` escapes. */\nconst DQ_RUN = `\"(?:[^\"\\\\\\\\]|\\\\\\\\.)*\"`;\n/** Regex source for a single-quoted run with `\\'` / `\\\\` escapes. */\nconst SQ_RUN = `'(?:[^'\\\\\\\\]|\\\\\\\\.)*'`;\n\n/**\n * Quote-aware match for a trailing `{[…]}` annotation: the interior is any\n * mix of quoted runs and characters other than `]` and quotes, so quoted\n * values may contain `]`. The trailing `[\\s\\]}]*` tolerates an accidental\n * doubled `]}` that users sometimes type when learning the syntax.\n */\nconst TEMPLATE_STRICT_RE = new RegExp(\n `\\\\s*\\\\{\\\\[((?:[^\\\\]\"']|${DQ_RUN}|${SQ_RUN})+)\\\\]\\\\}[\\\\s\\\\]}]*$`,\n);\n\n/**\n * Legacy fallback matching the pre-quote-aware grammar (`[^\\]]+` interior).\n * Tried after the strict form so inputs with unbalanced quotes keep parsing\n * exactly as they did before quote support landed.\n */\nconst TEMPLATE_LEGACY_RE = /\\s*\\{\\[([^\\]]+)\\]\\}[\\s\\]}]*$/;\n\n/**\n * Quote-aware match for a trailing Pandoc `{…}` block. The negative\n * lookahead `(?!\\[)` keeps it from colliding with the `{[…]}` template\n * form. Quoted values may contain `}`.\n */\nconst PANDOC_STRICT_RE = new RegExp(`\\\\s*\\\\{(?!\\\\[)((?:[^}\"']|${DQ_RUN}|${SQ_RUN})*)\\\\}\\\\s*$`);\n\n/** Legacy fallback for the Pandoc block (`[^}]*` interior). */\nconst PANDOC_LEGACY_RE = /\\s*\\{(?!\\[)([^}]*)\\}\\s*$/;\n\n/** Result of matching a trailing annotation against a text run. */\nexport interface TrailingAnnotationMatch {\n /** Interior of the brace block (between `{[`/`{` and `]}`/`}`), untrimmed. */\n inner: string;\n /** Offset of the match start (including leading whitespace) in the input. */\n index: number;\n}\n\nfunction matchTrailing(\n text: string,\n strict: RegExp,\n legacy: RegExp,\n): TrailingAnnotationMatch | null {\n const m = strict.exec(text) ?? legacy.exec(text);\n return m ? { inner: m[1], index: m.index } : null;\n}\n\n/**\n * Match a trailing `{[templateName key=value …]}` annotation. Quote-aware\n * (values may contain `]` when quoted), with a legacy fallback so inputs\n * the old `[^\\]]+` grammar accepted still match.\n */\nexport function matchTrailingTemplateAnnotation(text: string): TrailingAnnotationMatch | null {\n return matchTrailing(text, TEMPLATE_STRICT_RE, TEMPLATE_LEGACY_RE);\n}\n\n/**\n * Match a trailing Pandoc `{#id .class key=value}` block. Quote-aware\n * (values may contain `}` when quoted), with a legacy fallback.\n */\nexport function matchTrailingPandocAttr(text: string): TrailingAnnotationMatch | null {\n return matchTrailing(text, PANDOC_STRICT_RE, PANDOC_LEGACY_RE);\n}\n\n// ============================================\n// Tokenizer\n// ============================================\n\nconst WHITESPACE_RE = /\\s/;\n\n/**\n * Quote-aware whitespace tokenizer for the interior of either annotation\n * form.\n *\n * A quote run starts only at the beginning of a token or immediately after\n * `=` — a quote elsewhere (e.g. `name=O'Brien`) is a literal character.\n * Inside a run, `\\` escapes the active quote character and itself; the\n * escape pair is preserved in the returned token (callers strip and\n * unescape via {@link splitKeyValueToken} / {@link unquoteAttrValue}).\n * An unbalanced opening quote is tolerated: the run extends to the end of\n * the input.\n */\nexport function tokenizeAttrTokens(input: string): string[] {\n const tokens: string[] = [];\n let i = 0;\n const n = input.length;\n while (i < n) {\n while (i < n && WHITESPACE_RE.test(input[i])) i++;\n if (i >= n) break;\n\n let token = '';\n while (i < n && !WHITESPACE_RE.test(input[i])) {\n const ch = input[i];\n const atValueStart = token === '' || token.endsWith('=');\n if ((ch === '\"' || ch === \"'\") && atValueStart) {\n token += ch;\n i++;\n while (i < n && input[i] !== ch) {\n if (input[i] === '\\\\' && i + 1 < n && (input[i + 1] === ch || input[i + 1] === '\\\\')) {\n token += input[i] + input[i + 1];\n i += 2;\n } else {\n token += input[i];\n i++;\n }\n }\n if (i < n && input[i] === ch) {\n token += ch;\n i++;\n }\n } else {\n token += ch;\n i++;\n }\n }\n if (token) tokens.push(token);\n }\n return tokens;\n}\n\n// ============================================\n// Value un/escaping\n// ============================================\n\nconst DQ_UNESCAPE_RE = /\\\\([\\\\\"])/g;\nconst SQ_UNESCAPE_RE = /\\\\([\\\\'])/g;\n\n/**\n * Strip surrounding quotes from a raw token value and unescape the active\n * quote character and backslash. Values not wrapped in matching quotes are\n * returned verbatim (including any interior quotes or backslashes).\n */\nexport function unquoteAttrValue(raw: string): string {\n if (raw.length >= 2) {\n const q = raw[0];\n if ((q === '\"' || q === \"'\") && raw.endsWith(q)) {\n const body = raw.slice(1, -1);\n return body.replace(q === '\"' ? DQ_UNESCAPE_RE : SQ_UNESCAPE_RE, '$1');\n }\n }\n return raw;\n}\n\n/**\n * Split a `key=value` token into its parts, unquoting/unescaping the value.\n * Returns null for tokens with no `=` past the first character (bare words,\n * `#id`, `.class`, or a leading `=`).\n */\nexport function splitKeyValueToken(token: string): { key: string; value: string } | null {\n const eqIdx = token.indexOf('=');\n if (eqIdx <= 0) return null;\n return { key: token.slice(0, eqIdx), value: unquoteAttrValue(token.slice(eqIdx + 1)) };\n}\n\n// ============================================\n// Serialization\n// ============================================\n\nconst NEEDS_QUOTING_RE = /[\\s\"'[\\]{}]/;\nconst DQ_ESCAPE_RE = /[\\\\\"]/g;\nconst SQ_ESCAPE_RE = /[\\\\']/g;\n\n/**\n * Whether a value must be quoted to survive tokenization and the outer\n * annotation regexes: any whitespace, quote character, bracket, or brace —\n * or an empty value, which would otherwise serialize as a bare `key=`.\n */\nexport function needsQuoting(value: string): boolean {\n return value === '' || NEEDS_QUOTING_RE.test(value);\n}\n\n/**\n * Serialize an attribute value, quoting when {@link needsQuoting} says the\n * bare form would not round-trip.\n *\n * Double quotes are canonical; a value containing a double quote (but no\n * single quote) is single-quoted instead, because markdown resolves\n * backslash escapes in text runs before our parser sees them — switching\n * the quote character round-trips through `.md` source where `\\\"` would\n * not. Backslashes and the active quote character are escaped.\n */\nexport function quoteAttrValue(value: string): string {\n if (!needsQuoting(value)) return value;\n if (value.includes('\"') && !value.includes(\"'\")) {\n return `'${value.replace(SQ_ESCAPE_RE, (ch) => `\\\\${ch}`)}'`;\n }\n return `\"${value.replace(DQ_ESCAPE_RE, (ch) => `\\\\${ch}`)}\"`;\n}\n\n/**\n * Serialize a `{[templateName key=value …]}` template annotation. The inverse\n * of {@link parseStandaloneAnnotation} / the tokenizer: emits the template\n * name (when present) followed by each `key=value` pair in insertion order,\n * quoting values via {@link quoteAttrValue}. Used to re-emit heading-less\n * standalone annotation blocks in `docToMarkdown`.\n */\nexport function serializeAnnotation(\n template: string | undefined,\n params?: Record<string, string>,\n): string {\n const parts: string[] = [];\n if (template) parts.push(template);\n for (const [key, value] of Object.entries(params ?? {})) {\n parts.push(`${key}=${quoteAttrValue(value)}`);\n }\n return `{[${parts.join(' ')}]}`;\n}\n","/**\n * mdast ↔ Squisq Markdown Node Conversion\n *\n * Converts between the remark/mdast AST format and the squisq\n * MarkdownDocument JSON format. This is the bridge layer that allows\n * us to use the battle-tested unified/remark ecosystem for parsing\n * and serialization while exposing our own clean, well-typed interfaces.\n *\n * The conversion is designed for perfect round-tripping:\n * toMdast(fromMdast(tree)) should produce an equivalent mdast tree.\n */\n\nimport type {\n MarkdownDocument,\n MarkdownBlockNode,\n MarkdownInlineNode,\n MarkdownListItem,\n MarkdownTableRow,\n MarkdownTableCell,\n MarkdownSourcePosition,\n HeadingTemplateAnnotation,\n HeadingAttributes,\n MarkdownHeading,\n MarkdownInlineIcon,\n} from './types.js';\nimport { parseHtmlToNodes } from './htmlParse.js';\nimport { resolveIcon } from '../icons/resolve.js';\nimport { coerceAnnotationValues } from './annotationCoercion.js';\nimport {\n matchTrailingTemplateAnnotation,\n matchTrailingPandocAttr,\n tokenizeAttrTokens,\n splitKeyValueToken,\n quoteAttrValue,\n} from './attrTokens.js';\n\n// ============================================\n// Generic mdast node shape\n// ============================================\n\n/**\n * Minimal interface for mdast/unist nodes.\n * Using a generic shape avoids hard dependency on @types/mdast\n * and handles extension nodes (GFM, math, directive) uniformly.\n */\ninterface MdastPosition {\n start: { line: number; column: number; offset?: number };\n end: { line: number; column: number; offset?: number };\n}\n\ninterface MdastNode {\n type: string;\n position?: MdastPosition;\n children?: MdastNode[];\n value?: string;\n depth?: number;\n ordered?: boolean | null;\n start?: number | null;\n spread?: boolean | null;\n checked?: boolean | null;\n lang?: string | null;\n meta?: string | null;\n url?: string;\n title?: string | null;\n alt?: string;\n identifier?: string;\n label?: string;\n referenceType?: string;\n align?: (string | null)[];\n name?: string;\n attributes?: Record<string, string>;\n data?: Record<string, unknown>;\n}\n\n// ============================================\n// Position conversion\n// ============================================\n\nfunction convertPosition(pos?: MdastPosition): MarkdownSourcePosition | undefined {\n if (!pos) return undefined;\n return {\n start: {\n line: pos.start.line,\n column: pos.start.column,\n ...(pos.start.offset != null ? { offset: pos.start.offset } : {}),\n },\n end: {\n line: pos.end.line,\n column: pos.end.column,\n ...(pos.end.offset != null ? { offset: pos.end.offset } : {}),\n },\n };\n}\n\nfunction toMdastPosition(pos: MarkdownSourcePosition): MdastPosition {\n return {\n start: {\n line: pos.start.line,\n column: pos.start.column,\n ...(pos.start.offset != null ? { offset: pos.start.offset } : {}),\n },\n end: {\n line: pos.end.line,\n column: pos.end.column,\n ...(pos.end.offset != null ? { offset: pos.end.offset } : {}),\n },\n };\n}\n\n/** Conditionally include position in node. */\nfunction posField(\n pos?: MarkdownSourcePosition,\n): { position: MarkdownSourcePosition } | Record<string, never> {\n return pos ? { position: pos } : {};\n}\n\nfunction mdastPosField(\n pos?: MarkdownSourcePosition,\n): { position: MdastPosition } | Record<string, never> {\n return pos ? { position: toMdastPosition(pos) } : {};\n}\n\n// ============================================\n// mdast → Squisq (fromMdast)\n// ============================================\n\n/**\n * Extract plain text content from an mdast node tree.\n * Used for directive labels.\n */\nfunction extractText(node: MdastNode): string {\n if (node.value != null) return node.value;\n if (node.children) {\n return node.children.map(extractText).join('');\n }\n return '';\n}\n\n// ============================================\n// Template annotation helpers\n// ============================================\n\n/**\n * Extract a `{[templateName key=value …]}` annotation from a heading's\n * inline children. Mutates the children array in-place: strips the\n * annotation text from the last text node (or removes the node entirely).\n *\n * Matching is delegated to {@link matchTrailingTemplateAnnotation}: a\n * quote-aware grammar (values may contain `]` when quoted) with a legacy\n * fallback, plus tolerance for an accidental doubled trailing `]}`. A\n * non-trailing annotation like `## The {[chart]} section` doesn't match.\n *\n * @returns The parsed annotation, or null if none found.\n */\nfunction extractTemplateAnnotation(\n children: MarkdownInlineNode[],\n): HeadingTemplateAnnotation | null {\n // Walk backwards to find the last text node\n for (let i = children.length - 1; i >= 0; i--) {\n const child = children[i];\n if (child.type === 'text') {\n const match = matchTrailingTemplateAnnotation(child.value);\n if (match) {\n const inner = match.inner.trim();\n const annotation = parseAnnotationTokens(inner);\n\n // Strip the matched portion from the text\n const stripped = child.value.slice(0, match.index).replace(/\\s+$/, '');\n if (stripped) {\n (child as { value: string }).value = stripped;\n } else {\n // Remove the now-empty text node\n children.splice(i, 1);\n }\n return annotation;\n }\n // Only check the last text node (trailing position)\n break;\n }\n // If the last child isn't a text node, there's no annotation\n break;\n }\n return null;\n}\n\n/**\n * Parse the inner content of a `{[…]}` annotation into template + params.\n *\n * Input: `\"chart colorScheme=blue caption=\\\"Sales by region\\\"\"`\n * Output: `{ template: 'chart', params: { colorScheme: 'blue', caption: 'Sales by region' } }`\n *\n * Tokenization is shared with the Pandoc attribute parser\n * ({@link tokenizeAttrTokens}), so quoting and escaping behave\n * identically in both annotation forms.\n */\nfunction parseAnnotationTokens(inner: string): HeadingTemplateAnnotation {\n const tokens = tokenizeAttrTokens(inner);\n const params: Record<string, string> = {};\n\n // If the first token contains '=', there's no template name —\n // the annotation is purely key-value (e.g., `{[audio=intro.mp3]}`).\n const firstIsParam = tokens.length > 0 && tokens[0].indexOf('=') > 0;\n const template = firstIsParam || tokens.length === 0 ? undefined : tokens[0];\n const startIdx = firstIsParam ? 0 : 1;\n\n for (let i = startIdx; i < tokens.length; i++) {\n const kv = splitKeyValueToken(tokens[i]);\n if (kv) {\n params[kv.key] = kv.value;\n }\n }\n\n const result: HeadingTemplateAnnotation = {};\n if (template) {\n result.template = template;\n }\n if (Object.keys(params).length > 0) {\n result.params = params;\n }\n return result;\n}\n\n// ============================================\n// Pandoc-style heading attribute helpers\n// ============================================\n\n/**\n * Extract a trailing Pandoc-style `{…}` attribute block from a heading's\n * inline children. Matching is delegated to {@link matchTrailingPandocAttr}\n * (quote-aware — values may contain `}` when quoted — with a legacy\n * fallback; a `(?!\\[)` lookahead keeps it from colliding with the\n * squisq-native `{[…]}` template annotation).\n *\n * Unlike `extractTemplateAnnotation()`, this walks backward through both\n * text nodes AND bare `textDirective` nodes. remark-directive may have\n * fragmented a `{key=val:type,…}` attribute block at colon-prefixed\n * segments like `:type` that look like text directives but are really\n * attribute-value fragments. We virtually re-stitch the trailing run as a\n * single string, match against it, and only physically modify the children\n * array if a match is found — so a heading that legitimately contains a\n * non-bare `:directive` is left alone.\n *\n * Returns the parsed `HeadingAttributes`, or null if no match.\n */\nfunction extractPandocAttributes(children: MarkdownInlineNode[]): HeadingAttributes | null {\n // Collect the trailing run of [text | bare textDirective] children.\n const trail: { val: string; idx: number; len: number }[] = [];\n for (let i = children.length - 1; i >= 0; i--) {\n const c = children[i];\n if (c.type === 'text') {\n trail.unshift({ val: c.value, idx: i, len: c.value.length });\n } else if (c.type === 'textDirective') {\n const td = c as {\n name?: string;\n children?: unknown[];\n attributes?: Record<string, string>;\n };\n const isBare =\n (!td.children || td.children.length === 0) &&\n (!td.attributes || Object.keys(td.attributes).length === 0);\n if (!isBare) break;\n const synthesized = ':' + (td.name ?? '');\n trail.unshift({ val: synthesized, idx: i, len: synthesized.length });\n } else {\n break;\n }\n }\n if (trail.length === 0) return null;\n\n const joined = trail.map((t) => t.val).join('');\n const match = matchTrailingPandocAttr(joined);\n if (!match) return null;\n\n const inner = match.inner.trim();\n const attrs = parsePandocAttrTokens(inner);\n\n // Strip the matched portion. `match.index` is an offset into `joined`.\n // Walk forward through `trail` until we find the child that straddles\n // the match boundary; trim that child (if text) or drop it (if directive),\n // and remove everything after it from `children`.\n const matchStart = match.index;\n let scannedLen = 0;\n for (let ti = 0; ti < trail.length; ti++) {\n const t = trail[ti];\n if (scannedLen + t.len <= matchStart) {\n scannedLen += t.len;\n continue;\n }\n const firstNode = children[t.idx];\n const keepLen = matchStart - scannedLen;\n if (firstNode.type === 'text') {\n const trimmed = firstNode.value.slice(0, keepLen).replace(/\\s+$/, '');\n if (trimmed) {\n (firstNode as { value: string }).value = trimmed;\n children.splice(t.idx + 1);\n } else {\n children.splice(t.idx);\n }\n } else {\n // Match boundary fell inside a textDirective's synthesized \":name\"\n // string — drop the directive and everything after.\n children.splice(t.idx);\n }\n return attrs;\n }\n // Match consumes the entire trail.\n children.splice(trail[0].idx);\n return attrs;\n}\n\n/**\n * Parse the inner content of a Pandoc-style `{…}` block. Tokens are\n * whitespace-separated, with quoted runs (single or double) treated as\n * single tokens — tokenization is shared with the `{[…]}` template\n * annotation parser via {@link tokenizeAttrTokens}.\n *\n * - `#id` → `attributes.id`\n * - `.class` → push onto `attributes.classes`\n * - `key=value` or `key=\"quoted value\"` → `attributes.params[key] = value`\n *\n * Empty `{}` yields `{}` (preserved as an annotation marker for round-tripping).\n * Duplicate ids and keys: last wins, silent.\n */\nexport function parsePandocAttrTokens(inner: string): HeadingAttributes {\n const tokens = tokenizeAttrTokens(inner);\n const attrs: HeadingAttributes = {};\n const params: Record<string, string> = {};\n const classes: string[] = [];\n\n for (const token of tokens) {\n if (token.startsWith('#') && !token.includes('=')) {\n attrs.id = token.slice(1);\n } else if (token.startsWith('.') && !token.includes('=')) {\n const cls = token.slice(1);\n if (cls) classes.push(cls);\n } else {\n const kv = splitKeyValueToken(token);\n if (kv) {\n params[kv.key] = kv.value;\n }\n }\n }\n\n if (classes.length > 0) attrs.classes = classes;\n if (Object.keys(params).length > 0) {\n attrs.params = params;\n const { blockMeta, metadata } = coerceAnnotationValues(params);\n if (Object.keys(blockMeta).length > 0) attrs.blockMeta = blockMeta;\n if (Object.keys(metadata).length > 0) attrs.metadata = metadata;\n }\n return attrs;\n}\n\n/**\n * Serialize a HeadingAttributes object back to a Pandoc `{#id .class key=value}` string.\n * Returns null when the attributes object is entirely empty (nothing to emit).\n *\n * Canonical key order: `#id`, then `.classes` (in original order), then\n * params in their original insertion order. Values are quoted via the\n * shared {@link quoteAttrValue} rule (same as `{[…]}` serialization).\n */\nexport function serializePandocAttributes(attrs: HeadingAttributes): string | null {\n const parts: string[] = [];\n if (attrs.id) parts.push(`#${attrs.id}`);\n if (attrs.classes) {\n for (const cls of attrs.classes) parts.push(`.${cls}`);\n }\n if (attrs.params) {\n for (const [key, value] of Object.entries(attrs.params)) {\n parts.push(`${key}=${quoteAttrValue(value)}`);\n }\n }\n // Preserve an empty `{}` annotation marker if it was authored deliberately\n // (e.g., reserved for downstream tooling). Caller decides whether to emit.\n if (parts.length === 0) return '{}';\n return `{${parts.join(' ')}}`;\n}\n\nfunction hasPrintablePandocAttributes(attrs: HeadingAttributes): boolean {\n return Boolean(\n attrs.id ||\n (attrs.classes && attrs.classes.length > 0) ||\n (attrs.params && Object.keys(attrs.params).length > 0),\n );\n}\n\n/**\n * Serialize a HeadingTemplateAnnotation back to `{[templateName key=value …]}` text.\n * Values are quoted via the shared {@link quoteAttrValue} rule (same as\n * Pandoc attribute serialization).\n */\nfunction serializeTemplateAnnotation(annotation: HeadingTemplateAnnotation): string {\n const parts: string[] = [];\n if (annotation.template) {\n parts.push(annotation.template);\n }\n if (annotation.params) {\n for (const [key, value] of Object.entries(annotation.params)) {\n parts.push(`${key}=${quoteAttrValue(value)}`);\n }\n }\n return `{[${parts.join(' ')}]}`;\n}\n\n/**\n * Convert an mdast Root node to a MarkdownDocument.\n *\n * @param root - The mdast root node (from remark-parse)\n * @param options - Conversion options\n * @returns A MarkdownDocument\n */\nexport function fromMdast(root: MdastNode, options?: { parseHtml?: boolean }): MarkdownDocument {\n const doParseHtml = options?.parseHtml !== false;\n return {\n type: 'document',\n children: convertBlockChildren(root.children ?? [], doParseHtml),\n ...posField(convertPosition(root.position)),\n };\n}\n\nfunction convertBlockChildren(children: MdastNode[], parseHtml: boolean): MarkdownBlockNode[] {\n const result: MarkdownBlockNode[] = [];\n for (const child of children) {\n const node = convertBlockNode(child, parseHtml);\n if (node) result.push(node);\n }\n return result;\n}\n\nfunction convertInlineChildren(\n children: MdastNode[],\n parseHtml: boolean,\n /** When false, skip the inline-icon split. Headings opt out so the\n * `extractTemplateAnnotation` pass still sees the trailing\n * `{[…]}` in raw text form; the caller runs `splitInlineIcons`\n * itself afterward on the remaining children. */\n applyIconSplit: boolean = true,\n): MarkdownInlineNode[] {\n const result: MarkdownInlineNode[] = [];\n for (const child of children) {\n const node = convertInlineNode(child, parseHtml);\n if (node) result.push(node);\n }\n const mentioned = coalesceMentions(result);\n return applyIconSplit ? splitInlineIcons(mentioned) : mentioned;\n}\n\n/**\n * Walk an inline-children list and split each text node on `{[token]}`\n * occurrences whose tokens resolve to known FontAwesome icons. Returns\n * a new array — unresolved tokens are left as literal text so authors\n * can write `{[notathing]}` verbatim if they really mean to.\n *\n * Exposed for the heading code path, which first strips the trailing\n * template annotation and then runs this on what remains.\n */\nexport function splitInlineIcons(nodes: MarkdownInlineNode[]): MarkdownInlineNode[] {\n // First pass: reassemble `text + textDirective + text` patterns\n // formed when a qualified token like `{[fa-solid:user]}` was split\n // by remark-directive's colon recognition. We collapse those triples\n // back into a single text node containing the original token so the\n // regex below can match.\n const recombined = recombineDirectiveTokens(nodes);\n\n const out: MarkdownInlineNode[] = [];\n for (const node of recombined) {\n if (node.type !== 'text') {\n out.push(node);\n continue;\n }\n out.push(...splitTextOnIcons(node.value, node.position));\n }\n return out;\n}\n\n/**\n * When the user types `{[fa-solid:user]}`, remark's directive plugin\n * sees `:user` and emits a `textDirective` node. The result is a\n * three-node sequence `text(\"…{[fa-solid\") + textDirective(\"user\") +\n * text(\"]}…\")`. This helper detects that shape and stitches the three\n * back into one text node so downstream icon detection works on the\n * authored token.\n */\nfunction recombineDirectiveTokens(nodes: MarkdownInlineNode[]): MarkdownInlineNode[] {\n const out: MarkdownInlineNode[] = [];\n for (let i = 0; i < nodes.length; i++) {\n const cur = nodes[i];\n const next = nodes[i + 1];\n const after = nodes[i + 2];\n if (\n cur?.type === 'text' &&\n next?.type === 'textDirective' &&\n after?.type === 'text' &&\n /\\{\\[[a-zA-Z0-9_-]+$/.test(cur.value) &&\n /^\\]\\}/.test(after.value)\n ) {\n const directive = next as unknown as { name?: string };\n const directiveName = directive.name ?? '';\n const merged: MarkdownInlineNode = {\n type: 'text',\n value: cur.value + ':' + directiveName + after.value,\n ...(cur.position ? { position: cur.position } : {}),\n };\n out.push(merged);\n i += 2;\n continue;\n }\n out.push(cur);\n }\n return out;\n}\n\nconst ICON_TOKEN_RE = /\\{\\[([a-zA-Z0-9_:-]+)\\]\\}/g;\n\nfunction splitTextOnIcons(value: string, position?: MarkdownSourcePosition): MarkdownInlineNode[] {\n if (!value) return [];\n ICON_TOKEN_RE.lastIndex = 0;\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n const out: MarkdownInlineNode[] = [];\n while ((match = ICON_TOKEN_RE.exec(value)) !== null) {\n const token = match[1];\n const icon = resolveIcon(token);\n if (!icon) continue; // leave the literal `{[…]}` in place\n if (match.index > lastIndex) {\n out.push({\n type: 'text',\n value: value.slice(lastIndex, match.index),\n ...(position ? { position } : {}),\n });\n }\n const iconNode: MarkdownInlineIcon = {\n type: 'inlineIcon',\n token,\n family: icon.family,\n name: icon.name,\n ...(position ? { position } : {}),\n };\n out.push(iconNode);\n lastIndex = match.index + match[0].length;\n }\n if (lastIndex === 0) {\n // No matches — return the original text node unchanged.\n return [{ type: 'text', value, ...(position ? { position } : {}) }];\n }\n if (lastIndex < value.length) {\n out.push({\n type: 'text',\n value: value.slice(lastIndex),\n ...(position ? { position } : {}),\n });\n }\n return out;\n}\n\n/**\n * Post-process an inline-children list to collapse `text('@') + link(scheme:id)`\n * pairs into a single `mention` node. The wire format `@[Display](scheme:id)`\n * comes back from remark as two adjacent nodes — we merge them so consumers\n * see a single atomic mention. A plain link whose URL doesn't have `scheme:id`\n * shape, or that isn't immediately preceded by `@`, is left alone so ordinary\n * links keep working.\n */\nfunction coalesceMentions(inlines: MarkdownInlineNode[]): MarkdownInlineNode[] {\n const out: MarkdownInlineNode[] = [];\n for (const cur of inlines) {\n const prev = out[out.length - 1];\n if (\n cur.type === 'link' &&\n typeof cur.url === 'string' &&\n prev &&\n prev.type === 'text' &&\n prev.value.endsWith('@')\n ) {\n const schemeMatch = cur.url.match(/^([a-z][a-z0-9+.-]*):(.+)$/i);\n if (schemeMatch && schemeMatch[1] && schemeMatch[2]) {\n const targetKind = schemeMatch[1];\n const targetId = schemeMatch[2];\n const displayName = extractInlineText(cur.children);\n if (prev.value === '@') {\n out.pop();\n } else {\n prev.value = prev.value.slice(0, -1);\n }\n out.push({\n type: 'mention',\n targetKind,\n targetId,\n displayName,\n ...(cur.position ? { position: cur.position } : {}),\n });\n continue;\n }\n }\n out.push(cur);\n }\n return out;\n}\n\n/** Plain-text of an inline children list — used to pull the display name\n * out of a mention link's children. */\nfunction extractInlineText(nodes: MarkdownInlineNode[]): string {\n let out = '';\n for (const n of nodes) {\n if (n.type === 'text' || n.type === 'inlineCode') out += n.value;\n else if ('children' in n && Array.isArray(n.children)) {\n out += extractInlineText(n.children as MarkdownInlineNode[]);\n }\n }\n return out;\n}\n\nfunction convertBlockNode(node: MdastNode, parseHtml: boolean): MarkdownBlockNode | null {\n const pos = convertPosition(node.position);\n\n switch (node.type) {\n case 'heading': {\n // Defer icon splitting so `extractTemplateAnnotation` sees the\n // trailing `{[name]}` as plain text and can decide whether it's\n // a template annotation. Anything that's NOT the template still\n // needs to become an icon node, so we run `splitInlineIcons` on\n // what's left after the template extraction.\n //\n // Both annotation forms ({[…]} and Pandoc {#…}) may appear at the\n // end of the heading in either order. Extract them in a loop so\n // we catch the second-from-last one too. A small bounded retry\n // (max 4) is plenty — authors don't stack more than two trailing\n // brace-blocks in practice.\n const headingChildren = convertInlineChildren(node.children ?? [], parseHtml, false);\n let annotation: HeadingTemplateAnnotation | null = null;\n let attrs: HeadingAttributes | null = null;\n for (let pass = 0; pass < 4; pass++) {\n let matched = false;\n if (!annotation) {\n const t = extractTemplateAnnotation(headingChildren);\n if (t) {\n annotation = t;\n matched = true;\n }\n }\n if (!attrs) {\n const a = extractPandocAttributes(headingChildren);\n if (a) {\n attrs = a;\n matched = true;\n }\n }\n if (!matched) break;\n }\n const finalChildren = splitInlineIcons(headingChildren);\n const result: MarkdownHeading = {\n type: 'heading',\n depth: (node.depth ?? 1) as 1 | 2 | 3 | 4 | 5 | 6,\n children: finalChildren,\n ...posField(pos),\n };\n if (annotation) {\n result.templateAnnotation = annotation;\n }\n if (attrs) {\n result.attributes = attrs;\n }\n return result;\n }\n\n case 'paragraph':\n return {\n type: 'paragraph',\n children: convertInlineChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n case 'blockquote':\n return {\n type: 'blockquote',\n children: convertBlockChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n case 'list':\n return {\n type: 'list',\n ordered: node.ordered ?? false,\n ...(node.start != null ? { start: node.start } : {}),\n ...(node.spread != null ? { spread: node.spread } : {}),\n children: (node.children ?? []).map((item) => convertListItem(item, parseHtml)),\n ...posField(pos),\n };\n\n case 'code':\n return {\n type: 'code',\n ...(node.lang != null ? { lang: node.lang } : {}),\n ...(node.meta != null ? { meta: node.meta } : {}),\n value: node.value ?? '',\n ...posField(pos),\n };\n\n case 'thematicBreak':\n return {\n type: 'thematicBreak',\n ...posField(pos),\n };\n\n case 'table':\n return {\n type: 'table',\n ...(node.align\n ? { align: node.align.map((a) => a as 'left' | 'right' | 'center' | null) }\n : {}),\n children: (node.children ?? []).map((row, i) => convertTableRow(row, i === 0, parseHtml)),\n ...posField(pos),\n };\n\n case 'html':\n return {\n type: 'htmlBlock',\n rawHtml: node.value ?? '',\n htmlChildren: parseHtml ? parseHtmlToNodes(node.value ?? '') : [],\n ...posField(pos),\n };\n\n case 'math':\n return {\n type: 'math',\n value: node.value ?? '',\n ...(node.meta != null ? { meta: node.meta } : {}),\n ...posField(pos),\n };\n\n case 'definition':\n return {\n type: 'definition',\n identifier: node.identifier ?? '',\n ...(node.label != null ? { label: node.label } : {}),\n url: node.url ?? '',\n ...(node.title != null ? { title: node.title } : {}),\n ...posField(pos),\n };\n\n case 'footnoteDefinition':\n return {\n type: 'footnoteDefinition',\n identifier: node.identifier ?? '',\n ...(node.label != null ? { label: node.label } : {}),\n children: convertBlockChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n case 'containerDirective': {\n const allChildren = node.children ?? [];\n let label: string | undefined;\n let contentChildren = allChildren;\n\n // Extract directive label (first paragraph with directiveLabel flag)\n if (\n allChildren.length > 0 &&\n allChildren[0].type === 'paragraph' &&\n allChildren[0].data?.directiveLabel === true\n ) {\n label = extractText(allChildren[0]);\n contentChildren = allChildren.slice(1);\n }\n\n return {\n type: 'containerDirective',\n name: node.name ?? '',\n ...(label ? { label } : {}),\n ...(node.attributes && Object.keys(node.attributes).length > 0\n ? { attributes: node.attributes }\n : {}),\n children: convertBlockChildren(contentChildren, parseHtml),\n ...posField(pos),\n };\n }\n\n case 'leafDirective':\n return {\n type: 'leafDirective',\n name: node.name ?? '',\n ...(node.attributes && Object.keys(node.attributes).length > 0\n ? { attributes: node.attributes }\n : {}),\n children: convertInlineChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n case 'yaml':\n // YAML frontmatter nodes are handled separately in parse.ts — skip silently\n return null;\n\n default:\n // Unknown block node — skip with a warning\n if (typeof console !== 'undefined') {\n console.warn(`[squisq/markdown] Unknown mdast block node type: \"${node.type}\"`);\n }\n return null;\n }\n}\n\nfunction convertListItem(node: MdastNode, parseHtml: boolean): MarkdownListItem {\n const pos = convertPosition(node.position);\n return {\n type: 'listItem',\n ...(node.checked != null ? { checked: node.checked } : {}),\n ...(node.spread != null ? { spread: node.spread } : {}),\n children: convertBlockChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n}\n\nfunction convertTableRow(node: MdastNode, isHeader: boolean, parseHtml: boolean): MarkdownTableRow {\n const pos = convertPosition(node.position);\n return {\n type: 'tableRow',\n children: (node.children ?? []).map((cell) => convertTableCell(cell, isHeader, parseHtml)),\n ...posField(pos),\n };\n}\n\nfunction convertTableCell(\n node: MdastNode,\n isHeader: boolean,\n parseHtml: boolean,\n): MarkdownTableCell {\n const pos = convertPosition(node.position);\n return {\n type: 'tableCell',\n ...(isHeader ? { isHeader: true } : {}),\n children: convertInlineChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n}\n\nfunction convertInlineNode(node: MdastNode, parseHtml: boolean): MarkdownInlineNode | null {\n const pos = convertPosition(node.position);\n\n switch (node.type) {\n case 'text':\n return {\n type: 'text',\n value: node.value ?? '',\n ...posField(pos),\n };\n\n case 'emphasis':\n return {\n type: 'emphasis',\n children: convertInlineChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n case 'strong':\n return {\n type: 'strong',\n children: convertInlineChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n case 'delete':\n return {\n type: 'delete',\n children: convertInlineChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n case 'inlineCode':\n return {\n type: 'inlineCode',\n value: node.value ?? '',\n ...posField(pos),\n };\n\n case 'link':\n return {\n type: 'link',\n url: node.url ?? '',\n ...(node.title != null ? { title: node.title } : {}),\n children: convertInlineChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n case 'image':\n return {\n type: 'image',\n url: node.url ?? '',\n ...(node.alt != null ? { alt: node.alt } : {}),\n ...(node.title != null ? { title: node.title } : {}),\n ...posField(pos),\n };\n\n case 'break':\n return {\n type: 'break',\n ...posField(pos),\n };\n\n case 'html':\n return {\n type: 'htmlInline',\n rawHtml: node.value ?? '',\n htmlChildren: parseHtml ? parseHtmlToNodes(node.value ?? '') : [],\n ...posField(pos),\n };\n\n case 'inlineMath':\n return {\n type: 'inlineMath',\n value: node.value ?? '',\n ...posField(pos),\n };\n\n case 'footnoteReference':\n return {\n type: 'footnoteReference',\n identifier: node.identifier ?? '',\n ...(node.label != null ? { label: node.label } : {}),\n ...posField(pos),\n };\n\n case 'linkReference':\n return {\n type: 'linkReference',\n identifier: node.identifier ?? '',\n ...(node.label != null ? { label: node.label } : {}),\n referenceType: (node.referenceType ?? 'full') as 'full' | 'collapsed' | 'shortcut',\n children: convertInlineChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n case 'imageReference':\n return {\n type: 'imageReference',\n identifier: node.identifier ?? '',\n ...(node.label != null ? { label: node.label } : {}),\n referenceType: (node.referenceType ?? 'full') as 'full' | 'collapsed' | 'shortcut',\n ...(node.alt != null ? { alt: node.alt } : {}),\n ...posField(pos),\n };\n\n case 'textDirective':\n return {\n type: 'textDirective',\n name: node.name ?? '',\n ...(node.attributes && Object.keys(node.attributes).length > 0\n ? { attributes: node.attributes }\n : {}),\n children: convertInlineChildren(node.children ?? [], parseHtml),\n ...posField(pos),\n };\n\n default:\n if (typeof console !== 'undefined') {\n console.warn(`[squisq/markdown] Unknown mdast inline node type: \"${node.type}\"`);\n }\n return null;\n }\n}\n\n// ============================================\n// Squisq → mdast (toMdast)\n// ============================================\n\n/**\n * Convert a MarkdownDocument back to an mdast Root node.\n *\n * @param doc - A MarkdownDocument\n * @returns An mdast Root node suitable for remark-stringify\n */\nexport function toMdast(doc: MarkdownDocument): MdastNode {\n return {\n type: 'root',\n children: doc.children.map((n) => blockToMdast(n)),\n ...mdastPosField(doc.position),\n };\n}\n\nfunction blockToMdast(node: MarkdownBlockNode): MdastNode {\n switch (node.type) {\n case 'heading': {\n const mdastChildren = inlineChildrenToMdast(node.children);\n // Canonical trailing order: Pandoc attrs first, then template annotation.\n // Author-supplied order is not preserved across round-trips by design —\n // canonicalization gives stable diffs.\n const suffixes: string[] = [];\n if (node.attributes) {\n const pandoc = serializePandocAttributes(node.attributes);\n if (pandoc != null && (pandoc !== '{}' || hasPrintablePandocAttributes(node.attributes))) {\n suffixes.push(pandoc);\n }\n }\n if (node.templateAnnotation) {\n suffixes.push(serializeTemplateAnnotation(node.templateAnnotation));\n }\n if (suffixes.length > 0) {\n const suffix = suffixes.join(' ');\n const lastChild = mdastChildren[mdastChildren.length - 1];\n if (lastChild && lastChild.type === 'text') {\n lastChild.value = (lastChild.value ?? '') + ' ' + suffix;\n } else {\n mdastChildren.push({ type: 'text', value: ' ' + suffix });\n }\n }\n return {\n type: 'heading',\n depth: node.depth,\n children: mdastChildren,\n ...mdastPosField(node.position),\n };\n }\n\n case 'paragraph':\n return {\n type: 'paragraph',\n children: inlineChildrenToMdast(node.children),\n ...mdastPosField(node.position),\n };\n\n case 'blockquote':\n return {\n type: 'blockquote',\n children: node.children.map(blockToMdast),\n ...mdastPosField(node.position),\n };\n\n case 'list':\n return {\n type: 'list',\n ordered: node.ordered,\n ...(node.start != null ? { start: node.start } : {}),\n ...(node.spread != null ? { spread: node.spread } : {}),\n children: node.children.map(listItemToMdast),\n ...mdastPosField(node.position),\n };\n\n case 'code':\n return {\n type: 'code',\n ...(node.lang != null ? { lang: node.lang } : {}),\n ...(node.meta != null ? { meta: node.meta } : {}),\n value: node.value,\n ...mdastPosField(node.position),\n };\n\n case 'thematicBreak':\n return {\n type: 'thematicBreak',\n ...mdastPosField(node.position),\n };\n\n case 'table':\n return {\n type: 'table',\n ...(node.align ? { align: node.align } : {}),\n children: node.children.map(tableRowToMdast),\n ...mdastPosField(node.position),\n };\n\n case 'htmlBlock':\n return {\n type: 'html',\n value: node.rawHtml,\n ...mdastPosField(node.position),\n };\n\n case 'math':\n return {\n type: 'math',\n value: node.value,\n ...(node.meta != null ? { meta: node.meta } : {}),\n ...mdastPosField(node.position),\n };\n\n case 'definition':\n return {\n type: 'definition',\n identifier: node.identifier,\n ...(node.label != null ? { label: node.label } : {}),\n url: node.url,\n ...(node.title != null ? { title: node.title } : {}),\n ...mdastPosField(node.position),\n };\n\n case 'footnoteDefinition':\n return {\n type: 'footnoteDefinition',\n identifier: node.identifier,\n ...(node.label != null ? { label: node.label } : {}),\n children: node.children.map(blockToMdast),\n ...mdastPosField(node.position),\n };\n\n case 'containerDirective': {\n const children: MdastNode[] = [];\n\n // Reinject label as first paragraph with directiveLabel flag\n if (node.label) {\n children.push({\n type: 'paragraph',\n data: { directiveLabel: true },\n children: [{ type: 'text', value: node.label }],\n });\n }\n\n children.push(...node.children.map(blockToMdast));\n\n return {\n type: 'containerDirective',\n name: node.name,\n ...(node.attributes ? { attributes: node.attributes } : {}),\n children,\n ...mdastPosField(node.position),\n };\n }\n\n case 'leafDirective':\n return {\n type: 'leafDirective',\n name: node.name,\n ...(node.attributes ? { attributes: node.attributes } : {}),\n children: inlineChildrenToMdast(node.children),\n ...mdastPosField(node.position),\n };\n\n case 'definitionList':\n // Definition lists don't map to standard mdast.\n // Convert to HTML as fallback.\n return {\n type: 'html',\n value: definitionListToHtml(node),\n ...mdastPosField(node.position),\n };\n\n default:\n return { type: 'html', value: '' };\n }\n}\n\nfunction listItemToMdast(item: MarkdownListItem): MdastNode {\n return {\n type: 'listItem',\n ...(item.checked != null ? { checked: item.checked } : {}),\n ...(item.spread != null ? { spread: item.spread } : {}),\n children: item.children.map(blockToMdast),\n ...mdastPosField(item.position),\n };\n}\n\nfunction tableRowToMdast(row: MarkdownTableRow): MdastNode {\n return {\n type: 'tableRow',\n children: row.children.map(tableCellToMdast),\n ...mdastPosField(row.position),\n };\n}\n\nfunction tableCellToMdast(cell: MarkdownTableCell): MdastNode {\n return {\n type: 'tableCell',\n children: inlineChildrenToMdast(cell.children),\n ...mdastPosField(cell.position),\n };\n}\n\n/**\n * Serialize an array of inline nodes to mdast, flat-mapping the result so\n * a single `mention` expands to two mdast nodes (`text('@')` + `link`).\n * Use in place of `.map(inlineToMdast)` wherever we walk a children array.\n */\nfunction inlineChildrenToMdast(nodes: MarkdownInlineNode[]): MdastNode[] {\n const out: MdastNode[] = [];\n for (const n of nodes) {\n if (n.type === 'mention') {\n out.push({ type: 'text', value: '@' });\n out.push({\n type: 'link',\n url: `${n.targetKind}:${n.targetId}`,\n children: [{ type: 'text', value: n.displayName }],\n ...mdastPosField(n.position),\n });\n } else {\n out.push(inlineToMdast(n));\n }\n }\n return out;\n}\n\nfunction inlineToMdast(node: MarkdownInlineNode): MdastNode {\n switch (node.type) {\n case 'text':\n return {\n type: 'text',\n value: node.value,\n ...mdastPosField(node.position),\n };\n\n case 'emphasis':\n return {\n type: 'emphasis',\n children: inlineChildrenToMdast(node.children),\n ...mdastPosField(node.position),\n };\n\n case 'strong':\n return {\n type: 'strong',\n children: inlineChildrenToMdast(node.children),\n ...mdastPosField(node.position),\n };\n\n case 'delete':\n return {\n type: 'delete',\n children: inlineChildrenToMdast(node.children),\n ...mdastPosField(node.position),\n };\n\n case 'inlineCode':\n return {\n type: 'inlineCode',\n value: node.value,\n ...mdastPosField(node.position),\n };\n\n case 'link':\n return {\n type: 'link',\n url: node.url,\n ...(node.title != null ? { title: node.title } : {}),\n children: inlineChildrenToMdast(node.children),\n ...mdastPosField(node.position),\n };\n\n case 'image':\n return {\n type: 'image',\n url: node.url,\n ...(node.alt != null ? { alt: node.alt } : {}),\n ...(node.title != null ? { title: node.title } : {}),\n ...mdastPosField(node.position),\n };\n\n case 'break':\n return {\n type: 'break',\n ...mdastPosField(node.position),\n };\n\n case 'htmlInline':\n return {\n type: 'html',\n value: node.rawHtml,\n ...mdastPosField(node.position),\n };\n\n case 'inlineMath':\n return {\n type: 'inlineMath',\n value: node.value,\n ...mdastPosField(node.position),\n };\n\n case 'footnoteReference':\n return {\n type: 'footnoteReference',\n identifier: node.identifier,\n ...(node.label != null ? { label: node.label } : {}),\n ...mdastPosField(node.position),\n };\n\n case 'linkReference':\n return {\n type: 'linkReference',\n identifier: node.identifier,\n ...(node.label != null ? { label: node.label } : {}),\n referenceType: node.referenceType,\n children: inlineChildrenToMdast(node.children),\n ...mdastPosField(node.position),\n };\n\n case 'imageReference':\n return {\n type: 'imageReference',\n identifier: node.identifier,\n ...(node.label != null ? { label: node.label } : {}),\n referenceType: node.referenceType,\n ...(node.alt != null ? { alt: node.alt } : {}),\n ...mdastPosField(node.position),\n };\n\n case 'textDirective':\n return {\n type: 'textDirective',\n name: node.name,\n ...(node.attributes ? { attributes: node.attributes } : {}),\n children: inlineChildrenToMdast(node.children),\n ...mdastPosField(node.position),\n };\n\n case 'mention':\n // Fallback: when `inlineToMdast` is called directly (not via\n // `inlineChildrenToMdast`), collapse the mention to just the link\n // form. The `@` prefix is lost, but the id+name still round-trip.\n // Normal call sites go through `inlineChildrenToMdast` and get\n // the proper text('@') + link pair.\n return {\n type: 'link',\n url: `${node.targetKind}:${node.targetId}`,\n children: [{ type: 'text', value: node.displayName }],\n ...mdastPosField(node.position),\n };\n\n case 'inlineIcon':\n // Serialize back to the authored token so source round-trips\n // byte-stable. The token already carries the qualified form when\n // required (the parser preserved it verbatim).\n return {\n type: 'text',\n value: `{[${node.token}]}`,\n ...mdastPosField(node.position),\n };\n\n default:\n return { type: 'text', value: '' };\n }\n}\n\n// ============================================\n// Helpers\n// ============================================\n\n/**\n * Convert a MarkdownDefinitionList to an HTML <dl> string.\n * Fallback for serialization since mdast doesn't support definition lists.\n */\nfunction definitionListToHtml(list: {\n children: Array<{ type: string; children?: unknown[] }>;\n}): string {\n let html = '<dl>\\n';\n for (const child of list.children) {\n if (child.type === 'definitionTerm') {\n // Extract plain text from inline children\n const text = (child.children as Array<{ value?: string; children?: unknown[] }>)\n .map((c) => c.value ?? '')\n .join('');\n html += ` <dt>${text}</dt>\\n`;\n } else if (child.type === 'definitionDescription') {\n html += ` <dd>...</dd>\\n`;\n }\n }\n html += '</dl>';\n return html;\n}\n","/**\n * Markdown → JSON Parser\n *\n * Parses a markdown string into a MarkdownDocument JSON structure\n * using the unified/remark ecosystem with GFM, math, and directive extensions.\n */\n\nimport { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkGfm from 'remark-gfm';\nimport remarkMath from 'remark-math';\nimport remarkDirective from 'remark-directive';\nimport remarkFrontmatter from 'remark-frontmatter';\nimport type { MarkdownDocument, ParseOptions } from './types.js';\nimport { fromMdast } from './convert.js';\nimport { parseFrontmatter } from './utils.js';\n\n// Cache the default processor (all extensions enabled) to avoid rebuilding on every call.\nlet defaultProcessor: any;\n\n/**\n * Parse a markdown string into a MarkdownDocument.\n *\n * All extensions (GFM, math, directives) are enabled by default.\n * Use the `options` parameter to disable specific extensions.\n *\n * @param markdown - The markdown source text\n * @param options - Parser options (extensions, HTML parsing)\n * @returns A MarkdownDocument representing the parsed markdown\n *\n * @example\n * ```ts\n * const doc = parseMarkdown('# Hello\\n\\nWorld **bold** and *italic*');\n * // doc.type === 'document'\n * // doc.children[0].type === 'heading'\n * // doc.children[1].type === 'paragraph'\n * ```\n */\nexport function parseMarkdown(markdown: string, options?: ParseOptions): MarkdownDocument {\n // Use cached default processor when all extensions are enabled (the common case).\n const useDefaults =\n !options ||\n (options.gfm !== false &&\n options.math !== false &&\n options.directive !== false &&\n options.frontmatter !== false);\n\n let processor: any;\n\n if (useDefaults) {\n if (!defaultProcessor) {\n defaultProcessor = unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkMath)\n .use(remarkDirective)\n .use(remarkFrontmatter, ['yaml']);\n }\n processor = defaultProcessor;\n } else {\n // Build a custom processor with requested extensions.\n // unified's .use() chaining changes the generic signature each time,\n // making strict typing impractical — use a widened Processor type.\n processor = unified().use(remarkParse);\n\n if (options?.gfm !== false) {\n processor = processor.use(remarkGfm);\n }\n if (options?.math !== false) {\n processor = processor.use(remarkMath);\n }\n if (options?.directive !== false) {\n processor = processor.use(remarkDirective);\n }\n if (options?.frontmatter !== false) {\n processor = processor.use(remarkFrontmatter, ['yaml']);\n }\n }\n\n // Parse markdown → mdast tree (result is an mdast Root node)\n const mdastTree = processor.parse(markdown) as {\n type: string;\n children?: Array<{ type: string; value?: string }>;\n };\n\n // Convert mdast → MarkdownDocument\n const doc = fromMdast(mdastTree as Parameters<typeof fromMdast>[0], {\n parseHtml: options?.parseHtml,\n });\n\n // Extract YAML frontmatter if present\n if (options?.frontmatter !== false) {\n const yamlNode = mdastTree.children?.find((n) => n.type === 'yaml');\n if (yamlNode?.value) {\n const fm = parseFrontmatter(yamlNode.value);\n if (fm && Object.keys(fm).length > 0) {\n doc.frontmatter = fm;\n }\n }\n }\n\n return doc;\n}\n"],"mappings":";;;;;;;;;;;;;;;AA8BO,IAAM,wBAAwB;AAAA,EACnC,GAAG;AAAA,EACH,GAAG;AAAA,EACH,WAAW;AAAA,EACX,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,qBAAqB;AACvB;AA4BO,IAAM,6BAAgE;AAAA,EAC3E;AAAA,IACE,KAAK;AAAA,IACL,aAAa;AAAA,IACb,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,aAAa;AAAA,IACb,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,aAAa;AAAA,IACb,QAAQ;AAAA,EACV;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,aAAa;AAAA,IACb,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,aAAa;AAAA,IACb,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,aAAa;AAAA,IACb,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,aAAa;AAAA,IACb,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,aAAa;AAAA,IACb,WAAW;AAAA,EACb;AACF;AAgCO,SAAS,uBAAuB,QAA8C;AACnF,QAAM,YAA8B,CAAC;AACrC,QAAM,WAAmC,CAAC;AAC1C,QAAM,WAAqB,CAAC;AAC5B,MAAI,iBAAwC;AAC5C,MAAI;AACJ,MAAI;AAEJ,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,UAAM,OAAQ,sBAAiD,GAAG;AAClE,QAAI,CAAC,MAAM;AACT,eAAS,GAAG,IAAI;AAChB;AAAA,IACF;AACA,QAAI,SAAS,UAAU;AACrB,YAAM,IAAI,YAAY,GAAG;AACzB,UAAI,KAAK,MAAM;AACb,iBAAS,KAAK,uBAAuB,GAAG,MAAM,KAAK,UAAU,GAAG,CAAC,EAAE;AAAA,MACrE,OAAO;AACL,QAAC,UAAsC,GAAG,IAAI;AAAA,MAChD;AAAA,IACF,WAAW,SAAS,QAAQ;AAC1B,YAAM,IAAI,iBAAiB,GAAG;AAC9B,UAAI,KAAK,MAAM;AACb,iBAAS,KAAK,qBAAqB,GAAG,MAAM,KAAK,UAAU,GAAG,CAAC,EAAE;AAAA,MACnE,WAAW,QAAQ,sBAAsB;AACvC,6BAAqB;AAAA,MACvB,OAAO;AACL,QAAC,UAAsC,GAAG,IAAI;AAAA,MAChD;AAAA,IACF,WAAW,SAAS,kBAAkB;AACpC,YAAM,EAAE,MAAM,QAAQ,IAAI,oBAAoB,GAAG;AACjD,UAAI,QAAS,UAAS,KAAK,IAAI,GAAG,MAAM,OAAO,EAAE;AACjD,gBAAU,aAAa;AAAA,IACzB,WAAW,SAAS,cAAc;AAChC,YAAM,aAAa,wBAAwB,GAAG;AAC9C,UAAI,cAAc,MAAM;AACtB,iBAAS,KAAK,2BAA2B,GAAG,MAAM,KAAK,UAAU,GAAG,CAAC,EAAE;AAAA,MACzE,OAAO;AACL,yBAAiB;AAAA,MACnB;AAAA,IACF,WAAW,SAAS,uBAAuB;AACzC,YAAM,YAAY,6BAA6B,GAAG;AAClD,UAAI,aAAa,MAAM;AACrB,iBAAS,KAAK,qCAAqC,GAAG,MAAM,KAAK,UAAU,GAAG,CAAC,EAAE;AAAA,MACnF,OAAO;AACL,8BAAsB;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,gBAAgB;AAClB,cAAU,aAAa;AAAA,MACrB,MAAM;AAAA,MACN,GAAI,uBAAuB,SAAY,EAAE,UAAU,mBAAmB,IAAI,CAAC;AAAA,MAC3E,GAAI,wBAAwB,SAAY,EAAE,WAAW,oBAAoB,IAAI,CAAC;AAAA,IAChF;AAAA,EACF;AAEA,SAAO,EAAE,WAAW,UAAU,SAAS;AACzC;AAOO,SAAS,YAAY,KAA4B;AACtD,MAAI,IAAI,KAAK,MAAM,GAAI,QAAO;AAC9B,QAAM,IAAI,OAAO,GAAG;AACpB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAEA,IAAM,eAAe;AACrB,IAAM,aAAa;AACnB,IAAM,eAAe;AAWd,SAAS,iBAAiB,KAA4B;AAC3D,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,CAAC,QAAS,QAAO;AAErB,MAAI,aAAa,KAAK,OAAO,GAAG;AAC9B,UAAM,IAAI,OAAO,OAAO;AACxB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AAEA,QAAM,UAAU,QAAQ,MAAM,UAAU;AACxC,MAAI,SAAS;AACX,UAAM,IAAI,OAAO,QAAQ,CAAC,CAAC;AAC3B,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI,MAAO;AAAA,EACzC;AAEA,QAAM,YAAY,QAAQ,MAAM,YAAY;AAC5C,MAAI,WAAW;AACb,UAAM,OAAO,OAAO,UAAU,CAAC,CAAC;AAChC,UAAM,OAAO,OAAO,UAAU,CAAC,CAAC;AAChC,UAAM,OAAO,UAAU,CAAC,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,EAAE,IAAI;AAC1D,QAAI,CAAC,OAAO,SAAS,IAAI,KAAK,CAAC,OAAO,SAAS,IAAI,KAAK,CAAC,OAAO,SAAS,IAAI,GAAG;AAC9E,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,GAAI,QAAO;AACvB,WAAO,OAAO,KAAK,OAAO;AAAA,EAC5B;AAEA,SAAO;AACT;AAOO,SAAS,oBAAoB,KAGlC;AACA,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,CAAC,QAAS,QAAO,EAAE,MAAM,CAAC,GAAG,SAAS,KAAK;AAE/C,QAAM,OAA0B,CAAC;AACjC,MAAI,eAAe;AACnB,aAAW,QAAQ,QAAQ,MAAM,GAAG,GAAG;AACrC,UAAM,QAAQ,KAAK,KAAK;AACxB,QAAI,CAAC,OAAO;AACV,qBAAe;AACf;AAAA,IACF;AACA,UAAM,WAAW,MAAM,QAAQ,GAAG;AAClC,QAAI,WAAW,GAAG;AAChB,WAAK,KAAK,EAAE,QAAQ,MAAM,CAAC;AAAA,IAC7B,OAAO;AACL,YAAM,SAAS,MAAM,MAAM,GAAG,QAAQ,EAAE,KAAK;AAC7C,YAAM,OAAO,MAAM,MAAM,WAAW,CAAC,EAAE,KAAK;AAC5C,UAAI,CAAC,QAAQ;AACX,uBAAe;AACf;AAAA,MACF;AACA,WAAK,KAAK,OAAO,EAAE,QAAQ,KAAK,IAAI,EAAE,OAAO,CAAC;AAAA,IAChD;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS,eAAe,qCAAqC;AAAA,EAC/D;AACF;;;ACrQA,IAAM,SAAS;AAEf,IAAM,SAAS;AAQf,IAAM,qBAAqB,IAAI;AAAA,EAC7B,0BAA0B,MAAM,IAAI,MAAM;AAC5C;AAOA,IAAM,qBAAqB;AAO3B,IAAM,mBAAmB,IAAI,OAAO,4BAA4B,MAAM,IAAI,MAAM,aAAa;AAG7F,IAAM,mBAAmB;AAUzB,SAAS,cACP,MACA,QACA,QACgC;AAChC,QAAM,IAAI,OAAO,KAAK,IAAI,KAAK,OAAO,KAAK,IAAI;AAC/C,SAAO,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,MAAM,IAAI;AAC/C;AAOO,SAAS,gCAAgC,MAA8C;AAC5F,SAAO,cAAc,MAAM,oBAAoB,kBAAkB;AACnE;AAMO,SAAS,wBAAwB,MAA8C;AACpF,SAAO,cAAc,MAAM,kBAAkB,gBAAgB;AAC/D;AAMA,IAAM,gBAAgB;AAcf,SAAS,mBAAmB,OAAyB;AAC1D,QAAM,SAAmB,CAAC;AAC1B,MAAI,IAAI;AACR,QAAM,IAAI,MAAM;AAChB,SAAO,IAAI,GAAG;AACZ,WAAO,IAAI,KAAK,cAAc,KAAK,MAAM,CAAC,CAAC,EAAG;AAC9C,QAAI,KAAK,EAAG;AAEZ,QAAI,QAAQ;AACZ,WAAO,IAAI,KAAK,CAAC,cAAc,KAAK,MAAM,CAAC,CAAC,GAAG;AAC7C,YAAM,KAAK,MAAM,CAAC;AAClB,YAAM,eAAe,UAAU,MAAM,MAAM,SAAS,GAAG;AACvD,WAAK,OAAO,OAAO,OAAO,QAAQ,cAAc;AAC9C,iBAAS;AACT;AACA,eAAO,IAAI,KAAK,MAAM,CAAC,MAAM,IAAI;AAC/B,cAAI,MAAM,CAAC,MAAM,QAAQ,IAAI,IAAI,MAAM,MAAM,IAAI,CAAC,MAAM,MAAM,MAAM,IAAI,CAAC,MAAM,OAAO;AACpF,qBAAS,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;AAC/B,iBAAK;AAAA,UACP,OAAO;AACL,qBAAS,MAAM,CAAC;AAChB;AAAA,UACF;AAAA,QACF;AACA,YAAI,IAAI,KAAK,MAAM,CAAC,MAAM,IAAI;AAC5B,mBAAS;AACT;AAAA,QACF;AAAA,MACF,OAAO;AACL,iBAAS;AACT;AAAA,MACF;AAAA,IACF;AACA,QAAI,MAAO,QAAO,KAAK,KAAK;AAAA,EAC9B;AACA,SAAO;AACT;AAMA,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AAOhB,SAAS,iBAAiB,KAAqB;AACpD,MAAI,IAAI,UAAU,GAAG;AACnB,UAAM,IAAI,IAAI,CAAC;AACf,SAAK,MAAM,OAAO,MAAM,QAAQ,IAAI,SAAS,CAAC,GAAG;AAC/C,YAAM,OAAO,IAAI,MAAM,GAAG,EAAE;AAC5B,aAAO,KAAK,QAAQ,MAAM,MAAM,iBAAiB,gBAAgB,IAAI;AAAA,IACvE;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,mBAAmB,OAAsD;AACvF,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,MAAI,SAAS,EAAG,QAAO;AACvB,SAAO,EAAE,KAAK,MAAM,MAAM,GAAG,KAAK,GAAG,OAAO,iBAAiB,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE;AACvF;AAMA,IAAM,mBAAmB;AACzB,IAAM,eAAe;AACrB,IAAM,eAAe;AAOd,SAAS,aAAa,OAAwB;AACnD,SAAO,UAAU,MAAM,iBAAiB,KAAK,KAAK;AACpD;AAYO,SAAS,eAAe,OAAuB;AACpD,MAAI,CAAC,aAAa,KAAK,EAAG,QAAO;AACjC,MAAI,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,SAAS,GAAG,GAAG;AAC/C,WAAO,IAAI,MAAM,QAAQ,cAAc,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;AAAA,EAC3D;AACA,SAAO,IAAI,MAAM,QAAQ,cAAc,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;AAC3D;AASO,SAAS,oBACd,UACA,QACQ;AACR,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAU,OAAM,KAAK,QAAQ;AACjC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,CAAC,CAAC,GAAG;AACvD,UAAM,KAAK,GAAG,GAAG,IAAI,eAAe,KAAK,CAAC,EAAE;AAAA,EAC9C;AACA,SAAO,KAAK,MAAM,KAAK,GAAG,CAAC;AAC7B;;;AClKA,SAAS,gBAAgB,KAAyD;AAChF,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO;AAAA,IACL,OAAO;AAAA,MACL,MAAM,IAAI,MAAM;AAAA,MAChB,QAAQ,IAAI,MAAM;AAAA,MAClB,GAAI,IAAI,MAAM,UAAU,OAAO,EAAE,QAAQ,IAAI,MAAM,OAAO,IAAI,CAAC;AAAA,IACjE;AAAA,IACA,KAAK;AAAA,MACH,MAAM,IAAI,IAAI;AAAA,MACd,QAAQ,IAAI,IAAI;AAAA,MAChB,GAAI,IAAI,IAAI,UAAU,OAAO,EAAE,QAAQ,IAAI,IAAI,OAAO,IAAI,CAAC;AAAA,IAC7D;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,KAA4C;AACnE,SAAO;AAAA,IACL,OAAO;AAAA,MACL,MAAM,IAAI,MAAM;AAAA,MAChB,QAAQ,IAAI,MAAM;AAAA,MAClB,GAAI,IAAI,MAAM,UAAU,OAAO,EAAE,QAAQ,IAAI,MAAM,OAAO,IAAI,CAAC;AAAA,IACjE;AAAA,IACA,KAAK;AAAA,MACH,MAAM,IAAI,IAAI;AAAA,MACd,QAAQ,IAAI,IAAI;AAAA,MAChB,GAAI,IAAI,IAAI,UAAU,OAAO,EAAE,QAAQ,IAAI,IAAI,OAAO,IAAI,CAAC;AAAA,IAC7D;AAAA,EACF;AACF;AAGA,SAAS,SACP,KAC8D;AAC9D,SAAO,MAAM,EAAE,UAAU,IAAI,IAAI,CAAC;AACpC;AAEA,SAAS,cACP,KACqD;AACrD,SAAO,MAAM,EAAE,UAAU,gBAAgB,GAAG,EAAE,IAAI,CAAC;AACrD;AAUA,SAAS,YAAY,MAAyB;AAC5C,MAAI,KAAK,SAAS,KAAM,QAAO,KAAK;AACpC,MAAI,KAAK,UAAU;AACjB,WAAO,KAAK,SAAS,IAAI,WAAW,EAAE,KAAK,EAAE;AAAA,EAC/C;AACA,SAAO;AACT;AAkBA,SAAS,0BACP,UACkC;AAElC,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,QAAQ,SAAS,CAAC;AACxB,QAAI,MAAM,SAAS,QAAQ;AACzB,YAAM,QAAQ,gCAAgC,MAAM,KAAK;AACzD,UAAI,OAAO;AACT,cAAM,QAAQ,MAAM,MAAM,KAAK;AAC/B,cAAM,aAAa,sBAAsB,KAAK;AAG9C,cAAM,WAAW,MAAM,MAAM,MAAM,GAAG,MAAM,KAAK,EAAE,QAAQ,QAAQ,EAAE;AACrE,YAAI,UAAU;AACZ,UAAC,MAA4B,QAAQ;AAAA,QACvC,OAAO;AAEL,mBAAS,OAAO,GAAG,CAAC;AAAA,QACtB;AACA,eAAO;AAAA,MACT;AAEA;AAAA,IACF;AAEA;AAAA,EACF;AACA,SAAO;AACT;AAYA,SAAS,sBAAsB,OAA0C;AACvE,QAAM,SAAS,mBAAmB,KAAK;AACvC,QAAM,SAAiC,CAAC;AAIxC,QAAM,eAAe,OAAO,SAAS,KAAK,OAAO,CAAC,EAAE,QAAQ,GAAG,IAAI;AACnE,QAAM,WAAW,gBAAgB,OAAO,WAAW,IAAI,SAAY,OAAO,CAAC;AAC3E,QAAM,WAAW,eAAe,IAAI;AAEpC,WAAS,IAAI,UAAU,IAAI,OAAO,QAAQ,KAAK;AAC7C,UAAM,KAAK,mBAAmB,OAAO,CAAC,CAAC;AACvC,QAAI,IAAI;AACN,aAAO,GAAG,GAAG,IAAI,GAAG;AAAA,IACtB;AAAA,EACF;AAEA,QAAM,SAAoC,CAAC;AAC3C,MAAI,UAAU;AACZ,WAAO,WAAW;AAAA,EACpB;AACA,MAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAClC,WAAO,SAAS;AAAA,EAClB;AACA,SAAO;AACT;AAwBA,SAAS,wBAAwB,UAA0D;AAEzF,QAAM,QAAqD,CAAC;AAC5D,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,IAAI,SAAS,CAAC;AACpB,QAAI,EAAE,SAAS,QAAQ;AACrB,YAAM,QAAQ,EAAE,KAAK,EAAE,OAAO,KAAK,GAAG,KAAK,EAAE,MAAM,OAAO,CAAC;AAAA,IAC7D,WAAW,EAAE,SAAS,iBAAiB;AACrC,YAAM,KAAK;AAKX,YAAM,UACH,CAAC,GAAG,YAAY,GAAG,SAAS,WAAW,OACvC,CAAC,GAAG,cAAc,OAAO,KAAK,GAAG,UAAU,EAAE,WAAW;AAC3D,UAAI,CAAC,OAAQ;AACb,YAAM,cAAc,OAAO,GAAG,QAAQ;AACtC,YAAM,QAAQ,EAAE,KAAK,aAAa,KAAK,GAAG,KAAK,YAAY,OAAO,CAAC;AAAA,IACrE,OAAO;AACL;AAAA,IACF;AAAA,EACF;AACA,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE;AAC9C,QAAM,QAAQ,wBAAwB,MAAM;AAC5C,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,QAAQ,MAAM,MAAM,KAAK;AAC/B,QAAM,QAAQ,sBAAsB,KAAK;AAMzC,QAAM,aAAa,MAAM;AACzB,MAAI,aAAa;AACjB,WAAS,KAAK,GAAG,KAAK,MAAM,QAAQ,MAAM;AACxC,UAAM,IAAI,MAAM,EAAE;AAClB,QAAI,aAAa,EAAE,OAAO,YAAY;AACpC,oBAAc,EAAE;AAChB;AAAA,IACF;AACA,UAAM,YAAY,SAAS,EAAE,GAAG;AAChC,UAAM,UAAU,aAAa;AAC7B,QAAI,UAAU,SAAS,QAAQ;AAC7B,YAAM,UAAU,UAAU,MAAM,MAAM,GAAG,OAAO,EAAE,QAAQ,QAAQ,EAAE;AACpE,UAAI,SAAS;AACX,QAAC,UAAgC,QAAQ;AACzC,iBAAS,OAAO,EAAE,MAAM,CAAC;AAAA,MAC3B,OAAO;AACL,iBAAS,OAAO,EAAE,GAAG;AAAA,MACvB;AAAA,IACF,OAAO;AAGL,eAAS,OAAO,EAAE,GAAG;AAAA,IACvB;AACA,WAAO;AAAA,EACT;AAEA,WAAS,OAAO,MAAM,CAAC,EAAE,GAAG;AAC5B,SAAO;AACT;AAeO,SAAS,sBAAsB,OAAkC;AACtE,QAAM,SAAS,mBAAmB,KAAK;AACvC,QAAM,QAA2B,CAAC;AAClC,QAAM,SAAiC,CAAC;AACxC,QAAM,UAAoB,CAAC;AAE3B,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,SAAS,GAAG,GAAG;AACjD,YAAM,KAAK,MAAM,MAAM,CAAC;AAAA,IAC1B,WAAW,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,SAAS,GAAG,GAAG;AACxD,YAAM,MAAM,MAAM,MAAM,CAAC;AACzB,UAAI,IAAK,SAAQ,KAAK,GAAG;AAAA,IAC3B,OAAO;AACL,YAAM,KAAK,mBAAmB,KAAK;AACnC,UAAI,IAAI;AACN,eAAO,GAAG,GAAG,IAAI,GAAG;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,SAAS,EAAG,OAAM,UAAU;AACxC,MAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAClC,UAAM,SAAS;AACf,UAAM,EAAE,WAAW,SAAS,IAAI,uBAAuB,MAAM;AAC7D,QAAI,OAAO,KAAK,SAAS,EAAE,SAAS,EAAG,OAAM,YAAY;AACzD,QAAI,OAAO,KAAK,QAAQ,EAAE,SAAS,EAAG,OAAM,WAAW;AAAA,EACzD;AACA,SAAO;AACT;AAUO,SAAS,0BAA0B,OAAyC;AACjF,QAAM,QAAkB,CAAC;AACzB,MAAI,MAAM,GAAI,OAAM,KAAK,IAAI,MAAM,EAAE,EAAE;AACvC,MAAI,MAAM,SAAS;AACjB,eAAW,OAAO,MAAM,QAAS,OAAM,KAAK,IAAI,GAAG,EAAE;AAAA,EACvD;AACA,MAAI,MAAM,QAAQ;AAChB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,MAAM,GAAG;AACvD,YAAM,KAAK,GAAG,GAAG,IAAI,eAAe,KAAK,CAAC,EAAE;AAAA,IAC9C;AAAA,EACF;AAGA,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO,IAAI,MAAM,KAAK,GAAG,CAAC;AAC5B;AAEA,SAAS,6BAA6B,OAAmC;AACvE,SAAO;AAAA,IACL,MAAM,MACL,MAAM,WAAW,MAAM,QAAQ,SAAS,KACxC,MAAM,UAAU,OAAO,KAAK,MAAM,MAAM,EAAE,SAAS;AAAA,EACtD;AACF;AAOA,SAAS,4BAA4B,YAA+C;AAClF,QAAM,QAAkB,CAAC;AACzB,MAAI,WAAW,UAAU;AACvB,UAAM,KAAK,WAAW,QAAQ;AAAA,EAChC;AACA,MAAI,WAAW,QAAQ;AACrB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,WAAW,MAAM,GAAG;AAC5D,YAAM,KAAK,GAAG,GAAG,IAAI,eAAe,KAAK,CAAC,EAAE;AAAA,IAC9C;AAAA,EACF;AACA,SAAO,KAAK,MAAM,KAAK,GAAG,CAAC;AAC7B;AASO,SAAS,UAAU,MAAiB,SAAqD;AAC9F,QAAM,cAAc,SAAS,cAAc;AAC3C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,qBAAqB,KAAK,YAAY,CAAC,GAAG,WAAW;AAAA,IAC/D,GAAG,SAAS,gBAAgB,KAAK,QAAQ,CAAC;AAAA,EAC5C;AACF;AAEA,SAAS,qBAAqB,UAAuB,WAAyC;AAC5F,QAAM,SAA8B,CAAC;AACrC,aAAW,SAAS,UAAU;AAC5B,UAAM,OAAO,iBAAiB,OAAO,SAAS;AAC9C,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B;AACA,SAAO;AACT;AAEA,SAAS,sBACP,UACA,WAKA,iBAA0B,MACJ;AACtB,QAAM,SAA+B,CAAC;AACtC,aAAW,SAAS,UAAU;AAC5B,UAAM,OAAO,kBAAkB,OAAO,SAAS;AAC/C,QAAI,KAAM,QAAO,KAAK,IAAI;AAAA,EAC5B;AACA,QAAM,YAAY,iBAAiB,MAAM;AACzC,SAAO,iBAAiB,iBAAiB,SAAS,IAAI;AACxD;AAWO,SAAS,iBAAiB,OAAmD;AAMlF,QAAM,aAAa,yBAAyB,KAAK;AAEjD,QAAM,MAA4B,CAAC;AACnC,aAAW,QAAQ,YAAY;AAC7B,QAAI,KAAK,SAAS,QAAQ;AACxB,UAAI,KAAK,IAAI;AACb;AAAA,IACF;AACA,QAAI,KAAK,GAAG,iBAAiB,KAAK,OAAO,KAAK,QAAQ,CAAC;AAAA,EACzD;AACA,SAAO;AACT;AAUA,SAAS,yBAAyB,OAAmD;AACnF,QAAM,MAA4B,CAAC;AACnC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,MAAM,MAAM,CAAC;AACnB,UAAM,OAAO,MAAM,IAAI,CAAC;AACxB,UAAM,QAAQ,MAAM,IAAI,CAAC;AACzB,QACE,KAAK,SAAS,UACd,MAAM,SAAS,mBACf,OAAO,SAAS,UAChB,sBAAsB,KAAK,IAAI,KAAK,KACpC,QAAQ,KAAK,MAAM,KAAK,GACxB;AACA,YAAM,YAAY;AAClB,YAAM,gBAAgB,UAAU,QAAQ;AACxC,YAAM,SAA6B;AAAA,QACjC,MAAM;AAAA,QACN,OAAO,IAAI,QAAQ,MAAM,gBAAgB,MAAM;AAAA,QAC/C,GAAI,IAAI,WAAW,EAAE,UAAU,IAAI,SAAS,IAAI,CAAC;AAAA,MACnD;AACA,UAAI,KAAK,MAAM;AACf,WAAK;AACL;AAAA,IACF;AACA,QAAI,KAAK,GAAG;AAAA,EACd;AACA,SAAO;AACT;AAEA,IAAM,gBAAgB;AAEtB,SAAS,iBAAiB,OAAe,UAAyD;AAChG,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,gBAAc,YAAY;AAC1B,MAAI,YAAY;AAChB,MAAI;AACJ,QAAM,MAA4B,CAAC;AACnC,UAAQ,QAAQ,cAAc,KAAK,KAAK,OAAO,MAAM;AACnD,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,OAAO,YAAY,KAAK;AAC9B,QAAI,CAAC,KAAM;AACX,QAAI,MAAM,QAAQ,WAAW;AAC3B,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,QACN,OAAO,MAAM,MAAM,WAAW,MAAM,KAAK;AAAA,QACzC,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,MACjC,CAAC;AAAA,IACH;AACA,UAAM,WAA+B;AAAA,MACnC,MAAM;AAAA,MACN;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,MAAM,KAAK;AAAA,MACX,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IACjC;AACA,QAAI,KAAK,QAAQ;AACjB,gBAAY,MAAM,QAAQ,MAAM,CAAC,EAAE;AAAA,EACrC;AACA,MAAI,cAAc,GAAG;AAEnB,WAAO,CAAC,EAAE,MAAM,QAAQ,OAAO,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC,EAAG,CAAC;AAAA,EACpE;AACA,MAAI,YAAY,MAAM,QAAQ;AAC5B,QAAI,KAAK;AAAA,MACP,MAAM;AAAA,MACN,OAAO,MAAM,MAAM,SAAS;AAAA,MAC5B,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IACjC,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAUA,SAAS,iBAAiB,SAAqD;AAC7E,QAAM,MAA4B,CAAC;AACnC,aAAW,OAAO,SAAS;AACzB,UAAM,OAAO,IAAI,IAAI,SAAS,CAAC;AAC/B,QACE,IAAI,SAAS,UACb,OAAO,IAAI,QAAQ,YACnB,QACA,KAAK,SAAS,UACd,KAAK,MAAM,SAAS,GAAG,GACvB;AACA,YAAM,cAAc,IAAI,IAAI,MAAM,6BAA6B;AAC/D,UAAI,eAAe,YAAY,CAAC,KAAK,YAAY,CAAC,GAAG;AACnD,cAAM,aAAa,YAAY,CAAC;AAChC,cAAM,WAAW,YAAY,CAAC;AAC9B,cAAM,cAAc,kBAAkB,IAAI,QAAQ;AAClD,YAAI,KAAK,UAAU,KAAK;AACtB,cAAI,IAAI;AAAA,QACV,OAAO;AACL,eAAK,QAAQ,KAAK,MAAM,MAAM,GAAG,EAAE;AAAA,QACrC;AACA,YAAI,KAAK;AAAA,UACP,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAI,IAAI,WAAW,EAAE,UAAU,IAAI,SAAS,IAAI,CAAC;AAAA,QACnD,CAAC;AACD;AAAA,MACF;AAAA,IACF;AACA,QAAI,KAAK,GAAG;AAAA,EACd;AACA,SAAO;AACT;AAIA,SAAS,kBAAkB,OAAqC;AAC9D,MAAI,MAAM;AACV,aAAW,KAAK,OAAO;AACrB,QAAI,EAAE,SAAS,UAAU,EAAE,SAAS,aAAc,QAAO,EAAE;AAAA,aAClD,cAAc,KAAK,MAAM,QAAQ,EAAE,QAAQ,GAAG;AACrD,aAAO,kBAAkB,EAAE,QAAgC;AAAA,IAC7D;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,MAAiB,WAA8C;AACvF,QAAM,MAAM,gBAAgB,KAAK,QAAQ;AAEzC,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK,WAAW;AAYd,YAAM,kBAAkB,sBAAsB,KAAK,YAAY,CAAC,GAAG,WAAW,KAAK;AACnF,UAAI,aAA+C;AACnD,UAAI,QAAkC;AACtC,eAAS,OAAO,GAAG,OAAO,GAAG,QAAQ;AACnC,YAAI,UAAU;AACd,YAAI,CAAC,YAAY;AACf,gBAAM,IAAI,0BAA0B,eAAe;AACnD,cAAI,GAAG;AACL,yBAAa;AACb,sBAAU;AAAA,UACZ;AAAA,QACF;AACA,YAAI,CAAC,OAAO;AACV,gBAAM,IAAI,wBAAwB,eAAe;AACjD,cAAI,GAAG;AACL,oBAAQ;AACR,sBAAU;AAAA,UACZ;AAAA,QACF;AACA,YAAI,CAAC,QAAS;AAAA,MAChB;AACA,YAAM,gBAAgB,iBAAiB,eAAe;AACtD,YAAM,SAA0B;AAAA,QAC9B,MAAM;AAAA,QACN,OAAQ,KAAK,SAAS;AAAA,QACtB,UAAU;AAAA,QACV,GAAG,SAAS,GAAG;AAAA,MACjB;AACA,UAAI,YAAY;AACd,eAAO,qBAAqB;AAAA,MAC9B;AACA,UAAI,OAAO;AACT,eAAO,aAAa;AAAA,MACtB;AACA,aAAO;AAAA,IACT;AAAA,IAEA,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,sBAAsB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC9D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,qBAAqB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC7D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,KAAK,WAAW;AAAA,QACzB,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,GAAI,KAAK,UAAU,OAAO,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,QACrD,WAAW,KAAK,YAAY,CAAC,GAAG,IAAI,CAAC,SAAS,gBAAgB,MAAM,SAAS,CAAC;AAAA,QAC9E,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,GAAI,KAAK,QAAQ,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QAC/C,GAAI,KAAK,QAAQ,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QAC/C,OAAO,KAAK,SAAS;AAAA,QACrB,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,GAAI,KAAK,QACL,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC,MAAM,CAAuC,EAAE,IACxE,CAAC;AAAA,QACL,WAAW,KAAK,YAAY,CAAC,GAAG,IAAI,CAAC,KAAK,MAAM,gBAAgB,KAAK,MAAM,GAAG,SAAS,CAAC;AAAA,QACxF,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,KAAK,SAAS;AAAA,QACvB,cAAc,YAAY,iBAAiB,KAAK,SAAS,EAAE,IAAI,CAAC;AAAA,QAChE,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK,SAAS;AAAA,QACrB,GAAI,KAAK,QAAQ,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QAC/C,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK,cAAc;AAAA,QAC/B,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,KAAK,KAAK,OAAO;AAAA,QACjB,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK,cAAc;AAAA,QAC/B,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,UAAU,qBAAqB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC7D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK,sBAAsB;AACzB,YAAM,cAAc,KAAK,YAAY,CAAC;AACtC,UAAI;AACJ,UAAI,kBAAkB;AAGtB,UACE,YAAY,SAAS,KACrB,YAAY,CAAC,EAAE,SAAS,eACxB,YAAY,CAAC,EAAE,MAAM,mBAAmB,MACxC;AACA,gBAAQ,YAAY,YAAY,CAAC,CAAC;AAClC,0BAAkB,YAAY,MAAM,CAAC;AAAA,MACvC;AAEA,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,KAAK,QAAQ;AAAA,QACnB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,QACzB,GAAI,KAAK,cAAc,OAAO,KAAK,KAAK,UAAU,EAAE,SAAS,IACzD,EAAE,YAAY,KAAK,WAAW,IAC9B,CAAC;AAAA,QACL,UAAU,qBAAqB,iBAAiB,SAAS;AAAA,QACzD,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IACF;AAAA,IAEA,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,KAAK,QAAQ;AAAA,QACnB,GAAI,KAAK,cAAc,OAAO,KAAK,KAAK,UAAU,EAAE,SAAS,IACzD,EAAE,YAAY,KAAK,WAAW,IAC9B,CAAC;AAAA,QACL,UAAU,sBAAsB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC9D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AAEH,aAAO;AAAA,IAET;AAEE,UAAI,OAAO,YAAY,aAAa;AAClC,gBAAQ,KAAK,qDAAqD,KAAK,IAAI,GAAG;AAAA,MAChF;AACA,aAAO;AAAA,EACX;AACF;AAEA,SAAS,gBAAgB,MAAiB,WAAsC;AAC9E,QAAM,MAAM,gBAAgB,KAAK,QAAQ;AACzC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,GAAI,KAAK,WAAW,OAAO,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IACxD,GAAI,KAAK,UAAU,OAAO,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,IACrD,UAAU,qBAAqB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,IAC7D,GAAG,SAAS,GAAG;AAAA,EACjB;AACF;AAEA,SAAS,gBAAgB,MAAiB,UAAmB,WAAsC;AACjG,QAAM,MAAM,gBAAgB,KAAK,QAAQ;AACzC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,WAAW,KAAK,YAAY,CAAC,GAAG,IAAI,CAAC,SAAS,iBAAiB,MAAM,UAAU,SAAS,CAAC;AAAA,IACzF,GAAG,SAAS,GAAG;AAAA,EACjB;AACF;AAEA,SAAS,iBACP,MACA,UACA,WACmB;AACnB,QAAM,MAAM,gBAAgB,KAAK,QAAQ;AACzC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,GAAI,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;AAAA,IACrC,UAAU,sBAAsB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,IAC9D,GAAG,SAAS,GAAG;AAAA,EACjB;AACF;AAEA,SAAS,kBAAkB,MAAiB,WAA+C;AACzF,QAAM,MAAM,gBAAgB,KAAK,QAAQ;AAEzC,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK,SAAS;AAAA,QACrB,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,sBAAsB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC9D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,sBAAsB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC9D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,sBAAsB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC9D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK,SAAS;AAAA,QACrB,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,KAAK,OAAO;AAAA,QACjB,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,UAAU,sBAAsB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC9D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,KAAK,OAAO;AAAA,QACjB,GAAI,KAAK,OAAO,OAAO,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,QAC5C,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,KAAK,SAAS;AAAA,QACvB,cAAc,YAAY,iBAAiB,KAAK,SAAS,EAAE,IAAI,CAAC;AAAA,QAChE,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK,SAAS;AAAA,QACrB,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK,cAAc;AAAA,QAC/B,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK,cAAc;AAAA,QAC/B,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,eAAgB,KAAK,iBAAiB;AAAA,QACtC,UAAU,sBAAsB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC9D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK,cAAc;AAAA,QAC/B,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,eAAgB,KAAK,iBAAiB;AAAA,QACtC,GAAI,KAAK,OAAO,OAAO,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,QAC5C,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,KAAK,QAAQ;AAAA,QACnB,GAAI,KAAK,cAAc,OAAO,KAAK,KAAK,UAAU,EAAE,SAAS,IACzD,EAAE,YAAY,KAAK,WAAW,IAC9B,CAAC;AAAA,QACL,UAAU,sBAAsB,KAAK,YAAY,CAAC,GAAG,SAAS;AAAA,QAC9D,GAAG,SAAS,GAAG;AAAA,MACjB;AAAA,IAEF;AACE,UAAI,OAAO,YAAY,aAAa;AAClC,gBAAQ,KAAK,sDAAsD,KAAK,IAAI,GAAG;AAAA,MACjF;AACA,aAAO;AAAA,EACX;AACF;AAYO,SAAS,QAAQ,KAAkC;AACxD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,IAAI,SAAS,IAAI,CAAC,MAAM,aAAa,CAAC,CAAC;AAAA,IACjD,GAAG,cAAc,IAAI,QAAQ;AAAA,EAC/B;AACF;AAEA,SAAS,aAAa,MAAoC;AACxD,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK,WAAW;AACd,YAAM,gBAAgB,sBAAsB,KAAK,QAAQ;AAIzD,YAAM,WAAqB,CAAC;AAC5B,UAAI,KAAK,YAAY;AACnB,cAAM,SAAS,0BAA0B,KAAK,UAAU;AACxD,YAAI,UAAU,SAAS,WAAW,QAAQ,6BAA6B,KAAK,UAAU,IAAI;AACxF,mBAAS,KAAK,MAAM;AAAA,QACtB;AAAA,MACF;AACA,UAAI,KAAK,oBAAoB;AAC3B,iBAAS,KAAK,4BAA4B,KAAK,kBAAkB,CAAC;AAAA,MACpE;AACA,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,SAAS,SAAS,KAAK,GAAG;AAChC,cAAM,YAAY,cAAc,cAAc,SAAS,CAAC;AACxD,YAAI,aAAa,UAAU,SAAS,QAAQ;AAC1C,oBAAU,SAAS,UAAU,SAAS,MAAM,MAAM;AAAA,QACpD,OAAO;AACL,wBAAc,KAAK,EAAE,MAAM,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,QAC1D;AAAA,MACF;AACA,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,UAAU;AAAA,QACV,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IACF;AAAA,IAEA,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,sBAAsB,KAAK,QAAQ;AAAA,QAC7C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,KAAK,SAAS,IAAI,YAAY;AAAA,QACxC,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,KAAK;AAAA,QACd,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,GAAI,KAAK,UAAU,OAAO,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,QACrD,UAAU,KAAK,SAAS,IAAI,eAAe;AAAA,QAC3C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,GAAI,KAAK,QAAQ,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QAC/C,GAAI,KAAK,QAAQ,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QAC/C,OAAO,KAAK;AAAA,QACZ,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAC1C,UAAU,KAAK,SAAS,IAAI,eAAe;AAAA,QAC3C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,GAAI,KAAK,QAAQ,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,QAC/C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK;AAAA,QACjB,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,KAAK,KAAK;AAAA,QACV,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK;AAAA,QACjB,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,UAAU,KAAK,SAAS,IAAI,YAAY;AAAA,QACxC,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK,sBAAsB;AACzB,YAAM,WAAwB,CAAC;AAG/B,UAAI,KAAK,OAAO;AACd,iBAAS,KAAK;AAAA,UACZ,MAAM;AAAA,UACN,MAAM,EAAE,gBAAgB,KAAK;AAAA,UAC7B,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,KAAK,MAAM,CAAC;AAAA,QAChD,CAAC;AAAA,MACH;AAEA,eAAS,KAAK,GAAG,KAAK,SAAS,IAAI,YAAY,CAAC;AAEhD,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,QACX,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,QACzD;AAAA,QACA,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IACF;AAAA,IAEA,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,QACX,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,QACzD,UAAU,sBAAsB,KAAK,QAAQ;AAAA,QAC7C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AAGH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,qBAAqB,IAAI;AAAA,QAChC,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF;AACE,aAAO,EAAE,MAAM,QAAQ,OAAO,GAAG;AAAA,EACrC;AACF;AAEA,SAAS,gBAAgB,MAAmC;AAC1D,SAAO;AAAA,IACL,MAAM;AAAA,IACN,GAAI,KAAK,WAAW,OAAO,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IACxD,GAAI,KAAK,UAAU,OAAO,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,IACrD,UAAU,KAAK,SAAS,IAAI,YAAY;AAAA,IACxC,GAAG,cAAc,KAAK,QAAQ;AAAA,EAChC;AACF;AAEA,SAAS,gBAAgB,KAAkC;AACzD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,IAAI,SAAS,IAAI,gBAAgB;AAAA,IAC3C,GAAG,cAAc,IAAI,QAAQ;AAAA,EAC/B;AACF;AAEA,SAAS,iBAAiB,MAAoC;AAC5D,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,sBAAsB,KAAK,QAAQ;AAAA,IAC7C,GAAG,cAAc,KAAK,QAAQ;AAAA,EAChC;AACF;AAOA,SAAS,sBAAsB,OAA0C;AACvE,QAAM,MAAmB,CAAC;AAC1B,aAAW,KAAK,OAAO;AACrB,QAAI,EAAE,SAAS,WAAW;AACxB,UAAI,KAAK,EAAE,MAAM,QAAQ,OAAO,IAAI,CAAC;AACrC,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,QACN,KAAK,GAAG,EAAE,UAAU,IAAI,EAAE,QAAQ;AAAA,QAClC,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,EAAE,YAAY,CAAC;AAAA,QACjD,GAAG,cAAc,EAAE,QAAQ;AAAA,MAC7B,CAAC;AAAA,IACH,OAAO;AACL,UAAI,KAAK,cAAc,CAAC,CAAC;AAAA,IAC3B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cAAc,MAAqC;AAC1D,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,sBAAsB,KAAK,QAAQ;AAAA,QAC7C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,sBAAsB,KAAK,QAAQ;AAAA,QAC7C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,sBAAsB,KAAK,QAAQ;AAAA,QAC7C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,KAAK;AAAA,QACV,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,UAAU,sBAAsB,KAAK,QAAQ;AAAA,QAC7C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,KAAK;AAAA,QACV,GAAI,KAAK,OAAO,OAAO,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,QAC5C,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK;AAAA,QACjB,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK;AAAA,QACjB,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,eAAe,KAAK;AAAA,QACpB,UAAU,sBAAsB,KAAK,QAAQ;AAAA,QAC7C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,YAAY,KAAK;AAAA,QACjB,GAAI,KAAK,SAAS,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QAClD,eAAe,KAAK;AAAA,QACpB,GAAI,KAAK,OAAO,OAAO,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,QAC5C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,KAAK;AAAA,QACX,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,QACzD,UAAU,sBAAsB,KAAK,QAAQ;AAAA,QAC7C,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AAMH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,KAAK,GAAG,KAAK,UAAU,IAAI,KAAK,QAAQ;AAAA,QACxC,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,KAAK,YAAY,CAAC;AAAA,QACpD,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF,KAAK;AAIH,aAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO,KAAK,KAAK,KAAK;AAAA,QACtB,GAAG,cAAc,KAAK,QAAQ;AAAA,MAChC;AAAA,IAEF;AACE,aAAO,EAAE,MAAM,QAAQ,OAAO,GAAG;AAAA,EACrC;AACF;AAUA,SAAS,qBAAqB,MAEnB;AACT,MAAI,OAAO;AACX,aAAW,SAAS,KAAK,UAAU;AACjC,QAAI,MAAM,SAAS,kBAAkB;AAEnC,YAAM,OAAQ,MAAM,SACjB,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EACxB,KAAK,EAAE;AACV,cAAQ,SAAS,IAAI;AAAA;AAAA,IACvB,WAAW,MAAM,SAAS,yBAAyB;AACjD,cAAQ;AAAA;AAAA,IACV;AAAA,EACF;AACA,UAAQ;AACR,SAAO;AACT;;;ACj0CA,SAAS,eAAe;AACxB,OAAO,iBAAiB;AACxB,OAAO,eAAe;AACtB,OAAO,gBAAgB;AACvB,OAAO,qBAAqB;AAC5B,OAAO,uBAAuB;AAM9B,IAAI;AAoBG,SAAS,cAAc,UAAkB,SAA0C;AAExF,QAAM,cACJ,CAAC,WACA,QAAQ,QAAQ,SACf,QAAQ,SAAS,SACjB,QAAQ,cAAc,SACtB,QAAQ,gBAAgB;AAE5B,MAAI;AAEJ,MAAI,aAAa;AACf,QAAI,CAAC,kBAAkB;AACrB,yBAAmB,QAAQ,EACxB,IAAI,WAAW,EACf,IAAI,SAAS,EACb,IAAI,UAAU,EACd,IAAI,eAAe,EACnB,IAAI,mBAAmB,CAAC,MAAM,CAAC;AAAA,IACpC;AACA,gBAAY;AAAA,EACd,OAAO;AAIL,gBAAY,QAAQ,EAAE,IAAI,WAAW;AAErC,QAAI,SAAS,QAAQ,OAAO;AAC1B,kBAAY,UAAU,IAAI,SAAS;AAAA,IACrC;AACA,QAAI,SAAS,SAAS,OAAO;AAC3B,kBAAY,UAAU,IAAI,UAAU;AAAA,IACtC;AACA,QAAI,SAAS,cAAc,OAAO;AAChC,kBAAY,UAAU,IAAI,eAAe;AAAA,IAC3C;AACA,QAAI,SAAS,gBAAgB,OAAO;AAClC,kBAAY,UAAU,IAAI,mBAAmB,CAAC,MAAM,CAAC;AAAA,IACvD;AAAA,EACF;AAGA,QAAM,YAAY,UAAU,MAAM,QAAQ;AAM1C,QAAM,MAAM,UAAU,WAA8C;AAAA,IAClE,WAAW,SAAS;AAAA,EACtB,CAAC;AAGD,MAAI,SAAS,gBAAgB,OAAO;AAClC,UAAM,WAAW,UAAU,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAClE,QAAI,UAAU,OAAO;AACnB,YAAM,KAAK,iBAAiB,SAAS,KAAK;AAC1C,UAAI,MAAM,OAAO,KAAK,EAAE,EAAE,SAAS,GAAG;AACpC,YAAI,cAAc;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-ET53IIEP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
toMdast
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BEQODJWV.js";
|
|
4
|
+
import {
|
|
5
|
+
formatBlockScalar
|
|
6
|
+
} from "./chunk-HWVFJAAH.js";
|
|
4
7
|
|
|
5
8
|
// src/markdown/stringify.ts
|
|
6
9
|
import { unified } from "unified";
|
|
@@ -75,9 +78,12 @@ function stringifyMarkdown(doc, options) {
|
|
|
75
78
|
);
|
|
76
79
|
cleaned = cleaned.replace(/\{(?!\\?\[)[^}]*\}/g, (match) => match.replace(/\\:/g, ":"));
|
|
77
80
|
if (doc.frontmatter && Object.keys(doc.frontmatter).length > 0) {
|
|
78
|
-
const yamlLines = Object.entries(doc.frontmatter).map(
|
|
79
|
-
(
|
|
80
|
-
|
|
81
|
+
const yamlLines = Object.entries(doc.frontmatter).map(([k, v]) => {
|
|
82
|
+
if (typeof v === "string") {
|
|
83
|
+
return /[\r\n]/.test(v) ? `${k}: ${formatBlockScalar(v.replace(/\r\n?/g, "\n"))}` : `${k}: ${v}`;
|
|
84
|
+
}
|
|
85
|
+
return `${k}: ${JSON.stringify(v)}`;
|
|
86
|
+
});
|
|
81
87
|
return `---
|
|
82
88
|
${yamlLines.join("\n")}
|
|
83
89
|
---
|
|
@@ -90,6 +96,7 @@ ${cleaned}`;
|
|
|
90
96
|
// src/markdown/sanitize.ts
|
|
91
97
|
var SAFE_LINK_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
92
98
|
var SAFE_MEDIA_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "blob"]);
|
|
99
|
+
var NEVER_ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["javascript", "vbscript", "data"]);
|
|
93
100
|
var SAFE_DATA_MEDIA_RE = /^data:(?:image\/(?!svg\+xml)[a-z0-9.+-]+|audio\/[a-z0-9.+-]+|video\/[a-z0-9.+-]+);/i;
|
|
94
101
|
var SAFE_TAGS = /* @__PURE__ */ new Set([
|
|
95
102
|
"a",
|
|
@@ -203,7 +210,7 @@ var SAFE_ATTR_NAME_RE = /^[a-z][a-z0-9:_.-]*$/;
|
|
|
203
210
|
var SAFE_OL_TYPES = /* @__PURE__ */ new Set(["1", "a", "A", "i", "I"]);
|
|
204
211
|
var SAFE_PRELOAD_VALUES = /* @__PURE__ */ new Set(["none", "metadata", "auto"]);
|
|
205
212
|
var SAFE_TRACK_KINDS = /* @__PURE__ */ new Set(["subtitles", "captions", "descriptions", "chapters", "metadata"]);
|
|
206
|
-
function sanitizeUrl(url, kind = "link") {
|
|
213
|
+
function sanitizeUrl(url, kind = "link", options) {
|
|
207
214
|
if (typeof url !== "string") return null;
|
|
208
215
|
const trimmed = url.trim();
|
|
209
216
|
if (!trimmed) return null;
|
|
@@ -217,7 +224,11 @@ function sanitizeUrl(url, kind = "link") {
|
|
|
217
224
|
if (scheme === "data") return SAFE_DATA_MEDIA_RE.test(compact) ? trimmed : null;
|
|
218
225
|
return SAFE_MEDIA_SCHEMES.has(scheme) ? trimmed : null;
|
|
219
226
|
}
|
|
220
|
-
|
|
227
|
+
if (SAFE_LINK_SCHEMES.has(scheme)) return trimmed;
|
|
228
|
+
if (options?.extraLinkSchemes?.includes(scheme) && !NEVER_ALLOWED_SCHEMES.has(scheme)) {
|
|
229
|
+
return trimmed;
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
221
232
|
}
|
|
222
233
|
function sanitizeHtmlNodes(nodes) {
|
|
223
234
|
const out = [];
|
|
@@ -330,4 +341,4 @@ export {
|
|
|
330
341
|
sanitizeUrl,
|
|
331
342
|
sanitizeHtmlNodes
|
|
332
343
|
};
|
|
333
|
-
//# sourceMappingURL=chunk-
|
|
344
|
+
//# sourceMappingURL=chunk-GPLTCSXO.js.map
|