@bendyline/squisq 1.2.2 → 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 (427) hide show
  1. package/dist/{Doc-BfBhiCNr.d.ts → Doc-BeSeH2iX.d.ts} +141 -33
  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__/markdown.test.js +40 -0
  42. package/dist/__tests__/markdown.test.js.map +1 -1
  43. package/dist/__tests__/markdownIcons.test.d.ts +2 -0
  44. package/dist/__tests__/markdownIcons.test.d.ts.map +1 -0
  45. package/dist/__tests__/markdownIcons.test.js +93 -0
  46. package/dist/__tests__/markdownIcons.test.js.map +1 -0
  47. package/dist/__tests__/markdownToDoc.test.js +15 -4
  48. package/dist/__tests__/markdownToDoc.test.js.map +1 -1
  49. package/dist/__tests__/recommendTemplates.test.d.ts +2 -0
  50. package/dist/__tests__/recommendTemplates.test.d.ts.map +1 -0
  51. package/dist/__tests__/recommendTemplates.test.js +158 -0
  52. package/dist/__tests__/recommendTemplates.test.js.map +1 -0
  53. package/dist/__tests__/scopedContentContainer.test.d.ts +2 -0
  54. package/dist/__tests__/scopedContentContainer.test.d.ts.map +1 -0
  55. package/dist/__tests__/scopedContentContainer.test.js +74 -0
  56. package/dist/__tests__/scopedContentContainer.test.js.map +1 -0
  57. package/dist/__tests__/slideshowGenerator.test.js +1 -1
  58. package/dist/__tests__/slideshowGenerator.test.js.map +1 -1
  59. package/dist/__tests__/surface.test.d.ts +2 -0
  60. package/dist/__tests__/surface.test.d.ts.map +1 -0
  61. package/dist/__tests__/surface.test.js +57 -0
  62. package/dist/__tests__/surface.test.js.map +1 -0
  63. package/dist/__tests__/templateAnnotationParse.test.d.ts +2 -0
  64. package/dist/__tests__/templateAnnotationParse.test.d.ts.map +1 -0
  65. package/dist/__tests__/templateAnnotationParse.test.js +42 -0
  66. package/dist/__tests__/templateAnnotationParse.test.js.map +1 -0
  67. package/dist/__tests__/templates.test.js +90 -9
  68. package/dist/__tests__/templates.test.js.map +1 -1
  69. package/dist/__tests__/themeCompile.test.d.ts +2 -0
  70. package/dist/__tests__/themeCompile.test.d.ts.map +1 -0
  71. package/dist/__tests__/themeCompile.test.js +134 -0
  72. package/dist/__tests__/themeCompile.test.js.map +1 -0
  73. package/dist/__tests__/themeValidator.test.d.ts +2 -0
  74. package/dist/__tests__/themeValidator.test.d.ts.map +1 -0
  75. package/dist/__tests__/themeValidator.test.js +107 -0
  76. package/dist/__tests__/themeValidator.test.js.map +1 -0
  77. package/dist/__tests__/versions.test.d.ts +2 -0
  78. package/dist/__tests__/versions.test.d.ts.map +1 -0
  79. package/dist/__tests__/versions.test.js +339 -0
  80. package/dist/__tests__/versions.test.js.map +1 -0
  81. package/dist/{chunk-CYQHG27J.js → chunk-2TMAWB4Y.js} +74 -1
  82. package/dist/chunk-2TMAWB4Y.js.map +1 -0
  83. package/dist/{chunk-LAZELBON.js → chunk-3K5OG6KI.js} +165 -17
  84. package/dist/chunk-3K5OG6KI.js.map +1 -0
  85. package/dist/chunk-57CVQCFX.js +193 -0
  86. package/dist/chunk-57CVQCFX.js.map +1 -0
  87. package/dist/{chunk-QM5PKNPW.js → chunk-5WFRKURX.js} +1 -1
  88. package/dist/chunk-5WFRKURX.js.map +1 -0
  89. package/dist/chunk-B4EEON3N.js +1317 -0
  90. package/dist/chunk-B4EEON3N.js.map +1 -0
  91. package/dist/chunk-DBS6RIFL.js +81 -0
  92. package/dist/chunk-DBS6RIFL.js.map +1 -0
  93. package/dist/{chunk-MIBRIRGJ.js → chunk-FR2RBTKO.js} +3 -3
  94. package/dist/{chunk-MIBRIRGJ.js.map → chunk-FR2RBTKO.js.map} +1 -1
  95. package/dist/chunk-H3AXU4MJ.js +179 -0
  96. package/dist/chunk-H3AXU4MJ.js.map +1 -0
  97. package/dist/chunk-JELIRVDP.js +24 -0
  98. package/dist/chunk-JELIRVDP.js.map +1 -0
  99. package/dist/chunk-K32VJONL.js +193 -0
  100. package/dist/chunk-K32VJONL.js.map +1 -0
  101. package/dist/{chunk-GAQLMORN.js → chunk-KSWJR66U.js} +248 -44
  102. package/dist/chunk-KSWJR66U.js.map +1 -0
  103. package/dist/{chunk-TSG2OSE6.js → chunk-MYIH7FWD.js} +3 -3
  104. package/dist/chunk-MYIH7FWD.js.map +1 -0
  105. package/dist/chunk-VLJJHDAC.js +218 -0
  106. package/dist/chunk-VLJJHDAC.js.map +1 -0
  107. package/dist/chunk-WMOI7FN3.js +182 -0
  108. package/dist/chunk-WMOI7FN3.js.map +1 -0
  109. package/dist/chunk-ZTX4PFFT.js +520 -0
  110. package/dist/chunk-ZTX4PFFT.js.map +1 -0
  111. package/dist/doc/audioMapping.d.ts.map +1 -1
  112. package/dist/doc/audioMapping.js +80 -9
  113. package/dist/doc/audioMapping.js.map +1 -1
  114. package/dist/doc/getLayers.d.ts.map +1 -1
  115. package/dist/doc/getLayers.js +29 -18
  116. package/dist/doc/getLayers.js.map +1 -1
  117. package/dist/doc/markdownToDoc.d.ts.map +1 -1
  118. package/dist/doc/markdownToDoc.js +40 -6
  119. package/dist/doc/markdownToDoc.js.map +1 -1
  120. package/dist/doc/templates/featureBlock.d.ts +18 -0
  121. package/dist/doc/templates/featureBlock.d.ts.map +1 -0
  122. package/dist/doc/templates/featureBlock.js +180 -0
  123. package/dist/doc/templates/featureBlock.js.map +1 -0
  124. package/dist/doc/templates/fullBleedQuote.d.ts +12 -0
  125. package/dist/doc/templates/fullBleedQuote.d.ts.map +1 -1
  126. package/dist/doc/templates/fullBleedQuote.js +12 -0
  127. package/dist/doc/templates/fullBleedQuote.js.map +1 -1
  128. package/dist/doc/templates/index.d.ts +28 -4
  129. package/dist/doc/templates/index.d.ts.map +1 -1
  130. package/dist/doc/templates/index.js +45 -12
  131. package/dist/doc/templates/index.js.map +1 -1
  132. package/dist/doc/templates/listBlock.d.ts.map +1 -1
  133. package/dist/doc/templates/listBlock.js +22 -5
  134. package/dist/doc/templates/listBlock.js.map +1 -1
  135. package/dist/doc/templates/statHighlight.d.ts +12 -0
  136. package/dist/doc/templates/statHighlight.d.ts.map +1 -1
  137. package/dist/doc/templates/statHighlight.js +12 -0
  138. package/dist/doc/templates/statHighlight.js.map +1 -1
  139. package/dist/doc/templates/titleBlock.d.ts +11 -0
  140. package/dist/doc/templates/titleBlock.d.ts.map +1 -1
  141. package/dist/doc/templates/titleBlock.js +8 -0
  142. package/dist/doc/templates/titleBlock.js.map +1 -1
  143. package/dist/doc/utils/themeUtils.d.ts +4 -4
  144. package/dist/doc/utils/themeUtils.d.ts.map +1 -1
  145. package/dist/doc/utils/themeUtils.js +8 -7
  146. package/dist/doc/utils/themeUtils.js.map +1 -1
  147. package/dist/generate/slideshowGenerator.js +1 -1
  148. package/dist/generate/slideshowGenerator.js.map +1 -1
  149. package/dist/generate/templateMapper.js +2 -2
  150. package/dist/generate/templateMapper.js.map +1 -1
  151. package/dist/icons/iconData.d.ts +26 -0
  152. package/dist/icons/iconData.d.ts.map +1 -0
  153. package/dist/icons/iconData.js +12 -0
  154. package/dist/icons/iconData.js.map +1 -0
  155. package/dist/icons/index.d.ts +12 -0
  156. package/dist/icons/index.d.ts.map +1 -0
  157. package/dist/icons/index.js +10 -0
  158. package/dist/icons/index.js.map +1 -0
  159. package/dist/icons/resolve.d.ts +61 -0
  160. package/dist/icons/resolve.d.ts.map +1 -0
  161. package/dist/icons/resolve.js +130 -0
  162. package/dist/icons/resolve.js.map +1 -0
  163. package/dist/imageEdit/ImageEditVersionManager.d.ts +31 -0
  164. package/dist/imageEdit/ImageEditVersionManager.d.ts.map +1 -0
  165. package/dist/imageEdit/ImageEditVersionManager.js +39 -0
  166. package/dist/imageEdit/ImageEditVersionManager.js.map +1 -0
  167. package/dist/imageEdit/export.d.ts +33 -0
  168. package/dist/imageEdit/export.d.ts.map +1 -0
  169. package/dist/imageEdit/export.js +253 -0
  170. package/dist/imageEdit/export.js.map +1 -0
  171. package/dist/imageEdit/index.d.ts +14 -0
  172. package/dist/imageEdit/index.d.ts.map +1 -0
  173. package/dist/imageEdit/index.js +22 -0
  174. package/dist/imageEdit/index.js.map +1 -0
  175. package/dist/imageEdit/persistence.d.ts +15 -0
  176. package/dist/imageEdit/persistence.d.ts.map +1 -0
  177. package/dist/imageEdit/persistence.js +49 -0
  178. package/dist/imageEdit/persistence.js.map +1 -0
  179. package/dist/imageEdit/state.d.ts +42 -0
  180. package/dist/imageEdit/state.d.ts.map +1 -0
  181. package/dist/imageEdit/state.js +98 -0
  182. package/dist/imageEdit/state.js.map +1 -0
  183. package/dist/imageEdit/versionPaths.d.ts +27 -0
  184. package/dist/imageEdit/versionPaths.d.ts.map +1 -0
  185. package/dist/imageEdit/versionPaths.js +57 -0
  186. package/dist/imageEdit/versionPaths.js.map +1 -0
  187. package/dist/imageEdit/versions.d.ts +67 -0
  188. package/dist/imageEdit/versions.d.ts.map +1 -0
  189. package/dist/imageEdit/versions.js +161 -0
  190. package/dist/imageEdit/versions.js.map +1 -0
  191. package/dist/index.d.ts +4 -0
  192. package/dist/index.d.ts.map +1 -1
  193. package/dist/index.js +4 -0
  194. package/dist/index.js.map +1 -1
  195. package/dist/jsonForm/chooseControl.d.ts +20 -0
  196. package/dist/jsonForm/chooseControl.d.ts.map +1 -0
  197. package/dist/jsonForm/chooseControl.js +82 -0
  198. package/dist/jsonForm/chooseControl.js.map +1 -0
  199. package/dist/jsonForm/evaluateWhen.d.ts +12 -0
  200. package/dist/jsonForm/evaluateWhen.d.ts.map +1 -0
  201. package/dist/jsonForm/evaluateWhen.js +65 -0
  202. package/dist/jsonForm/evaluateWhen.js.map +1 -0
  203. package/dist/jsonForm/index.d.ts +14 -0
  204. package/dist/jsonForm/index.d.ts.map +1 -0
  205. package/dist/jsonForm/index.js +12 -0
  206. package/dist/jsonForm/index.js.map +1 -0
  207. package/dist/jsonForm/inferSchema.d.ts +12 -0
  208. package/dist/jsonForm/inferSchema.d.ts.map +1 -0
  209. package/dist/jsonForm/inferSchema.js +15 -0
  210. package/dist/jsonForm/inferSchema.js.map +1 -0
  211. package/dist/jsonForm/pointer.d.ts +26 -0
  212. package/dist/jsonForm/pointer.d.ts.map +1 -0
  213. package/dist/jsonForm/pointer.js +102 -0
  214. package/dist/jsonForm/pointer.js.map +1 -0
  215. package/dist/jsonForm/types.d.ts +120 -0
  216. package/dist/jsonForm/types.d.ts.map +1 -0
  217. package/dist/jsonForm/types.js +10 -0
  218. package/dist/jsonForm/types.js.map +1 -0
  219. package/dist/markdown/convert.d.ts +11 -1
  220. package/dist/markdown/convert.d.ts.map +1 -1
  221. package/dist/markdown/convert.js +235 -15
  222. package/dist/markdown/convert.js.map +1 -1
  223. package/dist/markdown/index.d.ts +2 -2
  224. package/dist/markdown/index.d.ts.map +1 -1
  225. package/dist/markdown/index.js +1 -1
  226. package/dist/markdown/index.js.map +1 -1
  227. package/dist/markdown/types.d.ts +34 -1
  228. package/dist/markdown/types.d.ts.map +1 -1
  229. package/dist/markdown/utils.d.ts +40 -0
  230. package/dist/markdown/utils.d.ts.map +1 -1
  231. package/dist/markdown/utils.js +155 -0
  232. package/dist/markdown/utils.js.map +1 -1
  233. package/dist/recommend/index.d.ts +16 -0
  234. package/dist/recommend/index.d.ts.map +1 -0
  235. package/dist/recommend/index.js +15 -0
  236. package/dist/recommend/index.js.map +1 -0
  237. package/dist/recommend/templates.d.ts +39 -0
  238. package/dist/recommend/templates.d.ts.map +1 -0
  239. package/dist/recommend/templates.js +235 -0
  240. package/dist/recommend/templates.js.map +1 -0
  241. package/dist/schemas/BlockTemplates.d.ts +46 -8
  242. package/dist/schemas/BlockTemplates.d.ts.map +1 -1
  243. package/dist/schemas/BlockTemplates.js +3 -3
  244. package/dist/schemas/BlockTemplates.js.map +1 -1
  245. package/dist/schemas/ImageEditDoc.d.ts +81 -0
  246. package/dist/schemas/ImageEditDoc.d.ts.map +1 -0
  247. package/dist/schemas/ImageEditDoc.js +20 -0
  248. package/dist/schemas/ImageEditDoc.js.map +1 -0
  249. package/dist/schemas/Theme.d.ts +93 -23
  250. package/dist/schemas/Theme.d.ts.map +1 -1
  251. package/dist/schemas/Theme.js +80 -13
  252. package/dist/schemas/Theme.js.map +1 -1
  253. package/dist/schemas/colorUtils.d.ts +51 -0
  254. package/dist/schemas/colorUtils.d.ts.map +1 -0
  255. package/dist/schemas/colorUtils.js +154 -0
  256. package/dist/schemas/colorUtils.js.map +1 -0
  257. package/dist/schemas/fontStacks.d.ts +57 -0
  258. package/dist/schemas/fontStacks.d.ts.map +1 -0
  259. package/dist/schemas/fontStacks.js +227 -0
  260. package/dist/schemas/fontStacks.js.map +1 -0
  261. package/dist/schemas/index.d.ts +5 -0
  262. package/dist/schemas/index.d.ts.map +1 -1
  263. package/dist/schemas/index.js +4 -0
  264. package/dist/schemas/index.js.map +1 -1
  265. package/dist/schemas/themeCompile.d.ts +45 -0
  266. package/dist/schemas/themeCompile.d.ts.map +1 -0
  267. package/dist/schemas/themeCompile.js +160 -0
  268. package/dist/schemas/themeCompile.js.map +1 -0
  269. package/dist/schemas/themeLibrary.d.ts +11 -15
  270. package/dist/schemas/themeLibrary.d.ts.map +1 -1
  271. package/dist/schemas/themeLibrary.js +18 -499
  272. package/dist/schemas/themeLibrary.js.map +1 -1
  273. package/dist/schemas/themeValidator.d.ts +26 -0
  274. package/dist/schemas/themeValidator.d.ts.map +1 -0
  275. package/dist/schemas/themeValidator.js +352 -0
  276. package/dist/schemas/themeValidator.js.map +1 -0
  277. package/dist/schemas/themes/bold.json +48 -0
  278. package/dist/schemas/themes/cinematic.json +54 -0
  279. package/dist/schemas/themes/documentary.json +49 -0
  280. package/dist/schemas/themes/gezellig.json +44 -0
  281. package/dist/schemas/themes/index.d.ts +26 -0
  282. package/dist/schemas/themes/index.d.ts.map +1 -0
  283. package/dist/schemas/themes/index.js +49 -0
  284. package/dist/schemas/themes/index.js.map +1 -0
  285. package/dist/schemas/themes/magazine.json +44 -0
  286. package/dist/schemas/themes/minimalist.json +45 -0
  287. package/dist/schemas/themes/morning-light.json +42 -0
  288. package/dist/schemas/themes/standard-dark.json +50 -0
  289. package/dist/schemas/themes/standard.json +42 -0
  290. package/dist/schemas/themes/tech-dark.json +48 -0
  291. package/dist/schemas/themes/warm-earth.json +50 -0
  292. package/dist/storage/ScopedContentContainer.d.ts +44 -0
  293. package/dist/storage/ScopedContentContainer.d.ts.map +1 -0
  294. package/dist/storage/ScopedContentContainer.js +94 -0
  295. package/dist/storage/ScopedContentContainer.js.map +1 -0
  296. package/dist/storage/index.d.ts +1 -0
  297. package/dist/storage/index.d.ts.map +1 -1
  298. package/dist/storage/index.js +1 -0
  299. package/dist/storage/index.js.map +1 -1
  300. package/dist/story/index.d.ts +53 -13
  301. package/dist/story/index.js +12 -4
  302. package/dist/themeLibrary-BHjJ0Qx6.d.ts +40 -0
  303. package/dist/{types-DYAzisMG.d.ts → types-BdZkdJ3z.d.ts} +35 -2
  304. package/dist/types-D98li1gV.d.ts +93 -0
  305. package/dist/versions/DocumentVersionManager.d.ts +31 -0
  306. package/dist/versions/DocumentVersionManager.d.ts.map +1 -0
  307. package/dist/versions/DocumentVersionManager.js +33 -0
  308. package/dist/versions/DocumentVersionManager.js.map +1 -0
  309. package/dist/versions/index.d.ts +29 -0
  310. package/dist/versions/index.d.ts.map +1 -0
  311. package/dist/versions/index.js +28 -0
  312. package/dist/versions/index.js.map +1 -0
  313. package/dist/versions/operations.d.ts +44 -0
  314. package/dist/versions/operations.d.ts.map +1 -0
  315. package/dist/versions/operations.js +190 -0
  316. package/dist/versions/operations.js.map +1 -0
  317. package/dist/versions/paths.d.ts +29 -0
  318. package/dist/versions/paths.d.ts.map +1 -0
  319. package/dist/versions/paths.js +66 -0
  320. package/dist/versions/paths.js.map +1 -0
  321. package/dist/versions/timestamp.d.ts +22 -0
  322. package/dist/versions/timestamp.d.ts.map +1 -0
  323. package/dist/versions/timestamp.js +44 -0
  324. package/dist/versions/timestamp.js.map +1 -0
  325. package/dist/versions/types.d.ts +92 -0
  326. package/dist/versions/types.d.ts.map +1 -0
  327. package/dist/versions/types.js +11 -0
  328. package/dist/versions/types.js.map +1 -0
  329. package/package.json +28 -1
  330. package/src/__tests__/colorUtils.test.ts +112 -0
  331. package/src/__tests__/getLayers.test.ts +32 -7
  332. package/src/__tests__/iconResolve.test.ts +122 -0
  333. package/src/__tests__/imageEditState.test.ts +74 -0
  334. package/src/__tests__/imageEditVersions.test.ts +169 -0
  335. package/src/__tests__/inferDocumentTitle.test.ts +50 -0
  336. package/src/__tests__/jsonForm.chooseControl.test.ts +86 -0
  337. package/src/__tests__/jsonForm.evaluateWhen.test.ts +59 -0
  338. package/src/__tests__/jsonForm.inferSchema.test.ts +25 -0
  339. package/src/__tests__/jsonForm.pointer.test.ts +103 -0
  340. package/src/__tests__/markdown.test.ts +44 -0
  341. package/src/__tests__/markdownIcons.test.ts +104 -0
  342. package/src/__tests__/markdownToDoc.test.ts +19 -4
  343. package/src/__tests__/recommendTemplates.test.ts +183 -0
  344. package/src/__tests__/scopedContentContainer.test.ts +89 -0
  345. package/src/__tests__/slideshowGenerator.test.ts +1 -1
  346. package/src/__tests__/surface.test.ts +68 -0
  347. package/src/__tests__/templateAnnotationParse.test.ts +42 -0
  348. package/src/__tests__/templates.test.ts +93 -9
  349. package/src/__tests__/themeCompile.test.ts +156 -0
  350. package/src/__tests__/themeValidator.test.ts +125 -0
  351. package/src/__tests__/versions.test.ts +415 -0
  352. package/src/doc/audioMapping.ts +95 -13
  353. package/src/doc/getLayers.ts +32 -21
  354. package/src/doc/markdownToDoc.ts +45 -7
  355. package/src/doc/templates/featureBlock.ts +202 -0
  356. package/src/doc/templates/fullBleedQuote.ts +13 -0
  357. package/src/doc/templates/index.ts +50 -16
  358. package/src/doc/templates/listBlock.ts +24 -5
  359. package/src/doc/templates/statHighlight.ts +13 -0
  360. package/src/doc/templates/titleBlock.ts +9 -0
  361. package/src/doc/utils/themeUtils.ts +8 -7
  362. package/src/generate/slideshowGenerator.ts +1 -1
  363. package/src/generate/templateMapper.ts +2 -2
  364. package/src/icons/iconData.ts +31 -0
  365. package/src/icons/index.ts +18 -0
  366. package/src/icons/resolve.ts +139 -0
  367. package/src/imageEdit/ImageEditVersionManager.ts +73 -0
  368. package/src/imageEdit/export.ts +326 -0
  369. package/src/imageEdit/index.ts +71 -0
  370. package/src/imageEdit/persistence.ts +62 -0
  371. package/src/imageEdit/state.ts +112 -0
  372. package/src/imageEdit/versionPaths.ts +57 -0
  373. package/src/imageEdit/versions.ts +231 -0
  374. package/src/index.ts +4 -0
  375. package/src/jsonForm/chooseControl.ts +88 -0
  376. package/src/jsonForm/evaluateWhen.ts +59 -0
  377. package/src/jsonForm/index.ts +22 -0
  378. package/src/jsonForm/inferSchema.ts +25 -0
  379. package/src/jsonForm/pointer.ts +109 -0
  380. package/src/jsonForm/types.ts +167 -0
  381. package/src/markdown/convert.ts +248 -15
  382. package/src/markdown/index.ts +4 -0
  383. package/src/markdown/types.ts +38 -1
  384. package/src/markdown/utils.ts +159 -0
  385. package/src/recommend/index.ts +16 -0
  386. package/src/recommend/templates.ts +273 -0
  387. package/src/schemas/BlockTemplates.ts +50 -7
  388. package/src/schemas/ImageEditDoc.ts +95 -0
  389. package/src/schemas/Theme.ts +152 -23
  390. package/src/schemas/colorUtils.ts +186 -0
  391. package/src/schemas/fontStacks.ts +257 -0
  392. package/src/schemas/index.ts +12 -0
  393. package/src/schemas/themeCompile.ts +203 -0
  394. package/src/schemas/themeLibrary.ts +18 -524
  395. package/src/schemas/themeValidator.ts +388 -0
  396. package/src/schemas/themes/bold.json +48 -0
  397. package/src/schemas/themes/cinematic.json +54 -0
  398. package/src/schemas/themes/documentary.json +49 -0
  399. package/src/schemas/themes/gezellig.json +44 -0
  400. package/src/schemas/themes/index.ts +53 -0
  401. package/src/schemas/themes/magazine.json +44 -0
  402. package/src/schemas/themes/minimalist.json +45 -0
  403. package/src/schemas/themes/morning-light.json +42 -0
  404. package/src/schemas/themes/standard-dark.json +50 -0
  405. package/src/schemas/themes/standard.json +42 -0
  406. package/src/schemas/themes/tech-dark.json +48 -0
  407. package/src/schemas/themes/warm-earth.json +50 -0
  408. package/src/storage/ScopedContentContainer.ts +113 -0
  409. package/src/storage/index.ts +1 -0
  410. package/src/versions/DocumentVersionManager.ts +70 -0
  411. package/src/versions/index.ts +54 -0
  412. package/src/versions/operations.ts +230 -0
  413. package/src/versions/paths.ts +66 -0
  414. package/src/versions/timestamp.ts +48 -0
  415. package/src/versions/types.ts +91 -0
  416. package/dist/chunk-53TZ2UTL.js +0 -470
  417. package/dist/chunk-53TZ2UTL.js.map +0 -1
  418. package/dist/chunk-7GF6RDED.js +0 -82
  419. package/dist/chunk-7GF6RDED.js.map +0 -1
  420. package/dist/chunk-CYQHG27J.js.map +0 -1
  421. package/dist/chunk-GAQLMORN.js.map +0 -1
  422. package/dist/chunk-LAZELBON.js.map +0 -1
  423. package/dist/chunk-QM5PKNPW.js.map +0 -1
  424. package/dist/chunk-TSG2OSE6.js.map +0 -1
  425. package/dist/chunk-XM4WSOHB.js +0 -92
  426. package/dist/chunk-XM4WSOHB.js.map +0 -1
  427. package/dist/themeLibrary-IP7EdyRq.d.ts +0 -44
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ toPointer,
4
+ pointerSegments,
5
+ getByPointer,
6
+ setByPointer,
7
+ resolveRef,
8
+ } from '../jsonForm/index.js';
9
+
10
+ describe('toPointer', () => {
11
+ it('keeps pointer-form paths intact', () => {
12
+ expect(toPointer('/a/b')).toBe('/a/b');
13
+ expect(toPointer('/0/1')).toBe('/0/1');
14
+ });
15
+ it('converts dotted-form to pointer-form', () => {
16
+ expect(toPointer('user.role')).toBe('/user/role');
17
+ expect(toPointer('a')).toBe('/a');
18
+ });
19
+ it('returns empty for root', () => {
20
+ expect(toPointer('')).toBe('');
21
+ expect(toPointer('/')).toBe('');
22
+ });
23
+ it('escapes ~ and / per RFC 6901', () => {
24
+ // dotted form does not allow / inside a segment; we test pointer-form decoding instead.
25
+ expect(pointerSegments('/a~1b')).toEqual(['a/b']);
26
+ expect(pointerSegments('/a~0b')).toEqual(['a~b']);
27
+ });
28
+ });
29
+
30
+ describe('getByPointer', () => {
31
+ const data = { user: { role: 'admin', tags: ['a', 'b'] } };
32
+ it('resolves nested object paths', () => {
33
+ expect(getByPointer(data, '/user/role')).toBe('admin');
34
+ expect(getByPointer(data, 'user.role')).toBe('admin');
35
+ });
36
+ it('resolves array indices', () => {
37
+ expect(getByPointer(data, '/user/tags/1')).toBe('b');
38
+ });
39
+ it('returns undefined for missing paths', () => {
40
+ expect(getByPointer(data, '/user/missing')).toBeUndefined();
41
+ expect(getByPointer(data, '/user/tags/9')).toBeUndefined();
42
+ });
43
+ it('returns the data itself for empty pointer', () => {
44
+ expect(getByPointer(data, '')).toBe(data);
45
+ });
46
+ });
47
+
48
+ describe('setByPointer', () => {
49
+ it('writes a leaf value immutably', () => {
50
+ const before = { a: 1, b: 2 };
51
+ const after = setByPointer(before, '/a', 99);
52
+ expect(after).toEqual({ a: 99, b: 2 });
53
+ expect(before).toEqual({ a: 1, b: 2 });
54
+ });
55
+ it('clones intermediate objects, shares siblings', () => {
56
+ const before = { x: { y: 1 }, z: { w: 2 } };
57
+ const after = setByPointer(before, '/x/y', 7);
58
+ expect(after).toEqual({ x: { y: 7 }, z: { w: 2 } });
59
+ expect(after.z).toBe(before.z);
60
+ expect(after.x).not.toBe(before.x);
61
+ });
62
+ it('writes into array indices', () => {
63
+ const before = { items: ['a', 'b', 'c'] };
64
+ const after = setByPointer(before, '/items/1', 'B');
65
+ expect(after.items).toEqual(['a', 'B', 'c']);
66
+ expect(after.items).not.toBe(before.items);
67
+ });
68
+ it('creates intermediate containers when missing', () => {
69
+ const after = setByPointer({}, '/a/b/0/c', 'leaf');
70
+ expect(after).toEqual({ a: { b: [{ c: 'leaf' }] } });
71
+ });
72
+ it('round-trips with getByPointer', () => {
73
+ const before = { a: { b: [{ c: 1 }, { c: 2 }] } };
74
+ const after = setByPointer(before, '/a/b/1/c', 42);
75
+ expect(getByPointer(after, '/a/b/1/c')).toBe(42);
76
+ expect(getByPointer(after, '/a/b/0/c')).toBe(1);
77
+ });
78
+ });
79
+
80
+ describe('resolveRef', () => {
81
+ const root = {
82
+ type: 'object',
83
+ properties: {
84
+ pet: { $ref: '#/$defs/Pet' },
85
+ },
86
+ $defs: {
87
+ Pet: { type: 'object', properties: { name: { type: 'string' } } },
88
+ },
89
+ };
90
+ it('returns referenced subschema for a local $ref', () => {
91
+ const ref = { $ref: '#/$defs/Pet' };
92
+ const resolved = resolveRef(ref, root);
93
+ expect(resolved?.type).toBe('object');
94
+ expect(resolved?.properties?.name?.type).toBe('string');
95
+ });
96
+ it('returns the schema unchanged when no $ref present', () => {
97
+ const node = { type: 'string' };
98
+ expect(resolveRef(node, root)).toBe(node);
99
+ });
100
+ it('returns undefined for unknown refs', () => {
101
+ expect(resolveRef({ $ref: '#/$defs/Nope' }, root)).toBeUndefined();
102
+ });
103
+ });
@@ -33,6 +33,7 @@ import type {
33
33
  MarkdownLinkReference,
34
34
  MarkdownImageReference,
35
35
  MarkdownText,
36
+ MarkdownMention,
36
37
  } from '../markdown/index';
