@tiptap/core 3.0.0-next.3 → 3.0.0-next.5

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 (146) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +5 -1
  3. package/dist/index.cjs +2627 -2651
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +2423 -2688
  6. package/dist/index.d.ts +2423 -2688
  7. package/dist/index.js +2639 -2684
  8. package/dist/index.js.map +1 -1
  9. package/dist/jsx-runtime/jsx-runtime.cjs +56 -0
  10. package/dist/jsx-runtime/jsx-runtime.cjs.map +1 -0
  11. package/dist/jsx-runtime/jsx-runtime.d.cts +22 -0
  12. package/dist/jsx-runtime/jsx-runtime.d.ts +22 -0
  13. package/dist/jsx-runtime/jsx-runtime.js +26 -0
  14. package/dist/jsx-runtime/jsx-runtime.js.map +1 -0
  15. package/jsx-runtime/index.cjs +1 -0
  16. package/jsx-runtime/index.d.cts +1 -0
  17. package/jsx-runtime/index.d.ts +1 -0
  18. package/jsx-runtime/index.js +1 -0
  19. package/package.json +27 -6
  20. package/src/CommandManager.ts +2 -9
  21. package/src/Editor.ts +191 -94
  22. package/src/EventEmitter.ts +7 -10
  23. package/src/Extendable.ts +483 -0
  24. package/src/Extension.ts +5 -496
  25. package/src/ExtensionManager.ts +81 -135
  26. package/src/InputRule.ts +35 -48
  27. package/src/Mark.ts +135 -623
  28. package/src/MarkView.ts +66 -0
  29. package/src/Node.ts +325 -829
  30. package/src/NodePos.ts +1 -3
  31. package/src/NodeView.ts +10 -20
  32. package/src/PasteRule.ts +43 -55
  33. package/src/Tracker.ts +7 -9
  34. package/src/commands/blur.ts +14 -12
  35. package/src/commands/clearContent.ts +12 -5
  36. package/src/commands/clearNodes.ts +32 -30
  37. package/src/commands/command.ts +1 -1
  38. package/src/commands/createParagraphNear.ts +5 -3
  39. package/src/commands/cut.ts +12 -10
  40. package/src/commands/deleteCurrentNode.ts +23 -21
  41. package/src/commands/deleteNode.ts +18 -16
  42. package/src/commands/deleteRange.ts +10 -8
  43. package/src/commands/deleteSelection.ts +5 -3
  44. package/src/commands/enter.ts +6 -4
  45. package/src/commands/exitCode.ts +5 -3
  46. package/src/commands/extendMarkRange.ts +14 -12
  47. package/src/commands/first.ts +2 -4
  48. package/src/commands/focus.ts +51 -48
  49. package/src/commands/forEach.ts +2 -2
  50. package/src/commands/insertContent.ts +12 -14
  51. package/src/commands/insertContentAt.ts +105 -98
  52. package/src/commands/join.ts +20 -12
  53. package/src/commands/joinItemBackward.ts +16 -18
  54. package/src/commands/joinItemForward.ts +16 -18
  55. package/src/commands/joinTextblockBackward.ts +5 -3
  56. package/src/commands/joinTextblockForward.ts +5 -3
  57. package/src/commands/keyboardShortcut.ts +29 -34
  58. package/src/commands/lift.ts +10 -8
  59. package/src/commands/liftEmptyBlock.ts +6 -4
  60. package/src/commands/liftListItem.ts +6 -4
  61. package/src/commands/newlineInCode.ts +5 -3
  62. package/src/commands/resetAttributes.ts +36 -41
  63. package/src/commands/scrollIntoView.ts +9 -7
  64. package/src/commands/selectAll.ts +10 -8
  65. package/src/commands/selectNodeBackward.ts +5 -3
  66. package/src/commands/selectNodeForward.ts +5 -3
  67. package/src/commands/selectParentNode.ts +5 -3
  68. package/src/commands/selectTextblockEnd.ts +5 -3
  69. package/src/commands/selectTextblockStart.ts +5 -3
  70. package/src/commands/setContent.ts +37 -36
  71. package/src/commands/setMark.ts +55 -57
  72. package/src/commands/setMeta.ts +7 -5
  73. package/src/commands/setNode.ts +32 -30
  74. package/src/commands/setNodeSelection.ts +11 -9
  75. package/src/commands/setTextSelection.ts +15 -13
  76. package/src/commands/sinkListItem.ts +6 -4
  77. package/src/commands/splitBlock.ts +67 -76
  78. package/src/commands/splitListItem.ts +93 -106
  79. package/src/commands/toggleList.ts +73 -71
  80. package/src/commands/toggleMark.ts +11 -9
  81. package/src/commands/toggleNode.ts +18 -16
  82. package/src/commands/toggleWrap.ts +10 -8
  83. package/src/commands/undoInputRule.ts +31 -29
  84. package/src/commands/unsetAllMarks.ts +16 -14
  85. package/src/commands/unsetMark.ts +27 -25
  86. package/src/commands/updateAttributes.ts +92 -100
  87. package/src/commands/wrapIn.ts +6 -4
  88. package/src/commands/wrapInList.ts +6 -4
  89. package/src/extensions/clipboardTextSerializer.ts +2 -4
  90. package/src/extensions/delete.ts +89 -0
  91. package/src/extensions/focusEvents.ts +2 -6
  92. package/src/extensions/index.ts +1 -0
  93. package/src/extensions/keymap.ts +58 -50
  94. package/src/extensions/paste.ts +0 -1
  95. package/src/extensions/tabindex.ts +1 -1
  96. package/src/helpers/combineTransactionSteps.ts +1 -4
  97. package/src/helpers/createChainableState.ts +1 -4
  98. package/src/helpers/createDocument.ts +1 -3
  99. package/src/helpers/createNodeFromContent.ts +4 -10
  100. package/src/helpers/findChildrenInRange.ts +1 -5
  101. package/src/helpers/findParentNode.ts +3 -1
  102. package/src/helpers/flattenExtensions.ts +30 -0
  103. package/src/helpers/getAttributes.ts +1 -4
  104. package/src/helpers/getAttributesFromExtensions.ts +28 -37
  105. package/src/helpers/getChangedRanges.ts +13 -11
  106. package/src/helpers/getExtensionField.ts +11 -11
  107. package/src/helpers/getMarkAttributes.ts +1 -4
  108. package/src/helpers/getMarkRange.ts +5 -15
  109. package/src/helpers/getMarkType.ts +1 -3
  110. package/src/helpers/getNodeAttributes.ts +1 -4
  111. package/src/helpers/getNodeType.ts +1 -3
  112. package/src/helpers/getRenderedAttributes.ts +1 -3
  113. package/src/helpers/getSchema.ts +2 -2
  114. package/src/helpers/getSchemaByResolvedExtensions.ts +45 -77
  115. package/src/helpers/getSplittedAttributes.ts +4 -4
  116. package/src/helpers/getTextContentFromNodes.ts +8 -11
  117. package/src/helpers/index.ts +4 -0
  118. package/src/helpers/injectExtensionAttributesToParseRule.ts +1 -1
  119. package/src/helpers/isActive.ts +1 -5
  120. package/src/helpers/isExtensionRulesEnabled.ts +1 -3
  121. package/src/helpers/isNodeEmpty.ts +2 -2
  122. package/src/helpers/resolveExtensions.ts +25 -0
  123. package/src/helpers/resolveFocusPosition.ts +3 -14
  124. package/src/helpers/rewriteUnknownContent.ts +149 -0
  125. package/src/helpers/sortExtensions.ts +26 -0
  126. package/src/index.ts +3 -7
  127. package/src/inputRules/markInputRule.ts +1 -5
  128. package/src/inputRules/nodeInputRule.ts +2 -9
  129. package/src/inputRules/textInputRule.ts +1 -4
  130. package/src/inputRules/textblockTypeInputRule.ts +2 -8
  131. package/src/inputRules/wrappingInputRule.ts +13 -19
  132. package/src/jsx-runtime.ts +64 -0
  133. package/src/pasteRules/markPasteRule.ts +1 -3
  134. package/src/pasteRules/nodePasteRule.ts +2 -8
  135. package/src/pasteRules/textPasteRule.ts +1 -4
  136. package/src/types.ts +529 -174
  137. package/src/utilities/createStyleTag.ts +3 -1
  138. package/src/utilities/deleteProps.ts +7 -11
  139. package/src/utilities/elementFromString.ts +3 -0
  140. package/src/utilities/findDuplicates.ts +4 -1
  141. package/src/utilities/index.ts +1 -0
  142. package/src/utilities/isFunction.ts +1 -0
  143. package/src/utilities/isMacOS.ts +1 -3
  144. package/src/utilities/isiOS.ts +5 -10
  145. package/src/utilities/mergeAttributes.ts +17 -7
  146. package/src/utilities/removeDuplicates.ts +1 -3
