Compare commits

..

5 Commits

Author SHA1 Message Date
Kevin Jahns
be1449a7af 13.5.32 2022-03-26 10:31:13 +01:00
Kevin Jahns
a22b3cdbc1 add option to UndoManager to ignore remote map changes. implements #390 2022-03-26 10:29:19 +01:00
Kevin Jahns
e9a0dc4ed2 add destroy logic 2022-03-25 11:08:30 +01:00
Kevin Jahns
b0b276d964 13.5.31 2022-03-25 11:01:57 +01:00
Kevin Jahns
d3e117702c add method to add & remove tracked origins 2022-03-25 11:00:07 +01:00
5 changed files with 60 additions and 10 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "yjs",
"version": "13.5.30",
"version": "13.5.32",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "yjs",
"version": "13.5.30",
"version": "13.5.32",
"license": "MIT",
"dependencies": {
"lib0": "^0.2.47"

View File

@@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "13.5.30",
"version": "13.5.32",
"description": "Shared Editing Library",
"main": "./dist/yjs.cjs",
"module": "./dist/yjs.mjs",

View File

@@ -127,12 +127,13 @@ export const splitItem = (transaction, leftItem, diff) => {
* @param {Item} item
* @param {Set<Item>} redoitems
* @param {DeleteSet} itemsToDelete
* @param {boolean} ignoreRemoteMapChanges
*
* @return {Item|null}
*
* @private
*/
export const redoItem = (transaction, item, redoitems, itemsToDelete) => {
export const redoItem = (transaction, item, redoitems, itemsToDelete, ignoreRemoteMapChanges) => {
const doc = transaction.doc
const store = doc.store
const ownClientID = doc.clientID
@@ -152,7 +153,7 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete) => {
// make sure that parent is redone
if (parentItem !== null && parentItem.deleted === true) {
// try to undo parent if it will be undone anyway
if (parentItem.redone === null && (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete) === null)) {
if (parentItem.redone === null && (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete, ignoreRemoteMapChanges) === null)) {
return null
}
while (parentItem.redone !== null) {
@@ -198,7 +199,7 @@ export const redoItem = (transaction, item, redoitems, itemsToDelete) => {
}
} else {
right = null
if (item.right) {
if (item.right && !ignoreRemoteMapChanges) {
left = item
// Iterate right while right is in itemsToDelete
// If it is intended to delete right while item is redone, we can expect that item should replace right.

View File

@@ -101,7 +101,7 @@ const popStackItem = (undoManager, stack, eventType) => {
}
})
itemsToRedo.forEach(struct => {
performedChange = redoItem(transaction, struct, itemsToRedo, stackItem.insertions) !== null || performedChange
performedChange = redoItem(transaction, struct, itemsToRedo, stackItem.insertions, undoManager.ignoreRemoteMapChanges) !== null || performedChange
})
// We want to delete in reverse order so that children are deleted before
// parents, so we have more information available when items are filtered.
@@ -137,6 +137,7 @@ const popStackItem = (undoManager, stack, eventType) => {
* filter returns false, the type/item won't be deleted even it is in the
* undo/redo scope.
* @property {Set<any>} [UndoManagerOptions.trackedOrigins=new Set([null])]
* @property {boolean} [ignoreRemoteMapChanges] Experimental. By default, the UndoManager will never overwrite remote changes. Enable this property to enable overwriting remote changes on key-value changes (Y.Map, properties on Y.Xml, etc..).
*/
/**
@@ -153,7 +154,7 @@ export class UndoManager extends Observable {
* @param {AbstractType<any>|Array<AbstractType<any>>} typeScope Accepts either a single type, or an array of types
* @param {UndoManagerOptions} options
*/
constructor (typeScope, { captureTimeout = 500, deleteFilter = () => true, trackedOrigins = new Set([null]) } = {}) {
constructor (typeScope, { captureTimeout = 500, deleteFilter = () => true, trackedOrigins = new Set([null]), ignoreRemoteMapChanges = false } = {}) {
super()
/**
* @type {Array<AbstractType<any>>}
@@ -180,7 +181,11 @@ export class UndoManager extends Observable {
this.redoing = false
this.doc = /** @type {Doc} */ (this.scope[0].doc)
this.lastChange = 0
this.doc.on('afterTransaction', /** @param {Transaction} transaction */ transaction => {
this.ignoreRemoteMapChanges = ignoreRemoteMapChanges
/**
* @param {Transaction} transaction
*/
this.afterTransactionHandler = transaction => {
// Only track certain transactions
if (!this.scope.some(type => transaction.changedParentTypes.has(type)) || (!this.trackedOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedOrigins.has(transaction.origin.constructor)))) {
return
@@ -229,7 +234,8 @@ export class UndoManager extends Observable {
} else {
this.emit('stack-item-updated', changeEvent)
}
})
}
this.doc.on('afterTransaction', this.afterTransactionHandler)
this.doc.on('destroy', () => {
this.destroy()
})
@@ -247,6 +253,20 @@ export class UndoManager extends Observable {
})
}
/**
* @param {any} origin
*/
addTrackedOrigin (origin) {
this.trackedOrigins.add(origin)
}
/**
* @param {any} origin
*/
removeTrackedOrigin (origin) {
this.trackedOrigins.delete(origin)
}
clear (clearUndoStack = true, clearRedoStack = true) {
if ((clearUndoStack && this.canUndo()) || (clearRedoStack && this.canRedo())) {
this.doc.transact(tr => {
@@ -336,4 +356,10 @@ export class UndoManager extends Observable {
canRedo () {
return this.redoStack.length > 0
}
destroy () {
this.trackedOrigins.delete(this)
this.doc.off('afterTransaction', this.afterTransactionHandler)
super.destroy()
}
}

View File

@@ -565,3 +565,26 @@ export const testUndoDeleteTextFormat = tc => {
t.compare(text.toDelta(), expect)
t.compare(text2.toDelta(), expect)
}
/**
* Undo text formatting delete should not corrupt peer state.
*
* @see https://github.com/yjs/yjs/issues/392
* @param {t.TestCase} tc
*/
export const testBehaviorOfIgnoreremotemapchangesProperty = tc => {
const doc = new Y.Doc()
const doc2 = new Y.Doc()
doc.on('update', update => Y.applyUpdate(doc2, update, doc))
doc2.on('update', update => Y.applyUpdate(doc, update, doc2))
const map1 = doc.getMap()
const map2 = doc2.getMap()
const um1 = new Y.UndoManager(map1, { ignoreRemoteMapChanges: true })
map1.set('x', 1)
map2.set('x', 2)
map1.set('x', 3)
map2.set('x', 4)
um1.undo()
t.assert(map1.get('x') === 2)
t.assert(map2.get('x') === 2)
}