@bendyline/squisq 1.3.0 → 1.4.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 (419) hide show
  1. package/dist/{Doc-Ch4tc3vv.d.ts → Doc-BeSeH2iX.d.ts} +118 -41
  2. package/dist/ImageEditDoc-BTIvw0oq.d.ts +84 -0
  3. package/dist/__tests__/colorUtils.test.d.ts +2 -0
  4. package/dist/__tests__/colorUtils.test.d.ts.map +1 -0
  5. package/dist/__tests__/colorUtils.test.js +94 -0
  6. package/dist/__tests__/colorUtils.test.js.map +1 -0
  7. package/dist/__tests__/getLayers.test.js +31 -7
  8. package/dist/__tests__/getLayers.test.js.map +1 -1
  9. package/dist/__tests__/iconResolve.test.d.ts +2 -0
  10. package/dist/__tests__/iconResolve.test.d.ts.map +1 -0
  11. package/dist/__tests__/iconResolve.test.js +99 -0
  12. package/dist/__tests__/iconResolve.test.js.map +1 -0
  13. package/dist/__tests__/imageEditState.test.d.ts +2 -0
  14. package/dist/__tests__/imageEditState.test.d.ts.map +1 -0
  15. package/dist/__tests__/imageEditState.test.js +61 -0
  16. package/dist/__tests__/imageEditState.test.js.map +1 -0
  17. package/dist/__tests__/imageEditVersions.test.d.ts +2 -0
  18. package/dist/__tests__/imageEditVersions.test.d.ts.map +1 -0
  19. package/dist/__tests__/imageEditVersions.test.js +141 -0
  20. package/dist/__tests__/imageEditVersions.test.js.map +1 -0
  21. package/dist/__tests__/inferDocumentTitle.test.d.ts +2 -0
  22. package/dist/__tests__/inferDocumentTitle.test.d.ts.map +1 -0
  23. package/dist/__tests__/inferDocumentTitle.test.js +42 -0
  24. package/dist/__tests__/inferDocumentTitle.test.js.map +1 -0
  25. package/dist/__tests__/jsonForm.chooseControl.test.d.ts +2 -0
  26. package/dist/__tests__/jsonForm.chooseControl.test.d.ts.map +1 -0
  27. package/dist/__tests__/jsonForm.chooseControl.test.js +74 -0
  28. package/dist/__tests__/jsonForm.chooseControl.test.js.map +1 -0
  29. package/dist/__tests__/jsonForm.evaluateWhen.test.d.ts +2 -0
  30. package/dist/__tests__/jsonForm.evaluateWhen.test.d.ts.map +1 -0
  31. package/dist/__tests__/jsonForm.evaluateWhen.test.js +51 -0
  32. package/dist/__tests__/jsonForm.evaluateWhen.test.js.map +1 -0
  33. package/dist/__tests__/jsonForm.inferSchema.test.d.ts +2 -0
  34. package/dist/__tests__/jsonForm.inferSchema.test.d.ts.map +1 -0
  35. package/dist/__tests__/jsonForm.inferSchema.test.js +23 -0
  36. package/dist/__tests__/jsonForm.inferSchema.test.js.map +1 -0
  37. package/dist/__tests__/jsonForm.pointer.test.d.ts +2 -0
  38. package/dist/__tests__/jsonForm.pointer.test.d.ts.map +1 -0
  39. package/dist/__tests__/jsonForm.pointer.test.js +94 -0
  40. package/dist/__tests__/jsonForm.pointer.test.js.map +1 -0
  41. package/dist/__tests__/markdownIcons.test.d.ts +2 -0
  42. package/dist/__tests__/markdownIcons.test.d.ts.map +1 -0
  43. package/dist/__tests__/markdownIcons.test.js +93 -0
  44. package/dist/__tests__/markdownIcons.test.js.map +1 -0
  45. package/dist/__tests__/markdownToDoc.test.js +15 -4
  46. package/dist/__tests__/markdownToDoc.test.js.map +1 -1
  47. package/dist/__tests__/recommendTemplates.test.d.ts +2 -0
  48. package/dist/__tests__/recommendTemplates.test.d.ts.map +1 -0
  49. package/dist/__tests__/recommendTemplates.test.js +158 -0
  50. package/dist/__tests__/recommendTemplates.test.js.map +1 -0
  51. package/dist/__tests__/scopedContentContainer.test.d.ts +2 -0
  52. package/dist/__tests__/scopedContentContainer.test.d.ts.map +1 -0
  53. package/dist/__tests__/scopedContentContainer.test.js +74 -0
  54. package/dist/__tests__/scopedContentContainer.test.js.map +1 -0
  55. package/dist/__tests__/slideshowGenerator.test.js +1 -1
  56. package/dist/__tests__/slideshowGenerator.test.js.map +1 -1
  57. package/dist/__tests__/templateAnnotationParse.test.d.ts +2 -0
  58. package/dist/__tests__/templateAnnotationParse.test.d.ts.map +1 -0
  59. package/dist/__tests__/templateAnnotationParse.test.js +42 -0
  60. package/dist/__tests__/templateAnnotationParse.test.js.map +1 -0
  61. package/dist/__tests__/templates.test.js +90 -9
  62. package/dist/__tests__/templates.test.js.map +1 -1
  63. package/dist/__tests__/themeCompile.test.d.ts +2 -0
  64. package/dist/__tests__/themeCompile.test.d.ts.map +1 -0
  65. package/dist/__tests__/themeCompile.test.js +134 -0
  66. package/dist/__tests__/themeCompile.test.js.map +1 -0
  67. package/dist/__tests__/themeValidator.test.d.ts +2 -0
  68. package/dist/__tests__/themeValidator.test.d.ts.map +1 -0
  69. package/dist/__tests__/themeValidator.test.js +107 -0
  70. package/dist/__tests__/themeValidator.test.js.map +1 -0
  71. package/dist/__tests__/versions.test.d.ts +2 -0
  72. package/dist/__tests__/versions.test.d.ts.map +1 -0
  73. package/dist/__tests__/versions.test.js +339 -0
  74. package/dist/__tests__/versions.test.js.map +1 -0
  75. package/dist/{chunk-CYQHG27J.js → chunk-2TMAWB4Y.js} +74 -1
  76. package/dist/chunk-2TMAWB4Y.js.map +1 -0
  77. package/dist/{chunk-OASJKWO5.js → chunk-3K5OG6KI.js} +92 -7
  78. package/dist/chunk-3K5OG6KI.js.map +1 -0
  79. package/dist/chunk-57CVQCFX.js +193 -0
  80. package/dist/chunk-57CVQCFX.js.map +1 -0
  81. package/dist/{chunk-QM5PKNPW.js → chunk-5WFRKURX.js} +1 -1
  82. package/dist/chunk-5WFRKURX.js.map +1 -0
  83. package/dist/chunk-B4EEON3N.js +1317 -0
  84. package/dist/chunk-B4EEON3N.js.map +1 -0
  85. package/dist/chunk-DBS6RIFL.js +81 -0
  86. package/dist/chunk-DBS6RIFL.js.map +1 -0
  87. package/dist/{chunk-MIBRIRGJ.js → chunk-FR2RBTKO.js} +3 -3
  88. package/dist/{chunk-MIBRIRGJ.js.map → chunk-FR2RBTKO.js.map} +1 -1
  89. package/dist/chunk-H3AXU4MJ.js +179 -0
  90. package/dist/chunk-H3AXU4MJ.js.map +1 -0
  91. package/dist/chunk-JELIRVDP.js +24 -0
  92. package/dist/chunk-JELIRVDP.js.map +1 -0
  93. package/dist/chunk-K32VJONL.js +193 -0
  94. package/dist/chunk-K32VJONL.js.map +1 -0
  95. package/dist/{chunk-6BAVXFYC.js → chunk-KSWJR66U.js} +218 -42
  96. package/dist/chunk-KSWJR66U.js.map +1 -0
  97. package/dist/{chunk-TSG2OSE6.js → chunk-MYIH7FWD.js} +3 -3
  98. package/dist/chunk-MYIH7FWD.js.map +1 -0
  99. package/dist/chunk-VLJJHDAC.js +218 -0
  100. package/dist/chunk-VLJJHDAC.js.map +1 -0
  101. package/dist/chunk-WMOI7FN3.js +182 -0
  102. package/dist/chunk-WMOI7FN3.js.map +1 -0
  103. package/dist/chunk-ZTX4PFFT.js +520 -0
  104. package/dist/chunk-ZTX4PFFT.js.map +1 -0
  105. package/dist/doc/audioMapping.d.ts.map +1 -1
  106. package/dist/doc/audioMapping.js +42 -7
  107. package/dist/doc/audioMapping.js.map +1 -1
  108. package/dist/doc/getLayers.d.ts.map +1 -1
  109. package/dist/doc/getLayers.js +29 -18
  110. package/dist/doc/getLayers.js.map +1 -1
  111. package/dist/doc/markdownToDoc.d.ts.map +1 -1
  112. package/dist/doc/markdownToDoc.js +40 -6
  113. package/dist/doc/markdownToDoc.js.map +1 -1
  114. package/dist/doc/templates/featureBlock.d.ts +18 -0
  115. package/dist/doc/templates/featureBlock.d.ts.map +1 -0
  116. package/dist/doc/templates/featureBlock.js +180 -0
  117. package/dist/doc/templates/featureBlock.js.map +1 -0
  118. package/dist/doc/templates/fullBleedQuote.d.ts +12 -0
  119. package/dist/doc/templates/fullBleedQuote.d.ts.map +1 -1
  120. package/dist/doc/templates/fullBleedQuote.js +12 -0
  121. package/dist/doc/templates/fullBleedQuote.js.map +1 -1
  122. package/dist/doc/templates/index.d.ts +28 -4
  123. package/dist/doc/templates/index.d.ts.map +1 -1
  124. package/dist/doc/templates/index.js +45 -12
  125. package/dist/doc/templates/index.js.map +1 -1
  126. package/dist/doc/templates/listBlock.d.ts.map +1 -1
  127. package/dist/doc/templates/listBlock.js +22 -5
  128. package/dist/doc/templates/listBlock.js.map +1 -1
  129. package/dist/doc/templates/statHighlight.d.ts +12 -0
  130. package/dist/doc/templates/statHighlight.d.ts.map +1 -1
  131. package/dist/doc/templates/statHighlight.js +12 -0
  132. package/dist/doc/templates/statHighlight.js.map +1 -1
  133. package/dist/doc/templates/titleBlock.d.ts +11 -0
  134. package/dist/doc/templates/titleBlock.d.ts.map +1 -1
  135. package/dist/doc/templates/titleBlock.js +8 -0
  136. package/dist/doc/templates/titleBlock.js.map +1 -1
  137. package/dist/doc/utils/themeUtils.d.ts +4 -4
  138. package/dist/doc/utils/themeUtils.d.ts.map +1 -1
  139. package/dist/doc/utils/themeUtils.js +8 -7
  140. package/dist/doc/utils/themeUtils.js.map +1 -1
  141. package/dist/generate/slideshowGenerator.js +1 -1
  142. package/dist/generate/slideshowGenerator.js.map +1 -1
  143. package/dist/generate/templateMapper.js +2 -2
  144. package/dist/generate/templateMapper.js.map +1 -1
  145. package/dist/icons/iconData.d.ts +26 -0
  146. package/dist/icons/iconData.d.ts.map +1 -0
  147. package/dist/icons/iconData.js +12 -0
  148. package/dist/icons/iconData.js.map +1 -0
  149. package/dist/icons/index.d.ts +12 -0
  150. package/dist/icons/index.d.ts.map +1 -0
  151. package/dist/icons/index.js +10 -0
  152. package/dist/icons/index.js.map +1 -0
  153. package/dist/icons/resolve.d.ts +61 -0
  154. package/dist/icons/resolve.d.ts.map +1 -0
  155. package/dist/icons/resolve.js +130 -0
  156. package/dist/icons/resolve.js.map +1 -0
  157. package/dist/imageEdit/ImageEditVersionManager.d.ts +31 -0
  158. package/dist/imageEdit/ImageEditVersionManager.d.ts.map +1 -0
  159. package/dist/imageEdit/ImageEditVersionManager.js +39 -0
  160. package/dist/imageEdit/ImageEditVersionManager.js.map +1 -0
  161. package/dist/imageEdit/export.d.ts +33 -0
  162. package/dist/imageEdit/export.d.ts.map +1 -0
  163. package/dist/imageEdit/export.js +253 -0
  164. package/dist/imageEdit/export.js.map +1 -0
  165. package/dist/imageEdit/index.d.ts +14 -0
  166. package/dist/imageEdit/index.d.ts.map +1 -0
  167. package/dist/imageEdit/index.js +22 -0
  168. package/dist/imageEdit/index.js.map +1 -0
  169. package/dist/imageEdit/persistence.d.ts +15 -0
  170. package/dist/imageEdit/persistence.d.ts.map +1 -0
  171. package/dist/imageEdit/persistence.js +49 -0
  172. package/dist/imageEdit/persistence.js.map +1 -0
  173. package/dist/imageEdit/state.d.ts +42 -0
  174. package/dist/imageEdit/state.d.ts.map +1 -0
  175. package/dist/imageEdit/state.js +98 -0
  176. package/dist/imageEdit/state.js.map +1 -0
  177. package/dist/imageEdit/versionPaths.d.ts +27 -0
  178. package/dist/imageEdit/versionPaths.d.ts.map +1 -0
  179. package/dist/imageEdit/versionPaths.js +57 -0
  180. package/dist/imageEdit/versionPaths.js.map +1 -0
  181. package/dist/imageEdit/versions.d.ts +67 -0
  182. package/dist/imageEdit/versions.d.ts.map +1 -0
  183. package/dist/imageEdit/versions.js +161 -0
  184. package/dist/imageEdit/versions.js.map +1 -0
  185. package/dist/index.d.ts +4 -0
  186. package/dist/index.d.ts.map +1 -1
  187. package/dist/index.js +4 -0
  188. package/dist/index.js.map +1 -1
  189. package/dist/jsonForm/chooseControl.d.ts +20 -0
  190. package/dist/jsonForm/chooseControl.d.ts.map +1 -0
  191. package/dist/jsonForm/chooseControl.js +82 -0
  192. package/dist/jsonForm/chooseControl.js.map +1 -0
  193. package/dist/jsonForm/evaluateWhen.d.ts +12 -0
  194. package/dist/jsonForm/evaluateWhen.d.ts.map +1 -0
  195. package/dist/jsonForm/evaluateWhen.js +65 -0
  196. package/dist/jsonForm/evaluateWhen.js.map +1 -0
  197. package/dist/jsonForm/index.d.ts +14 -0
  198. package/dist/jsonForm/index.d.ts.map +1 -0
  199. package/dist/jsonForm/index.js +12 -0
  200. package/dist/jsonForm/index.js.map +1 -0
  201. package/dist/jsonForm/inferSchema.d.ts +12 -0
  202. package/dist/jsonForm/inferSchema.d.ts.map +1 -0
  203. package/dist/jsonForm/inferSchema.js +15 -0
  204. package/dist/jsonForm/inferSchema.js.map +1 -0
  205. package/dist/jsonForm/pointer.d.ts +26 -0
  206. package/dist/jsonForm/pointer.d.ts.map +1 -0
  207. package/dist/jsonForm/pointer.js +102 -0
  208. package/dist/jsonForm/pointer.js.map +1 -0
  209. package/dist/jsonForm/types.d.ts +120 -0
  210. package/dist/jsonForm/types.d.ts.map +1 -0
  211. package/dist/jsonForm/types.js +10 -0
  212. package/dist/jsonForm/types.js.map +1 -0
  213. package/dist/markdown/convert.d.ts +11 -1
  214. package/dist/markdown/convert.d.ts.map +1 -1
  215. package/dist/markdown/convert.js +135 -5
  216. package/dist/markdown/convert.js.map +1 -1
  217. package/dist/markdown/index.d.ts +1 -1
  218. package/dist/markdown/index.d.ts.map +1 -1
  219. package/dist/markdown/index.js +1 -1
  220. package/dist/markdown/index.js.map +1 -1
  221. package/dist/markdown/types.d.ts +17 -2
  222. package/dist/markdown/types.d.ts.map +1 -1
  223. package/dist/markdown/utils.d.ts +40 -0
  224. package/dist/markdown/utils.d.ts.map +1 -1
  225. package/dist/markdown/utils.js +155 -0
  226. package/dist/markdown/utils.js.map +1 -1
  227. package/dist/recommend/index.d.ts +16 -0
  228. package/dist/recommend/index.d.ts.map +1 -0
  229. package/dist/recommend/index.js +15 -0
  230. package/dist/recommend/index.js.map +1 -0
  231. package/dist/recommend/templates.d.ts +39 -0
  232. package/dist/recommend/templates.d.ts.map +1 -0
  233. package/dist/recommend/templates.js +235 -0
  234. package/dist/recommend/templates.js.map +1 -0
  235. package/dist/schemas/BlockTemplates.d.ts +46 -8
  236. package/dist/schemas/BlockTemplates.d.ts.map +1 -1
  237. package/dist/schemas/BlockTemplates.js +3 -3
  238. package/dist/schemas/BlockTemplates.js.map +1 -1
  239. package/dist/schemas/ImageEditDoc.d.ts +81 -0
  240. package/dist/schemas/ImageEditDoc.d.ts.map +1 -0
  241. package/dist/schemas/ImageEditDoc.js +20 -0
  242. package/dist/schemas/ImageEditDoc.js.map +1 -0
  243. package/dist/schemas/Theme.d.ts +70 -31
  244. package/dist/schemas/Theme.d.ts.map +1 -1
  245. package/dist/schemas/Theme.js +48 -16
  246. package/dist/schemas/Theme.js.map +1 -1
  247. package/dist/schemas/colorUtils.d.ts +51 -0
  248. package/dist/schemas/colorUtils.d.ts.map +1 -0
  249. package/dist/schemas/colorUtils.js +154 -0
  250. package/dist/schemas/colorUtils.js.map +1 -0
  251. package/dist/schemas/fontStacks.d.ts +57 -0
  252. package/dist/schemas/fontStacks.d.ts.map +1 -0
  253. package/dist/schemas/fontStacks.js +227 -0
  254. package/dist/schemas/fontStacks.js.map +1 -0
  255. package/dist/schemas/index.d.ts +5 -0
  256. package/dist/schemas/index.d.ts.map +1 -1
  257. package/dist/schemas/index.js +4 -0
  258. package/dist/schemas/index.js.map +1 -1
  259. package/dist/schemas/themeCompile.d.ts +45 -0
  260. package/dist/schemas/themeCompile.d.ts.map +1 -0
  261. package/dist/schemas/themeCompile.js +160 -0
  262. package/dist/schemas/themeCompile.js.map +1 -0
  263. package/dist/schemas/themeLibrary.d.ts +11 -15
  264. package/dist/schemas/themeLibrary.d.ts.map +1 -1
  265. package/dist/schemas/themeLibrary.js +18 -566
  266. package/dist/schemas/themeLibrary.js.map +1 -1
  267. package/dist/schemas/themeValidator.d.ts +26 -0
  268. package/dist/schemas/themeValidator.d.ts.map +1 -0
  269. package/dist/schemas/themeValidator.js +352 -0
  270. package/dist/schemas/themeValidator.js.map +1 -0
  271. package/dist/schemas/themes/bold.json +48 -0
  272. package/dist/schemas/themes/cinematic.json +54 -0
  273. package/dist/schemas/themes/documentary.json +49 -0
  274. package/dist/schemas/themes/gezellig.json +44 -0
  275. package/dist/schemas/themes/index.d.ts +26 -0
  276. package/dist/schemas/themes/index.d.ts.map +1 -0
  277. package/dist/schemas/themes/index.js +49 -0
  278. package/dist/schemas/themes/index.js.map +1 -0
  279. package/dist/schemas/themes/magazine.json +44 -0
  280. package/dist/schemas/themes/minimalist.json +45 -0
  281. package/dist/schemas/themes/morning-light.json +42 -0
  282. package/dist/schemas/themes/standard-dark.json +50 -0
  283. package/dist/schemas/themes/standard.json +42 -0
  284. package/dist/schemas/themes/tech-dark.json +48 -0
  285. package/dist/schemas/themes/warm-earth.json +50 -0
  286. package/dist/storage/ScopedContentContainer.d.ts +44 -0
  287. package/dist/storage/ScopedContentContainer.d.ts.map +1 -0
  288. package/dist/storage/ScopedContentContainer.js +94 -0
  289. package/dist/storage/ScopedContentContainer.js.map +1 -0
  290. package/dist/storage/index.d.ts +1 -0
  291. package/dist/storage/index.d.ts.map +1 -1
  292. package/dist/storage/index.js +1 -0
  293. package/dist/storage/index.js.map +1 -1
  294. package/dist/story/index.d.ts +53 -13
  295. package/dist/story/index.js +12 -4
  296. package/dist/themeLibrary-BHjJ0Qx6.d.ts +40 -0
  297. package/dist/{types-CkNttVSF.d.ts → types-BdZkdJ3z.d.ts} +17 -2
  298. package/dist/types-D98li1gV.d.ts +93 -0
  299. package/dist/versions/DocumentVersionManager.d.ts +31 -0
  300. package/dist/versions/DocumentVersionManager.d.ts.map +1 -0
  301. package/dist/versions/DocumentVersionManager.js +33 -0
  302. package/dist/versions/DocumentVersionManager.js.map +1 -0
  303. package/dist/versions/index.d.ts +29 -0
  304. package/dist/versions/index.d.ts.map +1 -0
  305. package/dist/versions/index.js +28 -0
  306. package/dist/versions/index.js.map +1 -0
  307. package/dist/versions/operations.d.ts +44 -0
  308. package/dist/versions/operations.d.ts.map +1 -0
  309. package/dist/versions/operations.js +190 -0
  310. package/dist/versions/operations.js.map +1 -0
  311. package/dist/versions/paths.d.ts +29 -0
  312. package/dist/versions/paths.d.ts.map +1 -0
  313. package/dist/versions/paths.js +66 -0
  314. package/dist/versions/paths.js.map +1 -0
  315. package/dist/versions/timestamp.d.ts +22 -0
  316. package/dist/versions/timestamp.d.ts.map +1 -0
  317. package/dist/versions/timestamp.js +44 -0
  318. package/dist/versions/timestamp.js.map +1 -0
  319. package/dist/versions/types.d.ts +92 -0
  320. package/dist/versions/types.d.ts.map +1 -0
  321. package/dist/versions/types.js +11 -0
  322. package/dist/versions/types.js.map +1 -0
  323. package/package.json +28 -1
  324. package/src/__tests__/colorUtils.test.ts +112 -0
  325. package/src/__tests__/getLayers.test.ts +32 -7
  326. package/src/__tests__/iconResolve.test.ts +122 -0
  327. package/src/__tests__/imageEditState.test.ts +74 -0
  328. package/src/__tests__/imageEditVersions.test.ts +169 -0
  329. package/src/__tests__/inferDocumentTitle.test.ts +50 -0
  330. package/src/__tests__/jsonForm.chooseControl.test.ts +86 -0
  331. package/src/__tests__/jsonForm.evaluateWhen.test.ts +59 -0
  332. package/src/__tests__/jsonForm.inferSchema.test.ts +25 -0
  333. package/src/__tests__/jsonForm.pointer.test.ts +103 -0
  334. package/src/__tests__/markdownIcons.test.ts +104 -0
  335. package/src/__tests__/markdownToDoc.test.ts +19 -4
  336. package/src/__tests__/recommendTemplates.test.ts +183 -0
  337. package/src/__tests__/scopedContentContainer.test.ts +89 -0
  338. package/src/__tests__/slideshowGenerator.test.ts +1 -1
  339. package/src/__tests__/templateAnnotationParse.test.ts +42 -0
  340. package/src/__tests__/templates.test.ts +93 -9
  341. package/src/__tests__/themeCompile.test.ts +156 -0
  342. package/src/__tests__/themeValidator.test.ts +125 -0
  343. package/src/__tests__/versions.test.ts +415 -0
  344. package/src/doc/audioMapping.ts +55 -11
  345. package/src/doc/getLayers.ts +32 -21
  346. package/src/doc/markdownToDoc.ts +45 -7
  347. package/src/doc/templates/featureBlock.ts +202 -0
  348. package/src/doc/templates/fullBleedQuote.ts +13 -0
  349. package/src/doc/templates/index.ts +50 -16
  350. package/src/doc/templates/listBlock.ts +24 -5
  351. package/src/doc/templates/statHighlight.ts +13 -0
  352. package/src/doc/templates/titleBlock.ts +9 -0
  353. package/src/doc/utils/themeUtils.ts +8 -7
  354. package/src/generate/slideshowGenerator.ts +1 -1
  355. package/src/generate/templateMapper.ts +2 -2
  356. package/src/icons/iconData.ts +31 -0
  357. package/src/icons/index.ts +18 -0
  358. package/src/icons/resolve.ts +139 -0
  359. package/src/imageEdit/ImageEditVersionManager.ts +73 -0
  360. package/src/imageEdit/export.ts +326 -0
  361. package/src/imageEdit/index.ts +71 -0
  362. package/src/imageEdit/persistence.ts +62 -0
  363. package/src/imageEdit/state.ts +112 -0
  364. package/src/imageEdit/versionPaths.ts +57 -0
  365. package/src/imageEdit/versions.ts +231 -0
  366. package/src/index.ts +4 -0
  367. package/src/jsonForm/chooseControl.ts +88 -0
  368. package/src/jsonForm/evaluateWhen.ts +59 -0
  369. package/src/jsonForm/index.ts +22 -0
  370. package/src/jsonForm/inferSchema.ts +25 -0
  371. package/src/jsonForm/pointer.ts +109 -0
  372. package/src/jsonForm/types.ts +167 -0
  373. package/src/markdown/convert.ts +145 -5
  374. package/src/markdown/index.ts +3 -0
  375. package/src/markdown/types.ts +19 -2
  376. package/src/markdown/utils.ts +159 -0
  377. package/src/recommend/index.ts +16 -0
  378. package/src/recommend/templates.ts +273 -0
  379. package/src/schemas/BlockTemplates.ts +50 -7
  380. package/src/schemas/ImageEditDoc.ts +95 -0
  381. package/src/schemas/Theme.ts +98 -31
  382. package/src/schemas/colorUtils.ts +186 -0
  383. package/src/schemas/fontStacks.ts +257 -0
  384. package/src/schemas/index.ts +12 -0
  385. package/src/schemas/themeCompile.ts +203 -0
  386. package/src/schemas/themeLibrary.ts +18 -597
  387. package/src/schemas/themeValidator.ts +388 -0
  388. package/src/schemas/themes/bold.json +48 -0
  389. package/src/schemas/themes/cinematic.json +54 -0
  390. package/src/schemas/themes/documentary.json +49 -0
  391. package/src/schemas/themes/gezellig.json +44 -0
  392. package/src/schemas/themes/index.ts +53 -0
  393. package/src/schemas/themes/magazine.json +44 -0
  394. package/src/schemas/themes/minimalist.json +45 -0
  395. package/src/schemas/themes/morning-light.json +42 -0
  396. package/src/schemas/themes/standard-dark.json +50 -0
  397. package/src/schemas/themes/standard.json +42 -0
  398. package/src/schemas/themes/tech-dark.json +48 -0
  399. package/src/schemas/themes/warm-earth.json +50 -0
  400. package/src/storage/ScopedContentContainer.ts +113 -0
  401. package/src/storage/index.ts +1 -0
  402. package/src/versions/DocumentVersionManager.ts +70 -0
  403. package/src/versions/index.ts +54 -0
  404. package/src/versions/operations.ts +230 -0
  405. package/src/versions/paths.ts +66 -0
  406. package/src/versions/timestamp.ts +48 -0
  407. package/src/versions/types.ts +91 -0
  408. package/dist/chunk-6BAVXFYC.js.map +0 -1
  409. package/dist/chunk-7GF6RDED.js +0 -82
  410. package/dist/chunk-7GF6RDED.js.map +0 -1
  411. package/dist/chunk-CYQHG27J.js.map +0 -1
  412. package/dist/chunk-F22AE6BY.js +0 -121
  413. package/dist/chunk-F22AE6BY.js.map +0 -1
  414. package/dist/chunk-FIQR4AJG.js +0 -517
  415. package/dist/chunk-FIQR4AJG.js.map +0 -1
  416. package/dist/chunk-OASJKWO5.js.map +0 -1
  417. package/dist/chunk-QM5PKNPW.js.map +0 -1
  418. package/dist/chunk-TSG2OSE6.js.map +0 -1
  419. package/dist/themeLibrary-CEo2D4b6.d.ts +0 -44
