@tiptap/core 3.0.0-next.2 → 3.0.0-next.4

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.
Files changed (128) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +5 -1
  3. package/dist/index.cjs +2402 -2540
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +1355 -1273
  6. package/dist/index.d.ts +1355 -1273
  7. package/dist/index.js +2408 -2562
  8. package/dist/index.js.map +1 -1
  9. package/package.json +9 -5
  10. package/src/CommandManager.ts +2 -9
  11. package/src/Editor.ts +87 -72
  12. package/src/EventEmitter.ts +7 -10
  13. package/src/Extension.ts +8 -14
  14. package/src/ExtensionManager.ts +26 -125
  15. package/src/InputRule.ts +35 -48
  16. package/src/Mark.ts +9 -9
  17. package/src/Node.ts +9 -9
  18. package/src/NodePos.ts +1 -3
  19. package/src/NodeView.ts +10 -20
  20. package/src/PasteRule.ts +43 -55
  21. package/src/Tracker.ts +7 -9
  22. package/src/commands/blur.ts +14 -12
  23. package/src/commands/clearContent.ts +6 -4
  24. package/src/commands/clearNodes.ts +32 -30
  25. package/src/commands/command.ts +1 -1
  26. package/src/commands/createParagraphNear.ts +5 -3
  27. package/src/commands/cut.ts +12 -10
  28. package/src/commands/deleteCurrentNode.ts +23 -21
  29. package/src/commands/deleteNode.ts +18 -16
  30. package/src/commands/deleteRange.ts +10 -8
  31. package/src/commands/deleteSelection.ts +5 -3
  32. package/src/commands/enter.ts +6 -4
  33. package/src/commands/exitCode.ts +5 -3
  34. package/src/commands/extendMarkRange.ts +14 -12
  35. package/src/commands/first.ts +2 -4
  36. package/src/commands/focus.ts +45 -48
  37. package/src/commands/forEach.ts +2 -2
  38. package/src/commands/insertContent.ts +12 -14
  39. package/src/commands/insertContentAt.ts +101 -98
  40. package/src/commands/join.ts +20 -12
  41. package/src/commands/joinItemBackward.ts +16 -18
  42. package/src/commands/joinItemForward.ts +16 -18
  43. package/src/commands/joinTextblockBackward.ts +5 -3
  44. package/src/commands/joinTextblockForward.ts +5 -3
  45. package/src/commands/keyboardShortcut.ts +29 -34
  46. package/src/commands/lift.ts +10 -8
  47. package/src/commands/liftEmptyBlock.ts +6 -4
  48. package/src/commands/liftListItem.ts +6 -4
  49. package/src/commands/newlineInCode.ts +5 -3
  50. package/src/commands/resetAttributes.ts +36 -41
  51. package/src/commands/scrollIntoView.ts +9 -7
  52. package/src/commands/selectAll.ts +10 -8
  53. package/src/commands/selectNodeBackward.ts +5 -3
  54. package/src/commands/selectNodeForward.ts +5 -3
  55. package/src/commands/selectParentNode.ts +5 -3
  56. package/src/commands/selectTextblockEnd.ts +5 -3
  57. package/src/commands/selectTextblockStart.ts +5 -3
  58. package/src/commands/setContent.ts +25 -25
  59. package/src/commands/setMark.ts +55 -57
  60. package/src/commands/setMeta.ts +7 -5
  61. package/src/commands/setNode.ts +32 -30
  62. package/src/commands/setNodeSelection.ts +11 -9
  63. package/src/commands/setTextSelection.ts +15 -13
  64. package/src/commands/sinkListItem.ts +6 -4
  65. package/src/commands/splitBlock.ts +67 -76
  66. package/src/commands/splitListItem.ts +93 -106
  67. package/src/commands/toggleList.ts +73 -71
  68. package/src/commands/toggleMark.ts +11 -9
  69. package/src/commands/toggleNode.ts +18 -16
  70. package/src/commands/toggleWrap.ts +10 -8
  71. package/src/commands/undoInputRule.ts +31 -29
  72. package/src/commands/unsetAllMarks.ts +16 -14
  73. package/src/commands/unsetMark.ts +27 -25
  74. package/src/commands/updateAttributes.ts +92 -100
  75. package/src/commands/wrapIn.ts +6 -4
  76. package/src/commands/wrapInList.ts +6 -4
  77. package/src/extensions/clipboardTextSerializer.ts +2 -4
  78. package/src/extensions/focusEvents.ts +2 -6
  79. package/src/extensions/keymap.ts +54 -50
  80. package/src/extensions/paste.ts +0 -1
  81. package/src/extensions/tabindex.ts +1 -1
  82. package/src/helpers/combineTransactionSteps.ts +1 -4
  83. package/src/helpers/createChainableState.ts +1 -4
  84. package/src/helpers/createDocument.ts +1 -3
  85. package/src/helpers/createNodeFromContent.ts +4 -10
  86. package/src/helpers/findChildrenInRange.ts +1 -5
  87. package/src/helpers/findParentNode.ts +3 -1
  88. package/src/helpers/flattenExtensions.ts +30 -0
  89. package/src/helpers/getAttributes.ts +1 -4
  90. package/src/helpers/getAttributesFromExtensions.ts +28 -37
  91. package/src/helpers/getChangedRanges.ts +13 -11
  92. package/src/helpers/getExtensionField.ts +1 -4
  93. package/src/helpers/getMarkAttributes.ts +1 -4
  94. package/src/helpers/getMarkRange.ts +5 -15
  95. package/src/helpers/getMarkType.ts +1 -3
  96. package/src/helpers/getNodeAttributes.ts +1 -4
  97. package/src/helpers/getNodeType.ts +1 -3
  98. package/src/helpers/getRenderedAttributes.ts +1 -3
  99. package/src/helpers/getSchema.ts +2 -2
  100. package/src/helpers/getSchemaByResolvedExtensions.ts +45 -77
  101. package/src/helpers/getSplittedAttributes.ts +4 -4
  102. package/src/helpers/getTextContentFromNodes.ts +8 -11
  103. package/src/helpers/index.ts +4 -0
  104. package/src/helpers/injectExtensionAttributesToParseRule.ts +1 -1
  105. package/src/helpers/isActive.ts +1 -5
  106. package/src/helpers/isExtensionRulesEnabled.ts +1 -3
  107. package/src/helpers/isNodeEmpty.ts +2 -2
  108. package/src/helpers/resolveExtensions.ts +25 -0
  109. package/src/helpers/resolveFocusPosition.ts +3 -14
  110. package/src/helpers/rewriteUnknownContent.ts +149 -0
  111. package/src/helpers/sortExtensions.ts +26 -0
  112. package/src/inputRules/markInputRule.ts +1 -5
  113. package/src/inputRules/nodeInputRule.ts +2 -9
  114. package/src/inputRules/textInputRule.ts +1 -4
  115. package/src/inputRules/textblockTypeInputRule.ts +2 -8
  116. package/src/inputRules/wrappingInputRule.ts +13 -19
  117. package/src/pasteRules/markPasteRule.ts +1 -3
  118. package/src/pasteRules/nodePasteRule.ts +2 -8
  119. package/src/pasteRules/textPasteRule.ts +1 -4
  120. package/src/types.ts +212 -172
  121. package/src/utilities/createStyleTag.ts +3 -1
  122. package/src/utilities/deleteProps.ts +7 -11
  123. package/src/utilities/findDuplicates.ts +4 -1
  124. package/src/utilities/isFunction.ts +1 -0
  125. package/src/utilities/isMacOS.ts +1 -3
  126. package/src/utilities/isiOS.ts +5 -10
  127. package/src/utilities/mergeAttributes.ts +16 -6
  128. package/src/utilities/removeDuplicates.ts +1 -3
