@bendyline/squisq-editor-react 1.5.3 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +57 -10
  2. package/dist/index.d.ts +1114 -52
  3. package/dist/index.js +18446 -8050
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles/fa-brands-400-AHOAZHCU.woff2 +0 -0
  6. package/dist/styles/fa-regular-400-VRZYIBIZ.woff2 +0 -0
  7. package/dist/styles/fa-solid-900-MDEYK55F.woff2 +0 -0
  8. package/dist/styles/fa-v4compatibility-ETEVP6IB.woff2 +0 -0
  9. package/dist/styles/index.css +13867 -0
  10. package/package.json +15 -7
  11. package/src/BlockCardView.tsx +121 -0
  12. package/src/BlockPreviewPanel.tsx +69 -0
  13. package/src/BlockPropertiesPopover.tsx +191 -0
  14. package/src/EditorContext.tsx +165 -16
  15. package/src/EditorShell.tsx +265 -144
  16. package/src/FolderView.tsx +131 -0
  17. package/src/Icon.tsx +26 -0
  18. package/src/ImageEditor.tsx +69 -22
  19. package/src/OutlinePanel.tsx +64 -7
  20. package/src/PlainHtmlPreview.tsx +30 -3
  21. package/src/PreviewControls.tsx +491 -122
  22. package/src/PreviewPanel.tsx +15 -9
  23. package/src/RawEditor.tsx +179 -17
  24. package/src/RecorderEntry.tsx +9 -16
  25. package/src/TemplateAnnotation.ts +44 -0
  26. package/src/TemplatePicker.tsx +329 -54
  27. package/src/ThemeCustomizerPanel.tsx +30 -336
  28. package/src/ThemePicker.tsx +112 -3
  29. package/src/TimelineBlockPreview.tsx +37 -0
  30. package/src/TimelineTrack.tsx +671 -0
  31. package/src/Toolbar.tsx +544 -180
  32. package/src/Tooltip.tsx +22 -4
  33. package/src/TransitionPicker.tsx +351 -0
  34. package/src/VersionHistoryPanel.tsx +4 -16
  35. package/src/ViewMenuPanel.tsx +17 -14
  36. package/src/WysiwygEditor.tsx +161 -65
  37. package/src/__tests__/blockProperties.test.ts +92 -0
  38. package/src/__tests__/blockRange.test.ts +105 -0
  39. package/src/__tests__/buildPreviewDocTransition.test.ts +73 -0
  40. package/src/__tests__/codeContextSectionView.test.tsx +95 -0
  41. package/src/__tests__/codeContextZoneManager.test.ts +127 -0
  42. package/src/__tests__/createShapeLayer.test.ts +46 -0
  43. package/src/__tests__/diffContextSections.test.ts +39 -0
  44. package/src/__tests__/drawingShapeRoundTrip.test.ts +49 -0
  45. package/src/__tests__/editorShellCodeContext.test.tsx +86 -0
  46. package/src/__tests__/editorShellProps.test.tsx +96 -0
  47. package/src/__tests__/embeddedMedia.test.ts +48 -0
  48. package/src/__tests__/headingTransition.test.ts +138 -0
  49. package/src/__tests__/layoutChildRoundTrip.test.ts +71 -0
  50. package/src/__tests__/plainHtmlPreview.test.tsx +10 -8
  51. package/src/__tests__/previewControls.test.tsx +70 -0
  52. package/src/__tests__/recorderMediaInsert.test.ts +86 -0
  53. package/src/__tests__/templateAnnotationRoundTrip.test.ts +18 -0
  54. package/src/__tests__/templatePickerMetadata.test.ts +32 -0
  55. package/src/__tests__/timelineSource.test.ts +134 -0
  56. package/src/__tests__/tiptapBridge.test.ts +92 -0
  57. package/src/__tests__/tiptapBridgeConformance.test.ts +47 -0
  58. package/src/__tests__/tooltip.test.tsx +72 -0
  59. package/src/__tests__/transitionCatalog.test.ts +64 -0
  60. package/src/__tests__/useBlockNavigator.test.tsx +67 -0
  61. package/src/__tests__/useJsonEditorTokens.test.ts +59 -0
  62. package/src/__tests__/useMediaRecorder.test.ts +41 -0
  63. package/src/__tests__/useTimelineClock.test.ts +21 -0
  64. package/src/blockProperties.ts +88 -0
  65. package/src/blockRange.ts +132 -0
  66. package/src/buildPreviewDoc.ts +98 -9
  67. package/src/codeContext/CodeContextSectionView.tsx +124 -0
  68. package/src/codeContext/CodeContextZoneManager.ts +149 -0
  69. package/src/codeContext/CodeContextZones.tsx +121 -0
  70. package/src/codeContext/diffContextSections.ts +38 -0
  71. package/src/codeContext/types.ts +75 -0
  72. package/src/customTemplates/AddBin.tsx +126 -0
  73. package/src/customTemplates/CustomLayoutManager.tsx +233 -0
  74. package/src/customTemplates/CustomTemplateContext.tsx +182 -0
  75. package/src/customTemplates/LayerToolbar.tsx +580 -0
  76. package/src/customTemplates/ShapeGlyph.tsx +47 -0
  77. package/src/customTemplates/TemplateDesigner.tsx +430 -0
  78. package/src/customTemplates/__tests__/library.test.ts +88 -0
  79. package/src/customTemplates/__tests__/normalizePositions.test.ts +109 -0
  80. package/src/customTemplates/__tests__/shapeDefs.test.ts +49 -0
  81. package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +95 -0
  82. package/src/customTemplates/designer.css +673 -0
  83. package/src/customTemplates/index.ts +31 -0
  84. package/src/customTemplates/library.ts +97 -0
  85. package/src/customTemplates/normalizePositions.ts +75 -0
  86. package/src/customTemplates/shapeDefs.ts +131 -0
  87. package/src/customTemplates/thumbnail.tsx +63 -0
  88. package/src/customTemplates/tokenDefs.ts +60 -0
  89. package/src/customTemplates/useDocCustomTemplates.ts +52 -0
  90. package/src/customTemplates/useMemoryLayerAdapter.ts +123 -0
  91. package/src/customThemes/CustomThemeContext.tsx +179 -0
  92. package/src/customThemes/CustomThemeDialog.tsx +286 -0
  93. package/src/customThemes/__tests__/CustomThemeContext.test.tsx +64 -0
  94. package/src/customThemes/__tests__/CustomThemeDialog.test.tsx +47 -0
  95. package/src/customThemes/__tests__/customThemeLibrary.test.ts +51 -0
  96. package/src/customThemes/customThemeLibrary.ts +97 -0
  97. package/src/customThemes/index.ts +31 -0
  98. package/src/customThemes/themeControls.tsx +229 -0
  99. package/src/customThemes/themeDraft.ts +272 -0
  100. package/src/customThemes/useDocCustomThemes.ts +49 -0
  101. package/src/diagram/DiagramCanvas.tsx +240 -0
  102. package/src/diagram/DiagramExtension.ts +209 -0
  103. package/src/diagram/DiagramMaximizedOverlay.tsx +46 -0
  104. package/src/diagram/DiagramWidget.tsx +270 -0
  105. package/src/diagram/diagramCommands.ts +604 -0
  106. package/src/diagram/diagramConstants.ts +17 -0
  107. package/src/diagram/useDiagramData.ts +126 -0
  108. package/src/embeddedMedia.ts +78 -0
  109. package/src/frontmatter.ts +29 -0
  110. package/src/headingTransition.ts +231 -0
  111. package/src/imageEditor/CanvasSurface.tsx +383 -88
  112. package/src/imageEditor/PropertiesPanel.tsx +47 -1
  113. package/src/imageEditor/Toolbar.tsx +229 -16
  114. package/src/imageEditor/createShapeLayer.ts +280 -0
  115. package/src/imageEditor/icons.tsx +34 -114
  116. package/src/imageEditor/image-editor.css +54 -5
  117. package/src/imageEditor/state.ts +23 -3
  118. package/src/index.ts +109 -1
  119. package/src/jsonEditor/useJsonEditorTokens.ts +13 -43
  120. package/src/recorder/RecorderModal.tsx +120 -53
  121. package/src/recorder/RecorderPanel.tsx +2 -26
  122. package/src/recorder/hooks/useMediaRecorder.ts +17 -11
  123. package/src/recorder/insertMediaBlock.ts +30 -0
  124. package/src/resolveBlockVisual.ts +33 -0
  125. package/src/scene/Scene.tsx +540 -0
  126. package/src/scene/SceneBlockExtension.ts +198 -0
  127. package/src/scene/SceneBlockToolbar.tsx +201 -0
  128. package/src/scene/SceneBlockWidget.tsx +434 -0
  129. package/src/scene/ScenePropsBar.tsx +85 -0
  130. package/src/scene/SceneSelection.tsx +107 -0
  131. package/src/scene/SceneViewport.tsx +102 -0
  132. package/src/scene/ShapePalette.tsx +181 -0
  133. package/src/scene/__tests__/DiagramAdapter.test.ts +56 -0
  134. package/src/scene/__tests__/bezierEdit.test.ts +85 -0
  135. package/src/scene/__tests__/blockLayers.test.ts +57 -0
  136. package/src/scene/__tests__/shapeLayers.test.ts +106 -0
  137. package/src/scene/__tests__/useSceneHitTest.test.ts +90 -0
  138. package/src/scene/__tests__/useScenePanZoom.test.ts +103 -0
  139. package/src/scene/adapters/DiagramAdapter.ts +168 -0
  140. package/src/scene/adapters/DrawingAdapter.ts +415 -0
  141. package/src/scene/adapters/LayoutAdapter.ts +310 -0
  142. package/src/scene/adapters/blockLayers.ts +159 -0
  143. package/src/scene/commands/SceneCommand.ts +70 -0
  144. package/src/scene/commands/drawingCommands.ts +318 -0
  145. package/src/scene/commands/layoutCommands.ts +301 -0
  146. package/src/scene/hooks/useSceneHitTest.ts +105 -0
  147. package/src/scene/hooks/useScenePanZoom.ts +147 -0
  148. package/src/scene/hooks/useSceneSelection.ts +62 -0
  149. package/src/scene/index.ts +95 -0
  150. package/src/scene/layers/DiagramEdges.tsx +127 -0
  151. package/src/scene/layers/edgeGeometry.ts +77 -0
  152. package/src/scene/layers/nodeCard.tsx +145 -0
  153. package/src/scene/layers/renderLayer.tsx +70 -0
  154. package/src/scene/layers/shapeLayers.ts +201 -0
  155. package/src/scene/paths/bezierEdit.ts +208 -0
  156. package/src/scene/scene.css +649 -0
  157. package/src/scene/text/SceneTextOverlay.tsx +161 -0
  158. package/src/scene/text/sceneTextChannel.ts +40 -0
  159. package/src/scene/text/sceneTextConfig.ts +27 -0
  160. package/src/scene/text/sceneTiptap.ts +36 -0
  161. package/src/scene/text/useSceneTextEditing.ts +39 -0
  162. package/src/scene/tools/ConnectTool.ts +111 -0
  163. package/src/scene/tools/DrawingConnectTool.ts +161 -0
  164. package/src/scene/tools/PathTool.ts +158 -0
  165. package/src/scene/tools/PlaceTool.ts +47 -0
  166. package/src/scene/tools/SceneTool.ts +75 -0
  167. package/src/scene/tools/SelectTool.ts +284 -0
  168. package/src/scene/tools/ShapeTool.ts +144 -0
  169. package/src/scene/tools/TextTool.ts +72 -0
  170. package/src/scene/tools/TokenTool.ts +95 -0
  171. package/src/scene/tools/createDrawShapeTool.ts +82 -0
  172. package/src/styles/code-context.css +155 -0
  173. package/src/styles/diagram.css +183 -0
  174. package/src/styles/editor.css +1708 -150
  175. package/src/styles/folder-view.css +210 -0
  176. package/src/styles/image-edit-affordance.css +2 -2
  177. package/src/styles/index.css +5 -0
  178. package/src/timelineSource.ts +244 -0
  179. package/src/tiptapBridge.ts +115 -34
  180. package/src/tooltipPlacement.ts +13 -0
  181. package/src/transitionCatalog.ts +159 -0
  182. package/src/types/monaco-shims.d.ts +10 -0
  183. package/src/useBlockNavigator.ts +153 -0
  184. package/src/useMonacoLoader.ts +23 -1
  185. package/src/useTimelineClock.ts +76 -0
  186. package/src/utils/dropUtils.ts +1 -1
