@bendyline/squisq 1.5.0 → 1.5.1

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.
Files changed (124) hide show
  1. package/README.md +14 -1
  2. package/dist/{ContentContainer-DNx460y8.d.ts → ContentContainer-BXUlIf18.d.ts} +6 -2
  3. package/dist/{Doc-BsMkmzH8.d.ts → Doc-BBVGq1_9.d.ts} +94 -15
  4. package/dist/{ImageEditDoc-DvoeHMP9.d.ts → ImageEditDoc-FuyGtt6o.d.ts} +1 -1
  5. package/dist/{chunk-KZNXZ5SM.js → chunk-6ATE2PSM.js} +18 -7
  6. package/dist/chunk-6ATE2PSM.js.map +1 -0
  7. package/dist/{chunk-7MQGNR76.js → chunk-A4O7GIWE.js} +2 -27
  8. package/dist/chunk-A4O7GIWE.js.map +1 -0
  9. package/dist/chunk-ET53IIEP.js +1 -0
  10. package/dist/chunk-ET53IIEP.js.map +1 -0
  11. package/dist/{chunk-OKJOQXK4.js → chunk-HWVFJAAH.js} +38 -2
  12. package/dist/chunk-HWVFJAAH.js.map +1 -0
  13. package/dist/{chunk-2TMAWB4Y.js → chunk-IMSCRLLV.js} +1 -1
  14. package/dist/chunk-IMSCRLLV.js.map +1 -0
  15. package/dist/{chunk-4AKUQPEE.js → chunk-KKNUBQ6Y.js} +1 -1
  16. package/dist/chunk-KKNUBQ6Y.js.map +1 -0
  17. package/dist/{chunk-TVSGLUID.js → chunk-LDQ2HJIX.js} +3 -3
  18. package/dist/{chunk-4ZFRZBU2.js → chunk-LH7I6SH7.js} +777 -151
  19. package/dist/chunk-LH7I6SH7.js.map +1 -0
  20. package/dist/{chunk-K32VJONL.js → chunk-T5UK6YOB.js} +41 -2
  21. package/dist/chunk-T5UK6YOB.js.map +1 -0
  22. package/dist/{chunk-OH43RPTP.js → chunk-TEEEILMP.js} +12 -2
  23. package/dist/chunk-TEEEILMP.js.map +1 -0
  24. package/dist/{chunk-FR2RBTKO.js → chunk-TQWLI6S2.js} +93 -27
  25. package/dist/chunk-TQWLI6S2.js.map +1 -0
  26. package/dist/{chunk-LRCV7N7F.js → chunk-UY7KGQ5R.js} +2 -2
  27. package/dist/{chunk-3E5F2XMR.js → chunk-VWUFZ6ZG.js} +29 -2
  28. package/dist/chunk-VWUFZ6ZG.js.map +1 -0
  29. package/dist/doc/index.d.ts +4 -4
  30. package/dist/doc/index.js +16 -7
  31. package/dist/generate/index.d.ts +2 -80
  32. package/dist/generate/index.js +2 -7
  33. package/dist/imageEdit/index.d.ts +4 -4
  34. package/dist/index.d.ts +8 -8
  35. package/dist/index.js +32 -23
  36. package/dist/jsonForm/index.d.ts +42 -1
  37. package/dist/jsonForm/index.js +9 -1
  38. package/dist/markdown/index.d.ts +18 -2
  39. package/dist/markdown/index.js +5 -3
  40. package/dist/recommend/index.js +2 -2
  41. package/dist/schemas/index.d.ts +4 -4
  42. package/dist/schemas/index.js +3 -5
  43. package/dist/storage/index.d.ts +2 -2
  44. package/dist/storage/index.js +1 -1
  45. package/dist/story/index.d.ts +125 -11
  46. package/dist/story/index.js +16 -7
  47. package/dist/{themeLibrary-DQQKuRMx.d.ts → themeLibrary-CehcJhzz.d.ts} +1 -1
  48. package/dist/timing/index.js +3 -5
  49. package/dist/transform/index.d.ts +2 -2
  50. package/dist/transform/index.js +3 -3
  51. package/dist/versions/index.d.ts +1 -1
  52. package/package.json +1 -1
  53. package/src/__tests__/contentExtractor.test.ts +60 -0
  54. package/src/__tests__/coverBlock.test.ts +46 -1
  55. package/src/__tests__/customTemplateSample.test.ts +63 -2
  56. package/src/__tests__/customTemplatesFrontmatter.test.ts +31 -0
  57. package/src/__tests__/customThemesFrontmatter.test.ts +27 -0
  58. package/src/__tests__/inputDescriptors.test.ts +162 -0
  59. package/src/__tests__/jsonForm.tokens.test.ts +84 -0
  60. package/src/__tests__/markdown.test.ts +64 -0
  61. package/src/__tests__/markdownSanitize.test.ts +15 -0
  62. package/src/__tests__/markdownToDoc.test.ts +168 -0
  63. package/src/__tests__/mediaAnnotations.test.ts +27 -0
  64. package/src/__tests__/structuredData.test.ts +73 -3
  65. package/src/__tests__/templateMetadata.test.ts +11 -1
  66. package/src/__tests__/templates.test.ts +112 -0
  67. package/src/__tests__/validateDoc.test.ts +160 -0
  68. package/src/doc/annotationBlocks.ts +117 -0
  69. package/src/doc/customTemplatesFrontmatter.ts +7 -1
  70. package/src/doc/customThemesFrontmatter.ts +6 -1
  71. package/src/doc/docToMarkdown.ts +31 -1
  72. package/src/doc/getLayers.ts +11 -2
  73. package/src/doc/index.ts +12 -0
  74. package/src/doc/markdownToDoc.ts +80 -3
  75. package/src/doc/mediaAnnotations.ts +15 -33
  76. package/src/doc/standaloneAnnotation.ts +58 -0
  77. package/src/doc/structuredData.ts +92 -21
  78. package/src/doc/templates/__tests__/customTemplate.test.ts +21 -2
  79. package/src/doc/templates/comparisonBar.ts +8 -1
  80. package/src/doc/templates/coverBlock.ts +29 -2
  81. package/src/doc/templates/customTemplate.ts +10 -4
  82. package/src/doc/templates/factCard.ts +4 -2
  83. package/src/doc/templates/imageWithCaption.ts +10 -8
  84. package/src/doc/templates/index.ts +12 -2
  85. package/src/doc/templates/inputDescriptors.ts +432 -0
  86. package/src/doc/templates/photoGrid.ts +16 -39
  87. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +267 -1
  88. package/src/doc/templates/tokens/resolveTokens.ts +0 -0
  89. package/src/doc/templates/videoWithCaption.ts +15 -14
  90. package/src/doc/utils/nearest.ts +59 -0
  91. package/src/doc/validate.ts +153 -54
  92. package/src/generate/contentExtractor.ts +148 -35
  93. package/src/generate/index.ts +0 -3
  94. package/src/jsonForm/index.ts +2 -0
  95. package/src/jsonForm/tokens.ts +78 -0
  96. package/src/markdown/annotationCoercion.ts +1 -1
  97. package/src/markdown/attrTokens.ts +19 -0
  98. package/src/markdown/index.ts +2 -1
  99. package/src/markdown/sanitize.ts +22 -2
  100. package/src/markdown/stringify.ts +12 -3
  101. package/src/markdown/utils.ts +62 -1
  102. package/src/schemas/CustomTemplates.ts +56 -4
  103. package/src/schemas/Doc.ts +39 -41
  104. package/src/schemas/themeCompile.ts +1 -1
  105. package/src/storage/ContentContainer.ts +6 -2
  106. package/src/transform/types.ts +1 -1
  107. package/dist/chunk-2TMAWB4Y.js.map +0 -1
  108. package/dist/chunk-3E5F2XMR.js.map +0 -1
  109. package/dist/chunk-4AKUQPEE.js.map +0 -1
  110. package/dist/chunk-4X3JQXNM.js +0 -35
  111. package/dist/chunk-4X3JQXNM.js.map +0 -1
  112. package/dist/chunk-4ZFRZBU2.js.map +0 -1
  113. package/dist/chunk-7MQGNR76.js.map +0 -1
  114. package/dist/chunk-FBKP5CAO.js +0 -145
  115. package/dist/chunk-FBKP5CAO.js.map +0 -1
  116. package/dist/chunk-FR2RBTKO.js.map +0 -1
  117. package/dist/chunk-K32VJONL.js.map +0 -1
  118. package/dist/chunk-KZNXZ5SM.js.map +0 -1
  119. package/dist/chunk-OH43RPTP.js.map +0 -1
  120. package/dist/chunk-OKJOQXK4.js.map +0 -1
  121. package/src/__tests__/slideshowGenerator.test.ts +0 -128
  122. package/src/generate/slideshowGenerator.ts +0 -280
  123. /package/dist/{chunk-TVSGLUID.js.map → chunk-LDQ2HJIX.js.map} +0 -0
  124. /package/dist/{chunk-LRCV7N7F.js.map → chunk-UY7KGQ5R.js.map} +0 -0
