@bendyline/squisq-editor-react 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/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 +173 -4
  10. package/dist/EditorShell.d.ts.map +1 -1
  11. package/dist/EditorShell.js +110 -10
  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/MentionExtension.js +10 -7
  42. package/dist/MentionExtension.js.map +1 -1
  43. package/dist/OutlinePanel.d.ts +17 -0
  44. package/dist/OutlinePanel.d.ts.map +1 -0
  45. package/dist/OutlinePanel.js +167 -0
  46. package/dist/OutlinePanel.js.map +1 -0
  47. package/dist/PlainHtmlPreview.d.ts +50 -0
  48. package/dist/PlainHtmlPreview.d.ts.map +1 -0
  49. package/dist/PlainHtmlPreview.js +155 -0
  50. package/dist/PlainHtmlPreview.js.map +1 -0
  51. package/dist/PreviewControls.d.ts +15 -1
  52. package/dist/PreviewControls.d.ts.map +1 -1
  53. package/dist/PreviewControls.js +75 -18
  54. package/dist/PreviewControls.js.map +1 -1
  55. package/dist/PreviewPanel.d.ts +11 -10
  56. package/dist/PreviewPanel.d.ts.map +1 -1
  57. package/dist/PreviewPanel.js +20 -17
  58. package/dist/PreviewPanel.js.map +1 -1
  59. package/dist/RawEditor.d.ts.map +1 -1
  60. package/dist/RawEditor.js +198 -4
  61. package/dist/RawEditor.js.map +1 -1
  62. package/dist/RecorderEntry.d.ts +24 -0
  63. package/dist/RecorderEntry.d.ts.map +1 -0
  64. package/dist/RecorderEntry.js +139 -0
  65. package/dist/RecorderEntry.js.map +1 -0
  66. package/dist/TemplateAnnotation.d.ts.map +1 -1
  67. package/dist/TemplateAnnotation.js +32 -6
  68. package/dist/TemplateAnnotation.js.map +1 -1
  69. package/dist/TemplatePicker.d.ts +53 -0
  70. package/dist/TemplatePicker.d.ts.map +1 -0
  71. package/dist/TemplatePicker.js +388 -0
  72. package/dist/TemplatePicker.js.map +1 -0
  73. package/dist/ThemeCustomizerPanel.d.ts +32 -0
  74. package/dist/ThemeCustomizerPanel.d.ts.map +1 -0
  75. package/dist/ThemeCustomizerPanel.js +256 -0
  76. package/dist/ThemeCustomizerPanel.js.map +1 -0
  77. package/dist/ThemePicker.d.ts +33 -0
  78. package/dist/ThemePicker.d.ts.map +1 -0
  79. package/dist/ThemePicker.js +148 -0
  80. package/dist/ThemePicker.js.map +1 -0
  81. package/dist/Toolbar.d.ts.map +1 -1
  82. package/dist/Toolbar.js +508 -33
  83. package/dist/Toolbar.js.map +1 -1
  84. package/dist/VersionHistoryPanel.d.ts +14 -0
  85. package/dist/VersionHistoryPanel.d.ts.map +1 -0
  86. package/dist/VersionHistoryPanel.js +147 -0
  87. package/dist/VersionHistoryPanel.js.map +1 -0
  88. package/dist/ViewMenuPanel.d.ts +13 -0
  89. package/dist/ViewMenuPanel.d.ts.map +1 -0
  90. package/dist/ViewMenuPanel.js +58 -0
  91. package/dist/ViewMenuPanel.js.map +1 -0
  92. package/dist/WysiwygEditor.d.ts.map +1 -1
  93. package/dist/WysiwygEditor.js +198 -9
  94. package/dist/WysiwygEditor.js.map +1 -1
  95. package/dist/__tests__/detectMarkdown.test.js +0 -14
  96. package/dist/__tests__/detectMarkdown.test.js.map +1 -1
  97. package/dist/__tests__/documentSettingsDialog.test.d.ts +2 -0
  98. package/dist/__tests__/documentSettingsDialog.test.d.ts.map +1 -0
  99. package/dist/__tests__/documentSettingsDialog.test.js +132 -0
  100. package/dist/__tests__/documentSettingsDialog.test.js.map +1 -0
  101. package/dist/__tests__/emojiPicker.test.d.ts +2 -0
  102. package/dist/__tests__/emojiPicker.test.d.ts.map +1 -0
  103. package/dist/__tests__/emojiPicker.test.js +111 -0
  104. package/dist/__tests__/emojiPicker.test.js.map +1 -0
  105. package/dist/__tests__/fileKind.test.js +13 -0
  106. package/dist/__tests__/fileKind.test.js.map +1 -1
  107. package/dist/__tests__/imageEditAffordance.test.d.ts +2 -0
  108. package/dist/__tests__/imageEditAffordance.test.d.ts.map +1 -0
  109. package/dist/__tests__/imageEditAffordance.test.js +188 -0
  110. package/dist/__tests__/imageEditAffordance.test.js.map +1 -0
  111. package/dist/__tests__/imageEditorShell.test.d.ts +2 -0
  112. package/dist/__tests__/imageEditorShell.test.d.ts.map +1 -0
  113. package/dist/__tests__/imageEditorShell.test.js +52 -0
  114. package/dist/__tests__/imageEditorShell.test.js.map +1 -0
  115. package/dist/__tests__/imageEditorState.test.d.ts +3 -0
  116. package/dist/__tests__/imageEditorState.test.d.ts.map +1 -0
  117. package/dist/__tests__/imageEditorState.test.js +148 -0
  118. package/dist/__tests__/imageEditorState.test.js.map +1 -0
  119. package/dist/__tests__/inlinePreviewGutter.test.d.ts +2 -0
  120. package/dist/__tests__/inlinePreviewGutter.test.d.ts.map +1 -0
  121. package/dist/__tests__/inlinePreviewGutter.test.js +51 -0
  122. package/dist/__tests__/inlinePreviewGutter.test.js.map +1 -0
  123. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts +2 -0
  124. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts.map +1 -0
  125. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js +63 -0
  126. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js.map +1 -0
  127. package/dist/__tests__/jsonEditor.test.d.ts +2 -0
  128. package/dist/__tests__/jsonEditor.test.d.ts.map +1 -0
  129. package/dist/__tests__/jsonEditor.test.js +134 -0
  130. package/dist/__tests__/jsonEditor.test.js.map +1 -0
  131. package/dist/__tests__/layersPanel.test.d.ts +2 -0
  132. package/dist/__tests__/layersPanel.test.d.ts.map +1 -0
  133. package/dist/__tests__/layersPanel.test.js +84 -0
  134. package/dist/__tests__/layersPanel.test.js.map +1 -0
  135. package/dist/__tests__/linkDialogDocPicker.test.d.ts +7 -0
  136. package/dist/__tests__/linkDialogDocPicker.test.d.ts.map +1 -0
  137. package/dist/__tests__/linkDialogDocPicker.test.js +75 -0
  138. package/dist/__tests__/linkDialogDocPicker.test.js.map +1 -0
  139. package/dist/__tests__/outlinePanel.test.d.ts +2 -0
  140. package/dist/__tests__/outlinePanel.test.d.ts.map +1 -0
  141. package/dist/__tests__/outlinePanel.test.js +68 -0
  142. package/dist/__tests__/outlinePanel.test.js.map +1 -0
  143. package/dist/__tests__/plainHtmlPreview.test.d.ts +2 -0
  144. package/dist/__tests__/plainHtmlPreview.test.d.ts.map +1 -0
  145. package/dist/__tests__/plainHtmlPreview.test.js +87 -0
  146. package/dist/__tests__/plainHtmlPreview.test.js.map +1 -0
  147. package/dist/__tests__/propertiesPanel.test.d.ts +2 -0
  148. package/dist/__tests__/propertiesPanel.test.d.ts.map +1 -0
  149. package/dist/__tests__/propertiesPanel.test.js +64 -0
  150. package/dist/__tests__/propertiesPanel.test.js.map +1 -0
  151. package/dist/__tests__/recorderFormats.test.d.ts +2 -0
  152. package/dist/__tests__/recorderFormats.test.d.ts.map +1 -0
  153. package/dist/__tests__/recorderFormats.test.js +121 -0
  154. package/dist/__tests__/recorderFormats.test.js.map +1 -0
  155. package/dist/__tests__/recorderTimingJson.test.d.ts +2 -0
  156. package/dist/__tests__/recorderTimingJson.test.d.ts.map +1 -0
  157. package/dist/__tests__/recorderTimingJson.test.js +37 -0
  158. package/dist/__tests__/recorderTimingJson.test.js.map +1 -0
  159. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts +2 -0
  160. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts.map +1 -0
  161. package/dist/__tests__/templateAnnotationRoundTrip.test.js +31 -0
  162. package/dist/__tests__/templateAnnotationRoundTrip.test.js.map +1 -0
  163. package/dist/__tests__/tiptapBridge.test.js +13 -0
  164. package/dist/__tests__/tiptapBridge.test.js.map +1 -1
  165. package/dist/__tests__/useImageEditor.test.d.ts +2 -0
  166. package/dist/__tests__/useImageEditor.test.d.ts.map +1 -0
  167. package/dist/__tests__/useImageEditor.test.js +131 -0
  168. package/dist/__tests__/useImageEditor.test.js.map +1 -0
  169. package/dist/__tests__/useMediaRecorder.test.d.ts +2 -0
  170. package/dist/__tests__/useMediaRecorder.test.d.ts.map +1 -0
  171. package/dist/__tests__/useMediaRecorder.test.js +153 -0
  172. package/dist/__tests__/useMediaRecorder.test.js.map +1 -0
  173. package/dist/__tests__/versionHistory.test.d.ts +2 -0
  174. package/dist/__tests__/versionHistory.test.d.ts.map +1 -0
  175. package/dist/__tests__/versionHistory.test.js +124 -0
  176. package/dist/__tests__/versionHistory.test.js.map +1 -0
  177. package/dist/blockSlice.d.ts +24 -0
  178. package/dist/blockSlice.d.ts.map +1 -0
  179. package/dist/blockSlice.js +63 -0
  180. package/dist/blockSlice.js.map +1 -0
  181. package/dist/buildPreviewDoc.d.ts.map +1 -1
  182. package/dist/buildPreviewDoc.js +52 -2
  183. package/dist/buildPreviewDoc.js.map +1 -1
  184. package/dist/emojiData.d.ts +81 -0
  185. package/dist/emojiData.d.ts.map +1 -0
  186. package/dist/emojiData.js +1283 -0
  187. package/dist/emojiData.js.map +1 -0
  188. package/dist/fileKind.d.ts +6 -2
  189. package/dist/fileKind.d.ts.map +1 -1
  190. package/dist/fileKind.js +25 -4
  191. package/dist/fileKind.js.map +1 -1
  192. package/dist/hooks/useFileDrop.d.ts.map +1 -1
  193. package/dist/hooks/useFileDrop.js +40 -4
  194. package/dist/hooks/useFileDrop.js.map +1 -1
  195. package/dist/imageEditor/CanvasSurface.d.ts +31 -0
  196. package/dist/imageEditor/CanvasSurface.d.ts.map +1 -0
  197. package/dist/imageEditor/CanvasSurface.js +264 -0
  198. package/dist/imageEditor/CanvasSurface.js.map +1 -0
  199. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts +39 -0
  200. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts.map +1 -0
  201. package/dist/imageEditor/ImageVersionHistoryDropdown.js +283 -0
  202. package/dist/imageEditor/ImageVersionHistoryDropdown.js.map +1 -0
  203. package/dist/imageEditor/LayersPanel.d.ts +14 -0
  204. package/dist/imageEditor/LayersPanel.d.ts.map +1 -0
  205. package/dist/imageEditor/LayersPanel.js +43 -0
  206. package/dist/imageEditor/LayersPanel.js.map +1 -0
  207. package/dist/imageEditor/PropertiesPanel.d.ts +14 -0
  208. package/dist/imageEditor/PropertiesPanel.d.ts.map +1 -0
  209. package/dist/imageEditor/PropertiesPanel.js +97 -0
  210. package/dist/imageEditor/PropertiesPanel.js.map +1 -0
  211. package/dist/imageEditor/Toolbar.d.ts +30 -0
  212. package/dist/imageEditor/Toolbar.d.ts.map +1 -0
  213. package/dist/imageEditor/Toolbar.js +108 -0
  214. package/dist/imageEditor/Toolbar.js.map +1 -0
  215. package/dist/imageEditor/icons.d.ts +24 -0
  216. package/dist/imageEditor/icons.d.ts.map +1 -0
  217. package/dist/imageEditor/icons.js +45 -0
  218. package/dist/imageEditor/icons.js.map +1 -0
  219. package/dist/imageEditor/layers/EditorImageLayer.d.ts +16 -0
  220. package/dist/imageEditor/layers/EditorImageLayer.d.ts.map +1 -0
  221. package/dist/imageEditor/layers/EditorImageLayer.js +37 -0
  222. package/dist/imageEditor/layers/EditorImageLayer.js.map +1 -0
  223. package/dist/imageEditor/layers/EditorShapeLayer.d.ts +15 -0
  224. package/dist/imageEditor/layers/EditorShapeLayer.d.ts.map +1 -0
  225. package/dist/imageEditor/layers/EditorShapeLayer.js +20 -0
  226. package/dist/imageEditor/layers/EditorShapeLayer.js.map +1 -0
  227. package/dist/imageEditor/layers/EditorTextLayer.d.ts +18 -0
  228. package/dist/imageEditor/layers/EditorTextLayer.d.ts.map +1 -0
  229. package/dist/imageEditor/layers/EditorTextLayer.js +13 -0
  230. package/dist/imageEditor/layers/EditorTextLayer.js.map +1 -0
  231. package/dist/imageEditor/layers/SelectionHandles.d.ts +17 -0
  232. package/dist/imageEditor/layers/SelectionHandles.d.ts.map +1 -0
  233. package/dist/imageEditor/layers/SelectionHandles.js +19 -0
  234. package/dist/imageEditor/layers/SelectionHandles.js.map +1 -0
  235. package/dist/imageEditor/state.d.ts +76 -0
  236. package/dist/imageEditor/state.d.ts.map +1 -0
  237. package/dist/imageEditor/state.js +87 -0
  238. package/dist/imageEditor/state.js.map +1 -0
  239. package/dist/imageEditor/useImageEditor.d.ts +53 -0
  240. package/dist/imageEditor/useImageEditor.d.ts.map +1 -0
  241. package/dist/imageEditor/useImageEditor.js +244 -0
  242. package/dist/imageEditor/useImageEditor.js.map +1 -0
  243. package/dist/imageEditor/useImageEditorTokens.d.ts +16 -0
  244. package/dist/imageEditor/useImageEditorTokens.d.ts.map +1 -0
  245. package/dist/imageEditor/useImageEditorTokens.js +45 -0
  246. package/dist/imageEditor/useImageEditorTokens.js.map +1 -0
  247. package/dist/index.d.ts +48 -1
  248. package/dist/index.d.ts.map +1 -1
  249. package/dist/index.js +36 -0
  250. package/dist/index.js.map +1 -1
  251. package/dist/jsonEditor/EmbeddedRichTextField.d.ts +15 -0
  252. package/dist/jsonEditor/EmbeddedRichTextField.d.ts.map +1 -0
  253. package/dist/jsonEditor/EmbeddedRichTextField.js +74 -0
  254. package/dist/jsonEditor/EmbeddedRichTextField.js.map +1 -0
  255. package/dist/jsonEditor/JsonEditor.d.ts +36 -0
  256. package/dist/jsonEditor/JsonEditor.d.ts.map +1 -0
  257. package/dist/jsonEditor/JsonEditor.js +15 -0
  258. package/dist/jsonEditor/JsonEditor.js.map +1 -0
  259. package/dist/jsonEditor/JsonEditorContext.d.ts +28 -0
  260. package/dist/jsonEditor/JsonEditorContext.d.ts.map +1 -0
  261. package/dist/jsonEditor/JsonEditorContext.js +41 -0
  262. package/dist/jsonEditor/JsonEditorContext.js.map +1 -0
  263. package/dist/jsonEditor/RenderNode.d.ts +16 -0
  264. package/dist/jsonEditor/RenderNode.d.ts.map +1 -0
  265. package/dist/jsonEditor/RenderNode.js +32 -0
  266. package/dist/jsonEditor/RenderNode.js.map +1 -0
  267. package/dist/jsonEditor/editors.d.ts +36 -0
  268. package/dist/jsonEditor/editors.d.ts.map +1 -0
  269. package/dist/jsonEditor/editors.js +347 -0
  270. package/dist/jsonEditor/editors.js.map +1 -0
  271. package/dist/jsonEditor/index.d.ts +3 -0
  272. package/dist/jsonEditor/index.d.ts.map +1 -0
  273. package/dist/jsonEditor/index.js +2 -0
  274. package/dist/jsonEditor/index.js.map +1 -0
  275. package/dist/jsonEditor/useJsonEditorTokens.d.ts +13 -0
  276. package/dist/jsonEditor/useJsonEditorTokens.d.ts.map +1 -0
  277. package/dist/jsonEditor/useJsonEditorTokens.js +38 -0
  278. package/dist/jsonEditor/useJsonEditorTokens.js.map +1 -0
  279. package/dist/recorder/RecorderButton.d.ts +31 -0
  280. package/dist/recorder/RecorderButton.d.ts.map +1 -0
  281. package/dist/recorder/RecorderButton.js +24 -0
  282. package/dist/recorder/RecorderButton.js.map +1 -0
  283. package/dist/recorder/RecorderModal.d.ts +59 -0
  284. package/dist/recorder/RecorderModal.d.ts.map +1 -0
  285. package/dist/recorder/RecorderModal.js +333 -0
  286. package/dist/recorder/RecorderModal.js.map +1 -0
  287. package/dist/recorder/RecorderPanel.d.ts +25 -0
  288. package/dist/recorder/RecorderPanel.d.ts.map +1 -0
  289. package/dist/recorder/RecorderPanel.js +30 -0
  290. package/dist/recorder/RecorderPanel.js.map +1 -0
  291. package/dist/recorder/formats.d.ts +51 -0
  292. package/dist/recorder/formats.d.ts.map +1 -0
  293. package/dist/recorder/formats.js +144 -0
  294. package/dist/recorder/formats.js.map +1 -0
  295. package/dist/recorder/hooks/useMediaRecorder.d.ts +90 -0
  296. package/dist/recorder/hooks/useMediaRecorder.d.ts.map +1 -0
  297. package/dist/recorder/hooks/useMediaRecorder.js +277 -0
  298. package/dist/recorder/hooks/useMediaRecorder.js.map +1 -0
  299. package/dist/recorder/hooks/useStreamPreview.d.ts +22 -0
  300. package/dist/recorder/hooks/useStreamPreview.d.ts.map +1 -0
  301. package/dist/recorder/hooks/useStreamPreview.js +44 -0
  302. package/dist/recorder/hooks/useStreamPreview.js.map +1 -0
  303. package/dist/recorder/sources/cameraStream.d.ts +22 -0
  304. package/dist/recorder/sources/cameraStream.d.ts.map +1 -0
  305. package/dist/recorder/sources/cameraStream.js +24 -0
  306. package/dist/recorder/sources/cameraStream.js.map +1 -0
  307. package/dist/recorder/sources/micStream.d.ts +15 -0
  308. package/dist/recorder/sources/micStream.d.ts.map +1 -0
  309. package/dist/recorder/sources/micStream.js +24 -0
  310. package/dist/recorder/sources/micStream.js.map +1 -0
  311. package/dist/recorder/sources/screenStream.d.ts +53 -0
  312. package/dist/recorder/sources/screenStream.d.ts.map +1 -0
  313. package/dist/recorder/sources/screenStream.js +114 -0
  314. package/dist/recorder/sources/screenStream.js.map +1 -0
  315. package/dist/recorder/timingJson.d.ts +51 -0
  316. package/dist/recorder/timingJson.d.ts.map +1 -0
  317. package/dist/recorder/timingJson.js +42 -0
  318. package/dist/recorder/timingJson.js.map +1 -0
  319. package/dist/tiptap/TiptapAudio.d.ts +26 -0
  320. package/dist/tiptap/TiptapAudio.d.ts.map +1 -0
  321. package/dist/tiptap/TiptapAudio.js +58 -0
  322. package/dist/tiptap/TiptapAudio.js.map +1 -0
  323. package/dist/tiptap/TiptapVideo.d.ts +30 -0
  324. package/dist/tiptap/TiptapVideo.d.ts.map +1 -0
  325. package/dist/tiptap/TiptapVideo.js +66 -0
  326. package/dist/tiptap/TiptapVideo.js.map +1 -0
  327. package/dist/tiptap/useResolvedMediaSrc.d.ts +2 -0
  328. package/dist/tiptap/useResolvedMediaSrc.d.ts.map +1 -0
  329. package/dist/tiptap/useResolvedMediaSrc.js +42 -0
  330. package/dist/tiptap/useResolvedMediaSrc.js.map +1 -0
  331. package/dist/tiptapBridge.d.ts.map +1 -1
  332. package/dist/tiptapBridge.js +171 -14
  333. package/dist/tiptapBridge.js.map +1 -1
  334. package/dist/useHeadingLayout.d.ts +54 -0
  335. package/dist/useHeadingLayout.d.ts.map +1 -0
  336. package/dist/useHeadingLayout.js +260 -0
  337. package/dist/useHeadingLayout.js.map +1 -0
  338. package/dist/utils/collectInlineFontAwesomeCss.d.ts +21 -0
  339. package/dist/utils/collectInlineFontAwesomeCss.d.ts.map +1 -0
  340. package/dist/utils/collectInlineFontAwesomeCss.js +68 -0
  341. package/dist/utils/collectInlineFontAwesomeCss.js.map +1 -0
  342. package/dist/utils/dropUtils.d.ts +21 -2
  343. package/dist/utils/dropUtils.d.ts.map +1 -1
  344. package/dist/utils/dropUtils.js +43 -4
  345. package/dist/utils/dropUtils.js.map +1 -1
  346. package/dist/utils/normalizeMalformedAssetUrl.d.ts +15 -0
  347. package/dist/utils/normalizeMalformedAssetUrl.d.ts.map +1 -0
  348. package/dist/utils/normalizeMalformedAssetUrl.js +27 -0
  349. package/dist/utils/normalizeMalformedAssetUrl.js.map +1 -0
  350. package/package.json +8 -5
  351. package/src/DocumentSettingsDialog.tsx +266 -0
  352. package/src/EditorContext.tsx +534 -10
  353. package/src/EditorShell.tsx +571 -55
  354. package/src/EmojiPicker.tsx +332 -0
  355. package/src/ImageEditor.tsx +327 -0
  356. package/src/ImageNodeView.tsx +222 -21
  357. package/src/ImageViewer.tsx +221 -0
  358. package/src/InlineIcon.ts +84 -0
  359. package/src/InlinePreviewGutter.tsx +582 -0
  360. package/src/LinkDialog.tsx +276 -0
  361. package/src/MentionExtension.tsx +10 -7
  362. package/src/OutlinePanel.tsx +295 -0
  363. package/src/PlainHtmlPreview.tsx +211 -0
  364. package/src/PreviewControls.tsx +130 -24
  365. package/src/PreviewPanel.tsx +38 -21
  366. package/src/RawEditor.tsx +215 -4
  367. package/src/RecorderEntry.tsx +164 -0
  368. package/src/TemplateAnnotation.ts +32 -6
  369. package/src/TemplatePicker.tsx +818 -0
  370. package/src/ThemeCustomizerPanel.tsx +595 -0
  371. package/src/ThemePicker.tsx +319 -0
  372. package/src/Toolbar.tsx +708 -111
  373. package/src/VersionHistoryPanel.tsx +329 -0
  374. package/src/ViewMenuPanel.tsx +188 -0
  375. package/src/WysiwygEditor.tsx +229 -9
  376. package/src/__tests__/detectMarkdown.test.ts +0 -15
  377. package/src/__tests__/documentSettingsDialog.test.tsx +147 -0
  378. package/src/__tests__/emojiPicker.test.tsx +133 -0
  379. package/src/__tests__/fileKind.test.ts +16 -0
  380. package/src/__tests__/imageEditAffordance.test.tsx +268 -0
  381. package/src/__tests__/imageEditorShell.test.tsx +57 -0
  382. package/src/__tests__/imageEditorState.test.ts +171 -0
  383. package/src/__tests__/inlinePreviewGutter.test.tsx +62 -0
  384. package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +103 -0
  385. package/src/__tests__/jsonEditor.test.tsx +168 -0
  386. package/src/__tests__/layersPanel.test.tsx +97 -0
  387. package/src/__tests__/linkDialogDocPicker.test.tsx +137 -0
  388. package/src/__tests__/outlinePanel.test.tsx +79 -0
  389. package/src/__tests__/plainHtmlPreview.test.tsx +107 -0
  390. package/src/__tests__/propertiesPanel.test.tsx +69 -0
  391. package/src/__tests__/recorderFormats.test.ts +146 -0
  392. package/src/__tests__/recorderTimingJson.test.ts +41 -0
  393. package/src/__tests__/templateAnnotationRoundTrip.test.ts +34 -0
  394. package/src/__tests__/tiptapBridge.test.ts +15 -0
  395. package/src/__tests__/useImageEditor.test.tsx +159 -0
  396. package/src/__tests__/useMediaRecorder.test.ts +186 -0
  397. package/src/__tests__/versionHistory.test.tsx +197 -0
  398. package/src/blockSlice.ts +75 -0
  399. package/src/buildPreviewDoc.ts +61 -6
  400. package/src/emojiData.ts +1337 -0
  401. package/src/fileKind.ts +30 -6
  402. package/src/hooks/useFileDrop.ts +40 -4
  403. package/src/imageEditor/CanvasSurface.tsx +402 -0
  404. package/src/imageEditor/ImageVersionHistoryDropdown.tsx +396 -0
  405. package/src/imageEditor/LayersPanel.tsx +143 -0
  406. package/src/imageEditor/PropertiesPanel.tsx +428 -0
  407. package/src/imageEditor/Toolbar.tsx +242 -0
  408. package/src/imageEditor/icons.tsx +144 -0
  409. package/src/imageEditor/image-editor.css +450 -0
  410. package/src/imageEditor/layers/EditorImageLayer.tsx +45 -0
  411. package/src/imageEditor/layers/EditorShapeLayer.tsx +62 -0
  412. package/src/imageEditor/layers/EditorTextLayer.tsx +45 -0
  413. package/src/imageEditor/layers/SelectionHandles.tsx +86 -0
  414. package/src/imageEditor/state.ts +153 -0
  415. package/src/imageEditor/useImageEditor.ts +328 -0
  416. package/src/imageEditor/useImageEditorTokens.ts +70 -0
  417. package/src/index.ts +82 -0
  418. package/src/jsonEditor/EmbeddedRichTextField.tsx +81 -0
  419. package/src/jsonEditor/JsonEditor.tsx +81 -0
  420. package/src/jsonEditor/JsonEditorContext.tsx +75 -0
  421. package/src/jsonEditor/RenderNode.tsx +66 -0
  422. package/src/jsonEditor/editors.tsx +678 -0
  423. package/src/jsonEditor/index.ts +2 -0
  424. package/src/jsonEditor/json-editor.css +463 -0
  425. package/src/jsonEditor/useJsonEditorTokens.ts +63 -0
  426. package/src/recorder/RecorderButton.tsx +72 -0
  427. package/src/recorder/RecorderModal.tsx +596 -0
  428. package/src/recorder/RecorderPanel.tsx +93 -0
  429. package/src/recorder/formats.ts +159 -0
  430. package/src/recorder/hooks/useMediaRecorder.ts +378 -0
  431. package/src/recorder/hooks/useStreamPreview.ts +47 -0
  432. package/src/recorder/sources/cameraStream.ts +32 -0
  433. package/src/recorder/sources/micStream.ts +25 -0
  434. package/src/recorder/sources/screenStream.ts +162 -0
  435. package/src/recorder/timingJson.ts +66 -0
  436. package/src/styles/editor.css +2490 -51
  437. package/src/styles/image-edit-affordance.css +201 -0
  438. package/src/styles/index.css +10 -0
  439. package/src/tiptap/TiptapAudio.tsx +86 -0
  440. package/src/tiptap/TiptapVideo.tsx +119 -0
  441. package/src/tiptap/useResolvedMediaSrc.ts +47 -0
  442. package/src/tiptapBridge.ts +188 -20
  443. package/src/useHeadingLayout.ts +294 -0
  444. package/src/utils/collectInlineFontAwesomeCss.ts +69 -0
  445. package/src/utils/dropUtils.ts +54 -6
  446. package/src/utils/normalizeMalformedAssetUrl.ts +22 -0
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { useState } from 'react';
3
+ import { fireEvent, render, screen } from '@testing-library/react';
4
+ import type { SquisqAnnotatedSchema } from '@bendyline/squisq/jsonForm';
5
+ import { JsonEditor } from '../jsonEditor';
6
+
7
+ function Controlled<T>({
8
+ schema,
9
+ initial,
10
+ onValueChange,
11
+ }: {
12
+ schema: SquisqAnnotatedSchema;
13
+ initial: T;
14
+ onValueChange?: (next: T) => void;
15
+ }) {
16
+ const [value, setValue] = useState<T>(initial);
17
+ return (
18
+ <JsonEditor
19
+ schema={schema}
20
+ value={value}
21
+ onChange={(v) => {
22
+ setValue(v as T);
23
+ onValueChange?.(v as T);
24
+ }}
25
+ />
26
+ );
27
+ }
28
+
29
+ describe('JsonEditor', () => {
30
+ it('renders text input and propagates edits through onChange', () => {
31
+ const schema: SquisqAnnotatedSchema = {
32
+ type: 'object',
33
+ properties: { title: { type: 'string', title: 'Title' } },
34
+ };
35
+ const onChange = vi.fn();
36
+ render(<Controlled schema={schema} initial={{ title: 'Hi' }} onValueChange={onChange} />);
37
+ const input = screen.getByDisplayValue('Hi') as HTMLInputElement;
38
+ fireEvent.change(input, { target: { value: 'Hello' } });
39
+ expect(onChange).toHaveBeenLastCalledWith({ title: 'Hello' });
40
+ });
41
+
42
+ it('toggles a boolean via the toggle control', () => {
43
+ const schema: SquisqAnnotatedSchema = {
44
+ type: 'object',
45
+ properties: { active: { type: 'boolean', title: 'Active' } },
46
+ };
47
+ const onChange = vi.fn();
48
+ render(<Controlled schema={schema} initial={{ active: false }} onValueChange={onChange} />);
49
+ const button = screen.getByRole('button', { pressed: false });
50
+ fireEvent.click(button);
51
+ expect(onChange).toHaveBeenLastCalledWith({ active: true });
52
+ });
53
+
54
+ it('hides fields whose hidden rule matches', () => {
55
+ const schema: SquisqAnnotatedSchema = {
56
+ type: 'object',
57
+ properties: {
58
+ showAuthor: { type: 'boolean', title: 'Show author' },
59
+ authorName: {
60
+ type: 'string',
61
+ title: 'Author name',
62
+ squisq: { hidden: { field: 'showAuthor', truthy: false } },
63
+ },
64
+ },
65
+ };
66
+ const { container } = render(
67
+ <Controlled schema={schema} initial={{ showAuthor: false, authorName: 'Alex' }} />,
68
+ );
69
+ expect(container.textContent).not.toContain('Author name');
70
+ });
71
+
72
+ it('selecting a different segmented option commits the new enum value', () => {
73
+ const schema: SquisqAnnotatedSchema = {
74
+ type: 'object',
75
+ properties: {
76
+ size: { type: 'string', title: 'Size', enum: ['s', 'm', 'l'] },
77
+ },
78
+ };
79
+ const onChange = vi.fn();
80
+ render(<Controlled schema={schema} initial={{ size: 's' }} onValueChange={onChange} />);
81
+ fireEvent.click(screen.getByRole('button', { name: 'l' }));
82
+ expect(onChange).toHaveBeenLastCalledWith({ size: 'l' });
83
+ });
84
+
85
+ it('card-stack: + Add appends, × Remove removes, ↑↓ reorder', () => {
86
+ const schema: SquisqAnnotatedSchema = {
87
+ type: 'object',
88
+ properties: {
89
+ sections: {
90
+ type: 'array',
91
+ title: 'Sections',
92
+ items: {
93
+ type: 'object',
94
+ properties: { heading: { type: 'string' } },
95
+ squisq: { itemLabel: { fromField: 'heading' } },
96
+ },
97
+ squisq: { control: 'card-stack', addLabel: '+ Add section' },
98
+ },
99
+ },
100
+ };
101
+ const onChange = vi.fn();
102
+ render(
103
+ <Controlled
104
+ schema={schema}
105
+ initial={{ sections: [{ heading: 'A' }, { heading: 'B' }] }}
106
+ onValueChange={onChange}
107
+ />,
108
+ );
109
+
110
+ // + Add appends a new empty item.
111
+ fireEvent.click(screen.getByText('+ Add section'));
112
+ expect(onChange).toHaveBeenLastCalledWith({
113
+ sections: [{ heading: 'A' }, { heading: 'B' }, { heading: '' }],
114
+ });
115
+
116
+ // × on the first card removes it.
117
+ const removeButtons = screen.getAllByLabelText('Remove');
118
+ fireEvent.click(removeButtons[0]);
119
+ expect(onChange).toHaveBeenLastCalledWith({
120
+ sections: [{ heading: 'B' }, { heading: '' }],
121
+ });
122
+
123
+ // ↓ on the first card reorders.
124
+ const downs = screen.getAllByLabelText('Move down');
125
+ fireEvent.click(downs[0]);
126
+ expect(onChange).toHaveBeenLastCalledWith({
127
+ sections: [{ heading: '' }, { heading: 'B' }],
128
+ });
129
+ });
130
+
131
+ it('chip-bin: typing + Enter appends, × removes', () => {
132
+ const schema: SquisqAnnotatedSchema = {
133
+ type: 'object',
134
+ properties: {
135
+ tags: {
136
+ type: 'array',
137
+ items: { type: 'string' },
138
+ squisq: { control: 'chip-bin', addLabel: '+ Add tag' },
139
+ },
140
+ },
141
+ };
142
+ const onChange = vi.fn();
143
+ render(<Controlled schema={schema} initial={{ tags: ['one'] }} onValueChange={onChange} />);
144
+
145
+ const addInput = screen.getByPlaceholderText('+ Add tag');
146
+ fireEvent.change(addInput, { target: { value: 'two' } });
147
+ fireEvent.keyDown(addInput, { key: 'Enter' });
148
+ expect(onChange).toHaveBeenLastCalledWith({ tags: ['one', 'two'] });
149
+
150
+ fireEvent.click(screen.getByLabelText('Remove one'));
151
+ expect(onChange).toHaveBeenLastCalledWith({ tags: ['two'] });
152
+ });
153
+
154
+ it('omitting onChange disables every editor', () => {
155
+ const schema: SquisqAnnotatedSchema = {
156
+ type: 'object',
157
+ properties: { title: { type: 'string', title: 'Title' } },
158
+ };
159
+ const onChange = vi.fn();
160
+ // Use a sniffer onChange we can verify is never called by simulating an
161
+ // attempted change. We render WITHOUT passing onChange to JsonEditor.
162
+ render(<JsonEditor schema={schema} value={{ title: 'Hi' }} />);
163
+ const input = screen.getByDisplayValue('Hi') as HTMLInputElement;
164
+ expect(input.disabled).toBe(true);
165
+ fireEvent.change(input, { target: { value: 'changed' } });
166
+ expect(onChange).not.toHaveBeenCalled();
167
+ });
168
+ });
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import { fireEvent, render, screen } from '@testing-library/react';
6
+ import type { ImageEditDoc } from '@bendyline/squisq/schemas';
7
+ import { LayersPanel } from '../imageEditor/LayersPanel.js';
8
+ import type { ImageEditorAction } from '../imageEditor/state.js';
9
+
10
+ function buildDoc(): ImageEditDoc {
11
+ return {
12
+ version: 1,
13
+ canvas: { width: 100, height: 100 },
14
+ layers: [
15
+ {
16
+ id: 'a',
17
+ type: 'shape',
18
+ name: 'Bottom',
19
+ position: { x: 0, y: 0, width: 10, height: 10 },
20
+ content: { shape: 'rect' },
21
+ },
22
+ {
23
+ id: 'b',
24
+ type: 'text',
25
+ name: 'Middle',
26
+ position: { x: 0, y: 0, width: 50, height: 20 },
27
+ content: { text: 'Hi', style: { fontSize: 16, color: '#000' } },
28
+ },
29
+ {
30
+ id: 'c',
31
+ type: 'shape',
32
+ name: 'Top',
33
+ visible: false,
34
+ position: { x: 0, y: 0, width: 10, height: 10 },
35
+ content: { shape: 'rect' },
36
+ },
37
+ ],
38
+ };
39
+ }
40
+
41
+ describe('LayersPanel', () => {
42
+ it('renders layers with the top of the SVG stack first', () => {
43
+ const dispatch = vi.fn<(a: ImageEditorAction) => void>();
44
+ render(<LayersPanel doc={buildDoc()} selectedLayerId={null} dispatch={dispatch} />);
45
+ const items = screen.getAllByText(/Bottom|Middle|Top/);
46
+ expect(items[0]?.textContent).toContain('Top');
47
+ expect(items[items.length - 1]?.textContent).toContain('Bottom');
48
+ });
49
+
50
+ it('clicking the layer name dispatches a select action', () => {
51
+ const dispatch = vi.fn<(a: ImageEditorAction) => void>();
52
+ render(<LayersPanel doc={buildDoc()} selectedLayerId={null} dispatch={dispatch} />);
53
+ fireEvent.click(screen.getByText('Middle'));
54
+ expect(dispatch).toHaveBeenCalledWith({ type: 'select', layerId: 'b' });
55
+ });
56
+
57
+ it('toggling visibility dispatches an update-layer action', () => {
58
+ const dispatch = vi.fn<(a: ImageEditorAction) => void>();
59
+ render(<LayersPanel doc={buildDoc()} selectedLayerId={null} dispatch={dispatch} />);
60
+ // The "Hide layer" buttons exist for visible layers.
61
+ const hideButtons = screen.getAllByRole('button', { name: 'Hide layer' });
62
+ fireEvent.click(hideButtons[0]!); // top-most visible layer is 'b'
63
+ expect(dispatch).toHaveBeenCalledWith({
64
+ type: 'update-layer',
65
+ layerId: 'b',
66
+ patch: { visible: false },
67
+ });
68
+ });
69
+
70
+ it('move-up button is disabled at the top of the stack', () => {
71
+ const dispatch = vi.fn<(a: ImageEditorAction) => void>();
72
+ render(<LayersPanel doc={buildDoc()} selectedLayerId={null} dispatch={dispatch} />);
73
+ const upButtons = screen.getAllByRole('button', { name: 'Move layer up' });
74
+ // Visual order is c, b, a; layer 'c' is at the top of the stack and cannot move up.
75
+ expect((upButtons[0] as HTMLButtonElement).disabled).toBe(true);
76
+ });
77
+
78
+ it('delete button dispatches a remove-layer action', () => {
79
+ const dispatch = vi.fn<(a: ImageEditorAction) => void>();
80
+ render(<LayersPanel doc={buildDoc()} selectedLayerId={null} dispatch={dispatch} />);
81
+ const delButtons = screen.getAllByRole('button', { name: 'Delete layer' });
82
+ fireEvent.click(delButtons[0]!); // top of stack ('c')
83
+ expect(dispatch).toHaveBeenCalledWith({ type: 'remove-layer', layerId: 'c' });
84
+ });
85
+
86
+ it('shows an empty-state message when there are no layers', () => {
87
+ const dispatch = vi.fn<(a: ImageEditorAction) => void>();
88
+ render(
89
+ <LayersPanel
90
+ doc={{ version: 1, canvas: { width: 1, height: 1 }, layers: [] }}
91
+ selectedLayerId={null}
92
+ dispatch={dispatch}
93
+ />,
94
+ );
95
+ expect(screen.getByText(/no layers yet/i)).toBeTruthy();
96
+ });
97
+ });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Covers the LinkDialog's "Browse documents" picker added behind the
3
+ * `documentLinkProvider` prop. The picker is purely additive — when
4
+ * the prop is absent the dialog renders its original URL-only layout.
5
+ */
6
+
7
+ import { describe, expect, it, vi } from 'vitest';
8
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
9
+ import { LinkDialog } from '../LinkDialog';
10
+ import type { DocumentLinkProvider } from '../EditorContext';
11
+
12
+ const NEIGHBORS = [
13
+ { path: 'resume.md', label: 'Resume', description: 'My CV' },
14
+ { path: 'projects.md', label: 'Projects' },
15
+ { path: 'misc/notes.md', label: 'Notes', description: 'Scratchpad' },
16
+ ];
17
+
18
+ const provider: DocumentLinkProvider = async (q) => {
19
+ const query = q.trim().toLowerCase();
20
+ if (!query) return NEIGHBORS;
21
+ return NEIGHBORS.filter(
22
+ (n) => n.label.toLowerCase().includes(query) || n.path.toLowerCase().includes(query),
23
+ );
24
+ };
25
+
26
+ describe('LinkDialog — document picker', () => {
27
+ it('hides the documents tab when no provider is supplied', () => {
28
+ render(
29
+ <LinkDialog
30
+ mode="insert"
31
+ initialText=""
32
+ initialUrl=""
33
+ onConfirm={() => {}}
34
+ onClose={() => {}}
35
+ />,
36
+ );
37
+ expect(screen.queryByRole('tab', { name: /browse documents/i })).toBeNull();
38
+ });
39
+
40
+ it('shows both tabs when the provider is supplied', () => {
41
+ render(
42
+ <LinkDialog
43
+ mode="insert"
44
+ initialText=""
45
+ initialUrl=""
46
+ onConfirm={() => {}}
47
+ onClose={() => {}}
48
+ documentLinkProvider={provider}
49
+ />,
50
+ );
51
+ expect(screen.getByRole('tab', { name: 'URL' })).toBeTruthy();
52
+ expect(screen.getByRole('tab', { name: 'Browse documents' })).toBeTruthy();
53
+ });
54
+
55
+ it('lists initial candidates when the documents tab opens', async () => {
56
+ render(
57
+ <LinkDialog
58
+ mode="insert"
59
+ initialText=""
60
+ initialUrl=""
61
+ onConfirm={() => {}}
62
+ onClose={() => {}}
63
+ documentLinkProvider={provider}
64
+ />,
65
+ );
66
+ fireEvent.click(screen.getByRole('tab', { name: 'Browse documents' }));
67
+ await waitFor(() => {
68
+ expect(screen.getByText('Resume')).toBeTruthy();
69
+ expect(screen.getByText('Projects')).toBeTruthy();
70
+ expect(screen.getByText('Notes')).toBeTruthy();
71
+ });
72
+ });
73
+
74
+ it('filters candidates as the user types', async () => {
75
+ render(
76
+ <LinkDialog
77
+ mode="insert"
78
+ initialText=""
79
+ initialUrl=""
80
+ onConfirm={() => {}}
81
+ onClose={() => {}}
82
+ documentLinkProvider={provider}
83
+ />,
84
+ );
85
+ fireEvent.click(screen.getByRole('tab', { name: 'Browse documents' }));
86
+ await waitFor(() => screen.getByText('Resume'));
87
+ const search = screen.getByLabelText('Search') as HTMLInputElement;
88
+ fireEvent.change(search, { target: { value: 'proj' } });
89
+ await waitFor(() => {
90
+ expect(screen.getByText('Projects')).toBeTruthy();
91
+ expect(screen.queryByText('Resume')).toBeNull();
92
+ });
93
+ });
94
+
95
+ it('picking a document fills the URL and auto-fills the caption when empty', async () => {
96
+ const onConfirm = vi.fn();
97
+ render(
98
+ <LinkDialog
99
+ mode="insert"
100
+ initialText=""
101
+ initialUrl=""
102
+ onConfirm={onConfirm}
103
+ onClose={() => {}}
104
+ documentLinkProvider={provider}
105
+ />,
106
+ );
107
+ fireEvent.click(screen.getByRole('tab', { name: 'Browse documents' }));
108
+ await waitFor(() => screen.getByText('Resume'));
109
+ fireEvent.click(screen.getByRole('option', { name: /Resume/i }));
110
+ // After picking, the dialog jumps back to the URL tab so Enter submits.
111
+ const urlInput = screen.getByLabelText('URL') as HTMLInputElement;
112
+ expect(urlInput.value).toBe('resume.md');
113
+ const textInput = screen.getByLabelText('Text') as HTMLInputElement;
114
+ expect(textInput.value).toBe('Resume');
115
+ // Submit
116
+ fireEvent.submit(urlInput.closest('form')!);
117
+ expect(onConfirm).toHaveBeenCalledWith('Resume', 'resume.md');
118
+ });
119
+
120
+ it('preserves a caption the user already typed when picking a document', async () => {
121
+ render(
122
+ <LinkDialog
123
+ mode="insert"
124
+ initialText="Read my work"
125
+ initialUrl=""
126
+ onConfirm={() => {}}
127
+ onClose={() => {}}
128
+ documentLinkProvider={provider}
129
+ />,
130
+ );
131
+ fireEvent.click(screen.getByRole('tab', { name: 'Browse documents' }));
132
+ await waitFor(() => screen.getByText('Resume'));
133
+ fireEvent.click(screen.getByRole('option', { name: /Resume/i }));
134
+ const textInput = screen.getByLabelText('Text') as HTMLInputElement;
135
+ expect(textInput.value).toBe('Read my work');
136
+ });
137
+ });
@@ -0,0 +1,79 @@
1
+ import { describe, expect, it, beforeAll } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { EditorProvider } from '../EditorContext';
4
+ import { OutlinePanel } from '../OutlinePanel';
5
+
6
+ // jsdom lacks ResizeObserver — the pane wires one up to track the editor's
7
+ // page edge. A no-op shim is enough for the rendering tests below.
8
+ beforeAll(() => {
9
+ if (typeof globalThis.ResizeObserver === 'undefined') {
10
+ globalThis.ResizeObserver = class {
11
+ observe() {}
12
+ unobserve() {}
13
+ disconnect() {}
14
+ } as unknown as typeof ResizeObserver;
15
+ }
16
+ });
17
+
18
+ function renderOutline(markdown: string) {
19
+ return render(
20
+ <EditorProvider initialMarkdown={markdown} initialView="wysiwyg" articleId="test">
21
+ <OutlinePanel />
22
+ </EditorProvider>,
23
+ );
24
+ }
25
+
26
+ describe('OutlinePanel', () => {
27
+ it('renders the empty placeholder when the doc has no headings', async () => {
28
+ renderOutline('Just a paragraph, no headings.\n');
29
+ expect(await screen.findByText(/add a heading to populate the outline/i)).toBeTruthy();
30
+ });
31
+
32
+ it('renders one row per heading and nests by depth', async () => {
33
+ const md = [
34
+ '# Top Level',
35
+ '',
36
+ '## Subsection A',
37
+ '',
38
+ 'Body.',
39
+ '',
40
+ '### Detail',
41
+ '',
42
+ 'Body.',
43
+ '',
44
+ '## Subsection B',
45
+ '',
46
+ 'Body.',
47
+ '',
48
+ ].join('\n');
49
+
50
+ const { container } = renderOutline(md);
51
+ await screen.findByTestId('outline-panel');
52
+
53
+ const rows = container.querySelectorAll('.squisq-outline-row');
54
+ expect(rows.length).toBe(4);
55
+
56
+ // Depth modifier classes are applied per heading level.
57
+ const depths = Array.from(rows).map((r) => {
58
+ const match = r.className.match(/squisq-outline-row--depth-(\d)/);
59
+ return match ? Number(match[1]) : null;
60
+ });
61
+ expect(depths).toEqual([1, 2, 3, 2]);
62
+
63
+ // Heading text is reflected in row labels.
64
+ const labels = Array.from(rows).map((r) => r.textContent?.trim());
65
+ expect(labels).toContain('Top Level');
66
+ expect(labels).toContain('Subsection A');
67
+ expect(labels).toContain('Detail');
68
+ expect(labels).toContain('Subsection B');
69
+ });
70
+
71
+ it('shows a template chip on annotated headings', async () => {
72
+ const md = '# Welcome {[title]}\n\nIntro.\n';
73
+ const { container } = renderOutline(md);
74
+ await screen.findByTestId('outline-panel');
75
+ const chip = container.querySelector('.squisq-outline-template-chip');
76
+ expect(chip).not.toBeNull();
77
+ expect(chip!.textContent).toContain('Title');
78
+ });
79
+ });
@@ -0,0 +1,107 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import type { MediaProvider } from '@bendyline/squisq/schemas';
4
+ import { resolveTheme } from '@bendyline/squisq/schemas';
5
+ import { PlainHtmlPreview } from '../PlainHtmlPreview';
6
+
7
+ function getIframeSrcDoc(): string {
8
+ const iframe = screen.getByTestId('plain-html-preview') as HTMLIFrameElement;
9
+ return iframe.srcdoc;
10
+ }
11
+
12
+ describe('PlainHtmlPreview', () => {
13
+ it('renders the markdown into the iframe srcDoc', () => {
14
+ render(<PlainHtmlPreview markdown={'# Hello\n\nWorld'} />);
15
+ const srcdoc = getIframeSrcDoc();
16
+ expect(srcdoc).toContain('<!DOCTYPE html>');
17
+ expect(srcdoc).toContain('<h1>Hello</h1>');
18
+ expect(srcdoc).toContain('<p>World</p>');
19
+ });
20
+
21
+ it('refuses scripts via sandbox attribute (allow-same-origin only)', () => {
22
+ render(<PlainHtmlPreview markdown={'hi'} />);
23
+ const iframe = screen.getByTestId('plain-html-preview') as HTMLIFrameElement;
24
+ const sandbox = iframe.getAttribute('sandbox') ?? '';
25
+ expect(sandbox).toContain('allow-same-origin');
26
+ expect(sandbox).not.toContain('allow-scripts');
27
+ });
28
+
29
+ it('applies pre-resolved images via the images prop', () => {
30
+ render(
31
+ <PlainHtmlPreview
32
+ markdown={'![a](a.jpg)'}
33
+ images={new Map([['a.jpg', 'data:image/png;base64,AAA']])}
34
+ />,
35
+ );
36
+ expect(getIframeSrcDoc()).toContain('src="data:image/png;base64,AAA"');
37
+ });
38
+
39
+ it('resolves image URLs through the supplied mediaProvider', async () => {
40
+ const provider: MediaProvider = {
41
+ resolveUrl: vi.fn(async (ref: string) => `blob:resolved/${ref}`),
42
+ listMedia: vi.fn(async () => []),
43
+ addMedia: vi.fn(async () => ''),
44
+ removeMedia: vi.fn(async () => {}),
45
+ dispose: vi.fn(() => {}),
46
+ };
47
+ render(<PlainHtmlPreview markdown={'![a](cat.jpg)'} mediaProvider={provider} />);
48
+ await waitFor(() => {
49
+ expect(getIframeSrcDoc()).toContain('src="blob:resolved/cat.jpg"');
50
+ });
51
+ expect(provider.resolveUrl).toHaveBeenCalledWith('cat.jpg');
52
+ });
53
+
54
+ it('does not resolve external URLs through the provider', async () => {
55
+ const provider: MediaProvider = {
56
+ resolveUrl: vi.fn(async (ref: string) => `blob:should-not-fire/${ref}`),
57
+ listMedia: vi.fn(async () => []),
58
+ addMedia: vi.fn(async () => ''),
59
+ removeMedia: vi.fn(async () => {}),
60
+ dispose: vi.fn(() => {}),
61
+ };
62
+ render(
63
+ <PlainHtmlPreview markdown={'![ext](https://example.com/x.jpg)'} mediaProvider={provider} />,
64
+ );
65
+ await waitFor(() => {
66
+ expect(getIframeSrcDoc()).toContain('src="https://example.com/x.jpg"');
67
+ });
68
+ expect(provider.resolveUrl).not.toHaveBeenCalled();
69
+ });
70
+
71
+ it('applies the supplied theme to the iframe document', () => {
72
+ const theme = resolveTheme('warm-earth');
73
+ render(<PlainHtmlPreview markdown={'# Hi'} theme={theme} />);
74
+ const srcdoc = getIframeSrcDoc();
75
+ expect(srcdoc).toContain(`--plain-bg: ${theme.colors.background};`);
76
+ expect(srcdoc).toContain(`--plain-primary: ${theme.colors.primary};`);
77
+ expect(srcdoc).toContain('--plain-body-font:');
78
+ });
79
+
80
+ it('loads Google Fonts for themes that reference google-hosted faces', () => {
81
+ const theme = resolveTheme('documentary');
82
+ render(<PlainHtmlPreview markdown={'# Hi'} theme={theme} />);
83
+ expect(getIframeSrcDoc()).toContain('https://fonts.googleapis.com/css2?');
84
+ });
85
+
86
+ it('resolves raw HTML <img> tags too (resized image case)', async () => {
87
+ // Resized images round-trip through the markdown source as raw
88
+ // HTML, which the preview must scan to keep the iframe's <img>
89
+ // pointing at a fetchable blob URL.
90
+ const provider: MediaProvider = {
91
+ resolveUrl: vi.fn(async (ref: string) => `blob:r/${ref}`),
92
+ listMedia: vi.fn(async () => []),
93
+ addMedia: vi.fn(async () => ''),
94
+ removeMedia: vi.fn(async () => {}),
95
+ dispose: vi.fn(() => {}),
96
+ };
97
+ render(
98
+ <PlainHtmlPreview
99
+ markdown={'<img alt="resized" src="resized.png" width="194">'}
100
+ mediaProvider={provider}
101
+ />,
102
+ );
103
+ await waitFor(() => {
104
+ expect(getIframeSrcDoc()).toContain('src="blob:r/resized.png"');
105
+ });
106
+ });
107
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import { fireEvent, render, screen } from '@testing-library/react';
6
+ import type { ImageEditDoc } from '@bendyline/squisq/schemas';
7
+ import { PropertiesPanel } from '../imageEditor/PropertiesPanel.js';
8
+ import type { ImageEditorAction } from '../imageEditor/state.js';
9
+
10
+ function buildDoc(): ImageEditDoc {
11
+ return {
12
+ version: 1,
13
+ canvas: { width: 100, height: 80, background: '#ffffff' },
14
+ layers: [
15
+ {
16
+ id: 'a',
17
+ type: 'shape',
18
+ name: 'Box',
19
+ position: { x: 5, y: 6, width: 30, height: 20 },
20
+ content: { shape: 'rect', fill: '#3399ff' },
21
+ },
22
+ ],
23
+ };
24
+ }
25
+
26
+ describe('PropertiesPanel', () => {
27
+ it('shows an empty hint when no layer is selected', () => {
28
+ const dispatch = vi.fn();
29
+ render(<PropertiesPanel doc={buildDoc()} selectedLayerId={null} dispatch={dispatch} />);
30
+ expect(screen.getByText(/no layer selected/i)).toBeTruthy();
31
+ });
32
+
33
+ it('editing the canvas width dispatches set-canvas', () => {
34
+ const dispatch = vi.fn<(a: ImageEditorAction) => void>();
35
+ render(<PropertiesPanel doc={buildDoc()} selectedLayerId={null} dispatch={dispatch} />);
36
+ const widthInput = screen.getByDisplayValue('100') as HTMLInputElement;
37
+ fireEvent.change(widthInput, { target: { value: '200' } });
38
+ expect(dispatch).toHaveBeenCalledWith({
39
+ type: 'set-canvas',
40
+ canvas: expect.objectContaining({ width: 200, height: 80 }),
41
+ });
42
+ });
43
+
44
+ it('renaming a layer dispatches update-layer with patch.name', () => {
45
+ const dispatch = vi.fn<(a: ImageEditorAction) => void>();
46
+ render(<PropertiesPanel doc={buildDoc()} selectedLayerId="a" dispatch={dispatch} />);
47
+ const nameInput = screen.getByDisplayValue('Box') as HTMLInputElement;
48
+ fireEvent.change(nameInput, { target: { value: 'Renamed' } });
49
+ expect(dispatch).toHaveBeenCalledWith({
50
+ type: 'update-layer',
51
+ layerId: 'a',
52
+ patch: expect.objectContaining({ name: 'Renamed' }),
53
+ });
54
+ });
55
+
56
+ it('changing position X dispatches update-layer with merged position', () => {
57
+ const dispatch = vi.fn<(a: ImageEditorAction) => void>();
58
+ render(<PropertiesPanel doc={buildDoc()} selectedLayerId="a" dispatch={dispatch} />);
59
+ const xInput = screen.getByDisplayValue('5') as HTMLInputElement;
60
+ fireEvent.change(xInput, { target: { value: '50' } });
61
+ const calls = dispatch.mock.calls.map((c) => c[0]);
62
+ const last = calls[calls.length - 1];
63
+ expect(last).toMatchObject({
64
+ type: 'update-layer',
65
+ layerId: 'a',
66
+ patch: { position: { x: 50, y: 6, width: 30, height: 20 } },
67
+ });
68
+ });
69
+ });