@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
@@ -11,6 +11,9 @@
11
11
  * using squisq's own parser.
12
12
  */
13
13
 
14
+ import { templateLabel } from './TemplatePicker';
15
+ import { resolveIcon } from '@bendyline/squisq/icons';
16
+
14
17
  // Hoisted regex patterns for inline markdown ↔ HTML conversion
15
18
  const RE_BOLD_STAR = /\*\*(.+?)\*\*/g;
16
19
  const RE_BOLD_UNDER = /__(.+?)__/g;
@@ -19,12 +22,24 @@ const RE_ITALIC_UNDER = /_(.+?)_/g;
19
22
  const RE_STRIKETHROUGH = /~~(.+?)~~/g;
20
23
  const RE_INLINE_CODE = /`(.+?)`/g;
21
24
  const RE_LINK = /\[(.+?)\]\((.+?)\)/g;
22
- const RE_IMAGE = /!\[(.+?)\]\((.+?)\)/g;
25
+ // `*?` on the alt — an empty alt (`![](foo.png)`) is valid markdown and
26
+ // the most common shape for pasted/uploaded images that don't yet have
27
+ // a human-picked caption. Previously required at least one alt char,
28
+ // which dropped those images on the floor during markdown→HTML.
29
+ const RE_IMAGE = /!\[(.*?)\]\((.+?)\)/g;
23
30
  // Mentions: `@[Display](scheme:id)` — scheme-part must start with a letter
24
31
  // so plain `$100` or price-style parentheticals don't accidentally match.
25
32
  // remark-stringify may round-trip the colon as `\:` — tolerate either.
26
33
  const RE_MENTION = /@\[([^\]]+?)\]\(([a-z][a-z0-9+.-]*)\\?:([^)\s]+)\)/gi;
27
34
  const RE_MENTION_TAG = /<span\b[^>]*?\bdata-mention\b[^>]*?>(?:<[^>]+>)*([^<]*)<\/span>/gi;
35
+
36
+ // Inline FontAwesome icon. Markdown form: `{[github]}` (bare) or
37
+ // `{[fa-solid:user]}` (qualified). HTML form (produced by the
38
+ // `InlineIcon` Tiptap node and consumed back by `htmlToInline`):
39
+ // `<i data-icon="github" data-family="brands" data-name="github"
40
+ // class="fa-brands fa-github" contenteditable="false"></i>`.
41
+ const RE_ICON_MD = /\{\[([a-zA-Z0-9_:-]+)\]\}/g;
42
+ const RE_ICON_TAG = /<i\b[^>]*?\bdata-icon="([^"]*)"[^>]*?><\/i>/gi;
28
43
  const RE_STRONG_TAG = /<strong>(.*?)<\/strong>/g;
29
44
  const RE_B_TAG = /<b>(.*?)<\/b>/g;
30
45
  const RE_EM_TAG = /<em>(.*?)<\/em>/g;
@@ -33,7 +48,14 @@ const RE_S_TAG = /<s>(.*?)<\/s>/g;
33
48
  const RE_DEL_TAG = /<del>(.*?)<\/del>/g;
34
49
  const RE_CODE_TAG = /<code>(.*?)<\/code>/g;
35
50
  const RE_A_TAG = /<a[^>]+href="([^"]*)"[^>]*>(.*?)<\/a>/g;
