@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.
@@ -1,2 +1,8 @@
1
1
  import { Node as ProseMirrorNode } from '@tiptap/pm/model';
2
- export declare function isNodeEmpty(node: ProseMirrorNode): boolean;
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.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.5"
35
+ "@tiptap/pm": "^2.5.7"
36
36
  },
37
37
  "peerDependencies": {
38
- "@tiptap/pm": "^2.5.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
- if (dispatch) {
65
- const atEnd = $to.parentOffset === $to.parent.content.size
66
-
67
- if (selection instanceof TextSelection) {
68
- tr.deleteSelection()
69
- }
70
-
71
- const deflt = $from.depth === 0
72
- ? undefined
73
- : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)))
74
-
75
- let types = atEnd && deflt
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
- let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types)
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 true
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
- // Only ever check for invalid content if we're supposed to throw an error
62
+ // Check for invalid content
66
63
  if (options.errorOnInvalidContent) {
67
- schemaToUse = new Schema({
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
- const parser = DOMParser.fromSchema(schemaToUse)
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
- const response = options.slice
96
- ? parser.parseSlice(elementFromString(content), options.parseOptions).content
97
- : parser.parse(elementFromString(content), options.parseOptions)
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.errorOnInvalidContent && hasInvalidContent) {
100
- throw new Error('[tiptap error]: Invalid HTML content', { cause: new Error(`Invalid element found: ${invalidContent}`) })
106
+ if (options.slice) {
107
+ return parser.parseSlice(elementFromString(content), options.parseOptions).content
101
108
  }
102
109
 
103
- return response
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
- export function isNodeEmpty(node: ProseMirrorNode): boolean {
4
- const defaultContent = node.type.createAndFill()
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 (!defaultContent) {
19
+ if (node.isLeaf) {
7
20
  return false
8
21
  }
9
22
 
10
- return node.eq(defaultContent)
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
  }