@@ -0,0 +1,109 @@
1
+ /**
2
+ * JSON Pointer helpers (RFC 6901) plus immutable get/set, plus a tiny
3
+ * dotted-path normalizer so hosts can write `user.role` instead of
4
+ * `/user/role`. `$ref` resolution is local-only (`#/$defs/Foo`).
5
+ */
6
+
7
+ import type { SquisqAnnotatedSchema } from './types.js';
8
+
9
+ /** Normalize a path to a JSON Pointer string. Accepts both forms. */
10
+ export function toPointer(path: string): string {
11
+ if (path === '' || path === '/') return '';
12
+ if (path.startsWith('/')) return path;
13
+ // Dotted form: split on `.` and escape per RFC 6901.
14
+ return (
15
+ '/' +
16
+ path
17
+ .split('.')
18
+ .map((seg) => seg.replace(/~/g, '~0').replace(/\//g, '~1'))
19
+ .join('/')
20
+ );
21
+ }
22
+
23
+ /** Split a JSON Pointer into decoded segments. Empty string → []. */
24
+ export function pointerSegments(pointer: string): string[] {
25
+ if (pointer === '') return [];
26
+ return pointer
27
+ .slice(1)
28
+ .split('/')
29
+ .map((seg) => seg.replace(/~1/g, '/').replace(/~0/g, '~'));
30
+ }
31
+
32
+ /** Read a value from data by path (dotted or pointer). Returns undefined if missing. */
33
+ export function getByPointer(data: unknown, path: string): unknown {
34
+ const segments = pointerSegments(toPointer(path));
35
+ let cur: unknown = data;
36
+ for (const seg of segments) {
37
+ if (cur === null || cur === undefined) return undefined;
38
+ if (Array.isArray(cur)) {
39
+ const idx = Number(seg);
40
+ if (!Number.isInteger(idx) || idx < 0 || idx >= cur.length) return undefined;
41
+ cur = cur[idx];
42
+ } else if (typeof cur === 'object') {
43
+ cur = (cur as Record<string, unknown>)[seg];
44
+ } else {
45
+ return undefined;
46
+ }
47
+ }
48
+ return cur;
49
+ }
50
+
51
+ /**
52
+ * Return a structurally-shared copy of `data` with the value at `path`
53
+ * replaced. Intermediate objects/arrays are cloned along the path; the
54
+ * rest is shared by reference. Creates intermediate objects if missing.
55
+ */
56
+ export function setByPointer<T>(data: T, path: string, value: unknown): T {
57
+ const segments = pointerSegments(toPointer(path));
58
+ if (segments.length === 0) return value as T;
59
+ return setRec(data, segments, 0, value) as T;
60
+ }
61
+
62
+ function setRec(node: unknown, segments: string[], i: number, value: unknown): unknown {
63
+ const seg = segments[i];
64
+ const isLast = i === segments.length - 1;
65
+
66
+ // Decide whether the slot we're about to write into is an array index.
67
+ const nextSeg = isLast ? undefined : segments[i + 1];
68
+ const writingArrayChild = nextSeg !== undefined && /^\d+$/.test(nextSeg);
69
+
70
+ if (Array.isArray(node)) {
71
+ const idx = Number(seg);
72
+ const next = node.slice();
73
+ next[idx] = isLast
74
+ ? value
75
+ : setRec(node[idx] ?? (writingArrayChild ? [] : {}), segments, i + 1, value);
76
+ return next;
77
+ }
78
+
79
+ const obj = (node && typeof node === 'object' ? { ...(node as object) } : {}) as Record<
80
+ string,
81
+ unknown
82
+ >;
83
+ obj[seg] = isLast
84
+ ? value
85
+ : setRec(obj[seg] ?? (writingArrayChild ? [] : {}), segments, i + 1, value);
86
+ return obj;
87
+ }
88
+
89
+ /**
90
+ * Resolve a local `$ref` (e.g. `#/$defs/Foo`) against the root schema.
91
+ * Returns the original schema if no `$ref` is present, or `undefined`
92
+ * if the reference cannot be resolved. Cross-document refs are not
93
+ * supported.
94
+ */
95
+ export function resolveRef(
96
+ schema: SquisqAnnotatedSchema,
97
+ root: SquisqAnnotatedSchema,
98
+ ): SquisqAnnotatedSchema | undefined {
99
+ if (!schema.$ref) return schema;
100
+ const ref = schema.$ref;
101
+ if (!ref.startsWith('#/')) return undefined;
102
+ const segments = pointerSegments(ref.slice(1));
103
+ let cur: unknown = root;
104
+ for (const seg of segments) {
105
+ if (cur === null || cur === undefined || typeof cur !== 'object') return undefined;
106
+ cur = (cur as Record<string, unknown>)[seg];
107
+ }
108
+ return (cur as SquisqAnnotatedSchema | undefined) ?? undefined;
109
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * JSON Form types — Squisq's hint vocabulary layered on JSON Schema.
3
+ *
4
+ * Hints live inline on a JSON Schema under a single `squisq` key. The
5
+ * schema remains a valid JSON Schema; the extra key is ignored by
6
+ * standard validators. Renderers consult the hints to pick a control
7
+ * and tune its presentation.
8
+ */
9
+
10
+ /**
11
+ * Every concrete control rendered by `<JsonView>` and `<JsonEditor>`.
12
+ *
13
+ * Auto-defaults pick one based on schema shape; hosts override via
14
+ * `squisq.control` when they want something specific (e.g. radio over
15
+ * segmented for a long, descriptive enum).
16
+ */
17
+ export type ControlKind =
18
+ | 'text'
19
+ | 'multiline'
20
+ | 'richtext'
21
+ | 'color'
22
+ | 'date'
23
+ | 'time'
24
+ | 'datetime'
25
+ | 'slider'
26
+ | 'stepper'
27
+ | 'segmented'
28
+ | 'radio'
29
+ | 'combobox'
30
+ | 'toggle'
31
+ | 'checkbox'
32
+ | 'card'
33
+ | 'card-stack'
34
+ | 'chip-bin'
35
+ | 'tabs'
36
+ | 'group';
37
+
38
+ /**
39
+ * Conditional rule: looked up by field path against the root data and
40
+ * compared with one of the operators. The first defined operator wins
41
+ * (in declaration order: equals, oneOf, matches, truthy).
42
+ */
43
+ export interface SquisqWhen {
44
+ /**
45
+ * Path to the controlling field, relative to the root data.
46
+ * Accepts dotted-path (`user.role`) or JSON Pointer (`/user/role`).
47
+ */
48
+ field: string;
49
+ equals?: unknown;
50
+ oneOf?: readonly unknown[];
51
+ /** ECMAScript regex source; matched against the string value. */
52
+ matches?: string;
53
+ /** When true, condition holds if value is truthy; when false, if falsy. */
54
+ truthy?: boolean;
55
+ }
56
+
57
+ /**
58
+ * Hints for a single schema node. Attach via `squisq` on any JSON
59
+ * Schema object — properties, items, the root, or sub-schemas inside
60
+ * `oneOf`/`anyOf`.
61
+ */
62
+ export interface SquisqHints {
63
+ /** Force a specific control. If omitted, picked from type/format/enum cardinality. */
64
+ control?: ControlKind;
65
+
66
+ /** Override schema `title` for display. */
67
+ label?: string;
68
+ /** Override schema `description` for the inline helper text. */
69
+ help?: string;
70
+ /** Placeholder text for empty fields. */
71
+ placeholder?: string;
72
+
73
+ /** Suggested layout sizing in a parent grid. Renderers may ignore on narrow viewports. */
74
+ width?: 'full' | 'half' | 'third' | 'auto';
75
+
76
+ /** Hide the field — literal, or a `SquisqWhen` evaluated against root data. */
77
+ hidden?: boolean | SquisqWhen;
78
+ /** Disable the field — literal, or a `SquisqWhen` evaluated against root data. */
79
+ disabled?: boolean | SquisqWhen;
80
+
81
+ /** Override schema's required-ness for the UI only. */
82
+ required?: boolean;
83
+
84
+ /** For arrays of objects: how to label each card in a `card-stack`. */
85
+ itemLabel?: string | { fromField: string };
86
+ /** Label for the "+ Add" button on arrays. */
87
+ addLabel?: string;
88
+ /** Label for the remove button on arrays. */
89
+ removeLabel?: string;
90
+
91
+ /** Slider/stepper visual step. Falls back to schema `multipleOf`. */
92
+ step?: number;
93
+
94
+ /** Optional human-readable labels for enum values. */
95
+ enumLabels?: Record<string, string>;
96
+ }
97
+
98
+ /**
99
+ * The subset of JSON Schema we understand, with an optional `squisq`
100
+ * key. We deliberately keep this as a structural type so any JSON
101
+ * Schema authored elsewhere can be passed in without conversion.
102
+ */
103
+ export interface SquisqAnnotatedSchema {
104
+ type?: string | readonly string[];
105
+ format?: string;
106
+ title?: string;
107
+ description?: string;
108
+ default?: unknown;
109
+ examples?: readonly unknown[];
110
+
111
+ enum?: readonly unknown[];
112
+ const?: unknown;
113
+
114
+ // Object
115
+ properties?: Record<string, SquisqAnnotatedSchema>;
116
+ required?: readonly string[];
117
+ additionalProperties?: boolean | SquisqAnnotatedSchema;
118
+
119
+ // Array
120
+ items?: SquisqAnnotatedSchema | readonly SquisqAnnotatedSchema[];
121
+ minItems?: number;
122
+ maxItems?: number;
123
+
124
+ // Number
125
+ minimum?: number;
126
+ maximum?: number;
127
+ exclusiveMinimum?: number;
128
+ exclusiveMaximum?: number;
129
+ multipleOf?: number;
130
+
131
+ // String
132
+ minLength?: number;
133
+ maxLength?: number;
134
+ pattern?: string;
135
+
136
+ // Combinators
137
+ oneOf?: readonly SquisqAnnotatedSchema[];
138
+ anyOf?: readonly SquisqAnnotatedSchema[];
139
+ allOf?: readonly SquisqAnnotatedSchema[];
140
+
141
+ // References
142
+ $ref?: string;
143
+ $defs?: Record<string, SquisqAnnotatedSchema>;
144
+ definitions?: Record<string, SquisqAnnotatedSchema>;
145
+
146
+ // Squisq-native UI hints
147
+ squisq?: SquisqHints;
148
+ }
149
+
150
+ /**
151
+ * A validation error reported by the consumer-supplied `validate` prop.
152
+ * Matches the shape of common validators (ajv, etc.) without depending
153
+ * on any specific one.
154
+ */
155
+ export interface JsonFormValidationError {
156
+ /** JSON Pointer to the failing value, e.g. `/sections/0/heading`. */
157
+ path: string;
158
+ /** Human-readable message. */
159
+ message: string;
160
+ /** Optional schema keyword that failed (e.g. "required", "minimum"). */
161
+ keyword?: string;
162
+ }
163
+
164
+ export type JsonFormValidator = (
165
+ value: unknown,
166
+ schema: SquisqAnnotatedSchema,
167
+ ) => readonly JsonFormValidationError[];
@@ -20,8 +20,10 @@ import type {
20
20
  MarkdownSourcePosition,
21
21
  HeadingTemplateAnnotation,
22
22
  MarkdownHeading,
23
+ MarkdownInlineIcon,
23
24
  } from './types.js';
24
25
  import { parseHtmlToNodes } from './htmlParse.js';
26
+ import { resolveIcon } from '../icons/resolve.js';
25
27
 
26
28
  // ============================================
27
29
  // Generic mdast node shape
@@ -132,8 +134,13 @@ function extractText(node: MdastNode): string {
132
134
  /**
133
135
  * Regex matching a trailing `{[templateName key=value …]}` annotation.
134
136
  * Captures the content between `{[` and `]}`.
137
+ *
138
+ * The trailing `[\s\]\}]*` tolerates accidental doubled `]}` that users
139
+ * sometimes type when learning the syntax (e.g. `{[foo]}]}`). It does
140
+ * not allow trailing word characters, so a non-trailing annotation like
141
+ * `## The {[chart]} section` still doesn't match.
135
142
  */
136
- const TEMPLATE_ANNOTATION_RE = /\s*\{\[([^\]]+)\]\}\s*$/;
143
+ const TEMPLATE_ANNOTATION_RE = /\s*\{\[([^\]]+)\]\}[\s\]}]*$/;
137
144
 