@@ -7,36 +7,38 @@ declare module '@tiptap/core' {
7
7
  * Delete the node that currently has the selection anchor.
8
8
  * @example editor.commands.deleteCurrentNode()
9
9
  */
10
- deleteCurrentNode: () => ReturnType,
10
+ deleteCurrentNode: () => ReturnType
11
11
  }
12
12
  }
13
13
  }
14
14
 
15
- export const deleteCurrentNode: RawCommands['deleteCurrentNode'] = () => ({ tr, dispatch }) => {
16
- const { selection } = tr
17
- const currentNode = selection.$anchor.node()
15
+ export const deleteCurrentNode: RawCommands['deleteCurrentNode'] =
16
+ () =>
17
+ ({ tr, dispatch }) => {
18
+ const { selection } = tr
19
+ const currentNode = selection.$anchor.node()
18
20
 
19
- // if there is content inside the current node, break out of this command
20
- if (currentNode.content.size > 0) {
21
- return false
22
- }
21
+ // if there is content inside the current node, break out of this command
22
+ if (currentNode.content.size > 0) {
23
+ return false
24
+ }
23
25
 
24
- const $pos = tr.selection.$anchor
26
+ const $pos = tr.selection.$anchor
25
27
 
26
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
27
- const node = $pos.node(depth)
28
+ for (let depth = $pos.depth; depth > 0; depth -= 1) {
29
+ const node = $pos.node(depth)
28
30
 
29
- if (node.type === currentNode.type) {
30
- if (dispatch) {
31
- const from = $pos.before(depth)
32
- const to = $pos.after(depth)
31
+ if (node.type === currentNode.type) {
32
+ if (dispatch) {
33
+ const from = $pos.before(depth)
34
+ const to = $pos.after(depth)
33
35
 
34
- tr.delete(from, to).scrollIntoView()
35
- }
36
+ tr.delete(from, to).scrollIntoView()
37
+ }
36
38
 
37
- return true
39
+ return true
40
+ }
38
41
  }
39
- }
40
42
 
41
- return false
42
- }
43
+ return false
44
+ }
@@ -11,29 +11,31 @@ declare module '@tiptap/core' {
11
11
  * @param typeOrName The type or name of the node.
12
12
  * @example editor.commands.deleteNode('paragraph')
13
13
  */
14
- deleteNode: (typeOrName: string | NodeType) => ReturnType,
14
+ deleteNode: (typeOrName: string | NodeType) => ReturnType
15
15
  }
