@tiptap/core 3.0.0-next.8 → 3.0.1
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/dist/index.cjs +140 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -8
- package/dist/index.d.ts +38 -8
- package/dist/index.js +138 -28
- package/dist/index.js.map +1 -1
- package/jsx-dev-runtime/index.cjs +1 -0
- package/jsx-dev-runtime/index.d.cts +1 -0
- package/jsx-dev-runtime/index.d.ts +1 -0
- package/jsx-dev-runtime/index.js +1 -0
- package/jsx-runtime/index.cjs +1 -1
- package/package.json +5 -4
- package/src/Editor.ts +4 -3
- package/src/Extension.ts +14 -4
- package/src/ExtensionManager.ts +4 -1
- package/src/Mark.ts +12 -4
- package/src/MarkView.ts +56 -0
- package/src/Node.ts +12 -4
- package/src/NodePos.ts +6 -0
- package/src/NodeView.ts +3 -1
- package/src/PasteRule.ts +1 -1
- package/src/commands/cut.ts +1 -1
- package/src/commands/insertContentAt.ts +31 -11
- package/src/types.ts +12 -1
- package/src/utilities/canInsertNode.ts +30 -0
- package/src/utilities/index.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('../dist/jsx-runtime/jsx-runtime.cjs')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../src/jsx-runtime.ts'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type * from '../src/jsx-runtime.js'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../dist/jsx-runtime/jsx-runtime.js'
|
package/jsx-runtime/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
module.exports = require('../dist/jsx-runtime/jsx-runtime.cjs')
|
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.0.
|
|
4
|
+
"version": "3.0.1",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -48,13 +48,14 @@
|
|
|
48
48
|
"files": [
|
|
49
49
|
"src",
|
|
50
50
|
"dist",
|
|
51
|
-
"jsx-runtime"
|
|
51
|
+
"jsx-runtime",
|
|
52
|
+
"jsx-dev-runtime"
|
|
52
53
|
],
|
|
53
54
|
"devDependencies": {
|
|
54
|
-
"@tiptap/pm": "^3.0.
|
|
55
|
+
"@tiptap/pm": "^3.0.1"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
57
|
-
"@tiptap/pm": "^3.0.
|
|
58
|
+
"@tiptap/pm": "^3.0.1"
|
|
58
59
|
},
|
|
59
60
|
"repository": {
|
|
60
61
|
"type": "git",
|
package/src/Editor.ts
CHANGED
|
@@ -92,6 +92,7 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
92
92
|
enablePasteRules: true,
|
|
93
93
|
enableCoreExtensions: true,
|
|
94
94
|
enableContentCheck: false,
|
|
95
|
+
emitContentError: false,
|
|
95
96
|
onBeforeCreate: () => null,
|
|
96
97
|
onCreate: () => null,
|
|
97
98
|
onUpdate: () => null,
|
|
@@ -287,6 +288,7 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
287
288
|
composing: false,
|
|
288
289
|
dragging: null,
|
|
289
290
|
editable: true,
|
|
291
|
+
isDestroyed: false,
|
|
290
292
|
} as EditorView,
|
|
291
293
|
{
|
|
292
294
|
get: (obj, key) => {
|
|
@@ -361,7 +363,7 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
361
363
|
const name = typeof nameOrPluginKey === 'string' ? `${nameOrPluginKey}$` : nameOrPluginKey.key
|
|
362
364
|
|
|
363
365
|
// @ts-ignore
|
|
364
|
-
plugins =
|
|
366
|
+
plugins = plugins.filter(plugin => !plugin.key.startsWith(name))
|
|
365
367
|
})
|
|
366
368
|
|
|
367
369
|
if (prevPlugins.length === plugins.length) {
|
|
@@ -717,8 +719,7 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
717
719
|
* Check if the editor is already destroyed.
|
|
718
720
|
*/
|
|
719
721
|
public get isDestroyed(): boolean {
|
|
720
|
-
|
|
721
|
-
return !this.view?.docView
|
|
722
|
+
return this.editorView?.isDestroyed ?? true
|
|
722
723
|
}
|
|
723
724
|
|
|
724
725
|
public $node(selector: string, attributes?: { [key: string]: any }): NodePos | null {
|
package/src/Extension.ts
CHANGED
|
@@ -15,8 +15,16 @@ export class Extension<Options = any, Storage = any> extends Extendable<
|
|
|
15
15
|
> {
|
|
16
16
|
type = 'extension'
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Create a new Extension instance
|
|
20
|
+
* @param config - Extension configuration object or a function that returns a configuration object
|
|
21
|
+
*/
|
|
22
|
+
static create<O = any, S = any>(
|
|
23
|
+
config: Partial<ExtensionConfig<O, S>> | (() => Partial<ExtensionConfig<O, S>>) = {},
|
|
24
|
+
) {
|
|
25
|
+
// If the config is a function, execute it to get the configuration object
|
|
26
|
+
const resolvedConfig = typeof config === 'function' ? config() : config
|
|
27
|
+
return new Extension<O, S>(resolvedConfig)
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
configure(options?: Partial<Options>) {
|
|
@@ -27,7 +35,9 @@ export class Extension<Options = any, Storage = any> extends Extendable<
|
|
|
27
35
|
ExtendedOptions = Options,
|
|
28
36
|
ExtendedStorage = Storage,
|
|
29
37
|
ExtendedConfig = ExtensionConfig<ExtendedOptions, ExtendedStorage>,
|
|
30
|
-
>(extendedConfig?: Partial<ExtendedConfig>) {
|
|
31
|
-
|
|
38
|
+
>(extendedConfig?: Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)) {
|
|
39
|
+
// If the extended config is a function, execute it to get the configuration object
|
|
40
|
+
const resolvedConfig = typeof extendedConfig === 'function' ? extendedConfig() : extendedConfig
|
|
41
|
+
return super.extend(resolvedConfig) as Extension<ExtendedOptions, ExtendedStorage>
|
|
32
42
|
}
|
|
33
43
|
}
|
package/src/ExtensionManager.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
sortExtensions,
|
|
18
18
|
splitExtensions,
|
|
19
19
|
} from './helpers/index.js'
|
|
20
|
-
import { type MarkConfig, type NodeConfig, type Storage, getMarkType } from './index.js'
|
|
20
|
+
import { type MarkConfig, type NodeConfig, type Storage, getMarkType, updateMarkViewAttributes } from './index.js'
|
|
21
21
|
import type { InputRule } from './InputRule.js'
|
|
22
22
|
import { inputRulesPlugin } from './InputRule.js'
|
|
23
23
|
import { Mark } from './Mark.js'
|
|
@@ -262,6 +262,9 @@ export class ExtensionManager {
|
|
|
262
262
|
editor,
|
|
263
263
|
extension,
|
|
264
264
|
HTMLAttributes,
|
|
265
|
+
updateAttributes: (attrs: Record<string, any>) => {
|
|
266
|
+
updateMarkViewAttributes(mark, editor, attrs)
|
|
267
|
+
},
|
|
265
268
|
})
|
|
266
269
|
}
|
|
267
270
|
|
package/src/Mark.ts
CHANGED
|
@@ -146,8 +146,14 @@ export interface MarkConfig<Options = any, Storage = any>
|
|
|
146
146
|
export class Mark<Options = any, Storage = any> extends Extendable<Options, Storage, MarkConfig<Options, Storage>> {
|
|
147
147
|
type = 'mark'
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Create a new Mark instance
|
|
151
|
+
* @param config - Mark configuration object or a function that returns a configuration object
|
|
152
|
+
*/
|
|
153
|
+
static create<O = any, S = any>(config: Partial<MarkConfig<O, S>> | (() => Partial<MarkConfig<O, S>>) = {}) {
|
|
154
|
+
// If the config is a function, execute it to get the configuration object
|
|
155
|
+
const resolvedConfig = typeof config === 'function' ? config() : config
|
|
156
|
+
return new Mark<O, S>(resolvedConfig)
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
static handleExit({ editor, mark }: { editor: Editor; mark: Mark }) {
|
|
@@ -186,7 +192,9 @@ export class Mark<Options = any, Storage = any> extends Extendable<Options, Stor
|
|
|
186
192
|
ExtendedOptions = Options,
|
|
187
193
|
ExtendedStorage = Storage,
|
|
188
194
|
ExtendedConfig = MarkConfig<ExtendedOptions, ExtendedStorage>,
|
|
189
|
-
>(extendedConfig?: Partial<ExtendedConfig>) {
|
|
190
|
-
|
|
195
|
+
>(extendedConfig?: Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)) {
|
|
196
|
+
// If the extended config is a function, execute it to get the configuration object
|
|
197
|
+
const resolvedConfig = typeof extendedConfig === 'function' ? extendedConfig() : extendedConfig
|
|
198
|
+
return super.extend(resolvedConfig) as Mark<ExtendedOptions, ExtendedStorage>
|
|
191
199
|
}
|
|
192
200
|
}
|
package/src/MarkView.ts
CHANGED
|
@@ -1,9 +1,57 @@
|
|
|
1
|
+
import type { Mark } from '@tiptap/pm/model'
|
|
1
2
|
import type { ViewMutationRecord } from '@tiptap/pm/view'
|
|
2
3
|
|
|
3
4
|
import type { Editor } from './Editor.js'
|
|
4
5
|
import type { MarkViewProps, MarkViewRendererOptions } from './types.js'
|
|
5
6
|
import { isAndroid, isiOS } from './utilities/index.js'
|
|
6
7
|
|
|
8
|
+
export function updateMarkViewAttributes(checkMark: Mark, editor: Editor, attrs: Record<string, any> = {}): void {
|
|
9
|
+
const { state } = editor
|
|
10
|
+
const { doc, tr } = state
|
|
11
|
+
const thisMark = checkMark
|
|
12
|
+
|
|
13
|
+
doc.descendants((node, pos) => {
|
|
14
|
+
const from = tr.mapping.map(pos)
|
|
15
|
+
const to = tr.mapping.map(pos) + node.nodeSize
|
|
16
|
+
let foundMark: Mark | null = null
|
|
17
|
+
|
|
18
|
+
// find the mark on the current node
|
|
19
|
+
node.marks.forEach(mark => {
|
|
20
|
+
if (mark !== thisMark) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
foundMark = mark
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (!foundMark) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// check if we need to update given the attributes
|
|
32
|
+
let needsUpdate = false
|
|
33
|
+
Object.keys(attrs).forEach(k => {
|
|
34
|
+
if (attrs[k] !== foundMark!.attrs[k]) {
|
|
35
|
+
needsUpdate = true
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (needsUpdate) {
|
|
40
|
+
const updatedMark = checkMark.type.create({
|
|
41
|
+
...checkMark.attrs,
|
|
42
|
+
...attrs,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
tr.removeMark(from, to, checkMark.type)
|
|
46
|
+
tr.addMark(from, to, updatedMark)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (tr.docChanged) {
|
|
51
|
+
editor.view.dispatch(tr)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
7
55
|
export class MarkView<Component, Options extends MarkViewRendererOptions = MarkViewRendererOptions> {
|
|
8
56
|
component: Component
|
|
9
57
|
editor: Editor
|
|
@@ -27,6 +75,14 @@ export class MarkView<Component, Options extends MarkViewRendererOptions = MarkV
|
|
|
27
75
|
return null
|
|
28
76
|
}
|
|
29
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Update the attributes of the mark in the document.
|
|
80
|
+
* @param attrs The attributes to update.
|
|
81
|
+
*/
|
|
82
|
+
updateAttributes(attrs: Record<string, any>, checkMark?: Mark): void {
|
|
83
|
+
updateMarkViewAttributes(checkMark || this.mark, this.editor, attrs)
|
|
84
|
+
}
|
|
85
|
+
|
|
30
86
|
ignoreMutation(mutation: ViewMutationRecord): boolean {
|
|
31
87
|
if (!this.dom || !this.contentDOM) {
|
|
32
88
|
return true
|
package/src/Node.ts
CHANGED
|
@@ -340,8 +340,14 @@ export interface NodeConfig<Options = any, Storage = any>
|
|
|
340
340
|
export class Node<Options = any, Storage = any> extends Extendable<Options, Storage, NodeConfig<Options, Storage>> {
|
|
341
341
|
type = 'node'
|
|
342
342
|
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
/**
|
|
344
|
+
* Create a new Node instance
|
|
345
|
+
* @param config - Node configuration object or a function that returns a configuration object
|
|
346
|
+
*/
|
|
347
|
+
static create<O = any, S = any>(config: Partial<NodeConfig<O, S>> | (() => Partial<NodeConfig<O, S>>) = {}) {
|
|
348
|
+
// If the config is a function, execute it to get the configuration object
|
|
349
|
+
const resolvedConfig = typeof config === 'function' ? config() : config
|
|
350
|
+
return new Node<O, S>(resolvedConfig)
|
|
345
351
|
}
|
|
346
352
|
|
|
347
353
|
configure(options?: Partial<Options>) {
|
|
@@ -352,7 +358,9 @@ export class Node<Options = any, Storage = any> extends Extendable<Options, Stor
|
|
|
352
358
|
ExtendedOptions = Options,
|
|
353
359
|
ExtendedStorage = Storage,
|
|
354
360
|
ExtendedConfig = NodeConfig<ExtendedOptions, ExtendedStorage>,
|
|
355
|
-
>(extendedConfig?: Partial<ExtendedConfig>) {
|
|
356
|
-
|
|
361
|
+
>(extendedConfig?: Partial<ExtendedConfig> | (() => Partial<ExtendedConfig>)) {
|
|
362
|
+
// If the extended config is a function, execute it to get the configuration object
|
|
363
|
+
const resolvedConfig = typeof extendedConfig === 'function' ? extendedConfig() : extendedConfig
|
|
364
|
+
return super.extend(resolvedConfig) as Node<ExtendedOptions, ExtendedStorage>
|
|
357
365
|
}
|
|
358
366
|
}
|
package/src/NodePos.ts
CHANGED
|
@@ -136,6 +136,12 @@ export class NodePos {
|
|
|
136
136
|
const isNonTextAtom = node.isAtom && !node.isText
|
|
137
137
|
|
|
138
138
|
const targetPos = this.pos + offset + (isNonTextAtom ? 0 : 1)
|
|
139
|
+
|
|
140
|
+
// Check if targetPos is within valid document range
|
|
141
|
+
if (targetPos < 0 || targetPos > this.resolvedPos.doc.nodeSize - 2) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
139
145
|
const $pos = this.resolvedPos.doc.resolve(targetPos)
|
|
140
146
|
|
|
141
147
|
if (!isBlock && $pos.depth <= this.depth) {
|
package/src/NodeView.ts
CHANGED
|
@@ -98,7 +98,9 @@ export class NodeView<
|
|
|
98
98
|
y = handleBox.y - domBox.y + offsetY
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
const clonedNode = this.dom.cloneNode(true) as HTMLElement
|
|
102
|
+
|
|
103
|
+
event.dataTransfer?.setDragImage(clonedNode, x, y)
|
|
102
104
|
|
|
103
105
|
const pos = this.getPos()
|
|
104
106
|
|
package/src/PasteRule.ts
CHANGED
|
@@ -267,7 +267,7 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }):
|
|
|
267
267
|
if (!isDroppedFromProseMirror) {
|
|
268
268
|
const dragFromOtherEditor = tiptapDragFromOtherEditor
|
|
269
269
|
|
|
270
|
-
if (dragFromOtherEditor) {
|
|
270
|
+
if (dragFromOtherEditor?.isEditable) {
|
|
271
271
|
// setTimeout to avoid the wrong content after drop, timeout arg can't be empty or 0
|
|
272
272
|
setTimeout(() => {
|
|
273
273
|
const selection = dragFromOtherEditor.state.selection
|
package/src/commands/cut.ts
CHANGED
|
@@ -76,18 +76,10 @@ export const insertContentAt: RawCommands['insertContentAt'] =
|
|
|
76
76
|
let content: Fragment | ProseMirrorNode
|
|
77
77
|
const { selection } = editor.state
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
content = createNodeFromContent(value, editor.schema, {
|
|
81
|
-
parseOptions: {
|
|
82
|
-
preserveWhitespace: 'full',
|
|
83
|
-
...options.parseOptions,
|
|
84
|
-
},
|
|
85
|
-
errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
|
|
86
|
-
})
|
|
87
|
-
} catch (e) {
|
|
79
|
+
const emitContentError = (error: Error) => {
|
|
88
80
|
editor.emit('contentError', {
|
|
89
81
|
editor,
|
|
90
|
-
error
|
|
82
|
+
error,
|
|
91
83
|
disableCollaboration: () => {
|
|
92
84
|
if (
|
|
93
85
|
'collaboration' in editor.storage &&
|
|
@@ -98,6 +90,33 @@ export const insertContentAt: RawCommands['insertContentAt'] =
|
|
|
98
90
|
}
|
|
99
91
|
},
|
|
100
92
|
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const parseOptions: ParseOptions = {
|
|
96
|
+
preserveWhitespace: 'full',
|
|
97
|
+
...options.parseOptions,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// If `emitContentError` is enabled, we want to check the content for errors
|
|
101
|
+
// but ignore them (do not remove the invalid content from the document)
|
|
102
|
+
if (!options.errorOnInvalidContent && !editor.options.enableContentCheck && editor.options.emitContentError) {
|
|
103
|
+
try {
|
|
104
|
+
createNodeFromContent(value, editor.schema, {
|
|
105
|
+
parseOptions,
|
|
106
|
+
errorOnInvalidContent: true,
|
|
107
|
+
})
|
|
108
|
+
} catch (e) {
|
|
109
|
+
emitContentError(e as Error)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
content = createNodeFromContent(value, editor.schema, {
|
|
115
|
+
parseOptions,
|
|
116
|
+
errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
|
|
117
|
+
})
|
|
118
|
+
} catch (e) {
|
|
119
|
+
emitContentError(e as Error)
|
|
101
120
|
return false
|
|
102
121
|
}
|
|
103
122
|
|
|
@@ -163,8 +182,9 @@ export const insertContentAt: RawCommands['insertContentAt'] =
|
|
|
163
182
|
|
|
164
183
|
const fromSelectionAtStart = selection.$from.parentOffset === 0
|
|
165
184
|
const isTextSelection = selection.$from.node().isText || selection.$from.node().isTextblock
|
|
185
|
+
const hasContent = selection.$from.node().content.size > 0
|
|
166
186
|
|
|
167
|
-
if (fromSelectionAtStart && isTextSelection) {
|
|
187
|
+
if (fromSelectionAtStart && isTextSelection && hasContent) {
|
|
168
188
|
from = Math.max(0, from - 1)
|
|
169
189
|
}
|
|
170
190
|
|
package/src/types.ts
CHANGED
|
@@ -354,6 +354,15 @@ export interface EditorOptions {
|
|
|
354
354
|
* @default false
|
|
355
355
|
*/
|
|
356
356
|
enableContentCheck: boolean
|
|
357
|
+
/**
|
|
358
|
+
* If `true`, the editor will emit the `contentError` event if invalid content is
|
|
359
|
+
* encountered but `enableContentCheck` is `false`. This lets you preserve the
|
|
360
|
+
* invalid editor content while still showing a warning or error message to
|
|
361
|
+
* the user.
|
|
362
|
+
*
|
|
363
|
+
* @default false
|
|
364
|
+
*/
|
|
365
|
+
emitContentError: boolean
|
|
357
366
|
/**
|
|
358
367
|
* Called before the editor is constructed.
|
|
359
368
|
*/
|
|
@@ -653,9 +662,11 @@ export interface MarkViewRendererProps {
|
|
|
653
662
|
* The HTML attributes that should be added to the mark's DOM element.
|
|
654
663
|
*/
|
|
655
664
|
HTMLAttributes: Record<string, any>
|
|
665
|
+
|
|
666
|
+
updateAttributes: (attrs: Record<string, any>) => void
|
|
656
667
|
}
|
|
657
668
|
|
|
658
|
-
export type MarkViewRenderer = (props:
|
|
669
|
+
export type MarkViewRenderer<Props = MarkViewRendererProps> = (props: Props) => MarkView
|
|
659
670
|
|
|
660
671
|
export interface MarkViewRendererOptions {
|
|
661
672
|
ignoreMutation: ((props: { mutation: ViewMutationRecord }) => boolean) | null
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { NodeType } from '@tiptap/pm/model'
|
|
2
|
+
import { type EditorState, NodeSelection } from '@tiptap/pm/state'
|
|
3
|
+
|
|
4
|
+
export function canInsertNode(state: EditorState, nodeType: NodeType): boolean {
|
|
5
|
+
const { selection } = state
|
|
6
|
+
const { $from } = selection
|
|
7
|
+
|
|
8
|
+
// Special handling for NodeSelection
|
|
9
|
+
if (selection instanceof NodeSelection) {
|
|
10
|
+
const index = $from.index()
|
|
11
|
+
const parent = $from.parent
|
|
12
|
+
|
|
13
|
+
// Can we replace the selected node with the horizontal rule?
|
|
14
|
+
return parent.canReplaceWith(index, index + 1, nodeType)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Default: check if we can insert at the current position
|
|
18
|
+
let depth = $from.depth
|
|
19
|
+
|
|
20
|
+
while (depth >= 0) {
|
|
21
|
+
const index = $from.index(depth)
|
|
22
|
+
const parent = $from.node(depth)
|
|
23
|
+
const match = parent.contentMatchAt(index)
|
|
24
|
+
if (match.matchType(nodeType)) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
depth -= 1
|
|
28
|
+
}
|
|
29
|
+
return false
|
|
30
|
+
}
|