@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,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,110 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { MediaProvider } from '@bendyline/squisq/schemas';
3
+ import { markdownToTiptap, tiptapToMarkdown } from '../tiptapBridge';
4
+
5
+ /**
6
+ * Attachment-flow regression: earlier versions of MediaBin dropped
7
+ * uploaded files into the bin without inserting a markdown ref into
8
+ * the editor body. A user would upload an image, hit Send in the
9
+ * downstream chat composer, and the outgoing markdown would have no
10
+ * image reference — the gezel would reply "nothing came through."
11
+ *
12
+ * The fix: after `mediaProvider.addMedia(...)` succeeds, MediaBin
13
+ * fires `onMediaUploaded(relativePath, name, mimeType)`. The
14
+ * EditorShell wires this to an `insertAtCursor` that emits
15
+ * `![alt](attachments/<filename>)` so the file actually participates
16
+ * in the outgoing markdown.
17
+ *
18
+ * These tests exercise the contract directly: the markdown snippet
19
+ * produced by the upload callback, once round-tripped through the
20
+ * editor's markdown↔HTML bridge, must round-trip back to a form
21
+ * the gezel service's image-extraction regex can see.
22
+ */
23
+
24
+ function fakeMediaProvider(records: string[]): MediaProvider {
25
+ let counter = 0;
26
+ return {
27
+ async addMedia(name: string, _data: ArrayBuffer | Blob | Uint8Array, _mime: string) {
28
+ counter += 1;
29
+ const relative = `attachments/${counter}-${name}`;
30
+ records.push(relative);
31
+ return relative;
32
+ },
33
+ async resolveUrl(relPath: string) {
34
+ return relPath;
35
+ },
36
+ async listMedia() {
37
+ return [];
38
+ },
39
+ async removeMedia(_relPath: string) {
40
+ /* no-op */
41
+ },
42
+ dispose() {
43
+ /* no-op */
44
+ },
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Re-implements the exact snippet EditorShell's `insertMediaRef`
50
+ * builds. Keeping this aligned with the real impl would ordinarily
51
+ * rely on directly importing the helper; since it's currently inline
52
+ * in EditorShell, mirror the logic here and lean on the test to
53
+ * alert us if we drift apart.
54
+ */
55
+ function buildAttachmentSnippet(relativePath: string, name: string, mimeType: string): string {
56
+ const alt = name.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ');
57
+ return mimeType.startsWith('image/') ? `![${alt}](${relativePath})` : `[${alt}](${relativePath})`;
58
+ }
59
+
60
+ describe('media attachment flow', () => {
61
+ it('addMedia → buildAttachmentSnippet → markdown round-trip keeps the ref', async () => {
62
+ const records: string[] = [];
63
+ const provider = fakeMediaProvider(records);
64
+
65
+ // Simulate MediaBin.handleFileChange for a single PNG drop.
66
+ const pngBytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47]);
67
+ const relative = await provider.addMedia('my_screenshot.png', pngBytes, 'image/png');
68
+
69
+ expect(records).toEqual([relative]);
70
+ const snippet = buildAttachmentSnippet(relative, 'my_screenshot.png', 'image/png');
71
+ expect(snippet).toBe(`![my screenshot](${relative})`);
72
+
73
+ // Insert snippet into the editor: markdown → HTML → markdown.
74
+ // This is the path a real insertAtCursor + tiptap onUpdate goes
75
+ // through. The outbound markdown must still contain the ref.
76
+ const html = markdownToTiptap(snippet);
77
+ expect(html).toMatch(/<img\b/);
78
+ expect(html).toContain(`src="${relative}"`);
79
+
80
+ const back = tiptapToMarkdown(html);
81
+ expect(back).toContain(`![my screenshot](${relative})`);
82
+ });
83
+
84
+ it('handles empty-alt (most common pasted-image shape)', async () => {
85
+ const records: string[] = [];
86
+ const provider = fakeMediaProvider(records);
87
+ const relative = await provider.addMedia('pasted.png', new Uint8Array([0]), 'image/png');
88
+
89
+ // Simulate what happens when alt is empty — common for bare pastes
90
+ // where the user hasn't typed a caption.
91
+ const snippet = `![](${relative})`;
92
+ const html = markdownToTiptap(snippet);
93
+ expect(html).toMatch(/<img\b/);
94
+ expect(html).toContain(`src="${relative}"`);
95
+ const back = tiptapToMarkdown(html);
96
+ expect(back).toContain(`![](${relative})`);
97
+ });
98
+
99
+ it('non-image files fall back to a plain link, still preserving the ref', async () => {
100
+ const records: string[] = [];
101
+ const provider = fakeMediaProvider(records);
102
+ const relative = await provider.addMedia('design.pdf', new Uint8Array([0]), 'application/pdf');
103
+ const snippet = buildAttachmentSnippet(relative, 'design.pdf', 'application/pdf');
104
+ expect(snippet).toBe(`[design](${relative})`);
105
+ // Non-images don't go through the `<img>` regex — they stay as
106
+ // plain markdown links, which the service-side extractor ignores
107
+ // but the UI renders as normal hyperlinks.
108
+ expect(snippet).not.toContain('!');
109
+ });
110
+ });
@@ -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
+ });