@bendyline/squisq-editor-react 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LinkDialog — modal for inserting or editing a markdown link.
|
|
3
|
+
*
|
|
4
|
+
* Used by both the WYSIWYG and Raw markdown toolbars so the link-editing
|
|
5
|
+
* UX is identical regardless of view. Shows separate fields for the link
|
|
6
|
+
* text (caption) and the URL target so the user can see and edit both.
|
|
7
|
+
*
|
|
8
|
+
* When a `documentLinkProvider` is supplied, the dialog also exposes a
|
|
9
|
+
* second tab — "Browse documents" — that lists neighbor docs from the
|
|
10
|
+
* host's workspace. Selecting one fills the URL with the candidate
|
|
11
|
+
* path (and the caption, if it was still empty), so authors can link
|
|
12
|
+
* `home.md → resume.md` without typing the relative path by hand.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
16
|
+
import type { DocumentLinkProvider, DocumentLinkCandidate } from './EditorContext';
|
|
17
|
+
|
|
18
|
+
export interface LinkDialogProps {
|
|
19
|
+
/** Whether this is a brand-new link (Insert) or an existing one (Update). */
|
|
20
|
+
mode: 'insert' | 'update';
|
|
21
|
+
/** Initial value for the caption field. */
|
|
22
|
+
initialText: string;
|
|
23
|
+
/** Initial value for the URL field. */
|
|
24
|
+
initialUrl: string;
|
|
25
|
+
/**
|
|
26
|
+
* Confirm — `text` may be empty if the caller wants to fall back to URL
|
|
27
|
+
* as the visible text. Empty `url` means the user cleared it; callers
|
|
28
|
+
* should treat that as "remove link" when in update mode.
|
|
29
|
+
*/
|
|
30
|
+
onConfirm: (text: string, url: string) => void;
|
|
31
|
+
/** Dismiss without applying changes. */
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Optional sibling-document picker. When provided, the dialog shows
|
|
35
|
+
* a "Browse documents" tab that queries the provider as the author
|
|
36
|
+
* types and offers click-to-pick suggestions.
|
|
37
|
+
*/
|
|
38
|
+
documentLinkProvider?: DocumentLinkProvider | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Centered modal with Text and URL inputs. Submits on Enter, dismisses
|
|
43
|
+
* on Escape or backdrop click. Auto-focuses URL when the text field is
|
|
44
|
+
* already populated; otherwise focuses Text.
|
|
45
|
+
*/
|
|
46
|
+
export function LinkDialog({
|
|
47
|
+
mode,
|
|
48
|
+
initialText,
|
|
49
|
+
initialUrl,
|
|
50
|
+
onConfirm,
|
|
51
|
+
onClose,
|
|
52
|
+
documentLinkProvider,
|
|
53
|
+
}: LinkDialogProps) {
|
|
54
|
+
const [text, setText] = useState(initialText);
|
|
55
|
+
const [url, setUrl] = useState(initialUrl);
|
|
56
|
+
const [tab, setTab] = useState<'url' | 'documents'>('url');
|
|
57
|
+
const [docQuery, setDocQuery] = useState('');
|
|
58
|
+
const [docResults, setDocResults] = useState<DocumentLinkCandidate[]>([]);
|
|
59
|
+
const [docLoading, setDocLoading] = useState(false);
|
|
60
|
+
const textRef = useRef<HTMLInputElement>(null);
|
|
61
|
+
const urlRef = useRef<HTMLInputElement>(null);
|
|
62
|
+
const docSearchRef = useRef<HTMLInputElement>(null);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
// If we have a caption already, the URL is what the user is most
|
|
66
|
+
// likely here to edit; otherwise focus the caption field first.
|
|
67
|
+
const target = initialText ? urlRef.current : textRef.current;
|
|
68
|
+
target?.focus();
|
|
69
|
+
target?.select();
|
|
70
|
+
}, [initialText]);
|
|
71
|
+
|
|
72
|
+
// Refresh the document candidate list whenever the user types into
|
|
73
|
+
// the picker or first opens it. Empty query = initial list.
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!documentLinkProvider || tab !== 'documents') return;
|
|
76
|
+
let cancelled = false;
|
|
77
|
+
setDocLoading(true);
|
|
78
|
+
documentLinkProvider(docQuery)
|
|
79
|
+
.then((results) => {
|
|
80
|
+
if (!cancelled) {
|
|
81
|
+
setDocResults(results);
|
|
82
|
+
setDocLoading(false);
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
.catch(() => {
|
|
86
|
+
if (!cancelled) {
|
|
87
|
+
setDocResults([]);
|
|
88
|
+
setDocLoading(false);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return () => {
|
|
92
|
+
cancelled = true;
|
|
93
|
+
};
|
|
94
|
+
}, [docQuery, documentLinkProvider, tab]);
|
|
95
|
+
|
|
96
|
+
// Auto-focus the search field when the documents tab opens.
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (tab === 'documents') {
|
|
99
|
+
const t = setTimeout(() => docSearchRef.current?.focus(), 0);
|
|
100
|
+
return () => clearTimeout(t);
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}, [tab]);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
const onKey = (e: KeyboardEvent) => {
|
|
107
|
+
if (e.key === 'Escape') {
|
|
108
|
+
e.stopPropagation();
|
|
109
|
+
onClose();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
window.addEventListener('keydown', onKey);
|
|
113
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
114
|
+
}, [onClose]);
|
|
115
|
+
|
|
116
|
+
const handleSubmit = useCallback(
|
|
117
|
+
(e: React.FormEvent) => {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
onConfirm(text, url);
|
|
120
|
+
},
|
|
121
|
+
[text, url, onConfirm],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const handleBackdropClick = useCallback(
|
|
125
|
+
(e: React.MouseEvent) => {
|
|
126
|
+
if (e.target === e.currentTarget) onClose();
|
|
127
|
+
},
|
|
128
|
+
[onClose],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const isUpdate = mode === 'update';
|
|
132
|
+
const submitLabel = isUpdate ? 'Update' : 'Insert';
|
|
133
|
+
const heading = isUpdate ? 'Edit link' : 'Insert link';
|
|
134
|
+
|
|
135
|
+
const handlePickCandidate = useCallback((candidate: DocumentLinkCandidate) => {
|
|
136
|
+
// Picking a document fills the URL and the caption (when the
|
|
137
|
+
// caption is still empty — preserve any caption the user already
|
|
138
|
+
// typed). Switch back to the URL tab so the result is immediately
|
|
139
|
+
// visible and editable, and so Enter submits the form.
|
|
140
|
+
setUrl(candidate.path);
|
|
141
|
+
setText((existing) => existing || candidate.label);
|
|
142
|
+
setTab('url');
|
|
143
|
+
}, []);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div className="squisq-link-dialog-overlay" onMouseDown={handleBackdropClick}>
|
|
147
|
+
<form className="squisq-link-dialog" onSubmit={handleSubmit}>
|
|
148
|
+
<div className="squisq-link-dialog-header">
|
|
149
|
+
<h2 className="squisq-link-dialog-title">{heading}</h2>
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
className="squisq-link-dialog-close"
|
|
153
|
+
onClick={onClose}
|
|
154
|
+
aria-label="Close"
|
|
155
|
+
>
|
|
156
|
+
×
|
|
157
|
+
</button>
|
|
158
|
+
</div>
|
|
159
|
+
<div className="squisq-link-dialog-body">
|
|
160
|
+
{documentLinkProvider && (
|
|
161
|
+
<div className="squisq-link-dialog-tabs" role="tablist">
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
role="tab"
|
|
165
|
+
aria-selected={tab === 'url'}
|
|
166
|
+
className={`squisq-link-dialog-tab${tab === 'url' ? ' squisq-link-dialog-tab--active' : ''}`}
|
|
167
|
+
onClick={() => setTab('url')}
|
|
168
|
+
>
|
|
169
|
+
URL
|
|
170
|
+
</button>
|
|
171
|
+
<button
|
|
172
|
+
type="button"
|
|
173
|
+
role="tab"
|
|
174
|
+
aria-selected={tab === 'documents'}
|
|
175
|
+
className={`squisq-link-dialog-tab${tab === 'documents' ? ' squisq-link-dialog-tab--active' : ''}`}
|
|
176
|
+
onClick={() => setTab('documents')}
|
|
177
|
+
>
|
|
178
|
+
Browse documents
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
<label className="squisq-link-dialog-field">
|
|
183
|
+
<span className="squisq-link-dialog-label">Text</span>
|
|
184
|
+
<input
|
|
185
|
+
ref={textRef}
|
|
186
|
+
type="text"
|
|
187
|
+
className="squisq-link-dialog-input"
|
|
188
|
+
value={text}
|
|
189
|
+
onChange={(e) => setText(e.target.value)}
|
|
190
|
+
placeholder="Link caption"
|
|
191
|
+
/>
|
|
192
|
+
</label>
|
|
193
|
+
{tab === 'url' ? (
|
|
194
|
+
<label className="squisq-link-dialog-field">
|
|
195
|
+
<span className="squisq-link-dialog-label">URL</span>
|
|
196
|
+
<input
|
|
197
|
+
ref={urlRef}
|
|
198
|
+
type="text"
|
|
199
|
+
className="squisq-link-dialog-input"
|
|
200
|
+
value={url}
|
|
201
|
+
onChange={(e) => setUrl(e.target.value)}
|
|
202
|
+
placeholder="https://example.com"
|
|
203
|
+
spellCheck={false}
|
|
204
|
+
autoComplete="off"
|
|
205
|
+
/>
|
|
206
|
+
</label>
|
|
207
|
+
) : (
|
|
208
|
+
<div className="squisq-link-dialog-doc-picker">
|
|
209
|
+
<label className="squisq-link-dialog-field">
|
|
210
|
+
<span className="squisq-link-dialog-label">Search</span>
|
|
211
|
+
<input
|
|
212
|
+
ref={docSearchRef}
|
|
213
|
+
type="text"
|
|
214
|
+
className="squisq-link-dialog-input"
|
|
215
|
+
value={docQuery}
|
|
216
|
+
onChange={(e) => setDocQuery(e.target.value)}
|
|
217
|
+
placeholder="Type to filter…"
|
|
218
|
+
spellCheck={false}
|
|
219
|
+
autoComplete="off"
|
|
220
|
+
/>
|
|
221
|
+
</label>
|
|
222
|
+
<div
|
|
223
|
+
className="squisq-link-dialog-doc-list"
|
|
224
|
+
role="listbox"
|
|
225
|
+
aria-label="Document candidates"
|
|
226
|
+
aria-busy={docLoading}
|
|
227
|
+
>
|
|
228
|
+
{docLoading && docResults.length === 0 ? (
|
|
229
|
+
<div className="squisq-link-dialog-doc-empty">Loading…</div>
|
|
230
|
+
) : docResults.length === 0 ? (
|
|
231
|
+
<div className="squisq-link-dialog-doc-empty">
|
|
232
|
+
{docQuery.trim() ? `No matches for "${docQuery.trim()}"` : 'No documents'}
|
|
233
|
+
</div>
|
|
234
|
+
) : (
|
|
235
|
+
docResults.map((c) => (
|
|
236
|
+
<button
|
|
237
|
+
key={c.path}
|
|
238
|
+
type="button"
|
|
239
|
+
role="option"
|
|
240
|
+
aria-selected={url === c.path}
|
|
241
|
+
className={`squisq-link-dialog-doc-item${url === c.path ? ' squisq-link-dialog-doc-item--selected' : ''}`}
|
|
242
|
+
onClick={() => handlePickCandidate(c)}
|
|
243
|
+
>
|
|
244
|
+
<span className="squisq-link-dialog-doc-item-label">{c.label}</span>
|
|
245
|
+
<span className="squisq-link-dialog-doc-item-path">{c.path}</span>
|
|
246
|
+
{c.description && (
|
|
247
|
+
<span className="squisq-link-dialog-doc-item-desc">{c.description}</span>
|
|
248
|
+
)}
|
|
249
|
+
</button>
|
|
250
|
+
))
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
{url && (
|
|
254
|
+
<div className="squisq-link-dialog-doc-current">
|
|
255
|
+
Selected: <code>{url}</code>
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
)}
|
|
260
|
+
</div>
|
|
261
|
+
<div className="squisq-link-dialog-footer">
|
|
262
|
+
<button
|
|
263
|
+
type="button"
|
|
264
|
+
className="squisq-link-dialog-btn squisq-link-dialog-btn--secondary"
|
|
265
|
+
onClick={onClose}
|
|
266
|
+
>
|
|
267
|
+
Cancel
|
|
268
|
+
</button>
|
|
269
|
+
<button type="submit" className="squisq-link-dialog-btn squisq-link-dialog-btn--primary">
|
|
270
|
+
{submitLabel}
|
|
271
|
+
</button>
|
|
272
|
+
</div>
|
|
273
|
+
</form>
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
}
|
package/src/MentionExtension.tsx
CHANGED
|
@@ -244,15 +244,18 @@ function positionTo(
|
|
|
244
244
|
): void {
|
|
245
245
|
const rect = clientRect?.();
|
|
246
246
|
if (!rect) return;
|
|
247
|
-
// Anchor
|
|
247
|
+
// Anchor above the caret first — chat composers live near the
|
|
248
|
+
// bottom of the viewport, where a "below" popover gets clipped or
|
|
249
|
+
// covers the just-typed text. Fall back to below only when there's
|
|
250
|
+
// no room above (top of a long document, etc.).
|
|
248
251
|
const viewportH = window.innerHeight;
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
+
const estH = Math.min(el.offsetHeight || 240, viewportH - 16);
|
|
253
|
+
const above = rect.top - estH - 4;
|
|
254
|
+
const fitsAbove = above >= 0;
|
|
252
255
|
el.style.left = `${rect.left + window.scrollX}px`;
|
|
253
|
-
if (
|
|
254
|
-
el.style.top = `${
|
|
256
|
+
if (fitsAbove) {
|
|
257
|
+
el.style.top = `${above + window.scrollY}px`;
|
|
255
258
|
} else {
|
|
256
|
-
el.style.top = `${rect.
|
|
259
|
+
el.style.top = `${rect.bottom + 4 + window.scrollY}px`;
|
|
257
260
|
}
|
|
258
261
|
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OutlinePanel
|
|
3
|
+
*
|
|
4
|
+
* Left-side companion to the InlinePreviewGutter. Renders a hierarchical
|
|
5
|
+
* tree of the document's headings (h1 → h2 → h3 …) so the structure is
|
|
6
|
+
* graspable at a glance and the user can jump to any section. Works in
|
|
7
|
+
* BOTH the WYSIWYG and Markdown editor views — view-specific positioning
|
|
8
|
+
* lives in `useHeadingLayout`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { type CSSProperties, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
|
+
import type { Block } from '@bendyline/squisq/schemas';
|
|
13
|
+
import { flattenBlocks, hasTemplate } from '@bendyline/squisq/doc';
|
|
14
|
+
import { extractPlainText } from '@bendyline/squisq/markdown';
|
|
15
|
+
import { useEditorContext } from './EditorContext';
|
|
16
|
+
import { templateLabel } from './TemplatePicker';
|
|
17
|
+
import { useHeadingLayout } from './useHeadingLayout';
|
|
18
|
+
import { usePreviewSettingsOptional } from './PreviewControls';
|
|
19
|
+
|
|
20
|
+
export interface OutlinePanelProps {
|
|
21
|
+
/** Width of the pane in pixels (default: 240). */
|
|
22
|
+
width?: number;
|
|
23
|
+
/** Optional CSS class for the outer container. */
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function OutlinePanel({ width = 240, className }: OutlinePanelProps) {
|
|
28
|
+
const { doc, markdownSource, setMarkdownSource } = useEditorContext();
|
|
29
|
+
const paneRef = useRef<HTMLElement | null>(null);
|
|
30
|
+
const { scrollToBlock } = useHeadingLayout(paneRef);
|
|
31
|
+
const activeBlockId = useActiveOutlineBlockId();
|
|
32
|
+
|
|
33
|
+
// Promote / demote the row's heading by rewriting just the `#` prefix
|
|
34
|
+
// on the heading line. Falls through when the new depth would leave the
|
|
35
|
+
// legal H1–H6 range, so the buttons disable themselves at the edges.
|
|
36
|
+
// Both editor surfaces resync from `markdownSource` automatically.
|
|
37
|
+
const changeHeadingLevel = useCallback(
|
|
38
|
+
(block: Block, delta: number) => {
|
|
39
|
+
const line = block.sourceHeading?.position?.start.line;
|
|
40
|
+
if (typeof line !== 'number') return;
|
|
41
|
+
const next = bumpHeadingLevelInSource(markdownSource, line, delta);
|
|
42
|
+
if (next != null) setMarkdownSource(next);
|
|
43
|
+
},
|
|
44
|
+
[markdownSource, setMarkdownSource],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Inherit the active document theme's primary color so the current-row
|
|
48
|
+
// highlight and template-name chips match the rest of the editor's
|
|
49
|
+
// accent palette (e.g. warm-earth's terracotta) instead of the
|
|
50
|
+
// hard-coded purple fallback. Falls through to the CSS defaults when
|
|
51
|
+
// no PreviewSettingsProvider is mounted.
|
|
52
|
+
const previewSettings = usePreviewSettingsOptional();
|
|
53
|
+
const accentColor = previewSettings?.activeTheme?.colors?.primary;
|
|
54
|
+
|
|
55
|
+
const isEmpty = !doc || doc.blocks.length === 0 || !hasAnyHeading(doc.blocks);
|
|
56
|
+
const paneStyle: CSSProperties = {
|
|
57
|
+
width: `${width}px`,
|
|
58
|
+
flex: `0 0 ${width}px`,
|
|
59
|
+
overflow: 'auto',
|
|
60
|
+
...(accentColor
|
|
61
|
+
? ({ ['--squisq-outline-accent' as string]: accentColor } as CSSProperties)
|
|
62
|
+
: {}),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<aside
|
|
67
|
+
ref={paneRef}
|
|
68
|
+
className={`squisq-outline${className ? ` ${className}` : ''}`}
|
|
69
|
+
style={paneStyle}
|
|
70
|
+
data-testid="outline-panel"
|
|
71
|
+
aria-label="Document outline"
|
|
72
|
+
>
|
|
73
|
+
{isEmpty ? (
|
|
74
|
+
<div className="squisq-outline-empty">
|
|
75
|
+
<p>Add a heading to populate the outline.</p>
|
|
76
|
+
</div>
|
|
77
|
+
) : (
|
|
78
|
+
<ul className="squisq-outline-tree" role="tree">
|
|
79
|
+
{doc!.blocks.map((b) => (
|
|
80
|
+
<OutlineNode
|
|
81
|
+
key={b.id}
|
|
82
|
+
block={b}
|
|
83
|
+
activeBlockId={activeBlockId}
|
|
84
|
+
onSelect={scrollToBlock}
|
|
85
|
+
onChangeLevel={changeHeadingLevel}
|
|
86
|
+
/>
|
|
87
|
+
))}
|
|
88
|
+
</ul>
|
|
89
|
+
)}
|
|
90
|
+
</aside>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Subcomponents ──────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
function OutlineNode({
|
|
97
|
+
block,
|
|
98
|
+
activeBlockId,
|
|
99
|
+
onSelect,
|
|
100
|
+
onChangeLevel,
|
|
101
|
+
}: {
|
|
102
|
+
block: Block;
|
|
103
|
+
activeBlockId: string | null;
|
|
104
|
+
onSelect: (b: Block) => void;
|
|
105
|
+
onChangeLevel: (block: Block, delta: number) => void;
|
|
106
|
+
}) {
|
|
107
|
+
const heading = block.sourceHeading;
|
|
108
|
+
const depth = heading?.depth ?? 1;
|
|
109
|
+
const text = heading ? extractPlainText(heading).trim() : '';
|
|
110
|
+
const annotation = heading?.templateAnnotation;
|
|
111
|
+
const tplName = annotation?.template;
|
|
112
|
+
const showChip = tplName && hasTemplate(tplName);
|
|
113
|
+
const isActive = block.id === activeBlockId;
|
|
114
|
+
const canPromote = !!heading && depth > 1;
|
|
115
|
+
const canDemote = !!heading && depth < 6;
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<li className="squisq-outline-item" role="treeitem" aria-current={isActive || undefined}>
|
|
119
|
+
<div className="squisq-outline-row-wrap">
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
className={`squisq-outline-row squisq-outline-row--depth-${depth}${
|
|
123
|
+
isActive ? ' squisq-outline-row--current' : ''
|
|
124
|
+
}`}
|
|
125
|
+
onClick={() => onSelect(block)}
|
|
126
|
+
title={text || '(empty heading)'}
|
|
127
|
+
>
|
|
128
|
+
<span className="squisq-outline-row-text">{text || '(untitled)'}</span>
|
|
129
|
+
{showChip && (
|
|
130
|
+
<span className="squisq-outline-template-chip">{templateLabel(tplName!)}</span>
|
|
131
|
+
)}
|
|
132
|
+
</button>
|
|
133
|
+
{heading && (
|
|
134
|
+
<span className="squisq-outline-row-actions">
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
className="squisq-outline-row-arrow"
|
|
138
|
+
aria-label={`Promote heading (currently H${depth})`}
|
|
139
|
+
title="Promote heading"
|
|
140
|
+
disabled={!canPromote}
|
|
141
|
+
onClick={() => onChangeLevel(block, -1)}
|
|
142
|
+
>
|
|
143
|
+
<svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
|
|
144
|
+
<path
|
|
145
|
+
d="M6.5 2.5 L3 5 L6.5 7.5"
|
|
146
|
+
stroke="currentColor"
|
|
147
|
+
strokeWidth="1.5"
|
|
148
|
+
strokeLinecap="round"
|
|
149
|
+
strokeLinejoin="round"
|
|
150
|
+
fill="none"
|
|
151
|
+
/>
|
|
152
|
+
</svg>
|
|
153
|
+
</button>
|
|
154
|
+
<button
|
|
155
|
+
type="button"
|
|
156
|
+
className="squisq-outline-row-arrow"
|
|
157
|
+
aria-label={`Demote heading (currently H${depth})`}
|
|
158
|
+
title="Demote heading"
|
|
159
|
+
disabled={!canDemote}
|
|
160
|
+
onClick={() => onChangeLevel(block, +1)}
|
|
161
|
+
>
|
|
162
|
+
<svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
|
|
163
|
+
<path
|
|
164
|
+
d="M3.5 2.5 L7 5 L3.5 7.5"
|
|
165
|
+
stroke="currentColor"
|
|
166
|
+
strokeWidth="1.5"
|
|
167
|
+
strokeLinecap="round"
|
|
168
|
+
strokeLinejoin="round"
|
|
169
|
+
fill="none"
|
|
170
|
+
/>
|
|
171
|
+
</svg>
|
|
172
|
+
</button>
|
|
173
|
+
</span>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
{block.children && block.children.length > 0 && (
|
|
177
|
+
<ul className="squisq-outline-tree">
|
|
178
|
+
{block.children.map((child) => (
|
|
179
|
+
<OutlineNode
|
|
180
|
+
key={child.id}
|
|
181
|
+
block={child}
|
|
182
|
+
activeBlockId={activeBlockId}
|
|
183
|
+
onSelect={onSelect}
|
|
184
|
+
onChangeLevel={onChangeLevel}
|
|
185
|
+
/>
|
|
186
|
+
))}
|
|
187
|
+
</ul>
|
|
188
|
+
)}
|
|
189
|
+
</li>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── Active-block tracking ──────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Tracks which heading the user's cursor is currently inside (or most
|
|
197
|
+
* recently passed). In WYSIWYG mode this watches Tiptap's selection;
|
|
198
|
+
* in Raw mode it watches Monaco's cursor line. The Preview surface has
|
|
199
|
+
* no cursor concept and reports `null`.
|
|
200
|
+
*
|
|
201
|
+
* The lookup mirrors the heading-pairing logic in `useHeadingLayout`:
|
|
202
|
+
* the Nth heading in document order maps to `flattenBlocks(doc.blocks)[N]`.
|
|
203
|
+
*/
|
|
204
|
+
function useActiveOutlineBlockId(): string | null {
|
|
205
|
+
const { doc, activeView, tiptapEditor, monacoEditor } = useEditorContext();
|
|
206
|
+
const flatBlocks = useMemo(() => (doc ? flattenBlocks(doc.blocks) : []), [doc]);
|
|
207
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
|
208
|
+
|
|
209
|
+
// Reset whenever the active surface changes — a stale highlight from
|
|
210
|
+
// the previous view would mislead the user before the new surface's
|
|
211
|
+
// cursor handler runs.
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
setActiveId(null);
|
|
214
|
+
}, [activeView]);
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
if (activeView !== 'wysiwyg' || !tiptapEditor) return;
|
|
218
|
+
|
|
219
|
+
const update = () => {
|
|
220
|
+
const { from } = tiptapEditor.state.selection;
|
|
221
|
+
let lastIndex = -1;
|
|
222
|
+
let seen = -1;
|
|
223
|
+
tiptapEditor.state.doc.forEach((node, offset) => {
|
|
224
|
+
if (node.type.name !== 'heading') return;
|
|
225
|
+
seen += 1;
|
|
226
|
+
if (offset <= from) lastIndex = seen;
|
|
227
|
+
});
|
|
228
|
+
const block = lastIndex >= 0 ? flatBlocks[lastIndex] : null;
|
|
229
|
+
setActiveId(block?.id ?? null);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
update();
|
|
233
|
+
tiptapEditor.on('selectionUpdate', update);
|
|
234
|
+
tiptapEditor.on('update', update);
|
|
235
|
+
return () => {
|
|
236
|
+
tiptapEditor.off('selectionUpdate', update);
|
|
237
|
+
tiptapEditor.off('update', update);
|
|
238
|
+
};
|
|
239
|
+
}, [activeView, tiptapEditor, flatBlocks]);
|
|
240
|
+
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (activeView !== 'raw' || !monacoEditor) return;
|
|
243
|
+
|
|
244
|
+
const update = () => {
|
|
245
|
+
const line = monacoEditor.getPosition()?.lineNumber;
|
|
246
|
+
if (typeof line !== 'number') {
|
|
247
|
+
setActiveId(null);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
let lastIndex = -1;
|
|
251
|
+
flatBlocks.forEach((b, i) => {
|
|
252
|
+
const headingLine = b.sourceHeading?.position?.start.line;
|
|
253
|
+
if (typeof headingLine === 'number' && headingLine <= line) lastIndex = i;
|
|
254
|
+
});
|
|
255
|
+
const block = lastIndex >= 0 ? flatBlocks[lastIndex] : null;
|
|
256
|
+
setActiveId(block?.id ?? null);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
update();
|
|
260
|
+
const sub = monacoEditor.onDidChangeCursorPosition(update);
|
|
261
|
+
return () => sub.dispose();
|
|
262
|
+
}, [activeView, monacoEditor, flatBlocks]);
|
|
263
|
+
|
|
264
|
+
return activeId;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
function hasAnyHeading(blocks: Block[]): boolean {
|
|
270
|
+
for (const b of blocks) {
|
|
271
|
+
if (b.sourceHeading) return true;
|
|
272
|
+
if (b.children && hasAnyHeading(b.children)) return true;
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Rewrites just the leading `#` run on the given 1-based line, shifting
|
|
279
|
+
* the heading depth by `delta`. Returns `null` when the line isn't an
|
|
280
|
+
* ATX heading or the resulting depth would fall outside 1–6. Leaves the
|
|
281
|
+
* rest of the line (including any `{[template]}` annotation) untouched.
|
|
282
|
+
*/
|
|
283
|
+
function bumpHeadingLevelInSource(source: string, line: number, delta: number): string | null {
|
|
284
|
+
const lines = source.split('\n');
|
|
285
|
+
const idx = line - 1;
|
|
286
|
+
if (idx < 0 || idx >= lines.length) return null;
|
|
287
|
+
const original = lines[idx];
|
|
288
|
+
const match = original.match(/^(#{1,6})(\s|$)/);
|
|
289
|
+
if (!match) return null;
|
|
290
|
+
const currentDepth = match[1].length;
|
|
291
|
+
const newDepth = currentDepth + delta;
|
|
292
|
+
if (newDepth < 1 || newDepth > 6) return null;
|
|
293
|
+
lines[idx] = '#'.repeat(newDepth) + original.slice(currentDepth);
|
|
294
|
+
return lines.join('\n');
|
|
295
|
+
}
|