diff --git a/examples/html-editor/index.js b/examples/html-editor/index.js index 330fadf0..946fdb3a 100644 --- a/examples/html-editor/index.js +++ b/examples/html-editor/index.js @@ -18,7 +18,7 @@ window.undoManager = new Y.utils.UndoManager(window.yXmlType, { }) document.onkeydown = function interceptUndoRedo (e) { - if (e.keyCode === 90 && e.metaKey) { + if (e.keyCode === 90 && (e.metaKey || e.ctrlKey)) { if (!e.shiftKey) { window.undoManager.undo() } else { diff --git a/package-lock.json b/package-lock.json index 8bc1546c..112a1bcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-50", + "version": "13.0.0-53", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2e5cbd06..1290580a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-50", + "version": "13.0.0-53", "description": "A framework for real-time p2p shared editing on any data", "main": "./y.node.js", "browser": "./y.js", diff --git a/src/MessageHandler/binaryEncode.js b/src/MessageHandler/binaryEncode.js index 1efa2d98..3a767a13 100644 --- a/src/MessageHandler/binaryEncode.js +++ b/src/MessageHandler/binaryEncode.js @@ -4,8 +4,10 @@ import { readDeleteSet, writeDeleteSet } from './deleteSet.js' import BinaryEncoder from '../Binary/Encoder.js' export function fromBinary (y, decoder) { - integrateRemoteStructs(y, decoder) - readDeleteSet(y, decoder) + y.transact(function () { + integrateRemoteStructs(y, decoder) + readDeleteSet(y, decoder) + }) } export function toBinary (y) { diff --git a/src/Struct/Item.js b/src/Struct/Item.js index d6fd420b..120c4e75 100644 --- a/src/Struct/Item.js +++ b/src/Struct/Item.js @@ -56,22 +56,56 @@ export default class Item { this._parent = null this._parentSub = null this._deleted = false + this._redone = null } /** - * Copy the effect of struct + * Create a operation with the same effect (without position effect) */ - _copy (undeleteChildren, copyPosition) { - let struct = new this.constructor() - if (copyPosition) { - struct._origin = this._left - struct._left = this._left - struct._right = this - struct._right_origin = this - struct._parent = this._parent - struct._parentSub = this._parentSub + _copy () { + return new this.constructor() + } + /** + * Redo the effect of this operation. + */ + _redo (y) { + if (this._redone !== null) { + return this._redone } + let struct = this._copy() + let left = this._left + let right = this + let parent = this._parent + // make sure that parent is redone + if (parent._deleted === true && parent._redone === null) { + parent._redo(y) + } + if (parent._redone !== null) { + parent = parent._redone + // find next cloned items + while (left !== null && left._redone === null) { + left = left._left + } + if (left !== null) { + left = left._redone + } + while (right !== null && right._redone === null) { + right = right._right + } + if (right !== null) { + right = right._redone + } + } + struct._origin = left + struct._left = left + struct._right = right + struct._right_origin = right + struct._parent = parent + struct._parentSub = this._parentSub + struct._integrate(y) + this._redone = struct return struct } + get _lastId () { return new ID(this._id.user, this._id.clock + this._length - 1) } @@ -104,11 +138,15 @@ export default class Item { if (!this._deleted) { this._deleted = true y.ds.markDeleted(this._id, this._length) + let del = new Delete() + del._targetID = this._id + del._length = this._length if (createDelete) { - let del = new Delete() - del._targetID = this._id - del._length = this._length + // broadcast and persists Delete del._integrate(y, true) + } else if (y.persistence !== null) { + // only persist Delete + y.persistence.saveStruct(y, del) } transactionTypeChanged(y, this._parent, this._parentSub) y._transaction.deletedStructs.add(this) diff --git a/src/Struct/ItemJSON.js b/src/Struct/ItemJSON.js index f2d773d1..f8d22dd4 100644 --- a/src/Struct/ItemJSON.js +++ b/src/Struct/ItemJSON.js @@ -6,8 +6,8 @@ export default class ItemJSON extends Item { super() this._content = null } - _copy (undeleteChildren, copyPosition) { - let struct = super._copy(undeleteChildren, copyPosition) + _copy () { + let struct = super._copy() struct._content = this._content return struct } diff --git a/src/Struct/ItemString.js b/src/Struct/ItemString.js index 7b888b64..a5a0b90c 100644 --- a/src/Struct/ItemString.js +++ b/src/Struct/ItemString.js @@ -6,8 +6,8 @@ export default class ItemString extends Item { super() this._content = null } - _copy (undeleteChildren, copyPosition) { - let struct = super._copy(undeleteChildren, copyPosition) + _copy () { + let struct = super._copy() struct._content = this._content return struct } diff --git a/src/Struct/Type.js b/src/Struct/Type.js index 734dcdcf..80d93fed 100644 --- a/src/Struct/Type.js +++ b/src/Struct/Type.js @@ -79,40 +79,6 @@ export default class Type extends Item { type = type._parent } } - _copy (undeleteChildren, copyPosition) { - let copy = super._copy(undeleteChildren, copyPosition) - let map = new Map() - copy._map = map - for (let [key, value] of this._map) { - if (undeleteChildren.has(value) || !value.deleted) { - let _item = value._copy(undeleteChildren, false) - _item._parent = copy - _item._parentSub = key - map.set(key, _item) - } - } - let prevUndeleted = null - copy._start = null - let item = this._start - while (item !== null) { - if (undeleteChildren.has(item) || !item.deleted) { - let _item = item._copy(undeleteChildren, false) - _item._left = prevUndeleted - _item._origin = prevUndeleted - _item._right = null - _item._right_origin = null - _item._parent = copy - if (prevUndeleted === null) { - copy._start = _item - } else { - prevUndeleted._right = _item - } - prevUndeleted = _item - } - item = item._right - } - return copy - } _transact (f) { const y = this._y if (y !== null) { diff --git a/src/Type/y-xml/YXmlElement.js b/src/Type/y-xml/YXmlElement.js index 39c4a3ae..c795879c 100644 --- a/src/Type/y-xml/YXmlElement.js +++ b/src/Type/y-xml/YXmlElement.js @@ -20,8 +20,8 @@ export default class YXmlElement extends YXmlFragment { this._domFilter = arg2 } } - _copy (undeleteChildren, copyPosition) { - let struct = super._copy(undeleteChildren, copyPosition) + _copy () { + let struct = super._copy() struct.nodeName = this.nodeName return struct } @@ -36,7 +36,8 @@ export default class YXmlElement extends YXmlFragment { let attributes = new Map() for (let i = 0; i < dom.attributes.length; i++) { let attr = dom.attributes[i] - attributes.set(attr.name, attr.value) + // get attribute via getAttribute for custom element support (some write something different in attr.value) + attributes.set(attr.name, dom.getAttribute(attr.name)) } attributes = this._domFilter(dom, attributes) attributes.forEach((value, name) => { diff --git a/src/Type/y-xml/YXmlHook.js b/src/Type/y-xml/YXmlHook.js index 60926148..ee2ce4f7 100644 --- a/src/Type/y-xml/YXmlHook.js +++ b/src/Type/y-xml/YXmlHook.js @@ -14,8 +14,8 @@ export default class YXmlHook extends YMap { getHook(hookName).fillType(dom, this) } } - _copy (undeleteChildren, copyPosition) { - const struct = super._copy(undeleteChildren, copyPosition) + _copy () { + const struct = super._copy() struct.hookName = this.hookName return struct } diff --git a/src/Util/UndoManager.js b/src/Util/UndoManager.js index 9d8a16ef..c18c36d3 100644 --- a/src/Util/UndoManager.js +++ b/src/Util/UndoManager.js @@ -4,11 +4,12 @@ class ReverseOperation { constructor (y, transaction) { this.created = new Date() const beforeState = transaction.beforeState - this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1) if (beforeState.has(y.userID)) { + this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1) this.fromState = new ID(y.userID, beforeState.get(y.userID)) } else { - this.fromState = this.toState + this.toState = null + this.fromState = null } this.deletedStructs = transaction.deletedStructs } @@ -30,28 +31,32 @@ function applyReverseOperation (y, scope, reverseBuffer) { while (!performedUndo && reverseBuffer.length > 0) { let undoOp = reverseBuffer.pop() // make sure that it is possible to iterate {from}-{to} - y.os.getItemCleanStart(undoOp.fromState) - y.os.getItemCleanEnd(undoOp.toState) - y.os.iterate(undoOp.fromState, undoOp.toState, op => { - if (!op._deleted && isStructInScope(y, op, scope)) { - performedUndo = true - op._delete(y) - } - }) + if (undoOp.fromState !== null) { + y.os.getItemCleanStart(undoOp.fromState) + y.os.getItemCleanEnd(undoOp.toState) + y.os.iterate(undoOp.fromState, undoOp.toState, op => { + while (op._deleted && op._redone !== null) { + op = op._redone + } + if (op._deleted === false && isStructInScope(y, op, scope)) { + performedUndo = true + op._delete(y) + } + }) + } for (let op of undoOp.deletedStructs) { if ( isStructInScope(y, op, scope) && op._parent !== y && - !op._parent._deleted && ( - op._parent._id.user !== y.userID || - op._parent._id.clock < undoOp.fromState.clock || - op._parent._id.clock > undoOp.fromState.clock + op._id.user !== y.userID || + undoOp.fromState === null || + op._id.clock < undoOp.fromState.clock || + op._id.clock > undoOp.toState.clock ) ) { performedUndo = true - op = op._copy(undoOp.deletedStructs, true) - op._integrate(y) + op._redo(y) } } } @@ -77,7 +82,12 @@ export default class UndoManager { let lastUndoOp = this._undoBuffer.length > 0 ? this._undoBuffer[this._undoBuffer.length - 1] : null if (lastUndoOp !== null && reverseOperation.created - lastUndoOp.created <= options.captureTimeout) { lastUndoOp.created = reverseOperation.created - lastUndoOp.toState = reverseOperation.toState + if (reverseOperation.toState !== null) { + lastUndoOp.toState = reverseOperation.toState + if (lastUndoOp.fromState === null) { + lastUndoOp.fromState = reverseOperation.fromState + } + } reverseOperation.deletedStructs.forEach(lastUndoOp.deletedStructs.add, lastUndoOp.deletedStructs) } else { this._undoBuffer.push(reverseOperation)