@tiptap/core 3.0.0-next.3 → 3.0.0-next.5
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/LICENSE.md +21 -0
- package/README.md +5 -1
- package/dist/index.cjs +2627 -2651
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2423 -2688
- package/dist/index.d.ts +2423 -2688
- package/dist/index.js +2639 -2684
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime/jsx-runtime.cjs +56 -0
- package/dist/jsx-runtime/jsx-runtime.cjs.map +1 -0
- package/dist/jsx-runtime/jsx-runtime.d.cts +22 -0
- package/dist/jsx-runtime/jsx-runtime.d.ts +22 -0
- package/dist/jsx-runtime/jsx-runtime.js +26 -0
- package/dist/jsx-runtime/jsx-runtime.js.map +1 -0
- package/jsx-runtime/index.cjs +1 -0
- package/jsx-runtime/index.d.cts +1 -0
- package/jsx-runtime/index.d.ts +1 -0
- package/jsx-runtime/index.js +1 -0
- package/package.json +27 -6
- package/src/CommandManager.ts +2 -9
- package/src/Editor.ts +191 -94
- package/src/EventEmitter.ts +7 -10
- package/src/Extendable.ts +483 -0
- package/src/Extension.ts +5 -496
- package/src/ExtensionManager.ts +81 -135
- package/src/InputRule.ts +35 -48
- package/src/Mark.ts +135 -623
- package/src/MarkView.ts +66 -0
- package/src/Node.ts +325 -829
- package/src/NodePos.ts +1 -3
- package/src/NodeView.ts +10 -20
- package/src/PasteRule.ts +43 -55
- package/src/Tracker.ts +7 -9
- package/src/commands/blur.ts +14 -12
- package/src/commands/clearContent.ts +12 -5
- package/src/commands/clearNodes.ts +32 -30
- package/src/commands/command.ts +1 -1
- package/src/commands/createParagraphNear.ts +5 -3
- package/src/commands/cut.ts +12 -10
- package/src/commands/deleteCurrentNode.ts +23 -21
- package/src/commands/deleteNode.ts +18 -16
- package/src/commands/deleteRange.ts +10 -8
- package/src/commands/deleteSelection.ts +5 -3
- package/src/commands/enter.ts +6 -4
- package/src/commands/exitCode.ts +5 -3
- package/src/commands/extendMarkRange.ts +14 -12
- package/src/commands/first.ts +2 -4
- package/src/commands/focus.ts +51 -48
- package/src/commands/forEach.ts +2 -2
- package/src/commands/insertContent.ts +12 -14
- package/src/commands/insertContentAt.ts +105 -98
- package/src/commands/join.ts +20 -12
- package/src/commands/joinItemBackward.ts +16 -18
- package/src/commands/joinItemForward.ts +16 -18
- package/src/commands/joinTextblockBackward.ts +5 -3
- package/src/commands/joinTextblockForward.ts +5 -3
- package/src/commands/keyboardShortcut.ts +29 -34
- package/src/commands/lift.ts +10 -8
- package/src/commands/liftEmptyBlock.ts +6 -4
- package/src/commands/liftListItem.ts +6 -4
- package/src/commands/newlineInCode.ts +5 -3
- package/src/commands/resetAttributes.ts +36 -41
- package/src/commands/scrollIntoView.ts +9 -7
- package/src/commands/selectAll.ts +10 -8
- package/src/commands/selectNodeBackward.ts +5 -3
- package/src/commands/selectNodeForward.ts +5 -3
- package/src/commands/selectParentNode.ts +5 -3
- package/src/commands/selectTextblockEnd.ts +5 -3
- package/src/commands/selectTextblockStart.ts +5 -3
- package/src/commands/setContent.ts +37 -36
- package/src/commands/setMark.ts +55 -57
- package/src/commands/setMeta.ts +7 -5
- package/src/commands/setNode.ts +32 -30
- package/src/commands/setNodeSelection.ts +11 -9
- package/src/commands/setTextSelection.ts +15 -13
- package/src/commands/sinkListItem.ts +6 -4
- package/src/commands/splitBlock.ts +67 -76
- package/src/commands/splitListItem.ts +93 -106
- package/src/commands/toggleList.ts +73 -71
- package/src/commands/toggleMark.ts +11 -9
- package/src/commands/toggleNode.ts +18 -16
- package/src/commands/toggleWrap.ts +10 -8
- package/src/commands/undoInputRule.ts +31 -29
- package/src/commands/unsetAllMarks.ts +16 -14
- package/src/commands/unsetMark.ts +27 -25
- package/src/commands/updateAttributes.ts +92 -100
- package/src/commands/wrapIn.ts +6 -4
- package/src/commands/wrapInList.ts +6 -4
- package/src/extensions/clipboardTextSerializer.ts +2 -4
- package/src/extensions/delete.ts +89 -0
- package/src/extensions/focusEvents.ts +2 -6
- package/src/extensions/index.ts +1 -0
- package/src/extensions/keymap.ts +58 -50
- package/src/extensions/paste.ts +0 -1
- package/src/extensions/tabindex.ts +1 -1
- package/src/helpers/combineTransactionSteps.ts +1 -4
- package/src/helpers/createChainableState.ts +1 -4
- package/src/helpers/createDocument.ts +1 -3
- package/src/helpers/createNodeFromContent.ts +4 -10
- package/src/helpers/findChildrenInRange.ts +1 -5
- package/src/helpers/findParentNode.ts +3 -1
- package/src/helpers/flattenExtensions.ts +30 -0
- package/src/helpers/getAttributes.ts +1 -4
- package/src/helpers/getAttributesFromExtensions.ts +28 -37
- package/src/helpers/getChangedRanges.ts +13 -11
- package/src/helpers/getExtensionField.ts +11 -11
- package/src/helpers/getMarkAttributes.ts +1 -4
- package/src/helpers/getMarkRange.ts +5 -15
- package/src/helpers/getMarkType.ts +1 -3
- package/src/helpers/getNodeAttributes.ts +1 -4
- package/src/helpers/getNodeType.ts +1 -3
- package/src/helpers/getRenderedAttributes.ts +1 -3
- package/src/helpers/getSchema.ts +2 -2
- package/src/helpers/getSchemaByResolvedExtensions.ts +45 -77
- package/src/helpers/getSplittedAttributes.ts +4 -4
- package/src/helpers/getTextContentFromNodes.ts +8 -11
- package/src/helpers/index.ts +4 -0
- package/src/helpers/injectExtensionAttributesToParseRule.ts +1 -1
- package/src/helpers/isActive.ts +1 -5
- package/src/helpers/isExtensionRulesEnabled.ts +1 -3
- package/src/helpers/isNodeEmpty.ts +2 -2
- package/src/helpers/resolveExtensions.ts +25 -0
- package/src/helpers/resolveFocusPosition.ts +3 -14
- package/src/helpers/rewriteUnknownContent.ts +149 -0
- package/src/helpers/sortExtensions.ts +26 -0
- package/src/index.ts +3 -7
- package/src/inputRules/markInputRule.ts +1 -5
- package/src/inputRules/nodeInputRule.ts +2 -9
- package/src/inputRules/textInputRule.ts +1 -4
- package/src/inputRules/textblockTypeInputRule.ts +2 -8
- package/src/inputRules/wrappingInputRule.ts +13 -19
- package/src/jsx-runtime.ts +64 -0
- package/src/pasteRules/markPasteRule.ts +1 -3
- package/src/pasteRules/nodePasteRule.ts +2 -8
- package/src/pasteRules/textPasteRule.ts +1 -4
- package/src/types.ts +529 -174
- package/src/utilities/createStyleTag.ts +3 -1
- package/src/utilities/deleteProps.ts +7 -11
- package/src/utilities/elementFromString.ts +3 -0
- package/src/utilities/findDuplicates.ts +4 -1
- package/src/utilities/index.ts +1 -0
- package/src/utilities/isFunction.ts +1 -0
- package/src/utilities/isMacOS.ts +1 -3
- package/src/utilities/isiOS.ts +5 -10
- package/src/utilities/mergeAttributes.ts +17 -7
- package/src/utilities/removeDuplicates.ts +1 -3
|
@@ -10,8 +10,8 @@ function findMarkInSet(
|
|
|
10
10
|
): ProseMirrorMark | undefined {
|
|
11
11
|
return marks.find(item => {
|
|
12
12
|
return (
|
|
13
|
-
item.type === type
|
|
14
|
-
|
|
13
|
+
item.type === type &&
|
|
14
|
+
objectIncludes(
|
|
15
15
|
// Only check equality for the attributes that are provided
|
|
16
16
|
Object.fromEntries(Object.keys(attributes).map(k => [k, item.attrs[k]])),
|
|
17
17
|
attributes,
|
|
@@ -20,11 +20,7 @@ function findMarkInSet(
|
|
|
20
20
|
})
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function isMarkInSet(
|
|
24
|
-
marks: ProseMirrorMark[],
|
|
25
|
-
type: MarkType,
|
|
26
|
-
attributes: Record<string, any> = {},
|
|
27
|
-
): boolean {
|
|
23
|
+
function isMarkInSet(marks: ProseMirrorMark[], type: MarkType, attributes: Record<string, any> = {}): boolean {
|
|
28
24
|
return !!findMarkInSet(marks, type, attributes)
|
|
29
25
|
}
|
|
30
26
|
|
|
@@ -77,18 +73,12 @@ export function getMarkRange(
|
|
|
77
73
|
let endIndex = startIndex + 1
|
|
78
74
|
let endPos = startPos + start.node.nodeSize
|
|
79
75
|
|
|
80
|
-
while (
|
|
81
|
-
startIndex > 0
|
|
82
|
-
&& isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)
|
|
83
|
-
) {
|
|
76
|
+
while (startIndex > 0 && isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)) {
|
|
84
77
|
startIndex -= 1
|
|
85
78
|
startPos -= $pos.parent.child(startIndex).nodeSize
|
|
86
79
|
}
|
|
87
80
|
|
|
88
|
-
while (
|
|
89
|
-
endIndex < $pos.parent.childCount
|
|
90
|
-
&& isMarkInSet([...$pos.parent.child(endIndex).marks], type, attributes)
|
|
91
|
-
) {
|
|
81
|
+
while (endIndex < $pos.parent.childCount && isMarkInSet([...$pos.parent.child(endIndex).marks], type, attributes)) {
|
|
92
82
|
endPos += $pos.parent.child(endIndex).nodeSize
|
|
93
83
|
endIndex += 1
|
|
94
84
|
}
|
|
@@ -3,9 +3,7 @@ import { MarkType, Schema } from '@tiptap/pm/model'
|
|
|
3
3
|
export function getMarkType(nameOrType: string | MarkType, schema: Schema): MarkType {
|
|
4
4
|
if (typeof nameOrType === 'string') {
|
|
5
5
|
if (!schema.marks[nameOrType]) {
|
|
6
|
-
throw Error(
|
|
7
|
-
`There is no mark type named '${nameOrType}'. Maybe you forgot to add the extension?`,
|
|
8
|
-
)
|
|
6
|
+
throw Error(`There is no mark type named '${nameOrType}'. Maybe you forgot to add the extension?`)
|
|
9
7
|
}
|
|
10
8
|
|
|
11
9
|
return schema.marks[nameOrType]
|
|
@@ -3,10 +3,7 @@ import { EditorState } from '@tiptap/pm/state'
|
|
|
3
3
|
|
|
4
4
|
import { getNodeType } from './getNodeType.js'
|
|
5
5
|
|
|
6
|
-
export function getNodeAttributes(
|
|
7
|
-
state: EditorState,
|
|
8
|
-
typeOrName: string | NodeType,
|
|
9
|
-
): Record<string, any> {
|
|
6
|
+
export function getNodeAttributes(state: EditorState, typeOrName: string | NodeType): Record<string, any> {
|
|
10
7
|
const type = getNodeType(typeOrName, state.schema)
|
|
11
8
|
const { from, to } = state.selection
|
|
12
9
|
const nodes: Node[] = []
|
|
@@ -3,9 +3,7 @@ import { NodeType, Schema } from '@tiptap/pm/model'
|
|
|
3
3
|
export function getNodeType(nameOrType: string | NodeType, schema: Schema): NodeType {
|
|
4
4
|
if (typeof nameOrType === 'string') {
|
|
5
5
|
if (!schema.nodes[nameOrType]) {
|
|
6
|
-
throw Error(
|
|
7
|
-
`There is no node type named '${nameOrType}'. Maybe you forgot to add the extension?`,
|
|
8
|
-
)
|
|
6
|
+
throw Error(`There is no node type named '${nameOrType}'. Maybe you forgot to add the extension?`)
|
|
9
7
|
}
|
|
10
8
|
|
|
11
9
|
return schema.nodes[nameOrType]
|
|
@@ -8,9 +8,7 @@ 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
|
+
.filter(attribute => attribute.type === nodeOrMark.type.name)
|
|
14
12
|
.filter(item => item.attribute.rendered)
|
|
15
13
|
.map(item => {
|
|
16
14
|
if (!item.attribute.renderHTML) {
|
package/src/helpers/getSchema.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Schema } from '@tiptap/pm/model'
|
|
2
2
|
|
|
3
3
|
import { Editor } from '../Editor.js'
|
|
4
|
-
import { ExtensionManager } from '../ExtensionManager.js'
|
|
5
4
|
import { Extensions } from '../types.js'
|
|
6
5
|
import { getSchemaByResolvedExtensions } from './getSchemaByResolvedExtensions.js'
|
|
6
|
+
import { resolveExtensions } from './resolveExtensions.js'
|
|
7
7
|
|
|
8
8
|
export function getSchema(extensions: Extensions, editor?: Editor): Schema {
|
|
9
|
-
const resolvedExtensions =
|
|
9
|
+
const resolvedExtensions = resolveExtensions(extensions)
|
|
10
10
|
|
|
11
11
|
return getSchemaByResolvedExtensions(resolvedExtensions, editor)
|
|
12
12
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MarkSpec, NodeSpec, Schema, TagParseRule,
|
|
3
|
-
} from '@tiptap/pm/model'
|
|
1
|
+
import { MarkSpec, NodeSpec, Schema, TagParseRule } from '@tiptap/pm/model'
|
|
4
2
|
|
|
5
3
|
import type { Editor, MarkConfig, NodeConfig } from '../index.js'
|
|
6
4
|
import { AnyConfig, Extensions } from '../types.js'
|
|
@@ -16,7 +14,7 @@ function cleanUpSchemaItem<T>(data: T) {
|
|
|
16
14
|
return Object.fromEntries(
|
|
17
15
|
// @ts-ignore
|
|
18
16
|
Object.entries(data).filter(([key, value]) => {
|
|
19
|
-
if (key === 'attrs' && isEmptyObject(value as
|
|
17
|
+
if (key === 'attrs' && isEmptyObject(value as object | undefined)) {
|
|
20
18
|
return false
|
|
21
19
|
}
|
|
22
20
|
|
|
@@ -38,9 +36,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
|
|
|
38
36
|
|
|
39
37
|
const nodes = Object.fromEntries(
|
|
40
38
|
nodeExtensions.map(extension => {
|
|
41
|
-
const extensionAttributes = allAttributes.filter(
|
|
42
|
-
attribute => attribute.type === extension.name,
|
|
43
|
-
)
|
|
39
|
+
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
|
|
44
40
|
const context = {
|
|
45
41
|
name: extension.name,
|
|
46
42
|
options: extension.options,
|
|
@@ -49,11 +45,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
|
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
const extraNodeFields = extensions.reduce((fields, e) => {
|
|
52
|
-
const extendNodeSchema = getExtensionField<AnyConfig['extendNodeSchema']>(
|
|
53
|
-
e,
|
|
54
|
-
'extendNodeSchema',
|
|
55
|
-
context,
|
|
56
|
-
)
|
|
48
|
+
const extendNodeSchema = getExtensionField<AnyConfig['extendNodeSchema']>(e, 'extendNodeSchema', context)
|
|
57
49
|
|
|
58
50
|
return {
|
|
59
51
|
...fields,
|
|
@@ -63,61 +55,49 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
|
|
|
63
55
|
|
|
64
56
|
const schema: NodeSpec = cleanUpSchemaItem({
|
|
65
57
|
...extraNodeFields,
|
|
66
|
-
content: callOrReturn(
|
|
67
|
-
getExtensionField<NodeConfig['content']>(extension, 'content', context),
|
|
68
|
-
),
|
|
58
|
+
content: callOrReturn(getExtensionField<NodeConfig['content']>(extension, 'content', context)),
|
|
69
59
|
marks: callOrReturn(getExtensionField<NodeConfig['marks']>(extension, 'marks', context)),
|
|
70
60
|
group: callOrReturn(getExtensionField<NodeConfig['group']>(extension, 'group', context)),
|
|
71
61
|
inline: callOrReturn(getExtensionField<NodeConfig['inline']>(extension, 'inline', context)),
|
|
72
62
|
atom: callOrReturn(getExtensionField<NodeConfig['atom']>(extension, 'atom', context)),
|
|
73
|
-
selectable: callOrReturn(
|
|
74
|
-
|
|
75
|
-
),
|
|
76
|
-
draggable: callOrReturn(
|
|
77
|
-
getExtensionField<NodeConfig['draggable']>(extension, 'draggable', context),
|
|
78
|
-
),
|
|
63
|
+
selectable: callOrReturn(getExtensionField<NodeConfig['selectable']>(extension, 'selectable', context)),
|
|
64
|
+
draggable: callOrReturn(getExtensionField<NodeConfig['draggable']>(extension, 'draggable', context)),
|
|
79
65
|
code: callOrReturn(getExtensionField<NodeConfig['code']>(extension, 'code', context)),
|
|
80
66
|
whitespace: callOrReturn(getExtensionField<NodeConfig['whitespace']>(extension, 'whitespace', context)),
|
|
81
|
-
linebreakReplacement: callOrReturn(
|
|
82
|
-
|
|
83
|
-
getExtensionField<NodeConfig['defining']>(extension, 'defining', context),
|
|
84
|
-
),
|
|
85
|
-
isolating: callOrReturn(
|
|
86
|
-
getExtensionField<NodeConfig['isolating']>(extension, 'isolating', context),
|
|
67
|
+
linebreakReplacement: callOrReturn(
|
|
68
|
+
getExtensionField<NodeConfig['linebreakReplacement']>(extension, 'linebreakReplacement', context),
|
|
87
69
|
),
|
|
70
|
+
defining: callOrReturn(getExtensionField<NodeConfig['defining']>(extension, 'defining', context)),
|
|
71
|
+
isolating: callOrReturn(getExtensionField<NodeConfig['isolating']>(extension, 'isolating', context)),
|
|
88
72
|
attrs: Object.fromEntries(
|
|
89
73
|
extensionAttributes.map(extensionAttribute => {
|
|
90
|
-
return [
|
|
74
|
+
return [
|
|
75
|
+
extensionAttribute.name,
|
|
76
|
+
{ default: extensionAttribute?.attribute?.default, validate: extensionAttribute?.attribute?.validate },
|
|
77
|
+
]
|
|
91
78
|
}),
|
|
92
79
|
),
|
|
93
80
|
})
|
|
94
81
|
|
|
95
|
-
const parseHTML = callOrReturn(
|
|
96
|
-
getExtensionField<NodeConfig['parseHTML']>(extension, 'parseHTML', context),
|
|
97
|
-
)
|
|
82
|
+
const parseHTML = callOrReturn(getExtensionField<NodeConfig['parseHTML']>(extension, 'parseHTML', context))
|
|
98
83
|
|
|
99
84
|
if (parseHTML) {
|
|
100
|
-
schema.parseDOM = parseHTML.map(parseRule =>
|
|
85
|
+
schema.parseDOM = parseHTML.map(parseRule =>
|
|
86
|
+
injectExtensionAttributesToParseRule(parseRule, extensionAttributes),
|
|
87
|
+
) as TagParseRule[]
|
|
101
88
|
}
|
|
102
89
|
|
|
103
|
-
const renderHTML = getExtensionField<NodeConfig['renderHTML']>(
|
|
104
|
-
extension,
|
|
105
|
-
'renderHTML',
|
|
106
|
-
context,
|
|
107
|
-
)
|
|
90
|
+
const renderHTML = getExtensionField<NodeConfig['renderHTML']>(extension, 'renderHTML', context)
|
|
108
91
|
|
|
109
92
|
if (renderHTML) {
|
|
110
|
-
schema.toDOM = node =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
93
|
+
schema.toDOM = node =>
|
|
94
|
+
renderHTML({
|
|
95
|
+
node,
|
|
96
|
+
HTMLAttributes: getRenderedAttributes(node, extensionAttributes),
|
|
97
|
+
})
|
|
114
98
|
}
|
|
115
99
|
|
|
116
|
-
const renderText = getExtensionField<NodeConfig['renderText']>(
|
|
117
|
-
extension,
|
|
118
|
-
'renderText',
|
|
119
|
-
context,
|
|
120
|
-
)
|
|
100
|
+
const renderText = getExtensionField<NodeConfig['renderText']>(extension, 'renderText', context)
|
|
121
101
|
|
|
122
102
|
if (renderText) {
|
|
123
103
|
schema.toText = renderText
|
|
@@ -129,9 +109,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
|
|
|
129
109
|
|
|
130
110
|
const marks = Object.fromEntries(
|
|
131
111
|
markExtensions.map(extension => {
|
|
132
|
-
const extensionAttributes = allAttributes.filter(
|
|
133
|
-
attribute => attribute.type === extension.name,
|
|
134
|
-
)
|
|
112
|
+
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
|
|
135
113
|
const context = {
|
|
136
114
|
name: extension.name,
|
|
137
115
|
options: extension.options,
|
|
@@ -140,11 +118,7 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
|
|
|
140
118
|
}
|
|
141
119
|
|
|
142
120
|
const extraMarkFields = extensions.reduce((fields, e) => {
|
|
143
|
-
const extendMarkSchema = getExtensionField<AnyConfig['extendMarkSchema']>(
|
|
144
|
-
e,
|
|
145
|
-
'extendMarkSchema',
|
|
146
|
-
context,
|
|
147
|
-
)
|
|
121
|
+
const extendMarkSchema = getExtensionField<AnyConfig['extendMarkSchema']>(e, 'extendMarkSchema', context)
|
|
148
122
|
|
|
149
123
|
return {
|
|
150
124
|
...fields,
|
|
@@ -154,43 +128,37 @@ export function getSchemaByResolvedExtensions(extensions: Extensions, editor?: E
|
|
|
154
128
|
|
|
155
129
|
const schema: MarkSpec = cleanUpSchemaItem({
|
|
156
130
|
...extraMarkFields,
|
|
157
|
-
inclusive: callOrReturn(
|
|
158
|
-
|
|
159
|
-
),
|
|
160
|
-
excludes: callOrReturn(
|
|
161
|
-
getExtensionField<MarkConfig['excludes']>(extension, 'excludes', context),
|
|
162
|
-
),
|
|
131
|
+
inclusive: callOrReturn(getExtensionField<MarkConfig['inclusive']>(extension, 'inclusive', context)),
|
|
132
|
+
excludes: callOrReturn(getExtensionField<MarkConfig['excludes']>(extension, 'excludes', context)),
|
|
163
133
|
group: callOrReturn(getExtensionField<MarkConfig['group']>(extension, 'group', context)),
|
|
164
|
-
spanning: callOrReturn(
|
|
165
|
-
getExtensionField<MarkConfig['spanning']>(extension, 'spanning', context),
|
|
166
|
-
),
|
|
134
|
+
spanning: callOrReturn(getExtensionField<MarkConfig['spanning']>(extension, 'spanning', context)),
|
|
167
135
|
code: callOrReturn(getExtensionField<MarkConfig['code']>(extension, 'code', context)),
|
|
168
136
|
attrs: Object.fromEntries(
|
|
169
137
|
extensionAttributes.map(extensionAttribute => {
|
|
170
|
-
return [
|
|
138
|
+
return [
|
|
139
|
+
extensionAttribute.name,
|
|
140
|
+
{ default: extensionAttribute?.attribute?.default, validate: extensionAttribute?.attribute?.validate },
|
|
141
|
+
]
|
|
171
142
|
}),
|
|
172
143
|
),
|
|
173
144
|
})
|
|
174
145
|
|
|
175
|
-
const parseHTML = callOrReturn(
|
|
176
|
-
getExtensionField<MarkConfig['parseHTML']>(extension, 'parseHTML', context),
|
|
177
|
-
)
|
|
146
|
+
const parseHTML = callOrReturn(getExtensionField<MarkConfig['parseHTML']>(extension, 'parseHTML', context))
|
|
178
147
|
|
|
179
148
|
if (parseHTML) {
|
|
180
|
-
schema.parseDOM = parseHTML.map(parseRule =>
|
|
149
|
+
schema.parseDOM = parseHTML.map(parseRule =>
|
|
150
|
+
injectExtensionAttributesToParseRule(parseRule, extensionAttributes),
|
|
151
|
+
)
|
|
181
152
|
}
|
|
182
153
|
|
|
183
|
-
const renderHTML = getExtensionField<MarkConfig['renderHTML']>(
|
|
184
|
-
extension,
|
|
185
|
-
'renderHTML',
|
|
186
|
-
context,
|
|
187
|
-
)
|
|
154
|
+
const renderHTML = getExtensionField<MarkConfig['renderHTML']>(extension, 'renderHTML', context)
|
|
188
155
|
|
|
189
156
|
if (renderHTML) {
|
|
190
|
-
schema.toDOM = mark =>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
157
|
+
schema.toDOM = mark =>
|
|
158
|
+
renderHTML({
|
|
159
|
+
mark,
|
|
160
|
+
HTMLAttributes: getRenderedAttributes(mark, extensionAttributes),
|
|
161
|
+
})
|
|
194
162
|
}
|
|
195
163
|
|
|
196
164
|
return [extension.name, schema]
|
|
@@ -12,9 +12,8 @@ export function getSplittedAttributes(
|
|
|
12
12
|
typeName: string,
|
|
13
13
|
attributes: Record<string, any>,
|
|
14
14
|
): Record<string, any> {
|
|
15
|
-
return Object.fromEntries(
|
|
16
|
-
.entries(attributes)
|
|
17
|
-
.filter(([name]) => {
|
|
15
|
+
return Object.fromEntries(
|
|
16
|
+
Object.entries(attributes).filter(([name]) => {
|
|
18
17
|
const extensionAttribute = extensionAttributes.find(item => {
|
|
19
18
|
return item.type === typeName && item.name === name
|
|
20
19
|
})
|
|
@@ -24,5 +23,6 @@ export function getSplittedAttributes(
|
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
return extensionAttribute.attribute.keepOnSplit
|
|
27
|
-
})
|
|
26
|
+
}),
|
|
27
|
+
)
|
|
28
28
|
}
|
|
@@ -11,22 +11,19 @@ export const getTextContentFromNodes = ($from: ResolvedPos, maxMatch = 500) => {
|
|
|
11
11
|
|
|
12
12
|
const sliceEndPos = $from.parentOffset
|
|
13
13
|
|
|
14
|
-
$from.parent.nodesBetween(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
(node, pos, parent, index) => {
|
|
18
|
-
const chunk = node.type.spec.toText?.({
|
|
14
|
+
$from.parent.nodesBetween(Math.max(0, sliceEndPos - maxMatch), sliceEndPos, (node, pos, parent, index) => {
|
|
15
|
+
const chunk =
|
|
16
|
+
node.type.spec.toText?.({
|
|
19
17
|
node,
|
|
20
18
|
pos,
|
|
21
19
|
parent,
|
|
22
20
|
index,
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
}) ||
|
|
22
|
+
node.textContent ||
|
|
23
|
+
'%leaf%'
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
25
|
+
textBefore += node.isAtom && !node.isText ? chunk : chunk.slice(0, Math.max(0, sliceEndPos - pos))
|
|
26
|
+
})
|
|
30
27
|
|
|
31
28
|
return textBefore
|
|
32
29
|
}
|
package/src/helpers/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './findChildren.js'
|
|
|
7
7
|
export * from './findChildrenInRange.js'
|
|
8
8
|
export * from './findParentNode.js'
|
|
9
9
|
export * from './findParentNodeClosestToPos.js'
|
|
10
|
+
export * from './flattenExtensions.js'
|
|
10
11
|
export * from './generateHTML.js'
|
|
11
12
|
export * from './generateJSON.js'
|
|
12
13
|
export * from './generateText.js'
|
|
@@ -45,6 +46,9 @@ export * from './isNodeEmpty.js'
|
|
|
45
46
|
export * from './isNodeSelection.js'
|
|
46
47
|
export * from './isTextSelection.js'
|
|
47
48
|
export * from './posToDOMRect.js'
|
|
49
|
+
export * from './resolveExtensions.js'
|
|
48
50
|
export * from './resolveFocusPosition.js'
|
|
51
|
+
export * from './rewriteUnknownContent.js'
|
|
49
52
|
export * from './selectionToInsertionEnd.js'
|
|
53
|
+
export * from './sortExtensions.js'
|
|
50
54
|
export * from './splitExtensions.js'
|
|
@@ -29,7 +29,7 @@ export function injectExtensionAttributesToParseRule(
|
|
|
29
29
|
const newAttributes = extensionAttributes.reduce((items, item) => {
|
|
30
30
|
const value = item.attribute.parseHTML
|
|
31
31
|
? item.attribute.parseHTML(node)
|
|
32
|
-
: fromString(
|
|
32
|
+
: fromString(node.getAttribute(item.name))
|
|
33
33
|
|
|
34
34
|
if (value === null || value === undefined) {
|
|
35
35
|
return items
|
package/src/helpers/isActive.ts
CHANGED
|
@@ -4,11 +4,7 @@ import { getSchemaTypeNameByName } from './getSchemaTypeNameByName.js'
|
|
|
4
4
|
import { isMarkActive } from './isMarkActive.js'
|
|
5
5
|
import { isNodeActive } from './isNodeActive.js'
|
|
6
6
|
|
|
7
|
-
export function isActive(
|
|
8
|
-
state: EditorState,
|
|
9
|
-
name: string | null,
|
|
10
|
-
attributes: Record<string, any> = {},
|
|
11
|
-
): boolean {
|
|
7
|
+
export function isActive(state: EditorState, name: string | null, attributes: Record<string, any> = {}): boolean {
|
|
12
8
|
if (!name) {
|
|
13
9
|
return isNodeActive(state, null, attributes) || isMarkActive(state, null, attributes)
|
|
14
10
|
}
|
|
@@ -3,9 +3,7 @@ import { AnyExtension, EnableRules } from '../types.js'
|
|
|
3
3
|
export function isExtensionRulesEnabled(extension: AnyExtension, enabled: EnableRules): boolean {
|
|
4
4
|
if (Array.isArray(enabled)) {
|
|
5
5
|
return enabled.some(enabledExtension => {
|
|
6
|
-
const name = typeof enabledExtension === 'string'
|
|
7
|
-
? enabledExtension
|
|
8
|
-
: enabledExtension.name
|
|
6
|
+
const name = typeof enabledExtension === 'string' ? enabledExtension : enabledExtension.name
|
|
9
7
|
|
|
10
8
|
return name === extension.name
|
|
11
9
|
})
|
|
@@ -12,11 +12,11 @@ export function isNodeEmpty(
|
|
|
12
12
|
/**
|
|
13
13
|
* When true (default), it will also check if all children are empty.
|
|
14
14
|
*/
|
|
15
|
-
checkChildren?: boolean
|
|
15
|
+
checkChildren?: boolean
|
|
16
16
|
/**
|
|
17
17
|
* When true, it will ignore whitespace when checking for emptiness.
|
|
18
18
|
*/
|
|
19
|
-
ignoreWhitespace?: boolean
|
|
19
|
+
ignoreWhitespace?: boolean
|
|
20
20
|
} = {},
|
|
21
21
|
): boolean {
|
|
22
22
|
if (ignoreWhitespace) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Extensions } from '../types.js'
|
|
2
|
+
import { findDuplicates } from '../utilities/findDuplicates.js'
|
|
3
|
+
import { flattenExtensions } from './flattenExtensions.js'
|
|
4
|
+
import { sortExtensions } from './sortExtensions.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a flattened and sorted extension list while
|
|
8
|
+
* also checking for duplicated extensions and warns the user.
|
|
9
|
+
* @param extensions An array of Tiptap extensions
|
|
10
|
+
* @returns An flattened and sorted array of Tiptap extensions
|
|
11
|
+
*/
|
|
12
|
+
export function resolveExtensions(extensions: Extensions): Extensions {
|
|
13
|
+
const resolvedExtensions = sortExtensions(flattenExtensions(extensions))
|
|
14
|
+
const duplicatedNames = findDuplicates(resolvedExtensions.map(extension => extension.name))
|
|
15
|
+
|
|
16
|
+
if (duplicatedNames.length) {
|
|
17
|
+
console.warn(
|
|
18
|
+
`[tiptap warn]: Duplicate extension names found: [${duplicatedNames
|
|
19
|
+
.map(item => `'${item}'`)
|
|
20
|
+
.join(', ')}]. This can lead to issues.`,
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return resolvedExtensions
|
|
25
|
+
}
|
|
@@ -4,10 +4,7 @@ import { Selection, TextSelection } from '@tiptap/pm/state'
|
|
|
4
4
|
import { FocusPosition } from '../types.js'
|
|
5
5
|
import { minMax } from '../utilities/minMax.js'
|
|
6
6
|
|
|
7
|
-
export function resolveFocusPosition(
|
|
8
|
-
doc: ProseMirrorNode,
|
|
9
|
-
position: FocusPosition = null,
|
|
10
|
-
): Selection | null {
|
|
7
|
+
export function resolveFocusPosition(doc: ProseMirrorNode, position: FocusPosition = null): Selection | null {
|
|
11
8
|
if (!position) {
|
|
12
9
|
return null
|
|
13
10
|
}
|
|
@@ -27,16 +24,8 @@ export function resolveFocusPosition(
|
|
|
27
24
|
const maxPos = selectionAtEnd.to
|
|
28
25
|
|
|
29
26
|
if (position === 'all') {
|
|
30
|
-
return TextSelection.create(
|
|
31
|
-
doc,
|
|
32
|
-
minMax(0, minPos, maxPos),
|
|
33
|
-
minMax(doc.content.size, minPos, maxPos),
|
|
34
|
-
)
|
|
27
|
+
return TextSelection.create(doc, minMax(0, minPos, maxPos), minMax(doc.content.size, minPos, maxPos))
|
|
35
28
|
}
|
|
36
29
|
|
|
37
|
-
return TextSelection.create(
|
|
38
|
-
doc,
|
|
39
|
-
minMax(position, minPos, maxPos),
|
|
40
|
-
minMax(position, minPos, maxPos),
|
|
41
|
-
)
|
|
30
|
+
return TextSelection.create(doc, minMax(position, minPos, maxPos), minMax(position, minPos, maxPos))
|
|
42
31
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Schema } from '@tiptap/pm/model'
|
|
2
|
+
|
|
3
|
+
import type { JSONContent } from '../types.js'
|
|
4
|
+
|
|
5
|
+
type RewriteUnknownContentOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* If true, unknown nodes will be treated as paragraphs
|
|
8
|
+
* @default true
|
|
9
|
+
*/
|
|
10
|
+
fallbackToParagraph?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type RewrittenContent = {
|
|
14
|
+
/**
|
|
15
|
+
* The original JSON content that was rewritten
|
|
16
|
+
*/
|
|
17
|
+
original: JSONContent
|
|
18
|
+
/**
|
|
19
|
+
* The name of the node or mark that was unsupported
|
|
20
|
+
*/
|
|
21
|
+
unsupported: string
|
|
22
|
+
}[]
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The actual implementation of the rewriteUnknownContent function
|
|
26
|
+
*/
|
|
27
|
+
function rewriteUnknownContentInner({
|
|
28
|
+
json,
|
|
29
|
+
validMarks,
|
|
30
|
+
validNodes,
|
|
31
|
+
options,
|
|
32
|
+
rewrittenContent = [],
|
|
33
|
+
}: {
|
|
34
|
+
json: JSONContent
|
|
35
|
+
validMarks: Set<string>
|
|
36
|
+
validNodes: Set<string>
|
|
37
|
+
options?: RewriteUnknownContentOptions
|
|
38
|
+
rewrittenContent?: RewrittenContent
|
|
39
|
+
}): {
|
|
40
|
+
/**
|
|
41
|
+
* The cleaned JSON content
|
|
42
|
+
*/
|
|
43
|
+
json: JSONContent | null
|
|
44
|
+
/**
|
|
45
|
+
* The array of nodes and marks that were rewritten
|
|
46
|
+
*/
|
|
47
|
+
rewrittenContent: RewrittenContent
|
|
48
|
+
} {
|
|
49
|
+
if (json.marks && Array.isArray(json.marks)) {
|
|
50
|
+
json.marks = json.marks.filter(mark => {
|
|
51
|
+
const name = typeof mark === 'string' ? mark : mark.type
|
|
52
|
+
|
|
53
|
+
if (validMarks.has(name)) {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
rewrittenContent.push({
|
|
58
|
+
original: JSON.parse(JSON.stringify(mark)),
|
|
59
|
+
unsupported: name,
|
|
60
|
+
})
|
|
61
|
+
// Just ignore any unknown marks
|
|
62
|
+
return false
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (json.content && Array.isArray(json.content)) {
|
|
67
|
+
json.content = json.content
|
|
68
|
+
.map(
|
|
69
|
+
value =>
|
|
70
|
+
rewriteUnknownContentInner({
|
|
71
|
+
json: value,
|
|
72
|
+
validMarks,
|
|
73
|
+
validNodes,
|
|
74
|
+
options,
|
|
75
|
+
rewrittenContent,
|
|
76
|
+
}).json,
|
|
77
|
+
)
|
|
78
|
+
.filter(a => a !== null && a !== undefined)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (json.type && !validNodes.has(json.type)) {
|
|
82
|
+
rewrittenContent.push({
|
|
83
|
+
original: JSON.parse(JSON.stringify(json)),
|
|
84
|
+
unsupported: json.type,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (json.content && Array.isArray(json.content) && options?.fallbackToParagraph !== false) {
|
|
88
|
+
// Just treat it like a paragraph and hope for the best
|
|
89
|
+
json.type = 'paragraph'
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
json,
|
|
93
|
+
rewrittenContent,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// or just omit it entirely
|
|
98
|
+
return {
|
|
99
|
+
json: null,
|
|
100
|
+
rewrittenContent,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { json, rewrittenContent }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Rewrite unknown nodes and marks within JSON content
|
|
109
|
+
* Allowing for user within the editor
|
|
110
|
+
*/
|
|
111
|
+
export function rewriteUnknownContent(
|
|
112
|
+
/**
|
|
113
|
+
* The JSON content to clean of unknown nodes and marks
|
|
114
|
+
*/
|
|
115
|
+
json: JSONContent,
|
|
116
|
+
/**
|
|
117
|
+
* The schema to use for validation
|
|
118
|
+
*/
|
|
119
|
+
schema: Schema,
|
|
120
|
+
/**
|
|
121
|
+
* Options for the cleaning process
|
|
122
|
+
*/
|
|
123
|
+
options?: RewriteUnknownContentOptions,
|
|
124
|
+
): {
|
|
125
|
+
/**
|
|
126
|
+
* The cleaned JSON content
|
|
127
|
+
*/
|
|
128
|
+
json: JSONContent | null
|
|
129
|
+
/**
|
|
130
|
+
* The array of nodes and marks that were rewritten
|
|
131
|
+
*/
|
|
132
|
+
rewrittenContent: {
|
|
133
|
+
/**
|
|
134
|
+
* The original JSON content that was rewritten
|
|
135
|
+
*/
|
|
136
|
+
original: JSONContent
|
|
137
|
+
/**
|
|
138
|
+
* The name of the node or mark that was unsupported
|
|
139
|
+
*/
|
|
140
|
+
unsupported: string
|
|
141
|
+
}[]
|
|
142
|
+
} {
|
|
143
|
+
return rewriteUnknownContentInner({
|
|
144
|
+
json,
|
|
145
|
+
validNodes: new Set(Object.keys(schema.nodes)),
|
|
146
|
+
validMarks: new Set(Object.keys(schema.marks)),
|
|
147
|
+
options,
|
|
148
|
+
})
|
|
149
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AnyConfig, Extensions } from '../types.js'
|
|
2
|
+
import { getExtensionField } from './getExtensionField.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sort extensions by priority.
|
|
6
|
+
* @param extensions An array of Tiptap extensions
|
|
7
|
+
* @returns A sorted array of Tiptap extensions by priority
|
|
8
|
+
*/
|
|
9
|
+
export function sortExtensions(extensions: Extensions): Extensions {
|
|
10
|
+
const defaultPriority = 100
|
|
11
|
+
|
|
12
|
+
return extensions.sort((a, b) => {
|
|
13
|
+
const priorityA = getExtensionField<AnyConfig['priority']>(a, 'priority') || defaultPriority
|
|
14
|
+
const priorityB = getExtensionField<AnyConfig['priority']>(b, 'priority') || defaultPriority
|
|
15
|
+
|
|
16
|
+
if (priorityA > priorityB) {
|
|
17
|
+
return -1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (priorityA < priorityB) {
|
|
21
|
+
return 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return 0
|
|
25
|
+
})
|
|
26
|
+
}
|