@bendyline/squisq-editor-react 1.4.0 → 1.5.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/dist/DocumentSettingsDialog.d.ts +26 -0
- package/dist/DocumentSettingsDialog.d.ts.map +1 -0
- package/dist/DocumentSettingsDialog.js +115 -0
- package/dist/DocumentSettingsDialog.js.map +1 -0
- package/dist/EditorContext.d.ts +248 -4
- package/dist/EditorContext.d.ts.map +1 -1
- package/dist/EditorContext.js +248 -10
- package/dist/EditorContext.js.map +1 -1
- package/dist/EditorShell.d.ts +173 -4
- package/dist/EditorShell.d.ts.map +1 -1
- package/dist/EditorShell.js +110 -10
- package/dist/EditorShell.js.map +1 -1
- package/dist/EmojiPicker.d.ts +50 -0
- package/dist/EmojiPicker.d.ts.map +1 -0
- package/dist/EmojiPicker.js +182 -0
- package/dist/EmojiPicker.js.map +1 -0
- package/dist/ImageEditor.d.ts +68 -0
- package/dist/ImageEditor.d.ts.map +1 -0
- package/dist/ImageEditor.js +166 -0
- package/dist/ImageEditor.js.map +1 -0
- package/dist/ImageNodeView.d.ts +13 -1
- package/dist/ImageNodeView.d.ts.map +1 -1
- package/dist/ImageNodeView.js +172 -19
- package/dist/ImageNodeView.js.map +1 -1
- package/dist/ImageViewer.d.ts +26 -0
- package/dist/ImageViewer.d.ts.map +1 -0
- package/dist/ImageViewer.js +119 -0
- package/dist/ImageViewer.js.map +1 -0
- package/dist/InlineIcon.d.ts +17 -0
- package/dist/InlineIcon.d.ts.map +1 -0
- package/dist/InlineIcon.js +72 -0
- package/dist/InlineIcon.js.map +1 -0
- package/dist/InlinePreviewGutter.d.ts +52 -0
- package/dist/InlinePreviewGutter.d.ts.map +1 -0
- package/dist/InlinePreviewGutter.js +397 -0
- package/dist/InlinePreviewGutter.js.map +1 -0
- package/dist/LinkDialog.d.ts +43 -0
- package/dist/LinkDialog.d.ts.map +1 -0
- package/dist/LinkDialog.js +102 -0
- package/dist/LinkDialog.js.map +1 -0
- package/dist/MentionExtension.js +10 -7
- package/dist/MentionExtension.js.map +1 -1
- package/dist/OutlinePanel.d.ts +17 -0
- package/dist/OutlinePanel.d.ts.map +1 -0
- package/dist/OutlinePanel.js +167 -0
- package/dist/OutlinePanel.js.map +1 -0
- package/dist/PlainHtmlPreview.d.ts +50 -0
- package/dist/PlainHtmlPreview.d.ts.map +1 -0
- package/dist/PlainHtmlPreview.js +155 -0
- package/dist/PlainHtmlPreview.js.map +1 -0
- package/dist/PreviewControls.d.ts +15 -1
- package/dist/PreviewControls.d.ts.map +1 -1
- package/dist/PreviewControls.js +75 -18
- package/dist/PreviewControls.js.map +1 -1
- package/dist/PreviewPanel.d.ts +11 -10
- package/dist/PreviewPanel.d.ts.map +1 -1
- package/dist/PreviewPanel.js +20 -17
- package/dist/PreviewPanel.js.map +1 -1
- package/dist/RawEditor.d.ts.map +1 -1
- package/dist/RawEditor.js +198 -4
- package/dist/RawEditor.js.map +1 -1
- package/dist/RecorderEntry.d.ts +24 -0
- package/dist/RecorderEntry.d.ts.map +1 -0
- package/dist/RecorderEntry.js +139 -0
- package/dist/RecorderEntry.js.map +1 -0
- package/dist/TemplateAnnotation.d.ts.map +1 -1
- package/dist/TemplateAnnotation.js +32 -6
- package/dist/TemplateAnnotation.js.map +1 -1
- package/dist/TemplatePicker.d.ts +53 -0
- package/dist/TemplatePicker.d.ts.map +1 -0
- package/dist/TemplatePicker.js +388 -0
- package/dist/TemplatePicker.js.map +1 -0
- package/dist/ThemeCustomizerPanel.d.ts +32 -0
- package/dist/ThemeCustomizerPanel.d.ts.map +1 -0
- package/dist/ThemeCustomizerPanel.js +256 -0
- package/dist/ThemeCustomizerPanel.js.map +1 -0
- package/dist/ThemePicker.d.ts +33 -0
- package/dist/ThemePicker.d.ts.map +1 -0
- package/dist/ThemePicker.js +148 -0
- package/dist/ThemePicker.js.map +1 -0
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar.js +508 -33
- package/dist/Toolbar.js.map +1 -1
- package/dist/VersionHistoryPanel.d.ts +14 -0
- package/dist/VersionHistoryPanel.d.ts.map +1 -0
- package/dist/VersionHistoryPanel.js +147 -0
- package/dist/VersionHistoryPanel.js.map +1 -0
- package/dist/ViewMenuPanel.d.ts +13 -0
- package/dist/ViewMenuPanel.d.ts.map +1 -0
- package/dist/ViewMenuPanel.js +58 -0
- package/dist/ViewMenuPanel.js.map +1 -0
- package/dist/WysiwygEditor.d.ts.map +1 -1
- package/dist/WysiwygEditor.js +198 -9
- package/dist/WysiwygEditor.js.map +1 -1
- package/dist/__tests__/detectMarkdown.test.js +0 -14
- package/dist/__tests__/detectMarkdown.test.js.map +1 -1
- package/dist/__tests__/documentSettingsDialog.test.d.ts +2 -0
- package/dist/__tests__/documentSettingsDialog.test.d.ts.map +1 -0
- package/dist/__tests__/documentSettingsDialog.test.js +132 -0
- package/dist/__tests__/documentSettingsDialog.test.js.map +1 -0
- package/dist/__tests__/emojiPicker.test.d.ts +2 -0
- package/dist/__tests__/emojiPicker.test.d.ts.map +1 -0
- package/dist/__tests__/emojiPicker.test.js +111 -0
- package/dist/__tests__/emojiPicker.test.js.map +1 -0
- package/dist/__tests__/fileKind.test.js +13 -0
- package/dist/__tests__/fileKind.test.js.map +1 -1
- package/dist/__tests__/imageEditAffordance.test.d.ts +2 -0
- package/dist/__tests__/imageEditAffordance.test.d.ts.map +1 -0
- package/dist/__tests__/imageEditAffordance.test.js +188 -0
- package/dist/__tests__/imageEditAffordance.test.js.map +1 -0
- package/dist/__tests__/imageEditorShell.test.d.ts +2 -0
- package/dist/__tests__/imageEditorShell.test.d.ts.map +1 -0
- package/dist/__tests__/imageEditorShell.test.js +52 -0
- package/dist/__tests__/imageEditorShell.test.js.map +1 -0
- package/dist/__tests__/imageEditorState.test.d.ts +3 -0
- package/dist/__tests__/imageEditorState.test.d.ts.map +1 -0
- package/dist/__tests__/imageEditorState.test.js +148 -0
- package/dist/__tests__/imageEditorState.test.js.map +1 -0
- package/dist/__tests__/inlinePreviewGutter.test.d.ts +2 -0
- package/dist/__tests__/inlinePreviewGutter.test.d.ts.map +1 -0
- package/dist/__tests__/inlinePreviewGutter.test.js +51 -0
- package/dist/__tests__/inlinePreviewGutter.test.js.map +1 -0
- package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts +2 -0
- package/dist/__tests__/inlinePreviewGutterAllBlocks.test.d.ts.map +1 -0
- package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js +63 -0
- package/dist/__tests__/inlinePreviewGutterAllBlocks.test.js.map +1 -0
- package/dist/__tests__/jsonEditor.test.d.ts +2 -0
- package/dist/__tests__/jsonEditor.test.d.ts.map +1 -0
- package/dist/__tests__/jsonEditor.test.js +134 -0
- package/dist/__tests__/jsonEditor.test.js.map +1 -0
- package/dist/__tests__/layersPanel.test.d.ts +2 -0
- package/dist/__tests__/layersPanel.test.d.ts.map +1 -0
- package/dist/__tests__/layersPanel.test.js +84 -0
- package/dist/__tests__/layersPanel.test.js.map +1 -0
- package/dist/__tests__/linkDialogDocPicker.test.d.ts +7 -0
- package/dist/__tests__/linkDialogDocPicker.test.d.ts.map +1 -0
- package/dist/__tests__/linkDialogDocPicker.test.js +75 -0
- package/dist/__tests__/linkDialogDocPicker.test.js.map +1 -0
- package/dist/__tests__/outlinePanel.test.d.ts +2 -0
- package/dist/__tests__/outlinePanel.test.d.ts.map +1 -0
- package/dist/__tests__/outlinePanel.test.js +68 -0
- package/dist/__tests__/outlinePanel.test.js.map +1 -0
- package/dist/__tests__/plainHtmlPreview.test.d.ts +2 -0
- package/dist/__tests__/plainHtmlPreview.test.d.ts.map +1 -0
- package/dist/__tests__/plainHtmlPreview.test.js +87 -0
- package/dist/__tests__/plainHtmlPreview.test.js.map +1 -0
- package/dist/__tests__/propertiesPanel.test.d.ts +2 -0
- package/dist/__tests__/propertiesPanel.test.d.ts.map +1 -0
- package/dist/__tests__/propertiesPanel.test.js +64 -0
- package/dist/__tests__/propertiesPanel.test.js.map +1 -0
- package/dist/__tests__/recorderFormats.test.d.ts +2 -0
- package/dist/__tests__/recorderFormats.test.d.ts.map +1 -0
- package/dist/__tests__/recorderFormats.test.js +121 -0
- package/dist/__tests__/recorderFormats.test.js.map +1 -0
- package/dist/__tests__/recorderTimingJson.test.d.ts +2 -0
- package/dist/__tests__/recorderTimingJson.test.d.ts.map +1 -0
- package/dist/__tests__/recorderTimingJson.test.js +37 -0
- package/dist/__tests__/recorderTimingJson.test.js.map +1 -0
- package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts +2 -0
- package/dist/__tests__/templateAnnotationRoundTrip.test.d.ts.map +1 -0
- package/dist/__tests__/templateAnnotationRoundTrip.test.js +31 -0
- package/dist/__tests__/templateAnnotationRoundTrip.test.js.map +1 -0
- package/dist/__tests__/tiptapBridge.test.js +13 -0
- package/dist/__tests__/tiptapBridge.test.js.map +1 -1
- package/dist/__tests__/useImageEditor.test.d.ts +2 -0
- package/dist/__tests__/useImageEditor.test.d.ts.map +1 -0
- package/dist/__tests__/useImageEditor.test.js +131 -0
- package/dist/__tests__/useImageEditor.test.js.map +1 -0
- package/dist/__tests__/useMediaRecorder.test.d.ts +2 -0
- package/dist/__tests__/useMediaRecorder.test.d.ts.map +1 -0
- package/dist/__tests__/useMediaRecorder.test.js +153 -0
- package/dist/__tests__/useMediaRecorder.test.js.map +1 -0
- package/dist/__tests__/versionHistory.test.d.ts +2 -0
- package/dist/__tests__/versionHistory.test.d.ts.map +1 -0
- package/dist/__tests__/versionHistory.test.js +124 -0
- package/dist/__tests__/versionHistory.test.js.map +1 -0
- package/dist/blockSlice.d.ts +24 -0
- package/dist/blockSlice.d.ts.map +1 -0
- package/dist/blockSlice.js +63 -0
- package/dist/blockSlice.js.map +1 -0
- package/dist/buildPreviewDoc.d.ts.map +1 -1
- package/dist/buildPreviewDoc.js +52 -2
- package/dist/buildPreviewDoc.js.map +1 -1
- package/dist/emojiData.d.ts +81 -0
- package/dist/emojiData.d.ts.map +1 -0
- package/dist/emojiData.js +1283 -0
- package/dist/emojiData.js.map +1 -0
- package/dist/fileKind.d.ts +6 -2
- package/dist/fileKind.d.ts.map +1 -1
- package/dist/fileKind.js +25 -4
- package/dist/fileKind.js.map +1 -1
- package/dist/hooks/useFileDrop.d.ts.map +1 -1
- package/dist/hooks/useFileDrop.js +40 -4
- package/dist/hooks/useFileDrop.js.map +1 -1
- package/dist/imageEditor/CanvasSurface.d.ts +31 -0
- package/dist/imageEditor/CanvasSurface.d.ts.map +1 -0
- package/dist/imageEditor/CanvasSurface.js +264 -0
- package/dist/imageEditor/CanvasSurface.js.map +1 -0
- package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts +39 -0
- package/dist/imageEditor/ImageVersionHistoryDropdown.d.ts.map +1 -0
- package/dist/imageEditor/ImageVersionHistoryDropdown.js +283 -0
- package/dist/imageEditor/ImageVersionHistoryDropdown.js.map +1 -0
- package/dist/imageEditor/LayersPanel.d.ts +14 -0
- package/dist/imageEditor/LayersPanel.d.ts.map +1 -0
- package/dist/imageEditor/LayersPanel.js +43 -0
- package/dist/imageEditor/LayersPanel.js.map +1 -0
- package/dist/imageEditor/PropertiesPanel.d.ts +14 -0
- package/dist/imageEditor/PropertiesPanel.d.ts.map +1 -0
- package/dist/imageEditor/PropertiesPanel.js +97 -0
- package/dist/imageEditor/PropertiesPanel.js.map +1 -0
- package/dist/imageEditor/Toolbar.d.ts +30 -0
- package/dist/imageEditor/Toolbar.d.ts.map +1 -0
- package/dist/imageEditor/Toolbar.js +108 -0
- package/dist/imageEditor/Toolbar.js.map +1 -0
- package/dist/imageEditor/icons.d.ts +24 -0
- package/dist/imageEditor/icons.d.ts.map +1 -0
- package/dist/imageEditor/icons.js +45 -0
- package/dist/imageEditor/icons.js.map +1 -0
- package/dist/imageEditor/layers/EditorImageLayer.d.ts +16 -0
- package/dist/imageEditor/layers/EditorImageLayer.d.ts.map +1 -0
- package/dist/imageEditor/layers/EditorImageLayer.js +37 -0
- package/dist/imageEditor/layers/EditorImageLayer.js.map +1 -0
- package/dist/imageEditor/layers/EditorShapeLayer.d.ts +15 -0
- package/dist/imageEditor/layers/EditorShapeLayer.d.ts.map +1 -0
- package/dist/imageEditor/layers/EditorShapeLayer.js +20 -0
- package/dist/imageEditor/layers/EditorShapeLayer.js.map +1 -0
- package/dist/imageEditor/layers/EditorTextLayer.d.ts +18 -0
- package/dist/imageEditor/layers/EditorTextLayer.d.ts.map +1 -0
- package/dist/imageEditor/layers/EditorTextLayer.js +13 -0
- package/dist/imageEditor/layers/EditorTextLayer.js.map +1 -0
- package/dist/imageEditor/layers/SelectionHandles.d.ts +17 -0
- package/dist/imageEditor/layers/SelectionHandles.d.ts.map +1 -0
- package/dist/imageEditor/layers/SelectionHandles.js +19 -0
- package/dist/imageEditor/layers/SelectionHandles.js.map +1 -0
- package/dist/imageEditor/state.d.ts +76 -0
- package/dist/imageEditor/state.d.ts.map +1 -0
- package/dist/imageEditor/state.js +87 -0
- package/dist/imageEditor/state.js.map +1 -0
- package/dist/imageEditor/useImageEditor.d.ts +53 -0
- package/dist/imageEditor/useImageEditor.d.ts.map +1 -0
- package/dist/imageEditor/useImageEditor.js +244 -0
- package/dist/imageEditor/useImageEditor.js.map +1 -0
- package/dist/imageEditor/useImageEditorTokens.d.ts +16 -0
- package/dist/imageEditor/useImageEditorTokens.d.ts.map +1 -0
- package/dist/imageEditor/useImageEditorTokens.js +45 -0
- package/dist/imageEditor/useImageEditorTokens.js.map +1 -0
- package/dist/index.d.ts +48 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -1
- package/dist/jsonEditor/EmbeddedRichTextField.d.ts +15 -0
- package/dist/jsonEditor/EmbeddedRichTextField.d.ts.map +1 -0
- package/dist/jsonEditor/EmbeddedRichTextField.js +74 -0
- package/dist/jsonEditor/EmbeddedRichTextField.js.map +1 -0
- package/dist/jsonEditor/JsonEditor.d.ts +36 -0
- package/dist/jsonEditor/JsonEditor.d.ts.map +1 -0
- package/dist/jsonEditor/JsonEditor.js +15 -0
- package/dist/jsonEditor/JsonEditor.js.map +1 -0
- package/dist/jsonEditor/JsonEditorContext.d.ts +28 -0
- package/dist/jsonEditor/JsonEditorContext.d.ts.map +1 -0
- package/dist/jsonEditor/JsonEditorContext.js +41 -0
- package/dist/jsonEditor/JsonEditorContext.js.map +1 -0
- package/dist/jsonEditor/RenderNode.d.ts +16 -0
- package/dist/jsonEditor/RenderNode.d.ts.map +1 -0
- package/dist/jsonEditor/RenderNode.js +32 -0
- package/dist/jsonEditor/RenderNode.js.map +1 -0
- package/dist/jsonEditor/editors.d.ts +36 -0
- package/dist/jsonEditor/editors.d.ts.map +1 -0
- package/dist/jsonEditor/editors.js +347 -0
- package/dist/jsonEditor/editors.js.map +1 -0
- package/dist/jsonEditor/index.d.ts +3 -0
- package/dist/jsonEditor/index.d.ts.map +1 -0
- package/dist/jsonEditor/index.js +2 -0
- package/dist/jsonEditor/index.js.map +1 -0
- package/dist/jsonEditor/useJsonEditorTokens.d.ts +13 -0
- package/dist/jsonEditor/useJsonEditorTokens.d.ts.map +1 -0
- package/dist/jsonEditor/useJsonEditorTokens.js +38 -0
- package/dist/jsonEditor/useJsonEditorTokens.js.map +1 -0
- package/dist/recorder/RecorderButton.d.ts +31 -0
- package/dist/recorder/RecorderButton.d.ts.map +1 -0
- package/dist/recorder/RecorderButton.js +24 -0
- package/dist/recorder/RecorderButton.js.map +1 -0
- package/dist/recorder/RecorderModal.d.ts +59 -0
- package/dist/recorder/RecorderModal.d.ts.map +1 -0
- package/dist/recorder/RecorderModal.js +333 -0
- package/dist/recorder/RecorderModal.js.map +1 -0
- package/dist/recorder/RecorderPanel.d.ts +25 -0
- package/dist/recorder/RecorderPanel.d.ts.map +1 -0
- package/dist/recorder/RecorderPanel.js +30 -0
- package/dist/recorder/RecorderPanel.js.map +1 -0
- package/dist/recorder/formats.d.ts +51 -0
- package/dist/recorder/formats.d.ts.map +1 -0
- package/dist/recorder/formats.js +144 -0
- package/dist/recorder/formats.js.map +1 -0
- package/dist/recorder/hooks/useMediaRecorder.d.ts +90 -0
- package/dist/recorder/hooks/useMediaRecorder.d.ts.map +1 -0
- package/dist/recorder/hooks/useMediaRecorder.js +277 -0
- package/dist/recorder/hooks/useMediaRecorder.js.map +1 -0
- package/dist/recorder/hooks/useStreamPreview.d.ts +22 -0
- package/dist/recorder/hooks/useStreamPreview.d.ts.map +1 -0
- package/dist/recorder/hooks/useStreamPreview.js +44 -0
- package/dist/recorder/hooks/useStreamPreview.js.map +1 -0
- package/dist/recorder/sources/cameraStream.d.ts +22 -0
- package/dist/recorder/sources/cameraStream.d.ts.map +1 -0
- package/dist/recorder/sources/cameraStream.js +24 -0
- package/dist/recorder/sources/cameraStream.js.map +1 -0
- package/dist/recorder/sources/micStream.d.ts +15 -0
- package/dist/recorder/sources/micStream.d.ts.map +1 -0
- package/dist/recorder/sources/micStream.js +24 -0
- package/dist/recorder/sources/micStream.js.map +1 -0
- package/dist/recorder/sources/screenStream.d.ts +53 -0
- package/dist/recorder/sources/screenStream.d.ts.map +1 -0
- package/dist/recorder/sources/screenStream.js +114 -0
- package/dist/recorder/sources/screenStream.js.map +1 -0
- package/dist/recorder/timingJson.d.ts +51 -0
- package/dist/recorder/timingJson.d.ts.map +1 -0
- package/dist/recorder/timingJson.js +42 -0
- package/dist/recorder/timingJson.js.map +1 -0
- package/dist/tiptap/TiptapAudio.d.ts +26 -0
- package/dist/tiptap/TiptapAudio.d.ts.map +1 -0
- package/dist/tiptap/TiptapAudio.js +58 -0
- package/dist/tiptap/TiptapAudio.js.map +1 -0
- package/dist/tiptap/TiptapVideo.d.ts +30 -0
- package/dist/tiptap/TiptapVideo.d.ts.map +1 -0
- package/dist/tiptap/TiptapVideo.js +66 -0
- package/dist/tiptap/TiptapVideo.js.map +1 -0
- package/dist/tiptap/useResolvedMediaSrc.d.ts +2 -0
- package/dist/tiptap/useResolvedMediaSrc.d.ts.map +1 -0
- package/dist/tiptap/useResolvedMediaSrc.js +42 -0
- package/dist/tiptap/useResolvedMediaSrc.js.map +1 -0
- package/dist/tiptapBridge.d.ts.map +1 -1
- package/dist/tiptapBridge.js +171 -14
- package/dist/tiptapBridge.js.map +1 -1
- package/dist/useHeadingLayout.d.ts +54 -0
- package/dist/useHeadingLayout.d.ts.map +1 -0
- package/dist/useHeadingLayout.js +260 -0
- package/dist/useHeadingLayout.js.map +1 -0
- package/dist/utils/collectInlineFontAwesomeCss.d.ts +21 -0
- package/dist/utils/collectInlineFontAwesomeCss.d.ts.map +1 -0
- package/dist/utils/collectInlineFontAwesomeCss.js +68 -0
- package/dist/utils/collectInlineFontAwesomeCss.js.map +1 -0
- package/dist/utils/dropUtils.d.ts +21 -2
- package/dist/utils/dropUtils.d.ts.map +1 -1
- package/dist/utils/dropUtils.js +43 -4
- package/dist/utils/dropUtils.js.map +1 -1
- package/dist/utils/normalizeMalformedAssetUrl.d.ts +15 -0
- package/dist/utils/normalizeMalformedAssetUrl.d.ts.map +1 -0
- package/dist/utils/normalizeMalformedAssetUrl.js +27 -0
- package/dist/utils/normalizeMalformedAssetUrl.js.map +1 -0
- package/package.json +8 -5
- package/src/DocumentSettingsDialog.tsx +266 -0
- package/src/EditorContext.tsx +534 -10
- package/src/EditorShell.tsx +571 -55
- package/src/EmojiPicker.tsx +332 -0
- package/src/ImageEditor.tsx +327 -0
- package/src/ImageNodeView.tsx +222 -21
- package/src/ImageViewer.tsx +221 -0
- package/src/InlineIcon.ts +84 -0
- package/src/InlinePreviewGutter.tsx +582 -0
- package/src/LinkDialog.tsx +276 -0
- package/src/MentionExtension.tsx +10 -7
- package/src/OutlinePanel.tsx +295 -0
- package/src/PlainHtmlPreview.tsx +211 -0
- package/src/PreviewControls.tsx +130 -24
- package/src/PreviewPanel.tsx +38 -21
- package/src/RawEditor.tsx +215 -4
- package/src/RecorderEntry.tsx +164 -0
- package/src/TemplateAnnotation.ts +32 -6
- package/src/TemplatePicker.tsx +818 -0
- package/src/ThemeCustomizerPanel.tsx +595 -0
- package/src/ThemePicker.tsx +319 -0
- package/src/Toolbar.tsx +708 -111
- package/src/VersionHistoryPanel.tsx +329 -0
- package/src/ViewMenuPanel.tsx +188 -0
- package/src/WysiwygEditor.tsx +229 -9
- package/src/__tests__/detectMarkdown.test.ts +0 -15
- package/src/__tests__/documentSettingsDialog.test.tsx +147 -0
- package/src/__tests__/emojiPicker.test.tsx +133 -0
- package/src/__tests__/fileKind.test.ts +16 -0
- package/src/__tests__/imageEditAffordance.test.tsx +268 -0
- package/src/__tests__/imageEditorShell.test.tsx +57 -0
- package/src/__tests__/imageEditorState.test.ts +171 -0
- package/src/__tests__/inlinePreviewGutter.test.tsx +62 -0
- package/src/__tests__/inlinePreviewGutterAllBlocks.test.tsx +103 -0
- package/src/__tests__/jsonEditor.test.tsx +168 -0
- package/src/__tests__/layersPanel.test.tsx +97 -0
- package/src/__tests__/linkDialogDocPicker.test.tsx +137 -0
- package/src/__tests__/outlinePanel.test.tsx +79 -0
- package/src/__tests__/plainHtmlPreview.test.tsx +107 -0
- package/src/__tests__/propertiesPanel.test.tsx +69 -0
- package/src/__tests__/recorderFormats.test.ts +146 -0
- package/src/__tests__/recorderTimingJson.test.ts +41 -0
- package/src/__tests__/templateAnnotationRoundTrip.test.ts +34 -0
- package/src/__tests__/tiptapBridge.test.ts +15 -0
- package/src/__tests__/useImageEditor.test.tsx +159 -0
- package/src/__tests__/useMediaRecorder.test.ts +186 -0
- package/src/__tests__/versionHistory.test.tsx +197 -0
- package/src/blockSlice.ts +75 -0
- package/src/buildPreviewDoc.ts +61 -6
- package/src/emojiData.ts +1337 -0
- package/src/fileKind.ts +30 -6
- package/src/hooks/useFileDrop.ts +40 -4
- package/src/imageEditor/CanvasSurface.tsx +402 -0
- package/src/imageEditor/ImageVersionHistoryDropdown.tsx +396 -0
- package/src/imageEditor/LayersPanel.tsx +143 -0
- package/src/imageEditor/PropertiesPanel.tsx +428 -0
- package/src/imageEditor/Toolbar.tsx +242 -0
- package/src/imageEditor/icons.tsx +144 -0
- package/src/imageEditor/image-editor.css +450 -0
- package/src/imageEditor/layers/EditorImageLayer.tsx +45 -0
- package/src/imageEditor/layers/EditorShapeLayer.tsx +62 -0
- package/src/imageEditor/layers/EditorTextLayer.tsx +45 -0
- package/src/imageEditor/layers/SelectionHandles.tsx +86 -0
- package/src/imageEditor/state.ts +153 -0
- package/src/imageEditor/useImageEditor.ts +328 -0
- package/src/imageEditor/useImageEditorTokens.ts +70 -0
- package/src/index.ts +82 -0
- package/src/jsonEditor/EmbeddedRichTextField.tsx +81 -0
- package/src/jsonEditor/JsonEditor.tsx +81 -0
- package/src/jsonEditor/JsonEditorContext.tsx +75 -0
- package/src/jsonEditor/RenderNode.tsx +66 -0
- package/src/jsonEditor/editors.tsx +678 -0
- package/src/jsonEditor/index.ts +2 -0
- package/src/jsonEditor/json-editor.css +463 -0
- package/src/jsonEditor/useJsonEditorTokens.ts +63 -0
- package/src/recorder/RecorderButton.tsx +72 -0
- package/src/recorder/RecorderModal.tsx +596 -0
- package/src/recorder/RecorderPanel.tsx +93 -0
- package/src/recorder/formats.ts +159 -0
- package/src/recorder/hooks/useMediaRecorder.ts +378 -0
- package/src/recorder/hooks/useStreamPreview.ts +47 -0
- package/src/recorder/sources/cameraStream.ts +32 -0
- package/src/recorder/sources/micStream.ts +25 -0
- package/src/recorder/sources/screenStream.ts +162 -0
- package/src/recorder/timingJson.ts +66 -0
- package/src/styles/editor.css +2490 -51
- package/src/styles/image-edit-affordance.css +201 -0
- package/src/styles/index.css +10 -0
- package/src/tiptap/TiptapAudio.tsx +86 -0
- package/src/tiptap/TiptapVideo.tsx +119 -0
- package/src/tiptap/useResolvedMediaSrc.ts +47 -0
- package/src/tiptapBridge.ts +188 -20
- package/src/useHeadingLayout.ts +294 -0
- package/src/utils/collectInlineFontAwesomeCss.ts +69 -0
- package/src/utils/dropUtils.ts +54 -6
- package/src/utils/normalizeMalformedAssetUrl.ts +22 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlainHtmlPreview
|
|
3
|
+
*
|
|
4
|
+
* Live WYSIWYG preview of the plain-HTML export. Renders the result of
|
|
5
|
+
* `markdownDocToPlainHtml` inside a sandboxed `<iframe srcDoc>` so the
|
|
6
|
+
* exported document's inline `<style>` block can't leak into the host
|
|
7
|
+
* page — and so the preview looks identical to what users get when they
|
|
8
|
+
* open the downloaded `.html`.
|
|
9
|
+
*
|
|
10
|
+
* Image handling: relative `<img src>` references in the markdown can't
|
|
11
|
+
* load directly from inside the iframe (there's no real document
|
|
12
|
+
* origin). When a `mediaProvider` is supplied, this component walks the
|
|
13
|
+
* parsed markdown for image refs, resolves each through
|
|
14
|
+
* `mediaProvider.resolveUrl()` (which returns a cached blob URL), and
|
|
15
|
+
* passes the resolved map to the renderer so the iframe gets blob URLs
|
|
16
|
+
* it can fetch.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
20
|
+
import type { CSSProperties } from 'react';
|
|
21
|
+
import { parseMarkdown } from '@bendyline/squisq/markdown';
|
|
22
|
+
import type { MarkdownDocument, HtmlNode } from '@bendyline/squisq/markdown';
|
|
23
|
+
import type { MediaProvider, Theme } from '@bendyline/squisq/schemas';
|
|
24
|
+
import { markdownDocToPlainHtml } from '@bendyline/squisq-formats/html';
|
|
25
|
+
import { normalizeMalformedAssetUrl } from './utils/normalizeMalformedAssetUrl';
|
|
26
|
+
import { collectInlineFontAwesomeCss } from './utils/collectInlineFontAwesomeCss';
|
|
27
|
+
|
|
28
|
+
export interface PlainHtmlPreviewProps {
|
|
29
|
+
/** Raw markdown source. */
|
|
30
|
+
markdown: string;
|
|
31
|
+
/** Document title — populates the iframe's `<title>`. */
|
|
32
|
+
title?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Pre-resolved image substitutions (export-time use). Takes precedence
|
|
35
|
+
* over live `mediaProvider` resolution for any URL it contains.
|
|
36
|
+
*/
|
|
37
|
+
images?: Map<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* When passed, relative image URLs in the markdown are resolved live
|
|
40
|
+
* via this provider. Skip for static previews where `images` already
|
|
41
|
+
* contains everything.
|
|
42
|
+
*/
|
|
43
|
+
mediaProvider?: MediaProvider | null;
|
|
44
|
+
/** Token that, when changed, forces re-resolution of media URLs.
|
|
45
|
+
* Mirrors the `mediaRevision` bump the editor uses after an image
|
|
46
|
+
* edit so saves show up in the preview without remount. */
|
|
47
|
+
mediaRevision?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Squisq theme to apply. When set, the iframe loads any Google-
|
|
50
|
+
* hosted fonts the theme uses and the rendered HTML adopts the
|
|
51
|
+
* theme's colors and typography.
|
|
52
|
+
*/
|
|
53
|
+
theme?: Theme;
|
|
54
|
+
className?: string;
|
|
55
|
+
style?: CSSProperties;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const IFRAME_STYLE: CSSProperties = {
|
|
59
|
+
width: '100%',
|
|
60
|
+
height: '100%',
|
|
61
|
+
border: 'none',
|
|
62
|
+
background: '#fff',
|
|
63
|
+
display: 'block',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export function PlainHtmlPreview({
|
|
67
|
+
markdown,
|
|
68
|
+
title,
|
|
69
|
+
images,
|
|
70
|
+
mediaProvider,
|
|
71
|
+
mediaRevision,
|
|
72
|
+
theme,
|
|
73
|
+
className,
|
|
74
|
+
style,
|
|
75
|
+
}: PlainHtmlPreviewProps) {
|
|
76
|
+
const mdDoc = useMemo<MarkdownDocument>(() => parseMarkdown(markdown), [markdown]);
|
|
77
|
+
|
|
78
|
+
// Resolve any relative image URLs the doc references. Blob URLs are
|
|
79
|
+
// cheap once cached, so re-resolving on every keystroke is fine —
|
|
80
|
+
// `resolveUrl` is memoized inside the provider.
|
|
81
|
+
const [resolvedImages, setResolvedImages] = useState<Map<string, string> | null>(null);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!mediaProvider) {
|
|
85
|
+
setResolvedImages(null);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let cancelled = false;
|
|
89
|
+
const refs = Array.from(collectImageRefs(mdDoc));
|
|
90
|
+
Promise.all(
|
|
91
|
+
refs.map(async (ref) => {
|
|
92
|
+
// Word-style imports may have `http://<doc>_files/foo.png` —
|
|
93
|
+
// recover the relative path so the workspace provider resolves
|
|
94
|
+
// it, otherwise the iframe tries to fetch a nonsense hostname.
|
|
95
|
+
const recovered = normalizeMalformedAssetUrl(ref);
|
|
96
|
+
if (!recovered && isExternal(ref)) return [ref, ref] as const;
|
|
97
|
+
const lookup = recovered ?? ref;
|
|
98
|
+
try {
|
|
99
|
+
const url = await mediaProvider.resolveUrl(lookup);
|
|
100
|
+
return [ref, url] as const;
|
|
101
|
+
} catch {
|
|
102
|
+
return [ref, ref] as const;
|
|
103
|
+
}
|
|
104
|
+
}),
|
|
105
|
+
).then((pairs) => {
|
|
106
|
+
if (cancelled) return;
|
|
107
|
+
const next = new Map<string, string>(pairs);
|
|
108
|
+
setResolvedImages(next);
|
|
109
|
+
});
|
|
110
|
+
return () => {
|
|
111
|
+
cancelled = true;
|
|
112
|
+
};
|
|
113
|
+
}, [mdDoc, mediaProvider, mediaRevision]);
|
|
114
|
+
|
|
115
|
+
const mergedImages = useMemo(() => {
|
|
116
|
+
if (!resolvedImages && !images) return undefined;
|
|
117
|
+
const merged = new Map<string, string>();
|
|
118
|
+
if (resolvedImages) for (const [k, v] of resolvedImages) merged.set(k, v);
|
|
119
|
+
if (images) for (const [k, v] of images) merged.set(k, v);
|
|
120
|
+
return merged;
|
|
121
|
+
}, [resolvedImages, images]);
|
|
122
|
+
|
|
123
|
+
// Gather FontAwesome @font-face + utility rules from the host page's
|
|
124
|
+
// own stylesheets so the iframe doesn't have to depend on a cross-
|
|
125
|
+
// origin CDN fetch (which sandbox / tracking-prevention can silently
|
|
126
|
+
// drop, leaving the icons invisible). The host (editor-react) already
|
|
127
|
+
// bundles FA, so the rules are guaranteed to be present and the font
|
|
128
|
+
// URLs inside them resolve to same-origin assets the iframe can
|
|
129
|
+
// fetch under `allow-same-origin`.
|
|
130
|
+
const iconsCss = useMemo(() => collectInlineFontAwesomeCss(), []);
|
|
131
|
+
|
|
132
|
+
const html = useMemo(
|
|
133
|
+
() => markdownDocToPlainHtml(mdDoc, { title, images: mergedImages, theme, iconsCss }),
|
|
134
|
+
[mdDoc, title, mergedImages, theme, iconsCss],
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<iframe
|
|
139
|
+
className={className}
|
|
140
|
+
data-testid="plain-html-preview"
|
|
141
|
+
title={title ?? 'HTML preview'}
|
|
142
|
+
srcDoc={html}
|
|
143
|
+
// `allow-same-origin` is required so the iframe can fetch blob:
|
|
144
|
+
// URLs created by the host's media provider. We intentionally do
|
|
145
|
+
// NOT include `allow-scripts` — the rendered HTML is plain-output
|
|
146
|
+
// markup with no JS, and refusing scripts hardens against
|
|
147
|
+
// accidental `<script>` content in user markdown.
|
|
148
|
+
sandbox="allow-same-origin"
|
|
149
|
+
style={{ ...IFRAME_STYLE, ...style }}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Image collection ───────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
function isExternal(url: string): boolean {
|
|
157
|
+
return (
|
|
158
|
+
!url ||
|
|
159
|
+
url.startsWith('data:') ||
|
|
160
|
+
url.startsWith('blob:') ||
|
|
161
|
+
url.startsWith('http://') ||
|
|
162
|
+
url.startsWith('https://') ||
|
|
163
|
+
url.startsWith('//')
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Collect every image URL referenced anywhere in the doc — markdown
|
|
169
|
+
* `image` nodes plus any `<img src>` inside raw HTML blocks/inlines
|
|
170
|
+
* (the WYSIWYG editor emits the HTML form for resized images).
|
|
171
|
+
*/
|
|
172
|
+
function collectImageRefs(doc: MarkdownDocument): Set<string> {
|
|
173
|
+
const refs = new Set<string>();
|
|
174
|
+
|
|
175
|
+
function visitHtml(nodes: HtmlNode[]): void {
|
|
176
|
+
for (const n of nodes) {
|
|
177
|
+
if (n.type !== 'htmlElement') continue;
|
|
178
|
+
const tag = n.tagName.toLowerCase();
|
|
179
|
+
// <img>, <video>, and <audio> all reference media via `src`.
|
|
180
|
+
// The export pipeline rewrites whichever map entries exist, so
|
|
181
|
+
// collecting them under the same set is enough — `ctx.images`
|
|
182
|
+
// is generic media despite the historical name.
|
|
183
|
+
if (tag === 'img' || tag === 'video' || tag === 'audio' || tag === 'source') {
|
|
184
|
+
const src = n.attributes.src;
|
|
185
|
+
if (typeof src === 'string' && src) refs.add(src);
|
|
186
|
+
}
|
|
187
|
+
if (tag === 'video' || tag === 'audio') {
|
|
188
|
+
const poster = n.attributes.poster;
|
|
189
|
+
if (typeof poster === 'string' && poster) refs.add(poster);
|
|
190
|
+
}
|
|
191
|
+
visitHtml(n.children);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function visit(node: unknown): void {
|
|
196
|
+
if (!node || typeof node !== 'object') return;
|
|
197
|
+
const n = node as Record<string, unknown>;
|
|
198
|
+
if (n.type === 'image' && typeof n.url === 'string' && n.url) {
|
|
199
|
+
refs.add(n.url);
|
|
200
|
+
}
|
|
201
|
+
if ((n.type === 'htmlBlock' || n.type === 'htmlInline') && Array.isArray(n.htmlChildren)) {
|
|
202
|
+
visitHtml(n.htmlChildren as HtmlNode[]);
|
|
203
|
+
}
|
|
204
|
+
if (Array.isArray(n.children)) {
|
|
205
|
+
for (const child of n.children) visit(child);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const child of doc.children) visit(child);
|
|
210
|
+
return refs;
|
|
211
|
+
}
|
package/src/PreviewControls.tsx
CHANGED
|
@@ -10,14 +10,25 @@
|
|
|
10
10
|
* - PreviewPanel (the actual player, which reads the selected values)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createContext,
|
|
15
|
+
useCallback,
|
|
16
|
+
useContext,
|
|
17
|
+
useState,
|
|
18
|
+
useMemo,
|
|
19
|
+
useEffect,
|
|
20
|
+
useRef,
|
|
21
|
+
} from 'react';
|
|
14
22
|
import type { ReactNode } from 'react';
|
|
15
23
|
import type { DisplayMode, CaptionStyle } from '@bendyline/squisq-react';
|
|
16
24
|
import type { ViewportPreset, ViewportConfig } from '@bendyline/squisq/schemas';
|
|
17
25
|
import { VIEWPORT_PRESETS, getThemeSummaries, resolveTheme } from '@bendyline/squisq/schemas';
|
|
18
26
|
import type { Theme } from '@bendyline/squisq/schemas';
|
|
27
|
+
import { ThemePicker } from './ThemePicker';
|
|
19
28
|
import { getTransformStyleSummaries } from '@bendyline/squisq/transform';
|
|
20
29
|
import type { Doc } from '@bendyline/squisq/schemas';
|
|
30
|
+
import { setFrontmatterValues } from '@bendyline/squisq/markdown';
|
|
31
|
+
import { useEditorContext } from './EditorContext';
|
|
21
32
|
|
|
22
33
|
// ── Context ──────────────────────────────────────────────────────
|
|
23
34
|
|
|
@@ -44,6 +55,16 @@ export function usePreviewSettings(): PreviewSettings {
|
|
|
44
55
|
return ctx;
|
|
45
56
|
}
|
|
46
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Like {@link usePreviewSettings} but returns `null` when no provider is
|
|
60
|
+
* mounted. For consumers (e.g. WysiwygEditor) that want to react to the
|
|
61
|
+
* active theme when available without forcing every test harness to
|
|
62
|
+
* wrap them in a PreviewSettingsProvider.
|
|
63
|
+
*/
|
|
64
|
+
export function usePreviewSettingsOptional(): PreviewSettings | null {
|
|
65
|
+
return useContext(PreviewSettingsContext);
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
// ── Frontmatter resolvers ────────────────────────────────────────
|
|
48
69
|
|
|
49
70
|
function resolveRenderAs(value: unknown): ViewportPreset | null {
|
|
@@ -68,9 +89,10 @@ function resolveRenderAs(value: unknown): ViewportPreset | null {
|
|
|
68
89
|
function resolveDisplayMode(value: unknown): DisplayMode | null {
|
|
69
90
|
if (typeof value !== 'string') return null;
|
|
70
91
|
const v = value.trim().toLowerCase();
|
|
71
|
-
if (v === 'video' || v === 'slideshow' || v === 'linear') return v;
|
|
92
|
+
if (v === 'video' || v === 'slideshow' || v === 'linear' || v === 'page') return v;
|
|
72
93
|
if (v === 'slides' || v === 'presentation' || v === 'deck') return 'slideshow';
|
|
73
|
-
if (v === 'document' || v === 'scroll'
|
|
94
|
+
if (v === 'document' || v === 'scroll') return 'linear';
|
|
95
|
+
if (v === 'html' || v === 'plain' || v === 'reader') return 'page';
|
|
74
96
|
return null;
|
|
75
97
|
}
|
|
76
98
|
|
|
@@ -109,10 +131,50 @@ function resolveFrontmatterCaptionStyle(value: unknown): CaptionStyle | null {
|
|
|
109
131
|
export interface PreviewSettingsProviderProps {
|
|
110
132
|
doc: Doc | null;
|
|
111
133
|
children: ReactNode;
|
|
134
|
+
/**
|
|
135
|
+
* Optional Theme to use for the preview, regardless of `Doc.themeId` or
|
|
136
|
+
* the user's theme dropdown selection. Used by the theme customizer to
|
|
137
|
+
* preview an in-progress theme without mutating the document. When
|
|
138
|
+
* present, `activeTheme` is this value and `activeThemeId` is its `id`.
|
|
139
|
+
*/
|
|
140
|
+
themeOverride?: Theme | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Frontmatter keys we read/write for preview settings. The squisq-prefixed
|
|
144
|
+
* keys are the canonical names; the legacy keys are still read so existing
|
|
145
|
+
* documents keep working. Persistence (writes) uses only the squisq names. */
|
|
146
|
+
const FM_KEYS = {
|
|
147
|
+
theme: { canonical: 'squisq-theme', legacy: 'theme' as const },
|
|
148
|
+
transform: { canonical: 'squisq-transform', legacy: 'transform-style' as const },
|
|
149
|
+
captions: { canonical: 'squisq-captions', legacy: 'caption-style' as const },
|
|
150
|
+
} as const;
|
|
151
|
+
|
|
152
|
+
function readFrontmatterKey(
|
|
153
|
+
fm: Record<string, unknown> | undefined,
|
|
154
|
+
canonical: string,
|
|
155
|
+
legacy: string,
|
|
156
|
+
): unknown {
|
|
157
|
+
if (!fm) return undefined;
|
|
158
|
+
return Object.prototype.hasOwnProperty.call(fm, canonical) ? fm[canonical] : fm[legacy];
|
|
112
159
|
}
|
|
113
160
|
|
|
114
|
-
export function PreviewSettingsProvider({
|
|
161
|
+
export function PreviewSettingsProvider({
|
|
162
|
+
doc,
|
|
163
|
+
children,
|
|
164
|
+
themeOverride,
|
|
165
|
+
}: PreviewSettingsProviderProps) {
|
|
115
166
|
const frontmatter = doc?.frontmatter;
|
|
167
|
+
const { markdownSource, setMarkdownSource } = useEditorContext();
|
|
168
|
+
|
|
169
|
+
const persistFrontmatter = useCallback(
|
|
170
|
+
(updates: Record<string, string | null>) => {
|
|
171
|
+
const next = setFrontmatterValues(markdownSource, updates);
|
|
172
|
+
if (next !== markdownSource) {
|
|
173
|
+
setMarkdownSource(next);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
[markdownSource, setMarkdownSource],
|
|
177
|
+
);
|
|
116
178
|
|
|
117
179
|
// Viewport
|
|
118
180
|
const fmPreset = useMemo(
|
|
@@ -130,30 +192,69 @@ export function PreviewSettingsProvider({ doc, children }: PreviewSettingsProvid
|
|
|
130
192
|
useEffect(() => setSelectedDisplayMode(null), [fmMode]);
|
|
131
193
|
const activeDisplayMode = selectedDisplayMode ?? fmMode ?? 'video';
|
|
132
194
|
|
|
133
|
-
// Theme
|
|
134
|
-
const fmTheme = useMemo(
|
|
195
|
+
// Theme — persisted to `squisq-theme` (legacy `theme` still read for compat)
|
|
196
|
+
const fmTheme = useMemo(
|
|
197
|
+
() =>
|
|
198
|
+
resolveFrontmatterTheme(
|
|
199
|
+
readFrontmatterKey(frontmatter, FM_KEYS.theme.canonical, FM_KEYS.theme.legacy),
|
|
200
|
+
),
|
|
201
|
+
[frontmatter],
|
|
202
|
+
);
|
|
135
203
|
const [selectedThemeId, setSelectedThemeId] = useState<string | null>(null);
|
|
136
204
|
useEffect(() => setSelectedThemeId(null), [fmTheme]);
|
|
137
|
-
const
|
|
138
|
-
const
|
|
205
|
+
const resolvedThemeId = selectedThemeId ?? fmTheme ?? 'standard';
|
|
206
|
+
const resolvedTheme = useMemo(() => resolveTheme(resolvedThemeId), [resolvedThemeId]);
|
|
207
|
+
// themeOverride wins over both dropdown selection and frontmatter
|
|
208
|
+
const activeThemeId = themeOverride?.id ?? resolvedThemeId;
|
|
209
|
+
const activeTheme = themeOverride ?? resolvedTheme;
|
|
210
|
+
const handleSetThemeId = useCallback(
|
|
211
|
+
(id: string | null) => {
|
|
212
|
+
setSelectedThemeId(id);
|
|
213
|
+
if (id !== null) persistFrontmatter({ [FM_KEYS.theme.canonical]: id });
|
|
214
|
+
},
|
|
215
|
+
[persistFrontmatter],
|
|
216
|
+
);
|
|
139
217
|
|
|
140
|
-
// Transform
|
|
218
|
+
// Transform — persisted to `squisq-transform` (legacy `transform-style` read for compat)
|
|
141
219
|
const fmTransform = useMemo(
|
|
142
|
-
() =>
|
|
220
|
+
() =>
|
|
221
|
+
resolveFrontmatterTransform(
|
|
222
|
+
readFrontmatterKey(frontmatter, FM_KEYS.transform.canonical, FM_KEYS.transform.legacy),
|
|
223
|
+
),
|
|
143
224
|
[frontmatter],
|
|
144
225
|
);
|
|
145
226
|
const [selectedTransformStyle, setSelectedTransformStyle] = useState<string | null>(null);
|
|
146
227
|
useEffect(() => setSelectedTransformStyle(null), [fmTransform]);
|
|
147
228
|
const activeTransformStyle = selectedTransformStyle ?? fmTransform ?? '';
|
|
229
|
+
const handleSetTransformStyle = useCallback(
|
|
230
|
+
(id: string | null) => {
|
|
231
|
+
setSelectedTransformStyle(id);
|
|
232
|
+
if (id !== null) {
|
|
233
|
+
// Empty string = "None" — remove the key rather than writing a blank value.
|
|
234
|
+
persistFrontmatter({ [FM_KEYS.transform.canonical]: id === '' ? null : id });
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
[persistFrontmatter],
|
|
238
|
+
);
|
|
148
239
|
|
|
149
|
-
// Caption style
|
|
240
|
+
// Caption style — persisted to `squisq-captions` (legacy `caption-style` read for compat)
|
|
150
241
|
const fmCaption = useMemo(
|
|
151
|
-
() =>
|
|
242
|
+
() =>
|
|
243
|
+
resolveFrontmatterCaptionStyle(
|
|
244
|
+
readFrontmatterKey(frontmatter, FM_KEYS.captions.canonical, FM_KEYS.captions.legacy),
|
|
245
|
+
),
|
|
152
246
|
[frontmatter],
|
|
153
247
|
);
|
|
154
248
|
const [selectedCaptionStyle, setSelectedCaptionStyle] = useState<CaptionStyle | null>(null);
|
|
155
249
|
useEffect(() => setSelectedCaptionStyle(null), [fmCaption]);
|
|
156
250
|
const activeCaptionStyle = selectedCaptionStyle ?? fmCaption ?? 'standard';
|
|
251
|
+
const handleSetCaptionStyle = useCallback(
|
|
252
|
+
(style: CaptionStyle | null) => {
|
|
253
|
+
setSelectedCaptionStyle(style);
|
|
254
|
+
if (style !== null) persistFrontmatter({ [FM_KEYS.captions.canonical]: style });
|
|
255
|
+
},
|
|
256
|
+
[persistFrontmatter],
|
|
257
|
+
);
|
|
157
258
|
|
|
158
259
|
const value = useMemo<PreviewSettings>(
|
|
159
260
|
() => ({
|
|
@@ -163,12 +264,12 @@ export function PreviewSettingsProvider({ doc, children }: PreviewSettingsProvid
|
|
|
163
264
|
activeDisplayMode,
|
|
164
265
|
setSelectedDisplayMode,
|
|
165
266
|
activeThemeId,
|
|
166
|
-
setSelectedThemeId,
|
|
267
|
+
setSelectedThemeId: handleSetThemeId,
|
|
167
268
|
activeTheme,
|
|
168
269
|
activeTransformStyle,
|
|
169
|
-
setSelectedTransformStyle,
|
|
270
|
+
setSelectedTransformStyle: handleSetTransformStyle,
|
|
170
271
|
activeCaptionStyle,
|
|
171
|
-
setSelectedCaptionStyle,
|
|
272
|
+
setSelectedCaptionStyle: handleSetCaptionStyle,
|
|
172
273
|
}),
|
|
173
274
|
[
|
|
174
275
|
activePreset,
|
|
@@ -178,6 +279,9 @@ export function PreviewSettingsProvider({ doc, children }: PreviewSettingsProvid
|
|
|
178
279
|
activeTheme,
|
|
179
280
|
activeTransformStyle,
|
|
180
281
|
activeCaptionStyle,
|
|
282
|
+
handleSetThemeId,
|
|
283
|
+
handleSetTransformStyle,
|
|
284
|
+
handleSetCaptionStyle,
|
|
181
285
|
],
|
|
182
286
|
);
|
|
183
287
|
|
|
@@ -199,10 +303,9 @@ const DISPLAY_MODE_OPTIONS: { key: DisplayMode; label: string }[] = [
|
|
|
199
303
|
{ key: 'video', label: 'Video' },
|
|
200
304
|
{ key: 'slideshow', label: 'Slideshow' },
|
|
201
305
|
{ key: 'linear', label: 'Document' },
|
|
306
|
+
{ key: 'page', label: 'Page' },
|
|
202
307
|
];
|
|
203
308
|
|
|
204
|
-
const THEME_OPTIONS = getThemeSummaries().map((s) => ({ key: s.id, label: s.name }));
|
|
205
|
-
|
|
206
309
|
const TRANSFORM_STYLE_OPTIONS = [
|
|
207
310
|
{ key: '', label: 'None' },
|
|
208
311
|
...getTransformStyleSummaries().map((s) => ({ key: s.id, label: s.name })),
|
|
@@ -285,13 +388,16 @@ export function PreviewToolbarControls() {
|
|
|
285
388
|
onChange={(v) => s.setSelectedDisplayMode(v as DisplayMode)}
|
|
286
389
|
compact={isNarrow}
|
|
287
390
|
/>
|
|
288
|
-
<
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
391
|
+
<div
|
|
392
|
+
className={`squisq-preview-control${isNarrow ? ' squisq-preview-control--compact' : ''}`}
|
|
393
|
+
>
|
|
394
|
+
<label style={labelStyle}>Theme:</label>
|
|
395
|
+
<ThemePicker
|
|
396
|
+
value={s.activeThemeId}
|
|
397
|
+
onChange={(v) => s.setSelectedThemeId(v)}
|
|
398
|
+
ariaLabel="Theme"
|
|
399
|
+
/>
|
|
400
|
+
</div>
|
|
295
401
|
<PreviewSelect
|
|
296
402
|
label="Transform"
|
|
297
403
|
value={s.activeTransformStyle}
|
package/src/PreviewPanel.tsx
CHANGED
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
* PreviewPanel
|
|
3
3
|
*
|
|
4
4
|
* Renders a live preview of the current markdown document as a slideshow
|
|
5
|
-
* using the DocPlayer component from @bendyline/squisq-react.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* visual layers. This component bridges the gap by using buildPreviewDoc()
|
|
10
|
-
* to flatten blocks, convert them to TemplateBlock slides with interleaved
|
|
11
|
-
* images, and synthesize timing.
|
|
5
|
+
* using the DocPlayer component from @bendyline/squisq-react. The
|
|
6
|
+
* markdown → player-Doc conversion is delegated to the shared
|
|
7
|
+
* `buildPreviewDoc` helper so live preview and the export pipeline stay
|
|
8
|
+
* in sync.
|
|
12
9
|
*/
|
|
13
10
|
|
|
14
11
|
import { useState, useEffect } from 'react';
|
|
15
|
-
import { DocPlayer, LinearDocView } from '@bendyline/squisq-react';
|
|
12
|
+
import { DocPlayer, LinearDocView, useMediaProvider } from '@bendyline/squisq-react';
|
|
16
13
|
import type { Doc } from '@bendyline/squisq/schemas';
|
|
17
14
|
import { applyTransform } from '@bendyline/squisq/transform';
|
|
18
15
|
import { resolveAudioMapping } from '@bendyline/squisq/doc';
|
|
@@ -20,14 +17,19 @@ import type { ContentContainer } from '@bendyline/squisq/storage';
|
|
|
20
17
|
import { useEditorContext } from './EditorContext';
|
|
21
18
|
import { usePreviewSettings } from './PreviewControls';
|
|
22
19
|
import { buildPreviewDoc } from './buildPreviewDoc';
|
|
20
|
+
import { PlainHtmlPreview } from './PlainHtmlPreview';
|
|
23
21
|
|
|
24
22
|
export interface PreviewPanelProps {
|
|
25
23
|
/** Base path for resolving media URLs in DocPlayer */
|
|
26
24
|
basePath?: string;
|
|
27
25
|
/** Additional class name for the container */
|
|
28
26
|
className?: string;
|
|
29
|
-
/**
|
|
30
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Workspace-scoped `ContentContainer` (the folder holding the doc and
|
|
29
|
+
* its siblings). Used here for audio mapping — MP3 discovery and
|
|
30
|
+
* `timing.json` reading.
|
|
31
|
+
*/
|
|
32
|
+
workspaceContainer?: ContentContainer | null;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
// ── Component ──────────────────────────────────────────────────────
|
|
@@ -37,8 +39,9 @@ export interface PreviewPanelProps {
|
|
|
37
39
|
* or document view. Controls (viewport, mode, theme, transform, captions)
|
|
38
40
|
* are rendered in the main toolbar via PreviewToolbarControls.
|
|
39
41
|
*/
|
|
40
|
-
export function PreviewPanel({ basePath = '/', className,
|
|
41
|
-
const { doc, parseError, isParsing } = useEditorContext();
|
|
42
|
+
export function PreviewPanel({ basePath = '/', className, workspaceContainer }: PreviewPanelProps) {
|
|
43
|
+
const { doc, parseError, isParsing, markdownSource, mediaRevision } = useEditorContext();
|
|
44
|
+
const mediaProvider = useMediaProvider();
|
|
42
45
|
const {
|
|
43
46
|
activeViewport,
|
|
44
47
|
activeDisplayMode,
|
|
@@ -69,10 +72,10 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
|
|
|
69
72
|
sourceDoc = result.doc;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
// If we have a container, try to resolve audio mapping before building preview
|
|
73
|
-
if (
|
|
75
|
+
// If we have a workspace container, try to resolve audio mapping before building preview
|
|
76
|
+
if (workspaceContainer) {
|
|
74
77
|
let cancelled = false;
|
|
75
|
-
resolveAudioMapping(sourceDoc,
|
|
78
|
+
resolveAudioMapping(sourceDoc, workspaceContainer).then((audioDoc) => {
|
|
76
79
|
if (!cancelled) {
|
|
77
80
|
setPreviewDoc(buildPreviewDoc(audioDoc));
|
|
78
81
|
}
|
|
@@ -85,7 +88,7 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
setPreviewDoc(buildPreviewDoc(sourceDoc));
|
|
88
|
-
}, [doc, activeTransformStyle,
|
|
91
|
+
}, [doc, activeTransformStyle, workspaceContainer]);
|
|
89
92
|
|
|
90
93
|
// Status overlays for non-ready states
|
|
91
94
|
if (isParsing) {
|
|
@@ -105,7 +108,10 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
|
|
|
105
108
|
);
|
|
106
109
|
}
|
|
107
110
|
|
|
108
|
-
|
|
111
|
+
// Page mode renders directly from markdown — it doesn't depend on the
|
|
112
|
+
// parsed Doc tree or the player preview build, so let it fall through
|
|
113
|
+
// even when those aren't ready yet.
|
|
114
|
+
if (!previewDoc && activeDisplayMode !== 'page') {
|
|
109
115
|
return (
|
|
110
116
|
<div className={`squisq-preview-status ${className || ''}`} data-testid="preview-panel">
|
|
111
117
|
<p>No content to preview. Start typing in the editor.</p>
|
|
@@ -113,6 +119,9 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
|
|
|
113
119
|
);
|
|
114
120
|
}
|
|
115
121
|
|
|
122
|
+
const fillsContainer =
|
|
123
|
+
activeDisplayMode === 'linear' || activeDisplayMode === 'page' ? 'stretch' : 'center';
|
|
124
|
+
|
|
116
125
|
return (
|
|
117
126
|
<div
|
|
118
127
|
className={`squisq-preview-container ${className || ''}`}
|
|
@@ -126,19 +135,27 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
|
|
|
126
135
|
background: 'var(--squisq-bg, #f5f5f5)',
|
|
127
136
|
}}
|
|
128
137
|
>
|
|
129
|
-
{/* Player / Document view */}
|
|
138
|
+
{/* Player / Document / Page view */}
|
|
130
139
|
<div
|
|
131
140
|
className="squisq-preview-player"
|
|
132
141
|
style={{
|
|
133
142
|
flex: 1,
|
|
134
143
|
display: 'flex',
|
|
135
|
-
alignItems:
|
|
144
|
+
alignItems: fillsContainer,
|
|
136
145
|
justifyContent: 'center',
|
|
137
146
|
overflow: 'hidden',
|
|
138
147
|
minHeight: 0,
|
|
139
148
|
}}
|
|
140
149
|
>
|
|
141
|
-
{activeDisplayMode === '
|
|
150
|
+
{activeDisplayMode === 'page' ? (
|
|
151
|
+
<PlainHtmlPreview
|
|
152
|
+
markdown={markdownSource}
|
|
153
|
+
title={(doc?.frontmatter?.title as string | undefined) ?? undefined}
|
|
154
|
+
mediaProvider={mediaProvider}
|
|
155
|
+
mediaRevision={mediaRevision}
|
|
156
|
+
theme={activeTheme}
|
|
157
|
+
/>
|
|
158
|
+
) : activeDisplayMode === 'linear' ? (
|
|
142
159
|
<LinearDocView
|
|
143
160
|
doc={doc!}
|
|
144
161
|
basePath={basePath}
|
|
@@ -147,7 +164,7 @@ export function PreviewPanel({ basePath = '/', className, container }: PreviewPa
|
|
|
147
164
|
/>
|
|
148
165
|
) : (
|
|
149
166
|
<DocPlayer
|
|
150
|
-
script={previewDoc}
|
|
167
|
+
script={previewDoc!}
|
|
151
168
|
basePath={basePath}
|
|
152
169
|
showControls
|
|
153
170
|
muted
|