@bendyline/squisq 1.5.1 → 2.0.0

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 (268) hide show
  1. package/README.md +63 -10
  2. package/dist/{Doc-BBVGq1_9.d.ts → Doc-ylqWKDc_.d.ts} +248 -37
  3. package/dist/{ImageEditDoc-FuyGtt6o.d.ts → ImageEditDoc-D828IXJ1.d.ts} +1 -1
  4. package/dist/annotationCoercion-Dr_DY9Hx.d.ts +86 -0
  5. package/dist/{chunk-VWUFZ6ZG.js → chunk-3E5F2XMR.js} +2 -29
  6. package/dist/chunk-3E5F2XMR.js.map +1 -0
  7. package/dist/chunk-4X3JQXNM.js +35 -0
  8. package/dist/chunk-4X3JQXNM.js.map +1 -0
  9. package/dist/{chunk-2OIBZYKP.js → chunk-5DKBFXPY.js} +31 -121
  10. package/dist/chunk-5DKBFXPY.js.map +1 -0
  11. package/dist/{chunk-DLXZMS5K.js → chunk-5J4HKRJF.js} +51 -3
  12. package/dist/chunk-5J4HKRJF.js.map +1 -0
  13. package/dist/chunk-5UYI3RJB.js +2503 -0
  14. package/dist/chunk-5UYI3RJB.js.map +1 -0
  15. package/dist/{chunk-TEEEILMP.js → chunk-66QO7XFB.js} +19 -292
  16. package/dist/chunk-66QO7XFB.js.map +1 -0
  17. package/dist/{chunk-KKNUBQ6Y.js → chunk-6PJRB2CF.js} +156 -91
  18. package/dist/chunk-6PJRB2CF.js.map +1 -0
  19. package/dist/{chunk-HWVFJAAH.js → chunk-AMIR72JP.js} +28 -10
  20. package/dist/chunk-AMIR72JP.js.map +1 -0
  21. package/dist/chunk-FVJUUTEM.js +74 -0
  22. package/dist/chunk-FVJUUTEM.js.map +1 -0
  23. package/dist/{chunk-LH7I6SH7.js → chunk-GF3BVNLU.js} +6360 -5723
  24. package/dist/chunk-GF3BVNLU.js.map +1 -0
  25. package/dist/{chunk-Q3ROPT5H.js → chunk-GZY66SDN.js} +1 -1
  26. package/dist/{chunk-Q3ROPT5H.js.map → chunk-GZY66SDN.js.map} +1 -1
  27. package/dist/chunk-JOQYPRDZ.js +127 -0
  28. package/dist/chunk-JOQYPRDZ.js.map +1 -0
  29. package/dist/{chunk-A4O7GIWE.js → chunk-LYNJEOAC.js} +10 -73
  30. package/dist/chunk-LYNJEOAC.js.map +1 -0
  31. package/dist/{chunk-LDQ2HJIX.js → chunk-PXLVXTLW.js} +252 -54
  32. package/dist/chunk-PXLVXTLW.js.map +1 -0
  33. package/dist/{chunk-TQWLI6S2.js → chunk-Q7LROV2H.js} +4 -2
  34. package/dist/{chunk-TQWLI6S2.js.map → chunk-Q7LROV2H.js.map} +1 -1
  35. package/dist/{chunk-T5UK6YOB.js → chunk-QTSJOY54.js} +60 -17
  36. package/dist/chunk-QTSJOY54.js.map +1 -0
  37. package/dist/chunk-QVANWDPH.js +2358 -0
  38. package/dist/chunk-QVANWDPH.js.map +1 -0
  39. package/dist/{chunk-6ATE2PSM.js → chunk-R2GC6RLD.js} +7 -7
  40. package/dist/chunk-R2GC6RLD.js.map +1 -0
  41. package/dist/chunk-R2YZTCLO.js +292 -0
  42. package/dist/chunk-R2YZTCLO.js.map +1 -0
  43. package/dist/chunk-S7A5D7ZJ.js +174 -0
  44. package/dist/chunk-S7A5D7ZJ.js.map +1 -0
  45. package/dist/chunk-T3TI7GSZ.js +884 -0
  46. package/dist/chunk-T3TI7GSZ.js.map +1 -0
  47. package/dist/{chunk-RUDYOTA6.js → chunk-TUWMK62B.js} +110 -4
  48. package/dist/chunk-TUWMK62B.js.map +1 -0
  49. package/dist/{chunk-IMSCRLLV.js → chunk-VR7UPJ7C.js} +35 -25
  50. package/dist/chunk-VR7UPJ7C.js.map +1 -0
  51. package/dist/doc/index.d.ts +2334 -5
  52. package/dist/doc/index.js +134 -34
  53. package/dist/generate/index.d.ts +7 -2
  54. package/dist/generate/index.js +1 -1
  55. package/dist/imageEdit/index.d.ts +9 -6
  56. package/dist/imageEdit/index.js +3 -3
  57. package/dist/index.d.ts +12 -10
  58. package/dist/index.js +253 -81
  59. package/dist/jsonForm/index.d.ts +7 -3
  60. package/dist/jsonForm/index.js +9 -4
  61. package/dist/markdown/index.d.ts +6 -70
  62. package/dist/markdown/index.js +13 -9
  63. package/dist/narration/index.d.ts +618 -0
  64. package/dist/narration/index.js +82 -0
  65. package/dist/recommend/index.d.ts +15 -1
  66. package/dist/recommend/index.js +2 -2
  67. package/dist/schemas/index.d.ts +28 -6
  68. package/dist/schemas/index.js +15 -13
  69. package/dist/spatial/index.d.ts +0 -1
  70. package/dist/spatial/index.js +1 -1
  71. package/dist/storage/index.js +1 -1
  72. package/dist/{themeLibrary-CehcJhzz.d.ts → themeLibrary-RjVgLpUi.d.ts} +10 -11
  73. package/dist/timing/index.js +5 -3
  74. package/dist/transform/index.d.ts +35 -24
  75. package/dist/transform/index.js +9 -10
  76. package/dist/{types-DlAZ7MW4.d.ts → types-BwMPl2CB.d.ts} +1 -1
  77. package/dist/{types-D98li1gV.d.ts → types-C4kiRn_k.d.ts} +3 -1
  78. package/dist/versions/index.d.ts +2 -1
  79. package/dist/versions/index.js +2 -2
  80. package/package.json +6 -6
  81. package/src/__tests__/applyNarrationTiming.test.ts +190 -0
  82. package/src/__tests__/applyRenderStyle.test.ts +3 -3
  83. package/src/__tests__/asciiDiagramBattle.test.ts +246 -0
  84. package/src/__tests__/asciiDiagramDetect.test.ts +162 -0
  85. package/src/__tests__/asciiDiagramMapping.test.ts +38 -0
  86. package/src/__tests__/asciiDiagramParse.test.ts +233 -0
  87. package/src/__tests__/asciiDiagramPipeline.test.ts +273 -0
  88. package/src/__tests__/asciiDiagramRender.test.ts +162 -0
  89. package/src/__tests__/asciiDiagramRepair.test.ts +84 -0
  90. package/src/__tests__/asciiTimelineDetect.test.ts +157 -0
  91. package/src/__tests__/asciiTimelineParse.test.ts +205 -0
  92. package/src/__tests__/asciiTimelinePipeline.test.ts +349 -0
  93. package/src/__tests__/asciiTimelineRender.test.ts +376 -0
  94. package/src/__tests__/attrTokens.test.ts +5 -0
  95. package/src/__tests__/contentContainer.test.ts +43 -0
  96. package/src/__tests__/coverBlock.test.ts +33 -2
  97. package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -15
  98. package/src/__tests__/dataTable.test.ts +25 -15
  99. package/src/__tests__/diagramBlock.test.ts +3 -3
  100. package/src/__tests__/diagramText.test.ts +26 -0
  101. package/src/__tests__/docToMarkdownMedia.test.ts +83 -0
  102. package/src/__tests__/drawingBlock.test.ts +3 -3
  103. package/src/__tests__/fixtures/asciiDiagramBattle.ts +512 -0
  104. package/src/__tests__/fixtures/asciiDiagrams.ts +287 -0
  105. package/src/__tests__/fixtures/asciiTimelines.ts +35 -0
  106. package/src/__tests__/fixtures/treeviewFixtures.ts +243 -0
  107. package/src/__tests__/getLayers.test.ts +32 -18
  108. package/src/__tests__/getLayersFallback.test.ts +8 -8
  109. package/src/__tests__/imageEditVersions.test.ts +2 -0
  110. package/src/__tests__/jsonForm.pointer.test.ts +47 -1
  111. package/src/__tests__/layoutBlock.test.ts +3 -3
  112. package/src/__tests__/majorApiTypes.test.ts +18 -0
  113. package/src/__tests__/markdown.test.ts +41 -0
  114. package/src/__tests__/markdownToDoc.test.ts +5 -2
  115. package/src/__tests__/matchFontFamily.test.ts +114 -0
  116. package/src/__tests__/materializeBlockLayers.test.ts +135 -0
  117. package/src/__tests__/materializeTestUtils.ts +17 -0
  118. package/src/__tests__/narrationAlign.test.ts +150 -0
  119. package/src/__tests__/narrationFeatures.test.ts +71 -0
  120. package/src/__tests__/narrationNuclei.test.ts +45 -0
  121. package/src/__tests__/narrationPacing.test.ts +189 -0
  122. package/src/__tests__/narrationScript.test.ts +149 -0
  123. package/src/__tests__/narrationSessionRepro.test.ts +62 -0
  124. package/src/__tests__/narrationSidecar.test.ts +88 -0
  125. package/src/__tests__/narrationSyllables.test.ts +65 -0
  126. package/src/__tests__/narrationTestSignals.ts +167 -0
  127. package/src/__tests__/narrationVad.test.ts +66 -0
  128. package/src/__tests__/resolveDocTheme.test.ts +16 -2
  129. package/src/__tests__/rootExports.test.ts +18 -0
  130. package/src/__tests__/scopedContentContainer.test.ts +20 -0
  131. package/src/__tests__/shapeGeometry.test.ts +49 -0
  132. package/src/__tests__/templateAnnotationParse.test.ts +10 -0
  133. package/src/__tests__/templateInputs.test.ts +69 -0
  134. package/src/__tests__/templateMetadata.test.ts +2 -1
  135. package/src/__tests__/templates.test.ts +143 -17
  136. package/src/__tests__/themeCompile.test.ts +49 -22
  137. package/src/__tests__/transformProvenance.test.ts +160 -0
  138. package/src/__tests__/transformV2.test.ts +40 -14
  139. package/src/__tests__/treeviewBattle.test.ts +76 -0
  140. package/src/__tests__/treeviewDetect.test.ts +168 -0
  141. package/src/__tests__/treeviewParse.test.ts +118 -0
  142. package/src/__tests__/treeviewPipeline.test.ts +183 -0
  143. package/src/__tests__/treeviewRender.test.ts +91 -0
  144. package/src/__tests__/versions.test.ts +44 -0
  145. package/src/doc/applyNarrationTiming.ts +244 -0
  146. package/src/doc/asciiDiagram/chars.ts +210 -0
  147. package/src/doc/asciiDiagram/detect.ts +175 -0
  148. package/src/doc/asciiDiagram/index.ts +38 -0
  149. package/src/doc/asciiDiagram/mapping.ts +225 -0
  150. package/src/doc/asciiDiagram/parse.ts +1095 -0
  151. package/src/doc/asciiDiagram/render.ts +963 -0
  152. package/src/doc/asciiDiagram/repair.ts +400 -0
  153. package/src/doc/asciiDiagram/types.ts +65 -0
  154. package/src/doc/asciiTimeline/detect.ts +138 -0
  155. package/src/doc/asciiTimeline/index.ts +26 -0
  156. package/src/doc/asciiTimeline/mapping.ts +43 -0
  157. package/src/doc/asciiTimeline/parse.ts +870 -0
  158. package/src/doc/asciiTimeline/render.ts +427 -0
  159. package/src/doc/asciiTimeline/types.ts +73 -0
  160. package/src/doc/asciiTimeline/wrappedFlow.ts +427 -0
  161. package/src/doc/audioMapping.ts +60 -46
  162. package/src/doc/customTemplatesFrontmatter.ts +0 -41
  163. package/src/doc/docToMarkdown.ts +126 -39
  164. package/src/doc/index.ts +76 -4
  165. package/src/doc/markdownToDoc.ts +135 -1
  166. package/src/doc/materializeBlockLayers.ts +304 -0
  167. package/src/doc/mediaAnnotations.ts +20 -0
  168. package/src/doc/resolveDocTheme.ts +8 -7
  169. package/src/doc/templateInputs.ts +185 -2
  170. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +22 -8
  171. package/src/doc/templates/coverBlock.ts +49 -13
  172. package/src/doc/templates/diagramBlock.ts +237 -38
  173. package/src/doc/templates/drawingBlock.ts +2 -2
  174. package/src/doc/templates/fallbackBlock.ts +1 -1
  175. package/src/doc/templates/index.ts +134 -279
  176. package/src/doc/templates/inputDescriptors.ts +20 -1
  177. package/src/doc/templates/metadata.ts +10 -0
  178. package/src/doc/templates/persistentLayers.ts +12 -126
  179. package/src/doc/templates/registry.ts +88 -0
  180. package/src/doc/templates/templateNames.ts +22 -0
  181. package/src/doc/templates/timelineBlock.ts +613 -0
  182. package/src/doc/templates/treeBlock.ts +131 -0
  183. package/src/doc/treeview/chars.ts +72 -0
  184. package/src/doc/treeview/detect.ts +145 -0
  185. package/src/doc/treeview/index.ts +30 -0
  186. package/src/doc/treeview/mapping.ts +95 -0
  187. package/src/doc/treeview/parse.ts +201 -0
  188. package/src/doc/treeview/render.ts +41 -0
  189. package/src/doc/treeview/types.ts +43 -0
  190. package/src/doc/utils/applyRenderStyle.ts +2 -9
  191. package/src/doc/utils/diagramText.ts +78 -0
  192. package/src/doc/utils/shapeGeometry.ts +157 -7
  193. package/src/doc/validate.ts +1 -1
  194. package/src/generate/templateMapper.ts +7 -0
  195. package/src/imageEdit/export.ts +6 -5
  196. package/src/imageEdit/versionPaths.ts +13 -26
  197. package/src/imageEdit/versions.ts +21 -100
  198. package/src/index.ts +1 -0
  199. package/src/internal/immutable.ts +58 -0
  200. package/src/jsonForm/index.ts +9 -1
  201. package/src/jsonForm/pointer.ts +68 -23
  202. package/src/markdown/attrTokens.ts +7 -5
  203. package/src/markdown/convert.ts +11 -1
  204. package/src/markdown/index.ts +1 -0
  205. package/src/markdown/stringify.ts +4 -9
  206. package/src/markdown/utils.ts +36 -14
  207. package/src/narration/align.ts +514 -0
  208. package/src/narration/features.ts +231 -0
  209. package/src/narration/index.ts +86 -0
  210. package/src/narration/nuclei.ts +118 -0
  211. package/src/narration/pacing.ts +211 -0
  212. package/src/narration/script.ts +239 -0
  213. package/src/narration/session.ts +100 -0
  214. package/src/narration/sidecar.ts +187 -0
  215. package/src/narration/syllables.ts +85 -0
  216. package/src/narration/trace.ts +50 -0
  217. package/src/narration/types.ts +299 -0
  218. package/src/narration/vad.ts +131 -0
  219. package/src/recommend/numberHighlight.ts +25 -0
  220. package/src/recommend/templates.ts +108 -12
  221. package/src/schemas/BlockTemplates.ts +156 -7
  222. package/src/schemas/Doc.ts +72 -9
  223. package/src/schemas/Media.ts +12 -2
  224. package/src/schemas/Theme.ts +45 -25
  225. package/src/schemas/Viewport.ts +2 -2
  226. package/src/schemas/fontStacks.ts +79 -0
  227. package/src/schemas/themeCompile.ts +15 -0
  228. package/src/schemas/themeConstants.ts +2 -0
  229. package/src/schemas/themeLibrary.ts +25 -19
  230. package/src/schemas/themeValidator.ts +1 -1
  231. package/src/spatial/Geohash.ts +0 -3
  232. package/src/storage/ContentContainer.ts +17 -9
  233. package/src/storage/ScopedContentContainer.ts +45 -15
  234. package/src/transform/applyTransform.ts +5 -5
  235. package/src/transform/index.ts +3 -1
  236. package/src/transform/registry.ts +169 -49
  237. package/src/transform/templateSelector.ts +21 -2
  238. package/src/transform/timingAllocator.ts +152 -21
  239. package/src/transform/types.ts +14 -1
  240. package/src/versions/gitignore.ts +34 -0
  241. package/src/versions/index.ts +2 -0
  242. package/src/versions/operations.ts +18 -104
  243. package/src/versions/paths.ts +13 -28
  244. package/src/versions/snapshotStore.ts +202 -0
  245. package/src/versions/types.ts +3 -1
  246. package/dist/chunk-2OIBZYKP.js.map +0 -1
  247. package/dist/chunk-6ATE2PSM.js.map +0 -1
  248. package/dist/chunk-A4O7GIWE.js.map +0 -1
  249. package/dist/chunk-DLXZMS5K.js.map +0 -1
  250. package/dist/chunk-HWVFJAAH.js.map +0 -1
  251. package/dist/chunk-IMSCRLLV.js.map +0 -1
  252. package/dist/chunk-JELIRVDP.js +0 -24
  253. package/dist/chunk-JELIRVDP.js.map +0 -1
  254. package/dist/chunk-KKNUBQ6Y.js.map +0 -1
  255. package/dist/chunk-LDQ2HJIX.js.map +0 -1
  256. package/dist/chunk-LH7I6SH7.js.map +0 -1
  257. package/dist/chunk-RUDYOTA6.js.map +0 -1
  258. package/dist/chunk-T5UK6YOB.js.map +0 -1
  259. package/dist/chunk-TEEEILMP.js.map +0 -1
  260. package/dist/chunk-UY7KGQ5R.js +0 -192
  261. package/dist/chunk-UY7KGQ5R.js.map +0 -1
  262. package/dist/chunk-VLJJHDAC.js +0 -218
  263. package/dist/chunk-VLJJHDAC.js.map +0 -1
  264. package/dist/chunk-VWUFZ6ZG.js.map +0 -1
  265. package/dist/story/index.d.ts +0 -1777
  266. package/dist/story/index.js +0 -273
  267. package/src/doc/getLayers.ts +0 -195
  268. /package/dist/{story → narration}/index.js.map +0 -0
