diff --git a/package-lock.json b/package-lock.json index e7a81dc3..69ea9528 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "yjs", - "version": "13.5.39", + "version": "13.5.41", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "yjs", - "version": "13.5.39", + "version": "13.5.41", "license": "MIT", "dependencies": { "lib0": "^0.2.49" diff --git a/package.json b/package.json index d1c17746..83a3db73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.5.39", + "version": "13.5.41", "description": "Shared Editing Library", "main": "./dist/yjs.cjs", "module": "./dist/yjs.mjs", diff --git a/src/structs/Item.js b/src/structs/Item.js index 0b39a246..704dcd32 100644 --- a/src/structs/Item.js +++ b/src/structs/Item.js @@ -211,11 +211,6 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemo while (left !== null && left.redone !== null) { left = getItemCleanStart(transaction, left.redone) } - // check wether we were allowed to follow right (indicating that originally this op was replaced by another item) - if (left === null || /** @type {AbstractType} */ (left.parent)._item !== parentItem) { - // invalid parent; should never happen - return null - } if (left && left.right !== null) { // It is not possible to redo this item because it conflicts with a // change from another client diff --git a/src/utils/UndoManager.js b/src/utils/UndoManager.js index ecbe5ff3..81e9ed04 100644 --- a/src/utils/UndoManager.js +++ b/src/utils/UndoManager.js @@ -142,6 +142,7 @@ const popStackItem = (undoManager, stack, eventType) => { * @property {boolean} [UndoManagerOptions.shouldDestroyUndoManager=true] Disable default destroy behavior if false. Sometimes * when use undoManager to manage multiply components globally, each component (like y-prosemirror.yUndoPlugin...) may call destroy once being removed, then cause the global undoManager being destoryed. * In this case, disable this option maybe be a choice to get the control back to yourself. + * @property {Doc} [doc] The document that this UndoManager operates on. Only needed if typeScope is empty. */ /** @@ -165,6 +166,7 @@ export class UndoManager extends Observable { trackedOrigins = new Set([null]), ignoreRemoteMapChanges = false, shouldDestroyUndoManager = true + doc = /** @type {Doc} */ (array.isArray(typeScope) ? typeScope[0].doc : typeScope.doc) } = {}) { super() /** @@ -192,7 +194,7 @@ export class UndoManager extends Observable { */ this.undoing = false this.redoing = false - this.doc = /** @type {Doc} */ (this.scope[0].doc) + this.doc = doc this.lastChange = 0 this.ignoreRemoteMapChanges = ignoreRemoteMapChanges /** diff --git a/tests/undo-redo.tests.js b/tests/undo-redo.tests.js index c1889d9f..3b3794f0 100644 --- a/tests/undo-redo.tests.js +++ b/tests/undo-redo.tests.js @@ -49,6 +49,20 @@ export const testUndoText = tc => { t.compare(text0.toDelta(), [{ insert: 'b' }, { insert: 'cxy', attributes: { bold: true } }, { insert: 'z' }]) } +/** + * Test case to fix #241 + * @param {t.TestCase} tc + */ +export const testEmptyTypeScope = tc => { + const ydoc = new Y.Doc() + const um = new Y.UndoManager([], { doc: ydoc }) + const yarray = ydoc.getArray() + um.addToScope(yarray) + yarray.insert(0, [1]) + um.undo() + t.assert(yarray.length === 0) +} + /** * Test case to fix #241 * @param {t.TestCase} tc @@ -588,3 +602,32 @@ export const testBehaviorOfIgnoreremotemapchangesProperty = tc => { t.assert(map1.get('x') === 2) t.assert(map2.get('x') === 2) } + +/** + * Special deletion case. + * + * @see https://github.com/yjs/yjs/issues/447 + * @param {t.TestCase} tc + */ +export const testSpecialDeletionCase = tc => { + const origin = 'undoable' + const doc = new Y.Doc() + const fragment = doc.getXmlFragment() + const undoManager = new Y.UndoManager(fragment, { trackedOrigins: new Set([origin]) }) + doc.transact(() => { + const e = new Y.XmlElement('test') + e.setAttribute('a', '1') + e.setAttribute('b', '2') + fragment.insert(0, [e]) + }) + t.compareStrings(fragment.toString(), '') + doc.transact(() => { + // change attribute "b" and delete test-node + const e = fragment.get(0) + e.setAttribute('b', '3') + fragment.delete(0) + }, origin) + t.compareStrings(fragment.toString(), '') + undoManager.undo() + t.compareStrings(fragment.toString(), '') +}