16
16
  }
17
17
  }
18
18
 
19
- export const deleteNode: RawCommands['deleteNode'] = typeOrName => ({ tr, state, dispatch }) => {
20
- const type = getNodeType(typeOrName, state.schema)
21
- const $pos = tr.selection.$anchor
19
+ export const deleteNode: RawCommands['deleteNode'] =
20
+ typeOrName =>
21
+ ({ tr, state, dispatch }) => {
22
+ const type = getNodeType(typeOrName, state.schema)
23
+ const $pos = tr.selection.$anchor
22
24
 
23
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
24
- const node = $pos.node(depth)
25
+ for (let depth = $pos.depth; depth > 0; depth -= 1) {
26
+ const node = $pos.node(depth)
25
27
 
26
- if (node.type === type) {
27
- if (dispatch) {
28
- const from = $pos.before(depth)
29
- const to = $pos.after(depth)
28
+ if (node.type === type) {
29
+ if (dispatch) {
30
+ const from = $pos.before(depth)
31
+ const to = $pos.after(depth)
30
32
 
31
- tr.delete(from, to).scrollIntoView()
32
- }
33
+ tr.delete(from, to).scrollIntoView()
34
+ }
33
35
 
34
- return true
36
+ return true
37
+ }
35
38
  }
36
- }
37
39
 
38
- return false
39
- }
40
+ return false
41
+ }
@@ -8,17 +8,19 @@ declare module '@tiptap/core' {
8
8
  * @param range The range to delete.
9
9
  * @example editor.commands.deleteRange({ from: 1, to: 3 })
10
10
  */
11
- deleteRange: (range: Range) => ReturnType,
11
+ deleteRange: (range: Range) => ReturnType
12
12
  }
