@bendyline/squisq-editor-react 1.6.0 → 1.6.2
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 +57 -10
- package/dist/index.d.ts +476 -105
- package/dist/index.js +8703 -6741
- package/dist/index.js.map +1 -1
- package/dist/styles/fa-brands-400-AHOAZHCU.woff2 +0 -0
- package/dist/styles/fa-regular-400-VRZYIBIZ.woff2 +0 -0
- package/dist/styles/fa-solid-900-MDEYK55F.woff2 +0 -0
- package/dist/styles/fa-v4compatibility-ETEVP6IB.woff2 +0 -0
- package/dist/styles/index.css +14617 -0
- package/package.json +15 -7
- package/src/BlockPropertiesPopover.tsx +23 -7
- package/src/EditorContext.tsx +22 -16
- package/src/EditorShell.tsx +121 -35
- package/src/MediaBin.tsx +171 -28
- package/src/OutlinePanel.tsx +26 -4
- package/src/PreviewControls.tsx +475 -145
- package/src/PreviewPanel.tsx +17 -9
- package/src/RawEditor.tsx +10 -4
- package/src/TemplateAnnotation.ts +22 -7
- package/src/TemplateContentPreview.tsx +56 -0
- package/src/TemplatePicker.tsx +295 -128
- package/src/ThemeCustomizerPanel.tsx +22 -15
- package/src/Toolbar.tsx +547 -217
- package/src/TransitionPicker.tsx +8 -1
- package/src/VersionHistoryPanel.tsx +2 -2
- package/src/ViewSwitcher.tsx +4 -4
- package/src/WysiwygEditor.tsx +45 -3
- package/src/__tests__/buildPreviewDocTransition.test.ts +1 -2
- package/src/__tests__/codeContextSectionView.test.tsx +95 -0
- package/src/__tests__/codeContextZoneManager.test.ts +127 -0
- package/src/__tests__/diffContextSections.test.ts +39 -0
- package/src/__tests__/editorShellCodeContext.test.tsx +86 -0
- package/src/__tests__/editorShellProps.test.tsx +363 -0
- package/src/__tests__/headingTransition.test.ts +59 -9
- package/src/__tests__/imageEditorShell.test.tsx +23 -0
- package/src/__tests__/mediaReferences.test.ts +82 -0
- package/src/__tests__/previewControls.test.tsx +163 -0
- package/src/__tests__/templateAnnotationRoundTrip.test.ts +23 -2
- package/src/__tests__/templateContentPreview.test.ts +101 -0
- package/src/__tests__/tiptapBridge.test.ts +47 -0
- package/src/__tests__/useJsonEditorTokens.test.ts +59 -0
- package/src/__tests__/useMediaRecorder.test.ts +17 -0
- package/src/codeContext/CodeContextSectionView.tsx +124 -0
- package/src/codeContext/CodeContextZoneManager.ts +149 -0
- package/src/codeContext/CodeContextZones.tsx +121 -0
- package/src/codeContext/diffContextSections.ts +38 -0
- package/src/codeContext/types.ts +75 -0
- package/src/diagram/DiagramWidget.tsx +6 -4
- package/src/headingTransition.ts +96 -21
- package/src/index.ts +34 -2
- package/src/jsonEditor/useJsonEditorTokens.ts +13 -43
- package/src/mediaReferences.ts +299 -0
- package/src/recorder/hooks/useMediaRecorder.ts +9 -10
- package/src/scene/Scene.tsx +53 -7
- package/src/scene/SceneBlockWidget.tsx +6 -3
- package/src/scene/SceneSelection.tsx +19 -15
- package/src/scene/SceneSideToolbar.tsx +89 -0
- package/src/scene/layers/DiagramEdges.tsx +4 -3
- package/src/scene/layers/edgeGeometry.ts +23 -4
- package/src/scene/scene.css +142 -2
- package/src/scene/tools/ConnectTool.ts +113 -24
- package/src/scene/tools/DrawingConnectTool.ts +86 -16
- package/src/scene/tools/SceneTool.ts +2 -0
- package/src/styles/code-context.css +155 -0
- package/src/styles/editor.css +991 -84
- package/src/styles/index.css +1 -0
- package/src/templateContentPreviewResolver.ts +353 -0
- package/src/tiptapBridge.ts +60 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-editor-react",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "React editor shell with raw/WYSIWYG/preview modes for Squisq documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"types": "./dist/index.d.ts",
|
|
35
35
|
"import": "./dist/index.js"
|
|
36
36
|
},
|
|
37
|
-
"./styles": "./
|
|
37
|
+
"./styles": "./dist/styles/index.css"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
|
-
"build": "tsup",
|
|
40
|
+
"build": "tsup && node ../../scripts/build-styles.mjs",
|
|
41
41
|
"dev": "concurrently -n js,dts -c blue,gray -r \"tsup --watch --no-dts --no-clean\" \"tsup --watch --dts-only --no-clean\"",
|
|
42
42
|
"typecheck": "tsc --noEmit"
|
|
43
43
|
},
|
|
@@ -46,10 +46,15 @@
|
|
|
46
46
|
"react": "^18.0.0 || ^19.0.0",
|
|
47
47
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
48
48
|
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"monaco-editor": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
49
54
|
"dependencies": {
|
|
50
|
-
"@bendyline/squisq": "1.5.
|
|
51
|
-
"@bendyline/squisq-formats": "1.4.
|
|
52
|
-
"@bendyline/squisq-react": "1.4.
|
|
55
|
+
"@bendyline/squisq": "1.5.2",
|
|
56
|
+
"@bendyline/squisq-formats": "1.4.2",
|
|
57
|
+
"@bendyline/squisq-react": "1.4.2",
|
|
53
58
|
"@fortawesome/fontawesome-free": "7.2.0",
|
|
54
59
|
"@monaco-editor/react": "4.7.0",
|
|
55
60
|
"@tiptap/extension-image": "2.27.2",
|
|
@@ -74,5 +79,8 @@
|
|
|
74
79
|
"react-dom": "18.3.1",
|
|
75
80
|
"tsup": "8.5.1",
|
|
76
81
|
"typescript": "5.9.3"
|
|
77
|
-
}
|
|
82
|
+
},
|
|
83
|
+
"sideEffects": [
|
|
84
|
+
"**/*.css"
|
|
85
|
+
]
|
|
78
86
|
}
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The on-canvas "block properties" palette — the sibling of the block-template
|
|
5
5
|
* badge. Anchored at the `.squisq-props-badge` chip on a heading, it edits the
|
|
6
|
-
* block's playback/animation metadata
|
|
7
|
-
* attribute block (`dataBlockAttrs`):
|
|
6
|
+
* block's playback/animation metadata:
|
|
8
7
|
*
|
|
9
|
-
* - Transition (type / direction / duration) —
|
|
10
|
-
* - Duration (`duration`) —
|
|
11
|
-
* - Start time (`startTime`) —
|
|
8
|
+
* - Transition (type / direction / duration) — stored in `{[…]}` params
|
|
9
|
+
* - Duration (`duration`) — stored in Pandoc `dataBlockAttrs`
|
|
10
|
+
* - Start time (`startTime`) — stored in Pandoc `dataBlockAttrs`
|
|
12
11
|
*
|
|
13
12
|
* The popover holds the `dataBlockAttrs` inner string as working state and
|
|
14
13
|
* re-derives each control from it, so successive edits compose. Every change
|
|
@@ -23,6 +22,7 @@ import { TransitionPicker } from './TransitionPicker';
|
|
|
23
22
|
import {
|
|
24
23
|
readBlockAttrsTransition,
|
|
25
24
|
setBlockAttrsTransition,
|
|
25
|
+
setHeadingAttrsTransition,
|
|
26
26
|
type TransitionFields,
|
|
27
27
|
} from './headingTransition';
|
|
28
28
|
import { readBlockAttrsValue, setBlockAttrsValue } from './blockProperties';
|
|
@@ -36,6 +36,11 @@ export interface BlockPropertiesPopoverProps {
|
|
|
36
36
|
templateParams: string | null;
|
|
37
37
|
/** Apply a new `dataBlockAttrs` inner to the heading (null clears it). */
|
|
38
38
|
onChange: (nextInner: string | null) => void;
|
|
39
|
+
/** Apply a paired `dataBlockAttrs` / `dataTemplateParams` transition rewrite. */
|
|
40
|
+
onAnnotationChange?: (next: {
|
|
41
|
+
blockAttrsInner: string | null;
|
|
42
|
+
templateParams: string | null;
|
|
43
|
+
}) => void;
|
|
39
44
|
onClose: () => void;
|
|
40
45
|
}
|
|
41
46
|
|
|
@@ -48,10 +53,12 @@ export function BlockPropertiesPopover({
|
|
|
48
53
|
blockAttrs,
|
|
49
54
|
templateParams,
|
|
50
55
|
onChange,
|
|
56
|
+
onAnnotationChange,
|
|
51
57
|
onClose,
|
|
52
58
|
}: BlockPropertiesPopoverProps) {
|
|
53
59
|
// Working copy of the Pandoc inner; successive edits compose off it.
|
|
54
60
|
const [inner, setInner] = useState<string | null>(blockAttrs);
|
|
61
|
+
const [templateInner, setTemplateInner] = useState<string | null>(templateParams);
|
|
55
62
|
const [style, setStyle] = useState<React.CSSProperties>(() => computeStyle(anchorRect));
|
|
56
63
|
|
|
57
64
|
useEffect(() => {
|
|
@@ -84,11 +91,20 @@ export function BlockPropertiesPopover({
|
|
|
84
91
|
onChange(next);
|
|
85
92
|
};
|
|
86
93
|
|
|
87
|
-
const transition = readBlockAttrsTransition(inner,
|
|
94
|
+
const transition = readBlockAttrsTransition(inner, templateInner);
|
|
88
95
|
const duration = readBlockAttrsValue(inner, 'duration');
|
|
89
96
|
const startTime = readBlockAttrsValue(inner, 'startTime');
|
|
90
97
|
|
|
91
|
-
const onTransition = (next: TransitionFields) =>
|
|
98
|
+
const onTransition = (next: TransitionFields) => {
|
|
99
|
+
if (!onAnnotationChange) {
|
|
100
|
+
apply(setBlockAttrsTransition(inner, next));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const updated = setHeadingAttrsTransition(inner, templateInner, next);
|
|
104
|
+
setInner(updated.blockAttrsInner);
|
|
105
|
+
setTemplateInner(updated.templateParams);
|
|
106
|
+
onAnnotationChange(updated);
|
|
107
|
+
};
|
|
92
108
|
const onDuration = (v: string) => apply(setBlockAttrsValue(inner, 'duration', v));
|
|
93
109
|
const onStartTime = (v: string) => apply(setBlockAttrsValue(inner, 'startTime', v));
|
|
94
110
|
|
package/src/EditorContext.tsx
CHANGED
|
@@ -91,7 +91,13 @@ export type DocumentLinkProvider = (query: string) => Promise<DocumentLinkCandid
|
|
|
91
91
|
// ─── Types ───────────────────────────────────────────────
|
|
92
92
|
|
|
93
93
|
export type EditorView = 'raw' | 'wysiwyg' | 'preview';
|
|
94
|
-
|
|
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';
|
|
95
101
|
/**
|
|
96
102
|
* Document layout mode. `'document'` shows the whole markdown document in
|
|
97
103
|
* the active view (the historical behavior). `'block'` is the
|
|
@@ -128,8 +134,8 @@ export interface EditorState {
|
|
|
128
134
|
parseError: string | null;
|
|
129
135
|
/** Whether a parse is pending */
|
|
130
136
|
isParsing: boolean;
|
|
131
|
-
/** Current color
|
|
132
|
-
|
|
137
|
+
/** Current light/dark chrome color scheme for the editor shell. */
|
|
138
|
+
colorScheme: EditorColorScheme;
|
|
133
139
|
/** Operating mode — 'markdown' for the full shell, 'code' for Monaco-only. */
|
|
134
140
|
editorMode: EditorMode;
|
|
135
141
|
/** Monaco language ID for the Raw editor. */
|
|
@@ -240,8 +246,8 @@ export interface EditorActions {
|
|
|
240
246
|
setTiptapEditor: (editor: TiptapEditor | null) => void;
|
|
241
247
|
/** Register / unregister the Monaco editor instance (called by RawEditor) */
|
|
242
248
|
setMonacoEditor: (editor: MonacoEditor | null) => void;
|
|
243
|
-
/** Set the color
|
|
244
|
-
|
|
249
|
+
/** Set the light/dark chrome color scheme for the editor shell. */
|
|
250
|
+
setColorScheme: (colorScheme: EditorColorScheme) => void;
|
|
245
251
|
/** Show or hide the inline preview gutter at runtime (driven by the View menu). */
|
|
246
252
|
setInlinePreviewVisible: (visible: boolean) => void;
|
|
247
253
|
/** Show or hide the bottom status bar at runtime (driven by the View menu). */
|
|
@@ -360,8 +366,8 @@ export interface EditorProviderProps {
|
|
|
360
366
|
initialView?: EditorView;
|
|
361
367
|
/** Article ID used when generating the Doc */
|
|
362
368
|
articleId?: string;
|
|
363
|
-
/**
|
|
364
|
-
|
|
369
|
+
/** Light/dark chrome color scheme for the editor shell. */
|
|
370
|
+
colorScheme?: EditorColorScheme;
|
|
365
371
|
/**
|
|
366
372
|
* Workspace-scoped `ContentContainer` for this document — the folder
|
|
367
373
|
* holding the doc, its `_files/` sidecar, sibling documents, and any
|
|
@@ -509,7 +515,7 @@ export function EditorProvider({
|
|
|
509
515
|
initialMarkdown = '',
|
|
510
516
|
initialView = 'raw',
|
|
511
517
|
articleId = 'untitled',
|
|
512
|
-
|
|
518
|
+
colorScheme: initialColorScheme = 'light',
|
|
513
519
|
workspaceContainer = null,
|
|
514
520
|
allowVersioning = false,
|
|
515
521
|
versionBasename,
|
|
@@ -570,7 +576,7 @@ export function EditorProvider({
|
|
|
570
576
|
);
|
|
571
577
|
const [parseError, setParseError] = useState<string | null>(null);
|
|
572
578
|
const [isParsing, setIsParsing] = useState(false);
|
|
573
|
-
const [
|
|
579
|
+
const [colorScheme, setColorScheme] = useState<EditorColorScheme>(initialColorScheme);
|
|
574
580
|
const [inlinePreviewVisible, setInlinePreviewVisibleRaw] =
|
|
575
581
|
useState<boolean>(effectiveInlinePreview);
|
|
576
582
|
// Sync visibility when the host changes the prop (e.g., toggle from outside).
|
|
@@ -731,10 +737,10 @@ export function EditorProvider({
|
|
|
731
737
|
const articleIdRef = useRef(articleId);
|
|
732
738
|
articleIdRef.current = articleId;
|
|
733
739
|
|
|
734
|
-
// Sync
|
|
740
|
+
// Sync color scheme when prop changes
|
|
735
741
|
useEffect(() => {
|
|
736
|
-
|
|
737
|
-
}, [
|
|
742
|
+
setColorScheme(initialColorScheme);
|
|
743
|
+
}, [initialColorScheme]);
|
|
738
744
|
|
|
739
745
|
// Debounced parse on markdown source change
|
|
740
746
|
const parseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -971,7 +977,7 @@ export function EditorProvider({
|
|
|
971
977
|
activeView,
|
|
972
978
|
parseError,
|
|
973
979
|
isParsing,
|
|
974
|
-
|
|
980
|
+
colorScheme,
|
|
975
981
|
editorMode,
|
|
976
982
|
language: resolvedLanguage,
|
|
977
983
|
inlinePreviewVisible,
|
|
@@ -1009,7 +1015,7 @@ export function EditorProvider({
|
|
|
1009
1015
|
setActiveView,
|
|
1010
1016
|
setTiptapEditor,
|
|
1011
1017
|
setMonacoEditor,
|
|
1012
|
-
|
|
1018
|
+
setColorScheme,
|
|
1013
1019
|
setInlinePreviewVisible,
|
|
1014
1020
|
setStatusBarVisible,
|
|
1015
1021
|
setOutlineVisible,
|
|
@@ -1028,7 +1034,7 @@ export function EditorProvider({
|
|
|
1028
1034
|
activeView,
|
|
1029
1035
|
parseError,
|
|
1030
1036
|
isParsing,
|
|
1031
|
-
|
|
1037
|
+
colorScheme,
|
|
1032
1038
|
editorMode,
|
|
1033
1039
|
resolvedLanguage,
|
|
1034
1040
|
inlinePreviewVisible,
|
|
@@ -1063,7 +1069,7 @@ export function EditorProvider({
|
|
|
1063
1069
|
setActiveView,
|
|
1064
1070
|
setTiptapEditor,
|
|
1065
1071
|
setMonacoEditor,
|
|
1066
|
-
|
|
1072
|
+
setColorScheme,
|
|
1067
1073
|
setInlinePreviewVisible,
|
|
1068
1074
|
setStatusBarVisible,
|
|
1069
1075
|
setOutlineVisible,
|
package/src/EditorShell.tsx
CHANGED
|
@@ -23,7 +23,9 @@ import { RawEditor } from './RawEditor';
|
|
|
23
23
|
import { WysiwygEditor } from './WysiwygEditor';
|
|
24
24
|
import { InlinePreviewGutter } from './InlinePreviewGutter';
|
|
25
25
|
import { BlockPreviewPanel } from './BlockPreviewPanel';
|
|
26
|
-
import { OutlinePanel } from './OutlinePanel';
|
|
26
|
+
import { OutlinePanel, OUTLINE_RESPONSIVE_WIDTH } from './OutlinePanel';
|
|
27
|
+
import { CodeContextZones } from './codeContext/CodeContextZones';
|
|
28
|
+
import type { CodeContext } from './codeContext/types';
|
|
27
29
|
import { BlockCardView } from './BlockCardView';
|
|
28
30
|
import { TimelineTrack } from './TimelineTrack';
|
|
29
31
|
import { PreviewPanel } from './PreviewPanel';
|
|
@@ -45,6 +47,10 @@ import {
|
|
|
45
47
|
processTextFile,
|
|
46
48
|
processTextFiles,
|
|
47
49
|
} from './utils/dropUtils';
|
|
50
|
+
import {
|
|
51
|
+
collectMediaReferencesFromMarkdown,
|
|
52
|
+
removeMediaReferencesFromMarkdown,
|
|
53
|
+
} from './mediaReferences';
|
|
48
54
|
import type { MediaProvider, Theme } from '@bendyline/squisq/schemas';
|
|
49
55
|
import { DARK_SURFACE, LIGHT_SURFACE } from '@bendyline/squisq/schemas';
|
|
50
56
|
import type { ContentContainer } from '@bendyline/squisq/storage';
|
|
@@ -56,7 +62,7 @@ import {
|
|
|
56
62
|
import type { PrunePolicy, SaveVersionResult } from '@bendyline/squisq/versions';
|
|
57
63
|
import type { CSSProperties, ReactNode } from 'react';
|
|
58
64
|
|
|
59
|
-
export type {
|
|
65
|
+
export type { EditorColorScheme } from './EditorContext';
|
|
60
66
|
|
|
61
67
|
export interface EditorShellProps {
|
|
62
68
|
/** Initial markdown content */
|
|
@@ -70,8 +76,13 @@ export interface EditorShellProps {
|
|
|
70
76
|
basePath?: string;
|
|
71
77
|
/** Called when markdown source changes */
|
|
72
78
|
onChange?: (source: string) => void;
|
|
73
|
-
/**
|
|
74
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Light/dark chrome color scheme for the editor shell — toolbar, tabs,
|
|
81
|
+
* status bar, and side panes (default: `'light'`). This is the editor's
|
|
82
|
+
* UI mode, **not** a Squisq `Theme` object; the rendered document's
|
|
83
|
+
* styling is controlled separately via `themeOverride` / `Doc.themeId`.
|
|
84
|
+
*/
|
|
85
|
+
colorScheme?: 'light' | 'dark';
|
|
75
86
|
/** Additional class name */
|
|
76
87
|
className?: string;
|
|
77
88
|
/** CSS height for the shell container (default: '100vh') */
|
|
@@ -166,6 +177,13 @@ export interface EditorShellProps {
|
|
|
166
177
|
* (Slack, Discord). When omitted, the editor behaves normally.
|
|
167
178
|
*/
|
|
168
179
|
submitOnEnter?: () => void;
|
|
180
|
+
/**
|
|
181
|
+
* Host-supplied context dictionary rendered inside the Monaco (raw / code)
|
|
182
|
+
* surface: collapsible markdown sections injected above anchor lines, plus
|
|
183
|
+
* an optional file-top summary. See {@link CodeContext}. Ignored in
|
|
184
|
+
* WYSIWYG / preview / image surfaces.
|
|
185
|
+
*/
|
|
186
|
+
codeContext?: CodeContext;
|
|
169
187
|
/**
|
|
170
188
|
* Let the WYSIWYG editing surface fill its container instead of rendering
|
|
171
189
|
* as a centered 800px "page" column. Useful when embedding in chat
|
|
@@ -324,9 +342,11 @@ export interface EditorShellProps {
|
|
|
324
342
|
*/
|
|
325
343
|
outline?: boolean;
|
|
326
344
|
/**
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
345
|
+
* Fixed width in pixels for the outline pane. When omitted, the pane sizes
|
|
346
|
+
* responsively — never narrower than 260px, growing with the window and
|
|
347
|
+
* capped at 460px — so it stretches out when there's horizontal space to
|
|
348
|
+
* spare. Only takes effect when {@link EditorShellProps.outline} is true (or
|
|
349
|
+
* the View menu has toggled it on).
|
|
330
350
|
*/
|
|
331
351
|
outlineWidth?: number;
|
|
332
352
|
/**
|
|
@@ -378,7 +398,7 @@ export function EditorShell({
|
|
|
378
398
|
articleId = 'untitled',
|
|
379
399
|
basePath = '/',
|
|
380
400
|
onChange,
|
|
381
|
-
|
|
401
|
+
colorScheme = 'light',
|
|
382
402
|
className,
|
|
383
403
|
height = '100vh',
|
|
384
404
|
minHeight,
|
|
@@ -397,6 +417,7 @@ export function EditorShell({
|
|
|
397
417
|
toolbarSlotRight,
|
|
398
418
|
showPlayTab = true,
|
|
399
419
|
submitOnEnter,
|
|
420
|
+
codeContext,
|
|
400
421
|
fullWidth = false,
|
|
401
422
|
uxFont,
|
|
402
423
|
thinMargins = false,
|
|
@@ -417,7 +438,7 @@ export function EditorShell({
|
|
|
417
438
|
inlinePreview = false,
|
|
418
439
|
inlinePreviewWidth = 320,
|
|
419
440
|
outline = false,
|
|
420
|
-
outlineWidth
|
|
441
|
+
outlineWidth,
|
|
421
442
|
blockTags = true,
|
|
422
443
|
themeInheritance = 'fonts',
|
|
423
444
|
viewPreferences,
|
|
@@ -449,7 +470,7 @@ export function EditorShell({
|
|
|
449
470
|
initialMarkdown={initialMarkdown}
|
|
450
471
|
initialView={effectiveInitialView}
|
|
451
472
|
articleId={articleId}
|
|
452
|
-
|
|
473
|
+
colorScheme={colorScheme}
|
|
453
474
|
workspaceContainer={effectiveContainer}
|
|
454
475
|
allowVersioning={allowVersioning}
|
|
455
476
|
versionBasename={versionBasename}
|
|
@@ -487,6 +508,7 @@ export function EditorShell({
|
|
|
487
508
|
toolbarSlotRight={toolbarSlotRight}
|
|
488
509
|
showPlayTab={showPlayTab}
|
|
489
510
|
submitOnEnter={submitOnEnter}
|
|
511
|
+
codeContext={codeContext}
|
|
490
512
|
fullWidth={fullWidth}
|
|
491
513
|
uxFont={uxFont}
|
|
492
514
|
thinMargins={thinMargins}
|
|
@@ -522,6 +544,7 @@ interface EditorShellInnerProps {
|
|
|
522
544
|
toolbarSlotRight?: ReactNode;
|
|
523
545
|
showPlayTab: boolean;
|
|
524
546
|
submitOnEnter?: () => void;
|
|
547
|
+
codeContext?: CodeContext;
|
|
525
548
|
fullWidth: boolean;
|
|
526
549
|
uxFont?: string;
|
|
527
550
|
thinMargins: boolean;
|
|
@@ -534,7 +557,7 @@ interface EditorShellInnerProps {
|
|
|
534
557
|
allowVersioning: boolean;
|
|
535
558
|
versioningAutoSaveIdleMs?: number;
|
|
536
559
|
inlinePreviewWidth: number;
|
|
537
|
-
outlineWidth
|
|
560
|
+
outlineWidth?: number;
|
|
538
561
|
themeOverride: Theme | null;
|
|
539
562
|
}
|
|
540
563
|
|
|
@@ -554,6 +577,7 @@ function EditorShellInner({
|
|
|
554
577
|
toolbarSlotRight,
|
|
555
578
|
showPlayTab,
|
|
556
579
|
submitOnEnter,
|
|
580
|
+
codeContext,
|
|
557
581
|
fullWidth,
|
|
558
582
|
uxFont,
|
|
559
583
|
thinMargins,
|
|
@@ -573,7 +597,7 @@ function EditorShellInner({
|
|
|
573
597
|
activeView,
|
|
574
598
|
markdownSource,
|
|
575
599
|
doc,
|
|
576
|
-
|
|
600
|
+
colorScheme,
|
|
577
601
|
editorMode,
|
|
578
602
|
insertAtCursor,
|
|
579
603
|
replaceAll,
|
|
@@ -592,6 +616,7 @@ function EditorShellInner({
|
|
|
592
616
|
imageEditTarget,
|
|
593
617
|
closeImageEdit,
|
|
594
618
|
bumpMediaRevision,
|
|
619
|
+
mediaRevision,
|
|
595
620
|
} = useEditorContext();
|
|
596
621
|
// Dual-catalog custom themes (doc + browser library), mirroring how
|
|
597
622
|
// CustomTemplateProvider is fed. Wraps the preview subtree so the theme
|
|
@@ -610,6 +635,12 @@ function EditorShellInner({
|
|
|
610
635
|
const isTimelineMode = isMarkdownMode && layoutMode === 'timeline' && !isPreview;
|
|
611
636
|
const [showFiles, setShowFiles] = useState(false);
|
|
612
637
|
const [mediaRefreshKey, setMediaRefreshKey] = useState(0);
|
|
638
|
+
const [mediaCount, setMediaCount] = useState(0);
|
|
639
|
+
const mediaListRefreshKey = mediaRefreshKey + mediaRevision;
|
|
640
|
+
const usedMediaPaths = useMemo(
|
|
641
|
+
() => collectMediaReferencesFromMarkdown(markdownSource),
|
|
642
|
+
[markdownSource],
|
|
643
|
+
);
|
|
613
644
|
// Persistent fallback container for image-edit sidecars when the host
|
|
614
645
|
// didn't supply one. Lifted to shell scope so opening the same image
|
|
615
646
|
// multiple times sees the same `.imageEdits/<sanitized>/.versions/...`
|
|
@@ -620,7 +651,34 @@ function EditorShellInner({
|
|
|
620
651
|
imageEditFallbackContainerRef.current = new MemoryContentContainer();
|
|
621
652
|
}
|
|
622
653
|
const imageEditFallbackContainer = imageEditFallbackContainerRef.current;
|
|
623
|
-
const isDark =
|
|
654
|
+
const isDark = colorScheme === 'dark';
|
|
655
|
+
|
|
656
|
+
useEffect(() => {
|
|
657
|
+
if (!mediaProvider) {
|
|
658
|
+
setMediaCount(0);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
let cancelled = false;
|
|
663
|
+
mediaProvider.listMedia().then(
|
|
664
|
+
(entries) => {
|
|
665
|
+
if (!cancelled) setMediaCount(entries.length);
|
|
666
|
+
},
|
|
667
|
+
(err: unknown) => {
|
|
668
|
+
if (!cancelled) {
|
|
669
|
+
setMediaCount(0);
|
|
670
|
+
console.warn(
|
|
671
|
+
'[squisq-editor] Failed to read media list:',
|
|
672
|
+
err instanceof Error ? err.message : err,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
return () => {
|
|
679
|
+
cancelled = true;
|
|
680
|
+
};
|
|
681
|
+
}, [mediaProvider, mediaListRefreshKey]);
|
|
624
682
|
|
|
625
683
|
const handleToggleFiles = useCallback(() => {
|
|
626
684
|
setShowFiles((prev) => !prev);
|
|
@@ -679,6 +737,24 @@ function EditorShellInner({
|
|
|
679
737
|
[activeView, tiptapEditor, monacoEditor, insertAtCursor, markdownSource, setMarkdownSource],
|
|
680
738
|
);
|
|
681
739
|
|
|
740
|
+
const handleMediaUploaded = useCallback(
|
|
741
|
+
(relativePath: string, name: string, mimeType: string) => {
|
|
742
|
+
insertMediaRef(relativePath, name, mimeType);
|
|
743
|
+
setMediaRefreshKey((k) => k + 1);
|
|
744
|
+
},
|
|
745
|
+
[insertMediaRef],
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
const handleMediaRemoved = useCallback(
|
|
749
|
+
(relativePath: string) => {
|
|
750
|
+
const nextSource = removeMediaReferencesFromMarkdown(markdownSource, relativePath);
|
|
751
|
+
if (nextSource !== markdownSource) replaceAll(nextSource);
|
|
752
|
+
setMediaRefreshKey((k) => k + 1);
|
|
753
|
+
bumpMediaRevision();
|
|
754
|
+
},
|
|
755
|
+
[bumpMediaRevision, markdownSource, replaceAll],
|
|
756
|
+
);
|
|
757
|
+
|
|
682
758
|
const handleFileDrop = useCallback(
|
|
683
759
|
async (files: File[], target: DropTarget) => {
|
|
684
760
|
try {
|
|
@@ -761,7 +837,7 @@ function EditorShellInner({
|
|
|
761
837
|
return (
|
|
762
838
|
<div
|
|
763
839
|
className={`squisq-editor-shell ${className || ''}`}
|
|
764
|
-
data-theme={
|
|
840
|
+
data-theme={colorScheme}
|
|
765
841
|
data-full-width={fullWidth ? 'true' : undefined}
|
|
766
842
|
data-thin-margins={thinMargins ? 'true' : undefined}
|
|
767
843
|
data-outline-visible={isMarkdownMode && outlineVisible ? 'true' : undefined}
|
|
@@ -775,13 +851,16 @@ function EditorShellInner({
|
|
|
775
851
|
// tabs, status bar) consume `--squisq-ux-font` as their
|
|
776
852
|
// `font-family`, falling back to the system stack when unset.
|
|
777
853
|
...(uxFont ? ({ '--squisq-ux-font': uxFont } as CSSProperties) : {}),
|
|
778
|
-
// Exposed so the toolbar's view-tabs section
|
|
779
|
-
//
|
|
780
|
-
// outline's right edge.
|
|
781
|
-
//
|
|
782
|
-
// `data-outline-visible` gate above keeps the
|
|
783
|
-
// scoped to the case where alignment matters.
|
|
784
|
-
...({
|
|
854
|
+
// Exposed so the toolbar's view-tabs section AND the outline pane read
|
|
855
|
+
// one shared width, lining up the view-tabs' right-edge separator with
|
|
856
|
+
// the outline's right edge. A fixed `outlineWidth` pins it to px;
|
|
857
|
+
// otherwise it's the responsive clamp so the pane stretches when
|
|
858
|
+
// there's room. The `data-outline-visible` gate above keeps the
|
|
859
|
+
// toolbar override scoped to the case where alignment matters.
|
|
860
|
+
...({
|
|
861
|
+
'--squisq-outline-width':
|
|
862
|
+
outlineWidth != null ? `${outlineWidth}px` : OUTLINE_RESPONSIVE_WIDTH,
|
|
863
|
+
} as CSSProperties),
|
|
785
864
|
}}
|
|
786
865
|
{...containerProps}
|
|
787
866
|
>
|
|
@@ -802,14 +881,11 @@ function EditorShellInner({
|
|
|
802
881
|
<div className="squisq-editor-header">
|
|
803
882
|
<Toolbar
|
|
804
883
|
showFiles={showFiles}
|
|
884
|
+
fileCount={mediaCount}
|
|
805
885
|
onToggleFiles={!isCodeMode && filesToggleEnabled ? handleToggleFiles : undefined}
|
|
806
886
|
slotLeft={toolbarSlotLeft}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
{toolbarSlotAfterActions}
|
|
810
|
-
{!isCodeMode && isPreview && <PreviewToolbarControls />}
|
|
811
|
-
</>
|
|
812
|
-
}
|
|
887
|
+
slotAfterTabs={!isCodeMode && isPreview && <PreviewToolbarControls />}
|
|
888
|
+
slotAfterActions={toolbarSlotAfterActions}
|
|
813
889
|
slotRight={toolbarSlotRight}
|
|
814
890
|
showPlayTab={showPlayTab}
|
|
815
891
|
/>
|
|
@@ -845,9 +921,10 @@ function EditorShellInner({
|
|
|
845
921
|
allowVersioning={allowVersioning}
|
|
846
922
|
versioningAutoSaveIdleMs={versioningAutoSaveIdleMs}
|
|
847
923
|
onExport={onImageExport}
|
|
924
|
+
surface={colorScheme === 'dark' ? DARK_SURFACE : LIGHT_SURFACE}
|
|
848
925
|
/>
|
|
849
926
|
) : (
|
|
850
|
-
<ImageViewer src={imageSrc} alt={imageAlt} theme={
|
|
927
|
+
<ImageViewer src={imageSrc} alt={imageAlt} theme={colorScheme} />
|
|
851
928
|
))}
|
|
852
929
|
{/* Raw (Monaco) view. Always wrapped in `.squisq-editor-with-gutter`
|
|
853
930
|
so toggling a pane on/off doesn't change the editor's tree
|
|
@@ -870,7 +947,7 @@ function EditorShellInner({
|
|
|
870
947
|
>
|
|
871
948
|
<div key="raw-editor" className="squisq-raw-editor-container">
|
|
872
949
|
<RawEditor
|
|
873
|
-
|
|
950
|
+
monacoTheme={colorScheme === 'dark' ? 'vs-dark' : 'vs'}
|
|
874
951
|
submitOnEnter={submitOnEnter}
|
|
875
952
|
readOnly={readOnly}
|
|
876
953
|
/>
|
|
@@ -879,12 +956,18 @@ function EditorShellInner({
|
|
|
879
956
|
) : (
|
|
880
957
|
<div key="raw-editor" className="squisq-raw-editor-container">
|
|
881
958
|
<RawEditor
|
|
882
|
-
|
|
959
|
+
monacoTheme={colorScheme === 'dark' ? 'vs-dark' : 'vs'}
|
|
883
960
|
submitOnEnter={submitOnEnter}
|
|
884
961
|
readOnly={readOnly}
|
|
885
962
|
/>
|
|
886
963
|
</div>
|
|
887
964
|
)}
|
|
965
|
+
{/* Renders nothing in normal flow — portals context
|
|
966
|
+
sections into Monaco view zones via the context's
|
|
967
|
+
monacoEditor. Code mode only. */}
|
|
968
|
+
{isCodeMode && codeContext && (
|
|
969
|
+
<CodeContextZones key="code-context" options={codeContext} />
|
|
970
|
+
)}
|
|
888
971
|
{isMarkdownMode && isCardMode && inlinePreviewVisible && (
|
|
889
972
|
<BlockPreviewPanel key="block-preview" basePath={basePath} />
|
|
890
973
|
)}
|
|
@@ -952,8 +1035,11 @@ function EditorShellInner({
|
|
|
952
1035
|
<MediaBin
|
|
953
1036
|
mediaProvider={mediaProvider}
|
|
954
1037
|
isDark={isDark}
|
|
955
|
-
refreshKey={
|
|
956
|
-
|
|
1038
|
+
refreshKey={mediaListRefreshKey}
|
|
1039
|
+
usedMediaPaths={usedMediaPaths}
|
|
1040
|
+
onMediaUploaded={handleMediaUploaded}
|
|
1041
|
+
onMediaRemoved={handleMediaRemoved}
|
|
1042
|
+
onCountChange={setMediaCount}
|
|
957
1043
|
/>
|
|
958
1044
|
)}
|
|
959
1045
|
|
|
@@ -1006,7 +1092,7 @@ function EditorShellInner({
|
|
|
1006
1092
|
}}
|
|
1007
1093
|
allowVersioning={allowVersioning}
|
|
1008
1094
|
versioningAutoSaveIdleMs={versioningAutoSaveIdleMs}
|
|
1009
|
-
shellTheme={
|
|
1095
|
+
shellTheme={colorScheme}
|
|
1010
1096
|
/>
|
|
1011
1097
|
)}
|
|
1012
1098
|
</div>
|
|
@@ -1031,8 +1117,8 @@ interface ImageEditModalProps {
|
|
|
1031
1117
|
onSaved: () => void;
|
|
1032
1118
|
allowVersioning: boolean;
|
|
1033
1119
|
versioningAutoSaveIdleMs?: number;
|
|
1034
|
-
/** EditorShell's `
|
|
1035
|
-
* the image editor chrome matches the host shell. */
|
|
1120
|
+
/** EditorShell's `colorScheme` prop — 'light' or 'dark'. Threaded through
|
|
1121
|
+
* so the image editor chrome matches the host shell. */
|
|
1036
1122
|
shellTheme?: 'light' | 'dark';
|
|
1037
1123
|
}
|
|
1038
1124
|
|