Skip to content

Commit d9deeb0

Browse files
committed
fix: Enable cross-editor drag and drop functionality(#6690)
- Add support for dragging content between multiple editor instances - Improve content preservation using serializeForClipboard - Add smart deletion control based on source editor's editable state - Update React, Vue 2, and Vue 3 drag handle components - Add comprehensive demos and tests for cross-editor functionality This enhancement allows users to seamlessly move content between different Tiptap editor instances, making it easier to build applications with multiple interconnected editors.
1 parent 96a34a0 commit d9deeb0

File tree

12 files changed

+850
-253
lines changed

12 files changed

+850
-253
lines changed

.changeset/friendly-crabs-stare.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@tiptap/extension-drag-handle': minor
3+
'tiptap-demos': patch
4+
'@tiptap/core': patch
5+
'@tiptap/extension-drag-handle-react': patch
6+
'@tiptap/extension-drag-handle-vue-2': patch
7+
'@tiptap/extension-drag-handle-vue-3': patch
8+
---
9+
10+
Enable cross-editor drag and drop functionality
11+
12+
- Add support for dragging content between multiple editor instances
13+
- Improve content preservation using serializeForClipboard
14+
- Add smart deletion control based on source editor's editable state
15+
- Update React, Vue 2, and Vue 3 drag handle components
16+
- Add comprehensive demos and tests for cross-editor functionality
17+
18+
This enhancement allows users to seamlessly move content between different Tiptap editor instances, making it easier to build applications with multiple interconnected editors.

demos/src/Experiments/GlobalDragHandle/React/index.html

Whitespace-only changes.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
context('/src/Experiments/GlobalDragHandle/React/', () => {
2+
beforeEach(() => {
3+
cy.visit('/src/Experiments/GlobalDragHandle/React/')
4+
})
5+
6+
it('should have working tiptap instances with correct configuration', () => {
7+
// Verify 2 editors exist
8+
cy.get('.tiptap').should('have.length', 2)
9+
cy.get('.editor-container').should('have.length', 2)
10+
11+
// Verify both editors are editable
12+
cy.get('.editor-container').first().should('not.have.class', 'readonly') // Editor 1: Editable
13+
cy.get('.editor-container').eq(1).should('not.have.class', 'readonly') // Editor 2: Editable
14+
})
15+
16+
it('should show drag handle on hover and have correct properties', () => {
17+
// Initially hidden
18+
cy.get('.drag-handle').should('not.be.visible')
19+
20+
// Show on hover for first editor
21+
cy.get('.editor-container').first().find('.tiptap h2').trigger('mousemove')
22+
cy.get('.drag-handle').should('be.visible')
23+
cy.get('.drag-handle').should('have.attr', 'draggable', 'true')
24+
25+
// Show on hover for second editor too
26+
cy.get('.editor-container').eq(1).find('.tiptap h2').trigger('mousemove')
27+
cy.get('.drag-handle').should('be.visible')
28+
})
29+
30+
it('should allow editing in both editors', () => {
31+
// Both editors allow editing
32+
cy.get('.editor-container').first().find('.tiptap').click().type('{movetoend}{enter}Works in Editor 1!')
33+
cy.get('.editor-container').first().find('.tiptap').should('contain', 'Works in Editor 1!')
34+
35+
cy.get('.editor-container').eq(1).find('.tiptap').click().type('{movetoend}{enter}Works in Editor 2!')
36+
cy.get('.editor-container').eq(1).find('.tiptap').should('contain', 'Works in Editor 2!')
37+
})
38+
39+
// Note: Actual drag-and-drop testing in Cypress is complex and unreliable
40+
// The core DnD functionality is tested at the unit level in the extension code
41+
// These UI tests focus on verifying the basic infrastructure is working
42+
})
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import './styles.scss'
2+
3+
import { DragHandle } from '@tiptap/extension-drag-handle'
4+
import { EditorContent, useEditor } from '@tiptap/react'
5+
import StarterKit from '@tiptap/starter-kit'
6+
import { useState } from 'react'
7+
8+
const Editor = ({
9+
content,
10+
onUpdate,
11+
editable = true,
12+
}: {
13+
content: string
14+
onUpdate: (content: string) => void
15+
editable?: boolean
16+
}) => {
17+
// The key insight: ProseMirror's cross-editor drag handling in PasteRule.ts
18+
// automatically checks dragFromOtherEditor?.isEditable to decide whether to delete
19+
// So we just need to use the standard DragHandle and set editable correctly!
20+
21+
const editor = useEditor({
22+
extensions: [StarterKit, DragHandle],
23+
content,
24+
editable,
25+
onUpdate: ({ editor: _editor }) => {
26+
onUpdate(_editor.getHTML())
27+
},
28+
})
29+
30+
return (
31+
<div className={`editor-container ${!editable ? 'readonly' : ''}`}>
32+
<h3>{`Editor: ${editor.instanceId}`}</h3>
33+
<div className="editor-wrapper">
34+
<EditorContent editor={editor} />
35+
</div>
36+
</div>
37+
)
38+
}
39+
40+
export default () => {
41+
const [content1, setContent1] = useState(`
42+
<h2>First Editor</h2>
43+
<p>This is the first editor. You can drag content between editors.</p>
44+
<ul>
45+
<li>List item 1</li>
46+
<li>List item 2</li>
47+
</ul>
48+
<blockquote>
49+
This is a blockquote that can be dragged.
50+
</blockquote>
51+
`)
52+
53+
const [content2, setContent2] = useState(`
54+
<h2>Second Editor</h2>
55+
<p>This is the second editor. Try dragging content from the first editor here.</p>
56+
<pre><code>console.log('Hello, world!')</code></pre>
57+
<ol>
58+
<li>Ordered list item 1</li>
59+
<li>Ordered list item 2</li>
60+
</ol>
61+
<p><strong>Bold text</strong> and <em>italic text</em> can also be dragged.</p>
62+
`)
63+
64+
// Editor configurations
65+
const editorConfigs = {
66+
editor1: { editable: true },
67+
editor2: { editable: true },
68+
}
69+
70+
return (
71+
<div className="global-drag-handle-demo">
72+
<h1>Global Drag Handle - React Example</h1>
73+
<p>
74+
This example demonstrates a global drag handle that works across multiple editors. Hover over content in any
75+
editor to see the drag handle appear on the left. You can drag content between different editors.
76+
</p>
77+
<p>
78+
<strong>Behavior:</strong> Dragging moves content from the source editor to the target editor.
79+
</p>
80+
81+
<div className="editors-container">
82+
<Editor content={content1} onUpdate={setContent1} editable={editorConfigs.editor1.editable} />
83+
<Editor content={content2} onUpdate={setContent2} editable={editorConfigs.editor2.editable} />
84+
</div>
85+
86+
<div className="debug-info">
87+
<h3>Debug Information</h3>
88+
<details>
89+
<summary>Editor 1 Content</summary>
90+
<pre>{content1}</pre>
91+
</details>
92+
<details>
93+
<summary>Editor 2 Content</summary>
94+
<pre>{content2}</pre>
95+
</details>
96+
</div>
97+
</div>
98+
)
99+
}

0 commit comments

Comments
 (0)