diff --git a/src/utils/UndoManager.js b/src/utils/UndoManager.js index 98410e4d..2abe5924 100644 --- a/src/utils/UndoManager.js +++ b/src/utils/UndoManager.js @@ -131,6 +131,7 @@ const popStackItem = (undoManager, stack, eventType) => { * @typedef {Object} UndoManagerOptions * @property {number} [UndoManagerOptions.captureTimeout=500] * @property {function(Transaction):boolean} [UndoManagerOptions.captureTransaction] Do not capture changes of a Transaction if result false. + * @property {function(Transaction):boolean} [UndoManagerOptions.appendToLatestStackItem] Append changes to the previous stack item if result true (regardless of captureTimeout). * @property {function(Item):boolean} [UndoManagerOptions.deleteFilter=()=>true] Sometimes * it is necessary to filter what an Undo/Redo operation can delete. If this * filter returns false, the type/item won't be deleted even it is in the @@ -165,6 +166,7 @@ export class UndoManager extends ObservableV2 { constructor (typeScope, { captureTimeout = 500, captureTransaction = _tr => true, + appendToLatestStackItem = _tr => false, deleteFilter = () => true, trackedOrigins = new Set([null]), ignoreRemoteMapChanges = false, @@ -181,6 +183,7 @@ export class UndoManager extends ObservableV2 { trackedOrigins.add(this) this.trackedOrigins = trackedOrigins this.captureTransaction = captureTransaction + this.appendToLatestStackItem = appendToLatestStackItem /** * @type {Array} */ @@ -236,7 +239,10 @@ export class UndoManager extends ObservableV2 { }) const now = time.getUnixTime() let didAdd = false - if (this.lastChange > 0 && now - this.lastChange < this.captureTimeout && stack.length > 0 && !undoing && !redoing) { + if (this.lastChange > 0 && + (now - this.lastChange < this.captureTimeout || this.appendToLatestStackItem(transaction)) && + stack.length > 0 && !undoing && !redoing + ) { // append change to last stack op const lastOp = stack[stack.length - 1] lastOp.deletions = mergeDeleteSets([lastOp.deletions, transaction.deleteSet]) diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index faf42aff..a8811cf9 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -811,3 +811,26 @@ export const testUndoDoingStackItem = async (_tc) => { t.compare(metaRedo, '42', 'currStackItem is accessible while redoing') t.compare(undoManager.currStackItem, null, 'currStackItem is null after observe/transaction') } + +/** + * @param {t.TestCase} tc + */ +export const testAppendToLatestStackItem = tc => { + const { array0, array1 } = init(tc, { users: 2 }) + const undoManager0 = new Y.UndoManager(array0, { captureTimeout: 0, appendToLatestStackItem: (_transaction) => true}) + const undoManager1 = new Y.UndoManager(array1, { captureTimeout: 0, appendToLatestStackItem: (_transaction) => false}) + + array0.push([1, 2, 3]) + undoManager0.stopCapturing() + array0.push([4, 5, 6]) + array0.push([7, 8, 9]) + undoManager0.undo() + t.compare(array0.toArray(), [1, 2, 3]) + + array1.push([1, 2, 3]) + undoManager0.stopCapturing() + array1.push([4, 5, 6]) + array1.push([7, 8, 9]) + undoManager1.undo() + t.compare(array1.toArray(), [1, 2, 3, 4, 5, 6]) +} \ No newline at end of file