@@ -18,8 +18,10 @@ declare module '@tiptap/core' {
18
18
  }
19
19
  }
20
20
 
21
- export const wrapInList: RawCommands['wrapInList'] = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
22
- const type = getNodeType(typeOrName, state.schema)
21
+ export const wrapInList: RawCommands['wrapInList'] =
22
+ (typeOrName, attributes = {}) =>
23
+ ({ state, dispatch }) => {
24
+ const type = getNodeType(typeOrName, state.schema)
23
25
 
24
- return originalWrapInList(type, attributes)(state, dispatch)
25
- }
26
+ return originalWrapInList(type, attributes)(state, dispatch)
27
+ }
@@ -5,7 +5,7 @@ import { getTextBetween } from '../helpers/getTextBetween.js'
5
5
  import { getTextSerializersFromSchema } from '../helpers/getTextSerializersFromSchema.js'
6
6
 
7
7
  export type ClipboardTextSerializerOptions = {
8
- blockSeparator?: string,
8
+ blockSeparator?: string
9
9
  }
10
10
 
11
11
  export const ClipboardTextSerializer = Extension.create<ClipboardTextSerializerOptions>({
@@ -33,9 +33,7 @@ export const ClipboardTextSerializer = Extension.create<ClipboardTextSerializerO
33
33
  const range = { from, to }
34
34
 
35
35
  return getTextBetween(doc, range, {
36
- ...(this.options.blockSeparator !== undefined
37
- ? { blockSeparator: this.options.blockSeparator }
38
- : {}),
36
+ ...(this.options.blockSeparator !== undefined ? { blockSeparator: this.options.blockSeparator } : {}),
39
37
  textSerializers,
40
38
  })
41
39
  },
@@ -0,0 +1,89 @@
1
+ import { RemoveMarkStep } from '@tiptap/pm/transform'
2
+
3
+ import { Extension } from '../Extension.js'
4
+ import { combineTransactionSteps, getChangedRanges } from '../helpers/index.js'
5
+
6
+ /**
7
+ * This extension allows you to be notified when the user deletes content you are interested in.
8
+ */
9
+ export const Delete = Extension.create({
10
+ name: 'delete',
11
+
12
+ onUpdate({ transaction, appendedTransactions }) {
13
+ const callback = () => {
14
+ if (
15
+ this.editor.options.coreExtensionOptions?.delete?.filterTransaction?.(transaction) ??
16
+ transaction.getMeta('y-sync$')
17
+ ) {
18
+ return
19
+ }
20
+ const nextTransaction = combineTransactionSteps(transaction.before, [transaction, ...appendedTransactions])
21
+ const changes = getChangedRanges(nextTransaction)
22
+
23
+ changes.forEach(change => {
24
+ if (
25
+ nextTransaction.mapping.mapResult(change.oldRange.from).deletedAfter &&
26
+ nextTransaction.mapping.mapResult(change.oldRange.to).deletedBefore
27
+ ) {
28
+ nextTransaction.before.nodesBetween(change.oldRange.from, change.oldRange.to, (node, from) => {
29
+ const to = from + node.nodeSize - 2
30
+ const isFullyWithinRange = change.oldRange.from <= from && to <= change.oldRange.to
31
+
32
+ this.editor.emit('delete', {
33
+ type: 'node',
34
+ node,
35
+ from,
36
+ to,
37
+ newFrom: nextTransaction.mapping.map(from),
38
+ newTo: nextTransaction.mapping.map(to),
39
+ deletedRange: change.oldRange,
40
+ newRange: change.newRange,
41
+ partial: !isFullyWithinRange,
42
+ editor: this.editor,
43
+ transaction,
44
+ combinedTransform: nextTransaction,
45
+ })
46
+ })
47
+ }
48
+ })
49
+
50
+ const mapping = nextTransaction.mapping
51
+ nextTransaction.steps.forEach((step, index) => {
52
+ if (step instanceof RemoveMarkStep) {
53
+ const newStart = mapping.slice(index).map(step.from, -1)
54
+ const newEnd = mapping.slice(index).map(step.to)
55
+ const oldStart = mapping.invert().map(newStart, -1)
56
+ const oldEnd = mapping.invert().map(newEnd)
57
+
58
+ const foundBeforeMark = nextTransaction.doc.nodeAt(newStart - 1)?.marks.some(mark => mark.eq(step.mark))
59
+ const foundAfterMark = nextTransaction.doc.nodeAt(newEnd)?.marks.some(mark => mark.eq(step.mark))
60
+
61
+ this.editor.emit('delete', {
62
+ type: 'mark',
63
+ mark: step.mark,
64
+ from: step.from,
65
+ to: step.to,
66
+ deletedRange: {
67
+ from: oldStart,
68
+ to: oldEnd,
69
+ },
70
+ newRange: {
71
+ from: newStart,
72
+ to: newEnd,
73
+ },
74
+ partial: Boolean(foundAfterMark || foundBeforeMark),
75
+ editor: this.editor,
76
+ transaction,
77
+ combinedTransform: nextTransaction,
78
+ })
79
+ }
80
+ })
81
+ }
82
+
83
+ if (this.editor.options.coreExtensionOptions?.delete?.async ?? true) {
84
+ setTimeout(callback, 0)
85
+ } else {
86
+ callback()
87
+ }
88
+ },
89
+ })
@@ -16,9 +16,7 @@ export const FocusEvents = Extension.create({
16
16
  focus: (view, event: Event) => {
17
17
  editor.isFocused = true
18
18
 
19
- const transaction = editor.state.tr
20
- .setMeta('focus', { event })
21
- .setMeta('addToHistory', false)
19
+ const transaction = editor.state.tr.setMeta('focus', { event }).setMeta('addToHistory', false)
22
20
 
23
21
  view.dispatch(transaction)
24
22
 
@@ -27,9 +25,7 @@ export const FocusEvents = Extension.create({
27
25
  blur: (view, event: Event) => {
28
26
  editor.isFocused = false
29
27
 
30
- const transaction = editor.state.tr
31
- .setMeta('blur', { event })
32
- .setMeta('addToHistory', false)
28
+ const transaction = editor.state.tr.setMeta('blur', { event }).setMeta('addToHistory', false)
33
29
 
34
30
  view.dispatch(transaction)
35
31
 
@@ -1,5 +1,6 @@
1
1
  export { ClipboardTextSerializer } from './clipboardTextSerializer.js'
2
2
  export { Commands } from './commands.js'
3
+ export { Delete } from './delete.js'
3
4
  export { Drop } from './drop.js'
4
5
  export { Editable } from './editable.js'
5
6
  export { FocusEvents } from './focusEvents.js'
@@ -11,54 +11,59 @@ export const Keymap = Extension.create({
11
11
  name: 'keymap',
12
12
 
13
13
  addKeyboardShortcuts() {
14
- const handleBackspace = () => this.editor.commands.first(({ commands }) => [
15
- () => commands.undoInputRule(),
16
-
17
- // maybe convert first text block node to default node
18
- () => commands.command(({ tr }) => {
19
- const { selection, doc } = tr
20
- const { empty, $anchor } = selection
21
- const { pos, parent } = $anchor
22
- const $parentPos = $anchor.parent.isTextblock && pos > 0 ? tr.doc.resolve(pos - 1) : $anchor
23
- const parentIsIsolating = $parentPos.parent.type.spec.isolating
24
-
25
- const parentPos = $anchor.pos - $anchor.parentOffset
26
-
27
- const isAtStart = (parentIsIsolating && $parentPos.parent.childCount === 1)
28
- ? parentPos === $anchor.pos
29
- : Selection.atStart(doc).from === pos
30
-
31
- if (
32
- !empty
33
- || !parent.type.isTextblock
34
- || parent.textContent.length
35
- || !isAtStart
36
- || (isAtStart && $anchor.parent.type.name === 'paragraph') // prevent clearNodes when no nodes to clear, otherwise history stack is appended
37
- ) {
38
- return false
39
- }
40
-
41
- return commands.clearNodes()
42
- }),
43
-
44
- () => commands.deleteSelection(),
45
- () => commands.joinBackward(),
46
- () => commands.selectNodeBackward(),
47
- ])
48
-
49
- const handleDelete = () => this.editor.commands.first(({ commands }) => [
50
- () => commands.deleteSelection(),
51
- () => commands.deleteCurrentNode(),
52
- () => commands.joinForward(),
53
- () => commands.selectNodeForward(),
54
- ])
55
-
56
- const handleEnter = () => this.editor.commands.first(({ commands }) => [
57
- () => commands.newlineInCode(),
58
- () => commands.createParagraphNear(),
59
- () => commands.liftEmptyBlock(),
60
- () => commands.splitBlock(),
61
- ])
14
+ const handleBackspace = () =>
15
+ this.editor.commands.first(({ commands }) => [
16
+ () => commands.undoInputRule(),
17
+
18
+ // maybe convert first text block node to default node
19
+ () =>
20
+ commands.command(({ tr }) => {
21
+ const { selection, doc } = tr
22
+ const { empty, $anchor } = selection
23
+ const { pos, parent } = $anchor
24
+ const $parentPos = $anchor.parent.isTextblock && pos > 0 ? tr.doc.resolve(pos - 1) : $anchor
25
+ const parentIsIsolating = $parentPos.parent.type.spec.isolating
26
+
27
+ const parentPos = $anchor.pos - $anchor.parentOffset
28
+
29
+ const isAtStart =
30
+ parentIsIsolating && $parentPos.parent.childCount === 1
31
+ ? parentPos === $anchor.pos
32
+ : Selection.atStart(doc).from === pos
33
+
34
+ if (
35
+ !empty ||
36
+ !parent.type.isTextblock ||
37
+ parent.textContent.length ||
38
+ !isAtStart ||
39
+ (isAtStart && $anchor.parent.type.name === 'paragraph') // prevent clearNodes when no nodes to clear, otherwise history stack is appended
40
+ ) {
41
+ return false
42
+ }
43
+
44
+ return commands.clearNodes()
45
+ }),
46
+
47
+ () => commands.deleteSelection(),
48
+ () => commands.joinBackward(),
49
+ () => commands.selectNodeBackward(),
50
+ ])
51
+
52
+ const handleDelete = () =>
53
+ this.editor.commands.first(({ commands }) => [
54
+ () => commands.deleteSelection(),
55
+ () => commands.deleteCurrentNode(),
56
+ () => commands.joinForward(),
57
+ () => commands.selectNodeForward(),
58
+ ])
59
+
60
+ const handleEnter = () =>
61
+ this.editor.commands.first(({ commands }) => [
62
+ () => commands.newlineInCode(),
63
+ () => commands.createParagraphNear(),
64
+ () => commands.liftEmptyBlock(),
65
+ () => commands.splitBlock(),
66
+ ])
62
67
 
63
68
  const baseKeymap = {
64
69
  Enter: handleEnter,
@@ -104,8 +109,11 @@ export const Keymap = Extension.create({
104
109
  new Plugin({
105
110
  key: new PluginKey('clearDocument'),
106
111
  appendTransaction: (transactions, oldState, newState) => {
107
- const docChanges = transactions.some(transaction => transaction.docChanged)
108
- && !oldState.doc.eq(newState.doc)
112
+ if (transactions.some(tr => tr.getMeta('composition'))) {
113
+ return
114
+ }
115
+
116
+ const docChanges = transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)
109
117
 
110
118
  const ignoreTr = transactions.some(transaction => transaction.getMeta('preventClearDocument'))
111
119
 
@@ -6,7 +6,6 @@ export const Paste = Extension.create({
6
6
  name: 'paste',
7
7
 
8
8
  addProseMirrorPlugins() {
9
-
10
9
  return [
11
10
  new Plugin({
12
11
  key: new PluginKey('tiptapPaste'),
@@ -10,7 +10,7 @@ export const Tabindex = Extension.create({
10
10
  new Plugin({
11
11
  key: new PluginKey('tabindex'),
12
12
  props: {
13
- attributes: (): { [name: string]: string; } => (this.editor.isEditable ? { tabindex: '0' } : {}),
13
+ attributes: (): { [name: string]: string } => (this.editor.isEditable ? { tabindex: '0' } : {}),
14
14
  },
15
15
  }),
16
16
  ]
@@ -8,10 +8,7 @@ import { Transform } from '@tiptap/pm/transform'
8
8
  * @param transactions The transactions to combine
9
9
  * @returns A new `Transform` with all steps of the passed transactions
10
10
  */
11
- export function combineTransactionSteps(
12
- oldDoc: ProseMirrorNode,
13
- transactions: Transaction[],
14
- ): Transform {
11
+ export function combineTransactionSteps(oldDoc: ProseMirrorNode, transactions: Transaction[]): Transform {
15
12
  const transform = new Transform(oldDoc)
16
13
 
17
14
  transactions.forEach(transaction => {
@@ -5,10 +5,7 @@ import { EditorState, Transaction } from '@tiptap/pm/state'
5
5
  * @param config The transaction and state to create the chainable state from
6
6
  * @returns A chainable Editor state object
7
7
  */
8
- export function createChainableState(config: {
9
- transaction: Transaction
10
- state: EditorState
11
- }): EditorState {
8
+ export function createChainableState(config: { transaction: Transaction; state: EditorState }): EditorState {
12
9
  const { state, transaction } = config
13
10
  let { selection } = transaction
14
11
  let { doc } = transaction
@@ -1,6 +1,4 @@
1
- import {
2
- Fragment, Node as ProseMirrorNode, ParseOptions, Schema,
3
- } from '@tiptap/pm/model'
1
+ import { Fragment, Node as ProseMirrorNode, ParseOptions, Schema } from '@tiptap/pm/model'
4
2
 
5
3
  import { Content } from '../types.js'
6
4
  import { createNodeFromContent } from './createNodeFromContent.js'
@@ -1,10 +1,4 @@
1
- import {
2
- DOMParser,
3
- Fragment,
4
- Node as ProseMirrorNode,
5
- ParseOptions,
6
- Schema,
7
- } from '@tiptap/pm/model'
1
+ import { DOMParser, Fragment, Node as ProseMirrorNode, ParseOptions, Schema } from '@tiptap/pm/model'
8
2
 
9
3
  import { Content } from '../types.js'
10
4
  import { elementFromString } from '../utilities/elementFromString.js'
@@ -67,7 +61,6 @@ export function createNodeFromContent(
67
61
  }
68
62
 
69
63
  if (isTextContent) {
70
-
71
64
  // Check for invalid content
72
65
  if (options.errorOnInvalidContent) {
73
66
  let hasInvalidContent = false
@@ -106,7 +99,9 @@ export function createNodeFromContent(
106
99
  }
107
100
 
108
101
  if (options.errorOnInvalidContent && hasInvalidContent) {
109
- throw new Error('[tiptap error]: Invalid HTML content', { cause: new Error(`Invalid element found: ${invalidContent}`) })
102
+ throw new Error('[tiptap error]: Invalid HTML content', {
103
+ cause: new Error(`Invalid element found: ${invalidContent}`),
104
+ })
110
105
  }
111
106
  }
112
107
 
@@ -117,7 +112,6 @@ export function createNodeFromContent(
117
112
  }
118
113
 
119
114
  return parser.parse(elementFromString(content), options.parseOptions)
120
-
121
115
  }
122
116
 
123
117
  return createNodeFromContent('', schema, options)
@@ -9,11 +9,7 @@ import { NodeWithPos, Predicate, Range } from '../types.js'
9
9
  * @param predicate The predicate to match
10
10
  * @returns An array of nodes with their positions
11
11
  */
12
- export function findChildrenInRange(
13
- node: ProseMirrorNode,
14
- range: Range,
15
- predicate: Predicate,
16
- ): NodeWithPos[] {
12
+ export function findChildrenInRange(node: ProseMirrorNode, range: Range, predicate: Predicate): NodeWithPos[] {
17
13
  const nodesWithPos: NodeWithPos[] = []
18
14
 
19
15
  // if (range.from === range.to) {
@@ -11,6 +11,8 @@ import { findParentNodeClosestToPos } from './findParentNodeClosestToPos.js'
11
11
  * findParentNode(node => node.type.name === 'paragraph')
12
12
  * ```
13
13
  */
14
- export function findParentNode(predicate: Predicate) {
14
+ export function findParentNode(
15
+ predicate: Predicate,
16
+ ): (selection: Selection) => ReturnType<typeof findParentNodeClosestToPos> {
15
17
  return (selection: Selection) => findParentNodeClosestToPos(selection.$from, predicate)
16
18
  }
@@ -0,0 +1,30 @@
1
+ import { AnyConfig, Extensions } from '../types.js'
2
+ import { getExtensionField } from './getExtensionField.js'
3
+
4
+ /**
5
+ * Create a flattened array of extensions by traversing the `addExtensions` field.
6
+ * @param extensions An array of Tiptap extensions
7
+ * @returns A flattened array of Tiptap extensions
8
+ */
9
+ export function flattenExtensions(extensions: Extensions): Extensions {
10
+ return (
11
+ extensions
12
+ .map(extension => {
13
+ const context = {
14
+ name: extension.name,
15
+ options: extension.options,
16
+ storage: extension.storage,
17
+ }
18
+
19
+ const addExtensions = getExtensionField<AnyConfig['addExtensions']>(extension, 'addExtensions', context)
20
+
21
+ if (addExtensions) {
22
+ return [extension, ...flattenExtensions(addExtensions())]
23
+ }
24
+
25
+ return extension
26
+ })
27
+ // `Infinity` will break TypeScript so we set a number that is probably high enough
28
+ .flat(10)
29
+ )
30
+ }
@@ -11,10 +11,7 @@ import { getSchemaTypeNameByName } from './getSchemaTypeNameByName.js'
11
11
  * @param typeOrName The node or mark type or name
12
12
  * @returns The attributes of the node or mark or an empty object
13
13
  */
14
- export function getAttributes(
15
- state: EditorState,
16
- typeOrName: string | NodeType | MarkType,
17
- ): Record<string, any> {
14
+ export function getAttributes(state: EditorState, typeOrName: string | NodeType | MarkType): Record<string, any> {
18
15
  const schemaType = getSchemaTypeNameByName(
19
16
  typeof typeOrName === 'string' ? typeOrName : typeOrName.name,
20
17
  state.schema,
@@ -1,11 +1,5 @@
1
1
  import type { MarkConfig, NodeConfig } from '../index.js'
2
- import {
3
- AnyConfig,
4
- Attribute,
5
- Attributes,
6
- ExtensionAttribute,
7
- Extensions,
8
- } from '../types.js'
2
+ import { AnyConfig, Attribute, Attributes, ExtensionAttribute, Extensions } from '../types.js'
9
3
  import { getExtensionField } from './getExtensionField.js'
10
4
  import { splitExtensions } from './splitExtensions.js'
11
5
 
@@ -17,8 +11,9 @@ export function getAttributesFromExtensions(extensions: Extensions): ExtensionAt
17
11
  const extensionAttributes: ExtensionAttribute[] = []
18
12
  const { nodeExtensions, markExtensions } = splitExtensions(extensions)
19
13
  const nodeAndMarkExtensions = [...nodeExtensions, ...markExtensions]
20
- const defaultAttribute: Required<Attribute> = {
14
+ const defaultAttribute: Required<Omit<Attribute, 'validate'>> & Pick<Attribute, 'validate'> = {
21
15
  default: null,
16
+ validate: undefined,
22
17
  rendered: true,
23
18
  renderHTML: null,
24
19
  parseHTML: null,
@@ -48,18 +43,16 @@ export function getAttributesFromExtensions(extensions: Extensions): ExtensionAt
48
43
 
49
44
  globalAttributes.forEach(globalAttribute => {
50
45
  globalAttribute.types.forEach(type => {
51
- Object
52
- .entries(globalAttribute.attributes)
53
- .forEach(([name, attribute]) => {
54
- extensionAttributes.push({
55
- type,
56
- name,
57
- attribute: {
58
- ...defaultAttribute,
59
- ...attribute,
60
- },
61
- })
46
+ Object.entries(globalAttribute.attributes).forEach(([name, attribute]) => {
47
+ extensionAttributes.push({
48
+ type,
49
+ name,
50
+ attribute: {
51
+ ...defaultAttribute,
52
+ ...attribute,
53
+ },
62
54
  })
55
+ })
63
56
  })
64
57
  })
65
58
  })
@@ -84,28 +77,26 @@ export function getAttributesFromExtensions(extensions: Extensions): ExtensionAt
84
77
  // TODO: remove `as Attributes`
85
78
  const attributes = addAttributes() as Attributes
86
79
 
87
- Object
88
- .entries(attributes)
89
- .forEach(([name, attribute]) => {
90
- const mergedAttr = {
91
- ...defaultAttribute,
92
- ...attribute,
93
- }
80
+ Object.entries(attributes).forEach(([name, attribute]) => {
81
+ const mergedAttr = {
82
+ ...defaultAttribute,
83
+ ...attribute,
84
+ }
94
85
 
95
- if (typeof mergedAttr?.default === 'function') {
96
- mergedAttr.default = mergedAttr.default()
97
- }
86
+ if (typeof mergedAttr?.default === 'function') {
87
+ mergedAttr.default = mergedAttr.default()
88
+ }
98
89
 
99
- if (mergedAttr?.isRequired && mergedAttr?.default === undefined) {
100
- delete mergedAttr.default
101
- }
90
+ if (mergedAttr?.isRequired && mergedAttr?.default === undefined) {
91
+ delete mergedAttr.default
92
+ }
102
93
 
103
- extensionAttributes.push({
104
- type: extension.name,
105
- name,
106
- attribute: mergedAttr,
107
- })
94
+ extensionAttributes.push({
95
+ type: extension.name,
96
+ name,
97
+ attribute: mergedAttr,
108
98
  })
99
+ })
109
100
  })
110
101
 
111
102
  return extensionAttributes
@@ -4,8 +4,8 @@ import { Range } from '../types.js'
4
4
  import { removeDuplicates } from '../utilities/removeDuplicates.js'
5
5
 
6
6
  export type ChangedRange = {
7
- oldRange: Range,
8
- newRange: Range,
7
+ oldRange: Range
8
+ newRange: Range
9
9
  }
10
10
 
11
11
  /**
@@ -18,15 +18,17 @@ function simplifyChangedRanges(changes: ChangedRange[]): ChangedRange[] {
18
18
  return uniqueChanges.length === 1
19
19
  ? uniqueChanges
20
20
  : uniqueChanges.filter((change, index) => {
21
- const rest = uniqueChanges.filter((_, i) => i !== index)
21
+ const rest = uniqueChanges.filter((_, i) => i !== index)
22
22
 
23
- return !rest.some(otherChange => {
24
- return change.oldRange.from >= otherChange.oldRange.from
25
- && change.oldRange.to <= otherChange.oldRange.to
26
- && change.newRange.from >= otherChange.newRange.from
27
- && change.newRange.to <= otherChange.newRange.to
23
+ return !rest.some(otherChange => {
24
+ return (
25
+ change.oldRange.from >= otherChange.oldRange.from &&
26
+ change.oldRange.to <= otherChange.oldRange.to &&
27
+ change.newRange.from >= otherChange.newRange.from &&
28
+ change.newRange.to <= otherChange.newRange.to
29
+ )
30
+ })
28
31
  })
29
- })
30
32
  }
31
33
 
32
34
  /**
@@ -45,8 +47,8 @@ export function getChangedRanges(transform: Transform): ChangedRange[] {
45
47
  // @ts-ignore
46
48
  if (!stepMap.ranges.length) {
47
49
  const { from, to } = steps[index] as Step & {
48
- from?: number,
49
- to?: number,
50
+ from?: number
51
+ to?: number
50
52
  }
51
53
 
52
54
  if (from === undefined || to === undefined) {
@@ -1,3 +1,6 @@
1
+ import { ExtensionConfig } from '../Extension.js'
2
+ import { MarkConfig } from '../Mark.js'
3
+ import { NodeConfig } from '../Node.js'
1
4
  import { AnyExtension, MaybeThisParameterType, RemoveThis } from '../types.js'
2
5
 
3
6
  /**
@@ -7,26 +10,23 @@ import { AnyExtension, MaybeThisParameterType, RemoveThis } from '../types.js'
7
10
  * @param context The context object that should be passed as `this` into the function
8
11
  * @returns The field value
9
12
  */
10
- export function getExtensionField<T = any>(
11
- extension: AnyExtension,
12
- field: string,
13
+ export function getExtensionField<T = any, E extends AnyExtension = any>(
14
+ extension: E,
15
+ field: keyof ExtensionConfig | keyof MarkConfig | keyof NodeConfig,
13
16
  context?: Omit<MaybeThisParameterType<T>, 'parent'>,
14
17
  ): RemoveThis<T> {
15
-
16
- if (extension.config[field] === undefined && extension.parent) {
18
+ if (extension.config[field as keyof typeof extension.config] === undefined && extension.parent) {
17
19
  return getExtensionField(extension.parent, field, context)
18
20
  }
19
21
 
20
- if (typeof extension.config[field] === 'function') {
21
- const value = extension.config[field].bind({
22
+ if (typeof extension.config[field as keyof typeof extension.config] === 'function') {
23
+ const value = (extension.config[field as keyof typeof extension.config] as any).bind({
22
24
  ...context,
23
- parent: extension.parent
24
- ? getExtensionField(extension.parent, field, context)
25
- : null,
25
+ parent: extension.parent ? getExtensionField(extension.parent, field, context) : null,
26
26
  })
27
27
 
28
28
  return value
29
29
  }
30
30
 
31
- return extension.config[field]
31
+ return extension.config[field as keyof typeof extension.config] as RemoveThis<T>
32
32
  }
@@ -3,10 +3,7 @@ import { EditorState } from '@tiptap/pm/state'
3
3
 
4
4
  import { getMarkType } from './getMarkType.js'
5
5
 
6
- export function getMarkAttributes(
7
- state: EditorState,
8
- typeOrName: string | MarkType,
9
- ): Record<string, any> {
6
+ export function getMarkAttributes(state: EditorState, typeOrName: string | MarkType): Record<string, any> {
10
7
  const type = getMarkType(typeOrName, state.schema)
11
8
  const { from, to, empty } = state.selection
12
9
  const marks: Mark[] = []