Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4e48d1ddf | ||
|
|
0a39a92b33 | ||
|
|
bd819243eb | ||
|
|
2ec19defcb | ||
|
|
336f7b1b1d | ||
|
|
8abf5b85ff | ||
|
|
320e8cbe18 | ||
|
|
49150f4adb | ||
|
|
e22fed7af3 | ||
|
|
c91945228f | ||
|
|
3586d91925 | ||
|
|
f915ebda1b | ||
|
|
a9b92b9099 | ||
|
|
cbddf6ef90 | ||
|
|
491cd422c4 | ||
|
|
4b88e2aac5 | ||
|
|
e33c67fc72 | ||
|
|
085dda4cbd | ||
|
|
f382846874 | ||
|
|
9afc5cf615 | ||
|
|
ca0fb4b15d | ||
|
|
d369a771a9 | ||
|
|
995fbfa4cc | ||
|
|
7486ea7148 | ||
|
|
2c80a955da | ||
|
|
233872493b | ||
|
|
64d164a904 | ||
|
|
b4b8927550 | ||
|
|
b2761b50f2 | ||
|
|
28a9ce962d | ||
|
|
0ec67170d3 | ||
|
|
4922eeac56 | ||
|
|
57d6c6f831 |
@@ -168,7 +168,7 @@ An implementation of the syncing process is in
|
|||||||
## Snapshots
|
## Snapshots
|
||||||
|
|
||||||
A snapshot can be used to restore an old document state. It is a `state vector`
|
A snapshot can be used to restore an old document state. It is a `state vector`
|
||||||
+ `delete set`. I client can restore an old document state by iterating through
|
\+ `delete set`. I client can restore an old document state by iterating through
|
||||||
the sequence CRDT and ignoring all Items that have an `id.clock >
|
the sequence CRDT and ignoring all Items that have an `id.clock >
|
||||||
stateVector[id.client].clock`. Instead of using `item.deleted` the client will
|
stateVector[id.client].clock`. Instead of using `item.deleted` the client will
|
||||||
use the delete set to find out if an item was deleted or not.
|
use the delete set to find out if an item was deleted or not.
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -250,36 +250,36 @@ necessary.
|
|||||||
<dl>
|
<dl>
|
||||||
<b><code>parent:Y.AbstractType|null</code></b>
|
<b><code>parent:Y.AbstractType|null</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>insert(index:number, content:Array<object|boolean|Array|string|number|Uint8Array|Y.Type>)</code></b>
|
<b><code>insert(index:number, content:Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>)</code></b>
|
||||||
<dd>
|
<dd>
|
||||||
Insert content at <var>index</var>. Note that content is an array of elements.
|
Insert content at <var>index</var>. Note that content is an array of elements.
|
||||||
I.e. <code>array.insert(0, [1])</code> splices the list and inserts 1 at
|
I.e. <code>array.insert(0, [1])</code> splices the list and inserts 1 at
|
||||||
position 0.
|
position 0.
|
||||||
</dd>
|
</dd>
|
||||||
<b><code>push(Array<Object|boolean|Array|string|number|Uint8Array|Y.Type>)</code></b>
|
<b><code>push(Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>)</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>unshift(Array<Object|boolean|Array|string|number|Uint8Array|Y.Type>)</code></b>
|
<b><code>unshift(Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>)</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>delete(index:number, length:number)</code></b>
|
<b><code>delete(index:number, length:number)</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>get(index:number)</code></b>
|
<b><code>get(index:number)</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>slice(start:number, end:number):Array<Object|boolean|Array|string|number|Uint8Array|Y.Type></code></b>
|
<b><code>slice(start:number, end:number):Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type></code></b>
|
||||||
<dd>Retrieve a range of content</dd>
|
<dd>Retrieve a range of content</dd>
|
||||||
<b><code>length:number</code></b>
|
<b><code>length:number</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b>
|
<b>
|
||||||
<code>
|
<code>
|
||||||
forEach(function(value:object|boolean|Array|string|number|Uint8Array|Y.Type,
|
forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type,
|
||||||
index:number, array: Y.Array))
|
index:number, array: Y.Array))
|
||||||
</code>
|
</code>
|
||||||
</b>
|
</b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>map(function(T, number, YArray):M):Array<M></code></b>
|
<b><code>map(function(T, number, YArray):M):Array<M></code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>toArray():Array<object|boolean|Array|string|number|Uint8Array|Y.Type></code></b>
|
<b><code>toArray():Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type></code></b>
|
||||||
<dd>Copies the content of this YArray to a new Array.</dd>
|
<dd>Copies the content of this YArray to a new Array.</dd>
|
||||||
<b><code>toJSON():Array<Object|boolean|Array|string|number></code></b>
|
<b><code>toJSON():Array<Object|boolean|Array|string|number|null></code></b>
|
||||||
<dd>
|
<dd>
|
||||||
Copies the content of this YArray to a new Array. It transforms all child types
|
Copies the content of this YArray to a new Array. It transforms all child types
|
||||||
to JSON using their <code>toJSON</code> method.
|
to JSON using their <code>toJSON</code> method.
|
||||||
@@ -325,9 +325,9 @@ or any of its children.
|
|||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>size: number</code></b>
|
<b><code>size: number</code></b>
|
||||||
<dd>Total number of key/value pairs.</dd>
|
<dd>Total number of key/value pairs.</dd>
|
||||||
<b><code>get(key:string):object|boolean|string|number|Uint8Array|Y.Type</code></b>
|
<b><code>get(key:string):object|boolean|string|number|null|Uint8Array|Y.Type</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>set(key:string, value:object|boolean|string|number|Uint8Array|Y.Type)</code></b>
|
<b><code>set(key:string, value:object|boolean|string|number|null|Uint8Array|Y.Type)</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>delete(key:string)</code></b>
|
<b><code>delete(key:string)</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
@@ -339,12 +339,12 @@ or any of its children.
|
|||||||
<dd>Removes all elements from this YMap.</dd>
|
<dd>Removes all elements from this YMap.</dd>
|
||||||
<b><code>clone():Y.Map</code></b>
|
<b><code>clone():Y.Map</code></b>
|
||||||
<dd>Clone this type into a fresh Yjs type.</dd>
|
<dd>Clone this type into a fresh Yjs type.</dd>
|
||||||
<b><code>toJSON():Object<string, Object|boolean|Array|string|number|Uint8Array></code></b>
|
<b><code>toJSON():Object<string, Object|boolean|Array|string|number|null|Uint8Array></code></b>
|
||||||
<dd>
|
<dd>
|
||||||
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(value:object|boolean|Array|string|number|Uint8Array|Y.Type,
|
<b><code>forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type,
|
||||||
key:string, map: Y.Map))</code></b>
|
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.
|
||||||
|
|||||||
1776
package-lock.json
generated
1776
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.5.13",
|
"version": "13.5.20",
|
||||||
"description": "Shared Editing Library",
|
"description": "Shared Editing Library",
|
||||||
"main": "./dist/yjs.cjs",
|
"main": "./dist/yjs.cjs",
|
||||||
"module": "./dist/yjs.mjs",
|
"module": "./dist/yjs.mjs",
|
||||||
"types": "./dist/src/index.d.ts",
|
"types": "./dist/src/index.d.ts",
|
||||||
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "GitHub Sponsors ❤",
|
"type": "GitHub Sponsors ❤",
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
},
|
},
|
||||||
"./src/index.js": "./src/index.js",
|
"./src/index.js": "./src/index.js",
|
||||||
"./tests/testHelper.js": "./tests/testHelper.js",
|
"./tests/testHelper.js": "./tests/testHelper.js",
|
||||||
|
"./testHelper": "./dist/testHelper.mjs",
|
||||||
"./package.json": "./package.json"
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
"dist/src",
|
"dist/src",
|
||||||
"src",
|
"src",
|
||||||
"tests/testHelper.js",
|
"tests/testHelper.js",
|
||||||
|
"dist/testHelper.mjs",
|
||||||
"sponsor-y.js"
|
"sponsor-y.js"
|
||||||
],
|
],
|
||||||
"dictionaries": {
|
"dictionaries": {
|
||||||
@@ -71,19 +74,19 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://docs.yjs.dev",
|
"homepage": "https://docs.yjs.dev",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lib0": "^0.2.41"
|
"lib0": "^0.2.42"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^17.0.0",
|
"@rollup/plugin-commonjs": "^17.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^11.2.1",
|
"@rollup/plugin-node-resolve": "^11.2.1",
|
||||||
"concurrently": "^3.6.1",
|
"concurrently": "^3.6.1",
|
||||||
"http-server": "^0.12.3",
|
"http-server": "^0.12.3",
|
||||||
"jsdoc": "^3.6.6",
|
"jsdoc": "^3.6.7",
|
||||||
"markdownlint-cli": "^0.23.2",
|
"markdownlint-cli": "^0.23.2",
|
||||||
"rollup": "^2.47.0",
|
"rollup": "^2.58.0",
|
||||||
"standard": "^14.3.4",
|
"standard": "^16.0.4",
|
||||||
"tui-jsdoc-template": "^1.2.2",
|
"tui-jsdoc-template": "^1.2.2",
|
||||||
"typescript": "^4.2.4",
|
"typescript": "^4.4.4",
|
||||||
"y-protocols": "^1.0.4"
|
"y-protocols": "^1.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,23 @@ export default [{
|
|||||||
sourcemap: true
|
sourcemap: true
|
||||||
},
|
},
|
||||||
external: id => /^lib0\//.test(id)
|
external: id => /^lib0\//.test(id)
|
||||||
|
}, {
|
||||||
|
input: './tests/testHelper.js',
|
||||||
|
output: {
|
||||||
|
name: 'Y',
|
||||||
|
file: 'dist/testHelper.mjs',
|
||||||
|
format: 'esm',
|
||||||
|
sourcemap: true
|
||||||
|
},
|
||||||
|
external: id => /^lib0\//.test(id) || id === 'yjs',
|
||||||
|
plugins: [{
|
||||||
|
resolveId (importee) {
|
||||||
|
if (importee === '../src/index.js') {
|
||||||
|
return 'yjs'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}]
|
||||||
}, {
|
}, {
|
||||||
input: './tests/index.js',
|
input: './tests/index.js',
|
||||||
output: {
|
output: {
|
||||||
|
|||||||
27
src/index.js
27
src/index.js
@@ -1,3 +1,4 @@
|
|||||||
|
/** eslint-env browser */
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Doc,
|
Doc,
|
||||||
@@ -26,12 +27,13 @@ export {
|
|||||||
ContentString,
|
ContentString,
|
||||||
ContentType,
|
ContentType,
|
||||||
AbstractType,
|
AbstractType,
|
||||||
RelativePosition,
|
|
||||||
getTypeChildren,
|
getTypeChildren,
|
||||||
createRelativePositionFromTypeIndex,
|
createRelativePositionFromTypeIndex,
|
||||||
createRelativePositionFromJSON,
|
createRelativePositionFromJSON,
|
||||||
createAbsolutePositionFromRelativePosition,
|
createAbsolutePositionFromRelativePosition,
|
||||||
compareRelativePositions,
|
compareRelativePositions,
|
||||||
|
AbsolutePosition,
|
||||||
|
RelativePosition,
|
||||||
ID,
|
ID,
|
||||||
createID,
|
createID,
|
||||||
compareIDs,
|
compareIDs,
|
||||||
@@ -40,6 +42,7 @@ export {
|
|||||||
createSnapshot,
|
createSnapshot,
|
||||||
createDeleteSet,
|
createDeleteSet,
|
||||||
createDeleteSetFromStructStore,
|
createDeleteSetFromStructStore,
|
||||||
|
cleanupYTextFormatting,
|
||||||
snapshot,
|
snapshot,
|
||||||
emptySnapshot,
|
emptySnapshot,
|
||||||
findRootTypeKey,
|
findRootTypeKey,
|
||||||
@@ -84,3 +87,25 @@ export {
|
|||||||
diffUpdate,
|
diffUpdate,
|
||||||
diffUpdateV2
|
diffUpdateV2
|
||||||
} from './internals.js'
|
} from './internals.js'
|
||||||
|
|
||||||
|
const glo = /** @type {any} */ (typeof window !== 'undefined'
|
||||||
|
? window
|
||||||
|
: typeof global !== 'undefined' ? global : {})
|
||||||
|
const importIdentifier = '__ $YJS$ __'
|
||||||
|
|
||||||
|
if (glo[importIdentifier] === true) {
|
||||||
|
/**
|
||||||
|
* Dear reader of this warning message. Please take this seriously.
|
||||||
|
*
|
||||||
|
* If you see this message, please make sure that you only import one version of Yjs. In many cases,
|
||||||
|
* your package manager installs two versions of Yjs that are used by different packages within your project.
|
||||||
|
* Another reason for this message is that some parts of your project use the commonjs version of Yjs
|
||||||
|
* and others use the EcmaScript version of Yjs.
|
||||||
|
*
|
||||||
|
* This often leads to issues that are hard to debug. We often need to perform constructor checks,
|
||||||
|
* e.g. `struct instanceof GC`. If you imported different versions of Yjs, it is impossible for us to
|
||||||
|
* do the constructor checks anymore - which might break the CRDT algorithm.
|
||||||
|
*/
|
||||||
|
console.warn('Yjs was already imported. Importing different versions of Yjs often leads to issues.')
|
||||||
|
}
|
||||||
|
glo[importIdentifier] = true
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -623,7 +623,7 @@ export const typeListGet = (type, index) => {
|
|||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
* @param {Item?} referenceItem
|
* @param {Item?} referenceItem
|
||||||
* @param {Array<Object<string,any>|Array<any>|boolean|number|string|Uint8Array>} content
|
* @param {Array<Object<string,any>|Array<any>|boolean|number|null|string|Uint8Array>} content
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
@@ -635,7 +635,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
|
|||||||
const store = doc.store
|
const store = doc.store
|
||||||
const right = referenceItem === null ? parent._start : referenceItem.right
|
const right = referenceItem === null ? parent._start : referenceItem.right
|
||||||
/**
|
/**
|
||||||
* @type {Array<Object|Array<any>|number>}
|
* @type {Array<Object|Array<any>|number|null>}
|
||||||
*/
|
*/
|
||||||
let jsonContent = []
|
let jsonContent = []
|
||||||
const packJsonContent = () => {
|
const packJsonContent = () => {
|
||||||
@@ -646,34 +646,38 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
content.forEach(c => {
|
content.forEach(c => {
|
||||||
switch (c.constructor) {
|
if (c === null) {
|
||||||
case Number:
|
jsonContent.push(c)
|
||||||
case Object:
|
} else {
|
||||||
case Boolean:
|
switch (c.constructor) {
|
||||||
case Array:
|
case Number:
|
||||||
case String:
|
case Object:
|
||||||
jsonContent.push(c)
|
case Boolean:
|
||||||
break
|
case Array:
|
||||||
default:
|
case String:
|
||||||
packJsonContent()
|
jsonContent.push(c)
|
||||||
switch (c.constructor) {
|
break
|
||||||
case Uint8Array:
|
default:
|
||||||
case ArrayBuffer:
|
packJsonContent()
|
||||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
|
switch (c.constructor) {
|
||||||
left.integrate(transaction, 0)
|
case Uint8Array:
|
||||||
break
|
case ArrayBuffer:
|
||||||
case Doc:
|
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentBinary(new Uint8Array(/** @type {Uint8Array} */ (c))))
|
||||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
|
|
||||||
left.integrate(transaction, 0)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
if (c instanceof AbstractType) {
|
|
||||||
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
|
|
||||||
left.integrate(transaction, 0)
|
left.integrate(transaction, 0)
|
||||||
} else {
|
break
|
||||||
throw new Error('Unexpected content type in insert operation')
|
case Doc:
|
||||||
}
|
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
|
||||||
}
|
left.integrate(transaction, 0)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
if (c instanceof AbstractType) {
|
||||||
|
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
|
||||||
|
left.integrate(transaction, 0)
|
||||||
|
} else {
|
||||||
|
throw new Error('Unexpected content type in insert operation')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
packJsonContent()
|
packJsonContent()
|
||||||
@@ -685,7 +689,7 @@ const lengthExceeded = error.create('Length exceeded!')
|
|||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
* @param {number} index
|
* @param {number} index
|
||||||
* @param {Array<Object<string,any>|Array<any>|number|string|Uint8Array>} content
|
* @param {Array<Object<string,any>|Array<any>|number|null|string|Uint8Array>} content
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
@@ -797,7 +801,7 @@ export const typeMapDelete = (transaction, parent, key) => {
|
|||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {Object|number|Array<any>|string|Uint8Array|AbstractType<any>} value
|
* @param {Object|number|null|Array<any>|string|Uint8Array|AbstractType<any>} value
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
@@ -838,7 +842,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
|
|||||||
/**
|
/**
|
||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @return {Object<string,any>|number|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
|
* @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
@@ -850,7 +854,7 @@ export const typeMapGet = (parent, key) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
* @return {Object<string,Object<string,any>|number|Array<any>|string|Uint8Array|AbstractType<any>|undefined>}
|
* @return {Object<string,Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined>}
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
@@ -885,7 +889,7 @@ export const typeMapHas = (parent, key) => {
|
|||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {Snapshot} snapshot
|
* @param {Snapshot} snapshot
|
||||||
* @return {Object<string,any>|number|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
|
* @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|AbstractType<any>|undefined}
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @function
|
* @function
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export class YArray extends AbstractType {
|
|||||||
* Returns an Array with the result of calling a provided function on every
|
* Returns an Array with the result of calling a provided function on every
|
||||||
* element of this YArray.
|
* element of this YArray.
|
||||||
*
|
*
|
||||||
* @template T,M
|
* @template M
|
||||||
* @param {function(T,number,YArray<T>):M} f Function that produces an element of the new Array
|
* @param {function(T,number,YArray<T>):M} f Function that produces an element of the new Array
|
||||||
* @return {Array<M>} A new array with each element being the result of the
|
* @return {Array<M>} A new array with each element being the result of the
|
||||||
* callback function
|
* callback function
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
typeMapGet,
|
typeMapGet,
|
||||||
typeMapGetAll,
|
typeMapGetAll,
|
||||||
updateMarkerChanges,
|
updateMarkerChanges,
|
||||||
|
ContentType,
|
||||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
|
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@@ -62,17 +63,16 @@ export class ItemTextListPosition {
|
|||||||
error.unexpectedCase()
|
error.unexpectedCase()
|
||||||
}
|
}
|
||||||
switch (this.right.content.constructor) {
|
switch (this.right.content.constructor) {
|
||||||
case ContentEmbed:
|
|
||||||
case ContentString:
|
|
||||||
if (!this.right.deleted) {
|
|
||||||
this.index += this.right.length
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case ContentFormat:
|
case ContentFormat:
|
||||||
if (!this.right.deleted) {
|
if (!this.right.deleted) {
|
||||||
updateCurrentAttributes(this.currentAttributes, /** @type {ContentFormat} */ (this.right.content))
|
updateCurrentAttributes(this.currentAttributes, /** @type {ContentFormat} */ (this.right.content))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
default:
|
||||||
|
if (!this.right.deleted) {
|
||||||
|
this.index += this.right.length
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
this.left = this.right
|
this.left = this.right
|
||||||
this.right = this.right.right
|
this.right = this.right.right
|
||||||
@@ -91,8 +91,12 @@ export class ItemTextListPosition {
|
|||||||
const findNextPosition = (transaction, pos, count) => {
|
const findNextPosition = (transaction, pos, count) => {
|
||||||
while (pos.right !== null && count > 0) {
|
while (pos.right !== null && count > 0) {
|
||||||
switch (pos.right.content.constructor) {
|
switch (pos.right.content.constructor) {
|
||||||
case ContentEmbed:
|
case ContentFormat:
|
||||||
case ContentString:
|
if (!pos.right.deleted) {
|
||||||
|
updateCurrentAttributes(pos.currentAttributes, /** @type {ContentFormat} */ (pos.right.content))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
if (!pos.right.deleted) {
|
if (!pos.right.deleted) {
|
||||||
if (count < pos.right.length) {
|
if (count < pos.right.length) {
|
||||||
// split right
|
// split right
|
||||||
@@ -102,11 +106,6 @@ const findNextPosition = (transaction, pos, count) => {
|
|||||||
count -= pos.right.length
|
count -= pos.right.length
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case ContentFormat:
|
|
||||||
if (!pos.right.deleted) {
|
|
||||||
updateCurrentAttributes(pos.currentAttributes, /** @type {ContentFormat} */ (pos.right.content))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
pos.left = pos.right
|
pos.left = pos.right
|
||||||
pos.right = pos.right.right
|
pos.right = pos.right.right
|
||||||
@@ -245,7 +244,7 @@ const insertAttributes = (transaction, parent, currPos, attributes) => {
|
|||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {AbstractType<any>} parent
|
* @param {AbstractType<any>} parent
|
||||||
* @param {ItemTextListPosition} currPos
|
* @param {ItemTextListPosition} currPos
|
||||||
* @param {string|object} text
|
* @param {string|object|AbstractType<any>} text
|
||||||
* @param {Object<string,any>} attributes
|
* @param {Object<string,any>} attributes
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
@@ -262,7 +261,7 @@ const insertText = (transaction, parent, currPos, text, attributes) => {
|
|||||||
minimizeAttributeChanges(currPos, attributes)
|
minimizeAttributeChanges(currPos, attributes)
|
||||||
const negatedAttributes = insertAttributes(transaction, parent, currPos, attributes)
|
const negatedAttributes = insertAttributes(transaction, parent, currPos, attributes)
|
||||||
// insert content
|
// insert content
|
||||||
const content = text.constructor === String ? new ContentString(/** @type {string} */ (text)) : new ContentEmbed(text)
|
const content = text.constructor === String ? new ContentString(/** @type {string} */ (text)) : (text instanceof AbstractType ? new ContentType(text) : new ContentEmbed(text))
|
||||||
let { left, right, index } = currPos
|
let { left, right, index } = currPos
|
||||||
if (parent._searchMarker) {
|
if (parent._searchMarker) {
|
||||||
updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength())
|
updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength())
|
||||||
@@ -308,8 +307,7 @@ const formatText = (transaction, parent, currPos, length, attributes) => {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case ContentEmbed:
|
default:
|
||||||
case ContentString:
|
|
||||||
if (length < currPos.right.length) {
|
if (length < currPos.right.length) {
|
||||||
getItemCleanStart(transaction, createID(currPos.right.id.client, currPos.right.id.clock + length))
|
getItemCleanStart(transaction, createID(currPos.right.id.client, currPos.right.id.clock + length))
|
||||||
}
|
}
|
||||||
@@ -348,7 +346,7 @@ const formatText = (transaction, parent, currPos, length, attributes) => {
|
|||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttributes) => {
|
const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttributes) => {
|
||||||
while (end && end.content.constructor !== ContentString && end.content.constructor !== ContentEmbed) {
|
while (end && (!end.countable || end.deleted)) {
|
||||||
if (!end.deleted && end.content.constructor === ContentFormat) {
|
if (!end.deleted && end.content.constructor === ContentFormat) {
|
||||||
updateCurrentAttributes(endAttributes, /** @type {ContentFormat} */ (end.content))
|
updateCurrentAttributes(endAttributes, /** @type {ContentFormat} */ (end.content))
|
||||||
}
|
}
|
||||||
@@ -381,12 +379,12 @@ const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttri
|
|||||||
*/
|
*/
|
||||||
const cleanupContextlessFormattingGap = (transaction, item) => {
|
const cleanupContextlessFormattingGap = (transaction, item) => {
|
||||||
// iterate until item.right is null or content
|
// iterate until item.right is null or content
|
||||||
while (item && item.right && (item.right.deleted || (item.right.content.constructor !== ContentString && item.right.content.constructor !== ContentEmbed))) {
|
while (item && item.right && (item.right.deleted || !item.right.countable)) {
|
||||||
item = item.right
|
item = item.right
|
||||||
}
|
}
|
||||||
const attrs = new Set()
|
const attrs = new Set()
|
||||||
// iterate back until a content item is found
|
// iterate back until a content item is found
|
||||||
while (item && (item.deleted || (item.content.constructor !== ContentString && item.content.constructor !== ContentEmbed))) {
|
while (item && (item.deleted || !item.countable)) {
|
||||||
if (!item.deleted && item.content.constructor === ContentFormat) {
|
if (!item.deleted && item.content.constructor === ContentFormat) {
|
||||||
const key = /** @type {ContentFormat} */ (item.content).key
|
const key = /** @type {ContentFormat} */ (item.content).key
|
||||||
if (attrs.has(key)) {
|
if (attrs.has(key)) {
|
||||||
@@ -424,8 +422,7 @@ export const cleanupYTextFormatting = type => {
|
|||||||
case ContentFormat:
|
case ContentFormat:
|
||||||
updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (end.content))
|
updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (end.content))
|
||||||
break
|
break
|
||||||
case ContentEmbed:
|
default:
|
||||||
case ContentString:
|
|
||||||
res += cleanupFormattingGap(transaction, start, end, startAttributes, currentAttributes)
|
res += cleanupFormattingGap(transaction, start, end, startAttributes, currentAttributes)
|
||||||
startAttributes = map.copy(currentAttributes)
|
startAttributes = map.copy(currentAttributes)
|
||||||
start = end
|
start = end
|
||||||
@@ -454,6 +451,7 @@ const deleteText = (transaction, currPos, length) => {
|
|||||||
while (length > 0 && currPos.right !== null) {
|
while (length > 0 && currPos.right !== null) {
|
||||||
if (currPos.right.deleted === false) {
|
if (currPos.right.deleted === false) {
|
||||||
switch (currPos.right.content.constructor) {
|
switch (currPos.right.content.constructor) {
|
||||||
|
case ContentType:
|
||||||
case ContentEmbed:
|
case ContentEmbed:
|
||||||
case ContentString:
|
case ContentString:
|
||||||
if (length < currPos.right.length) {
|
if (length < currPos.right.length) {
|
||||||
@@ -540,7 +538,7 @@ export class YTextEvent extends YEvent {
|
|||||||
get changes () {
|
get changes () {
|
||||||
if (this._changes === null) {
|
if (this._changes === null) {
|
||||||
/**
|
/**
|
||||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string, delete?:number, retain?:number}>}}
|
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string|AbstractType<any>|object, delete?:number, retain?:number}>}}
|
||||||
*/
|
*/
|
||||||
const changes = {
|
const changes = {
|
||||||
keys: this.keys,
|
keys: this.keys,
|
||||||
@@ -557,7 +555,7 @@ export class YTextEvent extends YEvent {
|
|||||||
* Compute the changes in the delta format.
|
* Compute the changes in the delta format.
|
||||||
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
|
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
|
||||||
*
|
*
|
||||||
* @type {Array<{insert?:string, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
* @type {Array<{insert?:string|object|AbstractType<any>, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@@ -565,7 +563,7 @@ export class YTextEvent extends YEvent {
|
|||||||
if (this._delta === null) {
|
if (this._delta === null) {
|
||||||
const y = /** @type {Doc} */ (this.target.doc)
|
const y = /** @type {Doc} */ (this.target.doc)
|
||||||
/**
|
/**
|
||||||
* @type {Array<{insert?:string, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
* @type {Array<{insert?:string|object|AbstractType<any>, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||||
*/
|
*/
|
||||||
const delta = []
|
const delta = []
|
||||||
transact(y, transaction => {
|
transact(y, transaction => {
|
||||||
@@ -626,12 +624,13 @@ export class YTextEvent extends YEvent {
|
|||||||
}
|
}
|
||||||
while (item !== null) {
|
while (item !== null) {
|
||||||
switch (item.content.constructor) {
|
switch (item.content.constructor) {
|
||||||
|
case ContentType:
|
||||||
case ContentEmbed:
|
case ContentEmbed:
|
||||||
if (this.adds(item)) {
|
if (this.adds(item)) {
|
||||||
if (!this.deletes(item)) {
|
if (!this.deletes(item)) {
|
||||||
addOp()
|
addOp()
|
||||||
action = 'insert'
|
action = 'insert'
|
||||||
insert = /** @type {ContentEmbed} */ (item.content).embed
|
insert = item.content.getContent()[0]
|
||||||
addOp()
|
addOp()
|
||||||
}
|
}
|
||||||
} else if (this.deletes(item)) {
|
} else if (this.deletes(item)) {
|
||||||
@@ -707,9 +706,9 @@ export class YTextEvent extends YEvent {
|
|||||||
addOp()
|
addOp()
|
||||||
}
|
}
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
attributes[key] = value
|
|
||||||
} else {
|
|
||||||
delete attributes[key]
|
delete attributes[key]
|
||||||
|
} else {
|
||||||
|
attributes[key] = value
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item.delete(transaction)
|
item.delete(transaction)
|
||||||
@@ -1008,13 +1007,14 @@ export class YText extends AbstractType {
|
|||||||
str += /** @type {ContentString} */ (n.content).str
|
str += /** @type {ContentString} */ (n.content).str
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case ContentType:
|
||||||
case ContentEmbed: {
|
case ContentEmbed: {
|
||||||
packStr()
|
packStr()
|
||||||
/**
|
/**
|
||||||
* @type {Object<string,any>}
|
* @type {Object<string,any>}
|
||||||
*/
|
*/
|
||||||
const op = {
|
const op = {
|
||||||
insert: /** @type {ContentEmbed} */ (n.content).embed
|
insert: n.content.getContent()[0]
|
||||||
}
|
}
|
||||||
if (currentAttributes.size > 0) {
|
if (currentAttributes.size > 0) {
|
||||||
const attrs = /** @type {Object<string,any>} */ ({})
|
const attrs = /** @type {Object<string,any>} */ ({})
|
||||||
@@ -1075,16 +1075,13 @@ export class YText extends AbstractType {
|
|||||||
* Inserts an embed at a index.
|
* Inserts an embed at a index.
|
||||||
*
|
*
|
||||||
* @param {number} index The index to insert the embed at.
|
* @param {number} index The index to insert the embed at.
|
||||||
* @param {Object} embed The Object that represents the embed.
|
* @param {Object | AbstractType<any>} embed The Object that represents the embed.
|
||||||
* @param {TextAttributes} attributes Attribute information to apply on the
|
* @param {TextAttributes} attributes Attribute information to apply on the
|
||||||
* embed
|
* embed
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
insertEmbed (index, embed, attributes = {}) {
|
insertEmbed (index, embed, attributes = {}) {
|
||||||
if (embed.constructor !== Object) {
|
|
||||||
throw new Error('Embed must be an Object')
|
|
||||||
}
|
|
||||||
const y = this.doc
|
const y = this.doc
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
transact(y, transaction => {
|
transact(y, transaction => {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const generateNewClientId = random.uint32
|
|||||||
* @property {boolean} [DocOpts.gc=true] Disable garbage collection (default: gc=true)
|
* @property {boolean} [DocOpts.gc=true] Disable garbage collection (default: gc=true)
|
||||||
* @property {function(Item):boolean} [DocOpts.gcFilter] Will be called before an Item is garbage collected. Return false to keep the Item.
|
* @property {function(Item):boolean} [DocOpts.gcFilter] Will be called before an Item is garbage collected. Return false to keep the Item.
|
||||||
* @property {string} [DocOpts.guid] Define a globally unique identifier for this document
|
* @property {string} [DocOpts.guid] Define a globally unique identifier for this document
|
||||||
|
* @property {string | null} [DocOpts.collectionid] Associate this document with a collection. This only plays a role if your provider has a concept of collection.
|
||||||
* @property {any} [DocOpts.meta] Any kind of meta information you want to associate with this document. If this is a subdocument, remote peers will store the meta information as well.
|
* @property {any} [DocOpts.meta] Any kind of meta information you want to associate with this document. If this is a subdocument, remote peers will store the meta information as well.
|
||||||
* @property {boolean} [DocOpts.autoLoad] If a subdocument, automatically load document. If this is a subdocument, remote peers will load the document as well automatically.
|
* @property {boolean} [DocOpts.autoLoad] If a subdocument, automatically load document. If this is a subdocument, remote peers will load the document as well automatically.
|
||||||
*/
|
*/
|
||||||
@@ -37,12 +38,13 @@ export class Doc extends Observable {
|
|||||||
/**
|
/**
|
||||||
* @param {DocOpts} [opts] configuration
|
* @param {DocOpts} [opts] configuration
|
||||||
*/
|
*/
|
||||||
constructor ({ guid = random.uuidv4(), gc = true, gcFilter = () => true, meta = null, autoLoad = false } = {}) {
|
constructor ({ guid = random.uuidv4(), collectionid = null, gc = true, gcFilter = () => true, meta = null, autoLoad = false } = {}) {
|
||||||
super()
|
super()
|
||||||
this.gc = gc
|
this.gc = gc
|
||||||
this.gcFilter = gcFilter
|
this.gcFilter = gcFilter
|
||||||
this.clientID = generateNewClientId()
|
this.clientID = generateNewClientId()
|
||||||
this.guid = guid
|
this.guid = guid
|
||||||
|
this.collectionid = collectionid
|
||||||
/**
|
/**
|
||||||
* @type {Map<string, AbstractType<YEvent>>}
|
* @type {Map<string, AbstractType<YEvent>>}
|
||||||
*/
|
*/
|
||||||
@@ -194,8 +196,9 @@ export class Doc extends Observable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @template T
|
||||||
* @param {string} [name]
|
* @param {string} [name]
|
||||||
* @return {YMap<any>}
|
* @return {YMap<T>}
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -129,9 +129,9 @@ export const snapshot = doc => createSnapshot(createDeleteSetFromStructStore(doc
|
|||||||
* @protected
|
* @protected
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const isVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : (
|
export const isVisible = (item, snapshot) => snapshot === undefined
|
||||||
snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id)
|
? !item.deleted
|
||||||
)
|
: snapshot.sv.has(item.id.client) && (snapshot.sv.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item.id)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
|
|||||||
@@ -350,11 +350,19 @@ const cleanupTransactions = (transactionCleanups, i) => {
|
|||||||
doc.emit('updateV2', [encoder.toUint8Array(), transaction.origin, doc, transaction])
|
doc.emit('updateV2', [encoder.toUint8Array(), transaction.origin, doc, transaction])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transaction.subdocsAdded.forEach(subdoc => doc.subdocs.add(subdoc))
|
const { subdocsAdded, subdocsLoaded, subdocsRemoved } = transaction
|
||||||
transaction.subdocsRemoved.forEach(subdoc => doc.subdocs.delete(subdoc))
|
if (subdocsAdded.size > 0 || subdocsRemoved.size > 0 || subdocsLoaded.size > 0) {
|
||||||
|
subdocsAdded.forEach(subdoc => {
|
||||||
doc.emit('subdocs', [{ loaded: transaction.subdocsLoaded, added: transaction.subdocsAdded, removed: transaction.subdocsRemoved }])
|
subdoc.clientID = doc.clientID
|
||||||
transaction.subdocsRemoved.forEach(subdoc => subdoc.destroy())
|
if (subdoc.collectionid == null) {
|
||||||
|
subdoc.collectionid = doc.collectionid
|
||||||
|
}
|
||||||
|
doc.subdocs.add(subdoc)
|
||||||
|
})
|
||||||
|
subdocsRemoved.forEach(subdoc => doc.subdocs.delete(subdoc))
|
||||||
|
doc.emit('subdocs', [{ loaded: subdocsLoaded, added: subdocsAdded, removed: subdocsRemoved }, doc, transaction])
|
||||||
|
subdocsRemoved.forEach(subdoc => subdoc.destroy())
|
||||||
|
}
|
||||||
|
|
||||||
if (transactionCleanups.length <= i + 1) {
|
if (transactionCleanups.length <= i + 1) {
|
||||||
doc._transactionCleanups = []
|
doc._transactionCleanups = []
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export class YEvent {
|
|||||||
*/
|
*/
|
||||||
this._keys = null
|
this._keys = null
|
||||||
/**
|
/**
|
||||||
* @type {null | Array<{ insert?: string | Array<any>, retain?: number, delete?: number, attributes?: Object<string, any> }>}
|
* @type {null | Array<{ insert?: string | Array<any> | object | AbstractType<any>, retain?: number, delete?: number, attributes?: Object<string, any> }>}
|
||||||
*/
|
*/
|
||||||
this._delta = null
|
this._delta = null
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ export class YEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array<{insert?: string | Array<any>, retain?: number, delete?: number, attributes?: Object<string, any>}>}
|
* @type {Array<{insert?: string | Array<any> | object | AbstractType<any>, retain?: number, delete?: number, attributes?: Object<string, any>}>}
|
||||||
*/
|
*/
|
||||||
get delta () {
|
get delta () {
|
||||||
return this.changes.delta
|
return this.changes.delta
|
||||||
|
|||||||
@@ -601,7 +601,7 @@ export const decodeStateVector = decodedState => readStateVector(new DSDecoderV1
|
|||||||
*/
|
*/
|
||||||
export const writeStateVector = (encoder, sv) => {
|
export const writeStateVector = (encoder, sv) => {
|
||||||
encoding.writeVarUint(encoder.restEncoder, sv.size)
|
encoding.writeVarUint(encoder.restEncoder, sv.size)
|
||||||
sv.forEach((clock, client) => {
|
Array.from(sv.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => {
|
||||||
encoding.writeVarUint(encoder.restEncoder, client) // @todo use a special client decoder that is based on mapping
|
encoding.writeVarUint(encoder.restEncoder, client) // @todo use a special client decoder that is based on mapping
|
||||||
encoding.writeVarUint(encoder.restEncoder, clock)
|
encoding.writeVarUint(encoder.restEncoder, clock)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -316,9 +316,9 @@ export const mergeUpdatesV2 = (updates, YDecoder = UpdateDecoderV2, YEncoder = U
|
|||||||
const clockDiff = dec1.curr.id.clock - dec2.curr.id.clock
|
const clockDiff = dec1.curr.id.clock - dec2.curr.id.clock
|
||||||
if (clockDiff === 0) {
|
if (clockDiff === 0) {
|
||||||
// @todo remove references to skip since the structDecoders must filter Skips.
|
// @todo remove references to skip since the structDecoders must filter Skips.
|
||||||
return dec1.curr.constructor === dec2.curr.constructor ? 0 : (
|
return dec1.curr.constructor === dec2.curr.constructor
|
||||||
dec1.curr.constructor === Skip ? 1 : -1 // we are filtering skips anyway.
|
? 0
|
||||||
)
|
: dec1.curr.constructor === Skip ? 1 : -1 // we are filtering skips anyway.
|
||||||
} else {
|
} else {
|
||||||
return clockDiff
|
return clockDiff
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
import * as Y from '../src/internals'
|
import * as Y from '../src/index.js'
|
||||||
import * as t from 'lib0/testing'
|
import * as t from 'lib0/testing'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Y.YText} ytext
|
* @param {Y.Text} ytext
|
||||||
*/
|
*/
|
||||||
const checkRelativePositions = ytext => {
|
const checkRelativePositions = ytext => {
|
||||||
// test if all positions are encoded and restored correctly
|
// test if all positions are encoded and restored correctly
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { createDocFromSnapshot, Doc, snapshot, YMap } from '../src/internals'
|
import * as Y from '../src/index.js'
|
||||||
import * as t from 'lib0/testing'
|
import * as t from 'lib0/testing'
|
||||||
import { init } from './testHelper'
|
import { init } from './testHelper.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testBasicRestoreSnapshot = tc => {
|
export const testBasicRestoreSnapshot = tc => {
|
||||||
const doc = new Doc({ gc: false })
|
const doc = new Y.Doc({ gc: false })
|
||||||
doc.getArray('array').insert(0, ['hello'])
|
doc.getArray('array').insert(0, ['hello'])
|
||||||
const snap = snapshot(doc)
|
const snap = Y.snapshot(doc)
|
||||||
doc.getArray('array').insert(1, ['world'])
|
doc.getArray('array').insert(1, ['world'])
|
||||||
|
|
||||||
const docRestored = createDocFromSnapshot(doc, snap)
|
const docRestored = Y.createDocFromSnapshot(doc, snap)
|
||||||
|
|
||||||
t.compare(docRestored.getArray('array').toArray(), ['hello'])
|
t.compare(docRestored.getArray('array').toArray(), ['hello'])
|
||||||
t.compare(doc.getArray('array').toArray(), ['hello', 'world'])
|
t.compare(doc.getArray('array').toArray(), ['hello', 'world'])
|
||||||
@@ -21,19 +21,19 @@ export const testBasicRestoreSnapshot = tc => {
|
|||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testEmptyRestoreSnapshot = tc => {
|
export const testEmptyRestoreSnapshot = tc => {
|
||||||
const doc = new Doc({ gc: false })
|
const doc = new Y.Doc({ gc: false })
|
||||||
const snap = snapshot(doc)
|
const snap = Y.snapshot(doc)
|
||||||
snap.sv.set(9999, 0)
|
snap.sv.set(9999, 0)
|
||||||
doc.getArray().insert(0, ['world'])
|
doc.getArray().insert(0, ['world'])
|
||||||
|
|
||||||
const docRestored = createDocFromSnapshot(doc, snap)
|
const docRestored = Y.createDocFromSnapshot(doc, snap)
|
||||||
|
|
||||||
t.compare(docRestored.getArray().toArray(), [])
|
t.compare(docRestored.getArray().toArray(), [])
|
||||||
t.compare(doc.getArray().toArray(), ['world'])
|
t.compare(doc.getArray().toArray(), ['world'])
|
||||||
|
|
||||||
// now this snapshot reflects the latest state. It shoult still work.
|
// now this snapshot reflects the latest state. It shoult still work.
|
||||||
const snap2 = snapshot(doc)
|
const snap2 = Y.snapshot(doc)
|
||||||
const docRestored2 = createDocFromSnapshot(doc, snap2)
|
const docRestored2 = Y.createDocFromSnapshot(doc, snap2)
|
||||||
t.compare(docRestored2.getArray().toArray(), ['world'])
|
t.compare(docRestored2.getArray().toArray(), ['world'])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,15 +41,15 @@ export const testEmptyRestoreSnapshot = tc => {
|
|||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testRestoreSnapshotWithSubType = tc => {
|
export const testRestoreSnapshotWithSubType = tc => {
|
||||||
const doc = new Doc({ gc: false })
|
const doc = new Y.Doc({ gc: false })
|
||||||
doc.getArray('array').insert(0, [new YMap()])
|
doc.getArray('array').insert(0, [new Y.Map()])
|
||||||
const subMap = doc.getArray('array').get(0)
|
const subMap = doc.getArray('array').get(0)
|
||||||
subMap.set('key1', 'value1')
|
subMap.set('key1', 'value1')
|
||||||
|
|
||||||
const snap = snapshot(doc)
|
const snap = Y.snapshot(doc)
|
||||||
subMap.set('key2', 'value2')
|
subMap.set('key2', 'value2')
|
||||||
|
|
||||||
const docRestored = createDocFromSnapshot(doc, snap)
|
const docRestored = Y.createDocFromSnapshot(doc, snap)
|
||||||
|
|
||||||
t.compare(docRestored.getArray('array').toJSON(), [{
|
t.compare(docRestored.getArray('array').toJSON(), [{
|
||||||
key1: 'value1'
|
key1: 'value1'
|
||||||
@@ -64,13 +64,13 @@ export const testRestoreSnapshotWithSubType = tc => {
|
|||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testRestoreDeletedItem1 = tc => {
|
export const testRestoreDeletedItem1 = tc => {
|
||||||
const doc = new Doc({ gc: false })
|
const doc = new Y.Doc({ gc: false })
|
||||||
doc.getArray('array').insert(0, ['item1', 'item2'])
|
doc.getArray('array').insert(0, ['item1', 'item2'])
|
||||||
|
|
||||||
const snap = snapshot(doc)
|
const snap = Y.snapshot(doc)
|
||||||
doc.getArray('array').delete(0)
|
doc.getArray('array').delete(0)
|
||||||
|
|
||||||
const docRestored = createDocFromSnapshot(doc, snap)
|
const docRestored = Y.createDocFromSnapshot(doc, snap)
|
||||||
|
|
||||||
t.compare(docRestored.getArray('array').toArray(), ['item1', 'item2'])
|
t.compare(docRestored.getArray('array').toArray(), ['item1', 'item2'])
|
||||||
t.compare(doc.getArray('array').toArray(), ['item2'])
|
t.compare(doc.getArray('array').toArray(), ['item2'])
|
||||||
@@ -80,15 +80,15 @@ export const testRestoreDeletedItem1 = tc => {
|
|||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testRestoreLeftItem = tc => {
|
export const testRestoreLeftItem = tc => {
|
||||||
const doc = new Doc({ gc: false })
|
const doc = new Y.Doc({ gc: false })
|
||||||
doc.getArray('array').insert(0, ['item1'])
|
doc.getArray('array').insert(0, ['item1'])
|
||||||
doc.getMap('map').set('test', 1)
|
doc.getMap('map').set('test', 1)
|
||||||
doc.getArray('array').insert(0, ['item0'])
|
doc.getArray('array').insert(0, ['item0'])
|
||||||
|
|
||||||
const snap = snapshot(doc)
|
const snap = Y.snapshot(doc)
|
||||||
doc.getArray('array').delete(1)
|
doc.getArray('array').delete(1)
|
||||||
|
|
||||||
const docRestored = createDocFromSnapshot(doc, snap)
|
const docRestored = Y.createDocFromSnapshot(doc, snap)
|
||||||
|
|
||||||
t.compare(docRestored.getArray('array').toArray(), ['item0', 'item1'])
|
t.compare(docRestored.getArray('array').toArray(), ['item0', 'item1'])
|
||||||
t.compare(doc.getArray('array').toArray(), ['item0'])
|
t.compare(doc.getArray('array').toArray(), ['item0'])
|
||||||
@@ -98,13 +98,13 @@ export const testRestoreLeftItem = tc => {
|
|||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testDeletedItemsBase = tc => {
|
export const testDeletedItemsBase = tc => {
|
||||||
const doc = new Doc({ gc: false })
|
const doc = new Y.Doc({ gc: false })
|
||||||
doc.getArray('array').insert(0, ['item1'])
|
doc.getArray('array').insert(0, ['item1'])
|
||||||
doc.getArray('array').delete(0)
|
doc.getArray('array').delete(0)
|
||||||
const snap = snapshot(doc)
|
const snap = Y.snapshot(doc)
|
||||||
doc.getArray('array').insert(0, ['item0'])
|
doc.getArray('array').insert(0, ['item0'])
|
||||||
|
|
||||||
const docRestored = createDocFromSnapshot(doc, snap)
|
const docRestored = Y.createDocFromSnapshot(doc, snap)
|
||||||
|
|
||||||
t.compare(docRestored.getArray('array').toArray(), [])
|
t.compare(docRestored.getArray('array').toArray(), [])
|
||||||
t.compare(doc.getArray('array').toArray(), ['item0'])
|
t.compare(doc.getArray('array').toArray(), ['item0'])
|
||||||
@@ -114,13 +114,13 @@ export const testDeletedItemsBase = tc => {
|
|||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testDeletedItems2 = tc => {
|
export const testDeletedItems2 = tc => {
|
||||||
const doc = new Doc({ gc: false })
|
const doc = new Y.Doc({ gc: false })
|
||||||
doc.getArray('array').insert(0, ['item1', 'item2', 'item3'])
|
doc.getArray('array').insert(0, ['item1', 'item2', 'item3'])
|
||||||
doc.getArray('array').delete(1)
|
doc.getArray('array').delete(1)
|
||||||
const snap = snapshot(doc)
|
const snap = Y.snapshot(doc)
|
||||||
doc.getArray('array').insert(0, ['item0'])
|
doc.getArray('array').insert(0, ['item0'])
|
||||||
|
|
||||||
const docRestored = createDocFromSnapshot(doc, snap)
|
const docRestored = Y.createDocFromSnapshot(doc, snap)
|
||||||
|
|
||||||
t.compare(docRestored.getArray('array').toArray(), ['item1', 'item3'])
|
t.compare(docRestored.getArray('array').toArray(), ['item1', 'item3'])
|
||||||
t.compare(doc.getArray('array').toArray(), ['item0', 'item1', 'item3'])
|
t.compare(doc.getArray('array').toArray(), ['item0', 'item1', 'item3'])
|
||||||
@@ -140,11 +140,11 @@ export const testDependentChanges = tc => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type Doc
|
* @type {Y.Doc}
|
||||||
*/
|
*/
|
||||||
const doc0 = array0.doc
|
const doc0 = array0.doc
|
||||||
/**
|
/**
|
||||||
* @type Doc
|
* @type {Y.Doc}
|
||||||
*/
|
*/
|
||||||
const doc1 = array1.doc
|
const doc1 = array1.doc
|
||||||
|
|
||||||
@@ -156,16 +156,16 @@ export const testDependentChanges = tc => {
|
|||||||
array1.insert(1, ['user2item1'])
|
array1.insert(1, ['user2item1'])
|
||||||
testConnector.syncAll()
|
testConnector.syncAll()
|
||||||
|
|
||||||
const snap = snapshot(array0.doc)
|
const snap = Y.snapshot(array0.doc)
|
||||||
|
|
||||||
array0.insert(2, ['user1item2'])
|
array0.insert(2, ['user1item2'])
|
||||||
testConnector.syncAll()
|
testConnector.syncAll()
|
||||||
array1.insert(3, ['user2item2'])
|
array1.insert(3, ['user2item2'])
|
||||||
testConnector.syncAll()
|
testConnector.syncAll()
|
||||||
|
|
||||||
const docRestored0 = createDocFromSnapshot(array0.doc, snap)
|
const docRestored0 = Y.createDocFromSnapshot(array0.doc, snap)
|
||||||
t.compare(docRestored0.getArray('array').toArray(), ['user1item1', 'user2item1'])
|
t.compare(docRestored0.getArray('array').toArray(), ['user1item1', 'user2item1'])
|
||||||
|
|
||||||
const docRestored1 = createDocFromSnapshot(array1.doc, snap)
|
const docRestored1 = Y.createDocFromSnapshot(array1.doc, snap)
|
||||||
t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1'])
|
t.compare(docRestored1.getArray('array').toArray(), ['user1item1', 'user2item1'])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import * as t from 'lib0/testing'
|
|||||||
import * as prng from 'lib0/prng'
|
import * as prng from 'lib0/prng'
|
||||||
import * as encoding from 'lib0/encoding'
|
import * as encoding from 'lib0/encoding'
|
||||||
import * as decoding from 'lib0/decoding'
|
import * as decoding from 'lib0/decoding'
|
||||||
import * as syncProtocol from 'y-protocols/sync.js'
|
import * as syncProtocol from 'y-protocols/sync'
|
||||||
import * as object from 'lib0/object'
|
import * as object from 'lib0/object'
|
||||||
import * as Y from '../src/internals.js'
|
import * as Y from '../src/index.js'
|
||||||
export * from '../src/internals.js'
|
export * from '../src/index.js'
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -279,7 +279,7 @@ export class TestConnector {
|
|||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
* @param {{users?:number}} conf
|
* @param {{users?:number}} conf
|
||||||
* @param {InitTestObjectCallback<T>} [initTestObject]
|
* @param {InitTestObjectCallback<T>} [initTestObject]
|
||||||
* @return {{testObjects:Array<any>,testConnector:TestConnector,users:Array<TestYInstance>,array0:Y.YArray<any>,array1:Y.YArray<any>,array2:Y.YArray<any>,map0:Y.YMap<any>,map1:Y.YMap<any>,map2:Y.YMap<any>,map3:Y.YMap<any>,text0:Y.YText,text1:Y.YText,text2:Y.YText,xml0:Y.YXmlElement,xml1:Y.YXmlElement,xml2:Y.YXmlElement}}
|
* @return {{testObjects:Array<any>,testConnector:TestConnector,users:Array<TestYInstance>,array0:Y.Array<any>,array1:Y.Array<any>,array2:Y.Array<any>,map0:Y.Map<any>,map1:Y.Map<any>,map2:Y.Map<any>,map3:Y.Map<any>,text0:Y.Text,text1:Y.Text,text2:Y.Text,xml0:Y.XmlElement,xml1:Y.XmlElement,xml2:Y.XmlElement}}
|
||||||
*/
|
*/
|
||||||
export const init = (tc, { users = 5 } = {}, initTestObject) => {
|
export const init = (tc, { users = 5 } = {}, initTestObject) => {
|
||||||
/**
|
/**
|
||||||
@@ -304,7 +304,7 @@ export const init = (tc, { users = 5 } = {}, initTestObject) => {
|
|||||||
result.users.push(y)
|
result.users.push(y)
|
||||||
result['array' + i] = y.getArray('array')
|
result['array' + i] = y.getArray('array')
|
||||||
result['map' + i] = y.getMap('map')
|
result['map' + i] = y.getMap('map')
|
||||||
result['xml' + i] = y.get('xml', Y.YXmlElement)
|
result['xml' + i] = y.get('xml', Y.XmlElement)
|
||||||
result['text' + i] = y.getText('text')
|
result['text' + i] = y.getText('text')
|
||||||
}
|
}
|
||||||
testConnector.syncAll()
|
testConnector.syncAll()
|
||||||
@@ -324,7 +324,7 @@ export const init = (tc, { users = 5 } = {}, initTestObject) => {
|
|||||||
*/
|
*/
|
||||||
export const compare = users => {
|
export const compare = users => {
|
||||||
users.forEach(u => u.connect())
|
users.forEach(u => u.connect())
|
||||||
while (users[0].tc.flushAllMessages()) {}
|
while (users[0].tc.flushAllMessages()) {} // eslint-disable-line
|
||||||
// For each document, merge all received document updates with Y.mergeUpdates and create a new document which will be added to the list of "users"
|
// For each document, merge all received document updates with Y.mergeUpdates and create a new document which will be added to the list of "users"
|
||||||
// This ensures that mergeUpdates works correctly
|
// This ensures that mergeUpdates works correctly
|
||||||
const mergedDocs = users.map(user => {
|
const mergedDocs = users.map(user => {
|
||||||
@@ -335,7 +335,7 @@ export const compare = users => {
|
|||||||
users.push(.../** @type {any} */(mergedDocs))
|
users.push(.../** @type {any} */(mergedDocs))
|
||||||
const userArrayValues = users.map(u => u.getArray('array').toJSON())
|
const userArrayValues = users.map(u => u.getArray('array').toJSON())
|
||||||
const userMapValues = users.map(u => u.getMap('map').toJSON())
|
const userMapValues = users.map(u => u.getMap('map').toJSON())
|
||||||
const userXmlValues = users.map(u => u.get('xml', Y.YXmlElement).toString())
|
const userXmlValues = users.map(u => u.get('xml', Y.XmlElement).toString())
|
||||||
const userTextValues = users.map(u => u.getText('text').toDelta())
|
const userTextValues = users.map(u => u.getText('text').toDelta())
|
||||||
for (const u of users) {
|
for (const u of users) {
|
||||||
t.assert(u.store.pendingDs === null)
|
t.assert(u.store.pendingDs === null)
|
||||||
@@ -362,8 +362,15 @@ export const compare = users => {
|
|||||||
t.compare(userMapValues[i], userMapValues[i + 1])
|
t.compare(userMapValues[i], userMapValues[i + 1])
|
||||||
t.compare(userXmlValues[i], userXmlValues[i + 1])
|
t.compare(userXmlValues[i], userXmlValues[i + 1])
|
||||||
t.compare(userTextValues[i].map(/** @param {any} a */ a => typeof a.insert === 'string' ? a.insert : ' ').join('').length, users[i].getText('text').length)
|
t.compare(userTextValues[i].map(/** @param {any} a */ a => typeof a.insert === 'string' ? a.insert : ' ').join('').length, users[i].getText('text').length)
|
||||||
t.compare(userTextValues[i], userTextValues[i + 1])
|
t.compare(userTextValues[i], userTextValues[i + 1], '', (constructor, a, b) => {
|
||||||
t.compare(Y.getStateVector(users[i].store), Y.getStateVector(users[i + 1].store))
|
if (a instanceof Y.AbstractType) {
|
||||||
|
t.compare(a.toJSON(), b.toJSON())
|
||||||
|
} else if (a !== b) {
|
||||||
|
t.fail('Deltas dont match')
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
t.compare(Y.encodeStateVector(users[i]), Y.encodeStateVector(users[i + 1]))
|
||||||
compareDS(Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store))
|
compareDS(Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store))
|
||||||
compareStructStores(users[i].store, users[i + 1].store)
|
compareStructStores(users[i].store, users[i + 1].store)
|
||||||
}
|
}
|
||||||
@@ -378,8 +385,8 @@ export const compare = users => {
|
|||||||
export const compareItemIDs = (a, b) => a === b || (a !== null && b != null && Y.compareIDs(a.id, b.id))
|
export const compareItemIDs = (a, b) => a === b || (a !== null && b != null && Y.compareIDs(a.id, b.id))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Y.StructStore} ss1
|
* @param {import('../src/internals').StructStore} ss1
|
||||||
* @param {Y.StructStore} ss2
|
* @param {import('../src/internals').StructStore} ss2
|
||||||
*/
|
*/
|
||||||
export const compareStructStores = (ss1, ss2) => {
|
export const compareStructStores = (ss1, ss2) => {
|
||||||
t.assert(ss1.clients.size === ss2.clients.size)
|
t.assert(ss1.clients.size === ss2.clients.size)
|
||||||
@@ -421,13 +428,13 @@ export const compareStructStores = (ss1, ss2) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Y.DeleteSet} ds1
|
* @param {import('../src/internals').DeleteSet} ds1
|
||||||
* @param {Y.DeleteSet} ds2
|
* @param {import('../src/internals').DeleteSet} ds2
|
||||||
*/
|
*/
|
||||||
export const compareDS = (ds1, ds2) => {
|
export const compareDS = (ds1, ds2) => {
|
||||||
t.assert(ds1.clients.size === ds2.clients.size)
|
t.assert(ds1.clients.size === ds2.clients.size)
|
||||||
ds1.clients.forEach((deleteItems1, client) => {
|
ds1.clients.forEach((deleteItems1, client) => {
|
||||||
const deleteItems2 = /** @type {Array<Y.DeleteItem>} */ (ds2.clients.get(client))
|
const deleteItems2 = /** @type {Array<import('../src/internals').DeleteItem>} */ (ds2.clients.get(client))
|
||||||
t.assert(deleteItems2 !== undefined && deleteItems1.length === deleteItems2.length)
|
t.assert(deleteItems2 !== undefined && deleteItems1.length === deleteItems2.length)
|
||||||
for (let i = 0; i < deleteItems1.length; i++) {
|
for (let i = 0; i < deleteItems1.length; i++) {
|
||||||
const di1 = deleteItems1[i]
|
const di1 = deleteItems1[i]
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line
|
import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line
|
||||||
|
|
||||||
import {
|
|
||||||
UndoManager
|
|
||||||
} from '../src/internals.js'
|
|
||||||
|
|
||||||
import * as Y from '../src/index.js'
|
import * as Y from '../src/index.js'
|
||||||
import * as t from 'lib0/testing'
|
import * as t from 'lib0/testing'
|
||||||
|
|
||||||
@@ -12,7 +8,7 @@ import * as t from 'lib0/testing'
|
|||||||
*/
|
*/
|
||||||
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 Y.UndoManager(text0)
|
||||||
|
|
||||||
// items that are added & deleted in the same transaction won't be undo
|
// items that are added & deleted in the same transaction won't be undo
|
||||||
text0.insert(0, 'test')
|
text0.insert(0, 'test')
|
||||||
@@ -81,7 +77,7 @@ export const testDoubleUndo = tc => {
|
|||||||
export const testUndoMap = tc => {
|
export const testUndoMap = tc => {
|
||||||
const { testConnector, map0, map1 } = init(tc, { users: 2 })
|
const { testConnector, map0, map1 } = init(tc, { users: 2 })
|
||||||
map0.set('a', 0)
|
map0.set('a', 0)
|
||||||
const undoManager = new UndoManager(map0)
|
const undoManager = new Y.UndoManager(map0)
|
||||||
map0.set('a', 1)
|
map0.set('a', 1)
|
||||||
undoManager.undo()
|
undoManager.undo()
|
||||||
t.assert(map0.get('a') === 0)
|
t.assert(map0.get('a') === 0)
|
||||||
@@ -120,7 +116,7 @@ export const testUndoMap = tc => {
|
|||||||
*/
|
*/
|
||||||
export const testUndoArray = tc => {
|
export const testUndoArray = tc => {
|
||||||
const { testConnector, array0, array1 } = init(tc, { users: 3 })
|
const { testConnector, array0, array1 } = init(tc, { users: 3 })
|
||||||
const undoManager = new UndoManager(array0)
|
const undoManager = new Y.UndoManager(array0)
|
||||||
array0.insert(0, [1, 2, 3])
|
array0.insert(0, [1, 2, 3])
|
||||||
array1.insert(0, [4, 5, 6])
|
array1.insert(0, [4, 5, 6])
|
||||||
testConnector.syncAll()
|
testConnector.syncAll()
|
||||||
@@ -171,7 +167,7 @@ export const testUndoArray = tc => {
|
|||||||
*/
|
*/
|
||||||
export const testUndoXml = tc => {
|
export const testUndoXml = tc => {
|
||||||
const { xml0 } = init(tc, { users: 3 })
|
const { xml0 } = init(tc, { users: 3 })
|
||||||
const undoManager = new UndoManager(xml0)
|
const undoManager = new Y.UndoManager(xml0)
|
||||||
const child = new Y.XmlElement('p')
|
const child = new Y.XmlElement('p')
|
||||||
xml0.insert(0, [child])
|
xml0.insert(0, [child])
|
||||||
const textchild = new Y.XmlText('content')
|
const textchild = new Y.XmlText('content')
|
||||||
@@ -196,7 +192,7 @@ export const testUndoXml = tc => {
|
|||||||
*/
|
*/
|
||||||
export const testUndoEvents = tc => {
|
export const testUndoEvents = tc => {
|
||||||
const { text0 } = init(tc, { users: 3 })
|
const { text0 } = init(tc, { users: 3 })
|
||||||
const undoManager = new UndoManager(text0)
|
const undoManager = new Y.UndoManager(text0)
|
||||||
let counter = 0
|
let counter = 0
|
||||||
let receivedMetadata = -1
|
let receivedMetadata = -1
|
||||||
undoManager.on('stack-item-added', /** @param {any} event */ event => {
|
undoManager.on('stack-item-added', /** @param {any} event */ event => {
|
||||||
@@ -222,7 +218,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, { trackedOrigins: new Set([Number]) })
|
const undoManager = new Y.UndoManager(text0, { trackedOrigins: new Set([Number]) })
|
||||||
users[0].transact(() => {
|
users[0].transact(() => {
|
||||||
text0.insert(0, 'abc')
|
text0.insert(0, 'abc')
|
||||||
}, 42)
|
}, 42)
|
||||||
@@ -240,8 +236,8 @@ export const testTypeScope = tc => {
|
|||||||
const text0 = new Y.Text()
|
const text0 = new Y.Text()
|
||||||
const text1 = new Y.Text()
|
const text1 = new Y.Text()
|
||||||
array0.insert(0, [text0, text1])
|
array0.insert(0, [text0, text1])
|
||||||
const undoManager = new UndoManager(text0)
|
const undoManager = new Y.UndoManager(text0)
|
||||||
const undoManagerBoth = new UndoManager([text0, text1])
|
const undoManagerBoth = new Y.UndoManager([text0, text1])
|
||||||
text1.insert(0, 'abc')
|
text1.insert(0, 'abc')
|
||||||
t.assert(undoManager.undoStack.length === 0)
|
t.assert(undoManager.undoStack.length === 0)
|
||||||
t.assert(undoManagerBoth.undoStack.length === 1)
|
t.assert(undoManagerBoth.undoStack.length === 1)
|
||||||
@@ -260,7 +256,7 @@ export const testUndoDeleteFilter = tc => {
|
|||||||
* @type {Array<Y.Map<any>>}
|
* @type {Array<Y.Map<any>>}
|
||||||
*/
|
*/
|
||||||
const array0 = /** @type {any} */ (init(tc, { users: 3 }).array0)
|
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 undoManager = new Y.UndoManager(array0, { deleteFilter: item => !(item instanceof Y.Item) || (item.content instanceof Y.ContentType && item.content.type._map.size === 0) })
|
||||||
const map0 = new Y.Map()
|
const map0 = new Y.Map()
|
||||||
map0.set('hi', 1)
|
map0.set('hi', 1)
|
||||||
const map1 = new Y.Map()
|
const map1 = new Y.Map()
|
||||||
@@ -301,3 +297,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' } } })
|
||||||
|
}
|
||||||
|
|||||||
@@ -488,6 +488,11 @@ const arrayTransactions = [
|
|||||||
map.set('someprop', 43)
|
map.set('someprop', 43)
|
||||||
map.set('someprop', 44)
|
map.set('someprop', 44)
|
||||||
},
|
},
|
||||||
|
function insertTypeNull (user, gen) {
|
||||||
|
const yarray = user.getArray('array')
|
||||||
|
const pos = prng.int32(gen, 0, yarray.length)
|
||||||
|
yarray.insert(pos, [null])
|
||||||
|
},
|
||||||
function _delete (user, gen) {
|
function _delete (user, gen) {
|
||||||
const yarray = user.getArray('array')
|
const yarray = user.getArray('array')
|
||||||
const length = yarray.length
|
const length = yarray.length
|
||||||
@@ -496,7 +501,7 @@ const arrayTransactions = [
|
|||||||
let delLength = prng.int32(gen, 1, math.min(2, length - somePos))
|
let delLength = prng.int32(gen, 1, math.min(2, length - somePos))
|
||||||
if (prng.bool(gen)) {
|
if (prng.bool(gen)) {
|
||||||
const type = yarray.get(somePos)
|
const type = yarray.get(somePos)
|
||||||
if (type.length > 0) {
|
if (type instanceof Y.Array && type.length > 0) {
|
||||||
somePos = prng.int32(gen, 0, type.length - 1)
|
somePos = prng.int32(gen, 0, type.length - 1)
|
||||||
delLength = prng.int32(gen, 0, math.min(2, type.length - somePos))
|
delLength = prng.int32(gen, 0, math.min(2, type.length - somePos))
|
||||||
type.delete(somePos, delLength)
|
type.delete(somePos, delLength)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export const testBasicMapTests = tc => {
|
|||||||
const { testConnector, users, map0, map1, map2 } = init(tc, { users: 3 })
|
const { testConnector, users, map0, map1, map2 } = init(tc, { users: 3 })
|
||||||
users[2].disconnect()
|
users[2].disconnect()
|
||||||
|
|
||||||
|
map0.set('null', null)
|
||||||
map0.set('number', 1)
|
map0.set('number', 1)
|
||||||
map0.set('string', 'hello Y')
|
map0.set('string', 'hello Y')
|
||||||
map0.set('object', { key: { key2: 'value' } })
|
map0.set('object', { key: { key2: 'value' } })
|
||||||
@@ -54,26 +55,29 @@ export const testBasicMapTests = tc => {
|
|||||||
array.insert(0, [0])
|
array.insert(0, [0])
|
||||||
array.insert(0, [-1])
|
array.insert(0, [-1])
|
||||||
|
|
||||||
|
t.assert(map0.get('null') === null, 'client 0 computed the change (null)')
|
||||||
t.assert(map0.get('number') === 1, 'client 0 computed the change (number)')
|
t.assert(map0.get('number') === 1, 'client 0 computed the change (number)')
|
||||||
t.assert(map0.get('string') === 'hello Y', 'client 0 computed the change (string)')
|
t.assert(map0.get('string') === 'hello Y', 'client 0 computed the change (string)')
|
||||||
t.assert(map0.get('boolean0') === false, 'client 0 computed the change (boolean)')
|
t.assert(map0.get('boolean0') === false, 'client 0 computed the change (boolean)')
|
||||||
t.assert(map0.get('boolean1') === true, 'client 0 computed the change (boolean)')
|
t.assert(map0.get('boolean1') === true, 'client 0 computed the change (boolean)')
|
||||||
t.compare(map0.get('object'), { key: { key2: 'value' } }, 'client 0 computed the change (object)')
|
t.compare(map0.get('object'), { key: { key2: 'value' } }, 'client 0 computed the change (object)')
|
||||||
t.assert(map0.get('y-map').get('y-array').get(0) === -1, 'client 0 computed the change (type)')
|
t.assert(map0.get('y-map').get('y-array').get(0) === -1, 'client 0 computed the change (type)')
|
||||||
t.assert(map0.size === 6, 'client 0 map has correct size')
|
t.assert(map0.size === 7, 'client 0 map has correct size')
|
||||||
|
|
||||||
users[2].connect()
|
users[2].connect()
|
||||||
testConnector.flushAllMessages()
|
testConnector.flushAllMessages()
|
||||||
|
|
||||||
|
t.assert(map1.get('null') === null, 'client 1 received the update (null)')
|
||||||
t.assert(map1.get('number') === 1, 'client 1 received the update (number)')
|
t.assert(map1.get('number') === 1, 'client 1 received the update (number)')
|
||||||
t.assert(map1.get('string') === 'hello Y', 'client 1 received the update (string)')
|
t.assert(map1.get('string') === 'hello Y', 'client 1 received the update (string)')
|
||||||
t.assert(map1.get('boolean0') === false, 'client 1 computed the change (boolean)')
|
t.assert(map1.get('boolean0') === false, 'client 1 computed the change (boolean)')
|
||||||
t.assert(map1.get('boolean1') === true, 'client 1 computed the change (boolean)')
|
t.assert(map1.get('boolean1') === true, 'client 1 computed the change (boolean)')
|
||||||
t.compare(map1.get('object'), { key: { key2: 'value' } }, 'client 1 received the update (object)')
|
t.compare(map1.get('object'), { key: { key2: 'value' } }, 'client 1 received the update (object)')
|
||||||
t.assert(map1.get('y-map').get('y-array').get(0) === -1, 'client 1 received the update (type)')
|
t.assert(map1.get('y-map').get('y-array').get(0) === -1, 'client 1 received the update (type)')
|
||||||
t.assert(map1.size === 6, 'client 1 map has correct size')
|
t.assert(map1.size === 7, 'client 1 map has correct size')
|
||||||
|
|
||||||
// compare disconnected user
|
// compare disconnected user
|
||||||
|
t.assert(map2.get('null') === null, 'client 2 received the update (null) - was disconnected')
|
||||||
t.assert(map2.get('number') === 1, 'client 2 received the update (number) - was disconnected')
|
t.assert(map2.get('number') === 1, 'client 2 received the update (number) - was disconnected')
|
||||||
t.assert(map2.get('string') === 'hello Y', 'client 2 received the update (string) - was disconnected')
|
t.assert(map2.get('string') === 'hello Y', 'client 2 received the update (string) - was disconnected')
|
||||||
t.assert(map2.get('boolean0') === false, 'client 2 computed the change (boolean)')
|
t.assert(map2.get('boolean0') === false, 'client 2 computed the change (boolean)')
|
||||||
|
|||||||
@@ -151,6 +151,29 @@ export const testGetDeltaWithEmbeds = tc => {
|
|||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testTypesAsEmbed = tc => {
|
||||||
|
const { text0, text1, testConnector } = init(tc, { users: 2 })
|
||||||
|
text0.applyDelta([{
|
||||||
|
insert: new Y.Map([['key', 'val']])
|
||||||
|
}])
|
||||||
|
t.compare(text0.toDelta()[0].insert.toJSON(), { key: 'val' })
|
||||||
|
let firedEvent = false
|
||||||
|
text1.observe(event => {
|
||||||
|
const d = event.delta
|
||||||
|
t.assert(d.length === 1)
|
||||||
|
t.compare(d.map(x => /** @type {Y.AbstractType<any>} */ (x.insert).toJSON()), [{ key: 'val' }])
|
||||||
|
firedEvent = true
|
||||||
|
})
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
const delta = text1.toDelta()
|
||||||
|
t.assert(delta.length === 1)
|
||||||
|
t.compare(delta[0].insert.toJSON(), { key: 'val' })
|
||||||
|
t.assert(firedEvent, 'fired the event observer containing a Type-Embed')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
@@ -265,6 +288,41 @@ export const testFormattingRemovedInMidText = tc => {
|
|||||||
t.assert(Y.getTypeChildren(text0).length === 3)
|
t.assert(Y.getTypeChildren(text0).length === 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reported in https://github.com/yjs/yjs/issues/344
|
||||||
|
*
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testFormattingDeltaUnnecessaryAttributeChange = tc => {
|
||||||
|
const { text0, text1, testConnector } = init(tc, { users: 2 })
|
||||||
|
text0.insert(0, '\n', {
|
||||||
|
PARAGRAPH_STYLES: 'normal',
|
||||||
|
LIST_STYLES: 'bullet'
|
||||||
|
})
|
||||||
|
text0.insert(1, 'abc', {
|
||||||
|
PARAGRAPH_STYLES: 'normal'
|
||||||
|
})
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
/**
|
||||||
|
* @type {Array<any>}
|
||||||
|
*/
|
||||||
|
const deltas = []
|
||||||
|
text0.observe(event => {
|
||||||
|
deltas.push(event.delta)
|
||||||
|
})
|
||||||
|
text1.observe(event => {
|
||||||
|
deltas.push(event.delta)
|
||||||
|
})
|
||||||
|
text1.format(0, 1, { LIST_STYLES: 'number' })
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
const filteredDeltas = deltas.filter(d => d.length > 0)
|
||||||
|
t.assert(filteredDeltas.length === 2)
|
||||||
|
t.compare(filteredDeltas[0], [
|
||||||
|
{ retain: 1, attributes: { LIST_STYLES: 'number' } }
|
||||||
|
])
|
||||||
|
t.compare(filteredDeltas[0], filteredDeltas[1])
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
@@ -493,6 +551,31 @@ export const testSearchMarkerBug1 = tc => {
|
|||||||
compare(users)
|
compare(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reported in https://github.com/yjs/yjs/pull/32
|
||||||
|
*
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testFormattingBug = async tc => {
|
||||||
|
const ydoc1 = new Y.Doc()
|
||||||
|
const ydoc2 = new Y.Doc()
|
||||||
|
const text1 = ydoc1.getText()
|
||||||
|
text1.insert(0, '\n\n\n')
|
||||||
|
text1.format(0, 3, { url: 'http://example.com' })
|
||||||
|
ydoc1.getText().format(1, 1, { url: 'http://docs.yjs.dev' })
|
||||||
|
ydoc2.getText().format(1, 1, { url: 'http://docs.yjs.dev' })
|
||||||
|
Y.applyUpdate(ydoc2, Y.encodeStateAsUpdate(ydoc1))
|
||||||
|
const text2 = ydoc2.getText()
|
||||||
|
const expectedResult = [
|
||||||
|
{ insert: '\n', attributes: { url: 'http://example.com' } },
|
||||||
|
{ insert: '\n', attributes: { url: 'http://docs.yjs.dev' } },
|
||||||
|
{ insert: '\n', attributes: { url: 'http://example.com' } }
|
||||||
|
]
|
||||||
|
t.compare(text1.toDelta(), expectedResult)
|
||||||
|
t.compare(text1.toDelta(), text2.toDelta())
|
||||||
|
console.log(text1.toDelta())
|
||||||
|
}
|
||||||
|
|
||||||
// RANDOM TESTS
|
// RANDOM TESTS
|
||||||
|
|
||||||
let charCounter = 0
|
let charCounter = 0
|
||||||
@@ -628,7 +711,11 @@ const qChanges = [
|
|||||||
(y, gen) => { // insert embed
|
(y, gen) => { // insert embed
|
||||||
const ytext = y.getText('text')
|
const ytext = y.getText('text')
|
||||||
const insertPos = prng.int32(gen, 0, ytext.length)
|
const insertPos = prng.int32(gen, 0, ytext.length)
|
||||||
ytext.insertEmbed(insertPos, { image: 'https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png' })
|
if (prng.bool(gen)) {
|
||||||
|
ytext.insertEmbed(insertPos, { image: 'https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png' })
|
||||||
|
} else {
|
||||||
|
ytext.insertEmbed(insertPos, new Y.Map([[prng.word(gen), prng.word(gen)]]))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @param {Y.Doc} y
|
* @param {Y.Doc} y
|
||||||
@@ -675,8 +762,12 @@ const qChanges = [
|
|||||||
*/
|
*/
|
||||||
const checkResult = result => {
|
const checkResult = result => {
|
||||||
for (let i = 1; i < result.testObjects.length; i++) {
|
for (let i = 1; i < result.testObjects.length; i++) {
|
||||||
const p1 = result.users[i].getText('text').toDelta()
|
/**
|
||||||
const p2 = result.users[i].getText('text').toDelta()
|
* @param {any} d
|
||||||
|
*/
|
||||||
|
const typeToObject = d => d.insert instanceof Y.AbstractType ? d.insert.toJSON() : d
|
||||||
|
const p1 = result.users[i].getText('text').toDelta().map(typeToObject)
|
||||||
|
const p2 = result.users[i].getText('text').toDelta().map(typeToObject)
|
||||||
t.compare(p1, p2)
|
t.compare(p1, p2)
|
||||||
}
|
}
|
||||||
// Uncomment this to find formatting-cleanup issues
|
// Uncomment this to find formatting-cleanup issues
|
||||||
|
|||||||
Reference in New Issue
Block a user