diff --git a/examples/html-editor/index.js b/examples/html-editor/index.js index e91883e4..8eb4c56d 100644 --- a/examples/html-editor/index.js +++ b/examples/html-editor/index.js @@ -16,7 +16,9 @@ window.onload = function () { // Bind children of XmlFragment to the document.body window.yXmlType.bindToDom(document.body) } -window.undoManager = new Y.utils.UndoManager(window.yXmlType) +window.undoManager = new Y.utils.UndoManager(window.yXmlType, { + captureTimeout: 1000 +}) document.onkeydown = function interceptUndoRedo (e) { if (e.keyCode === 90 && e.ctrlKey) { diff --git a/src/Struct/Type.js b/src/Struct/Type.js index 58c17eb6..6d7c4d1b 100644 --- a/src/Struct/Type.js +++ b/src/Struct/Type.js @@ -39,6 +39,32 @@ export default class Type extends Item { this._eventHandler = new EventHandler() this._deepEventHandler = new EventHandler() } + getPathTo (type) { + if (type === this) { + return [] + } + const path = [] + const y = this._y + while (type._parent !== this && this._parent !== y) { + let parent = type._parent + if (type._parentSub !== null) { + path.push(type._parentSub) + } else { + // parent is array-ish + for (let [i, child] of parent) { + if (child === type) { + path.push(i) + break + } + } + } + type = parent + } + if (this._parent !== this) { + throw new Error('The type is not a child of this node') + } + return path + } _callEventHandler (event) { const changedParentTypes = this._y._transaction.changedParentTypes this._eventHandler.callEventListeners(event) diff --git a/src/Util/UndoManager.js b/src/Util/UndoManager.js index aed06735..7c535d6d 100644 --- a/src/Util/UndoManager.js +++ b/src/Util/UndoManager.js @@ -2,6 +2,7 @@ import ID from './ID.js' class ReverseOperation { constructor (y) { + this.created = new Date() const beforeState = y._transaction.beforeState this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1) if (beforeState.has(y.userID)) { @@ -24,7 +25,9 @@ function isStructInScope (y, struct, scope) { } export default class UndoManager { - constructor (scope) { + constructor (scope, options = {}) { + this.options = options + options.captureTimeout = options.captureTimeout || 0 this._undoBuffer = [] this._redoBuffer = [] this._scope = scope @@ -36,7 +39,15 @@ export default class UndoManager { if (!remote && (y._transaction.beforeState.has(y.userID) || y._transaction.deletedStructs.size > 0)) { let reverseOperation = new ReverseOperation(y) if (!this._undoing) { - this._undoBuffer.push(reverseOperation) + let lastUndoOp = this._undoBuffer.length > 0 ? this._undoBuffer[this._undoBuffer.length - 1] : null + if (lastUndoOp !== null && lastUndoOp.created - reverseOperation.created <= options.captureTimeout) { + console.log('appending', lastUndoOp, reverseOperation) + lastUndoOp.created = reverseOperation.created + lastUndoOp.toState = reverseOperation.toState + reverseOperation.deletedStructs.forEach(lastUndoOp.deletedStructs.add, lastUndoOp.deletedStructs) + } else { + this._undoBuffer.push(reverseOperation) + } if (!this._redoing) { this._redoBuffer = [] } @@ -47,6 +58,7 @@ export default class UndoManager { }) } undo () { + console.log('undoing') this._undoing = true this._applyReverseOperation(this._undoBuffer) this._undoing = false diff --git a/src/Util/YEvent.js b/src/Util/YEvent.js index a6c486b7..cf37bf35 100644 --- a/src/Util/YEvent.js +++ b/src/Util/YEvent.js @@ -2,32 +2,27 @@ export default class YEvent { constructor (target) { this.target = target - this._path = null + this.currentTarget = target } get path () { - if (this._path !== null) { - return this._path - } else { - const path = [] - let type = this.target - const y = type._y - while (type._parent !== y) { - let parent = type._parent - if (type._parentSub !== null) { - path.push(type._parentSub) - } else { - // parent is array-ish - for (let [i, child] of parent) { - if (child === type) { - path.push(i) - break - } + const path = [] + let type = this.target + const y = type._y + while (type._parent !== this._currentTarget && type._parent !== y) { + let parent = type._parent + if (type._parentSub !== null) { + path.push(type._parentSub) + } else { + // parent is array-ish + for (let [i, child] of parent) { + if (child === type) { + path.push(i) + break } } - type = parent } - this._path = path - return path + type = parent } + return path } } diff --git a/src/Y.js b/src/Y.js index 51c22276..6cdf6e74 100644 --- a/src/Y.js +++ b/src/Y.js @@ -61,9 +61,14 @@ export default class Y extends NamedEventHandler { }) this._transaction.changedParentTypes.forEach(function (events, type) { if (!type._deleted) { - events = events.filter(event => - !event.target._deleted - ) + events = events + .filter(event => + !event.target._deleted + ) + events + .forEach(event => { + event.currentTarget = type + }) // we don't have to check for events.length // because there is no way events is empty.. type._deepEventHandler.callEventListeners(events)