@tiptap/core 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/core",
3
3
  "description": "headless rich text editor",
4
- "version": "3.2.2",
4
+ "version": "3.3.0",
5
5
  "homepage": "https://tiptap.dev",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -52,10 +52,10 @@
52
52
  "jsx-dev-runtime"
53
53
  ],
54
54
  "devDependencies": {
55
- "@tiptap/pm": "^3.2.2"
55
+ "@tiptap/pm": "^3.3.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "@tiptap/pm": "^3.2.2"
58
+ "@tiptap/pm": "^3.3.0"
59
59
  },
60
60
  "repository": {
61
61
  "type": "git",
@@ -18,10 +18,8 @@ import {
18
18
  splitExtensions,
19
19
  } from './helpers/index.js'
20
20
  import { type MarkConfig, type NodeConfig, type Storage, getMarkType, updateMarkViewAttributes } from './index.js'
21
- import type { InputRule } from './InputRule.js'
22
21
  import { inputRulesPlugin } from './InputRule.js'
23
22
  import { Mark } from './Mark.js'
24
- import type { PasteRule } from './PasteRule.js'
25
23
  import { pasteRulesPlugin } from './PasteRule.js'
26
24
  import type { AnyConfig, Extensions, RawCommands } from './types.js'
27
25
  import { callOrReturn } from './utilities/callOrReturn.js'
@@ -89,9 +87,6 @@ export class ExtensionManager {
89
87
  // based on the `priority` option.
90
88
  const extensions = sortExtensions([...this.extensions].reverse())
91
89
 
92
- const inputRules: InputRule[] = []
93
- const pasteRules: PasteRule[] = []
94
-
95
90
  const allPlugins = extensions
96
91
  .map(extension => {
97
92
  const context = {
@@ -134,13 +129,30 @@ export class ExtensionManager {
134
129
  const addInputRules = getExtensionField<AnyConfig['addInputRules']>(extension, 'addInputRules', context)
135
130
 
136
131
  if (isExtensionRulesEnabled(extension, editor.options.enableInputRules) && addInputRules) {
137
- inputRules.push(...addInputRules())
132
+ const rules = addInputRules()
133
+
134
+ if (rules && rules.length) {
135
+ const inputResult = inputRulesPlugin({
136
+ editor,
137
+ rules,
138
+ })
139
+
140
+ const inputPlugins = Array.isArray(inputResult) ? inputResult : [inputResult]
141
+
142
+ plugins.push(...inputPlugins)
143
+ }
138
144
  }
139
145
 
140
146
  const addPasteRules = getExtensionField<AnyConfig['addPasteRules']>(extension, 'addPasteRules', context)
141
147
 
142
148
  if (isExtensionRulesEnabled(extension, editor.options.enablePasteRules) && addPasteRules) {
143
- pasteRules.push(...addPasteRules())
149
+ const rules = addPasteRules()
150
+
151
+ if (rules && rules.length) {
152
+ const pasteRules = pasteRulesPlugin({ editor, rules })
153
+
154
+ plugins.push(...pasteRules)
155
+ }
144
156
  }
145
157
 
146
158
  const addProseMirrorPlugins = getExtensionField<AnyConfig['addProseMirrorPlugins']>(
@@ -159,17 +171,7 @@ export class ExtensionManager {
159
171
  })
160
172
  .flat()
161
173
 
162
- return [
163
- inputRulesPlugin({
164
- editor,
165
- rules: inputRules,
166
- }),
167
- ...pasteRulesPlugin({
168
- editor,
169
- rules: pasteRules,
170
- }),
171
- ...allPlugins,
172
- ]
174
+ return allPlugins
173
175
  }
174
176
 
175
177
  /**
package/src/PasteRule.ts CHANGED
@@ -112,13 +112,30 @@ function run(config: {
112
112
  const handlers: (void | null)[] = []
113
113
 
114
114
  state.doc.nodesBetween(from, to, (node, pos) => {
115
- if (!node.isTextblock || node.type.spec.code) {
115
+ // Skip code blocks and non-textual nodes.
116
+ // Be defensive: `node` may be a Fragment without a `type`. Only text,
117
+ // inline, or textblock nodes are processed by paste rules.
118
+ if (node.type?.spec?.code || !(node.isText || node.isTextblock || node.isInline)) {
116
119
  return
117
120
  }
118
121
 
122
+ // For textblock and inline/text nodes, compute the range relative to the node.
123
+ // Prefer `node.nodeSize` when available (some Node shapes expose this),
124
+ // otherwise fall back to `node.content?.size`. Default to 0 if neither exists.
125
+ const contentSize = node.content?.size ?? node.nodeSize ?? 0
119
126
  const resolvedFrom = Math.max(from, pos)
120
- const resolvedTo = Math.min(to, pos + node.content.size)
121
- const textToMatch = node.textBetween(resolvedFrom - pos, resolvedTo - pos, undefined, '\ufffc')
127
+ const resolvedTo = Math.min(to, pos + contentSize)
128
+
129
+ // If the resolved range is empty or invalid for this node, skip it. This
130
+ // avoids calling `textBetween` with start > end which can cause internal
131
+ // Fragment/Node traversal to access undefined `nodeSize` values.
132
+ if (resolvedFrom >= resolvedTo) {
133
+ return
134
+ }
135
+
136
+ const textToMatch = node.isText
137
+ ? node.text || ''
138
+ : node.textBetween(resolvedFrom - pos, resolvedTo - pos, undefined, '\ufffc')
122
139
 
123
140
  const matches = pasteRuleMatcherHandler(textToMatch, rule.find, pasteEvent)
124
141