138
145
  /**
139
146
  * Extract a `{[templateName key=value …]}` annotation from a heading's
@@ -247,13 +254,130 @@ function convertBlockChildren(children: MdastNode[], parseHtml: boolean): Markdo
247
254
  return result;
248
255
  }
249
256
 
250
- function convertInlineChildren(children: MdastNode[], parseHtml: boolean): MarkdownInlineNode[] {
257
+ function convertInlineChildren(
258
+ children: MdastNode[],
259
+ parseHtml: boolean,
260
+ /** When false, skip the inline-icon split. Headings opt out so the
261
+ * `extractTemplateAnnotation` pass still sees the trailing
262
+ * `{[…]}` in raw text form; the caller runs `splitInlineIcons`
263
+ * itself afterward on the remaining children. */
264
+ applyIconSplit: boolean = true,
265
+ ): MarkdownInlineNode[] {
251
266
  const result: MarkdownInlineNode[] = [];
252
267
  for (const child of children) {
253
268
  const node = convertInlineNode(child, parseHtml);
254
269
  if (node) result.push(node);
255
270
  }
256
- return coalesceMentions(result);
271
+ const mentioned = coalesceMentions(result);
272
+ return applyIconSplit ? splitInlineIcons(mentioned) : mentioned;
273
+ }
274
+
275
+ /**
276
+ * Walk an inline-children list and split each text node on `{[token]}`
277
+ * occurrences whose tokens resolve to known FontAwesome icons. Returns
278
+ * a new array — unresolved tokens are left as literal text so authors
279
+ * can write `{[notathing]}` verbatim if they really mean to.
280
+ *
281
+ * Exposed for the heading code path, which first strips the trailing
282
+ * template annotation and then runs this on what remains.
283
+ */
284
+ export function splitInlineIcons(nodes: MarkdownInlineNode[]): MarkdownInlineNode[] {
285
+ // First pass: reassemble `text + textDirective + text` patterns
286
+ // formed when a qualified token like `{[fa-solid:user]}` was split
287
+ // by remark-directive's colon recognition. We collapse those triples
288
+ // back into a single text node containing the original token so the
289
+ // regex below can match.
290
+ const recombined = recombineDirectiveTokens(nodes);
291
+
292
+ const out: MarkdownInlineNode[] = [];
293
+ for (const node of recombined) {
294
+ if (node.type !== 'text') {
295
+ out.push(node);
296
+ continue;
297
+ }
298
+ out.push(...splitTextOnIcons(node.value, node.position));
299
+ }
300
+ return out;
301
+ }
302
+
303
+ /**
304
+ * When the user types `{[fa-solid:user]}`, remark's directive plugin
305
+ * sees `:user` and emits a `textDirective` node. The result is a
306
+ * three-node sequence `text("…{[fa-solid") + textDirective("user") +
307
+ * text("]}…")`. This helper detects that shape and stitches the three
308
+ * back into one text node so downstream icon detection works on the
309
+ * authored token.
310
+ */
311
+ function recombineDirectiveTokens(nodes: MarkdownInlineNode[]): MarkdownInlineNode[] {
312
+ const out: MarkdownInlineNode[] = [];
313
+ for (let i = 0; i < nodes.length; i++) {
314
+ const cur = nodes[i];
315
+ const next = nodes[i + 1];
316
+ const after = nodes[i + 2];
317
+ if (
318
+ cur?.type === 'text' &&
319
+ next?.type === 'textDirective' &&
320
+ after?.type === 'text' &&
321
+ /\{\[[a-zA-Z0-9_-]+$/.test(cur.value) &&
322
+ /^\]\}/.test(after.value)
323
+ ) {
324
+ const directive = next as unknown as { name?: string };
325
+ const directiveName = directive.name ?? '';
326
+ const merged: MarkdownInlineNode = {
327
+ type: 'text',
328
+ value: cur.value + ':' + directiveName + after.value,
329
+ ...(cur.position ? { position: cur.position } : {}),
330
+ };
331
+ out.push(merged);
332
+ i += 2;
333
+ continue;
334
+ }
335
+ out.push(cur);
336
+ }
337
+ return out;
338
+ }
339
+
340
+ const ICON_TOKEN_RE = /\{\[([a-zA-Z0-9_:-]+)\]\}/g;
341
+
342
+ function splitTextOnIcons(value: string, position?: MarkdownSourcePosition): MarkdownInlineNode[] {
343
+ if (!value) return [];
344
+ ICON_TOKEN_RE.lastIndex = 0;
345
+ let lastIndex = 0;
346
+ let match: RegExpExecArray | null;
347
+ const out: MarkdownInlineNode[] = [];
348
+ while ((match = ICON_TOKEN_RE.exec(value)) !== null) {
349
+ const token = match[1];
350
+ const icon = resolveIcon(token);
351
+ if (!icon) continue; // leave the literal `{[…]}` in place
352
+ if (match.index > lastIndex) {
353
+ out.push({
354
+ type: 'text',
355
+ value: value.slice(lastIndex, match.index),
356
+ ...(position ? { position } : {}),
357
+ });
358
+ }
359
+ const iconNode: MarkdownInlineIcon = {
360
+ type: 'inlineIcon',
361
+ token,
362
+ family: icon.family,
363
+ name: icon.name,
364
+ ...(position ? { position } : {}),
365
+ };
366
+ out.push(iconNode);
367
+ lastIndex = match.index + match[0].length;
368
+ }
369
+ if (lastIndex === 0) {
370
+ // No matches — return the original text node unchanged.
371
+ return [{ type: 'text', value, ...(position ? { position } : {}) }];
372
+ }
373
+ if (lastIndex < value.length) {
374
+ out.push({
375
+ type: 'text',
376
+ value: value.slice(lastIndex),
377
+ ...(position ? { position } : {}),
378
+ });
379
+ }
380
+ return out;
257
381
  }
