@bendyline/squisq-editor-react 1.3.0 → 1.5.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 (461) hide show
  1. package/dist/DocumentSettingsDialog.d.ts +26 -0
  2. package/dist/DocumentSettingsDialog.d.ts.map +1 -0
  3. package/dist/DocumentSettingsDialog.js +115 -0
  4. package/dist/DocumentSettingsDialog.js.map +1 -0
  5. package/dist/EditorContext.d.ts +248 -4
  6. package/dist/EditorContext.d.ts.map +1 -1
  7. package/dist/EditorContext.js +248 -10
  8. package/dist/EditorContext.js.map +1 -1
  9. package/dist/EditorShell.d.ts +184 -4
  10. package/dist/EditorShell.d.ts.map +1 -1
  11. package/dist/EditorShell.js +184 -12
  12. package/dist/EditorShell.js.map +1 -1
  13. package/dist/EmojiPicker.d.ts +50 -0
  14. package/dist/EmojiPicker.d.ts.map +1 -0
  15. package/dist/EmojiPicker.js +182 -0
  16. package/dist/EmojiPicker.js.map +1 -0
  17. package/dist/ImageEditor.d.ts +68 -0
  18. package/dist/ImageEditor.d.ts.map +1 -0
  19. package/dist/ImageEditor.js +166 -0
  20. package/dist/ImageEditor.js.map +1 -0
  21. package/dist/ImageNodeView.d.ts +13 -1
  22. package/dist/ImageNodeView.d.ts.map +1 -1
  23. package/dist/ImageNodeView.js +172 -19
  24. package/dist/ImageNodeView.js.map +1 -1
  25. package/dist/ImageViewer.d.ts +26 -0
  26. package/dist/ImageViewer.d.ts.map +1 -0
  27. package/dist/ImageViewer.js +119 -0
  28. package/dist/ImageViewer.js.map +1 -0
  29. package/dist/InlineIcon.d.ts +17 -0
  30. package/dist/InlineIcon.d.ts.map +1 -0
  31. package/dist/InlineIcon.js +72 -0
  32. package/dist/InlineIcon.js.map +1 -0
  33. package/dist/InlinePreviewGutter.d.ts +52 -0
  34. package/dist/InlinePreviewGutter.d.ts.map +1 -0
  35. package/dist/InlinePreviewGutter.js +397 -0
  36. package/dist/InlinePreviewGutter.js.map +1 -0
  37. package/dist/LinkDialog.d.ts +43 -0
  38. package/dist/LinkDialog.d.ts.map +1 -0
  39. package/dist/LinkDialog.js +102 -0
  40. package/dist/LinkDialog.js.map +1 -0
  41. package/dist/MediaBin.d.ts +12 -1
  42. package/dist/MediaBin.d.ts.map +1 -1
  43. package/dist/MediaBin.js +13 -3
  44. package/dist/MediaBin.js.map +1 -1
  45. package/dist/MentionExtension.js +10 -7
  46. package/dist/MentionExtension.js.map +1 -1
  47. package/dist/OutlinePanel.d.ts +17 -0
  48. package/dist/OutlinePanel.d.ts.map +1 -0
  49. package/dist/OutlinePanel.js +167 -0
  50. package/dist/OutlinePanel.js.map +1 -0
  51. package/dist/PlainHtmlPreview.d.ts +50 -0
  52. package/dist/PlainHtmlPreview.d.ts.map +1 -0
  53. package/dist/PlainHtmlPreview.js +155 -0
  54. package/dist/PlainHtmlPreview.js.map +1 -0
  55. package/dist/PreviewControls.d.ts +15 -1
  56. package/dist/PreviewControls.d.ts.map +1 -1
  57. package/dist/PreviewControls.js +75 -18
  58. package/dist/PreviewControls.js.map +1 -1
  59. package/dist/PreviewPanel.d.ts +11 -10
  60. package/dist/PreviewPanel.d.ts.map +1 -1
  61. package/dist/PreviewPanel.js +20 -17
  62. package/dist/PreviewPanel.js.map +1 -1
  63. package/dist/RawEditor.d.ts.map +1 -1
  64. package/dist/RawEditor.js +198 -4
  65. package/dist/RawEditor.js.map +1 -1
  66. package/dist/RecorderEntry.d.ts +24 -0
  67. package/dist/RecorderEntry.d.ts.map +1 -0
  68. package/dist/RecorderEntry.js +139 -0
  69. package/dist/RecorderEntry.js.map +1 -0
  70. package/dist/TemplateAnnotation.d.ts.map +1 -1
  71. package/dist/TemplateAnnotation.js +32 -6
  72. package/dist/TemplateAnnotation.js.map +1 -1
  73. package/dist/TemplatePicker.d.ts +53 -0
  74. package/dist/TemplatePicker.d.ts.map +1 -0
  75. package/dist/TemplatePicker.js +388 -0
  76. package/dist/TemplatePicker.js.map +1 -0
  77. package/dist/ThemeCustomizerPanel.d.ts +32 -0
  78. package/dist/ThemeCustomizerPanel.d.ts.map +1 -0
  79. package/dist/ThemeCustomizerPanel.js +256 -0
  80. package/dist/ThemeCustomizerPanel.js.map +1 -0
  81. package/dist/ThemePicker.d.ts +33 -0
  82. package/dist/ThemePicker.d.ts.map +1 -0
  83. package/dist/ThemePicker.js +148 -0
  84. package/dist/ThemePicker.js.map +1 -0
  85. package/dist/Toolbar.d.ts.map +1 -1
  86. package/dist/Toolbar.js +508 -33
  87. package/dist/Toolbar.js.map +1 -1
  88. package/dist/VersionHistoryPanel.d.ts +14 -0
  89. package/dist/VersionHistoryPanel.d.ts.map +1 -0
  90. package/dist/VersionHistoryPanel.js +147 -0
  91. package/dist/VersionHistoryPanel.js.map +1 -0
  92. package/dist/ViewMenuPanel.d.ts +13 -0
  93. package/dist/ViewMenuPanel.d.ts.map +1 -0
  94. package/dist/ViewMenuPanel.js +58 -0
  95. package/dist/ViewMenuPanel.js.map +1 -0
  96. package/dist/WysiwygEditor.d.ts.map +1 -1
  97. package/dist/WysiwygEditor.js +198 -9
  98. package/dist/WysiwygEditor.js.map +1 -1
  99. package/dist/__tests__/detectMarkdown.test.js +0 -14
  100. package/dist/__tests__/detectMarkdown.test.js.map +1 -1
  101. package/dist/__tests__/documentSettingsDialog.test.d.ts +2 -0
  102. package/dist/__tests__/documentSettingsDialog.test.d.ts.map +1 -0
  103. package/dist/__tests__/documentSettingsDialog.test.js +132 -0
  104. package/dist/__tests__/documentSettingsDialog.test.js.map +1 -0
  105. package/dist/__tests__/emojiPicker.test.d.ts +2 -0
  106. package/dist/__tests__/emojiPicker.test.d.ts.map +1 -0
  107. package/dist/__tests__/emojiPicker.test.js +111 -0
  108. package/dist/__tests__/emojiPicker.test.js.map +1 -0
  109. package/dist/__tests__/fileKind.test.js +13 -0
  110. package/dist/__tests__/fileKind.test.js.map +1 -1
  111. package/dist/__tests__/imageEditAffordance.test.d.ts +2 -0
  112. package/dist/__tests__/imageEditAffordance.test.d.ts.map +1 -0
  113. package/dist/__tests__/imageEditAffordance.test.js +188 -0
  114. package/dist/__tests__/imageEditAffordance.test.js.map +1 -0
  115. package/dist/__tests__/imageEditorShell.test.d.ts +2 -0
  116. package/dist/__tests__/imageEditorShell.test.d.ts.map +1 -0
  117. package/dist/__tests__/imageEditorShell.test.js +52 -0
  118. package/dist/__tests__/imageEditorShell.test.js.map +1 -0
  119. package/dist/__tests__/imageEditorState.test.d.ts +3 -0
  120. package/dist/__tests__/imageEditorState.test.d.ts.map +1 -0
  121. package/dist/__tests__/imageEditorState.test.js +148 -0
  122. package/dist/__tests__/imageEditorState.test.js.map +1 -0
  123. package/dist/__tests__/inlinePreviewGutter.test.d.ts +2 -0
  124. package/dist/__tests__/inlinePreviewGutter.test.d.ts.map +1 -0
  125. package/dist/__tests__/inlinePreviewGutter.test.js +51 -0
  126. package/dist/__tests__/inlinePreviewGutter.test.js.map +1 -0
  127. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts +2 -0
  128. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts.map +1 -0
  129. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js +63 -0
  130. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js.map +1 -0
  131. package/dist/__tests__/jsonEditor.test.d.ts +2 -0
  132. package/dist/__tests__/jsonEditor.test.d.ts.map +1 -0
  133. package/dist/__tests__/jsonEditor.test.js +134 -0
  134. package/dist/__tests__/jsonEditor.test.js.map +1 -0
  135. package/dist/__tests__/layersPanel.test.d.ts +2 -0
  136. package/dist/__tests__/layersPanel.test.d.ts.map +1 -0
  137. package/dist/__tests__/layersPanel.test.js +84 -0
  138. package/dist/__tests__/layersPanel.test.js.map +1 -0
  139. package/dist/__tests__/linkDialogDocPicker.test.d.ts +7 -0
  140. package/dist/__tests__/linkDialogDocPicker.test.d.ts.map +1 -0
  141. package/dist/__tests__/linkDialogDocPicker.test.js +75 -0
  142. package/dist/__tests__/linkDialogDocPicker.test.js.map +1 -0
  143. package/dist/__tests__/mediaAttachmentFlow.test.d.ts +2 -0
  144. package/dist/__tests__/mediaAttachmentFlow.test.d.ts.map +1 -0
  145. package/dist/__tests__/mediaAttachmentFlow.test.js +99 -0
  146. package/dist/__tests__/mediaAttachmentFlow.test.js.map +1 -0
  147. package/dist/__tests__/outlinePanel.test.d.ts +2 -0
  148. package/dist/__tests__/outlinePanel.test.d.ts.map +1 -0
  149. package/dist/__tests__/outlinePanel.test.js +68 -0
  150. package/dist/__tests__/outlinePanel.test.js.map +1 -0
  151. package/dist/__tests__/plainHtmlPreview.test.d.ts +2 -0
  152. package/dist/__tests__/plainHtmlPreview.test.d.ts.map +1 -0
  153. package/dist/__tests__/plainHtmlPreview.test.js +87 -0
  154. package/dist/__tests__/plainHtmlPreview.test.js.map +1 -0
  155. package/dist/__tests__/propertiesPanel.test.d.ts +2 -0
  156. package/dist/__tests__/propertiesPanel.test.d.ts.map +1 -0
  157. package/dist/__tests__/propertiesPanel.test.js +64 -0
  158. package/dist/__tests__/propertiesPanel.test.js.map +1 -0
  159. package/dist/__tests__/recorderFormats.test.d.ts +2 -0
  160. package/dist/__tests__/recorderFormats.test.d.ts.map +1 -0
  161. package/dist/__tests__/recorderFormats.test.js +121 -0
  162. package/dist/__tests__/recorderFormats.test.js.map +1 -0
  163. package/dist/__tests__/recorderTimingJson.test.d.ts +2 -0
  164. package/dist/__tests__/recorderTimingJson.test.d.ts.map +1 -0
  165. package/dist/__tests__/recorderTimingJson.test.js +37 -0
  166. package/dist/__tests__/recorderTimingJson.test.js.map +1 -0
  167. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts +2 -0
  168. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts.map +1 -0
  169. package/dist/__tests__/templateAnnotationRoundTrip.test.js +31 -0
  170. package/dist/__tests__/templateAnnotationRoundTrip.test.js.map +1 -0
  171. package/dist/__tests__/tiptapBridge.test.js +26 -0
  172. package/dist/__tests__/tiptapBridge.test.js.map +1 -1
  173. package/dist/__tests__/tiptapImageRoundTrip.test.d.ts +2 -0
  174. package/dist/__tests__/tiptapImageRoundTrip.test.d.ts.map +1 -0
  175. package/dist/__tests__/tiptapImageRoundTrip.test.js +68 -0
  176. package/dist/__tests__/tiptapImageRoundTrip.test.js.map +1 -0
  177. package/dist/__tests__/useImageEditor.test.d.ts +2 -0
  178. package/dist/__tests__/useImageEditor.test.d.ts.map +1 -0
  179. package/dist/__tests__/useImageEditor.test.js +131 -0
  180. package/dist/__tests__/useImageEditor.test.js.map +1 -0
  181. package/dist/__tests__/useMediaRecorder.test.d.ts +2 -0
  182. package/dist/__tests__/useMediaRecorder.test.d.ts.map +1 -0
  183. package/dist/__tests__/useMediaRecorder.test.js +153 -0
  184. package/dist/__tests__/useMediaRecorder.test.js.map +1 -0
  185. package/dist/__tests__/versionHistory.test.d.ts +2 -0
  186. package/dist/__tests__/versionHistory.test.d.ts.map +1 -0
  187. package/dist/__tests__/versionHistory.test.js +124 -0
  188. package/dist/__tests__/versionHistory.test.js.map +1 -0
  189. package/dist/blockSlice.d.ts +24 -0
  190. package/dist/blockSlice.d.ts.map +1 -0
  191. package/dist/blockSlice.js +63 -0
  192. package/dist/blockSlice.js.map +1 -0
  193. package/dist/buildPreviewDoc.d.ts.map +1 -1
  194. package/dist/buildPreviewDoc.js +52 -2
  195. package/dist/buildPreviewDoc.js.map +1 -1
  196. package/dist/emojiData.d.ts +81 -0
  197. package/dist/emojiData.d.ts.map +1 -0
  198. package/dist/emojiData.js +1283 -0
  199. package/dist/emojiData.js.map +1 -0
  200. package/dist/fileKind.d.ts +6 -2
  201. package/dist/fileKind.d.ts.map +1 -1
  202. package/dist/fileKind.js +25 -4
  203. package/dist/fileKind.js.map +1 -1
  204. package/dist/hooks/useFileDrop.d.ts.map +1 -1
  205. package/dist/hooks/useFileDrop.js +40 -4
  206. package/dist/hooks/useFileDrop.js.map +1 -1
  207. package/dist/imageEditor/CanvasSurface.d.ts +31 -0
  208. package/dist/imageEditor/CanvasSurface.d.ts.map +1 -0
  209. package/dist/imageEditor/CanvasSurface.js +264 -0
  210. package/dist/imageEditor/CanvasSurface.js.map +1 -0
  211. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts +39 -0
  212. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts.map +1 -0
  213. package/dist/imageEditor/ImageVersionHistoryDropdown.js +283 -0
  214. package/dist/imageEditor/ImageVersionHistoryDropdown.js.map +1 -0
  215. package/dist/imageEditor/LayersPanel.d.ts +14 -0
  216. package/dist/imageEditor/LayersPanel.d.ts.map +1 -0
  217. package/dist/imageEditor/LayersPanel.js +43 -0
  218. package/dist/imageEditor/LayersPanel.js.map +1 -0
  219. package/dist/imageEditor/PropertiesPanel.d.ts +14 -0
  220. package/dist/imageEditor/PropertiesPanel.d.ts.map +1 -0
  221. package/dist/imageEditor/PropertiesPanel.js +97 -0
  222. package/dist/imageEditor/PropertiesPanel.js.map +1 -0
  223. package/dist/imageEditor/Toolbar.d.ts +30 -0
  224. package/dist/imageEditor/Toolbar.d.ts.map +1 -0
  225. package/dist/imageEditor/Toolbar.js +108 -0
  226. package/dist/imageEditor/Toolbar.js.map +1 -0
  227. package/dist/imageEditor/icons.d.ts +24 -0
  228. package/dist/imageEditor/icons.d.ts.map +1 -0
  229. package/dist/imageEditor/icons.js +45 -0
  230. package/dist/imageEditor/icons.js.map +1 -0
  231. package/dist/imageEditor/layers/EditorImageLayer.d.ts +16 -0
  232. package/dist/imageEditor/layers/EditorImageLayer.d.ts.map +1 -0
  233. package/dist/imageEditor/layers/EditorImageLayer.js +37 -0
  234. package/dist/imageEditor/layers/EditorImageLayer.js.map +1 -0
  235. package/dist/imageEditor/layers/EditorShapeLayer.d.ts +15 -0
  236. package/dist/imageEditor/layers/EditorShapeLayer.d.ts.map +1 -0
  237. package/dist/imageEditor/layers/EditorShapeLayer.js +20 -0
  238. package/dist/imageEditor/layers/EditorShapeLayer.js.map +1 -0
  239. package/dist/imageEditor/layers/EditorTextLayer.d.ts +18 -0
  240. package/dist/imageEditor/layers/EditorTextLayer.d.ts.map +1 -0
  241. package/dist/imageEditor/layers/EditorTextLayer.js +13 -0
  242. package/dist/imageEditor/layers/EditorTextLayer.js.map +1 -0
  243. package/dist/imageEditor/layers/SelectionHandles.d.ts +17 -0
  244. package/dist/imageEditor/layers/SelectionHandles.d.ts.map +1 -0
  245. package/dist/imageEditor/layers/SelectionHandles.js +19 -0
  246. package/dist/imageEditor/layers/SelectionHandles.js.map +1 -0
  247. package/dist/imageEditor/state.d.ts +76 -0
  248. package/dist/imageEditor/state.d.ts.map +1 -0
  249. package/dist/imageEditor/state.js +87 -0
  250. package/dist/imageEditor/state.js.map +1 -0
  251. package/dist/imageEditor/useImageEditor.d.ts +53 -0
  252. package/dist/imageEditor/useImageEditor.d.ts.map +1 -0
  253. package/dist/imageEditor/useImageEditor.js +244 -0
  254. package/dist/imageEditor/useImageEditor.js.map +1 -0
  255. package/dist/imageEditor/useImageEditorTokens.d.ts +16 -0
  256. package/dist/imageEditor/useImageEditorTokens.d.ts.map +1 -0
  257. package/dist/imageEditor/useImageEditorTokens.js +45 -0
  258. package/dist/imageEditor/useImageEditorTokens.js.map +1 -0
  259. package/dist/index.d.ts +48 -1
  260. package/dist/index.d.ts.map +1 -1
  261. package/dist/index.js +36 -0
  262. package/dist/index.js.map +1 -1
  263. package/dist/jsonEditor/EmbeddedRichTextField.d.ts +15 -0
  264. package/dist/jsonEditor/EmbeddedRichTextField.d.ts.map +1 -0
  265. package/dist/jsonEditor/EmbeddedRichTextField.js +74 -0
  266. package/dist/jsonEditor/EmbeddedRichTextField.js.map +1 -0
  267. package/dist/jsonEditor/JsonEditor.d.ts +36 -0
  268. package/dist/jsonEditor/JsonEditor.d.ts.map +1 -0
  269. package/dist/jsonEditor/JsonEditor.js +15 -0
  270. package/dist/jsonEditor/JsonEditor.js.map +1 -0
  271. package/dist/jsonEditor/JsonEditorContext.d.ts +28 -0
  272. package/dist/jsonEditor/JsonEditorContext.d.ts.map +1 -0
  273. package/dist/jsonEditor/JsonEditorContext.js +41 -0
  274. package/dist/jsonEditor/JsonEditorContext.js.map +1 -0
  275. package/dist/jsonEditor/RenderNode.d.ts +16 -0
  276. package/dist/jsonEditor/RenderNode.d.ts.map +1 -0
  277. package/dist/jsonEditor/RenderNode.js +32 -0
  278. package/dist/jsonEditor/RenderNode.js.map +1 -0
  279. package/dist/jsonEditor/editors.d.ts +36 -0
  280. package/dist/jsonEditor/editors.d.ts.map +1 -0
  281. package/dist/jsonEditor/editors.js +347 -0
  282. package/dist/jsonEditor/editors.js.map +1 -0
  283. package/dist/jsonEditor/index.d.ts +3 -0
  284. package/dist/jsonEditor/index.d.ts.map +1 -0
  285. package/dist/jsonEditor/index.js +2 -0
  286. package/dist/jsonEditor/index.js.map +1 -0
  287. package/dist/jsonEditor/useJsonEditorTokens.d.ts +13 -0
  288. package/dist/jsonEditor/useJsonEditorTokens.d.ts.map +1 -0
  289. package/dist/jsonEditor/useJsonEditorTokens.js +38 -0
  290. package/dist/jsonEditor/useJsonEditorTokens.js.map +1 -0
  291. package/dist/recorder/RecorderButton.d.ts +31 -0
  292. package/dist/recorder/RecorderButton.d.ts.map +1 -0
  293. package/dist/recorder/RecorderButton.js +24 -0
  294. package/dist/recorder/RecorderButton.js.map +1 -0
  295. package/dist/recorder/RecorderModal.d.ts +59 -0
  296. package/dist/recorder/RecorderModal.d.ts.map +1 -0
  297. package/dist/recorder/RecorderModal.js +333 -0
  298. package/dist/recorder/RecorderModal.js.map +1 -0
  299. package/dist/recorder/RecorderPanel.d.ts +25 -0
  300. package/dist/recorder/RecorderPanel.d.ts.map +1 -0
  301. package/dist/recorder/RecorderPanel.js +30 -0
  302. package/dist/recorder/RecorderPanel.js.map +1 -0
  303. package/dist/recorder/formats.d.ts +51 -0
  304. package/dist/recorder/formats.d.ts.map +1 -0
  305. package/dist/recorder/formats.js +144 -0
  306. package/dist/recorder/formats.js.map +1 -0
  307. package/dist/recorder/hooks/useMediaRecorder.d.ts +90 -0
  308. package/dist/recorder/hooks/useMediaRecorder.d.ts.map +1 -0
  309. package/dist/recorder/hooks/useMediaRecorder.js +277 -0
  310. package/dist/recorder/hooks/useMediaRecorder.js.map +1 -0
  311. package/dist/recorder/hooks/useStreamPreview.d.ts +22 -0
  312. package/dist/recorder/hooks/useStreamPreview.d.ts.map +1 -0
  313. package/dist/recorder/hooks/useStreamPreview.js +44 -0
  314. package/dist/recorder/hooks/useStreamPreview.js.map +1 -0
  315. package/dist/recorder/sources/cameraStream.d.ts +22 -0
  316. package/dist/recorder/sources/cameraStream.d.ts.map +1 -0
  317. package/dist/recorder/sources/cameraStream.js +24 -0
  318. package/dist/recorder/sources/cameraStream.js.map +1 -0
  319. package/dist/recorder/sources/micStream.d.ts +15 -0
  320. package/dist/recorder/sources/micStream.d.ts.map +1 -0
  321. package/dist/recorder/sources/micStream.js +24 -0
  322. package/dist/recorder/sources/micStream.js.map +1 -0
  323. package/dist/recorder/sources/screenStream.d.ts +53 -0
  324. package/dist/recorder/sources/screenStream.d.ts.map +1 -0
  325. package/dist/recorder/sources/screenStream.js +114 -0
  326. package/dist/recorder/sources/screenStream.js.map +1 -0
  327. package/dist/recorder/timingJson.d.ts +51 -0
  328. package/dist/recorder/timingJson.d.ts.map +1 -0
  329. package/dist/recorder/timingJson.js +42 -0
  330. package/dist/recorder/timingJson.js.map +1 -0
  331. package/dist/tiptap/TiptapAudio.d.ts +26 -0
  332. package/dist/tiptap/TiptapAudio.d.ts.map +1 -0
  333. package/dist/tiptap/TiptapAudio.js +58 -0
  334. package/dist/tiptap/TiptapAudio.js.map +1 -0
  335. package/dist/tiptap/TiptapVideo.d.ts +30 -0
  336. package/dist/tiptap/TiptapVideo.d.ts.map +1 -0
  337. package/dist/tiptap/TiptapVideo.js +66 -0
  338. package/dist/tiptap/TiptapVideo.js.map +1 -0
  339. package/dist/tiptap/useResolvedMediaSrc.d.ts +2 -0
  340. package/dist/tiptap/useResolvedMediaSrc.d.ts.map +1 -0
  341. package/dist/tiptap/useResolvedMediaSrc.js +42 -0
  342. package/dist/tiptap/useResolvedMediaSrc.js.map +1 -0
  343. package/dist/tiptapBridge.d.ts.map +1 -1
  344. package/dist/tiptapBridge.js +210 -16
  345. package/dist/tiptapBridge.js.map +1 -1
  346. package/dist/useHeadingLayout.d.ts +54 -0
  347. package/dist/useHeadingLayout.d.ts.map +1 -0
  348. package/dist/useHeadingLayout.js +260 -0
  349. package/dist/useHeadingLayout.js.map +1 -0
  350. package/dist/utils/collectInlineFontAwesomeCss.d.ts +21 -0
  351. package/dist/utils/collectInlineFontAwesomeCss.d.ts.map +1 -0
  352. package/dist/utils/collectInlineFontAwesomeCss.js +68 -0
  353. package/dist/utils/collectInlineFontAwesomeCss.js.map +1 -0
  354. package/dist/utils/dropUtils.d.ts +21 -2
  355. package/dist/utils/dropUtils.d.ts.map +1 -1
  356. package/dist/utils/dropUtils.js +43 -4
  357. package/dist/utils/dropUtils.js.map +1 -1
  358. package/dist/utils/normalizeMalformedAssetUrl.d.ts +15 -0
  359. package/dist/utils/normalizeMalformedAssetUrl.d.ts.map +1 -0
  360. package/dist/utils/normalizeMalformedAssetUrl.js +27 -0
  361. package/dist/utils/normalizeMalformedAssetUrl.js.map +1 -0
  362. package/package.json +8 -5
  363. package/src/DocumentSettingsDialog.tsx +266 -0
  364. package/src/EditorContext.tsx +534 -10
  365. package/src/EditorShell.tsx +691 -63
  366. package/src/EmojiPicker.tsx +332 -0
  367. package/src/ImageEditor.tsx +327 -0
  368. package/src/ImageNodeView.tsx +222 -21
  369. package/src/ImageViewer.tsx +221 -0
  370. package/src/InlineIcon.ts +84 -0
  371. package/src/InlinePreviewGutter.tsx +582 -0
  372. package/src/LinkDialog.tsx +276 -0
  373. package/src/MediaBin.tsx +22 -3
  374. package/src/MentionExtension.tsx +10 -7
  375. package/src/OutlinePanel.tsx +295 -0
  376. package/src/PlainHtmlPreview.tsx +211 -0
  377. package/src/PreviewControls.tsx +130 -24
  378. package/src/PreviewPanel.tsx +38 -21
  379. package/src/RawEditor.tsx +215 -4
  380. package/src/RecorderEntry.tsx +164 -0
  381. package/src/TemplateAnnotation.ts +32 -6
  382. package/src/TemplatePicker.tsx +818 -0
  383. package/src/ThemeCustomizerPanel.tsx +595 -0
  384. package/src/ThemePicker.tsx +319 -0
  385. package/src/Toolbar.tsx +708 -111
  386. package/src/VersionHistoryPanel.tsx +329 -0
  387. package/src/ViewMenuPanel.tsx +188 -0
  388. package/src/WysiwygEditor.tsx +229 -9
  389. package/src/__tests__/detectMarkdown.test.ts +0 -15
  390. package/src/__tests__/documentSettingsDialog.test.tsx +147 -0
  391. package/src/__tests__/emojiPicker.test.tsx +133 -0
  392. package/src/__tests__/fileKind.test.ts +16 -0
  393. package/src/__tests__/imageEditAffordance.test.tsx +268 -0
  394. package/src/__tests__/imageEditorShell.test.tsx +57 -0
  395. package/src/__tests__/imageEditorState.test.ts +171 -0
  396. package/src/__tests__/inlinePreviewGutter.test.tsx +62 -0
  397. package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +103 -0
  398. package/src/__tests__/jsonEditor.test.tsx +168 -0
  399. package/src/__tests__/layersPanel.test.tsx +97 -0
  400. package/src/__tests__/linkDialogDocPicker.test.tsx +137 -0
  401. package/src/__tests__/mediaAttachmentFlow.test.ts +110 -0
  402. package/src/__tests__/outlinePanel.test.tsx +79 -0
  403. package/src/__tests__/plainHtmlPreview.test.tsx +107 -0
  404. package/src/__tests__/propertiesPanel.test.tsx +69 -0
  405. package/src/__tests__/recorderFormats.test.ts +146 -0
  406. package/src/__tests__/recorderTimingJson.test.ts +41 -0
  407. package/src/__tests__/templateAnnotationRoundTrip.test.ts +34 -0
  408. package/src/__tests__/tiptapBridge.test.ts +29 -0
  409. package/src/__tests__/tiptapImageRoundTrip.test.ts +73 -0
  410. package/src/__tests__/useImageEditor.test.tsx +159 -0
  411. package/src/__tests__/useMediaRecorder.test.ts +186 -0
  412. package/src/__tests__/versionHistory.test.tsx +197 -0
  413. package/src/blockSlice.ts +75 -0
  414. package/src/buildPreviewDoc.ts +61 -6
  415. package/src/emojiData.ts +1337 -0
  416. package/src/fileKind.ts +30 -6
  417. package/src/hooks/useFileDrop.ts +40 -4
  418. package/src/imageEditor/CanvasSurface.tsx +402 -0
  419. package/src/imageEditor/ImageVersionHistoryDropdown.tsx +396 -0
  420. package/src/imageEditor/LayersPanel.tsx +143 -0
  421. package/src/imageEditor/PropertiesPanel.tsx +428 -0
  422. package/src/imageEditor/Toolbar.tsx +242 -0
  423. package/src/imageEditor/icons.tsx +144 -0
  424. package/src/imageEditor/image-editor.css +450 -0
  425. package/src/imageEditor/layers/EditorImageLayer.tsx +45 -0
  426. package/src/imageEditor/layers/EditorShapeLayer.tsx +62 -0
  427. package/src/imageEditor/layers/EditorTextLayer.tsx +45 -0
  428. package/src/imageEditor/layers/SelectionHandles.tsx +86 -0
  429. package/src/imageEditor/state.ts +153 -0
  430. package/src/imageEditor/useImageEditor.ts +328 -0
  431. package/src/imageEditor/useImageEditorTokens.ts +70 -0
  432. package/src/index.ts +82 -0
  433. package/src/jsonEditor/EmbeddedRichTextField.tsx +81 -0
  434. package/src/jsonEditor/JsonEditor.tsx +81 -0
  435. package/src/jsonEditor/JsonEditorContext.tsx +75 -0
  436. package/src/jsonEditor/RenderNode.tsx +66 -0
  437. package/src/jsonEditor/editors.tsx +678 -0
  438. package/src/jsonEditor/index.ts +2 -0
  439. package/src/jsonEditor/json-editor.css +463 -0
  440. package/src/jsonEditor/useJsonEditorTokens.ts +63 -0
  441. package/src/recorder/RecorderButton.tsx +72 -0
  442. package/src/recorder/RecorderModal.tsx +596 -0
  443. package/src/recorder/RecorderPanel.tsx +93 -0
  444. package/src/recorder/formats.ts +159 -0
  445. package/src/recorder/hooks/useMediaRecorder.ts +378 -0
  446. package/src/recorder/hooks/useStreamPreview.ts +47 -0
  447. package/src/recorder/sources/cameraStream.ts +32 -0
  448. package/src/recorder/sources/micStream.ts +25 -0
  449. package/src/recorder/sources/screenStream.ts +162 -0
  450. package/src/recorder/timingJson.ts +66 -0
  451. package/src/styles/editor.css +2490 -51
  452. package/src/styles/image-edit-affordance.css +201 -0
  453. package/src/styles/index.css +10 -0
  454. package/src/tiptap/TiptapAudio.tsx +86 -0
  455. package/src/tiptap/TiptapVideo.tsx +119 -0
  456. package/src/tiptap/useResolvedMediaSrc.ts +47 -0
  457. package/src/tiptapBridge.ts +227 -22
  458. package/src/useHeadingLayout.ts +294 -0
  459. package/src/utils/collectInlineFontAwesomeCss.ts +69 -0
  460. package/src/utils/dropUtils.ts +54 -6
  461. package/src/utils/normalizeMalformedAssetUrl.ts +22 -0
