@tiptap/core 3.0.0-next.1 → 3.0.0-next.2
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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
|
2
|
+
|
|
3
|
+
import { Extension } from '../Extension.js'
|
|
4
|
+
|
|
5
|
+
export const Paste = Extension.create({
|
|
6
|
+
name: 'paste',
|
|
7
|
+
|
|
8
|
+
addProseMirrorPlugins() {
|
|
9
|
+
|
|
10
|
+
return [
|
|
11
|
+
new Plugin({
|
|
12
|
+
key: new PluginKey('tiptapPaste'),
|
|
13
|
+
|
|
14
|
+
props: {
|
|
15
|
+
handlePaste: (_view, e, slice) => {
|
|
16
|
+
this.editor.emit('paste', {
|
|
17
|
+
editor: this.editor,
|
|
18
|
+
event: e,
|
|
19
|
+
slice,
|
|
20
|
+
})
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
})
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Fragment, Node as ProseMirrorNode, ParseOptions, Schema,
|
|
3
|
+
} from '@tiptap/pm/model'
|
|
2
4
|
|
|
3
5
|
import { Content } from '../types.js'
|
|
4
6
|
import { createNodeFromContent } from './createNodeFromContent.js'
|
|
@@ -11,7 +13,7 @@ import { createNodeFromContent } from './createNodeFromContent.js'
|
|
|
11
13
|
* @returns The created Prosemirror document node
|
|
12
14
|
*/
|
|
13
15
|
export function createDocument(
|
|
14
|
-
content: Content,
|
|
16
|
+
content: Content | ProseMirrorNode | Fragment,
|
|
15
17
|
schema: Schema,
|
|
16
18
|
parseOptions: ParseOptions = {},
|
|
17
19
|
options: { errorOnInvalidContent?: boolean } = {},
|
|
@@ -23,10 +23,13 @@ export type CreateNodeFromContentOptions = {
|
|
|
23
23
|
* @returns The created Prosemirror node or fragment
|
|
24
24
|
*/
|
|
25
25
|
export function createNodeFromContent(
|
|
26
|
-
content: Content,
|
|
26
|
+
content: Content | ProseMirrorNode | Fragment,
|
|
27
27
|
schema: Schema,
|
|
28
28
|
options?: CreateNodeFromContentOptions,
|
|
29
29
|
): ProseMirrorNode | Fragment {
|
|
30
|
+
if (content instanceof ProseMirrorNode || content instanceof Fragment) {
|
|
31
|
+
return content
|
|
32
|
+
}
|
|
30
33
|
options = {
|
|
31
34
|
slice: true,
|
|
32
35
|
parseOptions: {},
|
|
@@ -45,7 +48,13 @@ export function createNodeFromContent(
|
|
|
45
48
|
return Fragment.fromArray(content.map(item => schema.nodeFromJSON(item)))
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
const node = schema.nodeFromJSON(content)
|
|
52
|
+
|
|
53
|
+
if (options.errorOnInvalidContent) {
|
|
54
|
+
node.check()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return node
|
|
49
58
|
} catch (error) {
|
|
50
59
|
if (options.errorOnInvalidContent) {
|
|
51
60
|
throw new Error('[tiptap error]: Invalid JSON content', { cause: error as Error })
|
|
@@ -9,7 +9,14 @@ function findMarkInSet(
|
|
|
9
9
|
attributes: Record<string, any> = {},
|
|
10
10
|
): ProseMirrorMark | undefined {
|
|
11
11
|
return marks.find(item => {
|
|
12
|
-
return
|
|
12
|
+
return (
|
|
13
|
+
item.type === type
|
|
14
|
+
&& objectIncludes(
|
|
15
|
+
// Only check equality for the attributes that are provided
|
|
16
|
+
Object.fromEntries(Object.keys(attributes).map(k => [k, item.attrs[k]])),
|
|
17
|
+
attributes,
|
|
18
|
+
)
|
|
19
|
+
)
|
|
13
20
|
})
|
|
14
21
|
}
|
|
15
22
|
|
|
@@ -21,25 +28,44 @@ function isMarkInSet(
|
|
|
21
28
|
return !!findMarkInSet(marks, type, attributes)
|
|
22
29
|
}
|
|
23
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Get the range of a mark at a resolved position.
|
|
33
|
+
*/
|
|
24
34
|
export function getMarkRange(
|
|
35
|
+
/**
|
|
36
|
+
* The position to get the mark range for.
|
|
37
|
+
*/
|
|
25
38
|
$pos: ResolvedPos,
|
|
39
|
+
/**
|
|
40
|
+
* The mark type to get the range for.
|
|
41
|
+
*/
|
|
26
42
|
type: MarkType,
|
|
27
|
-
|
|
43
|
+
/**
|
|
44
|
+
* The attributes to match against.
|
|
45
|
+
* If not provided, only the first mark at the position will be matched.
|
|
46
|
+
*/
|
|
47
|
+
attributes?: Record<string, any>,
|
|
28
48
|
): Range | void {
|
|
29
49
|
if (!$pos || !type) {
|
|
30
50
|
return
|
|
31
51
|
}
|
|
32
|
-
|
|
33
52
|
let start = $pos.parent.childAfter($pos.parentOffset)
|
|
34
53
|
|
|
35
|
-
|
|
54
|
+
// If the cursor is at the start of a text node that does not have the mark, look backward
|
|
55
|
+
if (!start.node || !start.node.marks.some(mark => mark.type === type)) {
|
|
36
56
|
start = $pos.parent.childBefore($pos.parentOffset)
|
|
37
57
|
}
|
|
38
58
|
|
|
39
|
-
|
|
59
|
+
// If there is no text node with the mark even backward, return undefined
|
|
60
|
+
if (!start.node || !start.node.marks.some(mark => mark.type === type)) {
|
|
40
61
|
return
|
|
41
62
|
}
|
|
42
63
|
|
|
64
|
+
// Default to only matching against the first mark's attributes
|
|
65
|
+
attributes = attributes || start.node.marks[0]?.attrs
|
|
66
|
+
|
|
67
|
+
// We now know that the cursor is either at the start, middle or end of a text node with the specified mark
|
|
68
|
+
// so we can look it up on the targeted mark
|
|
43
69
|
const mark = findMarkInSet([...start.node.marks], type, attributes)
|
|
44
70
|
|
|
45
71
|
if (!mark) {
|
|
@@ -51,9 +77,10 @@ export function getMarkRange(
|
|
|
51
77
|
let endIndex = startIndex + 1
|
|
52
78
|
let endPos = startPos + start.node.nodeSize
|
|
53
79
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
while (
|
|
81
|
+
startIndex > 0
|
|
82
|
+
&& isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)
|
|
83
|
+
) {
|
|
57
84
|
startIndex -= 1
|
|
58
85
|
startPos -= $pos.parent.child(startIndex).nodeSize
|
|
59
86
|
}
|
|
@@ -8,6 +8,9 @@ export function getRenderedAttributes(
|
|
|
8
8
|
extensionAttributes: ExtensionAttribute[],
|
|
9
9
|
): Record<string, any> {
|
|
10
10
|
return extensionAttributes
|
|
11
|
+
.filter(
|
|
12
|
+
attribute => attribute.type === nodeOrMark.type.name,
|
|
13
|
+
)
|
|
11
14
|
.filter(item => item.attribute.rendered)
|
|
12
15
|
.map(item => {
|
|
13
16
|
if (!item.attribute.renderHTML) {
|
|
@@ -78,6 +78,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
|
|
|
78
78
|
),
|
|
79
79
|
code: callOrReturn(getExtensionField<NodeConfig['code']>(extension, 'code', context)),
|
|
80
80
|
whitespace: callOrReturn(getExtensionField<NodeConfig['whitespace']>(extension, 'whitespace', context)),
|
|
81
|
+
linebreakReplacement: callOrReturn(getExtensionField<NodeConfig['linebreakReplacement']>(extension, 'linebreakReplacement', context)),
|
|
81
82
|
defining: callOrReturn(
|
|
82
83
|
getExtensionField<NodeConfig['defining']>(extension, 'defining', context),
|
|
83
84
|
),
|
|
@@ -147,7 +148,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
|
|
|
147
148
|
|
|
148
149
|
return {
|
|
149
150
|
...fields,
|
|
150
|
-
...(extendMarkSchema ? extendMarkSchema(extension) : {}),
|
|
151
|
+
...(extendMarkSchema ? extendMarkSchema(extension as any) : {}),
|
|
151
152
|
}
|
|
152
153
|
}, {})
|
|
153
154
|
|
|
@@ -8,7 +8,7 @@ import { callOrReturn } from '../utilities/callOrReturn.js'
|
|
|
8
8
|
/**
|
|
9
9
|
* Build an input rule that adds a mark when the
|
|
10
10
|
* matched text is typed into it.
|
|
11
|
-
* @see https://tiptap.dev/
|
|
11
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
12
12
|
*/
|
|
13
13
|
export function markInputRule(config: {
|
|
14
14
|
find: InputRuleFinder
|
|
@@ -7,7 +7,7 @@ import { callOrReturn } from '../utilities/callOrReturn.js'
|
|
|
7
7
|
/**
|
|
8
8
|
* Build an input rule that adds a node when the
|
|
9
9
|
* matched text is typed into it.
|
|
10
|
-
* @see https://tiptap.dev/
|
|
10
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
11
11
|
*/
|
|
12
12
|
export function nodeInputRule(config: {
|
|
13
13
|
/**
|
|
@@ -3,7 +3,7 @@ import { InputRule, InputRuleFinder } from '../InputRule.js'
|
|
|
3
3
|
/**
|
|
4
4
|
* Build an input rule that replaces text when the
|
|
5
5
|
* matched text is typed into it.
|
|
6
|
-
* @see https://tiptap.dev/
|
|
6
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
7
7
|
*/
|
|
8
8
|
export function textInputRule(config: {
|
|
9
9
|
find: InputRuleFinder,
|
|
@@ -9,7 +9,7 @@ import { callOrReturn } from '../utilities/callOrReturn.js'
|
|
|
9
9
|
* matched text is typed into it. When using a regular expresion you’ll
|
|
10
10
|
* probably want the regexp to start with `^`, so that the pattern can
|
|
11
11
|
* only occur at the start of a textblock.
|
|
12
|
-
* @see https://tiptap.dev/
|
|
12
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
13
13
|
*/
|
|
14
14
|
export function textblockTypeInputRule(config: {
|
|
15
15
|
find: InputRuleFinder
|
|
@@ -19,7 +19,7 @@ import { callOrReturn } from '../utilities/callOrReturn.js'
|
|
|
19
19
|
* two nodes. You can pass a join predicate, which takes a regular
|
|
20
20
|
* expression match and the node before the wrapped node, and can
|
|
21
21
|
* return a boolean to indicate whether a join should happen.
|
|
22
|
-
* @see https://tiptap.dev/
|
|
22
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#input-rules
|
|
23
23
|
*/
|
|
24
24
|
export function wrappingInputRule(config: {
|
|
25
25
|
find: InputRuleFinder,
|
|
@@ -8,7 +8,7 @@ import { callOrReturn } from '../utilities/callOrReturn.js'
|
|
|
8
8
|
/**
|
|
9
9
|
* Build an paste rule that adds a mark when the
|
|
10
10
|
* matched text is pasted into it.
|
|
11
|
-
* @see https://tiptap.dev/
|
|
11
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#paste-rules
|
|
12
12
|
*/
|
|
13
13
|
export function markPasteRule(config: {
|
|
14
14
|
find: PasteRuleFinder
|
|
@@ -7,7 +7,7 @@ import { callOrReturn } from '../utilities/index.js'
|
|
|
7
7
|
/**
|
|
8
8
|
* Build an paste rule that adds a node when the
|
|
9
9
|
* matched text is pasted into it.
|
|
10
|
-
* @see https://tiptap.dev/
|
|
10
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#paste-rules
|
|
11
11
|
*/
|
|
12
12
|
export function nodePasteRule(config: {
|
|
13
13
|
find: PasteRuleFinder
|
|
@@ -3,7 +3,7 @@ import { PasteRule, PasteRuleFinder } from '../PasteRule.js'
|
|
|
3
3
|
/**
|
|
4
4
|
* Build an paste rule that replaces text when the
|
|
5
5
|
* matched text is pasted into it.
|
|
6
|
-
* @see https://tiptap.dev/
|
|
6
|
+
* @see https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing#paste-rules
|
|
7
7
|
*/
|
|
8
8
|
export function textPasteRule(config: {
|
|
9
9
|
find: PasteRuleFinder,
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Mark as ProseMirrorMark,
|
|
3
3
|
Node as ProseMirrorNode,
|
|
4
|
-
NodeType,
|
|
5
4
|
ParseOptions,
|
|
5
|
+
Slice,
|
|
6
6
|
} from '@tiptap/pm/model'
|
|
7
7
|
import { EditorState, Transaction } from '@tiptap/pm/state'
|
|
8
|
+
import { Mappable } from '@tiptap/pm/transform'
|
|
8
9
|
import {
|
|
9
|
-
Decoration,
|
|
10
|
+
Decoration,
|
|
11
|
+
DecorationAttrs,
|
|
12
|
+
EditorProps,
|
|
13
|
+
EditorView,
|
|
14
|
+
NodeView,
|
|
15
|
+
NodeViewConstructor,
|
|
16
|
+
ViewMutationRecord,
|
|
10
17
|
} from '@tiptap/pm/view'
|
|
11
18
|
|
|
12
19
|
import { Editor } from './Editor.js'
|
|
@@ -58,6 +65,8 @@ export interface EditorEvents {
|
|
|
58
65
|
focus: { editor: Editor; event: FocusEvent; transaction: Transaction };
|
|
59
66
|
blur: { editor: Editor; event: FocusEvent; transaction: Transaction };
|
|
60
67
|
destroy: void;
|
|
68
|
+
paste: { editor: Editor; event: ClipboardEvent; slice: Slice };
|
|
69
|
+
drop: { editor: Editor; event: DragEvent; slice: Slice; moved: boolean };
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
export type EnableRules = (AnyExtension | string)[] | boolean;
|
|
@@ -79,7 +88,38 @@ export interface EditorOptions {
|
|
|
79
88
|
};
|
|
80
89
|
enableInputRules: EnableRules;
|
|
81
90
|
enablePasteRules: EnableRules;
|
|
82
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Determines whether core extensions are enabled.
|
|
93
|
+
*
|
|
94
|
+
* If set to `false`, all core extensions will be disabled.
|
|
95
|
+
* To disable specific core extensions, provide an object where the keys are the extension names and the values are `false`.
|
|
96
|
+
* Extensions not listed in the object will remain enabled.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // Disable all core extensions
|
|
100
|
+
* enabledCoreExtensions: false
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* // Disable only the keymap core extension
|
|
104
|
+
* enabledCoreExtensions: { keymap: false }
|
|
105
|
+
*
|
|
106
|
+
* @default true
|
|
107
|
+
*/
|
|
108
|
+
enableCoreExtensions?:
|
|
109
|
+
| boolean
|
|
110
|
+
| Partial<
|
|
111
|
+
Record<
|
|
112
|
+
| 'editable'
|
|
113
|
+
| 'clipboardTextSerializer'
|
|
114
|
+
| 'commands'
|
|
115
|
+
| 'focusEvents'
|
|
116
|
+
| 'keymap'
|
|
117
|
+
| 'tabindex'
|
|
118
|
+
| 'drop'
|
|
119
|
+
| 'paste',
|
|
120
|
+
false
|
|
121
|
+
>
|
|
122
|
+
>;
|
|
83
123
|
/**
|
|
84
124
|
* If `true`, the editor will check the content for errors on initialization.
|
|
85
125
|
* Emitting the `contentError` event if the content is invalid.
|
|
@@ -100,6 +140,8 @@ export interface EditorOptions {
|
|
|
100
140
|
onFocus: (props: EditorEvents['focus']) => void;
|
|
101
141
|
onBlur: (props: EditorEvents['blur']) => void;
|
|
102
142
|
onDestroy: (props: EditorEvents['destroy']) => void;
|
|
143
|
+
onPaste: (e: ClipboardEvent, slice: Slice) => void;
|
|
144
|
+
onDrop: (e: DragEvent, slice: Slice, moved: boolean) => void;
|
|
103
145
|
}
|
|
104
146
|
|
|
105
147
|
export type HTMLContent = string;
|
|
@@ -184,39 +226,85 @@ export type ValuesOf<T> = T[keyof T];
|
|
|
184
226
|
|
|
185
227
|
export type KeysWithTypeOf<T, Type> = { [P in keyof T]: T[P] extends Type ? P : never }[keyof T];
|
|
186
228
|
|
|
229
|
+
export type DOMNode = InstanceType<typeof window.Node>
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* prosemirror-view does not export the `type` property of `Decoration`.
|
|
233
|
+
* So, this defines the `DecorationType` interface to include the `type` property.
|
|
234
|
+
*/
|
|
235
|
+
export interface DecorationType {
|
|
236
|
+
spec: any
|
|
237
|
+
map(mapping: Mappable, span: Decoration, offset: number, oldOffset: number): Decoration | null
|
|
238
|
+
valid(node: Node, span: Decoration): boolean
|
|
239
|
+
eq(other: DecorationType): boolean
|
|
240
|
+
destroy(dom: DOMNode): void
|
|
241
|
+
readonly attrs: DecorationAttrs
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* prosemirror-view does not export the `type` property of `Decoration`.
|
|
246
|
+
* This adds the `type` property to the `Decoration` type.
|
|
247
|
+
*/
|
|
187
248
|
export type DecorationWithType = Decoration & {
|
|
188
|
-
type:
|
|
249
|
+
type: DecorationType;
|
|
189
250
|
};
|
|
190
251
|
|
|
191
|
-
export
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
decorations: DecorationWithType[];
|
|
252
|
+
export interface NodeViewProps extends NodeViewRendererProps {
|
|
253
|
+
// 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
|
|
254
|
+
decorations: readonly DecorationWithType[];
|
|
195
255
|
selected: boolean;
|
|
196
|
-
extension: Node;
|
|
197
|
-
getPos: () => number;
|
|
198
256
|
updateAttributes: (attributes: Record<string, any>) => void;
|
|
199
257
|
deleteNode: () => void;
|
|
200
|
-
}
|
|
258
|
+
}
|
|
201
259
|
|
|
202
260
|
export interface NodeViewRendererOptions {
|
|
203
261
|
stopEvent: ((props: { event: Event }) => boolean) | null;
|
|
204
262
|
ignoreMutation:
|
|
205
|
-
| ((props: { mutation:
|
|
263
|
+
| ((props: { mutation: ViewMutationRecord }) => boolean)
|
|
206
264
|
| null;
|
|
207
265
|
contentDOMElementTag: string;
|
|
208
266
|
}
|
|
209
267
|
|
|
210
|
-
export
|
|
268
|
+
export interface NodeViewRendererProps {
|
|
269
|
+
// pass-through from prosemirror
|
|
270
|
+
/**
|
|
271
|
+
* The node that is being rendered.
|
|
272
|
+
*/
|
|
273
|
+
node: Parameters<NodeViewConstructor>[0];
|
|
274
|
+
/**
|
|
275
|
+
* The editor's view.
|
|
276
|
+
*/
|
|
277
|
+
view: Parameters<NodeViewConstructor>[1];
|
|
278
|
+
/**
|
|
279
|
+
* A function that can be called to get the node's current position in the document.
|
|
280
|
+
*/
|
|
281
|
+
getPos: Parameters<NodeViewConstructor>[2];
|
|
282
|
+
/**
|
|
283
|
+
* is an array of node or inline decorations that are active around the node.
|
|
284
|
+
* They are automatically drawn in the normal way, and you will usually just want to ignore this, but they can also be used as a way to provide context information to the node view without adding it to the document itself.
|
|
285
|
+
*/
|
|
286
|
+
decorations: Parameters<NodeViewConstructor>[3];
|
|
287
|
+
/**
|
|
288
|
+
* holds the decorations for the node's content. You can safely ignore this if your view has no content or a contentDOM property, since the editor will draw the decorations on the content.
|
|
289
|
+
* But if you, for example, want to create a nested editor with the content, it may make sense to provide it with the inner decorations.
|
|
290
|
+
*/
|
|
291
|
+
innerDecorations: Parameters<NodeViewConstructor>[4];
|
|
292
|
+
// tiptap-specific
|
|
293
|
+
/**
|
|
294
|
+
* The editor instance.
|
|
295
|
+
*/
|
|
211
296
|
editor: Editor;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
decorations: Decoration[];
|
|
297
|
+
/**
|
|
298
|
+
* The extension that is responsible for the node.
|
|
299
|
+
*/
|
|
216
300
|
extension: Node;
|
|
217
|
-
|
|
301
|
+
/**
|
|
302
|
+
* The HTML attributes that should be added to the node's DOM element.
|
|
303
|
+
*/
|
|
304
|
+
HTMLAttributes: Record<string, any>;
|
|
305
|
+
}
|
|
218
306
|
|
|
219
|
-
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView
|
|
307
|
+
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView;
|
|
220
308
|
|
|
221
309
|
export type AnyCommands = Record<string, (...args: any[]) => Command>;
|
|
222
310
|
|
|
@@ -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
|
}
|