@bendyline/squisq-editor-react 1.6.2 → 2.0.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.
- package/README.md +12 -3
- package/dist/index.d.ts +1275 -183
- package/dist/index.js +20345 -12777
- package/dist/index.js.map +1 -1
- package/dist/monaco.d.ts +1 -0
- package/dist/monaco.js +4 -0
- package/dist/monaco.js.map +1 -0
- package/dist/styles/index.css +1607 -241
- package/package.json +9 -5
- package/src/BlockPropertiesPopover.tsx +30 -15
- package/src/DocumentSettingsDialog.tsx +39 -33
- package/src/EditorContext.tsx +199 -42
- package/src/EditorShell.tsx +349 -261
- package/src/ImageEditor.tsx +22 -1
- package/src/InlinePreviewGutter.tsx +16 -8
- package/src/MediaBin.tsx +107 -14
- package/src/OutlinePanel.tsx +202 -7
- package/src/PlainHtmlPreview.tsx +40 -1
- package/src/PreviewControls.tsx +362 -89
- package/src/PreviewPanel.tsx +312 -78
- package/src/RawEditor.tsx +13 -2
- package/src/RecorderEntry.tsx +2 -0
- package/src/StatusBar.tsx +5 -2
- package/src/TemplateAnnotation.ts +12 -3
- package/src/TemplatePicker.tsx +75 -21
- package/src/ThemeCustomizerPanel.tsx +8 -0
- package/src/ThemePicker.tsx +10 -5
- package/src/TimelineBlockPreview.tsx +1 -1
- package/src/Toolbar.tsx +357 -51
- package/src/TransitionPicker.tsx +36 -4
- package/src/ViewMenuPanel.tsx +12 -14
- package/src/WysiwygEditor.tsx +124 -91
- package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +33 -0
- package/src/__tests__/blockTagActivity.test.ts +183 -0
- package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +75 -0
- package/src/__tests__/buildPreviewDocContent.test.ts +48 -0
- package/src/__tests__/buildPreviewDocTransition.test.ts +21 -1
- package/src/__tests__/canvasSurfaceTextEditing.test.tsx +60 -0
- package/src/__tests__/codeContextSectionView.test.tsx +8 -6
- package/src/__tests__/customLayoutManagerTheme.test.tsx +34 -0
- package/src/__tests__/documentSettingsDialog.test.tsx +51 -1
- package/src/__tests__/editorScrollHandoff.test.tsx +102 -0
- package/src/__tests__/editorScrollSync.test.ts +65 -0
- package/src/__tests__/editorShellProps.test.tsx +123 -1
- package/src/__tests__/emojiPicker.test.tsx +2 -42
- package/src/__tests__/headingTransition.test.ts +0 -12
- package/src/__tests__/imageEditAffordance.test.tsx +5 -1
- package/src/__tests__/imageEditModalContract.test.tsx +119 -0
- package/src/__tests__/imageEditorShell.test.tsx +15 -1
- package/src/__tests__/jsonEditor.test.tsx +56 -1
- package/src/__tests__/layersPanel.test.tsx +75 -5
- package/src/__tests__/majorApiTypes.test.ts +26 -0
- package/src/__tests__/mediaAttachmentFlow.test.ts +2 -2
- package/src/__tests__/mediaBinDrop.test.tsx +90 -0
- package/src/__tests__/narrationSave.test.ts +148 -0
- package/src/__tests__/outlinePanel.test.tsx +93 -3
- package/src/__tests__/outlineSource.test.ts +217 -0
- package/src/__tests__/plainHtmlPreview.test.tsx +17 -1
- package/src/__tests__/presentationMode.test.tsx +290 -0
- package/src/__tests__/previewControls.test.tsx +382 -5
- package/src/__tests__/previewPanelAudioFailure.test.tsx +46 -0
- package/src/__tests__/previewPanelPresentation.test.tsx +126 -0
- package/src/__tests__/previewPanelTransforms.test.tsx +117 -0
- package/src/__tests__/rawEditorModelIsolation.test.ts +19 -0
- package/src/__tests__/recorderTheme.test.tsx +42 -0
- package/src/__tests__/resolveBlockVisual.test.ts +43 -0
- package/src/__tests__/selectionConversions.test.ts +80 -0
- package/src/__tests__/statusBar.test.tsx +27 -0
- package/src/__tests__/teleprompter.test.tsx +190 -0
- package/src/__tests__/templateAnnotationRoundTrip.test.ts +26 -0
- package/src/__tests__/templatePickerPortal.test.tsx +62 -0
- package/src/__tests__/tiptapBridge.test.ts +71 -7
- package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +109 -0
- package/src/__tests__/tiptapImageRoundTrip.test.ts +1 -1
- package/src/__tests__/toolbarSelectionConversion.test.tsx +164 -0
- package/src/__tests__/transformStyleId.test.ts +13 -0
- package/src/__tests__/useImageEditor.test.tsx +67 -0
- package/src/__tests__/useMediaRecorder.test.ts +75 -0
- package/src/__tests__/viewMenuPanel.test.tsx +130 -0
- package/src/__tests__/wysiwygImageUpload.test.ts +69 -0
- package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +56 -0
- package/src/asciiDiagram/AsciiDiagramExtension.ts +343 -0
- package/src/asciiDiagram/AsciiDiagramWidget.tsx +254 -0
- package/src/asciiDiagram/RepairableDiagramExtension.ts +200 -0
- package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +145 -0
- package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +159 -0
- package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +284 -0
- package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +114 -0
- package/src/asciiDiagram/__tests__/asciiPaste.test.ts +43 -0
- package/src/asciiDiagram/asciiDiagramCommands.ts +174 -0
- package/src/asciiDiagram/asciiDiagramData.ts +149 -0
- package/src/asciiDiagram/asciiDiagramOps.ts +238 -0
- package/src/asciiDiagram/asciiPaste.ts +19 -0
- package/src/blockTagActivity.ts +233 -0
- package/src/buildDocumentPreviewMarkdown.ts +168 -0
- package/src/buildPreviewDoc.ts +19 -12
- package/src/codeContext/types.ts +1 -1
- package/src/customTemplates/CustomLayoutManager.tsx +3 -2
- package/src/customTemplates/CustomTemplateContext.tsx +6 -0
- package/src/customTemplates/TemplateDesigner.tsx +7 -4
- package/src/customTemplates/__tests__/library.test.ts +8 -0
- package/src/customTemplates/__tests__/useMemoryLayerAdapter.test.ts +13 -0
- package/src/customTemplates/designer.css +158 -99
- package/src/customTemplates/library.ts +16 -3
- package/src/customTemplates/useMemoryLayerAdapter.ts +7 -1
- package/src/customThemes/CustomThemeContext.tsx +6 -0
- package/src/customThemes/CustomThemeDialog.tsx +35 -5
- package/src/customThemes/ImportThemeSection.tsx +178 -0
- package/src/customThemes/__tests__/customThemeLibrary.test.ts +9 -0
- package/src/customThemes/__tests__/importThemeSection.test.tsx +192 -0
- package/src/customThemes/customThemeLibrary.ts +7 -4
- package/src/customThemes/index.ts +7 -1
- package/src/customThemes/themeDraft.ts +23 -7
- package/src/diagram/DiagramCanvas.tsx +25 -6
- package/src/diagram/types.ts +35 -0
- package/src/editorScrollSync.ts +73 -0
- package/src/emojiData.ts +3 -23
- package/src/frontmatterSettings.ts +23 -0
- package/src/headingTransition.ts +0 -15
- package/src/imageEditor/CanvasSurface.tsx +29 -23
- package/src/imageEditor/LayersPanel.tsx +78 -3
- package/src/imageEditor/Toolbar.tsx +5 -1
- package/src/imageEditor/icons.tsx +4 -0
- package/src/imageEditor/image-editor.css +75 -0
- package/src/imageEditor/layers/SelectionHandles.tsx +1 -1
- package/src/imageEditor/useImageEditor.ts +71 -14
- package/src/index.ts +198 -20
- package/src/jsonEditor/JsonEditorContext.tsx +12 -6
- package/src/jsonEditor/RenderNode.tsx +24 -3
- package/src/jsonEditor/editors.tsx +86 -19
- package/src/mediaEntries.ts +12 -0
- package/src/monaco.ts +35 -0
- package/src/monacoWorkers.ts +89 -0
- package/src/outlineSource.ts +171 -0
- package/src/presentation/PresentationMode.tsx +596 -0
- package/src/rawEditorIsolation.ts +18 -0
- package/src/recorder/RecorderButton.tsx +9 -1
- package/src/recorder/RecorderModal.tsx +84 -41
- package/src/recorder/RecorderPanel.tsx +9 -1
- package/src/recorder/hooks/useMediaRecorder.ts +97 -53
- package/src/resolveBlockVisual.ts +10 -4
- package/src/scene/Scene.tsx +48 -18
- package/src/scene/SceneBlockExtension.ts +17 -10
- package/src/scene/SceneBlockWidget.tsx +5 -1
- package/src/scene/SceneViewport.tsx +7 -3
- package/src/scene/ShapePalette.tsx +17 -2
- package/src/scene/__tests__/DiagramAdapter.test.ts +86 -0
- package/src/scene/__tests__/SceneBlockExtension.test.ts +33 -0
- package/src/scene/__tests__/sceneIsolation.test.tsx +119 -0
- package/src/scene/adapters/DiagramAdapter.ts +12 -101
- package/src/scene/adapters/DrawingAdapter.ts +6 -1
- package/src/scene/adapters/LayoutAdapter.ts +3 -0
- package/src/scene/commands/SceneCommand.ts +17 -3
- package/src/scene/index.ts +1 -6
- package/src/scene/layers/DiagramEdges.tsx +21 -7
- package/src/scene/layers/edgeGeometry.ts +9 -1
- package/src/scene/layers/nodeCard.tsx +27 -8
- package/src/scene/scene.css +5 -2
- package/src/scene/text/SceneTextOverlay.tsx +5 -5
- package/src/scene/text/sceneTextChannel.ts +26 -20
- package/src/scene/text/sceneTextConfig.ts +4 -0
- package/src/scene/tools/ConnectTool.ts +13 -4
- package/src/scene/tools/SceneTool.ts +8 -0
- package/src/scene/tools/SelectTool.ts +39 -23
- package/src/scene/tools/ShapeTool.ts +2 -3
- package/src/selectionConversions.ts +155 -0
- package/src/styles/ascii-diagram.css +79 -0
- package/src/styles/ascii-timeline.css +596 -0
- package/src/styles/editor.css +736 -120
- package/src/styles/index.css +3 -0
- package/src/styles/tree-view.css +189 -0
- package/src/teleprompter/TeleprompterControls.tsx +218 -0
- package/src/teleprompter/TeleprompterSelfView.tsx +22 -0
- package/src/teleprompter/TeleprompterSurface.tsx +267 -0
- package/src/teleprompter/TeleprompterView.tsx +338 -0
- package/src/teleprompter/canvasRenderer.ts +161 -0
- package/src/teleprompter/floatingWindow.ts +352 -0
- package/src/teleprompter/index.ts +61 -0
- package/src/teleprompter/pcmWorklet.ts +62 -0
- package/src/teleprompter/recording/insertPreamble.ts +80 -0
- package/src/teleprompter/recording/narrationSave.ts +134 -0
- package/src/teleprompter/recording/useNarrationRecorder.ts +367 -0
- package/src/teleprompter/scrollModel.ts +85 -0
- package/src/teleprompter/teleprompterTheme.ts +303 -0
- package/src/teleprompter/types.ts +38 -0
- package/src/teleprompter/useFloatingWindow.ts +74 -0
- package/src/teleprompter/useMicAnalysis.ts +211 -0
- package/src/teleprompter/useTeleprompter.ts +421 -0
- package/src/templateContentPreviewResolver.ts +12 -5
- package/src/timeline/TimelineEditorWidget.tsx +861 -0
- package/src/timeline/TimelineViewExtension.ts +252 -0
- package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +414 -0
- package/src/timeline/__tests__/TimelineViewExtension.test.ts +163 -0
- package/src/timeline/__tests__/timelineCommands.test.ts +359 -0
- package/src/timeline/__tests__/timelineOps.test.ts +277 -0
- package/src/timeline/__tests__/timelinePaste.test.ts +34 -0
- package/src/timeline/timelineCommands.ts +421 -0
- package/src/timeline/timelineData.ts +54 -0
- package/src/timeline/timelineOps.ts +381 -0
- package/src/timeline/timelinePaste.ts +8 -0
- package/src/tiptapBridge.ts +47 -21
- package/src/transformStyleId.ts +17 -0
- package/src/treeview/TreeOutlineWidget.tsx +390 -0
- package/src/treeview/TreeViewExtension.ts +250 -0
- package/src/treeview/__tests__/TreeOutlineWidget.test.tsx +156 -0
- package/src/treeview/__tests__/TreeViewExtension.test.ts +122 -0
- package/src/treeview/__tests__/treeOps.test.ts +177 -0
- package/src/treeview/__tests__/treePaste.test.ts +40 -0
- package/src/treeview/__tests__/treeViewCommands.test.ts +179 -0
- package/src/treeview/treeOps.ts +231 -0
- package/src/treeview/treePaste.ts +13 -0
- package/src/treeview/treeViewCommands.ts +99 -0
- package/src/treeview/treeViewData.ts +41 -0
- package/src/useMonacoLoader.ts +11 -34
- package/src/wysiwygImageUpload.ts +113 -0
- package/src/diagram/DiagramExtension.ts +0 -209
- package/src/diagram/DiagramWidget.tsx +0 -272
- package/src/diagram/useDiagramData.ts +0 -126
package/src/EditorContext.tsx
CHANGED
|
@@ -33,7 +33,17 @@ import type { editor as MonacoEditorNs } from 'monaco-editor';
|
|
|
33
33
|
import { markdownToTiptap } from './tiptapBridge';
|
|
34
34
|
import { resolveFileKind } from './fileKind';
|
|
35
35
|
import { useBlockNavigator } from './useBlockNavigator';
|
|
36
|
-
import {
|
|
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';
|
|
37
47
|
|
|
38
48
|
/** Monaco standalone code editor instance type */
|
|
39
49
|
type MonacoEditor = MonacoEditorNs.IStandaloneCodeEditor;
|
|
@@ -114,6 +124,11 @@ export type LayoutMode = 'document' | 'block' | 'timeline';
|
|
|
114
124
|
* colors. `'none'` opts out completely.
|
|
115
125
|
*/
|
|
116
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';
|
|
117
132
|
/**
|
|
118
133
|
* Editor operating mode. `markdown` is the full experience (WYSIWYG +
|
|
119
134
|
* Preview tabs, formatting toolbar). `code` is a Monaco-only view used
|
|
@@ -159,12 +174,11 @@ export interface EditorState {
|
|
|
159
174
|
* the toolbar can toggle it at runtime.
|
|
160
175
|
*/
|
|
161
176
|
outlineVisible: boolean;
|
|
177
|
+
/** When inline block-template tags are shown in the WYSIWYG view. */
|
|
178
|
+
blockTagVisibility: BlockTagVisibility;
|
|
162
179
|
/**
|
|
163
|
-
* Whether inline block-template tags
|
|
164
|
-
*
|
|
165
|
-
* headings) are currently visible. Initialized from the EditorShell
|
|
166
|
-
* `blockTags` prop (default true); the View menu in the toolbar can
|
|
167
|
-
* toggle it at runtime.
|
|
180
|
+
* Whether inline block-template tags can currently be visible.
|
|
181
|
+
* Kept for compatibility; prefer {@link blockTagVisibility}.
|
|
168
182
|
*/
|
|
169
183
|
blockTagsVisible: boolean;
|
|
170
184
|
/**
|
|
@@ -196,6 +210,13 @@ export interface EditorState {
|
|
|
196
210
|
* shell.
|
|
197
211
|
*/
|
|
198
212
|
allowRecording: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Whether the Narrate (teleprompter) display mode is offered under the
|
|
215
|
+
* Use tab. Orthogonal to `allowRecording`: the teleprompter is useful
|
|
216
|
+
* without any capture (reading for external recording software), and a
|
|
217
|
+
* host may allow the recorder modal but not want a prompter surface.
|
|
218
|
+
*/
|
|
219
|
+
allowNarrate: boolean;
|
|
199
220
|
/**
|
|
200
221
|
* Document layout mode. `'document'` (default) edits the whole document;
|
|
201
222
|
* `'block'` activates the block-at-a-time card view. Initialized from the
|
|
@@ -256,6 +277,8 @@ export interface EditorActions {
|
|
|
256
277
|
setOutlineVisible: (visible: boolean) => void;
|
|
257
278
|
/** Show or hide inline block-template tags at runtime (driven by the View menu). */
|
|
258
279
|
setBlockTagsVisible: (visible: boolean) => void;
|
|
280
|
+
/** Choose when inline block-template tags are shown. */
|
|
281
|
+
setBlockTagVisibility: (visibility: BlockTagVisibility) => void;
|
|
259
282
|
/** Change how much of the active Squisq theme the WYSIWYG surface mirrors. */
|
|
260
283
|
setThemeInheritance: (mode: ThemeInheritance) => void;
|
|
261
284
|
/** Insert text at the current cursor position in the active editor */
|
|
@@ -292,6 +315,8 @@ export interface EditorContextValue extends EditorState, EditorActions {
|
|
|
292
315
|
* buttons apply (`inline` = marks only; `rich` = headings/lists too).
|
|
293
316
|
*/
|
|
294
317
|
activeSceneText: SceneTextHandle | null;
|
|
318
|
+
/** Instance-owned bridge used by detached scene widget roots. */
|
|
319
|
+
sceneTextChannel: SceneTextChannel;
|
|
295
320
|
/**
|
|
296
321
|
* Workspace-scoped `ContentContainer` for this document — the folder
|
|
297
322
|
* holding the doc, its `_files/` sidecar, sibling documents, and any
|
|
@@ -424,6 +449,12 @@ export interface EditorProviderProps {
|
|
|
424
449
|
* surfaces where camera/screen prompts would be jarring).
|
|
425
450
|
*/
|
|
426
451
|
allowRecording?: boolean;
|
|
452
|
+
/**
|
|
453
|
+
* Whether the Narrate (teleprompter) display mode is offered under the
|
|
454
|
+
* Use tab. Defaults to true. When false the mode button is hidden and a
|
|
455
|
+
* frontmatter-forced `display-mode: narrate` clamps back to video.
|
|
456
|
+
*/
|
|
457
|
+
allowNarrate?: boolean;
|
|
427
458
|
/**
|
|
428
459
|
* File name (e.g. `foo.ts`) or bare extension — used to pick a Monaco
|
|
429
460
|
* language and decide between markdown vs. code mode.
|
|
@@ -447,10 +478,16 @@ export interface EditorProviderProps {
|
|
|
447
478
|
*/
|
|
448
479
|
outline?: boolean;
|
|
449
480
|
/**
|
|
450
|
-
*
|
|
451
|
-
*
|
|
481
|
+
* Legacy initial visibility of inline block-template tags on headings.
|
|
482
|
+
* `true` maps to always visible and `false` maps to hidden. When omitted,
|
|
483
|
+
* {@link blockTagVisibility} defaults to `'active'`.
|
|
452
484
|
*/
|
|
453
485
|
blockTags?: boolean;
|
|
486
|
+
/**
|
|
487
|
+
* Initial block-tag visibility mode. When set, this takes precedence over
|
|
488
|
+
* the legacy boolean {@link blockTags} prop. Defaults to `'active'`.
|
|
489
|
+
*/
|
|
490
|
+
blockTagVisibility?: BlockTagVisibility;
|
|
454
491
|
/**
|
|
455
492
|
* Initial value for how much of the active Squisq theme the WYSIWYG
|
|
456
493
|
* editing surface should mirror. Defaults to `'fonts'` — the
|
|
@@ -468,9 +505,9 @@ export interface EditorProviderProps {
|
|
|
468
505
|
* Bundled view preferences — a serializable JSON blob covering all
|
|
469
506
|
* runtime-toggleable view options. When provided, individual values
|
|
470
507
|
* here override the matching individual props (`inlinePreview`,
|
|
471
|
-
* `showStatusBar`, `outline`). Hosts
|
|
472
|
-
* the blob from their own preferences storage
|
|
473
|
-
* {@link onViewPreferencesChange}.
|
|
508
|
+
* `showStatusBar`, `outline`, `blockTagVisibility`, `blockTags`). Hosts
|
|
509
|
+
* wiring this up typically load the blob from their own preferences storage
|
|
510
|
+
* and pair it with {@link onViewPreferencesChange}.
|
|
474
511
|
*/
|
|
475
512
|
viewPreferences?: ViewPreferences;
|
|
476
513
|
/**
|
|
@@ -498,6 +535,8 @@ export interface ViewPreferences {
|
|
|
498
535
|
showStatusBar?: boolean;
|
|
499
536
|
/** Whether inline block-template tags on headings are visible. */
|
|
500
537
|
blockTags?: boolean;
|
|
538
|
+
/** When inline block-template tags are shown. Takes precedence over `blockTags`. */
|
|
539
|
+
blockTagVisibility?: BlockTagVisibility;
|
|
501
540
|
/** How much of the active Squisq theme the WYSIWYG surface mirrors. */
|
|
502
541
|
themeInheritance?: ThemeInheritance;
|
|
503
542
|
/** Document vs. block-at-a-time layout. */
|
|
@@ -511,6 +550,14 @@ export interface ViewPreferences {
|
|
|
511
550
|
const DEFAULT_PRUNE_POLICY: PrunePolicy = { type: 'keep-last-n', n: 50 };
|
|
512
551
|
const DEFAULT_AUTOSAVE_IDLE_MS = 5_000;
|
|
513
552
|
|
|
553
|
+
function normalizeBlockTagVisibility(
|
|
554
|
+
value: BlockTagVisibility | boolean | undefined,
|
|
555
|
+
): BlockTagVisibility {
|
|
556
|
+
if (value === false || value === 'none') return 'none';
|
|
557
|
+
if (value === undefined || value === 'active') return 'active';
|
|
558
|
+
return 'always';
|
|
559
|
+
}
|
|
560
|
+
|
|
514
561
|
export function EditorProvider({
|
|
515
562
|
initialMarkdown = '',
|
|
516
563
|
initialView = 'raw',
|
|
@@ -527,12 +574,14 @@ export function EditorProvider({
|
|
|
527
574
|
mentionProvider = null,
|
|
528
575
|
documentLinkProvider = null,
|
|
529
576
|
allowRecording = true,
|
|
577
|
+
allowNarrate = true,
|
|
530
578
|
fileName,
|
|
531
579
|
language,
|
|
532
580
|
inlinePreview = false,
|
|
533
581
|
showStatusBar = true,
|
|
534
582
|
outline = false,
|
|
535
|
-
blockTags
|
|
583
|
+
blockTags,
|
|
584
|
+
blockTagVisibility: initialBlockTagVisibility,
|
|
536
585
|
themeInheritance = 'fonts',
|
|
537
586
|
layoutMode = 'document',
|
|
538
587
|
viewPreferences,
|
|
@@ -545,7 +594,12 @@ export function EditorProvider({
|
|
|
545
594
|
const effectiveInlinePreview = viewPreferences?.inlinePreview ?? inlinePreview;
|
|
546
595
|
const effectiveShowStatusBar = viewPreferences?.showStatusBar ?? showStatusBar;
|
|
547
596
|
const effectiveOutline = viewPreferences?.outline ?? outline;
|
|
548
|
-
const
|
|
597
|
+
const effectiveBlockTagVisibility = normalizeBlockTagVisibility(
|
|
598
|
+
viewPreferences?.blockTagVisibility ??
|
|
599
|
+
viewPreferences?.blockTags ??
|
|
600
|
+
initialBlockTagVisibility ??
|
|
601
|
+
blockTags,
|
|
602
|
+
);
|
|
549
603
|
const effectiveThemeInheritance = viewPreferences?.themeInheritance ?? themeInheritance;
|
|
550
604
|
const effectiveLayoutMode = viewPreferences?.layoutMode ?? layoutMode;
|
|
551
605
|
// Resolve once per provider mount. Changing fileName/language after mount
|
|
@@ -564,12 +618,36 @@ export function EditorProvider({
|
|
|
564
618
|
const [activeView, setActiveViewRaw] = useState<EditorView>(
|
|
565
619
|
editorMode === 'markdown' ? initialView : 'raw',
|
|
566
620
|
);
|
|
621
|
+
const activeViewRef = useRef(activeView);
|
|
622
|
+
activeViewRef.current = activeView;
|
|
623
|
+
const tiptapEditorRef = useRef<TiptapEditor | null>(null);
|
|
624
|
+
const monacoEditorRef = useRef<MonacoEditor | null>(null);
|
|
625
|
+
const pendingEditorScrollRef = useRef<{ view: 'raw' | 'wysiwyg'; ratio: number } | null>(null);
|
|
567
626
|
const setActiveView = useCallback(
|
|
568
627
|
(view: EditorView) => {
|
|
569
628
|
// In code mode only the raw view is valid. In image mode no text view
|
|
570
629
|
// is valid at all — ignore any switch attempt.
|
|
571
630
|
if (editorMode === 'code' && view !== 'raw') return;
|
|
572
631
|
if (editorMode === 'image') return;
|
|
632
|
+
|
|
633
|
+
const currentView = activeViewRef.current;
|
|
634
|
+
if (view === currentView) return;
|
|
635
|
+
|
|
636
|
+
// Write and Source are separate editor instances and are unmounted as
|
|
637
|
+
// their tabs are hidden. Capture a proportional document position
|
|
638
|
+
// before the current instance disappears, then restore it after the
|
|
639
|
+
// destination has mounted and completed layout.
|
|
640
|
+
let ratio: number | null = null;
|
|
641
|
+
let targetView: 'raw' | 'wysiwyg' | null = null;
|
|
642
|
+
if (currentView === 'raw' && view === 'wysiwyg' && monacoEditorRef.current) {
|
|
643
|
+
ratio = readMonacoScrollRatio(monacoEditorRef.current);
|
|
644
|
+
targetView = 'wysiwyg';
|
|
645
|
+
} else if (currentView === 'wysiwyg' && view === 'raw' && tiptapEditorRef.current) {
|
|
646
|
+
ratio = readWysiwygScrollRatio(tiptapEditorRef.current);
|
|
647
|
+
targetView = 'raw';
|
|
648
|
+
}
|
|
649
|
+
pendingEditorScrollRef.current =
|
|
650
|
+
ratio === null || targetView === null ? null : { view: targetView, ratio };
|
|
573
651
|
setActiveViewRaw(view);
|
|
574
652
|
},
|
|
575
653
|
[editorMode],
|
|
@@ -581,29 +659,39 @@ export function EditorProvider({
|
|
|
581
659
|
useState<boolean>(effectiveInlinePreview);
|
|
582
660
|
// Sync visibility when the host changes the prop (e.g., toggle from outside).
|
|
583
661
|
useEffect(() => {
|
|
584
|
-
setInlinePreviewVisibleRaw(inlinePreview);
|
|
585
|
-
}, [inlinePreview]);
|
|
662
|
+
if (viewPreferences?.inlinePreview === undefined) setInlinePreviewVisibleRaw(inlinePreview);
|
|
663
|
+
}, [inlinePreview, viewPreferences?.inlinePreview]);
|
|
586
664
|
const [statusBarVisible, setStatusBarVisibleRaw] = useState<boolean>(effectiveShowStatusBar);
|
|
587
665
|
useEffect(() => {
|
|
588
|
-
setStatusBarVisibleRaw(showStatusBar);
|
|
589
|
-
}, [showStatusBar]);
|
|
666
|
+
if (viewPreferences?.showStatusBar === undefined) setStatusBarVisibleRaw(showStatusBar);
|
|
667
|
+
}, [showStatusBar, viewPreferences?.showStatusBar]);
|
|
590
668
|
const [outlineVisible, setOutlineVisibleRaw] = useState<boolean>(effectiveOutline);
|
|
591
669
|
useEffect(() => {
|
|
592
|
-
setOutlineVisibleRaw(outline);
|
|
593
|
-
}, [outline]);
|
|
594
|
-
const [
|
|
670
|
+
if (viewPreferences?.outline === undefined) setOutlineVisibleRaw(outline);
|
|
671
|
+
}, [outline, viewPreferences?.outline]);
|
|
672
|
+
const [blockTagVisibilityState, setBlockTagVisibilityRaw] = useState<BlockTagVisibility>(
|
|
673
|
+
effectiveBlockTagVisibility,
|
|
674
|
+
);
|
|
675
|
+
const blockTagsVisible = blockTagVisibilityState !== 'none';
|
|
595
676
|
useEffect(() => {
|
|
596
|
-
|
|
597
|
-
|
|
677
|
+
if (
|
|
678
|
+
viewPreferences?.blockTagVisibility === undefined &&
|
|
679
|
+
viewPreferences?.blockTags === undefined
|
|
680
|
+
) {
|
|
681
|
+
setBlockTagVisibilityRaw(normalizeBlockTagVisibility(initialBlockTagVisibility ?? blockTags));
|
|
682
|
+
}
|
|
683
|
+
}, [blockTags, initialBlockTagVisibility, viewPreferences]);
|
|
598
684
|
const [themeInheritanceState, setThemeInheritanceRaw] =
|
|
599
685
|
useState<ThemeInheritance>(effectiveThemeInheritance);
|
|
600
686
|
useEffect(() => {
|
|
601
|
-
|
|
602
|
-
|
|
687
|
+
if (viewPreferences?.themeInheritance === undefined) {
|
|
688
|
+
setThemeInheritanceRaw(themeInheritance);
|
|
689
|
+
}
|
|
690
|
+
}, [themeInheritance, viewPreferences?.themeInheritance]);
|
|
603
691
|
const [layoutModeState, setLayoutModeRaw] = useState<LayoutMode>(effectiveLayoutMode);
|
|
604
692
|
useEffect(() => {
|
|
605
|
-
setLayoutModeRaw(layoutMode);
|
|
606
|
-
}, [layoutMode]);
|
|
693
|
+
if (viewPreferences?.layoutMode === undefined) setLayoutModeRaw(layoutMode);
|
|
694
|
+
}, [layoutMode, viewPreferences?.layoutMode]);
|
|
607
695
|
const [imageEditTarget, setImageEditTarget] = useState<string | null>(null);
|
|
608
696
|
const [mediaRevision, setMediaRevision] = useState(0);
|
|
609
697
|
const openImageEdit = useCallback((relativePath: string) => {
|
|
@@ -630,8 +718,10 @@ export function EditorProvider({
|
|
|
630
718
|
if (viewPreferences.outline !== undefined) {
|
|
631
719
|
setOutlineVisibleRaw(viewPreferences.outline);
|
|
632
720
|
}
|
|
633
|
-
if (viewPreferences.
|
|
634
|
-
|
|
721
|
+
if (viewPreferences.blockTagVisibility !== undefined) {
|
|
722
|
+
setBlockTagVisibilityRaw(normalizeBlockTagVisibility(viewPreferences.blockTagVisibility));
|
|
723
|
+
} else if (viewPreferences.blockTags !== undefined) {
|
|
724
|
+
setBlockTagVisibilityRaw(normalizeBlockTagVisibility(viewPreferences.blockTags));
|
|
635
725
|
}
|
|
636
726
|
if (viewPreferences.themeInheritance !== undefined) {
|
|
637
727
|
setThemeInheritanceRaw(viewPreferences.themeInheritance);
|
|
@@ -641,7 +731,7 @@ export function EditorProvider({
|
|
|
641
731
|
}
|
|
642
732
|
}, [viewPreferences]);
|
|
643
733
|
|
|
644
|
-
// Wrap the
|
|
734
|
+
// Wrap the view setters so user-driven changes emit a snapshot via
|
|
645
735
|
// `onViewPreferencesChange`. Refs hold the latest values + callback so
|
|
646
736
|
// each wrapper can build a current snapshot without re-creating itself
|
|
647
737
|
// on every state change (the setters are kept referentially stable for
|
|
@@ -654,8 +744,8 @@ export function EditorProvider({
|
|
|
654
744
|
statusBarRef.current = statusBarVisible;
|
|
655
745
|
const outlineRef = useRef(outlineVisible);
|
|
656
746
|
outlineRef.current = outlineVisible;
|
|
657
|
-
const
|
|
658
|
-
|
|
747
|
+
const blockTagVisibilityRef = useRef(blockTagVisibilityState);
|
|
748
|
+
blockTagVisibilityRef.current = blockTagVisibilityState;
|
|
659
749
|
const themeInheritanceRef = useRef(themeInheritanceState);
|
|
660
750
|
themeInheritanceRef.current = themeInheritanceState;
|
|
661
751
|
const layoutModeRef = useRef(layoutModeState);
|
|
@@ -666,7 +756,8 @@ export function EditorProvider({
|
|
|
666
756
|
inlinePreview: visible,
|
|
667
757
|
showStatusBar: statusBarRef.current,
|
|
668
758
|
outline: outlineRef.current,
|
|
669
|
-
blockTags:
|
|
759
|
+
blockTags: blockTagVisibilityRef.current !== 'none',
|
|
760
|
+
blockTagVisibility: blockTagVisibilityRef.current,
|
|
670
761
|
themeInheritance: themeInheritanceRef.current,
|
|
671
762
|
layoutMode: layoutModeRef.current,
|
|
672
763
|
});
|
|
@@ -677,7 +768,8 @@ export function EditorProvider({
|
|
|
677
768
|
inlinePreview: inlinePreviewRef.current,
|
|
678
769
|
showStatusBar: visible,
|
|
679
770
|
outline: outlineRef.current,
|
|
680
|
-
blockTags:
|
|
771
|
+
blockTags: blockTagVisibilityRef.current !== 'none',
|
|
772
|
+
blockTagVisibility: blockTagVisibilityRef.current,
|
|
681
773
|
themeInheritance: themeInheritanceRef.current,
|
|
682
774
|
layoutMode: layoutModeRef.current,
|
|
683
775
|
});
|
|
@@ -688,29 +780,36 @@ export function EditorProvider({
|
|
|
688
780
|
inlinePreview: inlinePreviewRef.current,
|
|
689
781
|
showStatusBar: statusBarRef.current,
|
|
690
782
|
outline: visible,
|
|
691
|
-
blockTags:
|
|
783
|
+
blockTags: blockTagVisibilityRef.current !== 'none',
|
|
784
|
+
blockTagVisibility: blockTagVisibilityRef.current,
|
|
692
785
|
themeInheritance: themeInheritanceRef.current,
|
|
693
786
|
layoutMode: layoutModeRef.current,
|
|
694
787
|
});
|
|
695
788
|
}, []);
|
|
696
|
-
const
|
|
697
|
-
|
|
789
|
+
const setBlockTagVisibility = useCallback((visibility: BlockTagVisibility) => {
|
|
790
|
+
setBlockTagVisibilityRaw(visibility);
|
|
698
791
|
onViewPreferencesChangeRef.current?.({
|
|
699
792
|
inlinePreview: inlinePreviewRef.current,
|
|
700
793
|
showStatusBar: statusBarRef.current,
|
|
701
794
|
outline: outlineRef.current,
|
|
702
|
-
blockTags:
|
|
795
|
+
blockTags: visibility !== 'none',
|
|
796
|
+
blockTagVisibility: visibility,
|
|
703
797
|
themeInheritance: themeInheritanceRef.current,
|
|
704
798
|
layoutMode: layoutModeRef.current,
|
|
705
799
|
});
|
|
706
800
|
}, []);
|
|
801
|
+
const setBlockTagsVisible = useCallback(
|
|
802
|
+
(visible: boolean) => setBlockTagVisibility(visible ? 'always' : 'none'),
|
|
803
|
+
[setBlockTagVisibility],
|
|
804
|
+
);
|
|
707
805
|
const setThemeInheritance = useCallback((mode: ThemeInheritance) => {
|
|
708
806
|
setThemeInheritanceRaw(mode);
|
|
709
807
|
onViewPreferencesChangeRef.current?.({
|
|
710
808
|
inlinePreview: inlinePreviewRef.current,
|
|
711
809
|
showStatusBar: statusBarRef.current,
|
|
712
810
|
outline: outlineRef.current,
|
|
713
|
-
blockTags:
|
|
811
|
+
blockTags: blockTagVisibilityRef.current !== 'none',
|
|
812
|
+
blockTagVisibility: blockTagVisibilityRef.current,
|
|
714
813
|
themeInheritance: mode,
|
|
715
814
|
layoutMode: layoutModeRef.current,
|
|
716
815
|
});
|
|
@@ -721,18 +820,62 @@ export function EditorProvider({
|
|
|
721
820
|
inlinePreview: inlinePreviewRef.current,
|
|
722
821
|
showStatusBar: statusBarRef.current,
|
|
723
822
|
outline: outlineRef.current,
|
|
724
|
-
blockTags:
|
|
823
|
+
blockTags: blockTagVisibilityRef.current !== 'none',
|
|
824
|
+
blockTagVisibility: blockTagVisibilityRef.current,
|
|
725
825
|
themeInheritance: themeInheritanceRef.current,
|
|
726
826
|
layoutMode: mode,
|
|
727
827
|
});
|
|
728
828
|
}, []);
|
|
729
|
-
const [tiptapEditor,
|
|
730
|
-
const [monacoEditor,
|
|
829
|
+
const [tiptapEditor, setTiptapEditorRaw] = useState<TiptapEditor | null>(null);
|
|
830
|
+
const [monacoEditor, setMonacoEditorRaw] = useState<MonacoEditor | null>(null);
|
|
831
|
+
const setTiptapEditor = useCallback((editor: TiptapEditor | null) => {
|
|
832
|
+
tiptapEditorRef.current = editor;
|
|
833
|
+
setTiptapEditorRaw(editor);
|
|
834
|
+
}, []);
|
|
835
|
+
const setMonacoEditor = useCallback((editor: MonacoEditor | null) => {
|
|
836
|
+
monacoEditorRef.current = editor;
|
|
837
|
+
setMonacoEditorRaw(editor);
|
|
838
|
+
}, []);
|
|
839
|
+
|
|
840
|
+
useEffect(() => {
|
|
841
|
+
const pending = pendingEditorScrollRef.current;
|
|
842
|
+
if (!pending || pending.view !== activeView) return;
|
|
843
|
+
|
|
844
|
+
const restore =
|
|
845
|
+
activeView === 'raw' && monacoEditor
|
|
846
|
+
? () => restoreMonacoScrollRatio(monacoEditor, pending.ratio)
|
|
847
|
+
: activeView === 'wysiwyg' && tiptapEditor
|
|
848
|
+
? () => restoreWysiwygScrollRatio(tiptapEditor, pending.ratio)
|
|
849
|
+
: null;
|
|
850
|
+
if (!restore) return;
|
|
851
|
+
|
|
852
|
+
let cancelled = false;
|
|
853
|
+
const apply = () => {
|
|
854
|
+
if (cancelled) return;
|
|
855
|
+
restore();
|
|
856
|
+
if (pendingEditorScrollRef.current === pending) pendingEditorScrollRef.current = null;
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
860
|
+
const frame = requestAnimationFrame(apply);
|
|
861
|
+
return () => {
|
|
862
|
+
cancelled = true;
|
|
863
|
+
cancelAnimationFrame(frame);
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const timeout = setTimeout(apply, 0);
|
|
868
|
+
return () => {
|
|
869
|
+
cancelled = true;
|
|
870
|
+
clearTimeout(timeout);
|
|
871
|
+
};
|
|
872
|
+
}, [activeView, monacoEditor, tiptapEditor]);
|
|
731
873
|
// Mirror the focused canvas textbox editor (published by the inline
|
|
732
874
|
// SceneTextOverlay through a module channel, since the canvas renders in
|
|
733
875
|
// a detached React root outside this provider) so the toolbar can target it.
|
|
734
876
|
const [activeSceneText, setActiveSceneText] = useState<SceneTextHandle | null>(null);
|
|
735
|
-
|
|
877
|
+
const sceneTextChannel = useMemo(createSceneTextChannel, []);
|
|
878
|
+
useEffect(() => sceneTextChannel.subscribe(setActiveSceneText), [sceneTextChannel]);
|
|
736
879
|
|
|
737
880
|
const articleIdRef = useRef(articleId);
|
|
738
881
|
articleIdRef.current = articleId;
|
|
@@ -744,6 +887,7 @@ export function EditorProvider({
|
|
|
744
887
|
|
|
745
888
|
// Debounced parse on markdown source change
|
|
746
889
|
const parseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
890
|
+
const skipInitialDebouncedParseRef = useRef(Boolean(initialMarkdown));
|
|
747
891
|
|
|
748
892
|
const doParse = useCallback((source: string) => {
|
|
749
893
|
setIsParsing(true);
|
|
@@ -778,9 +922,14 @@ export function EditorProvider({
|
|
|
778
922
|
// parser on TypeScript / JSON / a binary image asset.
|
|
779
923
|
useEffect(() => {
|
|
780
924
|
if (editorMode !== 'markdown') return;
|
|
925
|
+
if (skipInitialDebouncedParseRef.current) {
|
|
926
|
+
skipInitialDebouncedParseRef.current = false;
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
781
929
|
if (parseTimeoutRef.current) {
|
|
782
930
|
clearTimeout(parseTimeoutRef.current);
|
|
783
931
|
}
|
|
932
|
+
setIsParsing(true);
|
|
784
933
|
parseTimeoutRef.current = setTimeout(() => {
|
|
785
934
|
doParse(markdownSource);
|
|
786
935
|
}, 150);
|
|
@@ -983,6 +1132,7 @@ export function EditorProvider({
|
|
|
983
1132
|
inlinePreviewVisible,
|
|
984
1133
|
statusBarVisible,
|
|
985
1134
|
outlineVisible,
|
|
1135
|
+
blockTagVisibility: blockTagVisibilityState,
|
|
986
1136
|
blockTagsVisible,
|
|
987
1137
|
themeInheritance: themeInheritanceState,
|
|
988
1138
|
layoutMode: layoutModeState,
|
|
@@ -993,9 +1143,11 @@ export function EditorProvider({
|
|
|
993
1143
|
imageEditTarget,
|
|
994
1144
|
mediaRevision,
|
|
995
1145
|
allowRecording,
|
|
1146
|
+
allowNarrate,
|
|
996
1147
|
tiptapEditor,
|
|
997
1148
|
monacoEditor,
|
|
998
1149
|
activeSceneText,
|
|
1150
|
+
sceneTextChannel,
|
|
999
1151
|
workspaceContainer,
|
|
1000
1152
|
versioning,
|
|
1001
1153
|
saveVersion,
|
|
@@ -1019,6 +1171,7 @@ export function EditorProvider({
|
|
|
1019
1171
|
setInlinePreviewVisible,
|
|
1020
1172
|
setStatusBarVisible,
|
|
1021
1173
|
setOutlineVisible,
|
|
1174
|
+
setBlockTagVisibility,
|
|
1022
1175
|
setBlockTagsVisible,
|
|
1023
1176
|
setThemeInheritance,
|
|
1024
1177
|
insertAtCursor,
|
|
@@ -1040,6 +1193,7 @@ export function EditorProvider({
|
|
|
1040
1193
|
inlinePreviewVisible,
|
|
1041
1194
|
statusBarVisible,
|
|
1042
1195
|
outlineVisible,
|
|
1196
|
+
blockTagVisibilityState,
|
|
1043
1197
|
blockTagsVisible,
|
|
1044
1198
|
themeInheritanceState,
|
|
1045
1199
|
layoutModeState,
|
|
@@ -1050,6 +1204,7 @@ export function EditorProvider({
|
|
|
1050
1204
|
tiptapEditor,
|
|
1051
1205
|
monacoEditor,
|
|
1052
1206
|
activeSceneText,
|
|
1207
|
+
sceneTextChannel,
|
|
1053
1208
|
workspaceContainer,
|
|
1054
1209
|
versioning,
|
|
1055
1210
|
saveVersion,
|
|
@@ -1073,6 +1228,7 @@ export function EditorProvider({
|
|
|
1073
1228
|
setInlinePreviewVisible,
|
|
1074
1229
|
setStatusBarVisible,
|
|
1075
1230
|
setOutlineVisible,
|
|
1231
|
+
setBlockTagVisibility,
|
|
1076
1232
|
setBlockTagsVisible,
|
|
1077
1233
|
setThemeInheritance,
|
|
1078
1234
|
insertAtCursor,
|
|
@@ -1080,6 +1236,7 @@ export function EditorProvider({
|
|
|
1080
1236
|
imageEditTarget,
|
|
1081
1237
|
mediaRevision,
|
|
1082
1238
|
allowRecording,
|
|
1239
|
+
allowNarrate,
|
|
1083
1240
|
openImageEdit,
|
|
1084
1241
|
closeImageEdit,
|
|
1085
1242
|
bumpMediaRevision,
|