@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/dist/Toolbar.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useReducer, useRef, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
|
3
3
|
import { useEditorContext } from './EditorContext';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { VersionHistoryPanel } from './VersionHistoryPanel';
|
|
5
|
+
import { RecorderEntry } from './RecorderEntry';
|
|
6
|
+
import { ViewMenuPanel } from './ViewMenuPanel';
|
|
7
|
+
import { TemplatePicker, TEMPLATE_NAMES } from './TemplatePicker';
|
|
8
|
+
import { profileBlockContents, recommendTemplatesForBlock } from '@bendyline/squisq/recommend';
|
|
9
|
+
import { findBlockSliceAtLine, findBlockSliceByHeadingIndex } from './blockSlice';
|
|
10
|
+
import { LinkDialog } from './LinkDialog';
|
|
11
|
+
import { DocumentSettingsDialog } from './DocumentSettingsDialog';
|
|
12
|
+
import { EmojiPicker, EMOJI_PICKER_WIDTH, EMOJI_PICKER_MAX_HEIGHT } from './EmojiPicker';
|
|
13
|
+
import { createPortal } from 'react-dom';
|
|
7
14
|
const VIEWS = [
|
|
8
15
|
{ id: 'wysiwyg', label: 'Editor', shortcut: '⌘1' },
|
|
9
16
|
{ id: 'raw', label: 'Markdown', shortLabel: 'MD', shortcut: '⌘2' },
|
|
@@ -43,16 +50,41 @@ const BUTTONS = [
|
|
|
43
50
|
{ id: 'h1', label: 'H1', icon: 'H1', title: 'Heading 1', group: 'structure' },
|
|
44
51
|
{ id: 'h2', label: 'H2', icon: 'H2', title: 'Heading 2', group: 'structure' },
|
|
45
52
|
{ id: 'h3', label: 'H3', icon: 'H3', title: 'Heading 3', group: 'structure' },
|
|
53
|
+
{ id: 'h4', label: 'H4', icon: 'H4', title: 'Heading 4', group: 'structure' },
|
|
54
|
+
{ id: 'h5', label: 'H5', icon: 'H5', title: 'Heading 5', group: 'structure' },
|
|
55
|
+
{ id: 'h6', label: 'H6', icon: 'H6', title: 'Heading 6', group: 'structure' },
|
|
46
56
|
// Insert group — block-level inserts (quote, code blocks, rules)
|
|
47
57
|
{ id: 'quote', label: '❝', icon: '❝', title: 'Blockquote', group: 'insert' },
|
|
48
58
|
{ id: 'codeblock', label: '{ }', icon: '{ }', title: 'Code block', group: 'insert' },
|
|
49
59
|
{ id: 'code', label: '</>', icon: '</>', title: 'Inline code', group: 'insert' },
|
|
50
60
|
{ id: 'hr', label: '—', icon: '—', title: 'Horizontal rule', group: 'insert' },
|
|
51
|
-
// Media group — links, tables, images
|
|
61
|
+
// Media group — links, tables, images, emoji
|
|
52
62
|
{ id: 'link', label: '🔗', icon: '🔗', title: 'Insert link', group: 'media' },
|
|
53
63
|
{ id: 'table', label: 'table', icon: '', title: 'Insert table', group: 'media' },
|
|
54
64
|
{ id: 'image', label: '🖼', icon: '🖼', title: 'Insert image', group: 'media' },
|
|
65
|
+
{ id: 'emoji', label: '😊', icon: '😊', title: 'Insert emoji', group: 'media' },
|
|
55
66
|
];
|
|
67
|
+
// ─── Inline SVG icons (line-art, currentColor) ──────────
|
|
68
|
+
const TABLE_ICON = (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "1", width: "12", height: "12", rx: "1" }), _jsx("line", { x1: "1", y1: "5", x2: "13", y2: "5" }), _jsx("line", { x1: "1", y1: "9", x2: "13", y2: "9" }), _jsx("line", { x1: "5", y1: "1", x2: "5", y2: "13" }), _jsx("line", { x1: "9", y1: "1", x2: "9", y2: "13" })] }));
|
|
69
|
+
const LINK_ICON = (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M5.75 8.25 L8.25 5.75" }), _jsx("path", { d: "M6.5 3.75 L8 2.25 a2.5 2.5 0 0 1 3.54 3.54 L10 7.25" }), _jsx("path", { d: "M7.5 10.25 L6 11.75 a2.5 2.5 0 0 1 -3.54 -3.54 L4 6.75" })] }));
|
|
70
|
+
const IMAGE_ICON = (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("rect", { x: "1.5", y: "2.5", width: "11", height: "9", rx: "1" }), _jsx("circle", { cx: "5", cy: "5.5", r: "0.9" }), _jsx("path", { d: "M2 10 L5.5 7 L8 9 L10 7.5 L12.5 10" })] }));
|
|
71
|
+
const PAPERCLIP_ICON = (_jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M11 4 L5.5 9.5 a1.75 1.75 0 0 0 2.5 2.5 L12.5 7.5 a3 3 0 0 0 -4.25 -4.25 L3 8.5 a4.25 4.25 0 0 0 6 6 L13 10.5" }) }));
|
|
72
|
+
const EMOJI_ICON = (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "7", cy: "7", r: "5.25" }), _jsx("circle", { cx: "5.25", cy: "5.75", r: "0.6", fill: "currentColor", stroke: "none" }), _jsx("circle", { cx: "8.75", cy: "5.75", r: "0.6", fill: "currentColor", stroke: "none" }), _jsx("path", { d: "M4.75 8.5 a2.5 2.5 0 0 0 4.5 0" })] }));
|
|
73
|
+
/** Returns an SVG element when the button id maps to one, otherwise null. */
|
|
74
|
+
function buttonIconSvg(id) {
|
|
75
|
+
switch (id) {
|
|
76
|
+
case 'table':
|
|
77
|
+
return TABLE_ICON;
|
|
78
|
+
case 'link':
|
|
79
|
+
return LINK_ICON;
|
|
80
|
+
case 'image':
|
|
81
|
+
return IMAGE_ICON;
|
|
82
|
+
case 'emoji':
|
|
83
|
+
return EMOJI_ICON;
|
|
84
|
+
default:
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
56
88
|
// ─── Tiptap active-state map ────────────────────────────
|
|
57
89
|
/** Returns true if the given button id is currently active in Tiptap */
|
|
58
90
|
function isTiptapActive(editor, id) {
|
|
@@ -73,6 +105,12 @@ function isTiptapActive(editor, id) {
|
|
|
73
105
|
return editor.isActive('heading', { level: 2 });
|
|
74
106
|
case 'h3':
|
|
75
107
|
return editor.isActive('heading', { level: 3 });
|
|
108
|
+
case 'h4':
|
|
109
|
+
return editor.isActive('heading', { level: 4 });
|
|
110
|
+
case 'h5':
|
|
111
|
+
return editor.isActive('heading', { level: 5 });
|
|
112
|
+
case 'h6':
|
|
113
|
+
return editor.isActive('heading', { level: 6 });
|
|
76
114
|
case 'quote':
|
|
77
115
|
return editor.isActive('blockquote');
|
|
78
116
|
case 'ul':
|
|
@@ -91,7 +129,7 @@ function isTiptapActive(editor, id) {
|
|
|
91
129
|
* - Raw: appends markdown syntax to the source
|
|
92
130
|
*/
|
|
93
131
|
export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAfterActions, slotRight, showPlayTab = true, }) {
|
|
94
|
-
const { activeView, setActiveView, markdownSource, setMarkdownSource, tiptapEditor, monacoEditor, mediaProvider, editorMode, } = useEditorContext();
|
|
132
|
+
const { activeView, setActiveView, markdownSource, setMarkdownSource, tiptapEditor, monacoEditor, mediaProvider, editorMode, versioning, allowRecording, documentLinkProvider, theme, } = useEditorContext();
|
|
95
133
|
const isCodeMode = editorMode === 'code';
|
|
96
134
|
// In code mode only the raw view is meaningful; the WYSIWYG and Preview
|
|
97
135
|
// surfaces aren't mounted, so hide their tabs.
|
|
@@ -105,6 +143,41 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
|
|
|
105
143
|
const showViewTabs = visibleViews.length > 1;
|
|
106
144
|
// Hidden file input for image picker
|
|
107
145
|
const imageInputRef = useRef(null);
|
|
146
|
+
// Link dialog — shared by WYSIWYG and Raw views.
|
|
147
|
+
const [linkDialog, setLinkDialog] = useState(null);
|
|
148
|
+
// Emoji picker — toolbar-anchored popover. We track the trigger
|
|
149
|
+
// button's screen rect so the picker can position itself just below
|
|
150
|
+
// it via createPortal (the toolbar's overflow:hidden actions row
|
|
151
|
+
// would otherwise clip the popover).
|
|
152
|
+
const emojiButtonRef = useRef(null);
|
|
153
|
+
const [emojiPickerAnchor, setEmojiPickerAnchor] = useState(null);
|
|
154
|
+
const openEmojiPicker = useCallback(() => {
|
|
155
|
+
const btn = emojiButtonRef.current;
|
|
156
|
+
if (!btn)
|
|
157
|
+
return;
|
|
158
|
+
const rect = btn.getBoundingClientRect();
|
|
159
|
+
// Position just below the trigger by default, then clamp into the
|
|
160
|
+
// visible viewport so the picker is never clipped on the right or
|
|
161
|
+
// bottom — flips above the trigger when there isn't room below.
|
|
162
|
+
const gap = 6;
|
|
163
|
+
const margin = 8;
|
|
164
|
+
const vw = window.innerWidth;
|
|
165
|
+
const vh = window.innerHeight;
|
|
166
|
+
let left = rect.left;
|
|
167
|
+
if (left + EMOJI_PICKER_WIDTH + margin > vw) {
|
|
168
|
+
left = Math.max(margin, vw - EMOJI_PICKER_WIDTH - margin);
|
|
169
|
+
}
|
|
170
|
+
let top = rect.bottom + gap;
|
|
171
|
+
if (top + EMOJI_PICKER_MAX_HEIGHT + margin > vh) {
|
|
172
|
+
const flipped = rect.top - EMOJI_PICKER_MAX_HEIGHT - gap;
|
|
173
|
+
// Prefer flipping above when there's more room there; otherwise
|
|
174
|
+
// pin to the top edge with margin and let the picker's own
|
|
175
|
+
// maxHeight clip it.
|
|
176
|
+
top = flipped >= margin ? flipped : margin;
|
|
177
|
+
}
|
|
178
|
+
setEmojiPickerAnchor({ top, left });
|
|
179
|
+
}, []);
|
|
180
|
+
const closeEmojiPicker = useCallback(() => setEmojiPickerAnchor(null), []);
|
|
108
181
|
// ── Narrow-screen detection ──────────────────────────
|
|
109
182
|
const [isNarrow, setIsNarrow] = useState(() => typeof window !== 'undefined' && window.matchMedia('(max-width: 768px)').matches);
|
|
110
183
|
useEffect(() => {
|
|
@@ -118,6 +191,8 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
|
|
|
118
191
|
const [measuredOverflowIndex, setMeasuredOverflowIndex] = useState(null);
|
|
119
192
|
const [showOverflow, setShowOverflow] = useState(false);
|
|
120
193
|
const overflowRef = useRef(null);
|
|
194
|
+
// Document settings (frontmatter) dialog
|
|
195
|
+
const [showDocSettings, setShowDocSettings] = useState(false);
|
|
121
196
|
// On narrow screens, force all buttons into the overflow menu
|
|
122
197
|
const overflowIndex = isNarrow ? 0 : measuredOverflowIndex;
|
|
123
198
|
useEffect(() => {
|
|
@@ -220,6 +295,15 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
|
|
|
220
295
|
case 'h3':
|
|
221
296
|
chain.toggleHeading({ level: 3 }).run();
|
|
222
297
|
break;
|
|
298
|
+
case 'h4':
|
|
299
|
+
chain.toggleHeading({ level: 4 }).run();
|
|
300
|
+
break;
|
|
301
|
+
case 'h5':
|
|
302
|
+
chain.toggleHeading({ level: 5 }).run();
|
|
303
|
+
break;
|
|
304
|
+
case 'h6':
|
|
305
|
+
chain.toggleHeading({ level: 6 }).run();
|
|
306
|
+
break;
|
|
223
307
|
case 'quote':
|
|
224
308
|
chain.toggleBlockquote().run();
|
|
225
309
|
break;
|
|
@@ -236,12 +320,30 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
|
|
|
236
320
|
chain.setHorizontalRule().run();
|
|
237
321
|
break;
|
|
238
322
|
case 'link': {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
323
|
+
const isActive = tiptapEditor.isActive('link');
|
|
324
|
+
let initialText = '';
|
|
325
|
+
let initialUrl = '';
|
|
326
|
+
if (isActive) {
|
|
327
|
+
// Snap selection to the full link mark so editing replaces
|
|
328
|
+
// the entire `[text](url)` rather than just the cursor word.
|
|
329
|
+
tiptapEditor.chain().focus().extendMarkRange('link').run();
|
|
330
|
+
const sel = tiptapEditor.state.selection;
|
|
331
|
+
initialText = tiptapEditor.state.doc.textBetween(sel.from, sel.to, ' ');
|
|
332
|
+
initialUrl = tiptapEditor.getAttributes('link').href ?? '';
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
const { from, to, empty } = tiptapEditor.state.selection;
|
|
336
|
+
if (!empty) {
|
|
337
|
+
initialText = tiptapEditor.state.doc.textBetween(from, to, ' ');
|
|
338
|
+
}
|
|
244
339
|
}
|
|
340
|
+
setLinkDialog({
|
|
341
|
+
mode: isActive ? 'update' : 'insert',
|
|
342
|
+
target: 'wysiwyg',
|
|
343
|
+
initialText,
|
|
344
|
+
initialUrl,
|
|
345
|
+
rawRange: null,
|
|
346
|
+
});
|
|
245
347
|
break;
|
|
246
348
|
}
|
|
247
349
|
case 'table':
|
|
@@ -307,6 +409,15 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
|
|
|
307
409
|
case 'h3':
|
|
308
410
|
prefixLines('### ', 'Heading 3');
|
|
309
411
|
break;
|
|
412
|
+
case 'h4':
|
|
413
|
+
prefixLines('#### ', 'Heading 4');
|
|
414
|
+
break;
|
|
415
|
+
case 'h5':
|
|
416
|
+
prefixLines('##### ', 'Heading 5');
|
|
417
|
+
break;
|
|
418
|
+
case 'h6':
|
|
419
|
+
prefixLines('###### ', 'Heading 6');
|
|
420
|
+
break;
|
|
310
421
|
case 'quote':
|
|
311
422
|
prefixLines('> ', 'Quote');
|
|
312
423
|
break;
|
|
@@ -328,14 +439,42 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
|
|
|
328
439
|
break;
|
|
329
440
|
}
|
|
330
441
|
case 'link': {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
442
|
+
// Open the LinkDialog instead of inserting literal text. If the
|
|
443
|
+
// cursor sits inside an existing `[text](url)` on this line,
|
|
444
|
+
// prefill from it and replace the whole match on confirm.
|
|
445
|
+
const lineNumber = selection.startLineNumber;
|
|
446
|
+
const lineText = model.getLineContent(lineNumber);
|
|
447
|
+
const cursorCol = selection.startColumn;
|
|
448
|
+
const linkRe = /\[([^\]]*)\]\(([^)]*)\)/g;
|
|
449
|
+
let match;
|
|
450
|
+
let existing = null;
|
|
451
|
+
while ((match = linkRe.exec(lineText)) !== null) {
|
|
452
|
+
const startCol = match.index + 1; // 1-based
|
|
453
|
+
const endCol = startCol + match[0].length;
|
|
454
|
+
if (cursorCol >= startCol && cursorCol <= endCol) {
|
|
455
|
+
existing = {
|
|
456
|
+
text: match[1],
|
|
457
|
+
url: match[2],
|
|
458
|
+
range: {
|
|
459
|
+
startLineNumber: lineNumber,
|
|
460
|
+
startColumn: startCol,
|
|
461
|
+
endLineNumber: lineNumber,
|
|
462
|
+
endColumn: endCol,
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
337
467
|
}
|
|
338
|
-
|
|
468
|
+
setLinkDialog({
|
|
469
|
+
mode: existing ? 'update' : 'insert',
|
|
470
|
+
target: 'raw',
|
|
471
|
+
initialText: existing ? existing.text : hasSelection ? selectedText : '',
|
|
472
|
+
initialUrl: existing ? existing.url : '',
|
|
473
|
+
rawRange: existing ? existing.range : null,
|
|
474
|
+
});
|
|
475
|
+
// Skip the executeEdits/setPosition tail below — the dialog will
|
|
476
|
+
// apply its own edit on confirm.
|
|
477
|
+
return;
|
|
339
478
|
}
|
|
340
479
|
case 'table': {
|
|
341
480
|
const tpl = '| Header 1 | Header 2 | Header 3 |\n| --- | --- | --- |\n| Cell | Cell | Cell |\n| Cell | Cell | Cell |';
|
|
@@ -436,29 +575,343 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
|
|
|
436
575
|
imageInputRef.current?.click();
|
|
437
576
|
return;
|
|
438
577
|
}
|
|
578
|
+
if (id === 'emoji') {
|
|
579
|
+
// Toggle the popover: clicking the button again closes it.
|
|
580
|
+
if (emojiPickerAnchor)
|
|
581
|
+
closeEmojiPicker();
|
|
582
|
+
else
|
|
583
|
+
openEmojiPicker();
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
439
586
|
if (activeView === 'wysiwyg' && tiptapEditor) {
|
|
440
587
|
handleTiptap(id);
|
|
441
588
|
}
|
|
442
589
|
else {
|
|
443
590
|
handleRaw(id);
|
|
444
591
|
}
|
|
445
|
-
}, [
|
|
592
|
+
}, [
|
|
593
|
+
activeView,
|
|
594
|
+
tiptapEditor,
|
|
595
|
+
handleTiptap,
|
|
596
|
+
handleRaw,
|
|
597
|
+
emojiPickerAnchor,
|
|
598
|
+
openEmojiPicker,
|
|
599
|
+
closeEmojiPicker,
|
|
600
|
+
]);
|
|
601
|
+
// ── Picker insert (emoji or FontAwesome icon) ──────
|
|
602
|
+
// Inserts a chosen picker entry at the cursor. We bypass
|
|
603
|
+
// `insertAtCursor` (which routes through markdown→Tiptap conversion
|
|
604
|
+
// and wraps the input in a paragraph) so entries land inline at the
|
|
605
|
+
// caret rather than starting a new block. Emoji insert as a plain
|
|
606
|
+
// character; FontAwesome icons insert as the `InlineIcon` Tiptap
|
|
607
|
+
// node so the editor renders them inline immediately.
|
|
608
|
+
const handleEmojiSelect = useCallback((entry) => {
|
|
609
|
+
if (activeView === 'wysiwyg' && tiptapEditor) {
|
|
610
|
+
if (entry.kind === 'emoji') {
|
|
611
|
+
tiptapEditor.chain().focus().insertContent(entry.char).run();
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
tiptapEditor
|
|
615
|
+
.chain()
|
|
616
|
+
.focus()
|
|
617
|
+
.insertContent({
|
|
618
|
+
type: 'inlineIcon',
|
|
619
|
+
attrs: { token: entry.token, family: entry.family, name: entry.name },
|
|
620
|
+
})
|
|
621
|
+
.run();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
else if (activeView === 'raw' && monacoEditor) {
|
|
625
|
+
const insertion = entry.kind === 'emoji' ? entry.char : `{[${entry.token}]}`;
|
|
626
|
+
const position = monacoEditor.getPosition();
|
|
627
|
+
if (position) {
|
|
628
|
+
const range = {
|
|
629
|
+
startLineNumber: position.lineNumber,
|
|
630
|
+
startColumn: position.column,
|
|
631
|
+
endLineNumber: position.lineNumber,
|
|
632
|
+
endColumn: position.column,
|
|
633
|
+
};
|
|
634
|
+
monacoEditor.executeEdits('picker-insert', [{ range, text: insertion }]);
|
|
635
|
+
monacoEditor.focus();
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
setMarkdownSource(markdownSource + insertion);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
const insertion = entry.kind === 'emoji' ? entry.char : `{[${entry.token}]}`;
|
|
643
|
+
setMarkdownSource(markdownSource + insertion);
|
|
644
|
+
}
|
|
645
|
+
closeEmojiPicker();
|
|
646
|
+
}, [activeView, tiptapEditor, monacoEditor, markdownSource, setMarkdownSource, closeEmojiPicker]);
|
|
647
|
+
// ── Ctrl+K / Cmd+K → open the link dialog ────────────
|
|
648
|
+
// Mirrors the behaviour of common editors (Word, Google Docs, VS Code's
|
|
649
|
+
// Markdown preview): if the cursor is in a Squisq editor surface, the
|
|
650
|
+
// shortcut routes through the same handler the toolbar Link button uses,
|
|
651
|
+
// which prefills the dialog from the current selection (or the link
|
|
652
|
+
// under the cursor) before opening.
|
|
653
|
+
useEffect(() => {
|
|
654
|
+
const onKeyDown = (e) => {
|
|
655
|
+
if (!(e.ctrlKey || e.metaKey) || e.altKey || e.shiftKey)
|
|
656
|
+
return;
|
|
657
|
+
if (e.key.toLowerCase() !== 'k')
|
|
658
|
+
return;
|
|
659
|
+
const target = e.target;
|
|
660
|
+
if (!target)
|
|
661
|
+
return;
|
|
662
|
+
// Only intercept when focus is inside one of our editor surfaces.
|
|
663
|
+
const inEditor = !!target.closest('.squisq-wysiwyg-editor, .ProseMirror, .squisq-raw-editor-container, .monaco-editor');
|
|
664
|
+
if (!inEditor)
|
|
665
|
+
return;
|
|
666
|
+
e.preventDefault();
|
|
667
|
+
e.stopPropagation();
|
|
668
|
+
handleAction('link');
|
|
669
|
+
};
|
|
670
|
+
window.addEventListener('keydown', onKeyDown, true);
|
|
671
|
+
return () => window.removeEventListener('keydown', onKeyDown, true);
|
|
672
|
+
}, [handleAction]);
|
|
673
|
+
// ── Link dialog confirm ──────────────────────────────
|
|
674
|
+
const handleLinkConfirm = useCallback((text, url) => {
|
|
675
|
+
if (!linkDialog)
|
|
676
|
+
return;
|
|
677
|
+
const trimmedUrl = url.trim();
|
|
678
|
+
const trimmedText = text.trim();
|
|
679
|
+
if (linkDialog.target === 'wysiwyg' && tiptapEditor) {
|
|
680
|
+
if (!trimmedUrl) {
|
|
681
|
+
// Empty URL on update = unlink. On insert with no URL, do nothing.
|
|
682
|
+
if (linkDialog.mode === 'update') {
|
|
683
|
+
tiptapEditor.chain().focus().unsetLink().run();
|
|
684
|
+
}
|
|
685
|
+
setLinkDialog(null);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const visibleText = trimmedText || trimmedUrl;
|
|
689
|
+
const chain = tiptapEditor.chain().focus();
|
|
690
|
+
// Insert (or replace selection) with text carrying a link mark. When
|
|
691
|
+
// updating an existing link, the selection was extended to the full
|
|
692
|
+
// mark range earlier, so this replaces the entire `[text](url)`.
|
|
693
|
+
chain
|
|
694
|
+
.insertContent({
|
|
695
|
+
type: 'text',
|
|
696
|
+
text: visibleText,
|
|
697
|
+
marks: [{ type: 'link', attrs: { href: trimmedUrl } }],
|
|
698
|
+
})
|
|
699
|
+
.run();
|
|
700
|
+
setLinkDialog(null);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (linkDialog.target === 'raw' && monacoEditor) {
|
|
704
|
+
const model = monacoEditor.getModel();
|
|
705
|
+
if (!model) {
|
|
706
|
+
setLinkDialog(null);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (!trimmedUrl && linkDialog.mode === 'update' && linkDialog.rawRange) {
|
|
710
|
+
// Empty URL on update = strip the markdown link, keep the text.
|
|
711
|
+
monacoEditor.executeEdits('toolbar-link-edit', [
|
|
712
|
+
{ range: linkDialog.rawRange, text: trimmedText || linkDialog.initialText },
|
|
713
|
+
]);
|
|
714
|
+
monacoEditor.focus();
|
|
715
|
+
setLinkDialog(null);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (!trimmedUrl) {
|
|
719
|
+
setLinkDialog(null);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const visibleText = trimmedText || trimmedUrl;
|
|
723
|
+
const replacement = `[${visibleText}](${trimmedUrl})`;
|
|
724
|
+
const range = linkDialog.rawRange ?? monacoEditor.getSelection();
|
|
725
|
+
if (!range) {
|
|
726
|
+
setLinkDialog(null);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
monacoEditor.executeEdits('toolbar-link-edit', [{ range, text: replacement }]);
|
|
730
|
+
monacoEditor.focus();
|
|
731
|
+
setLinkDialog(null);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
setLinkDialog(null);
|
|
735
|
+
}, [linkDialog, tiptapEditor, monacoEditor]);
|
|
446
736
|
const groups = ['format', 'lists', 'structure', 'insert', 'media'];
|
|
447
737
|
const isWysiwyg = activeView === 'wysiwyg' && tiptapEditor;
|
|
448
738
|
const isPreview = activeView === 'preview';
|
|
739
|
+
// ── Progressive heading disclosure ───────────────────
|
|
740
|
+
// H1\u2013H3 are always visible. H4 appears once the document already
|
|
741
|
+
// contains an H3, H5 once it contains an H4, and H6 once it contains
|
|
742
|
+
// an H5. This keeps the toolbar compact for typical short documents
|
|
743
|
+
// while letting deeply nested documents reach every level.
|
|
744
|
+
const maxHeadingLevelInDoc = useMemo(() => {
|
|
745
|
+
if (!markdownSource)
|
|
746
|
+
return 0;
|
|
747
|
+
let max = 0;
|
|
748
|
+
let inFence = false;
|
|
749
|
+
for (const rawLine of markdownSource.split('\n')) {
|
|
750
|
+
const line = rawLine.trimEnd();
|
|
751
|
+
if (/^\s*```/.test(line)) {
|
|
752
|
+
inFence = !inFence;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (inFence)
|
|
756
|
+
continue;
|
|
757
|
+
const m = /^(#{1,6})\s+\S/.exec(line);
|
|
758
|
+
if (m && m[1].length > max)
|
|
759
|
+
max = m[1].length;
|
|
760
|
+
}
|
|
761
|
+
return max;
|
|
762
|
+
}, [markdownSource]);
|
|
763
|
+
// Show H(n+1) when the document already contains H(n), starting from H3.
|
|
764
|
+
const visibleHeadingMax = Math.min(6, Math.max(3, maxHeadingLevelInDoc + 1));
|
|
765
|
+
const isButtonVisible = (id) => {
|
|
766
|
+
const m = /^h([1-6])$/.exec(id);
|
|
767
|
+
if (!m)
|
|
768
|
+
return true;
|
|
769
|
+
return Number(m[1]) <= visibleHeadingMax;
|
|
770
|
+
};
|
|
449
771
|
// Detect whether cursor is inside a table (WYSIWYG mode only)
|
|
450
772
|
const isInTable = isWysiwyg ? tiptapEditor.isActive('table') : false;
|
|
451
773
|
// Detect current heading template (WYSIWYG mode only)
|
|
452
|
-
const
|
|
774
|
+
const wysiwygTemplate = isWysiwyg
|
|
453
775
|
? tiptapEditor.isActive('heading')
|
|
454
776
|
? (tiptapEditor.getAttributes('heading')?.dataTemplate ?? '')
|
|
455
777
|
: null
|
|
456
778
|
: null;
|
|
779
|
+
// ── Monaco heading detection (Markdown view) ─────────────────────
|
|
780
|
+
// Watch the Monaco cursor and surface the template picker whenever the
|
|
781
|
+
// cursor is on a heading line. `null` hides the picker; '' shows it
|
|
782
|
+
// with no template selected; any other string is the current template.
|
|
783
|
+
const isRawView = activeView === 'raw';
|
|
784
|
+
const [rawTemplate, setRawTemplate] = useState(null);
|
|
785
|
+
const [rawHeadingLine, setRawHeadingLine] = useState(null);
|
|
786
|
+
useEffect(() => {
|
|
787
|
+
if (!isRawView || !monacoEditor) {
|
|
788
|
+
setRawTemplate(null);
|
|
789
|
+
setRawHeadingLine(null);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const recompute = () => {
|
|
793
|
+
const model = monacoEditor.getModel();
|
|
794
|
+
const pos = monacoEditor.getPosition();
|
|
795
|
+
if (!model || !pos) {
|
|
796
|
+
setRawTemplate(null);
|
|
797
|
+
setRawHeadingLine(null);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
const line = model.getLineContent(pos.lineNumber);
|
|
801
|
+
const headingMatch = line.match(/^#{1,6}\s+(.+)$/);
|
|
802
|
+
if (!headingMatch) {
|
|
803
|
+
setRawTemplate(null);
|
|
804
|
+
setRawHeadingLine(null);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
setRawHeadingLine(pos.lineNumber);
|
|
808
|
+
const annotMatch = headingMatch[1].match(/\s*\{\[([^\]]+)\]\}[\s\]}]*$/);
|
|
809
|
+
if (annotMatch) {
|
|
810
|
+
// First whitespace-delimited token is the template name; the rest are params.
|
|
811
|
+
const name = annotMatch[1].trim().split(/\s+/)[0];
|
|
812
|
+
setRawTemplate(name);
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
setRawTemplate('');
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
recompute();
|
|
819
|
+
const cursorSub = monacoEditor.onDidChangeCursorPosition(recompute);
|
|
820
|
+
const contentSub = monacoEditor.onDidChangeModelContent(recompute);
|
|
821
|
+
return () => {
|
|
822
|
+
cursorSub.dispose();
|
|
823
|
+
contentSub.dispose();
|
|
824
|
+
};
|
|
825
|
+
}, [isRawView, monacoEditor]);
|
|
826
|
+
// Track the index of the heading the WYSIWYG cursor is in among all
|
|
827
|
+
// top-level headings. Used to locate the same heading in the markdown
|
|
828
|
+
// source for content-based template recommendations.
|
|
829
|
+
const [wysiwygHeadingIndex, setWysiwygHeadingIndex] = useState(null);
|
|
830
|
+
useEffect(() => {
|
|
831
|
+
if (!isWysiwyg || !tiptapEditor) {
|
|
832
|
+
setWysiwygHeadingIndex(null);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const recompute = () => {
|
|
836
|
+
if (!tiptapEditor.isActive('heading')) {
|
|
837
|
+
setWysiwygHeadingIndex(null);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const cursor = tiptapEditor.state.selection.from;
|
|
841
|
+
let index = -1;
|
|
842
|
+
let count = 0;
|
|
843
|
+
tiptapEditor.state.doc.descendants((node, pos) => {
|
|
844
|
+
if (node.type.name !== 'heading')
|
|
845
|
+
return;
|
|
846
|
+
if (pos <= cursor && pos + node.nodeSize > cursor) {
|
|
847
|
+
index = count;
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
count++;
|
|
851
|
+
});
|
|
852
|
+
setWysiwygHeadingIndex(index >= 0 ? index : null);
|
|
853
|
+
};
|
|
854
|
+
recompute();
|
|
855
|
+
tiptapEditor.on('selectionUpdate', recompute);
|
|
856
|
+
tiptapEditor.on('update', recompute);
|
|
857
|
+
return () => {
|
|
858
|
+
tiptapEditor.off('selectionUpdate', recompute);
|
|
859
|
+
tiptapEditor.off('update', recompute);
|
|
860
|
+
};
|
|
861
|
+
}, [isWysiwyg, tiptapEditor]);
|
|
862
|
+
const currentTemplate = isWysiwyg ? wysiwygTemplate : isRawView ? rawTemplate : null;
|
|
863
|
+
// Compute recommended templates for the active block. Heading slice
|
|
864
|
+
// comes from markdownSource — raw view supplies the cursor line,
|
|
865
|
+
// WYSIWYG supplies the heading index.
|
|
866
|
+
const recommendedTemplates = useMemo(() => {
|
|
867
|
+
if (currentTemplate === null)
|
|
868
|
+
return undefined;
|
|
869
|
+
let slice = null;
|
|
870
|
+
if (isRawView && rawHeadingLine !== null) {
|
|
871
|
+
slice = findBlockSliceAtLine(markdownSource, rawHeadingLine);
|
|
872
|
+
}
|
|
873
|
+
else if (isWysiwyg && wysiwygHeadingIndex !== null) {
|
|
874
|
+
slice = findBlockSliceByHeadingIndex(markdownSource, wysiwygHeadingIndex);
|
|
875
|
+
}
|
|
876
|
+
if (slice === null)
|
|
877
|
+
return undefined;
|
|
878
|
+
const profile = profileBlockContents(slice);
|
|
879
|
+
return recommendTemplatesForBlock(profile, TEMPLATE_NAMES).recommended;
|
|
880
|
+
}, [currentTemplate, isRawView, isWysiwyg, rawHeadingLine, wysiwygHeadingIndex, markdownSource]);
|
|
457
881
|
const handleTemplatePick = (value) => {
|
|
882
|
+
// Raw (Monaco) — rewrite the heading line's annotation suffix in place.
|
|
883
|
+
if (isRawView && monacoEditor) {
|
|
884
|
+
const model = monacoEditor.getModel();
|
|
885
|
+
const pos = monacoEditor.getPosition();
|
|
886
|
+
if (!model || !pos)
|
|
887
|
+
return;
|
|
888
|
+
const lineNumber = pos.lineNumber;
|
|
889
|
+
const lineText = model.getLineContent(lineNumber);
|
|
890
|
+
const headingMatch = lineText.match(/^(#{1,6}\s+)(.+)$/);
|
|
891
|
+
if (!headingMatch)
|
|
892
|
+
return;
|
|
893
|
+
const prefix = headingMatch[1];
|
|
894
|
+
// Strip any existing trailing annotation
|
|
895
|
+
const bareText = headingMatch[2].replace(/\s*\{\[[^\]]+\]\}[\s\]}]*$/, '').trimEnd();
|
|
896
|
+
const newLine = value === '' ? `${prefix}${bareText}` : `${prefix}${bareText} {[${value}]}`;
|
|
897
|
+
monacoEditor.executeEdits('toolbar-template-pick', [
|
|
898
|
+
{
|
|
899
|
+
range: {
|
|
900
|
+
startLineNumber: lineNumber,
|
|
901
|
+
startColumn: 1,
|
|
902
|
+
endLineNumber: lineNumber,
|
|
903
|
+
endColumn: lineText.length + 1,
|
|
904
|
+
},
|
|
905
|
+
text: newLine,
|
|
906
|
+
},
|
|
907
|
+
]);
|
|
908
|
+
monacoEditor.focus();
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
// WYSIWYG — update the heading node attributes.
|
|
458
912
|
if (!tiptapEditor)
|
|
459
913
|
return;
|
|
460
914
|
if (value === '') {
|
|
461
|
-
// Clear template
|
|
462
915
|
tiptapEditor
|
|
463
916
|
.chain()
|
|
464
917
|
.focus()
|
|
@@ -475,21 +928,35 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
|
|
|
475
928
|
handleImageFile(file);
|
|
476
929
|
// Reset so the same file can be re-selected
|
|
477
930
|
e.target.value = '';
|
|
478
|
-
} }), slotLeft, showViewTabs && (_jsx("div", { className: "squisq-toolbar-view-tabs", role: "tablist", "aria-label": "Editor view", children: visibleViews.map((view) => (_jsxs("button", { role: "tab", "data-view": view.id, "aria-selected": activeView === view.id, className: `squisq-toolbar-view-tab${activeView === view.id ? ' squisq-toolbar-view-tab--active' : ''}`, onClick: () => setActiveView(view.id), "data-tooltip": `${view.label} (${view.shortcut})`, children: [_jsx("span", { className: "squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--long", "data-label": view.label, children: view.label }), view.shortLabel && view.shortLabel !== view.label && (_jsx("span", { className: "squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--short", "data-label": view.shortLabel, children: view.shortLabel }))] }, view.id))) })), !isPreview && !isNarrow && !isCodeMode && (_jsxs("div", { className: "squisq-toolbar-actions", ref: actionsRef, children: [groups.map((group, gi) => (_jsxs("div", { className: "squisq-toolbar-group", children: [gi > 0 && _jsx("div", { className: "squisq-toolbar-separator" }), BUTTONS.filter((b) => b.group === group).map((btn) => {
|
|
479
|
-
const active =
|
|
931
|
+
} }), slotLeft, showViewTabs && (_jsx("div", { className: "squisq-toolbar-view-tabs", role: "tablist", "aria-label": "Editor view", children: visibleViews.map((view) => (_jsxs("button", { role: "tab", "data-view": view.id, "aria-selected": activeView === view.id, className: `squisq-toolbar-view-tab${activeView === view.id ? ' squisq-toolbar-view-tab--active' : ''}`, onClick: () => setActiveView(view.id), "data-tooltip": `${view.label} (${view.shortcut})`, children: [_jsx("span", { className: "squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--long", "data-label": view.label, children: view.label }), view.shortLabel && view.shortLabel !== view.label && (_jsx("span", { className: "squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--short", "data-label": view.shortLabel, children: view.shortLabel }))] }, view.id))) })), !isPreview && !isNarrow && !isCodeMode && (_jsxs("div", { className: "squisq-toolbar-actions", ref: actionsRef, children: [groups.map((group, gi) => (_jsxs("div", { className: "squisq-toolbar-group", children: [gi > 0 && _jsx("div", { className: "squisq-toolbar-separator" }), BUTTONS.filter((b) => b.group === group && isButtonVisible(b.id)).map((btn) => {
|
|
932
|
+
const active = btn.id === 'emoji'
|
|
933
|
+
? emojiPickerAnchor !== null
|
|
934
|
+
: isWysiwyg
|
|
935
|
+
? isTiptapActive(tiptapEditor, btn.id)
|
|
936
|
+
: false;
|
|
480
937
|
const disabled = btn.id === 'image' && !mediaProvider;
|
|
481
|
-
return (_jsx("button", { className: `squisq-toolbar-button${active ? ' squisq-toolbar-button--active' : ''}`, "data-tooltip": disabled ? 'Insert image (requires media provider)' : btn.title, onClick: () => handleAction(btn.id), "aria-label": btn.title, "aria-pressed": active, disabled: disabled, style: btn.iconStyle, children: btn.id
|
|
482
|
-
})] }, group))), currentTemplate !== null && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator" }), _jsx("div", { className: "squisq-toolbar-group squisq-template-picker", children:
|
|
483
|
-
|
|
938
|
+
return (_jsx("button", { ref: btn.id === 'emoji' ? emojiButtonRef : undefined, className: `squisq-toolbar-button${active ? ' squisq-toolbar-button--active' : ''}`, "data-tooltip": disabled ? 'Insert image (requires media provider)' : btn.title, onClick: () => handleAction(btn.id), "aria-label": btn.title, "aria-pressed": active, disabled: disabled, style: btn.iconStyle, children: buttonIconSvg(btn.id) ?? btn.icon }, btn.id));
|
|
939
|
+
})] }, group))), currentTemplate !== null && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator" }), _jsx("div", { className: "squisq-toolbar-group squisq-template-picker", children: _jsx(TemplatePicker, { value: currentTemplate, onChange: handleTemplatePick, recommended: recommendedTemplates }) })] })), isInTable && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator" }), _jsxs("div", { className: "squisq-toolbar-group squisq-table-controls", children: [_jsx("span", { className: "squisq-table-controls-label", children: "Table:" }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add column before", onClick: () => tiptapEditor.chain().focus().addColumnBefore().run(), "aria-label": "Add column before", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "7", y: "2", width: "8", height: "12", rx: "1" }), _jsx("line", { x1: "11", y1: "2", x2: "11", y2: "14" }), _jsx("line", { x1: "1", y1: "8", x2: "4.5", y2: "8" }), _jsx("line", { x1: "2.75", y1: "6.25", x2: "2.75", y2: "9.75" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add column after", onClick: () => tiptapEditor.chain().focus().addColumnAfter().run(), "aria-label": "Add column after", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "2", width: "8", height: "12", rx: "1" }), _jsx("line", { x1: "5", y1: "2", x2: "5", y2: "14" }), _jsx("line", { x1: "11.5", y1: "8", x2: "15", y2: "8" }), _jsx("line", { x1: "13.25", y1: "6.25", x2: "13.25", y2: "9.75" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Delete column", onClick: () => tiptapEditor.chain().focus().deleteColumn().run(), "aria-label": "Delete column", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "4", y: "1", width: "8", height: "14", rx: "1" }), _jsx("line", { x1: "6", y1: "5.5", x2: "10", y2: "10.5" }), _jsx("line", { x1: "10", y1: "5.5", x2: "6", y2: "10.5" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add row above", onClick: () => tiptapEditor.chain().focus().addRowBefore().run(), "aria-label": "Add row above", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "2", y: "6", width: "12", height: "9", rx: "1" }), _jsx("line", { x1: "2", y1: "10.5", x2: "14", y2: "10.5" }), _jsx("line", { x1: "8", y1: "1", x2: "8", y2: "4.5" }), _jsx("line", { x1: "6.25", y1: "2.75", x2: "9.75", y2: "2.75" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Add row below", onClick: () => tiptapEditor.chain().focus().addRowAfter().run(), "aria-label": "Add row below", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "2", y: "1", width: "12", height: "9", rx: "1" }), _jsx("line", { x1: "2", y1: "5.5", x2: "14", y2: "5.5" }), _jsx("line", { x1: "8", y1: "11.5", x2: "8", y2: "15" }), _jsx("line", { x1: "6.25", y1: "13.25", x2: "9.75", y2: "13.25" })] }) }), _jsx("button", { className: "squisq-toolbar-button", "data-tooltip": "Delete row", onClick: () => tiptapEditor.chain().focus().deleteRow().run(), "aria-label": "Delete row", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "4", width: "14", height: "8", rx: "1" }), _jsx("line", { x1: "5.5", y1: "6", x2: "10.5", y2: "10" }), _jsx("line", { x1: "10.5", y1: "6", x2: "5.5", y2: "10" })] }) }), _jsx("button", { className: "squisq-toolbar-button squisq-toolbar-button--danger", "data-tooltip": "Delete table", onClick: () => tiptapEditor.chain().focus().deleteTable().run(), "aria-label": "Delete table", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [_jsx("rect", { x: "1", y: "1", width: "14", height: "14", rx: "1" }), _jsx("line", { x1: "1", y1: "5.5", x2: "15", y2: "5.5" }), _jsx("line", { x1: "5.5", y1: "1", x2: "5.5", y2: "15" }), _jsx("line", { x1: "4.5", y1: "4.5", x2: "11.5", y2: "11.5", strokeWidth: "2" }), _jsx("line", { x1: "11.5", y1: "4.5", x2: "4.5", y2: "11.5", strokeWidth: "2" })] }) })] })] }))] })), !isPreview && !isCodeMode && overflowIndex !== null && (_jsxs("div", { className: "squisq-toolbar-overflow", ref: overflowRef, children: [_jsx("button", { className: `squisq-toolbar-button squisq-toolbar-overflow-trigger${showOverflow ? ' squisq-toolbar-button--active' : ''}`, "data-tooltip": "More actions", onClick: () => setShowOverflow((v) => !v), "aria-label": "More actions", "aria-expanded": showOverflow, children: "\u00B7\u00B7\u00B7" }), showOverflow && (_jsxs("div", { className: `squisq-toolbar-overflow-menu squisq-toolbar-overflow-menu--${overflowPlacement}`, children: [BUTTONS.slice(overflowIndex)
|
|
940
|
+
.filter((b) => isButtonVisible(b.id))
|
|
941
|
+
.map((btn) => {
|
|
942
|
+
const active = btn.id === 'emoji'
|
|
943
|
+
? emojiPickerAnchor !== null
|
|
944
|
+
: isWysiwyg
|
|
945
|
+
? isTiptapActive(tiptapEditor, btn.id)
|
|
946
|
+
: false;
|
|
484
947
|
const disabled = btn.id === 'image' && !mediaProvider;
|
|
485
|
-
return (_jsxs("button", { className: `squisq-toolbar-overflow-item${active ? ' squisq-toolbar-overflow-item--active' : ''}`, onClick: () => {
|
|
948
|
+
return (_jsxs("button", { ref: btn.id === 'emoji' ? emojiButtonRef : undefined, className: `squisq-toolbar-overflow-item${active ? ' squisq-toolbar-overflow-item--active' : ''}`, onClick: () => {
|
|
486
949
|
handleAction(btn.id);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
950
|
+
// Keep the overflow open when opening the emoji
|
|
951
|
+
// picker — otherwise its anchor (the overflow
|
|
952
|
+
// item) unmounts and the popover loses its ref.
|
|
953
|
+
if (btn.id !== 'emoji')
|
|
954
|
+
setShowOverflow(false);
|
|
955
|
+
}, disabled: disabled, children: [buttonIconSvg(btn.id) ?? (_jsx("span", { className: "squisq-toolbar-overflow-icon", style: btn.iconStyle, children: btn.icon })), _jsx("span", { children: btn.title })] }, btn.id));
|
|
956
|
+
}), currentTemplate !== null && (_jsxs("div", { className: "squisq-toolbar-overflow-item squisq-toolbar-overflow-template", children: [_jsx("span", { children: "Template:" }), _jsx(TemplatePicker, { value: currentTemplate, onChange: (v) => {
|
|
957
|
+
handleTemplatePick(v);
|
|
491
958
|
setShowOverflow(false);
|
|
492
|
-
},
|
|
959
|
+
}, recommended: recommendedTemplates })] })), isInTable && (_jsxs(_Fragment, { children: [_jsx("div", { className: "squisq-toolbar-separator", style: { margin: '4px 0', width: '100%', height: 1 } }), [
|
|
493
960
|
{
|
|
494
961
|
label: 'Add column before',
|
|
495
962
|
action: () => tiptapEditor.chain().focus().addColumnBefore().run(),
|
|
@@ -521,6 +988,14 @@ export function Toolbar({ className, showFiles, onToggleFiles, slotLeft, slotAft
|
|
|
521
988
|
].map((item) => (_jsx("button", { className: `squisq-toolbar-overflow-item${item.label.startsWith('Delete') ? ' squisq-toolbar-overflow-item--danger' : ''}`, onClick: () => {
|
|
522
989
|
item.action();
|
|
523
990
|
setShowOverflow(false);
|
|
524
|
-
}, children: _jsx("span", { children: item.label }) }, item.label)))] }))] }))] })), slotAfterActions, (isPreview || isNarrow || isCodeMode) && _jsx("div", { style: { flex: 1 } }), onToggleFiles && (_jsx("button", { className: `squisq-toolbar-button squisq-toolbar-files-toggle${showFiles ? ' squisq-toolbar-button--active' : ''}`, onClick: onToggleFiles, "data-tooltip": showFiles ? 'Hide Files panel' : 'Show Files panel', "aria-pressed": showFiles, "aria-label": "Toggle Files panel", children:
|
|
991
|
+
}, children: _jsx("span", { children: item.label }) }, item.label)))] }))] }))] })), slotAfterActions, (isPreview || isNarrow || isCodeMode) && _jsx("div", { style: { flex: 1 } }), versioning && !isCodeMode && _jsx(VersionHistoryPanel, {}), allowRecording && !isCodeMode && mediaProvider && _jsx(RecorderEntry, {}), !isCodeMode && (_jsx("button", { type: "button", className: "squisq-toolbar-button", onClick: () => setShowDocSettings(true), "data-tooltip": "Document settings", "aria-label": "Document settings", children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M3 2.5h7l3 3v8a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1Z", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }), _jsx("path", { d: "M10 2.5v3h3", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }), _jsx("path", { d: "M5 8.5h6M5 11h4", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round" })] }) })), !isCodeMode && _jsx(ViewMenuPanel, {}), onToggleFiles && (_jsx("button", { className: `squisq-toolbar-button squisq-toolbar-files-toggle${showFiles ? ' squisq-toolbar-button--active' : ''}`, onClick: onToggleFiles, "data-tooltip": showFiles ? 'Hide Files panel' : 'Show Files panel', "aria-pressed": showFiles, "aria-label": "Toggle Files panel", children: PAPERCLIP_ICON })), slotRight, showDocSettings && (_jsx(DocumentSettingsDialog, { markdownSource: markdownSource, onSave: (next) => {
|
|
992
|
+
setMarkdownSource(next);
|
|
993
|
+
setShowDocSettings(false);
|
|
994
|
+
}, onClose: () => setShowDocSettings(false) })), linkDialog && (_jsx(LinkDialog, { mode: linkDialog.mode, initialText: linkDialog.initialText, initialUrl: linkDialog.initialUrl, onConfirm: handleLinkConfirm, onClose: () => setLinkDialog(null), documentLinkProvider: documentLinkProvider })), emojiPickerAnchor &&
|
|
995
|
+
createPortal(_jsx(EmojiPicker, { open: true, onSelect: handleEmojiSelect, onClose: closeEmojiPicker, anchorRef: emojiButtonRef, theme: theme === 'dark' ? 'dark' : 'light', style: {
|
|
996
|
+
position: 'fixed',
|
|
997
|
+
top: emojiPickerAnchor.top,
|
|
998
|
+
left: emojiPickerAnchor.left,
|
|
999
|
+
} }), document.body)] }));
|
|
525
1000
|
}
|
|
526
1001
|
//# sourceMappingURL=Toolbar.js.map
|