@bendyline/squisq-editor-react 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (446) hide show
  1. package/dist/DocumentSettingsDialog.d.ts +26 -0
  2. package/dist/DocumentSettingsDialog.d.ts.map +1 -0
  3. package/dist/DocumentSettingsDialog.js +115 -0
  4. package/dist/DocumentSettingsDialog.js.map +1 -0
  5. package/dist/EditorContext.d.ts +248 -4
  6. package/dist/EditorContext.d.ts.map +1 -1
  7. package/dist/EditorContext.js +248 -10
  8. package/dist/EditorContext.js.map +1 -1
  9. package/dist/EditorShell.d.ts +173 -4
  10. package/dist/EditorShell.d.ts.map +1 -1
  11. package/dist/EditorShell.js +110 -10
  12. package/dist/EditorShell.js.map +1 -1
  13. package/dist/EmojiPicker.d.ts +50 -0
  14. package/dist/EmojiPicker.d.ts.map +1 -0
  15. package/dist/EmojiPicker.js +182 -0
  16. package/dist/EmojiPicker.js.map +1 -0
  17. package/dist/ImageEditor.d.ts +68 -0
  18. package/dist/ImageEditor.d.ts.map +1 -0
  19. package/dist/ImageEditor.js +166 -0
  20. package/dist/ImageEditor.js.map +1 -0
  21. package/dist/ImageNodeView.d.ts +13 -1
  22. package/dist/ImageNodeView.d.ts.map +1 -1
  23. package/dist/ImageNodeView.js +172 -19
  24. package/dist/ImageNodeView.js.map +1 -1
  25. package/dist/ImageViewer.d.ts +26 -0
  26. package/dist/ImageViewer.d.ts.map +1 -0
  27. package/dist/ImageViewer.js +119 -0
  28. package/dist/ImageViewer.js.map +1 -0
  29. package/dist/InlineIcon.d.ts +17 -0
  30. package/dist/InlineIcon.d.ts.map +1 -0
  31. package/dist/InlineIcon.js +72 -0
  32. package/dist/InlineIcon.js.map +1 -0
  33. package/dist/InlinePreviewGutter.d.ts +52 -0
  34. package/dist/InlinePreviewGutter.d.ts.map +1 -0
  35. package/dist/InlinePreviewGutter.js +397 -0
  36. package/dist/InlinePreviewGutter.js.map +1 -0
  37. package/dist/LinkDialog.d.ts +43 -0
  38. package/dist/LinkDialog.d.ts.map +1 -0
  39. package/dist/LinkDialog.js +102 -0
  40. package/dist/LinkDialog.js.map +1 -0
  41. package/dist/MentionExtension.js +10 -7
  42. package/dist/MentionExtension.js.map +1 -1
  43. package/dist/OutlinePanel.d.ts +17 -0
  44. package/dist/OutlinePanel.d.ts.map +1 -0
  45. package/dist/OutlinePanel.js +167 -0
  46. package/dist/OutlinePanel.js.map +1 -0
  47. package/dist/PlainHtmlPreview.d.ts +50 -0
  48. package/dist/PlainHtmlPreview.d.ts.map +1 -0
  49. package/dist/PlainHtmlPreview.js +155 -0
  50. package/dist/PlainHtmlPreview.js.map +1 -0
  51. package/dist/PreviewControls.d.ts +15 -1
  52. package/dist/PreviewControls.d.ts.map +1 -1
  53. package/dist/PreviewControls.js +75 -18
  54. package/dist/PreviewControls.js.map +1 -1
  55. package/dist/PreviewPanel.d.ts +11 -10
  56. package/dist/PreviewPanel.d.ts.map +1 -1
  57. package/dist/PreviewPanel.js +20 -17
  58. package/dist/PreviewPanel.js.map +1 -1
  59. package/dist/RawEditor.d.ts.map +1 -1
  60. package/dist/RawEditor.js +198 -4
  61. package/dist/RawEditor.js.map +1 -1
  62. package/dist/RecorderEntry.d.ts +24 -0
  63. package/dist/RecorderEntry.d.ts.map +1 -0
  64. package/dist/RecorderEntry.js +139 -0
  65. package/dist/RecorderEntry.js.map +1 -0
  66. package/dist/TemplateAnnotation.d.ts.map +1 -1
  67. package/dist/TemplateAnnotation.js +32 -6
  68. package/dist/TemplateAnnotation.js.map +1 -1
  69. package/dist/TemplatePicker.d.ts +53 -0
  70. package/dist/TemplatePicker.d.ts.map +1 -0
  71. package/dist/TemplatePicker.js +388 -0
  72. package/dist/TemplatePicker.js.map +1 -0
  73. package/dist/ThemeCustomizerPanel.d.ts +32 -0
  74. package/dist/ThemeCustomizerPanel.d.ts.map +1 -0
  75. package/dist/ThemeCustomizerPanel.js +256 -0
  76. package/dist/ThemeCustomizerPanel.js.map +1 -0
  77. package/dist/ThemePicker.d.ts +33 -0
  78. package/dist/ThemePicker.d.ts.map +1 -0
  79. package/dist/ThemePicker.js +148 -0
  80. package/dist/ThemePicker.js.map +1 -0
  81. package/dist/Toolbar.d.ts.map +1 -1
  82. package/dist/Toolbar.js +508 -33
  83. package/dist/Toolbar.js.map +1 -1
  84. package/dist/VersionHistoryPanel.d.ts +14 -0
  85. package/dist/VersionHistoryPanel.d.ts.map +1 -0
  86. package/dist/VersionHistoryPanel.js +147 -0
  87. package/dist/VersionHistoryPanel.js.map +1 -0
  88. package/dist/ViewMenuPanel.d.ts +13 -0
  89. package/dist/ViewMenuPanel.d.ts.map +1 -0
  90. package/dist/ViewMenuPanel.js +58 -0
  91. package/dist/ViewMenuPanel.js.map +1 -0
  92. package/dist/WysiwygEditor.d.ts.map +1 -1
  93. package/dist/WysiwygEditor.js +198 -9
  94. package/dist/WysiwygEditor.js.map +1 -1
  95. package/dist/__tests__/detectMarkdown.test.js +0 -14
  96. package/dist/__tests__/detectMarkdown.test.js.map +1 -1
  97. package/dist/__tests__/documentSettingsDialog.test.d.ts +2 -0
  98. package/dist/__tests__/documentSettingsDialog.test.d.ts.map +1 -0
  99. package/dist/__tests__/documentSettingsDialog.test.js +132 -0
  100. package/dist/__tests__/documentSettingsDialog.test.js.map +1 -0
  101. package/dist/__tests__/emojiPicker.test.d.ts +2 -0
  102. package/dist/__tests__/emojiPicker.test.d.ts.map +1 -0
  103. package/dist/__tests__/emojiPicker.test.js +111 -0
  104. package/dist/__tests__/emojiPicker.test.js.map +1 -0
  105. package/dist/__tests__/fileKind.test.js +13 -0
  106. package/dist/__tests__/fileKind.test.js.map +1 -1
  107. package/dist/__tests__/imageEditAffordance.test.d.ts +2 -0
  108. package/dist/__tests__/imageEditAffordance.test.d.ts.map +1 -0
  109. package/dist/__tests__/imageEditAffordance.test.js +188 -0
  110. package/dist/__tests__/imageEditAffordance.test.js.map +1 -0
  111. package/dist/__tests__/imageEditorShell.test.d.ts +2 -0
  112. package/dist/__tests__/imageEditorShell.test.d.ts.map +1 -0
  113. package/dist/__tests__/imageEditorShell.test.js +52 -0
  114. package/dist/__tests__/imageEditorShell.test.js.map +1 -0
  115. package/dist/__tests__/imageEditorState.test.d.ts +3 -0
  116. package/dist/__tests__/imageEditorState.test.d.ts.map +1 -0
  117. package/dist/__tests__/imageEditorState.test.js +148 -0
  118. package/dist/__tests__/imageEditorState.test.js.map +1 -0
  119. package/dist/__tests__/inlinePreviewGutter.test.d.ts +2 -0
  120. package/dist/__tests__/inlinePreviewGutter.test.d.ts.map +1 -0
  121. package/dist/__tests__/inlinePreviewGutter.test.js +51 -0
  122. package/dist/__tests__/inlinePreviewGutter.test.js.map +1 -0
  123. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts +2 -0
  124. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts.map +1 -0
  125. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js +63 -0
  126. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js.map +1 -0
  127. package/dist/__tests__/jsonEditor.test.d.ts +2 -0
  128. package/dist/__tests__/jsonEditor.test.d.ts.map +1 -0
  129. package/dist/__tests__/jsonEditor.test.js +134 -0
  130. package/dist/__tests__/jsonEditor.test.js.map +1 -0
  131. package/dist/__tests__/layersPanel.test.d.ts +2 -0
  132. package/dist/__tests__/layersPanel.test.d.ts.map +1 -0
  133. package/dist/__tests__/layersPanel.test.js +84 -0
  134. package/dist/__tests__/layersPanel.test.js.map +1 -0
  135. package/dist/__tests__/linkDialogDocPicker.test.d.ts +7 -0
  136. package/dist/__tests__/linkDialogDocPicker.test.d.ts.map +1 -0
  137. package/dist/__tests__/linkDialogDocPicker.test.js +75 -0
  138. package/dist/__tests__/linkDialogDocPicker.test.js.map +1 -0
  139. package/dist/__tests__/outlinePanel.test.d.ts +2 -0
  140. package/dist/__tests__/outlinePanel.test.d.ts.map +1 -0
  141. package/dist/__tests__/outlinePanel.test.js +68 -0
  142. package/dist/__tests__/outlinePanel.test.js.map +1 -0
  143. package/dist/__tests__/plainHtmlPreview.test.d.ts +2 -0
  144. package/dist/__tests__/plainHtmlPreview.test.d.ts.map +1 -0
  145. package/dist/__tests__/plainHtmlPreview.test.js +87 -0
  146. package/dist/__tests__/plainHtmlPreview.test.js.map +1 -0
  147. package/dist/__tests__/propertiesPanel.test.d.ts +2 -0
  148. package/dist/__tests__/propertiesPanel.test.d.ts.map +1 -0
  149. package/dist/__tests__/propertiesPanel.test.js +64 -0
  150. package/dist/__tests__/propertiesPanel.test.js.map +1 -0
  151. package/dist/__tests__/recorderFormats.test.d.ts +2 -0
  152. package/dist/__tests__/recorderFormats.test.d.ts.map +1 -0
  153. package/dist/__tests__/recorderFormats.test.js +121 -0
  154. package/dist/__tests__/recorderFormats.test.js.map +1 -0
  155. package/dist/__tests__/recorderTimingJson.test.d.ts +2 -0
  156. package/dist/__tests__/recorderTimingJson.test.d.ts.map +1 -0
  157. package/dist/__tests__/recorderTimingJson.test.js +37 -0
  158. package/dist/__tests__/recorderTimingJson.test.js.map +1 -0
  159. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts +2 -0
  160. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts.map +1 -0
  161. package/dist/__tests__/templateAnnotationRoundTrip.test.js +31 -0
  162. package/dist/__tests__/templateAnnotationRoundTrip.test.js.map +1 -0
  163. package/dist/__tests__/tiptapBridge.test.js +13 -0
  164. package/dist/__tests__/tiptapBridge.test.js.map +1 -1
  165. package/dist/__tests__/useImageEditor.test.d.ts +2 -0
  166. package/dist/__tests__/useImageEditor.test.d.ts.map +1 -0
  167. package/dist/__tests__/useImageEditor.test.js +131 -0
  168. package/dist/__tests__/useImageEditor.test.js.map +1 -0
  169. package/dist/__tests__/useMediaRecorder.test.d.ts +2 -0
  170. package/dist/__tests__/useMediaRecorder.test.d.ts.map +1 -0
  171. package/dist/__tests__/useMediaRecorder.test.js +153 -0
  172. package/dist/__tests__/useMediaRecorder.test.js.map +1 -0
  173. package/dist/__tests__/versionHistory.test.d.ts +2 -0
  174. package/dist/__tests__/versionHistory.test.d.ts.map +1 -0
  175. package/dist/__tests__/versionHistory.test.js +124 -0
  176. package/dist/__tests__/versionHistory.test.js.map +1 -0
  177. package/dist/blockSlice.d.ts +24 -0
  178. package/dist/blockSlice.d.ts.map +1 -0
  179. package/dist/blockSlice.js +63 -0
  180. package/dist/blockSlice.js.map +1 -0
  181. package/dist/buildPreviewDoc.d.ts.map +1 -1
  182. package/dist/buildPreviewDoc.js +52 -2
  183. package/dist/buildPreviewDoc.js.map +1 -1
  184. package/dist/emojiData.d.ts +81 -0
  185. package/dist/emojiData.d.ts.map +1 -0
  186. package/dist/emojiData.js +1283 -0
  187. package/dist/emojiData.js.map +1 -0
  188. package/dist/fileKind.d.ts +6 -2
  189. package/dist/fileKind.d.ts.map +1 -1
  190. package/dist/fileKind.js +25 -4
  191. package/dist/fileKind.js.map +1 -1
  192. package/dist/hooks/useFileDrop.d.ts.map +1 -1
  193. package/dist/hooks/useFileDrop.js +40 -4
  194. package/dist/hooks/useFileDrop.js.map +1 -1
  195. package/dist/imageEditor/CanvasSurface.d.ts +31 -0
  196. package/dist/imageEditor/CanvasSurface.d.ts.map +1 -0
  197. package/dist/imageEditor/CanvasSurface.js +264 -0
  198. package/dist/imageEditor/CanvasSurface.js.map +1 -0
  199. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts +39 -0
  200. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts.map +1 -0
  201. package/dist/imageEditor/ImageVersionHistoryDropdown.js +283 -0
  202. package/dist/imageEditor/ImageVersionHistoryDropdown.js.map +1 -0
  203. package/dist/imageEditor/LayersPanel.d.ts +14 -0
  204. package/dist/imageEditor/LayersPanel.d.ts.map +1 -0
  205. package/dist/imageEditor/LayersPanel.js +43 -0
  206. package/dist/imageEditor/LayersPanel.js.map +1 -0
  207. package/dist/imageEditor/PropertiesPanel.d.ts +14 -0
  208. package/dist/imageEditor/PropertiesPanel.d.ts.map +1 -0
  209. package/dist/imageEditor/PropertiesPanel.js +97 -0
  210. package/dist/imageEditor/PropertiesPanel.js.map +1 -0
  211. package/dist/imageEditor/Toolbar.d.ts +30 -0
  212. package/dist/imageEditor/Toolbar.d.ts.map +1 -0
  213. package/dist/imageEditor/Toolbar.js +108 -0
  214. package/dist/imageEditor/Toolbar.js.map +1 -0
  215. package/dist/imageEditor/icons.d.ts +24 -0
  216. package/dist/imageEditor/icons.d.ts.map +1 -0
  217. package/dist/imageEditor/icons.js +45 -0
  218. package/dist/imageEditor/icons.js.map +1 -0
  219. package/dist/imageEditor/layers/EditorImageLayer.d.ts +16 -0
  220. package/dist/imageEditor/layers/EditorImageLayer.d.ts.map +1 -0
  221. package/dist/imageEditor/layers/EditorImageLayer.js +37 -0
  222. package/dist/imageEditor/layers/EditorImageLayer.js.map +1 -0
  223. package/dist/imageEditor/layers/EditorShapeLayer.d.ts +15 -0
  224. package/dist/imageEditor/layers/EditorShapeLayer.d.ts.map +1 -0
  225. package/dist/imageEditor/layers/EditorShapeLayer.js +20 -0
  226. package/dist/imageEditor/layers/EditorShapeLayer.js.map +1 -0
  227. package/dist/imageEditor/layers/EditorTextLayer.d.ts +18 -0
  228. package/dist/imageEditor/layers/EditorTextLayer.d.ts.map +1 -0
  229. package/dist/imageEditor/layers/EditorTextLayer.js +13 -0
  230. package/dist/imageEditor/layers/EditorTextLayer.js.map +1 -0
  231. package/dist/imageEditor/layers/SelectionHandles.d.ts +17 -0
  232. package/dist/imageEditor/layers/SelectionHandles.d.ts.map +1 -0
  233. package/dist/imageEditor/layers/SelectionHandles.js +19 -0
  234. package/dist/imageEditor/layers/SelectionHandles.js.map +1 -0
  235. package/dist/imageEditor/state.d.ts +76 -0
  236. package/dist/imageEditor/state.d.ts.map +1 -0
  237. package/dist/imageEditor/state.js +87 -0
  238. package/dist/imageEditor/state.js.map +1 -0
  239. package/dist/imageEditor/useImageEditor.d.ts +53 -0
  240. package/dist/imageEditor/useImageEditor.d.ts.map +1 -0
  241. package/dist/imageEditor/useImageEditor.js +244 -0
  242. package/dist/imageEditor/useImageEditor.js.map +1 -0
  243. package/dist/imageEditor/useImageEditorTokens.d.ts +16 -0
  244. package/dist/imageEditor/useImageEditorTokens.d.ts.map +1 -0
  245. package/dist/imageEditor/useImageEditorTokens.js +45 -0
  246. package/dist/imageEditor/useImageEditorTokens.js.map +1 -0
  247. package/dist/index.d.ts +48 -1
  248. package/dist/index.d.ts.map +1 -1
  249. package/dist/index.js +36 -0
  250. package/dist/index.js.map +1 -1
  251. package/dist/jsonEditor/EmbeddedRichTextField.d.ts +15 -0
  252. package/dist/jsonEditor/EmbeddedRichTextField.d.ts.map +1 -0
  253. package/dist/jsonEditor/EmbeddedRichTextField.js +74 -0
  254. package/dist/jsonEditor/EmbeddedRichTextField.js.map +1 -0
  255. package/dist/jsonEditor/JsonEditor.d.ts +36 -0
  256. package/dist/jsonEditor/JsonEditor.d.ts.map +1 -0
  257. package/dist/jsonEditor/JsonEditor.js +15 -0
  258. package/dist/jsonEditor/JsonEditor.js.map +1 -0
  259. package/dist/jsonEditor/JsonEditorContext.d.ts +28 -0
  260. package/dist/jsonEditor/JsonEditorContext.d.ts.map +1 -0
  261. package/dist/jsonEditor/JsonEditorContext.js +41 -0
  262. package/dist/jsonEditor/JsonEditorContext.js.map +1 -0
  263. package/dist/jsonEditor/RenderNode.d.ts +16 -0
  264. package/dist/jsonEditor/RenderNode.d.ts.map +1 -0
  265. package/dist/jsonEditor/RenderNode.js +32 -0
  266. package/dist/jsonEditor/RenderNode.js.map +1 -0
  267. package/dist/jsonEditor/editors.d.ts +36 -0
  268. package/dist/jsonEditor/editors.d.ts.map +1 -0
  269. package/dist/jsonEditor/editors.js +347 -0
  270. package/dist/jsonEditor/editors.js.map +1 -0
  271. package/dist/jsonEditor/index.d.ts +3 -0
  272. package/dist/jsonEditor/index.d.ts.map +1 -0
  273. package/dist/jsonEditor/index.js +2 -0
  274. package/dist/jsonEditor/index.js.map +1 -0
  275. package/dist/jsonEditor/useJsonEditorTokens.d.ts +13 -0
  276. package/dist/jsonEditor/useJsonEditorTokens.d.ts.map +1 -0
  277. package/dist/jsonEditor/useJsonEditorTokens.js +38 -0
  278. package/dist/jsonEditor/useJsonEditorTokens.js.map +1 -0
  279. package/dist/recorder/RecorderButton.d.ts +31 -0
  280. package/dist/recorder/RecorderButton.d.ts.map +1 -0
  281. package/dist/recorder/RecorderButton.js +24 -0
  282. package/dist/recorder/RecorderButton.js.map +1 -0
  283. package/dist/recorder/RecorderModal.d.ts +59 -0
  284. package/dist/recorder/RecorderModal.d.ts.map +1 -0
  285. package/dist/recorder/RecorderModal.js +333 -0
  286. package/dist/recorder/RecorderModal.js.map +1 -0
  287. package/dist/recorder/RecorderPanel.d.ts +25 -0
  288. package/dist/recorder/RecorderPanel.d.ts.map +1 -0
  289. package/dist/recorder/RecorderPanel.js +30 -0
  290. package/dist/recorder/RecorderPanel.js.map +1 -0
  291. package/dist/recorder/formats.d.ts +51 -0
  292. package/dist/recorder/formats.d.ts.map +1 -0
  293. package/dist/recorder/formats.js +144 -0
  294. package/dist/recorder/formats.js.map +1 -0
  295. package/dist/recorder/hooks/useMediaRecorder.d.ts +90 -0
  296. package/dist/recorder/hooks/useMediaRecorder.d.ts.map +1 -0
  297. package/dist/recorder/hooks/useMediaRecorder.js +277 -0
  298. package/dist/recorder/hooks/useMediaRecorder.js.map +1 -0
  299. package/dist/recorder/hooks/useStreamPreview.d.ts +22 -0
  300. package/dist/recorder/hooks/useStreamPreview.d.ts.map +1 -0
  301. package/dist/recorder/hooks/useStreamPreview.js +44 -0
  302. package/dist/recorder/hooks/useStreamPreview.js.map +1 -0
  303. package/dist/recorder/sources/cameraStream.d.ts +22 -0
  304. package/dist/recorder/sources/cameraStream.d.ts.map +1 -0
  305. package/dist/recorder/sources/cameraStream.js +24 -0
  306. package/dist/recorder/sources/cameraStream.js.map +1 -0
  307. package/dist/recorder/sources/micStream.d.ts +15 -0
  308. package/dist/recorder/sources/micStream.d.ts.map +1 -0
  309. package/dist/recorder/sources/micStream.js +24 -0
  310. package/dist/recorder/sources/micStream.js.map +1 -0
  311. package/dist/recorder/sources/screenStream.d.ts +53 -0
  312. package/dist/recorder/sources/screenStream.d.ts.map +1 -0
  313. package/dist/recorder/sources/screenStream.js +114 -0
  314. package/dist/recorder/sources/screenStream.js.map +1 -0
  315. package/dist/recorder/timingJson.d.ts +51 -0
  316. package/dist/recorder/timingJson.d.ts.map +1 -0
  317. package/dist/recorder/timingJson.js +42 -0
  318. package/dist/recorder/timingJson.js.map +1 -0
  319. package/dist/tiptap/TiptapAudio.d.ts +26 -0
  320. package/dist/tiptap/TiptapAudio.d.ts.map +1 -0
  321. package/dist/tiptap/TiptapAudio.js +58 -0
  322. package/dist/tiptap/TiptapAudio.js.map +1 -0
  323. package/dist/tiptap/TiptapVideo.d.ts +30 -0
  324. package/dist/tiptap/TiptapVideo.d.ts.map +1 -0
  325. package/dist/tiptap/TiptapVideo.js +66 -0
  326. package/dist/tiptap/TiptapVideo.js.map +1 -0
  327. package/dist/tiptap/useResolvedMediaSrc.d.ts +2 -0
  328. package/dist/tiptap/useResolvedMediaSrc.d.ts.map +1 -0
  329. package/dist/tiptap/useResolvedMediaSrc.js +42 -0
  330. package/dist/tiptap/useResolvedMediaSrc.js.map +1 -0
  331. package/dist/tiptapBridge.d.ts.map +1 -1
  332. package/dist/tiptapBridge.js +171 -14
  333. package/dist/tiptapBridge.js.map +1 -1
  334. package/dist/useHeadingLayout.d.ts +54 -0
  335. package/dist/useHeadingLayout.d.ts.map +1 -0
  336. package/dist/useHeadingLayout.js +260 -0
  337. package/dist/useHeadingLayout.js.map +1 -0
  338. package/dist/utils/collectInlineFontAwesomeCss.d.ts +21 -0
  339. package/dist/utils/collectInlineFontAwesomeCss.d.ts.map +1 -0
  340. package/dist/utils/collectInlineFontAwesomeCss.js +68 -0
  341. package/dist/utils/collectInlineFontAwesomeCss.js.map +1 -0
  342. package/dist/utils/dropUtils.d.ts +21 -2
  343. package/dist/utils/dropUtils.d.ts.map +1 -1
  344. package/dist/utils/dropUtils.js +43 -4
  345. package/dist/utils/dropUtils.js.map +1 -1
  346. package/dist/utils/normalizeMalformedAssetUrl.d.ts +15 -0
  347. package/dist/utils/normalizeMalformedAssetUrl.d.ts.map +1 -0
  348. package/dist/utils/normalizeMalformedAssetUrl.js +27 -0
  349. package/dist/utils/normalizeMalformedAssetUrl.js.map +1 -0
  350. package/package.json +8 -5
  351. package/src/DocumentSettingsDialog.tsx +266 -0
  352. package/src/EditorContext.tsx +534 -10
  353. package/src/EditorShell.tsx +571 -55
  354. package/src/EmojiPicker.tsx +332 -0
  355. package/src/ImageEditor.tsx +327 -0
  356. package/src/ImageNodeView.tsx +222 -21
  357. package/src/ImageViewer.tsx +221 -0
  358. package/src/InlineIcon.ts +84 -0
  359. package/src/InlinePreviewGutter.tsx +582 -0
  360. package/src/LinkDialog.tsx +276 -0
  361. package/src/MentionExtension.tsx +10 -7
  362. package/src/OutlinePanel.tsx +295 -0
  363. package/src/PlainHtmlPreview.tsx +211 -0
  364. package/src/PreviewControls.tsx +130 -24
  365. package/src/PreviewPanel.tsx +38 -21
  366. package/src/RawEditor.tsx +215 -4
  367. package/src/RecorderEntry.tsx +164 -0
  368. package/src/TemplateAnnotation.ts +32 -6
  369. package/src/TemplatePicker.tsx +818 -0
  370. package/src/ThemeCustomizerPanel.tsx +595 -0
  371. package/src/ThemePicker.tsx +319 -0
  372. package/src/Toolbar.tsx +708 -111
  373. package/src/VersionHistoryPanel.tsx +329 -0
  374. package/src/ViewMenuPanel.tsx +188 -0
  375. package/src/WysiwygEditor.tsx +229 -9
  376. package/src/__tests__/detectMarkdown.test.ts +0 -15
  377. package/src/__tests__/documentSettingsDialog.test.tsx +147 -0
  378. package/src/__tests__/emojiPicker.test.tsx +133 -0
  379. package/src/__tests__/fileKind.test.ts +16 -0
  380. package/src/__tests__/imageEditAffordance.test.tsx +268 -0
  381. package/src/__tests__/imageEditorShell.test.tsx +57 -0
  382. package/src/__tests__/imageEditorState.test.ts +171 -0
  383. package/src/__tests__/inlinePreviewGutter.test.tsx +62 -0
  384. package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +103 -0
  385. package/src/__tests__/jsonEditor.test.tsx +168 -0
  386. package/src/__tests__/layersPanel.test.tsx +97 -0
  387. package/src/__tests__/linkDialogDocPicker.test.tsx +137 -0
  388. package/src/__tests__/outlinePanel.test.tsx +79 -0
  389. package/src/__tests__/plainHtmlPreview.test.tsx +107 -0
  390. package/src/__tests__/propertiesPanel.test.tsx +69 -0
  391. package/src/__tests__/recorderFormats.test.ts +146 -0
  392. package/src/__tests__/recorderTimingJson.test.ts +41 -0
  393. package/src/__tests__/templateAnnotationRoundTrip.test.ts +34 -0
  394. package/src/__tests__/tiptapBridge.test.ts +15 -0
  395. package/src/__tests__/useImageEditor.test.tsx +159 -0
  396. package/src/__tests__/useMediaRecorder.test.ts +186 -0
  397. package/src/__tests__/versionHistory.test.tsx +197 -0
  398. package/src/blockSlice.ts +75 -0
  399. package/src/buildPreviewDoc.ts +61 -6
  400. package/src/emojiData.ts +1337 -0
  401. package/src/fileKind.ts +30 -6
  402. package/src/hooks/useFileDrop.ts +40 -4
  403. package/src/imageEditor/CanvasSurface.tsx +402 -0
  404. package/src/imageEditor/ImageVersionHistoryDropdown.tsx +396 -0
  405. package/src/imageEditor/LayersPanel.tsx +143 -0
  406. package/src/imageEditor/PropertiesPanel.tsx +428 -0
  407. package/src/imageEditor/Toolbar.tsx +242 -0
  408. package/src/imageEditor/icons.tsx +144 -0
  409. package/src/imageEditor/image-editor.css +450 -0
  410. package/src/imageEditor/layers/EditorImageLayer.tsx +45 -0
  411. package/src/imageEditor/layers/EditorShapeLayer.tsx +62 -0
  412. package/src/imageEditor/layers/EditorTextLayer.tsx +45 -0
  413. package/src/imageEditor/layers/SelectionHandles.tsx +86 -0
  414. package/src/imageEditor/state.ts +153 -0
  415. package/src/imageEditor/useImageEditor.ts +328 -0
  416. package/src/imageEditor/useImageEditorTokens.ts +70 -0
  417. package/src/index.ts +82 -0
  418. package/src/jsonEditor/EmbeddedRichTextField.tsx +81 -0
  419. package/src/jsonEditor/JsonEditor.tsx +81 -0
  420. package/src/jsonEditor/JsonEditorContext.tsx +75 -0
  421. package/src/jsonEditor/RenderNode.tsx +66 -0
  422. package/src/jsonEditor/editors.tsx +678 -0
  423. package/src/jsonEditor/index.ts +2 -0
  424. package/src/jsonEditor/json-editor.css +463 -0
  425. package/src/jsonEditor/useJsonEditorTokens.ts +63 -0
  426. package/src/recorder/RecorderButton.tsx +72 -0
  427. package/src/recorder/RecorderModal.tsx +596 -0
  428. package/src/recorder/RecorderPanel.tsx +93 -0
  429. package/src/recorder/formats.ts +159 -0
  430. package/src/recorder/hooks/useMediaRecorder.ts +378 -0
  431. package/src/recorder/hooks/useStreamPreview.ts +47 -0
  432. package/src/recorder/sources/cameraStream.ts +32 -0
  433. package/src/recorder/sources/micStream.ts +25 -0
  434. package/src/recorder/sources/screenStream.ts +162 -0
  435. package/src/recorder/timingJson.ts +66 -0
  436. package/src/styles/editor.css +2490 -51
  437. package/src/styles/image-edit-affordance.css +201 -0
  438. package/src/styles/index.css +10 -0
  439. package/src/tiptap/TiptapAudio.tsx +86 -0
  440. package/src/tiptap/TiptapVideo.tsx +119 -0
  441. package/src/tiptap/useResolvedMediaSrc.ts +47 -0
  442. package/src/tiptapBridge.ts +188 -20
  443. package/src/useHeadingLayout.ts +294 -0
  444. package/src/utils/collectInlineFontAwesomeCss.ts +69 -0
  445. package/src/utils/dropUtils.ts +54 -6
  446. package/src/utils/normalizeMalformedAssetUrl.ts +22 -0