258
382
 
259
383
  /**
@@ -318,12 +442,18 @@ function convertBlockNode(node: MdastNode, parseHtml: boolean): MarkdownBlockNod
318
442
 
319
443
  switch (node.type) {
320
444
  case 'heading': {
321
- const headingChildren = convertInlineChildren(node.children ?? [], parseHtml);
445
+ // Defer icon splitting so `extractTemplateAnnotation` sees the
446
+ // trailing `{[name]}` as plain text and can decide whether it's
447
+ // a template annotation. Anything that's NOT the template still
448
+ // needs to become an icon node, so we run `splitInlineIcons` on
449
+ // what's left after the template extraction.
450
+ const headingChildren = convertInlineChildren(node.children ?? [], parseHtml, false);
322
451
  const annotation = extractTemplateAnnotation(headingChildren);
452
+ const finalChildren = splitInlineIcons(headingChildren);
323
453
  const result: MarkdownHeading = {
324
454
  type: 'heading',
325
455
  depth: (node.depth ?? 1) as 1 | 2 | 3 | 4 | 5 | 6,
326
- children: headingChildren,
456
+ children: finalChildren,
327
457
  ...posField(pos),
328
458
  };
329
459
  if (annotation) {
@@ -967,6 +1097,16 @@ function inlineToMdast(node: MarkdownInlineNode): MdastNode {
967
1097
  ...mdastPosField(node.position),
968
1098
  };
969
1099
 
1100
+ case 'inlineIcon':
1101
+ // Serialize back to the authored token so source round-trips
1102
+ // byte-stable. The token already carries the qualified form when
1103
+ // required (the parser preserved it verbatim).
1104
+ return {
1105
+ type: 'text',
1106
+ value: `{[${node.token}]}`,
1107
+ ...mdastPosField(node.position),
1108
+ };
1109
+
970
1110
  default:
971
1111
  return { type: 'text', value: '' };
972
1112
  }
@@ -108,4 +108,7 @@ export {
108
108
  countNodes,
109
109
  createDocument,
110
110
  parseFrontmatter,
111
+ setFrontmatterValues,
112
+ inferDocumentTitle,
113
+ readFrontmatterThemeId,
111
114
  } from './utils.js';
@@ -453,6 +453,22 @@ export interface MarkdownInlineMath extends MarkdownNodeBase {
453
453
  value: string;
454
454
  }
455
455
 
456
+ /**
457
+ * Inline FontAwesome icon. Authored as `{[name]}` (bare) or
458
+ * `{[fa-<family>:name]}` (qualified) in markdown source. The parser
459
+ * resolves the token against the FA Free catalog and emits this node;
460
+ * unknown / ambiguous tokens stay as literal text.
461
+ */
462
+ export interface MarkdownInlineIcon extends MarkdownNodeBase {
463
+ type: 'inlineIcon';
464
+ /** Token exactly as authored — e.g. `github` or `fa-solid:user`. */
465
+ token: string;
466
+ /** Resolved icon family. */
467
+ family: 'brands' | 'solid' | 'regular';
468
+ /** Resolved icon name (without the `fa-` prefix). */
469
+ name: string;
470
+ }
471
+
456
472
  /**
457
473
  * A footnote reference [^id]. GFM extension.
458
474
  */
