@bendyline/squisq 2.1.0 → 2.2.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 (446) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +24 -0
  3. package/dist/{ContentContainer-BXUlIf18.d.ts → ContentContainer-B2w9sUoL.d.ts} +15 -0
  4. package/dist/{Doc-DynJleo-.d.ts → Doc-BubIpWsp.d.ts} +1 -1
  5. package/dist/{ImageEditDoc-Ca4Tr9uN.d.ts → ImageEditDoc-CGxk5CHc.d.ts} +1 -1
  6. package/dist/{annotationCoercion-Dr_DY9Hx.d.ts → annotationCoercion-PtRQhk5V.d.ts} +1 -1
  7. package/dist/chunk-2L55443L.js +0 -0
  8. package/dist/{chunk-VWCJ2WXC.js → chunk-2ZWIXGAC.js} +0 -1
  9. package/dist/{chunk-VYFRI7R6.js → chunk-32R2RZAO.js} +72 -3
  10. package/dist/chunk-3IGPAPQ7.js +645 -0
  11. package/dist/{chunk-QE7TWGVT.js → chunk-4VOD55SX.js} +0 -1
  12. package/dist/{chunk-3E5F2XMR.js → chunk-67L6WRJN.js} +0 -1
  13. package/dist/{chunk-5DKBFXPY.js → chunk-6ECMNUIP.js} +26 -18
  14. package/dist/{chunk-JI77MGAK.js → chunk-7ARJZYQY.js} +159 -120
  15. package/dist/{chunk-DBS6RIFL.js → chunk-7N4G32LG.js} +0 -1
  16. package/dist/{chunk-JOQYPRDZ.js → chunk-7YEZFHLS.js} +10 -8
  17. package/dist/{chunk-2UQOHYE2.js → chunk-7ZAAICW4.js} +0 -1
  18. package/dist/{chunk-66QO7XFB.js → chunk-ACZOVWX3.js} +117 -7
  19. package/dist/{chunk-5J4HKRJF.js → chunk-BAOV476U.js} +0 -1
  20. package/dist/chunk-BCCXTMN5.js +699 -0
  21. package/dist/{chunk-JHZWYRSW.js → chunk-C33GTPUZ.js} +11 -4
  22. package/dist/{chunk-QUUPYOSP.js → chunk-CB45YGAM.js} +2 -3
  23. package/dist/{chunk-XNLB57UD.js → chunk-DYPMOPIW.js} +18 -2
  24. package/dist/{chunk-VR7UPJ7C.js → chunk-FCG3BIZN.js} +12 -1
  25. package/dist/{chunk-5UYI3RJB.js → chunk-FDYDYL77.js} +54 -40
  26. package/dist/{chunk-OTKVFVL6.js → chunk-I2O6W7DX.js} +116 -44
  27. package/dist/{chunk-Q7LROV2H.js → chunk-NKNMGIIN.js} +0 -1
  28. package/dist/chunk-OPO5K4DZ.js +241 -0
  29. package/dist/{chunk-TUWMK62B.js → chunk-PUS54YU6.js} +45 -2
  30. package/dist/chunk-QKPXHDLR.js +0 -0
  31. package/dist/{chunk-GZY66SDN.js → chunk-R3KTTOMS.js} +0 -1
  32. package/dist/{chunk-ILWDXLUK.js → chunk-RS5AP3J4.js} +49 -10
  33. package/dist/chunk-WV5ARNDA.js +0 -0
  34. package/dist/{chunk-4X3JQXNM.js → chunk-XOFT65EX.js} +1 -2
  35. package/dist/{chunk-FVTQSNR7.js → chunk-XRW6P7IF.js} +13 -5
  36. package/dist/chunk-XWNNA3KE.js +0 -0
  37. package/dist/chunk-YZNGMV3G.js +0 -0
  38. package/dist/{chunk-R2YZTCLO.js → chunk-ZQKZSJAX.js} +1 -2
  39. package/dist/doc/index.d.ts +27 -12
  40. package/dist/doc/index.js +16 -17
  41. package/dist/generate/index.d.ts +2 -2
  42. package/dist/generate/index.js +2 -3
  43. package/dist/icons/index.js +3 -4
  44. package/dist/icons/inlineIconMarker.js +1 -2
  45. package/dist/imageEdit/index.d.ts +48 -17
  46. package/dist/imageEdit/index.js +3 -4
  47. package/dist/index.d.ts +11 -11
  48. package/dist/index.js +60 -33
  49. package/dist/jsonForm/index.d.ts +10 -3
  50. package/dist/jsonForm/index.js +8 -7
  51. package/dist/markdown/index.d.ts +75 -11
  52. package/dist/markdown/index.js +32 -7
  53. package/dist/{materializePageSection-DDzubQXo.d.ts → materializePageSection-wSLA2A1g.d.ts} +2 -2
  54. package/dist/narration/index.d.ts +16 -3
  55. package/dist/narration/index.js +13 -14
  56. package/dist/random/index.d.ts +11 -2
  57. package/dist/random/index.js +2 -3
  58. package/dist/recommend/index.d.ts +1 -1
  59. package/dist/recommend/index.js +3 -4
  60. package/dist/schemas/index.d.ts +5 -5
  61. package/dist/schemas/index.js +5 -6
  62. package/dist/spatial/index.d.ts +2 -1
  63. package/dist/spatial/index.js +1 -2
  64. package/dist/storage/index.d.ts +4 -2
  65. package/dist/storage/index.js +1 -2
  66. package/dist/{themeLibrary-B72m0W1y.d.ts → themeLibrary-DU9mUmVl.d.ts} +1 -1
  67. package/dist/timing/index.js +3 -4
  68. package/dist/transform/index.d.ts +3 -3
  69. package/dist/transform/index.js +6 -7
  70. package/dist/{types-C4kiRn_k.d.ts → types-8QjefM9J.d.ts} +38 -2
  71. package/dist/{types-BwMPl2CB.d.ts → types-CUNc9biN.d.ts} +24 -1
  72. package/dist/versions/index.d.ts +14 -11
  73. package/dist/versions/index.js +2 -3
  74. package/package.json +4 -2
  75. package/dist/chunk-2UQOHYE2.js.map +0 -1
  76. package/dist/chunk-3E5F2XMR.js.map +0 -1
  77. package/dist/chunk-4X3JQXNM.js.map +0 -1
  78. package/dist/chunk-5DKBFXPY.js.map +0 -1
  79. package/dist/chunk-5J4HKRJF.js.map +0 -1
  80. package/dist/chunk-5UYI3RJB.js.map +0 -1
  81. package/dist/chunk-66QO7XFB.js.map +0 -1
  82. package/dist/chunk-AMIR72JP.js +0 -380
  83. package/dist/chunk-AMIR72JP.js.map +0 -1
  84. package/dist/chunk-DBS6RIFL.js.map +0 -1
  85. package/dist/chunk-DNJKBAI7.js +0 -1
  86. package/dist/chunk-DNJKBAI7.js.map +0 -1
  87. package/dist/chunk-ET53IIEP.js +0 -1
  88. package/dist/chunk-ET53IIEP.js.map +0 -1
  89. package/dist/chunk-FVTQSNR7.js.map +0 -1
  90. package/dist/chunk-GZY66SDN.js.map +0 -1
  91. package/dist/chunk-ILWDXLUK.js.map +0 -1
  92. package/dist/chunk-JHZWYRSW.js.map +0 -1
  93. package/dist/chunk-JI77MGAK.js.map +0 -1
  94. package/dist/chunk-JOQYPRDZ.js.map +0 -1
  95. package/dist/chunk-OTKVFVL6.js.map +0 -1
  96. package/dist/chunk-PGGGKYWG.js +0 -1
  97. package/dist/chunk-PGGGKYWG.js.map +0 -1
  98. package/dist/chunk-Q7LROV2H.js.map +0 -1
  99. package/dist/chunk-QE7TWGVT.js.map +0 -1
  100. package/dist/chunk-QUUPYOSP.js.map +0 -1
  101. package/dist/chunk-R2GC6RLD.js +0 -344
  102. package/dist/chunk-R2GC6RLD.js.map +0 -1
  103. package/dist/chunk-R2YZTCLO.js.map +0 -1
  104. package/dist/chunk-S7A5D7ZJ.js +0 -174
  105. package/dist/chunk-S7A5D7ZJ.js.map +0 -1
  106. package/dist/chunk-TUWMK62B.js.map +0 -1
  107. package/dist/chunk-V543AI2F.js +0 -1
  108. package/dist/chunk-V543AI2F.js.map +0 -1
  109. package/dist/chunk-VR7UPJ7C.js.map +0 -1
  110. package/dist/chunk-VWCJ2WXC.js.map +0 -1
  111. package/dist/chunk-VYFRI7R6.js.map +0 -1
  112. package/dist/chunk-XNLB57UD.js.map +0 -1
  113. package/dist/chunk-ZLQKNOZQ.js +0 -1
  114. package/dist/chunk-ZLQKNOZQ.js.map +0 -1
  115. package/dist/doc/index.js.map +0 -1
  116. package/dist/generate/index.js.map +0 -1
  117. package/dist/icons/index.js.map +0 -1
  118. package/dist/icons/inlineIconMarker.js.map +0 -1
  119. package/dist/imageEdit/index.js.map +0 -1
  120. package/dist/index.js.map +0 -1
  121. package/dist/jsonForm/index.js.map +0 -1
  122. package/dist/markdown/index.js.map +0 -1
  123. package/dist/narration/index.js.map +0 -1
  124. package/dist/random/index.js.map +0 -1
  125. package/dist/recommend/index.js.map +0 -1
  126. package/dist/schemas/index.js.map +0 -1
  127. package/dist/spatial/index.js.map +0 -1
  128. package/dist/storage/index.js.map +0 -1
  129. package/dist/timing/index.js.map +0 -1
  130. package/dist/transform/index.js.map +0 -1
  131. package/dist/versions/index.js.map +0 -1
  132. package/src/__tests__/animationUtils.test.ts +0 -117
  133. package/src/__tests__/annotationCoercion.test.ts +0 -243
  134. package/src/__tests__/applyNarrationTiming.test.ts +0 -190
  135. package/src/__tests__/applyRenderStyle.test.ts +0 -146
  136. package/src/__tests__/asciiDiagramBattle.test.ts +0 -246
  137. package/src/__tests__/asciiDiagramDetect.test.ts +0 -162
  138. package/src/__tests__/asciiDiagramMapping.test.ts +0 -38
  139. package/src/__tests__/asciiDiagramParse.test.ts +0 -233
  140. package/src/__tests__/asciiDiagramPipeline.test.ts +0 -273
  141. package/src/__tests__/asciiDiagramRender.test.ts +0 -162
  142. package/src/__tests__/asciiDiagramRepair.test.ts +0 -84
  143. package/src/__tests__/asciiTimelineDetect.test.ts +0 -157
  144. package/src/__tests__/asciiTimelineParse.test.ts +0 -205
  145. package/src/__tests__/asciiTimelinePipeline.test.ts +0 -349
  146. package/src/__tests__/asciiTimelineRender.test.ts +0 -376
  147. package/src/__tests__/atmosphereLayers.test.ts +0 -108
  148. package/src/__tests__/attrTokens.test.ts +0 -167
  149. package/src/__tests__/colorUtils.test.ts +0 -112
  150. package/src/__tests__/contentContainer.test.ts +0 -325
  151. package/src/__tests__/contentExtractor.test.ts +0 -243
  152. package/src/__tests__/coverBlock.test.ts +0 -310
  153. package/src/__tests__/customTemplateSample.test.ts +0 -144
  154. package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -221
  155. package/src/__tests__/customThemesFrontmatter.test.ts +0 -146
  156. package/src/__tests__/dataTable.test.ts +0 -145
  157. package/src/__tests__/diagramBlock.test.ts +0 -122
  158. package/src/__tests__/diagramLayout.test.ts +0 -134
  159. package/src/__tests__/diagramText.test.ts +0 -26
  160. package/src/__tests__/docToMarkdownMedia.test.ts +0 -83
  161. package/src/__tests__/drawing.test.ts +0 -109
  162. package/src/__tests__/drawingBlock.test.ts +0 -124
  163. package/src/__tests__/drawingLayout.test.ts +0 -171
  164. package/src/__tests__/fixtures/asciiDiagramBattle.ts +0 -512
  165. package/src/__tests__/fixtures/asciiDiagrams.ts +0 -287
  166. package/src/__tests__/fixtures/asciiTimelines.ts +0 -35
  167. package/src/__tests__/fixtures/treeviewFixtures.ts +0 -243
  168. package/src/__tests__/geohash.test.ts +0 -110
  169. package/src/__tests__/getLayers.test.ts +0 -243
  170. package/src/__tests__/getLayersFallback.test.ts +0 -103
  171. package/src/__tests__/haversine.test.ts +0 -63
  172. package/src/__tests__/iconResolve.test.ts +0 -122
  173. package/src/__tests__/imageEditExportSvg.test.ts +0 -70
  174. package/src/__tests__/imageEditState.test.ts +0 -74
  175. package/src/__tests__/imageEditVersions.test.ts +0 -243
  176. package/src/__tests__/inferDocumentTitle.test.ts +0 -50
  177. package/src/__tests__/inlineIconMarker.test.ts +0 -36
  178. package/src/__tests__/inputDescriptors.test.ts +0 -162
  179. package/src/__tests__/jsonForm.chooseControl.test.ts +0 -86
  180. package/src/__tests__/jsonForm.evaluateWhen.test.ts +0 -59
  181. package/src/__tests__/jsonForm.inferSchema.test.ts +0 -25
  182. package/src/__tests__/jsonForm.pointer.test.ts +0 -149
  183. package/src/__tests__/jsonForm.tokens.test.ts +0 -84
  184. package/src/__tests__/layoutBlock.test.ts +0 -83
  185. package/src/__tests__/layoutLayout.test.ts +0 -169
  186. package/src/__tests__/localForageAdapter.test.ts +0 -155
  187. package/src/__tests__/majorApiTypes.test.ts +0 -18
  188. package/src/__tests__/markdown.test.ts +0 -1009
  189. package/src/__tests__/markdownIcons.test.ts +0 -104
  190. package/src/__tests__/markdownSanitize.test.ts +0 -81
  191. package/src/__tests__/markdownToDoc.test.ts +0 -957
  192. package/src/__tests__/matchFontFamily.test.ts +0 -114
  193. package/src/__tests__/materializeBlockLayers.test.ts +0 -167
  194. package/src/__tests__/materializePageSections.test.ts +0 -404
  195. package/src/__tests__/materializeTestUtils.ts +0 -17
  196. package/src/__tests__/mediaAnnotations.test.ts +0 -91
  197. package/src/__tests__/mediaSchedule.test.ts +0 -118
  198. package/src/__tests__/narrationAlign.test.ts +0 -150
  199. package/src/__tests__/narrationFeatures.test.ts +0 -71
  200. package/src/__tests__/narrationNuclei.test.ts +0 -45
  201. package/src/__tests__/narrationPacing.test.ts +0 -189
  202. package/src/__tests__/narrationScript.test.ts +0 -149
  203. package/src/__tests__/narrationSessionRepro.test.ts +0 -62
  204. package/src/__tests__/narrationSidecar.test.ts +0 -88
  205. package/src/__tests__/narrationSyllables.test.ts +0 -65
  206. package/src/__tests__/narrationTestSignals.ts +0 -167
  207. package/src/__tests__/narrationVad.test.ts +0 -66
  208. package/src/__tests__/pageStyle.test.ts +0 -258
  209. package/src/__tests__/pandocAttrParse.test.ts +0 -187
  210. package/src/__tests__/recommendTemplates.test.ts +0 -183
  211. package/src/__tests__/resolveDocTheme.test.ts +0 -69
  212. package/src/__tests__/rootExports.test.ts +0 -61
  213. package/src/__tests__/scopedContentContainer.test.ts +0 -109
  214. package/src/__tests__/seededRandom.test.ts +0 -142
  215. package/src/__tests__/shapeGeometry.test.ts +0 -138
  216. package/src/__tests__/shapeVocabulary.test.ts +0 -133
  217. package/src/__tests__/storage.test.ts +0 -60
  218. package/src/__tests__/structuredData.test.ts +0 -249
  219. package/src/__tests__/surface.test.ts +0 -68
  220. package/src/__tests__/templateAnnotationParse.test.ts +0 -173
  221. package/src/__tests__/templateInputs.test.ts +0 -69
  222. package/src/__tests__/templateMetadata.test.ts +0 -32
  223. package/src/__tests__/templates.test.ts +0 -710
  224. package/src/__tests__/themeCompile.test.ts +0 -225
  225. package/src/__tests__/themeValidator.test.ts +0 -131
  226. package/src/__tests__/timing.test.ts +0 -167
  227. package/src/__tests__/transformProvenance.test.ts +0 -160
  228. package/src/__tests__/transformV2.test.ts +0 -163
  229. package/src/__tests__/transitionNormalize.test.ts +0 -61
  230. package/src/__tests__/treeviewBattle.test.ts +0 -76
  231. package/src/__tests__/treeviewDetect.test.ts +0 -168
  232. package/src/__tests__/treeviewParse.test.ts +0 -118
  233. package/src/__tests__/treeviewPipeline.test.ts +0 -183
  234. package/src/__tests__/treeviewRender.test.ts +0 -91
  235. package/src/__tests__/validateDoc.test.ts +0 -260
  236. package/src/__tests__/versions.test.ts +0 -459
  237. package/src/doc/annotationBlocks.ts +0 -117
  238. package/src/doc/applyNarrationTiming.ts +0 -244
  239. package/src/doc/asciiDiagram/chars.ts +0 -210
  240. package/src/doc/asciiDiagram/detect.ts +0 -175
  241. package/src/doc/asciiDiagram/index.ts +0 -38
  242. package/src/doc/asciiDiagram/mapping.ts +0 -225
  243. package/src/doc/asciiDiagram/parse.ts +0 -1095
  244. package/src/doc/asciiDiagram/render.ts +0 -963
  245. package/src/doc/asciiDiagram/repair.ts +0 -400
  246. package/src/doc/asciiDiagram/types.ts +0 -65
  247. package/src/doc/asciiTimeline/detect.ts +0 -138
  248. package/src/doc/asciiTimeline/index.ts +0 -26
  249. package/src/doc/asciiTimeline/mapping.ts +0 -43
  250. package/src/doc/asciiTimeline/parse.ts +0 -870
  251. package/src/doc/asciiTimeline/render.ts +0 -427
  252. package/src/doc/asciiTimeline/types.ts +0 -73
  253. package/src/doc/asciiTimeline/wrappedFlow.ts +0 -427
  254. package/src/doc/audioMapping.ts +0 -442
  255. package/src/doc/customTemplatesFrontmatter.ts +0 -273
  256. package/src/doc/customThemesFrontmatter.ts +0 -95
  257. package/src/doc/docToMarkdown.ts +0 -284
  258. package/src/doc/index.ts +0 -161
  259. package/src/doc/markdownToDoc.ts +0 -1054
  260. package/src/doc/materializeBlockLayers.ts +0 -465
  261. package/src/doc/mediaAnnotations.ts +0 -120
  262. package/src/doc/page/PageSection.ts +0 -174
  263. package/src/doc/page/materializePageSection.ts +0 -616
  264. package/src/doc/page/resolvePageBlock.ts +0 -108
  265. package/src/doc/page/sectionExtractors.ts +0 -466
  266. package/src/doc/pageCss.ts +0 -628
  267. package/src/doc/resolveDocTheme.ts +0 -56
  268. package/src/doc/standaloneAnnotation.ts +0 -58
  269. package/src/doc/structuredData.ts +0 -301
  270. package/src/doc/templateInputs.ts +0 -424
  271. package/src/doc/templates/__tests__/customTemplate.test.ts +0 -170
  272. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +0 -124
  273. package/src/doc/templates/__tests__/listBlock.test.ts +0 -64
  274. package/src/doc/templates/accentImage.ts +0 -362
  275. package/src/doc/templates/captionUtils.ts +0 -56
  276. package/src/doc/templates/comparisonBar.ts +0 -173
  277. package/src/doc/templates/coverBlock.ts +0 -255
  278. package/src/doc/templates/customTemplate.ts +0 -51
  279. package/src/doc/templates/dataTable.ts +0 -102
  280. package/src/doc/templates/dateEvent.ts +0 -147
  281. package/src/doc/templates/definitionCard.ts +0 -140
  282. package/src/doc/templates/diagramBlock.ts +0 -403
  283. package/src/doc/templates/diagramLayout.ts +0 -159
  284. package/src/doc/templates/drawingBlock.ts +0 -325
  285. package/src/doc/templates/drawingLayout.ts +0 -539
  286. package/src/doc/templates/factCard.ts +0 -146
  287. package/src/doc/templates/fallbackBlock.ts +0 -123
  288. package/src/doc/templates/featureBlock.ts +0 -209
  289. package/src/doc/templates/fullBleedQuote.ts +0 -83
  290. package/src/doc/templates/imageWithCaption.ts +0 -186
  291. package/src/doc/templates/index.ts +0 -642
  292. package/src/doc/templates/inputDescriptors.ts +0 -451
  293. package/src/doc/templates/layoutBlock.ts +0 -43
  294. package/src/doc/templates/layoutLayout.ts +0 -243
  295. package/src/doc/templates/listBlock.ts +0 -207
  296. package/src/doc/templates/mapBlock.ts +0 -138
  297. package/src/doc/templates/metadata.ts +0 -130
  298. package/src/doc/templates/persistentLayers.ts +0 -526
  299. package/src/doc/templates/photoGrid.ts +0 -190
  300. package/src/doc/templates/pullQuote.ts +0 -141
  301. package/src/doc/templates/quoteBlock.ts +0 -141
  302. package/src/doc/templates/registry.ts +0 -88
  303. package/src/doc/templates/sectionHeader.ts +0 -120
  304. package/src/doc/templates/statHighlight.ts +0 -146
  305. package/src/doc/templates/templateNames.ts +0 -22
  306. package/src/doc/templates/timelineBlock.ts +0 -613
  307. package/src/doc/templates/titleBlock.ts +0 -150
  308. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +0 -487
  309. package/src/doc/templates/tokens/resolveTokens.ts +0 -453
  310. package/src/doc/templates/treeBlock.ts +0 -131
  311. package/src/doc/templates/twoColumn.ts +0 -263
  312. package/src/doc/templates/videoPullQuote.ts +0 -142
  313. package/src/doc/templates/videoWithCaption.ts +0 -135
  314. package/src/doc/treeview/chars.ts +0 -72
  315. package/src/doc/treeview/detect.ts +0 -145
  316. package/src/doc/treeview/index.ts +0 -30
  317. package/src/doc/treeview/mapping.ts +0 -95
  318. package/src/doc/treeview/parse.ts +0 -201
  319. package/src/doc/treeview/render.ts +0 -41
  320. package/src/doc/treeview/types.ts +0 -43
  321. package/src/doc/utils/animationUtils.ts +0 -399
  322. package/src/doc/utils/applyRenderStyle.ts +0 -106
  323. package/src/doc/utils/diagramText.ts +0 -78
  324. package/src/doc/utils/imageTreatment.ts +0 -70
  325. package/src/doc/utils/nearest.ts +0 -59
  326. package/src/doc/utils/shapeGeometry.ts +0 -614
  327. package/src/doc/utils/themeUtils.ts +0 -243
  328. package/src/doc/validate.ts +0 -575
  329. package/src/generate/contentExtractor.ts +0 -921
  330. package/src/generate/index.ts +0 -18
  331. package/src/generate/templateMapper.ts +0 -177
  332. package/src/icons/iconData.ts +0 -31
  333. package/src/icons/index.ts +0 -26
  334. package/src/icons/inlineIconMarker.ts +0 -73
  335. package/src/icons/resolve.ts +0 -139
  336. package/src/imageEdit/ImageEditVersionManager.ts +0 -73
  337. package/src/imageEdit/export.ts +0 -410
  338. package/src/imageEdit/index.ts +0 -72
  339. package/src/imageEdit/persistence.ts +0 -113
  340. package/src/imageEdit/state.ts +0 -112
  341. package/src/imageEdit/versionPaths.ts +0 -44
  342. package/src/imageEdit/versions.ts +0 -152
  343. package/src/index.ts +0 -15
  344. package/src/internal/immutable.ts +0 -58
  345. package/src/jsonForm/chooseControl.ts +0 -88
  346. package/src/jsonForm/evaluateWhen.ts +0 -59
  347. package/src/jsonForm/index.ts +0 -32
  348. package/src/jsonForm/inferSchema.ts +0 -25
  349. package/src/jsonForm/pointer.ts +0 -154
  350. package/src/jsonForm/tokens.ts +0 -78
  351. package/src/jsonForm/types.ts +0 -167
  352. package/src/markdown/annotationCoercion.ts +0 -295
  353. package/src/markdown/attrTokens.ts +0 -243
  354. package/src/markdown/convert.ts +0 -1353
  355. package/src/markdown/htmlParse.ts +0 -212
  356. package/src/markdown/index.ts +0 -152
  357. package/src/markdown/parse.ts +0 -103
  358. package/src/markdown/sanitize.ts +0 -318
  359. package/src/markdown/stringify.ts +0 -207
  360. package/src/markdown/types.ts +0 -710
  361. package/src/markdown/utils.ts +0 -459
  362. package/src/narration/align.ts +0 -514
  363. package/src/narration/features.ts +0 -231
  364. package/src/narration/index.ts +0 -86
  365. package/src/narration/nuclei.ts +0 -118
  366. package/src/narration/pacing.ts +0 -211
  367. package/src/narration/script.ts +0 -239
  368. package/src/narration/session.ts +0 -100
  369. package/src/narration/sidecar.ts +0 -187
  370. package/src/narration/syllables.ts +0 -85
  371. package/src/narration/trace.ts +0 -50
  372. package/src/narration/types.ts +0 -299
  373. package/src/narration/vad.ts +0 -131
  374. package/src/ngeohash.d.ts +0 -11
  375. package/src/random/SeededRandom.ts +0 -127
  376. package/src/random/index.ts +0 -1
  377. package/src/recommend/index.ts +0 -16
  378. package/src/recommend/numberHighlight.ts +0 -25
  379. package/src/recommend/templates.ts +0 -393
  380. package/src/schemas/BlockTemplates.ts +0 -1046
  381. package/src/schemas/CustomTemplates.ts +0 -126
  382. package/src/schemas/Doc.ts +0 -1073
  383. package/src/schemas/ImageEditDoc.ts +0 -102
  384. package/src/schemas/LayoutStrategy.ts +0 -178
  385. package/src/schemas/Media.ts +0 -179
  386. package/src/schemas/MediaProvider.ts +0 -70
  387. package/src/schemas/PageStyle.ts +0 -213
  388. package/src/schemas/Theme.ts +0 -413
  389. package/src/schemas/Transitions.ts +0 -224
  390. package/src/schemas/Types.ts +0 -15
  391. package/src/schemas/Viewport.ts +0 -91
  392. package/src/schemas/colorUtils.ts +0 -219
  393. package/src/schemas/fontStacks.ts +0 -339
  394. package/src/schemas/index.ts +0 -31
  395. package/src/schemas/pageStyleDefaults.ts +0 -204
  396. package/src/schemas/themeCompile.ts +0 -244
  397. package/src/schemas/themeConstants.ts +0 -2
  398. package/src/schemas/themeLibrary.ts +0 -60
  399. package/src/schemas/themeValidator.ts +0 -609
  400. package/src/schemas/themes/bold.json +0 -77
  401. package/src/schemas/themes/cinematic.json +0 -143
  402. package/src/schemas/themes/documentary.json +0 -125
  403. package/src/schemas/themes/gezellig.json +0 -123
  404. package/src/schemas/themes/index.ts +0 -53
  405. package/src/schemas/themes/magazine.json +0 -126
  406. package/src/schemas/themes/minimalist.json +0 -74
  407. package/src/schemas/themes/morning-light.json +0 -117
  408. package/src/schemas/themes/standard-dark.json +0 -109
  409. package/src/schemas/themes/standard.json +0 -98
  410. package/src/schemas/themes/tech-dark.json +0 -143
  411. package/src/schemas/themes/warm-earth.json +0 -74
  412. package/src/spatial/Geohash.ts +0 -196
  413. package/src/spatial/Haversine.ts +0 -59
  414. package/src/spatial/index.ts +0 -2
  415. package/src/storage/ContentContainer.ts +0 -220
  416. package/src/storage/LocalForageAdapter.ts +0 -103
  417. package/src/storage/LocalStorageAdapter.ts +0 -57
  418. package/src/storage/MediaProviderFromContainer.ts +0 -92
  419. package/src/storage/MemoryStorageAdapter.ts +0 -37
  420. package/src/storage/ScopedContentContainer.ts +0 -143
  421. package/src/storage/Storage.ts +0 -28
  422. package/src/storage/index.ts +0 -9
  423. package/src/timing/index.ts +0 -17
  424. package/src/timing/narrationTiming.ts +0 -220
  425. package/src/timing/readingTime.ts +0 -94
  426. package/src/transform/applyTransform.ts +0 -122
  427. package/src/transform/blockAnalyzer.ts +0 -181
  428. package/src/transform/index.ts +0 -23
  429. package/src/transform/registry.ts +0 -217
  430. package/src/transform/styles/dataDriven.ts +0 -18
  431. package/src/transform/styles/documentary.ts +0 -18
  432. package/src/transform/styles/index.ts +0 -5
  433. package/src/transform/styles/magazine.ts +0 -19
  434. package/src/transform/styles/minimal.ts +0 -18
  435. package/src/transform/styles/narrative.ts +0 -19
  436. package/src/transform/templateSelector.ts +0 -389
  437. package/src/transform/timingAllocator.ts +0 -257
  438. package/src/transform/types.ts +0 -158
  439. package/src/versions/DocumentVersionManager.ts +0 -70
  440. package/src/versions/gitignore.ts +0 -34
  441. package/src/versions/index.ts +0 -56
  442. package/src/versions/operations.ts +0 -144
  443. package/src/versions/paths.ts +0 -51
  444. package/src/versions/snapshotStore.ts +0 -202
  445. package/src/versions/timestamp.ts +0 -48
  446. package/src/versions/types.ts +0 -93
