Compare commits
6 Commits
v13.0.0-92
...
v13.0.0-94
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd2b8bc6c7 | ||
|
|
463065ac21 | ||
|
|
d064e6e96e | ||
|
|
b1ed2df208 | ||
|
|
1fe4ef135c | ||
|
|
e376b5d472 |
@@ -25,10 +25,10 @@ suited for even large documents.
|
||||
* [Getting Started](#Getting-Started)
|
||||
* [API](#API)
|
||||
* [Shared Types](#Shared-Types)
|
||||
* [Y.Doc](#Y.Doc)
|
||||
* [Y.Doc](#YDoc)
|
||||
* [Document Updates](#Document-Updates)
|
||||
* [Relative Positions](#Relative-Positions)
|
||||
* [Y.UndoManager](#Y.UndoManager)
|
||||
* [Y.UndoManager](#YUndoManager)
|
||||
* [Miscellaneous](#Miscellaneous)
|
||||
* [Typescript Declarations](#Typescript-Declarations)
|
||||
* [Yjs CRDT Algorithm](#Yjs-CRDT-Algorithm)
|
||||
@@ -661,9 +661,9 @@ ytext.toString() // => 'abc'
|
||||
```
|
||||
|
||||
<dl>
|
||||
<b><code>constructor(type:Y.AbstractType,
|
||||
<b><code>constructor(scope:Y.AbstractType|Array<Y.AbstractType>,
|
||||
[trackedTransactionOrigins:Set<any>, [{captureTimeout: number}]])</code></b>
|
||||
<dd></dd>
|
||||
<dd>Accepts either single type as scope or an array of types.</dd>
|
||||
<b><code>undo()</code></b>
|
||||
<dd></dd>
|
||||
<b><code>redo()</code></b>
|
||||
|
||||
43
package-lock.json
generated
43
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yjs",
|
||||
"version": "13.0.0-92",
|
||||
"version": "13.0.0-94",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1490,8 +1490,7 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -1512,14 +1511,12 @@
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -1534,20 +1531,17 @@
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -1664,8 +1658,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -1677,7 +1670,6 @@
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -1692,7 +1684,6 @@
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
@@ -1700,14 +1691,12 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -1726,7 +1715,6 @@
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -1807,8 +1795,7 @@
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -1820,7 +1807,6 @@
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -1906,8 +1892,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -1943,7 +1928,6 @@
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -1963,7 +1947,6 @@
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -2007,14 +1990,12 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yjs",
|
||||
"version": "13.0.0-92",
|
||||
"version": "13.0.0-94",
|
||||
"description": "Shared Editing Library",
|
||||
"main": "./dist/yjs.js",
|
||||
"module": "./dist/yjs.mjs",
|
||||
|
||||
@@ -42,5 +42,6 @@ export {
|
||||
iterateDeletedStructs,
|
||||
applyUpdate,
|
||||
encodeStateAsUpdate,
|
||||
encodeStateVector
|
||||
encodeStateVector,
|
||||
UndoManager
|
||||
} from './internals.js'
|
||||
|
||||
@@ -33,6 +33,31 @@ import * as maplib from 'lib0/map.js'
|
||||
import * as set from 'lib0/set.js'
|
||||
import * as binary from 'lib0/binary.js'
|
||||
|
||||
/**
|
||||
* @param {StructStore} store
|
||||
* @param {ID} id
|
||||
* @return {{item:Item, diff:number}}
|
||||
*/
|
||||
export const followRedone = (store, id) => {
|
||||
/**
|
||||
* @type {ID|null}
|
||||
*/
|
||||
let nextID = id
|
||||
let diff = 0
|
||||
let item
|
||||
do {
|
||||
if (diff > 0) {
|
||||
nextID = createID(nextID.client, nextID.clock + diff)
|
||||
}
|
||||
item = getItem(store, nextID)
|
||||
diff = nextID.clock - item.id.clock
|
||||
nextID = item.redone
|
||||
} while (nextID !== null)
|
||||
return {
|
||||
item, diff
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that neither item nor any of its parents is ever deleted.
|
||||
*
|
||||
@@ -77,6 +102,9 @@ export const splitItem = (transaction, leftItem, diff) => {
|
||||
if (leftItem.keep) {
|
||||
rightItem.keep = true
|
||||
}
|
||||
if (leftItem.redone !== null) {
|
||||
rightItem.redone = createID(leftItem.redone.client, leftItem.redone.clock + diff)
|
||||
}
|
||||
// update left (do not set leftItem.rightOrigin as it will lead to problems when syncing)
|
||||
leftItem.right = rightItem
|
||||
// update right
|
||||
@@ -106,7 +134,7 @@ export const splitItem = (transaction, leftItem, diff) => {
|
||||
*/
|
||||
export const redoItem = (transaction, item, redoitems) => {
|
||||
if (item.redone !== null) {
|
||||
return item.redone
|
||||
return getItemCleanStart(transaction, transaction.doc.store, item.redone)
|
||||
}
|
||||
let parentItem = item.parent._item
|
||||
/**
|
||||
@@ -146,7 +174,7 @@ export const redoItem = (transaction, item, redoitems) => {
|
||||
}
|
||||
if (parentItem !== null && parentItem.redone !== null) {
|
||||
while (parentItem.redone !== null) {
|
||||
parentItem = parentItem.redone
|
||||
parentItem = getItemCleanStart(transaction, transaction.doc.store, parentItem.redone)
|
||||
}
|
||||
// find next cloned_redo items
|
||||
while (left !== null) {
|
||||
@@ -156,7 +184,7 @@ export const redoItem = (transaction, item, redoitems) => {
|
||||
let leftTrace = left
|
||||
// trace redone until parent matches
|
||||
while (leftTrace !== null && leftTrace.parent._item !== parentItem) {
|
||||
leftTrace = leftTrace.redone
|
||||
leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, transaction.doc.store, leftTrace.redone)
|
||||
}
|
||||
if (leftTrace !== null && leftTrace.parent._item === parentItem) {
|
||||
left = leftTrace
|
||||
@@ -171,7 +199,7 @@ export const redoItem = (transaction, item, redoitems) => {
|
||||
let rightTrace = right
|
||||
// trace redone until parent matches
|
||||
while (rightTrace !== null && rightTrace.parent._item !== parentItem) {
|
||||
rightTrace = rightTrace.redone
|
||||
rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, transaction.doc.store, rightTrace.redone)
|
||||
}
|
||||
if (rightTrace !== null && rightTrace.parent._item === parentItem) {
|
||||
right = rightTrace
|
||||
@@ -188,7 +216,8 @@ export const redoItem = (transaction, item, redoitems) => {
|
||||
item.parentSub,
|
||||
item.content.copy()
|
||||
)
|
||||
item.redone = redoneItem
|
||||
item.redone = redoneItem.id
|
||||
keepItem(redoneItem)
|
||||
redoneItem.integrate(transaction)
|
||||
return redoneItem
|
||||
}
|
||||
@@ -254,7 +283,7 @@ export class Item extends AbstractStruct {
|
||||
/**
|
||||
* If this type's effect is reundone this type refers to the type that undid
|
||||
* this operation.
|
||||
* @type {Item | null}
|
||||
* @type {ID | null}
|
||||
*/
|
||||
this.redone = null
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
import {
|
||||
getItem,
|
||||
createID,
|
||||
writeID,
|
||||
readID,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
findRootTypeKey,
|
||||
Item,
|
||||
ContentType,
|
||||
followRedone,
|
||||
ID, Doc, AbstractType // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
@@ -222,19 +222,22 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
|
||||
if (getState(store, rightID.client) <= rightID.clock) {
|
||||
return null
|
||||
}
|
||||
const right = getItem(store, rightID)
|
||||
const res = followRedone(store, rightID)
|
||||
const right = res.item
|
||||
if (!(right instanceof Item)) {
|
||||
return null
|
||||
}
|
||||
index = right.deleted || !right.countable ? 0 : rightID.clock - right.id.clock
|
||||
let n = right.left
|
||||
while (n !== null) {
|
||||
if (!n.deleted && n.countable) {
|
||||
index += n.length
|
||||
}
|
||||
n = n.left
|
||||
}
|
||||
type = right.parent
|
||||
if (type._item !== null && !type._item.deleted) {
|
||||
index = right.deleted || !right.countable ? 0 : res.diff
|
||||
let n = right.left
|
||||
while (n !== null) {
|
||||
if (!n.deleted && n.countable) {
|
||||
index += n.length
|
||||
}
|
||||
n = n.left
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (tname !== null) {
|
||||
type = doc.get(tname)
|
||||
@@ -243,9 +246,9 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
|
||||
// type does not exist yet
|
||||
return null
|
||||
}
|
||||
const struct = getItem(store, typeID)
|
||||
if (struct instanceof Item && struct.content instanceof ContentType) {
|
||||
type = struct.content.type
|
||||
const { item } = followRedone(store, typeID)
|
||||
if (item instanceof Item && item.content instanceof ContentType) {
|
||||
type = item.content.type
|
||||
} else {
|
||||
// struct is garbage collected
|
||||
return null
|
||||
@@ -255,9 +258,6 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
|
||||
}
|
||||
index = type._length
|
||||
}
|
||||
if (type._item !== null && type._item.deleted) {
|
||||
return null
|
||||
}
|
||||
return createAbsolutePosition(type, index)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,14 @@ import {
|
||||
redoItem,
|
||||
iterateStructs,
|
||||
isParentOf,
|
||||
Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line
|
||||
createID,
|
||||
followRedone,
|
||||
getItemCleanStart,
|
||||
Transaction, Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as time from 'lib0/time.js'
|
||||
import { Observable } from 'lib0/observable'
|
||||
import { Observable } from 'lib0/observable.js'
|
||||
|
||||
class StackItem {
|
||||
/**
|
||||
@@ -42,7 +45,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
*/
|
||||
let result = null
|
||||
const doc = undoManager.doc
|
||||
const type = undoManager.type
|
||||
const scope = undoManager.scope
|
||||
transact(doc, transaction => {
|
||||
while (stack.length > 0 && result === null) {
|
||||
const store = doc.store
|
||||
@@ -50,7 +53,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
const itemsToRedo = new Set()
|
||||
let performedChange = false
|
||||
iterateDeletedStructs(transaction, stackItem.ds, store, struct => {
|
||||
if (struct instanceof Item && isParentOf(type, struct)) {
|
||||
if (struct instanceof Item && scope.some(type => isParentOf(type, struct))) {
|
||||
itemsToRedo.add(struct)
|
||||
}
|
||||
})
|
||||
@@ -59,17 +62,28 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
})
|
||||
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(doc.clientID))
|
||||
iterateStructs(transaction, structs, stackItem.start, stackItem.len, struct => {
|
||||
if (!struct.deleted && isParentOf(type, /** @type {Item} */ (struct))) {
|
||||
if (struct instanceof Item && !struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) {
|
||||
if (struct.redone !== null) {
|
||||
let { item, diff } = followRedone(store, struct.id)
|
||||
if (diff > 0) {
|
||||
item = getItemCleanStart(transaction, store, createID(item.id.client, item.id.clock + diff))
|
||||
}
|
||||
if (item.length > stackItem.len) {
|
||||
getItemCleanStart(transaction, store, createID(item.id.client, item.id.clock + stackItem.len))
|
||||
}
|
||||
struct = item
|
||||
}
|
||||
keepItem(struct)
|
||||
struct.delete(transaction)
|
||||
performedChange = true
|
||||
}
|
||||
})
|
||||
result = stackItem
|
||||
if (result != null) {
|
||||
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
|
||||
}
|
||||
}
|
||||
}, undoManager)
|
||||
if (result != null) {
|
||||
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -84,13 +98,13 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
*/
|
||||
export class UndoManager extends Observable {
|
||||
/**
|
||||
* @param {AbstractType<any>} type
|
||||
* @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 {object} [options={captureTimeout=500}]
|
||||
*/
|
||||
constructor (type, trackedTransactionOrigins = new Set([null]), { captureTimeout = 500 } = {}) {
|
||||
constructor (typeScope, trackedTransactionOrigins = new Set([null]), { captureTimeout = 500 } = {}) {
|
||||
super()
|
||||
this.type = type
|
||||
this.scope = typeScope instanceof Array ? typeScope : [typeScope]
|
||||
trackedTransactionOrigins.add(this)
|
||||
this.trackedTransactionOrigins = trackedTransactionOrigins
|
||||
/**
|
||||
@@ -108,11 +122,11 @@ export class UndoManager extends Observable {
|
||||
*/
|
||||
this.undoing = false
|
||||
this.redoing = false
|
||||
this.doc = /** @type {Doc} */ (type.doc)
|
||||
this.doc = /** @type {Doc} */ (this.scope[0].doc)
|
||||
this.lastChange = 0
|
||||
type.observeDeep((events, transaction) => {
|
||||
this.doc.on('afterTransaction', /** @param {Transaction} transaction */ transaction => {
|
||||
// Only track certain transactions
|
||||
if (!this.trackedTransactionOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedTransactionOrigins.has(transaction.origin.constructor))) {
|
||||
if (!this.scope.some(type => transaction.changedParentTypes.has(type)) || (!this.trackedTransactionOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedTransactionOrigins.has(transaction.origin.constructor)))) {
|
||||
return
|
||||
}
|
||||
const undoing = this.undoing
|
||||
@@ -141,7 +155,7 @@ export class UndoManager extends Observable {
|
||||
}
|
||||
// make sure that deleted structs are not gc'd
|
||||
iterateDeletedStructs(transaction, transaction.deleteSet, transaction.doc.store, /** @param {Item|GC} item */ item => {
|
||||
if (item instanceof Item && isParentOf(type, item)) {
|
||||
if (item instanceof Item && this.scope.some(type => isParentOf(type, item))) {
|
||||
keepItem(item)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -180,3 +180,24 @@ export const testTrackClass = tc => {
|
||||
undoManager.undo()
|
||||
t.assert(text0.toString() === '')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
export const testTypeScope = tc => {
|
||||
const { array0 } = init(tc, { users: 3 })
|
||||
// only track origins that are numbers
|
||||
const text0 = new Y.Text()
|
||||
const text1 = new Y.Text()
|
||||
array0.insert(0, [text0, text1])
|
||||
const undoManager = new UndoManager(text0)
|
||||
const undoManagerBoth = new UndoManager([text0, text1])
|
||||
text1.insert(0, 'abc')
|
||||
t.assert(undoManager.undoStack.length === 0)
|
||||
t.assert(undoManagerBoth.undoStack.length === 1)
|
||||
t.assert(text1.toString() === 'abc')
|
||||
undoManager.undo()
|
||||
t.assert(text1.toString() === 'abc')
|
||||
undoManagerBoth.undo()
|
||||
t.assert(text1.toString() === '')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user