From ca6269e9282aa189fc9b6cec70c531a0862407cd Mon Sep 17 00:00:00 2001 From: bdbch <6538827+bdbch@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:16:45 +0100 Subject: [PATCH] feat: allow support for drag-and-drop between multiple editors (#5893) * feat: drag and drop across multi editors * feat: drag and drop across multi editors optimize * feat: drag and drop across multi editors optimize * added changeset --------- Co-authored-by: songhandong Co-authored-by: songispm <38745323+songispm@users.noreply.github.com> Co-authored-by: songispm --- .changeset/many-glasses-reflect.md | 5 ++++ .../GlobalDragHandle/Vue/DragHandle.js | 12 ++++++-- .../Experiments/MultipleEditors/Vue/index.vue | 6 ++++ packages/core/src/PasteRule.ts | 29 +++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 .changeset/many-glasses-reflect.md diff --git a/.changeset/many-glasses-reflect.md b/.changeset/many-glasses-reflect.md new file mode 100644 index 000000000..a96263df3 --- /dev/null +++ b/.changeset/many-glasses-reflect.md @@ -0,0 +1,5 @@ +--- +"@tiptap/core": patch +--- + +Added support for drag-and-drop between multiple editors diff --git a/demos/src/Experiments/GlobalDragHandle/Vue/DragHandle.js b/demos/src/Experiments/GlobalDragHandle/Vue/DragHandle.js index 2499174a9..c88050d4c 100644 --- a/demos/src/Experiments/GlobalDragHandle/Vue/DragHandle.js +++ b/demos/src/Experiments/GlobalDragHandle/Vue/DragHandle.js @@ -44,7 +44,8 @@ export default Extension.create({ } function dragStart(e, view) { - view.composing = true + // Must delete this line, Otherwise: Uncaught TypeError: Cannot set property composing of # which has only a getter + // view.composing = true if (!e.dataTransfer) { return @@ -75,6 +76,11 @@ export default Extension.create({ } } + function dragEnd(e, view) { + // reset the dragging, otherwise wrong content after dragging across multi editors repeatedly + view.dragging = null + } + let dropElement const WIDTH = 28 @@ -86,8 +92,10 @@ export default Extension.create({ element.draggable = 'true' element.classList.add('global-drag-handle') element.addEventListener('dragstart', e => dragStart(e, editorView)) + element.addEventListener('dragend', e => dragEnd(e, editorView)) dropElement = element - document.body.appendChild(dropElement) + // append to editor's parentNode (not document.body), to match the logic of dragging across multi editors in pasteRule.ts + editorView.dom.parentNode.appendChild(dropElement) return { // update(view, prevState) { diff --git a/demos/src/Experiments/MultipleEditors/Vue/index.vue b/demos/src/Experiments/MultipleEditors/Vue/index.vue index 7c6b6fbe7..82d64bd72 100644 --- a/demos/src/Experiments/MultipleEditors/Vue/index.vue +++ b/demos/src/Experiments/MultipleEditors/Vue/index.vue @@ -32,6 +32,7 @@ import Bold from '@tiptap/extension-bold' import Collaboration from '@tiptap/extension-collaboration' import Document from '@tiptap/extension-document' +import DropCursor from '@tiptap/extension-dropcursor' import Heading from '@tiptap/extension-heading' import Paragraph from '@tiptap/extension-paragraph' import TaskItem from '@tiptap/extension-task-item' @@ -75,6 +76,8 @@ export default { levels: [2], }), Text, + Bold, + DropCursor, Collaboration.configure({ document: this.ydoc, field: 'title', @@ -88,6 +91,8 @@ export default { TaskListDocument, Paragraph, Text, + Bold, + DropCursor, TaskList, CustomTaskItem, Collaboration.configure({ @@ -110,6 +115,7 @@ export default { Paragraph, Text, Bold, + DropCursor, Collaboration.configure({ document: this.ydoc, field: 'description', diff --git a/packages/core/src/PasteRule.ts b/packages/core/src/PasteRule.ts index 9224841c1..faa39bbae 100644 --- a/packages/core/src/PasteRule.ts +++ b/packages/core/src/PasteRule.ts @@ -162,6 +162,9 @@ function run(config: { return success } +// When dragging across editors, must get another editor instance to delete selection content. +let tiptapDragFromOtherEditor: Editor | null = null + const createClipboardPasteEvent = (text: string) => { const event = new ClipboardEvent('paste', { clipboardData: new DataTransfer(), @@ -242,13 +245,25 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }): dragSourceElement = view.dom.parentElement?.contains(event.target as Element) ? view.dom.parentElement : null + + if (dragSourceElement) { + tiptapDragFromOtherEditor = editor + } + } + + const handleDragend = () => { + if (tiptapDragFromOtherEditor) { + tiptapDragFromOtherEditor = null + } } window.addEventListener('dragstart', handleDragstart) + window.addEventListener('dragend', handleDragend) return { destroy() { window.removeEventListener('dragstart', handleDragstart) + window.removeEventListener('dragend', handleDragend) }, } }, @@ -259,6 +274,20 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }): isDroppedFromProseMirror = dragSourceElement === view.dom.parentElement dropEvent = event as DragEvent + if (!isDroppedFromProseMirror) { + const dragFromOtherEditor = tiptapDragFromOtherEditor + + if (dragFromOtherEditor) { + // setTimeout to avoid the wrong content after drop, timeout arg can't be empty or 0 + setTimeout(() => { + const selection = dragFromOtherEditor.state.selection + + if (selection) { + dragFromOtherEditor.commands.deleteRange({ from: selection.from, to: selection.to }) + } + }, 10) + } + } return false },