@@ -1,1353 +0,0 @@
1
- /**
2
- * mdast ↔ Squisq Markdown Node Conversion
3
- *
4
- * Converts between the remark/mdast AST format and the squisq
5
- * MarkdownDocument JSON format. This is the bridge layer that allows
6
- * us to use the battle-tested unified/remark ecosystem for parsing
7
- * and serialization while exposing our own clean, well-typed interfaces.
8
- *
9
- * The conversion is designed for perfect round-tripping:
10
- * toMdast(fromMdast(tree)) should produce an equivalent mdast tree.
11
- */
12
-
13
- import type {
14
- MarkdownDocument,
15
- MarkdownBlockNode,
16
- MarkdownInlineNode,
17
- MarkdownListItem,
18
- MarkdownTableRow,
19
- MarkdownTableCell,
20
- MarkdownSourcePosition,
21
- HeadingTemplateAnnotation,
22
- HeadingAttributes,
23
- MarkdownHeading,
24
- MarkdownInlineIcon,
25
- } from './types.js';
26
- import { parseHtmlToNodes } from './htmlParse.js';
27
- import { resolveIcon } from '../icons/resolve.js';
28
- import { coerceAnnotationValues } from './annotationCoercion.js';
29
- import {
30
- matchTrailingTemplateAnnotation,
31
- matchTrailingPandocAttr,
32
- tokenizeAttrTokens,
33
- splitKeyValueToken,
34
- quoteAttrValue,
35
- } from './attrTokens.js';
36
-
37
- // ============================================
38
- // Generic mdast node shape
39
- // ============================================
40
-
41
- /**
42
- * Minimal interface for mdast/unist nodes.
43
- * Using a generic shape avoids hard dependency on @types/mdast
44
- * and handles extension nodes (GFM, math, directive) uniformly.
45
- */
46
- interface MdastPosition {
47
- start: { line: number; column: number; offset?: number };
48
- end: { line: number; column: number; offset?: number };
49
- }
50
-
51
- interface MdastNode {
52
- type: string;
53
- position?: MdastPosition;
54
- children?: MdastNode[];
55
- value?: string;
56
- depth?: number;
57
- ordered?: boolean | null;
58
- start?: number | null;
59
- spread?: boolean | null;
60
- checked?: boolean | null;
61
- lang?: string | null;
62
- meta?: string | null;
63
- url?: string;
64
- title?: string | null;
65
- alt?: string;
66
- identifier?: string;
67
- label?: string;
68
- referenceType?: string;
69
- align?: (string | null)[];
70
- name?: string;
71
- attributes?: Record<string, string>;
72
- data?: Record<string, unknown>;
73
- }
74
-
75
- // ============================================
76
- // Position conversion
77
- // ============================================
78
-
79
- function convertPosition(pos?: MdastPosition): MarkdownSourcePosition | undefined {
80
- if (!pos) return undefined;
81
- return {
82
- start: {
83
- line: pos.start.line,
84
- column: pos.start.column,
85
- ...(pos.start.offset != null ? { offset: pos.start.offset } : {}),
86
- },
87
- end: {
88
- line: pos.end.line,
89
- column: pos.end.column,
90
- ...(pos.end.offset != null ? { offset: pos.end.offset } : {}),
91
- },
92
- };
93
- }
94
-
95
- function toMdastPosition(pos: MarkdownSourcePosition): MdastPosition {
96
- return {
97
- start: {
98
- line: pos.start.line,
99
- column: pos.start.column,
100
- ...(pos.start.offset != null ? { offset: pos.start.offset } : {}),
101
- },
102
- end: {
103
- line: pos.end.line,
104
- column: pos.end.column,
105
- ...(pos.end.offset != null ? { offset: pos.end.offset } : {}),
106
- },
107
- };
108
- }
109
-
110
- /** Conditionally include position in node. */
111
- function posField(
112
- pos?: MarkdownSourcePosition,
113
- ): { position: MarkdownSourcePosition } | Record<string, never> {
114
- return pos ? { position: pos } : {};
115
- }
116
-
117
- function mdastPosField(
118
- pos?: MarkdownSourcePosition,
119
- ): { position: MdastPosition } | Record<string, never> {
120
- return pos ? { position: toMdastPosition(pos) } : {};
121
- }
122
-
123
- // ============================================
124
- // mdast → Squisq (fromMdast)
125
- // ============================================
126
-
127
- /**
128
- * Extract plain text content from an mdast node tree.
129
- * Used for directive labels.
130
- */
131
- function extractText(node: MdastNode): string {
132
- if (node.value != null) return node.value;
133
- if (node.children) {
134
- return node.children.map(extractText).join('');
135
- }
136
- return '';
137
- }
138
-
139
- // ============================================
140
- // Template annotation helpers
141
- // ============================================
142
-
143
- /**
144
- * Extract a `{[templateName key=value …]}` annotation from a heading's
145
- * inline children. Mutates the children array in-place: strips the
146
- * annotation text from the last text node (or removes the node entirely).
147
- *
148
- * Matching is delegated to {@link matchTrailingTemplateAnnotation}: a
149
- * quote-aware grammar (values may contain `]` when quoted) with a legacy
150
- * fallback, plus tolerance for an accidental doubled trailing `]}`. A
151
- * non-trailing annotation like `## The {[chart]} section` doesn't match.
152
- *
153
- * @returns The parsed annotation, or null if none found.
154
- */
155
- function extractTemplateAnnotation(
156
- children: MarkdownInlineNode[],
157
- ): HeadingTemplateAnnotation | null {
158
- // Walk backwards to find the last text node
159
- for (let i = children.length - 1; i >= 0; i--) {
160
- const child = children[i];
161
- if (child.type === 'text') {
162
- const match = matchTrailingTemplateAnnotation(child.value);
163
- if (match) {
164
- const inner = match.inner.trim();
165
- const annotation = parseAnnotationTokens(inner);
166
-
167
- // Strip the matched portion from the text
168
- const stripped = child.value.slice(0, match.index).replace(/\s+$/, '');
169
- if (stripped) {
170
- (child as { value: string }).value = stripped;
171
- } else {
172
- // Remove the now-empty text node
173
- children.splice(i, 1);
174
- }
175
- return annotation;
176
- }
177
- // Only check the last text node (trailing position)
178
- break;
179
- }
180
- // If the last child isn't a text node, there's no annotation
181
- break;
182
- }
183
- return null;
184
- }
185
-
186
- /**
187
- * Parse the inner content of a `{[…]}` annotation into template + params.
188
- *
189
- * Input: `"chart colorScheme=blue caption=\"Sales by region\""`
190
- * Output: `{ template: 'chart', params: { colorScheme: 'blue', caption: 'Sales by region' } }`
191
- *
192
- * Tokenization is shared with the Pandoc attribute parser
193
- * ({@link tokenizeAttrTokens}), so quoting and escaping behave
194
- * identically in both annotation forms.
195
- */
196
- function parseAnnotationTokens(inner: string): HeadingTemplateAnnotation {
197
- const tokens = tokenizeAttrTokens(inner);
198
- const params: Record<string, string> = {};
199
-
200
- // If the first token contains '=', there's no template name —
201
- // the annotation is purely key-value (e.g., `{[audio=intro.mp3]}`).
202
- const firstIsParam = tokens.length > 0 && tokens[0].indexOf('=') > 0;
203
- const template = firstIsParam || tokens.length === 0 ? undefined : tokens[0];
204
- const startIdx = firstIsParam ? 0 : 1;
205
-
206
- for (let i = startIdx; i < tokens.length; i++) {
207
- const kv = splitKeyValueToken(tokens[i]);
208
- if (kv) {
209
- params[kv.key] = kv.value;
210
- }
211
- }
212
-
213
- const result: HeadingTemplateAnnotation = {};
214
- if (template) {
215
- result.template = template;
216
- }
217
- if (Object.keys(params).length > 0) {
218
- result.params = params;
219
- }
220
- return result;
221
- }
222
-
223
- // ============================================
224
- // Pandoc-style heading attribute helpers
225
- // ============================================
226
-
227
- /**
228
- * Extract a trailing Pandoc-style `{…}` attribute block from a heading's
229
- * inline children. Matching is delegated to {@link matchTrailingPandocAttr}
230
- * (quote-aware — values may contain `}` when quoted — with a legacy
231
- * fallback; a `(?!\[)` lookahead keeps it from colliding with the
232
- * squisq-native `{[…]}` template annotation).
233
- *
234
- * Unlike `extractTemplateAnnotation()`, this walks backward through both
235
- * text nodes AND bare `textDirective` nodes. remark-directive may have
236
- * fragmented a `{key=val:type,…}` attribute block at colon-prefixed
237
- * segments like `:type` that look like text directives but are really
238
- * attribute-value fragments. We virtually re-stitch the trailing run as a
239
- * single string, match against it, and only physically modify the children
240
- * array if a match is found — so a heading that legitimately contains a
241
- * non-bare `:directive` is left alone.
242
- *
243
- * Returns the parsed `HeadingAttributes`, or null if no match.
244
- */
245
- function extractPandocAttributes(children: MarkdownInlineNode[]): HeadingAttributes | null {
246
- // Collect the trailing run of [text | bare textDirective] children.
247
- const trail: { val: string; idx: number; len: number }[] = [];
248
- for (let i = children.length - 1; i >= 0; i--) {
249
- const c = children[i];
250
- if (c.type === 'text') {
251
- trail.unshift({ val: c.value, idx: i, len: c.value.length });
252
- } else if (c.type === 'textDirective') {
253
- const td = c as {
254
- name?: string;
255
- children?: unknown[];
256
- attributes?: Record<string, string>;
257
- };
258
- const isBare =
259
- (!td.children || td.children.length === 0) &&
260
- (!td.attributes || Object.keys(td.attributes).length === 0);
261
- if (!isBare) break;
262
- const synthesized = ':' + (td.name ?? '');
263
- trail.unshift({ val: synthesized, idx: i, len: synthesized.length });
264
- } else {
265
- break;
266
- }
267
- }
268
- if (trail.length === 0) return null;
269
-
270
- const joined = trail.map((t) => t.val).join('');
271
- const match = matchTrailingPandocAttr(joined);
272
- if (!match) return null;
273
-
274
- const inner = match.inner.trim();
275
- const attrs = parsePandocAttrTokens(inner);
276
-
277
- // Strip the matched portion. `match.index` is an offset into `joined`.
278
- // Walk forward through `trail` until we find the child that straddles
279
- // the match boundary; trim that child (if text) or drop it (if directive),
280
- // and remove everything after it from `children`.
281
- const matchStart = match.index;
282
- let scannedLen = 0;
283
- for (let ti = 0; ti < trail.length; ti++) {
284
- const t = trail[ti];
285
- if (scannedLen + t.len <= matchStart) {
286
- scannedLen += t.len;
287
- continue;
288
- }
289
- const firstNode = children[t.idx];
290
- const keepLen = matchStart - scannedLen;
291
- if (firstNode.type === 'text') {
292
- const trimmed = firstNode.value.slice(0, keepLen).replace(/\s+$/, '');
293
- if (trimmed) {
294
- (firstNode as { value: string }).value = trimmed;
295
- children.splice(t.idx + 1);
296
- } else {
297
- children.splice(t.idx);
298
- }
299
- } else {
300
- // Match boundary fell inside a textDirective's synthesized ":name"
301
- // string — drop the directive and everything after.
302
- children.splice(t.idx);
303
- }
304
- return attrs;
305
- }
306
- // Match consumes the entire trail.
307
- children.splice(trail[0].idx);
308
- return attrs;
309
- }
310
-
311
- /**
312
- * Parse the inner content of a Pandoc-style `{…}` block. Tokens are
313
- * whitespace-separated, with quoted runs (single or double) treated as
314
- * single tokens — tokenization is shared with the `{[…]}` template
315
- * annotation parser via {@link tokenizeAttrTokens}.
316
- *
317
- * - `#id` → `attributes.id`
318
- * - `.class` → push onto `attributes.classes`
319
- * - `key=value` or `key="quoted value"` → `attributes.params[key] = value`
320
- *
321
- * Empty `{}` yields `{}` (preserved as an annotation marker for round-tripping).
322
- * Duplicate ids and keys: last wins, silent.
323
- */
324
- export function parsePandocAttrTokens(inner: string): HeadingAttributes {
325
- const tokens = tokenizeAttrTokens(inner);
326
- const attrs: HeadingAttributes = {};
327
- const params: Record<string, string> = {};
328
- const classes: string[] = [];
329
-
330
- for (const token of tokens) {
331
- if (token.startsWith('#') && !token.includes('=')) {
332
- attrs.id = token.slice(1);
333
- } else if (token.startsWith('.') && !token.includes('=')) {
334
- const cls = token.slice(1);
335
- if (cls) classes.push(cls);
336
- } else {
337
- const kv = splitKeyValueToken(token);
338
- if (kv) {
339
- params[kv.key] = kv.value;
340
- }
341
- }
342
- }
343
-
344
- if (classes.length > 0) attrs.classes = classes;
345
- if (Object.keys(params).length > 0) {
346
- attrs.params = params;
347
- const { blockMeta, metadata } = coerceAnnotationValues(params);
348
- if (Object.keys(blockMeta).length > 0) attrs.blockMeta = blockMeta;
349
- if (Object.keys(metadata).length > 0) attrs.metadata = metadata;
350
- }
351
- return attrs;
352
- }
353
-
354
- /**
355
- * Serialize a HeadingAttributes object back to a Pandoc `{#id .class key=value}` string.
356
- * Returns null when the attributes object is entirely empty (nothing to emit).
357
- *
358
- * Canonical key order: `#id`, then `.classes` (in original order), then
359
- * params in their original insertion order. Values are quoted via the
360
- * shared {@link quoteAttrValue} rule (same as `{[…]}` serialization).
361
- */
362
- export function serializePandocAttributes(attrs: HeadingAttributes): string | null {
363
- const parts: string[] = [];
364
- if (attrs.id) parts.push(`#${attrs.id}`);
365
- if (attrs.classes) {
366
- for (const cls of attrs.classes) parts.push(`.${cls}`);
367
- }
368
- if (attrs.params) {
369
- for (const [key, value] of Object.entries(attrs.params)) {
370
- parts.push(`${key}=${quoteAttrValue(value)}`);
371
- }
372
- }
373
- // Preserve an empty `{}` annotation marker if it was authored deliberately
374
- // (e.g., reserved for downstream tooling). Caller decides whether to emit.
375
- if (parts.length === 0) return '{}';
376
- return `{${parts.join(' ')}}`;
377
- }
378
-
379
- function hasPrintablePandocAttributes(attrs: HeadingAttributes): boolean {
380
- return Boolean(
381
- attrs.id ||
382
- (attrs.classes && attrs.classes.length > 0) ||
383
- (attrs.params && Object.keys(attrs.params).length > 0),
384
- );
385
- }
386
-
387
- /**
388
- * Serialize a HeadingTemplateAnnotation back to `{[templateName key=value …]}` text.
389
- * Values are quoted via the shared {@link quoteAttrValue} rule (same as
390
- * Pandoc attribute serialization).
391
- */
392
- function serializeTemplateAnnotation(annotation: HeadingTemplateAnnotation): string {
393
- const parts: string[] = [];
394
- if (annotation.template) {
395
- parts.push(annotation.template);
396
- }
397
- if (annotation.params) {
398
- for (const [key, value] of Object.entries(annotation.params)) {
399
- parts.push(`${key}=${quoteAttrValue(value)}`);
400
- }
401
- }
402
- return `{[${parts.join(' ')}]}`;
403
- }
404
-
405
- /**
406
- * Convert an mdast Root node to a MarkdownDocument.
407
- *
408
- * @param root - The mdast root node (from remark-parse)
409
- * @param options - Conversion options
410
- * @returns A MarkdownDocument
411
- */
412
- export function fromMdast(root: MdastNode, options?: { parseHtml?: boolean }): MarkdownDocument {
413
- const doParseHtml = options?.parseHtml !== false;
414
- return {
415
- type: 'document',
416
- children: convertBlockChildren(root.children ?? [], doParseHtml),
417
- ...posField(convertPosition(root.position)),
418
- };
419
- }
420
-
421
- function convertBlockChildren(children: MdastNode[], parseHtml: boolean): MarkdownBlockNode[] {
422
- const result: MarkdownBlockNode[] = [];
423
- for (const child of children) {
424
- const node = convertBlockNode(child, parseHtml);
425
- if (node) result.push(node);
426
- }
427
- return result;
428
- }
429
-
430
- function convertInlineChildren(
431
- children: MdastNode[],
432
- parseHtml: boolean,
433
- /** When false, skip the inline-icon split. Headings opt out so the
434
- * `extractTemplateAnnotation` pass still sees the trailing
435
- * `{[…]}` in raw text form; the caller runs `splitInlineIcons`
436
- * itself afterward on the remaining children. */
437
- applyIconSplit: boolean = true,
438
- ): MarkdownInlineNode[] {
439
- const result: MarkdownInlineNode[] = [];
440
- for (const child of children) {
441
- const node = convertInlineNode(child, parseHtml);
442
- if (node) result.push(node);
443
- }
444
- const mentioned = coalesceMentions(result);
445
- return applyIconSplit ? splitInlineIcons(mentioned) : mentioned;
446
- }
447
-
448
- /**
449
- * Walk an inline-children list and split each text node on `{[token]}`
450
- * occurrences whose tokens resolve to known FontAwesome icons. Returns
451
- * a new array — unresolved tokens are left as literal text so authors
452
- * can write `{[notathing]}` verbatim if they really mean to.
453
- *
454
- * Exposed for the heading code path, which first strips the trailing
455
- * template annotation and then runs this on what remains.
456
- */
457
- export function splitInlineIcons(nodes: MarkdownInlineNode[]): MarkdownInlineNode[] {
458
- // First pass: reassemble `text + textDirective + text` patterns
459
- // formed when a qualified token like `{[fa-solid:user]}` was split
460
- // by remark-directive's colon recognition. We collapse those triples
461
- // back into a single text node containing the original token so the
462
- // regex below can match.
463
- const recombined = recombineDirectiveTokens(nodes);
464
-
465
- const out: MarkdownInlineNode[] = [];
466
- for (const node of recombined) {
467
- if (node.type !== 'text') {
468
- out.push(node);
469
- continue;
470
- }
471
- out.push(...splitTextOnIcons(node.value, node.position));
472
- }
473
- return out;
474
- }
475
-
476
- /**
477
- * When the user types `{[fa-solid:user]}`, remark's directive plugin
478
- * sees `:user` and emits a `textDirective` node. The result is a
479
- * three-node sequence `text("…{[fa-solid") + textDirective("user") +
480
- * text("]}…")`. This helper detects that shape and stitches the three
481
- * back into one text node so downstream icon detection works on the
482
- * authored token.
483
- */
484
- function recombineDirectiveTokens(nodes: MarkdownInlineNode[]): MarkdownInlineNode[] {
485
- const out: MarkdownInlineNode[] = [];
486
- for (let i = 0; i < nodes.length; i++) {
487
- const cur = nodes[i];
488
- const next = nodes[i + 1];
489
- const after = nodes[i + 2];
490
- if (
491
- cur?.type === 'text' &&
492
- next?.type === 'textDirective' &&
493
- after?.type === 'text' &&
494
- /\{\[[a-zA-Z0-9_-]+$/.test(cur.value) &&
495
- /^\]\}/.test(after.value)
496
- ) {
497
- const directive = next as unknown as { name?: string };
498
- const directiveName = directive.name ?? '';
499
- const merged: MarkdownInlineNode = {
500
- type: 'text',
501
- value: cur.value + ':' + directiveName + after.value,
502
- ...(cur.position ? { position: cur.position } : {}),
503
- };
504
- out.push(merged);
505
- i += 2;
506
- continue;
507
- }
508
- out.push(cur);
509
- }
510
- return out;
511
- }
512
-
513
- const ICON_TOKEN_RE = /\{\[([a-zA-Z0-9_:-]+)\]\}/g;
514
-
515
- function splitTextOnIcons(value: string, position?: MarkdownSourcePosition): MarkdownInlineNode[] {
516
- if (!value) return [];
517
- ICON_TOKEN_RE.lastIndex = 0;
518
- let lastIndex = 0;
519
- let match: RegExpExecArray | null;
520
- const out: MarkdownInlineNode[] = [];
521
- while ((match = ICON_TOKEN_RE.exec(value)) !== null) {
522
- const token = match[1];
523
- const icon = resolveIcon(token);
524
- if (!icon) continue; // leave the literal `{[…]}` in place
525
- if (match.index > lastIndex) {
526
- out.push({
527
- type: 'text',
528
- value: value.slice(lastIndex, match.index),
529
- ...(position ? { position } : {}),
530
- });
531
- }
532
- const iconNode: MarkdownInlineIcon = {
533
- type: 'inlineIcon',
534
- token,
535
- family: icon.family,
536
- name: icon.name,
537
- ...(position ? { position } : {}),
538
- };
539
- out.push(iconNode);
540
- lastIndex = match.index + match[0].length;
541
- }
542
- if (lastIndex === 0) {
543
- // No matches — return the original text node unchanged.
544
- return [{ type: 'text', value, ...(position ? { position } : {}) }];
545
- }
546
- if (lastIndex < value.length) {
547
- out.push({
548
- type: 'text',
549
- value: value.slice(lastIndex),
550
- ...(position ? { position } : {}),
551
- });
552
- }
553
- return out;
554
- }
555
-
556
- /**
557
- * Post-process an inline-children list to collapse `text('@') + link(scheme:id)`
558
- * pairs into a single `mention` node. The wire format `@[Display](scheme:id)`
559
- * comes back from remark as two adjacent nodes — we merge them so consumers
560
- * see a single atomic mention. A plain link whose URL doesn't have `scheme:id`
561
- * shape, or that isn't immediately preceded by `@`, is left alone so ordinary
562
- * links keep working.
563
- */
564
- function coalesceMentions(inlines: MarkdownInlineNode[]): MarkdownInlineNode[] {
565
- const out: MarkdownInlineNode[] = [];
566
- for (const cur of inlines) {
567
- const prev = out[out.length - 1];
568
- if (
569
- cur.type === 'link' &&
570
- typeof cur.url === 'string' &&
571
- prev &&
572
- prev.type === 'text' &&
573
- prev.value.endsWith('@')
574
- ) {
575
- const schemeMatch = cur.url.match(/^([a-z][a-z0-9+.-]*):(.+)$/i);
576
- if (schemeMatch && schemeMatch[1] && schemeMatch[2]) {
577
- const targetKind = schemeMatch[1];
578
- const targetId = schemeMatch[2];
579
- const displayName = extractInlineText(cur.children);
580
- if (prev.value === '@') {
581
- out.pop();
582
- } else {
583
- prev.value = prev.value.slice(0, -1);
584
- }
585
- out.push({
586
- type: 'mention',
587
- targetKind,
588
- targetId,
589
- displayName,
590
- ...(cur.position ? { position: cur.position } : {}),
591
- });
592
- continue;
593
- }
594
- }
595
- out.push(cur);
596
- }
597
- return out;
598
- }
599
-
600
- /** Plain-text of an inline children list — used to pull the display name
601
- * out of a mention link's children. */
602
- function extractInlineText(nodes: MarkdownInlineNode[]): string {
603
- let out = '';
604
- for (const n of nodes) {
605
- if (n.type === 'text' || n.type === 'inlineCode') out += n.value;
606
- else if ('children' in n && Array.isArray(n.children)) {
607
- out += extractInlineText(n.children as MarkdownInlineNode[]);
608
- }
609
- }
610
- return out;
611
- }
612
-
613
- function convertBlockNode(node: MdastNode, parseHtml: boolean): MarkdownBlockNode | null {
614
- const pos = convertPosition(node.position);
615
-
616
- switch (node.type) {
617
- case 'heading': {
618
- // Defer icon splitting so `extractTemplateAnnotation` sees the
619
- // trailing `{[name]}` as plain text and can decide whether it's
620
- // a template annotation. Anything that's NOT the template still
621
- // needs to become an icon node, so we run `splitInlineIcons` on
622
- // what's left after the template extraction.
623
- //
624
- // Both annotation forms ({[…]} and Pandoc {#…}) may appear at the
625
- // end of the heading in either order. Extract them in a loop so
626
- // we catch the second-from-last one too. A small bounded retry
627
- // (max 4) is plenty — authors don't stack more than two trailing
628
- // brace-blocks in practice.
629
- const headingChildren = convertInlineChildren(node.children ?? [], parseHtml, false);
630
- let annotation: HeadingTemplateAnnotation | null = null;
631
- let attrs: HeadingAttributes | null = null;
632
- for (let pass = 0; pass < 4; pass++) {
633
- let matched = false;
634
- if (!annotation) {
635
- const t = extractTemplateAnnotation(headingChildren);
636
- if (t) {
637
- annotation = t;
638
- matched = true;
639
- }
640
- }
641
- if (!attrs) {
642
- const a = extractPandocAttributes(headingChildren);
643
- if (a) {
644
- attrs = a;
645
- matched = true;
646
- }
647
- }
648
- if (!matched) break;
649
- }
650
- const finalChildren = splitInlineIcons(headingChildren);
651
- const result: MarkdownHeading = {
652
- type: 'heading',
653
- depth: (node.depth ?? 1) as 1 | 2 | 3 | 4 | 5 | 6,
654
- children: finalChildren,
655
- ...posField(pos),
656
- };
657
- if (annotation) {
658
- result.templateAnnotation = annotation;
659
- }
660
- if (attrs) {
661
- result.attributes = attrs;
662
- }
663
- return result;
664
- }
665
-
666
- case 'paragraph':
667
- return {
668
- type: 'paragraph',
669
- children: convertInlineChildren(node.children ?? [], parseHtml),
670
- ...posField(pos),
671
- };
672
-
673
- case 'blockquote':
674
- return {
675
- type: 'blockquote',
676
- children: convertBlockChildren(node.children ?? [], parseHtml),
677
- ...posField(pos),
678
- };
679
-
680
- case 'list':
681
- return {
682
- type: 'list',
683
- ordered: node.ordered ?? false,
684
- ...(node.start != null ? { start: node.start } : {}),
685
- ...(node.spread != null ? { spread: node.spread } : {}),
686
- children: (node.children ?? []).map((item) => convertListItem(item, parseHtml)),
687
- ...posField(pos),
688
- };
689
-
690
- case 'code':
691
- return {
692
- type: 'code',
693
- ...(node.lang != null ? { lang: node.lang } : {}),
694
- ...(node.meta != null ? { meta: node.meta } : {}),
695
- value: node.value ?? '',
696
- ...posField(pos),
697
- };
698
-
699
- case 'thematicBreak':
700
- return {
701
- type: 'thematicBreak',
702
- ...posField(pos),
703
- };
704
-
705
- case 'table':
706
- return {
707
- type: 'table',
708
- ...(node.align
709
- ? { align: node.align.map((a) => a as 'left' | 'right' | 'center' | null) }
710
- : {}),
711
- children: (node.children ?? []).map((row, i) => convertTableRow(row, i === 0, parseHtml)),
712
- ...posField(pos),
713
- };
714
-
715
- case 'html':
716
- return {
717
- type: 'htmlBlock',
718
- rawHtml: node.value ?? '',
719
- htmlChildren: parseHtml ? parseHtmlToNodes(node.value ?? '') : [],
720
- ...posField(pos),
721
- };
722
-
723
- case 'math':
724
- return {
725
- type: 'math',
726
- value: node.value ?? '',
727
- ...(node.meta != null ? { meta: node.meta } : {}),
728
- ...posField(pos),
729
- };
730
-
731
- case 'definition':
732
- return {
733
- type: 'definition',
734
- identifier: node.identifier ?? '',
735
- ...(node.label != null ? { label: node.label } : {}),
736
- url: node.url ?? '',
737
- ...(node.title != null ? { title: node.title } : {}),
738
- ...posField(pos),
739
- };
740
-
741
- case 'footnoteDefinition':
742
- return {
743
- type: 'footnoteDefinition',
744
- identifier: node.identifier ?? '',
745
- ...(node.label != null ? { label: node.label } : {}),
746
- children: convertBlockChildren(node.children ?? [], parseHtml),
747
- ...posField(pos),
748
- };
749
-
750
- case 'containerDirective': {
751
- const allChildren = node.children ?? [];
752
- let label: string | undefined;
753
- let contentChildren = allChildren;
754
-
755
- // Extract directive label (first paragraph with directiveLabel flag)
756
- if (
757
- allChildren.length > 0 &&
758
- allChildren[0].type === 'paragraph' &&
759
- allChildren[0].data?.directiveLabel === true
760
- ) {
761
- label = extractText(allChildren[0]);
762
- contentChildren = allChildren.slice(1);
763
- }
764
-
765
- return {
766
- type: 'containerDirective',
767
- name: node.name ?? '',
768
- ...(label ? { label } : {}),
769
- ...(node.attributes && Object.keys(node.attributes).length > 0
770
- ? { attributes: node.attributes }
771
- : {}),
772
- children: convertBlockChildren(contentChildren, parseHtml),
773
- ...posField(pos),
774
- };
775
- }
776
-
777
- case 'leafDirective':
778
- return {
779
- type: 'leafDirective',
780
- name: node.name ?? '',
781
- ...(node.attributes && Object.keys(node.attributes).length > 0
782
- ? { attributes: node.attributes }
783
- : {}),
784
- children: convertInlineChildren(node.children ?? [], parseHtml),
785
- ...posField(pos),
786
- };
787
-
788
- case 'yaml':
789
- // YAML frontmatter nodes are handled separately in parse.ts — skip silently
790
- return null;
791
-
792
- default:
793
- // Unknown block node — skip with a warning
794
- if (typeof console !== 'undefined') {
795
- console.warn(`[squisq/markdown] Unknown mdast block node type: "${node.type}"`);
796
- }
797
- return null;
798
- }
799
- }
800
-
801
- function convertListItem(node: MdastNode, parseHtml: boolean): MarkdownListItem {
802
- const pos = convertPosition(node.position);
803
- return {
804
- type: 'listItem',
805
- ...(node.checked != null ? { checked: node.checked } : {}),
806
- ...(node.spread != null ? { spread: node.spread } : {}),
807
- children: convertBlockChildren(node.children ?? [], parseHtml),
808
- ...posField(pos),
809
- };
810
- }
811
-
812
- function convertTableRow(node: MdastNode, isHeader: boolean, parseHtml: boolean): MarkdownTableRow {
813
- const pos = convertPosition(node.position);
814
- return {
815
- type: 'tableRow',
816
- children: (node.children ?? []).map((cell) => convertTableCell(cell, isHeader, parseHtml)),
817
- ...posField(pos),
818
- };
819
- }
820
-
821
- function convertTableCell(
822
- node: MdastNode,
823
- isHeader: boolean,
824
- parseHtml: boolean,
825
- ): MarkdownTableCell {
826
- const pos = convertPosition(node.position);
827
- return {
828
- type: 'tableCell',
829
- ...(isHeader ? { isHeader: true } : {}),
830
- children: convertInlineChildren(node.children ?? [], parseHtml),
831
- ...posField(pos),
832
- };
833
- }
834
-
835
- function convertInlineNode(node: MdastNode, parseHtml: boolean): MarkdownInlineNode | null {
836
- const pos = convertPosition(node.position);
837
-
838
- switch (node.type) {
839
- case 'text':
840
- return {
841
- type: 'text',
842
- value: node.value ?? '',
843
- ...posField(pos),
844
- };
845
-
846
- case 'emphasis':
847
- return {
848
- type: 'emphasis',
849
- children: convertInlineChildren(node.children ?? [], parseHtml),
850
- ...posField(pos),
851
- };
852
-
853
- case 'strong':
854
- return {
855
- type: 'strong',
856
- children: convertInlineChildren(node.children ?? [], parseHtml),
857
- ...posField(pos),
858
- };
859
-
860
- case 'delete':
861
- return {
862
- type: 'delete',
863
- children: convertInlineChildren(node.children ?? [], parseHtml),
864
- ...posField(pos),
865
- };
866
-
867
- case 'inlineCode':
868
- return {
869
- type: 'inlineCode',
870
- value: node.value ?? '',
871
- ...posField(pos),
872
- };
873
-
874
- case 'link':
875
- return {
876
- type: 'link',
877
- url: node.url ?? '',
878
- ...(node.title != null ? { title: node.title } : {}),
879
- children: convertInlineChildren(node.children ?? [], parseHtml),
880
- ...posField(pos),
881
- };
882
-
883
- case 'image':
884
- return {
885
- type: 'image',
886
- url: node.url ?? '',
887
- ...(node.alt != null ? { alt: node.alt } : {}),
888
- ...(node.title != null ? { title: node.title } : {}),
889
- ...posField(pos),
890
- };
891
-
892
- case 'break':
893
- return {
894
- type: 'break',
895
- ...posField(pos),
896
- };
897
-
898
- case 'html':
899
- return {
900
- type: 'htmlInline',
901
- rawHtml: node.value ?? '',
902
- htmlChildren: parseHtml ? parseHtmlToNodes(node.value ?? '') : [],
903
- ...posField(pos),
904
- };
905
-
906
- case 'inlineMath':
907
- return {
908
- type: 'inlineMath',
909
- value: node.value ?? '',
910
- ...posField(pos),
911
- };
912
-
913
- case 'footnoteReference':
914
- return {
915
- type: 'footnoteReference',
916
- identifier: node.identifier ?? '',
917
- ...(node.label != null ? { label: node.label } : {}),
918
- ...posField(pos),
919
- };
920
-
921
- case 'linkReference':
922
- return {
923
- type: 'linkReference',
924
- identifier: node.identifier ?? '',
925
- ...(node.label != null ? { label: node.label } : {}),
926
- referenceType: (node.referenceType ?? 'full') as 'full' | 'collapsed' | 'shortcut',
927
- children: convertInlineChildren(node.children ?? [], parseHtml),
928
- ...posField(pos),
929
- };
930
-
931
- case 'imageReference':
932
- return {
933
- type: 'imageReference',
934
- identifier: node.identifier ?? '',
935
- ...(node.label != null ? { label: node.label } : {}),
936
- referenceType: (node.referenceType ?? 'full') as 'full' | 'collapsed' | 'shortcut',
937
- ...(node.alt != null ? { alt: node.alt } : {}),
938
- ...posField(pos),
939
- };
940
-
941
- case 'textDirective':
942
- return {
943
- type: 'textDirective',
944
- name: node.name ?? '',
945
- ...(node.attributes && Object.keys(node.attributes).length > 0
946
- ? { attributes: node.attributes }
947
- : {}),
948
- children: convertInlineChildren(node.children ?? [], parseHtml),
949
- ...posField(pos),
950
- };
951
-
952
- default:
953
- if (typeof console !== 'undefined') {
954
- console.warn(`[squisq/markdown] Unknown mdast inline node type: "${node.type}"`);
955
- }
956
- return null;
957
- }
958
- }
959
-
960
- // ============================================
961
- // Squisq → mdast (toMdast)
962
- // ============================================
963
-
964
- /**
965
- * Convert a MarkdownDocument back to an mdast Root node.
966
- *
967
- * @param doc - A MarkdownDocument
968
- * @returns An mdast Root node suitable for remark-stringify
969
- */
970
- export function toMdast(doc: MarkdownDocument): MdastNode {
971
- return {
972
- type: 'root',
973
- children: doc.children.map((n) => blockToMdast(n)),
974
- ...mdastPosField(doc.position),
975
- };
976
- }
977
-
978
- function blockToMdast(node: MarkdownBlockNode): MdastNode {
979
- switch (node.type) {
980
- case 'heading': {
981
- const mdastChildren = inlineChildrenToMdast(node.children);
982
- // Canonical trailing order: Pandoc attrs first, then template annotation.
983
- // Author-supplied order is not preserved across round-trips by design —
984
- // canonicalization gives stable diffs.
985
- const suffixes: string[] = [];
986
- if (node.attributes) {
987
- const pandoc = serializePandocAttributes(node.attributes);
988
- if (pandoc != null && (pandoc !== '{}' || hasPrintablePandocAttributes(node.attributes))) {
989
- suffixes.push(pandoc);
990
- }
991
- }
992
- if (node.templateAnnotation) {
993
- suffixes.push(serializeTemplateAnnotation(node.templateAnnotation));
994
- }
995
- if (suffixes.length > 0) {
996
- const suffix = suffixes.join(' ');
997
- const lastChild = mdastChildren[mdastChildren.length - 1];
998
- if (lastChild && lastChild.type === 'text') {
999
- lastChild.value = (lastChild.value ?? '') + ' ' + suffix;
1000
- } else {
1001
- mdastChildren.push({ type: 'text', value: ' ' + suffix });
1002
- }
1003
- }
1004
- return {
1005
- type: 'heading',
1006
- depth: node.depth,
1007
- children: mdastChildren,
1008
- ...mdastPosField(node.position),
1009
- };
1010
- }
1011
-
1012
- case 'paragraph':
1013
- return {
1014
- type: 'paragraph',
1015
- children: inlineChildrenToMdast(node.children),
1016
- ...mdastPosField(node.position),
1017
- };
1018
-
1019
- case 'blockquote':
1020
- return {
1021
- type: 'blockquote',
1022
- children: node.children.map(blockToMdast),
1023
- ...mdastPosField(node.position),
1024
- };
1025
-
1026
- case 'list':
1027
- return {
1028
- type: 'list',
1029
- ordered: node.ordered,
1030
- ...(node.start != null ? { start: node.start } : {}),
1031
- ...(node.spread != null ? { spread: node.spread } : {}),
1032
- children: node.children.map(listItemToMdast),
1033
- ...mdastPosField(node.position),
1034
- };
1035
-
1036
- case 'code':
1037
- return {
1038
- type: 'code',
1039
- ...(node.lang != null ? { lang: node.lang } : {}),
1040
- ...(node.meta != null ? { meta: node.meta } : {}),
1041
- value: node.value,
1042
- ...mdastPosField(node.position),
1043
- };
1044
-
1045
- case 'thematicBreak':
1046
- return {
1047
- type: 'thematicBreak',
1048
- ...mdastPosField(node.position),
1049
- };
1050
-
1051
- case 'table':
1052
- return {
1053
- type: 'table',
1054
- ...(node.align ? { align: node.align } : {}),
1055
- children: node.children.map(tableRowToMdast),
1056
- ...mdastPosField(node.position),
1057
- };
1058
-
1059
- case 'htmlBlock':
1060
- return {
1061
- type: 'html',
1062
- value: node.rawHtml,
1063
- ...mdastPosField(node.position),
1064
- };
1065
-
1066
- case 'math':
1067
- return {
1068
- type: 'math',
1069
- value: node.value,
1070
- ...(node.meta != null ? { meta: node.meta } : {}),
1071
- ...mdastPosField(node.position),
1072
- };
1073
-
1074
- case 'definition':
1075
- return {
1076
- type: 'definition',
1077
- identifier: node.identifier,
1078
- ...(node.label != null ? { label: node.label } : {}),
1079
- url: node.url,
1080
- ...(node.title != null ? { title: node.title } : {}),
1081
- ...mdastPosField(node.position),
1082
- };
1083
-
1084
- case 'footnoteDefinition':
1085
- return {
1086
- type: 'footnoteDefinition',
1087
- identifier: node.identifier,
1088
- ...(node.label != null ? { label: node.label } : {}),
1089
- children: node.children.map(blockToMdast),
1090
- ...mdastPosField(node.position),
1091
- };
1092
-
1093
- case 'containerDirective': {
1094
- const children: MdastNode[] = [];
1095
-
1096
- // Reinject label as first paragraph with directiveLabel flag
1097
- if (node.label) {
1098
- children.push({
1099
- type: 'paragraph',
1100
- data: { directiveLabel: true },
1101
- children: [{ type: 'text', value: node.label }],
1102
- });
1103
- }
1104
-
1105
- children.push(...node.children.map(blockToMdast));
1106
-
1107
- return {
1108
- type: 'containerDirective',
1109
- name: node.name,
1110
- ...(node.attributes ? { attributes: node.attributes } : {}),
1111
- children,
1112
- ...mdastPosField(node.position),
1113
- };
1114
- }
1115
-
1116
- case 'leafDirective':
1117
- return {
1118
- type: 'leafDirective',
1119
- name: node.name,
1120
- ...(node.attributes ? { attributes: node.attributes } : {}),
1121
- children: inlineChildrenToMdast(node.children),
1122
- ...mdastPosField(node.position),
1123
- };
1124
-
1125
- case 'definitionList':
1126
- // Definition lists don't map to standard mdast.
1127
- // Convert to HTML as fallback.
1128
- return {
1129
- type: 'html',
1130
- value: definitionListToHtml(node),
1131
- ...mdastPosField(node.position),
1132
- };
1133
-
1134
- default:
1135
- return { type: 'html', value: '' };
1136
- }
1137
- }
1138
-
1139
- function listItemToMdast(item: MarkdownListItem): MdastNode {
1140
- return {
1141
- type: 'listItem',
1142
- ...(item.checked != null ? { checked: item.checked } : {}),
1143
- ...(item.spread != null ? { spread: item.spread } : {}),
1144
- children: item.children.map(blockToMdast),
1145
- ...mdastPosField(item.position),
1146
- };
1147
- }
1148
-
1149
- function tableRowToMdast(row: MarkdownTableRow): MdastNode {
1150
- return {
1151
- type: 'tableRow',
1152
- children: row.children.map(tableCellToMdast),
1153
- ...mdastPosField(row.position),
1154
- };
1155
- }
1156
-
1157
- function tableCellToMdast(cell: MarkdownTableCell): MdastNode {
1158
- return {
1159
- type: 'tableCell',
1160
- children: inlineChildrenToMdast(cell.children),
1161
- ...mdastPosField(cell.position),
1162
- };
1163
- }
1164
-
1165
- /**
1166
- * Serialize an array of inline nodes to mdast, flat-mapping the result so
1167
- * a single `mention` expands to two mdast nodes (`text('@')` + `link`).
1168
- * Use in place of `.map(inlineToMdast)` wherever we walk a children array.
1169
- */
1170
- function inlineChildrenToMdast(nodes: MarkdownInlineNode[]): MdastNode[] {
1171
- const out: MdastNode[] = [];
1172
- for (const n of nodes) {
1173
- if (n.type === 'mention') {
1174
- out.push({ type: 'text', value: '@' });
1175
- out.push({
1176
- type: 'link',
1177
- url: `${n.targetKind}:${n.targetId}`,
1178
- children: [{ type: 'text', value: n.displayName }],
1179
- ...mdastPosField(n.position),
1180
- });
1181
- } else {
1182
- out.push(inlineToMdast(n));
1183
- }
1184
- }
1185
- return out;
1186
- }
1187
-
1188
- function inlineToMdast(node: MarkdownInlineNode): MdastNode {
1189
- switch (node.type) {
1190
- case 'text':
1191
- return {
1192
- type: 'text',
1193
- value: node.value,
1194
- ...mdastPosField(node.position),
1195
- };
1196
-
1197
- case 'emphasis':
1198
- return {
1199
- type: 'emphasis',
1200
- children: inlineChildrenToMdast(node.children),
1201
- ...mdastPosField(node.position),
1202
- };
1203
-
1204
- case 'strong':
1205
- return {
1206
- type: 'strong',
1207
- children: inlineChildrenToMdast(node.children),
1208
- ...mdastPosField(node.position),
1209
- };
1210
-
1211
- case 'delete':
1212
- return {
1213
- type: 'delete',
1214
- children: inlineChildrenToMdast(node.children),
1215
- ...mdastPosField(node.position),
1216
- };
1217
-
1218
- case 'inlineCode':
1219
- return {
1220
- type: 'inlineCode',
1221
- value: node.value,
1222
- ...mdastPosField(node.position),
1223
- };
1224
-
1225
- case 'link':
1226
- return {
1227
- type: 'link',
1228
- url: node.url,
1229
- ...(node.title != null ? { title: node.title } : {}),
1230
- children: inlineChildrenToMdast(node.children),
1231
- ...mdastPosField(node.position),
1232
- };
1233
-
1234
- case 'image':
1235
- return {
1236
- type: 'image',
1237
- url: node.url,
1238
- ...(node.alt != null ? { alt: node.alt } : {}),
1239
- ...(node.title != null ? { title: node.title } : {}),
1240
- ...mdastPosField(node.position),
1241
- };
1242
-
1243
- case 'break':
1244
- return {
1245
- type: 'break',
1246
- ...mdastPosField(node.position),
1247
- };
1248
-
1249
- case 'htmlInline':
1250
- return {
1251
- type: 'html',
1252
- value: node.rawHtml,
1253
- ...mdastPosField(node.position),
1254
- };
1255
-
1256
- case 'inlineMath':
1257
- return {
1258
- type: 'inlineMath',
1259
- value: node.value,
1260
- ...mdastPosField(node.position),
1261
- };
1262
-
1263
- case 'footnoteReference':
1264
- return {
1265
- type: 'footnoteReference',
1266
- identifier: node.identifier,
1267
- ...(node.label != null ? { label: node.label } : {}),
1268
- ...mdastPosField(node.position),
1269
- };
1270
-
1271
- case 'linkReference':
1272
- return {
1273
- type: 'linkReference',
1274
- identifier: node.identifier,
1275
- ...(node.label != null ? { label: node.label } : {}),
1276
- referenceType: node.referenceType,
1277
- children: inlineChildrenToMdast(node.children),
1278
- ...mdastPosField(node.position),
1279
- };
1280
-
1281
- case 'imageReference':
1282
- return {
1283
- type: 'imageReference',
1284
- identifier: node.identifier,
1285
- ...(node.label != null ? { label: node.label } : {}),
1286
- referenceType: node.referenceType,
1287
- ...(node.alt != null ? { alt: node.alt } : {}),
1288
- ...mdastPosField(node.position),
1289
- };
1290
-
1291
- case 'textDirective':
1292
- return {
1293
- type: 'textDirective',
1294
- name: node.name,
1295
- ...(node.attributes ? { attributes: node.attributes } : {}),
1296
- children: inlineChildrenToMdast(node.children),
1297
- ...mdastPosField(node.position),
1298
- };
1299
-
1300
- case 'mention':
1301
- // Fallback: when `inlineToMdast` is called directly (not via
1302
- // `inlineChildrenToMdast`), collapse the mention to just the link
1303
- // form. The `@` prefix is lost, but the id+name still round-trip.
1304
- // Normal call sites go through `inlineChildrenToMdast` and get
1305
- // the proper text('@') + link pair.
1306
- return {
1307
- type: 'link',
1308
- url: `${node.targetKind}:${node.targetId}`,
1309
- children: [{ type: 'text', value: node.displayName }],
1310
- ...mdastPosField(node.position),
1311
- };
1312
-
1313
- case 'inlineIcon':
1314
- // Serialize back to the authored token so source round-trips
1315
- // byte-stable. The token already carries the qualified form when
1316
- // required (the parser preserved it verbatim).
1317
- return {
1318
- type: 'text',
1319
- value: `{[${node.token}]}`,
1320
- ...mdastPosField(node.position),
1321
- };
1322
-
1323
- default:
1324
- return { type: 'text', value: '' };
1325
- }
1326
- }
1327
-
1328
- // ============================================
1329
- // Helpers
1330
- // ============================================
1331
-
1332
- /**
1333
- * Convert a MarkdownDefinitionList to an HTML <dl> string.
1334
- * Fallback for serialization since mdast doesn't support definition lists.
1335
- */
1336
- function definitionListToHtml(list: {
1337
- children: Array<{ type: string; children?: unknown[] }>;
1338
- }): string {
1339
- let html = '<dl>\n';
1340
- for (const child of list.children) {
1341
- if (child.type === 'definitionTerm') {
1342
- // Extract plain text from inline children
1343
- const text = (child.children as Array<{ value?: string; children?: unknown[] }>)
1344
- .map((c) => c.value ?? '')
1345
- .join('');
1346
- html += ` <dt>${text}</dt>\n`;
1347
- } else if (child.type === 'definitionDescription') {
1348
- html += ` <dd>...</dd>\n`;
1349
- }
1350
- }
1351
- html += '</dl>';
1352
- return html;
1353
- }