@bendyline/squisq-editor-react 1.6.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/dist/index.d.ts +1229 -179
- package/dist/index.js +18017 -11231
- 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 +1471 -245
- package/package.json +9 -5
- package/src/BlockPropertiesPopover.tsx +30 -15
- package/src/DocumentSettingsDialog.tsx +13 -21
- package/src/EditorContext.tsx +199 -42
- package/src/EditorShell.tsx +348 -260
- 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 +309 -69
- package/src/PreviewPanel.tsx +312 -78
- package/src/RawEditor.tsx +13 -2
- 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 +202 -41
- 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__/customLayoutManagerTheme.test.tsx +34 -0
- package/src/__tests__/documentSettingsDialog.test.tsx +29 -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__/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 +218 -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__/resolveBlockVisual.test.ts +43 -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 +23 -0
- package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +109 -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 +224 -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 +227 -0
- package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +114 -0
- package/src/asciiDiagram/__tests__/asciiPaste.test.ts +43 -0
- package/src/asciiDiagram/asciiDiagramCommands.ts +148 -0
- package/src/asciiDiagram/asciiDiagramData.ts +116 -0
- package/src/asciiDiagram/asciiDiagramOps.ts +219 -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/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/designer.css +158 -99
- package/src/customTemplates/library.ts +16 -3
- 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 +9 -4
- package/src/diagram/types.ts +35 -0
- package/src/editorScrollSync.ts +73 -0
- package/src/emojiData.ts +3 -23
- 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 +191 -19
- 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/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 +93 -0
- package/src/scene/adapters/DiagramAdapter.ts +12 -101
- package/src/scene/commands/SceneCommand.ts +4 -1
- 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 +34 -18
- package/src/scene/tools/ShapeTool.ts +2 -3
- package/src/styles/ascii-diagram.css +79 -0
- package/src/styles/ascii-timeline.css +499 -0
- package/src/styles/editor.css +706 -120
- package/src/styles/index.css +3 -0
- package/src/styles/tree-view.css +139 -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 +702 -0
- package/src/timeline/TimelineViewExtension.ts +252 -0
- package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +355 -0
- package/src/timeline/__tests__/TimelineViewExtension.test.ts +163 -0
- package/src/timeline/__tests__/timelineCommands.test.ts +327 -0
- package/src/timeline/__tests__/timelineOps.test.ts +222 -0
- package/src/timeline/__tests__/timelinePaste.test.ts +34 -0
- package/src/timeline/timelineCommands.ts +367 -0
- package/src/timeline/timelineData.ts +54 -0
- package/src/timeline/timelineOps.ts +277 -0
- package/src/timeline/timelinePaste.ts +8 -0
- package/src/tiptapBridge.ts +24 -16
- package/src/transformStyleId.ts +17 -0
- package/src/treeview/TreeOutlineWidget.tsx +240 -0
- package/src/treeview/TreeViewExtension.ts +250 -0
- package/src/treeview/__tests__/TreeViewExtension.test.ts +122 -0
- package/src/treeview/__tests__/treeOps.test.ts +125 -0
- package/src/treeview/__tests__/treePaste.test.ts +40 -0
- package/src/treeview/__tests__/treeViewCommands.test.ts +163 -0
- package/src/treeview/treeOps.ts +172 -0
- package/src/treeview/treePaste.ts +13 -0
- package/src/treeview/treeViewCommands.ts +94 -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
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the text-first Document-mode projection for a transformed Doc.
|
|
3
|
+
*
|
|
4
|
+
* Transform-generated blocks are programmatic template inputs: unlike blocks
|
|
5
|
+
* parsed from Markdown, they do not carry `sourceHeading` / `contents` nodes.
|
|
6
|
+
* `docToMarkdown()` intentionally omits such blocks because its primary job is
|
|
7
|
+
* lossless authoring round-trips. For a summarized preview we instead give the
|
|
8
|
+
* generated blocks a temporary, readable authoring shape before serializing.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { docToMarkdown } from '@bendyline/squisq/doc';
|
|
12
|
+
import { stringifyMarkdown } from '@bendyline/squisq/markdown';
|
|
13
|
+
import type { MarkdownHeading, MarkdownParagraph } from '@bendyline/squisq/markdown';
|
|
14
|
+
import type { Block, Doc } from '@bendyline/squisq/schemas';
|
|
15
|
+
|
|
16
|
+
function textParagraph(value: string): MarkdownParagraph {
|
|
17
|
+
return { type: 'paragraph', children: [{ type: 'text', value }] };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function textHeading(value: string, depth: 1 | 2 = 2): MarkdownHeading {
|
|
21
|
+
return { type: 'heading', depth, children: [{ type: 'text', value }] };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function stringValue(record: Record<string, unknown>, key: string): string | undefined {
|
|
25
|
+
const value = record[key];
|
|
26
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function stringList(record: Record<string, unknown>, key: string): string[] {
|
|
30
|
+
const value = record[key];
|
|
31
|
+
if (!Array.isArray(value)) return [];
|
|
32
|
+
return value.filter((item): item is string => typeof item === 'string' && !!item.trim());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ReadableBlockContent {
|
|
36
|
+
title?: string;
|
|
37
|
+
body: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Extract the reader-facing copy carried by a programmatic template block. */
|
|
41
|
+
function readableTemplateContent(block: Block): ReadableBlockContent {
|
|
42
|
+
const value = block as unknown as Record<string, unknown>;
|
|
43
|
+
const template = stringValue(value, 'template');
|
|
44
|
+
const explicitTitle = stringValue(value, 'title');
|
|
45
|
+
|
|
46
|
+
switch (template) {
|
|
47
|
+
case 'statHighlight':
|
|
48
|
+
return {
|
|
49
|
+
title: stringValue(value, 'stat') ?? explicitTitle,
|
|
50
|
+
body: [stringValue(value, 'description')].filter(Boolean) as string[],
|
|
51
|
+
};
|
|
52
|
+
case 'factCard':
|
|
53
|
+
return {
|
|
54
|
+
title: stringValue(value, 'fact') ?? explicitTitle,
|
|
55
|
+
body: [stringValue(value, 'explanation')].filter(Boolean) as string[],
|
|
56
|
+
};
|
|
57
|
+
case 'quote':
|
|
58
|
+
return {
|
|
59
|
+
title: stringValue(value, 'quote') ?? explicitTitle,
|
|
60
|
+
body: [stringValue(value, 'attribution')].filter(Boolean) as string[],
|
|
61
|
+
};
|
|
62
|
+
case 'fullBleedQuote':
|
|
63
|
+
case 'pullQuote':
|
|
64
|
+
return {
|
|
65
|
+
title: stringValue(value, 'text') ?? stringValue(value, 'quote') ?? explicitTitle,
|
|
66
|
+
body: [stringValue(value, 'attribution')].filter(Boolean) as string[],
|
|
67
|
+
};
|
|
68
|
+
case 'dateEvent':
|
|
69
|
+
return {
|
|
70
|
+
title: stringValue(value, 'date') ?? explicitTitle,
|
|
71
|
+
body: [stringValue(value, 'description')].filter(Boolean) as string[],
|
|
72
|
+
};
|
|
73
|
+
case 'definitionCard':
|
|
74
|
+
return {
|
|
75
|
+
title: stringValue(value, 'term') ?? explicitTitle,
|
|
76
|
+
body: [stringValue(value, 'definition'), stringValue(value, 'origin')].filter(
|
|
77
|
+
Boolean,
|
|
78
|
+
) as string[],
|
|
79
|
+
};
|
|
80
|
+
case 'twoColumn':
|
|
81
|
+
return {
|
|
82
|
+
title: stringValue(value, 'header') ?? explicitTitle ?? 'Comparison',
|
|
83
|
+
body: [stringValue(value, 'left'), stringValue(value, 'right')].filter(Boolean) as string[],
|
|
84
|
+
};
|
|
85
|
+
case 'list':
|
|
86
|
+
return {
|
|
87
|
+
title: explicitTitle ?? 'Highlights',
|
|
88
|
+
body: stringList(value, 'items'),
|
|
89
|
+
};
|
|
90
|
+
case 'imageWithCaption':
|
|
91
|
+
return {
|
|
92
|
+
title: stringValue(value, 'caption') ?? stringValue(value, 'imageAlt') ?? explicitTitle,
|
|
93
|
+
body: [],
|
|
94
|
+
};
|
|
95
|
+
default:
|
|
96
|
+
return {
|
|
97
|
+
title: explicitTitle,
|
|
98
|
+
body: [
|
|
99
|
+
stringValue(value, 'body'),
|
|
100
|
+
stringValue(value, 'description'),
|
|
101
|
+
stringValue(value, 'text'),
|
|
102
|
+
].filter(Boolean) as string[],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function hasAuthoredShape(block: Block): boolean {
|
|
108
|
+
return !!(
|
|
109
|
+
block.sourceHeading ||
|
|
110
|
+
block.standaloneAnnotation ||
|
|
111
|
+
(block.contents && block.contents.length > 0)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function makeReadableBlock(block: Block): Block {
|
|
116
|
+
const children = block.children?.map(makeReadableBlock);
|
|
117
|
+
if (hasAuthoredShape(block)) {
|
|
118
|
+
return children === block.children ? block : { ...block, children };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const readable = readableTemplateContent(block);
|
|
122
|
+
return {
|
|
123
|
+
...block,
|
|
124
|
+
// Document mode is intentionally text-first. Removing the template from
|
|
125
|
+
// this temporary projection prevents docToMarkdown from re-adding a
|
|
126
|
+
// presentation annotation to the synthesized heading.
|
|
127
|
+
template: undefined,
|
|
128
|
+
sourceHeading: readable.title ? textHeading(readable.title) : undefined,
|
|
129
|
+
contents: readable.body.map(textParagraph),
|
|
130
|
+
children,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function documentTitle(doc: Doc): string | undefined {
|
|
135
|
+
if (doc.startBlock?.title?.trim()) return doc.startBlock.title.trim();
|
|
136
|
+
const title = doc.frontmatter?.title;
|
|
137
|
+
return typeof title === 'string' && title.trim() ? title.trim() : undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function hasLevelOneHeading(blocks: Block[]): boolean {
|
|
141
|
+
return blocks.some(
|
|
142
|
+
(block) =>
|
|
143
|
+
block.sourceHeading?.depth === 1 ||
|
|
144
|
+
(block.children ? hasLevelOneHeading(block.children) : false),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Serialize a transformed Doc into readable Markdown for Document mode. */
|
|
149
|
+
export function buildDocumentPreviewMarkdown(doc: Doc): string {
|
|
150
|
+
const blocks = doc.blocks.map(makeReadableBlock);
|
|
151
|
+
const title = documentTitle(doc);
|
|
152
|
+
const titledBlocks =
|
|
153
|
+
title && !hasLevelOneHeading(blocks)
|
|
154
|
+
? [
|
|
155
|
+
{
|
|
156
|
+
id: 'transform-document-title',
|
|
157
|
+
startTime: 0,
|
|
158
|
+
duration: 0,
|
|
159
|
+
audioSegment: 0,
|
|
160
|
+
sourceHeading: textHeading(title, 1),
|
|
161
|
+
contents: [],
|
|
162
|
+
} satisfies Block,
|
|
163
|
+
...blocks,
|
|
164
|
+
]
|
|
165
|
+
: blocks;
|
|
166
|
+
|
|
167
|
+
return stringifyMarkdown(docToMarkdown({ ...doc, blocks: titledBlocks }));
|
|
168
|
+
}
|
package/src/buildPreviewDoc.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 4. Synthesize a dummy audio segment for timer-based playback
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { flattenRenderableBlocks, hasTemplate } from '@bendyline/squisq/doc';
|
|
14
|
+
import { deriveTemplateInputs, flattenRenderableBlocks, hasTemplate } from '@bendyline/squisq/doc';
|
|
15
15
|
import { extractPlainText, KNOWN_BLOCK_META_KEYS } from '@bendyline/squisq/markdown';
|
|
16
16
|
import { getChildren } from '@bendyline/squisq/markdown';
|
|
17
17
|
import { iconMarker } from '@bendyline/squisq/icon-marker';
|
|
@@ -158,7 +158,12 @@ function getTemplateDefaults(
|
|
|
158
158
|
|
|
159
159
|
switch (templateName) {
|
|
160
160
|
case 'statHighlight':
|
|
161
|
-
return
|
|
161
|
+
return (
|
|
162
|
+
deriveTemplateInputs(templateName, headingText, block.contents) ?? {
|
|
163
|
+
stat: headingText,
|
|
164
|
+
description: body || headingText,
|
|
165
|
+
}
|
|
166
|
+
);
|
|
162
167
|
case 'quote':
|
|
163
168
|
case 'fullBleedQuote':
|
|
164
169
|
case 'pullQuote':
|
|
@@ -381,18 +386,20 @@ export function buildPreviewDoc(doc: Doc): Doc {
|
|
|
381
386
|
t += slide.duration as number;
|
|
382
387
|
}
|
|
383
388
|
|
|
389
|
+
const audio =
|
|
390
|
+
doc.audio?.segments?.length > 0
|
|
391
|
+
? doc.audio
|
|
392
|
+
: {
|
|
393
|
+
segments: t > 0 ? [{ src: '', name: 'preview', duration: t, startTime: 0 }] : [],
|
|
394
|
+
};
|
|
395
|
+
|
|
384
396
|
return {
|
|
385
|
-
|
|
397
|
+
// Preserve document-wide capabilities (custom themes, persistent layers,
|
|
398
|
+
// scheduled media, frontmatter, captions, and future schema fields).
|
|
399
|
+
// Preview preparation should replace only the slide/timing projection.
|
|
400
|
+
...doc,
|
|
386
401
|
duration: t,
|
|
387
402
|
blocks: slides as unknown as Block[],
|
|
388
|
-
audio
|
|
389
|
-
segments: t > 0 ? [{ src: '', name: 'preview', duration: t, startTime: 0 }] : [],
|
|
390
|
-
},
|
|
391
|
-
...(doc.captions ? { captions: doc.captions } : {}),
|
|
392
|
-
...(doc.startBlock ? { startBlock: doc.startBlock } : {}),
|
|
393
|
-
...(doc.themeId ? { themeId: doc.themeId } : {}),
|
|
394
|
-
// Custom templates ride along so `useDocPlayback` can merge them
|
|
395
|
-
// onto the registry before expanding slides.
|
|
396
|
-
...(doc.customTemplates ? { customTemplates: doc.customTemplates } : {}),
|
|
403
|
+
audio,
|
|
397
404
|
};
|
|
398
405
|
}
|
|
@@ -47,7 +47,7 @@ type Selection =
|
|
|
47
47
|
|
|
48
48
|
export function CustomLayoutManager({ onClose }: CustomLayoutManagerProps) {
|
|
49
49
|
const { docTemplates, onDocTemplatesChange } = useDocCustomTemplates();
|
|
50
|
-
const { mediaProvider } = useEditorContext();
|
|
50
|
+
const { mediaProvider, colorScheme } = useEditorContext();
|
|
51
51
|
|
|
52
52
|
// Close on Escape, mirroring the other editor dialogs.
|
|
53
53
|
useEffect(() => {
|
|
@@ -60,7 +60,8 @@ export function CustomLayoutManager({ onClose }: CustomLayoutManagerProps) {
|
|
|
60
60
|
|
|
61
61
|
return createPortal(
|
|
62
62
|
<div
|
|
63
|
-
className="squisq-layout-manager-overlay"
|
|
63
|
+
className="squisq-editor-shell squisq-layout-manager-overlay"
|
|
64
|
+
data-theme={colorScheme}
|
|
64
65
|
role="dialog"
|
|
65
66
|
aria-modal="true"
|
|
66
67
|
aria-label="Custom layouts"
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
listLibraryTemplates,
|
|
35
35
|
saveLibraryTemplate as saveLibraryTemplateRaw,
|
|
36
36
|
deleteLibraryTemplate as deleteLibraryTemplateRaw,
|
|
37
|
+
LIBRARY_STORAGE_KEY,
|
|
37
38
|
} from './library';
|
|
38
39
|
|
|
39
40
|
export interface CustomTemplateContextValue {
|
|
@@ -99,6 +100,11 @@ export function CustomTemplateProvider({
|
|
|
99
100
|
// re-reads, which is enough for the single-tab common case.
|
|
100
101
|
useEffect(() => {
|
|
101
102
|
setLibraryTemplates(listLibraryTemplates());
|
|
103
|
+
const onStorage = (event: StorageEvent) => {
|
|
104
|
+
if (event.key === LIBRARY_STORAGE_KEY) setLibraryTemplates(listLibraryTemplates());
|
|
105
|
+
};
|
|
106
|
+
window.addEventListener('storage', onStorage);
|
|
107
|
+
return () => window.removeEventListener('storage', onStorage);
|
|
102
108
|
}, []);
|
|
103
109
|
|
|
104
110
|
const upsertDocTemplate = useCallback(
|
|
@@ -77,6 +77,8 @@ interface TemplateDesignerProps {
|
|
|
77
77
|
* preview. Omit to disable media in the bin.
|
|
78
78
|
*/
|
|
79
79
|
mediaProvider?: MediaProvider | null;
|
|
80
|
+
/** Editor chrome scheme, copied onto the portal root for theme inheritance. */
|
|
81
|
+
colorScheme?: 'light' | 'dark';
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
const DESIGN_CANVAS = { width: 1920, height: 1080 };
|
|
@@ -112,6 +114,7 @@ export function TemplateDesigner({
|
|
|
112
114
|
embedded = false,
|
|
113
115
|
primarySaveLabel = 'Save to this doc',
|
|
114
116
|
mediaProvider = null,
|
|
117
|
+
colorScheme = 'light',
|
|
115
118
|
}: TemplateDesignerProps) {
|
|
116
119
|
const [name, setName] = useState(initial?.name ?? '');
|
|
117
120
|
const [label, setLabel] = useState(initial?.label ?? '');
|
|
@@ -147,9 +150,8 @@ export function TemplateDesigner({
|
|
|
147
150
|
);
|
|
148
151
|
|
|
149
152
|
// Build the toolset once. Each bin entry — placeholder token or shape —
|
|
150
|
-
// gets a click-to-place tool
|
|
151
|
-
//
|
|
152
|
-
// state-free closure.
|
|
153
|
+
// gets a click-to-place tool. Gesture state is supplied by each Scene,
|
|
154
|
+
// so the reusable tool definitions remain instance-safe.
|
|
153
155
|
const tools: SceneTool[] = useMemo(
|
|
154
156
|
() => [
|
|
155
157
|
SelectTool,
|
|
@@ -414,7 +416,8 @@ export function TemplateDesigner({
|
|
|
414
416
|
// Standalone: full-screen modal over a click-to-dismiss backdrop.
|
|
415
417
|
return createPortal(
|
|
416
418
|
<div
|
|
417
|
-
className="squisq-template-designer-overlay"
|
|
419
|
+
className="squisq-editor-shell squisq-template-designer-overlay"
|
|
420
|
+
data-theme={colorScheme}
|
|
418
421
|
role="dialog"
|
|
419
422
|
aria-modal="true"
|
|
420
423
|
aria-label="Custom layout designer"
|
|
@@ -85,4 +85,12 @@ describe('library', () => {
|
|
|
85
85
|
saveLibraryTemplate(defn('a'));
|
|
86
86
|
expect(listLibraryTemplates().map((t) => t.name)).toEqual(['a']);
|
|
87
87
|
});
|
|
88
|
+
|
|
89
|
+
it('filters structurally invalid array entries instead of crashing sort', () => {
|
|
90
|
+
window.localStorage.setItem(
|
|
91
|
+
LIBRARY_STORAGE_KEY,
|
|
92
|
+
JSON.stringify({ version: 1, templates: [null, {}, defn('valid')] }),
|
|
93
|
+
);
|
|
94
|
+
expect(listLibraryTemplates().map((template) => template.name)).toEqual(['valid']);
|
|
95
|
+
});
|
|
88
96
|
});
|