[Undo] add UndoManager.currStackItem

This commit is contained in:
Kevin Jahns 2024-02-29 14:46:43 +01:00
parent 917261a1ce
commit 29fa60ccf9
3 changed files with 23 additions and 63 deletions

View File

@ -12,9 +12,10 @@
"url": "https://github.com/sponsors/dmonad" "url": "https://github.com/sponsors/dmonad"
}, },
"scripts": { "scripts": {
"clean": "rm -rf dist docs",
"test": "npm run dist && node ./dist/tests.cjs --repetition-time 50", "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", "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", "watch": "rollup -wc",
"lint": "markdownlint README.md && standard && tsc", "lint": "markdownlint README.md && standard && tsc",
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.md --package ./package.json || true", "docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.md --package ./package.json || true",

View File

@ -52,11 +52,6 @@ const clearUndoManagerStackItem = (tr, um, stackItem) => {
* @return {StackItem?} * @return {StackItem?}
*/ */
const popStackItem = (undoManager, stack, eventType) => { 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 * Keep a reference to the transaction so we can fire the event with the changedParentTypes
* @type {any} * @type {any}
@ -65,7 +60,7 @@ const popStackItem = (undoManager, stack, eventType) => {
const doc = undoManager.doc const doc = undoManager.doc
const scope = undoManager.scope const scope = undoManager.scope
transact(doc, transaction => { transact(doc, transaction => {
while (stack.length > 0 && result === null) { while (stack.length > 0 && undoManager.currStackItem === null) {
const store = doc.store const store = doc.store
const stackItem = /** @type {StackItem} */ (stack.pop()) const stackItem = /** @type {StackItem} */ (stack.pop())
/** /**
@ -113,7 +108,7 @@ const popStackItem = (undoManager, stack, eventType) => {
performedChange = true performedChange = true
} }
} }
result = performedChange ? stackItem : null undoManager.currStackItem = performedChange ? stackItem : null
} }
transaction.changed.forEach((subProps, type) => { transaction.changed.forEach((subProps, type) => {
// destroy search marker if necessary // destroy search marker if necessary
@ -123,11 +118,12 @@ const popStackItem = (undoManager, stack, eventType) => {
}) })
_tr = transaction _tr = transaction
}, undoManager) }, undoManager)
if (result != null) { if (undoManager.currStackItem != null) {
const changedParentTypes = _tr.changedParentTypes 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
} }
/** /**
@ -196,7 +192,7 @@ export class UndoManager extends Observable {
* *
* @type {StackItem|null} * @type {StackItem|null}
*/ */
this.doingStackItem = null this.currStackItem = null
this.lastChange = 0 this.lastChange = 0
this.ignoreRemoteMapChanges = ignoreRemoteMapChanges this.ignoreRemoteMapChanges = ignoreRemoteMapChanges
this.captureTimeout = captureTimeout this.captureTimeout = captureTimeout
@ -337,12 +333,10 @@ export class UndoManager extends Observable {
*/ */
undo () { undo () {
this.undoing = true this.undoing = true
this.doingStackItem = array.last(this.undoStack) ?? null
let res let res
try { try {
res = popStackItem(this, this.undoStack, 'undo') res = popStackItem(this, this.undoStack, 'undo')
} finally { } finally {
this.doingStackItem = null
this.undoing = false this.undoing = false
} }
return res return res
@ -355,12 +349,10 @@ export class UndoManager extends Observable {
*/ */
redo () { redo () {
this.redoing = true this.redoing = true
this.doingStackItem = array.last(this.redoStack) ?? null
let res let res
try { try {
res = popStackItem(this, this.redoStack, 'redo') res = popStackItem(this, this.redoStack, 'redo')
} finally { } finally {
this.doingStackItem = null
this.redoing = false this.redoing = false
} }
return res return res

View File

@ -719,62 +719,29 @@ export const testUndoDeleteInMap = (tc) => {
/** /**
* It should expose the StackItem being processed if undoing * It should expose the StackItem being processed if undoing
* *
* @param {t.TestCase} tc * @param {t.TestCase} _tc
*/ */
export const testUndoDoingStackItem = async (tc) => { export const testUndoDoingStackItem = async (_tc) => {
const doc = new Y.Doc() const doc = new Y.Doc()
const text = doc.getText('text') const text = doc.getText('text')
const undoManager = new Y.UndoManager([text]) const undoManager = new Y.UndoManager([text])
undoManager.on('stack-item-added', /** @param {any} event */ event => { undoManager.on('stack-item-added', /** @param {any} event */ event => {
event.stackItem.meta.set('str', '42') event.stackItem.meta.set('str', '42')
}) })
let metaUndo = /** @type {any} */ (null)
const meta = new Promise((resolve) => { let metaRedo = /** @type {any} */ (null)
setTimeout(() => resolve('ABORTED'), 50) text.observe((event) => {
text.observe((event) => { const /** @type {Y.UndoManager} */ origin = event.transaction.origin
const /** @type {Y.UndoManager} */ origin = event.transaction.origin if (origin === undoManager && origin.undoing) {
if (origin === undoManager && origin.undoing) { metaUndo = origin.currStackItem?.meta.get('str')
resolve(origin.doingStackItem?.meta.get('str')) } else if (origin === undoManager && origin.redoing) {
} metaRedo = origin.currStackItem?.meta.get('str')
}) }
}) })
text.insert(0, 'abc')
undoManager.undo()
t.compare(await meta, '42')
t.compare(undoManager.doingStackItem, null)
}
/**
* It should expose the StackItem being processed if redoing
*
* @param {t.TestCase} tc
*/
export const testRedoDoingStackItem = 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')
})
const meta = new Promise(resolve => {
setTimeout(() => resolve('ABORTED'), 50)
text.observe((event) => {
const /** @type {Y.UndoManager} */ origin = event.transaction.origin
if (origin === undoManager && origin.redoing) {
resolve(origin.doingStackItem?.meta.get('str'))
}
})
})
text.insert(0, 'abc') text.insert(0, 'abc')
undoManager.undo() undoManager.undo()
undoManager.redo() undoManager.redo()
t.compare(metaUndo, '42', 'currStackItem is accessible while undoing')
t.compare(await meta, '42') t.compare(metaRedo, '42', 'currStackItem is accessible while redoing')
t.compare(undoManager.doingStackItem, null) t.compare(undoManager.currStackItem, null, 'currStackItem is null after observe/transaction')
} }