sanitize items before undoing. fixes #165
This commit is contained in:
parent
ad373a3dce
commit
303138f309
@ -9,6 +9,7 @@ import {
|
|||||||
createID,
|
createID,
|
||||||
followRedone,
|
followRedone,
|
||||||
getItemCleanStart,
|
getItemCleanStart,
|
||||||
|
getState,
|
||||||
Transaction, Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line
|
Transaction, Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@ -49,23 +50,39 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
transact(doc, transaction => {
|
transact(doc, transaction => {
|
||||||
while (stack.length > 0 && result === null) {
|
while (stack.length > 0 && result === null) {
|
||||||
const store = doc.store
|
const store = doc.store
|
||||||
|
const clientID = doc.clientID
|
||||||
const stackItem = /** @type {StackItem} */ (stack.pop())
|
const stackItem = /** @type {StackItem} */ (stack.pop())
|
||||||
|
const stackStartClock = stackItem.start
|
||||||
|
const stackEndClock = stackItem.start + stackItem.len
|
||||||
const itemsToRedo = new Set()
|
const itemsToRedo = new Set()
|
||||||
|
// @todo iterateStructs should not need the structs parameter
|
||||||
|
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(clientID))
|
||||||
let performedChange = false
|
let performedChange = false
|
||||||
|
if (stackStartClock !== stackEndClock) {
|
||||||
|
// make sure structs don't overlap with the range of created operations [stackItem.start, stackItem.start + stackItem.end)
|
||||||
|
getItemCleanStart(transaction, createID(clientID, stackStartClock))
|
||||||
|
if (stackEndClock < getState(doc.store, clientID)) {
|
||||||
|
getItemCleanStart(transaction, createID(clientID, stackEndClock))
|
||||||
|
}
|
||||||
|
}
|
||||||
iterateDeletedStructs(transaction, stackItem.ds, struct => {
|
iterateDeletedStructs(transaction, stackItem.ds, struct => {
|
||||||
if (struct instanceof Item && scope.some(type => isParentOf(type, struct))) {
|
if (
|
||||||
|
struct instanceof Item &&
|
||||||
|
scope.some(type => isParentOf(type, struct)) &&
|
||||||
|
// Never redo structs in [stackItem.start, stackItem.start + stackItem.end) because they were created and deleted in the same capture interval.
|
||||||
|
!(struct.id.client === clientID && struct.id.clock >= stackStartClock && struct.id.clock < stackEndClock)
|
||||||
|
) {
|
||||||
itemsToRedo.add(struct)
|
itemsToRedo.add(struct)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
itemsToRedo.forEach(item => {
|
itemsToRedo.forEach(item => {
|
||||||
performedChange = redoItem(transaction, item, itemsToRedo) !== null || performedChange
|
performedChange = redoItem(transaction, item, itemsToRedo) !== null || performedChange
|
||||||
})
|
})
|
||||||
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(doc.clientID))
|
|
||||||
/**
|
/**
|
||||||
* @type {Array<Item>}
|
* @type {Array<Item>}
|
||||||
*/
|
*/
|
||||||
const itemsToDelete = []
|
const itemsToDelete = []
|
||||||
iterateStructs(transaction, structs, stackItem.start, stackItem.len, struct => {
|
iterateStructs(transaction, structs, stackStartClock, stackItem.len, struct => {
|
||||||
if (struct instanceof Item && !struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) {
|
if (struct instanceof Item && !struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) {
|
||||||
if (struct.redone !== null) {
|
if (struct.redone !== null) {
|
||||||
let { item, diff } = followRedone(store, struct.id)
|
let { item, diff } = followRedone(store, struct.id)
|
||||||
@ -73,7 +90,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + diff))
|
item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + diff))
|
||||||
}
|
}
|
||||||
if (item.length > stackItem.len) {
|
if (item.length > stackItem.len) {
|
||||||
getItemCleanStart(transaction, createID(item.id.client, item.id.clock + stackItem.len))
|
getItemCleanStart(transaction, createID(item.id.client, stackEndClock))
|
||||||
}
|
}
|
||||||
struct = item
|
struct = item
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,10 @@ import * as t from 'lib0/testing.js'
|
|||||||
export const testUndoText = tc => {
|
export const testUndoText = tc => {
|
||||||
const { testConnector, text0, text1 } = init(tc, { users: 3 })
|
const { testConnector, text0, text1 } = init(tc, { users: 3 })
|
||||||
const undoManager = new UndoManager(text0)
|
const undoManager = new UndoManager(text0)
|
||||||
|
text0.insert(0, 'test')
|
||||||
|
text0.delete(0, 4)
|
||||||
|
undoManager.undo()
|
||||||
|
t.assert(text0.toString() === '')
|
||||||
text0.insert(0, 'abc')
|
text0.insert(0, 'abc')
|
||||||
text1.insert(0, 'xyz')
|
text1.insert(0, 'xyz')
|
||||||
testConnector.syncAll()
|
testConnector.syncAll()
|
||||||
@ -65,6 +69,15 @@ export const testUndoMap = tc => {
|
|||||||
t.assert(map0.get('a') === 44)
|
t.assert(map0.get('a') === 44)
|
||||||
undoManager.redo()
|
undoManager.redo()
|
||||||
t.assert(map0.get('a') === 44)
|
t.assert(map0.get('a') === 44)
|
||||||
|
|
||||||
|
// test setting value multiple times
|
||||||
|
map0.set('b', 'initial')
|
||||||
|
undoManager.stopCapturing()
|
||||||
|
map0.set('b', 'val1')
|
||||||
|
map0.set('b', 'val2')
|
||||||
|
undoManager.stopCapturing()
|
||||||
|
undoManager.undo()
|
||||||
|
t.assert(map0.get('b') === 'initial')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user