@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
|
@@ -18,8 +18,10 @@ declare module '@tiptap/core' {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export const wrapInList: RawCommands['wrapInList'] =
|
|
22
|
-
|
|
21
|
+
export const wrapInList: RawCommands['wrapInList'] =
|
|
22
|
+
(typeOrName, attributes = {}) =>
|
|
23
|
+
({ state, dispatch }) => {
|
|
24
|
+
const type = getNodeType(typeOrName, state.schema)
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
+
return originalWrapInList(type, attributes)(state, dispatch)
|
|
27
|
+
}
|
|
@@ -5,7 +5,7 @@ import { getTextBetween } from '../helpers/getTextBetween.js'
|
|
|
5
5
|
import { getTextSerializersFromSchema } from '../helpers/getTextSerializersFromSchema.js'
|
|
6
6
|
|
|
7
7
|
export type ClipboardTextSerializerOptions = {
|
|
8
|
-
blockSeparator?: string
|
|
8
|
+
blockSeparator?: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export const ClipboardTextSerializer = Extension.create<ClipboardTextSerializerOptions>({
|
|
@@ -33,9 +33,7 @@ export const ClipboardTextSerializer = Extension.create<ClipboardTextSerializerO
|
|
|
33
33
|
const range = { from, to }
|
|
34
34
|
|
|
35
35
|
return getTextBetween(doc, range, {
|
|
36
|
-
...(this.options.blockSeparator !== undefined
|
|
37
|
-
? { blockSeparator: this.options.blockSeparator }
|
|
38
|
-
: {}),
|
|
36
|
+
...(this.options.blockSeparator !== undefined ? { blockSeparator: this.options.blockSeparator } : {}),
|
|
39
37
|
textSerializers,
|
|
40
38
|
})
|
|
41
39
|
},
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { RemoveMarkStep } from '@tiptap/pm/transform'
|
|
2
|
+
|
|
3
|
+
import { Extension } from '../Extension.js'
|
|
4
|
+
import { combineTransactionSteps, getChangedRanges } from '../helpers/index.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This extension allows you to be notified when the user deletes content you are interested in.
|
|
8
|
+
*/
|
|
9
|
+
export const Delete = Extension.create({
|
|
10
|
+
name: 'delete',
|
|
11
|
+
|
|
12
|
+
onUpdate({ transaction, appendedTransactions }) {
|
|
13
|
+
const callback = () => {
|
|
14
|
+
if (
|
|
15
|
+
this.editor.options.coreExtensionOptions?.delete?.filterTransaction?.(transaction) ??
|
|
16
|
+
transaction.getMeta('y-sync$')
|
|
17
|
+
) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
const nextTransaction = combineTransactionSteps(transaction.before, [transaction, ...appendedTransactions])
|
|
21
|
+
const changes = getChangedRanges(nextTransaction)
|
|
22
|
+
|
|
23
|
+
changes.forEach(change => {
|
|
24
|
+
if (
|
|
25
|
+
nextTransaction.mapping.mapResult(change.oldRange.from).deletedAfter &&
|
|
26
|
+
nextTransaction.mapping.mapResult(change.oldRange.to).deletedBefore
|
|
27
|
+
) {
|
|
28
|
+
nextTransaction.before.nodesBetween(change.oldRange.from, change.oldRange.to, (node, from) => {
|
|
29
|
+
const to = from + node.nodeSize - 2
|
|
30
|
+
const isFullyWithinRange = change.oldRange.from <= from && to <= change.oldRange.to
|
|
31
|
+
|
|
32
|
+
this.editor.emit('delete', {
|
|
33
|
+
type: 'node',
|
|
34
|
+
node,
|
|
35
|
+
from,
|
|
36
|
+
to,
|
|
37
|
+
newFrom: nextTransaction.mapping.map(from),
|
|
38
|
+
newTo: nextTransaction.mapping.map(to),
|
|
39
|
+
deletedRange: change.oldRange,
|
|
40
|
+
newRange: change.newRange,
|
|
41
|
+
partial: !isFullyWithinRange,
|
|
42
|
+
editor: this.editor,
|
|
43
|
+
transaction,
|
|
44
|
+
combinedTransform: nextTransaction,
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const mapping = nextTransaction.mapping
|
|
51
|
+
nextTransaction.steps.forEach((step, index) => {
|
|
52
|
+
if (step instanceof RemoveMarkStep) {
|
|
53
|
+
const newStart = mapping.slice(index).map(step.from, -1)
|
|
54
|
+
const newEnd = mapping.slice(index).map(step.to)
|
|
55
|
+
const oldStart = mapping.invert().map(newStart, -1)
|
|
56
|
+
const oldEnd = mapping.invert().map(newEnd)
|
|
57
|
+
|
|
58
|
+
const foundBeforeMark = nextTransaction.doc.nodeAt(newStart - 1)?.marks.some(mark => mark.eq(step.mark))
|
|
59
|
+
const foundAfterMark = nextTransaction.doc.nodeAt(newEnd)?.marks.some(mark => mark.eq(step.mark))
|
|
60
|
+
|
|
61
|
+
this.editor.emit('delete', {
|
|
62
|
+
type: 'mark',
|
|
63
|
+
mark: step.mark,
|
|
64
|
+
from: step.from,
|
|
65
|
+
to: step.to,
|
|
66
|
+
deletedRange: {
|
|
67
|
+
from: oldStart,
|
|
68
|
+
to: oldEnd,
|
|
69
|
+
},
|
|
70
|
+
newRange: {
|
|
71
|
+
from: newStart,
|
|
72
|
+
to: newEnd,
|
|
73
|
+
},
|
|
74
|
+
partial: Boolean(foundAfterMark || foundBeforeMark),
|
|
75
|
+
editor: this.editor,
|
|
76
|
+
transaction,
|
|
77
|
+
combinedTransform: nextTransaction,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (this.editor.options.coreExtensionOptions?.delete?.async ?? true) {
|
|
84
|
+
setTimeout(callback, 0)
|
|
85
|
+
} else {
|
|
86
|
+
callback()
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
})
|
|
@@ -16,9 +16,7 @@ export const FocusEvents = Extension.create({
|
|
|
16
16
|
focus: (view, event: Event) => {
|
|
17
17
|
editor.isFocused = true
|
|
18
18
|
|
|
19
|
-
const transaction = editor.state.tr
|
|
20
|
-
.setMeta('focus', { event })
|
|
21
|
-
.setMeta('addToHistory', false)
|
|
19
|
+
const transaction = editor.state.tr.setMeta('focus', { event }).setMeta('addToHistory', false)
|
|
22
20
|
|
|
23
21
|
view.dispatch(transaction)
|
|
24
22
|
|
|
@@ -27,9 +25,7 @@ export const FocusEvents = Extension.create({
|
|
|
27
25
|
blur: (view, event: Event) => {
|
|
28
26
|
editor.isFocused = false
|
|
29
27
|
|
|
30
|
-
const transaction = editor.state.tr
|
|
31
|
-
.setMeta('blur', { event })
|
|
32
|
-
.setMeta('addToHistory', false)
|
|
28
|
+
const transaction = editor.state.tr.setMeta('blur', { event }).setMeta('addToHistory', false)
|
|
33
29
|
|
|
34
30
|
view.dispatch(transaction)
|
|
35
31
|
|
package/src/extensions/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { ClipboardTextSerializer } from './clipboardTextSerializer.js'
|
|
2
2
|
export { Commands } from './commands.js'
|
|
3
|
+
export { Delete } from './delete.js'
|
|
3
4
|
export { Drop } from './drop.js'
|
|
4
5
|
export { Editable } from './editable.js'
|
|
5
6
|
export { FocusEvents } from './focusEvents.js'
|
package/src/extensions/keymap.ts
CHANGED
|
@@ -11,54 +11,59 @@ export const Keymap = Extension.create({
|
|
|
11
11
|
name: 'keymap',
|
|
12
12
|
|
|
13
13
|
addKeyboardShortcuts() {
|
|
14
|
-
const handleBackspace = () =>
|
|
15
|
-
() =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
() =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
14
|
+
const handleBackspace = () =>
|
|
15
|
+
this.editor.commands.first(({ commands }) => [
|
|
16
|
+
() => commands.undoInputRule(),
|
|
17
|
+
|
|
18
|
+
// maybe convert first text block node to default node
|
|
19
|
+
() =>
|
|
20
|
+
commands.command(({ tr }) => {
|
|
21
|
+
const { selection, doc } = tr
|
|
22
|
+
const { empty, $anchor } = selection
|
|
23
|
+
const { pos, parent } = $anchor
|
|
24
|
+
const $parentPos = $anchor.parent.isTextblock && pos > 0 ? tr.doc.resolve(pos - 1) : $anchor
|
|
25
|
+
const parentIsIsolating = $parentPos.parent.type.spec.isolating
|
|
26
|
+
|
|
27
|
+
const parentPos = $anchor.pos - $anchor.parentOffset
|
|
28
|
+
|
|
29
|
+
const isAtStart =
|
|
30
|
+
parentIsIsolating && $parentPos.parent.childCount === 1
|
|
31
|
+
? parentPos === $anchor.pos
|
|
32
|
+
: Selection.atStart(doc).from === pos
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
!empty ||
|
|
36
|
+
!parent.type.isTextblock ||
|
|
37
|
+
parent.textContent.length ||
|
|
38
|
+
!isAtStart ||
|
|
39
|
+
(isAtStart && $anchor.parent.type.name === 'paragraph') // prevent clearNodes when no nodes to clear, otherwise history stack is appended
|
|
40
|
+
) {
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return commands.clearNodes()
|
|
45
|
+
}),
|
|
46
|
+
|
|
47
|
+
() => commands.deleteSelection(),
|
|
48
|
+
() => commands.joinBackward(),
|
|
49
|
+
() => commands.selectNodeBackward(),
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
const handleDelete = () =>
|
|
53
|
+
this.editor.commands.first(({ commands }) => [
|
|
54
|
+
() => commands.deleteSelection(),
|
|
55
|
+
() => commands.deleteCurrentNode(),
|
|
56
|
+
() => commands.joinForward(),
|
|
57
|
+
() => commands.selectNodeForward(),
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
const handleEnter = () =>
|
|
61
|
+
this.editor.commands.first(({ commands }) => [
|
|
62
|
+
() => commands.newlineInCode(),
|
|
63
|
+
() => commands.createParagraphNear(),
|
|
64
|
+
() => commands.liftEmptyBlock(),
|
|
65
|
+
() => commands.splitBlock(),
|
|
66
|
+
])
|
|
62
67
|
|
|
63
68
|
const baseKeymap = {
|
|
64
69
|
Enter: handleEnter,
|
|
@@ -104,8 +109,11 @@ export const Keymap = Extension.create({
|
|
|
104
109
|
new Plugin({
|
|
105
110
|
key: new PluginKey('clearDocument'),
|
|
106
111
|
appendTransaction: (transactions, oldState, newState) => {
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
if (transactions.some(tr => tr.getMeta('composition'))) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const docChanges = transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)
|
|
109
117
|
|
|
110
118
|
const ignoreTr = transactions.some(transaction => transaction.getMeta('preventClearDocument'))
|
|
111
119
|
|
package/src/extensions/paste.ts
CHANGED
|
@@ -10,7 +10,7 @@ export const Tabindex = Extension.create({
|
|
|
10
10
|
new Plugin({
|
|
11
11
|
key: new PluginKey('tabindex'),
|
|
12
12
|
props: {
|
|
13
|
-
attributes: (): { [name: string]: string
|
|
13
|
+
attributes: (): { [name: string]: string } => (this.editor.isEditable ? { tabindex: '0' } : {}),
|
|
14
14
|
},
|
|
15
15
|
}),
|
|
16
16
|
]
|
|
@@ -8,10 +8,7 @@ import { Transform } from '@tiptap/pm/transform'
|
|
|
8
8
|
* @param transactions The transactions to combine
|
|
9
9
|
* @returns A new `Transform` with all steps of the passed transactions
|
|
10
10
|
*/
|
|
11
|
-
export function combineTransactionSteps(
|
|
12
|
-
oldDoc: ProseMirrorNode,
|
|
13
|
-
transactions: Transaction[],
|
|
14
|
-
): Transform {
|
|
11
|
+
export function combineTransactionSteps(oldDoc: ProseMirrorNode, transactions: Transaction[]): Transform {
|
|
15
12
|
const transform = new Transform(oldDoc)
|
|
16
13
|
|
|
17
14
|
transactions.forEach(transaction => {
|
|
@@ -5,10 +5,7 @@ import { EditorState, Transaction } from '@tiptap/pm/state'
|
|
|
5
5
|
* @param config The transaction and state to create the chainable state from
|
|
6
6
|
* @returns A chainable Editor state object
|
|
7
7
|
*/
|
|
8
|
-
export function createChainableState(config: {
|
|
9
|
-
transaction: Transaction
|
|
10
|
-
state: EditorState
|
|
11
|
-
}): EditorState {
|
|
8
|
+
export function createChainableState(config: { transaction: Transaction; state: EditorState }): EditorState {
|
|
12
9
|
const { state, transaction } = config
|
|
13
10
|
let { selection } = transaction
|
|
14
11
|
let { doc } = transaction
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Fragment, Node as ProseMirrorNode, ParseOptions, Schema,
|
|
3
|
-
} from '@tiptap/pm/model'
|
|
1
|
+
import { Fragment, Node as ProseMirrorNode, ParseOptions, Schema } from '@tiptap/pm/model'
|
|
4
2
|
|
|
5
3
|
import { Content } from '../types.js'
|
|
6
4
|
import { createNodeFromContent } from './createNodeFromContent.js'
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DOMParser,
|
|
3
|
-
Fragment,
|
|
4
|
-
Node as ProseMirrorNode,
|
|
5
|
-
ParseOptions,
|
|
6
|
-
Schema,
|
|
7
|
-
} from '@tiptap/pm/model'
|
|
1
|
+
import { DOMParser, Fragment, Node as ProseMirrorNode, ParseOptions, Schema } from '@tiptap/pm/model'
|
|
8
2
|
|
|
9
3
|
import { Content } from '../types.js'
|
|
10
4
|
import { elementFromString } from '../utilities/elementFromString.js'
|
|
@@ -67,7 +61,6 @@ export function createNodeFromContent(
|
|
|
67
61
|
}
|
|
68
62
|
|
|
69
63
|
if (isTextContent) {
|
|
70
|
-
|
|
71
64
|
// Check for invalid content
|
|
72
65
|
if (options.errorOnInvalidContent) {
|
|
73
66
|
let hasInvalidContent = false
|
|
@@ -106,7 +99,9 @@ export function createNodeFromContent(
|
|
|
106
99
|
}
|
|
107
100
|
|
|
108
101
|
if (options.errorOnInvalidContent && hasInvalidContent) {
|
|
109
|
-
throw new Error('[tiptap error]: Invalid HTML content', {
|
|
102
|
+
throw new Error('[tiptap error]: Invalid HTML content', {
|
|
103
|
+
cause: new Error(`Invalid element found: ${invalidContent}`),
|
|
104
|
+
})
|
|
110
105
|
}
|
|
111
106
|
}
|
|
112
107
|
|
|
@@ -117,7 +112,6 @@ export function createNodeFromContent(
|
|
|
117
112
|
}
|
|
118
113
|
|
|
119
114
|
return parser.parse(elementFromString(content), options.parseOptions)
|
|
120
|
-
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
return createNodeFromContent('', schema, options)
|
|
@@ -9,11 +9,7 @@ import { NodeWithPos, Predicate, Range } from '../types.js'
|
|
|
9
9
|
* @param predicate The predicate to match
|
|
10
10
|
* @returns An array of nodes with their positions
|
|
11
11
|
*/
|
|
12
|
-
export function findChildrenInRange(
|
|
13
|
-
node: ProseMirrorNode,
|
|
14
|
-
range: Range,
|
|
15
|
-
predicate: Predicate,
|
|
16
|
-
): NodeWithPos[] {
|
|
12
|
+
export function findChildrenInRange(node: ProseMirrorNode, range: Range, predicate: Predicate): NodeWithPos[] {
|
|
17
13
|
const nodesWithPos: NodeWithPos[] = []
|
|
18
14
|
|
|
19
15
|
// if (range.from === range.to) {
|
|
@@ -11,6 +11,8 @@ import { findParentNodeClosestToPos } from './findParentNodeClosestToPos.js'
|
|
|
11
11
|
* findParentNode(node => node.type.name === 'paragraph')
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
|
-
export function findParentNode(
|
|
14
|
+
export function findParentNode(
|
|
15
|
+
predicate: Predicate,
|
|
16
|
+
): (selection: Selection) => ReturnType<typeof findParentNodeClosestToPos> {
|
|
15
17
|
return (selection: Selection) => findParentNodeClosestToPos(selection.$from, predicate)
|
|
16
18
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AnyConfig, Extensions } from '../types.js'
|
|
2
|
+
import { getExtensionField } from './getExtensionField.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a flattened array of extensions by traversing the `addExtensions` field.
|
|
6
|
+
* @param extensions An array of Tiptap extensions
|
|
7
|
+
* @returns A flattened array of Tiptap extensions
|
|
8
|
+
*/
|
|
9
|
+
export function flattenExtensions(extensions: Extensions): Extensions {
|
|
10
|
+
return (
|
|
11
|
+
extensions
|
|
12
|
+
.map(extension => {
|
|
13
|
+
const context = {
|
|
14
|
+
name: extension.name,
|
|
15
|
+
options: extension.options,
|
|
16
|
+
storage: extension.storage,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const addExtensions = getExtensionField<AnyConfig['addExtensions']>(extension, 'addExtensions', context)
|
|
20
|
+
|
|
21
|
+
if (addExtensions) {
|
|
22
|
+
return [extension, ...flattenExtensions(addExtensions())]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return extension
|
|
26
|
+
})
|
|
27
|
+
// `Infinity` will break TypeScript so we set a number that is probably high enough
|
|
28
|
+
.flat(10)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -11,10 +11,7 @@ import { getSchemaTypeNameByName } from './getSchemaTypeNameByName.js'
|
|
|
11
11
|
* @param typeOrName The node or mark type or name
|
|
12
12
|
* @returns The attributes of the node or mark or an empty object
|
|
13
13
|
*/
|
|
14
|
-
export function getAttributes(
|
|
15
|
-
state: EditorState,
|
|
16
|
-
typeOrName: string | NodeType | MarkType,
|
|
17
|
-
): Record<string, any> {
|
|
14
|
+
export function getAttributes(state: EditorState, typeOrName: string | NodeType | MarkType): Record<string, any> {
|
|
18
15
|
const schemaType = getSchemaTypeNameByName(
|
|
19
16
|
typeof typeOrName === 'string' ? typeOrName : typeOrName.name,
|
|
20
17
|
state.schema,
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { MarkConfig, NodeConfig } from '../index.js'
|
|
2
|
-
import {
|
|
3
|
-
AnyConfig,
|
|
4
|
-
Attribute,
|
|
5
|
-
Attributes,
|
|
6
|
-
ExtensionAttribute,
|
|
7
|
-
Extensions,
|
|
8
|
-
} from '../types.js'
|
|
2
|
+
import { AnyConfig, Attribute, Attributes, ExtensionAttribute, Extensions } from '../types.js'
|
|
9
3
|
import { getExtensionField } from './getExtensionField.js'
|
|
10
4
|
import { splitExtensions } from './splitExtensions.js'
|
|
11
5
|
|
|
@@ -17,8 +11,9 @@ export function getAttributesFromExtensions(extensions: Extensions): ExtensionAt
|
|
|
17
11
|
const extensionAttributes: ExtensionAttribute[] = []
|
|
18
12
|
const { nodeExtensions, markExtensions } = splitExtensions(extensions)
|
|
19
13
|
const nodeAndMarkExtensions = [...nodeExtensions, ...markExtensions]
|
|
20
|
-
const defaultAttribute: Required<Attribute> = {
|
|
14
|
+
const defaultAttribute: Required<Omit<Attribute, 'validate'>> & Pick<Attribute, 'validate'> = {
|
|
21
15
|
default: null,
|
|
16
|
+
validate: undefined,
|
|
22
17
|
rendered: true,
|
|
23
18
|
renderHTML: null,
|
|
24
19
|
parseHTML: null,
|
|
@@ -48,18 +43,16 @@ export function getAttributesFromExtensions(extensions: Extensions): ExtensionAt
|
|
|
48
43
|
|
|
49
44
|
globalAttributes.forEach(globalAttribute => {
|
|
50
45
|
globalAttribute.types.forEach(type => {
|
|
51
|
-
Object
|
|
52
|
-
.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
attribute
|
|
58
|
-
|
|
59
|
-
...attribute,
|
|
60
|
-
},
|
|
61
|
-
})
|
|
46
|
+
Object.entries(globalAttribute.attributes).forEach(([name, attribute]) => {
|
|
47
|
+
extensionAttributes.push({
|
|
48
|
+
type,
|
|
49
|
+
name,
|
|
50
|
+
attribute: {
|
|
51
|
+
...defaultAttribute,
|
|
52
|
+
...attribute,
|
|
53
|
+
},
|
|
62
54
|
})
|
|
55
|
+
})
|
|
63
56
|
})
|
|
64
57
|
})
|
|
65
58
|
})
|
|
@@ -84,28 +77,26 @@ export function getAttributesFromExtensions(extensions: Extensions): ExtensionAt
|
|
|
84
77
|
// TODO: remove `as Attributes`
|
|
85
78
|
const attributes = addAttributes() as Attributes
|
|
86
79
|
|
|
87
|
-
Object
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
...attribute,
|
|
93
|
-
}
|
|
80
|
+
Object.entries(attributes).forEach(([name, attribute]) => {
|
|
81
|
+
const mergedAttr = {
|
|
82
|
+
...defaultAttribute,
|
|
83
|
+
...attribute,
|
|
84
|
+
}
|
|
94
85
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
86
|
+
if (typeof mergedAttr?.default === 'function') {
|
|
87
|
+
mergedAttr.default = mergedAttr.default()
|
|
88
|
+
}
|
|
98
89
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
if (mergedAttr?.isRequired && mergedAttr?.default === undefined) {
|
|
91
|
+
delete mergedAttr.default
|
|
92
|
+
}
|
|
102
93
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
})
|
|
94
|
+
extensionAttributes.push({
|
|
95
|
+
type: extension.name,
|
|
96
|
+
name,
|
|
97
|
+
attribute: mergedAttr,
|
|
108
98
|
})
|
|
99
|
+
})
|
|
109
100
|
})
|
|
110
101
|
|
|
111
102
|
return extensionAttributes
|
|
@@ -4,8 +4,8 @@ import { Range } from '../types.js'
|
|
|
4
4
|
import { removeDuplicates } from '../utilities/removeDuplicates.js'
|
|
5
5
|
|
|
6
6
|
export type ChangedRange = {
|
|
7
|
-
oldRange: Range
|
|
8
|
-
newRange: Range
|
|
7
|
+
oldRange: Range
|
|
8
|
+
newRange: Range
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -18,15 +18,17 @@ function simplifyChangedRanges(changes: ChangedRange[]): ChangedRange[] {
|
|
|
18
18
|
return uniqueChanges.length === 1
|
|
19
19
|
? uniqueChanges
|
|
20
20
|
: uniqueChanges.filter((change, index) => {
|
|
21
|
-
|
|
21
|
+
const rest = uniqueChanges.filter((_, i) => i !== index)
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
return !rest.some(otherChange => {
|
|
24
|
+
return (
|
|
25
|
+
change.oldRange.from >= otherChange.oldRange.from &&
|
|
26
|
+
change.oldRange.to <= otherChange.oldRange.to &&
|
|
27
|
+
change.newRange.from >= otherChange.newRange.from &&
|
|
28
|
+
change.newRange.to <= otherChange.newRange.to
|
|
29
|
+
)
|
|
30
|
+
})
|
|
28
31
|
})
|
|
29
|
-
})
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
@@ -45,8 +47,8 @@ export function getChangedRanges(transform: Transform): ChangedRange[] {
|
|
|
45
47
|
// @ts-ignore
|
|
46
48
|
if (!stepMap.ranges.length) {
|
|
47
49
|
const { from, to } = steps[index] as Step & {
|
|
48
|
-
from?: number
|
|
49
|
-
to?: number
|
|
50
|
+
from?: number
|
|
51
|
+
to?: number
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
if (from === undefined || to === undefined) {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { ExtensionConfig } from '../Extension.js'
|
|
2
|
+
import { MarkConfig } from '../Mark.js'
|
|
3
|
+
import { NodeConfig } from '../Node.js'
|
|
1
4
|
import { AnyExtension, MaybeThisParameterType, RemoveThis } from '../types.js'
|
|
2
5
|
|
|
3
6
|
/**
|
|
@@ -7,26 +10,23 @@ import { AnyExtension, MaybeThisParameterType, RemoveThis } from '../types.js'
|
|
|
7
10
|
* @param context The context object that should be passed as `this` into the function
|
|
8
11
|
* @returns The field value
|
|
9
12
|
*/
|
|
10
|
-
export function getExtensionField<T = any>(
|
|
11
|
-
extension:
|
|
12
|
-
field:
|
|
13
|
+
export function getExtensionField<T = any, E extends AnyExtension = any>(
|
|
14
|
+
extension: E,
|
|
15
|
+
field: keyof ExtensionConfig | keyof MarkConfig | keyof NodeConfig,
|
|
13
16
|
context?: Omit<MaybeThisParameterType<T>, 'parent'>,
|
|
14
17
|
): RemoveThis<T> {
|
|
15
|
-
|
|
16
|
-
if (extension.config[field] === undefined && extension.parent) {
|
|
18
|
+
if (extension.config[field as keyof typeof extension.config] === undefined && extension.parent) {
|
|
17
19
|
return getExtensionField(extension.parent, field, context)
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
if (typeof extension.config[field] === 'function') {
|
|
21
|
-
const value = extension.config[field].bind({
|
|
22
|
+
if (typeof extension.config[field as keyof typeof extension.config] === 'function') {
|
|
23
|
+
const value = (extension.config[field as keyof typeof extension.config] as any).bind({
|
|
22
24
|
...context,
|
|
23
|
-
parent: extension.parent
|
|
24
|
-
? getExtensionField(extension.parent, field, context)
|
|
25
|
-
: null,
|
|
25
|
+
parent: extension.parent ? getExtensionField(extension.parent, field, context) : null,
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
return value
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
return extension.config[field]
|
|
31
|
+
return extension.config[field as keyof typeof extension.config] as RemoveThis<T>
|
|
32
32
|
}
|
|
@@ -3,10 +3,7 @@ import { EditorState } from '@tiptap/pm/state'
|
|
|
3
3
|
|
|
4
4
|
import { getMarkType } from './getMarkType.js'
|
|
5
5
|
|
|
6
|
-
export function getMarkAttributes(
|
|
7
|
-
state: EditorState,
|
|
8
|
-
typeOrName: string | MarkType,
|
|
9
|
-
): Record<string, any> {
|
|
6
|
+
export function getMarkAttributes(state: EditorState, typeOrName: string | MarkType): Record<string, any> {
|
|
10
7
|
const type = getMarkType(typeOrName, state.schema)
|
|
11
8
|
const { from, to, empty } = state.selection
|
|
12
9
|
const marks: Mark[] = []
|