@@ -1,3 +1,9 @@
1
+ import {
2
+ DEFAULT_THEME,
3
+ applySurface,
4
+ resolveFontFamily
5
+ } from "./chunk-KKNUBQ6Y.js";
6
+
1
7
  // src/jsonForm/chooseControl.ts
2
8
  var ENUM_SEGMENTED_LIMIT = 4;
3
9
  var MULTILINE_LENGTH_THRESHOLD = 200;
@@ -177,6 +183,37 @@ function inferSchema(sample, options = {}) {
177
183
  return mergeSchemas([base, ...extras]);
178
184
  }
179
185
 
186
+ // src/jsonForm/tokens.ts
187
+ function buildJsonFormTokens(theme, surface, options) {
188
+ const { prefix } = options;
189
+ const baseTheme = theme ?? DEFAULT_THEME;
190
+ const finalTheme = surface ? applySurface(baseTheme, surface) : baseTheme;
191
+ const titleFont = resolveFontFamily(finalTheme.typography.titleFont, "system-ui, sans-serif");
192
+ const bodyFont = resolveFontFamily(finalTheme.typography.bodyFont, "system-ui, sans-serif");
193
+ const monoFont = resolveFontFamily(
194
+ finalTheme.typography.monoFont,
195
+ "ui-monospace, Consolas, monospace"
196
+ );
197
+ return {
198
+ [`${prefix}-bg`]: finalTheme.colors.background,
199
+ [`${prefix}-text`]: finalTheme.colors.text,
200
+ [`${prefix}-muted`]: finalTheme.colors.textMuted,
201
+ [`${prefix}-primary`]: finalTheme.colors.primary,
202
+ [`${prefix}-accent`]: finalTheme.colors.secondary,
203
+ [`${prefix}-warning`]: finalTheme.colors.warning,
204
+ [`${prefix}-border`]: `color-mix(in srgb, ${finalTheme.colors.textMuted} 35%, transparent)`,
205
+ [`${prefix}-input-bg`]: finalTheme.colors.backgroundLight,
206
+ [`${prefix}-title-font`]: titleFont,
207
+ [`${prefix}-body-font`]: bodyFont,
208
+ [`${prefix}-mono-font`]: monoFont,
209
+ [`${prefix}-radius`]: `${finalTheme.style.borderRadius ?? 8}px`
210
+ };
211
+ }
212
+ function resolveJsonFormTheme(theme, surface) {
213
+ const baseTheme = theme ?? DEFAULT_THEME;
214
+ return surface ? applySurface(baseTheme, surface) : baseTheme;
215
+ }
216
+
180
217
  export {
181
218
  chooseControl,
182
219
  primaryType,
@@ -188,6 +225,8 @@ export {
188
225
  resolveRef,
189
226
  evaluateWhen,
190
227
  resolveFlag,
191
- inferSchema
228
+ inferSchema,
229
+ buildJsonFormTokens,
230
+ resolveJsonFormTheme
192
231
  };
193
- //# sourceMappingURL=chunk-K32VJONL.js.map
232
+ //# sourceMappingURL=chunk-T5UK6YOB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jsonForm/chooseControl.ts","../src/jsonForm/pointer.ts","../src/jsonForm/evaluateWhen.ts","../src/jsonForm/inferSchema.ts","../src/jsonForm/tokens.ts"],"sourcesContent":["/**\n * Pick a `ControlKind` for a schema node. The single dispatcher used by\n * both `<JsonView>` and `<JsonEditor>` so view and edit modes always\n * agree on what each field _is_, even when their UIs differ.\n *\n * Order of precedence:\n * 1. Explicit `squisq.control` hint\n * 2. `format` keyword (color/date/time/datetime/markdown/textarea)\n * 3. Enum cardinality (segmented vs. combobox)\n * 4. Numeric range (slider when both min and max present)\n * 5. String length (multiline when maxLength > 200)\n * 6. Type-default (object/array/boolean/string/number)\n */\n\nimport type { ControlKind, SquisqAnnotatedSchema } from './types.js';\n\nconst ENUM_SEGMENTED_LIMIT = 4;\nconst MULTILINE_LENGTH_THRESHOLD = 200;\n\nexport function chooseControl(schema: SquisqAnnotatedSchema): ControlKind {\n const hint = schema.squisq?.control;\n if (hint) return hint;\n\n const type = primaryType(schema);\n\n if (schema.format && type === 'string') {\n switch (schema.format) {\n case 'color':\n return 'color';\n case 'date':\n return 'date';\n case 'time':\n return 'time';\n case 'date-time':\n return 'datetime';\n case 'markdown':\n return 'richtext';\n case 'textarea':\n return 'multiline';\n }\n }\n\n if (schema.enum && schema.enum.length > 0) {\n return schema.enum.length <= ENUM_SEGMENTED_LIMIT ? 'segmented' : 'combobox';\n }\n\n if (schema.oneOf || schema.anyOf) return 'tabs';\n\n switch (type) {\n case 'array':\n return arrayItemKind(schema) === 'object' ? 'card-stack' : 'chip-bin';\n case 'object':\n return 'group';\n case 'boolean':\n return 'toggle';\n case 'integer':\n case 'number':\n return hasFiniteRange(schema) ? 'slider' : 'stepper';\n case 'string':\n if (typeof schema.maxLength === 'number' && schema.maxLength > MULTILINE_LENGTH_THRESHOLD) {\n return 'multiline';\n }\n return 'text';\n default:\n return 'text';\n }\n}\n\n/** Return the first concrete type of a schema, ignoring `null` for nullable shorthand. */\nexport function primaryType(schema: SquisqAnnotatedSchema): string | undefined {\n const t = schema.type;\n if (typeof t === 'string') return t;\n if (Array.isArray(t)) return t.find((x) => x !== 'null');\n return undefined;\n}\n\n/** Inspect `items` to decide whether an array holds objects or primitives. */\nexport function arrayItemKind(schema: SquisqAnnotatedSchema): 'object' | 'primitive' {\n const items = Array.isArray(schema.items) ? schema.items[0] : schema.items;\n if (!items) return 'primitive';\n return primaryType(items) === 'object' ? 'object' : 'primitive';\n}\n\nfunction hasFiniteRange(schema: SquisqAnnotatedSchema): boolean {\n const min = schema.minimum ?? schema.exclusiveMinimum;\n const max = schema.maximum ?? schema.exclusiveMaximum;\n return typeof min === 'number' && typeof max === 'number';\n}\n","/**\n * JSON Pointer helpers (RFC 6901) plus immutable get/set, plus a tiny\n * dotted-path normalizer so hosts can write `user.role` instead of\n * `/user/role`. `$ref` resolution is local-only (`#/$defs/Foo`).\n */\n\nimport type { SquisqAnnotatedSchema } from './types.js';\n\n/** Normalize a path to a JSON Pointer string. Accepts both forms. */\nexport function toPointer(path: string): string {\n if (path === '' || path === '/') return '';\n if (path.startsWith('/')) return path;\n // Dotted form: split on `.` and escape per RFC 6901.\n return (\n '/' +\n path\n .split('.')\n .map((seg) => seg.replace(/~/g, '~0').replace(/\\//g, '~1'))\n .join('/')\n );\n}\n\n/** Split a JSON Pointer into decoded segments. Empty string → []. */\nexport function pointerSegments(pointer: string): string[] {\n if (pointer === '') return [];\n return pointer\n .slice(1)\n .split('/')\n .map((seg) => seg.replace(/~1/g, '/').replace(/~0/g, '~'));\n}\n\n/** Read a value from data by path (dotted or pointer). Returns undefined if missing. */\nexport function getByPointer(data: unknown, path: string): unknown {\n const segments = pointerSegments(toPointer(path));\n let cur: unknown = data;\n for (const seg of segments) {\n if (cur === null || cur === undefined) return undefined;\n if (Array.isArray(cur)) {\n const idx = Number(seg);\n if (!Number.isInteger(idx) || idx < 0 || idx >= cur.length) return undefined;\n cur = cur[idx];\n } else if (typeof cur === 'object') {\n cur = (cur as Record<string, unknown>)[seg];\n } else {\n return undefined;\n }\n }\n return cur;\n}\n\n/**\n * Return a structurally-shared copy of `data` with the value at `path`\n * replaced. Intermediate objects/arrays are cloned along the path; the\n * rest is shared by reference. Creates intermediate objects if missing.\n */\nexport function setByPointer<T>(data: T, path: string, value: unknown): T {\n const segments = pointerSegments(toPointer(path));\n if (segments.length === 0) return value as T;\n return setRec(data, segments, 0, value) as T;\n}\n\nfunction setRec(node: unknown, segments: string[], i: number, value: unknown): unknown {\n const seg = segments[i];\n const isLast = i === segments.length - 1;\n\n // Decide whether the slot we're about to write into is an array index.\n const nextSeg = isLast ? undefined : segments[i + 1];\n const writingArrayChild = nextSeg !== undefined && /^\\d+$/.test(nextSeg);\n\n if (Array.isArray(node)) {\n const idx = Number(seg);\n const next = node.slice();\n next[idx] = isLast\n ? value\n : setRec(node[idx] ?? (writingArrayChild ? [] : {}), segments, i + 1, value);\n return next;\n }\n\n const obj = (node && typeof node === 'object' ? { ...(node as object) } : {}) as Record<\n string,\n unknown\n >;\n obj[seg] = isLast\n ? value\n : setRec(obj[seg] ?? (writingArrayChild ? [] : {}), segments, i + 1, value);\n return obj;\n}\n\n/**\n * Resolve a local `$ref` (e.g. `#/$defs/Foo`) against the root schema.\n * Returns the original schema if no `$ref` is present, or `undefined`\n * if the reference cannot be resolved. Cross-document refs are not\n * supported.\n */\nexport function resolveRef(\n schema: SquisqAnnotatedSchema,\n root: SquisqAnnotatedSchema,\n): SquisqAnnotatedSchema | undefined {\n if (!schema.$ref) return schema;\n const ref = schema.$ref;\n if (!ref.startsWith('#/')) return undefined;\n const segments = pointerSegments(ref.slice(1));\n let cur: unknown = root;\n for (const seg of segments) {\n if (cur === null || cur === undefined || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[seg];\n }\n return (cur as SquisqAnnotatedSchema | undefined) ?? undefined;\n}\n","/**\n * Evaluate a `SquisqWhen` rule against the current root data. Pure;\n * renderers call this on every render with fresh data.\n */\n\nimport type { SquisqWhen } from './types.js';\nimport { getByPointer } from './pointer.js';\n\nexport function evaluateWhen(when: SquisqWhen, rootData: unknown): boolean {\n const actual = getByPointer(rootData, when.field);\n\n if (when.equals !== undefined) {\n return deepEqual(actual, when.equals);\n }\n if (when.oneOf !== undefined) {\n return when.oneOf.some((v) => deepEqual(actual, v));\n }\n if (when.matches !== undefined) {\n if (typeof actual !== 'string') return false;\n try {\n return new RegExp(when.matches).test(actual);\n } catch {\n return false;\n }\n }\n if (when.truthy !== undefined) {\n return Boolean(actual) === when.truthy;\n }\n // No operator specified — defaults to \"field has any defined value\".\n return actual !== undefined && actual !== null;\n}\n\n/**\n * Resolve a `boolean | SquisqWhen` flag (used by `hidden`/`disabled`)\n * to a final boolean. Literal booleans pass through.\n */\nexport function resolveFlag(flag: boolean | SquisqWhen | undefined, rootData: unknown): boolean {\n if (flag === undefined) return false;\n if (typeof flag === 'boolean') return flag;\n return evaluateWhen(flag, rootData);\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n if (typeof a !== 'object') return false;\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== b.length) return false;\n return a.every((v, i) => deepEqual(v, b[i]));\n }\n if (Array.isArray(b)) return false;\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n const ak = Object.keys(ao);\n const bk = Object.keys(bo);\n if (ak.length !== bk.length) return false;\n return ak.every((k) => deepEqual(ao[k], bo[k]));\n}\n","/**\n * Infer a JSON Schema from an example JSON value. Thin facade over\n * `genson-js` so consumers don't depend on it directly and we can swap\n * the implementation later.\n */\n\nimport { createSchema, mergeSchemas } from 'genson-js';\nimport type { SquisqAnnotatedSchema } from './types.js';\n\nexport interface InferSchemaOptions {\n /** Additional samples merged into the inferred schema for broader coverage. */\n additionalSamples?: readonly unknown[];\n}\n\nexport function inferSchema(\n sample: unknown,\n options: InferSchemaOptions = {},\n): SquisqAnnotatedSchema {\n const base = createSchema(sample) as SquisqAnnotatedSchema;\n if (!options.additionalSamples || options.additionalSamples.length === 0) {\n return base;\n }\n const extras = options.additionalSamples.map((s) => createSchema(s) as SquisqAnnotatedSchema);\n return mergeSchemas([base, ...extras] as never[]) as SquisqAnnotatedSchema;\n}\n","/**\n * Pure CSS-custom-property builder shared by `<JsonView>` (read-only, in\n * `@bendyline/squisq-react`) and `<JsonEditor>` (editable, in\n * `@bendyline/squisq-editor-react`).\n *\n * Both components derive an identical token bag from a Theme + resolved\n * Surface; they differ only in the CSS-var prefix (`--squisq-json` vs\n * `--squisq-jsonform`) so the viewer and editor can coexist on one page.\n * This function is the single source of that derivation. It is pure and\n * framework-free: it performs no `window` / `matchMedia` access — the\n * surface must already be resolved to a concrete `SurfaceScheme` (or left\n * `undefined` to skip surface application) by the calling React hook.\n */\n\nimport { applySurface, DEFAULT_THEME, resolveFontFamily } from '../schemas/index.js';\nimport type { SurfaceScheme, Theme } from '../schemas/index.js';\n\nexport interface BuildJsonFormTokensOptions {\n /**\n * CSS-var prefix WITHOUT the trailing dash, e.g. `--squisq-json` or\n * `--squisq-jsonform`. Each token key is `${prefix}-${name}`.\n */\n prefix: string;\n}\n\n/**\n * Build the CSS custom-property map for a JSON form/view surface.\n *\n * @param theme The theme to render with (falls back to `DEFAULT_THEME`).\n * @param surface Already-resolved concrete surface, or `undefined` to leave\n * the theme un-surfaced. (The `'auto'` → concrete resolution\n * is the React hook's job, so this stays pure.)\n * @param options `{ prefix }` — the CSS-var namespace.\n */\nexport function buildJsonFormTokens(\n theme: Theme | undefined,\n surface: SurfaceScheme | undefined,\n options: BuildJsonFormTokensOptions,\n): Record<string, string> {\n const { prefix } = options;\n const baseTheme = theme ?? DEFAULT_THEME;\n const finalTheme = surface ? applySurface(baseTheme, surface) : baseTheme;\n\n const titleFont = resolveFontFamily(finalTheme.typography.titleFont, 'system-ui, sans-serif');\n const bodyFont = resolveFontFamily(finalTheme.typography.bodyFont, 'system-ui, sans-serif');\n const monoFont = resolveFontFamily(\n finalTheme.typography.monoFont,\n 'ui-monospace, Consolas, monospace',\n );\n\n return {\n [`${prefix}-bg`]: finalTheme.colors.background,\n [`${prefix}-text`]: finalTheme.colors.text,\n [`${prefix}-muted`]: finalTheme.colors.textMuted,\n [`${prefix}-primary`]: finalTheme.colors.primary,\n [`${prefix}-accent`]: finalTheme.colors.secondary,\n [`${prefix}-warning`]: finalTheme.colors.warning,\n [`${prefix}-border`]: `color-mix(in srgb, ${finalTheme.colors.textMuted} 35%, transparent)`,\n [`${prefix}-input-bg`]: finalTheme.colors.backgroundLight,\n [`${prefix}-title-font`]: titleFont,\n [`${prefix}-body-font`]: bodyFont,\n [`${prefix}-mono-font`]: monoFont,\n [`${prefix}-radius`]: `${finalTheme.style.borderRadius ?? 8}px`,\n };\n}\n\n/**\n * Resolve the effective theme after surface application — the companion to\n * `buildJsonFormTokens` for callers that also need the `Theme` object (both\n * hooks return it alongside the style bag).\n */\nexport function resolveJsonFormTheme(\n theme: Theme | undefined,\n surface: SurfaceScheme | undefined,\n): Theme {\n const baseTheme = theme ?? DEFAULT_THEME;\n return surface ? applySurface(baseTheme, surface) : baseTheme;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseFrontmatter,
3
3
  parseHtmlToNodes
4
- } from "./chunk-OKJOQXK4.js";
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) {
@@ -1242,6 +1250,7 @@ export {
1242
1250
  KNOWN_BLOCK_META_KEYS,
1243
1251
  BLOCK_META_KEY_DESCRIPTORS,
1244
1252
  coerceAnnotationValues,
1253
+ parseNumber,
1245
1254
  parseTimeSeconds,
1246
1255
  matchTrailingTemplateAnnotation,
1247
1256
  matchTrailingPandocAttr,
@@ -1250,10 +1259,11 @@ export {
1250
1259
  splitKeyValueToken,
1251
1260
  needsQuoting,
1252
1261
  quoteAttrValue,
1262
+ serializeAnnotation,
1253
1263
  parsePandocAttrTokens,
1254
1264
  serializePandocAttributes,
1255
1265
  fromMdast,
1256
1266
  toMdast,
1257
1267
  parseMarkdown
1258
1268
  };
1259
- //# sourceMappingURL=chunk-OH43RPTP.js.map
1269
+ //# sourceMappingURL=chunk-TEEEILMP.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\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) suffixes.push(pandoc);\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":"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","names":[]}
@@ -18,11 +18,52 @@ var QUOTE_PATTERNS = [
18
18
  /\u201c([^\u201d]{15,200})\u201d/g,
19
19
  /(\w+(?:\s+\w+)?)\s+(?:said|stated|wrote|noted|observed|commented),?\s*"([^"]+)"/gi
20
20
  ];