@@ -32,6 +32,8 @@ import type { Editor as TiptapEditor } from '@tiptap/core';
32
32
  import type { editor as MonacoEditorNs } from 'monaco-editor';
33
33
  import { markdownToTiptap } from './tiptapBridge';
34
34
  import { resolveFileKind } from './fileKind';
35
+ import { useBlockNavigator } from './useBlockNavigator';
36
+ import { sceneTextChannel, type SceneTextHandle } from './scene/text/sceneTextChannel';
35
37
 
36
38
  /** Monaco standalone code editor instance type */
37
39
  type MonacoEditor = MonacoEditorNs.IStandaloneCodeEditor;
@@ -89,7 +91,22 @@ export type DocumentLinkProvider = (query: string) => Promise<DocumentLinkCandid
89
91
  // ─── Types ───────────────────────────────────────────────
90
92
 
91
93
  export type EditorView = 'raw' | 'wysiwyg' | 'preview';
92
- export type EditorTheme = 'light' | 'dark';
94
+ /**
95
+ * Light/dark chrome mode for the editor shell (toolbar, tabs, status bar,
96
+ * side panes). This is the editor's *UI color scheme* — distinct from a
97
+ * Squisq `Theme` object, which styles the rendered document. Renamed from
98
+ * the former `EditorTheme` to remove that ambiguity.
99
+ */
100
+ export type EditorColorScheme = 'light' | 'dark';
101
+ /**
102
+ * Document layout mode. `'document'` shows the whole markdown document in
103
+ * the active view (the historical behavior). `'block'` is the
104
+ * block-at-a-time view — one heading-defined block on a card at a time,
105
+ * with the editor scoped to just that block. `'timeline'` is block mode plus
106
+ * a horizontal timeline track for editing block durations and media slices.
107
+ * See {@link useBlockNavigator}.
108
+ */
109
+ export type LayoutMode = 'document' | 'block' | 'timeline';
93
110
  /**
94
111
  * How much of the active Squisq theme the WYSIWYG editing surface
95
112
  * mirrors. `'fonts'` is the historical default — body and heading
@@ -117,8 +134,8 @@ export interface EditorState {
117
134
  parseError: string | null;
118
135
  /** Whether a parse is pending */
