@tiptap/core
Version:
headless rich text editor
102 lines (92 loc) • 3.29 kB
text/typescript
import { RemoveMarkStep } from '@tiptap/pm/transform'
import { Extension } from '../Extension.js'
import { combineTransactionSteps, getChangedRanges } from '../helpers/index.js'
/**
* This extension allows you to be notified when the user deletes content you are interested in.
*/
export const Delete = Extension.create({
name: 'delete',
onUpdate({ transaction, appendedTransactions }) {
const callback = () => {
if (
this.editor.options.coreExtensionOptions?.delete?.filterTransaction?.(transaction) ??
transaction.getMeta('y-sync$')
) {
return
}
const nextTransaction = combineTransactionSteps(transaction.before, [
transaction,
...appendedTransactions,
])
const changes = getChangedRanges(nextTransaction)
changes.forEach(change => {
if (
nextTransaction.mapping.mapResult(change.oldRange.from).deletedAfter &&
nextTransaction.mapping.mapResult(change.oldRange.to).deletedBefore
) {
nextTransaction.before.nodesBetween(
change.oldRange.from,
change.oldRange.to,
(node, from) => {
const to = from + node.nodeSize - 2
const isFullyWithinRange = change.oldRange.from <= from && to <= change.oldRange.to
this.editor.emit('delete', {
type: 'node',
node,
from,
to,
newFrom: nextTransaction.mapping.map(from),
newTo: nextTransaction.mapping.map(to),
deletedRange: change.oldRange,
newRange: change.newRange,
partial: !isFullyWithinRange,
editor: this.editor,
transaction,
combinedTransform: nextTransaction,
})
},
)
}
})
const mapping = nextTransaction.mapping
nextTransaction.steps.forEach((step, index) => {
if (step instanceof RemoveMarkStep) {
const newStart = mapping.slice(index).map(step.from, -1)
const newEnd = mapping.slice(index).map(step.to)
const oldStart = mapping.invert().map(newStart, -1)
const oldEnd = mapping.invert().map(newEnd)
const foundBeforeMark =
newStart > 0
? nextTransaction.doc.nodeAt(newStart - 1)?.marks.some(mark => mark.eq(step.mark))
: false
const foundAfterMark = nextTransaction.doc
.nodeAt(newEnd)
?.marks.some(mark => mark.eq(step.mark))
this.editor.emit('delete', {
type: 'mark',
mark: step.mark,
from: step.from,
to: step.to,
deletedRange: {
from: oldStart,
to: oldEnd,
},
newRange: {
from: newStart,
to: newEnd,
},
partial: Boolean(foundAfterMark || foundBeforeMark),
editor: this.editor,
transaction,
combinedTransform: nextTransaction,
})
}
})
}
if (this.editor.options.coreExtensionOptions?.delete?.async ?? true) {
setTimeout(callback, 0)
} else {
callback()
}
},
})