13
13
  }
14
14
  }
15
15
 
16
- export const deleteRange: RawCommands['deleteRange'] = range => ({ tr, dispatch }) => {
17
- const { from, to } = range
16
+ export const deleteRange: RawCommands['deleteRange'] =
17
+ range =>
18
+ ({ tr, dispatch }) => {
19
+ const { from, to } = range
18
20
 
19
- if (dispatch) {
20
- tr.delete(from, to)
21
- }
21
+ if (dispatch) {
22
+ tr.delete(from, to)
23
+ }
22
24
 
23
- return true
24
- }
25
+ return true
26
+ }
@@ -14,6 +14,8 @@ declare module '@tiptap/core' {
14
14
  }
15
15
  }
16
16
 
17
- export const deleteSelection: RawCommands['deleteSelection'] = () => ({ state, dispatch }) => {
18
- return originalDeleteSelection(state, dispatch)
19
- }
17
+ export const deleteSelection: RawCommands['deleteSelection'] =
18
+ () =>
19
+ ({ state, dispatch }) => {
20
+ return originalDeleteSelection(state, dispatch)
21
+ }
@@ -7,11 +7,13 @@ declare module '@tiptap/core' {
7
7
  * Trigger enter.
8
8
  * @example editor.commands.enter()
9
9
  */
10
- enter: () => ReturnType,
10
+ enter: () => ReturnType
11
11
  }
12
12
  }
13
13
  }
14
14
 
15
- export const enter: RawCommands['enter'] = () => ({ commands }) => {
16
- return commands.keyboardShortcut('Enter')
17
- }
15
+ export const enter: RawCommands['enter'] =
16
+ () =>
17
+ ({ commands }) => {
18
+ return commands.keyboardShortcut('Enter')
19
+ }
@@ -14,6 +14,8 @@ declare module '@tiptap/core' {
14
14
  }
15
15
  }
16
16
 
17
- export const exitCode: RawCommands['exitCode'] = () => ({ state, dispatch }) => {
18
- return originalExitCode(state, dispatch)
19
- }
17
+ export const exitCode: RawCommands['exitCode'] =
18
+ () =>
19
+ ({ state, dispatch }) => {
20
+ return originalExitCode(state, dispatch)
21
+ }
@@ -30,20 +30,22 @@ declare module '@tiptap/core' {
30
30
  }
31
31
  }
32
32
 
33
- export const extendMarkRange: RawCommands['extendMarkRange'] = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => {
34
- const type = getMarkType(typeOrName, state.schema)
35
- const { doc, selection } = tr
36
- const { $from, from, to } = selection
33
+ export const extendMarkRange: RawCommands['extendMarkRange'] =
34
+ (typeOrName, attributes = {}) =>
35
+ ({ tr, state, dispatch }) => {
36
+ const type = getMarkType(typeOrName, state.schema)
37
+ const { doc, selection } = tr
38
+ const { $from, from, to } = selection
37
39
 
38
- if (dispatch) {
39
- const range = getMarkRange($from, type, attributes)
40
+ if (dispatch) {
41
+ const range = getMarkRange($from, type, attributes)
40
42
 
41
- if (range && range.from <= from && range.to >= to) {
42
- const newSelection = TextSelection.create(doc, range.from, range.to)
43
+ if (range && range.from <= from && range.to >= to) {
44
+ const newSelection = TextSelection.create(doc, range.from, range.to)
43
45
 
44
- tr.setSelection(newSelection)
46
+ tr.setSelection(newSelection)
47
+ }
45
48
  }
46
- }
47
49
 
48
- return true
49
- }
50
+ return true
51
+ }
@@ -8,15 +8,13 @@ declare module '@tiptap/core' {
8
8
  * @param commands The commands to run.
9
9
  * @example editor.commands.first([command1, command2])
10
10
  */
11
- first: (commands: Command[] | ((props: CommandProps) => Command[])) => ReturnType,
11
+ first: (commands: Command[] | ((props: CommandProps) => Command[])) => ReturnType
12
12
  }
13
13
  }
