@bendyline/squisq-editor-react 1.4.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 (446) hide show
  1. package/dist/DocumentSettingsDialog.d.ts +26 -0
  2. package/dist/DocumentSettingsDialog.d.ts.map +1 -0
  3. package/dist/DocumentSettingsDialog.js +115 -0
  4. package/dist/DocumentSettingsDialog.js.map +1 -0
  5. package/dist/EditorContext.d.ts +248 -4
  6. package/dist/EditorContext.d.ts.map +1 -1
  7. package/dist/EditorContext.js +248 -10
  8. package/dist/EditorContext.js.map +1 -1
  9. package/dist/EditorShell.d.ts +173 -4
  10. package/dist/EditorShell.d.ts.map +1 -1
  11. package/dist/EditorShell.js +110 -10
  12. package/dist/EditorShell.js.map +1 -1
  13. package/dist/EmojiPicker.d.ts +50 -0
  14. package/dist/EmojiPicker.d.ts.map +1 -0
  15. package/dist/EmojiPicker.js +182 -0
  16. package/dist/EmojiPicker.js.map +1 -0
  17. package/dist/ImageEditor.d.ts +68 -0
  18. package/dist/ImageEditor.d.ts.map +1 -0
  19. package/dist/ImageEditor.js +166 -0
  20. package/dist/ImageEditor.js.map +1 -0
  21. package/dist/ImageNodeView.d.ts +13 -1
  22. package/dist/ImageNodeView.d.ts.map +1 -1
  23. package/dist/ImageNodeView.js +172 -19
  24. package/dist/ImageNodeView.js.map +1 -1
  25. package/dist/ImageViewer.d.ts +26 -0
  26. package/dist/ImageViewer.d.ts.map +1 -0
  27. package/dist/ImageViewer.js +119 -0
  28. package/dist/ImageViewer.js.map +1 -0
  29. package/dist/InlineIcon.d.ts +17 -0
  30. package/dist/InlineIcon.d.ts.map +1 -0
  31. package/dist/InlineIcon.js +72 -0
  32. package/dist/InlineIcon.js.map +1 -0
  33. package/dist/InlinePreviewGutter.d.ts +52 -0
  34. package/dist/InlinePreviewGutter.d.ts.map +1 -0
  35. package/dist/InlinePreviewGutter.js +397 -0
  36. package/dist/InlinePreviewGutter.js.map +1 -0
  37. package/dist/LinkDialog.d.ts +43 -0
  38. package/dist/LinkDialog.d.ts.map +1 -0
  39. package/dist/LinkDialog.js +102 -0
  40. package/dist/LinkDialog.js.map +1 -0
  41. package/dist/MentionExtension.js +10 -7
  42. package/dist/MentionExtension.js.map +1 -1
  43. package/dist/OutlinePanel.d.ts +17 -0
  44. package/dist/OutlinePanel.d.ts.map +1 -0
  45. package/dist/OutlinePanel.js +167 -0
  46. package/dist/OutlinePanel.js.map +1 -0
  47. package/dist/PlainHtmlPreview.d.ts +50 -0
  48. package/dist/PlainHtmlPreview.d.ts.map +1 -0
  49. package/dist/PlainHtmlPreview.js +155 -0
  50. package/dist/PlainHtmlPreview.js.map +1 -0
  51. package/dist/PreviewControls.d.ts +15 -1
  52. package/dist/PreviewControls.d.ts.map +1 -1
  53. package/dist/PreviewControls.js +75 -18
  54. package/dist/PreviewControls.js.map +1 -1
  55. package/dist/PreviewPanel.d.ts +11 -10
  56. package/dist/PreviewPanel.d.ts.map +1 -1
  57. package/dist/PreviewPanel.js +20 -17
  58. package/dist/PreviewPanel.js.map +1 -1
  59. package/dist/RawEditor.d.ts.map +1 -1
  60. package/dist/RawEditor.js +198 -4
  61. package/dist/RawEditor.js.map +1 -1
  62. package/dist/RecorderEntry.d.ts +24 -0
  63. package/dist/RecorderEntry.d.ts.map +1 -0
  64. package/dist/RecorderEntry.js +139 -0
  65. package/dist/RecorderEntry.js.map +1 -0
  66. package/dist/TemplateAnnotation.d.ts.map +1 -1
  67. package/dist/TemplateAnnotation.js +32 -6
  68. package/dist/TemplateAnnotation.js.map +1 -1
  69. package/dist/TemplatePicker.d.ts +53 -0
  70. package/dist/TemplatePicker.d.ts.map +1 -0
  71. package/dist/TemplatePicker.js +388 -0
  72. package/dist/TemplatePicker.js.map +1 -0
  73. package/dist/ThemeCustomizerPanel.d.ts +32 -0
  74. package/dist/ThemeCustomizerPanel.d.ts.map +1 -0
  75. package/dist/ThemeCustomizerPanel.js +256 -0
  76. package/dist/ThemeCustomizerPanel.js.map +1 -0
  77. package/dist/ThemePicker.d.ts +33 -0
  78. package/dist/ThemePicker.d.ts.map +1 -0
  79. package/dist/ThemePicker.js +148 -0
  80. package/dist/ThemePicker.js.map +1 -0
  81. package/dist/Toolbar.d.ts.map +1 -1
  82. package/dist/Toolbar.js +508 -33
  83. package/dist/Toolbar.js.map +1 -1
  84. package/dist/VersionHistoryPanel.d.ts +14 -0
  85. package/dist/VersionHistoryPanel.d.ts.map +1 -0
  86. package/dist/VersionHistoryPanel.js +147 -0
  87. package/dist/VersionHistoryPanel.js.map +1 -0
  88. package/dist/ViewMenuPanel.d.ts +13 -0
  89. package/dist/ViewMenuPanel.d.ts.map +1 -0
  90. package/dist/ViewMenuPanel.js +58 -0
  91. package/dist/ViewMenuPanel.js.map +1 -0
  92. package/dist/WysiwygEditor.d.ts.map +1 -1
  93. package/dist/WysiwygEditor.js +198 -9
  94. package/dist/WysiwygEditor.js.map +1 -1
  95. package/dist/__tests__/detectMarkdown.test.js +0 -14
  96. package/dist/__tests__/detectMarkdown.test.js.map +1 -1
  97. package/dist/__tests__/documentSettingsDialog.test.d.ts +2 -0
  98. package/dist/__tests__/documentSettingsDialog.test.d.ts.map +1 -0
  99. package/dist/__tests__/documentSettingsDialog.test.js +132 -0
  100. package/dist/__tests__/documentSettingsDialog.test.js.map +1 -0
  101. package/dist/__tests__/emojiPicker.test.d.ts +2 -0
  102. package/dist/__tests__/emojiPicker.test.d.ts.map +1 -0
  103. package/dist/__tests__/emojiPicker.test.js +111 -0
  104. package/dist/__tests__/emojiPicker.test.js.map +1 -0
  105. package/dist/__tests__/fileKind.test.js +13 -0
  106. package/dist/__tests__/fileKind.test.js.map +1 -1
  107. package/dist/__tests__/imageEditAffordance.test.d.ts +2 -0
  108. package/dist/__tests__/imageEditAffordance.test.d.ts.map +1 -0
  109. package/dist/__tests__/imageEditAffordance.test.js +188 -0
  110. package/dist/__tests__/imageEditAffordance.test.js.map +1 -0
  111. package/dist/__tests__/imageEditorShell.test.d.ts +2 -0
  112. package/dist/__tests__/imageEditorShell.test.d.ts.map +1 -0
  113. package/dist/__tests__/imageEditorShell.test.js +52 -0
  114. package/dist/__tests__/imageEditorShell.test.js.map +1 -0
  115. package/dist/__tests__/imageEditorState.test.d.ts +3 -0
  116. package/dist/__tests__/imageEditorState.test.d.ts.map +1 -0
  117. package/dist/__tests__/imageEditorState.test.js +148 -0
  118. package/dist/__tests__/imageEditorState.test.js.map +1 -0
  119. package/dist/__tests__/inlinePreviewGutter.test.d.ts +2 -0
  120. package/dist/__tests__/inlinePreviewGutter.test.d.ts.map +1 -0
  121. package/dist/__tests__/inlinePreviewGutter.test.js +51 -0
  122. package/dist/__tests__/inlinePreviewGutter.test.js.map +1 -0
  123. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts +2 -0
  124. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts.map +1 -0
  125. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js +63 -0
  126. package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js.map +1 -0
  127. package/dist/__tests__/jsonEditor.test.d.ts +2 -0
  128. package/dist/__tests__/jsonEditor.test.d.ts.map +1 -0
  129. package/dist/__tests__/jsonEditor.test.js +134 -0
  130. package/dist/__tests__/jsonEditor.test.js.map +1 -0
  131. package/dist/__tests__/layersPanel.test.d.ts +2 -0
  132. package/dist/__tests__/layersPanel.test.d.ts.map +1 -0
  133. package/dist/__tests__/layersPanel.test.js +84 -0
  134. package/dist/__tests__/layersPanel.test.js.map +1 -0
  135. package/dist/__tests__/linkDialogDocPicker.test.d.ts +7 -0
  136. package/dist/__tests__/linkDialogDocPicker.test.d.ts.map +1 -0
  137. package/dist/__tests__/linkDialogDocPicker.test.js +75 -0
  138. package/dist/__tests__/linkDialogDocPicker.test.js.map +1 -0
  139. package/dist/__tests__/outlinePanel.test.d.ts +2 -0
  140. package/dist/__tests__/outlinePanel.test.d.ts.map +1 -0
  141. package/dist/__tests__/outlinePanel.test.js +68 -0
  142. package/dist/__tests__/outlinePanel.test.js.map +1 -0
  143. package/dist/__tests__/plainHtmlPreview.test.d.ts +2 -0
  144. package/dist/__tests__/plainHtmlPreview.test.d.ts.map +1 -0
  145. package/dist/__tests__/plainHtmlPreview.test.js +87 -0
  146. package/dist/__tests__/plainHtmlPreview.test.js.map +1 -0
  147. package/dist/__tests__/propertiesPanel.test.d.ts +2 -0
  148. package/dist/__tests__/propertiesPanel.test.d.ts.map +1 -0
  149. package/dist/__tests__/propertiesPanel.test.js +64 -0
  150. package/dist/__tests__/propertiesPanel.test.js.map +1 -0
  151. package/dist/__tests__/recorderFormats.test.d.ts +2 -0
  152. package/dist/__tests__/recorderFormats.test.d.ts.map +1 -0
  153. package/dist/__tests__/recorderFormats.test.js +121 -0
  154. package/dist/__tests__/recorderFormats.test.js.map +1 -0
  155. package/dist/__tests__/recorderTimingJson.test.d.ts +2 -0
  156. package/dist/__tests__/recorderTimingJson.test.d.ts.map +1 -0
  157. package/dist/__tests__/recorderTimingJson.test.js +37 -0
  158. package/dist/__tests__/recorderTimingJson.test.js.map +1 -0
  159. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts +2 -0
  160. package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts.map +1 -0
  161. package/dist/__tests__/templateAnnotationRoundTrip.test.js +31 -0
  162. package/dist/__tests__/templateAnnotationRoundTrip.test.js.map +1 -0
  163. package/dist/__tests__/tiptapBridge.test.js +13 -0
  164. package/dist/__tests__/tiptapBridge.test.js.map +1 -1
  165. package/dist/__tests__/useImageEditor.test.d.ts +2 -0
  166. package/dist/__tests__/useImageEditor.test.d.ts.map +1 -0
  167. package/dist/__tests__/useImageEditor.test.js +131 -0
  168. package/dist/__tests__/useImageEditor.test.js.map +1 -0
  169. package/dist/__tests__/useMediaRecorder.test.d.ts +2 -0
  170. package/dist/__tests__/useMediaRecorder.test.d.ts.map +1 -0
  171. package/dist/__tests__/useMediaRecorder.test.js +153 -0
  172. package/dist/__tests__/useMediaRecorder.test.js.map +1 -0
  173. package/dist/__tests__/versionHistory.test.d.ts +2 -0
  174. package/dist/__tests__/versionHistory.test.d.ts.map +1 -0
  175. package/dist/__tests__/versionHistory.test.js +124 -0
  176. package/dist/__tests__/versionHistory.test.js.map +1 -0
  177. package/dist/blockSlice.d.ts +24 -0
  178. package/dist/blockSlice.d.ts.map +1 -0
  179. package/dist/blockSlice.js +63 -0
  180. package/dist/blockSlice.js.map +1 -0
  181. package/dist/buildPreviewDoc.d.ts.map +1 -1
  182. package/dist/buildPreviewDoc.js +52 -2
  183. package/dist/buildPreviewDoc.js.map +1 -1
  184. package/dist/emojiData.d.ts +81 -0
  185. package/dist/emojiData.d.ts.map +1 -0
  186. package/dist/emojiData.js +1283 -0
  187. package/dist/emojiData.js.map +1 -0
  188. package/dist/fileKind.d.ts +6 -2
  189. package/dist/fileKind.d.ts.map +1 -1
  190. package/dist/fileKind.js +25 -4
  191. package/dist/fileKind.js.map +1 -1
  192. package/dist/hooks/useFileDrop.d.ts.map +1 -1
  193. package/dist/hooks/useFileDrop.js +40 -4
  194. package/dist/hooks/useFileDrop.js.map +1 -1
  195. package/dist/imageEditor/CanvasSurface.d.ts +31 -0
  196. package/dist/imageEditor/CanvasSurface.d.ts.map +1 -0
  197. package/dist/imageEditor/CanvasSurface.js +264 -0
  198. package/dist/imageEditor/CanvasSurface.js.map +1 -0
  199. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts +39 -0
  200. package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts.map +1 -0
  201. package/dist/imageEditor/ImageVersionHistoryDropdown.js +283 -0
  202. package/dist/imageEditor/ImageVersionHistoryDropdown.js.map +1 -0
  203. package/dist/imageEditor/LayersPanel.d.ts +14 -0
  204. package/dist/imageEditor/LayersPanel.d.ts.map +1 -0
  205. package/dist/imageEditor/LayersPanel.js +43 -0
  206. package/dist/imageEditor/LayersPanel.js.map +1 -0
  207. package/dist/imageEditor/PropertiesPanel.d.ts +14 -0
  208. package/dist/imageEditor/PropertiesPanel.d.ts.map +1 -0
  209. package/dist/imageEditor/PropertiesPanel.js +97 -0
  210. package/dist/imageEditor/PropertiesPanel.js.map +1 -0
  211. package/dist/imageEditor/Toolbar.d.ts +30 -0
  212. package/dist/imageEditor/Toolbar.d.ts.map +1 -0
  213. package/dist/imageEditor/Toolbar.js +108 -0
  214. package/dist/imageEditor/Toolbar.js.map +1 -0
  215. package/dist/imageEditor/icons.d.ts +24 -0
  216. package/dist/imageEditor/icons.d.ts.map +1 -0
  217. package/dist/imageEditor/icons.js +45 -0
  218. package/dist/imageEditor/icons.js.map +1 -0
  219. package/dist/imageEditor/layers/EditorImageLayer.d.ts +16 -0
  220. package/dist/imageEditor/layers/EditorImageLayer.d.ts.map +1 -0
  221. package/dist/imageEditor/layers/EditorImageLayer.js +37 -0
  222. package/dist/imageEditor/layers/EditorImageLayer.js.map +1 -0
  223. package/dist/imageEditor/layers/EditorShapeLayer.d.ts +15 -0
  224. package/dist/imageEditor/layers/EditorShapeLayer.d.ts.map +1 -0
  225. package/dist/imageEditor/layers/EditorShapeLayer.js +20 -0
  226. package/dist/imageEditor/layers/EditorShapeLayer.js.map +1 -0
  227. package/dist/imageEditor/layers/EditorTextLayer.d.ts +18 -0
  228. package/dist/imageEditor/layers/EditorTextLayer.d.ts.map +1 -0
  229. package/dist/imageEditor/layers/EditorTextLayer.js +13 -0
  230. package/dist/imageEditor/layers/EditorTextLayer.js.map +1 -0
  231. package/dist/imageEditor/layers/SelectionHandles.d.ts +17 -0
  232. package/dist/imageEditor/layers/SelectionHandles.d.ts.map +1 -0
  233. package/dist/imageEditor/layers/SelectionHandles.js +19 -0
  234. package/dist/imageEditor/layers/SelectionHandles.js.map +1 -0
  235. package/dist/imageEditor/state.d.ts +76 -0
  236. package/dist/imageEditor/state.d.ts.map +1 -0
  237. package/dist/imageEditor/state.js +87 -0
  238. package/dist/imageEditor/state.js.map +1 -0
  239. package/dist/imageEditor/useImageEditor.d.ts +53 -0
  240. package/dist/imageEditor/useImageEditor.d.ts.map +1 -0
  241. package/dist/imageEditor/useImageEditor.js +244 -0
  242. package/dist/imageEditor/useImageEditor.js.map +1 -0
  243. package/dist/imageEditor/useImageEditorTokens.d.ts +16 -0
  244. package/dist/imageEditor/useImageEditorTokens.d.ts.map +1 -0
  245. package/dist/imageEditor/useImageEditorTokens.js +45 -0
  246. package/dist/imageEditor/useImageEditorTokens.js.map +1 -0
  247. package/dist/index.d.ts +48 -1
  248. package/dist/index.d.ts.map +1 -1
  249. package/dist/index.js +36 -0
  250. package/dist/index.js.map +1 -1
  251. package/dist/jsonEditor/EmbeddedRichTextField.d.ts +15 -0
  252. package/dist/jsonEditor/EmbeddedRichTextField.d.ts.map +1 -0
  253. package/dist/jsonEditor/EmbeddedRichTextField.js +74 -0
  254. package/dist/jsonEditor/EmbeddedRichTextField.js.map +1 -0
  255. package/dist/jsonEditor/JsonEditor.d.ts +36 -0
  256. package/dist/jsonEditor/JsonEditor.d.ts.map +1 -0
  257. package/dist/jsonEditor/JsonEditor.js +15 -0
  258. package/dist/jsonEditor/JsonEditor.js.map +1 -0
  259. package/dist/jsonEditor/JsonEditorContext.d.ts +28 -0
  260. package/dist/jsonEditor/JsonEditorContext.d.ts.map +1 -0
  261. package/dist/jsonEditor/JsonEditorContext.js +41 -0
  262. package/dist/jsonEditor/JsonEditorContext.js.map +1 -0
  263. package/dist/jsonEditor/RenderNode.d.ts +16 -0
  264. package/dist/jsonEditor/RenderNode.d.ts.map +1 -0
  265. package/dist/jsonEditor/RenderNode.js +32 -0
  266. package/dist/jsonEditor/RenderNode.js.map +1 -0
  267. package/dist/jsonEditor/editors.d.ts +36 -0
  268. package/dist/jsonEditor/editors.d.ts.map +1 -0
  269. package/dist/jsonEditor/editors.js +347 -0
  270. package/dist/jsonEditor/editors.js.map +1 -0
  271. package/dist/jsonEditor/index.d.ts +3 -0
  272. package/dist/jsonEditor/index.d.ts.map +1 -0
  273. package/dist/jsonEditor/index.js +2 -0
  274. package/dist/jsonEditor/index.js.map +1 -0
  275. package/dist/jsonEditor/useJsonEditorTokens.d.ts +13 -0
  276. package/dist/jsonEditor/useJsonEditorTokens.d.ts.map +1 -0
  277. package/dist/jsonEditor/useJsonEditorTokens.js +38 -0
  278. package/dist/jsonEditor/useJsonEditorTokens.js.map +1 -0
  279. package/dist/recorder/RecorderButton.d.ts +31 -0
  280. package/dist/recorder/RecorderButton.d.ts.map +1 -0
  281. package/dist/recorder/RecorderButton.js +24 -0
  282. package/dist/recorder/RecorderButton.js.map +1 -0
  283. package/dist/recorder/RecorderModal.d.ts +59 -0
  284. package/dist/recorder/RecorderModal.d.ts.map +1 -0
  285. package/dist/recorder/RecorderModal.js +333 -0
  286. package/dist/recorder/RecorderModal.js.map +1 -0
  287. package/dist/recorder/RecorderPanel.d.ts +25 -0
  288. package/dist/recorder/RecorderPanel.d.ts.map +1 -0
  289. package/dist/recorder/RecorderPanel.js +30 -0
  290. package/dist/recorder/RecorderPanel.js.map +1 -0
  291. package/dist/recorder/formats.d.ts +51 -0
  292. package/dist/recorder/formats.d.ts.map +1 -0
  293. package/dist/recorder/formats.js +144 -0
  294. package/dist/recorder/formats.js.map +1 -0
  295. package/dist/recorder/hooks/useMediaRecorder.d.ts +90 -0
  296. package/dist/recorder/hooks/useMediaRecorder.d.ts.map +1 -0
  297. package/dist/recorder/hooks/useMediaRecorder.js +277 -0
  298. package/dist/recorder/hooks/useMediaRecorder.js.map +1 -0
  299. package/dist/recorder/hooks/useStreamPreview.d.ts +22 -0
  300. package/dist/recorder/hooks/useStreamPreview.d.ts.map +1 -0
  301. package/dist/recorder/hooks/useStreamPreview.js +44 -0
  302. package/dist/recorder/hooks/useStreamPreview.js.map +1 -0
  303. package/dist/recorder/sources/cameraStream.d.ts +22 -0
  304. package/dist/recorder/sources/cameraStream.d.ts.map +1 -0
  305. package/dist/recorder/sources/cameraStream.js +24 -0
  306. package/dist/recorder/sources/cameraStream.js.map +1 -0
  307. package/dist/recorder/sources/micStream.d.ts +15 -0
  308. package/dist/recorder/sources/micStream.d.ts.map +1 -0
  309. package/dist/recorder/sources/micStream.js +24 -0
  310. package/dist/recorder/sources/micStream.js.map +1 -0
  311. package/dist/recorder/sources/screenStream.d.ts +53 -0
  312. package/dist/recorder/sources/screenStream.d.ts.map +1 -0
  313. package/dist/recorder/sources/screenStream.js +114 -0
  314. package/dist/recorder/sources/screenStream.js.map +1 -0
  315. package/dist/recorder/timingJson.d.ts +51 -0
  316. package/dist/recorder/timingJson.d.ts.map +1 -0
  317. package/dist/recorder/timingJson.js +42 -0
  318. package/dist/recorder/timingJson.js.map +1 -0
  319. package/dist/tiptap/TiptapAudio.d.ts +26 -0
  320. package/dist/tiptap/TiptapAudio.d.ts.map +1 -0
  321. package/dist/tiptap/TiptapAudio.js +58 -0
  322. package/dist/tiptap/TiptapAudio.js.map +1 -0
  323. package/dist/tiptap/TiptapVideo.d.ts +30 -0
  324. package/dist/tiptap/TiptapVideo.d.ts.map +1 -0
  325. package/dist/tiptap/TiptapVideo.js +66 -0
  326. package/dist/tiptap/TiptapVideo.js.map +1 -0
  327. package/dist/tiptap/useResolvedMediaSrc.d.ts +2 -0
  328. package/dist/tiptap/useResolvedMediaSrc.d.ts.map +1 -0
  329. package/dist/tiptap/useResolvedMediaSrc.js +42 -0
  330. package/dist/tiptap/useResolvedMediaSrc.js.map +1 -0
  331. package/dist/tiptapBridge.d.ts.map +1 -1
  332. package/dist/tiptapBridge.js +171 -14
  333. package/dist/tiptapBridge.js.map +1 -1
  334. package/dist/useHeadingLayout.d.ts +54 -0
  335. package/dist/useHeadingLayout.d.ts.map +1 -0
  336. package/dist/useHeadingLayout.js +260 -0
  337. package/dist/useHeadingLayout.js.map +1 -0
  338. package/dist/utils/collectInlineFontAwesomeCss.d.ts +21 -0
  339. package/dist/utils/collectInlineFontAwesomeCss.d.ts.map +1 -0
  340. package/dist/utils/collectInlineFontAwesomeCss.js +68 -0
  341. package/dist/utils/collectInlineFontAwesomeCss.js.map +1 -0
  342. package/dist/utils/dropUtils.d.ts +21 -2
  343. package/dist/utils/dropUtils.d.ts.map +1 -1
  344. package/dist/utils/dropUtils.js +43 -4
  345. package/dist/utils/dropUtils.js.map +1 -1
  346. package/dist/utils/normalizeMalformedAssetUrl.d.ts +15 -0
  347. package/dist/utils/normalizeMalformedAssetUrl.d.ts.map +1 -0
  348. package/dist/utils/normalizeMalformedAssetUrl.js +27 -0
  349. package/dist/utils/normalizeMalformedAssetUrl.js.map +1 -0
  350. package/package.json +8 -5
  351. package/src/DocumentSettingsDialog.tsx +266 -0
  352. package/src/EditorContext.tsx +534 -10
  353. package/src/EditorShell.tsx +571 -55
  354. package/src/EmojiPicker.tsx +332 -0
  355. package/src/ImageEditor.tsx +327 -0
  356. package/src/ImageNodeView.tsx +222 -21
  357. package/src/ImageViewer.tsx +221 -0
  358. package/src/InlineIcon.ts +84 -0
  359. package/src/InlinePreviewGutter.tsx +582 -0
  360. package/src/LinkDialog.tsx +276 -0
  361. package/src/MentionExtension.tsx +10 -7
  362. package/src/OutlinePanel.tsx +295 -0
  363. package/src/PlainHtmlPreview.tsx +211 -0
  364. package/src/PreviewControls.tsx +130 -24
  365. package/src/PreviewPanel.tsx +38 -21
  366. package/src/RawEditor.tsx +215 -4
  367. package/src/RecorderEntry.tsx +164 -0
  368. package/src/TemplateAnnotation.ts +32 -6
  369. package/src/TemplatePicker.tsx +818 -0
  370. package/src/ThemeCustomizerPanel.tsx +595 -0
  371. package/src/ThemePicker.tsx +319 -0
  372. package/src/Toolbar.tsx +708 -111
  373. package/src/VersionHistoryPanel.tsx +329 -0
  374. package/src/ViewMenuPanel.tsx +188 -0
  375. package/src/WysiwygEditor.tsx +229 -9
  376. package/src/__tests__/detectMarkdown.test.ts +0 -15
  377. package/src/__tests__/documentSettingsDialog.test.tsx +147 -0
  378. package/src/__tests__/emojiPicker.test.tsx +133 -0
  379. package/src/__tests__/fileKind.test.ts +16 -0
  380. package/src/__tests__/imageEditAffordance.test.tsx +268 -0
  381. package/src/__tests__/imageEditorShell.test.tsx +57 -0
  382. package/src/__tests__/imageEditorState.test.ts +171 -0
  383. package/src/__tests__/inlinePreviewGutter.test.tsx +62 -0
  384. package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +103 -0
  385. package/src/__tests__/jsonEditor.test.tsx +168 -0
  386. package/src/__tests__/layersPanel.test.tsx +97 -0
  387. package/src/__tests__/linkDialogDocPicker.test.tsx +137 -0
  388. package/src/__tests__/outlinePanel.test.tsx +79 -0
  389. package/src/__tests__/plainHtmlPreview.test.tsx +107 -0
  390. package/src/__tests__/propertiesPanel.test.tsx +69 -0
  391. package/src/__tests__/recorderFormats.test.ts +146 -0
  392. package/src/__tests__/recorderTimingJson.test.ts +41 -0
  393. package/src/__tests__/templateAnnotationRoundTrip.test.ts +34 -0
  394. package/src/__tests__/tiptapBridge.test.ts +15 -0
  395. package/src/__tests__/useImageEditor.test.tsx +159 -0
  396. package/src/__tests__/useMediaRecorder.test.ts +186 -0
  397. package/src/__tests__/versionHistory.test.tsx +197 -0
  398. package/src/blockSlice.ts +75 -0
  399. package/src/buildPreviewDoc.ts +61 -6
  400. package/src/emojiData.ts +1337 -0
  401. package/src/fileKind.ts +30 -6
  402. package/src/hooks/useFileDrop.ts +40 -4
  403. package/src/imageEditor/CanvasSurface.tsx +402 -0
  404. package/src/imageEditor/ImageVersionHistoryDropdown.tsx +396 -0
  405. package/src/imageEditor/LayersPanel.tsx +143 -0
  406. package/src/imageEditor/PropertiesPanel.tsx +428 -0
  407. package/src/imageEditor/Toolbar.tsx +242 -0
  408. package/src/imageEditor/icons.tsx +144 -0
  409. package/src/imageEditor/image-editor.css +450 -0
  410. package/src/imageEditor/layers/EditorImageLayer.tsx +45 -0
  411. package/src/imageEditor/layers/EditorShapeLayer.tsx +62 -0
  412. package/src/imageEditor/layers/EditorTextLayer.tsx +45 -0
  413. package/src/imageEditor/layers/SelectionHandles.tsx +86 -0
  414. package/src/imageEditor/state.ts +153 -0
  415. package/src/imageEditor/useImageEditor.ts +328 -0
  416. package/src/imageEditor/useImageEditorTokens.ts +70 -0
  417. package/src/index.ts +82 -0
  418. package/src/jsonEditor/EmbeddedRichTextField.tsx +81 -0
  419. package/src/jsonEditor/JsonEditor.tsx +81 -0
  420. package/src/jsonEditor/JsonEditorContext.tsx +75 -0
  421. package/src/jsonEditor/RenderNode.tsx +66 -0
  422. package/src/jsonEditor/editors.tsx +678 -0
  423. package/src/jsonEditor/index.ts +2 -0
  424. package/src/jsonEditor/json-editor.css +463 -0
  425. package/src/jsonEditor/useJsonEditorTokens.ts +63 -0
  426. package/src/recorder/RecorderButton.tsx +72 -0
  427. package/src/recorder/RecorderModal.tsx +596 -0
  428. package/src/recorder/RecorderPanel.tsx +93 -0
  429. package/src/recorder/formats.ts +159 -0
  430. package/src/recorder/hooks/useMediaRecorder.ts +378 -0
  431. package/src/recorder/hooks/useStreamPreview.ts +47 -0
  432. package/src/recorder/sources/cameraStream.ts +32 -0
  433. package/src/recorder/sources/micStream.ts +25 -0
  434. package/src/recorder/sources/screenStream.ts +162 -0
  435. package/src/recorder/timingJson.ts +66 -0
  436. package/src/styles/editor.css +2490 -51
  437. package/src/styles/image-edit-affordance.css +201 -0
  438. package/src/styles/index.css +10 -0
  439. package/src/tiptap/TiptapAudio.tsx +86 -0
  440. package/src/tiptap/TiptapVideo.tsx +119 -0
  441. package/src/tiptap/useResolvedMediaSrc.ts +47 -0
  442. package/src/tiptapBridge.ts +188 -20
  443. package/src/useHeadingLayout.ts +294 -0
  444. package/src/utils/collectInlineFontAwesomeCss.ts +69 -0
  445. package/src/utils/dropUtils.ts +54 -6
  446. package/src/utils/normalizeMalformedAssetUrl.ts +22 -0