package/dist/Toolbar.js CHANGED
@@ -1,9 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useReducer, useRef, useState } from 'react';
2
+ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
3
3
  import { useEditorContext } from './EditorContext';
4
- import { getAvailableTemplates } from '@bendyline/squisq/doc';
5
- /** Template names are static — computed once at module load. */
6
- const TEMPLATE_NAMES = getAvailableTemplates();
4
+ import { VersionHistoryPanel } from './VersionHistoryPanel';
5
+ import { RecorderEntry } from './RecorderEntry';
6
+ import { ViewMenuPanel } from './ViewMenuPanel';
7
+ import { TemplatePicker, TEMPLATE_NAMES } from './TemplatePicker';
8
+ import { profileBlockContents, recommendTemplatesForBlock } from '@bendyline/squisq/recommend';
9
+ import { findBlockSliceAtLine, findBlockSliceByHeadingIndex } from './blockSlice';
10
+ import { LinkDialog } from './LinkDialog';
11
+ import { DocumentSettingsDialog } from './DocumentSettingsDialog';
12
+ import { EmojiPicker, EMOJI_PICKER_WIDTH, EMOJI_PICKER_MAX_HEIGHT } from './EmojiPicker';
13
+ import { createPortal } from 'react-dom';
7
14
  const VIEWS = [
8
15
  { id: 'wysiwyg', label: 'Editor', shortcut: '⌘1' },
9
16
  { id: 'raw', label: 'Markdown', shortLabel: 'MD', shortcut: '⌘2' },
@@ -43,16 +50,41 @@ const BUTTONS = [
43
50
  { id: 'h1', label: 'H1', icon: 'H1', title: 'Heading 1', group: 'structure' },
44
51
  { id: 'h2', label: 'H2', icon: 'H2', title: 'Heading 2', group: 'structure' },
45
52
  { id: 'h3', label: 'H3', icon: 'H3', title: 'Heading 3', group: 'structure' },
53
+ { id: 'h4', label: 'H4', icon: 'H4', title: 'Heading 4', group: 'structure' },
54
+ { id: 'h5', label: 'H5', icon: 'H5', title: 'Heading 5', group: 'structure' },
55
+ { id: 'h6', label: 'H6', icon: 'H6', title: 'Heading 6', group: 'structure' },
46
56
  // Insert group — block-level inserts (quote, code blocks, rules)
47
57
  { id: 'quote', label: '❝', icon: '❝', title: 'Blockquote', group: 'insert' },
48
58
  { id: 'codeblock', label: '{ }', icon: '{ }', title: 'Code block', group: 'insert' },
49
59
  { id: 'code', label: '</>', icon: '</>', title: 'Inline code', group: 'insert' },
50
60
  { id: 'hr', label: '—', icon: '—', title: 'Horizontal rule', group: 'insert' },
51
- // Media group — links, tables, images
61
+ // Media group — links, tables, images, emoji
52
62
  { id: 'link', label: '🔗', icon: '🔗', title: 'Insert link', group: 'media' },
53
63
  { id: 'table', label: 'table', icon: '', title: 'Insert table', group: 'media' },
54
64
  { id: 'image', label: '🖼', icon: '🖼', title: 'Insert image', group: 'media' },
65
+ { id: 'emoji', label: '😊', icon: '😊', title: 'Insert emoji', group: 'media' },
55
66
  ];
67
+ // ─── Inline SVG icons (line-art, currentColor) ──────────
68
+ const TABLE_ICON = (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "1", width: "12", height: "12", rx: "1" }), _jsx("line", { x1: "1", y1: "5", x2: "13", y2: "5" }), _jsx("line", { x1: "1", y1: "9", x2: "13", y2: "9" }), _jsx("line", { x1: "5", y1: "1", x2: "5", y2: "13" }), _jsx("line", { x1: "9", y1: "1", x2: "9", y2: "13" })] }));
69
+ const LINK_ICON = (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M5.75 8.25 L8.25 5.75" }), _jsx("path", { d: "M6.5 3.75 L8 2.25 a2.5 2.5 0 0 1 3.54 3.54 L10 7.25" }), _jsx("path", { d: "M7.5 10.25 L6 11.75 a2.5 2.5 0 0 1 -3.54 -3.54 L4 6.75" })] }));
70
+ const IMAGE_ICON = (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "1.5", y: "2.5", width: "11", height: "9", rx: "1" }), _jsx("circle", { cx: "5", cy: "5.5", r: "0.9" }), _jsx("path", { d: "M2 10 L5.5 7 L8 9 L10 7.5 L12.5 10" })] }));
71
+ const PAPERCLIP_ICON = (_jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M11 4 L5.5 9.5 a1.75 1.75 0 0 0 2.5 2.5 L12.5 7.5 a3 3 0 0 0 -4.25 -4.25 L3 8.5 a4.25 4.25 0 0 0 6 6 L13 10.5" }) }));
72
+ const EMOJI_ICON = (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "7", cy: "7", r: "5.25" }), _jsx("circle", { cx: "5.25", cy: "5.75", r: "0.6", fill: "currentColor", stroke: "none" }), _jsx("circle", { cx: "8.75", cy: "5.75", r: "0.6", fill: "currentColor", stroke: "none" }), _jsx("path", { d: "M4.75 8.5 a2.5 2.5 0 0 0 4.5 0" })] }));
73
+ /** Returns an SVG element when the button id maps to one, otherwise null. */
74
+ function buttonIconSvg(id) {
75
+ switch (id) {
76
+ case 'table':
77
+ return TABLE_ICON;
78
+ case 'link':
79
+ return LINK_ICON;
80
+ case 'image':
81
+ return IMAGE_ICON;
82
+ case 'emoji':
83
+ return EMOJI_ICON;
84
+ default:
85
+ return null;
86
+ }
87
+ }
56
88
  // ─── Tiptap active-state map ────────────────────────────
