@bendyline/squisq-editor-react 2.1.0 → 2.3.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 (415) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +40 -0
  3. package/dist/chunk-54UGTQBO.js +862 -0
  4. package/dist/chunk-5JMHFAVW.js +2408 -0
  5. package/dist/chunk-5Q4JN4I5.js +132 -0
  6. package/dist/chunk-6VDYKI3L.js +49 -0
  7. package/dist/chunk-GS7QWYFT.js +9 -0
  8. package/dist/chunk-MJJK7YQB.js +949 -0
  9. package/dist/chunk-NITZVAXL.js +986 -0
  10. package/dist/chunk-TRCKHRBS.js +35234 -0
  11. package/dist/chunk-V44VP242.js +3256 -0
  12. package/dist/image-editor/index.d.ts +223 -0
  13. package/dist/image-editor/index.js +15 -0
  14. package/dist/index.d.ts +2060 -4040
  15. package/dist/index.js +496 -40014
  16. package/dist/json-editor/index.d.ts +27 -0
  17. package/dist/json-editor/index.js +7 -0
  18. package/dist/monaco.d.ts +1 -1
  19. package/dist/monaco.js +1 -2
  20. package/dist/recorder/index.d.ts +407 -0
  21. package/dist/recorder/index.js +43 -0
  22. package/dist/shell/index.d.ts +9 -0
  23. package/dist/shell/index.js +22 -0
  24. package/dist/shell-BxSCBm4H.d.ts +1064 -0
  25. package/dist/styles/index.css +598 -161
  26. package/dist/teleprompter/index.d.ts +497 -0
  27. package/dist/teleprompter/index.js +57 -0
  28. package/package.json +34 -8
  29. package/dist/index.js.map +0 -1
  30. package/dist/monaco.js.map +0 -1
  31. package/src/BlockCardView.tsx +0 -121
  32. package/src/BlockPreviewPanel.tsx +0 -69
  33. package/src/BlockPropertiesPopover.tsx +0 -222
  34. package/src/DocumentSettingsDialog.tsx +0 -272
  35. package/src/DropZoneOverlay.tsx +0 -137
  36. package/src/EditorContext.tsx +0 -1274
  37. package/src/EditorShell.tsx +0 -1358
  38. package/src/EmojiPicker.tsx +0 -332
  39. package/src/FolderView.tsx +0 -131
  40. package/src/Icon.tsx +0 -26
  41. package/src/ImageEditor.tsx +0 -395
  42. package/src/ImageNodeView.tsx +0 -284
  43. package/src/ImageViewer.tsx +0 -221
  44. package/src/InlineIcon.ts +0 -84
  45. package/src/InlinePreviewGutter.tsx +0 -590
  46. package/src/LinkDialog.tsx +0 -276
  47. package/src/MediaBin.tsx +0 -500
  48. package/src/MentionExtension.tsx +0 -261
  49. package/src/OutlinePanel.tsx +0 -547
  50. package/src/PlainHtmlPreview.tsx +0 -277
  51. package/src/PreviewControls.tsx +0 -1265
  52. package/src/PreviewPanel.tsx +0 -426
  53. package/src/RawEditor.tsx +0 -743
  54. package/src/RecorderEntry.tsx +0 -162
  55. package/src/StatusBar.tsx +0 -51
  56. package/src/TemplateAnnotation.ts +0 -167
  57. package/src/TemplateContentPreview.tsx +0 -56
  58. package/src/TemplatePicker.tsx +0 -1314
  59. package/src/ThemeCustomizerPanel.tsx +0 -304
  60. package/src/ThemePicker.tsx +0 -433
  61. package/src/TimelineBlockPreview.tsx +0 -37
  62. package/src/TimelineTrack.tsx +0 -671
  63. package/src/Toolbar.tsx +0 -2885
  64. package/src/Tooltip.tsx +0 -142
  65. package/src/TransitionPicker.tsx +0 -390
  66. package/src/VersionHistoryPanel.tsx +0 -359
  67. package/src/ViewMenuPanel.tsx +0 -189
  68. package/src/ViewSwitcher.tsx +0 -56
  69. package/src/WysiwygEditor.tsx +0 -806
  70. package/src/__tests__/blockProperties.test.ts +0 -92
  71. package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +0 -33
  72. package/src/__tests__/blockRange.test.ts +0 -105
  73. package/src/__tests__/blockTagActivity.test.ts +0 -183
  74. package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +0 -75
  75. package/src/__tests__/buildPreviewDocContent.test.ts +0 -65
  76. package/src/__tests__/buildPreviewDocTransition.test.ts +0 -92
  77. package/src/__tests__/canvasSurfaceTextEditing.test.tsx +0 -60
  78. package/src/__tests__/codeContextSectionView.test.tsx +0 -97
  79. package/src/__tests__/codeContextZoneManager.test.ts +0 -127
  80. package/src/__tests__/createShapeLayer.test.ts +0 -46
  81. package/src/__tests__/customLayoutManagerTheme.test.tsx +0 -34
  82. package/src/__tests__/detectMarkdown.test.ts +0 -73
  83. package/src/__tests__/diffContextSections.test.ts +0 -39
  84. package/src/__tests__/documentSettingsDialog.test.tsx +0 -197
  85. package/src/__tests__/drawingShapeRoundTrip.test.ts +0 -49
  86. package/src/__tests__/editorScrollHandoff.test.tsx +0 -102
  87. package/src/__tests__/editorScrollSync.test.ts +0 -65
  88. package/src/__tests__/editorShellCodeContext.test.tsx +0 -86
  89. package/src/__tests__/editorShellProps.test.tsx +0 -550
  90. package/src/__tests__/embeddedMedia.test.ts +0 -48
  91. package/src/__tests__/emojiPicker.test.tsx +0 -93
  92. package/src/__tests__/fileKind.test.ts +0 -112
  93. package/src/__tests__/findMode.test.tsx +0 -104
  94. package/src/__tests__/findModel.test.ts +0 -55
  95. package/src/__tests__/headingTransition.test.ts +0 -176
  96. package/src/__tests__/imageEditAffordance.test.tsx +0 -272
  97. package/src/__tests__/imageEditModalContract.test.tsx +0 -119
  98. package/src/__tests__/imageEditorShell.test.tsx +0 -94
  99. package/src/__tests__/imageEditorState.test.ts +0 -171
  100. package/src/__tests__/inlinePreviewGutter.test.tsx +0 -62
  101. package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +0 -103
  102. package/src/__tests__/jsonEditor.test.tsx +0 -223
  103. package/src/__tests__/layersPanel.test.tsx +0 -167
  104. package/src/__tests__/layoutChildRoundTrip.test.ts +0 -71
  105. package/src/__tests__/linkDialogDocPicker.test.tsx +0 -137
  106. package/src/__tests__/majorApiTypes.test.ts +0 -26
  107. package/src/__tests__/markdownCodeFence.test.ts +0 -45
  108. package/src/__tests__/mediaAttachmentFlow.test.ts +0 -110
  109. package/src/__tests__/mediaBinDrop.test.tsx +0 -90
  110. package/src/__tests__/mediaReferences.test.ts +0 -97
  111. package/src/__tests__/narrationSave.test.ts +0 -148
  112. package/src/__tests__/outlinePanel.test.tsx +0 -169
  113. package/src/__tests__/outlineSource.test.ts +0 -217
  114. package/src/__tests__/plainHtmlPreview.test.tsx +0 -125
  115. package/src/__tests__/presentationMode.test.tsx +0 -290
  116. package/src/__tests__/previewControls.test.tsx +0 -571
  117. package/src/__tests__/previewPanelAudioFailure.test.tsx +0 -46
  118. package/src/__tests__/previewPanelPresentation.test.tsx +0 -126
  119. package/src/__tests__/previewPanelTransforms.test.tsx +0 -117
  120. package/src/__tests__/propertiesPanel.test.tsx +0 -69
  121. package/src/__tests__/rawEditorModelIsolation.test.ts +0 -19
  122. package/src/__tests__/recorderFormats.test.ts +0 -146
  123. package/src/__tests__/recorderMediaInsert.test.ts +0 -86
  124. package/src/__tests__/recorderTheme.test.tsx +0 -42
  125. package/src/__tests__/recorderTimingJson.test.ts +0 -41
  126. package/src/__tests__/resolveBlockVisual.test.ts +0 -43
  127. package/src/__tests__/selectionConversions.test.ts +0 -80
  128. package/src/__tests__/statusBar.test.tsx +0 -27
  129. package/src/__tests__/teleprompter.test.tsx +0 -190
  130. package/src/__tests__/templateAnnotationRoundTrip.test.ts +0 -99
  131. package/src/__tests__/templateContentPreview.test.ts +0 -101
  132. package/src/__tests__/templatePickerMetadata.test.ts +0 -32
  133. package/src/__tests__/templatePickerPortal.test.tsx +0 -62
  134. package/src/__tests__/timelineSource.test.ts +0 -134
  135. package/src/__tests__/tiptapBridge.test.ts +0 -575
  136. package/src/__tests__/tiptapBridgeConformance.test.ts +0 -47
  137. package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +0 -109
  138. package/src/__tests__/tiptapImageRoundTrip.test.ts +0 -73
  139. package/src/__tests__/toolbarSelectionConversion.test.tsx +0 -190
  140. package/src/__tests__/tooltip.test.tsx +0 -72
  141. package/src/__tests__/transformStyleId.test.ts +0 -13
  142. package/src/__tests__/transitionCatalog.test.ts +0 -64
  143. package/src/__tests__/useBlockNavigator.test.tsx +0 -67
  144. package/src/__tests__/useImageEditor.test.tsx +0 -226
  145. package/src/__tests__/useJsonEditorTokens.test.ts +0 -59
  146. package/src/__tests__/useMediaRecorder.test.ts +0 -302
  147. package/src/__tests__/useTimelineClock.test.ts +0 -21
  148. package/src/__tests__/versionHistory.test.tsx +0 -197
  149. package/src/__tests__/viewMenuPanel.test.tsx +0 -130
  150. package/src/__tests__/writeCanvasSettings.test.ts +0 -15
  151. package/src/__tests__/wysiwygImageUpload.test.ts +0 -69
  152. package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +0 -56
  153. package/src/asciiDiagram/AsciiDiagramExtension.ts +0 -343
  154. package/src/asciiDiagram/AsciiDiagramWidget.tsx +0 -254
  155. package/src/asciiDiagram/RepairableDiagramExtension.ts +0 -200
  156. package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +0 -164
  157. package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +0 -159
  158. package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +0 -284
  159. package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +0 -114
  160. package/src/asciiDiagram/__tests__/asciiPaste.test.ts +0 -43
  161. package/src/asciiDiagram/asciiDiagramCommands.ts +0 -174
  162. package/src/asciiDiagram/asciiDiagramData.ts +0 -149
  163. package/src/asciiDiagram/asciiDiagramOps.ts +0 -238
  164. package/src/asciiDiagram/asciiPaste.ts +0 -19
  165. package/src/blockProperties.ts +0 -88
  166. package/src/blockRange.ts +0 -132
  167. package/src/blockSlice.ts +0 -75
  168. package/src/blockTagActivity.ts +0 -233
  169. package/src/buildDocumentPreviewMarkdown.ts +0 -168
  170. package/src/buildPreviewDoc.ts +0 -407
  171. package/src/codeContext/CodeContextSectionView.tsx +0 -124
  172. package/src/codeContext/CodeContextZoneManager.ts +0 -149
  173. package/src/codeContext/CodeContextZones.tsx +0 -121
  174. package/src/codeContext/diffContextSections.ts +0 -38
  175. package/src/codeContext/types.ts +0 -75
  176. package/src/codeSnippet/CodeSnippetExtension.ts +0 -205
  177. package/src/codeSnippet/CodeSnippetWidget.tsx +0 -109
  178. package/src/codeSnippet/__tests__/CodeSnippetExtension.test.ts +0 -95
  179. package/src/codeSnippet/__tests__/codeSnippetLanguages.test.ts +0 -50
  180. package/src/codeSnippet/codeSnippetCommands.ts +0 -21
  181. package/src/codeSnippet/codeSnippetData.ts +0 -43
  182. package/src/codeSnippet/codeSnippetLanguages.ts +0 -216
  183. package/src/customTemplates/AddBin.tsx +0 -126
  184. package/src/customTemplates/CustomLayoutManager.tsx +0 -234
  185. package/src/customTemplates/CustomTemplateContext.tsx +0 -188
  186. package/src/customTemplates/LayerToolbar.tsx +0 -580
  187. package/src/customTemplates/ShapeGlyph.tsx +0 -47
  188. package/src/customTemplates/TemplateDesigner.tsx +0 -433
  189. package/src/customTemplates/__tests__/library.test.ts +0 -96
  190. package/src/customTemplates/__tests__/normalizePositions.test.ts +0 -109
  191. package/src/customTemplates/__tests__/shapeDefs.test.ts +0 -49
  192. package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +0 -108
  193. package/src/customTemplates/designer.css +0 -732
  194. package/src/customTemplates/index.ts +0 -31
  195. package/src/customTemplates/library.ts +0 -110
  196. package/src/customTemplates/normalizePositions.ts +0 -75
  197. package/src/customTemplates/shapeDefs.ts +0 -131
  198. package/src/customTemplates/thumbnail.tsx +0 -63
  199. package/src/customTemplates/tokenDefs.ts +0 -60
  200. package/src/customTemplates/useDocCustomTemplates.ts +0 -52
  201. package/src/customTemplates/useMemoryLayerAdapter.ts +0 -129
  202. package/src/customThemes/CustomThemeContext.tsx +0 -185
  203. package/src/customThemes/CustomThemeDialog.tsx +0 -316
  204. package/src/customThemes/ImportThemeSection.tsx +0 -178
  205. package/src/customThemes/__tests__/CustomThemeContext.test.tsx +0 -64
  206. package/src/customThemes/__tests__/CustomThemeDialog.test.tsx +0 -47
  207. package/src/customThemes/__tests__/customThemeLibrary.test.ts +0 -60
  208. package/src/customThemes/__tests__/importThemeSection.test.tsx +0 -192
  209. package/src/customThemes/customThemeLibrary.ts +0 -100
  210. package/src/customThemes/index.ts +0 -37
  211. package/src/customThemes/themeControls.tsx +0 -229
  212. package/src/customThemes/themeDraft.ts +0 -288
  213. package/src/customThemes/useDocCustomThemes.ts +0 -49
  214. package/src/detectMarkdown.ts +0 -62
  215. package/src/diagram/DiagramCanvas.tsx +0 -260
  216. package/src/diagram/DiagramMaximizedOverlay.tsx +0 -46
  217. package/src/diagram/diagramCommands.ts +0 -604
  218. package/src/diagram/diagramConstants.ts +0 -17
  219. package/src/diagram/types.ts +0 -35
  220. package/src/editorScrollSync.ts +0 -73
  221. package/src/embeddedMedia.ts +0 -78
  222. package/src/emojiData.ts +0 -1317
  223. package/src/fileKind.ts +0 -158
  224. package/src/find/FindHighlightExtension.ts +0 -81
  225. package/src/find/FindToolbar.tsx +0 -314
  226. package/src/find/findModel.ts +0 -77
  227. package/src/frontmatter.ts +0 -29
  228. package/src/frontmatterSettings.ts +0 -23
  229. package/src/headingTransition.ts +0 -291
  230. package/src/hooks/useFileDrop.ts +0 -266
  231. package/src/imageEditor/CanvasSurface.tsx +0 -703
  232. package/src/imageEditor/ImageVersionHistoryDropdown.tsx +0 -396
  233. package/src/imageEditor/LayersPanel.tsx +0 -218
  234. package/src/imageEditor/PropertiesPanel.tsx +0 -474
  235. package/src/imageEditor/Toolbar.tsx +0 -459
  236. package/src/imageEditor/createShapeLayer.ts +0 -280
  237. package/src/imageEditor/icons.tsx +0 -68
  238. package/src/imageEditor/image-editor.css +0 -574
  239. package/src/imageEditor/layers/EditorImageLayer.tsx +0 -45
  240. package/src/imageEditor/layers/EditorShapeLayer.tsx +0 -62
  241. package/src/imageEditor/layers/EditorTextLayer.tsx +0 -45
  242. package/src/imageEditor/layers/SelectionHandles.tsx +0 -86
  243. package/src/imageEditor/state.ts +0 -173
  244. package/src/imageEditor/useImageEditor.ts +0 -385
  245. package/src/imageEditor/useImageEditorTokens.ts +0 -70
  246. package/src/index.ts +0 -559
  247. package/src/jsonEditor/EmbeddedRichTextField.tsx +0 -81
  248. package/src/jsonEditor/JsonEditor.tsx +0 -81
  249. package/src/jsonEditor/JsonEditorContext.tsx +0 -81
  250. package/src/jsonEditor/RenderNode.tsx +0 -87
  251. package/src/jsonEditor/editors.tsx +0 -745
  252. package/src/jsonEditor/index.ts +0 -2
  253. package/src/jsonEditor/json-editor.css +0 -463
  254. package/src/jsonEditor/useJsonEditorTokens.ts +0 -33
  255. package/src/markdownCodeFence.ts +0 -72
  256. package/src/mediaDragMime.ts +0 -32
  257. package/src/mediaEntries.ts +0 -12
  258. package/src/mediaReferences.ts +0 -301
  259. package/src/mermaid/MermaidDiagramCanvas.tsx +0 -693
  260. package/src/mermaid/MermaidDiagramExtension.ts +0 -243
  261. package/src/mermaid/MermaidDiagramTypeThumbnail.tsx +0 -184
  262. package/src/mermaid/MermaidDiagramWidget.tsx +0 -653
  263. package/src/mermaid/MermaidShapePalette.tsx +0 -245
  264. package/src/mermaid/__tests__/MermaidDiagramExtension.test.ts +0 -361
  265. package/src/mermaid/__tests__/mermaidDiagramTypes.test.ts +0 -35
  266. package/src/mermaid/__tests__/mermaidRenderer.test.ts +0 -49
  267. package/src/mermaid/__tests__/mermaidSourceOps.test.ts +0 -213
  268. package/src/mermaid/__tests__/mermaidSyntax.test.ts +0 -71
  269. package/src/mermaid/mermaidCommands.ts +0 -34
  270. package/src/mermaid/mermaidData.ts +0 -31
  271. package/src/mermaid/mermaidDiagramTypes.ts +0 -325
  272. package/src/mermaid/mermaidModel.ts +0 -31
  273. package/src/mermaid/mermaidRenderer.ts +0 -181
  274. package/src/mermaid/mermaidShapes.ts +0 -113
  275. package/src/mermaid/mermaidSourceOps.ts +0 -454
  276. package/src/monaco.ts +0 -35
  277. package/src/monacoWorkers.ts +0 -89
  278. package/src/outlineSource.ts +0 -171
  279. package/src/presentation/PresentationMode.tsx +0 -596
  280. package/src/rawEditorIsolation.ts +0 -18
  281. package/src/recorder/RecorderButton.tsx +0 -80
  282. package/src/recorder/RecorderModal.tsx +0 -706
  283. package/src/recorder/RecorderPanel.tsx +0 -77
  284. package/src/recorder/formats.ts +0 -159
  285. package/src/recorder/hooks/useMediaRecorder.ts +0 -436
  286. package/src/recorder/hooks/useStreamPreview.ts +0 -47
  287. package/src/recorder/insertMediaBlock.ts +0 -30
  288. package/src/recorder/sources/cameraStream.ts +0 -32
  289. package/src/recorder/sources/micStream.ts +0 -25
  290. package/src/recorder/sources/screenStream.ts +0 -162
  291. package/src/recorder/timingJson.ts +0 -66
  292. package/src/resolveBlockVisual.ts +0 -39
  293. package/src/scene/Scene.tsx +0 -647
  294. package/src/scene/SceneBlockExtension.ts +0 -205
  295. package/src/scene/SceneBlockToolbar.tsx +0 -216
  296. package/src/scene/SceneBlockWidget.tsx +0 -441
  297. package/src/scene/ScenePropsBar.tsx +0 -85
  298. package/src/scene/SceneSelection.tsx +0 -111
  299. package/src/scene/SceneSideToolbar.tsx +0 -89
  300. package/src/scene/SceneViewControls.tsx +0 -43
  301. package/src/scene/SceneViewport.tsx +0 -106
  302. package/src/scene/ShapePalette.tsx +0 -196
  303. package/src/scene/__tests__/DiagramAdapter.test.ts +0 -142
  304. package/src/scene/__tests__/SceneBlockExtension.test.ts +0 -33
  305. package/src/scene/__tests__/bezierEdit.test.ts +0 -85
  306. package/src/scene/__tests__/blockLayers.test.ts +0 -57
  307. package/src/scene/__tests__/fitScale.test.ts +0 -19
  308. package/src/scene/__tests__/sceneIsolation.test.tsx +0 -119
  309. package/src/scene/__tests__/shapeLayers.test.ts +0 -106
  310. package/src/scene/__tests__/useSceneHitTest.test.ts +0 -90
  311. package/src/scene/__tests__/useScenePanZoom.test.ts +0 -130
  312. package/src/scene/adapters/DiagramAdapter.ts +0 -79
  313. package/src/scene/adapters/DrawingAdapter.ts +0 -420
  314. package/src/scene/adapters/LayoutAdapter.ts +0 -313
  315. package/src/scene/adapters/blockLayers.ts +0 -159
  316. package/src/scene/commands/SceneCommand.ts +0 -84
  317. package/src/scene/commands/drawingCommands.ts +0 -318
  318. package/src/scene/commands/layoutCommands.ts +0 -301
  319. package/src/scene/fitScale.ts +0 -17
  320. package/src/scene/hooks/useSceneHitTest.ts +0 -105
  321. package/src/scene/hooks/useScenePanZoom.ts +0 -154
  322. package/src/scene/hooks/useSceneSelection.ts +0 -62
  323. package/src/scene/index.ts +0 -90
  324. package/src/scene/layers/DiagramEdges.tsx +0 -142
  325. package/src/scene/layers/edgeGeometry.ts +0 -104
  326. package/src/scene/layers/nodeCard.tsx +0 -164
  327. package/src/scene/layers/renderLayer.tsx +0 -70
  328. package/src/scene/layers/shapeLayers.ts +0 -201
  329. package/src/scene/paths/bezierEdit.ts +0 -208
  330. package/src/scene/scene.css +0 -874
  331. package/src/scene/text/SceneTextOverlay.tsx +0 -161
  332. package/src/scene/text/sceneTextChannel.ts +0 -46
  333. package/src/scene/text/sceneTextConfig.ts +0 -31
  334. package/src/scene/text/sceneTiptap.ts +0 -36
  335. package/src/scene/text/useSceneTextEditing.ts +0 -39
  336. package/src/scene/tools/ConnectTool.ts +0 -209
  337. package/src/scene/tools/DrawingConnectTool.ts +0 -231
  338. package/src/scene/tools/PathTool.ts +0 -158
  339. package/src/scene/tools/PlaceTool.ts +0 -47
  340. package/src/scene/tools/SceneTool.ts +0 -85
  341. package/src/scene/tools/SelectTool.ts +0 -300
  342. package/src/scene/tools/ShapeTool.ts +0 -143
  343. package/src/scene/tools/TextTool.ts +0 -72
  344. package/src/scene/tools/TokenTool.ts +0 -95
  345. package/src/scene/tools/createDrawShapeTool.ts +0 -82
  346. package/src/selectionConversions.ts +0 -155
  347. package/src/styles/ascii-diagram.css +0 -79
  348. package/src/styles/ascii-timeline.css +0 -596
  349. package/src/styles/code-context.css +0 -155
  350. package/src/styles/code-snippet.css +0 -76
  351. package/src/styles/diagram.css +0 -183
  352. package/src/styles/editor.css +0 -7131
  353. package/src/styles/folder-view.css +0 -210
  354. package/src/styles/image-edit-affordance.css +0 -201
  355. package/src/styles/index.css +0 -20
  356. package/src/styles/mermaid-diagram.css +0 -487
  357. package/src/styles/tree-view.css +0 -189
  358. package/src/teleprompter/TeleprompterControls.tsx +0 -218
  359. package/src/teleprompter/TeleprompterSelfView.tsx +0 -22
  360. package/src/teleprompter/TeleprompterSurface.tsx +0 -267
  361. package/src/teleprompter/TeleprompterView.tsx +0 -338
  362. package/src/teleprompter/canvasRenderer.ts +0 -161
  363. package/src/teleprompter/floatingWindow.ts +0 -352
  364. package/src/teleprompter/index.ts +0 -61
  365. package/src/teleprompter/pcmWorklet.ts +0 -62
  366. package/src/teleprompter/recording/insertPreamble.ts +0 -80
  367. package/src/teleprompter/recording/narrationSave.ts +0 -134
  368. package/src/teleprompter/recording/useNarrationRecorder.ts +0 -367
  369. package/src/teleprompter/scrollModel.ts +0 -85
  370. package/src/teleprompter/teleprompterTheme.ts +0 -303
  371. package/src/teleprompter/types.ts +0 -38
  372. package/src/teleprompter/useFloatingWindow.ts +0 -74
  373. package/src/teleprompter/useMicAnalysis.ts +0 -211
  374. package/src/teleprompter/useTeleprompter.ts +0 -421
  375. package/src/templateContentPreviewResolver.ts +0 -360
  376. package/src/timeline/TimelineEditorWidget.tsx +0 -861
  377. package/src/timeline/TimelineViewExtension.ts +0 -252
  378. package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +0 -414
  379. package/src/timeline/__tests__/TimelineViewExtension.test.ts +0 -163
  380. package/src/timeline/__tests__/timelineCommands.test.ts +0 -359
  381. package/src/timeline/__tests__/timelineOps.test.ts +0 -277
  382. package/src/timeline/__tests__/timelinePaste.test.ts +0 -34
  383. package/src/timeline/timelineCommands.ts +0 -421
  384. package/src/timeline/timelineData.ts +0 -54
  385. package/src/timeline/timelineOps.ts +0 -381
  386. package/src/timeline/timelinePaste.ts +0 -8
  387. package/src/timelineSource.ts +0 -244
  388. package/src/tiptap/TiptapAudio.tsx +0 -86
  389. package/src/tiptap/TiptapVideo.tsx +0 -119
  390. package/src/tiptap/useResolvedMediaSrc.ts +0 -47
  391. package/src/tiptapBridge.ts +0 -986
  392. package/src/tooltipPlacement.ts +0 -13
  393. package/src/transformStyleId.ts +0 -17
  394. package/src/transitionCatalog.ts +0 -159
  395. package/src/treeview/TreeOutlineWidget.tsx +0 -390
  396. package/src/treeview/TreeViewExtension.ts +0 -250
  397. package/src/treeview/__tests__/TreeOutlineWidget.test.tsx +0 -156
  398. package/src/treeview/__tests__/TreeViewExtension.test.ts +0 -122
  399. package/src/treeview/__tests__/treeOps.test.ts +0 -177
  400. package/src/treeview/__tests__/treePaste.test.ts +0 -40
  401. package/src/treeview/__tests__/treeViewCommands.test.ts +0 -179
  402. package/src/treeview/treeOps.ts +0 -231
  403. package/src/treeview/treePaste.ts +0 -13
  404. package/src/treeview/treeViewCommands.ts +0 -99
  405. package/src/treeview/treeViewData.ts +0 -41
  406. package/src/types/monaco-shims.d.ts +0 -10
  407. package/src/useBlockNavigator.ts +0 -153
  408. package/src/useHeadingLayout.ts +0 -294
  409. package/src/useMonacoLoader.ts +0 -82
  410. package/src/useTimelineClock.ts +0 -76
  411. package/src/utils/collectInlineFontAwesomeCss.ts +0 -69
  412. package/src/utils/dropUtils.ts +0 -136
  413. package/src/utils/normalizeMalformedAssetUrl.ts +0 -22
  414. package/src/writeCanvasSettings.ts +0 -30
  415. package/src/wysiwygImageUpload.ts +0 -113
