@tiptap/core 2.9.1 → 2.10.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/Editor.d.ts.map +1 -1
- package/dist/EventEmitter.d.ts +1 -0
- package/dist/EventEmitter.d.ts.map +1 -1
- package/dist/Extension.d.ts +2 -2
- package/dist/Extension.d.ts.map +1 -1
- package/dist/InputRule.d.ts.map +1 -1
- package/dist/Mark.d.ts +2 -2
- package/dist/Mark.d.ts.map +1 -1
- package/dist/Node.d.ts +18 -2
- package/dist/Node.d.ts.map +1 -1
- package/dist/PasteRule.d.ts +1 -1
- package/dist/PasteRule.d.ts.map +1 -1
- package/dist/commands/insertContent.d.ts +2 -2
- package/dist/commands/insertContent.d.ts.map +1 -1
- package/dist/commands/insertContentAt.d.ts +2 -2
- package/dist/commands/insertContentAt.d.ts.map +1 -1
- package/dist/commands/setContent.d.ts +2 -2
- package/dist/commands/setContent.d.ts.map +1 -1
- package/dist/commands/setNode.d.ts.map +1 -1
- package/dist/commands/updateAttributes.d.ts.map +1 -1
- package/dist/helpers/createDocument.d.ts +2 -2
- package/dist/helpers/createDocument.d.ts.map +1 -1
- package/dist/helpers/createNodeFromContent.d.ts +1 -1
- package/dist/helpers/createNodeFromContent.d.ts.map +1 -1
- package/dist/helpers/getMarkRange.d.ts +17 -1
- package/dist/helpers/getMarkRange.d.ts.map +1 -1
- package/dist/helpers/getSchemaByResolvedExtensions.d.ts.map +1 -1
- package/dist/index.cjs +158 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +159 -45
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +158 -44
- package/dist/index.umd.js.map +1 -1
- package/dist/inputRules/markInputRule.d.ts +1 -1
- package/dist/inputRules/nodeInputRule.d.ts +1 -1
- package/dist/inputRules/textInputRule.d.ts +1 -1
- package/dist/inputRules/textblockTypeInputRule.d.ts +1 -1
- package/dist/inputRules/wrappingInputRule.d.ts +1 -1
- package/dist/pasteRules/markPasteRule.d.ts +1 -1
- package/dist/pasteRules/nodePasteRule.d.ts +1 -1
- package/dist/pasteRules/textPasteRule.d.ts +1 -1
- package/package.json +2 -2
- package/src/Editor.ts +5 -8
- package/src/EventEmitter.ts +9 -0
- package/src/Extension.ts +2 -2
- package/src/InputRule.ts +45 -30
- package/src/Mark.ts +2 -2
- package/src/Node.ts +21 -2
- package/src/PasteRule.ts +67 -42
- package/src/commands/insertContent.ts +9 -9
- package/src/commands/insertContentAt.ts +11 -1
- package/src/commands/setContent.ts +10 -14
- package/src/commands/setNode.ts +9 -2
- package/src/commands/updateAttributes.ts +72 -12
- package/src/helpers/createDocument.ts +4 -2
- package/src/helpers/createNodeFromContent.ts +4 -1
- package/src/helpers/getMarkRange.ts +29 -5
- package/src/helpers/getSchemaByResolvedExtensions.ts +1 -0
- 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
|
@@ -4,7 +4,7 @@ import { ExtendedRegExpMatchArray } from '../types.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Build an input rule that adds a mark when the
|
|
6
6
|
* matched text is typed into it.
|
|
7
|
-
* @see https://tiptap.dev/
|
|
7
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
8
8
|
*/
|
|
9
9
|
export declare function markInputRule(config: {
|
|
10
10
|
find: InputRuleFinder;
|
|
@@ -4,7 +4,7 @@ import { ExtendedRegExpMatchArray } from '../types.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Build an input rule that adds a node when the
|
|
6
6
|
* matched text is typed into it.
|
|
7
|
-
* @see https://tiptap.dev/
|
|
7
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
8
8
|
*/
|
|
9
9
|
export declare function nodeInputRule(config: {
|
|
10
10
|
/**
|
|
@@ -2,7 +2,7 @@ import { InputRule, InputRuleFinder } from '../InputRule.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Build an input rule that replaces text when the
|
|
4
4
|
* matched text is typed into it.
|
|
5
|
-
* @see https://tiptap.dev/
|
|
5
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
6
6
|
*/
|
|
7
7
|
export declare function textInputRule(config: {
|
|
8
8
|
find: InputRuleFinder;
|
|
@@ -6,7 +6,7 @@ import { ExtendedRegExpMatchArray } from '../types.js';
|
|
|
6
6
|
* matched text is typed into it. When using a regular expresion you’ll
|
|
7
7
|
* probably want the regexp to start with `^`, so that the pattern can
|
|
8
8
|
* only occur at the start of a textblock.
|
|
9
|
-
* @see https://tiptap.dev/
|
|
9
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
10
10
|
*/
|
|
11
11
|
export declare function textblockTypeInputRule(config: {
|
|
12
12
|
find: InputRuleFinder;
|
|
@@ -15,7 +15,7 @@ import { ExtendedRegExpMatchArray } from '../types.js';
|
|
|
15
15
|
* two nodes. You can pass a join predicate, which takes a regular
|
|
16
16
|
* expression match and the node before the wrapped node, and can
|
|
17
17
|
* return a boolean to indicate whether a join should happen.
|
|
18
|
-
* @see https://tiptap.dev/
|
|
18
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
19
19
|
*/
|
|
20
20
|
export declare function wrappingInputRule(config: {
|
|
21
21
|
find: InputRuleFinder;
|
|
@@ -4,7 +4,7 @@ import { ExtendedRegExpMatchArray } from '../types.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Build an paste rule that adds a mark when the
|
|
6
6
|
* matched text is pasted into it.
|
|
7
|
-
* @see https://tiptap.dev/
|
|
7
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#paste-rules
|
|
8
8
|
*/
|
|
9
9
|
export declare function markPasteRule(config: {
|
|
10
10
|
find: PasteRuleFinder;
|
|
@@ -4,7 +4,7 @@ import { ExtendedRegExpMatchArray, JSONContent } from '../types.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Build an paste rule that adds a node when the
|
|
6
6
|
* matched text is pasted into it.
|
|
7
|
-
* @see https://tiptap.dev/
|
|
7
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#paste-rules
|
|
8
8
|
*/
|
|
9
9
|
export declare function nodePasteRule(config: {
|
|
10
10
|
find: PasteRuleFinder;
|
|
@@ -2,7 +2,7 @@ import { PasteRule, PasteRuleFinder } from '../PasteRule.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Build an paste rule that replaces text when the
|
|
4
4
|
* matched text is pasted into it.
|
|
5
|
-
* @see https://tiptap.dev/
|
|
5
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#paste-rules
|
|
6
6
|
*/
|
|
7
7
|
export declare function textPasteRule(config: {
|
|
8
8
|
find: PasteRuleFinder;
|
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.10.0",
|
|
5
5
|
"homepage": "https://tiptap.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"tiptap",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@tiptap/pm": "^2.
|
|
35
|
+
"@tiptap/pm": "^2.10.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@tiptap/pm": "^2.7.0"
|
package/src/Editor.ts
CHANGED
|
@@ -360,6 +360,11 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
360
360
|
|
|
361
361
|
this.view = new EditorView(this.options.element, {
|
|
362
362
|
...this.options.editorProps,
|
|
363
|
+
attributes: {
|
|
364
|
+
// add `role="textbox"` to the editor element
|
|
365
|
+
role: 'textbox',
|
|
366
|
+
...this.options.editorProps?.attributes,
|
|
367
|
+
},
|
|
363
368
|
dispatchTransaction: this.dispatchTransaction.bind(this),
|
|
364
369
|
state: EditorState.create({
|
|
365
370
|
doc,
|
|
@@ -367,14 +372,6 @@ export class Editor extends EventEmitter<EditorEvents> {
|
|
|
367
372
|
}),
|
|
368
373
|
})
|
|
369
374
|
|
|
370
|
-
// add `role="textbox"` to the editor element
|
|
371
|
-
this.view.dom.setAttribute('role', 'textbox')
|
|
372
|
-
|
|
373
|
-
// add aria-label to the editor element
|
|
374
|
-
if (!this.view.dom.getAttribute('aria-label')) {
|
|
375
|
-
this.view.dom.setAttribute('aria-label', 'Rich-Text Editor')
|
|
376
|
-
}
|
|
377
|
-
|
|
378
375
|
// `editor.view` is not yet available at this time.
|
|
379
376
|
// Therefore we will add all plugins and node views directly afterwards.
|
|
380
377
|
const newState = this.state.reconfigure({
|
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/Extension.ts
CHANGED
|
@@ -61,7 +61,7 @@ declare module '@tiptap/core' {
|
|
|
61
61
|
*/
|
|
62
62
|
addOptions?: (this: {
|
|
63
63
|
name: string
|
|
64
|
-
parent:
|
|
64
|
+
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addOptions']
|
|
65
65
|
}) => Options
|
|
66
66
|
|
|
67
67
|
/**
|
|
@@ -76,7 +76,7 @@ declare module '@tiptap/core' {
|
|
|
76
76
|
addStorage?: (this: {
|
|
77
77
|
name: string
|
|
78
78
|
options: Options
|
|
79
|
-
parent:
|
|
79
|
+
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addStorage']
|
|
80
80
|
}) => Storage
|
|
81
81
|
|
|
82
82
|
/**
|
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/Mark.ts
CHANGED
|
@@ -64,7 +64,7 @@ declare module '@tiptap/core' {
|
|
|
64
64
|
*/
|
|
65
65
|
addOptions?: (this: {
|
|
66
66
|
name: string
|
|
67
|
-
parent:
|
|
67
|
+
parent: ParentConfig<MarkConfig<Options, Storage>>['addOptions']
|
|
68
68
|
}) => Options
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -79,7 +79,7 @@ declare module '@tiptap/core' {
|
|
|
79
79
|
addStorage?: (this: {
|
|
80
80
|
name: string
|
|
81
81
|
options: Options
|
|
82
|
-
parent:
|
|
82
|
+
parent: ParentConfig<MarkConfig<Options, Storage>>['addStorage']
|
|
83
83
|
}) => Storage
|
|
84
84
|
|
|
85
85
|
/**
|
package/src/Node.ts
CHANGED
|
@@ -65,7 +65,7 @@ declare module '@tiptap/core' {
|
|
|
65
65
|
*/
|
|
66
66
|
addOptions?: (this: {
|
|
67
67
|
name: string
|
|
68
|
-
parent:
|
|
68
|
+
parent: ParentConfig<NodeConfig<Options, Storage>>['addOptions']
|
|
69
69
|
}) => Options
|
|
70
70
|
|
|
71
71
|
/**
|
|
@@ -80,7 +80,7 @@ declare module '@tiptap/core' {
|
|
|
80
80
|
addStorage?: (this: {
|
|
81
81
|
name: string
|
|
82
82
|
options: Options
|
|
83
|
-
parent:
|
|
83
|
+
parent: ParentConfig<NodeConfig<Options, Storage>>['addStorage']
|
|
84
84
|
}) => Storage
|
|
85
85
|
|
|
86
86
|
/**
|
|
@@ -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/PasteRule.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { Fragment, Node as ProseMirrorNode } from '@tiptap/pm/model'
|
|
1
2
|
import { EditorState, Plugin } 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 {
|
|
7
9
|
CanCommands,
|
|
8
10
|
ChainedCommands,
|
|
@@ -14,45 +16,47 @@ import { isNumber } from './utilities/isNumber.js'
|
|
|
14
16
|
import { isRegExp } from './utilities/isRegExp.js'
|
|
15
17
|
|
|
16
18
|
export type PasteRuleMatch = {
|
|
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 PasteRuleFinder =
|
|
26
|
+
export type PasteRuleFinder =
|
|
27
|
+
| RegExp
|
|
28
|
+
| ((text: string, event?: ClipboardEvent | null) => PasteRuleMatch[] | null | undefined);
|
|
25
29
|
|
|
26
30
|
/**
|
|
27
31
|
* Paste rules are used to react to pasted content.
|
|
28
|
-
* @see https://tiptap.dev/
|
|
32
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#paste-rules
|
|
29
33
|
*/
|
|
30
34
|
export class PasteRule {
|
|
31
35
|
find: PasteRuleFinder
|
|
32
36
|
|
|
33
37
|
handler: (props: {
|
|
34
|
-
state: EditorState
|
|
35
|
-
range: Range
|
|
36
|
-
match: ExtendedRegExpMatchArray
|
|
37
|
-
commands: SingleCommands
|
|
38
|
-
chain: () => ChainedCommands
|
|
39
|
-
can: () => CanCommands
|
|
40
|
-
pasteEvent: ClipboardEvent | null
|
|
41
|
-
dropEvent: DragEvent | null
|
|
38
|
+
state: EditorState;
|
|
39
|
+
range: Range;
|
|
40
|
+
match: ExtendedRegExpMatchArray;
|
|
41
|
+
commands: SingleCommands;
|
|
42
|
+
chain: () => ChainedCommands;
|
|
43
|
+
can: () => CanCommands;
|
|
44
|
+
pasteEvent: ClipboardEvent | null;
|
|
45
|
+
dropEvent: DragEvent | null;
|
|
42
46
|
}) => void | null
|
|
43
47
|
|
|
44
48
|
constructor(config: {
|
|
45
|
-
find: PasteRuleFinder
|
|
49
|
+
find: PasteRuleFinder;
|
|
46
50
|
handler: (props: {
|
|
47
|
-
can: () => CanCommands
|
|
48
|
-
chain: () => ChainedCommands
|
|
49
|
-
commands: SingleCommands
|
|
50
|
-
dropEvent: DragEvent | null
|
|
51
|
-
match: ExtendedRegExpMatchArray
|
|
52
|
-
pasteEvent: ClipboardEvent | null
|
|
53
|
-
range: Range
|
|
54
|
-
state: EditorState
|
|
55
|
-
}) => void | null
|
|
51
|
+
can: () => CanCommands;
|
|
52
|
+
chain: () => ChainedCommands;
|
|
53
|
+
commands: SingleCommands;
|
|
54
|
+
dropEvent: DragEvent | null;
|
|
55
|
+
match: ExtendedRegExpMatchArray;
|
|
56
|
+
pasteEvent: ClipboardEvent | null;
|
|
57
|
+
range: Range;
|
|
58
|
+
state: EditorState;
|
|
59
|
+
}) => void | null;
|
|
56
60
|
}) {
|
|
57
61
|
this.find = config.find
|
|
58
62
|
this.handler = config.handler
|
|
@@ -96,13 +100,13 @@ const pasteRuleMatcherHandler = (
|
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
function run(config: {
|
|
99
|
-
editor: Editor
|
|
100
|
-
state: EditorState
|
|
101
|
-
from: number
|
|
102
|
-
to: number
|
|
103
|
-
rule: PasteRule
|
|
104
|
-
pasteEvent: ClipboardEvent | null
|
|
105
|
-
dropEvent: DragEvent | null
|
|
103
|
+
editor: Editor;
|
|
104
|
+
state: EditorState;
|
|
105
|
+
from: number;
|
|
106
|
+
to: number;
|
|
107
|
+
rule: PasteRule;
|
|
108
|
+
pasteEvent: ClipboardEvent | null;
|
|
109
|
+
dropEvent: DragEvent | null;
|
|
106
110
|
}): boolean {
|
|
107
111
|
const {
|
|
108
112
|
editor, state, from, to, rule, pasteEvent, dropEvent,
|
|
@@ -179,7 +183,13 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }):
|
|
|
179
183
|
let isPastedFromProseMirror = false
|
|
180
184
|
let isDroppedFromProseMirror = false
|
|
181
185
|
let pasteEvent = typeof ClipboardEvent !== 'undefined' ? new ClipboardEvent('paste') : null
|
|
182
|
-
let dropEvent
|
|
186
|
+
let dropEvent: DragEvent | null
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
dropEvent = typeof DragEvent !== 'undefined' ? new DragEvent('drop') : null
|
|
190
|
+
} catch (e) {
|
|
191
|
+
dropEvent = null
|
|
192
|
+
}
|
|
183
193
|
|
|
184
194
|
const processEvent = ({
|
|
185
195
|
state,
|
|
@@ -188,11 +198,11 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }):
|
|
|
188
198
|
rule,
|
|
189
199
|
pasteEvt,
|
|
190
200
|
}: {
|
|
191
|
-
state: EditorState
|
|
192
|
-
from: number
|
|
193
|
-
to: { b: number }
|
|
194
|
-
rule: PasteRule
|
|
195
|
-
pasteEvt: ClipboardEvent | null
|
|
201
|
+
state: EditorState;
|
|
202
|
+
from: number;
|
|
203
|
+
to: { b: number };
|
|
204
|
+
rule: PasteRule;
|
|
205
|
+
pasteEvt: ClipboardEvent | null;
|
|
196
206
|
}) => {
|
|
197
207
|
const tr = state.tr
|
|
198
208
|
const chainableState = createChainableState({
|
|
@@ -214,7 +224,11 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }):
|
|
|
214
224
|
return
|
|
215
225
|
}
|
|
216
226
|
|
|
217
|
-
|
|
227
|
+
try {
|
|
228
|
+
dropEvent = typeof DragEvent !== 'undefined' ? new DragEvent('drop') : null
|
|
229
|
+
} catch (e) {
|
|
230
|
+
dropEvent = null
|
|
231
|
+
}
|
|
218
232
|
pasteEvent = typeof ClipboardEvent !== 'undefined' ? new ClipboardEvent('paste') : null
|
|
219
233
|
|
|
220
234
|
return tr
|
|
@@ -266,7 +280,9 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }):
|
|
|
266
280
|
const isDrop = transaction.getMeta('uiEvent') === 'drop' && !isDroppedFromProseMirror
|
|
267
281
|
|
|
268
282
|
// if PasteRule is triggered by insertContent()
|
|
269
|
-
const simulatedPasteMeta = transaction.getMeta('applyPasteRules')
|
|
283
|
+
const simulatedPasteMeta = transaction.getMeta('applyPasteRules') as
|
|
284
|
+
| undefined
|
|
285
|
+
| { from: number; text: string | ProseMirrorNode | Fragment }
|
|
270
286
|
const isSimulatedPaste = !!simulatedPasteMeta
|
|
271
287
|
|
|
272
288
|
if (!isPaste && !isDrop && !isSimulatedPaste) {
|
|
@@ -275,8 +291,17 @@ export function pasteRulesPlugin(props: { editor: Editor; rules: PasteRule[] }):
|
|
|
275
291
|
|
|
276
292
|
// Handle simulated paste
|
|
277
293
|
if (isSimulatedPaste) {
|
|
278
|
-
|
|
294
|
+
let { text } = simulatedPasteMeta
|
|
295
|
+
|
|
296
|
+
if (typeof text === 'string') {
|
|
297
|
+
text = text as string
|
|
298
|
+
} else {
|
|
299
|
+
text = getHTMLFromFragment(Fragment.from(text), state.schema)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const { from } = simulatedPasteMeta
|
|
279
303
|
const to = from + text.length
|
|
304
|
+
|
|
280
305
|
const pasteEvt = createClipboardPasteEvent(text)
|
|
281
306
|
|
|
282
307
|
return processEvent({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ParseOptions } from '@tiptap/pm/model'
|
|
1
|
+
import { Fragment, Node as ProseMirrorNode, ParseOptions } from '@tiptap/pm/model'
|
|
2
2
|
|
|
3
3
|
import { Content, RawCommands } from '../types.js'
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ declare module '@tiptap/core' {
|
|
|
14
14
|
/**
|
|
15
15
|
* The ProseMirror content to insert.
|
|
16
16
|
*/
|
|
17
|
-
value: Content,
|
|
17
|
+
value: Content | ProseMirrorNode | Fragment,
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Optional options
|
|
@@ -23,17 +23,17 @@ declare module '@tiptap/core' {
|
|
|
23
23
|
/**
|
|
24
24
|
* Options for parsing the content.
|
|
25
25
|
*/
|
|
26
|
-
parseOptions?: ParseOptions
|
|
26
|
+
parseOptions?: ParseOptions;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Whether to update the selection after inserting the content.
|
|
30
30
|
*/
|
|
31
|
-
updateSelection?: boolean
|
|
32
|
-
applyInputRules?: boolean
|
|
33
|
-
applyPasteRules?: boolean
|
|
34
|
-
}
|
|
35
|
-
) => ReturnType
|
|
36
|
-
}
|
|
31
|
+
updateSelection?: boolean;
|
|
32
|
+
applyInputRules?: boolean;
|
|
33
|
+
applyPasteRules?: boolean;
|
|
34
|
+
}
|
|
35
|
+
) => ReturnType;
|
|
36
|
+
};
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -20,7 +20,7 @@ declare module '@tiptap/core' {
|
|
|
20
20
|
/**
|
|
21
21
|
* The ProseMirror content to insert.
|
|
22
22
|
*/
|
|
23
|
-
value: Content,
|
|
23
|
+
value: Content | ProseMirrorNode | Fragment,
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Optional options
|
|
@@ -132,6 +132,16 @@ export const insertContentAt: RawCommands['insertContentAt'] = (position, value,
|
|
|
132
132
|
// otherwise if it is an array, we have to join it
|
|
133
133
|
if (Array.isArray(value)) {
|
|
134
134
|
newContent = value.map(v => v.text || '').join('')
|
|
135
|
+
} else if (value instanceof Fragment) {
|
|
136
|
+
let text = ''
|
|
137
|
+
|
|
138
|
+
value.forEach(node => {
|
|
139
|
+
if (node.text) {
|
|
140
|
+
text += node.text
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
newContent = text
|
|
135
145
|
} else if (typeof value === 'object' && !!value && !!value.text) {
|
|
136
146
|
newContent = value.text
|
|
137
147
|
} else {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ParseOptions } from '@tiptap/pm/model'
|
|
1
|
+
import { Fragment, Node as ProseMirrorNode, ParseOptions } from '@tiptap/pm/model'
|
|
2
2
|
|
|
3
3
|
import { createDocument } from '../helpers/createDocument.js'
|
|
4
4
|
import { Content, RawCommands } from '../types.js'
|
|
@@ -17,7 +17,7 @@ declare module '@tiptap/core' {
|
|
|
17
17
|
/**
|
|
18
18
|
* The new content.
|
|
19
19
|
*/
|
|
20
|
-
content: Content,
|
|
20
|
+
content: Content | Fragment | ProseMirrorNode,
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Whether to emit an update event.
|
|
@@ -37,10 +37,10 @@ declare module '@tiptap/core' {
|
|
|
37
37
|
/**
|
|
38
38
|
* Whether to throw an error if the content is invalid.
|
|
39
39
|
*/
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
) => ReturnType
|
|
43
|
-
}
|
|
40
|
+
errorOnInvalidContent?: boolean;
|
|
41
|
+
}
|
|
42
|
+
) => ReturnType;
|
|
43
|
+
};
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -66,12 +66,8 @@ export const setContent: RawCommands['setContent'] = (content, emitUpdate = fals
|
|
|
66
66
|
tr.setMeta('preventUpdate', !emitUpdate)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
return commands.insertContentAt(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
parseOptions,
|
|
74
|
-
errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
|
|
75
|
-
},
|
|
76
|
-
)
|
|
69
|
+
return commands.insertContentAt({ from: 0, to: doc.content.size }, content, {
|
|
70
|
+
parseOptions,
|
|
71
|
+
errorOnInvalidContent: options.errorOnInvalidContent ?? editor.options.enableContentCheck,
|
|
72
|
+
})
|
|
77
73
|
}
|