@@ -0,0 +1,596 @@
1
+ /**
2
+ * RecorderModal — configure-and-capture dialog for browser-based audio,
3
+ * camera, and screen recording.
4
+ *
5
+ * States: configure (pick mode + optional script) → previewing (acquired
6
+ * stream, not yet recording) → recording → review (blob in hand) → saved
7
+ * | error. The user can cancel from any state.
8
+ *
9
+ * Persists the captured `Blob` into the supplied `MediaProvider` and,
10
+ * for narration mode, writes a `.timing.json` sidecar so
11
+ * `resolveAudioMapping()` in `@bendyline/squisq` picks it up at the next
12
+ * doc parse.
13
+ *
14
+ * Visual conventions match `VideoExportModal` from `@bendyline/squisq-
15
+ * video-react` (cream / gold palette, inline styles, no external CSS).
16
+ */
17
+
18
+ import { useCallback, useEffect, useRef, useState, type CSSProperties } from 'react';
19
+ import type { MediaProvider } from '@bendyline/squisq/schemas';
20
+ import type { ContentContainer } from '@bendyline/squisq/storage';
21
+ import { useMediaRecorder, type RecorderSource } from './hooks/useMediaRecorder.js';
22
+ import { useStreamPreview } from './hooks/useStreamPreview.js';
23
+ import { buildFilename } from './formats.js';
24
+ import { buildTimingJson, encodeTimingJson, timingPathFor } from './timingJson.js';
25
+
26
+ // ── Types ──────────────────────────────────────────────────────────
27
+
28
+ export interface RecorderModalProps {
29
+ /** Required — recordings are written here. */
30
+ mediaProvider: MediaProvider;
31
+ /**
32
+ * Optional — when provided, narration-mode recordings drop a
33
+ * `.timing.json` sidecar at the matching container path so
34
+ * `resolveAudioMapping()` can auto-link them. Without it, only the
35
+ * raw recording is saved.
36
+ */
37
+ container?: ContentContainer | null;
38
+ /** Initial capture source. Defaults to `'mic'` (narration). */
39
+ initialMode?: RecorderSource;
40
+ /** Called after the modal is dismissed (save or cancel). */
41
+ onClose: () => void;
42
+ /**
43
+ * Fired after a successful save. Hosts typically use this to insert a
44
+ * markdown reference at the cursor — see {@link RecorderSaveResult}
45
+ * for the fields a host needs to build that reference.
46
+ */
47
+ onSave?: (result: RecorderSaveResult) => void;
48
+ }
49
+
50
+ /** Payload handed to {@link RecorderModalProps.onSave} on a successful save. */
51
+ export interface RecorderSaveResult {
52
+ /** Path returned by `mediaProvider.addMedia()` — what the doc should reference. */
53
+ relativePath: string;
54
+ /** Filename the modal chose (e.g. `narration-20260516-091200.webm`). */
55
+ filename: string;
56
+ /** Capture source the user picked. */
57
+ source: RecorderSource;
58
+ /** MIME type of the saved blob. */
59
+ mimeType: string;
60
+ /** Recording length in seconds. */
61
+ duration: number;
62
+ /** Whether a narration sidecar was written. Always `false` for video sources. */
63
+ hasTimingSidecar: boolean;
64
+ /** Script text the user typed (narration only). */
65
+ sourceText?: string;
66
+ }
67
+
68
+ // ── Styles ─────────────────────────────────────────────────────────
69
+
70
+ const overlayStyle: CSSProperties = {
71
+ position: 'fixed',
72
+ inset: 0,
73
+ background: 'rgba(0, 0, 0, 0.5)',
74
+ display: 'flex',
75
+ alignItems: 'center',
76
+ justifyContent: 'center',
77
+ zIndex: 10000,
78
+ };
79
+
80
+ const modalStyle: CSSProperties = {
81
+ background: '#FFFDF7',
82
+ border: '1px solid #c9b98a',
83
+ borderRadius: 0,
84
+ padding: '24px 28px',
85
+ width: 'min(560px, calc(100vw - 48px))',
86
+ maxHeight: 'calc(100vh - 48px)',
87
+ overflowY: 'auto',
88
+ boxShadow: '0 8px 32px rgba(0,0,0,0.18)',
89
+ fontFamily: 'system-ui, -apple-system, sans-serif',
90
+ color: '#4a3c1f',
91
+ };
92
+
93
+ const titleStyle: CSSProperties = {
94
+ margin: '0 0 16px 0',
95
+ fontSize: 18,
96
+ fontWeight: 600,
97
+ color: '#2d2310',
98
+ };
99
+
100
+ const labelStyle: CSSProperties = {
101
+ display: 'block',
102
+ fontSize: 13,
103
+ fontWeight: 500,
104
+ marginBottom: 4,
105
+ color: '#5a4a2a',
106
+ };
107
+
108
+ const inputStyle: CSSProperties = {
109
+ width: '100%',
110
+ padding: '6px 8px',
111
+ fontSize: 13,
112
+ fontFamily: 'inherit',
113
+ border: '1px solid #c9b98a',
114
+ borderRadius: 0,
115
+ background: '#fff',
116
+ color: '#4a3c1f',
117
+ marginBottom: 12,
118
+ boxSizing: 'border-box',
119
+ };
120
+
121
+ const textareaStyle: CSSProperties = {
122
+ ...inputStyle,
123
+ resize: 'vertical',
124
+ minHeight: 72,
125
+ };
126
+
127
+ const btnPrimary: CSSProperties = {
128
+ padding: '8px 20px',
129
+ fontSize: 14,
130
+ fontFamily: 'inherit',
131
+ fontWeight: 500,
132
+ cursor: 'pointer',
133
+ background: '#8B6914',
134
+ color: '#fff',
135
+ border: '1px solid #7a5c10',
136
+ borderRadius: 0,
137
+ };
138
+
139
+ const btnSecondary: CSSProperties = {
140
+ padding: '8px 20px',
141
+ fontSize: 14,
142
+ fontFamily: 'inherit',
143
+ fontWeight: 500,
144
+ cursor: 'pointer',
145
+ background: '#E8DFC6',
146
+ color: '#4a3c1f',
147
+ border: '1px solid #c9b98a',
148
+ borderRadius: 0,
149
+ };
150
+
151
+ const btnDanger: CSSProperties = {
152
+ ...btnPrimary,
153
+ background: '#B33A3A',
154
+ borderColor: '#902929',
155
+ };
156
+
157
+ const tabRowStyle: CSSProperties = {
158
+ display: 'flex',
159
+ gap: 4,
160
+ marginBottom: 16,
161
+ borderBottom: '1px solid #c9b98a',
162
+ };
163
+
164
+ const tabBase: CSSProperties = {
165
+ padding: '6px 12px',
166
+ fontSize: 13,
167
+ fontFamily: 'inherit',
168
+ cursor: 'pointer',
169
+ background: 'transparent',
170
+ color: '#5a4a2a',
171
+ border: 'none',
172
+ borderBottom: '2px solid transparent',
173
+ marginBottom: -1,
174
+ };
175
+
176
+ const tabActive: CSSProperties = {
177
+ ...tabBase,
178
+ color: '#2d2310',
179
+ fontWeight: 600,
180
+ // Use the `borderBottom` shorthand (not `borderBottomColor` longhand)
181
+ // so React's style diff cleanly resets the underline when this tab
182
+ // goes inactive — mixing shorthand + longhand can leave the old
183
+ // color stuck on a previously-active tab between renders.
184
+ borderBottom: '2px solid #8B6914',
185
+ };
186
+
187
+ const previewBoxStyle: CSSProperties = {
188
+ width: '100%',
189
+ background: '#000',
190
+ borderRadius: 0,
191
+ marginBottom: 12,
192
+ overflow: 'hidden',
193
+ aspectRatio: '16 / 9',
194
+ display: 'flex',
195
+ alignItems: 'center',
196
+ justifyContent: 'center',
197
+ color: '#888',
198
+ fontSize: 13,
199
+ };
200
+
201
+ const audioMeterStyle: CSSProperties = {
202
+ width: '100%',
203
+ height: 56,
204
+ background: '#F2EBD9',
205
+ border: '1px solid #c9b98a',
206
+ marginBottom: 12,
207
+ display: 'flex',
208
+ alignItems: 'center',
209
+ justifyContent: 'center',
210
+ color: '#5a4a2a',
211
+ fontSize: 13,
212
+ fontVariantNumeric: 'tabular-nums',
213
+ };
214
+
215
+ const errorStyle: CSSProperties = {
216
+ background: '#FCEEEE',
217
+ border: '1px solid #D88A8A',
218
+ color: '#8C2A2A',
219
+ padding: '8px 10px',
220
+ fontSize: 13,
221
+ marginBottom: 12,
222
+ };
223
+
224
+ const buttonRowStyle: CSSProperties = {
225
+ display: 'flex',
226
+ gap: 8,
227
+ justifyContent: 'flex-end',
228
+ marginTop: 8,
229
+ };
230
+
231
+ // ── Helpers ────────────────────────────────────────────────────────
232
+
233
+ function formatDurationMs(ms: number): string {
234
+ const totalSec = Math.floor(ms / 1000);
235
+ const m = Math.floor(totalSec / 60);
236
+ const s = totalSec % 60;
237
+ return `${m}:${s.toString().padStart(2, '0')}`;
238
+ }
239
+
240
+ const TABS: Array<{ id: RecorderSource; label: string; description: string }> = [
241
+ {
242
+ id: 'mic',
243
+ label: 'Narration',
244
+ description: 'Voice-only audio. Pairs with a written script for auto-mapping to blocks.',
245
+ },
246
+ { id: 'camera', label: 'Camera', description: 'Camera + microphone. Saved as a video clip.' },
247
+ { id: 'screen', label: 'Screen', description: 'Screen capture. System audio when available.' },
248
+ { id: 'screen+mic', label: 'Screen + Mic', description: 'Screen with your microphone mixed in.' },
249
+ ];
250
+
251
+ // ── Component ──────────────────────────────────────────────────────
252
+
253
+ export function RecorderModal({
254
+ mediaProvider,
255
+ container = null,
256
+ initialMode = 'mic',
257
+ onClose,
258
+ onSave,
259
+ }: RecorderModalProps) {
260
+ const [source, setSource] = useState<RecorderSource>(initialMode);
261
+ const [sourceText, setSourceText] = useState('');
262
+ const [basename, setBasename] = useState('');
263
+ const [includeSystemAudio, setIncludeSystemAudio] = useState(false);
264
+ const [isSaving, setIsSaving] = useState(false);
265
+ const [saveError, setSaveError] = useState<string | null>(null);
266
+ const [playbackUrl, setPlaybackUrl] = useState<string | null>(null);
267
+
268
+ const previewRef = useRef<HTMLVideoElement | null>(null);
269
+
270
+ const recorder = useMediaRecorder({
271
+ source,
272
+ systemAudio: source === 'screen' || source === 'screen+mic' ? includeSystemAudio : false,
273
+ });
274
+
275
+ useStreamPreview(previewRef, recorder.state === 'stopped' ? null : recorder.stream);
276
+
277
+ // Generate (and later revoke) a blob URL for the recorded clip so the
278
+ // playback element has something to point at. The dependency on the
279
+ // blob identity means a new URL is created every time a fresh
280
+ // recording lands, and the cleanup callback revokes the previous one.
281
+ useEffect(() => {
282
+ if (!recorder.blob) {
283
+ setPlaybackUrl(null);
284
+ return;
285
+ }
286
+ const url = URL.createObjectURL(recorder.blob);
287
+ setPlaybackUrl(url);
288
+ return () => {
289
+ URL.revokeObjectURL(url);
290
+ };
291
+ }, [recorder.blob]);
292
+
293
+ // Switching capture source mid-session: tear down whatever stream/
294
+ // recorder we had so the new mode acquires a fresh one. The hook
295
+ // already handles its own internal teardown on unmount; cancel()
296
+ // here covers in-place source changes.
297
+ const previousSourceRef = useRef(source);
298
+ useEffect(() => {
299
+ if (previousSourceRef.current !== source) {
300
+ previousSourceRef.current = source;
301
+ recorder.cancel();
302
+ }
303
+ }, [source, recorder]);
304
+
305
+ // Make sure tearing down the modal always releases the camera /
306
+ // screen-capture indicator. The hook's own unmount effect handles
307
+ // this, but we also kill the stream eagerly on close so a slow
308
+ // unmount doesn't leave the indicator lit between renders.
309
+ const handleClose = useCallback(() => {
310
+ recorder.cancel();
311
+ onClose();
312
+ }, [recorder, onClose]);
313
+
314
+ const handleRequest = useCallback(async () => {
315
+ setSaveError(null);
316
+ try {
317
+ await recorder.request();
318
+ } catch {
319
+ // Already surfaced via recorder.error.
320
+ }
321
+ }, [recorder]);
322
+
323
+ const handleStart = useCallback(() => {
324
+ setSaveError(null);
325
+ recorder.start();
326
+ }, [recorder]);
327
+
328
+ const handleStop = useCallback(async () => {
329
+ setSaveError(null);
330
+ await recorder.stop();
331
+ }, [recorder]);
332
+
333
+ const handleSave = useCallback(async () => {
334
+ if (!recorder.blob || !recorder.mimeType || !recorder.extension || !recorder.directory) {
335
+ setSaveError('Nothing to save yet — record something first.');
336
+ return;
337
+ }
338
+ setIsSaving(true);
339
+ setSaveError(null);
340
+ try {
341
+ const filename = buildFilename(
342
+ source === 'mic' ? 'audio' : 'video',
343
+ recorder.extension,
344
+ basename,
345
+ );
346
+ const relativeName = `${recorder.directory}/${filename}`;
347
+ const relativePath = await mediaProvider.addMedia(
348
+ relativeName,
349
+ recorder.blob,
350
+ recorder.mimeType,
351
+ );
352
+
353
+ let hasTimingSidecar = false;
354
+ if (source === 'mic') {
355
+ const timing = buildTimingJson(sourceText, recorder.durationMs / 1000);
356
+ const encoded = encodeTimingJson(timing);
357
+ const sidecarPath = timingPathFor(relativePath);
358
+ // Prefer direct container write so the sidecar lands at the
359
+ // exact path the audio-mapping pipeline expects. Fall back to
360
+ // addMedia(), which may rename — log if so.
361
+ if (container) {
362
+ await container.writeFile(sidecarPath, encoded, 'application/json');
363
+ hasTimingSidecar = true;
364
+ } else {
365
+ const written = await mediaProvider.addMedia(sidecarPath, encoded, 'application/json');
366
+ hasTimingSidecar = written === sidecarPath;
367
+ if (!hasTimingSidecar) {
368
+ console.warn(
369
+ `[squisq-recorder] timing.json was saved as "${written}" instead of "${sidecarPath}" — auto-mapping may not pick it up.`,
370
+ );
371
+ }
372
+ }
373
+ }
374
+
375
+ const result: RecorderSaveResult = {
376
+ relativePath,
377
+ filename,
378
+ source,
379
+ mimeType: recorder.mimeType,
380
+ duration: recorder.durationMs / 1000,
381
+ hasTimingSidecar,
382
+ };
383
+ if (source === 'mic') {
384
+ result.sourceText = sourceText;
385
+ }
386
+ onSave?.(result);
387
+ handleClose();
388
+ } catch (err: unknown) {
389
+ setSaveError(err instanceof Error ? err.message : 'Failed to save recording');
390
+ } finally {
391
+ setIsSaving(false);
392
+ }
393
+ }, [recorder, source, basename, sourceText, mediaProvider, container, onSave, handleClose]);
394
+
395
+ const handleDiscard = useCallback(() => {
396
+ recorder.reset();
397
+ }, [recorder]);
398
+
399
+ const isAudioOnly = source === 'mic';
400
+ const showPreview = recorder.state !== 'idle' && recorder.state !== 'error';
401
+ const canRecord = recorder.state === 'ready';
402
+ const canStop = recorder.state === 'recording';
403
+ const canSave = recorder.state === 'stopped' && recorder.blob !== null;
404
+ const isBusy = recorder.state === 'requesting' || recorder.state === 'stopping' || isSaving;
405
+
406
+ const activeTabDescription = TABS.find((t) => t.id === source)?.description;
407
+
408
+ return (
409
+ <div style={overlayStyle} role="dialog" aria-modal="true" aria-label="Record media">
410
+ <div style={modalStyle} onClick={(e) => e.stopPropagation()}>
411
+ <h2 style={titleStyle}>Record media</h2>
412
+
413
+ <div style={tabRowStyle} role="tablist">
414
+ {TABS.map((tab) => {
415
+ const active = tab.id === source;
416
+ return (
417
+ <button
418
+ key={tab.id}
419
+ role="tab"
420
+ aria-selected={active}
421
+ type="button"
422
+ style={active ? tabActive : tabBase}
423
+ onClick={() => setSource(tab.id)}
424
+ disabled={recorder.state === 'recording' || recorder.state === 'requesting'}
425
+ >
426
+ {tab.label}
427
+ </button>
428
+ );
429
+ })}
430
+ </div>
431
+
432
+ {activeTabDescription && (
433
+ <p style={{ margin: '0 0 12px 0', fontSize: 12, color: '#5a4a2a' }}>
434
+ {activeTabDescription}
435
+ </p>
436
+ )}
437
+
438
+ {recorder.error && <div style={errorStyle}>{recorder.error.message}</div>}
439
+ {saveError && <div style={errorStyle}>{saveError}</div>}
440
+
441
+ {/* Preview surface. Three modes:
442
+ - Pre-acquisition (idle / error): a static prompt.
443
+ - Live (ready / recording / requesting / stopping): the stream
444
+ piped into a muted <video>, or a recording meter for mic.
445
+ - Playback (stopped): the captured blob bound to a <video>/<audio>
446
+ with native controls so the user can audition before saving.
447
+ */}
448
+ {!showPreview && (
449
+ <div style={previewBoxStyle}>
450
+ <span>Click Start Preview to start a recording.</span>
451
+ </div>
452
+ )}
453
+ {showPreview && recorder.state !== 'stopped' && !isAudioOnly && (
454
+ <div style={previewBoxStyle}>
455
+ <video
456
+ ref={previewRef}
457
+ autoPlay
458
+ muted
459
+ playsInline
460
+ style={{ width: '100%', height: '100%', objectFit: 'contain' }}
461
+ />
462
+ </div>
463
+ )}
464
+ {showPreview && recorder.state !== 'stopped' && isAudioOnly && (
465
+ <div style={audioMeterStyle}>
466
+ {recorder.state === 'recording' ? (
467
+ <>● Recording {formatDurationMs(recorder.durationMs)}</>
468
+ ) : (
469
+ <>Microphone ready</>
470
+ )}
471
+ </div>
472
+ )}
473
+ {recorder.state === 'stopped' && playbackUrl && !isAudioOnly && (
474
+ <div style={previewBoxStyle}>
475
+ <video
476
+ src={playbackUrl}
477
+ controls
478
+ playsInline
479
+ style={{ width: '100%', height: '100%', objectFit: 'contain' }}
480
+ />
481
+ </div>
482
+ )}
483
+ {recorder.state === 'stopped' && playbackUrl && isAudioOnly && (
484
+ <div style={{ marginBottom: 12 }}>
485
+ <div style={{ ...audioMeterStyle, marginBottom: 8 }}>
486
+ ✓ Recorded {formatDurationMs(recorder.durationMs)}
487
+ </div>
488
+ <audio src={playbackUrl} controls style={{ width: '100%' }} />
489
+ </div>
490
+ )}
491
+
492
+ {/* Mode-specific fields */}
493
+ {source === 'mic' && (
494
+ <>
495
+ <label style={labelStyle} htmlFor="recorder-source-text">
496
+ Script (used to auto-match this narration to a block)
497
+ </label>
498
+ <textarea
499
+ id="recorder-source-text"
500
+ style={textareaStyle}
501
+ placeholder="Type the text you're going to read aloud."
502
+ value={sourceText}
503
+ onChange={(e) => setSourceText(e.target.value)}
504
+ disabled={recorder.state === 'recording'}
505
+ />
506
+ </>
507
+ )}
508
+ {(source === 'screen' || source === 'screen+mic') && (
509
+ <label
510
+ style={{
511
+ display: 'flex',
512
+ alignItems: 'center',
513
+ gap: 6,
514
+ marginBottom: 12,
515
+ fontSize: 13,
516
+ }}
517
+ >
518
+ <input
519
+ type="checkbox"
520
+ checked={includeSystemAudio}
521
+ onChange={(e) => setIncludeSystemAudio(e.target.checked)}
522
+ disabled={recorder.state === 'recording' || recorder.state === 'requesting'}
523
+ />
524
+ Include system audio (Chrome only)
525
+ </label>
526
+ )}
527
+
528
+ <label style={labelStyle} htmlFor="recorder-basename">
529
+ Filename (optional)
530
+ </label>
531
+ <input
532
+ id="recorder-basename"
533
+ type="text"
534
+ style={inputStyle}
535
+ placeholder={source === 'mic' ? 'narration' : 'recording'}
536
+ value={basename}
537
+ onChange={(e) => setBasename(e.target.value)}
538
+ disabled={recorder.state === 'recording'}
539
+ />
540
+
541
+ {/* Live duration during recording */}
542
+ {recorder.state === 'recording' && !isAudioOnly && (
543
+ <div
544
+ style={{
545
+ fontSize: 13,
546
+ fontVariantNumeric: 'tabular-nums',
547
+ marginBottom: 12,
548
+ color: '#8B6914',
549
+ fontWeight: 600,
550
+ }}
551
+ >
552
+ ● Recording {formatDurationMs(recorder.durationMs)}
553
+ </div>
554
+ )}
555
+
556
+ {/* Action buttons. Layout depends on state. */}
557
+ <div style={buttonRowStyle}>
558
+ <button type="button" style={btnSecondary} onClick={handleClose} disabled={isBusy}>
559
+ Close
560
+ </button>
561
+
562
+ {(recorder.state === 'idle' ||
563
+ recorder.state === 'error' ||
564
+ recorder.state === 'requesting') && (
565
+ <button type="button" style={btnPrimary} onClick={handleRequest} disabled={isBusy}>
566
+ {recorder.state === 'requesting' ? 'Requesting…' : 'Start preview'}
567
+ </button>
568
+ )}
569
+
570
+ {canRecord && (
571
+ <button type="button" style={btnPrimary} onClick={handleStart} disabled={isBusy}>
572
+ Record
573
+ </button>
574
+ )}
575
+
576
+ {canStop && (
577
+ <button type="button" style={btnDanger} onClick={handleStop} disabled={isBusy}>
578
+ Stop
579
+ </button>
580
+ )}
581
+
582
+ {canSave && (
583
+ <>
584
+ <button type="button" style={btnSecondary} onClick={handleDiscard} disabled={isBusy}>
585
+ Discard & re-record
586
+ </button>
587
+ <button type="button" style={btnPrimary} onClick={handleSave} disabled={isBusy}>
588
+ {isSaving ? 'Saving…' : 'Save to document'}
589
+ </button>
590
+ </>
591
+ )}
592
+ </div>
593
+ </div>
594
+ </div>
595
+ );
596
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * RecorderPanel — toolbar-anchored trigger that opens the
3
+ * {@link RecorderModal} in a portal. Shaped to slot into an editor
4
+ * toolbar alongside other panels (e.g. `VersionHistoryPanel`); ships a
5
+ * compact mic/record icon and no label by default.
6
+ *
7
+ * For a button that owns its own visual label, use {@link RecorderButton}
8
+ * instead.
9
+ */
10
+
11
+ import { useCallback, useState } from 'react';
12
+ import { createPortal } from 'react-dom';
13
+ import type { MediaProvider } from '@bendyline/squisq/schemas';
14
+ import type { ContentContainer } from '@bendyline/squisq/storage';
15
+ import { RecorderModal, type RecorderSaveResult } from './RecorderModal.js';
16
+ import type { RecorderSource } from './hooks/useMediaRecorder.js';
17
+
18
+ export interface RecorderPanelProps {
19
+ mediaProvider: MediaProvider;
20
+ container?: ContentContainer | null;
21
+ initialMode?: RecorderSource;
22
+ onSave?: (result: RecorderSaveResult) => void;
23
+ /** ARIA / tooltip label. Defaults to `'Record media'`. */
24
+ tooltip?: string;
25
+ /** Optional className for the trigger button. */
26
+ className?: string;
27
+ }
28
+
29
+ /**
30
+ * Inline 16×16 SVG mic icon — currentColor-driven so it inherits the
31
+ * toolbar's icon color regardless of theme.
32
+ */
33
+ function MicIcon() {
34
+ return (
35
+ <svg
36
+ width={16}
37
+ height={16}
38
+ viewBox="0 0 16 16"
39
+ fill="none"
40
+ stroke="currentColor"
41
+ strokeWidth={1.5}
42
+ strokeLinecap="round"
43
+ strokeLinejoin="round"
44
+ aria-hidden="true"
45
+ >
46
+ <rect x={5.5} y={2} width={5} height={8} rx={2.5} />
47
+ <path d="M3.5 7.5v1a4.5 4.5 0 0 0 9 0v-1" />
48
+ <line x1={8} y1={13} x2={8} y2={15} />
49
+ <line x1={5.5} y1={15} x2={10.5} y2={15} />
50
+ </svg>
51
+ );
52
+ }
53
+
54
+ export function RecorderPanel({
55
+ mediaProvider,
56
+ container = null,
57
+ initialMode = 'mic',
58
+ onSave,
59
+ tooltip = 'Record media',
60
+ className,
61
+ }: RecorderPanelProps) {
62
+ const [open, setOpen] = useState(false);
63
+ const handleClose = useCallback(() => setOpen(false), []);
64
+
65
+ return (
66
+ <>
67
+ <button
68
+ type="button"
69
+ className={className}
70
+ data-tooltip={tooltip}
71
+ aria-label={tooltip}
72
+ aria-expanded={open}
73
+ onClick={() => setOpen((v) => !v)}
74
+ >
75
+ <MicIcon />
76
+ </button>
77
+ {open &&
78
+ typeof document !== 'undefined' &&
79
+ createPortal(
80
+ <RecorderModal
81
+ mediaProvider={mediaProvider}
82
+ container={container}
83
+ initialMode={initialMode}
84
+ onClose={handleClose}
85
+ onSave={(result) => {
86
+ onSave?.(result);
87
+ }}
88
+ />,
89
+ document.body,
90
+ )}
91
+ </>
92
+ );
93
+ }