119
136
  isParsing: boolean;
120
- /** Current color theme */
121
- theme: EditorTheme;
137
+ /** Current light/dark chrome color scheme for the editor shell. */
138
+ colorScheme: EditorColorScheme;
122
139
  /** Operating mode — 'markdown' for the full shell, 'code' for Monaco-only. */
123
140
  editorMode: EditorMode;
124
141
  /** Monaco language ID for the Raw editor. */
@@ -179,11 +196,48 @@ export interface EditorState {
179
196
  * shell.
180
197
  */
181
198
  allowRecording: boolean;
199
+ /**
200
+ * Document layout mode. `'document'` (default) edits the whole document;
201
+ * `'block'` activates the block-at-a-time card view. Initialized from the
202
+ * EditorShell `layoutMode` prop; the View menu can toggle it at runtime.
203
+ */
204
+ layoutMode: LayoutMode;
205
+ /**
206
+ * The markdown the active text editor should bind to: the full source in
207
+ * `'document'` mode, or just the active block's slice in `'block'` mode.
208
+ * Editors read this instead of `markdownSource` so the same surfaces work
209
+ * in both layouts.
210
+ */
211
+ editorSource: string;
212
+ /** Number of navigable blocks (cards) in the current document. */
213
+ blockCount: number;
214
+ /** Index of the block currently shown on the card (block mode). */
215
+ activeBlockKey: number;
216
+ /** 1-based source line where the active block begins, or null. */
217
+ activeBlockStartLine: number | null;
182
218
  }
