@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
@@ -22,11 +22,23 @@ import { StatusBar } from './StatusBar';
22
22
  import { RawEditor } from './RawEditor';
23
23
  import { WysiwygEditor } from './WysiwygEditor';
24
24
  import { InlinePreviewGutter } from './InlinePreviewGutter';
25
- import { OutlinePanel } from './OutlinePanel';
25
+ import { BlockPreviewPanel } from './BlockPreviewPanel';
26
+ import { OutlinePanel, OUTLINE_RESPONSIVE_WIDTH } from './OutlinePanel';
27
+ import { CodeContextZones } from './codeContext/CodeContextZones';
28
+ import type { CodeContext } from './codeContext/types';
29
+ import { BlockCardView } from './BlockCardView';
30
+ import { TimelineTrack } from './TimelineTrack';
26
31
  import { PreviewPanel } from './PreviewPanel';
27
32
  import { ImageViewer } from './ImageViewer';
28
33
  import { ImageEditor } from './ImageEditor';
29
- import { PreviewSettingsProvider, PreviewToolbarControls } from './PreviewControls';
34
+ import {
35
+ PreviewSettingsProvider,
36
+ PreviewToolbarControls,
37
+ PreviewModeSwitch,
38
+ PreviewFormatSwitch,
39
+ ThemeDesignerDock,
40
+ } from './PreviewControls';
41
+ import { CustomThemeProvider, useDocCustomThemes } from './customThemes';
30
42
  import { MediaBin } from './MediaBin';
31
43
  import { DropZoneOverlay } from './DropZoneOverlay';
32
44
  import { TooltipLayer } from './Tooltip';
