@tiptap/core 2.5.5 → 2.5.7
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/index.cjs +79 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +79 -40
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +79 -40
- package/dist/index.umd.js.map +1 -1
- package/dist/packages/core/src/helpers/isNodeEmpty.d.ts +7 -1
- package/dist/packages/core/src/types.d.ts +1 -3
- package/package.json +3 -3
- package/src/Editor.ts +6 -0
- package/src/commands/insertContentAt.ts +7 -0
- package/src/commands/splitBlock.ts +31 -31
- package/src/helpers/createNodeFromContent.ts +21 -13
- package/src/helpers/isNodeEmpty.ts +34 -4
- package/src/types.ts +157 -156
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { Node as ProseMirrorNode } from '@tiptap/pm/model';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Returns true if the given node is empty.
|
|
4
|
+
* When `checkChildren` is true (default), it will also check if all children are empty.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isNodeEmpty(node: ProseMirrorNode, { checkChildren }?: {
|
|
7
|
+
checkChildren: boolean;
|
|
8
|
+
}): boolean;
|
|
@@ -154,9 +154,7 @@ export type GlobalAttributes = {
|
|
|
154
154
|
/**
|
|
155
155
|
* The attributes to add to the node or mark types.
|
|
156
156
|
*/
|
|
157
|
-
attributes:
|
|
158
|
-
[key: string]: Attribute;
|
|
159
|
-
};
|
|
157
|
+
attributes: Record<string, Attribute | undefined>;
|
|
160
158
|
}[];
|
|
161
159
|
export type PickValue<T, K extends keyof T> = T[K];
|
|
162
160
|
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
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.5.
|
|
4
|
+
"version": "2.5.7",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@tiptap/pm": "^2.5.
|
|
35
|
+
"@tiptap/pm": "^2.5.7"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@tiptap/pm": "^2.5.
|
|
38
|
+
"@tiptap/pm": "^2.5.7"
|
|
39
39
|
},
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
package/src/Editor.ts
CHANGED
|
@@ -39,6 +39,7 @@ import { isFunction } from './utilities/isFunction.js'
|
|
|
39
39
|
|
|
40
40
|
export * as extensions from './extensions/index.js'
|
|
41
41
|
|
|
42
|
+
// @ts-ignore
|
|
42
43
|
export interface TiptapEditorHTMLElement extends HTMLElement {
|
|
43
44
|
editor?: Editor
|
|
44
45
|
}
|
|
@@ -340,6 +341,7 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
340
341
|
|
|
341
342
|
// Let’s store the editor instance in the DOM element.
|
|
342
343
|
// So we’ll have access to it for tests.
|
|
344
|
+
// @ts-ignore
|
|
343
345
|
const dom = this.view.dom as TiptapEditorHTMLElement
|
|
344
346
|
|
|
345
347
|
dom.editor = this
|
|
@@ -349,6 +351,10 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
349
351
|
* Creates all node views.
|
|
350
352
|
*/
|
|
351
353
|
public createNodeViews(): void {
|
|
354
|
+
if (this.view.isDestroyed) {
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
352
358
|
this.view.setProps({
|
|
353
359
|
nodeViews: this.extensionManager.nodeViews,
|
|
354
360
|
})
|
|
@@ -81,6 +81,13 @@ export const insertContentAt: RawCommands['insertContentAt'] = (position, value,
|
|
|
81
81
|
errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
|
|
82
82
|
})
|
|
83
83
|
} catch (e) {
|
|
84
|
+
editor.emit('contentError', {
|
|
85
|
+
editor,
|
|
86
|
+
error: e as Error,
|
|
87
|
+
disableCollaboration: () => {
|
|
88
|
+
console.error('[tiptap error]: Unable to disable collaboration at this point in time')
|
|
89
|
+
},
|
|
90
|
+
})
|
|
84
91
|
return false
|
|
85
92
|
}
|
|
86
93
|
|
|
@@ -61,18 +61,30 @@ export const splitBlock: RawCommands['splitBlock'] = ({ keepMarks = true } = {})
|
|
|
61
61
|
return false
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
64
|
+
const atEnd = $to.parentOffset === $to.parent.content.size
|
|
65
|
+
|
|
66
|
+
const deflt = $from.depth === 0
|
|
67
|
+
? undefined
|
|
68
|
+
: defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)))
|
|
69
|
+
|
|
70
|
+
let types = atEnd && deflt
|
|
71
|
+
? [
|
|
72
|
+
{
|
|
73
|
+
type: deflt,
|
|
74
|
+
attrs: newAttributes,
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
: undefined
|
|
78
|
+
|
|
79
|
+
let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types)
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
!types
|
|
83
|
+
&& !can
|
|
84
|
+
&& canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : undefined)
|
|
85
|
+
) {
|
|
86
|
+
can = true
|
|
87
|
+
types = deflt
|
|
76
88
|
? [
|
|
77
89
|
{
|
|
78
90
|
type: deflt,
|
|
@@ -80,26 +92,14 @@ export const splitBlock: RawCommands['splitBlock'] = ({ keepMarks = true } = {})
|
|
|
80
92
|
},
|
|
81
93
|
]
|
|
82
94
|
: undefined
|
|
95
|
+
}
|
|
83
96
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
!types
|
|
88
|
-
&& !can
|
|
89
|
-
&& canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : undefined)
|
|
90
|
-
) {
|
|
91
|
-
can = true
|
|
92
|
-
types = deflt
|
|
93
|
-
? [
|
|
94
|
-
{
|
|
95
|
-
type: deflt,
|
|
96
|
-
attrs: newAttributes,
|
|
97
|
-
},
|
|
98
|
-
]
|
|
99
|
-
: undefined
|
|
100
|
-
}
|
|
101
|
-
|
|
97
|
+
if (dispatch) {
|
|
102
98
|
if (can) {
|
|
99
|
+
if (selection instanceof TextSelection) {
|
|
100
|
+
tr.deleteSelection()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
103
|
tr.split(tr.mapping.map($from.pos), 1, types)
|
|
104
104
|
|
|
105
105
|
if (deflt && !atEnd && !$from.parentOffset && $from.parent.type !== deflt) {
|
|
@@ -119,5 +119,5 @@ export const splitBlock: RawCommands['splitBlock'] = ({ keepMarks = true } = {})
|
|
|
119
119
|
tr.scrollIntoView()
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
return
|
|
122
|
+
return can
|
|
123
123
|
}
|
|
@@ -58,13 +58,14 @@ export function createNodeFromContent(
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
if (isTextContent) {
|
|
61
|
-
let schemaToUse = schema
|
|
62
|
-
let hasInvalidContent = false
|
|
63
|
-
let invalidContent = ''
|
|
64
61
|
|
|
65
|
-
//
|
|
62
|
+
// Check for invalid content
|
|
66
63
|
if (options.errorOnInvalidContent) {
|
|
67
|
-
|
|
64
|
+
let hasInvalidContent = false
|
|
65
|
+
let invalidContent = ''
|
|
66
|
+
|
|
67
|
+
// A copy of the current schema with a catch-all node at the end
|
|
68
|
+
const contentCheckSchema = new Schema({
|
|
68
69
|
topNode: schema.spec.topNode,
|
|
69
70
|
marks: schema.spec.marks,
|
|
70
71
|
// Prosemirror's schemas are executed such that: the last to execute, matches last
|
|
@@ -88,19 +89,26 @@ export function createNodeFromContent(
|
|
|
88
89
|
},
|
|
89
90
|
}),
|
|
90
91
|
})
|
|
91
|
-
}
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
if (options.slice) {
|
|
94
|
+
DOMParser.fromSchema(contentCheckSchema).parseSlice(elementFromString(content), options.parseOptions)
|
|
95
|
+
} else {
|
|
96
|
+
DOMParser.fromSchema(contentCheckSchema).parse(elementFromString(content), options.parseOptions)
|
|
97
|
+
}
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
if (options.errorOnInvalidContent && hasInvalidContent) {
|
|
100
|
+
throw new Error('[tiptap error]: Invalid HTML content', { cause: new Error(`Invalid element found: ${invalidContent}`) })
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const parser = DOMParser.fromSchema(schema)
|
|
98
105
|
|
|
99
|
-
if (options.
|
|
100
|
-
|
|
106
|
+
if (options.slice) {
|
|
107
|
+
return parser.parseSlice(elementFromString(content), options.parseOptions).content
|
|
101
108
|
}
|
|
102
109
|
|
|
103
|
-
return
|
|
110
|
+
return parser.parse(elementFromString(content), options.parseOptions)
|
|
111
|
+
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
return createNodeFromContent('', schema, options)
|
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Returns true if the given node is empty.
|
|
5
|
+
* When `checkChildren` is true (default), it will also check if all children are empty.
|
|
6
|
+
*/
|
|
7
|
+
export function isNodeEmpty(
|
|
8
|
+
node: ProseMirrorNode,
|
|
9
|
+
{ checkChildren }: { checkChildren: boolean } = { checkChildren: true },
|
|
10
|
+
): boolean {
|
|
11
|
+
if (node.isText) {
|
|
12
|
+
return !node.text
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (node.content.childCount === 0) {
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
5
18
|
|
|
6
|
-
if (
|
|
19
|
+
if (node.isLeaf) {
|
|
7
20
|
return false
|
|
8
21
|
}
|
|
9
22
|
|
|
10
|
-
|
|
23
|
+
if (checkChildren) {
|
|
24
|
+
let hasSameContent = true
|
|
25
|
+
|
|
26
|
+
node.content.forEach(childNode => {
|
|
27
|
+
if (hasSameContent === false) {
|
|
28
|
+
// Exit early for perf
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!isNodeEmpty(childNode)) {
|
|
33
|
+
hasSameContent = false
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return hasSameContent
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return false
|
|
11
41
|
}
|