57
89
  /** Returns true if the given button id is currently active in Tiptap */
58
90
  function isTiptapActive(editor, id) {
@@ -73,6 +105,12 @@ function isTiptapActive(editor, id) {
73
105
  return editor.isActive('heading', { level: 2 });
74
106
  case 'h3':
75
107
  return editor.isActive('heading', { level: 3 });
108
+ case 'h4':
109
+ return editor.isActive('heading', { level: 4 });
110
+ case 'h5':
111
+ return editor.isActive('heading', { level: 5 });
112
+ case 'h6':
113
+ return editor.isActive('heading', { level: 6 });
76
114
  case 'quote':
77
115
  return editor.isActive('blockquote');
78
116
  case 'ul':
@@ -91,7 +129,7 @@ function isTiptapActive(editor, id) {
91
129
  * - Raw: appends markdown syntax to the source
92
130
  */
93
131
  export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAfterActions, slotRight, showPlayTab = true, }) {
94
- const { activeView, setActiveView, markdownSource, setMarkdownSource, tiptapEditor, monacoEditor, mediaProvider, editorMode, } = useEditorContext();
132
+ const { activeView, setActiveView, markdownSource, setMarkdownSource, tiptapEditor, monacoEditor, mediaProvider, editorMode, versioning, allowRecording, documentLinkProvider, theme, } = useEditorContext();
95
133
  const isCodeMode = editorMode === 'code';
96
134
  // In code mode only the raw view is meaningful; the WYSIWYG and Preview
97
135
  // surfaces aren't mounted, so hide their tabs.
@@ -105,6 +143,41 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
105
143
  const showViewTabs = visibleViews.length > 1;
106
144
  // Hidden file input for image picker
107
145
  const imageInputRef = useRef(null);
146
+ // Link dialog — shared by WYSIWYG and Raw views.
147
+ const [linkDialog, setLinkDialog] = useState(null);
148
+ // Emoji picker — toolbar-anchored popover. We track the trigger
149
+ // button's screen rect so the picker can position itself just below
150
+ // it via createPortal (the toolbar's overflow:hidden actions row
151
+ // would otherwise clip the popover).
152
+ const emojiButtonRef = useRef(null);
153
+ const [emojiPickerAnchor, setEmojiPickerAnchor] = useState(null);
154
+ const openEmojiPicker = useCallback(() => {
155
+ const btn = emojiButtonRef.current;
156
+ if (!btn)
157
+ return;
158
+ const rect = btn.getBoundingClientRect();
159
+ // Position just below the trigger by default, then clamp into the
160
+ // visible viewport so the picker is never clipped on the right or
161
+ // bottom — flips above the trigger when there isn't room below.
162
+ const gap = 6;
163
+ const margin = 8;
164
+ const vw = window.innerWidth;
165
+ const vh = window.innerHeight;
166
+ let left = rect.left;
167
+ if (left + EMOJI_PICKER_WIDTH + margin > vw) {
168
+ left = Math.max(margin, vw - EMOJI_PICKER_WIDTH - margin);
169
+ }
170
+ let top = rect.bottom + gap;
171
+ if (top + EMOJI_PICKER_MAX_HEIGHT + margin > vh) {
172
+ const flipped = rect.top - EMOJI_PICKER_MAX_HEIGHT - gap;
173
+ // Prefer flipping above when there's more room there; otherwise
174
+ // pin to the top edge with margin and let the picker's own
175
+ // maxHeight clip it.
176
+ top = flipped >= margin ? flipped : margin;
177
+ }
178
+ setEmojiPickerAnchor({ top, left });
179
+ }, []);
180
+ const closeEmojiPicker = useCallback(() => setEmojiPickerAnchor(null), []);
108
181
  // ── Narrow-screen detection ──────────────────────────
109
182
  const [isNarrow, setIsNarrow] = useState(() => typeof window !== 'undefined' && window.matchMedia('(max-width: 768px)').matches);
110
183
  useEffect(() => {
@@ -118,6 +191,8 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
118
191
  const [measuredOverflowIndex, setMeasuredOverflowIndex] = useState(null);
119
192
  const [showOverflow, setShowOverflow] = useState(false);
120
193
  const overflowRef = useRef(null);
194
+ // Document settings (frontmatter) dialog
195
+ const [showDocSettings, setShowDocSettings] = useState(false);
121
196
  // On narrow screens, force all buttons into the overflow menu
122
197
  const overflowIndex = isNarrow ? 0 : measuredOverflowIndex;
123
198
  useEffect(() => {
@@ -220,6 +295,15 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
220
295
  case 'h3':
221
296
  chain.toggleHeading({ level: 3 }).run();
222
297
  break;
298
+ case 'h4':
299
+ chain.toggleHeading({ level: 4 }).run();
300
+ break;
301
+ case 'h5':
302
+ chain.toggleHeading({ level: 5 }).run();
303
+ break;
304
+ case 'h6':
305
+ chain.toggleHeading({ level: 6 }).run();
306
+ break;
223
307
  case 'quote':
224
308
  chain.toggleBlockquote().run();
225
309
  break;
@@ -236,12 +320,30 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
236
320
  chain.setHorizontalRule().run();
237
321
  break;
238
322
  case 'link': {
239
- const url = window.prompt('URL:');
240
- if (url) {
241
- chain
242
- .setLink?.({ href: url })
243
- .run();
323
+ const isActive = tiptapEditor.isActive('link');
324
+ let initialText = '';
325
+ let initialUrl = '';
326
+ if (isActive) {
327
+ // Snap selection to the full link mark so editing replaces
328
+ // the entire `[text](url)` rather than just the cursor word.
329
+ tiptapEditor.chain().focus().extendMarkRange('link').run();
330
+ const sel = tiptapEditor.state.selection;
331
+ initialText = tiptapEditor.state.doc.textBetween(sel.from, sel.to, ' ');
332
+ initialUrl = tiptapEditor.getAttributes('link').href ?? '';
333
+ }
334
+ else {
335
+ const { from, to, empty } = tiptapEditor.state.selection;
336
+ if (!empty) {
337
+ initialText = tiptapEditor.state.doc.textBetween(from, to, ' ');
338
+ }
244
339
  }
340
+ setLinkDialog({
341
+ mode: isActive ? 'update' : 'insert',
342
+ target: 'wysiwyg',
343
+ initialText,
344
+ initialUrl,
345
+ rawRange: null,
346
+ });
245
347
  break;
246
348
  }
247
349
  case 'table':
@@ -307,6 +409,15 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
307
409
  case 'h3':
308
410
  prefixLines('### ', 'Heading 3');
309
411
  break;
412
+ case 'h4':
413
+ prefixLines('#### ', 'Heading 4');
414
+ break;
415
+ case 'h5':
416
+ prefixLines('##### ', 'Heading 5');
417
+ break;
418
+ case 'h6':
419
+ prefixLines('###### ', 'Heading 6');
420
+ break;
310
421
  case 'quote':
311
422
  prefixLines('> ', 'Quote');
312
423
  break;
@@ -328,14 +439,42 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
328
439
  break;
329
440
  }
330
441
  case 'link': {
331
- if (hasSelection) {
332
- replacement = '[' + selectedText + '](url)';
333
- }
334
- else {
335
- replacement = '[link text](url)';
336
- newCursorOffset = 1; // inside the []
442
+ // Open the LinkDialog instead of inserting literal text. If the
443
+ // cursor sits inside an existing `[text](url)` on this line,
444
+ // prefill from it and replace the whole match on confirm.
445
+ const lineNumber = selection.startLineNumber;
446
+ const lineText = model.getLineContent(lineNumber);
447
+ const cursorCol = selection.startColumn;
448
+ const linkRe = /\[([^\]]*)\]\(([^)]*)\)/g;
449
+ let match;
450
+ let existing = null;
451
+ while ((match = linkRe.exec(lineText)) !== null) {
452
+ const startCol = match.index + 1; // 1-based
453
+ const endCol = startCol + match[0].length;
454
+ if (cursorCol >= startCol && cursorCol <= endCol) {
455
+ existing = {
456
+ text: match[1],
457
+ url: match[2],
458
+ range: {
459
+ startLineNumber: lineNumber,
460
+ startColumn: startCol,
461
+ endLineNumber: lineNumber,
462
+ endColumn: endCol,
463
+ },
464
+ };
465
+ break;
466
+ }
337
467
  }
338
- break;
468
+ setLinkDialog({
469
+ mode: existing ? 'update' : 'insert',
470
+ target: 'raw',
471
+ initialText: existing ? existing.text : hasSelection ? selectedText : '',
472
+ initialUrl: existing ? existing.url : '',
473
+ rawRange: existing ? existing.range : null,
474
+ });
475
+ // Skip the executeEdits/setPosition tail below — the dialog will
476
+ // apply its own edit on confirm.
477
+ return;
339
478
  }
340
479
  case 'table': {
341
480
  const tpl = '| Header 1 | Header 2 | Header 3 |\n| --- | --- | --- |\n| Cell | Cell | Cell |\n| Cell | Cell | Cell |';
@@ -436,29 +575,343 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
436
575
  imageInputRef.current?.click();
437
576
  return;
438
577
  }
578
+ if (id === 'emoji') {
579
+ // Toggle the popover: clicking the button again closes it.
580
+ if (emojiPickerAnchor)
581
+ closeEmojiPicker();
582
+ else
583
+ openEmojiPicker();
584
+ return;
585
+ }
439
586
  if (activeView === 'wysiwyg' && tiptapEditor) {
440
587
  handleTiptap(id);
441
588
  }
442
589
  else {
443
590
  handleRaw(id);
444
591
  }
445
- }, [activeView, tiptapEditor, handleTiptap, handleRaw]);
592
+ }, [
593
+ activeView,
594
+ tiptapEditor,
595
+ handleTiptap,
596
+ handleRaw,
597
+ emojiPickerAnchor,
598
+ openEmojiPicker,
599
+ closeEmojiPicker,
600
+ ]);
601
+ // ── Picker insert (emoji or FontAwesome icon) ──────
602
+ // Inserts a chosen picker entry at the cursor. We bypass
603
+ // `insertAtCursor` (which routes through markdown→Tiptap conversion
604
+ // and wraps the input in a paragraph) so entries land inline at the
605
+ // caret rather than starting a new block. Emoji insert as a plain
606
+ // character; FontAwesome icons insert as the `InlineIcon` Tiptap
607
+ // node so the editor renders them inline immediately.
608
+ const handleEmojiSelect = useCallback((entry) => {
609
+ if (activeView === 'wysiwyg' && tiptapEditor) {
610
+ if (entry.kind === 'emoji') {
611
+ tiptapEditor.chain().focus().insertContent(entry.char).run();
612
+ }
613
+ else {
614
+ tiptapEditor
615
+ .chain()
616
+ .focus()
617
+ .insertContent({
618
+ type: 'inlineIcon',
619
+ attrs: { token: entry.token, family: entry.family, name: entry.name },
620
+ })
621
+ .run();
622
+ }
623
+ }
624
+ else if (activeView === 'raw' && monacoEditor) {
625
+ const insertion = entry.kind === 'emoji' ? entry.char : `{[${entry.token}]}`;
626
+ const position = monacoEditor.getPosition();
627
+ if (position) {
628
+ const range = {
629
+ startLineNumber: position.lineNumber,
630
+ startColumn: position.column,
631
+ endLineNumber: position.lineNumber,
632
+ endColumn: position.column,
633
+ };
634
+ monacoEditor.executeEdits('picker-insert', [{ range, text: insertion }]);
635
+ monacoEditor.focus();
636
+ }
637
+ else {
638
+ setMarkdownSource(markdownSource + insertion);
639
+ }
640
+ }
641
+ else {
642
+ const insertion = entry.kind === 'emoji' ? entry.char : `{[${entry.token}]}`;
643
+ setMarkdownSource(markdownSource + insertion);
644
+ }
645
+ closeEmojiPicker();
646
+ }, [activeView, tiptapEditor, monacoEditor, markdownSource, setMarkdownSource, closeEmojiPicker]);
647
+ // ── Ctrl+K / Cmd+K → open the link dialog ────────────
648
+ // Mirrors the behaviour of common editors (Word, Google Docs, VS Code's
649
+ // Markdown preview): if the cursor is in a Squisq editor surface, the
650
+ // shortcut routes through the same handler the toolbar Link button uses,
651
+ // which prefills the dialog from the current selection (or the link
652
+ // under the cursor) before opening.
653
+ useEffect(() => {
654
+ const onKeyDown = (e) => {
655
+ if (!(e.ctrlKey || e.metaKey) || e.altKey || e.shiftKey)
656
+ return;
657
+ if (e.key.toLowerCase() !== 'k')
658
+ return;
659
+ const target = e.target;
660
+ if (!target)
661
+ return;
662
+ // Only intercept when focus is inside one of our editor surfaces.
663
+ const inEditor = !!target.closest('.squisq-wysiwyg-editor, .ProseMirror, .squisq-raw-editor-container, .monaco-editor');
664
+ if (!inEditor)
665
+ return;
666
+ e.preventDefault();
667
+ e.stopPropagation();
668
+ handleAction('link');
669
+ };
670
+ window.addEventListener('keydown', onKeyDown, true);
671
+ return () => window.removeEventListener('keydown', onKeyDown, true);
672
+ }, [handleAction]);
673
+ // ── Link dialog confirm ──────────────────────────────
674
+ const handleLinkConfirm = useCallback((text, url) => {
675
+ if (!linkDialog)
676
+ return;
677
+ const trimmedUrl = url.trim();
678
+ const trimmedText = text.trim();
679
+ if (linkDialog.target === 'wysiwyg' && tiptapEditor) {
680
+ if (!trimmedUrl) {
681
+ // Empty URL on update = unlink. On insert with no URL, do nothing.
682
+ if (linkDialog.mode === 'update') {
683
+ tiptapEditor.chain().focus().unsetLink().run();
684
+ }
685
+ setLinkDialog(null);
686
+ return;
687
+ }
688
+ const visibleText = trimmedText || trimmedUrl;
689
+ const chain = tiptapEditor.chain().focus();
690
+ // Insert (or replace selection) with text carrying a link mark. When
691
+ // updating an existing link, the selection was extended to the full
692
+ // mark range earlier, so this replaces the entire `[text](url)`.
693
+ chain
694
+ .insertContent({
695
+ type: 'text',
696
+ text: visibleText,
697
+ marks: [{ type: 'link', attrs: { href: trimmedUrl } }],
698
+ })
699
+ .run();
700
+ setLinkDialog(null);
701
+ return;
702
+ }
703
+ if (linkDialog.target === 'raw' && monacoEditor) {
704
+ const model = monacoEditor.getModel();
705
+ if (!model) {
706
+ setLinkDialog(null);
707
+ return;
708
+ }
709
+ if (!trimmedUrl && linkDialog.mode === 'update' && linkDialog.rawRange) {
710
+ // Empty URL on update = strip the markdown link, keep the text.
711
+ monacoEditor.executeEdits('toolbar-link-edit', [
712
+ { range: linkDialog.rawRange, text: trimmedText || linkDialog.initialText },
713
+ ]);
714
+ monacoEditor.focus();
715
+ setLinkDialog(null);
716
+ return;
717
+ }
718
+ if (!trimmedUrl) {
719
+ setLinkDialog(null);
720
+ return;
721
+ }
722
+ const visibleText = trimmedText || trimmedUrl;
723
+ const replacement = `[${visibleText}](${trimmedUrl})`;
724
+ const range = linkDialog.rawRange ?? monacoEditor.getSelection();
725
+ if (!range) {
726
+ setLinkDialog(null);
727
+ return;
728
+ }
729
+ monacoEditor.executeEdits('toolbar-link-edit', [{ range, text: replacement }]);
730
+ monacoEditor.focus();
731
+ setLinkDialog(null);
732
+ return;
733
+ }
734
+ setLinkDialog(null);
735
+ }, [linkDialog, tiptapEditor, monacoEditor]);
446
736
  const groups = ['format', 'lists', 'structure', 'insert', 'media'];
447
737
  const isWysiwyg = activeView === 'wysiwyg' && tiptapEditor;
448
738
  const isPreview = activeView === 'preview';
739
+ // ── Progressive heading disclosure ───────────────────
740
+ // H1\u2013H3 are always visible. H4 appears once the document already
741
+ // contains an H3, H5 once it contains an H4, and H6 once it contains
742
+ // an H5. This keeps the toolbar compact for typical short documents
743
+ // while letting deeply nested documents reach every level.
744
+ const maxHeadingLevelInDoc = useMemo(() => {
745
+ if (!markdownSource)
746
+ return 0;
747
+ let max = 0;
748
+ let inFence = false;
749
+ for (const rawLine of markdownSource.split('\n')) {
750
+ const line = rawLine.trimEnd();
751
+ if (/^\s*```/.test(line)) {
752
+ inFence = !inFence;
753
+ continue;
754
+ }
755
+ if (inFence)
756
+ continue;
757
+ const m = /^(#{1,6})\s+\S/.exec(line);
758
+ if (m && m[1].length > max)
759
+ max = m[1].length;
760
+ }
761
+ return max;
762
+ }, [markdownSource]);
763
+ // Show H(n+1) when the document already contains H(n), starting from H3.
764
+ const visibleHeadingMax = Math.min(6, Math.max(3, maxHeadingLevelInDoc + 1));
765
+ const isButtonVisible = (id) => {
766
+ const m = /^h([1-6])$/.exec(id);
767
+ if (!m)
768
+ return true;
769
+ return Number(m[1]) <= visibleHeadingMax;
770
+ };
449
771
  // Detect whether cursor is inside a table (WYSIWYG mode only)
450
772
  const isInTable = isWysiwyg ? tiptapEditor.isActive('table') : false;
451
773
  // Detect current heading template (WYSIWYG mode only)
452
- const currentTemplate = isWysiwyg
774
+ const wysiwygTemplate = isWysiwyg
453
775
  ? tiptapEditor.isActive('heading')
454
776
  ? (tiptapEditor.getAttributes('heading')?.dataTemplate ?? '')
455
777
  : null
456
778
  : null;
779
+ // ── Monaco heading detection (Markdown view) ─────────────────────
780
+ // Watch the Monaco cursor and surface the template picker whenever the
781
+ // cursor is on a heading line. `null` hides the picker; '' shows it
782
+ // with no template selected; any other string is the current template.
783
+ const isRawView = activeView === 'raw';
784
+ const [rawTemplate, setRawTemplate] = useState(null);
785
+ const [rawHeadingLine, setRawHeadingLine] = useState(null);
786
+ useEffect(() => {
787
+ if (!isRawView || !monacoEditor) {
788
+ setRawTemplate(null);
789
+ setRawHeadingLine(null);
790
+ return;
791
+ }
792
+ const recompute = () => {
793
+ const model = monacoEditor.getModel();
794
+ const pos = monacoEditor.getPosition();
795
+ if (!model || !pos) {
796
+ setRawTemplate(null);
797
+ setRawHeadingLine(null);
798
+ return;
799
+ }
800
+ const line = model.getLineContent(pos.lineNumber);
801
+ const headingMatch = line.match(/^#{1,6}\s+(.+)$/);
802
+ if (!headingMatch) {
803
+ setRawTemplate(null);
804
+ setRawHeadingLine(null);
805
+ return;
806
+ }
807
+ setRawHeadingLine(pos.lineNumber);
808
+ const annotMatch = headingMatch[1].match(/\s*\{\[([^\]]+)\]\}[\s\]}]*$/);
809
+ if (annotMatch) {
810
+ // First whitespace-delimited token is the template name; the rest are params.
811
+ const name = annotMatch[1].trim().split(/\s+/)[0];
812
+ setRawTemplate(name);
813
+ }
814
+ else {
815
+ setRawTemplate('');
816
+ }
817
+ };
818
+ recompute();
819
+ const cursorSub = monacoEditor.onDidChangeCursorPosition(recompute);
820
+ const contentSub = monacoEditor.onDidChangeModelContent(recompute);
821
+ return () => {
822
+ cursorSub.dispose();
823
+ contentSub.dispose();
824
+ };
825
+ }, [isRawView, monacoEditor]);
826
+ // Track the index of the heading the WYSIWYG cursor is in among all
827
+ // top-level headings. Used to locate the same heading in the markdown
828
+ // source for content-based template recommendations.
829
+ const [wysiwygHeadingIndex, setWysiwygHeadingIndex] = useState(null);
830
+ useEffect(() => {
831
+ if (!isWysiwyg || !tiptapEditor) {
832
+ setWysiwygHeadingIndex(null);
833
+ return;
834
+ }
835
+ const recompute = () => {
836
+ if (!tiptapEditor.isActive('heading')) {
837
+ setWysiwygHeadingIndex(null);
838
+ return;
839
+ }
840
+ const cursor = tiptapEditor.state.selection.from;
841
+ let index = -1;
842
+ let count = 0;
843
+ tiptapEditor.state.doc.descendants((node, pos) => {
844
+ if (node.type.name !== 'heading')
845
+ return;
846
+ if (pos <= cursor && pos + node.nodeSize > cursor) {
847
+ index = count;
848
+ return false;
849
+ }
850
+ count++;
851
+ });
852
+ setWysiwygHeadingIndex(index >= 0 ? index : null);
853
+ };
854
+ recompute();
855
+ tiptapEditor.on('selectionUpdate', recompute);
856
+ tiptapEditor.on('update', recompute);
857
+ return () => {
858
+ tiptapEditor.off('selectionUpdate', recompute);
859
+ tiptapEditor.off('update', recompute);
860
+ };
861
+ }, [isWysiwyg, tiptapEditor]);
862
+ const currentTemplate = isWysiwyg ? wysiwygTemplate : isRawView ? rawTemplate : null;
863
+ // Compute recommended templates for the active block. Heading slice
864
+ // comes from markdownSource — raw view supplies the cursor line,
865
+ // WYSIWYG supplies the heading index.
866
+ const recommendedTemplates = useMemo(() => {
867
+ if (currentTemplate === null)
868
+ return undefined;
869
+ let slice = null;
870
+ if (isRawView && rawHeadingLine !== null) {
871
+ slice = findBlockSliceAtLine(markdownSource, rawHeadingLine);
872
+ }
873
+ else if (isWysiwyg && wysiwygHeadingIndex !== null) {
874
+ slice = findBlockSliceByHeadingIndex(markdownSource, wysiwygHeadingIndex);
875
+ }
876
+ if (slice === null)
877
+ return undefined;
878
+ const profile = profileBlockContents(slice);
879
+ return recommendTemplatesForBlock(profile, TEMPLATE_NAMES).recommended;
880
+ }, [currentTemplate, isRawView, isWysiwyg, rawHeadingLine, wysiwygHeadingIndex, markdownSource]);
457
881
  const handleTemplatePick = (value) => {
882
+ // Raw (Monaco) — rewrite the heading line's annotation suffix in place.
883
+ if (isRawView && monacoEditor) {
884
+ const model = monacoEditor.getModel();
885
+ const pos = monacoEditor.getPosition();
886
+ if (!model || !pos)
887
+ return;
888
+ const lineNumber = pos.lineNumber;
889
+ const lineText = model.getLineContent(lineNumber);
890
+ const headingMatch = lineText.match(/^(#{1,6}\s+)(.+)$/);
891
+ if (!headingMatch)
892
+ return;
893
+ const prefix = headingMatch[1];
894
+ // Strip any existing trailing annotation
895
+ const bareText = headingMatch[2].replace(/\s*\{\[[^\]]+\]\}[\s\]}]*$/, '').trimEnd();
896
+ const newLine = value === '' ? `${prefix}${bareText}` : `${prefix}${bareText} {[${value}]}`;
897
+ monacoEditor.executeEdits('toolbar-template-pick', [
898
+ {
899
+ range: {
900
+ startLineNumber: lineNumber,
901
+ startColumn: 1,
902
+ endLineNumber: lineNumber,
903
+ endColumn: lineText.length + 1,
904
+ },
905
+ text: newLine,
906
+ },
907
+ ]);
908
+ monacoEditor.focus();
909
+ return;
910
+ }
911
+ // WYSIWYG — update the heading node attributes.
458
912
  if (!tiptapEditor)
459
913
  return;
460
914
  if (value === '') {
461
- // Clear template
462
915
  tiptapEditor
463
916
  .chain()
464
917
  .focus()
@@ -475,21 +928,35 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
475
928
  handleImageFile(file);
476
929
  // Reset so the same file can be re-selected
477
930
  e.target.value = '';
478
- } }), slotLeft, showViewTabs && (_jsx("div", { className: "squisq-toolbar-view-tabs", role: "tablist", "aria-label": "Editor view", children: visibleViews.map((view) => (_jsxs("button", { role: "tab", "data-view": view.id, "aria-selected": activeView === view.id, className: `squisq-toolbar-view-tab${activeView === view.id ? ' squisq-toolbar-view-tab--active' : ''}`, onClick: () => setActiveView(view.id), "data-tooltip": `${view.label} (${view.shortcut})`, children: [_jsx("span", { className: "squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--long", "data-label": view.label, children: view.label }), view.shortLabel && view.shortLabel !== view.label && (_jsx("span", { className: "squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--short", "data-label": view.shortLabel, children: view.shortLabel }))] }, view.id))) })), !isPreview && !isNarrow && !isCodeMode && (_jsxs("div", { className: "squisq-toolbar-actions", ref: actionsRef, children: [groups.map((group, gi) => (_jsxs("div", { className: "squisq-toolbar-group", children: [gi > 0 && _jsx("div", { className: "squisq-toolbar-separator" }), BUTTONS.filter((b) => b.group === group).map((btn) => {
479
- const active = isWysiwyg ? isTiptapActive(tiptapEditor, btn.id) : false;
931
+ } }), slotLeft, showViewTabs && (_jsx("div", { className: "squisq-toolbar-view-tabs", role: "tablist", "aria-label": "Editor view", children: visibleViews.map((view) => (_jsxs("button", { role: "tab", "data-view": view.id, "aria-selected": activeView === view.id, className: `squisq-toolbar-view-tab${activeView === view.id ? ' squisq-toolbar-view-tab--active' : ''}`, onClick: () => setActiveView(view.id), "data-tooltip": `${view.label} (${view.shortcut})`, children: [_jsx("span", { className: "squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--long", "data-label": view.label, children: view.label }), view.shortLabel && view.shortLabel !== view.label && (_jsx("span", { className: "squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--short", "data-label": view.shortLabel, children: view.shortLabel }))] }, view.id))) })), !isPreview && !isNarrow && !isCodeMode && (_jsxs("div", { className: "squisq-toolbar-actions", ref: actionsRef, children: [groups.map((group, gi) => (_jsxs("div", { className: "squisq-toolbar-group", children: [gi > 0 && _jsx("div", { className: "squisq-toolbar-separator" }), BUTTONS.filter((b) => b.group === group && isButtonVisible(b.id)).map((btn) => {
932
+ const active = btn.id === 'emoji'
933
+ ? emojiPickerAnchor !== null
934
+ : isWysiwyg
935
+ ? isTiptapActive(tiptapEditor, btn.id)
936
+ : false;
480
937
  const disabled = btn.id === 'image' && !mediaProvider;
481
- return (_jsx("button", { className: `squisq-toolbar-button${active ? ' squisq-toolbar-button--active' : ''}`, "data-tooltip": disabled ? 'Insert image (requires media provider)' : btn.title, onClick: () => handleAction(btn.id), "aria-label": btn.title, "aria-pressed": active, disabled: disabled, style: btn.iconStyle, children: btn.id === 'table' ? (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "1", width: "12", height: "12", rx: "1" }), _jsx("line", { x1: "1", y1: "5", x2: "13", y2: "5" }), _jsx("line", { x1: "1", y1: "9", x2: "13", y2: "9" }), _jsx("line", { x1: "5", y1: "1", x2: "5", y2: "13" }), _jsx("line", { x1: "9", y1: "1", x2: "9", y2: "13" })] })) : (btn.icon) }, btn.id));
482
- })] }, group))), currentTemplate !== null && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator" }), _jsx("div", { className: "squisq-toolbar-group squisq-template-picker", children: _jsxs("label", { className: "squisq-template-picker-label", "data-tooltip": "Block template for this heading", children: ["Template:", _jsxs("select", { className: "squisq-template-picker-select", value: currentTemplate, onChange: (e) => handleTemplatePick(e.target.value), children: [_jsx("option", { value: "", children: "\u2014 none \u2014" }), TEMPLATE_NAMES.map((name) => (_jsx("option", { value: name, children: name }, name)))] })] }) })] })), isInTable && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator" }), _jsxs("div", { className: "squisq-toolbar-group squisq-table-controls", children: [_jsx("span", { className: "squisq-table-controls-label", children: "Table:" }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add column before", onClick: () => tiptapEditor.chain().focus().addColumnBefore().run(), "aria-label": "Add column before", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "7", y: "2", width: "8", height: "12", rx: "1" }), _jsx("line", { x1: "11", y1: "2", x2: "11", y2: "14" }), _jsx("line", { x1: "1", y1: "8", x2: "4.5", y2: "8" }), _jsx("line", { x1: "2.75", y1: "6.25", x2: "2.75", y2: "9.75" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add column after", onClick: () => tiptapEditor.chain().focus().addColumnAfter().run(), "aria-label": "Add column after", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "2", width: "8", height: "12", rx: "1" }), _jsx("line", { x1: "5", y1: "2", x2: "5", y2: "14" }), _jsx("line", { x1: "11.5", y1: "8", x2: "15", y2: "8" }), _jsx("line", { x1: "13.25", y1: "6.25", x2: "13.25", y2: "9.75" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Delete column", onClick: () => tiptapEditor.chain().focus().deleteColumn().run(), "aria-label": "Delete column", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "4", y: "1", width: "8", height: "14", rx: "1" }), _jsx("line", { x1: "6", y1: "5.5", x2: "10", y2: "10.5" }), _jsx("line", { x1: "10", y1: "5.5", x2: "6", y2: "10.5" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add row above", onClick: () => tiptapEditor.chain().focus().addRowBefore().run(), "aria-label": "Add row above", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "2", y: "6", width: "12", height: "9", rx: "1" }), _jsx("line", { x1: "2", y1: "10.5", x2: "14", y2: "10.5" }), _jsx("line", { x1: "8", y1: "1", x2: "8", y2: "4.5" }), _jsx("line", { x1: "6.25", y1: "2.75", x2: "9.75", y2: "2.75" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add row below", onClick: () => tiptapEditor.chain().focus().addRowAfter().run(), "aria-label": "Add row below", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "2", y: "1", width: "12", height: "9", rx: "1" }), _jsx("line", { x1: "2", y1: "5.5", x2: "14", y2: "5.5" }), _jsx("line", { x1: "8", y1: "11.5", x2: "8", y2: "15" }), _jsx("line", { x1: "6.25", y1: "13.25", x2: "9.75", y2: "13.25" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Delete row", onClick: () => tiptapEditor.chain().focus().deleteRow().run(), "aria-label": "Delete row", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "4", width: "14", height: "8", rx: "1" }), _jsx("line", { x1: "5.5", y1: "6", x2: "10.5", y2: "10" }), _jsx("line", { x1: "10.5", y1: "6", x2: "5.5", y2: "10" })] }) }), _jsx("button", { className: "squisq-toolbar-button squisq-toolbar-button--danger", "data-tooltip": "Delete table", onClick: () => tiptapEditor.chain().focus().deleteTable().run(), "aria-label": "Delete table", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "1", width: "14", height: "14", rx: "1" }), _jsx("line", { x1: "1", y1: "5.5", x2: "15", y2: "5.5" }), _jsx("line", { x1: "5.5", y1: "1", x2: "5.5", y2: "15" }), _jsx("line", { x1: "4.5", y1: "4.5", x2: "11.5", y2: "11.5", strokeWidth: "2" }), _jsx("line", { x1: "11.5", y1: "4.5", x2: "4.5", y2: "11.5", strokeWidth: "2" })] }) })] })] }))] })), !isPreview && !isCodeMode && overflowIndex !== null && (_jsxs("div", { className: "squisq-toolbar-overflow", ref: overflowRef, children: [_jsx("button", { className: `squisq-toolbar-button squisq-toolbar-overflow-trigger${showOverflow ? ' squisq-toolbar-button--active' : ''}`, "data-tooltip": "More actions", onClick: () => setShowOverflow((v) => !v), "aria-label": "More actions", "aria-expanded": showOverflow, children: "\u00B7\u00B7\u00B7" }), showOverflow && (_jsxs("div", { className: `squisq-toolbar-overflow-menu squisq-toolbar-overflow-menu--${overflowPlacement}`, children: [BUTTONS.slice(overflowIndex).map((btn) => {
483
- const active = isWysiwyg ? isTiptapActive(tiptapEditor, btn.id) : false;
938
+ return (_jsx("button", { ref: btn.id === 'emoji' ? emojiButtonRef : undefined, className: `squisq-toolbar-button${active ? ' squisq-toolbar-button--active' : ''}`, "data-tooltip": disabled ? 'Insert image (requires media provider)' : btn.title, onClick: () => handleAction(btn.id), "aria-label": btn.title, "aria-pressed": active, disabled: disabled, style: btn.iconStyle, children: buttonIconSvg(btn.id) ?? btn.icon }, btn.id));
939
+ })] }, group))), currentTemplate !== null && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator" }), _jsx("div", { className: "squisq-toolbar-group squisq-template-picker", children: _jsx(TemplatePicker, { value: currentTemplate, onChange: handleTemplatePick, recommended: recommendedTemplates }) })] })), isInTable && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator" }), _jsxs("div", { className: "squisq-toolbar-group squisq-table-controls", children: [_jsx("span", { className: "squisq-table-controls-label", children: "Table:" }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add column before", onClick: () => tiptapEditor.chain().focus().addColumnBefore().run(), "aria-label": "Add column before", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "7", y: "2", width: "8", height: "12", rx: "1" }), _jsx("line", { x1: "11", y1: "2", x2: "11", y2: "14" }), _jsx("line", { x1: "1", y1: "8", x2: "4.5", y2: "8" }), _jsx("line", { x1: "2.75", y1: "6.25", x2: "2.75", y2: "9.75" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add column after", onClick: () => tiptapEditor.chain().focus().addColumnAfter().run(), "aria-label": "Add column after", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "2", width: "8", height: "12", rx: "1" }), _jsx("line", { x1: "5", y1: "2", x2: "5", y2: "14" }), _jsx("line", { x1: "11.5", y1: "8", x2: "15", y2: "8" }), _jsx("line", { x1: "13.25", y1: "6.25", x2: "13.25", y2: "9.75" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Delete column", onClick: () => tiptapEditor.chain().focus().deleteColumn().run(), "aria-label": "Delete column", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "4", y: "1", width: "8", height: "14", rx: "1" }), _jsx("line", { x1: "6", y1: "5.5", x2: "10", y2: "10.5" }), _jsx("line", { x1: "10", y1: "5.5", x2: "6", y2: "10.5" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add row above", onClick: () => tiptapEditor.chain().focus().addRowBefore().run(), "aria-label": "Add row above", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "2", y: "6", width: "12", height: "9", rx: "1" }), _jsx("line", { x1: "2", y1: "10.5", x2: "14", y2: "10.5" }), _jsx("line", { x1: "8", y1: "1", x2: "8", y2: "4.5" }), _jsx("line", { x1: "6.25", y1: "2.75", x2: "9.75", y2: "2.75" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add row below", onClick: () => tiptapEditor.chain().focus().addRowAfter().run(), "aria-label": "Add row below", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "2", y: "1", width: "12", height: "9", rx: "1" }), _jsx("line", { x1: "2", y1: "5.5", x2: "14", y2: "5.5" }), _jsx("line", { x1: "8", y1: "11.5", x2: "8", y2: "15" }), _jsx("line", { x1: "6.25", y1: "13.25", x2: "9.75", y2: "13.25" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Delete row", onClick: () => tiptapEditor.chain().focus().deleteRow().run(), "aria-label": "Delete row", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "4", width: "14", height: "8", rx: "1" }), _jsx("line", { x1: "5.5", y1: "6", x2: "10.5", y2: "10" }), _jsx("line", { x1: "10.5", y1: "6", x2: "5.5", y2: "10" })] }) }), _jsx("button", { className: "squisq-toolbar-button squisq-toolbar-button--danger", "data-tooltip": "Delete table", onClick: () => tiptapEditor.chain().focus().deleteTable().run(), "aria-label": "Delete table", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "1", width: "14", height: "14", rx: "1" }), _jsx("line", { x1: "1", y1: "5.5", x2: "15", y2: "5.5" }), _jsx("line", { x1: "5.5", y1: "1", x2: "5.5", y2: "15" }), _jsx("line", { x1: "4.5", y1: "4.5", x2: "11.5", y2: "11.5", strokeWidth: "2" }), _jsx("line", { x1: "11.5", y1: "4.5", x2: "4.5", y2: "11.5", strokeWidth: "2" })] }) })] })] }))] })), !isPreview && !isCodeMode && overflowIndex !== null && (_jsxs("div", { className: "squisq-toolbar-overflow", ref: overflowRef, children: [_jsx("button", { className: `squisq-toolbar-button squisq-toolbar-overflow-trigger${showOverflow ? ' squisq-toolbar-button--active' : ''}`, "data-tooltip": "More actions", onClick: () => setShowOverflow((v) => !v), "aria-label": "More actions", "aria-expanded": showOverflow, children: "\u00B7\u00B7\u00B7" }), showOverflow && (_jsxs("div", { className: `squisq-toolbar-overflow-menu squisq-toolbar-overflow-menu--${overflowPlacement}`, children: [BUTTONS.slice(overflowIndex)
940
+ .filter((b) => isButtonVisible(b.id))
941
+ .map((btn) => {
942
+ const active = btn.id === 'emoji'
943
+ ? emojiPickerAnchor !== null
944
+ : isWysiwyg
945
+ ? isTiptapActive(tiptapEditor, btn.id)
946
+ : false;
484
947
  const disabled = btn.id === 'image' && !mediaProvider;
485
- return (_jsxs("button", { className: `squisq-toolbar-overflow-item${active ? ' squisq-toolbar-overflow-item--active' : ''}`, onClick: () => {
948
+ return (_jsxs("button", { ref: btn.id === 'emoji' ? emojiButtonRef : undefined, className: `squisq-toolbar-overflow-item${active ? ' squisq-toolbar-overflow-item--active' : ''}`, onClick: () => {
486
949
  handleAction(btn.id);
487
- setShowOverflow(false);
488
- }, disabled: disabled, children: [btn.id === 'table' ? (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "1", width: "12", height: "12", rx: "1" }), _jsx("line", { x1: "1", y1: "5", x2: "13", y2: "5" }), _jsx("line", { x1: "1", y1: "9", x2: "13", y2: "9" }), _jsx("line", { x1: "5", y1: "1", x2: "5", y2: "13" }), _jsx("line", { x1: "9", y1: "1", x2: "9", y2: "13" })] })) : (_jsx("span", { className: "squisq-toolbar-overflow-icon", style: btn.iconStyle, children: btn.icon })), _jsx("span", { children: btn.title })] }, btn.id));
489
- }), currentTemplate !== null && (_jsxs("div", { className: "squisq-toolbar-overflow-item squisq-toolbar-overflow-template", children: [_jsx("span", { children: "Template:" }), _jsxs("select", { className: "squisq-template-picker-select", value: currentTemplate, onChange: (e) => {
490
- handleTemplatePick(e.target.value);
950
+ // Keep the overflow open when opening the emoji
951
+ // picker otherwise its anchor (the overflow
952
+ // item) unmounts and the popover loses its ref.
953
+ if (btn.id !== 'emoji')
954
+ setShowOverflow(false);
955
+ }, disabled: disabled, children: [buttonIconSvg(btn.id) ?? (_jsx("span", { className: "squisq-toolbar-overflow-icon", style: btn.iconStyle, children: btn.icon })), _jsx("span", { children: btn.title })] }, btn.id));
956
+ }), currentTemplate !== null && (_jsxs("div", { className: "squisq-toolbar-overflow-item squisq-toolbar-overflow-template", children: [_jsx("span", { children: "Template:" }), _jsx(TemplatePicker, { value: currentTemplate, onChange: (v) => {
957
+ handleTemplatePick(v);
491
958
  setShowOverflow(false);
492
- }, children: [_jsx("option", { value: "", children: "\u2014 none \u2014" }), TEMPLATE_NAMES.map((name) => (_jsx("option", { value: name, children: name }, name)))] })] })), isInTable && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator", style: { margin: '4px 0', width: '100%', height: 1 } }), [
959
+ }, recommended: recommendedTemplates })] })), isInTable && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator", style: { margin: '4px 0', width: '100%', height: 1 } }), [
493
960
  {
494
961
  label: 'Add column before',
495
962
  action: () => tiptapEditor.chain().focus().addColumnBefore().run(),
@@ -521,6 +988,14 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
521
988
  ].map((item) => (_jsx("button", { className: `squisq-toolbar-overflow-item${item.label.startsWith('Delete') ? ' squisq-toolbar-overflow-item--danger' : ''}`, onClick: () => {
522
989
  item.action();
523
990
  setShowOverflow(false);
524
- }, children: _jsx("span", { children: item.label }) }, item.label)))] }))] }))] })), slotAfterActions, (isPreview || isNarrow || isCodeMode) && _jsx("div", { style: { flex: 1 } }), onToggleFiles && (_jsx("button", { className: `squisq-toolbar-button squisq-toolbar-files-toggle${showFiles ? ' squisq-toolbar-button--active' : ''}`, onClick: onToggleFiles, "data-tooltip": showFiles ? 'Hide Files panel' : 'Show Files panel', "aria-pressed": showFiles, "aria-label": "Toggle Files panel", children: '\u{1F4CE}' })), slotRight] }));
991
+ }, children: _jsx("span", { children: item.label }) }, item.label)))] }))] }))] })), slotAfterActions, (isPreview || isNarrow || isCodeMode) && _jsx("div", { style: { flex: 1 } }), versioning && !isCodeMode && _jsx(VersionHistoryPanel, {}), allowRecording && !isCodeMode && mediaProvider && _jsx(RecorderEntry, {}), !isCodeMode && (_jsx("button", { type: "button", className: "squisq-toolbar-button", onClick: () => setShowDocSettings(true), "data-tooltip": "Document settings", "aria-label": "Document settings", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M3 2.5h7l3 3v8a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1Z", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }), _jsx("path", { d: "M10 2.5v3h3", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }), _jsx("path", { d: "M5 8.5h6M5 11h4", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })] }) })), !isCodeMode && _jsx(ViewMenuPanel, {}), onToggleFiles && (_jsx("button", { className: `squisq-toolbar-button squisq-toolbar-files-toggle${showFiles ? ' squisq-toolbar-button--active' : ''}`, onClick: onToggleFiles, "data-tooltip": showFiles ? 'Hide Files panel' : 'Show Files panel', "aria-pressed": showFiles, "aria-label": "Toggle Files panel", children: PAPERCLIP_ICON })), slotRight, showDocSettings && (_jsx(DocumentSettingsDialog, { markdownSource: markdownSource, onSave: (next) => {
992
+ setMarkdownSource(next);
993
+ setShowDocSettings(false);
994
+ }, onClose: () => setShowDocSettings(false) })), linkDialog && (_jsx(LinkDialog, { mode: linkDialog.mode, initialText: linkDialog.initialText, initialUrl: linkDialog.initialUrl, onConfirm: handleLinkConfirm, onClose: () => setLinkDialog(null), documentLinkProvider: documentLinkProvider })), emojiPickerAnchor &&
995
+ createPortal(_jsx(EmojiPicker, { open: true, onSelect: handleEmojiSelect, onClose: closeEmojiPicker, anchorRef: emojiButtonRef, theme: theme === 'dark' ? 'dark' : 'light', style: {
996
+ position: 'fixed',
997
+ top: emojiPickerAnchor.top,
998
+ left: emojiPickerAnchor.left,
999
+ } }), document.body)] }));
525
1000
  }
526
1001
  //# sourceMappingURL=Toolbar.js.map