@tiptap/core 3.0.0-next.1 → 3.0.0-next.3
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 +403 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +177 -53
- package/dist/index.d.ts +177 -53
- package/dist/index.js +375 -108
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/Editor.ts +60 -10
- package/src/EventEmitter.ts +9 -0
- package/src/ExtensionManager.ts +16 -11
- package/src/InputRule.ts +45 -30
- package/src/Node.ts +19 -0
- package/src/NodePos.ts +9 -4
- package/src/NodeView.ts +43 -12
- package/src/PasteRule.ts +96 -42
- package/src/commands/focus.ts +1 -6
- package/src/commands/insertContent.ts +9 -9
- package/src/commands/insertContentAt.ts +23 -3
- package/src/commands/selectAll.ts +10 -5
- package/src/commands/setContent.ts +10 -14
- package/src/commands/setNode.ts +9 -2
- package/src/commands/toggleNode.ts +11 -2
- package/src/commands/updateAttributes.ts +72 -12
- package/src/extensions/drop.ts +26 -0
- package/src/extensions/index.ts +2 -0
- package/src/extensions/keymap.ts +5 -2
- package/src/extensions/paste.ts +26 -0
- package/src/helpers/createDocument.ts +4 -2
- package/src/helpers/createNodeFromContent.ts +11 -2
- package/src/helpers/getMarkRange.ts +35 -8
- package/src/helpers/getRenderedAttributes.ts +3 -0
- package/src/helpers/getSchemaByResolvedExtensions.ts +2 -1
- package/src/inputRules/markInputRule.ts +1 -1
- package/src/inputRules/nodeInputRule.ts +1 -1
- package/src/inputRules/textInputRule.ts +1 -1
- package/src/inputRules/textblockTypeInputRule.ts +1 -1
- package/src/inputRules/wrappingInputRule.ts +1 -1
- package/src/pasteRules/markPasteRule.ts +1 -1
- package/src/pasteRules/nodePasteRule.ts +1 -1
- package/src/pasteRules/textPasteRule.ts +1 -1
- package/src/types.ts +107 -19
- package/src/utilities/mergeAttributes.ts +18 -1
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.0-next.
|
|
4
|
+
"version": "3.0.0-next.3",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dist"
|
|
32
32
|
],
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@tiptap/pm": "^3.0.0-next.
|
|
34
|
+
"@tiptap/pm": "^3.0.0-next.3"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@tiptap/pm": "^3.0.0-next.1"
|
package/src/Editor.ts
CHANGED
|
@@ -13,7 +13,8 @@ import { CommandManager } from './CommandManager.js'
|
|
|
13
13
|
import { EventEmitter } from './EventEmitter.js'
|
|
14
14
|
import { ExtensionManager } from './ExtensionManager.js'
|
|
15
15
|
import {
|
|
16
|
-
ClipboardTextSerializer, Commands, Editable, FocusEvents, Keymap,
|
|
16
|
+
ClipboardTextSerializer, Commands, Drop, Editable, FocusEvents, Keymap, Paste,
|
|
17
|
+
Tabindex,
|
|
17
18
|
} from './extensions/index.js'
|
|
18
19
|
import { createDocument } from './helpers/createDocument.js'
|
|
19
20
|
import { getAttributes } from './helpers/getAttributes.js'
|
|
@@ -64,6 +65,11 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
64
65
|
|
|
65
66
|
public extensionStorage: Record<string, any> = {}
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* A unique ID for this editor instance.
|
|
70
|
+
*/
|
|
71
|
+
public instanceId = Math.random().toString(36).slice(2, 9)
|
|
72
|
+
|
|
67
73
|
public options: EditorOptions = {
|
|
68
74
|
element: document.createElement('div'),
|
|
69
75
|
content: '',
|
|
@@ -88,6 +94,8 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
88
94
|
onBlur: () => null,
|
|
89
95
|
onDestroy: () => null,
|
|
90
96
|
onContentError: ({ error }) => { throw error },
|
|
97
|
+
onPaste: () => null,
|
|
98
|
+
onDrop: () => null,
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
constructor(options: Partial<EditorOptions> = {}) {
|
|
@@ -108,6 +116,8 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
108
116
|
this.on('focus', this.options.onFocus)
|
|
109
117
|
this.on('blur', this.options.onBlur)
|
|
110
118
|
this.on('destroy', this.options.onDestroy)
|
|
119
|
+
this.on('drop', ({ event, slice, moved }) => this.options.onDrop(event, slice, moved))
|
|
120
|
+
this.on('paste', ({ event, slice }) => this.options.onPaste(event, slice))
|
|
111
121
|
|
|
112
122
|
window.setTimeout(() => {
|
|
113
123
|
if (this.isDestroyed) {
|
|
@@ -212,11 +222,12 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
212
222
|
*
|
|
213
223
|
* @param plugin A ProseMirror plugin
|
|
214
224
|
* @param handlePlugins Control how to merge the plugin into the existing plugins.
|
|
225
|
+
* @returns The new editor state
|
|
215
226
|
*/
|
|
216
227
|
public registerPlugin(
|
|
217
228
|
plugin: Plugin,
|
|
218
229
|
handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[],
|
|
219
|
-
):
|
|
230
|
+
): EditorState {
|
|
220
231
|
const plugins = isFunction(handlePlugins)
|
|
221
232
|
? handlePlugins(plugin, [...this.state.plugins])
|
|
222
233
|
: [...this.state.plugins, plugin]
|
|
@@ -224,27 +235,44 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
224
235
|
const state = this.state.reconfigure({ plugins })
|
|
225
236
|
|
|
226
237
|
this.view.updateState(state)
|
|
238
|
+
|
|
239
|
+
return state
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
/**
|
|
230
243
|
* Unregister a ProseMirror plugin.
|
|
231
244
|
*
|
|
232
|
-
* @param
|
|
245
|
+
* @param nameOrPluginKeyToRemove The plugins name
|
|
246
|
+
* @returns The new editor state or undefined if the editor is destroyed
|
|
233
247
|
*/
|
|
234
|
-
public unregisterPlugin(
|
|
248
|
+
public unregisterPlugin(nameOrPluginKeyToRemove: string | PluginKey | (string | PluginKey)[]): EditorState | undefined {
|
|
235
249
|
if (this.isDestroyed) {
|
|
236
|
-
return
|
|
250
|
+
return undefined
|
|
237
251
|
}
|
|
238
252
|
|
|
239
|
-
|
|
240
|
-
|
|
253
|
+
const prevPlugins = this.state.plugins
|
|
254
|
+
let plugins = prevPlugins;
|
|
241
255
|
|
|
242
|
-
|
|
256
|
+
([] as (string | PluginKey)[]).concat(nameOrPluginKeyToRemove).forEach(nameOrPluginKey => {
|
|
243
257
|
// @ts-ignore
|
|
244
|
-
|
|
258
|
+
const name = typeof nameOrPluginKey === 'string' ? `${nameOrPluginKey}$` : nameOrPluginKey.key
|
|
259
|
+
|
|
260
|
+
// @ts-ignore
|
|
261
|
+
plugins = prevPlugins.filter(plugin => !plugin.key.startsWith(name))
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
if (prevPlugins.length === plugins.length) {
|
|
265
|
+
// No plugin was removed, so we don’t need to update the state
|
|
266
|
+
return undefined
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const state = this.state.reconfigure({
|
|
270
|
+
plugins,
|
|
245
271
|
})
|
|
246
272
|
|
|
247
273
|
this.view.updateState(state)
|
|
274
|
+
|
|
275
|
+
return state
|
|
248
276
|
}
|
|
249
277
|
|
|
250
278
|
/**
|
|
@@ -261,7 +289,14 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
261
289
|
FocusEvents,
|
|
262
290
|
Keymap,
|
|
263
291
|
Tabindex,
|
|
264
|
-
|
|
292
|
+
Drop,
|
|
293
|
+
Paste,
|
|
294
|
+
].filter(ext => {
|
|
295
|
+
if (typeof this.options.enableCoreExtensions === 'object') {
|
|
296
|
+
return this.options.enableCoreExtensions[ext.name as keyof typeof this.options.enableCoreExtensions] !== false
|
|
297
|
+
}
|
|
298
|
+
return true
|
|
299
|
+
}) : []
|
|
265
300
|
const allExtensions = [...coreExtensions, ...this.options.extensions].filter(extension => {
|
|
266
301
|
return ['extension', 'node', 'mark'].includes(extension?.type)
|
|
267
302
|
})
|
|
@@ -307,6 +342,9 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
307
342
|
editor: this,
|
|
308
343
|
error: e as Error,
|
|
309
344
|
disableCollaboration: () => {
|
|
345
|
+
if (this.storage.collaboration) {
|
|
346
|
+
this.storage.collaboration.isDisabled = true
|
|
347
|
+
}
|
|
310
348
|
// To avoid syncing back invalid content, reinitialize the extensions without the collaboration extension
|
|
311
349
|
this.options.extensions = this.options.extensions.filter(extension => extension.name !== 'collaboration')
|
|
312
350
|
|
|
@@ -327,6 +365,11 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
327
365
|
|
|
328
366
|
this.view = new EditorView(this.options.element, {
|
|
329
367
|
...this.options.editorProps,
|
|
368
|
+
attributes: {
|
|
369
|
+
// add `role="textbox"` to the editor element
|
|
370
|
+
role: 'textbox',
|
|
371
|
+
...this.options.editorProps?.attributes,
|
|
372
|
+
},
|
|
330
373
|
dispatchTransaction: this.dispatchTransaction.bind(this),
|
|
331
374
|
state: EditorState.create({
|
|
332
375
|
doc,
|
|
@@ -545,6 +588,13 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
545
588
|
this.emit('destroy')
|
|
546
589
|
|
|
547
590
|
if (this.view) {
|
|
591
|
+
// Cleanup our reference to prevent circular references which caused memory leaks
|
|
592
|
+
// @ts-ignore
|
|
593
|
+
const dom = this.view.dom as TiptapEditorHTMLElement
|
|
594
|
+
|
|
595
|
+
if (dom && dom.editor) {
|
|
596
|
+
delete dom.editor
|
|
597
|
+
}
|
|
548
598
|
this.view.destroy()
|
|
549
599
|
}
|
|
550
600
|
|
package/src/EventEmitter.ts
CHANGED
|
@@ -46,6 +46,15 @@ export class EventEmitter<T extends Record<string, any>> {
|
|
|
46
46
|
return this
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
public once<EventName extends StringKeyOf<T>>(event: EventName, fn: CallbackFunction<T, EventName>): this {
|
|
50
|
+
const onceFn = (...args: CallbackType<T, EventName>) => {
|
|
51
|
+
this.off(event, onceFn)
|
|
52
|
+
fn.apply(this, args)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return this.on(event, onceFn)
|
|
56
|
+
}
|
|
57
|
+
|
|
49
58
|
public removeAllListeners(): void {
|
|
50
59
|
this.callbacks = {}
|
|
51
60
|
}
|
package/src/ExtensionManager.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { keymap } from '@tiptap/pm/keymap'
|
|
2
|
-
import {
|
|
2
|
+
import { Schema } from '@tiptap/pm/model'
|
|
3
3
|
import { Plugin } from '@tiptap/pm/state'
|
|
4
|
-
import {
|
|
4
|
+
import { NodeViewConstructor } from '@tiptap/pm/view'
|
|
5
5
|
|
|
6
6
|
import type { Editor } from './Editor.js'
|
|
7
7
|
import { getAttributesFromExtensions } from './helpers/getAttributesFromExtensions.js'
|
|
@@ -261,7 +261,7 @@ export class ExtensionManager {
|
|
|
261
261
|
* Get all node views from the extensions.
|
|
262
262
|
* @returns An object with all node views where the key is the node name and the value is the node view function
|
|
263
263
|
*/
|
|
264
|
-
get nodeViews() {
|
|
264
|
+
get nodeViews(): Record<string, NodeViewConstructor> {
|
|
265
265
|
const { editor } = this
|
|
266
266
|
const { nodeExtensions } = splitExtensions(this.extensions)
|
|
267
267
|
|
|
@@ -289,21 +289,26 @@ export class ExtensionManager {
|
|
|
289
289
|
return []
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
const nodeview = (
|
|
293
|
-
node
|
|
294
|
-
view
|
|
295
|
-
getPos
|
|
296
|
-
decorations
|
|
292
|
+
const nodeview: NodeViewConstructor = (
|
|
293
|
+
node,
|
|
294
|
+
view,
|
|
295
|
+
getPos,
|
|
296
|
+
decorations,
|
|
297
|
+
innerDecorations,
|
|
297
298
|
) => {
|
|
298
299
|
const HTMLAttributes = getRenderedAttributes(node, extensionAttributes)
|
|
299
300
|
|
|
300
301
|
return addNodeView()({
|
|
301
|
-
|
|
302
|
+
// pass-through
|
|
302
303
|
node,
|
|
303
|
-
|
|
304
|
+
view,
|
|
305
|
+
getPos: getPos as () => number,
|
|
304
306
|
decorations,
|
|
305
|
-
|
|
307
|
+
innerDecorations,
|
|
308
|
+
// tiptap-specific
|
|
309
|
+
editor,
|
|
306
310
|
extension,
|
|
311
|
+
HTMLAttributes,
|
|
307
312
|
})
|
|
308
313
|
}
|
|
309
314
|
|
package/src/InputRule.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { Fragment, Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
1
2
|
import { EditorState, Plugin, TextSelection } from '@tiptap/pm/state'
|
|
2
3
|
|
|
3
4
|
import { CommandManager } from './CommandManager.js'
|
|
4
5
|
import { Editor } from './Editor.js'
|
|
5
6
|
import { createChainableState } from './helpers/createChainableState.js'
|
|
7
|
+
import { getHTMLFromFragment } from './helpers/getHTMLFromFragment.js'
|
|
6
8
|
import { getTextContentFromNodes } from './helpers/getTextContentFromNodes.js'
|
|
7
9
|
import {
|
|
8
10
|
CanCommands,
|
|
@@ -14,37 +16,37 @@ import {
|
|
|
14
16
|
import { isRegExp } from './utilities/isRegExp.js'
|
|
15
17
|
|
|
16
18
|
export type InputRuleMatch = {
|
|
17
|
-
index: number
|
|
18
|
-
text: string
|
|
19
|
-
replaceWith?: string
|
|
20
|
-
match?: RegExpMatchArray
|
|
21
|
-
data?: Record<string, any
|
|
22
|
-
}
|
|
19
|
+
index: number;
|
|
20
|
+
text: string;
|
|
21
|
+
replaceWith?: string;
|
|
22
|
+
match?: RegExpMatchArray;
|
|
23
|
+
data?: Record<string, any>;
|
|
24
|
+
};
|
|
23
25
|
|
|
24
|
-
export type InputRuleFinder = RegExp | ((text: string) => InputRuleMatch | null)
|
|
26
|
+
export type InputRuleFinder = RegExp | ((text: string) => InputRuleMatch | null);
|
|
25
27
|
|
|
26
28
|
export class InputRule {
|
|
27
29
|
find: InputRuleFinder
|
|
28
30
|
|
|
29
31
|
handler: (props: {
|
|
30
|
-
state: EditorState
|
|
31
|
-
range: Range
|
|
32
|
-
match: ExtendedRegExpMatchArray
|
|
33
|
-
commands: SingleCommands
|
|
34
|
-
chain: () => ChainedCommands
|
|
35
|
-
can: () => CanCommands
|
|
32
|
+
state: EditorState;
|
|
33
|
+
range: Range;
|
|
34
|
+
match: ExtendedRegExpMatchArray;
|
|
35
|
+
commands: SingleCommands;
|
|
36
|
+
chain: () => ChainedCommands;
|
|
37
|
+
can: () => CanCommands;
|
|
36
38
|
}) => void | null
|
|
37
39
|
|
|
38
40
|
constructor(config: {
|
|
39
|
-
find: InputRuleFinder
|
|
41
|
+
find: InputRuleFinder;
|
|
40
42
|
handler: (props: {
|
|
41
|
-
state: EditorState
|
|
42
|
-
range: Range
|
|
43
|
-
match: ExtendedRegExpMatchArray
|
|
44
|
-
commands: SingleCommands
|
|
45
|
-
chain: () => ChainedCommands
|
|
46
|
-
can: () => CanCommands
|
|
47
|
-
}) => void | null
|
|
43
|
+
state: EditorState;
|
|
44
|
+
range: Range;
|
|
45
|
+
match: ExtendedRegExpMatchArray;
|
|
46
|
+
commands: SingleCommands;
|
|
47
|
+
chain: () => ChainedCommands;
|
|
48
|
+
can: () => CanCommands;
|
|
49
|
+
}) => void | null;
|
|
48
50
|
}) {
|
|
49
51
|
this.find = config.find
|
|
50
52
|
this.handler = config.handler
|
|
@@ -85,12 +87,12 @@ const inputRuleMatcherHandler = (
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
function run(config: {
|
|
88
|
-
editor: Editor
|
|
89
|
-
from: number
|
|
90
|
-
to: number
|
|
91
|
-
text: string
|
|
92
|
-
rules: InputRule[]
|
|
93
|
-
plugin: Plugin
|
|
90
|
+
editor: Editor;
|
|
91
|
+
from: number;
|
|
92
|
+
to: number;
|
|
93
|
+
text: string;
|
|
94
|
+
rules: InputRule[];
|
|
95
|
+
plugin: Plugin;
|
|
94
96
|
}): boolean {
|
|
95
97
|
const {
|
|
96
98
|
editor, from, to, text, rules, plugin,
|
|
@@ -184,7 +186,7 @@ export function inputRulesPlugin(props: { editor: Editor; rules: InputRule[] }):
|
|
|
184
186
|
init() {
|
|
185
187
|
return null
|
|
186
188
|
},
|
|
187
|
-
apply(tr, prev) {
|
|
189
|
+
apply(tr, prev, state) {
|
|
188
190
|
const stored = tr.getMeta(plugin)
|
|
189
191
|
|
|
190
192
|
if (stored) {
|
|
@@ -192,12 +194,25 @@ export function inputRulesPlugin(props: { editor: Editor; rules: InputRule[] }):
|
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
// if InputRule is triggered by insertContent()
|
|
195
|
-
const simulatedInputMeta = tr.getMeta('applyInputRules')
|
|
197
|
+
const simulatedInputMeta = tr.getMeta('applyInputRules') as
|
|
198
|
+
| undefined
|
|
199
|
+
| {
|
|
200
|
+
from: number;
|
|
201
|
+
text: string | ProseMirrorNode | Fragment;
|
|
202
|
+
}
|
|
196
203
|
const isSimulatedInput = !!simulatedInputMeta
|
|
197
204
|
|
|
198
205
|
if (isSimulatedInput) {
|
|
199
206
|
setTimeout(() => {
|
|
200
|
-
|
|
207
|
+
let { text } = simulatedInputMeta
|
|
208
|
+
|
|
209
|
+
if (typeof text === 'string') {
|
|
210
|
+
text = text as string
|
|
211
|
+
} else {
|
|
212
|
+
text = getHTMLFromFragment(Fragment.from(text), state.schema)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const { from } = simulatedInputMeta
|
|
201
216
|
const to = from + text.length
|
|
202
217
|
|
|
203
218
|
run({
|
package/src/Node.ts
CHANGED
|
@@ -595,6 +595,25 @@ declare module '@tiptap/core' {
|
|
|
595
595
|
editor?: Editor
|
|
596
596
|
}) => NodeSpec['whitespace'])
|
|
597
597
|
|
|
598
|
+
/**
|
|
599
|
+
* Allows a **single** node to be set as linebreak equivalent (e.g. hardBreak).
|
|
600
|
+
* When converting between block types that have whitespace set to "pre"
|
|
601
|
+
* and don't support the linebreak node (e.g. codeBlock) and other block types
|
|
602
|
+
* that do support the linebreak node (e.g. paragraphs) - this node will be used
|
|
603
|
+
* as the linebreak instead of stripping the newline.
|
|
604
|
+
*
|
|
605
|
+
* See [linebreakReplacement](https://prosemirror.net/docs/ref/#model.NodeSpec.linebreakReplacement).
|
|
606
|
+
*/
|
|
607
|
+
linebreakReplacement?:
|
|
608
|
+
| NodeSpec['linebreakReplacement']
|
|
609
|
+
| ((this: {
|
|
610
|
+
name: string
|
|
611
|
+
options: Options
|
|
612
|
+
storage: Storage
|
|
613
|
+
parent: ParentConfig<NodeConfig<Options, Storage>>['linebreakReplacement']
|
|
614
|
+
editor?: Editor
|
|
615
|
+
}) => NodeSpec['linebreakReplacement'])
|
|
616
|
+
|
|
598
617
|
/**
|
|
599
618
|
* When enabled, enables both
|
|
600
619
|
* [`definingAsContext`](https://prosemirror.net/docs/ref/#model.NodeSpec.definingAsContext) and
|
package/src/NodePos.ts
CHANGED
|
@@ -135,8 +135,9 @@ export class NodePos {
|
|
|
135
135
|
|
|
136
136
|
this.node.content.forEach((node, offset) => {
|
|
137
137
|
const isBlock = node.isBlock && !node.isTextblock
|
|
138
|
+
const isNonTextAtom = node.isAtom && !node.isText
|
|
138
139
|
|
|
139
|
-
const targetPos = this.pos + offset + 1
|
|
140
|
+
const targetPos = this.pos + offset + (isNonTextAtom ? 0 : 1)
|
|
140
141
|
const $pos = this.resolvedPos.doc.resolve(targetPos)
|
|
141
142
|
|
|
142
143
|
if (!isBlock && $pos.depth <= this.depth) {
|
|
@@ -235,9 +236,13 @@ export class NodePos {
|
|
|
235
236
|
}
|
|
236
237
|
|
|
237
238
|
setAttribute(attributes: { [key: string]: any }) {
|
|
238
|
-
const
|
|
239
|
+
const { tr } = this.editor.state
|
|
239
240
|
|
|
240
|
-
|
|
241
|
-
.
|
|
241
|
+
tr.setNodeMarkup(this.from, undefined, {
|
|
242
|
+
...this.node.attrs,
|
|
243
|
+
...attributes,
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
this.editor.view.dispatch(tr)
|
|
242
247
|
}
|
|
243
248
|
}
|
package/src/NodeView.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
2
1
|
import { NodeSelection } from '@tiptap/pm/state'
|
|
3
|
-
import { NodeView as ProseMirrorNodeView } from '@tiptap/pm/view'
|
|
2
|
+
import { NodeView as ProseMirrorNodeView, ViewMutationRecord } from '@tiptap/pm/view'
|
|
4
3
|
|
|
5
4
|
import { Editor as CoreEditor } from './Editor.js'
|
|
6
|
-
import { Node } from './Node.js'
|
|
7
5
|
import { DecorationWithType, NodeViewRendererOptions, NodeViewRendererProps } from './types.js'
|
|
8
6
|
import { isAndroid } from './utilities/isAndroid.js'
|
|
9
7
|
import { isiOS } from './utilities/isiOS.js'
|
|
@@ -23,13 +21,19 @@ export class NodeView<
|
|
|
23
21
|
|
|
24
22
|
options: Options
|
|
25
23
|
|
|
26
|
-
extension:
|
|
24
|
+
extension: NodeViewRendererProps['extension']
|
|
27
25
|
|
|
28
|
-
node:
|
|
26
|
+
node: NodeViewRendererProps['node']
|
|
29
27
|
|
|
30
|
-
decorations:
|
|
28
|
+
decorations: NodeViewRendererProps['decorations']
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
innerDecorations: NodeViewRendererProps['innerDecorations']
|
|
31
|
+
|
|
32
|
+
view: NodeViewRendererProps['view']
|
|
33
|
+
|
|
34
|
+
getPos: NodeViewRendererProps['getPos']
|
|
35
|
+
|
|
36
|
+
HTMLAttributes: NodeViewRendererProps['HTMLAttributes']
|
|
33
37
|
|
|
34
38
|
isDragging = false
|
|
35
39
|
|
|
@@ -44,6 +48,9 @@ export class NodeView<
|
|
|
44
48
|
this.extension = props.extension
|
|
45
49
|
this.node = props.node
|
|
46
50
|
this.decorations = props.decorations as DecorationWithType[]
|
|
51
|
+
this.innerDecorations = props.innerDecorations
|
|
52
|
+
this.view = props.view
|
|
53
|
+
this.HTMLAttributes = props.HTMLAttributes
|
|
47
54
|
this.getPos = props.getPos
|
|
48
55
|
this.mount()
|
|
49
56
|
}
|
|
@@ -93,9 +100,14 @@ export class NodeView<
|
|
|
93
100
|
|
|
94
101
|
event.dataTransfer?.setDragImage(this.dom, x, y)
|
|
95
102
|
|
|
103
|
+
const pos = this.getPos()
|
|
104
|
+
|
|
105
|
+
if (typeof pos !== 'number') {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
96
108
|
// we need to tell ProseMirror that we want to move the whole node
|
|
97
109
|
// so we create a NodeSelection
|
|
98
|
-
const selection = NodeSelection.create(view.state.doc,
|
|
110
|
+
const selection = NodeSelection.create(view.state.doc, pos)
|
|
99
111
|
const transaction = view.state.tr.setSelection(selection)
|
|
100
112
|
|
|
101
113
|
view.dispatch(transaction)
|
|
@@ -139,11 +151,11 @@ export class NodeView<
|
|
|
139
151
|
// ProseMirror tries to drag selectable nodes
|
|
140
152
|
// even if `draggable` is set to `false`
|
|
141
153
|
// this fix prevents that
|
|
142
|
-
if (!isDraggable && isSelectable && isDragEvent) {
|
|
154
|
+
if (!isDraggable && isSelectable && isDragEvent && event.target === this.dom) {
|
|
143
155
|
event.preventDefault()
|
|
144
156
|
}
|
|
145
157
|
|
|
146
|
-
if (isDraggable && isDragEvent && !isDragging) {
|
|
158
|
+
if (isDraggable && isDragEvent && !isDragging && event.target === this.dom) {
|
|
147
159
|
event.preventDefault()
|
|
148
160
|
return false
|
|
149
161
|
}
|
|
@@ -197,7 +209,12 @@ export class NodeView<
|
|
|
197
209
|
return true
|
|
198
210
|
}
|
|
199
211
|
|
|
200
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Called when a DOM [mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) or a selection change happens within the view.
|
|
214
|
+
* @return `false` if the editor should re-read the selection or re-parse the range around the mutation
|
|
215
|
+
* @return `true` if it can safely be ignored.
|
|
216
|
+
*/
|
|
217
|
+
ignoreMutation(mutation: ViewMutationRecord) {
|
|
201
218
|
if (!this.dom || !this.contentDOM) {
|
|
202
219
|
return true
|
|
203
220
|
}
|
|
@@ -254,10 +271,17 @@ export class NodeView<
|
|
|
254
271
|
return true
|
|
255
272
|
}
|
|
256
273
|
|
|
257
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Update the attributes of the prosemirror node.
|
|
276
|
+
*/
|
|
277
|
+
updateAttributes(attributes: Record<string, any>): void {
|
|
258
278
|
this.editor.commands.command(({ tr }) => {
|
|
259
279
|
const pos = this.getPos()
|
|
260
280
|
|
|
281
|
+
if (typeof pos !== 'number') {
|
|
282
|
+
return false
|
|
283
|
+
}
|
|
284
|
+
|
|
261
285
|
tr.setNodeMarkup(pos, undefined, {
|
|
262
286
|
...this.node.attrs,
|
|
263
287
|
...attributes,
|
|
@@ -267,8 +291,15 @@ export class NodeView<
|
|
|
267
291
|
})
|
|
268
292
|
}
|
|
269
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Delete the node.
|
|
296
|
+
*/
|
|
270
297
|
deleteNode(): void {
|
|
271
298
|
const from = this.getPos()
|
|
299
|
+
|
|
300
|
+
if (typeof from !== 'number') {
|
|
301
|
+
return
|
|
302
|
+
}
|
|
272
303
|
const to = from + this.node.nodeSize
|
|
273
304
|
|
|
274
305
|
this.editor.commands.deleteRange({ from, to })
|