diff --git a/rollup.browser.js b/rollup.browser.js index a0b87065..d5f6c137 100644 --- a/rollup.browser.js +++ b/rollup.browser.js @@ -19,8 +19,7 @@ export default { browser: true }), commonjs(), - // babel(), - /* + babel(), uglify({ mangle: { except: ['YMap', 'Y', 'YArray', 'YText', 'YXmlHook', 'YXmlFragment', 'YXmlElement', 'YXmlEvent', 'YXmlText', 'YEvent', 'YArrayEvent', 'YMapEvent', 'Type', 'Delete', 'ItemJSON', 'ItemString', 'Item'] @@ -36,7 +35,6 @@ export default { } } }) - */ ], banner: ` /** diff --git a/src/MessageHandler/deleteSet.js b/src/MessageHandler/deleteSet.js index cb4b1c3b..2b54abfc 100644 --- a/src/MessageHandler/deleteSet.js +++ b/src/MessageHandler/deleteSet.js @@ -92,7 +92,7 @@ export function readDeleteSet (y, decoder) { // delete maximum the len of d // else delete as much as possible diff = Math.min(n._id.clock - d[0], d[1]) - // deleteItemRange(y, user, d[0], diff) + // deleteItemRange(y, user, d[0], diff, true) deletions.push([user, d[0], diff]) } else { // 3) @@ -100,7 +100,7 @@ export function readDeleteSet (y, decoder) { if (d[2] && !n.gc) { // d marks as gc'd but n does not // then delete either way - // deleteItemRange(y, user, d[0], Math.min(diff, d[1])) + // deleteItemRange(y, user, d[0], Math.min(diff, d[1]), true) deletions.push([user, d[0], Math.min(diff, d[1])]) } } @@ -117,12 +117,12 @@ export function readDeleteSet (y, decoder) { // Adapt the Tree implementation to support delete while iterating for (let i = deletions.length - 1; i >= 0; i--) { const del = deletions[i] - deleteItemRange(y, del[0], del[1], del[2]) + deleteItemRange(y, del[0], del[1], del[2], true) } // for the rest.. just apply it for (; pos < dv.length; pos++) { d = dv[pos] - deleteItemRange(y, user, d[0], d[1]) + deleteItemRange(y, user, d[0], d[1], true) // deletions.push([user, d[0], d[1], d[2]]) } } diff --git a/src/MessageHandler/syncStep1.js b/src/MessageHandler/syncStep1.js index 8834d9f0..046177c3 100644 --- a/src/MessageHandler/syncStep1.js +++ b/src/MessageHandler/syncStep1.js @@ -42,7 +42,15 @@ export function writeStructs (y, encoder, ss) { for (let user of y.ss.state.keys()) { let clock = ss.get(user) || 0 if (user !== RootFakeUserID) { - y.os.iterate(new ID(user, clock), new ID(user, Number.MAX_VALUE), function (struct) { + const minBound = new ID(user, clock) + const overlappingLeft = y.os.findPrev(minBound) + const rightID = overlappingLeft === null ? null : overlappingLeft._id + if (rightID !== null && rightID.user === user && rightID.clock + overlappingLeft._length > clock) { + const struct = overlappingLeft._clonePartial(clock - rightID.clock) + struct._toBinary(encoder) + len++ + } + y.os.iterate(minBound, new ID(user, Number.MAX_VALUE), function (struct) { struct._toBinary(encoder) len++ }) diff --git a/src/Store/DeleteStore.js b/src/Store/DeleteStore.js index eb5b1a9e..f35bd928 100644 --- a/src/Store/DeleteStore.js +++ b/src/Store/DeleteStore.js @@ -1,3 +1,4 @@ + import Tree from '../Util/Tree.js' import ID from '../Util/ID/ID.js' diff --git a/src/Struct/Delete.js b/src/Struct/Delete.js index 8e1e7c21..74bdbeb1 100644 --- a/src/Struct/Delete.js +++ b/src/Struct/Delete.js @@ -7,7 +7,7 @@ import { logID } from '../MessageHandler/messageToString.js' * Delete all items in an ID-range * TODO: implement getItemCleanStartNode for better performance (only one lookup) */ -export function deleteItemRange (y, user, clock, range) { +export function deleteItemRange (y, user, clock, range, gcChildren) { const createDelete = y.connector !== null && y.connector._forwardAppliedStructs let item = y.os.getItemCleanStart(new ID(user, clock)) if (item !== null) { @@ -24,7 +24,7 @@ export function deleteItemRange (y, user, clock, range) { const nodeVal = node.val if (!nodeVal._deleted) { nodeVal._splitAt(y, range) - nodeVal._delete(y, createDelete, true) + nodeVal._delete(y, createDelete, gcChildren) } const nodeLen = nodeVal._length range -= nodeLen @@ -100,7 +100,7 @@ export default class Delete { if (!locallyCreated) { // from remote const id = this._targetID - deleteItemRange(y, id.user, id.clock, this._length) + deleteItemRange(y, id.user, id.clock, this._length, false) } else if (y.connector !== null) { // from local y.connector.broadcastStruct(this) diff --git a/src/Struct/GC.js b/src/Struct/GC.js index 1a90c888..a23d55b6 100644 --- a/src/Struct/GC.js +++ b/src/Struct/GC.js @@ -36,7 +36,6 @@ export default class GC { n._length += next._length y.os.delete(next._id) } - if (id.user !== RootFakeUserID) { if (y.connector !== null && (y.connector._forwardAppliedStructs || id.user === y.userID)) { y.connector.broadcastStruct(this) @@ -85,4 +84,11 @@ export default class GC { _splitAt () { return this } + + _clonePartial (diff) { + const gc = new GC() + gc._id = new ID(this._id.user, this._id.clock + diff) + gc._length = this._length - diff + return gc + } } \ No newline at end of file diff --git a/src/Struct/Item.js b/src/Struct/Item.js index 9b76e14b..1ff928f2 100644 --- a/src/Struct/Item.js +++ b/src/Struct/Item.js @@ -1,6 +1,6 @@ import { getStructReference } from '../Util/structReferences.js' import ID from '../Util/ID/ID.js' -import { RootFakeUserID } from '../Util/ID/RootID.js' +import { default as RootID, RootFakeUserID } from '../Util/ID/RootID.js' import Delete from './Delete.js' import { transactionTypeChanged } from '../Transaction.js' import GC from './GC.js' @@ -486,7 +486,12 @@ export default class Item { const parentID = decoder.readID() // parent does not change, so we don't have to search for it again if (this._parent === null) { - const parent = y.os.get(parentID) + let parent + if (parentID.constructor === RootID) { + parent = y.os.get(parentID) + } else { + parent = y.os.getItem(parentID) + } if (parent === null) { missing.push(parentID) } else { diff --git a/src/Struct/Type.js b/src/Struct/Type.js index 2d96dcc2..44f075ad 100644 --- a/src/Struct/Type.js +++ b/src/Struct/Type.js @@ -214,7 +214,10 @@ export default class Type extends Item { * @param {boolean} createDelete Whether to propagate a message that this * Type was deleted. */ - _delete (y, createDelete, gcChildren = true) { + _delete (y, createDelete, gcChildren) { + if (gcChildren === undefined) { + gcChildren = y._hasUndoManager === false + } super._delete(y, createDelete, gcChildren) y._transaction.changedTypes.delete(this) // delete map types diff --git a/src/Util/UndoManager.js b/src/Util/UndoManager.js index 54adbbde..0810ffc8 100644 --- a/src/Util/UndoManager.js +++ b/src/Util/UndoManager.js @@ -75,6 +75,7 @@ export default class UndoManager { this._lastTransactionWasUndo = false const y = scope._y this.y = y + y._hasUndoManager = true y.on('afterTransaction', (y, transaction, remote) => { if (!remote && transaction.changedParentTypes.has(scope)) { let reverseOperation = new ReverseOperation(y, transaction) diff --git a/src/Y.js b/src/Y.js index ca7b63ef..7a1ab318 100644 --- a/src/Y.js +++ b/src/Y.js @@ -79,6 +79,7 @@ export default class Y extends NamedEventHandler { } // for compatibility with isParentOf this._parent = null + this._hasUndoManager = false } _setContentReady () { if (!this._contentReady) {