21
+ var COMPARISON_SIDE = "[^.!?;\\r\\n]{2,30}";
21
22
  var COMPARISON_PATTERNS = [
22
- /from\s+(.{2,30})\s+to\s+(.{2,30})/gi,
23
- /(.{2,30})\s+(?:vs\.?|versus)\s+(.{2,30})/gi,
24
- /(.{2,30})\s+compared\s+to\s+(.{2,30})/gi
23
+ new RegExp(`from\\s+(${COMPARISON_SIDE})\\s+to\\s+(${COMPARISON_SIDE})`, "gi"),
24
+ new RegExp(`(${COMPARISON_SIDE})\\s+(?:vs\\.?|versus)\\s+(${COMPARISON_SIDE})`, "gi"),
25
+ new RegExp(`(${COMPARISON_SIDE})\\s+compared\\s+to\\s+(${COMPARISON_SIDE})`, "gi")
25
26
  ];
27
+ var SENTENCE_ABBREVIATIONS = /* @__PURE__ */ new Set([
28
+ "mr",
29
+ "mrs",
30
+ "ms",
31
+ "dr",
32
+ "prof",
33
+ "st",
34
+ "mt",
35
+ "jr",
36
+ "sr",
37
+ "gen",
38
+ "col",
39
+ "capt",
40
+ "lt",
41
+ "sgt",
42
+ "rev",
43
+ "hon",
44
+ "sen",
45
+ "rep",
46
+ "gov",
47
+ "pres",
48
+ "vs",
49
+ "etc",
50
+ "inc",
51
+ "co",
52
+ "ltd",
53
+ "corp",
54
+ "al",
55
+ "fig",
56
+ "eg",
57
+ "ie",
58
+ "approx",
59
+ "dept",
60
+ "univ",
61
+ "assn",
62
+ "bros",
63
+ "ave",
64
+ "blvd",
65
+ "rd"
66
+ ]);
26
67
  var FACT_INDICATORS = [
27
68
  "largest",
28
69
  "smallest",
@@ -261,10 +302,17 @@ function extractComparisons(text) {
261
302
  const position = match.index;
262
303
  if (seen.has(position)) continue;
263
304
  seen.add(position);
264
- const left = match[1].trim();
265
- const right = match[2].trim();
305
+ const trimClause = (s) => s.replace(/,\s+.*$/, "").trim();
306
+ const left = trimClause(match[1].trim());
307
+ const right = trimClause(match[2].trim());
266
308
  if (left.length < 2 || left.length > 30) continue;
267
309
  if (right.length < 2 || right.length > 30) continue;
310
+ const wordsOf = (s) => s.toLowerCase().split(/\s+/).map((w) => w.replace(/[^a-z0-9]+/g, "")).filter(Boolean);
311
+ const leftWords = wordsOf(left);
312
+ const rightWords = wordsOf(right);
313
+ if (leftWords.length > 0 && rightWords.length > 0 && leftWords[leftWords.length - 1] === rightWords[0]) {
314
+ continue;
315
+ }
268
316
  const context = getSentenceContext(text, position, match[0].length);
269
317
  elements.push({
270
318
  type: "comparison",
@@ -278,28 +326,46 @@ function extractComparisons(text) {
278
326
  }
279
327
  return elements;
280
328
  }
329
+ function isSentenceBoundary(text, index) {
330
+ const ch = text[index];
331
+ if (ch === "!" || ch === "?") return true;
332
+ if (ch !== ".") return false;
333
+ if (text[index + 1] === ".") return false;
334
+ if (/[0-9]/.test(text[index - 1] ?? "") && /[0-9]/.test(text[index + 1] ?? "")) return false;
335
+ let w = index - 1;
336
+ while (w >= 0 && /[A-Za-z]/.test(text[w])) w--;
337
+ const word = text.slice(w + 1, index);
338
+ if (word.length === 1) return false;
339
+ if (word && SENTENCE_ABBREVIATIONS.has(word.toLowerCase())) return false;
340
+ let j = index + 1;
341
+ while (j < text.length && /\s/.test(text[j])) j++;
342
+ if (j >= text.length) return true;
343
+ return !/[a-z]/.test(text[j]);
344
+ }
281
345
  function forEachSentenceSpan(text, callback) {
282
- const sentenceRegex = /[^.!?]+[.!?]*/g;
283
- let match;
284
- while ((match = sentenceRegex.exec(text)) !== null) {
285
- const raw = match[0];
286
- const rawStart = match.index;
287
- const rawEnd = rawStart + raw.length;
288
- let leading = 0;
289
- while (leading < raw.length && /\s/.test(raw[leading])) {
290
- leading++;
291
- }
292
- let trailing = 0;
293
- while (trailing < raw.length - leading && /[\s.!?]/.test(raw[raw.length - 1 - trailing])) {
294
- trailing++;
295
- }
296
- const start = rawStart + leading;
297
- const end = rawEnd - trailing;
298
- if (end <= start) continue;
346
+ const emit = (rawStart, rawEnd) => {
347
+ let start = rawStart;
348
+ while (start < rawEnd && /\s/.test(text[start])) start++;
349
+ let end = rawEnd;
350
+ while (end > start && /[\s.!?]/.test(text[end - 1])) end--;
351
+ if (end <= start) return;
299
352
  const trimmed = text.slice(start, end);
300
- if (!trimmed) continue;
301
- callback(trimmed, start, end);
353
+ if (trimmed) callback(trimmed, start, end);
354
+ };
355
+ let spanStart = 0;
356
+ let i = 0;
357
+ while (i < text.length) {
358
+ if (isSentenceBoundary(text, i)) {
359
+ let end = i + 1;
360
+ while (end < text.length && /[.!?]/.test(text[end])) end++;
361
+ emit(spanStart, end);
362
+ spanStart = end;
363
+ i = end;
364
+ } else {
365
+ i++;
366
+ }
302
367
  }
368
+ if (spanStart < text.length) emit(spanStart, text.length);
303
369
  }
304
370
  function extractFacts(text) {
305
371
  const elements = [];
@@ -401,10 +467,10 @@ function extractDefinitions(text) {
401
467
  }
402
468
  function getSentenceContext(text, matchStart, matchLength) {
403
469
  let start = matchStart;
404
- while (start > 0 && !/[.!?]/.test(text[start - 1])) start--;
470
+ while (start > 0 && !isSentenceBoundary(text, start - 1)) start--;
405
471
  while (start < matchStart && /\s/.test(text[start])) start++;
406
472
  let end = matchStart + matchLength;
407
- while (end < text.length && !/[.!?]/.test(text[end])) end++;
473
+ while (end < text.length && !isSentenceBoundary(text, end)) end++;
408
474
  if (end < text.length) end++;
409
475
  return { sentence: text.slice(start, end).trim(), start, end };
410
476
  }
@@ -652,4 +718,4 @@ export {
652
718
  stripMarkdown,
653
719
  mapElementToBlock
654
720
  };
655
- //# sourceMappingURL=chunk-FR2RBTKO.js.map
721
+ //# sourceMappingURL=chunk-TQWLI6S2.js.map