14
14
  }
15
15
 
16
16
  export const first: RawCommands['first'] = commands => props => {
17
- const items = typeof commands === 'function'
18
- ? commands(props)
19
- : commands
17
+ const items = typeof commands === 'function' ? commands(props) : commands
20
18
 
21
19
  for (let i = 0; i < items.length; i += 1) {
22
20
  if (items[i](props)) {
@@ -23,68 +23,65 @@ declare module '@tiptap/core' {
23
23
  * @default { scrollIntoView: true }
24
24
  */
25
25
  options?: {
26
- scrollIntoView?: boolean,
26
+ scrollIntoView?: boolean
27
27
  },
28
- ) => ReturnType,
28
+ ) => ReturnType
29
29
  }
30
30
  }
31
31
  }
32
32
 
33
- export const focus: RawCommands['focus'] = (position = null, options = {}) => ({
34
- editor,
35
- view,
36
- tr,
37
- dispatch,
38
- }) => {
39
- options = {
40
- scrollIntoView: true,
41
- ...options,
42
- }
33
+ export const focus: RawCommands['focus'] =
34
+ (position = null, options = {}) =>
35
+ ({ editor, view, tr, dispatch }) => {
36
+ options = {
37
+ scrollIntoView: true,
38
+ ...options,
39
+ }
43
40
 
44
- const delayedFocus = () => {
45
- (view.dom as HTMLElement).focus()
41
+ const delayedFocus = () => {
42
+ ;(view.dom as HTMLElement).focus()
46
43
 
47
- // For React we have to focus asynchronously. Otherwise wild things happen.
48
- // see: https://github.com/ueberdosis/tiptap/issues/1520
49
- requestAnimationFrame(() => {
50
- if (!editor.isDestroyed) {
51
- view.focus()
44
+ // For React we have to focus asynchronously. Otherwise wild things happen.
45
+ // see: https://github.com/ueberdosis/tiptap/issues/1520
46
+ requestAnimationFrame(() => {
47
+ if (!editor.isDestroyed) {
48
+ view.focus()
52
49
 
53
- if (options?.scrollIntoView) {
54
- editor.commands.scrollIntoView()
50
+ if (options?.scrollIntoView) {
51
+ editor.commands.scrollIntoView()
52
+ }
55
53
  }
56
- }
57
- })
58
- }
54
+ })
55
+ }
59
56
 
60
- if ((view.hasFocus() && position === null) || position === false) {
61
- return true
62
- }
57
+ if ((view.hasFocus() && position === null) || position === false) {
58
+ return true
59
+ }
63
60
 
64
- // we don’t try to resolve a NodeSelection or CellSelection
65
- if (dispatch && position === null && !isTextSelection(editor.state.selection)) {
66
- delayedFocus()
67
- return true
68
- }
61
+ // we don’t try to resolve a NodeSelection or CellSelection
62
+ if (dispatch && position === null && !isTextSelection(editor.state.selection)) {
63
+ delayedFocus()
64
+ return true
65
+ }
69
66
 
70
- // pass through tr.doc instead of editor.state.doc
71
- // since transactions could change the editors state before this command has been run
72
- const selection = resolveFocusPosition(tr.doc, position) || editor.state.selection
73
- const isSameSelection = editor.state.selection.eq(selection)
67
+ // pass through tr.doc instead of editor.state.doc
68
+ // since transactions could change the editors state before this command has been run
69
+ const selection = resolveFocusPosition(tr.doc, position) || editor.state.selection
70
+ const isSameSelection = editor.state.selection.eq(selection)
74
71
 
75
- if (dispatch) {
76
- if (!isSameSelection) {
77
- tr.setSelection(selection)
78
- }
72
+ if (dispatch) {
73
+ if (!isSameSelection) {
74
+ tr.setSelection(selection)
75
+ }
79
76
 
80
- // `tr.setSelection` resets the stored marks
81
- // so we’ll restore them if the selection is the same as before
82
- if (isSameSelection && tr.storedMarks) {
83
- tr.setStoredMarks(tr.storedMarks)
77
+ // `tr.setSelection` resets the stored marks
78
+ // so we’ll restore them if the selection is the same as before
79
+ if (isSameSelection && tr.storedMarks) {
80
+ tr.setStoredMarks(tr.storedMarks)
81
+ }
82
+
83
+ delayedFocus()
84
84
  }
85
85
 
86
- delayedFocus()
86
+ return true
87
87
  }
88
-
89
- return true
90
- }
@@ -11,10 +11,10 @@ declare module '@tiptap/core' {
11
11
  fn: (
12
12
  item: T,
13
13
  props: CommandProps & {
14
- index: number,
14
+ index: number
15
15
  },
16
16
  ) => boolean,
17
- ) => ReturnType,
17
+ ) => ReturnType
18
18
  }
19
19
  }
