@tiptap/core 2.6.6 → 2.7.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/dist/index.cjs +95 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +94 -12
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +95 -11
- package/dist/index.umd.js.map +1 -1
- package/dist/packages/core/src/NodeView.d.ts +20 -8
- package/dist/packages/core/src/index.d.ts +2 -0
- package/dist/packages/core/src/plugins/DropPlugin.d.ts +3 -0
- package/dist/packages/core/src/plugins/PastePlugin.d.ts +3 -0
- package/dist/packages/core/src/types.d.ts +35 -15
- package/package.json +3 -3
- package/src/Editor.ts +18 -1
- package/src/ExtensionManager.ts +15 -10
- package/src/NodeView.ts +39 -8
- package/src/commands/toggleNode.ts +11 -2
- package/src/extensions/keymap.ts +5 -2
- package/src/index.ts +2 -0
- package/src/plugins/DropPlugin.ts +14 -0
- package/src/plugins/PastePlugin.ts +14 -0
- package/src/types.ts +46 -17
- package/src/utilities/mergeAttributes.ts +18 -1
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Node as ProseMirrorNode } from '@tiptap/pm/model';
|
|
2
1
|
import { NodeView as ProseMirrorNodeView } from '@tiptap/pm/view';
|
|
3
2
|
import { Editor as CoreEditor } from './Editor.js';
|
|
4
|
-
import {
|
|
5
|
-
import { DecorationWithType, NodeViewRendererOptions, NodeViewRendererProps } from './types.js';
|
|
3
|
+
import { NodeViewRendererOptions, NodeViewRendererProps } from './types.js';
|
|
6
4
|
/**
|
|
7
5
|
* Node views are used to customize the rendered DOM structure of a node.
|
|
8
6
|
* @see https://tiptap.dev/guide/node-views
|
|
@@ -11,10 +9,13 @@ export declare class NodeView<Component, NodeEditor extends CoreEditor = CoreEdi
|
|
|
11
9
|
component: Component;
|
|
12
10
|
editor: NodeEditor;
|
|
13
11
|
options: Options;
|
|
14
|
-
extension:
|
|
15
|
-
node:
|
|
16
|
-
decorations:
|
|
17
|
-
|
|
12
|
+
extension: NodeViewRendererProps['extension'];
|
|
13
|
+
node: NodeViewRendererProps['node'];
|
|
14
|
+
decorations: NodeViewRendererProps['decorations'];
|
|
15
|
+
innerDecorations: NodeViewRendererProps['innerDecorations'];
|
|
16
|
+
view: NodeViewRendererProps['view'];
|
|
17
|
+
getPos: NodeViewRendererProps['getPos'];
|
|
18
|
+
HTMLAttributes: NodeViewRendererProps['HTMLAttributes'];
|
|
18
19
|
isDragging: boolean;
|
|
19
20
|
constructor(component: Component, props: NodeViewRendererProps, options?: Partial<Options>);
|
|
20
21
|
mount(): void;
|
|
@@ -22,10 +23,21 @@ export declare class NodeView<Component, NodeEditor extends CoreEditor = CoreEdi
|
|
|
22
23
|
get contentDOM(): HTMLElement | null;
|
|
23
24
|
onDragStart(event: DragEvent): void;
|
|
24
25
|
stopEvent(event: Event): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Called when a DOM [mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) or a selection change happens within the view.
|
|
28
|
+
* @return `false` if the editor should re-read the selection or re-parse the range around the mutation
|
|
29
|
+
* @return `true` if it can safely be ignored.
|
|
30
|
+
*/
|
|
25
31
|
ignoreMutation(mutation: MutationRecord | {
|
|
26
32
|
type: 'selection';
|
|
27
33
|
target: Element;
|
|
28
34
|
}): boolean;
|
|
29
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Update the attributes of the prosemirror node.
|
|
37
|
+
*/
|
|
38
|
+
updateAttributes(attributes: Record<string, any>): void;
|
|
39
|
+
/**
|
|
40
|
+
* Delete the node.
|
|
41
|
+
*/
|
|
30
42
|
deleteNode(): void;
|
|
31
43
|
}
|
|
@@ -11,6 +11,8 @@ export * from './NodePos.js';
|
|
|
11
11
|
export * from './NodeView.js';
|
|
12
12
|
export * from './PasteRule.js';
|
|
13
13
|
export * from './pasteRules/index.js';
|
|
14
|
+
export * from './plugins/DropPlugin.js';
|
|
15
|
+
export * from './plugins/PastePlugin.js';
|
|
14
16
|
export * from './Tracker.js';
|
|
15
17
|
export * from './types.js';
|
|
16
18
|
export * from './utilities/index.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions } from '@tiptap/pm/model';
|
|
1
|
+
import { Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions, Slice } from '@tiptap/pm/model';
|
|
2
2
|
import { EditorState, Transaction } from '@tiptap/pm/state';
|
|
3
|
-
import { Decoration, EditorProps, EditorView, NodeView } from '@tiptap/pm/view';
|
|
3
|
+
import { Decoration, EditorProps, EditorView, NodeView, NodeViewConstructor } from '@tiptap/pm/view';
|
|
4
4
|
import { Editor } from './Editor.js';
|
|
5
5
|
import { Extension } from './Extension.js';
|
|
6
6
|
import { Commands, ExtensionConfig, MarkConfig, NodeConfig } from './index.js';
|
|
@@ -79,7 +79,24 @@ export interface EditorOptions {
|
|
|
79
79
|
};
|
|
80
80
|
enableInputRules: EnableRules;
|
|
81
81
|
enablePasteRules: EnableRules;
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Determines whether core extensions are enabled.
|
|
84
|
+
*
|
|
85
|
+
* If set to `false`, all core extensions will be disabled.
|
|
86
|
+
* To disable specific core extensions, provide an object where the keys are the extension names and the values are `false`.
|
|
87
|
+
* Extensions not listed in the object will remain enabled.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Disable all core extensions
|
|
91
|
+
* enabledCoreExtensions: false
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* // Disable only the keymap core extension
|
|
95
|
+
* enabledCoreExtensions: { keymap: false }
|
|
96
|
+
*
|
|
97
|
+
* @default true
|
|
98
|
+
*/
|
|
99
|
+
enableCoreExtensions?: boolean | Partial<Record<'editable' | 'clipboardTextSerializer' | 'commands' | 'focusEvents' | 'keymap' | 'tabindex', false>>;
|
|
83
100
|
/**
|
|
84
101
|
* If `true`, the editor will check the content for errors on initialization.
|
|
85
102
|
* Emitting the `contentError` event if the content is invalid.
|
|
@@ -100,6 +117,8 @@ export interface EditorOptions {
|
|
|
100
117
|
onFocus: (props: EditorEvents['focus']) => void;
|
|
101
118
|
onBlur: (props: EditorEvents['blur']) => void;
|
|
102
119
|
onDestroy: (props: EditorEvents['destroy']) => void;
|
|
120
|
+
onPaste: (e: ClipboardEvent, slice: Slice) => void;
|
|
121
|
+
onDrop: (e: DragEvent, slice: Slice, moved: boolean) => void;
|
|
103
122
|
}
|
|
104
123
|
export type HTMLContent = string;
|
|
105
124
|
export type JSONContent = {
|
|
@@ -170,19 +189,18 @@ export type ValuesOf<T> = T[keyof T];
|
|
|
170
189
|
export type KeysWithTypeOf<T, Type> = {
|
|
171
190
|
[P in keyof T]: T[P] extends Type ? P : never;
|
|
172
191
|
}[keyof T];
|
|
192
|
+
export type Simplify<T> = {
|
|
193
|
+
[KeyType in keyof T]: T[KeyType];
|
|
194
|
+
} & {};
|
|
173
195
|
export type DecorationWithType = Decoration & {
|
|
174
196
|
type: NodeType;
|
|
175
197
|
};
|
|
176
|
-
export type NodeViewProps = {
|
|
177
|
-
|
|
178
|
-
node: ProseMirrorNode;
|
|
179
|
-
decorations: DecorationWithType[];
|
|
198
|
+
export type NodeViewProps = Simplify<Omit<NodeViewRendererProps, 'decorations'> & {
|
|
199
|
+
decorations: readonly DecorationWithType[];
|
|
180
200
|
selected: boolean;
|
|
181
|
-
extension: Node;
|
|
182
|
-
getPos: () => number;
|
|
183
201
|
updateAttributes: (attributes: Record<string, any>) => void;
|
|
184
202
|
deleteNode: () => void;
|
|
185
|
-
}
|
|
203
|
+
}>;
|
|
186
204
|
export interface NodeViewRendererOptions {
|
|
187
205
|
stopEvent: ((props: {
|
|
188
206
|
event: Event;
|
|
@@ -196,14 +214,16 @@ export interface NodeViewRendererOptions {
|
|
|
196
214
|
contentDOMElementTag: string;
|
|
197
215
|
}
|
|
198
216
|
export type NodeViewRendererProps = {
|
|
217
|
+
node: Parameters<NodeViewConstructor>[0];
|
|
218
|
+
view: Parameters<NodeViewConstructor>[1];
|
|
219
|
+
getPos: () => number;
|
|
220
|
+
decorations: Parameters<NodeViewConstructor>[3];
|
|
221
|
+
innerDecorations: Parameters<NodeViewConstructor>[4];
|
|
199
222
|
editor: Editor;
|
|
200
|
-
node: ProseMirrorNode;
|
|
201
|
-
getPos: (() => number) | boolean;
|
|
202
|
-
HTMLAttributes: Record<string, any>;
|
|
203
|
-
decorations: Decoration[];
|
|
204
223
|
extension: Node;
|
|
224
|
+
HTMLAttributes: Record<string, any>;
|
|
205
225
|
};
|
|
206
|
-
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView
|
|
226
|
+
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView;
|
|
207
227
|
export type AnyCommands = Record<string, (...args: any[]) => Command>;
|
|
208
228
|
export type UnionCommands<T = Command> = UnionToIntersection<ValuesOf<Pick<Commands<T>, KeysWithTypeOf<Commands<T>, {}>>>>;
|
|
209
229
|
export type RawCommands = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiptap/core",
|
|
3
3
|
"description": "headless rich text editor",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.7.0",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@tiptap/pm": "^2.
|
|
35
|
+
"@tiptap/pm": "^2.7.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@tiptap/pm": "^2.
|
|
38
|
+
"@tiptap/pm": "^2.7.0-pre.0"
|
|
39
39
|
},
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
package/src/Editor.ts
CHANGED
|
@@ -24,6 +24,8 @@ import { isActive } from './helpers/isActive.js'
|
|
|
24
24
|
import { isNodeEmpty } from './helpers/isNodeEmpty.js'
|
|
25
25
|
import { resolveFocusPosition } from './helpers/resolveFocusPosition.js'
|
|
26
26
|
import { NodePos } from './NodePos.js'
|
|
27
|
+
import { DropPlugin } from './plugins/DropPlugin.js'
|
|
28
|
+
import { PastePlugin } from './plugins/PastePlugin.js'
|
|
27
29
|
import { style } from './style.js'
|
|
28
30
|
import {
|
|
29
31
|
CanCommands,
|
|
@@ -88,6 +90,8 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
88
90
|
onBlur: () => null,
|
|
89
91
|
onDestroy: () => null,
|
|
90
92
|
onContentError: ({ error }) => { throw error },
|
|
93
|
+
onPaste: () => null,
|
|
94
|
+
onDrop: () => null,
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
constructor(options: Partial<EditorOptions> = {}) {
|
|
@@ -109,6 +113,14 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
109
113
|
this.on('blur', this.options.onBlur)
|
|
110
114
|
this.on('destroy', this.options.onDestroy)
|
|
111
115
|
|
|
116
|
+
if (this.options.onPaste) {
|
|
117
|
+
this.registerPlugin(PastePlugin(this.options.onPaste))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this.options.onDrop) {
|
|
121
|
+
this.registerPlugin(DropPlugin(this.options.onDrop))
|
|
122
|
+
}
|
|
123
|
+
|
|
112
124
|
window.setTimeout(() => {
|
|
113
125
|
if (this.isDestroyed) {
|
|
114
126
|
return
|
|
@@ -261,7 +273,12 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
261
273
|
FocusEvents,
|
|
262
274
|
Keymap,
|
|
263
275
|
Tabindex,
|
|
264
|
-
]
|
|
276
|
+
].filter(ext => {
|
|
277
|
+
if (typeof this.options.enableCoreExtensions === 'object') {
|
|
278
|
+
return this.options.enableCoreExtensions[ext.name as keyof typeof this.options.enableCoreExtensions] !== false
|
|
279
|
+
}
|
|
280
|
+
return true
|
|
281
|
+
}) : []
|
|
265
282
|
const allExtensions = [...coreExtensions, ...this.options.extensions].filter(extension => {
|
|
266
283
|
return ['extension', 'node', 'mark'].includes(extension?.type)
|
|
267
284
|
})
|
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'
|
|
@@ -288,21 +288,26 @@ export class ExtensionManager {
|
|
|
288
288
|
return []
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
const nodeview = (
|
|
292
|
-
node
|
|
293
|
-
view
|
|
294
|
-
getPos
|
|
295
|
-
decorations
|
|
291
|
+
const nodeview: NodeViewConstructor = (
|
|
292
|
+
node,
|
|
293
|
+
view,
|
|
294
|
+
getPos,
|
|
295
|
+
decorations,
|
|
296
|
+
innerDecorations,
|
|
296
297
|
) => {
|
|
297
298
|
const HTMLAttributes = getRenderedAttributes(node, extensionAttributes)
|
|
298
299
|
|
|
299
300
|
return addNodeView()({
|
|
300
|
-
|
|
301
|
+
// pass-through
|
|
301
302
|
node,
|
|
302
|
-
|
|
303
|
+
view,
|
|
304
|
+
getPos: getPos as () => number,
|
|
303
305
|
decorations,
|
|
304
|
-
|
|
306
|
+
innerDecorations,
|
|
307
|
+
// tiptap-specific
|
|
308
|
+
editor,
|
|
305
309
|
extension,
|
|
310
|
+
HTMLAttributes,
|
|
306
311
|
})
|
|
307
312
|
}
|
|
308
313
|
|
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
2
|
import { NodeView as ProseMirrorNodeView } 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)
|
|
@@ -197,6 +209,11 @@ export class NodeView<
|
|
|
197
209
|
return true
|
|
198
210
|
}
|
|
199
211
|
|
|
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
|
+
*/
|
|
200
217
|
ignoreMutation(mutation: MutationRecord | { type: 'selection'; target: Element }) {
|
|
201
218
|
if (!this.dom || !this.contentDOM) {
|
|
202
219
|
return true
|
|
@@ -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 })
|
|
@@ -28,9 +28,18 @@ export const toggleNode: RawCommands['toggleNode'] = (typeOrName, toggleTypeOrNa
|
|
|
28
28
|
const toggleType = getNodeType(toggleTypeOrName, state.schema)
|
|
29
29
|
const isActive = isNodeActive(state, type, attributes)
|
|
30
30
|
|
|
31
|
+
let attributesToCopy: Record<string, any> | undefined
|
|
32
|
+
|
|
33
|
+
if (state.selection.$anchor.sameParent(state.selection.$head)) {
|
|
34
|
+
// only copy attributes if the selection is pointing to a node of the same type
|
|
35
|
+
attributesToCopy = state.selection.$anchor.parent.attrs
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
if (isActive) {
|
|
32
|
-
return commands.setNode(toggleType)
|
|
39
|
+
return commands.setNode(toggleType, attributesToCopy)
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
// If the node is not active, we want to set the new node type with the given attributes
|
|
43
|
+
// Copying over the attributes from the current node if the selection is pointing to a node of the same type
|
|
44
|
+
return commands.setNode(type, { ...attributesToCopy, ...attributes })
|
|
36
45
|
}
|
package/src/extensions/keymap.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Plugin, PluginKey, Selection } from '@tiptap/pm/state'
|
|
|
3
3
|
import { CommandManager } from '../CommandManager.js'
|
|
4
4
|
import { Extension } from '../Extension.js'
|
|
5
5
|
import { createChainableState } from '../helpers/createChainableState.js'
|
|
6
|
+
import { isNodeEmpty } from '../helpers/isNodeEmpty.js'
|
|
6
7
|
import { isiOS } from '../utilities/isiOS.js'
|
|
7
8
|
import { isMacOS } from '../utilities/isMacOS.js'
|
|
8
9
|
|
|
@@ -106,7 +107,9 @@ export const Keymap = Extension.create({
|
|
|
106
107
|
const docChanges = transactions.some(transaction => transaction.docChanged)
|
|
107
108
|
&& !oldState.doc.eq(newState.doc)
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
const ignoreTr = transactions.some(transaction => transaction.getMeta('preventClearDocument'))
|
|
111
|
+
|
|
112
|
+
if (!docChanges || ignoreTr) {
|
|
110
113
|
return
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -119,7 +122,7 @@ export const Keymap = Extension.create({
|
|
|
119
122
|
return
|
|
120
123
|
}
|
|
121
124
|
|
|
122
|
-
const isEmpty =
|
|
125
|
+
const isEmpty = isNodeEmpty(newState.doc)
|
|
123
126
|
|
|
124
127
|
if (!isEmpty) {
|
|
125
128
|
return
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,8 @@ export * from './NodePos.js'
|
|
|
11
11
|
export * from './NodeView.js'
|
|
12
12
|
export * from './PasteRule.js'
|
|
13
13
|
export * from './pasteRules/index.js'
|
|
14
|
+
export * from './plugins/DropPlugin.js'
|
|
15
|
+
export * from './plugins/PastePlugin.js'
|
|
14
16
|
export * from './Tracker.js'
|
|
15
17
|
export * from './types.js'
|
|
16
18
|
export * from './utilities/index.js'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
|
2
|
+
import { Slice } from 'packages/pm/model'
|
|
3
|
+
|
|
4
|
+
export const DropPlugin = (onDrop: (e: DragEvent, slice: Slice, moved: boolean) => void) => {
|
|
5
|
+
return new Plugin({
|
|
6
|
+
key: new PluginKey('tiptapDrop'),
|
|
7
|
+
|
|
8
|
+
props: {
|
|
9
|
+
handleDrop: (_, e, slice, moved) => {
|
|
10
|
+
onDrop(e, slice, moved)
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Slice } from '@tiptap/pm/model'
|
|
2
|
+
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
|
3
|
+
|
|
4
|
+
export const PastePlugin = (onPaste: (e: ClipboardEvent, slice: Slice) => void) => {
|
|
5
|
+
return new Plugin({
|
|
6
|
+
key: new PluginKey('tiptapPaste'),
|
|
7
|
+
|
|
8
|
+
props: {
|
|
9
|
+
handlePaste: (_view, e, slice) => {
|
|
10
|
+
onPaste(e, slice)
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -3,10 +3,15 @@ import {
|
|
|
3
3
|
Node as ProseMirrorNode,
|
|
4
4
|
NodeType,
|
|
5
5
|
ParseOptions,
|
|
6
|
+
Slice,
|
|
6
7
|
} from '@tiptap/pm/model'
|
|
7
8
|
import { EditorState, Transaction } from '@tiptap/pm/state'
|
|
8
9
|
import {
|
|
9
|
-
Decoration,
|
|
10
|
+
Decoration,
|
|
11
|
+
EditorProps,
|
|
12
|
+
EditorView,
|
|
13
|
+
NodeView,
|
|
14
|
+
NodeViewConstructor,
|
|
10
15
|
} from '@tiptap/pm/view'
|
|
11
16
|
|
|
12
17
|
import { Editor } from './Editor.js'
|
|
@@ -79,7 +84,24 @@ export interface EditorOptions {
|
|
|
79
84
|
};
|
|
80
85
|
enableInputRules: EnableRules;
|
|
81
86
|
enablePasteRules: EnableRules;
|
|
82
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Determines whether core extensions are enabled.
|
|
89
|
+
*
|
|
90
|
+
* If set to `false`, all core extensions will be disabled.
|
|
91
|
+
* To disable specific core extensions, provide an object where the keys are the extension names and the values are `false`.
|
|
92
|
+
* Extensions not listed in the object will remain enabled.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // Disable all core extensions
|
|
96
|
+
* enabledCoreExtensions: false
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // Disable only the keymap core extension
|
|
100
|
+
* enabledCoreExtensions: { keymap: false }
|
|
101
|
+
*
|
|
102
|
+
* @default true
|
|
103
|
+
*/
|
|
104
|
+
enableCoreExtensions?: boolean | Partial<Record<'editable' | 'clipboardTextSerializer' | 'commands' | 'focusEvents' | 'keymap' | 'tabindex', false>>;
|
|
83
105
|
/**
|
|
84
106
|
* If `true`, the editor will check the content for errors on initialization.
|
|
85
107
|
* Emitting the `contentError` event if the content is invalid.
|
|
@@ -100,6 +122,8 @@ export interface EditorOptions {
|
|
|
100
122
|
onFocus: (props: EditorEvents['focus']) => void;
|
|
101
123
|
onBlur: (props: EditorEvents['blur']) => void;
|
|
102
124
|
onDestroy: (props: EditorEvents['destroy']) => void;
|
|
125
|
+
onPaste: (e: ClipboardEvent, slice: Slice) => void
|
|
126
|
+
onDrop: (e: DragEvent, slice: Slice, moved: boolean) => void
|
|
103
127
|
}
|
|
104
128
|
|
|
105
129
|
export type HTMLContent = string;
|
|
@@ -184,20 +208,21 @@ export type ValuesOf<T> = T[keyof T];
|
|
|
184
208
|
|
|
185
209
|
export type KeysWithTypeOf<T, Type> = { [P in keyof T]: T[P] extends Type ? P : never }[keyof T];
|
|
186
210
|
|
|
211
|
+
export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
|
|
212
|
+
|
|
187
213
|
export type DecorationWithType = Decoration & {
|
|
188
214
|
type: NodeType;
|
|
189
215
|
};
|
|
190
216
|
|
|
191
|
-
export type NodeViewProps =
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
};
|
|
217
|
+
export type NodeViewProps = Simplify<
|
|
218
|
+
Omit<NodeViewRendererProps, 'decorations'> & {
|
|
219
|
+
// TODO this type is not technically correct, but it's the best we can do for now since prosemirror doesn't expose the type of decorations
|
|
220
|
+
decorations: readonly DecorationWithType[];
|
|
221
|
+
selected: boolean;
|
|
222
|
+
updateAttributes: (attributes: Record<string, any>) => void;
|
|
223
|
+
deleteNode: () => void;
|
|
224
|
+
}
|
|
225
|
+
>;
|
|
201
226
|
|
|
202
227
|
export interface NodeViewRendererOptions {
|
|
203
228
|
stopEvent: ((props: { event: Event }) => boolean) | null;
|
|
@@ -208,15 +233,19 @@ export interface NodeViewRendererOptions {
|
|
|
208
233
|
}
|
|
209
234
|
|
|
210
235
|
export type NodeViewRendererProps = {
|
|
236
|
+
// pass-through from prosemirror
|
|
237
|
+
node: Parameters<NodeViewConstructor>[0];
|
|
238
|
+
view: Parameters<NodeViewConstructor>[1];
|
|
239
|
+
getPos: () => number; // TODO getPos was incorrectly typed before, change to `Parameters<NodeViewConstructor>[2];` in the next major version
|
|
240
|
+
decorations: Parameters<NodeViewConstructor>[3];
|
|
241
|
+
innerDecorations: Parameters<NodeViewConstructor>[4];
|
|
242
|
+
// tiptap-specific
|
|
211
243
|
editor: Editor;
|
|
212
|
-
node: ProseMirrorNode;
|
|
213
|
-
getPos: (() => number) | boolean;
|
|
214
|
-
HTMLAttributes: Record<string, any>;
|
|
215
|
-
decorations: Decoration[];
|
|
216
244
|
extension: Node;
|
|
245
|
+
HTMLAttributes: Record<string, any>;
|
|
217
246
|
};
|
|
218
247
|
|
|
219
|
-
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView
|
|
248
|
+
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView;
|
|
220
249
|
|
|
221
250
|
export type AnyCommands = Record<string, (...args: any[]) => Command>;
|
|
222
251
|
|
|
@@ -23,7 +23,24 @@ export function mergeAttributes(...objects: Record<string, any>[]): Record<strin
|
|
|
23
23
|
|
|
24
24
|
mergedAttributes[key] = [...existingClasses, ...insertClasses].join(' ')
|
|
25
25
|
} else if (key === 'style') {
|
|
26
|
-
|
|
26
|
+
const newStyles: string[] = value ? value.split(';').map((style: string) => style.trim()).filter(Boolean) : []
|
|
27
|
+
const existingStyles: string[] = mergedAttributes[key] ? mergedAttributes[key].split(';').map((style: string) => style.trim()).filter(Boolean) : []
|
|
28
|
+
|
|
29
|
+
const styleMap = new Map<string, string>()
|
|
30
|
+
|
|
31
|
+
existingStyles.forEach(style => {
|
|
32
|
+
const [property, val] = style.split(':').map(part => part.trim())
|
|
33
|
+
|
|
34
|
+
styleMap.set(property, val)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
newStyles.forEach(style => {
|
|
38
|
+
const [property, val] = style.split(':').map(part => part.trim())
|
|
39
|
+
|
|
40
|
+
styleMap.set(property, val)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
mergedAttributes[key] = Array.from(styleMap.entries()).map(([property, val]) => `${property}: ${val}`).join('; ')
|
|
27
44
|
} else {
|
|
28
45
|
mergedAttributes[key] = value
|
|
29
46
|
}
|