183
219
 
184
220
  export interface EditorActions {
185
221
  /** Set markdown source and trigger re-parse */
186
222
  setMarkdownSource: (source: string) => void;
223
+ /**
224
+ * Write through the active editor channel. In `'document'` mode this is
225
+ * `setMarkdownSource`; in `'block'` mode it splices the edited block back
226
+ * into the full document. Editors call this instead of `setMarkdownSource`.
227
+ */
228
+ setEditorSource: (source: string) => void;
229
+ /** Switch between Document and Block-at-a-time layouts. */
230
+ setLayoutMode: (mode: LayoutMode) => void;
231
+ /** Show a block by index in block mode (clamped to range). */
232
+ goToBlock: (key: number) => void;
233
+ /** Show the block that owns a given 1-based source line (used by the outline). */
234
+ goToBlockByLine: (line: number) => void;
235
+ /** Move the card to the previous block. */
236
+ prevBlock: () => void;
237
+ /** Move the card to the next block. */
238
+ nextBlock: () => void;
239
+ /** Insert a new heading block after the active one and move to it. */
240
+ addBlock: () => void;
187
241
  /** Set markdown from a MarkdownDocument (e.g. from WYSIWYG) */
188
242
  setMarkdownDoc: (doc: MarkdownDocument) => void;
189
243
  /** Switch the active view */
@@ -192,8 +246,8 @@ export interface EditorActions {
192
246
  setTiptapEditor: (editor: TiptapEditor | null) => void;
193
247
  /** Register / unregister the Monaco editor instance (called by RawEditor) */
194
248
  setMonacoEditor: (editor: MonacoEditor | null) => void;
195
- /** Set the color theme */
196
- setTheme: (theme: EditorTheme) => void;
249
+ /** Set the light/dark chrome color scheme for the editor shell. */
250
+ setColorScheme: (colorScheme: EditorColorScheme) => void;
197
251
  /** Show or hide the inline preview gutter at runtime (driven by the View menu). */
198
252
  setInlinePreviewVisible: (visible: boolean) => void;
199
253
  /** Show or hide the bottom status bar at runtime (driven by the View menu). */
@@ -230,6 +284,14 @@ export interface EditorContextValue extends EditorState, EditorActions {
230
284
  tiptapEditor: TiptapEditor | null;
231
285
  /** The live Monaco editor instance (null when Raw is not mounted) */
232
286
  monacoEditor: MonacoEditor | null;
287
+ /**
288
+ * The focused canvas textbox editor, if any — a small Tiptap instance for
289
+ * a diagram/drawing/layout textbox being edited inline. Published via
290
+ * `sceneTextChannel` (the canvas renders in a detached React root). The
291
+ * top formatting toolbar retargets to this when set. `level` gates which
292
+ * buttons apply (`inline` = marks only; `rich` = headings/lists too).
293
+ */
294
+ activeSceneText: SceneTextHandle | null;
233
295
  /**
234
296
  * Workspace-scoped `ContentContainer` for this document — the folder
235
297
  * holding the doc, its `_files/` sidecar, sibling documents, and any
@@ -304,8 +366,8 @@ export interface EditorProviderProps {
304
366
  initialView?: EditorView;
305
367
  /** Article ID used when generating the Doc */
306
368
  articleId?: string;
307
- /** Color theme */
308
- theme?: EditorTheme;
369
+ /** Light/dark chrome color scheme for the editor shell. */
370
+ colorScheme?: EditorColorScheme;
309
371
  /**
310
372
  * Workspace-scoped `ContentContainer` for this document — the folder
311
373
  * holding the doc, its `_files/` sidecar, sibling documents, and any
@@ -396,6 +458,12 @@ export interface EditorProviderProps {
396
458
  * toolbar's View menu can change it at runtime.
397
459
  */
398
460
  themeInheritance?: ThemeInheritance;
461
+ /**
462
+ * Initial layout mode. Defaults to `'document'` (whole-document editing).
463
+ * `'block'` boots into the block-at-a-time card view. The toolbar's View
464
+ * menu can toggle it at runtime.
465
+ */
466
+ layoutMode?: LayoutMode;
399
467
  /**
400
468
  * Bundled view preferences — a serializable JSON blob covering all
401
469
  * runtime-toggleable view options. When provided, individual values
@@ -432,6 +500,8 @@ export interface ViewPreferences {
432
500
  blockTags?: boolean;
433
501
  /** How much of the active Squisq theme the WYSIWYG surface mirrors. */
434
502
  themeInheritance?: ThemeInheritance;
503
+ /** Document vs. block-at-a-time layout. */
504
+ layoutMode?: LayoutMode;
435
505
  }
436
506
 
437
507
  /**
@@ -445,7 +515,7 @@ export function EditorProvider({
445
515
  initialMarkdown = '',
446
516
  initialView = 'raw',
447
517
  articleId = 'untitled',
448
- theme: initialTheme = 'light',
518
+ colorScheme: initialColorScheme = 'light',
449
519
  workspaceContainer = null,
450
520
  allowVersioning = false,
451
521
  versionBasename,
@@ -464,6 +534,7 @@ export function EditorProvider({
464
534
  outline = false,
465
535
  blockTags = true,
466
536
  themeInheritance = 'fonts',
537
+ layoutMode = 'document',
467
538
  viewPreferences,
468
539
  onViewPreferencesChange,
469
540
  children,
@@ -476,6 +547,7 @@ export function EditorProvider({
476
547
  const effectiveOutline = viewPreferences?.outline ?? outline;
477
548
  const effectiveBlockTags = viewPreferences?.blockTags ?? blockTags;
478
549
  const effectiveThemeInheritance = viewPreferences?.themeInheritance ?? themeInheritance;
550
+ const effectiveLayoutMode = viewPreferences?.layoutMode ?? layoutMode;
479
551
  // Resolve once per provider mount. Changing fileName/language after mount
480
552
  // would require recreating the Monaco model anyway, so treat it as static.
481
553
  const { mode: editorMode, language: resolvedLanguage } = useMemo(
@@ -504,7 +576,7 @@ export function EditorProvider({
504
576
  );
505
577
  const [parseError, setParseError] = useState<string | null>(null);
506
578
  const [isParsing, setIsParsing] = useState(false);
507
- const [theme, setTheme] = useState<EditorTheme>(initialTheme);
579
+ const [colorScheme, setColorScheme] = useState<EditorColorScheme>(initialColorScheme);
508
580
  const [inlinePreviewVisible, setInlinePreviewVisibleRaw] =
509
581
  useState<boolean>(effectiveInlinePreview);
510
582
  // Sync visibility when the host changes the prop (e.g., toggle from outside).
@@ -528,6 +600,10 @@ export function EditorProvider({
528
600
  useEffect(() => {
529
601
  setThemeInheritanceRaw(themeInheritance);
530
602
  }, [themeInheritance]);
603
+ const [layoutModeState, setLayoutModeRaw] = useState<LayoutMode>(effectiveLayoutMode);
604
+ useEffect(() => {
605
+ setLayoutModeRaw(layoutMode);
606
+ }, [layoutMode]);
531
607
  const [imageEditTarget, setImageEditTarget] = useState<string | null>(null);
532
608
  const [mediaRevision, setMediaRevision] = useState(0);
533
609
  const openImageEdit = useCallback((relativePath: string) => {
@@ -560,6 +636,9 @@ export function EditorProvider({
560
636
  if (viewPreferences.themeInheritance !== undefined) {
561
637
  setThemeInheritanceRaw(viewPreferences.themeInheritance);
562
638
  }
639
+ if (viewPreferences.layoutMode !== undefined) {
640
+ setLayoutModeRaw(viewPreferences.layoutMode);
641
+ }
563
642
  }, [viewPreferences]);
564
643
 
565
644
  // Wrap the three setters so user-driven toggles emit a snapshot via
@@ -579,6 +658,8 @@ export function EditorProvider({
579
658
  blockTagsRef.current = blockTagsVisible;
580
659
  const themeInheritanceRef = useRef(themeInheritanceState);
581
660
  themeInheritanceRef.current = themeInheritanceState;
661
+ const layoutModeRef = useRef(layoutModeState);
662
+ layoutModeRef.current = layoutModeState;
582
663
  const setInlinePreviewVisible = useCallback((visible: boolean) => {
583
664
  setInlinePreviewVisibleRaw(visible);
584
665
  onViewPreferencesChangeRef.current?.({
@@ -587,6 +668,7 @@ export function EditorProvider({
587
668
  outline: outlineRef.current,
588
669
  blockTags: blockTagsRef.current,
589
670
  themeInheritance: themeInheritanceRef.current,
671
+ layoutMode: layoutModeRef.current,
590
672
  });
591
673
  }, []);
592
674
  const setStatusBarVisible = useCallback((visible: boolean) => {
@@ -597,6 +679,7 @@ export function EditorProvider({
597
679
  outline: outlineRef.current,
598
680
  blockTags: blockTagsRef.current,
599
681
  themeInheritance: themeInheritanceRef.current,
682
+ layoutMode: layoutModeRef.current,
600
683
  });
601
684
  }, []);
602
685
  const setOutlineVisible = useCallback((visible: boolean) => {
@@ -607,6 +690,7 @@ export function EditorProvider({
607
690
  outline: visible,
608
691
  blockTags: blockTagsRef.current,
609
692
  themeInheritance: themeInheritanceRef.current,
693
+ layoutMode: layoutModeRef.current,
610
694
  });
611
695
  }, []);
612
696
  const setBlockTagsVisible = useCallback((visible: boolean) => {
@@ -617,6 +701,7 @@ export function EditorProvider({
617
701
  outline: outlineRef.current,
618
702
  blockTags: visible,
619
703
  themeInheritance: themeInheritanceRef.current,
704
+ layoutMode: layoutModeRef.current,
620
705
  });
621
706
  }, []);
622
707
  const setThemeInheritance = useCallback((mode: ThemeInheritance) => {
@@ -627,18 +712,35 @@ export function EditorProvider({
627
712
  outline: outlineRef.current,
628
713
  blockTags: blockTagsRef.current,
629
714
  themeInheritance: mode,
715
+ layoutMode: layoutModeRef.current,
716
+ });
717
+ }, []);
718
+ const setLayoutMode = useCallback((mode: LayoutMode) => {
719
+ setLayoutModeRaw(mode);
720
+ onViewPreferencesChangeRef.current?.({
721
+ inlinePreview: inlinePreviewRef.current,
722
+ showStatusBar: statusBarRef.current,
723
+ outline: outlineRef.current,
724
+ blockTags: blockTagsRef.current,
725
+ themeInheritance: themeInheritanceRef.current,
726
+ layoutMode: mode,
630
727
  });
631
728
  }, []);
632
729
  const [tiptapEditor, setTiptapEditor] = useState<TiptapEditor | null>(null);
633
730
  const [monacoEditor, setMonacoEditor] = useState<MonacoEditor | null>(null);
731
+ // Mirror the focused canvas textbox editor (published by the inline
732
+ // SceneTextOverlay through a module channel, since the canvas renders in
733
+ // a detached React root outside this provider) so the toolbar can target it.
734
+ const [activeSceneText, setActiveSceneText] = useState<SceneTextHandle | null>(null);
735
+ useEffect(() => sceneTextChannel.subscribe(setActiveSceneText), []);
634
736
 
635
737
  const articleIdRef = useRef(articleId);
636
738
  articleIdRef.current = articleId;
637
739
 
638
- // Sync theme when prop changes
740
+ // Sync color scheme when prop changes
639
741
  useEffect(() => {
640
- setTheme(initialTheme);
641
- }, [initialTheme]);
742
+ setColorScheme(initialColorScheme);
743
+ }, [initialColorScheme]);
642
744
 
643
745
  // Debounced parse on markdown source change
644
746
  const parseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -702,6 +804,27 @@ export function EditorProvider({
702
804
  setMarkdownSourceRaw(source);
703
805
  }, []);
704
806
 
807
+ // Block-at-a-time navigation. In 'document' mode the channel passes
808
+ // through to the full source; in 'block' mode `editorSource` is the active
809
+ // block's slice and `setEditorSource` splices edits back in. Gated to
810
+ // markdown mode — code/image surfaces always edit the whole file.
811
+ const blockNav = useBlockNavigator(markdownSource, setMarkdownSource, {
812
+ enabled:
813
+ (layoutModeState === 'block' || layoutModeState === 'timeline') && editorMode === 'markdown',
814
+ });
815
+ const {
816
+ editorSource,
817
+ setEditorSource,
818
+ blockCount,
819
+ activeBlockKey,
820
+ activeBlockStartLine,
821
+ goToBlock,
822
+ goToBlockByLine,
823
+ prevBlock,
824
+ nextBlock,
825
+ addBlock,
826
+ } = blockNav;
827
+
705
828
  const insertAtCursor = useCallback(
706
829
  (text: string) => {
707
830
  if (activeView === 'wysiwyg' && tiptapEditor) {
@@ -854,7 +977,7 @@ export function EditorProvider({
854
977
  activeView,
855
978
  parseError,
856
979
  isParsing,
857
- theme,
980
+ colorScheme,
858
981
  editorMode,
859
982
  language: resolvedLanguage,
860
983
  inlinePreviewVisible,
@@ -862,11 +985,17 @@ export function EditorProvider({
862
985
  outlineVisible,
863
986
  blockTagsVisible,
864
987
  themeInheritance: themeInheritanceState,
988
+ layoutMode: layoutModeState,
989
+ editorSource,
990
+ blockCount,
991
+ activeBlockKey,
992
+ activeBlockStartLine,
865
993
  imageEditTarget,
866
994
  mediaRevision,
867
995
  allowRecording,
868
996
  tiptapEditor,
869
997
  monacoEditor,
998
+ activeSceneText,
870
999
  workspaceContainer,
871
1000
  versioning,
872
1001
  saveVersion,
@@ -875,11 +1004,18 @@ export function EditorProvider({
875
1004
  mentionProvider,
876
1005
  documentLinkProvider,
877
1006
  setMarkdownSource,
1007
+ setEditorSource,
1008
+ setLayoutMode,
1009
+ goToBlock,
1010
+ goToBlockByLine,
1011
+ prevBlock,
1012
+ nextBlock,
1013
+ addBlock,
878
1014
  setMarkdownDoc,
879
1015
  setActiveView,
880
1016
  setTiptapEditor,
881
1017
  setMonacoEditor,
882
- setTheme,
1018
+ setColorScheme,
883
1019
  setInlinePreviewVisible,
884
1020
  setStatusBarVisible,
885
1021
  setOutlineVisible,
@@ -898,7 +1034,7 @@ export function EditorProvider({
898
1034
  activeView,
899
1035
  parseError,
900
1036
  isParsing,
901
- theme,
1037
+ colorScheme,
902
1038
  editorMode,
903
1039
  resolvedLanguage,
904
1040
  inlinePreviewVisible,
@@ -906,8 +1042,14 @@ export function EditorProvider({
906
1042
  outlineVisible,
907
1043
  blockTagsVisible,
908
1044
  themeInheritanceState,
1045
+ layoutModeState,
1046
+ editorSource,
1047
+ blockCount,
1048
+ activeBlockKey,
1049
+ activeBlockStartLine,
909
1050
  tiptapEditor,
910
1051
  monacoEditor,
1052
+ activeSceneText,
911
1053
  workspaceContainer,
912
1054
  versioning,
913
1055
  saveVersion,
@@ -916,11 +1058,18 @@ export function EditorProvider({
916
1058
  mentionProvider,
917
1059
  documentLinkProvider,
918
1060
  setMarkdownSource,
1061
+ setEditorSource,
1062
+ setLayoutMode,
1063
+ goToBlock,
1064
+ goToBlockByLine,
1065
+ prevBlock,
1066
+ nextBlock,
1067
+ addBlock,
919
1068
  setMarkdownDoc,
920
1069
  setActiveView,
921
1070
  setTiptapEditor,
922
1071
  setMonacoEditor,
923
- setTheme,
1072
+ setColorScheme,
924
1073
  setInlinePreviewVisible,
925
1074
  setStatusBarVisible,
926
1075
  setOutlineVisible,