@@ -1,1274 +0,0 @@
1
- /**
2
- * EditorContext
3
- *
4
- * Shared React context that synchronizes state across all three editor views
5
- * (Raw/Monaco, WYSIWYG/Tiptap, Preview/DocPlayer). When any view modifies the
6
- * markdown source, the context re-parses and regenerates the MarkdownDocument
7
- * and Doc so all views stay in sync.
8
- */
9
-
10
- import {
11
- createContext,
12
- useContext,
13
- useState,
14
- useCallback,
15
- useMemo,
16
- useRef,
17
- useEffect,
18
- type ReactNode,
19
- } from 'react';
20
- import type { Doc, MediaProvider } from '@bendyline/squisq/schemas';
21
- import type { MarkdownDocument } from '@bendyline/squisq/markdown';
22
- import { parseMarkdown, stringifyMarkdown } from '@bendyline/squisq/markdown';
23
- import { markdownToDoc } from '@bendyline/squisq/doc';
24
- import type { ContentContainer } from '@bendyline/squisq/storage';
25
- import {
26
- DocumentVersionManager,
27
- type PrunePolicy,
28
- type SaveVersionOptions,
29
- type SaveVersionResult,
30
- } from '@bendyline/squisq/versions';
31
- import type { Editor as TiptapEditor } from '@tiptap/core';
32
- import type { editor as MonacoEditorNs } from 'monaco-editor';
33
- import { markdownToTiptap } from './tiptapBridge';
34
- import { resolveFileKind } from './fileKind';
35
- import { useBlockNavigator } from './useBlockNavigator';
36
- import {
37
- createSceneTextChannel,
38
- type SceneTextChannel,
39
- type SceneTextHandle,
40
- } from './scene/text/sceneTextChannel';
41
- import {
42
- readMonacoScrollRatio,
43
- readWysiwygScrollRatio,
44
- restoreMonacoScrollRatio,
45
- restoreWysiwygScrollRatio,
46
- } from './editorScrollSync';
47
-
48
- /** Monaco standalone code editor instance type */
49
- type MonacoEditor = MonacoEditorNs.IStandaloneCodeEditor;
50
-
51
- /**
52
- * One candidate returned by a {@link MentionProvider}. Shown in the editor's
53
- * `@` popover. `id` is the stable identifier (serialized into the mention
54
- * wire format); `label` is what the reader sees; `scheme` is the namespace
55
- * (e.g. `'user'`, `'issue'`) written into the markdown as `@[label](scheme:id)`;
56
- * `description` and `group` are optional hints for richer suggestion UIs.
57
- *
58
- * Different candidates in the same result set may carry different schemes —
59
- * a provider that returns both users and issues, for example, tags each
60
- * candidate with its own namespace and the editor emits mentions accordingly.
61
- */
62
- export interface MentionCandidate {
63
- id: string;
64
- label: string;
65
- scheme: string;
66
- description?: string;
67
- group?: string;
68
- }
69
-
70
- /**
71
- * Looks up mention candidates matching a query. Called as the user types
72
- * after `@`. The provider is free to do server-side or client-side filtering;
73
- * the editor only cares that candidates come back in relevance order.
74
- */
75
- export type MentionProvider = (query: string) => Promise<MentionCandidate[]>;
76
-
77
- /**
78
- * A document that the link dialog's "Browse documents" picker can offer.
79
- * `path` is what lands in the markdown URL (typically relative to the
80
- * current document so `home.md → resume.md` round-trips through file-
81
- * system serializers). `label` is the human name shown in the list.
82
- * `description` is an optional secondary line (e.g. workspace folder,
83
- * last-modified date).
84
- */
85
- export interface DocumentLinkCandidate {
86
- path: string;
87
- label: string;
88
- description?: string;
89
- }
90
-
91
- /**
92
- * Resolves sibling / workspace document candidates for the link dialog.
93
- * The editor itself has no notion of "neighbors" — hosts that organize
94
- * docs in a workspace (e.g. docblocks) implement this to power the
95
- * dialog's document picker. Pass `''` as the query for an initial list
96
- * (the dialog calls it once on open); subsequent calls narrow by user
97
- * input.
98
- */
99
- export type DocumentLinkProvider = (query: string) => Promise<DocumentLinkCandidate[]>;
100
-
101
- // ─── Types ───────────────────────────────────────────────
102
-
103
- export type EditorView = 'raw' | 'wysiwyg' | 'preview';
104
- /**
105
- * Light/dark chrome mode for the editor shell (toolbar, tabs, status bar,
106
- * side panes). This is the editor's *UI color scheme* — distinct from a
107
- * Squisq `Theme` object, which styles the rendered document. Renamed from
108
- * the former `EditorTheme` to remove that ambiguity.
109
- */
110
- export type EditorColorScheme = 'light' | 'dark';
111
- /**
112
- * Document layout mode. `'document'` shows the whole markdown document in
113
- * the active view (the historical behavior). `'block'` is the
114
- * block-at-a-time view — one heading-defined block on a card at a time,
115
- * with the editor scoped to just that block. `'timeline'` is block mode plus
116
- * a horizontal timeline track for editing block durations and media slices.
117
- * See {@link useBlockNavigator}.
118
- */
119
- export type LayoutMode = 'document' | 'block' | 'timeline';
120
- /**
121
- * How much of the active Squisq theme the WYSIWYG editing surface
122
- * mirrors. `'fonts'` is the historical default — body and heading
123
- * fonts only. `'fonts-colors'` also borrows the theme canvas / text
124
- * colors. `'none'` opts out completely.
125
- */
126
- export type ThemeInheritance = 'none' | 'fonts' | 'fonts-colors';
127
- /**
128
- * When inline block-template tags are shown in the WYSIWYG surface.
129
- * `'active'` shows tags for the cursor's block and the block under the pointer.
130
- */
131
- export type BlockTagVisibility = 'none' | 'active' | 'always';
132
- /**
133
- * Editor operating mode. `markdown` is the full experience (WYSIWYG +
134
- * Preview tabs, formatting toolbar). `code` is a Monaco-only view used
135
- * when the content represents a non-markdown file like `foo.ts`.
136
- */
137
- export type EditorMode = 'markdown' | 'code' | 'image';
138
-
139
- export interface EditorState {
140
- /** Raw markdown source string */
141
- markdownSource: string;
142
- /** Parsed markdown document (JSON DOM) */
143
- markdownDoc: MarkdownDocument | null;
144
- /** Generated Doc (block hierarchy) */
145
- doc: Doc | null;
146
- /** Currently active editor view */
147
- activeView: EditorView;
148
- /** Parse error, if any */
149
- parseError: string | null;
150
- /** Whether a parse is pending */
151
- isParsing: boolean;
152
- /** Current light/dark chrome color scheme for the editor shell. */
153
- colorScheme: EditorColorScheme;
154
- /** Operating mode — 'markdown' for the full shell, 'code' for Monaco-only. */
155
- editorMode: EditorMode;
156
- /** Whether the host-triggered Find toolbar is active. */
157
- findMode: boolean;
158
- /** Monaco language ID for the Raw editor. */
159
- language: string;
160
- /**
161
- * Whether the inline preview gutter (per-block card previews next to the
162
- * WYSIWYG surface) is currently visible. Initialized from the EditorShell
163
- * `inlinePreview` prop; the View menu in the toolbar can toggle it at
164
- * runtime.
165
- */
166
- inlinePreviewVisible: boolean;
167
- /**
168
- * Whether the bottom status bar is currently visible. Initialized from
169
- * the EditorShell `showStatusBar` prop (default true); the View menu in
170
- * the toolbar can toggle it at runtime.
171
- */
172
- statusBarVisible: boolean;
173
- /**
174
- * Whether the left-side outline pane is currently visible. Initialized
175
- * from the EditorShell `outline` prop (default false); the View menu in
176
- * the toolbar can toggle it at runtime.
177
- */
178
- outlineVisible: boolean;
179
- /** When inline block-template tags are shown in the WYSIWYG view. */
180
- blockTagVisibility: BlockTagVisibility;
181
- /**
182
- * Whether inline block-template tags can currently be visible.
183
- * Kept for compatibility; prefer {@link blockTagVisibility}.
184
- */
185
- blockTagsVisible: boolean;
186
- /**
187
- * How much of the active Squisq theme the WYSIWYG editing surface should
188
- * inherit. `'none'` shows the default editor styling, `'fonts'` (the
189
- * default) matches body and heading fonts only, and `'fonts-colors'`
190
- * also borrows the theme's canvas / text colors so authors get a
191
- * closer preview while editing.
192
- */
193
- themeInheritance: ThemeInheritance;
194
- /**
195
- * Relative path of an image the user requested to edit, or `null` when
196
- * no editor is open. Surfaced by `<ImageNodeView>`'s hover affordance
197
- * and consumed by `<EditorShell>` to render the modal `<ImageEditor>`.
198
- */
199
- imageEditTarget: string | null;
200
- /**
201
- * Monotonic counter bumped whenever a managed media asset is rewritten
202
- * (e.g. after the image-editor modal saves back). Image render paths
203
- * that cache resolved blob URLs should include this in their effect
204
- * deps so the new bytes get picked up.
205
- */
206
- mediaRevision: number;
207
- /**
208
- * Whether the in-editor media recorder should be available. Defaults
209
- * to true when a `mediaProvider` is wired; hosts that explicitly
210
- * don't want the affordance (e.g. read-only embeds, surfaces where
211
- * camera/screen prompts would be jarring) can pass `false` on the
212
- * shell.
213
- */
214
- allowRecording: boolean;
215
- /**
216
- * Whether the Narrate (teleprompter) display mode is offered under the
217
- * Use tab. Orthogonal to `allowRecording`: the teleprompter is useful
218
- * without any capture (reading for external recording software), and a
219
- * host may allow the recorder modal but not want a prompter surface.
220
- */
221
- allowNarrate: boolean;
222
- /**
223
- * Document layout mode. `'document'` (default) edits the whole document;
224
- * `'block'` activates the block-at-a-time card view. Initialized from the
225
- * EditorShell `layoutMode` prop; the View menu can toggle it at runtime.
226
- */
227
- layoutMode: LayoutMode;
228
- /**
229
- * The markdown the active text editor should bind to: the full source in
230
- * `'document'` mode, or just the active block's slice in `'block'` mode.
231
- * Editors read this instead of `markdownSource` so the same surfaces work
232
- * in both layouts.
233
- */
234
- editorSource: string;
235
- /** Number of navigable blocks (cards) in the current document. */
236
- blockCount: number;
237
- /** Index of the block currently shown on the card (block mode). */
238
- activeBlockKey: number;
239
- /** 1-based source line where the active block begins, or null. */
240
- activeBlockStartLine: number | null;
241
- }
242
-
243
- export interface EditorActions {
244
- /** Set markdown source and trigger re-parse */
245
- setMarkdownSource: (source: string) => void;
246
- /**
247
- * Write through the active editor channel. In `'document'` mode this is
248
- * `setMarkdownSource`; in `'block'` mode it splices the edited block back
249
- * into the full document. Editors call this instead of `setMarkdownSource`.
250
- */
251
- setEditorSource: (source: string) => void;
252
- /** Switch between Document and Block-at-a-time layouts. */
253
- setLayoutMode: (mode: LayoutMode) => void;
254
- /** Show a block by index in block mode (clamped to range). */
255
- goToBlock: (key: number) => void;
256
- /** Show the block that owns a given 1-based source line (used by the outline). */
257
- goToBlockByLine: (line: number) => void;
258
- /** Move the card to the previous block. */
259
- prevBlock: () => void;
260
- /** Move the card to the next block. */
261
- nextBlock: () => void;
262
- /** Insert a new heading block after the active one and move to it. */
263
- addBlock: () => void;
264
- /** Set markdown from a MarkdownDocument (e.g. from WYSIWYG) */
265
- setMarkdownDoc: (doc: MarkdownDocument) => void;
266
- /** Switch the active view */
267
- setActiveView: (view: EditorView) => void;
268
- /** Enter or leave the host-triggered Find toolbar mode. */
269
- setFindMode: (active: boolean) => void;
270
- /** Register / unregister the Tiptap editor instance (called by WysiwygEditor) */
271
- setTiptapEditor: (editor: TiptapEditor | null) => void;
272
- /** Register / unregister the Monaco editor instance (called by RawEditor) */
273
- setMonacoEditor: (editor: MonacoEditor | null) => void;
274
- /** Set the light/dark chrome color scheme for the editor shell. */
275
- setColorScheme: (colorScheme: EditorColorScheme) => void;
276
- /** Show or hide the inline preview gutter at runtime (driven by the View menu). */
277
- setInlinePreviewVisible: (visible: boolean) => void;
278
- /** Show or hide the bottom status bar at runtime (driven by the View menu). */
279
- setStatusBarVisible: (visible: boolean) => void;
280
- /** Show or hide the left-side outline pane at runtime (driven by the View menu). */
281
- setOutlineVisible: (visible: boolean) => void;
282
- /** Show or hide inline block-template tags at runtime (driven by the View menu). */
283
- setBlockTagsVisible: (visible: boolean) => void;
284
- /** Choose when inline block-template tags are shown. */
285
- setBlockTagVisibility: (visibility: BlockTagVisibility) => void;
286
- /** Change how much of the active Squisq theme the WYSIWYG surface mirrors. */
287
- setThemeInheritance: (mode: ThemeInheritance) => void;
288
- /** Insert text at the current cursor position in the active editor */
289
- insertAtCursor: (text: string) => void;
290
- /** Replace all editor content with the given text */
291
- replaceAll: (text: string) => void;
292
- /**
293
- * Request the modal image editor open on the given relative media path.
294
- * The path must resolve through the active `mediaProvider`. No-op when
295
- * no provider is wired — callers should hide the affordance in that
296
- * case.
297
- */
298
- openImageEdit: (relativePath: string) => void;
299
- /** Close the image editor modal without saving. */
300
- closeImageEdit: () => void;
301
- /**
302
- * Bump `mediaRevision`. Called after the image editor writes back to
303
- * the original media path so dependent `<img>` nodes re-resolve their
304
- * blob URL.
305
- */
306
- bumpMediaRevision: () => void;
307
- }
308
-
309
- export interface EditorContextValue extends EditorState, EditorActions {
310
- /** The live Tiptap editor instance (null when WYSIWYG is not mounted) */
311
- tiptapEditor: TiptapEditor | null;
312
- /** The live Monaco editor instance (null when Raw is not mounted) */
313
- monacoEditor: MonacoEditor | null;
314
- /**
315
- * The focused canvas textbox editor, if any — a small Tiptap instance for
316
- * a diagram/drawing/layout textbox being edited inline. Published via
317
- * `sceneTextChannel` (the canvas renders in a detached React root). The
318
- * top formatting toolbar retargets to this when set. `level` gates which
319
- * buttons apply (`inline` = marks only; `rich` = headings/lists too).
320
- */
321
- activeSceneText: SceneTextHandle | null;
322
- /** Instance-owned bridge used by detached scene widget roots. */
323
- sceneTextChannel: SceneTextChannel;
324
- /**
325
- * Workspace-scoped `ContentContainer` for this document — the folder
326
- * holding the doc, its `_files/` sidecar, sibling documents, and any
327
- * version snapshots. Drives audio mapping, version history, and
328
- * sibling-doc reads for the recursive HTML export.
329
- */
330
- workspaceContainer: ContentContainer | null;
331
- /**
332
- * Version manager — non-null only when the host opted into versioning
333
- * (`allowVersioning` + a `workspaceContainer`). Components can call
334
- * `saveVersion` directly, or render the version-history panel which
335
- * reads it from here.
336
- */
337
- versioning: DocumentVersionManager | null;
338
- /**
339
- * Stamp a new snapshot of the current document. No-op (returns
340
- * `unchanged`) when content matches the latest version. Always safe
341
- * to call — when versioning is disabled, returns `no-document`
342
- * without writing.
343
- */
344
- saveVersion: (options?: SaveVersionOptions) => Promise<SaveVersionResult>;
345
- /** MediaProvider for resolving image URLs in the WYSIWYG editor */
346
- mediaProvider: MediaProvider | null;
347
- /**
348
- * How pasted/inserted images should be displayed in the WYSIWYG view.
349
- * `'inline'` (default) lets them flow at natural size up to the editor
350
- * width; `'thumbnail'` constrains them to a 100×100 box so chat
351
- * composers and other dense surfaces don't get dominated by a single
352
- * pasted screenshot. The stored image bytes are unchanged — this is a
353
- * pure render-time decision.
354
- */
355
- imageDisplayMode: ImageDisplayMode;
356
- /**
357
- * Optional provider for `@`-mention suggestions. When set, both the
358
- * WYSIWYG (Tiptap) and Raw (Monaco) editors show a mention popover as
359
- * the user types `@<query>`. When unset, `@` is just a literal character.
360
- */
361
- mentionProvider: MentionProvider | null;
362
- /**
363
- * Optional provider for sibling-document suggestions in the link
364
- * dialog. When set, the dialog shows a "Browse documents" picker that
365
- * lets authors search neighbor docs by name and insert a relative-
366
- * path link. When unset, the dialog falls back to URL-only.
367
- */
368
- documentLinkProvider: DocumentLinkProvider | null;
369
- }
370
-
371
- export type ImageDisplayMode = 'inline' | 'thumbnail';
372
-
373
- // ─── Context ─────────────────────────────────────────────
374
-
375
- const EditorContext = createContext<EditorContextValue | null>(null);
376
-
377
- /**
378
- * Hook to access the editor context. Must be used within an EditorProvider.
379
- */
380
- // eslint-disable-next-line react-refresh/only-export-components
381
- export function useEditorContext(): EditorContextValue {
382
- const ctx = useContext(EditorContext);
383
- if (!ctx) {
384
- throw new Error('useEditorContext must be used within an <EditorProvider>');
385
- }
386
- return ctx;
387
- }
388
-
389
- // ─── Provider ────────────────────────────────────────────
390
-
391
- export interface EditorProviderProps {
392
- /** Initial markdown content */
393
- initialMarkdown?: string;
394
- /** Initial active view */
395
- initialView?: EditorView;
396
- /** Article ID used when generating the Doc */
397
- articleId?: string;
398
- /** Light/dark chrome color scheme for the editor shell. */
399
- colorScheme?: EditorColorScheme;
400
- /**
401
- * Workspace-scoped `ContentContainer` for this document — the folder
402
- * holding the doc, its `_files/` sidecar, sibling documents, and any
403
- * version snapshots. Required for `allowVersioning` to take effect.
404
- */
405
- workspaceContainer?: ContentContainer | null;
406
- /**
407
- * Enable version history. Snapshots are stored at
408
- * `.versions/<basename>.<timestamp>.md` inside `workspaceContainer`.
409
- * Auto-save fires after `versioningAutoSaveIdleMs` of idle; hosts can
410
- * also call `saveVersion()` from the context. Without a
411
- * `workspaceContainer`, this prop is ignored (and a `console.warn` is
412
- * emitted).
413
- */
414
- allowVersioning?: boolean;
415
- /** Override the basename used in version filenames. Defaults to the
416
- * basename of the container's primary document path. */
417
- versionBasename?: string;
418
- /**
419
- * Prune policy applied after each successful auto-save. Defaults to
420
- * keeping the last 50 snapshots so the count doesn't grow unbounded.
421
- */
422
- versioningPrunePolicy?: PrunePolicy;
423
- /**
424
- * Idle delay (ms) before auto-saving a version. `0` disables auto-save
425
- * entirely (versions are saved only via host-driven `saveVersion`
426
- * calls). Default: 5000.
427
- */
428
- versioningAutoSaveIdleMs?: number;
429
- /**
430
- * Notified after each `saveVersion` attempt — both successful saves
431
- * (`reason: 'saved'`) and skips (`'unchanged'`, `'no-document'`,
432
- * `'empty'`). Useful for hosts that want a "Last saved" indicator.
433
- */
434
- onSaveVersion?: (result: SaveVersionResult) => void;
435
- /** MediaProvider for resolving image URLs */
436
- mediaProvider?: MediaProvider | null;
437
- /** Display mode for images in the WYSIWYG view. Defaults to `'inline'`. */
438
- imageDisplayMode?: ImageDisplayMode;
439
- /**
440
- * Async provider for `@`-mention suggestions. Omit to disable mentions
441
- * entirely — typing `@` becomes just a literal character again.
442
- */
443
- mentionProvider?: MentionProvider | null;
444
- /**
445
- * Async provider for sibling-document suggestions in the link dialog.
446
- * Omit to fall back to URL-only link insertion.
447
- */
448
- documentLinkProvider?: DocumentLinkProvider | null;
449
- /**
450
- * Whether the in-editor media recorder is available in the toolbar.
451
- * Defaults to true. Set to false to suppress the recorder affordance
452
- * even when a `mediaProvider` is wired (e.g. read-only embeds,
453
- * surfaces where camera/screen prompts would be jarring).
454
- */
455
- allowRecording?: boolean;
456
- /**
457
- * Whether the Narrate (teleprompter) display mode is offered under the
458
- * Use tab. Defaults to true. When false the mode button is hidden and a
459
- * frontmatter-forced `display-mode: narrate` clamps back to video.
460
- */
461
- allowNarrate?: boolean;
462
- /**
463
- * File name (e.g. `foo.ts`) or bare extension — used to pick a Monaco
464
- * language and decide between markdown vs. code mode.
465
- */
466
- fileName?: string;
467
- /** Explicit Monaco language ID — wins over the fileName-derived one. */
468
- language?: string;
469
- /**
470
- * Controlled Find-mode state. No trigger button is rendered by default;
471
- * hosts can set this prop or call `setFindMode` from editor context.
472
- */
473
- findMode?: boolean;
474
- /** Notified when Find mode requests a state change (for example, its X button). */
475
- onFindModeChange?: (active: boolean) => void;
476
- /**
477
- * Initial visibility of the inline preview gutter. Defaults to false.
478
- * The toolbar's View menu can toggle it at runtime.
479
- */
480
- inlinePreview?: boolean;
481
- /**
482
- * Initial visibility of the bottom status bar. Defaults to true.
483
- * The toolbar's View menu can toggle it at runtime.
484
- */
485
- showStatusBar?: boolean;
486
- /**
487
- * Initial visibility of the left-side outline pane. Defaults to false.
488
- * The toolbar's View menu can toggle it at runtime.
489
- */
490
- outline?: boolean;
491
- /**
492
- * Legacy initial visibility of inline block-template tags on headings.
493
- * `true` maps to always visible and `false` maps to hidden. When omitted,
494
- * {@link blockTagVisibility} defaults to `'active'`.
495
- */
496
- blockTags?: boolean;
497
- /**
498
- * Initial block-tag visibility mode. When set, this takes precedence over
499
- * the legacy boolean {@link blockTags} prop. Defaults to `'active'`.
500
- */
501
- blockTagVisibility?: BlockTagVisibility;
502
- /**
503
- * Initial value for how much of the active Squisq theme the WYSIWYG
504
- * editing surface should mirror. Defaults to `'fonts'` — the
505
- * historical behavior of inheriting body / heading fonts only. The
506
- * toolbar's View menu can change it at runtime.
507
- */
508
- themeInheritance?: ThemeInheritance;
509
- /**
510
- * Initial layout mode. Defaults to `'document'` (whole-document editing).
511
- * `'block'` boots into the block-at-a-time card view. The toolbar's View
512
- * menu can toggle it at runtime.
513
- */
514
- layoutMode?: LayoutMode;
515
- /**
516
- * Bundled view preferences — a serializable JSON blob covering all
517
- * runtime-toggleable view options. When provided, individual values
518
- * here override the matching individual props (`inlinePreview`,
519
- * `showStatusBar`, `outline`, `blockTagVisibility`, `blockTags`). Hosts
520
- * wiring this up typically load the blob from their own preferences storage
521
- * and pair it with {@link onViewPreferencesChange}.
522
- */
523
- viewPreferences?: ViewPreferences;
524
- /**
525
- * Notified after each user-driven toggle in the View menu (or any
526
- * programmatic call to the corresponding context setters). The
527
- * argument is a full snapshot — hosts can persist it as-is.
528
- * Not called when {@link viewPreferences} is changed externally.
529
- */
530
- onViewPreferencesChange?: (prefs: ViewPreferences) => void;
531
- children: ReactNode;
532
- }
533
-
534
- /**
535
- * Serializable bundle of all runtime-toggleable view preferences for
536
- * the editor shell. Hosts can persist this verbatim (e.g. to
537
- * localStorage) and pass it back via {@link EditorProviderProps.viewPreferences}
538
- * to restore the user's last view configuration.
539
- */
540
- export interface ViewPreferences {
541
- /** Whether the left-side outline pane is visible. */
542
- outline?: boolean;
543
- /** Whether the inline preview gutter (per-block cards) is visible. */
544
- inlinePreview?: boolean;
545
- /** Whether the bottom status bar is visible. */
546
- showStatusBar?: boolean;
547
- /** Whether inline block-template tags on headings are visible. */
548
- blockTags?: boolean;
549
- /** When inline block-template tags are shown. Takes precedence over `blockTags`. */
550
- blockTagVisibility?: BlockTagVisibility;
551
- /** How much of the active Squisq theme the WYSIWYG surface mirrors. */
552
- themeInheritance?: ThemeInheritance;
553
- /** Document vs. block-at-a-time layout. */
554
- layoutMode?: LayoutMode;
555
- }
556
-
557
- /**
558
- * Provides shared editor state to all child components.
559
- * Automatically parses markdown and generates a Doc whenever the source changes.
560
- */
561
- const DEFAULT_PRUNE_POLICY: PrunePolicy = { type: 'keep-last-n', n: 50 };
562
- const DEFAULT_AUTOSAVE_IDLE_MS = 5_000;
563
-
564
- function normalizeBlockTagVisibility(
565
- value: BlockTagVisibility | boolean | undefined,
566
- ): BlockTagVisibility {
567
- if (value === false || value === 'none') return 'none';
568
- if (value === undefined || value === 'active') return 'active';
569
- return 'always';
570
- }
571
-
572
- export function EditorProvider({
573
- initialMarkdown = '',
574
- initialView = 'raw',
575
- articleId = 'untitled',
576
- colorScheme: initialColorScheme = 'light',
577
- workspaceContainer = null,
578
- allowVersioning = false,
579
- versionBasename,
580
- versioningPrunePolicy = DEFAULT_PRUNE_POLICY,
581
- versioningAutoSaveIdleMs = DEFAULT_AUTOSAVE_IDLE_MS,
582
- onSaveVersion,
583
- mediaProvider = null,
584
- imageDisplayMode = 'inline',
585
- mentionProvider = null,
586
- documentLinkProvider = null,
587
- allowRecording = true,
588
- allowNarrate = true,
589
- fileName,
590
- language,
591
- findMode: controlledFindMode,
592
- onFindModeChange,
593
- inlinePreview = false,
594
- showStatusBar = true,
595
- outline = false,
596
- blockTags,
597
- blockTagVisibility: initialBlockTagVisibility,
598
- themeInheritance = 'fonts',
599
- layoutMode = 'document',
600
- viewPreferences,
601
- onViewPreferencesChange,
602
- children,
603
- }: EditorProviderProps) {
604
- // Resolve effective initial values: bundled `viewPreferences` wins over
605
- // individual props when both are passed. Individual props remain valid
606
- // for hosts that haven't migrated to the bundled API.
607
- const effectiveInlinePreview = viewPreferences?.inlinePreview ?? inlinePreview;
608
- const effectiveShowStatusBar = viewPreferences?.showStatusBar ?? showStatusBar;
609
- const effectiveOutline = viewPreferences?.outline ?? outline;
610
- const effectiveBlockTagVisibility = normalizeBlockTagVisibility(
611
- viewPreferences?.blockTagVisibility ??
612
- viewPreferences?.blockTags ??
613
- initialBlockTagVisibility ??
614
- blockTags,
615
- );
616
- const effectiveThemeInheritance = viewPreferences?.themeInheritance ?? themeInheritance;
617
- const effectiveLayoutMode = viewPreferences?.layoutMode ?? layoutMode;
618
- // Resolve once per provider mount. Changing fileName/language after mount
619
- // would require recreating the Monaco model anyway, so treat it as static.
620
- const { mode: editorMode, language: resolvedLanguage } = useMemo(
621
- () => resolveFileKind(fileName, language),
622
- [fileName, language],
623
- );
624
- // In code mode, WYSIWYG and Preview aren't rendered — force the starting
625
- // view to 'raw' so we don't boot into an unmounted surface. Image mode
626
- // has no text-editing surface at all; keep the same fallback so that any
627
- // host that switches into image mode doesn't end up in a stale view id.
628
- const [markdownSource, setMarkdownSourceRaw] = useState(initialMarkdown);
629
- const [markdownDoc, setMarkdownDocState] = useState<MarkdownDocument | null>(null);
630
- const [doc, setDoc] = useState<Doc | null>(null);
631
- const [activeView, setActiveViewRaw] = useState<EditorView>(
632
- editorMode === 'markdown' ? initialView : 'raw',
633
- );
634
- const [uncontrolledFindMode, setUncontrolledFindMode] = useState(false);
635
- const findMode = editorMode !== 'image' && (controlledFindMode ?? uncontrolledFindMode);
636
- const setFindMode = useCallback(
637
- (active: boolean) => {
638
- if (editorMode === 'image') return;
639
- if (controlledFindMode === undefined) setUncontrolledFindMode(active);
640
- onFindModeChange?.(active);
641
- },
642
- [controlledFindMode, editorMode, onFindModeChange],
643
- );
644
- const activeViewRef = useRef(activeView);
645
- activeViewRef.current = activeView;
646
- const tiptapEditorRef = useRef<TiptapEditor | null>(null);
647
- const monacoEditorRef = useRef<MonacoEditor | null>(null);
648
- const pendingEditorScrollRef = useRef<{ view: 'raw' | 'wysiwyg'; ratio: number } | null>(null);
649
- const setActiveView = useCallback(
650
- (view: EditorView) => {
651
- // In code mode only the raw view is valid. In image mode no text view
652
- // is valid at all — ignore any switch attempt.
653
- if (editorMode === 'code' && view !== 'raw') return;
654
- if (editorMode === 'image') return;
655
-
656
- const currentView = activeViewRef.current;
657
- if (view === currentView) return;
658
-
659
- // Write and Source are separate editor instances and are unmounted as
660
- // their tabs are hidden. Capture a proportional document position
661
- // before the current instance disappears, then restore it after the
662
- // destination has mounted and completed layout.
663
- let ratio: number | null = null;
664
- let targetView: 'raw' | 'wysiwyg' | null = null;
665
- if (currentView === 'raw' && view === 'wysiwyg' && monacoEditorRef.current) {
666
- ratio = readMonacoScrollRatio(monacoEditorRef.current);
667
- targetView = 'wysiwyg';
668
- } else if (currentView === 'wysiwyg' && view === 'raw' && tiptapEditorRef.current) {
669
- ratio = readWysiwygScrollRatio(tiptapEditorRef.current);
670
- targetView = 'raw';
671
- }
672
- pendingEditorScrollRef.current =
673
- ratio === null || targetView === null ? null : { view: targetView, ratio };
674
- setActiveViewRaw(view);
675
- },
676
- [editorMode],
677
- );
678
- const [parseError, setParseError] = useState<string | null>(null);
679
- const [isParsing, setIsParsing] = useState(false);
680
- const [colorScheme, setColorScheme] = useState<EditorColorScheme>(initialColorScheme);
681
- const [inlinePreviewVisible, setInlinePreviewVisibleRaw] =
682
- useState<boolean>(effectiveInlinePreview);
683
- // Sync visibility when the host changes the prop (e.g., toggle from outside).
684
- useEffect(() => {
685
- if (viewPreferences?.inlinePreview === undefined) setInlinePreviewVisibleRaw(inlinePreview);
686
- }, [inlinePreview, viewPreferences?.inlinePreview]);
687
- const [statusBarVisible, setStatusBarVisibleRaw] = useState<boolean>(effectiveShowStatusBar);
688
- useEffect(() => {
689
- if (viewPreferences?.showStatusBar === undefined) setStatusBarVisibleRaw(showStatusBar);
690
- }, [showStatusBar, viewPreferences?.showStatusBar]);
691
- const [outlineVisible, setOutlineVisibleRaw] = useState<boolean>(effectiveOutline);
692
- useEffect(() => {
693
- if (viewPreferences?.outline === undefined) setOutlineVisibleRaw(outline);
694
- }, [outline, viewPreferences?.outline]);
695
- const [blockTagVisibilityState, setBlockTagVisibilityRaw] = useState<BlockTagVisibility>(
696
- effectiveBlockTagVisibility,
697
- );
698
- const blockTagsVisible = blockTagVisibilityState !== 'none';
699
- useEffect(() => {
700
- if (
701
- viewPreferences?.blockTagVisibility === undefined &&
702
- viewPreferences?.blockTags === undefined
703
- ) {
704
- setBlockTagVisibilityRaw(normalizeBlockTagVisibility(initialBlockTagVisibility ?? blockTags));
705
- }
706
- }, [blockTags, initialBlockTagVisibility, viewPreferences]);
707
- const [themeInheritanceState, setThemeInheritanceRaw] =
708
- useState<ThemeInheritance>(effectiveThemeInheritance);
709
- useEffect(() => {
710
- if (viewPreferences?.themeInheritance === undefined) {
711
- setThemeInheritanceRaw(themeInheritance);
712
- }
713
- }, [themeInheritance, viewPreferences?.themeInheritance]);
714
- const [layoutModeState, setLayoutModeRaw] = useState<LayoutMode>(effectiveLayoutMode);
715
- useEffect(() => {
716
- if (viewPreferences?.layoutMode === undefined) setLayoutModeRaw(layoutMode);
717
- }, [layoutMode, viewPreferences?.layoutMode]);
718
- const [imageEditTarget, setImageEditTarget] = useState<string | null>(null);
719
- const [mediaRevision, setMediaRevision] = useState(0);
720
- const openImageEdit = useCallback((relativePath: string) => {
721
- setImageEditTarget(relativePath);
722
- }, []);
723
- const closeImageEdit = useCallback(() => {
724
- setImageEditTarget(null);
725
- }, []);
726
- const bumpMediaRevision = useCallback(() => {
727
- setMediaRevision((n) => n + 1);
728
- }, []);
729
-
730
- // Sync from the bundled `viewPreferences` prop. Runs in addition to the
731
- // individual prop syncs above. When both APIs are present, the bundled
732
- // values are applied here last, keeping `viewPreferences` authoritative.
733
- useEffect(() => {
734
- if (!viewPreferences) return;
735
- if (viewPreferences.inlinePreview !== undefined) {
736
- setInlinePreviewVisibleRaw(viewPreferences.inlinePreview);
737
- }
738
- if (viewPreferences.showStatusBar !== undefined) {
739
- setStatusBarVisibleRaw(viewPreferences.showStatusBar);
740
- }
741
- if (viewPreferences.outline !== undefined) {
742
- setOutlineVisibleRaw(viewPreferences.outline);
743
- }
744
- if (viewPreferences.blockTagVisibility !== undefined) {
745
- setBlockTagVisibilityRaw(normalizeBlockTagVisibility(viewPreferences.blockTagVisibility));
746
- } else if (viewPreferences.blockTags !== undefined) {
747
- setBlockTagVisibilityRaw(normalizeBlockTagVisibility(viewPreferences.blockTags));
748
- }
749
- if (viewPreferences.themeInheritance !== undefined) {
750
- setThemeInheritanceRaw(viewPreferences.themeInheritance);
751
- }
752
- if (viewPreferences.layoutMode !== undefined) {
753
- setLayoutModeRaw(viewPreferences.layoutMode);
754
- }
755
- }, [viewPreferences]);
756
-
757
- // Wrap the view setters so user-driven changes emit a snapshot via
758
- // `onViewPreferencesChange`. Refs hold the latest values + callback so
759
- // each wrapper can build a current snapshot without re-creating itself
760
- // on every state change (the setters are kept referentially stable for
761
- // the context value's memoization).
762
- const onViewPreferencesChangeRef = useRef(onViewPreferencesChange);
763
- onViewPreferencesChangeRef.current = onViewPreferencesChange;
764
- const inlinePreviewRef = useRef(inlinePreviewVisible);
765
- inlinePreviewRef.current = inlinePreviewVisible;
766
- const statusBarRef = useRef(statusBarVisible);
767
- statusBarRef.current = statusBarVisible;
768
- const outlineRef = useRef(outlineVisible);
769
- outlineRef.current = outlineVisible;
770
- const blockTagVisibilityRef = useRef(blockTagVisibilityState);
771
- blockTagVisibilityRef.current = blockTagVisibilityState;
772
- const themeInheritanceRef = useRef(themeInheritanceState);
773
- themeInheritanceRef.current = themeInheritanceState;
774
- const layoutModeRef = useRef(layoutModeState);
775
- layoutModeRef.current = layoutModeState;
776
- const setInlinePreviewVisible = useCallback((visible: boolean) => {
777
- setInlinePreviewVisibleRaw(visible);
778
- onViewPreferencesChangeRef.current?.({
779
- inlinePreview: visible,
780
- showStatusBar: statusBarRef.current,
781
- outline: outlineRef.current,
782
- blockTags: blockTagVisibilityRef.current !== 'none',
783
- blockTagVisibility: blockTagVisibilityRef.current,
784
- themeInheritance: themeInheritanceRef.current,
785
- layoutMode: layoutModeRef.current,
786
- });
787
- }, []);
788
- const setStatusBarVisible = useCallback((visible: boolean) => {
789
- setStatusBarVisibleRaw(visible);
790
- onViewPreferencesChangeRef.current?.({
791
- inlinePreview: inlinePreviewRef.current,
792
- showStatusBar: visible,
793
- outline: outlineRef.current,
794
- blockTags: blockTagVisibilityRef.current !== 'none',
795
- blockTagVisibility: blockTagVisibilityRef.current,
796
- themeInheritance: themeInheritanceRef.current,
797
- layoutMode: layoutModeRef.current,
798
- });
799
- }, []);
800
- const setOutlineVisible = useCallback((visible: boolean) => {
801
- setOutlineVisibleRaw(visible);
802
- onViewPreferencesChangeRef.current?.({
803
- inlinePreview: inlinePreviewRef.current,
804
- showStatusBar: statusBarRef.current,
805
- outline: visible,
806
- blockTags: blockTagVisibilityRef.current !== 'none',
807
- blockTagVisibility: blockTagVisibilityRef.current,
808
- themeInheritance: themeInheritanceRef.current,
809
- layoutMode: layoutModeRef.current,
810
- });
811
- }, []);
812
- const setBlockTagVisibility = useCallback((visibility: BlockTagVisibility) => {
813
- setBlockTagVisibilityRaw(visibility);
814
- onViewPreferencesChangeRef.current?.({
815
- inlinePreview: inlinePreviewRef.current,
816
- showStatusBar: statusBarRef.current,
817
- outline: outlineRef.current,
818
- blockTags: visibility !== 'none',
819
- blockTagVisibility: visibility,
820
- themeInheritance: themeInheritanceRef.current,
821
- layoutMode: layoutModeRef.current,
822
- });
823
- }, []);
824
- const setBlockTagsVisible = useCallback(
825
- (visible: boolean) => setBlockTagVisibility(visible ? 'always' : 'none'),
826
- [setBlockTagVisibility],
827
- );
828
- const setThemeInheritance = useCallback((mode: ThemeInheritance) => {
829
- setThemeInheritanceRaw(mode);
830
- onViewPreferencesChangeRef.current?.({
831
- inlinePreview: inlinePreviewRef.current,
832
- showStatusBar: statusBarRef.current,
833
- outline: outlineRef.current,
834
- blockTags: blockTagVisibilityRef.current !== 'none',
835
- blockTagVisibility: blockTagVisibilityRef.current,
836
- themeInheritance: mode,
837
- layoutMode: layoutModeRef.current,
838
- });
839
- }, []);
840
- const setLayoutMode = useCallback((mode: LayoutMode) => {
841
- setLayoutModeRaw(mode);
842
- onViewPreferencesChangeRef.current?.({
843
- inlinePreview: inlinePreviewRef.current,
844
- showStatusBar: statusBarRef.current,
845
- outline: outlineRef.current,
846
- blockTags: blockTagVisibilityRef.current !== 'none',
847
- blockTagVisibility: blockTagVisibilityRef.current,
848
- themeInheritance: themeInheritanceRef.current,
849
- layoutMode: mode,
850
- });
851
- }, []);
852
- const [tiptapEditor, setTiptapEditorRaw] = useState<TiptapEditor | null>(null);
853
- const [monacoEditor, setMonacoEditorRaw] = useState<MonacoEditor | null>(null);
854
- const setTiptapEditor = useCallback((editor: TiptapEditor | null) => {
855
- tiptapEditorRef.current = editor;
856
- setTiptapEditorRaw(editor);
857
- }, []);
858
- const setMonacoEditor = useCallback((editor: MonacoEditor | null) => {
859
- monacoEditorRef.current = editor;
860
- setMonacoEditorRaw(editor);
861
- }, []);
862
-
863
- useEffect(() => {
864
- const pending = pendingEditorScrollRef.current;
865
- if (!pending || pending.view !== activeView) return;
866
-
867
- const restore =
868
- activeView === 'raw' && monacoEditor
869
- ? () => restoreMonacoScrollRatio(monacoEditor, pending.ratio)
870
- : activeView === 'wysiwyg' && tiptapEditor
871
- ? () => restoreWysiwygScrollRatio(tiptapEditor, pending.ratio)
872
- : null;
873
- if (!restore) return;
874
-
875
- let cancelled = false;
876
- const apply = () => {
877
- if (cancelled) return;
878
- restore();
879
- if (pendingEditorScrollRef.current === pending) pendingEditorScrollRef.current = null;
880
- };
881
-
882
- if (typeof requestAnimationFrame === 'function') {
883
- const frame = requestAnimationFrame(apply);
884
- return () => {
885
- cancelled = true;
886
- cancelAnimationFrame(frame);
887
- };
888
- }
889
-
890
- const timeout = setTimeout(apply, 0);
891
- return () => {
892
- cancelled = true;
893
- clearTimeout(timeout);
894
- };
895
- }, [activeView, monacoEditor, tiptapEditor]);
896
- // Mirror the focused canvas textbox editor (published by the inline
897
- // SceneTextOverlay through a module channel, since the canvas renders in
898
- // a detached React root outside this provider) so the toolbar can target it.
899
- const [activeSceneText, setActiveSceneText] = useState<SceneTextHandle | null>(null);
900
- const sceneTextChannel = useMemo(createSceneTextChannel, []);
901
- useEffect(() => sceneTextChannel.subscribe(setActiveSceneText), [sceneTextChannel]);
902
-
903
- const articleIdRef = useRef(articleId);
904
- articleIdRef.current = articleId;
905
-
906
- // Sync color scheme when prop changes
907
- useEffect(() => {
908
- setColorScheme(initialColorScheme);
909
- }, [initialColorScheme]);
910
-
911
- // Debounced parse on markdown source change
912
- const parseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
913
- const skipInitialDebouncedParseRef = useRef(Boolean(initialMarkdown));
914
-
915
- const doParse = useCallback((source: string) => {
916
- setIsParsing(true);
917
- try {
918
- const parsed = parseMarkdown(source);
919
- setMarkdownDocState(parsed);
920
- setParseError(null);
921
-
922
- // Generate Doc from parsed markdown
923
- try {
924
- const generatedDoc = markdownToDoc(parsed, {
925
- articleId: articleIdRef.current,
926
- });
927
- setDoc(generatedDoc);
928
- } catch (docErr: unknown) {
929
- // Doc generation can fail but markdown parse succeeded
930
- setDoc(null);
931
- console.warn('Doc generation failed:', docErr instanceof Error ? docErr.message : docErr);
932
- }
933
- } catch (err: unknown) {
934
- setParseError(err instanceof Error ? err.message : 'Parse error');
935
- setMarkdownDocState(null);
936
- setDoc(null);
937
- } finally {
938
- setIsParsing(false);
939
- }
940
- }, []);
941
-
942
- // Parse on source changes with debounce. Skipped in code/image mode —
943
- // the WYSIWYG/Preview surfaces that consume markdownDoc/doc aren't
944
- // mounted, so there's nothing to feed and no reason to run the markdown
945
- // parser on TypeScript / JSON / a binary image asset.
946
- useEffect(() => {
947
- if (editorMode !== 'markdown') return;
948
- if (skipInitialDebouncedParseRef.current) {
949
- skipInitialDebouncedParseRef.current = false;
950
- return;
951
- }
952
- if (parseTimeoutRef.current) {
953
- clearTimeout(parseTimeoutRef.current);
954
- }
955
- setIsParsing(true);
956
- parseTimeoutRef.current = setTimeout(() => {
957
- doParse(markdownSource);
958
- }, 150);
959
- return () => {
960
- if (parseTimeoutRef.current) {
961
- clearTimeout(parseTimeoutRef.current);
962
- }
963
- };
964
- }, [markdownSource, doParse, editorMode]);
965
-
966
- // Initial parse
967
- useEffect(() => {
968
- if (editorMode !== 'markdown') return;
969
- if (initialMarkdown) {
970
- doParse(initialMarkdown);
971
- }
972
- // eslint-disable-next-line react-hooks/exhaustive-deps
973
- }, []);
974
-
975
- const setMarkdownSource = useCallback((source: string) => {
976
- setMarkdownSourceRaw(source);
977
- }, []);
978
-
979
- // Block-at-a-time navigation. In 'document' mode the channel passes
980
- // through to the full source; in 'block' mode `editorSource` is the active
981
- // block's slice and `setEditorSource` splices edits back in. Gated to
982
- // markdown mode — code/image surfaces always edit the whole file.
983
- const blockNav = useBlockNavigator(markdownSource, setMarkdownSource, {
984
- enabled:
985
- (layoutModeState === 'block' || layoutModeState === 'timeline') && editorMode === 'markdown',
986
- });
987
- const {
988
- editorSource,
989
- setEditorSource,
990
- blockCount,
991
- activeBlockKey,
992
- activeBlockStartLine,
993
- goToBlock,
994
- goToBlockByLine,
995
- prevBlock,
996
- nextBlock,
997
- addBlock,
998
- } = blockNav;
999
-
1000
- const insertAtCursor = useCallback(
1001
- (text: string) => {
1002
- if (activeView === 'wysiwyg' && tiptapEditor) {
1003
- // Insert as HTML so formatting is preserved
1004
- const html = markdownToTiptap(text);
1005
- tiptapEditor.chain().focus().insertContent(html).run();
1006
- } else if (activeView === 'raw' && monacoEditor) {
1007
- const position = monacoEditor.getPosition();
1008
- if (position) {
1009
- const model = monacoEditor.getModel();
1010
- if (model) {
1011
- const range = {
1012
- startLineNumber: position.lineNumber,
1013
- startColumn: position.column,
1014
- endLineNumber: position.lineNumber,
1015
- endColumn: position.column,
1016
- };
1017
- monacoEditor.executeEdits('drop', [{ range, text }]);
1018
- }
1019
- } else {
1020
- // No cursor — append
1021
- setMarkdownSourceRaw((prev) => prev + '\n\n' + text);
1022
- }
1023
- } else {
1024
- // Preview or no editor — append to end
1025
- setMarkdownSourceRaw((prev) => prev + '\n\n' + text);
1026
- }
1027
- },
1028
- [activeView, tiptapEditor, monacoEditor],
1029
- );
1030
-
1031
- const replaceAll = useCallback(
1032
- (text: string) => {
1033
- setMarkdownSourceRaw(text);
1034
-
1035
- // Push to editors if mounted
1036
- if (tiptapEditor) {
1037
- const html = markdownToTiptap(text);
1038
- tiptapEditor.commands.setContent(html);
1039
- }
1040
- if (monacoEditor) {
1041
- monacoEditor.setValue(text);
1042
- }
1043
- },
1044
- [tiptapEditor, monacoEditor],
1045
- );
1046
-
1047
- // ── Versioning ─────────────────────────────────────────
1048
- // Build a manager only when versioning is opted in *and* a workspace
1049
- // container exists. A versioning request without one is a misconfiguration
1050
- // — warn once so it surfaces in dev without breaking the editor.
1051
- const versioningWarnedRef = useRef(false);
1052
- useEffect(() => {
1053
- if (allowVersioning && !workspaceContainer && !versioningWarnedRef.current) {
1054
- console.warn(
1055
- '[squisq-editor] allowVersioning requires a `workspaceContainer` prop; versioning is disabled.',
1056
- );
1057
- versioningWarnedRef.current = true;
1058
- }
1059
- }, [allowVersioning, workspaceContainer]);
1060
-
1061
- const versioning = useMemo<DocumentVersionManager | null>(() => {
1062
- if (!allowVersioning || !workspaceContainer) return null;
1063
- return new DocumentVersionManager(workspaceContainer, { basename: versionBasename });
1064
- }, [allowVersioning, workspaceContainer, versionBasename]);
1065
-
1066
- const onSaveVersionRef = useRef(onSaveVersion);
1067
- onSaveVersionRef.current = onSaveVersion;
1068
- const prunePolicyRef = useRef(versioningPrunePolicy);
1069
- prunePolicyRef.current = versioningPrunePolicy;
1070
-
1071
- const saveVersion = useCallback(
1072
- async (options?: SaveVersionOptions): Promise<SaveVersionResult> => {
1073
- if (!versioning) {
1074
- const skipped: SaveVersionResult = { saved: false, version: null, reason: 'no-document' };
1075
- onSaveVersionRef.current?.(skipped);
1076
- return skipped;
1077
- }
1078
- const result = await versioning.saveVersion(options);
1079
- onSaveVersionRef.current?.(result);
1080
- if (result.saved) {
1081
- // Fire-and-forget prune. Failures here shouldn't block the save.
1082
- versioning.pruneVersions(prunePolicyRef.current).catch((err: unknown) => {
1083
- console.warn(
1084
- '[squisq-editor] pruneVersions failed:',
1085
- err instanceof Error ? err.message : err,
1086
- );
1087
- });
1088
- }
1089
- return result;
1090
- },
1091
- [versioning],
1092
- );
1093
-
1094
- // Auto-save: stamp a new snapshot after `versioningAutoSaveIdleMs` of
1095
- // idle. The "only save if different" check inside `saveVersion` makes
1096
- // most ticks no-ops, so this is cheap. Disabled when the idle delay is
1097
- // 0 or versioning isn't active.
1098
- //
1099
- // We pass the live `markdownSource` explicitly so saveVersion never has
1100
- // to fall back to `container.readDocument()`. That fallback would fail
1101
- // in setups where the markdown file lives outside the versioning
1102
- // container's scope (e.g. DocBlocks, where the container points at
1103
- // `<basename>_files/` while the doc itself lives in the parent
1104
- // directory). Using the editor's live state also ensures the snapshot
1105
- // captures the most recent edit even if the host's autosave to the
1106
- // container hasn't flushed yet.
1107
- useEffect(() => {
1108
- if (!versioning) return;
1109
- if (versioningAutoSaveIdleMs <= 0) return;
1110
- const timer = setTimeout(() => {
1111
- saveVersion({ content: markdownSource }).catch((err: unknown) => {
1112
- console.warn(
1113
- '[squisq-editor] auto-save version failed:',
1114
- err instanceof Error ? err.message : err,
1115
- );
1116
- });
1117
- }, versioningAutoSaveIdleMs);
1118
- return () => clearTimeout(timer);
1119
- }, [markdownSource, versioning, versioningAutoSaveIdleMs, saveVersion]);
1120
-
1121
- const setMarkdownDoc = useCallback((newDoc: MarkdownDocument) => {
1122
- setMarkdownDocState(newDoc);
1123
- // Stringify to update the raw source
1124
- try {
1125
- const newSource = stringifyMarkdown(newDoc);
1126
- setMarkdownSourceRaw(newSource);
1127
- setParseError(null);
1128
-
1129
- // Generate Doc
1130
- try {
1131
- const generatedDoc = markdownToDoc(newDoc, {
1132
- articleId: articleIdRef.current,
1133
- });
1134
- setDoc(generatedDoc);
1135
- } catch (docErr: unknown) {
1136
- setDoc(null);
1137
- console.warn('Doc generation failed:', docErr instanceof Error ? docErr.message : docErr);
1138
- }
1139
- } catch (err: unknown) {
1140
- setParseError(err instanceof Error ? err.message : 'Stringify error');
1141
- }
1142
- }, []);
1143
-
1144
- const value = useMemo<EditorContextValue>(
1145
- () => ({
1146
- markdownSource,
1147
- markdownDoc,
1148
- doc,
1149
- activeView,
1150
- parseError,
1151
- isParsing,
1152
- colorScheme,
1153
- editorMode,
1154
- findMode,
1155
- language: resolvedLanguage,
1156
- inlinePreviewVisible,
1157
- statusBarVisible,
1158
- outlineVisible,
1159
- blockTagVisibility: blockTagVisibilityState,
1160
- blockTagsVisible,
1161
- themeInheritance: themeInheritanceState,
1162
- layoutMode: layoutModeState,
1163
- editorSource,
1164
- blockCount,
1165
- activeBlockKey,
1166
- activeBlockStartLine,
1167
- imageEditTarget,
1168
- mediaRevision,
1169
- allowRecording,
1170
- allowNarrate,
1171
- tiptapEditor,
1172
- monacoEditor,
1173
- activeSceneText,
1174
- sceneTextChannel,
1175
- workspaceContainer,
1176
- versioning,
1177
- saveVersion,
1178
- mediaProvider,
1179
- imageDisplayMode,
1180
- mentionProvider,
1181
- documentLinkProvider,
1182
- setMarkdownSource,
1183
- setEditorSource,
1184
- setLayoutMode,
1185
- goToBlock,
1186
- goToBlockByLine,
1187
- prevBlock,
1188
- nextBlock,
1189
- addBlock,
1190
- setMarkdownDoc,
1191
- setActiveView,
1192
- setFindMode,
1193
- setTiptapEditor,
1194
- setMonacoEditor,
1195
- setColorScheme,
1196
- setInlinePreviewVisible,
1197
- setStatusBarVisible,
1198
- setOutlineVisible,
1199
- setBlockTagVisibility,
1200
- setBlockTagsVisible,
1201
- setThemeInheritance,
1202
- insertAtCursor,
1203
- replaceAll,
1204
- openImageEdit,
1205
- closeImageEdit,
1206
- bumpMediaRevision,
1207
- }),
1208
- [
1209
- markdownSource,
1210
- markdownDoc,
1211
- doc,
1212
- activeView,
1213
- parseError,
1214
- isParsing,
1215
- colorScheme,
1216
- editorMode,
1217
- findMode,
1218
- resolvedLanguage,
1219
- inlinePreviewVisible,
1220
- statusBarVisible,
1221
- outlineVisible,
1222
- blockTagVisibilityState,
1223
- blockTagsVisible,
1224
- themeInheritanceState,
1225
- layoutModeState,
1226
- editorSource,
1227
- blockCount,
1228
- activeBlockKey,
1229
- activeBlockStartLine,
1230
- tiptapEditor,
1231
- monacoEditor,
1232
- activeSceneText,
1233
- sceneTextChannel,
1234
- workspaceContainer,
1235
- versioning,
1236
- saveVersion,
1237
- mediaProvider,
1238
- imageDisplayMode,
1239
- mentionProvider,
1240
- documentLinkProvider,
1241
- setMarkdownSource,
1242
- setEditorSource,
1243
- setLayoutMode,
1244
- goToBlock,
1245
- goToBlockByLine,
1246
- prevBlock,
1247
- nextBlock,
1248
- addBlock,
1249
- setMarkdownDoc,
1250
- setActiveView,
1251
- setFindMode,
1252
- setTiptapEditor,
1253
- setMonacoEditor,
1254
- setColorScheme,
1255
- setInlinePreviewVisible,
1256
- setStatusBarVisible,
1257
- setOutlineVisible,
1258
- setBlockTagVisibility,
1259
- setBlockTagsVisible,
1260
- setThemeInheritance,
1261
- insertAtCursor,
1262
- replaceAll,
1263
- imageEditTarget,
1264
- mediaRevision,
1265
- allowRecording,
1266
- allowNarrate,
1267
- openImageEdit,
1268
- closeImageEdit,
1269
- bumpMediaRevision,
1270
- ],
1271
- );
1272
-
1273
- return <EditorContext.Provider value={value}>{children}</EditorContext.Provider>;
1274
- }