@@ -48,7 +60,7 @@ import {
48
60
  import type { PrunePolicy, SaveVersionResult } from '@bendyline/squisq/versions';
49
61
  import type { CSSProperties, ReactNode } from 'react';
50
62
 
51
- export type { EditorTheme } from './EditorContext';
63
+ export type { EditorColorScheme } from './EditorContext';
52
64
 
53
65
  export interface EditorShellProps {
54
66
  /** Initial markdown content */
@@ -62,8 +74,13 @@ export interface EditorShellProps {
62
74
  basePath?: string;
63
75
  /** Called when markdown source changes */
64
76
  onChange?: (source: string) => void;
65
- /** Color theme: 'light' or 'dark' (default: 'light') */
66
- theme?: 'light' | 'dark';
77
+ /**
78
+ * Light/dark chrome color scheme for the editor shell — toolbar, tabs,
79
+ * status bar, and side panes (default: `'light'`). This is the editor's
80
+ * UI mode, **not** a Squisq `Theme` object; the rendered document's
81
+ * styling is controlled separately via `themeOverride` / `Doc.themeId`.
82
+ */
83
+ colorScheme?: 'light' | 'dark';
67
84
  /** Additional class name */
68
85
  className?: string;
69
86
  /** CSS height for the shell container (default: '100vh') */
@@ -158,6 +175,13 @@ export interface EditorShellProps {
158
175
  * (Slack, Discord). When omitted, the editor behaves normally.
159
176
  */
160
177
  submitOnEnter?: () => void;
178
+ /**
179
+ * Host-supplied context dictionary rendered inside the Monaco (raw / code)
180
+ * surface: collapsible markdown sections injected above anchor lines, plus
181
+ * an optional file-top summary. See {@link CodeContext}. Ignored in
182
+ * WYSIWYG / preview / image surfaces.
183
+ */
184
+ codeContext?: CodeContext;
161
185
  /**
162
186
  * Let the WYSIWYG editing surface fill its container instead of rendering
163
187
  * as a centered 800px "page" column. Useful when embedding in chat
@@ -316,9 +340,11 @@ export interface EditorShellProps {
316
340
  */
317
341
  outline?: boolean;
318
342
  /**
319
- * Width in pixels for the outline pane. Defaults to 240. Only takes
320
- * effect when {@link EditorShellProps.outline} is true (or the View
321
- * menu has toggled it on).
343
+ * Fixed width in pixels for the outline pane. When omitted, the pane sizes
344
+ * responsively never narrower than 260px, growing with the window and
345
+ * capped at 460px — so it stretches out when there's horizontal space to
346
+ * spare. Only takes effect when {@link EditorShellProps.outline} is true (or
347
+ * the View menu has toggled it on).
322
348
  */
323
349
  outlineWidth?: number;
324
350
  /**
@@ -370,7 +396,7 @@ export function EditorShell({
370
396
  articleId = 'untitled',
371
397
  basePath = '/',
372
398
  onChange,
373
- theme = 'light',
399
+ colorScheme = 'light',
374
400
  className,
375
401
  height = '100vh',
376
402
  minHeight,
@@ -389,6 +415,7 @@ export function EditorShell({
389
415
  toolbarSlotRight,
390
416
  showPlayTab = true,
391
417
  submitOnEnter,
418
+ codeContext,
392
419
  fullWidth = false,
393
420
  uxFont,
394
421
  thinMargins = false,
@@ -409,7 +436,7 @@ export function EditorShell({
409
436
  inlinePreview = false,
410
437
  inlinePreviewWidth = 320,
411
438
  outline = false,
412
- outlineWidth = 240,
439
+ outlineWidth,
413
440
  blockTags = true,
414
441
  themeInheritance = 'fonts',
415
442
  viewPreferences,
@@ -441,7 +468,7 @@ export function EditorShell({
441
468
  initialMarkdown={initialMarkdown}
442
469
  initialView={effectiveInitialView}
443
470
  articleId={articleId}
444
- theme={theme}
471
+ colorScheme={colorScheme}
445
472
  workspaceContainer={effectiveContainer}
446
473
  allowVersioning={allowVersioning}
447
474
  versionBasename={versionBasename}
@@ -479,6 +506,7 @@ export function EditorShell({
479
506
  toolbarSlotRight={toolbarSlotRight}
480
507
  showPlayTab={showPlayTab}
481
508
  submitOnEnter={submitOnEnter}
509
+ codeContext={codeContext}
482
510
  fullWidth={fullWidth}
483
511
  uxFont={uxFont}
484
512
  thinMargins={thinMargins}
@@ -514,6 +542,7 @@ interface EditorShellInnerProps {
514
542
  toolbarSlotRight?: ReactNode;
515
543
  showPlayTab: boolean;
516
544
  submitOnEnter?: () => void;
545
+ codeContext?: CodeContext;
517
546
  fullWidth: boolean;
518
547
  uxFont?: string;
519
548
  thinMargins: boolean;
@@ -526,7 +555,7 @@ interface EditorShellInnerProps {
526
555
  allowVersioning: boolean;
527
556
  versioningAutoSaveIdleMs?: number;
528
557
  inlinePreviewWidth: number;
529
- outlineWidth: number;
558
+ outlineWidth?: number;
530
559
  themeOverride: Theme | null;
531
560
  }
532
561
 
@@ -546,6 +575,7 @@ function EditorShellInner({
546
575
  toolbarSlotRight,
547
576
  showPlayTab,
548
577
  submitOnEnter,
578
+ codeContext,
549
579
  fullWidth,
550
580
  uxFont,
551
581
  thinMargins,
@@ -565,7 +595,7 @@ function EditorShellInner({
565
595
  activeView,
566
596
  markdownSource,
567
597
  doc,
568
- theme,
598
+ colorScheme,
569
599
  editorMode,
570
600
  insertAtCursor,
571
601
  replaceAll,
@@ -575,14 +605,31 @@ function EditorShellInner({
575
605
  inlinePreviewVisible,
576
606
  statusBarVisible,
577
607
  outlineVisible,
608
+ layoutMode,
609
+ blockCount,
610
+ activeBlockKey,
611
+ prevBlock,
612
+ nextBlock,
613
+ addBlock,
578
614
  imageEditTarget,
579
615
  closeImageEdit,
580
616
  bumpMediaRevision,
581
617
  } = useEditorContext();
618
+ // Dual-catalog custom themes (doc + browser library), mirroring how
619
+ // CustomTemplateProvider is fed. Wraps the preview subtree so the theme
620
+ // picker + designer can read/write both pools.
621
+ const { docThemes, onDocThemesChange } = useDocCustomThemes();
582
622
  const isPreview = activeView === 'preview';
583
623
  const isCodeMode = editorMode === 'code';
584
624
  const isImageMode = editorMode === 'image';
585
625
  const isMarkdownMode = editorMode === 'markdown';
626
+ // The block card (one block at a time) applies to the editing surfaces (raw /
627
+ // wysiwyg) in markdown mode — never to preview, code, or image. Both the
628
+ // block-at-a-time and timeline layouts scope the editor to a single block.
629
+ const isCardMode =
630
+ isMarkdownMode && (layoutMode === 'block' || layoutMode === 'timeline') && !isPreview;
631
+ // Timeline mode additionally shows the horizontal timeline track below.
632
+ const isTimelineMode = isMarkdownMode && layoutMode === 'timeline' && !isPreview;
586
633
  const [showFiles, setShowFiles] = useState(false);
587
634
  const [mediaRefreshKey, setMediaRefreshKey] = useState(0);
588
635
  // Persistent fallback container for image-edit sidecars when the host
@@ -595,7 +642,7 @@ function EditorShellInner({
595
642
  imageEditFallbackContainerRef.current = new MemoryContentContainer();
596
643
  }
597
644
  const imageEditFallbackContainer = imageEditFallbackContainerRef.current;
598
- const isDark = theme === 'dark';
645
+ const isDark = colorScheme === 'dark';
599
646
 
600
647
  const handleToggleFiles = useCallback(() => {
601
648
  setShowFiles((prev) => !prev);
@@ -736,7 +783,7 @@ function EditorShellInner({
736
783
  return (
737
784
  <div
738
785
  className={`squisq-editor-shell ${className || ''}`}
739
- data-theme={theme}
786
+ data-theme={colorScheme}
740
787
  data-full-width={fullWidth ? 'true' : undefined}
741
788
  data-thin-margins={thinMargins ? 'true' : undefined}
742
789
  data-outline-visible={isMarkdownMode && outlineVisible ? 'true' : undefined}
@@ -750,145 +797,213 @@ function EditorShellInner({
750
797
  // tabs, status bar) consume `--squisq-ux-font` as their
751
798
  // `font-family`, falling back to the system stack when unset.
752
799
  ...(uxFont ? ({ '--squisq-ux-font': uxFont } as CSSProperties) : {}),
753
- // Exposed so the toolbar's view-tabs section can match the outline
754
- // pane's width, lining up its right-edge separator with the
755
- // outline's right edge. The variable is set unconditionally so the
756
- // outline pane itself can also read it if needed; the
757
- // `data-outline-visible` gate above keeps the toolbar override
758
- // scoped to the case where alignment matters.
759
- ...({ '--squisq-outline-width': `${outlineWidth}px` } as CSSProperties),
800
+ // Exposed so the toolbar's view-tabs section AND the outline pane read
801
+ // one shared width, lining up the view-tabs' right-edge separator with
802
+ // the outline's right edge. A fixed `outlineWidth` pins it to px;
803
+ // otherwise it's the responsive clamp so the pane stretches when
804
+ // there's room. The `data-outline-visible` gate above keeps the
805
+ // toolbar override scoped to the case where alignment matters.
806
+ ...({
807
+ '--squisq-outline-width':
808
+ outlineWidth != null ? `${outlineWidth}px` : OUTLINE_RESPONSIVE_WIDTH,
809
+ } as CSSProperties),
760
810
  }}
761
811
  {...containerProps}
762
812
  >
763
- <PreviewSettingsProvider doc={doc} themeOverride={themeOverride}>
764
- {/* Header. In image mode the full markdown/code Toolbar is replaced
813
+ <CustomThemeProvider docThemes={docThemes} onDocThemesChange={onDocThemesChange}>
814
+ <PreviewSettingsProvider doc={doc} themeOverride={themeOverride}>
815
+ {/* Header. In image mode the full markdown/code Toolbar is replaced
765
816
  with a minimal slot bar — view tabs, formatting, and preview
766
817
  controls don't apply to a binary asset. */}
767
- {isImageMode ? (
768
- (toolbarSlotLeft || toolbarSlotRight) && (
769
- <div className="squisq-editor-header squisq-editor-header--image">
770
- {toolbarSlotLeft}
771
- <div style={{ flex: 1 }} />
772
- {toolbarSlotRight}
818
+ {isImageMode ? (
819
+ (toolbarSlotLeft || toolbarSlotRight) && (
820
+ <div className="squisq-editor-header squisq-editor-header--image">
821
+ {toolbarSlotLeft}
822
+ <div style={{ flex: 1 }} />
823
+ {toolbarSlotRight}
824
+ </div>
825
+ )
826
+ ) : (
827
+ <div className="squisq-editor-header">
828
+ <Toolbar
829
+ showFiles={showFiles}
830
+ onToggleFiles={!isCodeMode && filesToggleEnabled ? handleToggleFiles : undefined}
831
+ slotLeft={toolbarSlotLeft}
832
+ slotAfterTabs={
833
+ !isCodeMode &&
834
+ isPreview && (
835
+ <div className="squisq-preview-left-controls">
836
+ <PreviewModeSwitch />
837
+ <span className="squisq-preview-seg-divider" aria-hidden="true" />
838
+ <PreviewFormatSwitch />
839
+ </div>
840
+ )
841
+ }
842
+ slotAfterActions={
843
+ <>
844
+ {toolbarSlotAfterActions}
845
+ {!isCodeMode && isPreview && <PreviewToolbarControls />}
846
+ </>
847
+ }
848
+ slotRight={toolbarSlotRight}
849
+ showPlayTab={showPlayTab}
850
+ />
773
851
  </div>
774
- )
775
- ) : (
776
- <div className="squisq-editor-header">
777
- <Toolbar
778
- showFiles={showFiles}
779
- onToggleFiles={!isCodeMode && filesToggleEnabled ? handleToggleFiles : undefined}
780
- slotLeft={toolbarSlotLeft}
781
- slotAfterActions={
782
- <>
783
- {toolbarSlotAfterActions}
784
- {!isCodeMode && isPreview && <PreviewToolbarControls />}
785
- </>
786
- }
787
- slotRight={toolbarSlotRight}
788
- showPlayTab={showPlayTab}
789
- />
790
- </div>
791
- )}
852
+ )}
792
853
 
793
- {/* Main content area */}
794
- <div
795
- className="squisq-editor-content"
796
- style={{
797
- flex: autoGrow ? '1 1 auto' : 1,
798
- overflowY: autoGrow ? 'auto' : 'hidden',
799
- overflowX: 'hidden',
800
- minHeight: 0,
801
- position: 'relative',
802
- display: 'flex',
803
- }}
804
- >
854
+ {/* Main content area */}
805
855
  <div
856
+ className="squisq-editor-content"
806
857
  style={{
807
858
  flex: autoGrow ? '1 1 auto' : 1,
808
- overflow: autoGrow ? 'visible' : 'hidden',
859
+ overflowY: autoGrow ? 'auto' : 'hidden',
860
+ overflowX: 'hidden',
809
861
  minHeight: 0,
810
862
  position: 'relative',
863
+ display: 'flex',
811
864
  }}
812
865
  >
813
- {isImageMode &&
814
- imageSrc &&
815
- (imageMode === 'edit' && imageEditorContainer ? (
816
- <ImageEditor
817
- filesContainer={imageEditorContainer}
818
- initialSrc={imageSrc}
819
- allowVersioning={allowVersioning}
820
- versioningAutoSaveIdleMs={versioningAutoSaveIdleMs}
821
- onExport={onImageExport}
822
- />
823
- ) : (
824
- <ImageViewer src={imageSrc} alt={imageAlt} theme={theme} />
825
- ))}
826
- {/* Raw (Monaco) view. Always wrapped in `.squisq-editor-with-gutter`
866
+ <div
867
+ style={{
868
+ flex: autoGrow ? '1 1 auto' : 1,
869
+ overflow: autoGrow ? 'visible' : 'hidden',
870
+ minHeight: 0,
871
+ position: 'relative',
872
+ }}
873
+ >
874
+ {isImageMode &&
875
+ imageSrc &&
876
+ (imageMode === 'edit' && imageEditorContainer ? (
877
+ <ImageEditor
878
+ filesContainer={imageEditorContainer}
879
+ initialSrc={imageSrc}
880
+ allowVersioning={allowVersioning}
881
+ versioningAutoSaveIdleMs={versioningAutoSaveIdleMs}
882
+ onExport={onImageExport}
883
+ />
884
+ ) : (
885
+ <ImageViewer src={imageSrc} alt={imageAlt} theme={colorScheme} />
886
+ ))}
887
+ {/* Raw (Monaco) view. Always wrapped in `.squisq-editor-with-gutter`
827
888
  so toggling a pane on/off doesn't change the editor's tree
828
889
  position — Monaco stays mounted and `monacoEditor` in
829
890
  context stays stable, which is what `useHeadingLayout` needs
830
891
  to compute positions. */}
831
- {!isImageMode && activeView === 'raw' && (
832
- <div className="squisq-editor-with-gutter" key="raw-shell">
833
- {isMarkdownMode && outlineVisible && (
834
- <OutlinePanel key="outline" width={outlineWidth} />
835
- )}
836
- <div key="raw-editor" className="squisq-raw-editor-container">
837
- <RawEditor
838
- theme={theme === 'dark' ? 'vs-dark' : 'vs'}
839
- submitOnEnter={submitOnEnter}
840
- readOnly={readOnly}
841
- />
892
+ {!isImageMode && activeView === 'raw' && (
893
+ <div className="squisq-editor-with-gutter" key="raw-shell">
894
+ {isMarkdownMode && outlineVisible && (
895
+ <OutlinePanel key="outline" width={outlineWidth} />
896
+ )}
897
+ {isCardMode ? (
898
+ <BlockCardView
899
+ key="raw-card"
900
+ blockCount={blockCount}
901
+ activeBlockKey={activeBlockKey}
902
+ onPrev={prevBlock}
903
+ onNext={nextBlock}
904
+ onAdd={addBlock}
905
+ >
906
+ <div key="raw-editor" className="squisq-raw-editor-container">
907
+ <RawEditor
908
+ monacoTheme={colorScheme === 'dark' ? 'vs-dark' : 'vs'}
909
+ submitOnEnter={submitOnEnter}
910
+ readOnly={readOnly}
911
+ />
912
+ </div>
913
+ </BlockCardView>
914
+ ) : (
915
+ <div key="raw-editor" className="squisq-raw-editor-container">
916
+ <RawEditor
917
+ monacoTheme={colorScheme === 'dark' ? 'vs-dark' : 'vs'}
918
+ submitOnEnter={submitOnEnter}
919
+ readOnly={readOnly}
920
+ />
921
+ </div>
922
+ )}
923
+ {/* Renders nothing in normal flow — portals context
924
+ sections into Monaco view zones via the context's
925
+ monacoEditor. Code mode only. */}
926
+ {isCodeMode && codeContext && (
927
+ <CodeContextZones key="code-context" options={codeContext} />
928
+ )}
929
+ {isMarkdownMode && isCardMode && inlinePreviewVisible && (
930
+ <BlockPreviewPanel key="block-preview" basePath={basePath} />
931
+ )}
932
+ {isMarkdownMode && !isCardMode && inlinePreviewVisible && (
933
+ <InlinePreviewGutter
934
+ key="inline"
935
+ width={inlinePreviewWidth}
936
+ basePath={basePath}
937
+ mediaProvider={mediaProvider}
938
+ />
939
+ )}
842
940
  </div>
843
- {isMarkdownMode && inlinePreviewVisible && (
844
- <InlinePreviewGutter
845
- key="inline"
846
- width={inlinePreviewWidth}
847
- basePath={basePath}
848
- mediaProvider={mediaProvider}
849
- />
850
- )}
851
- </div>
852
- )}
853
- {/* WYSIWYG + Preview are markdown-only surfaces — skip them
941
+ )}
942
+ {/* WYSIWYG + Preview are markdown-only surfaces — skip them
854
943
  entirely in code or image mode so Tiptap never initializes
855
944
  and the preview pipeline stays idle. Same always-wrapped
856
945
  pattern as the Raw branch above so pane toggles don't
857
946
  remount Tiptap. */}
858
- {isMarkdownMode && activeView === 'wysiwyg' && (
859
- <div className="squisq-editor-with-gutter" key="wysiwyg-shell">
860
- {outlineVisible && <OutlinePanel key="outline" width={outlineWidth} />}
861
- <WysiwygEditor
862
- key="wysiwyg-editor"
863
- submitOnEnter={submitOnEnter}
864
- placeholder={placeholder}
865
- readOnly={readOnly}
866
- />
867
- {inlinePreviewVisible && (
868
- <InlinePreviewGutter
869
- key="inline"
870
- width={inlinePreviewWidth}
871
- basePath={basePath}
872
- mediaProvider={mediaProvider}
873
- />
874
- )}
875
- </div>
876
- )}
877
- {isMarkdownMode && isPreview && (
878
- <PreviewPanel basePath={basePath} workspaceContainer={workspaceContainer} />
947
+ {isMarkdownMode && activeView === 'wysiwyg' && (
948
+ <div className="squisq-editor-with-gutter" key="wysiwyg-shell">
949
+ {outlineVisible && <OutlinePanel key="outline" width={outlineWidth} />}
950
+ {isCardMode ? (
951
+ <BlockCardView
952
+ key="wysiwyg-card"
953
+ blockCount={blockCount}
954
+ activeBlockKey={activeBlockKey}
955
+ onPrev={prevBlock}
956
+ onNext={nextBlock}
957
+ onAdd={addBlock}
958
+ >
959
+ <WysiwygEditor
960
+ key="wysiwyg-editor"
961
+ submitOnEnter={submitOnEnter}
962
+ placeholder={placeholder}
963
+ readOnly={readOnly}
964
+ />
965
+ </BlockCardView>
966
+ ) : (
967
+ <WysiwygEditor
968
+ key="wysiwyg-editor"
969
+ submitOnEnter={submitOnEnter}
970
+ placeholder={placeholder}
971
+ readOnly={readOnly}
972
+ />
973
+ )}
974
+ {isCardMode && inlinePreviewVisible && (
975
+ <BlockPreviewPanel key="block-preview" basePath={basePath} />
976
+ )}
977
+ {!isCardMode && inlinePreviewVisible && (
978
+ <InlinePreviewGutter
979
+ key="inline"
980
+ width={inlinePreviewWidth}
981
+ basePath={basePath}
982
+ mediaProvider={mediaProvider}
983
+ />
984
+ )}
985
+ </div>
986
+ )}
987
+ {isMarkdownMode && isPreview && (
988
+ <PreviewPanel basePath={basePath} workspaceContainer={workspaceContainer} />
989
+ )}
990
+ </div>
991
+
992
+ {isMarkdownMode && showFiles && (
993
+ <MediaBin
994
+ mediaProvider={mediaProvider}
995
+ isDark={isDark}
996
+ refreshKey={mediaRefreshKey}
997
+ onMediaUploaded={insertMediaRef}
998
+ />
879
999
  )}
880
- </div>
881
1000
 
882
- {isMarkdownMode && showFiles && (
883
- <MediaBin
884
- mediaProvider={mediaProvider}
885
- isDark={isDark}
886
- refreshKey={mediaRefreshKey}
887
- onMediaUploaded={insertMediaRef}
888
- />
889
- )}
1001
+ {/* Docked custom-theme designer — a flex sibling of the preview, so
1002
+ opening it reflows the preview narrower. Renders nothing when the
1003
+ designer is closed. */}
1004
+ {isMarkdownMode && <ThemeDesignerDock />}
890
1005
 
891
- {/* Drop zone overlay — image / text drop UX is markdown-specific.
1006
+ {/* Drop zone overlay — image / text drop UX is markdown-specific.
892
1007
  In WYSIWYG, image drops are handled directly by Tiptap's
893
1008
  `handleDrop` (uploads to the MediaProvider and inserts an
894
1009
  image node at the mouse position). The overlay would sit on
@@ -896,23 +1011,29 @@ function EditorShellInner({
896
1011
  when the dragged content is media-only on the WYSIWYG view —
897
1012
  the user gets a one-step "drop where you want it" flow
898
1013
  instead of a two-step "drop in bin, then insert" flow. */}
899
- {isMarkdownMode &&
900
- isDragging &&
901
- !(activeView === 'wysiwyg' && dragContentType === 'media') && (
902
- <DropZoneOverlay
903
- dragContentType={dragContentType}
904
- zoneProps={zoneProps}
905
- hasMediaProvider={mediaProvider !== null}
906
- />
907
- )}
908
- </div>
1014
+ {isMarkdownMode &&
1015
+ isDragging &&
1016
+ !(activeView === 'wysiwyg' && dragContentType === 'media') && (
1017
+ <DropZoneOverlay
1018
+ dragContentType={dragContentType}
1019
+ zoneProps={zoneProps}
1020
+ hasMediaProvider={mediaProvider !== null}
1021
+ />
1022
+ )}
1023
+ </div>
1024
+
1025
+ {/* Timeline track — horizontal strip of block + media bars shown
1026
+ below the editor in Timeline view. The card editor above (driven by
1027
+ the block navigator) shows whichever block is selected here. */}
1028
+ {isTimelineMode && <TimelineTrack />}
909
1029
 
910
- {/* Status bar — word / char / line / block counts. Host can
1030
+ {/* Status bar — word / char / line / block counts. Host can
911
1031
  suppress via `showStatusBar={false}` for embedded chat-style
912
1032
  composers where the stats are noise. The image viewer has its
913
1033
  own dimension/zoom status row, so suppress here too. */}
914
- {statusBarVisible && !isImageMode && <StatusBar />}
915
- </PreviewSettingsProvider>
1034
+ {statusBarVisible && !isImageMode && <StatusBar />}
1035
+ </PreviewSettingsProvider>
1036
+ </CustomThemeProvider>
916
1037
  <TooltipLayer />
917
1038
  {imageEditTarget !== null && mediaProvider && (
918
1039
  <ImageEditModal
@@ -926,7 +1047,7 @@ function EditorShellInner({
926
1047
  }}
927
1048
  allowVersioning={allowVersioning}
928
1049
  versioningAutoSaveIdleMs={versioningAutoSaveIdleMs}
929
- shellTheme={theme}
1050
+ shellTheme={colorScheme}
930
1051
  />
931
1052
  )}
932
1053
  </div>
@@ -951,8 +1072,8 @@ interface ImageEditModalProps {
951
1072
  onSaved: () => void;
952
1073
  allowVersioning: boolean;
953
1074
  versioningAutoSaveIdleMs?: number;
954
- /** EditorShell's `theme` prop — 'light' or 'dark'. Threaded through so
955
- * the image editor chrome matches the host shell. */
1075
+ /** EditorShell's `colorScheme` prop — 'light' or 'dark'. Threaded through
1076
+ * so the image editor chrome matches the host shell. */
956
1077
  shellTheme?: 'light' | 'dark';
957
1078
  }
958
1079