@tiptap/core 2.10.4 → 2.11.0
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/PasteRule.d.ts.map +1 -1
- package/dist/commands/focus.d.ts.map +1 -1
- package/dist/commands/selectAll.d.ts.map +1 -1
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/rewriteUnknownContent.d.ts +46 -0
- package/dist/helpers/rewriteUnknownContent.d.ts.map +1 -0
- package/dist/index.cjs +122 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +123 -27
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +122 -25
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/NodeView.ts +2 -2
- package/src/PasteRule.ts +29 -0
- package/src/commands/focus.ts +1 -6
- package/src/commands/selectAll.ts +10 -5
- package/src/helpers/index.ts +1 -0
- package/src/helpers/rewriteUnknownContent.ts +148 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/core",
|
|
3
3
|
"description": "headless rich text editor",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.11.0",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@tiptap/pm": "^2.
|
|
35
|
+
"@tiptap/pm": "^2.11.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@tiptap/pm": "^2.7.0"
|
package/src/NodeView.ts
CHANGED
|
@@ -151,11 +151,11 @@ export class NodeView<
|
|
|
151
151
|
// ProseMirror tries to drag selectable nodes
|
|
152
152
|
// even if `draggable` is set to `false`
|
|
153
153
|
// this fix prevents that
|
|
154
|
-
if (!isDraggable && isSelectable && isDragEvent) {
|
|
154
|
+
if (!isDraggable && isSelectable && isDragEvent && event.target === this.dom) {
|
|
155
155
|
event.preventDefault()
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
if (isDraggable && isDragEvent && !isDragging) {
|
|
158
|
+
if (isDraggable && isDragEvent && !isDragging && event.target === this.dom) {
|
|
159
159
|
event.preventDefault()
|
|
160
160
|
return false
|
|
161
161
|
}
|
package/src/PasteRule.ts
CHANGED
|
@@ -162,6 +162,9 @@ function run(config: {
|
|
|
162
162
|
return success
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// When dragging across editors, must get another editor instance to delete selection content.
|
|
166
|
+
let tiptapDragFromOtherEditor: Editor | null = null
|
|
167
|
+
|
|
165
168
|
const createClipboardPasteEvent = (text: string) => {
|
|
166
169
|
const event = new ClipboardEvent('paste', {
|
|
167
170
|
clipboardData: new DataTransfer(),
|
|
@@ -242,13 +245,25 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }):
|
|
|
242
245
|
dragSourceElement = view.dom.parentElement?.contains(event.target as Element)
|
|
243
246
|
? view.dom.parentElement
|
|
244
247
|
: null
|
|
248
|
+
|
|
249
|
+
if (dragSourceElement) {
|
|
250
|
+
tiptapDragFromOtherEditor = editor
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const handleDragend = () => {
|
|
255
|
+
if (tiptapDragFromOtherEditor) {
|
|
256
|
+
tiptapDragFromOtherEditor = null
|
|
257
|
+
}
|
|
245
258
|
}
|
|
246
259
|
|
|
247
260
|
window.addEventListener('dragstart', handleDragstart)
|
|
261
|
+
window.addEventListener('dragend', handleDragend)
|
|
248
262
|
|
|
249
263
|
return {
|
|
250
264
|
destroy() {
|
|
251
265
|
window.removeEventListener('dragstart', handleDragstart)
|
|
266
|
+
window.removeEventListener('dragend', handleDragend)
|
|
252
267
|
},
|
|
253
268
|
}
|
|
254
269
|
},
|
|
@@ -259,6 +274,20 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }):
|
|
|
259
274
|
isDroppedFromProseMirror = dragSourceElement === view.dom.parentElement
|
|
260
275
|
dropEvent = event as DragEvent
|
|
261
276
|
|
|
277
|
+
if (!isDroppedFromProseMirror) {
|
|
278
|
+
const dragFromOtherEditor = tiptapDragFromOtherEditor
|
|
279
|
+
|
|
280
|
+
if (dragFromOtherEditor) {
|
|
281
|
+
// setTimeout to avoid the wrong content after drop, timeout arg can't be empty or 0
|
|
282
|
+
setTimeout(() => {
|
|
283
|
+
const selection = dragFromOtherEditor.state.selection
|
|
284
|
+
|
|
285
|
+
if (selection) {
|
|
286
|
+
dragFromOtherEditor.commands.deleteRange({ from: selection.from, to: selection.to })
|
|
287
|
+
}
|
|
288
|
+
}, 10)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
262
291
|
return false
|
|
263
292
|
},
|
|
264
293
|
|
package/src/commands/focus.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { isTextSelection } from '../helpers/isTextSelection.js'
|
|
2
2
|
import { resolveFocusPosition } from '../helpers/resolveFocusPosition.js'
|
|
3
3
|
import { FocusPosition, RawCommands } from '../types.js'
|
|
4
|
-
import { isiOS } from '../utilities/isiOS.js'
|
|
5
4
|
|
|
6
5
|
declare module '@tiptap/core' {
|
|
7
6
|
interface Commands<ReturnType> {
|
|
@@ -43,11 +42,7 @@ export const focus: RawCommands['focus'] = (position = null, options = {}) => ({
|
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
const delayedFocus = () => {
|
|
46
|
-
|
|
47
|
-
// so we have to call this
|
|
48
|
-
if (isiOS()) {
|
|
49
|
-
(view.dom as HTMLElement).focus()
|
|
50
|
-
}
|
|
45
|
+
(view.dom as HTMLElement).focus()
|
|
51
46
|
|
|
52
47
|
// For React we have to focus asynchronously. Otherwise wild things happen.
|
|
53
48
|
// see: https://github.com/ueberdosis/tiptap/issues/1520
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AllSelection } from '@tiptap/pm/state'
|
|
2
|
+
|
|
1
3
|
import { RawCommands } from '../types.js'
|
|
2
4
|
|
|
3
5
|
declare module '@tiptap/core' {
|
|
@@ -12,9 +14,12 @@ declare module '@tiptap/core' {
|
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
export const selectAll: RawCommands['selectAll'] = () => ({ tr,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
export const selectAll: RawCommands['selectAll'] = () => ({ tr, dispatch }) => {
|
|
18
|
+
if (dispatch) {
|
|
19
|
+
const selection = new AllSelection(tr.doc)
|
|
20
|
+
|
|
21
|
+
tr.setSelection(selection)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true
|
|
20
25
|
}
|
package/src/helpers/index.ts
CHANGED
|
@@ -46,5 +46,6 @@ export * from './isNodeSelection.js'
|
|
|
46
46
|
export * from './isTextSelection.js'
|
|
47
47
|
export * from './posToDOMRect.js'
|
|
48
48
|
export * from './resolveFocusPosition.js'
|
|
49
|
+
export * from './rewriteUnknownContent.js'
|
|
49
50
|
export * from './selectionToInsertionEnd.js'
|
|
50
51
|
export * from './splitExtensions.js'
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { Schema } from '@tiptap/pm/model'
|
|
2
|
+
|
|
3
|
+
import type { JSONContent } from '../types.js'
|
|
4
|
+
|
|
5
|
+
type RewriteUnknownContentOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* If true, unknown nodes will be treated as paragraphs
|
|
8
|
+
* @default true
|
|
9
|
+
*/
|
|
10
|
+
fallbackToParagraph?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type RewrittenContent = {
|
|
14
|
+
/**
|
|
15
|
+
* The original JSON content that was rewritten
|
|
16
|
+
*/
|
|
17
|
+
original: JSONContent;
|
|
18
|
+
/**
|
|
19
|
+
* The name of the node or mark that was unsupported
|
|
20
|
+
*/
|
|
21
|
+
unsupported: string;
|
|
22
|
+
}[];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The actual implementation of the rewriteUnknownContent function
|
|
26
|
+
*/
|
|
27
|
+
function rewriteUnknownContentInner({
|
|
28
|
+
json,
|
|
29
|
+
validMarks,
|
|
30
|
+
validNodes,
|
|
31
|
+
options,
|
|
32
|
+
rewrittenContent = [],
|
|
33
|
+
}: {
|
|
34
|
+
json: JSONContent;
|
|
35
|
+
validMarks: Set<string>;
|
|
36
|
+
validNodes: Set<string>;
|
|
37
|
+
options?: RewriteUnknownContentOptions;
|
|
38
|
+
rewrittenContent?: RewrittenContent;
|
|
39
|
+
}): {
|
|
40
|
+
/**
|
|
41
|
+
* The cleaned JSON content
|
|
42
|
+
*/
|
|
43
|
+
json: JSONContent | null;
|
|
44
|
+
/**
|
|
45
|
+
* The array of nodes and marks that were rewritten
|
|
46
|
+
*/
|
|
47
|
+
rewrittenContent: RewrittenContent;
|
|
48
|
+
} {
|
|
49
|
+
if (json.marks && Array.isArray(json.marks)) {
|
|
50
|
+
json.marks = json.marks.filter(mark => {
|
|
51
|
+
const name = typeof mark === 'string' ? mark : mark.type
|
|
52
|
+
|
|
53
|
+
if (validMarks.has(name)) {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
rewrittenContent.push({
|
|
58
|
+
original: JSON.parse(JSON.stringify(mark)),
|
|
59
|
+
unsupported: name,
|
|
60
|
+
})
|
|
61
|
+
// Just ignore any unknown marks
|
|
62
|
+
return false
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (json.content && Array.isArray(json.content)) {
|
|
67
|
+
json.content = json.content
|
|
68
|
+
.map(
|
|
69
|
+
value => rewriteUnknownContentInner({
|
|
70
|
+
json: value,
|
|
71
|
+
validMarks,
|
|
72
|
+
validNodes,
|
|
73
|
+
options,
|
|
74
|
+
rewrittenContent,
|
|
75
|
+
}).json,
|
|
76
|
+
)
|
|
77
|
+
.filter(a => a !== null && a !== undefined)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (json.type && !validNodes.has(json.type)) {
|
|
81
|
+
rewrittenContent.push({
|
|
82
|
+
original: JSON.parse(JSON.stringify(json)),
|
|
83
|
+
unsupported: json.type,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if (json.content && Array.isArray(json.content) && (options?.fallbackToParagraph !== false)) {
|
|
87
|
+
// Just treat it like a paragraph and hope for the best
|
|
88
|
+
json.type = 'paragraph'
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
json,
|
|
92
|
+
rewrittenContent,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// or just omit it entirely
|
|
97
|
+
return {
|
|
98
|
+
json: null,
|
|
99
|
+
rewrittenContent,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { json, rewrittenContent }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Rewrite unknown nodes and marks within JSON content
|
|
108
|
+
* Allowing for user within the editor
|
|
109
|
+
*/
|
|
110
|
+
export function rewriteUnknownContent(
|
|
111
|
+
/**
|
|
112
|
+
* The JSON content to clean of unknown nodes and marks
|
|
113
|
+
*/
|
|
114
|
+
json: JSONContent,
|
|
115
|
+
/**
|
|
116
|
+
* The schema to use for validation
|
|
117
|
+
*/
|
|
118
|
+
schema: Schema,
|
|
119
|
+
/**
|
|
120
|
+
* Options for the cleaning process
|
|
121
|
+
*/
|
|
122
|
+
options?: RewriteUnknownContentOptions,
|
|
123
|
+
): {
|
|
124
|
+
/**
|
|
125
|
+
* The cleaned JSON content
|
|
126
|
+
*/
|
|
127
|
+
json: JSONContent | null;
|
|
128
|
+
/**
|
|
129
|
+
* The array of nodes and marks that were rewritten
|
|
130
|
+
*/
|
|
131
|
+
rewrittenContent: {
|
|
132
|
+
/**
|
|
133
|
+
* The original JSON content that was rewritten
|
|
134
|
+
*/
|
|
135
|
+
original: JSONContent;
|
|
136
|
+
/**
|
|
137
|
+
* The name of the node or mark that was unsupported
|
|
138
|
+
*/
|
|
139
|
+
unsupported: string;
|
|
140
|
+
}[];
|
|
141
|
+
} {
|
|
142
|
+
return rewriteUnknownContentInner({
|
|
143
|
+
json,
|
|
144
|
+
validNodes: new Set(Object.keys(schema.nodes)),
|
|
145
|
+
validMarks: new Set(Object.keys(schema.marks)),
|
|
146
|
+
options,
|
|
147
|
+
})
|
|
148
|
+
}
|