UndoManager configuration to filter deletes
This commit is contained in:
parent
1337d38ada
commit
251c8aaefc
@ -662,7 +662,7 @@ ytext.toString() // => 'abc'
|
|||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
<b><code>constructor(scope:Y.AbstractType|Array<Y.AbstractType>,
|
<b><code>constructor(scope:Y.AbstractType|Array<Y.AbstractType>,
|
||||||
[trackedTransactionOrigins:Set<any>, [{captureTimeout: number}]])</code></b>
|
[[{captureTimeout:number,trackedOrigins:Set<any>,deleteFilter:function(item):boolean}]])</code></b>
|
||||||
<dd>Accepts either single type as scope or an array of types.</dd>
|
<dd>Accepts either single type as scope or an array of types.</dd>
|
||||||
<b><code>undo()</code></b>
|
<b><code>undo()</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
|
@ -61,6 +61,10 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
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))
|
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(doc.clientID))
|
||||||
|
/**
|
||||||
|
* @type {Array<Item>}
|
||||||
|
*/
|
||||||
|
const itemsToDelete = []
|
||||||
iterateStructs(transaction, structs, stackItem.start, stackItem.len, struct => {
|
iterateStructs(transaction, structs, stackItem.start, 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) {
|
||||||
@ -73,11 +77,18 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
}
|
}
|
||||||
struct = item
|
struct = item
|
||||||
}
|
}
|
||||||
keepItem(struct)
|
itemsToDelete.push(struct)
|
||||||
struct.delete(transaction)
|
|
||||||
performedChange = true
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// We want to delete in reverse order so that children are deleted before
|
||||||
|
// parents, so we have more information available when items are filtered.
|
||||||
|
for (let i = itemsToDelete.length - 1; i >= 0; i--) {
|
||||||
|
const item = itemsToDelete[i]
|
||||||
|
if (undoManager.deleteFilter(item)) {
|
||||||
|
item.delete(transaction)
|
||||||
|
performedChange = true
|
||||||
|
}
|
||||||
|
}
|
||||||
result = stackItem
|
result = stackItem
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
|
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
|
||||||
@ -87,6 +98,16 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} UndoManagerOptions
|
||||||
|
* @property {number} [UndoManagerOptions.captureTimeout=500]
|
||||||
|
* @property {function(Item):boolean} [UndoManagerOptions.deleteFilter=()=>true] Sometimes
|
||||||
|
* it is necessary to filter whan an Undo/Redo operation can delete. If this
|
||||||
|
* 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])]
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires 'stack-item-added' event when a stack item was added to either the undo- or
|
* Fires 'stack-item-added' event when a stack item was added to either the undo- or
|
||||||
* the redo-stack. You may store additional stack information via the
|
* the redo-stack. You may store additional stack information via the
|
||||||
@ -99,17 +120,17 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
export class UndoManager extends Observable {
|
export class UndoManager extends Observable {
|
||||||
/**
|
/**
|
||||||
* @param {AbstractType<any>|Array<AbstractType<any>>} typeScope Accepts either a single type, or an array of types
|
* @param {AbstractType<any>|Array<AbstractType<any>>} typeScope Accepts either a single type, or an array of types
|
||||||
* @param {Set<any>} [trackedTransactionOrigins=new Set([null])]
|
* @param {UndoManagerOptions} options
|
||||||
* @param {object} [options={captureTimeout=500}]
|
|
||||||
*/
|
*/
|
||||||
constructor (typeScope, trackedTransactionOrigins = new Set([null]), { captureTimeout } = {}) {
|
constructor (typeScope, { captureTimeout, deleteFilter = () => true, trackedOrigins = new Set([null]) } = {}) {
|
||||||
if (captureTimeout == null) {
|
if (captureTimeout == null) {
|
||||||
captureTimeout = 500
|
captureTimeout = 500
|
||||||
}
|
}
|
||||||
super()
|
super()
|
||||||
this.scope = typeScope instanceof Array ? typeScope : [typeScope]
|
this.scope = typeScope instanceof Array ? typeScope : [typeScope]
|
||||||
trackedTransactionOrigins.add(this)
|
this.deleteFilter = deleteFilter
|
||||||
this.trackedTransactionOrigins = trackedTransactionOrigins
|
trackedOrigins.add(this)
|
||||||
|
this.trackedOrigins = trackedOrigins
|
||||||
/**
|
/**
|
||||||
* @type {Array<StackItem>}
|
* @type {Array<StackItem>}
|
||||||
*/
|
*/
|
||||||
@ -129,7 +150,7 @@ export class UndoManager extends Observable {
|
|||||||
this.lastChange = 0
|
this.lastChange = 0
|
||||||
this.doc.on('afterTransaction', /** @param {Transaction} transaction */ transaction => {
|
this.doc.on('afterTransaction', /** @param {Transaction} transaction */ transaction => {
|
||||||
// Only track certain transactions
|
// Only track certain transactions
|
||||||
if (!this.scope.some(type => transaction.changedParentTypes.has(type)) || (!this.trackedTransactionOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedTransactionOrigins.has(transaction.origin.constructor)))) {
|
if (!this.scope.some(type => transaction.changedParentTypes.has(type)) || (!this.trackedOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedOrigins.has(transaction.origin.constructor)))) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const undoing = this.undoing
|
const undoing = this.undoing
|
||||||
|
@ -172,7 +172,7 @@ export const testUndoEvents = tc => {
|
|||||||
export const testTrackClass = tc => {
|
export const testTrackClass = tc => {
|
||||||
const { users, text0 } = init(tc, { users: 3 })
|
const { users, text0 } = init(tc, { users: 3 })
|
||||||
// only track origins that are numbers
|
// only track origins that are numbers
|
||||||
const undoManager = new UndoManager(text0, new Set([Number]))
|
const undoManager = new UndoManager(text0, { trackedOrigins: new Set([Number]) })
|
||||||
users[0].transact(() => {
|
users[0].transact(() => {
|
||||||
text0.insert(0, 'abc')
|
text0.insert(0, 'abc')
|
||||||
}, 42)
|
}, 42)
|
||||||
@ -201,3 +201,22 @@ export const testTypeScope = tc => {
|
|||||||
undoManagerBoth.undo()
|
undoManagerBoth.undo()
|
||||||
t.assert(text1.toString() === '')
|
t.assert(text1.toString() === '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testUndoDeleteFilter = tc => {
|
||||||
|
/**
|
||||||
|
* @type {Array<Y.Map<any>>}
|
||||||
|
*/
|
||||||
|
const array0 = /** @type {any} */ (init(tc, { users: 3 }).array0)
|
||||||
|
const undoManager = new UndoManager(array0, { deleteFilter: item => !(item instanceof Y.Item) || (item.content instanceof Y.ContentType && item.content.type._map.size === 0) })
|
||||||
|
const map0 = new Y.Map()
|
||||||
|
map0.set('hi', 1)
|
||||||
|
const map1 = new Y.Map()
|
||||||
|
array0.insert(0, [map0, map1])
|
||||||
|
undoManager.undo()
|
||||||
|
t.assert(array0.length === 1)
|
||||||
|
array0.get(0)
|
||||||
|
t.assert(Array.from(array0.get(0).keys()).length === 1)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user