@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
package/src/tiptapBridge.ts
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* using squisq's own parser.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { templateLabel } from './TemplatePicker';
|
|
15
|
+
import { resolveIcon } from '@bendyline/squisq/icons';
|
|
16
|
+
|
|
14
17
|
// Hoisted regex patterns for inline markdown ↔ HTML conversion
|
|
15
18
|
const RE_BOLD_STAR = /\*\*(.+?)\*\*/g;
|
|
16
19
|
const RE_BOLD_UNDER = /__(.+?)__/g;
|
|
@@ -29,6 +32,14 @@ const RE_IMAGE = /!\[(.*?)\]\((.+?)\)/g;
|
|
|
29
32
|
// remark-stringify may round-trip the colon as `\:` — tolerate either.
|
|
30
33
|
const RE_MENTION = /@\[([^\]]+?)\]\(([a-z][a-z0-9+.-]*)\\?:([^)\s]+)\)/gi;
|
|
31
34
|
const RE_MENTION_TAG = /<span\b[^>]*?\bdata-mention\b[^>]*?>(?:<[^>]+>)*([^<]*)<\/span>/gi;
|
|
35
|
+
|
|
36
|
+
// Inline FontAwesome icon. Markdown form: `{[github]}` (bare) or
|
|
37
|
+
// `{[fa-solid:user]}` (qualified). HTML form (produced by the
|
|
38
|
+
// `InlineIcon` Tiptap node and consumed back by `htmlToInline`):
|
|
39
|
+
// `<i data-icon="github" data-family="brands" data-name="github"
|
|
40
|
+
// class="fa-brands fa-github" contenteditable="false"></i>`.
|
|
41
|
+
const RE_ICON_MD = /\{\[([a-zA-Z0-9_:-]+)\]\}/g;
|
|
42
|
+
const RE_ICON_TAG = /<i\b[^>]*?\bdata-icon="([^"]*)"[^>]*?><\/i>/gi;
|
|
32
43
|
const RE_STRONG_TAG = /<strong>(.*?)<\/strong>/g;
|
|
33
44
|
const RE_B_TAG = /<b>(.*?)<\/b>/g;
|
|
34
45
|
const RE_EM_TAG = /<em>(.*?)<\/em>/g;
|
|
@@ -183,8 +194,11 @@ export function markdownToTiptap(markdown: string): string {
|
|
|
183
194
|
let text = headingMatch[2];
|
|
184
195
|
let attrs = '';
|
|
185
196
|
|
|
186
|
-
// Extract {[template key=value …]} annotation
|
|
187
|
-
|
|
197
|
+
// Extract {[template key=value …]} annotation. Trailing `[\s\]\}]*`
|
|
198
|
+
// tolerates accidental doubled `]}` that users type while learning
|
|
199
|
+
// the syntax — must stay in sync with TEMPLATE_ANNOTATION_RE in
|
|
200
|
+
// packages/core/src/markdown/convert.ts.
|
|
201
|
+
const annotMatch = text.match(/\s*\{\[([^\]]+)\]\}[\s\]}]*$/);
|
|
188
202
|
if (annotMatch) {
|
|
189
203
|
text = text.slice(0, annotMatch.index!).trimEnd();
|
|
190
204
|
const tokens = annotMatch[1].trim().split(/\s+/);
|
|
@@ -263,6 +277,44 @@ export function markdownToTiptap(markdown: string): string {
|
|
|
263
277
|
continue;
|
|
264
278
|
}
|
|
265
279
|
|
|
280
|
+
// Standalone image — emit as a top-level block `<img>` instead of
|
|
281
|
+
// wrapping in `<p>`. The Tiptap Image extension is configured with
|
|
282
|
+
// `inline: false`, so `<p><img></p>` parses to an empty paragraph
|
|
283
|
+
// (the block image can't live inside the paragraph). That bug
|
|
284
|
+
// manifested as a broken-image glyph after a markdown → WYSIWYG
|
|
285
|
+
// round-trip for any dropped/pasted image.
|
|
286
|
+
const standaloneImageMatch = line.trim().match(/^!\[(.*?)\]\((.+?)\)$/);
|
|
287
|
+
if (standaloneImageMatch) {
|
|
288
|
+
flushList();
|
|
289
|
+
const alt = escapeHtml(standaloneImageMatch[1] ?? '');
|
|
290
|
+
const src = escapeHtml(standaloneImageMatch[2] ?? '');
|
|
291
|
+
outputBlocks.push(`<img alt="${alt}" src="${src}">`);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Standalone raw HTML `<img>` line — emitted by `tiptapToMarkdown`
|
|
296
|
+
// when the user has resized an image (width/height attrs are
|
|
297
|
+
// serialized as HTML rather than markdown shorthand so the
|
|
298
|
+
// dimensions survive round-trip). Pass the tag through unchanged
|
|
299
|
+
// so Tiptap's Image extension parses width/height attributes.
|
|
300
|
+
const trimmed = line.trim();
|
|
301
|
+
if (/^<img\b[^>]*>$/i.test(trimmed)) {
|
|
302
|
+
flushList();
|
|
303
|
+
outputBlocks.push(trimmed);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Standalone `<video>` / `<audio>` line — emitted by the recorder
|
|
308
|
+
// (RecorderEntry) when it saves a clip, and by `tiptapToMarkdown`
|
|
309
|
+
// when the WYSIWYG editor's TiptapVideo/TiptapAudio nodes serialize
|
|
310
|
+
// back to markdown. Pass through unchanged so the editor's parseHTML
|
|
311
|
+
// picks up the tag attributes (`src`, `controls`, …).
|
|
312
|
+
if (/^<(?:video|audio)\b[^>]*>(?:[\s\S]*?<\/(?:video|audio)>)?$/i.test(trimmed)) {
|
|
313
|
+
flushList();
|
|
314
|
+
outputBlocks.push(trimmed);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
266
318
|
// Regular paragraph
|
|
267
319
|
flushList();
|
|
268
320
|
outputBlocks.push(`<p>${inlineToHtml(line)}</p>`);
|
|
@@ -306,6 +358,15 @@ export function tiptapToMarkdown(html: string): string {
|
|
|
306
358
|
const tmplMatch = attrs.match(/data-template="([^"]+)"/);
|
|
307
359
|
if (tmplMatch) {
|
|
308
360
|
let annotation = tmplMatch[1];
|
|
361
|
+
// Defensive: an earlier broken build briefly rendered the
|
|
362
|
+
// template label as a real text node inside the badge, which
|
|
363
|
+
// bled the label into the heading's textContent. Strip a
|
|
364
|
+
// trailing copy of the template's label so existing documents
|
|
365
|
+
// self-heal on save.
|
|
366
|
+
const label = templateLabel(annotation);
|
|
367
|
+
if (label && text.endsWith(label)) {
|
|
368
|
+
text = text.slice(0, -label.length).trimEnd();
|
|
369
|
+
}
|
|
309
370
|
const paramsMatch = attrs.match(/data-template-params="([^"]+)"/);
|
|
310
371
|
if (paramsMatch) {
|
|
311
372
|
annotation += ' ' + unescapeHtml(paramsMatch[1]);
|
|
@@ -486,13 +547,27 @@ export function tiptapToMarkdown(html: string): string {
|
|
|
486
547
|
const src = /\bsrc="([^"]*)"/i.exec(attrs)?.[1];
|
|
487
548
|
if (src) {
|
|
488
549
|
const alt = /\balt="([^"]*)"/i.exec(attrs)?.[1] ?? '';
|
|
489
|
-
lines.push(
|
|
550
|
+
lines.push(serializeImage(src, alt, attrs));
|
|
490
551
|
lines.push('');
|
|
491
552
|
}
|
|
492
553
|
remaining = remaining.slice(imgMatch[0].length);
|
|
493
554
|
continue;
|
|
494
555
|
}
|
|
495
556
|
|
|
557
|
+
// Block-level `<video>` / `<audio>` — emitted by our TiptapVideo /
|
|
558
|
+
// TiptapAudio atom nodes (block group). Serialize the whole tag
|
|
559
|
+
// (opening + closing) back to markdown unchanged; CommonMark allows
|
|
560
|
+
// inline HTML, and our renderer plus the InlinePreviewGutter both
|
|
561
|
+
// know how to parse the htmlElement back out.
|
|
562
|
+
const mediaMatch = remaining.match(/^<(video|audio)\b([^>]*)>(?:[\s\S]*?<\/\1>)?/);
|
|
563
|
+
if (mediaMatch) {
|
|
564
|
+
const tag = mediaMatch[1] === 'video' ? 'video' : 'audio';
|
|
565
|
+
lines.push(serializeMediaTag(tag, mediaMatch[2] ?? ''));
|
|
566
|
+
lines.push('');
|
|
567
|
+
remaining = remaining.slice(mediaMatch[0].length);
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
|
|
496
571
|
// Skip unknown tags or whitespace
|
|
497
572
|
const skipMatch = remaining.match(/^(<[^>]+>|\s+)/);
|
|
498
573
|
if (skipMatch) {
|
|
@@ -579,6 +654,51 @@ function parseAlignments(separatorLine: string): (string | null)[] {
|
|
|
579
654
|
|
|
580
655
|
// ─── Helpers ─────────────────────────────────────────────
|
|
581
656
|
|
|
657
|
+
/**
|
|
658
|
+
* Serialize a parsed `<img>` tag back to markdown. When the tag carries
|
|
659
|
+
* an explicit `width` and/or `height` we emit a raw HTML `<img>` (the
|
|
660
|
+
* markdown shorthand `` has no syntax for dimensions);
|
|
661
|
+
* otherwise the friendlier shorthand is used. Markdown allows inline
|
|
662
|
+
* HTML, so the HTML form parses and renders identically in any
|
|
663
|
+
* CommonMark/GFM viewer.
|
|
664
|
+
*/
|
|
665
|
+
/**
|
|
666
|
+
* Serialize a `<video>` or `<audio>` tag (from the Tiptap atom node's
|
|
667
|
+
* `renderHTML`) back to markdown. We re-emit only the attributes the
|
|
668
|
+
* recorder + the renderer care about, in a stable order, so the
|
|
669
|
+
* round-tripped markdown stays deterministic regardless of how Tiptap
|
|
670
|
+
* decided to order them on its output.
|
|
671
|
+
*/
|
|
672
|
+
function serializeMediaTag(tag: 'video' | 'audio', attrs: string): string {
|
|
673
|
+
const src = /\bsrc="([^"]*)"/i.exec(attrs)?.[1] ?? '';
|
|
674
|
+
const controls = /\bcontrols\b/i.test(attrs);
|
|
675
|
+
const width = /\bwidth="([^"]*)"/i.exec(attrs)?.[1];
|
|
676
|
+
const height = /\bheight="([^"]*)"/i.exec(attrs)?.[1];
|
|
677
|
+
const poster = tag === 'video' ? /\bposter="([^"]*)"/i.exec(attrs)?.[1] : undefined;
|
|
678
|
+
const parts = [`<${tag} src="${src}"`];
|
|
679
|
+
if (controls) parts.push(' controls');
|
|
680
|
+
if (width) parts.push(` width="${width}"`);
|
|
681
|
+
if (height) parts.push(` height="${height}"`);
|
|
682
|
+
if (poster) parts.push(` poster="${poster}"`);
|
|
683
|
+
parts.push(`></${tag}>`);
|
|
684
|
+
return parts.join('');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function serializeImage(src: string, alt: string, attrs: string): string {
|
|
688
|
+
const width = /\bwidth="([^"]*)"/i.exec(attrs)?.[1];
|
|
689
|
+
const height = /\bheight="([^"]*)"/i.exec(attrs)?.[1];
|
|
690
|
+
const title = /\btitle="([^"]*)"/i.exec(attrs)?.[1];
|
|
691
|
+
if (!width && !height) {
|
|
692
|
+
return ``;
|
|
693
|
+
}
|
|
694
|
+
const parts = [`<img alt="${alt}" src="${src}"`];
|
|
695
|
+
if (width) parts.push(` width="${width}"`);
|
|
696
|
+
if (height) parts.push(` height="${height}"`);
|
|
697
|
+
if (title) parts.push(` title="${title}"`);
|
|
698
|
+
parts.push('>');
|
|
699
|
+
return parts.join('');
|
|
700
|
+
}
|
|
701
|
+
|
|
582
702
|
function escapeHtml(text: string): string {
|
|
583
703
|
return text
|
|
584
704
|
.replace(/&/g, '&')
|
|
@@ -597,7 +717,56 @@ function unescapeHtml(text: string): string {
|
|
|
597
717
|
|
|
598
718
|
/** Convert inline markdown to HTML for Tiptap consumption */
|
|
599
719
|
function inlineToHtml(text: string): string {
|
|
600
|
-
|
|
720
|
+
// Extract images/mentions/links to opaque placeholders BEFORE running
|
|
721
|
+
// any inline-formatting regexes. Otherwise `_` characters inside a URL
|
|
722
|
+
// (e.g. `mikehome_files/IMG_6829.JPEG`) get turned into `<em>` tags by
|
|
723
|
+
// the underscore-italic rule, mangling the src so the image renders
|
|
724
|
+
// broken after a markdown ↔ WYSIWYG round-trip.
|
|
725
|
+
const placeholders: string[] = [];
|
|
726
|
+
const stash = (html: string): string => {
|
|
727
|
+
const token = `\u0000PH${placeholders.length}\u0000`;
|
|
728
|
+
placeholders.push(html);
|
|
729
|
+
return token;
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// We run extraction on the RAW text (before escapeHtml) so the
|
|
733
|
+
// captured groups are the literal markdown contents; then we
|
|
734
|
+
// selectively escape the parts of each placeholder that need it.
|
|
735
|
+
|
|
736
|
+
// Images first:  — must be before links so the `!` prefix is consumed
|
|
737
|
+
let staged = text.replace(RE_IMAGE, (_m, alt, src) =>
|
|
738
|
+
stash(`<img alt="${escapeHtml(alt)}" src="${escapeHtml(src)}">`),
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
// Mentions: @[Display](scheme:id) — must run before links so the
|
|
742
|
+
// bracket+paren isn't consumed as a regular link.
|
|
743
|
+
staged = staged.replace(RE_MENTION, (_m, label, kind, id) =>
|
|
744
|
+
stash(
|
|
745
|
+
`<span data-mention="true" data-kind="${escapeHtml(kind)}" data-id="${escapeHtml(id)}" data-label="${escapeHtml(label)}" class="mention">@${escapeHtml(label)}</span>`,
|
|
746
|
+
),
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
// FontAwesome inline icons: {[github]} / {[fa-solid:user]} — resolve
|
|
750
|
+
// against the FA catalog so unknown / ambiguous tokens stay as
|
|
751
|
+
// literal text (preserved through escapeHtml later). Stashing here
|
|
752
|
+
// alongside images/mentions keeps the `_` and other punctuation in
|
|
753
|
+
// the class attribute from being mangled by the underscore-italic
|
|
754
|
+
// regex below.
|
|
755
|
+
staged = staged.replace(RE_ICON_MD, (full, token) => {
|
|
756
|
+
const icon = resolveIcon(token);
|
|
757
|
+
if (!icon) return full; // leave literal text — author may have meant it
|
|
758
|
+
return stash(
|
|
759
|
+
`<i class="fa-${icon.family} fa-${icon.name}" data-icon="${escapeHtml(token)}" data-family="${icon.family}" data-name="${icon.name}" contenteditable="false"></i>`,
|
|
760
|
+
);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Links: [text](url) — stash but keep the link text available to
|
|
764
|
+
// inline formatting by recursing.
|
|
765
|
+
staged = staged.replace(RE_LINK, (_m, linkText, href) =>
|
|
766
|
+
stash(`<a href="${escapeHtml(href)}">${inlineToHtml(linkText)}</a>`),
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
let result = escapeHtml(staged);
|
|
601
770
|
|
|
602
771
|
// Bold: **text** or __text__
|
|
603
772
|
result = result.replace(RE_BOLD_STAR, '<strong>$1</strong>');
|
|
@@ -613,21 +782,11 @@ function inlineToHtml(text: string): string {
|
|
|
613
782
|
// Inline code: `text`
|
|
614
783
|
result = result.replace(RE_INLINE_CODE, '<code>$1</code>');
|
|
615
784
|
|
|
616
|
-
//
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
//
|
|
620
|
-
|
|
621
|
-
// already been run through escapeHtml at the top of this function, so
|
|
622
|
-
// the captured groups are safe to interpolate directly.
|
|
623
|
-
result = result.replace(
|
|
624
|
-
RE_MENTION,
|
|
625
|
-
(_match, label, kind, id) =>
|
|
626
|
-
`<span data-mention="true" data-kind="${kind}" data-id="${id}" data-label="${label}" class="mention">@${label}</span>`,
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
// Links: [text](url)
|
|
630
|
-
result = result.replace(RE_LINK, '<a href="$2">$1</a>');
|
|
785
|
+
// Restore placeholders. escapeHtml turned each `\u0000PHn\u0000` into
|
|
786
|
+
// the same string (the NULs and digits are escape-safe), so the
|
|
787
|
+
// restoration regex still matches.
|
|
788
|
+
// eslint-disable-next-line no-control-regex
|
|
789
|
+
result = result.replace(/\u0000PH(\d+)\u0000/g, (_m, idx) => placeholders[Number(idx)] ?? '');
|
|
631
790
|
|
|
632
791
|
return result;
|
|
633
792
|
}
|
|
@@ -640,6 +799,12 @@ function htmlToInline(html: string): string {
|
|
|
640
799
|
// spaces + newline) before stripping tags so the newline survives.
|
|
641
800
|
result = result.replace(/<br\s*\/?>/gi, ' \n');
|
|
642
801
|
|
|
802
|
+
// FontAwesome inline icons — emit the original token. Must run before
|
|
803
|
+
// RE_I_TAG (which matches a bare `<i>` and would otherwise eat icon
|
|
804
|
+
// tags too). The token captured via data-icon already carries the
|
|
805
|
+
// qualified form when needed, so source round-trips exactly.
|
|
806
|
+
result = result.replace(RE_ICON_TAG, (_m, token) => `{[${token}]}`);
|
|
807
|
+
|
|
643
808
|
// Strong
|
|
644
809
|
result = result.replace(RE_STRONG_TAG, '**$1**');
|
|
645
810
|
result = result.replace(RE_B_TAG, '**$1**');
|
|
@@ -670,11 +835,14 @@ function htmlToInline(html: string): string {
|
|
|
670
835
|
|
|
671
836
|
// Images — order-agnostic attribute parsing (tiptap emits src-first,
|
|
672
837
|
// our markdown-to-html emits alt-first; either must serialize back).
|
|
838
|
+
// When a width/height is present we serialize as raw HTML `<img>` so
|
|
839
|
+
// the dimensions survive the round-trip; otherwise the markdown
|
|
840
|
+
// shorthand `` is used.
|
|
673
841
|
result = result.replace(RE_IMG_TAG, (match, attrs: string) => {
|
|
674
842
|
const src = /\bsrc="([^"]*)"/i.exec(attrs)?.[1];
|
|
675
843
|
if (!src) return match;
|
|
676
844
|
const alt = /\balt="([^"]*)"/i.exec(attrs)?.[1] ?? '';
|
|
677
|
-
return
|
|
845
|
+
return serializeImage(src, alt, attrs);
|
|
678
846
|
});
|
|
679
847
|
|
|
680
848
|
// Strip remaining tags
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useHeadingLayout
|
|
3
|
+
*
|
|
4
|
+
* Shared positioning hook for the editor gutters (`OutlinePanel` and
|
|
5
|
+
* `InlinePreviewGutter`). Each gutter needs to know:
|
|
6
|
+
* - where every heading sits vertically inside the wrapper
|
|
7
|
+
* - where the editor "page" edges sit horizontally
|
|
8
|
+
* - how to scroll the editor to a particular heading
|
|
9
|
+
*
|
|
10
|
+
* Two backends, picked from `EditorContext.activeView`:
|
|
11
|
+
* - **wysiwyg** — query DOM headings inside `.squisq-wysiwyg-container`,
|
|
12
|
+
* measure with `getBoundingClientRect`, scroll via `scrollIntoView`.
|
|
13
|
+
* Live updates via `ResizeObserver` + `MutationObserver`.
|
|
14
|
+
* - **raw** — walk `doc.blocks`, derive each heading's line number from
|
|
15
|
+
* `MarkdownHeading.position.start.line`, ask Monaco for the line's
|
|
16
|
+
* pixel offset via `getTopForLineNumber`, scroll via
|
|
17
|
+
* `revealLineInCenterIfOutsideViewport` + `setPosition`. Live updates
|
|
18
|
+
* via `onDidScrollChange` + `onDidChangeModelContent`.
|
|
19
|
+
*
|
|
20
|
+
* Both backends produce coordinates in the *wrapper's* reference frame
|
|
21
|
+
* (i.e., `.squisq-editor-with-gutter`), so the rendering layers — cards,
|
|
22
|
+
* extent bars, outline rows — don't care which editor is active.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
26
|
+
import type { RefObject } from 'react';
|
|
27
|
+
import type { Block } from '@bendyline/squisq/schemas';
|
|
28
|
+
import { flattenBlocks, hasTemplate } from '@bendyline/squisq/doc';
|
|
29
|
+
import { useEditorContext } from './EditorContext';
|
|
30
|
+
|
|
31
|
+
export interface HeadingLayoutEntry {
|
|
32
|
+
/** The block (heading-rooted unit) this entry represents. */
|
|
33
|
+
block: Block;
|
|
34
|
+
/** Heading top, in px relative to the wrapper's top edge. */
|
|
35
|
+
top: number;
|
|
36
|
+
/** Where this block ends — the next heading's top, or the editor bottom. */
|
|
37
|
+
bottom: number;
|
|
38
|
+
/** True when the heading has a recognised template annotation. */
|
|
39
|
+
annotated: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface HeadingLayout {
|
|
43
|
+
/** All headings, in document order. */
|
|
44
|
+
entries: HeadingLayoutEntry[];
|
|
45
|
+
/** Editor page's left/right edges, in px relative to the wrapper. */
|
|
46
|
+
pageEdges: { left: number; right: number } | null;
|
|
47
|
+
/** Scroll the active editor to bring the block's heading into view. */
|
|
48
|
+
scrollToBlock: (block: Block) => void;
|
|
49
|
+
/** True once a measurement has produced numbers (avoids flicker on mount). */
|
|
50
|
+
ready: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param refInsideWrapper — any DOM ref under `.squisq-editor-with-gutter`.
|
|
55
|
+
* The hook walks up to find the wrapper.
|
|
56
|
+
*/
|
|
57
|
+
export function useHeadingLayout(refInsideWrapper: RefObject<HTMLElement | null>): HeadingLayout {
|
|
58
|
+
const { doc, activeView, monacoEditor, tiptapEditor } = useEditorContext();
|
|
59
|
+
|
|
60
|
+
const flatBlocks = useMemo(() => (doc ? flattenBlocks(doc.blocks) : []), [doc]);
|
|
61
|
+
|
|
62
|
+
const [entries, setEntries] = useState<HeadingLayoutEntry[]>([]);
|
|
63
|
+
const [pageEdges, setPageEdges] = useState<{ left: number; right: number } | null>(null);
|
|
64
|
+
|
|
65
|
+
// ── WYSIWYG backend ────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (activeView !== 'wysiwyg') return;
|
|
69
|
+
const node = refInsideWrapper.current;
|
|
70
|
+
if (!node) return;
|
|
71
|
+
const wrapper = findWrapper(node);
|
|
72
|
+
if (!wrapper) return;
|
|
73
|
+
const wysiwygContainer = wrapper.querySelector<HTMLElement>('.squisq-wysiwyg-container');
|
|
74
|
+
if (!wysiwygContainer) return;
|
|
75
|
+
|
|
76
|
+
let raf = 0;
|
|
77
|
+
const recompute = () => {
|
|
78
|
+
cancelAnimationFrame(raf);
|
|
79
|
+
raf = requestAnimationFrame(() => {
|
|
80
|
+
const headings = Array.from(
|
|
81
|
+
wysiwygContainer.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6'),
|
|
82
|
+
);
|
|
83
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
84
|
+
const editorRect = wysiwygContainer.getBoundingClientRect();
|
|
85
|
+
const next: HeadingLayoutEntry[] = [];
|
|
86
|
+
flatBlocks.forEach((block, i) => {
|
|
87
|
+
const h = headings[i];
|
|
88
|
+
if (!h) return;
|
|
89
|
+
const top = h.getBoundingClientRect().top - wrapperRect.top;
|
|
90
|
+
const nextH = headings[i + 1];
|
|
91
|
+
const bottom = nextH
|
|
92
|
+
? nextH.getBoundingClientRect().top - wrapperRect.top
|
|
93
|
+
: editorRect.bottom - wrapperRect.top;
|
|
94
|
+
next.push({
|
|
95
|
+
block,
|
|
96
|
+
top,
|
|
97
|
+
bottom,
|
|
98
|
+
annotated: !!h.getAttribute('data-template'),
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
setEntries((prev) => (sameEntries(prev, next) ? prev : next));
|
|
102
|
+
|
|
103
|
+
// Page edges — anchor to the .squisq-wysiwyg-editor (the centered "page").
|
|
104
|
+
const page = wysiwygContainer.querySelector<HTMLElement>('.squisq-wysiwyg-editor');
|
|
105
|
+
if (page) {
|
|
106
|
+
const pageRect = page.getBoundingClientRect();
|
|
107
|
+
const left = pageRect.left - wrapperRect.left;
|
|
108
|
+
const right = pageRect.right - wrapperRect.left;
|
|
109
|
+
setPageEdges((prev) =>
|
|
110
|
+
prev != null && Math.abs(prev.left - left) < 0.5 && Math.abs(prev.right - right) < 0.5
|
|
111
|
+
? prev
|
|
112
|
+
: { left, right },
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
recompute();
|
|
119
|
+
const ro = new ResizeObserver(recompute);
|
|
120
|
+
ro.observe(wysiwygContainer);
|
|
121
|
+
const editorSurface = wysiwygContainer.querySelector('.squisq-wysiwyg-editor');
|
|
122
|
+
if (editorSurface) ro.observe(editorSurface);
|
|
123
|
+
const mo = new MutationObserver(recompute);
|
|
124
|
+
mo.observe(wysiwygContainer, {
|
|
125
|
+
childList: true,
|
|
126
|
+
subtree: true,
|
|
127
|
+
characterData: true,
|
|
128
|
+
attributes: true,
|
|
129
|
+
attributeFilter: ['data-template', 'data-template-params'],
|
|
130
|
+
});
|
|
131
|
+
wysiwygContainer.addEventListener('scroll', recompute, { passive: true });
|
|
132
|
+
window.addEventListener('resize', recompute);
|
|
133
|
+
const settle = window.setTimeout(recompute, 250);
|
|
134
|
+
|
|
135
|
+
return () => {
|
|
136
|
+
cancelAnimationFrame(raf);
|
|
137
|
+
window.clearTimeout(settle);
|
|
138
|
+
ro.disconnect();
|
|
139
|
+
mo.disconnect();
|
|
140
|
+
wysiwygContainer.removeEventListener('scroll', recompute);
|
|
141
|
+
window.removeEventListener('resize', recompute);
|
|
142
|
+
};
|
|
143
|
+
}, [activeView, flatBlocks, refInsideWrapper]);
|
|
144
|
+
|
|
145
|
+
// ── Raw (Monaco) backend ───────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (activeView !== 'raw') return;
|
|
149
|
+
if (!monacoEditor) return;
|
|
150
|
+
const node = refInsideWrapper.current;
|
|
151
|
+
if (!node) return;
|
|
152
|
+
const wrapper = findWrapper(node);
|
|
153
|
+
if (!wrapper) return;
|
|
154
|
+
|
|
155
|
+
let raf = 0;
|
|
156
|
+
const recompute = () => {
|
|
157
|
+
cancelAnimationFrame(raf);
|
|
158
|
+
raf = requestAnimationFrame(() => {
|
|
159
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
160
|
+
const monacoRoot = monacoEditor.getDomNode() as HTMLElement | null;
|
|
161
|
+
if (!monacoRoot) return;
|
|
162
|
+
const monacoRect = monacoRoot.getBoundingClientRect();
|
|
163
|
+
// Y of the editor's content top in wrapper coordinates.
|
|
164
|
+
const monacoTop = monacoRect.top - wrapperRect.top;
|
|
165
|
+
const scrollTop = monacoEditor.getScrollTop();
|
|
166
|
+
|
|
167
|
+
const layoutInfo = monacoEditor.getLayoutInfo();
|
|
168
|
+
const next: HeadingLayoutEntry[] = [];
|
|
169
|
+
flatBlocks.forEach((block, i) => {
|
|
170
|
+
const line = block.sourceHeading?.position?.start.line;
|
|
171
|
+
if (typeof line !== 'number') return;
|
|
172
|
+
const lineTop = monacoEditor.getTopForLineNumber(line) - scrollTop + monacoTop;
|
|
173
|
+
// Bottom = next block's top, or the visible bottom of the editor.
|
|
174
|
+
const nextBlock = flatBlocks[i + 1];
|
|
175
|
+
const nextLine = nextBlock?.sourceHeading?.position?.start.line;
|
|
176
|
+
const bottom =
|
|
177
|
+
typeof nextLine === 'number'
|
|
178
|
+
? monacoEditor.getTopForLineNumber(nextLine) - scrollTop + monacoTop
|
|
179
|
+
: monacoTop + layoutInfo.height;
|
|
180
|
+
const tplName = block.sourceHeading?.templateAnnotation?.template;
|
|
181
|
+
next.push({
|
|
182
|
+
block,
|
|
183
|
+
top: lineTop,
|
|
184
|
+
bottom,
|
|
185
|
+
annotated: !!tplName && hasTemplate(tplName),
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
setEntries((prev) => (sameEntries(prev, next) ? prev : next));
|
|
189
|
+
|
|
190
|
+
// Page edges — Monaco's editor area extends to its container's
|
|
191
|
+
// box edges; treat the whole editor as the "page".
|
|
192
|
+
const left = monacoRect.left - wrapperRect.left;
|
|
193
|
+
const right = monacoRect.right - wrapperRect.left;
|
|
194
|
+
setPageEdges((prev) =>
|
|
195
|
+
prev != null && Math.abs(prev.left - left) < 0.5 && Math.abs(prev.right - right) < 0.5
|
|
196
|
+
? prev
|
|
197
|
+
: { left, right },
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
recompute();
|
|
203
|
+
const scrollSub = monacoEditor.onDidScrollChange(recompute);
|
|
204
|
+
const contentSub = monacoEditor.onDidChangeModelContent(recompute);
|
|
205
|
+
const layoutSub = monacoEditor.onDidLayoutChange(recompute);
|
|
206
|
+
const ro = new ResizeObserver(recompute);
|
|
207
|
+
const monacoRoot = monacoEditor.getDomNode();
|
|
208
|
+
if (monacoRoot) ro.observe(monacoRoot);
|
|
209
|
+
window.addEventListener('resize', recompute);
|
|
210
|
+
const settle = window.setTimeout(recompute, 250);
|
|
211
|
+
|
|
212
|
+
return () => {
|
|
213
|
+
cancelAnimationFrame(raf);
|
|
214
|
+
window.clearTimeout(settle);
|
|
215
|
+
scrollSub.dispose();
|
|
216
|
+
contentSub.dispose();
|
|
217
|
+
layoutSub.dispose();
|
|
218
|
+
ro.disconnect();
|
|
219
|
+
window.removeEventListener('resize', recompute);
|
|
220
|
+
};
|
|
221
|
+
}, [activeView, monacoEditor, flatBlocks, refInsideWrapper]);
|
|
222
|
+
|
|
223
|
+
// Reset edges when the active view changes — the previous view's
|
|
224
|
+
// measurements don't carry over to the next.
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
setEntries([]);
|
|
227
|
+
setPageEdges(null);
|
|
228
|
+
}, [activeView]);
|
|
229
|
+
|
|
230
|
+
// ── scrollToBlock ──────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
const scrollToBlock = useCallback(
|
|
233
|
+
(block: Block) => {
|
|
234
|
+
if (activeView === 'wysiwyg') {
|
|
235
|
+
const node = refInsideWrapper.current;
|
|
236
|
+
const wrapper = node ? findWrapper(node) : null;
|
|
237
|
+
const wysiwygContainer = wrapper?.querySelector<HTMLElement>('.squisq-wysiwyg-container');
|
|
238
|
+
if (!wysiwygContainer) return;
|
|
239
|
+
const headings = wysiwygContainer.querySelectorAll<HTMLElement>('h1, h2, h3, h4, h5, h6');
|
|
240
|
+
const index = flatBlocks.findIndex((b) => b.id === block.id);
|
|
241
|
+
if (index < 0 || index >= headings.length) return;
|
|
242
|
+
headings[index].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
243
|
+
if (tiptapEditor) {
|
|
244
|
+
try {
|
|
245
|
+
tiptapEditor.chain().focus().run();
|
|
246
|
+
} catch {
|
|
247
|
+
// ignore
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (activeView === 'raw' && monacoEditor) {
|
|
253
|
+
const line = block.sourceHeading?.position?.start.line;
|
|
254
|
+
if (typeof line !== 'number') return;
|
|
255
|
+
monacoEditor.revealLineInCenter(line);
|
|
256
|
+
monacoEditor.setPosition({ lineNumber: line, column: 1 });
|
|
257
|
+
monacoEditor.focus();
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
[activeView, flatBlocks, monacoEditor, refInsideWrapper, tiptapEditor],
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
entries,
|
|
265
|
+
pageEdges,
|
|
266
|
+
scrollToBlock,
|
|
267
|
+
ready: entries.length > 0 || pageEdges != null,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
function findWrapper(node: HTMLElement): HTMLElement | null {
|
|
274
|
+
let cur: HTMLElement | null = node.parentElement;
|
|
275
|
+
while (cur) {
|
|
276
|
+
if (cur.classList.contains('squisq-editor-with-gutter')) return cur;
|
|
277
|
+
cur = cur.parentElement;
|
|
278
|
+
}
|
|
279
|
+
// Fallback: immediate parent (during transitional renames).
|
|
280
|
+
return node.parentElement;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function sameEntries(a: HeadingLayoutEntry[], b: HeadingLayoutEntry[]): boolean {
|
|
284
|
+
if (a.length !== b.length) return false;
|
|
285
|
+
for (let i = 0; i < a.length; i++) {
|
|
286
|
+
const x = a[i];
|
|
287
|
+
const y = b[i];
|
|
288
|
+
if (x.block.id !== y.block.id) return false;
|
|
289
|
+
if (x.annotated !== y.annotated) return false;
|
|
290
|
+
if (Math.abs(x.top - y.top) > 0.5) return false;
|
|
291
|
+
if (Math.abs(x.bottom - y.bottom) > 0.5) return false;
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collect FontAwesome `@font-face` + utility CSS rules from the host
|
|
3
|
+
* document's loaded stylesheets so we can inline them into a sandboxed
|
|
4
|
+
* iframe preview.
|
|
5
|
+
*
|
|
6
|
+
* Why: an iframe rendered via `srcDoc` cannot reliably load
|
|
7
|
+
* FontAwesome from a cross-origin CDN. Tracking prevention, stricter
|
|
8
|
+
* origin policies, and the iframe's about:srcdoc origin all conspire
|
|
9
|
+
* to silently drop font fetches. The icons end up invisible even
|
|
10
|
+
* though the `<i class="fa-…">` tags are present in the markup. The
|
|
11
|
+
* host page (editor-react ships a bundled `@import` of
|
|
12
|
+
* `@fortawesome/fontawesome-free`) already has the font loaded and its
|
|
13
|
+
* woff2 URLs resolve to same-origin assets, so we extract those rules
|
|
14
|
+
* once and pass them into the renderer as inline CSS.
|
|
15
|
+
*
|
|
16
|
+
* Returns `undefined` when no FA rules are found — typical for SSR or
|
|
17
|
+
* for hosts that don't bundle FA — in which case the renderer falls
|
|
18
|
+
* back to its cdnjs `<link>` (still works in plain standalone HTML).
|
|
19
|
+
*/
|
|
20
|
+
export function collectInlineFontAwesomeCss(): string | undefined {
|
|
21
|
+
if (typeof document === 'undefined') return undefined;
|
|
22
|
+
|
|
23
|
+
const collected: string[] = [];
|
|
24
|
+
|
|
25
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
26
|
+
let rules: CSSRuleList | null = null;
|
|
27
|
+
try {
|
|
28
|
+
rules = sheet.cssRules;
|
|
29
|
+
} catch {
|
|
30
|
+
// Cross-origin stylesheets throw on `.cssRules` access. We can't
|
|
31
|
+
// read them, but they're also not where editor-react's bundled
|
|
32
|
+
// FA lives, so it's safe to skip them.
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (!rules) continue;
|
|
36
|
+
|
|
37
|
+
for (const rule of Array.from(rules)) {
|
|
38
|
+
if (isFontAwesomeRule(rule)) {
|
|
39
|
+
collected.push(rule.cssText);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return collected.length > 0 ? collected.join('\n') : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Match `@font-face` declarations whose family starts with
|
|
49
|
+
* "Font Awesome", plus the FA utility classes (the `.fa-*` rules that
|
|
50
|
+
* set `content: "\fXXX"` and font-family). The former gets the woff2
|
|
51
|
+
* loaded into the iframe, the latter wires the `<i>` markup to it.
|
|
52
|
+
*/
|
|
53
|
+
function isFontAwesomeRule(rule: CSSRule): boolean {
|
|
54
|
+
const text = rule.cssText;
|
|
55
|
+
// `@font-face { font-family: "Font Awesome 6 Brands"; … }`
|
|
56
|
+
if (rule.type === CSSRule.FONT_FACE_RULE) {
|
|
57
|
+
return /font-family:\s*['"]?Font Awesome/i.test(text);
|
|
58
|
+
}
|
|
59
|
+
// `.fa-github::before { content: "\f09b"; }` and the base
|
|
60
|
+
// `.fa-brands { font-family: "Font Awesome 6 Brands"; … }` rules.
|
|
61
|
+
// FA's stylesheet prefixes selectors with `.fa`, `.fas`, `.far`,
|
|
62
|
+
// `.fab`, `.fa-solid`, `.fa-regular`, `.fa-brands`. Match the
|
|
63
|
+
// family name in the declaration block to avoid false positives
|
|
64
|
+
// (e.g. someone else's `.fa-…` class that doesn't use the font).
|
|
65
|
+
if (rule.type === CSSRule.STYLE_RULE) {
|
|
66
|
+
return /Font Awesome/i.test(text) || /content:\s*["']\\[ef]/.test(text);
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|