Proper follow redones in nested redos - fixes #317
This commit is contained in:
parent
7486ea7148
commit
995fbfa4cc
@ -125,12 +125,13 @@ export const splitItem = (transaction, leftItem, diff) => {
|
|||||||
* @param {Transaction} transaction The Yjs instance.
|
* @param {Transaction} transaction The Yjs instance.
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
* @param {Set<Item>} redoitems
|
* @param {Set<Item>} redoitems
|
||||||
|
* @param {Array<Item>} itemsToDelete
|
||||||
*
|
*
|
||||||
* @return {Item|null}
|
* @return {Item|null}
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export const redoItem = (transaction, item, redoitems) => {
|
export const redoItem = (transaction, item, redoitems, itemsToDelete) => {
|
||||||
const doc = transaction.doc
|
const doc = transaction.doc
|
||||||
const store = doc.store
|
const store = doc.store
|
||||||
const ownClientID = doc.clientID
|
const ownClientID = doc.clientID
|
||||||
@ -170,7 +171,7 @@ export const redoItem = (transaction, item, redoitems) => {
|
|||||||
// make sure that parent is redone
|
// make sure that parent is redone
|
||||||
if (parentItem !== null && parentItem.deleted === true && parentItem.redone === null) {
|
if (parentItem !== null && parentItem.deleted === true && parentItem.redone === null) {
|
||||||
// try to undo parent if it will be undone anyway
|
// try to undo parent if it will be undone anyway
|
||||||
if (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems) === null) {
|
if (!redoitems.has(parentItem) || redoItem(transaction, parentItem, redoitems, itemsToDelete) === null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,6 +210,11 @@ export const redoItem = (transaction, item, redoitems) => {
|
|||||||
}
|
}
|
||||||
right = right.right
|
right = right.right
|
||||||
}
|
}
|
||||||
|
// 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.
|
||||||
|
while (left !== null && left.right !== null && left.right !== right && itemsToDelete.findIndex(d => d === /** @type {Item} */ (left).right) >= 0) {
|
||||||
|
left = left.right
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const nextClock = getState(store, ownClientID)
|
const nextClock = getState(store, ownClientID)
|
||||||
const nextId = createID(ownClientID, nextClock)
|
const nextId = createID(ownClientID, nextClock)
|
||||||
|
@ -36,11 +36,11 @@ export class YMapEvent extends YEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template T number|string|Object|Array|Uint8Array
|
* @template MapType
|
||||||
* A shared Map implementation.
|
* A shared Map implementation.
|
||||||
*
|
*
|
||||||
* @extends AbstractType<YMapEvent<T>>
|
* @extends AbstractType<YMapEvent<MapType>>
|
||||||
* @implements {Iterable<T>}
|
* @implements {Iterable<MapType>}
|
||||||
*/
|
*/
|
||||||
export class YMap extends AbstractType {
|
export class YMap extends AbstractType {
|
||||||
/**
|
/**
|
||||||
@ -85,7 +85,7 @@ export class YMap extends AbstractType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {YMap<T>}
|
* @return {YMap<MapType>}
|
||||||
*/
|
*/
|
||||||
clone () {
|
clone () {
|
||||||
const map = new YMap()
|
const map = new YMap()
|
||||||
@ -108,11 +108,11 @@ export class YMap extends AbstractType {
|
|||||||
/**
|
/**
|
||||||
* Transforms this Shared Type to a JSON object.
|
* Transforms this Shared Type to a JSON object.
|
||||||
*
|
*
|
||||||
* @return {Object<string,T>}
|
* @return {Object<string,any>}
|
||||||
*/
|
*/
|
||||||
toJSON () {
|
toJSON () {
|
||||||
/**
|
/**
|
||||||
* @type {Object<string,T>}
|
* @type {Object<string,MapType>}
|
||||||
*/
|
*/
|
||||||
const map = {}
|
const map = {}
|
||||||
this._map.forEach((item, key) => {
|
this._map.forEach((item, key) => {
|
||||||
@ -163,11 +163,11 @@ export class YMap extends AbstractType {
|
|||||||
/**
|
/**
|
||||||
* Executes a provided function on once on every key-value pair.
|
* Executes a provided function on once on every key-value pair.
|
||||||
*
|
*
|
||||||
* @param {function(T,string,YMap<T>):void} f A function to execute on every element of this YArray.
|
* @param {function(MapType,string,YMap<MapType>):void} f A function to execute on every element of this YArray.
|
||||||
*/
|
*/
|
||||||
forEach (f) {
|
forEach (f) {
|
||||||
/**
|
/**
|
||||||
* @type {Object<string,T>}
|
* @type {Object<string,MapType>}
|
||||||
*/
|
*/
|
||||||
const map = {}
|
const map = {}
|
||||||
this._map.forEach((item, key) => {
|
this._map.forEach((item, key) => {
|
||||||
@ -179,7 +179,7 @@ export class YMap extends AbstractType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {IterableIterator<T>}
|
* @return {IterableIterator<MapType>}
|
||||||
*/
|
*/
|
||||||
[Symbol.iterator] () {
|
[Symbol.iterator] () {
|
||||||
return this.entries()
|
return this.entries()
|
||||||
@ -204,7 +204,7 @@ export class YMap extends AbstractType {
|
|||||||
* Adds or updates an element with a specified key and value.
|
* Adds or updates an element with a specified key and value.
|
||||||
*
|
*
|
||||||
* @param {string} key The key of the element to add to this YMap
|
* @param {string} key The key of the element to add to this YMap
|
||||||
* @param {T} value The value of the element to add
|
* @param {MapType} value The value of the element to add
|
||||||
*/
|
*/
|
||||||
set (key, value) {
|
set (key, value) {
|
||||||
if (this.doc !== null) {
|
if (this.doc !== null) {
|
||||||
@ -221,7 +221,7 @@ export class YMap extends AbstractType {
|
|||||||
* Returns a specified element from this YMap.
|
* Returns a specified element from this YMap.
|
||||||
*
|
*
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @return {T|undefined}
|
* @return {MapType|undefined}
|
||||||
*/
|
*/
|
||||||
get (key) {
|
get (key) {
|
||||||
return /** @type {any} */ (typeMapGet(this, key))
|
return /** @type {any} */ (typeMapGet(this, key))
|
||||||
|
@ -88,7 +88,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
itemsToRedo.forEach(struct => {
|
itemsToRedo.forEach(struct => {
|
||||||
performedChange = redoItem(transaction, struct, itemsToRedo) !== null || performedChange
|
performedChange = redoItem(transaction, struct, itemsToRedo, itemsToDelete) !== null || performedChange
|
||||||
})
|
})
|
||||||
// We want to delete in reverse order so that children are deleted before
|
// We want to delete in reverse order so that children are deleted before
|
||||||
// parents, so we have more information available when items are filtered.
|
// parents, so we have more information available when items are filtered.
|
||||||
|
@ -301,3 +301,58 @@ export const testUndoUntilChangePerformed = tc => {
|
|||||||
undoManager.undo()
|
undoManager.undo()
|
||||||
t.compareStrings(yMap2.get('key'), 'value')
|
t.compareStrings(yMap2.get('key'), 'value')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This issue has been reported in https://github.com/yjs/yjs/issues/317
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testUndoNestedUndoIssue = tc => {
|
||||||
|
const doc = new Y.Doc({ gc: false })
|
||||||
|
const design = doc.getMap()
|
||||||
|
const undoManager = new Y.UndoManager(design, { captureTimeout: 0 })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Y.Map<any>}
|
||||||
|
*/
|
||||||
|
const text = new Y.Map()
|
||||||
|
|
||||||
|
const blocks1 = new Y.Array()
|
||||||
|
const blocks1block = new Y.Map()
|
||||||
|
|
||||||
|
doc.transact(() => {
|
||||||
|
blocks1block.set('text', 'Type Something')
|
||||||
|
blocks1.push([blocks1block])
|
||||||
|
text.set('blocks', blocks1block)
|
||||||
|
design.set('text', text)
|
||||||
|
})
|
||||||
|
|
||||||
|
const blocks2 = new Y.Array()
|
||||||
|
const blocks2block = new Y.Map()
|
||||||
|
doc.transact(() => {
|
||||||
|
blocks2block.set('text', 'Something')
|
||||||
|
blocks2.push([blocks2block])
|
||||||
|
text.set('blocks', blocks2block)
|
||||||
|
})
|
||||||
|
|
||||||
|
const blocks3 = new Y.Array()
|
||||||
|
const blocks3block = new Y.Map()
|
||||||
|
doc.transact(() => {
|
||||||
|
blocks3block.set('text', 'Something Else')
|
||||||
|
blocks3.push([blocks3block])
|
||||||
|
text.set('blocks', blocks3block)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.compare(design.toJSON(), { text: { blocks: { text: 'Something Else' } } })
|
||||||
|
undoManager.undo()
|
||||||
|
t.compare(design.toJSON(), { text: { blocks: { text: 'Something' } } })
|
||||||
|
undoManager.undo()
|
||||||
|
t.compare(design.toJSON(), { text: { blocks: { text: 'Type Something' } } })
|
||||||
|
undoManager.undo()
|
||||||
|
t.compare(design.toJSON(), { })
|
||||||
|
undoManager.redo()
|
||||||
|
t.compare(design.toJSON(), { text: { blocks: { text: 'Type Something' } } })
|
||||||
|
undoManager.redo()
|
||||||
|
t.compare(design.toJSON(), { text: { blocks: { text: 'Something' } } })
|
||||||
|
undoManager.redo()
|
||||||
|
t.compare(design.toJSON(), { text: { blocks: { text: 'Something Else' } } })
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user