37
38
 
38
39
  // ============================================
@@ -151,6 +152,49 @@ describe('parseMarkdown / stringifyMarkdown', () => {
151
152
  expect(link.children[0]).toEqual({ type: 'text', value: 'Click here' });
152
153
  });
153
154
 
155
+ it('parses mentions from @[Name](scheme:id)', () => {
156
+ const doc = parse('Hey @[Leo](gezel:leo), take a look.');
157
+ const p = doc.children[0] as MarkdownParagraph;
158
+ const text0 = p.children[0] as MarkdownText;
159
+ expect(text0).toEqual({ type: 'text', value: 'Hey ' });
160
+ const mention = p.children[1] as MarkdownMention;
161
+ expect(mention).toEqual({
162
+ type: 'mention',
163
+ targetKind: 'gezel',
164
+ targetId: 'leo',
165
+ displayName: 'Leo',
166
+ });
167
+ const text2 = p.children[2] as MarkdownText;
168
+ expect(text2.value).toBe(', take a look.');
169
+ });
170
+
171
+ it('round-trips a mention back to markdown', () => {
172
+ // remark-stringify escapes `:` in link URLs that follow an `@` (to
173
+ // disambiguate from autolink syntax). The escape is idempotent —
174
+ // re-parsing `\:` produces the unescaped url, so the round-trip
175
+ // preserves the mention semantically even if the raw text changes
176
+ // on the first pass.
177
+ const input = 'Hey @[Leo](gezel:leo), ping @[Tess](gezel:tess) too.\n';
178
+ const once = stringifyMarkdown(parseMarkdown(input));
179
+ const twice = stringifyMarkdown(parseMarkdown(once));
180
+ expect(twice).toBe(once);
181
+ // Semantics are preserved: re-parsing yields mentions.
182
+ const doc = parseMarkdown(once) as MarkdownDocument;
183
+ const p = doc.children[0] as MarkdownParagraph;
184
+ const m1 = p.children.find((c) => c.type === 'mention') as MarkdownMention;
185
+ expect(m1.targetKind).toBe('gezel');
186
+ expect(m1.targetId).toBe('leo');
187
+ expect(m1.displayName).toBe('Leo');
188
+ });
189
+
190
+ it('does not collapse @ + link when the url has no scheme', () => {
191
+ const doc = parse('Cost @[100]($100)');
192
+ const p = doc.children[0] as MarkdownParagraph;
193
+ // "$100" doesn't match scheme:id because $ isn't a scheme character
194
+ // → stays as text("Cost @") + link
195
+ expect((p.children[1] as MarkdownLink).type).toBe('link');
196
+ });
197
+
154
198
  it('parses images', () => {
155
199
  const doc = parse('![Alt text](image.png "Image title")');
156
200
  const p = doc.children[0] as MarkdownParagraph;
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseMarkdown, stringifyMarkdown } from '../markdown/index.js';
3
+
4
+ describe('inline icon parsing', () => {
5
+ it('detects a unique bare {[name]} as an inlineIcon node', () => {
6
+ const doc = parseMarkdown('Hello {[github]} world');
7
+ const para = doc.children[0] as unknown as { children: Array<Record<string, unknown>> };
8
+ expect(para.children.length).toBe(3);
9
+ expect(para.children[0]).toMatchObject({ type: 'text', value: 'Hello ' });
10
+ expect(para.children[1]).toMatchObject({
11
+ type: 'inlineIcon',
12
+ token: 'github',
13
+ family: 'brands',
14
+ name: 'github',
15
+ });
16
+ expect(para.children[2]).toMatchObject({ type: 'text', value: ' world' });
17
+ });
18
+
19
+ it('resolves qualified tokens', () => {
20
+ const doc = parseMarkdown('Q: {[fa-solid:user]}');
21
+ const para = doc.children[0] as unknown as { children: Array<Record<string, unknown>> };
22
+ const icon = para.children.find((c) => c.type === 'inlineIcon');
23
+ expect(icon).toMatchObject({
24
+ type: 'inlineIcon',
25
+ token: 'fa-solid:user',
26
+ family: 'solid',
27
+ name: 'user',
28
+ });
29
+ });
30
+
31
+ it('leaves unknown / ambiguous tokens as literal text', () => {
32
+ // `user` is ambiguous (multiple families), `notathing` is unknown —
33
+ // both should round-trip as plain text so authors can write them.
34
+ const doc = parseMarkdown('a {[user]} b {[notathing]} c');
35
+ const para = doc.children[0] as unknown as { children: Array<Record<string, unknown>> };
36
+ expect(para.children).toHaveLength(1);
37
+ expect(para.children[0]).toMatchObject({
38
+ type: 'text',
39
+ value: 'a {[user]} b {[notathing]} c',
40
+ });
41
+ });
42
+
43
+ it('round-trips bare icons byte-stable through stringify', () => {
44
+ // Bare names contain no colon, so the serializer can re-emit them
45
+ // verbatim. (Qualified tokens like `fa-solid:user` get the colon
46
+ // escaped as `\:` for safety against remark-directive — that form
47
+ // re-parses to the same token, just isn't byte-stable.)
48
+ const src = 'Built with {[github]} and {[face-smile]}.';
49
+ expect(stringifyMarkdown(parseMarkdown(src)).trim()).toBe(src);
50
+ });
51
+
52
+ it('round-trips qualified icons semantically through stringify', () => {
53
+ // `{[fa-solid:user]}` may serialize as `{[fa-solid\:user]}` — both
54
+ // forms parse back to the same inlineIcon node, so we verify
55
+ // semantic equivalence rather than byte equality.
56
+ const src = 'Q: {[fa-solid:user]} end';
57
+ const round1 = stringifyMarkdown(parseMarkdown(src));
58
+ const reparsed = parseMarkdown(round1);
59
+ const para = reparsed.children[0] as unknown as { children: Array<Record<string, unknown>> };
60
+ const icon = para.children.find((c) => c.type === 'inlineIcon');
61
+ expect(icon).toMatchObject({
62
+ type: 'inlineIcon',
63
+ token: 'fa-solid:user',
64
+ family: 'solid',
65
+ name: 'user',
66
+ });
67
+ });
68
+ });
69
+
70
+ describe('inline icons inside headings', () => {
71
+ it('parses the trailing template annotation as the template, body icon as inlineIcon', () => {
72
+ const doc = parseMarkdown('# Repo {[github]} status {[sectionHeader]}\n');
73
+ const heading = doc.children[0] as unknown as {
74
+ type: string;
75
+ templateAnnotation?: { template?: string };
76
+ children: Array<Record<string, unknown>>;
77
+ };
78
+ expect(heading.type).toBe('heading');
79
+ expect(heading.templateAnnotation?.template).toBe('sectionHeader');
80
+ // Children should be: text('Repo '), inlineIcon(github), text(' status')
81
+ const icon = heading.children.find((c) => c.type === 'inlineIcon');
82
+ expect(icon).toMatchObject({ type: 'inlineIcon', name: 'github' });
83
+ });
84
+
85
+ it('round-trips heading template + inline icon', () => {
86
+ const src = '# Repo {[github]} status {[sectionHeader]}\n';
87
+ expect(stringifyMarkdown(parseMarkdown(src))).toBe(src);
88
+ });
89
+
90
+ it('round-trips a heading where the whole trailing annotation is an icon (not a template)', () => {
91
+ // `# Foo {[github]}` — `github` is a valid icon (not a known template),
92
+ // so the bare token should NOT be treated as a template annotation
93
+ // unless it also happens to be in the template registry. The parser
94
+ // is liberal here — `extractTemplateAnnotation` runs first and would
95
+ // capture `{[github]}` as a template annotation; we accept that as
96
+ // the documented precedence.
97
+ const src = '# Foo {[github]}\n';
98
+ const out = stringifyMarkdown(parseMarkdown(src));
99
+ // The annotation extraction happens first, so the round-trip is
100
+ // either `# Foo {[github]}` (when serialized back via template
101
+ // suffix) — verify the output still matches the source.
102
+ expect(out).toBe(src);
103
+ });
104
+ });
@@ -142,12 +142,12 @@ describe('markdownToDoc', () => {
142
142
  const md = parseMarkdown('# Test');
143
143
  const doc = markdownToDoc(md, {
144
144
  articleId: 'custom-article',
145
- defaultTemplate: 'titleBlock',
145
+ defaultTemplate: 'title',
146
146
  defaultDuration: 10,
147
147
  });
148
148
 
149
149
  expect(doc.articleId).toBe('custom-article');
150
- expect(doc.blocks[0].template).toBe('titleBlock');
150
+ expect(doc.blocks[0].template).toBe('title');
151
151
  expect(doc.blocks[0].duration).toBe(10);
152
152
  });
153
153
 
@@ -363,11 +363,11 @@ describe('template annotation in markdownToDoc', () => {
363
363
  });
364
364
 
365
365
  it('nested headings preserve their own annotations', () => {
366
- const input = '# Chapter {[titleBlock]}\n\nIntro\n\n## Section {[chart]}\n\nData';
366
+ const input = '# Chapter {[title]}\n\nIntro\n\n## Section {[chart]}\n\nData';
367
367
  const md = parseMarkdown(input);
368
368
  const doc = markdownToDoc(md);
369
369
 
370
- expect(doc.blocks[0].template).toBe('titleBlock');
370
+ expect(doc.blocks[0].template).toBe('title');
371
371
  expect(doc.blocks[0].children).toHaveLength(1);
372
372
  expect(doc.blocks[0].children![0].template).toBe('chart');
373
373
  });
@@ -509,6 +509,21 @@ describe('auto cover block generation', () => {
509
509
  expect(doc.startBlock!.heroSrc).toBe('deep.png');
510
510
  });
511
511
 
512
+ it('picks raw HTML <img> as heroSrc (WYSIWYG resize case)', () => {
513
+ // The WYSIWYG editor serializes resized images as raw `<img src width>`
514
+ // because markdown shorthand has no width syntax. The cover scanner
515
+ // must see through that, or every resized image silently drops out
516
+ // of exports.
517
+ const md = parseMarkdown(
518
+ '# Title\n\n<img alt="resized" src="resized.jpg" width="194">\n\nText.',
519
+ );
520
+ const doc = markdownToDoc(md);
521
+
522
+ expect(doc.startBlock).toBeDefined();
523
+ expect(doc.startBlock!.heroSrc).toBe('resized.jpg');
524
+ expect(doc.startBlock!.heroAlt).toBe('resized');
525
+ });
526
+
512
527
  it('works without images (no heroSrc)', () => {
513
528
  const md = parseMarkdown('# Title\n\nPlain text, no images.');
514
529
  const doc = markdownToDoc(md);
@@ -0,0 +1,183 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseMarkdown } from '../markdown/parse.js';
3
+ import { profileBlockContents, recommendTemplatesForBlock } from '../recommend/templates.js';
4
+
5
+ const ALL_TEMPLATES = [
6
+ 'title',
7
+ 'sectionHeader',
8
+ 'statHighlight',
9
+ 'quote',
10
+ 'factCard',
11
+ 'twoColumn',
12
+ 'dateEvent',
13
+ 'imageWithCaption',
14
+ 'leftFeature',
15
+ 'rightFeature',
16
+ 'map',
17
+ 'fullBleedQuote',
18
+ 'list',
19
+ 'photoGrid',
20
+ 'definitionCard',
21
+ 'comparisonBar',
22
+ 'pullQuote',
23
+ 'videoWithCaption',
24
+ 'videoPullQuote',
25
+ 'dataTable',
26
+ ];
27
+
28
+ function profileOf(source: string) {
29
+ const doc = parseMarkdown(source);
30
+ return profileBlockContents(doc.children);
31
+ }
32
+
33
+ describe('profileBlockContents', () => {
34
+ it('returns an empty profile for no nodes', () => {
35
+ const p = profileBlockContents([]);
36
+ expect(p.wordCount).toBe(0);
37
+ expect(p.hasImage).toBe(false);
38
+ expect(p.hasVideo).toBe(false);
39
+ });
40
+
41
+ it('detects a single markdown image', () => {
42
+ const p = profileOf('![alt](https://example.com/a.png)');
43
+ expect(p.imageCount).toBe(1);
44
+ expect(p.hasImage).toBe(true);
45
+ });
46
+
47
+ it('detects an HTML <img> tag', () => {
48
+ const p = profileOf('<img src="a.png" alt="x" />');
49
+ expect(p.imageCount).toBe(1);
50
+ expect(p.hasImage).toBe(true);
51
+ });
52
+
53
+ it('detects a blockquote', () => {
54
+ const p = profileOf('> The mountain was calling me.');
55
+ expect(p.hasBlockquote).toBe(true);
56
+ });
57
+
58
+ it('detects a bullet list', () => {
59
+ const p = profileOf('- one\n- two\n- three');
60
+ expect(p.hasList).toBe(true);
61
+ });
62
+
63
+ it('detects a GFM table', () => {
64
+ const p = profileOf('| a | b |\n| --- | --- |\n| 1 | 2 |');
65
+ expect(p.hasTable).toBe(true);
66
+ });
67
+
68
+ it('detects a date string in body text', () => {
69
+ const p = profileOf('Construction began in July 1899.');
70
+ expect(p.hasDate).toBe(true);
71
+ });
72
+
73
+ it('detects an ISO date', () => {
74
+ const p = profileOf('Launched on 2024-03-15.');
75
+ expect(p.hasDate).toBe(true);
76
+ });
77
+
78
+ it('detects a prominent number paragraph', () => {
79
+ const p = profileOf('$2.3M raised');
80
+ expect(p.hasNumberHighlight).toBe(true);
81
+ });
82
+
83
+ it('detects a YouTube embed via link', () => {
84
+ const p = profileOf('[Watch](https://www.youtube.com/watch?v=abc)');
85
+ expect(p.hasVideo).toBe(true);
86
+ });
87
+
88
+ it('detects a <video> tag', () => {
89
+ const p = profileOf('<video src="x.mp4"></video>');
90
+ expect(p.hasVideo).toBe(true);
91
+ });
92
+
93
+ it('counts multiple images', () => {
94
+ const p = profileOf('![a](a.png)\n\n![b](b.png)\n\n![c](c.png)');
95
+ expect(p.imageCount).toBe(3);
96
+ });
97
+ });
98
+
99
+ describe('recommendTemplatesForBlock', () => {
100
+ it('falls back to universal defaults when nothing is detected', () => {
101
+ const profile = profileBlockContents([]);
102
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
103
+ expect(recommended).toEqual(['title', 'sectionHeader', 'factCard', 'twoColumn']);
104
+ });
105
+
106
+ it('keeps recommended ordering aligned with the input list', () => {
107
+ const profile = profileOf('![a](a.png)');
108
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
109
+ // imageWithCaption comes before leftFeature/rightFeature in ALL_TEMPLATES
110
+ expect(recommended.indexOf('imageWithCaption')).toBeLessThan(
111
+ recommended.indexOf('leftFeature'),
112
+ );
113
+ });
114
+
115
+ it('recommends image templates for blocks with one image (no photoGrid)', () => {
116
+ const profile = profileOf('![a](a.png)');
117
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
118
+ expect(recommended).toContain('imageWithCaption');
119
+ expect(recommended).toContain('leftFeature');
120
+ expect(recommended).toContain('rightFeature');
121
+ expect(recommended).not.toContain('photoGrid');
122
+ });
123
+
124
+ it('adds photoGrid when 2+ images present', () => {
125
+ const profile = profileOf('![a](a.png)\n\n![b](b.png)');
126
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
127
+ expect(recommended).toContain('photoGrid');
128
+ });
129
+
130
+ it('recommends quote templates for blocks with blockquotes', () => {
131
+ const profile = profileOf('> something memorable');
132
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
133
+ expect(recommended).toContain('quote');
134
+ expect(recommended).toContain('pullQuote');
135
+ expect(recommended).toContain('fullBleedQuote');
136
+ });
137
+
138
+ it('recommends dateEvent for blocks with date strings', () => {
139
+ const profile = profileOf('On March 5, 2024, the ribbon was cut.');
140
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
141
+ expect(recommended).toContain('dateEvent');
142
+ });
143
+
144
+ it('recommends statHighlight for blocks with prominent numbers', () => {
145
+ const profile = profileOf('$2.3M raised');
146
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
147
+ expect(recommended).toContain('statHighlight');
148
+ });
149
+
150
+ it('recommends dataTable + comparisonBar for blocks with tables', () => {
151
+ const profile = profileOf('| a | b |\n| --- | --- |\n| 1 | 2 |');
152
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
153
+ expect(recommended).toContain('dataTable');
154
+ expect(recommended).toContain('comparisonBar');
155
+ });
156
+
157
+ it('omits universal generics (factCard, twoColumn) when specific signal fires', () => {
158
+ const profile = profileOf('> something memorable');
159
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
160
+ // title + sectionHeader still come along, but factCard/twoColumn do not
161
+ expect(recommended).toContain('title');
162
+ expect(recommended).toContain('sectionHeader');
163
+ expect(recommended).not.toContain('factCard');
164
+ expect(recommended).not.toContain('twoColumn');
165
+ });
166
+
167
+ it('combines multiple signals additively', () => {
168
+ const profile = profileOf('![a](a.png)\n\n> a great line\n\n- alpha\n- beta');
169
+ const { recommended } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
170
+ expect(recommended).toContain('imageWithCaption');
171
+ expect(recommended).toContain('quote');
172
+ expect(recommended).toContain('list');
173
+ });
174
+
175
+ it('places unrecommended templates in the rest bucket', () => {
176
+ const profile = profileOf('![a](a.png)');
177
+ const { recommended, rest } = recommendTemplatesForBlock(profile, ALL_TEMPLATES);
178
+ expect(rest).toContain('map');
179
+ expect(rest).toContain('definitionCard');
180
+ // No template appears in both buckets
181
+ for (const name of recommended) expect(rest).not.toContain(name);
182
+ });
183
+ });
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { MemoryContentContainer } from '../storage/ContentContainer';
3
+ import { ScopedContentContainer, scopeContainer } from '../storage/ScopedContentContainer';
4
+
5
+ const enc = (s: string) => new TextEncoder().encode(s);
6
+ const dec = (b: ArrayBuffer | null) => (b ? new TextDecoder().decode(b) : null);
7
+
8
+ describe('ScopedContentContainer', () => {
9
+ let parent: MemoryContentContainer;
10
+
11
+ beforeEach(() => {
12
+ parent = new MemoryContentContainer();
13
+ });
14
+
15
+ it('writeFile + readFile route through the prefix', async () => {
16
+ const scoped = scopeContainer(parent, 'image_files');
17
+ await scoped.writeFile('state.json', enc('{}'), 'application/json');
18
+
19
+ expect(dec(await parent.readFile('image_files/state.json'))).toBe('{}');
20
+ expect(dec(await scoped.readFile('state.json'))).toBe('{}');
21
+ });
22
+
23
+ it('exists + removeFile honor the prefix', async () => {
24
+ const scoped = scopeContainer(parent, 'image_files');
25
+ await scoped.writeFile('a.txt', enc('a'));
26
+ expect(await scoped.exists('a.txt')).toBe(true);
27
+ expect(await parent.exists('image_files/a.txt')).toBe(true);
28
+
29
+ await scoped.removeFile('a.txt');
30
+ expect(await scoped.exists('a.txt')).toBe(false);
31
+ expect(await parent.exists('image_files/a.txt')).toBe(false);
32
+ });
33
+
34
+ it('listFiles returns prefix-stripped paths and ignores siblings', async () => {
35
+ const scoped = scopeContainer(parent, 'image_files');
36
+ await parent.writeFile('outside.txt', enc('x'));
37
+ await parent.writeFile('image_files_sibling/peek.txt', enc('y')); // not in scope
38
+ await scoped.writeFile('a.txt', enc('1'));
39
+ await scoped.writeFile('sub/b.txt', enc('2'));
40
+
41
+ const list = await scoped.listFiles();
42
+ const paths = list.map((e) => e.path).sort();
43
+ expect(paths).toEqual(['a.txt', 'sub/b.txt']);
44
+ });
45
+
46
+ it('listFiles with prefix joins the scope prefix', async () => {
47
+ const scoped = scopeContainer(parent, 'sidecar');
48
+ await scoped.writeFile('assets/a.png', enc('a'));
49
+ await scoped.writeFile('assets/b.png', enc('b'));
50
+ await scoped.writeFile('state.json', enc('{}'));
51
+
52
+ const list = await scoped.listFiles('assets/');
53
+ expect(list.map((e) => e.path).sort()).toEqual(['assets/a.png', 'assets/b.png']);
54
+ });
55
+
56
+ it('nested scopes compose', async () => {
57
+ const a = scopeContainer(parent, 'a');
58
+ const ab = scopeContainer(a, 'b');
59
+ await ab.writeFile('c.txt', enc('hi'));
60
+ expect(dec(await parent.readFile('a/b/c.txt'))).toBe('hi');
61
+ expect(dec(await ab.readFile('c.txt'))).toBe('hi');
62
+ });
63
+
64
+ it('normalizes leading/trailing slashes in prefix and paths', async () => {
65
+ const scoped = scopeContainer(parent, '/sidecar/');
66
+ expect(scoped.prefix).toBe('sidecar');
67
+ await scoped.writeFile('/x.txt', enc('x'));
68
+ expect(dec(await parent.readFile('sidecar/x.txt'))).toBe('x');
69
+ });
70
+
71
+ it('rejects empty / dotted prefixes', () => {
72
+ expect(() => scopeContainer(parent, '')).toThrow();
73
+ expect(() => scopeContainer(parent, '/')).toThrow();
74
+ expect(() => scopeContainer(parent, 'a/../b')).toThrow();
75
+ expect(() => scopeContainer(parent, './a')).toThrow();
76
+ });
77
+
78
+ it('getDocumentPath / readDocument always return null', async () => {
79
+ const scoped = scopeContainer(parent, 'sidecar');
80
+ await scoped.writeFile('index.md', enc('# hi'));
81
+ expect(await scoped.getDocumentPath()).toBeNull();
82
+ expect(await scoped.readDocument()).toBeNull();
83
+ });
84
+
85
+ it('writeDocument throws', async () => {
86
+ const scoped = new ScopedContentContainer(parent, 'sidecar');
87
+ await expect(scoped.writeDocument()).rejects.toThrow();
88
+ });
89
+ });
@@ -34,7 +34,7 @@ describe('generateSlideshow', () => {
34
34
  expect(doc.articleId).toBe('Mount Rainier');
35
35
  const first = doc.blocks[0];
36
36
  if (isTemplateBlock(first)) {
37
- expect(first.template).toBe('titleBlock');
37
+ expect(first.template).toBe('title');
38
38
  }
39
39
  });
40
40
 
@@ -0,0 +1,68 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ applySurface,
4
+ DARK_SURFACE,
5
+ DEFAULT_THEME,
6
+ LIGHT_SURFACE,
7
+ type SurfaceScheme,
8
+ } from '../schemas/index.js';
9
+
10
+ describe('SurfaceScheme', () => {
11
+ it('LIGHT_SURFACE and DARK_SURFACE expose the full field set', () => {
12
+ for (const s of [LIGHT_SURFACE, DARK_SURFACE]) {
13
+ expect(typeof s.id).toBe('string');
14
+ expect(typeof s.background).toBe('string');
15
+ expect(typeof s.backgroundLight).toBe('string');
16
+ expect(typeof s.text).toBe('string');
17
+ expect(typeof s.textMuted).toBe('string');
18
+ }
19
+ });
20
+ });
21
+
22
+ describe('applySurface', () => {
23
+ it('overlays surface fields onto a theme', () => {
24
+ const out = applySurface(DEFAULT_THEME, LIGHT_SURFACE);
25
+ expect(out.colors.background).toBe(LIGHT_SURFACE.background);
26
+ expect(out.colors.backgroundLight).toBe(LIGHT_SURFACE.backgroundLight);
27
+ expect(out.colors.text).toBe(LIGHT_SURFACE.text);
28
+ expect(out.colors.textMuted).toBe(LIGHT_SURFACE.textMuted);
29
+ });
30
+
31
+ it('leaves editorial colors (primary, highlight, warning) untouched', () => {
32
+ const out = applySurface(DEFAULT_THEME, DARK_SURFACE);
33
+ expect(out.colors.primary).toBe(DEFAULT_THEME.colors.primary);
34
+ expect(out.colors.secondary).toBe(DEFAULT_THEME.colors.secondary);
35
+ expect(out.colors.highlight).toBe(DEFAULT_THEME.colors.highlight);
36
+ expect(out.colors.warning).toBe(DEFAULT_THEME.colors.warning);
37
+ });
38
+
39
+ it('leaves typography, style, renderStyle, colorSchemes untouched', () => {
40
+ const out = applySurface(DEFAULT_THEME, LIGHT_SURFACE);
41
+ expect(out.typography).toBe(DEFAULT_THEME.typography);
42
+ expect(out.style).toBe(DEFAULT_THEME.style);
43
+ expect(out.renderStyle).toBe(DEFAULT_THEME.renderStyle);
44
+ expect(out.colorSchemes).toBe(DEFAULT_THEME.colorSchemes);
45
+ });
46
+
47
+ it('returns a new theme (does not mutate the base)', () => {
48
+ const frozenTheme = DEFAULT_THEME;
49
+ const originalBg = frozenTheme.colors.background;
50
+ const out = applySurface(frozenTheme, DARK_SURFACE);
51
+ expect(out).not.toBe(frozenTheme);
52
+ expect(out.colors).not.toBe(frozenTheme.colors);
53
+ expect(frozenTheme.colors.background).toBe(originalBg);
54
+ });
55
+
56
+ it('accepts custom SurfaceScheme objects', () => {
57
+ const custom: SurfaceScheme = {
58
+ id: 'parchment',
59
+ background: '#f4ecd8',
60
+ backgroundLight: '#e8dfc4',
61
+ text: '#2b2b2b',
62
+ textMuted: '#6b6b6b',
63
+ };
64
+ const out = applySurface(DEFAULT_THEME, custom);
65
+ expect(out.colors.background).toBe('#f4ecd8');
66
+ expect(out.colors.textMuted).toBe('#6b6b6b');
67
+ });
68
+ });