20
20
  }
@@ -23,24 +23,22 @@ declare module '@tiptap/core' {
23
23
  /**
24
24
  * Options for parsing the content.
25
25
  */
26
- parseOptions?: ParseOptions;
26
+ parseOptions?: ParseOptions
27
27
 
28
28
  /**
29
29
  * Whether to update the selection after inserting the content.
30
30
  */
31
- updateSelection?: boolean;
32
- applyInputRules?: boolean;
33
- applyPasteRules?: boolean;
34
- }
35
- ) => ReturnType;
36
- };
31
+ updateSelection?: boolean
32
+ applyInputRules?: boolean
33
+ applyPasteRules?: boolean
34
+ },
35
+ ) => ReturnType
36
+ }
37
37
  }
38
38
  }
39
39
 
40
- export const insertContent: RawCommands['insertContent'] = (value, options) => ({ tr, commands }) => {
41
- return commands.insertContentAt(
42
- { from: tr.selection.from, to: tr.selection.to },
43
- value,
44
- options,
45
- )
46
- }
40
+ export const insertContent: RawCommands['insertContent'] =
41
+ (value, options) =>
42
+ ({ tr, commands }) => {
43
+ return commands.insertContentAt({ from: tr.selection.from, to: tr.selection.to }, value, options)
44
+ }
@@ -60,122 +60,125 @@ const isFragment = (nodeOrFragment: ProseMirrorNode | Fragment): nodeOrFragment
60
60
  return !('type' in nodeOrFragment)
61
61
  }
62
62
 
