diff --git a/package.json b/package.json index d0133c49..16544493 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,10 @@ "url": "https://github.com/sponsors/dmonad" }, "scripts": { + "clean": "rm -rf dist docs", "test": "npm run dist && node ./dist/tests.cjs --repetition-time 50", "test-extensive": "npm run lint && npm run dist && node ./dist/tests.cjs --production --repetition-time 10000", - "dist": "rm -rf dist && rollup -c && tsc", + "dist": "npm run clean && rollup -c && tsc", "watch": "rollup -wc", "lint": "markdownlint README.md && standard && tsc", "docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.md --package ./package.json || true", diff --git a/src/utils/UndoManager.js b/src/utils/UndoManager.js index f6f13ee5..16ccb7ae 100644 --- a/src/utils/UndoManager.js +++ b/src/utils/UndoManager.js @@ -52,11 +52,6 @@ const clearUndoManagerStackItem = (tr, um, stackItem) => { * @return {StackItem?} */ const popStackItem = (undoManager, stack, eventType) => { - /** - * Whether a change happened - * @type {StackItem?} - */ - let result = null /** * Keep a reference to the transaction so we can fire the event with the changedParentTypes * @type {any} @@ -65,7 +60,7 @@ const popStackItem = (undoManager, stack, eventType) => { const doc = undoManager.doc const scope = undoManager.scope transact(doc, transaction => { - while (stack.length > 0 && result === null) { + while (stack.length > 0 && undoManager.currStackItem === null) { const store = doc.store const stackItem = /** @type {StackItem} */ (stack.pop()) /** @@ -113,7 +108,7 @@ const popStackItem = (undoManager, stack, eventType) => { performedChange = true } } - result = performedChange ? stackItem : null + undoManager.currStackItem = performedChange ? stackItem : null } transaction.changed.forEach((subProps, type) => { // destroy search marker if necessary @@ -123,11 +118,12 @@ const popStackItem = (undoManager, stack, eventType) => { }) _tr = transaction }, undoManager) - if (result != null) { + if (undoManager.currStackItem != null) { const changedParentTypes = _tr.changedParentTypes - undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType, changedParentTypes }, undoManager]) + undoManager.emit('stack-item-popped', [{ stackItem: undoManager.currStackItem, type: eventType, changedParentTypes }, undoManager]) + undoManager.currStackItem = null } - return result + return undoManager.currStackItem } /** @@ -191,6 +187,12 @@ export class UndoManager extends Observable { */ this.undoing = false this.redoing = false + /** + * The currently popped stack item if UndoManager.undoing or UndoManager.redoing + * + * @type {StackItem|null} + */ + this.currStackItem = null this.lastChange = 0 this.ignoreRemoteMapChanges = ignoreRemoteMapChanges this.captureTimeout = captureTimeout diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index f1dbf428..2fb2ff2e 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -715,3 +715,33 @@ export const testUndoDeleteInMap = (tc) => { undoManager.undo() t.compare(map0.toJSON(), { a: 'a' }) } + +/** + * It should expose the StackItem being processed if undoing + * + * @param {t.TestCase} _tc + */ +export const testUndoDoingStackItem = async (_tc) => { + const doc = new Y.Doc() + const text = doc.getText('text') + const undoManager = new Y.UndoManager([text]) + undoManager.on('stack-item-added', /** @param {any} event */ event => { + event.stackItem.meta.set('str', '42') + }) + let metaUndo = /** @type {any} */ (null) + let metaRedo = /** @type {any} */ (null) + text.observe((event) => { + const /** @type {Y.UndoManager} */ origin = event.transaction.origin + if (origin === undoManager && origin.undoing) { + metaUndo = origin.currStackItem?.meta.get('str') + } else if (origin === undoManager && origin.redoing) { + metaRedo = origin.currStackItem?.meta.get('str') + } + }) + text.insert(0, 'abc') + undoManager.undo() + undoManager.redo() + t.compare(metaUndo, '42', 'currStackItem is accessible while undoing') + t.compare(metaRedo, '42', 'currStackItem is accessible while redoing') + t.compare(undoManager.currStackItem, null, 'currStackItem is null after observe/transaction') +}