Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5244755879 | ||
|
|
3a7a324a24 | ||
|
|
9e98fec504 | ||
|
|
c67428d715 | ||
|
|
45a9af96af | ||
|
|
249c4f9c45 | ||
|
|
cdc7d3ffe6 | ||
|
|
ac6a0e7667 | ||
|
|
12881e2be7 | ||
|
|
77958da657 | ||
|
|
8a8a60efde | ||
|
|
7a1d648e79 | ||
|
|
3af420e790 | ||
|
|
4f2d13e3ce | ||
|
|
e0b76cd2f4 | ||
|
|
d812636c5b | ||
|
|
21fee0fe96 | ||
|
|
fab14a09de | ||
|
|
710b4ba145 | ||
|
|
34091ae614 | ||
|
|
feb8ec1afc | ||
|
|
ce9139c9f4 | ||
|
|
e2e5d0870c | ||
|
|
04cff60931 | ||
|
|
5dfe4e8af2 |
68
README.md
68
README.md
@@ -97,6 +97,7 @@ are implemented in separate modules.
|
|||||||
| [Quill](https://quilljs.com/) | ✔ | [y-quill](https://github.com/yjs/y-quill) | [demo](https://demos.yjs.dev/quill/quill.html) |
|
| [Quill](https://quilljs.com/) | ✔ | [y-quill](https://github.com/yjs/y-quill) | [demo](https://demos.yjs.dev/quill/quill.html) |
|
||||||
| [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](https://github.com/yjs/y-codemirror) | [demo](https://demos.yjs.dev/codemirror/codemirror.html) |
|
| [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](https://github.com/yjs/y-codemirror) | [demo](https://demos.yjs.dev/codemirror/codemirror.html) |
|
||||||
| [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](https://github.com/yjs/y-monaco) | [demo](https://demos.yjs.dev/monaco/monaco.html) |
|
| [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](https://github.com/yjs/y-monaco) | [demo](https://demos.yjs.dev/monaco/monaco.html) |
|
||||||
|
| [Slate](https://github.com/ianstormtaylor/slate) | ✔ | [slate-yjs](https://github.com/bitphinix/slate-yjs) | [demo](https://bitphinix.github.io/slate-yjs-example) |
|
||||||
|
|
||||||
### Providers
|
### Providers
|
||||||
|
|
||||||
@@ -319,6 +320,8 @@ or any of its children.
|
|||||||
<dl>
|
<dl>
|
||||||
<b><code>parent:Y.AbstractType|null</code></b>
|
<b><code>parent:Y.AbstractType|null</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
|
<b><code>size: number</code></b>
|
||||||
|
<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|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|Uint8Array|Y.Type)</code></b>
|
||||||
@@ -409,7 +412,7 @@ YTextEvents compute changes as deltas.
|
|||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>format(index:number, length:number, formattingAttributes:Object<string,string>)</code></b>
|
<b><code>format(index:number, length:number, formattingAttributes:Object<string,string>)</code></b>
|
||||||
<dd>Assign formatting attributes to a range in the text</dd>
|
<dd>Assign formatting attributes to a range in the text</dd>
|
||||||
<b><code>applyDelta(delta, opts:Object<string,any>)</code></b>
|
<b><code>applyDelta(delta: Delta, opts:Object<string,any>)</code></b>
|
||||||
<dd>
|
<dd>
|
||||||
See <a href="https://quilljs.com/docs/delta/">Quill Delta</a>
|
See <a href="https://quilljs.com/docs/delta/">Quill Delta</a>
|
||||||
Can set options for preventing remove ending newLines, default is true.
|
Can set options for preventing remove ending newLines, default is true.
|
||||||
@@ -482,6 +485,8 @@ or any of its children.
|
|||||||
<dd>Get the XML serialization of all descendants.</dd>
|
<dd>Get the XML serialization of all descendants.</dd>
|
||||||
<b><code>toJSON():string</code></b>
|
<b><code>toJSON():string</code></b>
|
||||||
<dd>See <code>toString</code>.</dd>
|
<dd>See <code>toString</code>.</dd>
|
||||||
|
<b><code>createTreeWalker(filter: function(AbstractType<any>):boolean):Iterable</code></b>
|
||||||
|
<dd>Create an Iterable that walks through the children.</dd>
|
||||||
<b><code>observe(function(YXmlEvent, Transaction):void)</code></b>
|
<b><code>observe(function(YXmlEvent, Transaction):void)</code></b>
|
||||||
<dd>
|
<dd>
|
||||||
Adds an event listener to this type that will be called synchronously every time
|
Adds an event listener to this type that will be called synchronously every time
|
||||||
@@ -539,7 +544,7 @@ content and be actually XML compliant.
|
|||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>getAttribute(attributeName:string):string</code></b>
|
<b><code>getAttribute(attributeName:string):string</code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>getAttributes(attributeName:string):Object<string,string></code></b>
|
<b><code>getAttributes():Object<string,string></code></b>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<b><code>get(i:number):Y.XmlElement|Y.XmlText</code></b>
|
<b><code>get(i:number):Y.XmlElement|Y.XmlText</code></b>
|
||||||
<dd>Retrieve the i-th element.</dd>
|
<dd>Retrieve the i-th element.</dd>
|
||||||
@@ -608,7 +613,10 @@ parameter that is stored on <code>transaction.origin</code> and
|
|||||||
</dd>
|
</dd>
|
||||||
<b><code>toJSON():any</code></b>
|
<b><code>toJSON():any</code></b>
|
||||||
<dd>
|
<dd>
|
||||||
Converts the entire document into a js object, recursively traversing each yjs type.
|
Deprecated: It is recommended to call toJSON directly on the shared types.
|
||||||
|
Converts the entire document into a js object, recursively traversing each yjs
|
||||||
|
type. Doesn't log types that have not been defined (using
|
||||||
|
<code>ydoc.getType(..)</code>).
|
||||||
</dd>
|
</dd>
|
||||||
<b><code>get(string, Y.[TypeClass]):[Type]</code></b>
|
<b><code>get(string, Y.[TypeClass]):[Type]</code></b>
|
||||||
<dd>Define a shared type.</dd>
|
<dd>Define a shared type.</dd>
|
||||||
@@ -690,7 +698,7 @@ Y.applyUpdate(ydoc2, state1)
|
|||||||
This example shows how to sync two clients with the minimal amount of exchanged
|
This example shows how to sync two clients with the minimal amount of exchanged
|
||||||
data by computing only the differences using the state vector of the remote
|
data by computing only the differences using the state vector of the remote
|
||||||
client. Syncing clients using the state vector requires another roundtrip, but
|
client. Syncing clients using the state vector requires another roundtrip, but
|
||||||
can safe a lot of bandwidth.
|
can save a lot of bandwidth.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const stateVector1 = Y.encodeStateVector(ydoc1)
|
const stateVector1 = Y.encodeStateVector(ydoc1)
|
||||||
@@ -701,6 +709,30 @@ Y.applyUpdate(ydoc1, diff2)
|
|||||||
Y.applyUpdate(ydoc2, diff1)
|
Y.applyUpdate(ydoc2, diff1)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Example: Syncing clients without loading the Y.Doc
|
||||||
|
|
||||||
|
It is possible to sync clients and compute delta updates without loading the Yjs
|
||||||
|
document to memory. Yjs exposes an API to compute the differences directly on the
|
||||||
|
binary document updates.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// encode the current state as a binary buffer
|
||||||
|
let currentState1 = Y.encodeStateAsUpdate(ydoc1)
|
||||||
|
let currentState2 = Y.encodeStateAsUpdate(ydoc2)
|
||||||
|
// now we can continue syncing clients using state vectors without using the Y.Doc
|
||||||
|
ydoc1.destroy()
|
||||||
|
ydoc2.destroy()
|
||||||
|
|
||||||
|
const stateVector1 = Y.encodeStateVectorFromUpdate(currentState1)
|
||||||
|
const stateVector2 = Y.encodeStateVectorFromUpdate(currentState2)
|
||||||
|
const diff1 = Y.diffUpdate(currentState1, stateVector2)
|
||||||
|
const diff2 = Y.diffUpdate(currentState2, stateVector1)
|
||||||
|
|
||||||
|
// sync clients
|
||||||
|
currentState1 = Y.mergeUpdates([currentState1, diff2])
|
||||||
|
currentState1 = Y.mergeUpdates([currentState1, diff1])
|
||||||
|
```
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
<b><code>Y.applyUpdate(Y.Doc, update:Uint8Array, [transactionOrigin:any])</code></b>
|
<b><code>Y.applyUpdate(Y.Doc, update:Uint8Array, [transactionOrigin:any])</code></b>
|
||||||
<dd>
|
<dd>
|
||||||
@@ -717,22 +749,26 @@ differences to the update message.
|
|||||||
</dd>
|
</dd>
|
||||||
<b><code>Y.encodeStateVector(Y.Doc):Uint8Array</code></b>
|
<b><code>Y.encodeStateVector(Y.Doc):Uint8Array</code></b>
|
||||||
<dd>Computes the state vector and encodes it into an Uint8Array.</dd>
|
<dd>Computes the state vector and encodes it into an Uint8Array.</dd>
|
||||||
|
<b><code>Y.mergeUpdates(Array<Uint8Array>)</code></b>
|
||||||
|
<dd>
|
||||||
|
Merge several document updates into a single document update while removing
|
||||||
|
duplicate information. The merged document update is always smaller than
|
||||||
|
the separate updates because of the compressed encoding.
|
||||||
|
</dd>
|
||||||
|
<b><code>Y.encodeStateVectorFromUpdate(Uint8Array): Uint8Array</code></b>
|
||||||
|
<dd>
|
||||||
|
Computes the state vector from a document update and encodes it into an Uint8Array.
|
||||||
|
</dd>
|
||||||
|
<b><code>Y.diffUpdate(update: Uint8Array, stateVector: Uint8Array): Uint8Array</code></b>
|
||||||
|
<dd>
|
||||||
|
Encode the missing differences to another update message. This function works
|
||||||
|
similarly to <code>Y.encodeStateAsUpdate(ydoc, stateVector)</code> but works
|
||||||
|
on updates instead.
|
||||||
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
### Relative Positions
|
### Relative Positions
|
||||||
|
|
||||||
> This API is not stable yet
|
|
||||||
|
|
||||||
This feature is intended for managing selections / cursors. When working with
|
|
||||||
other users that manipulate the shared document, you can't trust that an index
|
|
||||||
position (an integer) will stay at the intended location. A *relative position*
|
|
||||||
is fixated to an element in the shared document and is not affected by remote
|
|
||||||
changes. I.e. given the document `"a|c"`, the relative position is attached to
|
|
||||||
`c`. When a remote user modifies the document by inserting a character before
|
|
||||||
the cursor, the cursor will stay attached to the character `c`. `insert(1,
|
|
||||||
'x')("a|c") = "ax|c"`. When the *relative position* is set to the end of the
|
|
||||||
document, it will stay attached to the end of the document.
|
|
||||||
|
|
||||||
#### Example: Transform to RelativePosition and back
|
#### Example: Transform to RelativePosition and back
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|||||||
54
package-lock.json
generated
54
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.5.0",
|
"version": "13.5.5",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -78,9 +78,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@rollup/plugin-node-resolve": {
|
"@rollup/plugin-node-resolve": {
|
||||||
"version": "11.1.1",
|
"version": "11.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz",
|
||||||
"integrity": "sha512-zlBXR4eRS+2m79TsUZWhsd0slrHUYdRx4JF+aVQm+MI0wsKdlpC2vlDVjmlGvtZY1vsefOT9w3JxvmWSBei+Lg==",
|
"integrity": "sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@rollup/pluginutils": "^3.1.0",
|
"@rollup/pluginutils": "^3.1.0",
|
||||||
@@ -92,12 +92,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"resolve": {
|
"resolve": {
|
||||||
"version": "1.19.0",
|
"version": "1.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
|
||||||
"integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
|
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-core-module": "^2.1.0",
|
"is-core-module": "^2.2.0",
|
||||||
"path-parse": "^1.0.6"
|
"path-parse": "^1.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,9 +135,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "14.14.25",
|
"version": "14.14.31",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz",
|
||||||
"integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==",
|
"integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/resolve": {
|
"@types/resolve": {
|
||||||
@@ -1479,9 +1479,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"isomorphic.js": {
|
"isomorphic.js": {
|
||||||
"version": "0.1.5",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.0.tgz",
|
||||||
"integrity": "sha512-MkX5lLQApx/8IAIU31PKvpAZosnu2Jqcj1rM8TzxyA4CR96tv3SgMKQNTCxL58G7696Q57zd7ubHV/hTg+5fNA=="
|
"integrity": "sha512-U7JlVUbmVYvV1ddGLhc7pvoMNp+1uGwE3sCIGHEh9I4ldSJJ9LtLog5/H8GzZkwLjmbeEvIbQb50nsre57nDuw=="
|
||||||
},
|
},
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@@ -1598,11 +1598,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib0": {
|
"lib0": {
|
||||||
"version": "0.2.35",
|
"version": "0.2.38",
|
||||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.35.tgz",
|
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.38.tgz",
|
||||||
"integrity": "sha512-drVD3EscB3TIxiFzceuZg7oF5Z6I8a0KX+7FowNcAXOEsTej/hlHB+ElJ8Pa/Ge73Gy3fklSJtPxpNd2PajdWg==",
|
"integrity": "sha512-ZxnX62R5weebi8bH/Ipc6JBiQIsiQ1D7p3r96zulSSu1byW6DDWSBeI8WC/W5UGtkZ80GktX3JNY2pqhNiXWGA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"isomorphic.js": "^0.1.3"
|
"isomorphic.js": "^0.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"linkify-it": {
|
"linkify-it": {
|
||||||
@@ -2430,9 +2430,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rollup": {
|
"rollup": {
|
||||||
"version": "2.38.5",
|
"version": "2.39.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.38.5.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.39.0.tgz",
|
||||||
"integrity": "sha512-VoWt8DysFGDVRGWuHTqZzT02J0ASgjVq/hPs9QcBOGMd7B+jfTr/iqMVEyOi901rE3xq+Deq66GzIT1yt7sGwQ==",
|
"integrity": "sha512-+WR3bttcq7zE+BntH09UxaW3bQo3vItuYeLsyk4dL2tuwbeSKJuvwiawyhEnvRdRgrII0Uzk00FpctHO/zB1kw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fsevents": "~2.3.1"
|
"fsevents": "~2.3.1"
|
||||||
@@ -2828,9 +2828,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "3.9.7",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz",
|
||||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
|
"integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uc.micro": {
|
"uc.micro": {
|
||||||
@@ -2940,9 +2940,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"y-protocols": {
|
"y-protocols": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.4.tgz",
|
||||||
"integrity": "sha512-2hSl0dqrD8Kph0SpvyakVYpKEnTLOLGIf7yvwmloQ4qS6RSvl6fUYHy6YocCvTvcd9MBuNeO4EqlmBcONJsvtw==",
|
"integrity": "sha512-5/Hd6DJ5Y2SlbqLIKq86BictdOS0iAcWJZCVop8MKqx0XWwA+BbMn4538n4Z0CGjFMUGnG1kGzagk3BKGz5SvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lib0": "^0.2.35"
|
"lib0": "^0.2.35"
|
||||||
|
|||||||
28
package.json
28
package.json
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.5.0",
|
"version": "13.5.5",
|
||||||
"description": "Shared Editing Library",
|
"description": "Shared Editing Library",
|
||||||
"main": "./dist/yjs.cjs",
|
"main": "./dist/yjs.cjs",
|
||||||
"module": "./dist/yjs.mjs",
|
"module": "./dist/yjs.mjs",
|
||||||
"unpkg": "./dist/yjs.mjs",
|
|
||||||
"types": "./dist/src/index.d.ts",
|
"types": "./dist/src/index.d.ts",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -12,19 +11,28 @@
|
|||||||
"url": "https://github.com/sponsors/dmonad"
|
"url": "https://github.com/sponsors/dmonad"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run dist && node ./dist/tests.cjs --repitition-time 50",
|
"test": "npm run dist && node ./dist/tests.cjs --repetition-time 50",
|
||||||
"test-extensive": "npm run lint && npm run dist && node ./dist/tests.cjs --production --repitition-time 10000",
|
"test-extensive": "npm run lint && npm run dist && node ./dist/tests.cjs --production --repetition-time 10000",
|
||||||
"dist": "rm -rf dist && rollup -c && tsc",
|
"dist": "rm -rf dist && rollup -c && tsc",
|
||||||
"watch": "rollup -wc",
|
"watch": "rollup -wc",
|
||||||
"lint": "markdownlint README.md && standard && tsc",
|
"lint": "markdownlint README.md && standard && tsc",
|
||||||
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.md --package ./package.json || true",
|
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.md --package ./package.json || true",
|
||||||
"serve-docs": "npm run docs && http-server ./docs/",
|
"serve-docs": "npm run docs && http-server ./docs/",
|
||||||
"preversion": "npm run lint && PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.cjs --repitition-time 1000 && test -e dist/src/index.d.ts && test -e dist/yjs.cjs && test -e dist/yjs.cjs",
|
"preversion": "npm run lint && PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.cjs --repetition-time 1000 && test -e dist/src/index.d.ts && test -e dist/yjs.cjs && test -e dist/yjs.cjs",
|
||||||
"debug": "concurrently 'http-server -o test.html' 'npm run watch'",
|
"debug": "concurrently 'http-server -o test.html' 'npm run watch'",
|
||||||
"trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.cjs",
|
"trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.cjs",
|
||||||
"trace-opt": "clear && rollup -c && node --trace-opt dist/test.cjs",
|
"trace-opt": "clear && rollup -c && node --trace-opt dist/test.cjs",
|
||||||
"postinstall": "node ./sponsor-y.js"
|
"postinstall": "node ./sponsor-y.js"
|
||||||
},
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/yjs.mjs",
|
||||||
|
"require": "./dist/yjs.cjs"
|
||||||
|
},
|
||||||
|
"./src/index.js": "./src/index.js",
|
||||||
|
"./tests/testHelper.js": "./tests/testHelper.js",
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/yjs.*",
|
"dist/yjs.*",
|
||||||
"dist/src",
|
"dist/src",
|
||||||
@@ -62,19 +70,19 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://docs.yjs.dev",
|
"homepage": "https://docs.yjs.dev",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lib0": "^0.2.35"
|
"lib0": "^0.2.38"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^17.0.0",
|
"@rollup/plugin-commonjs": "^17.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
"@rollup/plugin-node-resolve": "^11.2.0",
|
||||||
"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.6",
|
||||||
"markdownlint-cli": "^0.23.2",
|
"markdownlint-cli": "^0.23.2",
|
||||||
"rollup": "^2.36.1",
|
"rollup": "^2.39.0",
|
||||||
"standard": "^14.3.4",
|
"standard": "^14.3.4",
|
||||||
"tui-jsdoc-template": "^1.2.2",
|
"tui-jsdoc-template": "^1.2.2",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^4.1.5",
|
||||||
"y-protocols": "^1.0.2"
|
"y-protocols": "^1.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
sponsor-y.js
18
sponsor-y.js
@@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
const log = require('lib0/dist/logging.cjs')
|
try {
|
||||||
|
const log = require('lib0/dist/logging.cjs')
|
||||||
|
|
||||||
log.print()
|
log.print()
|
||||||
log.print(log.BOLD, log.GREEN, log.BOLD, 'Thank you for using Yjs ', log.RED, '❤\n')
|
log.print(log.BOLD, log.GREEN, log.BOLD, 'Thank you for using Yjs ', log.RED, '❤\n')
|
||||||
log.print(
|
log.print(
|
||||||
log.GREY,
|
log.GREY,
|
||||||
'The project has grown considerably in the past year. Too much for me to maintain\nin my spare time. Several companies built their products with Yjs.\nYet, this project receives very little funding. Yjs is far from done. I want to\ncreate more awesome extensions and work on the growing number of open issues.\n', log.BOLD, 'Dear user, the future of this project entirely depends on you.\n')
|
'The project has grown considerably in the past year. Too much for me to maintain\nin my spare time. Several companies built their products with Yjs.\nYet, this project receives very little funding. Yjs is far from done. I want to\ncreate more awesome extensions and work on the growing number of open issues.\n', log.BOLD, 'Dear user, the future of this project entirely depends on you.\n')
|
||||||
log.print(log.BLUE, log.BOLD, 'Please start funding the project now: https://github.com/sponsors/dmonad \n')
|
log.print(log.BLUE, log.BOLD, 'Please start funding the project now: https://github.com/sponsors/dmonad \n')
|
||||||
log.print(log.GREY, '(This message will be removed when I achieved my funding goal)\n\n')
|
log.print(log.GREY, '(This message will be removed when I achieved my funding goal)\n\n')
|
||||||
|
} catch (e) { }
|
||||||
|
|||||||
@@ -502,14 +502,6 @@ const deleteText = (transaction, currPos, length) => {
|
|||||||
* @typedef {Object} TextAttributes
|
* @typedef {Object} TextAttributes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} DeltaItem
|
|
||||||
* @property {number|undefined} DeltaItem.delete
|
|
||||||
* @property {number|undefined} DeltaItem.retain
|
|
||||||
* @property {string|undefined} DeltaItem.insert
|
|
||||||
* @property {Object<string,any>} DeltaItem.attributes
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event that describes the changes on a YText type.
|
* Event that describes the changes on a YText type.
|
||||||
*/
|
*/
|
||||||
@@ -521,10 +513,6 @@ export class YTextEvent extends YEvent {
|
|||||||
*/
|
*/
|
||||||
constructor (ytext, transaction, subs) {
|
constructor (ytext, transaction, subs) {
|
||||||
super(ytext, transaction)
|
super(ytext, transaction)
|
||||||
/**
|
|
||||||
* @type {Array<DeltaItem>|null}
|
|
||||||
*/
|
|
||||||
this._delta = null
|
|
||||||
/**
|
/**
|
||||||
* Whether the children changed.
|
* Whether the children changed.
|
||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
@@ -545,20 +533,41 @@ export class YTextEvent extends YEvent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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}>}}
|
||||||
|
*/
|
||||||
|
get changes () {
|
||||||
|
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}>}}
|
||||||
|
*/
|
||||||
|
const changes = {
|
||||||
|
keys: this.keys,
|
||||||
|
delta: this.delta,
|
||||||
|
added: new Set(),
|
||||||
|
deleted: new Set()
|
||||||
|
}
|
||||||
|
this._changes = changes
|
||||||
|
}
|
||||||
|
return /** @type {any} */ (this._changes)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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<DeltaItem>}
|
* @type {Array<{insert?:string, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
get delta () {
|
get delta () {
|
||||||
if (this._delta === null) {
|
if (this._delta === null) {
|
||||||
const y = /** @type {Doc} */ (this.target.doc)
|
const y = /** @type {Doc} */ (this.target.doc)
|
||||||
this._delta = []
|
/**
|
||||||
|
* @type {Array<{insert?:string, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||||
|
*/
|
||||||
|
const delta = []
|
||||||
transact(y, transaction => {
|
transact(y, transaction => {
|
||||||
const delta = /** @type {Array<DeltaItem>} */ (this._delta)
|
|
||||||
const currentAttributes = new Map() // saves all current attributes for insert
|
const currentAttributes = new Map() // saves all current attributes for insert
|
||||||
const oldAttributes = new Map()
|
const oldAttributes = new Map()
|
||||||
let item = this.target._start
|
let item = this.target._start
|
||||||
@@ -728,8 +737,9 @@ export class YTextEvent extends YEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
this._delta = delta
|
||||||
}
|
}
|
||||||
return this._delta
|
return /** @type {any} */ (this._delta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,6 +815,7 @@ export class YText extends AbstractType {
|
|||||||
super._callObserver(transaction, parentSubs)
|
super._callObserver(transaction, parentSubs)
|
||||||
const event = new YTextEvent(this, transaction, parentSubs)
|
const event = new YTextEvent(this, transaction, parentSubs)
|
||||||
const doc = transaction.doc
|
const doc = transaction.doc
|
||||||
|
callTypeObservers(this, transaction, event)
|
||||||
// If a remote change happened, we try to cleanup potential formatting duplicates.
|
// If a remote change happened, we try to cleanup potential formatting duplicates.
|
||||||
if (!transaction.local) {
|
if (!transaction.local) {
|
||||||
// check if another formatting item was inserted
|
// check if another formatting item was inserted
|
||||||
@@ -853,7 +864,6 @@ export class YText extends AbstractType {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
callTypeObservers(this, transaction, event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export class YXmlTreeWalker {
|
|||||||
* @type {Item|null}
|
* @type {Item|null}
|
||||||
*/
|
*/
|
||||||
let n = this._currentNode
|
let n = this._currentNode
|
||||||
let type = /** @type {any} */ (n.content).type
|
let type = n && n.content && /** @type {any} */ (n.content).type
|
||||||
if (n !== null && (!this._firstCall || n.deleted || !this._filter(type))) { // if first call, we check if we can use the first item
|
if (n !== null && (!this._firstCall || n.deleted || !this._filter(type))) { // if first call, we check if we can use the first item
|
||||||
do {
|
do {
|
||||||
type = /** @type {any} */ (n.content).type
|
type = /** @type {any} */ (n.content).type
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import {
|
|||||||
transact,
|
transact,
|
||||||
createID,
|
createID,
|
||||||
redoItem,
|
redoItem,
|
||||||
iterateStructs,
|
|
||||||
isParentOf,
|
isParentOf,
|
||||||
followRedone,
|
followRedone,
|
||||||
getItemCleanStart,
|
getItemCleanStart,
|
||||||
getState,
|
isDeleted,
|
||||||
|
addToDeleteSet,
|
||||||
Transaction, Doc, Item, GC, DeleteSet, AbstractType, YEvent // eslint-disable-line
|
Transaction, Doc, Item, GC, DeleteSet, AbstractType, YEvent // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@@ -18,14 +18,12 @@ import { Observable } from 'lib0/observable.js'
|
|||||||
|
|
||||||
class StackItem {
|
class StackItem {
|
||||||
/**
|
/**
|
||||||
* @param {DeleteSet} ds
|
* @param {DeleteSet} deletions
|
||||||
* @param {Map<number,number>} beforeState
|
* @param {DeleteSet} insertions
|
||||||
* @param {Map<number,number>} afterState
|
|
||||||
*/
|
*/
|
||||||
constructor (ds, beforeState, afterState) {
|
constructor (deletions, insertions) {
|
||||||
this.ds = ds
|
this.insertions = insertions
|
||||||
this.beforeState = beforeState
|
this.deletions = deletions
|
||||||
this.afterState = afterState
|
|
||||||
/**
|
/**
|
||||||
* Use this to save and restore metadata like selection range
|
* Use this to save and restore metadata like selection range
|
||||||
*/
|
*/
|
||||||
@@ -65,48 +63,26 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
*/
|
*/
|
||||||
const itemsToDelete = []
|
const itemsToDelete = []
|
||||||
let performedChange = false
|
let performedChange = false
|
||||||
stackItem.afterState.forEach((endClock, client) => {
|
iterateDeletedStructs(transaction, stackItem.insertions, struct => {
|
||||||
const startClock = stackItem.beforeState.get(client) || 0
|
if (struct instanceof Item) {
|
||||||
const len = endClock - startClock
|
if (struct.redone !== null) {
|
||||||
// @todo iterateStructs should not need the structs parameter
|
let { item, diff } = followRedone(store, struct.id)
|
||||||
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
|
if (diff > 0) {
|
||||||
if (startClock !== endClock) {
|
item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + diff))
|
||||||
// make sure structs don't overlap with the range of created operations [stackItem.start, stackItem.start + stackItem.end)
|
|
||||||
// this must be executed before deleted structs are iterated.
|
|
||||||
getItemCleanStart(transaction, createID(client, startClock))
|
|
||||||
if (endClock < getState(doc.store, client)) {
|
|
||||||
getItemCleanStart(transaction, createID(client, endClock))
|
|
||||||
}
|
|
||||||
iterateStructs(transaction, structs, startClock, len, struct => {
|
|
||||||
if (struct instanceof Item) {
|
|
||||||
if (struct.redone !== null) {
|
|
||||||
let { item, diff } = followRedone(store, struct.id)
|
|
||||||
if (diff > 0) {
|
|
||||||
item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + diff))
|
|
||||||
}
|
|
||||||
if (item.length > len) {
|
|
||||||
getItemCleanStart(transaction, createID(item.id.client, endClock))
|
|
||||||
}
|
|
||||||
struct = item
|
|
||||||
}
|
|
||||||
if (!struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) {
|
|
||||||
itemsToDelete.push(struct)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
struct = item
|
||||||
|
}
|
||||||
|
if (!struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) {
|
||||||
|
itemsToDelete.push(struct)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
iterateDeletedStructs(transaction, stackItem.ds, struct => {
|
iterateDeletedStructs(transaction, stackItem.deletions, struct => {
|
||||||
const id = struct.id
|
|
||||||
const clock = id.clock
|
|
||||||
const client = id.client
|
|
||||||
const startClock = stackItem.beforeState.get(client) || 0
|
|
||||||
const endClock = stackItem.afterState.get(client) || 0
|
|
||||||
if (
|
if (
|
||||||
struct instanceof Item &&
|
struct instanceof Item &&
|
||||||
scope.some(type => isParentOf(type, struct)) &&
|
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.
|
// Never redo structs in stackItem.insertions because they were created and deleted in the same capture interval.
|
||||||
!(clock >= startClock && clock < endClock)
|
!isDeleted(stackItem.insertions, struct.id)
|
||||||
) {
|
) {
|
||||||
itemsToRedo.add(struct)
|
itemsToRedo.add(struct)
|
||||||
}
|
}
|
||||||
@@ -123,7 +99,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
|||||||
performedChange = true
|
performedChange = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = stackItem
|
result = performedChange ? stackItem : null
|
||||||
}
|
}
|
||||||
transaction.changed.forEach((subProps, type) => {
|
transaction.changed.forEach((subProps, type) => {
|
||||||
// destroy search marker if necessary
|
// destroy search marker if necessary
|
||||||
@@ -201,17 +177,23 @@ export class UndoManager extends Observable {
|
|||||||
// neither undoing nor redoing: delete redoStack
|
// neither undoing nor redoing: delete redoStack
|
||||||
this.redoStack = []
|
this.redoStack = []
|
||||||
}
|
}
|
||||||
const beforeState = transaction.beforeState
|
const insertions = new DeleteSet()
|
||||||
const afterState = transaction.afterState
|
transaction.afterState.forEach((endClock, client) => {
|
||||||
|
const startClock = transaction.beforeState.get(client) || 0
|
||||||
|
const len = endClock - startClock
|
||||||
|
if (len > 0) {
|
||||||
|
addToDeleteSet(insertions, client, startClock, len)
|
||||||
|
}
|
||||||
|
})
|
||||||
const now = time.getUnixTime()
|
const now = time.getUnixTime()
|
||||||
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.deletions = mergeDeleteSets([lastOp.deletions, transaction.deleteSet])
|
||||||
lastOp.afterState = afterState
|
lastOp.insertions = mergeDeleteSets([lastOp.insertions, insertions])
|
||||||
} else {
|
} else {
|
||||||
// create a new stack op
|
// create a new stack op
|
||||||
stack.push(new StackItem(transaction.deleteSet, beforeState, afterState))
|
stack.push(new StackItem(transaction.deleteSet, insertions))
|
||||||
}
|
}
|
||||||
if (!undoing && !redoing) {
|
if (!undoing && !redoing) {
|
||||||
this.lastChange = now
|
this.lastChange = now
|
||||||
@@ -232,7 +214,7 @@ export class UndoManager extends Observable {
|
|||||||
* @param {StackItem} stackItem
|
* @param {StackItem} stackItem
|
||||||
*/
|
*/
|
||||||
const clearItem = stackItem => {
|
const clearItem = stackItem => {
|
||||||
iterateDeletedStructs(transaction, stackItem.ds, item => {
|
iterateDeletedStructs(transaction, stackItem.deletions, 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, false)
|
keepItem(item, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ export class YEvent {
|
|||||||
* @type {Object|null}
|
* @type {Object|null}
|
||||||
*/
|
*/
|
||||||
this._changes = null
|
this._changes = null
|
||||||
|
/**
|
||||||
|
* @type {null | Map<string, { action: 'add' | 'update' | 'delete', oldValue: any, newValue: any }>}
|
||||||
|
*/
|
||||||
|
this._keys = null
|
||||||
|
/**
|
||||||
|
* @type {null | Array<{ insert?: string | Array<any>, retain?: number, delete?: number, attributes?: Object<string, any> }>}
|
||||||
|
*/
|
||||||
|
this._delta = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,6 +75,66 @@ export class YEvent {
|
|||||||
return isDeleted(this.transaction.deleteSet, struct.id)
|
return isDeleted(this.transaction.deleteSet, struct.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Map<string, { action: 'add' | 'update' | 'delete', oldValue: any, newValue: any }>}
|
||||||
|
*/
|
||||||
|
get keys () {
|
||||||
|
if (this._keys === null) {
|
||||||
|
const keys = new Map()
|
||||||
|
const target = this.target
|
||||||
|
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
|
||||||
|
changed.forEach(key => {
|
||||||
|
if (key !== null) {
|
||||||
|
const item = /** @type {Item} */ (target._map.get(key))
|
||||||
|
/**
|
||||||
|
* @type {'delete' | 'add' | 'update'}
|
||||||
|
*/
|
||||||
|
let action
|
||||||
|
let oldValue
|
||||||
|
if (this.adds(item)) {
|
||||||
|
let prev = item.left
|
||||||
|
while (prev !== null && this.adds(prev)) {
|
||||||
|
prev = prev.left
|
||||||
|
}
|
||||||
|
if (this.deletes(item)) {
|
||||||
|
if (prev !== null && this.deletes(prev)) {
|
||||||
|
action = 'delete'
|
||||||
|
oldValue = array.last(prev.content.getContent())
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (prev !== null && this.deletes(prev)) {
|
||||||
|
action = 'update'
|
||||||
|
oldValue = array.last(prev.content.getContent())
|
||||||
|
} else {
|
||||||
|
action = 'add'
|
||||||
|
oldValue = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.deletes(item)) {
|
||||||
|
action = 'delete'
|
||||||
|
oldValue = array.last(/** @type {Item} */ item.content.getContent())
|
||||||
|
} else {
|
||||||
|
return // nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys.set(key, { action, oldValue })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this._keys = keys
|
||||||
|
}
|
||||||
|
return this._keys
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<{insert?: string | Array<any>, retain?: number, delete?: number, attributes?: Object<string, any>}>}
|
||||||
|
*/
|
||||||
|
get delta () {
|
||||||
|
return this.changes.delta
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a struct is added by this event.
|
* Check if a struct is added by this event.
|
||||||
*
|
*
|
||||||
@@ -80,7 +148,7 @@ export class YEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert:Array<any>}|{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, delete?:number, retain?:number}>}}
|
||||||
*/
|
*/
|
||||||
get changes () {
|
get changes () {
|
||||||
let changes = this._changes
|
let changes = this._changes
|
||||||
@@ -92,12 +160,11 @@ export class YEvent {
|
|||||||
* @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
|
* @type {Array<{insert:Array<any>}|{delete:number}|{retain:number}>}
|
||||||
*/
|
*/
|
||||||
const delta = []
|
const delta = []
|
||||||
/**
|
|
||||||
* @type {Map<string,{ action: 'add' | 'update' | 'delete', oldValue: any}>}
|
|
||||||
*/
|
|
||||||
const keys = new Map()
|
|
||||||
changes = {
|
changes = {
|
||||||
added, deleted, delta, keys
|
added,
|
||||||
|
deleted,
|
||||||
|
delta,
|
||||||
|
keys: this.keys
|
||||||
}
|
}
|
||||||
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
|
const changed = /** @type Set<string|null> */ (this.transaction.changed.get(target))
|
||||||
if (changed.has(null)) {
|
if (changed.has(null)) {
|
||||||
@@ -141,46 +208,6 @@ export class YEvent {
|
|||||||
packOp()
|
packOp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changed.forEach(key => {
|
|
||||||
if (key !== null) {
|
|
||||||
const item = /** @type {Item} */ (target._map.get(key))
|
|
||||||
/**
|
|
||||||
* @type {'delete' | 'add' | 'update'}
|
|
||||||
*/
|
|
||||||
let action
|
|
||||||
let oldValue
|
|
||||||
if (this.adds(item)) {
|
|
||||||
let prev = item.left
|
|
||||||
while (prev !== null && this.adds(prev)) {
|
|
||||||
prev = prev.left
|
|
||||||
}
|
|
||||||
if (this.deletes(item)) {
|
|
||||||
if (prev !== null && this.deletes(prev)) {
|
|
||||||
action = 'delete'
|
|
||||||
oldValue = array.last(prev.content.getContent())
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (prev !== null && this.deletes(prev)) {
|
|
||||||
action = 'update'
|
|
||||||
oldValue = array.last(prev.content.getContent())
|
|
||||||
} else {
|
|
||||||
action = 'add'
|
|
||||||
oldValue = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.deletes(item)) {
|
|
||||||
action = 'delete'
|
|
||||||
oldValue = array.last(/** @type {Item} */ item.content.getContent())
|
|
||||||
} else {
|
|
||||||
return // nop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keys.set(key, { action, oldValue })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this._changes = changes
|
this._changes = changes
|
||||||
}
|
}
|
||||||
return /** @type {any} */ (changes)
|
return /** @type {any} */ (changes)
|
||||||
|
|||||||
@@ -270,3 +270,34 @@ export const testUndoDeleteFilter = tc => {
|
|||||||
array0.get(0)
|
array0.get(0)
|
||||||
t.assert(Array.from(array0.get(0).keys()).length === 1)
|
t.assert(Array.from(array0.get(0).keys()).length === 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This issue has been reported in https://discuss.yjs.dev/t/undomanager-with-external-updates/454/6
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testUndoUntilChangePerformed = tc => {
|
||||||
|
const doc = new Y.Doc()
|
||||||
|
const doc2 = new Y.Doc()
|
||||||
|
doc.on('update', update => Y.applyUpdate(doc2, update))
|
||||||
|
doc2.on('update', update => Y.applyUpdate(doc, update))
|
||||||
|
|
||||||
|
const yArray = doc.getArray('array')
|
||||||
|
const yArray2 = doc2.getArray('array')
|
||||||
|
const yMap = new Y.Map()
|
||||||
|
yMap.set('hello', 'world')
|
||||||
|
yArray.push([yMap])
|
||||||
|
const yMap2 = new Y.Map()
|
||||||
|
yMap2.set('key', 'value')
|
||||||
|
yArray.push([yMap2])
|
||||||
|
|
||||||
|
const undoManager = new Y.UndoManager([yArray], { trackedOrigins: new Set([doc.clientID]) })
|
||||||
|
const undoManager2 = new Y.UndoManager([doc2.get('array')], { trackedOrigins: new Set([doc2.clientID]) })
|
||||||
|
|
||||||
|
Y.transact(doc, () => yMap2.set('key', 'value modified'), doc.clientID)
|
||||||
|
undoManager.stopCapturing()
|
||||||
|
Y.transact(doc, () => yMap.set('hello', 'world modified'), doc.clientID)
|
||||||
|
Y.transact(doc2, () => yArray2.delete(0), doc2.clientID)
|
||||||
|
undoManager2.undo()
|
||||||
|
undoManager.undo()
|
||||||
|
t.compareStrings(yMap2.get('key'), 'value')
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,23 @@ import * as math from 'lib0/math.js'
|
|||||||
|
|
||||||
const { init, compare } = Y
|
const { init, compare } = Y
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testDeltaAfterConcurrentFormatting = tc => {
|
||||||
|
const { text0, text1, testConnector } = init(tc, { users: 2 })
|
||||||
|
text0.insert(0, 'abcde')
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
text0.format(0, 3, { bold: true })
|
||||||
|
text1.format(2, 2, { bold: true })
|
||||||
|
let delta = null
|
||||||
|
text1.observe(event => {
|
||||||
|
delta = event.delta
|
||||||
|
})
|
||||||
|
testConnector.flushAllMessages()
|
||||||
|
t.compare(delta, [])
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
@@ -368,6 +385,42 @@ export const testLargeFragmentedDocument = tc => {
|
|||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testIncrementalUpdatesPerformanceOnLargeFragmentedDocument = tc => {
|
||||||
|
const itemsToInsert = largeDocumentSize
|
||||||
|
const updates = /** @type {Array<Uint8Array>} */ ([])
|
||||||
|
;(() => {
|
||||||
|
const doc1 = new Y.Doc()
|
||||||
|
doc1.on('update', update => {
|
||||||
|
updates.push(update)
|
||||||
|
})
|
||||||
|
const text0 = doc1.getText('txt')
|
||||||
|
tryGc()
|
||||||
|
t.measureTime(`time to insert ${itemsToInsert} items`, () => {
|
||||||
|
doc1.transact(() => {
|
||||||
|
for (let i = 0; i < itemsToInsert; i++) {
|
||||||
|
text0.insert(0, '0')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
tryGc()
|
||||||
|
})()
|
||||||
|
;(() => {
|
||||||
|
t.measureTime(`time to merge ${itemsToInsert} updates (differential updates)`, () => {
|
||||||
|
Y.mergeUpdates(updates)
|
||||||
|
})
|
||||||
|
tryGc()
|
||||||
|
t.measureTime(`time to merge ${itemsToInsert} updates (ydoc updates)`, () => {
|
||||||
|
const ydoc = new Y.Doc()
|
||||||
|
updates.forEach(update => {
|
||||||
|
Y.applyUpdate(ydoc, update)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splitting surrogates can lead to invalid encoded documents.
|
* Splitting surrogates can lead to invalid encoded documents.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user