@@ -518,7 +534,7 @@ export interface MarkdownTextDirective extends MarkdownNodeBase {
518
534
  */
519
535
  export interface MarkdownMention extends MarkdownNodeBase {
520
536
  type: 'mention';
521
- /** Namespace for the mentioned entity (e.g. `"gezel"`, `"user"`). */
537
+ /** Namespace for the mentioned entity (e.g. `"user"`). */
522
538
  targetKind: string;
523
539
  /** Stable identifier within the namespace. */
524
540
  targetId: string;
@@ -563,7 +579,8 @@ export type MarkdownInlineNode =
563
579
  | MarkdownLinkReference
564
580
  | MarkdownImageReference
565
581
  | MarkdownTextDirective
566
- | MarkdownMention;
582
+ | MarkdownMention
583
+ | MarkdownInlineIcon;
567
584
 
568
585
  /**
569
586
  * Any node in the markdown tree. Includes structural nodes (listItem,
@@ -157,6 +157,109 @@ export function parseFrontmatter(yaml: string): Record<string, unknown> | null {
157
157
  return Object.keys(result).length > 0 ? result : null;
158
158
  }
159
159
 
160
+ const FRONTMATTER_BLOCK_RE = /^---\r?\n([\s\S]*?)\r?\n---(\r?\n)?/;
161
+
162
+ /** Quote a frontmatter scalar so it round-trips cleanly through `parseFrontmatter`. */
163
+ function formatFrontmatterValue(value: string | number | boolean): string {
164
+ if (typeof value === 'boolean' || typeof value === 'number') return String(value);
165
+ // Quote when needed: leading/trailing whitespace, leading punctuation that
166
+ // could trigger YAML modes, or values that look like reserved literals.
167
+ const needsQuote =
168
+ /^\s|\s$/.test(value) ||
169
+ /^[!&*?|>%@`]/.test(value) ||
170
+ /[:#]/.test(value) ||
171
+ value === '' ||
172
+ value === 'true' ||
173
+ value === 'false' ||
174
+ /^-?\d+(\.\d+)?$/.test(value);
175
+ if (!needsQuote) return value;
176
+ // Prefer double quotes; escape any embedded double-quote / backslash.
177
+ const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
178
+ return `"${escaped}"`;
179
+ }
180
+
181
+ /**
182
+ * Update a markdown source string's YAML frontmatter block, applying the
183
+ * given key/value updates. A `null` or `undefined` value removes the key.
184
+ * If no frontmatter block exists and any non-null update is supplied, a
185
+ * new block is prepended; if every update is a removal and no block
186
+ * exists, the source is returned unchanged.
187
+ *
188
+ * Existing key order is preserved; new keys are appended in the order
189
+ * they appear in `updates`. The simple parser used here handles plain
190
+ * `key: value` pairs only — comments, multi-line scalars, and nested
191
+ * structures are left untouched (we only rewrite lines whose key matches
192
+ * one in `updates`).
193
+ *
194
+ * @param source - The markdown source string.
195
+ * @param updates - Map of key → new value (or null/undefined to remove).
196
+ * @returns The updated markdown source string.
197
+ */
198
+ export function setFrontmatterValues(
199
+ source: string,
200
+ updates: Record<string, string | number | boolean | null | undefined>,
201
+ ): string {
202
+ const updateKeys = Object.keys(updates);
203
+ if (updateKeys.length === 0) return source;
204
+
205
+ const match = source.match(FRONTMATTER_BLOCK_RE);
206
+ const handled = new Set<string>();
207
+
208
+ // Existing frontmatter block — rewrite line-by-line, preserving order.
209
+ if (match) {
210
+ const inner = match[1];
211
+ const lines = inner.split(/\r?\n/);
212
+ const newLines: string[] = [];
213
+
214
+ for (const line of lines) {
215
+ const trimmed = line.trim();
216
+ if (!trimmed || trimmed.startsWith('#')) {
217
+ newLines.push(line);
218
+ continue;
219
+ }
220
+ const colonIdx = trimmed.indexOf(':');
221
+ if (colonIdx < 1) {
222
+ newLines.push(line);
223
+ continue;
224
+ }
225
+ const key = trimmed.slice(0, colonIdx).trim();
226
+ if (!Object.prototype.hasOwnProperty.call(updates, key)) {
227
+ newLines.push(line);
228
+ continue;
229
+ }
230
+ handled.add(key);
231
+ const next = updates[key];
232
+ if (next === null || next === undefined) continue; // remove
233
+ newLines.push(`${key}: ${formatFrontmatterValue(next)}`);
234
+ }
235
+
236
+ // Append any keys that weren't already present.
237
+ for (const key of updateKeys) {
238
+ if (handled.has(key)) continue;
239
+ const next = updates[key];
240
+ if (next === null || next === undefined) continue;
241
+ newLines.push(`${key}: ${formatFrontmatterValue(next)}`);
242
+ }
243
+
244
+ // If every line got removed, drop the block entirely.
245
+ const nonEmpty = newLines.filter((l) => l.trim().length > 0);
246
+ const rest = source.slice(match[0].length);
247
+ if (nonEmpty.length === 0) return rest;
248
+ return `---\n${newLines.join('\n')}\n---\n${rest}`;
249
+ }
250
+
251
+ // No existing block — only create one if there's at least one non-null
252
+ // value to write.
253
+ const fresh: string[] = [];
254
+ for (const key of updateKeys) {
255
+ const next = updates[key];
256
+ if (next === null || next === undefined) continue;
257
+ fresh.push(`${key}: ${formatFrontmatterValue(next)}`);
258
+ }
259
+ if (fresh.length === 0) return source;
260
+ return `---\n${fresh.join('\n')}\n---\n${source}`;
261
+ }
262
+
160
263
  /**
161
264
  * Create a minimal MarkdownDocument from a list of block nodes.
162
265
  * Convenience function for programmatic document construction.
@@ -167,3 +270,59 @@ export function createDocument(...children: MarkdownDocument['children']): Markd
167
270
  children,
168
271
  };
169
272
  }
273
+
274
+ /**
275
+ * Infer a human-readable title for a markdown document.
276
+ *
277
+ * Resolution order:
278
+ * 1. `title:` value in YAML frontmatter (when present and non-empty)
279
+ * 2. Text of the first heading found, walking top-level depth (H1, then
280
+ * H2, etc.) — the shallowest heading wins regardless of source order
281
+ *
282
+ * Returns `undefined` when no usable title is found.
283
+ */
284
+ export function inferDocumentTitle(doc: MarkdownDocument): string | undefined {
285
+ const fmTitle = doc.frontmatter?.title;
286
+ if (typeof fmTitle === 'string') {
287
+ const trimmed = fmTitle.trim();
288
+ if (trimmed) return trimmed;
289
+ }
290
+
291
+ let best: { depth: number; text: string } | null = null;
292
+ for (const node of doc.children) {
293
+ if (node.type !== 'heading') continue;
294
+ const text = extractPlainText(node).trim();
295
+ if (!text) continue;
296
+ if (best === null || node.depth < best.depth) {
297
+ best = { depth: node.depth, text };
298
+ if (best.depth === 1) break;
299
+ }
300
+ }
301
+
302
+ return best?.text;
303
+ }
304
+
305
+ /**
306
+ * Read a theme id from a markdown document's frontmatter.
307
+ *
308
+ * Checks the editor's canonical `squisq-theme` key first, then the
309
+ * shorter legacy aliases (`themeId`, `theme`). Returns `undefined`
310
+ * when none of them carry a non-empty string. Centralizing the lookup
311
+ * keeps the export pipelines (DOCX, PPTX, HTML, plain-HTML bundle) in
312
+ * sync with whatever the editor writes — adding a new frontmatter
313
+ * spelling later is a one-file change.
314
+ */
315
+ export function readFrontmatterThemeId(
316
+ frontmatter: Record<string, unknown> | undefined,
317
+ ): string | undefined {
318
+ if (!frontmatter) return undefined;
319
+ const keys = ['squisq-theme', 'themeId', 'theme'] as const;
320
+ for (const key of keys) {
321
+ const raw = frontmatter[key];
322
+ if (typeof raw === 'string') {
323
+ const trimmed = raw.trim();
324
+ if (trimmed) return trimmed;
325
+ }
326
+ }
327
+ return undefined;
328
+ }