@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
package/src/RawEditor.tsx CHANGED
@@ -7,10 +7,16 @@
7
7
  */
8
8
 
9
9
  import { useRef, useCallback, useEffect } from 'react';
10
- import Editor, { loader, type OnMount, type OnChange } from '@monaco-editor/react';
10
+ import Editor, {
11
+ loader,
12
+ type OnMount,
13
+ type OnChange,
14
+ type BeforeMount,
15
+ } from '@monaco-editor/react';
11
16
  import * as monaco from 'monaco-editor';
12
17
  import { useEditorContext } from './EditorContext';
13
18
  import { getAvailableTemplates } from '@bendyline/squisq/doc';
19
+ import { suggestIcons, resolveIcon, iconGlyph } from '@bendyline/squisq/icons';
14
20
  import { SQUISQ_MEDIA_MIME, parseSquisqMediaPayload } from './mediaDragMime';
15
21
 
16
22
  // Use locally installed monaco-editor instead of CDN.
@@ -25,6 +31,23 @@ import { SQUISQ_MEDIA_MIME, parseSquisqMediaPayload } from './mediaDragMime';
25
31
  // only the language contributions needed (e.g. markdown, javascript, etc.).
26
32
  loader.config({ monaco });
27
33
 
34
+ // Squisq Monaco themes: same syntax highlighting as vs / vs-dark, but with
35
+ // Monaco's internal gutter (line numbers + folding margin) and overview
36
+ // ruler tinted to match the side-pane "desk" colors so the canvas's
37
+ // internal furniture blends with its surroundings. The seam color is the
38
+ // 1px line Monaco draws between the white canvas and the overview ruler;
39
+ // matches the `::after` border on `.margin` so both sides of the canvas
40
+ // frame look the same.
41
+ const SQUISQ_LIGHT_GUTTER = '#dcd8d0';
42
+ const SQUISQ_DARK_GUTTER = '#0f1219';
43
+ const SQUISQ_LIGHT_SEAM = '#b0a99a';
44
+ const SQUISQ_DARK_SEAM = '#2a3144';
45
+
46
+ const SQUISQ_THEMES: Record<string, string> = {
47
+ vs: 'squisq-light',
48
+ 'vs-dark': 'squisq-dark',
49
+ };
50
+
28
51
  export interface RawEditorProps {
29
52
  /** Monaco editor theme (default: 'vs-dark') */
30
53
  theme?: string;
@@ -64,6 +87,8 @@ export function RawEditor({
64
87
  const isExternalUpdate = useRef(false);
65
88
  const completionDisposable = useRef<monaco.IDisposable | null>(null);
66
89
  const mentionCompletionDisposable = useRef<monaco.IDisposable | null>(null);
90
+ const iconCompletionDisposable = useRef<monaco.IDisposable | null>(null);
91
+ const iconGlyphDecorations = useRef<monaco.editor.IEditorDecorationsCollection | null>(null);
67
92
  const dropCleanupRef = useRef<(() => void) | null>(null);
68
93
  const keyDisposable = useRef<monaco.IDisposable | null>(null);
69
94
  // Ref so the keydown handler always sees the latest callback.
@@ -78,6 +103,31 @@ export function RawEditor({
78
103
  mentionProviderRef.current = mentionProvider;
79
104
  }, [mentionProvider]);
80
105
 
106
+ const handleBeforeMount: BeforeMount = useCallback((monaco) => {
107
+ monaco.editor.defineTheme('squisq-light', {
108
+ base: 'vs',
109
+ inherit: true,
110
+ rules: [],
111
+ colors: {
112
+ 'editorGutter.background': SQUISQ_LIGHT_GUTTER,
113
+ 'editorOverviewRuler.background': SQUISQ_LIGHT_GUTTER,
114
+ 'editorOverviewRuler.border': SQUISQ_LIGHT_SEAM,
115
+ 'minimap.background': SQUISQ_LIGHT_GUTTER,
116
+ },
117
+ });
118
+ monaco.editor.defineTheme('squisq-dark', {
119
+ base: 'vs-dark',
120
+ inherit: true,
121
+ rules: [],
122
+ colors: {
123
+ 'editorGutter.background': SQUISQ_DARK_GUTTER,
124
+ 'editorOverviewRuler.background': SQUISQ_DARK_GUTTER,
125
+ 'editorOverviewRuler.border': SQUISQ_DARK_SEAM,
126
+ 'minimap.background': SQUISQ_DARK_GUTTER,
127
+ },
128
+ });
129
+ }, []);
130
+
81
131
  const handleMount: OnMount = useCallback(
82
132
  (editor, monaco) => {
83
133
  editorRef.current = editor;
@@ -89,6 +139,8 @@ export function RawEditor({
89
139
  completionDisposable.current = null;
90
140
  mentionCompletionDisposable.current?.dispose();
91
141
  mentionCompletionDisposable.current = null;
142
+ iconCompletionDisposable.current?.dispose();
143
+ iconCompletionDisposable.current = null;
92
144
 
93
145
  // Register the `{[template]}` completion provider only for markdown
94
146
  // files — it's meaningless for TypeScript, JSON, Python, etc.
@@ -103,10 +155,17 @@ export function RawEditor({
103
155
  if (!/^#{1,6}\s/.test(lineContent)) return { suggestions: [] };
104
156
 
105
157
  const textBeforeCursor = lineContent.substring(0, position.column - 1);
158
+ const textAfterCursor = lineContent.substring(position.column - 1);
106
159
  const bracketIdx = textBeforeCursor.lastIndexOf('{[');
107
160
  if (bracketIdx === -1) return { suggestions: [] };
108
161
 
109
- // The range to replace: from after {[ to the cursor
162
+ // When Monaco's bracket auto-pair has already produced the
163
+ // closing `]}` we just leave it in place and skip the
164
+ // suffix — otherwise accepting `sectionHeader` on
165
+ // `{[gi]}` would yield `{[sectionHeader]}]}`.
166
+ const closingMatch = textAfterCursor.match(/^\]\}/);
167
+ const suffix = closingMatch ? '' : ']}';
168
+
110
169
  const startCol = bracketIdx + 3; // after {[
111
170
  const range = new monaco.Range(
112
171
  position.lineNumber,
@@ -117,8 +176,9 @@ export function RawEditor({
117
176
 
118
177
  const suggestions = templates.map((name) => ({
119
178
  label: name,
179
+ filterText: name,
120
180
  kind: monaco.languages.CompletionItemKind.Value,
121
- insertText: name + ']}',
181
+ insertText: name + suffix,
122
182
  range,
123
183
  detail: 'Block template',
124
184
  sortText: name,
@@ -182,6 +242,86 @@ export function RawEditor({
182
242
  },
183
243
  },
184
244
  );
245
+
246
+ // FontAwesome icon completion. Fires inside any `{[…]}` opener
247
+ // anywhere in the doc (not just headings — icons are inline).
248
+ // Suggestions cover the whole FA Free catalog filtered by the
249
+ // partial token; we cap at 50 to keep the popup readable. The
250
+ // template provider above still handles heading lines, so on
251
+ // a `## Title {[…]}` the user sees both template names AND
252
+ // icons interleaved by the regular Monaco filter.
253
+ iconCompletionDisposable.current = monaco.languages.registerCompletionItemProvider(
254
+ 'markdown',
255
+ {
256
+ triggerCharacters: ['['],
257
+ provideCompletionItems(model, position) {
258
+ const lineContent = model.getLineContent(position.lineNumber);
259
+ const textBeforeCursor = lineContent.substring(0, position.column - 1);
260
+ const textAfterCursor = lineContent.substring(position.column - 1);
261
+ const bracketIdx = textBeforeCursor.lastIndexOf('{[');
262
+ if (bracketIdx === -1) return { suggestions: [] };
263
+ // Bail if any `]` already closes this annotation between
264
+ // `{[` and the cursor — we'd be past the token, not in it.
265
+ const between = textBeforeCursor.slice(bracketIdx + 2);
266
+ if (between.includes(']')) return { suggestions: [] };
267
+ // Tokens are alphanumeric + `-_:`; anything else means
268
+ // we're not inside an icon (probably a code/link bracket).
269
+ if (between && !/^[a-zA-Z0-9_:-]*$/.test(between)) {
270
+ return { suggestions: [] };
271
+ }
272
+ const query = between.toLowerCase();
273
+
274
+ // When Monaco's bracket auto-pair has already produced a
275
+ // closing `]}` we don't want to insert another — but we
276
+ // also don't want to consume the existing one, since
277
+ // then we'd have to re-emit it and the round-trip is
278
+ // brittle. Leave the closing in place and just insert
279
+ // the bare token; if no closing exists yet, append it.
280
+ const closingMatch = textAfterCursor.match(/^\]\}/);
281
+ const suffix = closingMatch ? '' : ']}';
282
+
283
+ const range = new monaco.Range(
284
+ position.lineNumber,
285
+ bracketIdx + 3, // after `{[`
286
+ position.lineNumber,
287
+ position.column,
288
+ );
289
+
290
+ const top = suggestIcons(query, 50);
291
+ return {
292
+ suggestions: top.map((m, i) => ({
293
+ // Embed the FA codepoint as the first character of
294
+ // the label. CSS targets the suggest widget with
295
+ // FontAwesome as a font fallback, so the codepoint
296
+ // renders as the glyph and the name renders in the
297
+ // editor's normal font.
298
+ label: {
299
+ label: `${iconGlyph(m.entry)} ${m.token}`,
300
+ description: `fa-${m.entry.family}`,
301
+ },
302
+ // `filterText` excludes the glyph + spacing so Monaco
303
+ // filters against the actual icon name only.
304
+ filterText: m.token,
305
+ kind: monaco.languages.CompletionItemKind.Constant,
306
+ insertText: `${m.token}${suffix}`,
307
+ range,
308
+ detail: m.entry.label,
309
+ // Documentation pane (rendered when Monaco's
310
+ // suggestion preview is expanded) shows a large
311
+ // version of the glyph alongside the canonical token.
312
+ documentation: {
313
+ value: `<i class="fa-${m.entry.family} fa-${m.entry.name}" style="font-size: 2em; display: inline-block; margin-right: 8px; vertical-align: middle"></i> **${m.token}** *(${m.entry.label})*`,
314
+ isTrusted: true,
315
+ supportHtml: true,
316
+ },
317
+ // Sort key: 1-digit score prefix keeps "starts with"
318
+ // matches above "contains" / keyword matches.
319
+ sortText: `${m.score}${String(i).padStart(4, '0')}`,
320
+ })),
321
+ };
322
+ },
323
+ },
324
+ );
185
325
  }
186
326
 
187
327
  // Chat-composer mode: intercept Enter before Monaco inserts a newline.
@@ -257,6 +397,10 @@ export function RawEditor({
257
397
  completionDisposable.current = null;
258
398
  mentionCompletionDisposable.current?.dispose();
259
399
  mentionCompletionDisposable.current = null;
400
+ iconCompletionDisposable.current?.dispose();
401
+ iconCompletionDisposable.current = null;
402
+ iconGlyphDecorations.current?.clear();
403
+ iconGlyphDecorations.current = null;
260
404
  dropCleanupRef.current?.();
261
405
  dropCleanupRef.current = null;
262
406
  keyDisposable.current?.dispose();
@@ -287,12 +431,63 @@ export function RawEditor({
287
431
  }
288
432
  }, [markdownSource]);
289
433
 
434
+ // ── Inline FontAwesome glyph decorations ────────────
435
+ // Walk the markdown source on every change, find each resolvable
436
+ // `{[icon-name]}` span, and overlay the actual glyph (via Monaco's
437
+ // `before:` content decoration) just before the brackets. The CSS
438
+ // classes `.fa-glyph-decoration-<family>` set the per-family font
439
+ // and weight; the codepoint character is the decoration's content.
440
+ useEffect(() => {
441
+ const editor = editorRef.current;
442
+ if (!editor) return;
443
+ if (language !== 'markdown') return;
444
+ const model = editor.getModel();
445
+ if (!model) return;
446
+
447
+ const decorations: monaco.editor.IModelDeltaDecoration[] = [];
448
+ const lines = model.getLineCount();
449
+ const re = /\{\[([a-zA-Z0-9_:-]+)\]\}/g;
450
+ for (let line = 1; line <= lines; line++) {
451
+ const text = model.getLineContent(line);
452
+ re.lastIndex = 0;
453
+ let match: RegExpExecArray | null;
454
+ while ((match = re.exec(text)) !== null) {
455
+ const icon = resolveIcon(match[1]);
456
+ if (!icon) continue;
457
+ const glyph = iconGlyph(icon);
458
+ if (!glyph) continue;
459
+ // Position the decoration as a zero-width range at the `{`
460
+ // of the matched token. `before.contentText` renders the
461
+ // glyph as content prepended visually to that position.
462
+ const col = match.index + 1; // Monaco columns are 1-based
463
+ decorations.push({
464
+ range: new monaco.Range(line, col, line, col),
465
+ options: {
466
+ before: {
467
+ content: glyph,
468
+ inlineClassName: `fa-glyph-decoration-${icon.family}`,
469
+ },
470
+ },
471
+ });
472
+ }
473
+ }
474
+
475
+ if (!iconGlyphDecorations.current) {
476
+ iconGlyphDecorations.current = editor.createDecorationsCollection(decorations);
477
+ } else {
478
+ iconGlyphDecorations.current.set(decorations);
479
+ }
480
+ }, [markdownSource, language]);
481
+
482
+ const effectiveTheme = SQUISQ_THEMES[theme] ?? theme;
483
+
290
484
  return (
291
485
  <div className={className} style={{ width: '100%', height: '100%' }} data-testid="raw-editor">
292
486
  <Editor
293
487
  defaultLanguage={language}
294
488
  value={markdownSource}
295
- theme={theme}
489
+ theme={effectiveTheme}
490
+ beforeMount={handleBeforeMount}
296
491
  onMount={handleMount}
297
492
  onChange={handleChange}
298
493
  options={{
@@ -307,6 +502,22 @@ export function RawEditor({
307
502
  bracketPairColorization: { enabled: true },
308
503
  guides: { indentation: true },
309
504
  padding: { top: 12, bottom: 12 },
505
+ // Markdown's tokenizer classifies most body text as "string"
506
+ // or "comment" context, which suppresses `quickSuggestions`
507
+ // by default. Enable in all three so our `{[icon]}` and
508
+ // `@mention` typeaheads keep firing as the user types past
509
+ // the trigger character.
510
+ quickSuggestions: { other: true, comments: true, strings: true },
511
+ suggestOnTriggerCharacters: true,
512
+ // Breathing room between the gutter and the first character.
513
+ // Done via Monaco's own option so cursor + hit-testing stay in
514
+ // sync — CSS-padding `.view-lines` shifts the text but not the
515
+ // cursors layer, which causes the cursor to drift left of the
516
+ // model column. Monaco's default is 10. We set 22 (12 extra),
517
+ // and CSS paints the rightmost 12px of the gutter as canvas
518
+ // color so the breathing room looks like it sits *inside* the
519
+ // canvas rather than widening the gutter.
520
+ lineDecorationsWidth: 22,
310
521
  readOnly,
311
522
  domReadOnly: readOnly,
312
523
  }}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * RecorderEntry — toolbar slot that wires `RecorderPanel` from
3
+ * `@bendyline/squisq-recorder-react` into the editor.
4
+ *
5
+ * Reads `mediaProvider`, `workspaceContainer`, `activeView`,
6
+ * `tiptapEditor`, and the markdown editing helpers from
7
+ * `useEditorContext()`. On a successful save it:
8
+ * - inserts an HTML5 media element at the cursor — `<video>` for
9
+ * camera / screen recordings, `<audio>` for narration / mic
10
+ * recordings — which the markdown renderer turns into an inline
11
+ * `<InlineVideoPlayer>` / `<InlineAudioPlayer>` with native
12
+ * controls;
13
+ * - for narration, also annotates the nearest heading with
14
+ * `{[audio=filename]}` so `resolveAudioMapping()` continues to tie
15
+ * the recording to that block during slideshow playback;
16
+ * - bumps `mediaRevision` so any blob URLs the media bin cached for
17
+ * the previous file list are invalidated.
18
+ *
19
+ * The component returns `null` when no `mediaProvider` is wired —
20
+ * recording without a place to write the captured bytes is a
21
+ * misconfiguration, not a feature.
22
+ */
23
+
24
+ import { useCallback } from 'react';
25
+ import { RecorderPanel } from './recorder/RecorderPanel.js';
26
+ import type { RecorderSaveResult } from './recorder/RecorderModal.js';
27
+ import { useEditorContext } from './EditorContext';
28
+
29
+ /**
30
+ * Insert a narration annotation `{[audio=filename]}` onto the heading
31
+ * line currently above the cursor in the Monaco editor. Pure-text
32
+ * insertion at the line's end keeps the markdown valid and matches the
33
+ * shape `resolveAudioMapping()` reads. Returns true if a heading line
34
+ * was found and updated.
35
+ */
36
+ function annotateMonacoHeading(
37
+ editor: NonNullable<ReturnType<typeof useEditorContext>['monacoEditor']>,
38
+ filename: string,
39
+ ): boolean {
40
+ const model = editor.getModel();
41
+ if (!model) return false;
42
+ const position = editor.getPosition();
43
+ if (!position) return false;
44
+ // Walk upward from the cursor to find the nearest heading line.
45
+ for (let line = position.lineNumber; line >= 1; line--) {
46
+ const text = model.getLineContent(line);
47
+ if (!/^#{1,6}\s/.test(text)) continue;
48
+ // Skip if the heading already has an audio annotation — don't
49
+ // stomp the user's existing wiring. Add a separate annotation in
50
+ // that case wouldn't help either (resolveAudioMapping reads only
51
+ // the first audio= entry).
52
+ if (/\{\[audio=/.test(text)) return true;
53
+ const trimmed = text.replace(/\s+$/, '');
54
+ const insertText = ` {[audio=${filename}]}`;
55
+ const column = trimmed.length + 1;
56
+ editor.executeEdits('recorder-annotation', [
57
+ {
58
+ range: {
59
+ startLineNumber: line,
60
+ startColumn: column,
61
+ endLineNumber: line,
62
+ endColumn: column,
63
+ },
64
+ text: insertText,
65
+ },
66
+ ]);
67
+ return true;
68
+ }
69
+ return false;
70
+ }
71
+
72
+ export function RecorderEntry() {
73
+ const {
74
+ mediaProvider,
75
+ workspaceContainer,
76
+ activeView,
77
+ monacoEditor,
78
+ tiptapEditor,
79
+ insertAtCursor,
80
+ bumpMediaRevision,
81
+ markdownSource,
82
+ setMarkdownSource,
83
+ } = useEditorContext();
84
+
85
+ const handleSave = useCallback(
86
+ (result: RecorderSaveResult) => {
87
+ bumpMediaRevision();
88
+
89
+ if (result.source === 'mic') {
90
+ // Narration — annotate the nearest heading (drives the
91
+ // slideshow narration pipeline at `core/src/doc/audioMapping.ts`)
92
+ // *and* insert an inline `<audio controls>` so the user can
93
+ // audition the recording inside the editor. The two roles are
94
+ // orthogonal: the annotation is metadata for playback timing,
95
+ // the HTML tag is an in-editor preview control.
96
+ if (activeView === 'raw' && monacoEditor) {
97
+ annotateMonacoHeading(monacoEditor, result.filename);
98
+ }
99
+ const audioTag = `<audio src="${result.relativePath}" controls></audio>`;
100
+ if (activeView === 'wysiwyg' && tiptapEditor) {
101
+ tiptapEditor
102
+ .chain()
103
+ .focus()
104
+ .insertContent({
105
+ type: 'audio',
106
+ attrs: { src: result.relativePath, controls: true },
107
+ })
108
+ .run();
109
+ return;
110
+ }
111
+ if (activeView === 'raw' && monacoEditor) {
112
+ // The annotation went onto the heading line; drop the player
113
+ // on a fresh line at the cursor.
114
+ insertAtCursor(`\n\n${audioTag}\n`);
115
+ return;
116
+ }
117
+ setMarkdownSource(markdownSource ? `${markdownSource}\n\n${audioTag}` : audioTag);
118
+ return;
119
+ }
120
+
121
+ // Camera / screen / screen+mic — inline video player. We pin
122
+ // width=480 so the editor preview doesn't blow up to natural
123
+ // resolution on big monitors; the height is intrinsic, so the
124
+ // aspect ratio is preserved regardless of source dimensions.
125
+ const videoTag = `<video src="${result.relativePath}" controls width="480"></video>`;
126
+ if (activeView === 'wysiwyg' && tiptapEditor) {
127
+ tiptapEditor
128
+ .chain()
129
+ .focus()
130
+ .insertContent({
131
+ type: 'video',
132
+ attrs: { src: result.relativePath, controls: true, width: 480 },
133
+ })
134
+ .run();
135
+ return;
136
+ }
137
+ if (activeView === 'raw' && monacoEditor) {
138
+ insertAtCursor(videoTag);
139
+ return;
140
+ }
141
+ setMarkdownSource(markdownSource ? `${markdownSource}\n\n${videoTag}` : videoTag);
142
+ },
143
+ [
144
+ activeView,
145
+ monacoEditor,
146
+ tiptapEditor,
147
+ insertAtCursor,
148
+ bumpMediaRevision,
149
+ markdownSource,
150
+ setMarkdownSource,
151
+ ],
152
+ );
153
+
154
+ if (!mediaProvider) return null;
155
+
156
+ return (
157
+ <RecorderPanel
158
+ mediaProvider={mediaProvider}
159
+ container={workspaceContainer}
160
+ onSave={handleSave}
161
+ className="squisq-toolbar-button"
162
+ />
163
+ );
164
+ }
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import Heading from '@tiptap/extension-heading';
17
+ import { templateLabel } from './TemplatePicker';
17
18
 
18
19
  /**
19
20
  * HeadingWithTemplate — drop-in replacement for Tiptap's Heading that
@@ -47,11 +48,17 @@ export const HeadingWithTemplate = Heading.extend({
47
48
  const tag = `h${level}`;
48
49
  const templateName = HTMLAttributes['data-template'];
49
50
 
51
+ // Render heading with a trailing badge span. The badge has no text
52
+ // content — its label is painted via CSS `content: attr(data-template-label)`
53
+ // so the template name never becomes part of the serialized heading
54
+ // text (which would leak into markdown on round-trip).
55
+ //
56
+ // When no template is set we still render a subtle "empty" badge so
57
+ // authors have a visible affordance for opening the template picker
58
+ // straight from the heading (matches the clicky chip shown for
59
+ // templated headings). The empty variant has no `data-template`
60
+ // attribute, so the bridge treats the heading as plain on save.
50
61
  if (templateName) {
51
- // Render heading with a trailing badge span. The badge has no text
52
- // content — its label is painted via CSS `content: attr(data-template)`
53
- // so the template name never becomes part of the serialized heading
54
- // text (which would leak into markdown on round-trip).
55
62
  return [
56
63
  tag,
57
64
  HTMLAttributes,
@@ -61,13 +68,32 @@ export const HeadingWithTemplate = Heading.extend({
61
68
  {
62
69
  class: 'squisq-template-badge',
63
70
  contenteditable: 'false',
71
+ role: 'button',
72
+ tabindex: '0',
73
+ 'aria-haspopup': 'listbox',
74
+ title: 'Change block template',
64
75
  'data-template': templateName,
76
+ 'data-template-label': templateLabel(templateName),
65
77
  },
66
78
  ],
67
79
  ];
68
80
  }
69
81
 
70
- // No template — render as normal heading
71
- return [tag, HTMLAttributes, 0];
82
+ return [
83
+ tag,
84
+ HTMLAttributes,
85
+ ['span', { class: 'squisq-heading-content' }, 0],
86
+ [
87
+ 'span',
88
+ {
89
+ class: 'squisq-template-badge squisq-template-badge--empty',
90
+ contenteditable: 'false',
91
+ role: 'button',
92
+ tabindex: '0',
93
+ 'aria-haspopup': 'listbox',
94
+ title: 'Choose block template',
95
+ },
96
+ ],
97
+ ];
72
98
  },
73
99
  });