@@ -160,6 +160,14 @@ function parseFrontmatter(yaml) {
160
160
  if (!yaml || !yaml.trim()) return null;
161
161
  const result = {};
162
162
  const lines = yaml.split("\n");
163
+ const assign = (key, value) => {
164
+ Object.defineProperty(result, key, {
165
+ value,
166
+ enumerable: true,
167
+ configurable: true,
168
+ writable: true
169
+ });
170
+ };
163
171
  for (let i = 0; i < lines.length; i++) {
164
172
  const line = lines[i];
165
173
  const trimmed = line.trim();
@@ -167,7 +175,7 @@ function parseFrontmatter(yaml) {
167
175
  const colonIdx = trimmed.indexOf(":");
168
176
  if (colonIdx < 1) continue;
169
177
  const key = trimmed.slice(0, colonIdx).trim();
170
- let value = trimmed.slice(colonIdx + 1).trim();
178
+ const value = trimmed.slice(colonIdx + 1).trim();
171
179
  if (value === "|" || value === "|-") {
172
180
  const keyIndent = line.length - line.trimStart().length;
173
181
  const blockLines = [];
@@ -192,20 +200,29 @@ function parseFrontmatter(yaml) {
192
200
  commonIndent = Math.min(commonIndent, bl.length - bl.trimStart().length);
193
201
  }
194
202
  if (!isFinite(commonIndent)) commonIndent = 0;
195
- result[key] = blockLines.map((bl) => bl === "" ? "" : bl.slice(commonIndent)).join("\n");
203
+ assign(key, blockLines.map((bl) => bl === "" ? "" : bl.slice(commonIndent)).join("\n"));
196
204
  continue;
197
205
  }
198
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
199
- value = value.slice(1, -1);
206
+ if (value.startsWith('"') && value.endsWith('"')) {
207
+ try {
208
+ assign(key, JSON.parse(value));
209
+ } catch {
210
+ assign(key, value.slice(1, -1));
211
+ }
212
+ continue;
213
+ }
214
+ if (value.startsWith("'") && value.endsWith("'")) {
215
+ assign(key, value.slice(1, -1).replace(/''/g, "'"));
216
+ continue;
200
217
  }
201
218
  if (value === "true") {
202
- result[key] = true;
219
+ assign(key, true);
203
220
  } else if (value === "false") {
204
- result[key] = false;
221
+ assign(key, false);
205
222
  } else if (value !== "" && !isNaN(Number(value))) {
206
- result[key] = Number(value);
223
+ assign(key, Number(value));
207
224
  } else {
208
- result[key] = value;
225
+ assign(key, value);
209
226
  }
210
227
  }
211
228
  return Object.keys(result).length > 0 ? result : null;
@@ -220,7 +237,7 @@ function formatFrontmatterValue(value) {
220
237
  if (typeof value === "boolean" || typeof value === "number") return String(value);
221
238
  if (/[\r\n]/.test(value)) return formatBlockScalar(value.replace(/\r\n?/g, "\n"));
222
239
  if (/^[{[]/.test(value) && !/[\r\n]/.test(value)) return value;
223
- const needsQuote = /^\s|\s$/.test(value) || /^[!&*?|>%@`]/.test(value) || /[:#]/.test(value) || value === "" || value === "true" || value === "false" || /^-?\d+(\.\d+)?$/.test(value);
240
+ const needsQuote = /^\s|\s$/.test(value) || /^[!&*?|>%@`]/.test(value) || /[:#]/.test(value) || value === "" || value === "true" || value === "false" || value !== "" && !Number.isNaN(Number(value)) || /^["']/.test(value) && /["']$/.test(value);
224
241
  if (!needsQuote) return value;
225
242
  const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
226
243
  return `"${escaped}"`;
@@ -353,10 +370,11 @@ export {
353
370
  countNodes,
354
371
  parseFrontmatter,
355
372
  formatBlockScalar,
373
+ formatFrontmatterValue,
356
374
  setFrontmatterValues,
357
375
  createDocument,
358
376
  inferDocumentTitle,
359
377
  readFrontmatterThemeId,
360
378
  plainTextFromInlineHtml
361
379
  };
362
- //# sourceMappingURL=chunk-HWVFJAAH.js.map
380
+ //# sourceMappingURL=chunk-AMIR72JP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/markdown/htmlParse.ts","../src/markdown/utils.ts"],"sourcesContent":["/**\n * HTML ↔ HtmlNode Conversion\n *\n * Parses raw HTML strings into the squisq HtmlNode tree representation\n * using hast-util-from-html (parse5-backed), and serializes HtmlNode trees\n * back to HTML strings with a lightweight custom serializer.\n */\n\nimport { fromHtml } from 'hast-util-from-html';\nimport type { HtmlNode } from './types.js';\n\n// ============================================\n// Hast → HtmlNode conversion\n// ============================================\n\n/**\n * Mapping from hast DOM-style property names to HTML attribute names.\n * Only includes names where the property and attribute differ.\n */\nconst HAST_PROP_TO_ATTR: Record<string, string> = {\n className: 'class',\n htmlFor: 'for',\n httpEquiv: 'http-equiv',\n acceptCharset: 'accept-charset',\n};\n\n/** HTML void elements that are self-closing. */\nconst VOID_ELEMENTS = new Set([\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n]);\n\n/**\n * Convert a hast property name to an HTML attribute name.\n */\nfunction propertyToAttribute(prop: string): string {\n if (HAST_PROP_TO_ATTR[prop]) return HAST_PROP_TO_ATTR[prop];\n\n // data-* attributes: dataFooBar → data-foo-bar\n if (prop.length > 4 && prop.startsWith('data') && prop[4] >= 'A' && prop[4] <= 'Z') {\n return (\n 'data-' +\n prop\n .slice(4)\n .replace(/[A-Z]/g, (m) => '-' + m.toLowerCase())\n .replace(/^-/, '')\n );\n }\n\n // aria-* attributes: ariaLabel → aria-label\n if (prop.length > 4 && prop.startsWith('aria') && prop[4] >= 'A' && prop[4] <= 'Z') {\n return (\n 'aria-' +\n prop\n .slice(4)\n .replace(/[A-Z]/g, (m) => '-' + m.toLowerCase())\n .replace(/^-/, '')\n );\n }\n\n // Most HTML attributes are lowercase (tabIndex → tabindex, readOnly → readonly, etc.)\n return prop.toLowerCase();\n}\n\n/**\n * Convert hast element properties to a flat HTML attribute map.\n */\nfunction convertHastProperties(properties: Record<string, unknown>): Record<string, string> {\n const attrs: Record<string, string> = {};\n for (const [key, value] of Object.entries(properties)) {\n if (value === false || value === undefined || value === null) continue;\n\n const attrName = propertyToAttribute(key);\n\n if (value === true) {\n attrs[attrName] = '';\n } else if (Array.isArray(value)) {\n attrs[attrName] = value.join(' ');\n } else {\n attrs[attrName] = String(value);\n }\n }\n return attrs;\n}\n\n/**\n * Recursively convert hast child nodes to HtmlNode[].\n */\nfunction hastChildrenToNodes(children: unknown[]): HtmlNode[] {\n const result: HtmlNode[] = [];\n for (const child of children) {\n const node = hastNodeToHtmlNode(child as Record<string, unknown>);\n if (node) result.push(node);\n }\n return result;\n}\n\n/**\n * Convert a single hast node to an HtmlNode.\n */\nfunction hastNodeToHtmlNode(node: Record<string, unknown>): HtmlNode | null {\n switch (node.type) {\n case 'element': {\n const tagName = node.tagName as string;\n const properties = (node.properties ?? {}) as Record<string, unknown>;\n const children = (node.children ?? []) as unknown[];\n return {\n type: 'htmlElement',\n tagName,\n attributes: convertHastProperties(properties),\n children: hastChildrenToNodes(children),\n selfClosing: VOID_ELEMENTS.has(tagName) && children.length === 0,\n };\n }\n case 'text':\n return {\n type: 'htmlText',\n value: node.value as string,\n };\n case 'comment':\n return {\n type: 'htmlComment',\n value: node.value as string,\n };\n case 'doctype':\n // Doctype nodes are rare in markdown HTML; skip them\n return null;\n default:\n return null;\n }\n}\n\n// ============================================\n// Public API\n// ============================================\n\n/**\n * Parse a raw HTML string into an HtmlNode[] tree.\n *\n * Uses the HTML5 parsing algorithm (via parse5) for correct handling of\n * self-closing tags, optional end tags, entity decoding, etc.\n *\n * @param html - Raw HTML string to parse\n * @returns Array of top-level HtmlNode elements\n */\nexport function parseHtmlToNodes(html: string): HtmlNode[] {\n if (!html || !html.trim()) return [];\n\n try {\n const tree = fromHtml(html, { fragment: true });\n return hastChildrenToNodes(tree.children as unknown[]);\n } catch {\n // If parsing fails, return the raw text as a single text node\n return [{ type: 'htmlText', value: html }];\n }\n}\n\n/**\n * Escape special characters in HTML attribute values.\n */\nfunction escapeHtmlAttr(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/**\n * Serialize an HtmlNode back to an HTML string.\n */\nfunction stringifyNode(node: HtmlNode): string {\n switch (node.type) {\n case 'htmlElement': {\n const attrs = Object.entries(node.attributes)\n .map(([k, v]) => (v === '' ? k : `${k}=\"${escapeHtmlAttr(v)}\"`))\n .join(' ');\n const open = attrs ? `<${node.tagName} ${attrs}>` : `<${node.tagName}>`;\n\n if (node.selfClosing) return open;\n\n const inner = node.children.map(stringifyNode).join('');\n return `${open}${inner}</${node.tagName}>`;\n }\n case 'htmlText':\n return node.value;\n case 'htmlComment':\n return `<!--${node.value}-->`;\n }\n}\n\n/**\n * Serialize an HtmlNode[] tree back to an HTML string.\n *\n * @param nodes - Array of HtmlNode elements to serialize\n * @returns HTML string\n */\nexport function stringifyHtmlNodes(nodes: HtmlNode[]): string {\n return nodes.map(stringifyNode).join('');\n}\n","/**\n * Markdown Tree Utilities\n *\n * Helper functions for traversing and querying the MarkdownDocument tree.\n * All functions are pure and operate on the JSON node interfaces.\n */\n\nimport type { MarkdownNode, MarkdownDocument, HtmlNode } from './types.js';\nimport { parseHtmlToNodes } from './htmlParse.js';\n\n/**\n * Get the children of any markdown node, if it has children.\n * Returns an empty array for leaf nodes (text, code, break, etc.).\n *\n * This is useful for generic tree walking where you don't want to\n * check the specific node type.\n */\nexport function getChildren(node: MarkdownNode): MarkdownNode[] {\n if ('children' in node && Array.isArray(node.children)) {\n return node.children as MarkdownNode[];\n }\n return [];\n}\n\n/**\n * Walk the markdown tree depth-first, calling the visitor for each node.\n *\n * The visitor receives the current node and its parent. Return `true`\n * from the visitor to skip the node's children (prune).\n *\n * @param node - The root node to start walking from\n * @param visitor - Called for each node; return true to skip children\n * @param parent - (internal) Parent node\n */\nexport function walkMarkdownTree(\n node: MarkdownNode,\n visitor: (node: MarkdownNode, parent?: MarkdownNode) => void | boolean,\n parent?: MarkdownNode,\n): void {\n const skip = visitor(node, parent);\n if (skip === true) return;\n\n const children = getChildren(node);\n for (const child of children) {\n walkMarkdownTree(child, visitor, node);\n }\n}\n\n/**\n * Find all nodes of a specific type in the tree.\n *\n * @param root - The document or node to search within\n * @param type - The node type to find (e.g., 'heading', 'link', 'text')\n * @returns Array of matching nodes\n *\n * @example\n * ```ts\n * const headings = findNodesByType(doc, 'heading');\n * const links = findNodesByType(doc, 'link');\n * ```\n */\nexport function findNodesByType<T extends MarkdownNode>(root: MarkdownNode, type: T['type']): T[] {\n const results: T[] = [];\n walkMarkdownTree(root, (node) => {\n if (node.type === type) {\n results.push(node as T);\n }\n });\n return results;\n}\n\n/**\n * Extract all plain text content from a node and its descendants.\n * Concatenates text values, ignoring formatting, links, etc.\n *\n * @param node - The node to extract text from\n * @returns Plain text content\n *\n * @example\n * ```ts\n * const heading = doc.children[0]; // { type: 'heading', children: [{ type: 'text', value: 'Hello' }] }\n * extractPlainText(heading); // 'Hello'\n * ```\n */\nexport function extractPlainText(node: MarkdownNode): string {\n if ('value' in node && typeof node.value === 'string') {\n return node.value;\n }\n\n const children = getChildren(node);\n // Preserve boundaries between block-level elements (list items, paragraphs\n // inside list items, blockquotes) so downstream consumers like caption\n // splitting can treat each item as a separate phrase.\n const separator = node.type === 'list' || node.type === 'listItem' ? '\\n' : '';\n return children.map(extractPlainText).join(separator);\n}\n\n/**\n * Count the total number of nodes in the tree.\n *\n * @param node - The root node\n * @returns Total node count (including the root)\n */\nexport function countNodes(node: MarkdownNode): number {\n let count = 1;\n const children = getChildren(node);\n for (const child of children) {\n count += countNodes(child);\n }\n return count;\n}\n\n/**\n * Parse a YAML frontmatter string into a key-value record.\n *\n * Handles simple `key: value` pairs common in markdown frontmatter.\n * Values are trimmed; quoted strings have their quotes removed.\n * Returns `null` if parsing fails or the input is empty.\n *\n * @param yaml - The raw YAML string (without the `---` delimiters)\n * @returns A record of string keys to parsed values, or null\n */\nexport function parseFrontmatter(yaml: string): Record<string, unknown> | null {\n if (!yaml || !yaml.trim()) return null;\n\n const result: Record<string, unknown> = {};\n const lines = yaml.split('\\n');\n\n // Define keys as data properties so a frontmatter key named `__proto__`\n // cannot invoke Object.prototype's legacy setter.\n const assign = (key: string, value: unknown): void => {\n Object.defineProperty(result, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n };\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx < 1) continue;\n\n const key = trimmed.slice(0, colonIdx).trim();\n const value = trimmed.slice(colonIdx + 1).trim();\n\n // YAML literal block scalar: `key: |` or `key: |-`. Subsequent lines\n // that are more-indented than the key belong to the scalar; they are\n // dedented by their common leading indent and joined with `\\n`. A line\n // at the key's indent (or less) ends the scalar. Both `|` and `|-`\n // yield the joined string here (`|-` without a trailing newline); we do\n // not model `|`'s trailing-newline / `+`/`-` chomping nuances beyond\n // stripping trailing blank lines.\n if (value === '|' || value === '|-') {\n const keyIndent = line.length - line.trimStart().length;\n const blockLines: string[] = [];\n let j = i + 1;\n for (; j < lines.length; j++) {\n const bl = lines[j];\n if (bl.trim() === '') {\n blockLines.push('');\n continue;\n }\n const indent = bl.length - bl.trimStart().length;\n if (indent <= keyIndent) break; // dedent ends the scalar\n blockLines.push(bl);\n }\n i = j - 1; // resume the outer walk at the terminating line\n\n // Drop trailing blank lines captured inside the scalar.\n while (blockLines.length > 0 && blockLines[blockLines.length - 1] === '') {\n blockLines.pop();\n }\n\n // Dedent by the common leading whitespace of the non-blank lines.\n let commonIndent = Infinity;\n for (const bl of blockLines) {\n if (bl === '') continue;\n commonIndent = Math.min(commonIndent, bl.length - bl.trimStart().length);\n }\n if (!isFinite(commonIndent)) commonIndent = 0;\n assign(key, blockLines.map((bl) => (bl === '' ? '' : bl.slice(commonIndent))).join('\\n'));\n continue;\n }\n\n // Quoted scalars are always strings, even when their contents look like a\n // number or boolean. Double-quoted values emitted by\n // formatFrontmatterValue use JSON-compatible escaping; single-quoted YAML\n // represents a literal quote as two consecutive quotes.\n if (value.startsWith('\"') && value.endsWith('\"')) {\n try {\n assign(key, JSON.parse(value));\n } catch {\n // Be permissive for hand-authored YAML outside our formatter's subset.\n assign(key, value.slice(1, -1));\n }\n continue;\n }\n if (value.startsWith(\"'\") && value.endsWith(\"'\")) {\n assign(key, value.slice(1, -1).replace(/''/g, \"'\"));\n continue;\n }\n\n // Parse booleans and numbers\n if (value === 'true') {\n assign(key, true);\n } else if (value === 'false') {\n assign(key, false);\n } else if (value !== '' && !isNaN(Number(value))) {\n assign(key, Number(value));\n } else {\n assign(key, value);\n }\n }\n\n return Object.keys(result).length > 0 ? result : null;\n}\n\n/**\n * Format a multi-line string as a YAML literal block scalar body, i.e.\n * `|-` followed by each source line indented two spaces. Paired with\n * {@link parseFrontmatter}'s block-scalar reader so multi-line frontmatter\n * values (e.g. pretty-printed JSON payloads) round-trip. Blank lines stay\n * blank (no trailing whitespace) so the output diffs cleanly.\n */\nexport function formatBlockScalar(value: string): string {\n const body = value\n .split('\\n')\n .map((line) => (line === '' ? '' : ` ${line}`))\n .join('\\n');\n return `|-\\n${body}`;\n}\n\nconst FRONTMATTER_BLOCK_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(\\r?\\n)?/;\n\n/** Quote a frontmatter scalar so it round-trips cleanly through `parseFrontmatter`. */\nexport function formatFrontmatterValue(value: string | number | boolean): string {\n if (typeof value === 'boolean' || typeof value === 'number') return String(value);\n // Multi-line strings can't live on one line — emit a YAML literal block\n // scalar (`|-`) that `parseFrontmatter` reads back. This carries pretty-\n // printed JSON payloads (opt-in custom templates/themes). Single-line\n // values keep their exact historical output below.\n if (/[\\r\\n]/.test(value)) return formatBlockScalar(value.replace(/\\r\\n?/g, '\\n'));\n // A single-line JSON object/array literal round-trips verbatim through\n // the line-based `parseFrontmatter` (no leading quote to strip, no\n // newline to split on), so write it unquoted to keep it human-readable\n // — matching how `stringifyMarkdown` emits string values. Used by the\n // compact custom-layouts payload.\n if (/^[{[]/.test(value) && !/[\\r\\n]/.test(value)) return value;\n // Quote when needed: leading/trailing whitespace, leading punctuation that\n // could trigger YAML modes, or values that look like reserved literals.\n const needsQuote =\n /^\\s|\\s$/.test(value) ||\n /^[!&*?|>%@`]/.test(value) ||\n /[:#]/.test(value) ||\n value === '' ||\n value === 'true' ||\n value === 'false' ||\n (value !== '' && !Number.isNaN(Number(value))) ||\n (/^[\"']/.test(value) && /[\"']$/.test(value));\n if (!needsQuote) return value;\n // Prefer double quotes; escape any embedded double-quote / backslash.\n const escaped = value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n return `\"${escaped}\"`;\n}\n\n/**\n * Update a markdown source string's YAML frontmatter block, applying the\n * given key/value updates. A `null` or `undefined` value removes the key.\n * If no frontmatter block exists and any non-null update is supplied, a\n * new block is prepended; if every update is a removal and no block\n * exists, the source is returned unchanged.\n *\n * Existing key order is preserved; new keys are appended in the order\n * they appear in `updates`. The simple parser used here handles plain\n * `key: value` pairs only — comments, multi-line scalars, and nested\n * structures are left untouched (we only rewrite lines whose key matches\n * one in `updates`).\n *\n * @param source - The markdown source string.\n * @param updates - Map of key → new value (or null/undefined to remove).\n * @returns The updated markdown source string.\n */\nexport function setFrontmatterValues(\n source: string,\n updates: Record<string, string | number | boolean | null | undefined>,\n): string {\n const updateKeys = Object.keys(updates);\n if (updateKeys.length === 0) return source;\n\n const match = source.match(FRONTMATTER_BLOCK_RE);\n const handled = new Set<string>();\n\n // Existing frontmatter block — rewrite line-by-line, preserving order.\n if (match) {\n const inner = match[1];\n const lines = inner.split(/\\r?\\n/);\n const newLines: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) {\n newLines.push(line);\n continue;\n }\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx < 1) {\n newLines.push(line);\n continue;\n }\n const key = trimmed.slice(0, colonIdx).trim();\n if (!Object.prototype.hasOwnProperty.call(updates, key)) {\n newLines.push(line);\n continue;\n }\n handled.add(key);\n const next = updates[key];\n if (next === null || next === undefined) continue; // remove\n newLines.push(`${key}: ${formatFrontmatterValue(next)}`);\n }\n\n // Append any keys that weren't already present.\n for (const key of updateKeys) {\n if (handled.has(key)) continue;\n const next = updates[key];\n if (next === null || next === undefined) continue;\n newLines.push(`${key}: ${formatFrontmatterValue(next)}`);\n }\n\n // If every line got removed, drop the block entirely.\n const nonEmpty = newLines.filter((l) => l.trim().length > 0);\n const rest = source.slice(match[0].length);\n if (nonEmpty.length === 0) return rest;\n return `---\\n${newLines.join('\\n')}\\n---\\n${rest}`;\n }\n\n // No existing block — only create one if there's at least one non-null\n // value to write.\n const fresh: string[] = [];\n for (const key of updateKeys) {\n const next = updates[key];\n if (next === null || next === undefined) continue;\n fresh.push(`${key}: ${formatFrontmatterValue(next)}`);\n }\n if (fresh.length === 0) return source;\n return `---\\n${fresh.join('\\n')}\\n---\\n${source}`;\n}\n\n/**\n * Create a minimal MarkdownDocument from a list of block nodes.\n * Convenience function for programmatic document construction.\n */\nexport function createDocument(...children: MarkdownDocument['children']): MarkdownDocument {\n return {\n type: 'document',\n children,\n };\n}\n\n/**\n * Infer a human-readable title for a markdown document.\n *\n * Resolution order:\n * 1. `title:` value in YAML frontmatter (when present and non-empty)\n * 2. Text of the first heading found, walking top-level depth (H1, then\n * H2, etc.) — the shallowest heading wins regardless of source order\n *\n * Returns `undefined` when no usable title is found.\n */\nexport function inferDocumentTitle(doc: MarkdownDocument): string | undefined {\n const fmTitle = doc.frontmatter?.title;\n if (typeof fmTitle === 'string') {\n const trimmed = fmTitle.trim();\n if (trimmed) return trimmed;\n }\n\n let best: { depth: number; text: string } | null = null;\n for (const node of doc.children) {\n if (node.type !== 'heading') continue;\n const text = extractPlainText(node).trim();\n if (!text) continue;\n if (best === null || node.depth < best.depth) {\n best = { depth: node.depth, text };\n if (best.depth === 1) break;\n }\n }\n\n return best?.text;\n}\n\n/**\n * Read a theme id from a markdown document's frontmatter.\n *\n * Checks the editor's canonical `squisq-theme` key first, then the\n * shorter legacy aliases (`themeId`, `theme`). Returns `undefined`\n * when none of them carry a non-empty string. Centralizing the lookup\n * keeps the export pipelines (DOCX, PPTX, HTML, plain-HTML bundle) in\n * sync with whatever the editor writes — adding a new frontmatter\n * spelling later is a one-file change.\n */\nexport function readFrontmatterThemeId(\n frontmatter: Record<string, unknown> | undefined,\n): string | undefined {\n if (!frontmatter) return undefined;\n const keys = ['squisq-theme', 'themeId', 'theme'] as const;\n for (const key of keys) {\n const raw = frontmatter[key];\n if (typeof raw === 'string') {\n const trimmed = raw.trim();\n if (trimmed) return trimmed;\n }\n }\n return undefined;\n}\n\n/**\n * Project an inline-HTML string (as stored on `TextLayer.content.html`) down\n * to plain text — the value mirrored into `content.text` for plain consumers\n * (PDF/markdown export, search, accessibility) and the SVG `<text>` fallback.\n *\n * `<br>` and block-level tags (`p`, `div`, `h1`–`h6`, `li`, `blockquote`,\n * `pre`) become line breaks so multi-line rich content reads sensibly. Entity\n * decoding is handled by {@link parseHtmlToNodes} (parse5-backed), so this is\n * more correct than a regex tag-stripper.\n */\nexport function plainTextFromInlineHtml(html: string): string {\n const BLOCK = new Set([\n 'p',\n 'div',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'li',\n 'blockquote',\n 'pre',\n ]);\n const walk = (nodes: HtmlNode[]): string =>\n nodes\n .map((n) => {\n if (n.type === 'htmlText') return n.value;\n if (n.type !== 'htmlElement') return '';\n const tag = n.tagName.toLowerCase();\n if (tag === 'br') return '\\n';\n const inner = walk(n.children);\n return BLOCK.has(tag) ? `${inner}\\n` : inner;\n })\n .join('');\n return walk(parseHtmlToNodes(html))\n .replace(/\\u00A0/g, ' ')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,74 @@
1
+ // src/schemas/Media.ts
2
+ function flatten(blocks, out = []) {
3
+ for (const b of blocks) {
4
+ out.push(b);
5
+ if (b.children && b.children.length > 0) flatten(b.children, out);
6
+ }
7
+ return out;
8
+ }
9
+ function clipLength(clip) {
10
+ if (clip.clipEnd == null) return null;
11
+ return Math.max(0, clip.clipEnd - (clip.clipStart ?? 0));
12
+ }
13
+ function baseTimelineEnd(doc) {
14
+ const blockEnd = flatten(doc.blocks).reduce(
15
+ (max, b) => Math.max(max, b.startTime + b.duration),
16
+ 0
17
+ );
18
+ return Math.max(doc.duration ?? 0, blockEnd);
19
+ }
20
+ function resolveMediaSchedule(doc) {
21
+ const out = [];
22
+ const docEnd = baseTimelineEnd(doc);
23
+ for (const block of flatten(doc.blocks)) {
24
+ const blockEnd = block.startTime + block.duration;
25
+ for (const clip of block.media ?? []) {
26
+ const start = block.startTime + clip.startAt;
27
+ const len = clipLength(clip);
28
+ let end;
29
+ if (clip.spillover) {
30
+ end = len != null ? start + len : blockEnd;
31
+ } else {
32
+ end = len != null ? Math.min(start + len, blockEnd) : blockEnd;
33
+ }
34
+ out.push({
35
+ id: clip.id,
36
+ src: clip.src,
37
+ kind: clip.kind,
38
+ absoluteStart: start,
39
+ absoluteEnd: Math.max(start, end),
40
+ sourceIn: clip.clipStart ?? 0,
41
+ anchor: "block",
42
+ blockId: block.id,
43
+ ...clip.sourceLine != null ? { sourceLine: clip.sourceLine } : {}
44
+ });
45
+ }
46
+ }
47
+ for (const clip of doc.documentMedia ?? []) {
48
+ const start = clip.startAt;
49
+ const len = clipLength(clip);
50
+ const end = len != null ? start + len : docEnd;
51
+ out.push({
52
+ id: clip.id,
53
+ src: clip.src,
54
+ kind: clip.kind,
55
+ absoluteStart: start,
56
+ absoluteEnd: Math.max(start, end),
57
+ sourceIn: clip.clipStart ?? 0,
58
+ anchor: "document",
59
+ ...clip.sourceLine != null ? { sourceLine: clip.sourceLine } : {}
60
+ });
61
+ }
62
+ return out;
63
+ }
64
+ function getDocPlaybackDuration(doc) {
65
+ const base = baseTimelineEnd(doc);
66
+ const mediaEnd = resolveMediaSchedule(doc).reduce((max, c) => Math.max(max, c.absoluteEnd), 0);
67
+ return Math.max(base, mediaEnd);
68
+ }
69
+
70
+ export {
71
+ resolveMediaSchedule,
72
+ getDocPlaybackDuration
73
+ };
74
+ //# sourceMappingURL=chunk-FVJUUTEM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schemas/Media.ts"],"sourcesContent":["/**\n * Media-clip timing model.\n *\n * An additive layer on top of the simple sequential `Doc.audio.segments[]`\n * narration track, which remains first-class. A {@link MediaClip} is a piece\n * 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 * Round-trip home of the authoring annotation: the block whose contents\n * held the paragraph, its position in that block's ORIGINAL contents\n * array (before extraction), and the paragraph's exact source text.\n * `docToMarkdown` re-inserts the annotation from this — `raw` verbatim\n * when present, a re-serialized annotation otherwise. Absent for\n * programmatically built clips (those emit at the document top).\n */\n origin?: { blockId: string; index: number; raw?: string };\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"],"mappings":";AAwFA,SAAS,QAAQ,QAAiB,MAAe,CAAC,GAAY;AAC5D,aAAW,KAAK,QAAQ;AACtB,QAAI,KAAK,CAAC;AACV,QAAI,EAAE,YAAY,EAAE,SAAS,SAAS,EAAG,SAAQ,EAAE,UAAU,GAAG;AAAA,EAClE;AACA,SAAO;AACT;AAGA,SAAS,WAAW,MAAgC;AAClD,MAAI,KAAK,WAAW,KAAM,QAAO;AACjC,SAAO,KAAK,IAAI,GAAG,KAAK,WAAW,KAAK,aAAa,EAAE;AACzD;AAOA,SAAS,gBAAgB,KAAkB;AACzC,QAAM,WAAW,QAAQ,IAAI,MAAM,EAAE;AAAA,IACnC,CAAC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ;AAAA,IAClD;AAAA,EACF;AACA,SAAO,KAAK,IAAI,IAAI,YAAY,GAAG,QAAQ;AAC7C;AAMO,SAAS,qBAAqB,KAA2B;AAC9D,QAAM,MAAuB,CAAC;AAC9B,QAAM,SAAS,gBAAgB,GAAG;AAElC,aAAW,SAAS,QAAQ,IAAI,MAAM,GAAG;AACvC,UAAM,WAAW,MAAM,YAAY,MAAM;AACzC,eAAW,QAAQ,MAAM,SAAS,CAAC,GAAG;AACpC,YAAM,QAAQ,MAAM,YAAY,KAAK;AACrC,YAAM,MAAM,WAAW,IAAI;AAC3B,UAAI;AACJ,UAAI,KAAK,WAAW;AAGlB,cAAM,OAAO,OAAO,QAAQ,MAAM;AAAA,MACpC,OAAO;AACL,cAAM,OAAO,OAAO,KAAK,IAAI,QAAQ,KAAK,QAAQ,IAAI;AAAA,MACxD;AACA,UAAI,KAAK;AAAA,QACP,IAAI,KAAK;AAAA,QACT,KAAK,KAAK;AAAA,QACV,MAAM,KAAK;AAAA,QACX,eAAe;AAAA,QACf,aAAa,KAAK,IAAI,OAAO,GAAG;AAAA,QAChC,UAAU,KAAK,aAAa;AAAA,QAC5B,QAAQ;AAAA,QACR,SAAS,MAAM;AAAA,QACf,GAAI,KAAK,cAAc,OAAO,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,MACnE,CAAC;AAAA,IACH;AAAA,EACF;AAEA,aAAW,QAAQ,IAAI,iBAAiB,CAAC,GAAG;AAC1C,UAAM,QAAQ,KAAK;AACnB,UAAM,MAAM,WAAW,IAAI;AAC3B,UAAM,MAAM,OAAO,OAAO,QAAQ,MAAM;AACxC,QAAI,KAAK;AAAA,MACP,IAAI,KAAK;AAAA,MACT,KAAK,KAAK;AAAA,MACV,MAAM,KAAK;AAAA,MACX,eAAe;AAAA,MACf,aAAa,KAAK,IAAI,OAAO,GAAG;AAAA,MAChC,UAAU,KAAK,aAAa;AAAA,MAC5B,QAAQ;AAAA,MACR,GAAI,KAAK,cAAc,OAAO,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,IACnE,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAOO,SAAS,uBAAuB,KAAkB;AACvD,QAAM,OAAO,gBAAgB,GAAG;AAChC,QAAM,WAAW,qBAAqB,GAAG,EAAE,OAAO,CAAC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,WAAW,GAAG,CAAC;AAC7F,SAAO,KAAK,IAAI,MAAM,QAAQ;AAChC;","names":[]}