From d064e6e96e72cfcf2926c23e628f35a644cc22d7 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Tue, 25 Jun 2019 02:26:18 +0200 Subject: [PATCH] UndoManager accepts an array of types as scope. Implements #156 --- README.v13.md | 4 ++-- package-lock.json | 41 +++++++++++----------------------------- src/utils/UndoManager.js | 22 ++++++++++----------- tests/undo-redo.tests.js | 21 ++++++++++++++++++++ 4 files changed, 45 insertions(+), 43 deletions(-) diff --git a/README.v13.md b/README.v13.md index 6d9a1fa9..8adcab73 100644 --- a/README.v13.md +++ b/README.v13.md @@ -661,9 +661,9 @@ ytext.toString() // => 'abc' ```
- constructor(type:Y.AbstractType, + constructor(scope:Y.AbstractType|Array<Y.AbstractType>, [trackedTransactionOrigins:Set<any>, [{captureTimeout: number}]]) -
+
Accepts either single type as scope or an array of types.
undo()
redo() diff --git a/package-lock.json b/package-lock.json index af860ef6..c60363cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1490,8 +1490,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -1512,14 +1511,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1534,20 +1531,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1664,8 +1658,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1677,7 +1670,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1692,7 +1684,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1700,14 +1691,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1726,7 +1715,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -1807,8 +1795,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -1820,7 +1807,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -1906,8 +1892,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -1943,7 +1928,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1963,7 +1947,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2007,14 +1990,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/src/utils/UndoManager.js b/src/utils/UndoManager.js index 737028ab..393a93fa 100644 --- a/src/utils/UndoManager.js +++ b/src/utils/UndoManager.js @@ -9,7 +9,7 @@ import { createID, followRedone, getItemCleanStart, - Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line + Transaction, Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line } from '../internals.js' import * as time from 'lib0/time.js' @@ -45,7 +45,7 @@ const popStackItem = (undoManager, stack, eventType) => { */ let result = null const doc = undoManager.doc - const type = undoManager.type + const scope = undoManager.scope transact(doc, transaction => { while (stack.length > 0 && result === null) { const store = doc.store @@ -53,7 +53,7 @@ const popStackItem = (undoManager, stack, eventType) => { const itemsToRedo = new Set() let performedChange = false iterateDeletedStructs(transaction, stackItem.ds, store, struct => { - if (struct instanceof Item && isParentOf(type, struct)) { + if (struct instanceof Item && scope.some(type => isParentOf(type, struct))) { itemsToRedo.add(struct) } }) @@ -72,7 +72,7 @@ const popStackItem = (undoManager, stack, eventType) => { } struct = item } - if (!struct.deleted && isParentOf(type, /** @type {Item} */ (struct))) { + if (!struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) { struct.delete(transaction) performedChange = true } @@ -97,13 +97,13 @@ const popStackItem = (undoManager, stack, eventType) => { */ export class UndoManager extends Observable { /** - * @param {AbstractType} type + * @param {AbstractType|Array>} typeScope Accepts either a single type, or an array of types * @param {Set} [trackedTransactionOrigins=new Set([null])] * @param {object} [options={captureTimeout=500}] */ - constructor (type, trackedTransactionOrigins = new Set([null]), { captureTimeout = 500 } = {}) { + constructor (typeScope, trackedTransactionOrigins = new Set([null]), { captureTimeout = 500 } = {}) { super() - this.type = type + this.scope = typeScope instanceof Array ? typeScope : [typeScope] trackedTransactionOrigins.add(this) this.trackedTransactionOrigins = trackedTransactionOrigins /** @@ -121,11 +121,11 @@ export class UndoManager extends Observable { */ this.undoing = false this.redoing = false - this.doc = /** @type {Doc} */ (type.doc) + this.doc = /** @type {Doc} */ (this.scope[0].doc) this.lastChange = 0 - type.observeDeep((events, transaction) => { + this.doc.on('afterTransaction', /** @param {Transaction} transaction */ transaction => { // Only track certain transactions - if (!this.trackedTransactionOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedTransactionOrigins.has(transaction.origin.constructor))) { + if (!this.scope.some(type => transaction.changedParentTypes.has(type)) || (!this.trackedTransactionOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedTransactionOrigins.has(transaction.origin.constructor)))) { return } const undoing = this.undoing @@ -154,7 +154,7 @@ export class UndoManager extends Observable { } // make sure that deleted structs are not gc'd iterateDeletedStructs(transaction, transaction.deleteSet, transaction.doc.store, /** @param {Item|GC} item */ item => { - if (item instanceof Item && isParentOf(type, item)) { + if (item instanceof Item && this.scope.some(type => isParentOf(type, item))) { keepItem(item) } }) diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index 8f622bd5..ca4051f8 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -180,3 +180,24 @@ export const testTrackClass = tc => { undoManager.undo() t.assert(text0.toString() === '') } + +/** + * @param {t.TestCase} tc + */ +export const testTypeScope = tc => { + const { array0 } = init(tc, { users: 3 }) + // only track origins that are numbers + const text0 = new Y.Text() + const text1 = new Y.Text() + array0.insert(0, [text0, text1]) + const undoManager = new UndoManager(text0) + const undoManagerBoth = new UndoManager([text0, text1]) + text1.insert(0, 'abc') + t.assert(undoManager.undoStack.length === 0) + t.assert(undoManagerBoth.undoStack.length === 1) + t.assert(text1.toString() === 'abc') + undoManager.undo() + t.assert(text1.toString() === 'abc') + undoManagerBoth.undo() + t.assert(text1.toString() === '') +}