From e4d4c23f0b86cfa2fc490c0e9a0ff7179c8f318a Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Wed, 7 Feb 2018 14:07:57 +0100 Subject: [PATCH 1/9] bugfix - persist deletes when syncing --- src/Struct/Item.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Struct/Item.js b/src/Struct/Item.js index 27f4224b..ae99c984 100644 --- a/src/Struct/Item.js +++ b/src/Struct/Item.js @@ -92,11 +92,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) From 4855b2d5909442aaf24aac4943c86ea2d9bb2c0c Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Wed, 7 Feb 2018 14:08:43 +0100 Subject: [PATCH 2/9] 13.0.0-51 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d698c8a1..631a980c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-50", + "version": "13.0.0-51", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d50e4c54..79a9b938 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-50", + "version": "13.0.0-51", "description": "A framework for real-time p2p shared editing on any data", "main": "./y.node.js", "browser": "./y.js", From f1f1bff901ba7e5f3cf076e91bf97c7ad4723f03 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Thu, 15 Feb 2018 17:58:14 +0100 Subject: [PATCH 3/9] preliminary undo-redo fixes --- examples/html-editor/index.js | 2 +- src/Struct/Item.js | 54 ++++++++++++++++++++++++++++------- src/Struct/ItemJSON.js | 4 +-- src/Struct/ItemString.js | 4 +-- src/Struct/Type.js | 34 ---------------------- src/Type/y-xml/YXmlElement.js | 4 +-- src/Type/y-xml/YXmlHook.js | 4 +-- src/Util/UndoManager.js | 39 +++++++++++++------------ 8 files changed, 73 insertions(+), 72 deletions(-) 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/src/Struct/Item.js b/src/Struct/Item.js index ae99c984..2f9f87b7 100644 --- a/src/Struct/Item.js +++ b/src/Struct/Item.js @@ -51,22 +51,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) } 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..e546c8b3 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 } 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..83b37530 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,28 @@ 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) { + let start = y.os.getItemCleanStart(undoOp.fromState) + y.os.getItemCleanEnd(undoOp.toState) + console.log(start) + y.os.iterate(undoOp.fromState, undoOp.toState, op => { + debugger + if (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._parent !== y ) { performedUndo = true - op = op._copy(undoOp.deletedStructs, true) - op._integrate(y) + op._redo(y) } } } From 937de2c59fa6b4850a210086ca0ca0e4e6dd6faa Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Thu, 15 Feb 2018 18:28:53 +0100 Subject: [PATCH 4/9] fix fast undo-redo bug --- src/Util/UndoManager.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Util/UndoManager.js b/src/Util/UndoManager.js index 83b37530..7fe07573 100644 --- a/src/Util/UndoManager.js +++ b/src/Util/UndoManager.js @@ -32,12 +32,10 @@ function applyReverseOperation (y, scope, reverseBuffer) { let undoOp = reverseBuffer.pop() // make sure that it is possible to iterate {from}-{to} if (undoOp.fromState !== null) { - let start = y.os.getItemCleanStart(undoOp.fromState) + y.os.getItemCleanStart(undoOp.fromState) y.os.getItemCleanEnd(undoOp.toState) - console.log(start) y.os.iterate(undoOp.fromState, undoOp.toState, op => { - debugger - if (op._deleted && op._redone !== null) { + while (op._deleted && op._redone !== null) { op = op._redone } if (op._deleted === false && isStructInScope(y, op, scope)) { @@ -49,7 +47,13 @@ function applyReverseOperation (y, scope, reverseBuffer) { for (let op of undoOp.deletedStructs) { if ( isStructInScope(y, op, scope) && - op._parent !== y + op._parent !== y && + ( + op._id.user !== y.userID || + undoOp.fromState === null || + op._id.clock < undoOp.fromState || + op._id.clock > undoOp.toState + ) ) { performedUndo = true op._redo(y) @@ -78,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) From 5e4b071693e49ad7168d55dd2ff9bd109e605767 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Thu, 15 Feb 2018 18:58:43 +0100 Subject: [PATCH 5/9] actually use clock in undo-manager --- src/Util/UndoManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Util/UndoManager.js b/src/Util/UndoManager.js index 7fe07573..c18c36d3 100644 --- a/src/Util/UndoManager.js +++ b/src/Util/UndoManager.js @@ -51,8 +51,8 @@ function applyReverseOperation (y, scope, reverseBuffer) { ( op._id.user !== y.userID || undoOp.fromState === null || - op._id.clock < undoOp.fromState || - op._id.clock > undoOp.toState + op._id.clock < undoOp.fromState.clock || + op._id.clock > undoOp.toState.clock ) ) { performedUndo = true From de14fe0f3e6a4d9edfa3e257619e0e7aaa29d486 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sun, 18 Feb 2018 18:58:49 +0100 Subject: [PATCH 6/9] fix getAttribute vs attributes.value fixes y-js/y-xml#8 --- src/Type/y-xml/YXmlElement.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Type/y-xml/YXmlElement.js b/src/Type/y-xml/YXmlElement.js index e546c8b3..c795879c 100644 --- a/src/Type/y-xml/YXmlElement.js +++ b/src/Type/y-xml/YXmlElement.js @@ -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) => { From 5f8ae0dd4336aca4dc6442df76258d5face80e40 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sun, 18 Feb 2018 19:20:00 +0100 Subject: [PATCH 7/9] 13.0.0-52 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 631a980c..8a56adea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-51", + "version": "13.0.0-52", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 79a9b938..f8705d27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-51", + "version": "13.0.0-52", "description": "A framework for real-time p2p shared editing on any data", "main": "./y.node.js", "browser": "./y.js", From fcbca65d8fc97e52fac5fff43fe70159d0998e5a Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sun, 25 Feb 2018 02:31:20 +0100 Subject: [PATCH 8/9] fromBinary is a transaction --- src/MessageHandler/binaryEncode.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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) { From 641f42633976eb23f15b0fbd16eb57bcdb939262 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Sun, 25 Feb 2018 02:31:59 +0100 Subject: [PATCH 9/9] 13.0.0-53 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a56adea..710c8166 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-52", + "version": "13.0.0-53", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f8705d27..d530ee81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yjs", - "version": "13.0.0-52", + "version": "13.0.0-53", "description": "A framework for real-time p2p shared editing on any data", "main": "./y.node.js", "browser": "./y.js",