63
- export const insertContentAt: RawCommands['insertContentAt'] = (position, value, options) => ({ tr, dispatch, editor }) => {
64
- if (dispatch) {
65
- options = {
66
- parseOptions: editor.options.parseOptions,
67
- updateSelection: true,
68
- applyInputRules: false,
69
- applyPasteRules: false,
70
- ...options,
71
- }
72
-
73
- let content: Fragment | ProseMirrorNode
74
- const { selection } = editor.state
63
+ export const insertContentAt: RawCommands['insertContentAt'] =
64
+ (position, value, options) =>
65
+ ({ tr, dispatch, editor }) => {
66
+ if (dispatch) {
67
+ options = {
68
+ parseOptions: editor.options.parseOptions,
69
+ updateSelection: true,
70
+ applyInputRules: false,
71
+ applyPasteRules: false,
72
+ ...options,
73
+ }
75
74
 
76
- try {
77
- content = createNodeFromContent(value, editor.schema, {
78
- parseOptions: {
79
- preserveWhitespace: 'full',
80
- ...options.parseOptions,
81
- },
82
- errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
83
- })
84
- } catch (e) {
85
- editor.emit('contentError', {
86
- editor,
87
- error: e as Error,
88
- disableCollaboration: () => {
89
- if (editor.storage.collaboration) {
90
- editor.storage.collaboration.isDisabled = true
91
- }
92
- },
93
- })
94
- return false
95
- }
75
+ let content: Fragment | ProseMirrorNode
76
+ const { selection } = editor.state
96
77
 
97
- let { from, to } = typeof position === 'number' ? { from: position, to: position } : { from: position.from, to: position.to }
78
+ try {
79
+ content = createNodeFromContent(value, editor.schema, {
80
+ parseOptions: {
81
+ preserveWhitespace: 'full',
82
+ ...options.parseOptions,
83
+ },
84
+ errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
85
+ })
86
+ } catch (e) {
87
+ editor.emit('contentError', {
88
+ editor,
89
+ error: e as Error,
90
+ disableCollaboration: () => {
91
+ if (editor.storage.collaboration) {
92
+ editor.storage.collaboration.isDisabled = true
93
+ }
94
+ },
95
+ })
96
+ return false
97
+ }
98
98
 
99
- let isOnlyTextContent = true
100
- let isOnlyBlockContent = true
101
- const nodes = isFragment(content) ? content : [content]
99
+ let { from, to } =
100
+ typeof position === 'number' ? { from: position, to: position } : { from: position.from, to: position.to }
102
101
 
103
- nodes.forEach(node => {
104
- // check if added node is valid
105
- node.check()
102
+ let isOnlyTextContent = true
103
+ let isOnlyBlockContent = true
104
+ const nodes = isFragment(content) ? content : [content]
106
105
 
107
- isOnlyTextContent = isOnlyTextContent ? node.isText && node.marks.length === 0 : false
106
+ nodes.forEach(node => {
107
+ // check if added node is valid
108
+ node.check()
108
109
 
109
- isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false
110
- })
110
+ isOnlyTextContent = isOnlyTextContent ? node.isText && node.marks.length === 0 : false
111
111
 
112
- // check if we can replace the wrapping node by
113
- // the newly inserted content
114
- // example:
115
- // replace an empty paragraph by an inserted image
116
- // instead of inserting the image below the paragraph
117
- if (from === to && isOnlyBlockContent) {
118
- const { parent } = tr.doc.resolve(from)
119
- const isEmptyTextBlock = parent.isTextblock && !parent.type.spec.code && !parent.childCount
112
+ isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false
113
+ })
120
114
 
121
- if (isEmptyTextBlock) {
122
- from -= 1
123
- to += 1
115
+ // check if we can replace the wrapping node by
116
+ // the newly inserted content
117
+ // example:
118
+ // replace an empty paragraph by an inserted image
119
+ // instead of inserting the image below the paragraph
120
+ if (from === to && isOnlyBlockContent) {
121
+ const { parent } = tr.doc.resolve(from)
122
+ const isEmptyTextBlock = parent.isTextblock && !parent.type.spec.code && !parent.childCount
123
+
124
+ if (isEmptyTextBlock) {
125
+ from -= 1
126
+ to += 1
127
+ }
124
128
  }
125
- }
126
-
127
- let newContent
128
-
129
- // if there is only plain text we have to use `insertText`
130
- // because this will keep the current marks
131
- if (isOnlyTextContent) {
132
- // if value is string, we can use it directly
133
- // otherwise if it is an array, we have to join it
134
- if (Array.isArray(value)) {
135
- newContent = value.map(v => v.text || '').join('')
136
- } else if (value instanceof Fragment) {
137
- let text = ''
138
-
139
- value.forEach(node => {
140
- if (node.text) {
141
- text += node.text
142
- }
143
- })
144
129
 
145
- newContent = text
146
- } else if (typeof value === 'object' && !!value && !!value.text) {
147
- newContent = value.text
130
+ let newContent
131
+
132
+ // if there is only plain text we have to use `insertText`
133
+ // because this will keep the current marks
134
+ if (isOnlyTextContent) {
135
+ // if value is string, we can use it directly
136
+ // otherwise if it is an array, we have to join it
137
+ if (Array.isArray(value)) {
138
+ newContent = value.map(v => v.text || '').join('')
139
+ } else if (value instanceof Fragment) {
140
+ let text = ''
141
+
142
+ value.forEach(node => {
143
+ if (node.text) {
144
+ text += node.text
145
+ }
146
+ })
147
+
148
+ newContent = text
149
+ } else if (typeof value === 'object' && !!value && !!value.text) {
150
+ newContent = value.text
151
+ } else {
152
+ newContent = value as string
153
+ }
154
+
155
+ tr.insertText(newContent, from, to)
148
156
  } else {
149
- newContent = value as string
150
- }
157
+ newContent = content
151
158
 
152
- tr.insertText(newContent, from, to)
153
- } else {
154
- newContent = content
159
+ const fromSelectionAtStart = selection.$from.parentOffset === 0
160
+ const isTextSelection = selection.$from.node().isText || selection.$from.node().isTextblock
155
161
 
156
- const fromSelectionAtStart = selection.$from.parentOffset === 0
157
- const isTextSelection = selection.$from.node().isText || selection.$from.node().isTextblock
162
+ if (fromSelectionAtStart && isTextSelection) {
163
+ from = Math.max(0, from - 1)
164
+ }
158
165
 
159
- if (fromSelectionAtStart && isTextSelection) {
160
- from = Math.max(0, from - 1)
166
+ tr.replaceWith(from, to, newContent)
161
167
  }
162
168
 
163
- tr.replaceWith(from, to, newContent)
164
- }
169
+ // set cursor at end of inserted content
170
+ if (options.updateSelection) {
171
+ selectionToInsertionEnd(tr, tr.steps.length - 1, -1)
172
+ }
165
173
 
166
- // set cursor at end of inserted content
167
- if (options.updateSelection) {
168
- selectionToInsertionEnd(tr, tr.steps.length - 1, -1)
169
- }
174
+ if (options.applyInputRules) {
175
+ tr.setMeta('applyInputRules', { from, text: newContent })
176
+ }
170
177
 
171
- if (options.applyInputRules) {
172
- tr.setMeta('applyInputRules', { from, text: newContent })
178
+ if (options.applyPasteRules) {
179
+ tr.setMeta('applyPasteRules', { from, text: newContent })
180
+ }
173
181
  }
174
182
 
175
- if (options.applyPasteRules) {
176
- tr.setMeta('applyPasteRules', { from, text: newContent })
177
- }
183
+ return true
178
184
  }
179
-
180
- return true
181
- }
@@ -44,18 +44,26 @@ declare module '@tiptap/core' {
44
44
  }
45
45
  }
46
46
 
47
- export const joinUp: RawCommands['joinUp'] = () => ({ state, dispatch }) => {
48
- return originalJoinUp(state, dispatch)
49
- }
47
+ export const joinUp: RawCommands['joinUp'] =
48
+ () =>
49
+ ({ state, dispatch }) => {
50
+ return originalJoinUp(state, dispatch)
51
+ }
50
52
 
51
- export const joinDown: RawCommands['joinDown'] = () => ({ state, dispatch }) => {
52
- return originalJoinDown(state, dispatch)
53
- }
53
+ export const joinDown: RawCommands['joinDown'] =
54
+ () =>
55
+ ({ state, dispatch }) => {
56
+ return originalJoinDown(state, dispatch)
57
+ }
54
58
 
55
- export const joinBackward: RawCommands['joinBackward'] = () => ({ state, dispatch }) => {
56
- return originalJoinBackward(state, dispatch)
57
- }
59
+ export const joinBackward: RawCommands['joinBackward'] =
60
+ () =>
61
+ ({ state, dispatch }) => {
62
+ return originalJoinBackward(state, dispatch)
63
+ }
58
64
 
59
- export const joinForward: RawCommands['joinForward'] = () => ({ state, dispatch }) => {
60
- return originalJoinForward(state, dispatch)
61
- }
65
+ export const joinForward: RawCommands['joinForward'] =
66
+ () =>
67
+ ({ state, dispatch }) => {
68
+ return originalJoinForward(state, dispatch)
69
+ }