Compare commits
17 Commits
v13.0.0-97
...
v13.0.0-10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4c919d9ec | ||
|
|
aeb23dbaa9 | ||
|
|
6d4f0c0cdd | ||
|
|
303138f309 | ||
|
|
ad373a3dce | ||
|
|
2150fa58f2 | ||
|
|
ece4841b5c | ||
|
|
8103220c05 | ||
|
|
66d500f08d | ||
|
|
5f8e7c7ba7 | ||
|
|
7b8eee6b25 | ||
|
|
1d5947c602 | ||
|
|
53e4028952 | ||
|
|
b38a8d99e5 | ||
|
|
6c4971ae25 | ||
|
|
d1f5ff0f59 | ||
|
|
1d297601e8 |
@@ -55,7 +55,7 @@ are implemented in separate modules.
|
|||||||
| Name | Cursors | Binding | Demo |
|
| Name | Cursors | Binding | Demo |
|
||||||
|---|:-:|---|---|
|
|---|:-:|---|---|
|
||||||
| [ProseMirror](https://prosemirror.net/) | ✔ | [y-prosemirror](http://github.com/yjs/y-prosemirror) | [demo](https://yjs-demos.now.sh/prosemirror/) |
|
| [ProseMirror](https://prosemirror.net/) | ✔ | [y-prosemirror](http://github.com/yjs/y-prosemirror) | [demo](https://yjs-demos.now.sh/prosemirror/) |
|
||||||
| [Quill](https://quilljs.com/) | | [y-quill](http://github.com/yjs/y-quill) | [demo](https://yjs-demos.now.sh/quill/) |
|
| [Quill](https://quilljs.com/) | ✔ | [y-quill](http://github.com/yjs/y-quill) | [demo](https://yjs-demos.now.sh/quill/) |
|
||||||
| [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](http://github.com/yjs/y-codemirror) | [demo](https://yjs-demos.now.sh/codemirror/) |
|
| [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](http://github.com/yjs/y-codemirror) | [demo](https://yjs-demos.now.sh/codemirror/) |
|
||||||
| [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](http://github.com/yjs/y-monaco) | [demo](https://yjs-demos.now.sh/monaco/) |
|
| [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](http://github.com/yjs/y-monaco) | [demo](https://yjs-demos.now.sh/monaco/) |
|
||||||
| [Ace](https://ace.c9.io/) | | [y-ace](http://github.com/yjs/y-ace) | [demo](https://yjs-demos.now.sh/ace/) |
|
| [Ace](https://ace.c9.io/) | | [y-ace](http://github.com/yjs/y-ace) | [demo](https://yjs-demos.now.sh/ace/) |
|
||||||
@@ -235,7 +235,8 @@ or any of its children.
|
|||||||
Copies the <code>[key,value]</code> pairs of this YMap to a new Object.It
|
Copies the <code>[key,value]</code> pairs of this YMap to a new Object.It
|
||||||
transforms all child types to JSON using their <code>toJSON</code> method.
|
transforms all child types to JSON using their <code>toJSON</code> method.
|
||||||
</dd>
|
</dd>
|
||||||
<b><code>forEach(function(key:string,value:object|boolean|Array|string|number|Uint8Array|Y.Type))</code></b>
|
<b><code>forEach(function(value:object|boolean|Array|string|number|Uint8Array|Y.Type,
|
||||||
|
key:string, map: Y.Map))</code></b>
|
||||||
<dd>
|
<dd>
|
||||||
Execute the provided function once for every key-value pair.
|
Execute the provided function once for every key-value pair.
|
||||||
</dd>
|
</dd>
|
||||||
@@ -343,7 +344,7 @@ or any of its children.
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>YXmlFragment</b></summary>
|
<summary><b>Y.XmlFragment</b></summary>
|
||||||
<br>
|
<br>
|
||||||
<p>
|
<p>
|
||||||
A container that holds an Array of Y.XmlElements.
|
A container that holds an Array of Y.XmlElements.
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.0.0-97",
|
"version": "13.0.0-101",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2596,9 +2596,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib0": {
|
"lib0": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.1.1.tgz",
|
||||||
"integrity": "sha512-pkpnv2IJEOb6iwpcJ6BVQu9GkZ9VINKeQ/0BcArHpozqaGQYWe+ychf2p9wHKToHUnivPoGZZ7rFqrxNXjqFBg=="
|
"integrity": "sha512-ghjoI4xL/xzVR1fRLYEOnJjYMguoI2dnDUf5HYOpTfD6R5GPKLml6xNKl4ZfBVmczkIOQPNthhukp6nlgbmDLw=="
|
||||||
},
|
},
|
||||||
"linkify-it": {
|
"linkify-it": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.0.0-97",
|
"version": "13.0.0-101",
|
||||||
"description": "Shared Editing Library",
|
"description": "Shared Editing Library",
|
||||||
"main": "./dist/yjs.js",
|
"main": "./dist/yjs.js",
|
||||||
"module": "./dist/yjs.mjs",
|
"module": "./dist/yjs.mjs",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://yjs.dev",
|
"homepage": "https://yjs.dev",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lib0": "^0.1.0"
|
"lib0": "^0.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^3.6.1",
|
"concurrently": "^3.6.1",
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export {
|
|||||||
getState,
|
getState,
|
||||||
Snapshot,
|
Snapshot,
|
||||||
createSnapshot,
|
createSnapshot,
|
||||||
|
createDeleteSet,
|
||||||
|
createDeleteSetFromStructStore,
|
||||||
snapshot,
|
snapshot,
|
||||||
emptySnapshot,
|
emptySnapshot,
|
||||||
findRootTypeKey,
|
findRootTypeKey,
|
||||||
@@ -51,5 +53,6 @@ export {
|
|||||||
decodeSnapshot,
|
decodeSnapshot,
|
||||||
encodeSnapshot,
|
encodeSnapshot,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
equalSnapshots
|
equalSnapshots,
|
||||||
|
PermanentUserData // @TODO experimental
|
||||||
} from './internals.js'
|
} from './internals.js'
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
|
|
||||||
export * from './utils/DeleteSet.js'
|
export * from './utils/DeleteSet.js'
|
||||||
|
export * from './utils/Doc.js'
|
||||||
|
export * from './utils/encoding.js'
|
||||||
export * from './utils/EventHandler.js'
|
export * from './utils/EventHandler.js'
|
||||||
export * from './utils/ID.js'
|
export * from './utils/ID.js'
|
||||||
export * from './utils/isParentOf.js'
|
export * from './utils/isParentOf.js'
|
||||||
|
export * from './utils/PermanentUserData.js'
|
||||||
export * from './utils/RelativePosition.js'
|
export * from './utils/RelativePosition.js'
|
||||||
export * from './utils/Snapshot.js'
|
export * from './utils/Snapshot.js'
|
||||||
export * from './utils/StructStore.js'
|
export * from './utils/StructStore.js'
|
||||||
export * from './utils/Transaction.js'
|
export * from './utils/Transaction.js'
|
||||||
export * from './utils/UndoManager.js'
|
export * from './utils/UndoManager.js'
|
||||||
export * from './utils/Doc.js'
|
|
||||||
export * from './utils/YEvent.js'
|
export * from './utils/YEvent.js'
|
||||||
|
|
||||||
export * from './types/AbstractType.js'
|
export * from './types/AbstractType.js'
|
||||||
@@ -31,5 +34,3 @@ export * from './structs/ContentAny.js'
|
|||||||
export * from './structs/ContentString.js'
|
export * from './structs/ContentString.js'
|
||||||
export * from './structs/ContentType.js'
|
export * from './structs/ContentType.js'
|
||||||
export * from './structs/Item.js'
|
export * from './structs/Item.js'
|
||||||
|
|
||||||
export * from './utils/encoding.js'
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import * as set from 'lib0/set.js'
|
|||||||
import * as binary from 'lib0/binary.js'
|
import * as binary from 'lib0/binary.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @todo This should return several items
|
||||||
|
*
|
||||||
* @param {StructStore} store
|
* @param {StructStore} store
|
||||||
* @param {ID} id
|
* @param {ID} id
|
||||||
* @return {{item:Item, diff:number}}
|
* @return {{item:Item, diff:number}}
|
||||||
@@ -135,7 +137,7 @@ export const splitItem = (transaction, leftItem, diff) => {
|
|||||||
*/
|
*/
|
||||||
export const redoItem = (transaction, item, redoitems) => {
|
export const redoItem = (transaction, item, redoitems) => {
|
||||||
if (item.redone !== null) {
|
if (item.redone !== null) {
|
||||||
return getItemCleanStart(transaction, transaction.doc.store, item.redone)
|
return getItemCleanStart(transaction, item.redone)
|
||||||
}
|
}
|
||||||
let parentItem = item.parent._item
|
let parentItem = item.parent._item
|
||||||
/**
|
/**
|
||||||
@@ -175,7 +177,7 @@ export const redoItem = (transaction, item, redoitems) => {
|
|||||||
}
|
}
|
||||||
if (parentItem !== null && parentItem.redone !== null) {
|
if (parentItem !== null && parentItem.redone !== null) {
|
||||||
while (parentItem.redone !== null) {
|
while (parentItem.redone !== null) {
|
||||||
parentItem = getItemCleanStart(transaction, transaction.doc.store, parentItem.redone)
|
parentItem = getItemCleanStart(transaction, parentItem.redone)
|
||||||
}
|
}
|
||||||
// find next cloned_redo items
|
// find next cloned_redo items
|
||||||
while (left !== null) {
|
while (left !== null) {
|
||||||
@@ -185,7 +187,7 @@ export const redoItem = (transaction, item, redoitems) => {
|
|||||||
let leftTrace = left
|
let leftTrace = left
|
||||||
// trace redone until parent matches
|
// trace redone until parent matches
|
||||||
while (leftTrace !== null && leftTrace.parent._item !== parentItem) {
|
while (leftTrace !== null && leftTrace.parent._item !== parentItem) {
|
||||||
leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, transaction.doc.store, leftTrace.redone)
|
leftTrace = leftTrace.redone === null ? null : getItemCleanStart(transaction, leftTrace.redone)
|
||||||
}
|
}
|
||||||
if (leftTrace !== null && leftTrace.parent._item === parentItem) {
|
if (leftTrace !== null && leftTrace.parent._item === parentItem) {
|
||||||
left = leftTrace
|
left = leftTrace
|
||||||
@@ -200,7 +202,7 @@ export const redoItem = (transaction, item, redoitems) => {
|
|||||||
let rightTrace = right
|
let rightTrace = right
|
||||||
// trace redone until parent matches
|
// trace redone until parent matches
|
||||||
while (rightTrace !== null && rightTrace.parent._item !== parentItem) {
|
while (rightTrace !== null && rightTrace.parent._item !== parentItem) {
|
||||||
rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, transaction.doc.store, rightTrace.redone)
|
rightTrace = rightTrace.redone === null ? null : getItemCleanStart(transaction, rightTrace.redone)
|
||||||
}
|
}
|
||||||
if (rightTrace !== null && rightTrace.parent._item === parentItem) {
|
if (rightTrace !== null && rightTrace.parent._item === parentItem) {
|
||||||
right = rightTrace
|
right = rightTrace
|
||||||
@@ -726,7 +728,7 @@ export class ItemRef extends AbstractStructRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const left = this.left === null ? null : getItemCleanEnd(transaction, store, this.left)
|
const left = this.left === null ? null : getItemCleanEnd(transaction, store, this.left)
|
||||||
const right = this.right === null ? null : getItemCleanStart(transaction, store, this.right)
|
const right = this.right === null ? null : getItemCleanStart(transaction, this.right)
|
||||||
let parent = null
|
let parent = null
|
||||||
let parentSub = this.parentSub
|
let parentSub = this.parentSub
|
||||||
if (this.parent !== null) {
|
if (this.parent !== null) {
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ export const typeListInsertGenerics = (transaction, parent, index, content) => {
|
|||||||
if (index <= n.length) {
|
if (index <= n.length) {
|
||||||
if (index < n.length) {
|
if (index < n.length) {
|
||||||
// insert in-between
|
// insert in-between
|
||||||
getItemCleanStart(transaction, transaction.doc.store, createID(n.id.client, n.id.clock + index))
|
getItemCleanStart(transaction, createID(n.id.client, n.id.clock + index))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -454,7 +454,7 @@ export const typeListDelete = (transaction, parent, index, length) => {
|
|||||||
for (; n !== null && index > 0; n = n.right) {
|
for (; n !== null && index > 0; n = n.right) {
|
||||||
if (!n.deleted && n.countable) {
|
if (!n.deleted && n.countable) {
|
||||||
if (index < n.length) {
|
if (index < n.length) {
|
||||||
getItemCleanStart(transaction, transaction.doc.store, createID(n.id.client, n.id.clock + index))
|
getItemCleanStart(transaction, createID(n.id.client, n.id.clock + index))
|
||||||
}
|
}
|
||||||
index -= n.length
|
index -= n.length
|
||||||
}
|
}
|
||||||
@@ -463,7 +463,7 @@ export const typeListDelete = (transaction, parent, index, length) => {
|
|||||||
while (length > 0 && n !== null) {
|
while (length > 0 && n !== null) {
|
||||||
if (!n.deleted) {
|
if (!n.deleted) {
|
||||||
if (length < n.length) {
|
if (length < n.length) {
|
||||||
getItemCleanStart(transaction, transaction.doc.store, createID(n.id.client, n.id.clock + length))
|
getItemCleanStart(transaction, createID(n.id.client, n.id.clock + length))
|
||||||
}
|
}
|
||||||
n.delete(transaction)
|
n.delete(transaction)
|
||||||
length -= n.length
|
length -= n.length
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
ContentFormat,
|
ContentFormat,
|
||||||
ContentString,
|
ContentString,
|
||||||
splitSnapshotAffectedStructs,
|
splitSnapshotAffectedStructs,
|
||||||
Doc, Item, Snapshot, StructStore, Transaction // eslint-disable-line
|
ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
|
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
|
||||||
@@ -68,7 +68,6 @@ export class ItemInsertionResult extends ItemListPosition {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {Map<string,any>} currentAttributes
|
* @param {Map<string,any>} currentAttributes
|
||||||
* @param {Item|null} left
|
* @param {Item|null} left
|
||||||
* @param {Item|null} right
|
* @param {Item|null} right
|
||||||
@@ -78,7 +77,7 @@ export class ItemInsertionResult extends ItemListPosition {
|
|||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
const findNextPosition = (transaction, store, currentAttributes, left, right, count) => {
|
const findNextPosition = (transaction, currentAttributes, left, right, count) => {
|
||||||
while (right !== null && count > 0) {
|
while (right !== null && count > 0) {
|
||||||
switch (right.content.constructor) {
|
switch (right.content.constructor) {
|
||||||
case ContentEmbed:
|
case ContentEmbed:
|
||||||
@@ -86,7 +85,7 @@ const findNextPosition = (transaction, store, currentAttributes, left, right, co
|
|||||||
if (!right.deleted) {
|
if (!right.deleted) {
|
||||||
if (count < right.length) {
|
if (count < right.length) {
|
||||||
// split right
|
// split right
|
||||||
getItemCleanStart(transaction, store, createID(right.id.client, right.id.clock + count))
|
getItemCleanStart(transaction, createID(right.id.client, right.id.clock + count))
|
||||||
}
|
}
|
||||||
count -= right.length
|
count -= right.length
|
||||||
}
|
}
|
||||||
@@ -105,7 +104,6 @@ const findNextPosition = (transaction, store, currentAttributes, left, right, co
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
* @param {number} index
|
* @param {number} index
|
||||||
* @return {ItemTextListPosition}
|
* @return {ItemTextListPosition}
|
||||||
@@ -113,11 +111,11 @@ const findNextPosition = (transaction, store, currentAttributes, left, right, co
|
|||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
const findPosition = (transaction, store, parent, index) => {
|
const findPosition = (transaction, parent, index) => {
|
||||||
let currentAttributes = new Map()
|
let currentAttributes = new Map()
|
||||||
let left = null
|
let left = null
|
||||||
let right = parent._start
|
let right = parent._start
|
||||||
return findNextPosition(transaction, store, currentAttributes, left, right, index)
|
return findNextPosition(transaction, currentAttributes, left, right, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -299,7 +297,7 @@ const formatText = (transaction, parent, left, right, currentAttributes, length,
|
|||||||
case ContentEmbed:
|
case ContentEmbed:
|
||||||
case ContentString:
|
case ContentString:
|
||||||
if (length < right.length) {
|
if (length < right.length) {
|
||||||
getItemCleanStart(transaction, transaction.doc.store, createID(right.id.client, right.id.clock + length))
|
getItemCleanStart(transaction, createID(right.id.client, right.id.clock + length))
|
||||||
}
|
}
|
||||||
length -= right.length
|
length -= right.length
|
||||||
break
|
break
|
||||||
@@ -343,7 +341,7 @@ const deleteText = (transaction, left, right, currentAttributes, length) => {
|
|||||||
case ContentEmbed:
|
case ContentEmbed:
|
||||||
case ContentString:
|
case ContentString:
|
||||||
if (length < right.length) {
|
if (length < right.length) {
|
||||||
getItemCleanStart(transaction, transaction.doc.store, createID(right.id.client, right.id.clock + length))
|
getItemCleanStart(transaction, createID(right.id.client, right.id.clock + length))
|
||||||
}
|
}
|
||||||
length -= right.length
|
length -= right.length
|
||||||
right.delete(transaction)
|
right.delete(transaction)
|
||||||
@@ -714,11 +712,12 @@ export class YText extends AbstractType {
|
|||||||
*
|
*
|
||||||
* @param {Snapshot} [snapshot]
|
* @param {Snapshot} [snapshot]
|
||||||
* @param {Snapshot} [prevSnapshot]
|
* @param {Snapshot} [prevSnapshot]
|
||||||
|
* @param {function('removed' | 'added', ID):any} [computeYChange]
|
||||||
* @return {any} The Delta representation of this type.
|
* @return {any} The Delta representation of this type.
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
toDelta (snapshot, prevSnapshot) {
|
toDelta (snapshot, prevSnapshot, computeYChange) {
|
||||||
/**
|
/**
|
||||||
* @type{Array<any>}
|
* @type{Array<any>}
|
||||||
*/
|
*/
|
||||||
@@ -767,12 +766,12 @@ export class YText extends AbstractType {
|
|||||||
if (snapshot !== undefined && !isVisible(n, snapshot)) {
|
if (snapshot !== undefined && !isVisible(n, snapshot)) {
|
||||||
if (cur === undefined || cur.user !== n.id.client || cur.state !== 'removed') {
|
if (cur === undefined || cur.user !== n.id.client || cur.state !== 'removed') {
|
||||||
packStr()
|
packStr()
|
||||||
currentAttributes.set('ychange', { user: n.id.client, state: 'removed' })
|
currentAttributes.set('ychange', computeYChange ? computeYChange('removed', n.id) : { type: 'removed' })
|
||||||
}
|
}
|
||||||
} else if (prevSnapshot !== undefined && !isVisible(n, prevSnapshot)) {
|
} else if (prevSnapshot !== undefined && !isVisible(n, prevSnapshot)) {
|
||||||
if (cur === undefined || cur.user !== n.id.client || cur.state !== 'added') {
|
if (cur === undefined || cur.user !== n.id.client || cur.state !== 'added') {
|
||||||
packStr()
|
packStr()
|
||||||
currentAttributes.set('ychange', { user: n.id.client, state: 'added' })
|
currentAttributes.set('ychange', computeYChange ? computeYChange('added', n.id) : { type: 'added' })
|
||||||
}
|
}
|
||||||
} else if (cur !== undefined) {
|
} else if (cur !== undefined) {
|
||||||
packStr()
|
packStr()
|
||||||
@@ -818,7 +817,7 @@ export class YText extends AbstractType {
|
|||||||
const y = this.doc
|
const y = this.doc
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
transact(y, transaction => {
|
transact(y, transaction => {
|
||||||
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
const { left, right, currentAttributes } = findPosition(transaction, this, index)
|
||||||
if (!attributes) {
|
if (!attributes) {
|
||||||
attributes = {}
|
attributes = {}
|
||||||
currentAttributes.forEach((v, k) => { attributes[k] = v })
|
currentAttributes.forEach((v, k) => { attributes[k] = v })
|
||||||
@@ -847,7 +846,7 @@ export class YText extends AbstractType {
|
|||||||
const y = this.doc
|
const y = this.doc
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
transact(y, transaction => {
|
transact(y, transaction => {
|
||||||
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
const { left, right, currentAttributes } = findPosition(transaction, this, index)
|
||||||
insertText(transaction, this, left, right, currentAttributes, embed, attributes)
|
insertText(transaction, this, left, right, currentAttributes, embed, attributes)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -870,7 +869,7 @@ export class YText extends AbstractType {
|
|||||||
const y = this.doc
|
const y = this.doc
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
transact(y, transaction => {
|
transact(y, transaction => {
|
||||||
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
const { left, right, currentAttributes } = findPosition(transaction, this, index)
|
||||||
deleteText(transaction, left, right, currentAttributes, length)
|
deleteText(transaction, left, right, currentAttributes, length)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -892,7 +891,7 @@ export class YText extends AbstractType {
|
|||||||
const y = this.doc
|
const y = this.doc
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
transact(y, transaction => {
|
transact(y, transaction => {
|
||||||
let { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
let { left, right, currentAttributes } = findPosition(transaction, this, index)
|
||||||
if (right === null) {
|
if (right === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Item, GC, StructStore, Transaction, ID // eslint-disable-line
|
Item, GC, StructStore, Transaction, ID // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
|
import * as array from 'lib0/array.js'
|
||||||
import * as math from 'lib0/math.js'
|
import * as math from 'lib0/math.js'
|
||||||
import * as map from 'lib0/map.js'
|
import * as map from 'lib0/map.js'
|
||||||
import * as encoding from 'lib0/encoding.js'
|
import * as encoding from 'lib0/encoding.js'
|
||||||
@@ -52,14 +53,13 @@ export class DeleteSet {
|
|||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {DeleteSet} ds
|
* @param {DeleteSet} ds
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {function(GC|Item):void} f
|
* @param {function(GC|Item):void} f
|
||||||
*
|
*
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const iterateDeletedStructs = (transaction, ds, store, f) =>
|
export const iterateDeletedStructs = (transaction, ds, f) =>
|
||||||
ds.clients.forEach((deletes, clientid) => {
|
ds.clients.forEach((deletes, clientid) => {
|
||||||
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(clientid))
|
const structs = /** @type {Array<GC|Item>} */ (transaction.doc.store.clients.get(clientid))
|
||||||
for (let i = 0; i < deletes.length; i++) {
|
for (let i = 0; i < deletes.length; i++) {
|
||||||
const del = deletes[i]
|
const del = deletes[i]
|
||||||
iterateStructs(transaction, structs, del.clock, del.len, f)
|
iterateStructs(transaction, structs, del.clock, del.len, f)
|
||||||
@@ -137,22 +137,27 @@ export const sortAndMergeDeleteSet = ds => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DeleteSet} ds1
|
* @param {Array<DeleteSet>} dss
|
||||||
* @param {DeleteSet} ds2
|
|
||||||
* @return {DeleteSet} A fresh DeleteSet
|
* @return {DeleteSet} A fresh DeleteSet
|
||||||
*/
|
*/
|
||||||
export const mergeDeleteSets = (ds1, ds2) => {
|
export const mergeDeleteSets = dss => {
|
||||||
const merged = new DeleteSet()
|
const merged = new DeleteSet()
|
||||||
// Write all keys from ds1 to merged. If ds2 has the same key, combine the sets.
|
for (let dssI = 0; dssI < dss.length; dssI++) {
|
||||||
ds1.clients.forEach((dels1, client) =>
|
dss[dssI].clients.forEach((delsLeft, client) => {
|
||||||
merged.clients.set(client, dels1.concat(ds2.clients.get(client) || []))
|
if (!merged.clients.has(client)) {
|
||||||
)
|
// Write all missing keys from current ds and all following.
|
||||||
// Write all missing keys from ds2 to merged.
|
// If merged already contains `client` current ds has already been added.
|
||||||
ds2.clients.forEach((dels2, client) => {
|
/**
|
||||||
if (!merged.clients.has(client)) {
|
* @type {Array<DeleteItem>}
|
||||||
merged.clients.set(client, dels2)
|
*/
|
||||||
}
|
const dels = delsLeft.slice()
|
||||||
})
|
for (let i = dssI + 1; i < dss.length; i++) {
|
||||||
|
array.appendTo(dels, dss[i].clients.get(client) || [])
|
||||||
|
}
|
||||||
|
merged.clients.set(client, dels)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
sortAndMergeDeleteSet(merged)
|
sortAndMergeDeleteSet(merged)
|
||||||
return merged
|
return merged
|
||||||
}
|
}
|
||||||
|
|||||||
134
src/utils/PermanentUserData.js
Normal file
134
src/utils/PermanentUserData.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
YArray,
|
||||||
|
YMap,
|
||||||
|
readDeleteSet,
|
||||||
|
writeDeleteSet,
|
||||||
|
createDeleteSet,
|
||||||
|
ID, DeleteSet, YArrayEvent, Transaction, Doc // eslint-disable-line
|
||||||
|
} from '../internals.js'
|
||||||
|
|
||||||
|
import * as decoding from 'lib0/decoding.js'
|
||||||
|
import * as encoding from 'lib0/encoding.js'
|
||||||
|
import { mergeDeleteSets, isDeleted } from './DeleteSet.js'
|
||||||
|
|
||||||
|
export class PermanentUserData {
|
||||||
|
/**
|
||||||
|
* @param {Doc} doc
|
||||||
|
* @param {string} key
|
||||||
|
*/
|
||||||
|
constructor (doc, key = 'users') {
|
||||||
|
const users = doc.getMap(key)
|
||||||
|
/**
|
||||||
|
* @type {Map<string,DeleteSet>}
|
||||||
|
*/
|
||||||
|
const dss = new Map()
|
||||||
|
this.yusers = users
|
||||||
|
this.doc = doc
|
||||||
|
/**
|
||||||
|
* Maps from clientid to userDescription
|
||||||
|
*
|
||||||
|
* @type {Map<number,string>}
|
||||||
|
*/
|
||||||
|
this.clients = new Map()
|
||||||
|
this.dss = dss
|
||||||
|
/**
|
||||||
|
* @param {YMap<any>} user
|
||||||
|
* @param {string} userDescription
|
||||||
|
*/
|
||||||
|
const initUser = (user, userDescription) => {
|
||||||
|
/**
|
||||||
|
* @type {YArray<Uint8Array>}
|
||||||
|
*/
|
||||||
|
const ds = user.get('ds')
|
||||||
|
const ids = user.get('ids')
|
||||||
|
const addClientId = /** @param {number} clientid */ clientid => this.clients.set(clientid, userDescription)
|
||||||
|
ds.observe(/** @param {YArrayEvent<any>} event */ event => {
|
||||||
|
event.changes.added.forEach(item => {
|
||||||
|
item.content.getContent().forEach(encodedDs => {
|
||||||
|
if (encodedDs instanceof Uint8Array) {
|
||||||
|
this.dss.set(userDescription, mergeDeleteSets([this.dss.get(userDescription) || createDeleteSet(), readDeleteSet(decoding.createDecoder(encodedDs))]))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.dss.set(userDescription, mergeDeleteSets(ds.map(encodedDs => readDeleteSet(decoding.createDecoder(encodedDs)))))
|
||||||
|
ids.observe(/** @param {YArrayEvent<any>} event */ event =>
|
||||||
|
event.changes.added.forEach(item => item.content.getContent().forEach(addClientId))
|
||||||
|
)
|
||||||
|
ids.forEach(addClientId)
|
||||||
|
}
|
||||||
|
// observe users
|
||||||
|
users.observe(event => {
|
||||||
|
event.keysChanged.forEach(userDescription =>
|
||||||
|
initUser(users.get(userDescription), userDescription)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// add intial data
|
||||||
|
users.forEach(initUser)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {Doc} doc
|
||||||
|
* @param {number} clientid
|
||||||
|
* @param {string} userDescription
|
||||||
|
*/
|
||||||
|
setUserMapping (doc, clientid, userDescription) {
|
||||||
|
const users = this.yusers
|
||||||
|
let user = users.get(userDescription)
|
||||||
|
if (!user) {
|
||||||
|
user = new YMap()
|
||||||
|
user.set('ids', new YArray())
|
||||||
|
user.set('ds', new YArray())
|
||||||
|
users.set(userDescription, user)
|
||||||
|
}
|
||||||
|
user.get('ids').push([clientid])
|
||||||
|
users.observe(event => {
|
||||||
|
const userOverwrite = users.get(userDescription)
|
||||||
|
if (userOverwrite !== user) {
|
||||||
|
// user was overwritten, port all data over to the next user object
|
||||||
|
// @todo Experiment with Y.Sets here
|
||||||
|
user = userOverwrite
|
||||||
|
// @todo iterate over old type
|
||||||
|
this.clients.forEach((_userDescription, clientid) => {
|
||||||
|
if (userDescription === _userDescription) {
|
||||||
|
user.get('ids').push([clientid])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const encoder = encoding.createEncoder()
|
||||||
|
const ds = this.dss.get(userDescription)
|
||||||
|
if (ds) {
|
||||||
|
writeDeleteSet(encoder, ds)
|
||||||
|
user.get('ds').push([encoding.toUint8Array(encoder)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
doc.on('afterTransaction', /** @param {Transaction} transaction */ transaction => {
|
||||||
|
const yds = user.get('ds')
|
||||||
|
const ds = transaction.deleteSet
|
||||||
|
if (transaction.local && ds.clients.size > 0) {
|
||||||
|
const encoder = encoding.createEncoder()
|
||||||
|
writeDeleteSet(encoder, ds)
|
||||||
|
yds.push([encoding.toUint8Array(encoder)])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {number} clientid
|
||||||
|
* @return {any}
|
||||||
|
*/
|
||||||
|
getUserByClientId (clientid) {
|
||||||
|
return this.clients.get(clientid) || null
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {ID} id
|
||||||
|
* @return {string | null}
|
||||||
|
*/
|
||||||
|
getUserByDeletedId (id) {
|
||||||
|
for (const [userDescription, ds] of this.dss) {
|
||||||
|
if (isDeleted(ds, id)) {
|
||||||
|
return userDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -228,7 +228,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
type = right.parent
|
type = right.parent
|
||||||
if (type._item !== null && !type._item.deleted) {
|
if (type._item === null || !type._item.deleted) {
|
||||||
index = right.deleted || !right.countable ? 0 : res.diff
|
index = right.deleted || !right.countable ? 0 : res.diff
|
||||||
let n = right.left
|
let n = right.left
|
||||||
while (n !== null) {
|
while (n !== null) {
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ export const splitSnapshotAffectedStructs = (transaction, snapshot) => {
|
|||||||
if (!meta.has(snapshot)) {
|
if (!meta.has(snapshot)) {
|
||||||
snapshot.sv.forEach((clock, client) => {
|
snapshot.sv.forEach((clock, client) => {
|
||||||
if (clock < getState(store, client)) {
|
if (clock < getState(store, client)) {
|
||||||
getItemCleanStart(transaction, store, createID(client, clock))
|
getItemCleanStart(transaction, createID(client, clock))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
iterateDeletedStructs(transaction, snapshot.ds, store, item => {})
|
iterateDeletedStructs(transaction, snapshot.ds, item => {})
|
||||||
meta.add(snapshot)
|
meta.add(snapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,16 +197,15 @@ export const findIndexCleanStart = (transaction, structs, clock) => {
|
|||||||
* Expects that id is actually in store. This function throws or is an infinite loop otherwise.
|
* Expects that id is actually in store. This function throws or is an infinite loop otherwise.
|
||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {StructStore} store
|
|
||||||
* @param {ID} id
|
* @param {ID} id
|
||||||
* @return {Item}
|
* @return {Item}
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const getItemCleanStart = (transaction, store, id) => {
|
export const getItemCleanStart = (transaction, id) => {
|
||||||
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(id.client))
|
const structs = /** @type {Array<Item>} */ (transaction.doc.store.clients.get(id.client))
|
||||||
return /** @type {Item} */ (structs[findIndexCleanStart(transaction, structs, id.clock)])
|
return structs[findIndexCleanStart(transaction, structs, id.clock)]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ export class Transaction {
|
|||||||
/**
|
/**
|
||||||
* @param {Doc} doc
|
* @param {Doc} doc
|
||||||
* @param {any} origin
|
* @param {any} origin
|
||||||
|
* @param {boolean} local
|
||||||
*/
|
*/
|
||||||
constructor (doc, origin) {
|
constructor (doc, origin, local) {
|
||||||
/**
|
/**
|
||||||
* The Yjs instance.
|
* The Yjs instance.
|
||||||
* @type {Doc}
|
* @type {Doc}
|
||||||
@@ -95,6 +96,11 @@ export class Transaction {
|
|||||||
* @type {Map<any,any>}
|
* @type {Map<any,any>}
|
||||||
*/
|
*/
|
||||||
this.meta = new Map()
|
this.meta = new Map()
|
||||||
|
/**
|
||||||
|
* Whether this change originates from this doc.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.local = local
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,17 +149,17 @@ export const addChangedTypeToTransaction = (transaction, type, parentSub) => {
|
|||||||
*
|
*
|
||||||
* @param {Doc} doc
|
* @param {Doc} doc
|
||||||
* @param {function(Transaction):void} f
|
* @param {function(Transaction):void} f
|
||||||
* @param {any} [origin]
|
* @param {any} [origin=true]
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const transact = (doc, f, origin = null) => {
|
export const transact = (doc, f, origin = null, local = true) => {
|
||||||
const transactionCleanups = doc._transactionCleanups
|
const transactionCleanups = doc._transactionCleanups
|
||||||
let initialCall = false
|
let initialCall = false
|
||||||
if (doc._transaction === null) {
|
if (doc._transaction === null) {
|
||||||
initialCall = true
|
initialCall = true
|
||||||
doc._transaction = new Transaction(doc, origin)
|
doc._transaction = new Transaction(doc, origin, local)
|
||||||
transactionCleanups.push(doc._transaction)
|
transactionCleanups.push(doc._transaction)
|
||||||
doc.emit('beforeTransaction', [doc._transaction, doc])
|
doc.emit('beforeTransaction', [doc._transaction, doc])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,35 +50,53 @@ 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
|
||||||
iterateDeletedStructs(transaction, stackItem.ds, store, struct => {
|
if (stackStartClock !== stackEndClock) {
|
||||||
if (struct instanceof Item && scope.some(type => isParentOf(type, struct))) {
|
// 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 => {
|
||||||
|
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(struct => {
|
||||||
performedChange = redoItem(transaction, item, itemsToRedo) !== null || performedChange
|
performedChange = redoItem(transaction, struct, 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) {
|
||||||
if (struct.redone !== null) {
|
if (struct.redone !== null) {
|
||||||
let { item, diff } = followRedone(store, struct.id)
|
let { item, diff } = followRedone(store, struct.id)
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
item = getItemCleanStart(transaction, store, 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, store, createID(item.id.client, item.id.clock + stackItem.len))
|
getItemCleanStart(transaction, createID(item.id.client, stackEndClock))
|
||||||
}
|
}
|
||||||
struct = item
|
struct = item
|
||||||
}
|
}
|
||||||
itemsToDelete.push(struct)
|
if (!struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) {
|
||||||
|
itemsToDelete.push(struct)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 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
|
||||||
@@ -111,9 +130,9 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
/**
|
/**
|
||||||
* 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
|
||||||
* metadata property on `event.stackItem.metadata` (it is a `Map` of metadata properties).
|
* metadata property on `event.stackItem.meta` (it is a `Map` of metadata properties).
|
||||||
* Fires 'stack-item-popped' event when a stack item was popped from either the
|
* Fires 'stack-item-popped' event when a stack item was popped from either the
|
||||||
* undo- or the redo-stack. You may restore the saved stack information from `event.stackItem.metadata`.
|
* undo- or the redo-stack. You may restore the saved stack information from `event.stackItem.meta`.
|
||||||
*
|
*
|
||||||
* @extends {Observable<'stack-item-added'|'stack-item-popped'>}
|
* @extends {Observable<'stack-item-added'|'stack-item-popped'>}
|
||||||
*/
|
*/
|
||||||
@@ -168,7 +187,7 @@ export class UndoManager extends Observable {
|
|||||||
if (now - this.lastChange < captureTimeout && stack.length > 0 && !undoing && !redoing) {
|
if (now - this.lastChange < captureTimeout && stack.length > 0 && !undoing && !redoing) {
|
||||||
// append change to last stack op
|
// append change to last stack op
|
||||||
const lastOp = stack[stack.length - 1]
|
const lastOp = stack[stack.length - 1]
|
||||||
lastOp.ds = mergeDeleteSets(lastOp.ds, transaction.deleteSet)
|
lastOp.ds = mergeDeleteSets([lastOp.ds, transaction.deleteSet])
|
||||||
lastOp.len = afterState - lastOp.start
|
lastOp.len = afterState - lastOp.start
|
||||||
} else {
|
} else {
|
||||||
// create a new stack op
|
// create a new stack op
|
||||||
@@ -178,7 +197,7 @@ export class UndoManager extends Observable {
|
|||||||
this.lastChange = now
|
this.lastChange = now
|
||||||
}
|
}
|
||||||
// make sure that deleted structs are not gc'd
|
// make sure that deleted structs are not gc'd
|
||||||
iterateDeletedStructs(transaction, transaction.deleteSet, transaction.doc.store, /** @param {Item|GC} item */ item => {
|
iterateDeletedStructs(transaction, transaction.deleteSet, /** @param {Item|GC} item */ item => {
|
||||||
if (item instanceof Item && this.scope.some(type => isParentOf(type, item))) {
|
if (item instanceof Item && this.scope.some(type => isParentOf(type, item))) {
|
||||||
keepItem(item)
|
keepItem(item)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ export class YEvent {
|
|||||||
/**
|
/**
|
||||||
* Check if a struct is deleted by this event.
|
* Check if a struct is deleted by this event.
|
||||||
*
|
*
|
||||||
|
* In contrast to change.deleted, this method also returns true if the struct was added and then deleted.
|
||||||
|
*
|
||||||
* @param {AbstractStruct} struct
|
* @param {AbstractStruct} struct
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
@@ -66,6 +68,8 @@ export class YEvent {
|
|||||||
/**
|
/**
|
||||||
* Check if a struct is added by this event.
|
* Check if a struct is added by this event.
|
||||||
*
|
*
|
||||||
|
* In contrast to change.deleted, this method also returns true if the struct was added and then deleted.
|
||||||
|
*
|
||||||
* @param {AbstractStruct} struct
|
* @param {AbstractStruct} struct
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
@@ -106,7 +110,7 @@ export class YEvent {
|
|||||||
}
|
}
|
||||||
for (let item = target._start; item !== null; item = item.right) {
|
for (let item = target._start; item !== null; item = item.right) {
|
||||||
if (item.deleted) {
|
if (item.deleted) {
|
||||||
if (this.deletes(item)) {
|
if (this.deletes(item) && !this.adds(item)) {
|
||||||
if (lastOp === null || lastOp.delete === undefined) {
|
if (lastOp === null || lastOp.delete === undefined) {
|
||||||
packOp()
|
packOp()
|
||||||
lastOp = { delete: 0 }
|
lastOp = { delete: 0 }
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
readAndApplyDeleteSet,
|
readAndApplyDeleteSet,
|
||||||
writeDeleteSet,
|
writeDeleteSet,
|
||||||
createDeleteSetFromStructStore,
|
createDeleteSetFromStructStore,
|
||||||
|
transact,
|
||||||
Doc, Transaction, AbstractStruct, StructStore, ID // eslint-disable-line
|
Doc, Transaction, AbstractStruct, StructStore, ID // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@@ -299,10 +300,10 @@ export const readStructs = (decoder, transaction, store) => {
|
|||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const readUpdate = (decoder, ydoc, transactionOrigin) =>
|
export const readUpdate = (decoder, ydoc, transactionOrigin) =>
|
||||||
ydoc.transact(transaction => {
|
transact(ydoc, transaction => {
|
||||||
readStructs(decoder, transaction, ydoc.store)
|
readStructs(decoder, transaction, ydoc.store)
|
||||||
readAndApplyDeleteSet(decoder, transaction, ydoc.store)
|
readAndApplyDeleteSet(decoder, transaction, ydoc.store)
|
||||||
}, transactionOrigin)
|
}, transactionOrigin, false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
|
* Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
|
||||||
|
|||||||
@@ -13,6 +13,23 @@ 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)
|
||||||
|
|
||||||
|
// items that are added & deleted in the same transaction won't be undo
|
||||||
|
text0.insert(0, 'test')
|
||||||
|
text0.delete(0, 4)
|
||||||
|
undoManager.undo()
|
||||||
|
t.assert(text0.toString() === '')
|
||||||
|
|
||||||
|
// follow redone items
|
||||||
|
text0.insert(0, 'a')
|
||||||
|
undoManager.stopCapturing()
|
||||||
|
text0.delete(0, 1)
|
||||||
|
undoManager.stopCapturing()
|
||||||
|
undoManager.undo()
|
||||||
|
t.assert(text0.toString() === 'a')
|
||||||
|
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 +82,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')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint
|
|||||||
import * as Y from '../src/index.js'
|
import * as Y from '../src/index.js'
|
||||||
import * as t from 'lib0/testing.js'
|
import * as t from 'lib0/testing.js'
|
||||||
import * as prng from 'lib0/prng.js'
|
import * as prng from 'lib0/prng.js'
|
||||||
|
import * as math from 'lib0/math.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
@@ -362,12 +363,12 @@ const arrayTransactions = [
|
|||||||
var length = yarray.length
|
var length = yarray.length
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
var somePos = prng.int31(gen, 0, length - 1)
|
var somePos = prng.int31(gen, 0, length - 1)
|
||||||
var delLength = prng.int31(gen, 1, Math.min(2, length - somePos))
|
var delLength = prng.int31(gen, 1, math.min(2, length - somePos))
|
||||||
if (prng.bool(gen)) {
|
if (prng.bool(gen)) {
|
||||||
var type = yarray.get(somePos)
|
var type = yarray.get(somePos)
|
||||||
if (type.length > 0) {
|
if (type.length > 0) {
|
||||||
somePos = prng.int31(gen, 0, type.length - 1)
|
somePos = prng.int31(gen, 0, type.length - 1)
|
||||||
delLength = prng.int31(gen, 0, Math.min(2, type.length - somePos))
|
delLength = prng.int31(gen, 0, math.min(2, type.length - somePos))
|
||||||
type.delete(somePos, delLength)
|
type.delete(somePos, delLength)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export const testSnapshot = tc => {
|
|||||||
delete v.attributes.ychange.user
|
delete v.attributes.ychange.user
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.compare(state2Diff, [{insert: 'a'}, {insert: 'x', attributes: {ychange: { state: 'added' }}}, {insert: 'b', attributes: {ychange: { state: 'removed' }}}, { insert: 'cd' }])
|
t.compare(state2Diff, [{insert: 'a'}, {insert: 'x', attributes: {ychange: { type: 'added' }}}, {insert: 'b', attributes: {ychange: { type: 'removed' }}}, { insert: 'cd' }])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user