@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
@@ -10,8 +10,8 @@ function findMarkInSet(
10
10
  ): ProseMirrorMark | undefined {
11
11
  return marks.find(item => {
12
12
  return (
13
- item.type === type
14
- && objectIncludes(
13
+ item.type === type &&
14
+ objectIncludes(
15
15
  // Only check equality for the attributes that are provided
16
16
  Object.fromEntries(Object.keys(attributes).map(k => [k, item.attrs[k]])),
17
17
  attributes,
@@ -20,11 +20,7 @@ function findMarkInSet(
20
20
  })
21
21
  }
22
22
 
23
- function isMarkInSet(
24
- marks: ProseMirrorMark[],
25
- type: MarkType,
26
- attributes: Record<string, any> = {},
27
- ): boolean {
23
+ function isMarkInSet(marks: ProseMirrorMark[], type: MarkType, attributes: Record<string, any> = {}): boolean {
28
24
  return !!findMarkInSet(marks, type, attributes)
29
25
  }
30
26
 
@@ -77,18 +73,12 @@ export function getMarkRange(
77
73
  let endIndex = startIndex + 1
78
74
  let endPos = startPos + start.node.nodeSize
79
75
 
80
- while (
81
- startIndex > 0
82
- && isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)
83
- ) {
76
+ while (startIndex > 0 && isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)) {
84
77
  startIndex -= 1
85
78
  startPos -= $pos.parent.child(startIndex).nodeSize
86
79
  }
87
80
 
88
- while (
89
- endIndex < $pos.parent.childCount
90
- && isMarkInSet([...$pos.parent.child(endIndex).marks], type, attributes)
91
- ) {
81
+ while (endIndex < $pos.parent.childCount && isMarkInSet([...$pos.parent.child(endIndex).marks], type, attributes)) {
92
82
  endPos += $pos.parent.child(endIndex).nodeSize
93
83
  endIndex += 1
94
84
  }
@@ -3,9 +3,7 @@ import { MarkType, Schema } from '@tiptap/pm/model'
3
3
  export function getMarkType(nameOrType: string | MarkType, schema: Schema): MarkType {
4
4
  if (typeof nameOrType === 'string') {
5
5
  if (!schema.marks[nameOrType]) {
6
- throw Error(
7
- `There is no mark type named '${nameOrType}'. Maybe you forgot to add the extension?`,
8
- )
6
+ throw Error(`There is no mark type named '${nameOrType}'. Maybe you forgot to add the extension?`)
9
7
  }
10
8
 
11
9
  return schema.marks[nameOrType]
@@ -3,10 +3,7 @@ import { EditorState } from '@tiptap/pm/state'
3
3
 
4
4
  import { getNodeType } from './getNodeType.js'
5
5
 
6
- export function getNodeAttributes(
7
- state: EditorState,
8
- typeOrName: string | NodeType,
9
- ): Record<string, any> {
6
+ export function getNodeAttributes(state: EditorState, typeOrName: string | NodeType): Record<string, any> {
10
7
  const type = getNodeType(typeOrName, state.schema)
11
8
  const { from, to } = state.selection
12
9
  const nodes: Node[] = []
@@ -3,9 +3,7 @@ import { NodeType, Schema } from '@tiptap/pm/model'
3
3
  export function getNodeType(nameOrType: string | NodeType, schema: Schema): NodeType {
4
4
  if (typeof nameOrType === 'string') {
5
5
  if (!schema.nodes[nameOrType]) {
6
- throw Error(
7
- `There is no node type named '${nameOrType}'. Maybe you forgot to add the extension?`,
8
- )
6
+ throw Error(`There is no node type named '${nameOrType}'. Maybe you forgot to add the extension?`)
9
7
  }
10
8
 
11
9
  return schema.nodes[nameOrType]
@@ -8,9 +8,7 @@ export function getRenderedAttributes(
8
8
  extensionAttributes: ExtensionAttribute[],
9
9
  ): Record<string, any> {
10
10
  return extensionAttributes
11
- .filter(
12
- attribute => attribute.type === nodeOrMark.type.name,
13
- )
11
+ .filter(attribute => attribute.type === nodeOrMark.type.name)
14
12
  .filter(item => item.attribute.rendered)
15
13
  .map(item => {
16
14
  if (!item.attribute.renderHTML) {
@@ -1,12 +1,12 @@
1
1
  import { Schema } from '@tiptap/pm/model'
2
2
 
3
3
  import { Editor } from '../Editor.js'
4
- import { ExtensionManager } from '../ExtensionManager.js'
5
4
  import { Extensions } from '../types.js'
6
5
  import { getSchemaByResolvedExtensions } from './getSchemaByResolvedExtensions.js'
6
+ import { resolveExtensions } from './resolveExtensions.js'
7
7
 
8
8
  export function getSchema(extensions: Extensions, editor?: Editor): Schema {
9
- const resolvedExtensions = ExtensionManager.resolve(extensions)
9
+ const resolvedExtensions = resolveExtensions(extensions)
10
10
 
11
11
  return getSchemaByResolvedExtensions(resolvedExtensions, editor)
12
12
  }
@@ -1,6 +1,4 @@
1
- import {
2
- MarkSpec, NodeSpec, Schema, TagParseRule,
3
- } from '@tiptap/pm/model'
1
+ import { MarkSpec, NodeSpec, Schema, TagParseRule } from '@tiptap/pm/model'
4
2
 
5
3
  import type { Editor, MarkConfig, NodeConfig } from '../index.js'
6
4
  import { AnyConfig, Extensions } from '../types.js'
@@ -16,7 +14,7 @@ function cleanUpSchemaItem<T>(data: T) {
16
14
  return Object.fromEntries(
17
15
  // @ts-ignore
18
16
  Object.entries(data).filter(([key, value]) => {
19
- if (key === 'attrs' && isEmptyObject(value as {} | undefined)) {
17
+ if (key === 'attrs' && isEmptyObject(value as object | undefined)) {
20
18
  return false
21
19
  }
22
20
 
@@ -38,9 +36,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
38
36
 
39
37
  const nodes = Object.fromEntries(
40
38
  nodeExtensions.map(extension => {
41
- const extensionAttributes = allAttributes.filter(
42
- attribute => attribute.type === extension.name,
43
- )
39
+ const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
44
40
  const context = {
45
41
  name: extension.name,
46
42
  options: extension.options,
@@ -49,11 +45,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
49
45
  }
50
46
 
51
47
  const extraNodeFields = extensions.reduce((fields, e) => {
52
- const extendNodeSchema = getExtensionField<AnyConfig['extendNodeSchema']>(
53
- e,
54
- 'extendNodeSchema',
55
- context,
56
- )
48
+ const extendNodeSchema = getExtensionField<AnyConfig['extendNodeSchema']>(e, 'extendNodeSchema', context)
57
49
 
58
50
  return {
59
51
  ...fields,
@@ -63,61 +55,49 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
63
55
 
64
56
  const schema: NodeSpec = cleanUpSchemaItem({
65
57
  ...extraNodeFields,
66
- content: callOrReturn(
67
- getExtensionField<NodeConfig['content']>(extension, 'content', context),
68
- ),
58
+ content: callOrReturn(getExtensionField<NodeConfig['content']>(extension, 'content', context)),
69
59
  marks: callOrReturn(getExtensionField<NodeConfig['marks']>(extension, 'marks', context)),
70
60
  group: callOrReturn(getExtensionField<NodeConfig['group']>(extension, 'group', context)),
71
61
  inline: callOrReturn(getExtensionField<NodeConfig['inline']>(extension, 'inline', context)),
72
62
  atom: callOrReturn(getExtensionField<NodeConfig['atom']>(extension, 'atom', context)),
73
- selectable: callOrReturn(
74
- getExtensionField<NodeConfig['selectable']>(extension, 'selectable', context),
75
- ),
76
- draggable: callOrReturn(
77
- getExtensionField<NodeConfig['draggable']>(extension, 'draggable', context),
78
- ),
63
+ selectable: callOrReturn(getExtensionField<NodeConfig['selectable']>(extension, 'selectable', context)),
64
+ draggable: callOrReturn(getExtensionField<NodeConfig['draggable']>(extension, 'draggable', context)),
79
65
  code: callOrReturn(getExtensionField<NodeConfig['code']>(extension, 'code', context)),
80
66
  whitespace: callOrReturn(getExtensionField<NodeConfig['whitespace']>(extension, 'whitespace', context)),
81
- linebreakReplacement: callOrReturn(getExtensionField<NodeConfig['linebreakReplacement']>(extension, 'linebreakReplacement', context)),
82
- defining: callOrReturn(
83
- getExtensionField<NodeConfig['defining']>(extension, 'defining', context),
84
- ),
85
- isolating: callOrReturn(
86
- getExtensionField<NodeConfig['isolating']>(extension, 'isolating', context),
67
+ linebreakReplacement: callOrReturn(
68
+ getExtensionField<NodeConfig['linebreakReplacement']>(extension, 'linebreakReplacement', context),
87
69
  ),
70
+ defining: callOrReturn(getExtensionField<NodeConfig['defining']>(extension, 'defining', context)),
71
+ isolating: callOrReturn(getExtensionField<NodeConfig['isolating']>(extension, 'isolating', context)),
88
72
  attrs: Object.fromEntries(
89
73
  extensionAttributes.map(extensionAttribute => {
90
- return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
74
+ return [
75
+ extensionAttribute.name,
76
+ { default: extensionAttribute?.attribute?.default, validate: extensionAttribute?.attribute?.validate },
77
+ ]
91
78
  }),
92
79
  ),
93
80
  })
94
81
 
95
- const parseHTML = callOrReturn(
96
- getExtensionField<NodeConfig['parseHTML']>(extension, 'parseHTML', context),
97
- )
82
+ const parseHTML = callOrReturn(getExtensionField<NodeConfig['parseHTML']>(extension, 'parseHTML', context))
98
83
 
99
84
  if (parseHTML) {
100
- schema.parseDOM = parseHTML.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes)) as TagParseRule[]
85
+ schema.parseDOM = parseHTML.map(parseRule =>
86
+ injectExtensionAttributesToParseRule(parseRule, extensionAttributes),
87
+ ) as TagParseRule[]
101
88
  }
102
89
 
103
- const renderHTML = getExtensionField<NodeConfig['renderHTML']>(
104
- extension,
105
- 'renderHTML',
106
- context,
107
- )
90
+ const renderHTML = getExtensionField<NodeConfig['renderHTML']>(extension, 'renderHTML', context)
108
91
 
109
92
  if (renderHTML) {
110
- schema.toDOM = node => renderHTML({
111
- node,
112
- HTMLAttributes: getRenderedAttributes(node, extensionAttributes),
113
- })
93
+ schema.toDOM = node =>
94
+ renderHTML({
95
+ node,
96
+ HTMLAttributes: getRenderedAttributes(node, extensionAttributes),
97
+ })
114
98
  }
115
99
 
116
- const renderText = getExtensionField<NodeConfig['renderText']>(
117
- extension,
118
- 'renderText',
119
- context,
120
- )
100
+ const renderText = getExtensionField<NodeConfig['renderText']>(extension, 'renderText', context)
121
101
 
122
102
  if (renderText) {
123
103
  schema.toText = renderText
@@ -129,9 +109,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
129
109
 
130
110
  const marks = Object.fromEntries(
131
111
  markExtensions.map(extension => {
132
- const extensionAttributes = allAttributes.filter(
133
- attribute => attribute.type === extension.name,
134
- )
112
+ const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
135
113
  const context = {
136
114
  name: extension.name,
137
115
  options: extension.options,
@@ -140,11 +118,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
140
118
  }
141
119
 
142
120
  const extraMarkFields = extensions.reduce((fields, e) => {
143
- const extendMarkSchema = getExtensionField<AnyConfig['extendMarkSchema']>(
144
- e,
145
- 'extendMarkSchema',
146
- context,
147
- )
121
+ const extendMarkSchema = getExtensionField<AnyConfig['extendMarkSchema']>(e, 'extendMarkSchema', context)
148
122
 
149
123
  return {
150
124
  ...fields,
@@ -154,43 +128,37 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
154
128
 
155
129
  const schema: MarkSpec = cleanUpSchemaItem({
156
130
  ...extraMarkFields,
157
- inclusive: callOrReturn(
158
- getExtensionField<MarkConfig['inclusive']>(extension, 'inclusive', context),
159
- ),
160
- excludes: callOrReturn(
161
- getExtensionField<MarkConfig['excludes']>(extension, 'excludes', context),
162
- ),
131
+ inclusive: callOrReturn(getExtensionField<MarkConfig['inclusive']>(extension, 'inclusive', context)),
132
+ excludes: callOrReturn(getExtensionField<MarkConfig['excludes']>(extension, 'excludes', context)),
163
133
  group: callOrReturn(getExtensionField<MarkConfig['group']>(extension, 'group', context)),
164
- spanning: callOrReturn(
165
- getExtensionField<MarkConfig['spanning']>(extension, 'spanning', context),
166
- ),
134
+ spanning: callOrReturn(getExtensionField<MarkConfig['spanning']>(extension, 'spanning', context)),
167
135
  code: callOrReturn(getExtensionField<MarkConfig['code']>(extension, 'code', context)),
168
136
  attrs: Object.fromEntries(
169
137
  extensionAttributes.map(extensionAttribute => {
170
- return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
138
+ return [
139
+ extensionAttribute.name,
140
+ { default: extensionAttribute?.attribute?.default, validate: extensionAttribute?.attribute?.validate },
141
+ ]
171
142
  }),
172
143
  ),
173
144
  })
174
145
 
175
- const parseHTML = callOrReturn(
176
- getExtensionField<MarkConfig['parseHTML']>(extension, 'parseHTML', context),
177
- )
146
+ const parseHTML = callOrReturn(getExtensionField<MarkConfig['parseHTML']>(extension, 'parseHTML', context))
178
147
 
179
148
  if (parseHTML) {
180
- schema.parseDOM = parseHTML.map(parseRule => injectExtensionAttributesToParseRule(parseRule, extensionAttributes))
149
+ schema.parseDOM = parseHTML.map(parseRule =>
150
+ injectExtensionAttributesToParseRule(parseRule, extensionAttributes),
151
+ )
181
152
  }
182
153
 
183
- const renderHTML = getExtensionField<MarkConfig['renderHTML']>(
184
- extension,
185
- 'renderHTML',
186
- context,
187
- )
154
+ const renderHTML = getExtensionField<MarkConfig['renderHTML']>(extension, 'renderHTML', context)
188
155
 
189
156
  if (renderHTML) {
190
- schema.toDOM = mark => renderHTML({
191
- mark,
192
- HTMLAttributes: getRenderedAttributes(mark, extensionAttributes),
193
- })
157
+ schema.toDOM = mark =>
158
+ renderHTML({
159
+ mark,
160
+ HTMLAttributes: getRenderedAttributes(mark, extensionAttributes),
161
+ })
194
162
  }
195
163
 
196
164
  return [extension.name, schema]
@@ -12,9 +12,8 @@ export function getSplittedAttributes(
12
12
  typeName: string,
13
13
  attributes: Record<string, any>,
14
14
  ): Record<string, any> {
15
- return Object.fromEntries(Object
16
- .entries(attributes)
17
- .filter(([name]) => {
15
+ return Object.fromEntries(
16
+ Object.entries(attributes).filter(([name]) => {
18
17
  const extensionAttribute = extensionAttributes.find(item => {
19
18
  return item.type === typeName && item.name === name
20
19
  })
@@ -24,5 +23,6 @@ export function getSplittedAttributes(
24
23
  }
25
24
 
26
25
  return extensionAttribute.attribute.keepOnSplit
27
- }))
26
+ }),
27
+ )
28
28
  }
@@ -11,22 +11,19 @@ export const getTextContentFromNodes = ($from: ResolvedPos, maxMatch = 500) => {
11
11
 
12
12
  const sliceEndPos = $from.parentOffset
13
13
 
14
- $from.parent.nodesBetween(
15
- Math.max(0, sliceEndPos - maxMatch),
16
- sliceEndPos,
17
- (node, pos, parent, index) => {
18
- const chunk = node.type.spec.toText?.({
14
+ $from.parent.nodesBetween(Math.max(0, sliceEndPos - maxMatch), sliceEndPos, (node, pos, parent, index) => {
15
+ const chunk =
16
+ node.type.spec.toText?.({
19
17
  node,
20
18
  pos,
21
19
  parent,
22
20
  index,
23
- })
24
- || node.textContent
25
- || '%leaf%'
21
+ }) ||
22
+ node.textContent ||
23
+ '%leaf%'
26
24
 
27
- textBefore += node.isAtom && !node.isText ? chunk : chunk.slice(0, Math.max(0, sliceEndPos - pos))
28
- },
29
- )
25
+ textBefore += node.isAtom && !node.isText ? chunk : chunk.slice(0, Math.max(0, sliceEndPos - pos))
26
+ })
30
27
 
31
28
  return textBefore
32
29
  }
@@ -7,6 +7,7 @@ export * from './findChildren.js'
7
7
  export * from './findChildrenInRange.js'
8
8
  export * from './findParentNode.js'
9
9
  export * from './findParentNodeClosestToPos.js'
10
+ export * from './flattenExtensions.js'
10
11
  export * from './generateHTML.js'
11
12
  export * from './generateJSON.js'
12
13
  export * from './generateText.js'
@@ -45,6 +46,9 @@ export * from './isNodeEmpty.js'
45
46
  export * from './isNodeSelection.js'
46
47
  export * from './isTextSelection.js'
47
48
  export * from './posToDOMRect.js'
49
+ export * from './resolveExtensions.js'
48
50
  export * from './resolveFocusPosition.js'
51
+ export * from './rewriteUnknownContent.js'
49
52
  export * from './selectionToInsertionEnd.js'
53
+ export * from './sortExtensions.js'
50
54
  export * from './splitExtensions.js'
@@ -29,7 +29,7 @@ export function injectExtensionAttributesToParseRule(
29
29
  const newAttributes = extensionAttributes.reduce((items, item) => {
30
30
  const value = item.attribute.parseHTML
31
31
  ? item.attribute.parseHTML(node)
32
- : fromString((node).getAttribute(item.name))
32
+ : fromString(node.getAttribute(item.name))
33
33
 
34
34
  if (value === null || value === undefined) {
35
35
  return items
@@ -4,11 +4,7 @@ import { getSchemaTypeNameByName } from './getSchemaTypeNameByName.js'
4
4
  import { isMarkActive } from './isMarkActive.js'
5
5
  import { isNodeActive } from './isNodeActive.js'
6
6
 
7
- export function isActive(
8
- state: EditorState,
9
- name: string | null,
10
- attributes: Record<string, any> = {},
11
- ): boolean {
7
+ export function isActive(state: EditorState, name: string | null, attributes: Record<string, any> = {}): boolean {
12
8
  if (!name) {
13
9
  return isNodeActive(state, null, attributes) || isMarkActive(state, null, attributes)
14
10
  }
@@ -3,9 +3,7 @@ import { AnyExtension, EnableRules } from '../types.js'
3
3
  export function isExtensionRulesEnabled(extension: AnyExtension, enabled: EnableRules): boolean {
4
4
  if (Array.isArray(enabled)) {
5
5
  return enabled.some(enabledExtension => {
6
- const name = typeof enabledExtension === 'string'
7
- ? enabledExtension
8
- : enabledExtension.name
6
+ const name = typeof enabledExtension === 'string' ? enabledExtension : enabledExtension.name
9
7
 
10
8
  return name === extension.name
11
9
  })
@@ -12,11 +12,11 @@ export function isNodeEmpty(
12
12
  /**
13
13
  * When true (default), it will also check if all children are empty.
14
14
  */
15
- checkChildren?: boolean;
15
+ checkChildren?: boolean
16
16
  /**
17
17
  * When true, it will ignore whitespace when checking for emptiness.
18
18
  */
19
- ignoreWhitespace?: boolean;
19
+ ignoreWhitespace?: boolean
20
20
  } = {},
21
21
  ): boolean {
22
22
  if (ignoreWhitespace) {
@@ -0,0 +1,25 @@
1
+ import { Extensions } from '../types.js'
2
+ import { findDuplicates } from '../utilities/findDuplicates.js'
3
+ import { flattenExtensions } from './flattenExtensions.js'
4
+ import { sortExtensions } from './sortExtensions.js'
5
+
6
+ /**
7
+ * Returns a flattened and sorted extension list while
8
+ * also checking for duplicated extensions and warns the user.
9
+ * @param extensions An array of Tiptap extensions
10
+ * @returns An flattened and sorted array of Tiptap extensions
11
+ */
12
+ export function resolveExtensions(extensions: Extensions): Extensions {
13
+ const resolvedExtensions = sortExtensions(flattenExtensions(extensions))
14
+ const duplicatedNames = findDuplicates(resolvedExtensions.map(extension => extension.name))
15
+
16
+ if (duplicatedNames.length) {
17
+ console.warn(
18
+ `[tiptap warn]: Duplicate extension names found: [${duplicatedNames
19
+ .map(item => `'${item}'`)
20
+ .join(', ')}]. This can lead to issues.`,
21
+ )
22
+ }
23
+
24
+ return resolvedExtensions
25
+ }
@@ -4,10 +4,7 @@ import { Selection, TextSelection } from '@tiptap/pm/state'
4
4
  import { FocusPosition } from '../types.js'
5
5
  import { minMax } from '../utilities/minMax.js'
6
6
 
7
- export function resolveFocusPosition(
8
- doc: ProseMirrorNode,
9
- position: FocusPosition = null,
10
- ): Selection | null {
7
+ export function resolveFocusPosition(doc: ProseMirrorNode, position: FocusPosition = null): Selection | null {
11
8
  if (!position) {
12
9
  return null
13
10
  }
@@ -27,16 +24,8 @@ export function resolveFocusPosition(
27
24
  const maxPos = selectionAtEnd.to
28
25
 
29
26
  if (position === 'all') {
30
- return TextSelection.create(
31
- doc,
32
- minMax(0, minPos, maxPos),
33
- minMax(doc.content.size, minPos, maxPos),
34
- )
27
+ return TextSelection.create(doc, minMax(0, minPos, maxPos), minMax(doc.content.size, minPos, maxPos))
35
28
  }
36
29
 
37
- return TextSelection.create(
38
- doc,
39
- minMax(position, minPos, maxPos),
40
- minMax(position, minPos, maxPos),
41
- )
30
+ return TextSelection.create(doc, minMax(position, minPos, maxPos), minMax(position, minPos, maxPos))
42
31
  }
@@ -0,0 +1,149 @@
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 =>
70
+ rewriteUnknownContentInner({
71
+ json: value,
72
+ validMarks,
73
+ validNodes,
74
+ options,
75
+ rewrittenContent,
76
+ }).json,
77
+ )
78
+ .filter(a => a !== null && a !== undefined)
79
+ }
80
+
81
+ if (json.type && !validNodes.has(json.type)) {
82
+ rewrittenContent.push({
83
+ original: JSON.parse(JSON.stringify(json)),
84
+ unsupported: json.type,
85
+ })
86
+
87
+ if (json.content && Array.isArray(json.content) && options?.fallbackToParagraph !== false) {
88
+ // Just treat it like a paragraph and hope for the best
89
+ json.type = 'paragraph'
90
+
91
+ return {
92
+ json,
93
+ rewrittenContent,
94
+ }
95
+ }
96
+
97
+ // or just omit it entirely
98
+ return {
99
+ json: null,
100
+ rewrittenContent,
101
+ }
102
+ }
103
+
104
+ return { json, rewrittenContent }
105
+ }
106
+
107
+ /**
108
+ * Rewrite unknown nodes and marks within JSON content
109
+ * Allowing for user within the editor
110
+ */
111
+ export function rewriteUnknownContent(
112
+ /**
113
+ * The JSON content to clean of unknown nodes and marks
114
+ */
115
+ json: JSONContent,
116
+ /**
117
+ * The schema to use for validation
118
+ */
119
+ schema: Schema,
120
+ /**
121
+ * Options for the cleaning process
122
+ */
123
+ options?: RewriteUnknownContentOptions,
124
+ ): {
125
+ /**
126
+ * The cleaned JSON content
127
+ */
128
+ json: JSONContent | null
129
+ /**
130
+ * The array of nodes and marks that were rewritten
131
+ */
132
+ rewrittenContent: {
133
+ /**
134
+ * The original JSON content that was rewritten
135
+ */
136
+ original: JSONContent
137
+ /**
138
+ * The name of the node or mark that was unsupported
139
+ */
140
+ unsupported: string
141
+ }[]
142
+ } {
143
+ return rewriteUnknownContentInner({
144
+ json,
145
+ validNodes: new Set(Object.keys(schema.nodes)),
146
+ validMarks: new Set(Object.keys(schema.marks)),
147
+ options,
148
+ })
149
+ }
@@ -0,0 +1,26 @@
1
+ import { AnyConfig, Extensions } from '../types.js'
2
+ import { getExtensionField } from './getExtensionField.js'
3
+
4
+ /**
5
+ * Sort extensions by priority.
6
+ * @param extensions An array of Tiptap extensions
7
+ * @returns A sorted array of Tiptap extensions by priority
8
+ */
9
+ export function sortExtensions(extensions: Extensions): Extensions {
10
+ const defaultPriority = 100
11
+
12
+ return extensions.sort((a, b) => {
13
+ const priorityA = getExtensionField<AnyConfig['priority']>(a, 'priority') || defaultPriority
14
+ const priorityB = getExtensionField<AnyConfig['priority']>(b, 'priority') || defaultPriority
15
+
16
+ if (priorityA > priorityB) {
17
+ return -1
18
+ }
19
+
20
+ if (priorityA < priorityB) {
21
+ return 1
22
+ }
23
+
24
+ return 0
25
+ })
26
+ }