36
- const RE_IMG_TAG = /<img[^>]+alt="([^"]*)"[^>]+src="([^"]*)"[^>]*>/g;
51
+ // Matches any `<img>` tag and captures its `src` + `alt` regardless of
52
+ // attribute order. TipTap's Image extension renders `<img src="..."
53
+ // alt="...">` (src first), while some other producers — including our
54
+ // own `markdownToTiptap` conversion — emit alt-first. The previous
55
+ // regex required alt-before-src and silently dropped every src-first
56
+ // image; `RE_STRIP_TAGS` below would then delete the unmatched tag,
57
+ // so the outgoing markdown had no image reference at all.
58
+ const RE_IMG_TAG = /<img\b([^>]*)>/g;
37
59
  const RE_STRIP_TAGS = /<[^>]+>/g;
38
60
 
39
61
  /**
@@ -172,8 +194,11 @@ export function markdownToTiptap(markdown: string): string {
172
194
  let text = headingMatch[2];
173
195
  let attrs = '';
174
196
 
175
- // Extract {[template key=value …]} annotation
176
- const annotMatch = text.match(/\s*\{\[([^\]]+)\]\}\s*$/);
197
+ // Extract {[template key=value …]} annotation. Trailing `[\s\]\}]*`
198
+ // tolerates accidental doubled `]}` that users type while learning
199
+ // the syntax — must stay in sync with TEMPLATE_ANNOTATION_RE in
200
+ // packages/core/src/markdown/convert.ts.
201
+ const annotMatch = text.match(/\s*\{\[([^\]]+)\]\}[\s\]}]*$/);
177
202
  if (annotMatch) {
178
203
  text = text.slice(0, annotMatch.index!).trimEnd();
179
204
  const tokens = annotMatch[1].trim().split(/\s+/);
@@ -252,6 +277,44 @@ export function markdownToTiptap(markdown: string): string {
252
277
  continue;
253
278
  }
254
279
 
280
+ // Standalone image — emit as a top-level block `<img>` instead of
281
+ // wrapping in `<p>`. The Tiptap Image extension is configured with
282
+ // `inline: false`, so `<p><img></p>` parses to an empty paragraph
283
+ // (the block image can't live inside the paragraph). That bug
284
+ // manifested as a broken-image glyph after a markdown → WYSIWYG
285
+ // round-trip for any dropped/pasted image.
286
+ const standaloneImageMatch = line.trim().match(/^!\[(.*?)\]\((.+?)\)$/);
287
+ if (standaloneImageMatch) {
288
+ flushList();
289
+ const alt = escapeHtml(standaloneImageMatch[1] ?? '');
290
+ const src = escapeHtml(standaloneImageMatch[2] ?? '');
291
+ outputBlocks.push(`<img alt="${alt}" src="${src}">`);
292
+ continue;
293
+ }
294
+
295
+ // Standalone raw HTML `<img>` line — emitted by `tiptapToMarkdown`
296
+ // when the user has resized an image (width/height attrs are
297
+ // serialized as HTML rather than markdown shorthand so the
298
+ // dimensions survive round-trip). Pass the tag through unchanged
299
+ // so Tiptap's Image extension parses width/height attributes.
300
+ const trimmed = line.trim();
301
+ if (/^<img\b[^>]*>$/i.test(trimmed)) {
302
+ flushList();
303
+ outputBlocks.push(trimmed);
304
+ continue;
305
+ }
306
+
307
+ // Standalone `<video>` / `<audio>` line — emitted by the recorder
308
+ // (RecorderEntry) when it saves a clip, and by `tiptapToMarkdown`
309
+ // when the WYSIWYG editor's TiptapVideo/TiptapAudio nodes serialize
310
+ // back to markdown. Pass through unchanged so the editor's parseHTML
311
+ // picks up the tag attributes (`src`, `controls`, …).
312
+ if (/^<(?:video|audio)\b[^>]*>(?:[\s\S]*?<\/(?:video|audio)>)?$/i.test(trimmed)) {
313
+ flushList();
314
+ outputBlocks.push(trimmed);
315
+ continue;
316
+ }
317
+
255
318
  // Regular paragraph
256
319
  flushList();
257
320
  outputBlocks.push(`<p>${inlineToHtml(line)}</p>`);
@@ -295,6 +358,15 @@ export function tiptapToMarkdown(html: string): string {
295
358
  const tmplMatch = attrs.match(/data-template="([^"]+)"/);
296
359
  if (tmplMatch) {
297
360
  let annotation = tmplMatch[1];
361
+ // Defensive: an earlier broken build briefly rendered the
362
+ // template label as a real text node inside the badge, which
363
+ // bled the label into the heading's textContent. Strip a
364
+ // trailing copy of the template's label so existing documents
365
+ // self-heal on save.
366
+ const label = templateLabel(annotation);
367
+ if (label && text.endsWith(label)) {
368
+ text = text.slice(0, -label.length).trimEnd();
369
+ }
298
370
  const paramsMatch = attrs.match(/data-template-params="([^"]+)"/);
299
371
  if (paramsMatch) {
300
372
  annotation += ' ' + unescapeHtml(paramsMatch[1]);
@@ -462,6 +534,40 @@ export function tiptapToMarkdown(html: string): string {
462
534
  continue;
463
535
  }
464
536
 
537
+ // Block-level image. TipTap's Image extension with `inline: false`
538
+ // emits `<img src alt>` as a bare top-level element (no wrapping
539
+ // `<p>`). Without this handler the skip-unknown-tags catch-all
540
+ // below silently drops the image from the outgoing markdown —
541
+ // the bug that made the chat composer ship image-less messages
542
+ // even though the editor showed the picture. Handled here,
543
+ // before the inline walker ever sees it.
544
+ const imgMatch = remaining.match(/^<img\b([^>]*)>/);
545
+ if (imgMatch) {
546
+ const attrs = imgMatch[1] ?? '';
547
+ const src = /\bsrc="([^"]*)"/i.exec(attrs)?.[1];
548
+ if (src) {
549
+ const alt = /\balt="([^"]*)"/i.exec(attrs)?.[1] ?? '';
550
+ lines.push(serializeImage(src, alt, attrs));
551
+ lines.push('');
552
+ }
553
+ remaining = remaining.slice(imgMatch[0].length);
554
+ continue;
555
+ }
556
+
557
+ // Block-level `<video>` / `<audio>` — emitted by our TiptapVideo /
558
+ // TiptapAudio atom nodes (block group). Serialize the whole tag
559
+ // (opening + closing) back to markdown unchanged; CommonMark allows
560
+ // inline HTML, and our renderer plus the InlinePreviewGutter both
561
+ // know how to parse the htmlElement back out.
562
+ const mediaMatch = remaining.match(/^<(video|audio)\b([^>]*)>(?:[\s\S]*?<\/\1>)?/);
563
+ if (mediaMatch) {
564
+ const tag = mediaMatch[1] === 'video' ? 'video' : 'audio';
565
+ lines.push(serializeMediaTag(tag, mediaMatch[2] ?? ''));
566
+ lines.push('');
567
+ remaining = remaining.slice(mediaMatch[0].length);
568
+ continue;
569
+ }
570
+
465
571
  // Skip unknown tags or whitespace
466
572
  const skipMatch = remaining.match(/^(<[^>]+>|\s+)/);
467
573
  if (skipMatch) {
@@ -548,6 +654,51 @@ function parseAlignments(separatorLine: string): (string | null)[] {
548
654
 
549
655
  // ─── Helpers ─────────────────────────────────────────────
550
656
 
657
+ /**
658
+ * Serialize a parsed `<img>` tag back to markdown. When the tag carries
659
+ * an explicit `width` and/or `height` we emit a raw HTML `<img>` (the
660
+ * markdown shorthand `![alt](src)` has no syntax for dimensions);
661
+ * otherwise the friendlier shorthand is used. Markdown allows inline
662
+ * HTML, so the HTML form parses and renders identically in any
663
+ * CommonMark/GFM viewer.
664
+ */
665
+ /**
666
+ * Serialize a `<video>` or `<audio>` tag (from the Tiptap atom node's
667
+ * `renderHTML`) back to markdown. We re-emit only the attributes the
668
+ * recorder + the renderer care about, in a stable order, so the
669
+ * round-tripped markdown stays deterministic regardless of how Tiptap
670
+ * decided to order them on its output.
671
+ */
672
+ function serializeMediaTag(tag: 'video' | 'audio', attrs: string): string {
673
+ const src = /\bsrc="([^"]*)"/i.exec(attrs)?.[1] ?? '';
674
+ const controls = /\bcontrols\b/i.test(attrs);
675
+ const width = /\bwidth="([^"]*)"/i.exec(attrs)?.[1];
676
+ const height = /\bheight="([^"]*)"/i.exec(attrs)?.[1];
677
+ const poster = tag === 'video' ? /\bposter="([^"]*)"/i.exec(attrs)?.[1] : undefined;
678
+ const parts = [`<${tag} src="${src}"`];
679
+ if (controls) parts.push(' controls');
680
+ if (width) parts.push(` width="${width}"`);
681
+ if (height) parts.push(` height="${height}"`);
682
+ if (poster) parts.push(` poster="${poster}"`);
683
+ parts.push(`></${tag}>`);
684
+ return parts.join('');
685
+ }
686
+
687
+ function serializeImage(src: string, alt: string, attrs: string): string {
688
+ const width = /\bwidth="([^"]*)"/i.exec(attrs)?.[1];
689
+ const height = /\bheight="([^"]*)"/i.exec(attrs)?.[1];
690
+ const title = /\btitle="([^"]*)"/i.exec(attrs)?.[1];
691
+ if (!width && !height) {
692
+ return `![${alt}](${src})`;
693
+ }
694
+ const parts = [`<img alt="${alt}" src="${src}"`];
695
+ if (width) parts.push(` width="${width}"`);
696
+ if (height) parts.push(` height="${height}"`);
697
+ if (title) parts.push(` title="${title}"`);
698
+ parts.push('>');
699
+ return parts.join('');
700
+ }
701
+
551
702
  function escapeHtml(text: string): string {
552
703
  return text
553
704
  .replace(/&/g, '&amp;')
@@ -566,7 +717,56 @@ function unescapeHtml(text: string): string {
566
717
 
567
718
  /** Convert inline markdown to HTML for Tiptap consumption */
568
719
  function inlineToHtml(text: string): string {
569
- let result = escapeHtml(text);
720
+ // Extract images/mentions/links to opaque placeholders BEFORE running
721
+ // any inline-formatting regexes. Otherwise `_` characters inside a URL
722
+ // (e.g. `mikehome_files/IMG_6829.JPEG`) get turned into `<em>` tags by
723
+ // the underscore-italic rule, mangling the src so the image renders
724
+ // broken after a markdown ↔ WYSIWYG round-trip.
725
+ const placeholders: string[] = [];
726
+ const stash = (html: string): string => {
727
+ const token = `\u0000PH${placeholders.length}\u0000`;
728
+ placeholders.push(html);
729
+ return token;
730
+ };
731
+
732
+ // We run extraction on the RAW text (before escapeHtml) so the
733
+ // captured groups are the literal markdown contents; then we
734
+ // selectively escape the parts of each placeholder that need it.
735
+
736
+ // Images first: ![alt](src) — must be before links so the `!` prefix is consumed
737
+ let staged = text.replace(RE_IMAGE, (_m, alt, src) =>
738
+ stash(`<img alt="${escapeHtml(alt)}" src="${escapeHtml(src)}">`),
739
+ );
740
+
741
+ // Mentions: @[Display](scheme:id) — must run before links so the
742
+ // bracket+paren isn't consumed as a regular link.
743
+ staged = staged.replace(RE_MENTION, (_m, label, kind, id) =>
744
+ stash(
745
+ `<span data-mention="true" data-kind="${escapeHtml(kind)}" data-id="${escapeHtml(id)}" data-label="${escapeHtml(label)}" class="mention">@${escapeHtml(label)}</span>`,
746
+ ),
747
+ );
748
+
749
+ // FontAwesome inline icons: {[github]} / {[fa-solid:user]} — resolve
750
+ // against the FA catalog so unknown / ambiguous tokens stay as
751
+ // literal text (preserved through escapeHtml later). Stashing here
752
+ // alongside images/mentions keeps the `_` and other punctuation in
753
+ // the class attribute from being mangled by the underscore-italic
754
+ // regex below.
755
+ staged = staged.replace(RE_ICON_MD, (full, token) => {
756
+ const icon = resolveIcon(token);
757
+ if (!icon) return full; // leave literal text — author may have meant it
758
+ return stash(
759
+ `<i class="fa-${icon.family} fa-${icon.name}" data-icon="${escapeHtml(token)}" data-family="${icon.family}" data-name="${icon.name}" contenteditable="false"></i>`,
760
+ );
761
+ });
762
+
763
+ // Links: [text](url) — stash but keep the link text available to
764
+ // inline formatting by recursing.
765
+ staged = staged.replace(RE_LINK, (_m, linkText, href) =>
766
+ stash(`<a href="${escapeHtml(href)}">${inlineToHtml(linkText)}</a>`),
767
+ );
768
+
769
+ let result = escapeHtml(staged);
570
770
 
571
771
  // Bold: **text** or __text__
572
772
  result = result.replace(RE_BOLD_STAR, '<strong>$1</strong>');
@@ -582,21 +782,11 @@ function inlineToHtml(text: string): string {
582
782
  // Inline code: `text`
583
783
  result = result.replace(RE_INLINE_CODE, '<code>$1</code>');
584
784
 
585
- // Images first: ![alt](src) must be before links so the `!` prefix is consumed
586
- result = result.replace(RE_IMAGE, '<img alt="$1" src="$2">');
587
-
588
- // Mentions: @[Display](scheme:id) — must run before links so the
589
- // bracket+paren isn't consumed as a regular link. The input here has
590
- // already been run through escapeHtml at the top of this function, so
591
- // the captured groups are safe to interpolate directly.
592
- result = result.replace(
593
- RE_MENTION,
594
- (_match, label, kind, id) =>
595
- `<span data-mention="true" data-kind="${kind}" data-id="${id}" data-label="${label}" class="mention">@${label}</span>`,
596
- );
597
-
598
- // Links: [text](url)
599
- result = result.replace(RE_LINK, '<a href="$2">$1</a>');
785
+ // Restore placeholders. escapeHtml turned each `\u0000PHn\u0000` into
786
+ // the same string (the NULs and digits are escape-safe), so the
787
+ // restoration regex still matches.
788
+ // eslint-disable-next-line no-control-regex
789
+ result = result.replace(/\u0000PH(\d+)\u0000/g, (_m, idx) => placeholders[Number(idx)] ?? '');
600
790
 
601
791
  return result;
602
792
  }
@@ -609,6 +799,12 @@ function htmlToInline(html: string): string {
609
799
  // spaces + newline) before stripping tags so the newline survives.
610
800
  result = result.replace(/<br\s*\/?>/gi, ' \n');
611
801
 
802
+ // FontAwesome inline icons — emit the original token. Must run before
803
+ // RE_I_TAG (which matches a bare `<i>` and would otherwise eat icon
804
+ // tags too). The token captured via data-icon already carries the
805
+ // qualified form when needed, so source round-trips exactly.
806
+ result = result.replace(RE_ICON_TAG, (_m, token) => `{[${token}]}`);
807
+
612
808
  // Strong
613
809
  result = result.replace(RE_STRONG_TAG, '**$1**');
614
810
  result = result.replace(RE_B_TAG, '**$1**');
@@ -637,8 +833,17 @@ function htmlToInline(html: string): string {
637
833
  // Links
638
834
  result = result.replace(RE_A_TAG, '[$2]($1)');
639
835
 
640
- // Images
641
- result = result.replace(RE_IMG_TAG, '![$1]($2)');
836
+ // Images — order-agnostic attribute parsing (tiptap emits src-first,
837
+ // our markdown-to-html emits alt-first; either must serialize back).
838
+ // When a width/height is present we serialize as raw HTML `<img>` so
839
+ // the dimensions survive the round-trip; otherwise the markdown
840
+ // shorthand `![alt](src)` is used.
841
+ result = result.replace(RE_IMG_TAG, (match, attrs: string) => {
842
+ const src = /\bsrc="([^"]*)"/i.exec(attrs)?.[1];
843
+ if (!src) return match;
844
+ const alt = /\balt="([^"]*)"/i.exec(attrs)?.[1] ?? '';
845
+ return serializeImage(src, alt, attrs);
846
+ });
642
847
 
643
848
  // Strip remaining tags
644
849
  result = result.replace(RE_STRIP_TAGS, '');
@@ -0,0 +1,294 @@
1
+ /**
2
+ * useHeadingLayout
3
+ *
4
+ * Shared positioning hook for the editor gutters (`OutlinePanel` and
5
+ * `InlinePreviewGutter`). Each gutter needs to know:
6
+ * - where every heading sits vertically inside the wrapper
7
+ * - where the editor "page" edges sit horizontally
8
+ * - how to scroll the editor to a particular heading
9
+ *
10
+ * Two backends, picked from `EditorContext.activeView`:
11
+ * - **wysiwyg** — query DOM headings inside `.squisq-wysiwyg-container`,
12
+ * measure with `getBoundingClientRect`, scroll via `scrollIntoView`.
13
+ * Live updates via `ResizeObserver` + `MutationObserver`.
14
+ * - **raw** — walk `doc.blocks`, derive each heading's line number from
15
+ * `MarkdownHeading.position.start.line`, ask Monaco for the line's
16
+ * pixel offset via `getTopForLineNumber`, scroll via
17
+ * `revealLineInCenterIfOutsideViewport` + `setPosition`. Live updates
18
+ * via `onDidScrollChange` + `onDidChangeModelContent`.
19
+ *
20
+ * Both backends produce coordinates in the *wrapper's* reference frame
21
+ * (i.e., `.squisq-editor-with-gutter`), so the rendering layers — cards,
22
+ * extent bars, outline rows — don't care which editor is active.
23
+ */
24
+
25
+ import { useCallback, useEffect, useMemo, useState } from 'react';
26
+ import type { RefObject } from 'react';
27
+ import type { Block } from '@bendyline/squisq/schemas';
28
+ import { flattenBlocks, hasTemplate } from '@bendyline/squisq/doc';
29
+ import { useEditorContext } from './EditorContext';
30
+
31
+ export interface HeadingLayoutEntry {
32
+ /** The block (heading-rooted unit) this entry represents. */
33
+ block: Block;
34
+ /** Heading top, in px relative to the wrapper's top edge. */
35
+ top: number;
36
+ /** Where this block ends — the next heading's top, or the editor bottom. */
37
+ bottom: number;
38
+ /** True when the heading has a recognised template annotation. */
39
+ annotated: boolean;
40
+ }
41
+
42
+ export interface HeadingLayout {
43
+ /** All headings, in document order. */
44
+ entries: HeadingLayoutEntry[];
45
+ /** Editor page's left/right edges, in px relative to the wrapper. */
46
+ pageEdges: { left: number; right: number } | null;
47
+ /** Scroll the active editor to bring the block's heading into view. */
48
+ scrollToBlock: (block: Block) => void;
49
+ /** True once a measurement has produced numbers (avoids flicker on mount). */
50
+ ready: boolean;
51
+ }
52
+
53
+ /**
54
+ * @param refInsideWrapper — any DOM ref under `.squisq-editor-with-gutter`.
55
+ * The hook walks up to find the wrapper.
56
+ */
57
+ export function useHeadingLayout(refInsideWrapper: RefObject<HTMLElement | null>): HeadingLayout {
58
+ const { doc, activeView, monacoEditor, tiptapEditor } = useEditorContext();
59
+
60
+ const flatBlocks = useMemo(() => (doc ? flattenBlocks(doc.blocks) : []), [doc]);
61
+
62
+ const [entries, setEntries] = useState<HeadingLayoutEntry[]>([]);
63
+ const [pageEdges, setPageEdges] = useState<{ left: number; right: number } | null>(null);
64
+
65
+ // ── WYSIWYG backend ────────────────────────────────────────────────
66
+
67
+ useEffect(() => {
68
+ if (activeView !== 'wysiwyg') return;
69
+ const node = refInsideWrapper.current;
70
+ if (!node) return;
71
+ const wrapper = findWrapper(node);
72
+ if (!wrapper) return;
73
+ const wysiwygContainer = wrapper.querySelector<HTMLElement>('.squisq-wysiwyg-container');
74
+ if (!wysiwygContainer) return;
75
+
76
+ let raf = 0;
77
+ const recompute = () => {
78
+ cancelAnimationFrame(raf);
79
+ raf = requestAnimationFrame(() => {
80
+ const headings = Array.from(
81
+ wysiwygContainer.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6'),
82
+ );
83
+ const wrapperRect = wrapper.getBoundingClientRect();
84
+ const editorRect = wysiwygContainer.getBoundingClientRect();
85
+ const next: HeadingLayoutEntry[] = [];
86
+ flatBlocks.forEach((block, i) => {
87
+ const h = headings[i];
88
+ if (!h) return;
89
+ const top = h.getBoundingClientRect().top - wrapperRect.top;
90
+ const nextH = headings[i + 1];
91
+ const bottom = nextH
92
+ ? nextH.getBoundingClientRect().top - wrapperRect.top
93
+ : editorRect.bottom - wrapperRect.top;
94
+ next.push({
95
+ block,
96
+ top,
97
+ bottom,
98
+ annotated: !!h.getAttribute('data-template'),
99
+ });
100
+ });
101
+ setEntries((prev) => (sameEntries(prev, next) ? prev : next));
102
+
103
+ // Page edges — anchor to the .squisq-wysiwyg-editor (the centered "page").
104
+ const page = wysiwygContainer.querySelector<HTMLElement>('.squisq-wysiwyg-editor');
105
+ if (page) {
106
+ const pageRect = page.getBoundingClientRect();
107
+ const left = pageRect.left - wrapperRect.left;
108
+ const right = pageRect.right - wrapperRect.left;
109
+ setPageEdges((prev) =>
110
+ prev != null && Math.abs(prev.left - left) < 0.5 && Math.abs(prev.right - right) < 0.5
111
+ ? prev
112
+ : { left, right },
113
+ );
114
+ }
115
+ });
116
+ };
117
+
118
+ recompute();
119
+ const ro = new ResizeObserver(recompute);
120
+ ro.observe(wysiwygContainer);
121
+ const editorSurface = wysiwygContainer.querySelector('.squisq-wysiwyg-editor');
122
+ if (editorSurface) ro.observe(editorSurface);
123
+ const mo = new MutationObserver(recompute);
124
+ mo.observe(wysiwygContainer, {
125
+ childList: true,
126
+ subtree: true,
127
+ characterData: true,
128
+ attributes: true,
129
+ attributeFilter: ['data-template', 'data-template-params'],
130
+ });
131
+ wysiwygContainer.addEventListener('scroll', recompute, { passive: true });
132
+ window.addEventListener('resize', recompute);
133
+ const settle = window.setTimeout(recompute, 250);
134
+
135
+ return () => {
136
+ cancelAnimationFrame(raf);
137
+ window.clearTimeout(settle);
138
+ ro.disconnect();
139
+ mo.disconnect();
140
+ wysiwygContainer.removeEventListener('scroll', recompute);
141
+ window.removeEventListener('resize', recompute);
142
+ };
143
+ }, [activeView, flatBlocks, refInsideWrapper]);
144
+
145
+ // ── Raw (Monaco) backend ───────────────────────────────────────────
146
+
147
+ useEffect(() => {
148
+ if (activeView !== 'raw') return;
149
+ if (!monacoEditor) return;
150
+ const node = refInsideWrapper.current;
151
+ if (!node) return;
152
+ const wrapper = findWrapper(node);
153
+ if (!wrapper) return;
154
+
155
+ let raf = 0;
156
+ const recompute = () => {
157
+ cancelAnimationFrame(raf);
158
+ raf = requestAnimationFrame(() => {
159
+ const wrapperRect = wrapper.getBoundingClientRect();
160
+ const monacoRoot = monacoEditor.getDomNode() as HTMLElement | null;
161
+ if (!monacoRoot) return;
162
+ const monacoRect = monacoRoot.getBoundingClientRect();
163
+ // Y of the editor's content top in wrapper coordinates.
164
+ const monacoTop = monacoRect.top - wrapperRect.top;
165
+ const scrollTop = monacoEditor.getScrollTop();
166
+
167
+ const layoutInfo = monacoEditor.getLayoutInfo();
168
+ const next: HeadingLayoutEntry[] = [];
169
+ flatBlocks.forEach((block, i) => {
170
+ const line = block.sourceHeading?.position?.start.line;
171
+ if (typeof line !== 'number') return;
172
+ const lineTop = monacoEditor.getTopForLineNumber(line) - scrollTop + monacoTop;
173
+ // Bottom = next block's top, or the visible bottom of the editor.
174
+ const nextBlock = flatBlocks[i + 1];
175
+ const nextLine = nextBlock?.sourceHeading?.position?.start.line;
176
+ const bottom =
177
+ typeof nextLine === 'number'
178
+ ? monacoEditor.getTopForLineNumber(nextLine) - scrollTop + monacoTop
179
+ : monacoTop + layoutInfo.height;
180
+ const tplName = block.sourceHeading?.templateAnnotation?.template;
181
+ next.push({
182
+ block,
183
+ top: lineTop,
184
+ bottom,
185
+ annotated: !!tplName && hasTemplate(tplName),
186
+ });
187
+ });
188
+ setEntries((prev) => (sameEntries(prev, next) ? prev : next));
189
+
190
+ // Page edges — Monaco's editor area extends to its container's
191
+ // box edges; treat the whole editor as the "page".
192
+ const left = monacoRect.left - wrapperRect.left;
193
+ const right = monacoRect.right - wrapperRect.left;
194
+ setPageEdges((prev) =>
195
+ prev != null && Math.abs(prev.left - left) < 0.5 && Math.abs(prev.right - right) < 0.5
196
+ ? prev
197
+ : { left, right },
198
+ );
199
+ });
200
+ };
201
+
202
+ recompute();
203
+ const scrollSub = monacoEditor.onDidScrollChange(recompute);
204
+ const contentSub = monacoEditor.onDidChangeModelContent(recompute);
205
+ const layoutSub = monacoEditor.onDidLayoutChange(recompute);
206
+ const ro = new ResizeObserver(recompute);
207
+ const monacoRoot = monacoEditor.getDomNode();
208
+ if (monacoRoot) ro.observe(monacoRoot);
209
+ window.addEventListener('resize', recompute);
210
+ const settle = window.setTimeout(recompute, 250);
211
+
212
+ return () => {
213
+ cancelAnimationFrame(raf);
214
+ window.clearTimeout(settle);
215
+ scrollSub.dispose();
216
+ contentSub.dispose();
217
+ layoutSub.dispose();
218
+ ro.disconnect();
219
+ window.removeEventListener('resize', recompute);
220
+ };
221
+ }, [activeView, monacoEditor, flatBlocks, refInsideWrapper]);
222
+
223
+ // Reset edges when the active view changes — the previous view's
224
+ // measurements don't carry over to the next.
225
+ useEffect(() => {
226
+ setEntries([]);
227
+ setPageEdges(null);
228
+ }, [activeView]);
229
+
230
+ // ── scrollToBlock ──────────────────────────────────────────────────
231
+
232
+ const scrollToBlock = useCallback(
233
+ (block: Block) => {
234
+ if (activeView === 'wysiwyg') {
235
+ const node = refInsideWrapper.current;
236
+ const wrapper = node ? findWrapper(node) : null;
237
+ const wysiwygContainer = wrapper?.querySelector<HTMLElement>('.squisq-wysiwyg-container');
238
+ if (!wysiwygContainer) return;
239
+ const headings = wysiwygContainer.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6');
240
+ const index = flatBlocks.findIndex((b) => b.id === block.id);
241
+ if (index < 0 || index >= headings.length) return;
242
+ headings[index].scrollIntoView({ behavior: 'smooth', block: 'start' });
243
+ if (tiptapEditor) {
244
+ try {
245
+ tiptapEditor.chain().focus().run();
246
+ } catch {
247
+ // ignore
248
+ }
249
+ }
250
+ return;
251
+ }
252
+ if (activeView === 'raw' && monacoEditor) {
253
+ const line = block.sourceHeading?.position?.start.line;
254
+ if (typeof line !== 'number') return;
255
+ monacoEditor.revealLineInCenter(line);
256
+ monacoEditor.setPosition({ lineNumber: line, column: 1 });
257
+ monacoEditor.focus();
258
+ }
259
+ },
260
+ [activeView, flatBlocks, monacoEditor, refInsideWrapper, tiptapEditor],
261
+ );
262
+
263
+ return {
264
+ entries,
265
+ pageEdges,
266
+ scrollToBlock,
267
+ ready: entries.length > 0 || pageEdges != null,
268
+ };
269
+ }
270
+
271
+ // ── Helpers ────────────────────────────────────────────────────────
272
+
273
+ function findWrapper(node: HTMLElement): HTMLElement | null {
274
+ let cur: HTMLElement | null = node.parentElement;
275
+ while (cur) {
276
+ if (cur.classList.contains('squisq-editor-with-gutter')) return cur;
277
+ cur = cur.parentElement;
278
+ }
279
+ // Fallback: immediate parent (during transitional renames).
280
+ return node.parentElement;
281
+ }
282
+
283
+ function sameEntries(a: HeadingLayoutEntry[], b: HeadingLayoutEntry[]): boolean {
284
+ if (a.length !== b.length) return false;
285
+ for (let i = 0; i < a.length; i++) {
286
+ const x = a[i];
287
+ const y = b[i];
288
+ if (x.block.id !== y.block.id) return false;
289
+ if (x.annotated !== y.annotated) return false;
290
+ if (Math.abs(x.top - y.top) > 0.5) return false;
291
+ if (Math.abs(x.bottom - y.bottom) > 0.5) return false;
292
+ }
293
+ return true;
294
+ }