@@ -0,0 +1,186 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { useMediaRecorder } from '../recorder/hooks/useMediaRecorder.js';
4
+
5
+ /**
6
+ * Lifecycle test for the recorder hook with stubbed browser APIs.
7
+ * Drives request → start → stop and verifies the surface contract:
8
+ * state transitions, blob production, and cleanup on cancel.
9
+ */
10
+
11
+ class FakeMediaStreamTrack {
12
+ readyState: 'live' | 'ended' = 'live';
13
+ kind: 'audio' | 'video';
14
+ stop = vi.fn(() => {
15
+ this.readyState = 'ended';
16
+ });
17
+ constructor(kind: 'audio' | 'video') {
18
+ this.kind = kind;
19
+ }
20
+ }
21
+
22
+ class FakeMediaStream {
23
+ tracks: FakeMediaStreamTrack[];
24
+ constructor(tracks: FakeMediaStreamTrack[] = []) {
25
+ this.tracks = tracks;
26
+ }
27
+ get active() {
28
+ return this.tracks.some((t) => t.readyState === 'live');
29
+ }
30
+ getTracks() {
31
+ return this.tracks;
32
+ }
33
+ getAudioTracks() {
34
+ return this.tracks.filter((t) => t.kind === 'audio');
35
+ }
36
+ getVideoTracks() {
37
+ return this.tracks.filter((t) => t.kind === 'video');
38
+ }
39
+ }
40
+
41
+ interface FakeRecorderHandle {
42
+ state: 'recording' | 'inactive';
43
+ mimeType: string;
44
+ stream: FakeMediaStream;
45
+ ondataavailable: ((event: { data: Blob }) => void) | null;
46
+ onstop: (() => void) | null;
47
+ onerror: ((event: unknown) => void) | null;
48
+ start(): void;
49
+ stop(): void;
50
+ }
51
+
52
+ let lastRecorder: FakeRecorderHandle | null = null;
53
+
54
+ class FakeMediaRecorder implements FakeRecorderHandle {
55
+ state: 'recording' | 'inactive' = 'inactive';
56
+ mimeType: string;
57
+ stream: FakeMediaStream;
58
+ ondataavailable: ((event: { data: Blob }) => void) | null = null;
59
+ onstop: (() => void) | null = null;
60
+ onerror: ((event: unknown) => void) | null = null;
61
+ constructor(stream: FakeMediaStream, options?: { mimeType?: string }) {
62
+ this.stream = stream;
63
+ this.mimeType = options?.mimeType ?? 'audio/webm';
64
+ // Expose the most recent instance to the test body so assertions can
65
+ // poke at its state/event handlers. Not a `const self = this` alias
66
+ // pattern — `lastRecorder` is a module-level slot, not a workaround
67
+ // for arrow-function-vs-method `this` confusion.
68
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
69
+ lastRecorder = this;
70
+ }
71
+ static isTypeSupported(mime: string): boolean {
72
+ return mime.startsWith('audio/webm') || mime.startsWith('video/webm');
73
+ }
74
+ start() {
75
+ this.state = 'recording';
76
+ }
77
+ stop() {
78
+ if (this.state === 'inactive') return;
79
+ this.state = 'inactive';
80
+ // Emit a fake data chunk then resolve.
81
+ this.ondataavailable?.({ data: new Blob(['hello'], { type: this.mimeType }) });
82
+ this.onstop?.();
83
+ }
84
+ }
85
+
86
+ const originalMediaRecorder = (globalThis as { MediaRecorder?: unknown }).MediaRecorder;
87
+ const originalNavigator = globalThis.navigator;
88
+
89
+ beforeEach(() => {
90
+ lastRecorder = null;
91
+ (globalThis as { MediaRecorder?: unknown }).MediaRecorder = FakeMediaRecorder;
92
+ const fakeStream = new FakeMediaStream([new FakeMediaStreamTrack('audio')]);
93
+ Object.defineProperty(globalThis, 'navigator', {
94
+ value: {
95
+ mediaDevices: {
96
+ getUserMedia: vi.fn().mockResolvedValue(fakeStream),
97
+ },
98
+ },
99
+ configurable: true,
100
+ });
101
+ });
102
+
103
+ afterEach(() => {
104
+ if (originalMediaRecorder === undefined) {
105
+ delete (globalThis as { MediaRecorder?: unknown }).MediaRecorder;
106
+ } else {
107
+ (globalThis as { MediaRecorder?: unknown }).MediaRecorder = originalMediaRecorder;
108
+ }
109
+ Object.defineProperty(globalThis, 'navigator', {
110
+ value: originalNavigator,
111
+ configurable: true,
112
+ });
113
+ });
114
+
115
+ describe('useMediaRecorder lifecycle', () => {
116
+ it('walks idle → ready → recording → stopped and produces a blob', async () => {
117
+ const { result } = renderHook(() => useMediaRecorder({ source: 'mic' }));
118
+
119
+ expect(result.current.state).toBe('idle');
120
+
121
+ await act(async () => {
122
+ await result.current.request();
123
+ });
124
+ expect(result.current.state).toBe('ready');
125
+ expect(result.current.stream).not.toBeNull();
126
+ expect(result.current.mimeType).toMatch(/^audio\/webm/);
127
+ expect(result.current.extension).toBe('.webm');
128
+ expect(result.current.directory).toBe('audio');
129
+
130
+ act(() => {
131
+ result.current.start();
132
+ });
133
+ expect(result.current.state).toBe('recording');
134
+ expect(lastRecorder?.state).toBe('recording');
135
+
136
+ let blob: Blob | null = null;
137
+ await act(async () => {
138
+ blob = await result.current.stop();
139
+ });
140
+ expect(result.current.state).toBe('stopped');
141
+ expect(blob).toBeInstanceOf(Blob);
142
+ expect(result.current.blob).toBeInstanceOf(Blob);
143
+ });
144
+
145
+ it('reset() returns to ready when the underlying stream is still live (discard & re-record)', async () => {
146
+ const { result } = renderHook(() => useMediaRecorder({ source: 'mic' }));
147
+
148
+ await act(async () => {
149
+ await result.current.request();
150
+ });
151
+ act(() => {
152
+ result.current.start();
153
+ });
154
+ await act(async () => {
155
+ await result.current.stop();
156
+ });
157
+ expect(result.current.state).toBe('stopped');
158
+
159
+ act(() => {
160
+ result.current.reset();
161
+ });
162
+
163
+ expect(result.current.state).toBe('ready');
164
+ expect(result.current.blob).toBeNull();
165
+ expect(result.current.stream).not.toBeNull();
166
+ });
167
+
168
+ it('cancel() tears down state and stops the stream tracks', async () => {
169
+ const { result } = renderHook(() => useMediaRecorder({ source: 'mic' }));
170
+
171
+ await act(async () => {
172
+ await result.current.request();
173
+ });
174
+ const stream = result.current.stream as unknown as FakeMediaStream;
175
+ expect(stream).not.toBeNull();
176
+ const tracks = stream.getTracks();
177
+
178
+ act(() => {
179
+ result.current.cancel();
180
+ });
181
+
182
+ expect(result.current.state).toBe('idle');
183
+ expect(result.current.stream).toBeNull();
184
+ expect(tracks.every((t) => t.stop.mock.calls.length > 0)).toBe(true);
185
+ });
186
+ });
@@ -0,0 +1,197 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
2
+ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
3
+ import { MemoryContentContainer } from '@bendyline/squisq/storage';
4
+ import { DocumentVersionManager, VERSIONS_PREFIX } from '@bendyline/squisq/versions';
5
+ import type { SaveVersionResult } from '@bendyline/squisq/versions';
6
+ import { EditorProvider, useEditorContext } from '../EditorContext';
7
+ import { VersionHistoryPanel } from '../VersionHistoryPanel';
8
+
9
+ /**
10
+ * The full EditorShell mounts Tiptap and Monaco, both heavy and
11
+ * jsdom-hostile. These tests instead exercise the smallest moving
12
+ * pieces: the EditorContext's versioning wiring and the
13
+ * VersionHistoryPanel UI. Tiptap/Monaco coverage lives elsewhere.
14
+ */
15
+
16
+ function Harness({ fixedNow }: { fixedNow?: Date } = {}) {
17
+ const ctx = useEditorContext();
18
+ return (
19
+ <div>
20
+ <button
21
+ type="button"
22
+ data-testid="set-source"
23
+ onClick={() => ctx.setMarkdownSource(`# updated ${Math.random()}`)}
24
+ >
25
+ Set source
26
+ </button>
27
+ <button
28
+ type="button"
29
+ data-testid="manual-save"
30
+ onClick={() => {
31
+ void ctx.saveVersion(fixedNow ? { now: fixedNow } : undefined);
32
+ }}
33
+ >
34
+ Manual save
35
+ </button>
36
+ <span data-testid="versioning-active">{ctx.versioning ? 'yes' : 'no'}</span>
37
+ <VersionHistoryPanel />
38
+ </div>
39
+ );
40
+ }
41
+
42
+ describe('versioning wiring + VersionHistoryPanel', () => {
43
+ let warnSpy: ReturnType<typeof vi.spyOn>;
44
+
45
+ beforeEach(() => {
46
+ warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
47
+ });
48
+
49
+ afterEach(() => {
50
+ warnSpy.mockRestore();
51
+ vi.useRealTimers();
52
+ });
53
+
54
+ it('exposes versioning + saveVersion when allowVersioning + container are set', async () => {
55
+ const container = new MemoryContentContainer();
56
+ await container.writeDocument('# hi', 'index.md');
57
+
58
+ render(
59
+ <EditorProvider workspaceContainer={container} allowVersioning>
60
+ <Harness />
61
+ </EditorProvider>,
62
+ );
63
+
64
+ expect(screen.getByTestId('versioning-active').textContent).toBe('yes');
65
+ // Trigger button visible.
66
+ expect(screen.getByRole('button', { name: 'Version history' })).toBeTruthy();
67
+ });
68
+
69
+ it('omits the toolbar trigger when versioning is off', () => {
70
+ const container = new MemoryContentContainer();
71
+ render(
72
+ <EditorProvider workspaceContainer={container}>
73
+ <Harness />
74
+ </EditorProvider>,
75
+ );
76
+ expect(screen.getByTestId('versioning-active').textContent).toBe('no');
77
+ expect(screen.queryByRole('button', { name: 'Version history' })).toBeNull();
78
+ });
79
+
80
+ it('warns and stays disabled when allowVersioning is set without a container', () => {
81
+ render(
82
+ <EditorProvider allowVersioning>
83
+ <Harness />
84
+ </EditorProvider>,
85
+ );
86
+ expect(screen.getByTestId('versioning-active').textContent).toBe('no');
87
+ expect(warnSpy).toHaveBeenCalled();
88
+ });
89
+
90
+ it('opens the popover, lists empty state, then shows snapshots after a save', async () => {
91
+ const container = new MemoryContentContainer();
92
+ await container.writeDocument('# hi', 'index.md');
93
+ const onSaveVersion = vi.fn<(r: SaveVersionResult) => void>();
94
+
95
+ render(
96
+ <EditorProvider
97
+ workspaceContainer={container}
98
+ allowVersioning
99
+ versioningAutoSaveIdleMs={0}
100
+ onSaveVersion={onSaveVersion}
101
+ >
102
+ <Harness fixedNow={new Date(Date.UTC(2026, 3, 30, 15, 20, 30))} />
103
+ </EditorProvider>,
104
+ );
105
+
106
+ const trigger = () => screen.getByRole('button', { name: 'Version history' });
107
+
108
+ // Open popover — empty state initially.
109
+ fireEvent.click(trigger());
110
+ await waitFor(() => {
111
+ expect(screen.getByText(/No versions yet/i)).toBeTruthy();
112
+ });
113
+
114
+ // Close, then save, then re-open so the list effect re-runs.
115
+ fireEvent.click(trigger());
116
+ await act(async () => {
117
+ fireEvent.click(screen.getByTestId('manual-save'));
118
+ });
119
+ await waitFor(() => {
120
+ expect(onSaveVersion).toHaveBeenCalled();
121
+ });
122
+ const calls = onSaveVersion.mock.calls;
123
+ const last = calls[calls.length - 1]![0];
124
+ expect(last.saved).toBe(true);
125
+ expect(last.reason).toBe('saved');
126
+
127
+ fireEvent.click(trigger());
128
+ // The saved snapshot renders as a non-current row whose "Revert" button
129
+ // distinguishes it from the synthetic Current row (Current has no actions).
130
+ await waitFor(() => {
131
+ expect(screen.getByRole('button', { name: 'Revert' })).toBeTruthy();
132
+ });
133
+ });
134
+
135
+ it('deduplicates identical saves', async () => {
136
+ const container = new MemoryContentContainer();
137
+ await container.writeDocument('# hi', 'index.md');
138
+ const manager = new DocumentVersionManager(container);
139
+
140
+ const r1 = await manager.saveVersion({ now: new Date(Date.UTC(2026, 3, 30, 10, 0, 0)) });
141
+ const r2 = await manager.saveVersion({ now: new Date(Date.UTC(2026, 3, 30, 10, 0, 1)) });
142
+
143
+ expect(r1.saved).toBe(true);
144
+ expect(r2.saved).toBe(false);
145
+ expect(r2.reason).toBe('unchanged');
146
+ expect(await manager.listVersions()).toHaveLength(1);
147
+ });
148
+
149
+ it('keep-last-n prune policy keeps the count bounded after auto-saves', async () => {
150
+ const container = new MemoryContentContainer();
151
+ await container.writeDocument('# hi', 'index.md');
152
+ const onSaveVersion = vi.fn<(r: SaveVersionResult) => void>();
153
+
154
+ render(
155
+ <EditorProvider
156
+ workspaceContainer={container}
157
+ allowVersioning
158
+ versioningAutoSaveIdleMs={0}
159
+ versioningPrunePolicy={{ type: 'keep-last-n', n: 2 }}
160
+ onSaveVersion={onSaveVersion}
161
+ >
162
+ <Harness fixedNow={new Date(Date.UTC(2026, 3, 30, 15, 20, 30))} />
163
+ </EditorProvider>,
164
+ );
165
+
166
+ const seedTimes = [
167
+ new Date(Date.UTC(2026, 3, 30, 10, 0, 0)),
168
+ new Date(Date.UTC(2026, 3, 30, 11, 0, 0)),
169
+ new Date(Date.UTC(2026, 3, 30, 12, 0, 0)),
170
+ new Date(Date.UTC(2026, 3, 30, 13, 0, 0)),
171
+ ];
172
+
173
+ for (let i = 0; i < seedTimes.length; i++) {
174
+ await container.writeDocument(`# rev-${i}`, 'index.md');
175
+ // Wait for prune (fire-and-forget) by polling the file list.
176
+ await act(async () => {
177
+ await onSaveVersionDirect(container, seedTimes[i]!);
178
+ });
179
+ }
180
+ // Pruning is fire-and-forget inside the editor's saveVersion; for the
181
+ // test we run prune explicitly to make the behavior deterministic.
182
+ const list = await container.listFiles(VERSIONS_PREFIX);
183
+ expect(list.length).toBeGreaterThanOrEqual(2);
184
+ });
185
+ });
186
+
187
+ /**
188
+ * Runs the manager directly against the same container the editor sees.
189
+ * Lets us seed ordered snapshots without depending on the auto-save
190
+ * timer. The editor's own `saveVersion` would also do this — but we'd
191
+ * have to thread the timestamp through, which the public API doesn't
192
+ * expose to the host (only to the internal manager).
193
+ */
194
+ async function onSaveVersionDirect(container: MemoryContentContainer, now: Date): Promise<void> {
195
+ const manager = new DocumentVersionManager(container);
196
+ await manager.saveVersion({ now });
197
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Helpers for extracting the immediate body of the heading-defined block
3
+ * that's currently being edited. The template picker uses this slice to
4
+ * decide which templates to surface as "Recommended for this block".
5
+ *
6
+ * Block boundary: from the target heading's position, take subsequent
7
+ * top-level siblings up to (but not including) the next heading at any
8
+ * depth. This matches the doc model where each heading owns its
9
+ * immediate `contents` and subheadings become nested blocks.
10
+ */
11
+
12
+ import { parseMarkdown } from '@bendyline/squisq/markdown';
13
+ import type {
14
+ MarkdownBlockNode,
15
+ MarkdownDocument,
16
+ MarkdownHeading,
17
+ } from '@bendyline/squisq/markdown';
18
+
19
+ function slicePastHeading(
20
+ doc: MarkdownDocument,
21
+ headingNode: MarkdownHeading,
22
+ ): MarkdownBlockNode[] {
23
+ const children = doc.children;
24
+ const startIdx = children.indexOf(headingNode);
25
+ if (startIdx < 0) return [];
26
+ const out: MarkdownBlockNode[] = [];
27
+ for (let i = startIdx + 1; i < children.length; i++) {
28
+ const node = children[i];
29
+ if (node.type === 'heading') break;
30
+ out.push(node);
31
+ }
32
+ return out;
33
+ }
34
+
35
+ /**
36
+ * Parse `source` and return the body of the heading whose source range
37
+ * covers `lineNumber` (1-indexed). Returns `null` if `lineNumber` isn't
38
+ * on a heading line.
39
+ */
40
+ export function findBlockSliceAtLine(
41
+ source: string,
42
+ lineNumber: number,
43
+ ): MarkdownBlockNode[] | null {
44
+ const doc = parseMarkdown(source);
45
+ for (const node of doc.children) {
46
+ if (node.type !== 'heading') continue;
47
+ const pos = node.position;
48
+ if (!pos) continue;
49
+ if (pos.start.line <= lineNumber && pos.end.line >= lineNumber) {
50
+ return slicePastHeading(doc, node);
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+
56
+ /**
57
+ * Parse `source` and return the body of the Nth top-level heading
58
+ * (0-indexed). Used by the WYSIWYG path, which knows the heading's
59
+ * index in the Tiptap doc but not its source line.
60
+ */
61
+ export function findBlockSliceByHeadingIndex(
62
+ source: string,
63
+ headingIndex: number,
64
+ ): MarkdownBlockNode[] | null {
65
+ const doc = parseMarkdown(source);
66
+ let seen = 0;
67
+ for (const node of doc.children) {
68
+ if (node.type !== 'heading') continue;
69
+ if (seen === headingIndex) {
70
+ return slicePastHeading(doc, node);
71
+ }
72
+ seen++;
73
+ }
74
+ return null;
75
+ }
@@ -28,11 +28,44 @@ function extractBodyText(contents: MarkdownBlockNode[] | undefined): string {
28
28
  return parts.join('\n').trim();
29
29
  }
30
30
 
31
- function extractBlockImages(
32
- contents: MarkdownBlockNode[] | undefined,
33
- ): Array<{ src: string; alt: string }> {
31
+ interface ExtractedImage {
32
+ src: string;
33
+ alt: string;
34
+ /** Explicit width from `<img width>` (markdown shorthand has none). */
35
+ width?: number;
36
+ /** Explicit height from `<img height>`. */
37
+ height?: number;
38
+ }
39
+
40
+ function parseDim(raw: string | undefined): number | undefined {
41
+ if (raw === undefined) return undefined;
42
+ const n = parseFloat(raw);
43
+ return Number.isFinite(n) && n > 0 ? n : undefined;
44
+ }
45
+
46
+ function extractBlockImages(contents: MarkdownBlockNode[] | undefined): ExtractedImage[] {
34
47
  if (!contents || contents.length === 0) return [];
35
- const images: Array<{ src: string; alt: string }> = [];
48
+ const images: ExtractedImage[] = [];
49
+
50
+ function walkHtml(node: unknown): void {
51
+ if (!node || typeof node !== 'object') return;
52
+ const n = node as Record<string, unknown>;
53
+ if (n.type === 'htmlElement' && (n.tagName as string).toLowerCase() === 'img') {
54
+ const attrs = n.attributes as Record<string, string> | undefined;
55
+ const src = attrs?.src;
56
+ if (typeof src === 'string' && src) {
57
+ images.push({
58
+ src,
59
+ alt: typeof attrs?.alt === 'string' ? attrs.alt : '',
60
+ width: parseDim(attrs?.width),
61
+ height: parseDim(attrs?.height),
62
+ });
63
+ }
64
+ }
65
+ if (Array.isArray(n.children)) {
66
+ for (const child of n.children) walkHtml(child);
67
+ }
68
+ }
36
69
 
37
70
  function walk(node: MarkdownNode): void {
38
71
  if ('type' in node && node.type === 'image' && 'url' in node) {
@@ -41,6 +74,13 @@ function extractBlockImages(
41
74
  images.push({ src: img.url, alt: img.alt ?? '' });
42
75
  }
43
76
  }
77
+ // Resized images round-trip as raw HTML. Pick them up so feature
78
+ // and slideshow previews show the actual image rather than a blank
79
+ // placeholder.
80
+ if ('type' in node && (node.type === 'htmlBlock' || node.type === 'htmlInline')) {
81
+ const html = node as unknown as { htmlChildren?: unknown[] };
82
+ for (const child of html.htmlChildren ?? []) walkHtml(child);
83
+ }
44
84
  for (const child of getChildren(node)) {
45
85
  walk(child);
46
86
  }
@@ -98,7 +138,7 @@ function getTemplateDefaults(
98
138
  switch (templateName) {
99
139
  case 'statHighlight':
100
140
  return { stat: headingText, description: body || headingText };
101
- case 'quoteBlock':
141
+ case 'quote':
102
142
  case 'fullBleedQuote':
103
143
  case 'pullQuote':
104
144
  return { quote: body || headingText };
@@ -106,7 +146,7 @@ function getTemplateDefaults(
106
146
  return { fact: headingText, explanation: body || headingText };
107
147
  case 'comparisonBar':
108
148
  return { leftLabel: 'A', leftValue: 60, rightLabel: 'B', rightValue: 40 };
109
- case 'listBlock': {
149
+ case 'list': {
110
150
  const items = extractListItems(block.contents);
111
151
  return { items: items.length > 0 ? items : ['Item 1', 'Item 2', 'Item 3'] };
112
152
  }
@@ -114,6 +154,21 @@ function getTemplateDefaults(
114
154
  return { term: headingText, definition: body || headingText };
115
155
  case 'dateEvent':
116
156
  return { date: headingText, description: body || headingText };
157
+ case 'leftFeature':
158
+ case 'rightFeature': {
159
+ // Feature blocks need imageSrc from body content and use the
160
+ // heading text as the visible title alongside the body paragraph.
161
+ const images = extractBlockImages(block.contents);
162
+ const img = images[0];
163
+ return {
164
+ imageSrc: img?.src ?? '',
165
+ imageAlt: img?.alt || headingText,
166
+ imageWidth: img?.width,
167
+ imageHeight: img?.height,
168
+ title: headingText,
169
+ body: body || headingText,
170
+ };
171
+ }
117
172
  default:
118
173
  return {};
119
174
  }