Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26a51bafc9 | ||
|
|
f40e09d156 | ||
|
|
81650bc8f6 | ||
|
|
c87caafeb6 | ||
|
|
195b26d90f | ||
|
|
7e0189ca84 | ||
|
|
192706f2a8 | ||
|
|
a4ce8ae07d | ||
|
|
e04a980af1 | ||
|
|
47d40eb6b0 | ||
|
|
fc4a39cc7d | ||
|
|
44e1fd9f14 | ||
|
|
02cc5a215f | ||
|
|
d1e8d50c43 | ||
|
|
18bb2d0719 | ||
|
|
45df311dd7 | ||
|
|
62888b4004 | ||
|
|
76c389dba0 | ||
|
|
78fa98c000 | ||
|
|
e9f9e08450 | ||
|
|
e3c59b0aa7 | ||
|
|
705dce7838 |
20
README.md
20
README.md
@@ -15,13 +15,15 @@ suited for even large documents.
|
|||||||
|
|
||||||
* Demos: [https://github.com/yjs/yjs-demos](https://github.com/yjs/yjs-demos)
|
* Demos: [https://github.com/yjs/yjs-demos](https://github.com/yjs/yjs-demos)
|
||||||
* Discuss: [https://discuss.yjs.dev](https://discuss.yjs.dev)
|
* Discuss: [https://discuss.yjs.dev](https://discuss.yjs.dev)
|
||||||
* Benchmarks:
|
* Benchmark Yjs vs. Automerge:
|
||||||
[https://github.com/dmonad/crdt-benchmarks](https://github.com/dmonad/crdt-benchmarks)
|
[https://github.com/dmonad/crdt-benchmarks](https://github.com/dmonad/crdt-benchmarks)
|
||||||
* Podcast [**"Yjs Deep Dive into real time collaborative editing solutions":**](https://www.tag1consulting.com/blog/deep-dive-real-time-collaborative-editing-solutions-tagteamtalk-001-0)
|
* Podcast [**"Yjs Deep Dive into real time collaborative editing solutions":**](https://www.tag1consulting.com/blog/deep-dive-real-time-collaborative-editing-solutions-tagteamtalk-001-0)
|
||||||
* Podcast [**"Google Docs-style editing in Gutenberg with the YJS framework":**](https://publishpress.com/blog/yjs/)
|
* Podcast [**"Google Docs-style editing in Gutenberg with the YJS framework":**](https://publishpress.com/blog/yjs/)
|
||||||
|
|
||||||
:construction_worker_woman: If you are looking for professional support to build
|
:construction_worker_woman: If you are looking for professional (paid) support to
|
||||||
collaborative or distributed applications ping us at <yjs@tag1consulting.com>.
|
build collaborative or distributed applications ping us at
|
||||||
|
<yjs@tag1consulting.com>. Otherwise you can find help on our
|
||||||
|
[discussion board](https://discuss.yjs.dev).
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@@ -38,12 +40,6 @@ collaborative or distributed applications ping us at <yjs@tag1consulting.com>.
|
|||||||
* [Miscellaneous](#Miscellaneous)
|
* [Miscellaneous](#Miscellaneous)
|
||||||
* [Typescript Declarations](#Typescript-Declarations)
|
* [Typescript Declarations](#Typescript-Declarations)
|
||||||
* [Yjs CRDT Algorithm](#Yjs-CRDT-Algorithm)
|
* [Yjs CRDT Algorithm](#Yjs-CRDT-Algorithm)
|
||||||
* [Evaluation](#Evaluation)
|
|
||||||
* [Existing shared editing libraries](#Exisisting-Javascript-Libraries)
|
|
||||||
* [CRDT Algorithms](#CRDT-Algorithms)
|
|
||||||
* [Comparison of CRDT with OT](#Comparing-CRDT-with-OT)
|
|
||||||
* [Comparison of CRDT Algorithms](#Comparing-CRDT-Algorithms)
|
|
||||||
* [Comparison of Yjs with other Implementations](#Comparing-Yjs-with-other-Implementations)
|
|
||||||
* [License and Author](#License-and-Author)
|
* [License and Author](#License-and-Author)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@@ -85,6 +81,12 @@ document private.
|
|||||||
A module that contains a simple websocket backend and a websocket client that
|
A module that contains a simple websocket backend and a websocket client that
|
||||||
connects to that backend. The backend can be extended to persist updates in a
|
connects to that backend. The backend can be extended to persist updates in a
|
||||||
leveldb database.
|
leveldb database.
|
||||||
|
</dd>
|
||||||
|
<dt><a href="http://github.com/yjs/y-indexeddb">y-indexeddb</a></dt>
|
||||||
|
<dd>
|
||||||
|
Efficiently persists document updates to the browsers indexeddb database.
|
||||||
|
The document is immediately available and only diffs need to be synced through the
|
||||||
|
network provider.
|
||||||
</dd>
|
</dd>
|
||||||
<dt><a href="http://github.com/yjs/y-dat">y-dat</a></dt>
|
<dt><a href="http://github.com/yjs/y-dat">y-dat</a></dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
|||||||
305
README.v12.md
305
README.v12.md
@@ -1,305 +0,0 @@
|
|||||||
|
|
||||||
# 
|
|
||||||
|
|
||||||
Yjs is a framework for offline-first p2p shared editing on structured data like
|
|
||||||
text, richtext, json, or XML. It is fairly easy to get started, as Yjs hides
|
|
||||||
most of the complexity of concurrent editing. For additional information, demos,
|
|
||||||
and tutorials visit [y-js.org](http://y-js.org/).
|
|
||||||
|
|
||||||
:warning: Checkout the [v13 docs](./README.md) for the upcoming release :warning:
|
|
||||||
|
|
||||||
### Extensions
|
|
||||||
Yjs only knows how to resolve conflicts on shared data. You have to choose a ..
|
|
||||||
|
|
||||||
* *Connector* - a communication protocol that propagates changes to the clients
|
|
||||||
* *Database* - a database to store your changes
|
|
||||||
* one or more *Types* - that represent the shared data
|
|
||||||
|
|
||||||
Connectors, Databases, and Types are available as modules that extend Yjs. Here
|
|
||||||
is a list of the modules we know of:
|
|
||||||
|
|
||||||
##### Connectors
|
|
||||||
|
|
||||||
|Name | Description |
|
|
||||||
|----------------|-----------------------------------|
|
|
||||||
|[webrtc](https://github.com/y-js/y-webrtc) | Propagate updates Browser2Browser via WebRTC|
|
|
||||||
|[websockets](https://github.com/y-js/y-websockets-client) | Set up [a central server](https://github.com/y-js/y-websockets-client), and connect to it via websockets |
|
|
||||||
|[xmpp](https://github.com/y-js/y-xmpp) | Propagate updates in a XMPP multi-user-chat room ([XEP-0045](http://xmpp.org/extensions/xep-0045.html))|
|
|
||||||
|[ipfs](https://github.com/ipfs-labs/y-ipfs-connector) | Connector for the [Interplanetary File System](https://ipfs.io/)!|
|
|
||||||
|[test](https://github.com/y-js/y-test) | A Connector for testing purposes. It is designed to simulate delays that happen in worst case scenarios|
|
|
||||||
|
|
||||||
##### Database adapters
|
|
||||||
|
|
||||||
|Name | Description |
|
|
||||||
|----------------|-----------------------------------|
|
|
||||||
|[memory](https://github.com/y-js/y-memory) | In-memory storage. |
|
|
||||||
|[indexeddb](https://github.com/y-js/y-indexeddb) | Offline storage for the browser |
|
|
||||||
|[leveldb](https://github.com/y-js/y-leveldb) | Persistent storage for node apps |
|
|
||||||
|
|
||||||
##### Types
|
|
||||||
|
|
||||||
| Name | Description |
|
|
||||||
|----------|-------------------|
|
|
||||||
|[map](https://github.com/y-js/y-map) | A shared Map implementation. Maps from text to any stringify-able object |
|
|
||||||
|[array](https://github.com/y-js/y-array) | A shared Array implementation |
|
|
||||||
|[xml](https://github.com/y-js/y-xml) | An implementation of the DOM. You can create a two way binding to Browser DOM objects |
|
|
||||||
|[text](https://github.com/y-js/y-text) | Collaborate on text. Supports two way binding to the [Ace Editor](https://ace.c9.io), [CodeMirror](https://codemirror.net/), [Monaco](https://github.com/Microsoft/monaco-editor), textareas, input elements, and HTML elements (e.g. <*h1*>, or <*p*>) |
|
|
||||||
|[richtext](https://github.com/y-js/y-richtext) | Collaborate on rich text. Supports two way binding to the [Quill Rich Text Editor](http://quilljs.com/)|
|
|
||||||
|
|
||||||
##### Other
|
|
||||||
|
|
||||||
| Name | Description |
|
|
||||||
|-----------|-------------------|
|
|
||||||
|[y-element](http://y-js.org/y-element/) | Yjs Polymer Element |
|
|
||||||
|
|
||||||
## Use it!
|
|
||||||
Install Yjs, and its modules with [bower](http://bower.io/), or
|
|
||||||
[npm](https://www.npmjs.org/package/yjs).
|
|
||||||
|
|
||||||
### Bower
|
|
||||||
|
|
||||||
```
|
|
||||||
bower install --save yjs y-array % add all y-* modules you want to use
|
|
||||||
```
|
|
||||||
You only need to include the `y.js` file. Yjs is able to automatically require
|
|
||||||
missing modules.
|
|
||||||
```
|
|
||||||
<script src="./bower_components/yjs/y.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### CDN
|
|
||||||
|
|
||||||
```
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/yjs@12/dist/y.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/y-array@10/dist/y-array.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/y-websockets-client@8/dist/y-websockets-client.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/y-memory@8/dist/y-memory.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/y-map@10/dist/y-map.js"></script>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/y-text@9/dist/y-text.js"></script>
|
|
||||||
// ..
|
|
||||||
// do the same for all modules you want to use
|
|
||||||
```
|
|
||||||
|
|
||||||
### Npm
|
|
||||||
|
|
||||||
```
|
|
||||||
npm install --save yjs % add all y-* modules you want to use
|
|
||||||
```
|
|
||||||
|
|
||||||
If you don't include via script tag, you have to explicitly include all modules!
|
|
||||||
(Same goes for other module systems)
|
|
||||||
```
|
|
||||||
var Y = require('yjs')
|
|
||||||
require('y-array')(Y) // add the y-array type to Yjs
|
|
||||||
require('y-websockets-client')(Y)
|
|
||||||
require('y-memory')(Y)
|
|
||||||
require('y-map')(Y)
|
|
||||||
require('y-text')(Y)
|
|
||||||
// ..
|
|
||||||
// do the same for all modules you want to use
|
|
||||||
```
|
|
||||||
|
|
||||||
### ES6 Syntax
|
|
||||||
|
|
||||||
```
|
|
||||||
import Y from 'yjs'
|
|
||||||
import yArray from 'y-array'
|
|
||||||
import yWebsocketsClient from 'y-webrtc'
|
|
||||||
import yMemory from 'y-memory'
|
|
||||||
import yMap from 'y-map'
|
|
||||||
import yText from 'y-text'
|
|
||||||
// ..
|
|
||||||
Y.extend(yArray, yWebsocketsClient, yMemory, yArray, yMap, yText /*, .. */)
|
|
||||||
```
|
|
||||||
|
|
||||||
# Text editing example
|
|
||||||
|
|
||||||
Install dependencies
|
|
||||||
```
|
|
||||||
bower i yjs y-memory y-webrtc y-array y-text
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is a simple example of a shared textarea
|
|
||||||
```HTML
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<script src="./bower_components/yjs/y.js"></script>
|
|
||||||
<!-- Yjs automatically includes all missing dependencies (browser only) -->
|
|
||||||
<script>
|
|
||||||
Y({
|
|
||||||
db: {
|
|
||||||
name: 'memory' // use memory database adapter.
|
|
||||||
// name: 'indexeddb' // use indexeddb database adapter instead for offline apps
|
|
||||||
},
|
|
||||||
connector: {
|
|
||||||
name: 'webrtc', // use webrtc connector
|
|
||||||
// name: 'websockets-client'
|
|
||||||
// name: 'xmpp'
|
|
||||||
room: 'my-room' // clients connecting to the same room share data
|
|
||||||
},
|
|
||||||
sourceDir: './bower_components', // location of the y-* modules (browser only)
|
|
||||||
share: {
|
|
||||||
textarea: 'Text' // y.share.textarea is of type y-text
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
// The Yjs instance `y` is available
|
|
||||||
// y.share.* contains the shared types
|
|
||||||
|
|
||||||
// Bind `y.share.textarea` to `<textarea/>`
|
|
||||||
y.share.textarea.bind(document.querySelector('textarea'))
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<textarea></textarea>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Get Help & Give Help
|
|
||||||
There are some friendly people on [](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) who are eager to help, and answer questions. Please join!
|
|
||||||
|
|
||||||
Report _any_ issues to the
|
|
||||||
[Github issue page](https://github.com/y-js/yjs/issues)! I try to fix them very
|
|
||||||
soon, if possible.
|
|
||||||
|
|
||||||
# API
|
|
||||||
|
|
||||||
### Y(options)
|
|
||||||
* Y.extend(module1, module2, ..)
|
|
||||||
* Add extensions to Y
|
|
||||||
* `Y.extend(require('y-webrtc'))` has the same semantics as
|
|
||||||
`require('y-webrtc')(Y)`
|
|
||||||
* options.db
|
|
||||||
* Will be forwarded to the database adapter. Specify the database adaper on
|
|
||||||
`options.db.name`.
|
|
||||||
* Have a look at the used database adapter repository to see all available
|
|
||||||
options.
|
|
||||||
* options.connector
|
|
||||||
* Will be forwarded to the connector adapter. Specify the connector adaper on
|
|
||||||
`options.connector.name`.
|
|
||||||
* All our connectors implement a `room` property. Clients that specify the
|
|
||||||
same room share the same data.
|
|
||||||
* All of our connectors specify an `url` property that defines the connection
|
|
||||||
endpoint of the used connector.
|
|
||||||
* All of our connectors also have a default connection endpoint that you can
|
|
||||||
use for development.
|
|
||||||
* Set `options.connector.generateUserId = true` in order to genenerate a
|
|
||||||
userid, instead of receiving one from the server. This way the `Y(..)` is
|
|
||||||
immediately going to be resolved, without waiting for any confirmation from
|
|
||||||
the server. Use with caution.
|
|
||||||
* Have a look at the used connector repository to see all available options.
|
|
||||||
* *Only if you know what you are doing:* Set
|
|
||||||
`options.connector.preferUntransformed = true` in order receive the shared
|
|
||||||
data untransformed. This is very efficient as the database content is simply
|
|
||||||
copied to this client. This does only work if this client receives content
|
|
||||||
from only one client.
|
|
||||||
* options.sourceDir (browser only)
|
|
||||||
* Path where all y-* modules are stored
|
|
||||||
* Defaults to `/bower_components`
|
|
||||||
* Not required when running on `nodejs` / `iojs`
|
|
||||||
* When using nodejs you need to manually extend Yjs:
|
|
||||||
```
|
|
||||||
var Y = require('yjs')
|
|
||||||
// you have to require a db, connector, and *all* types you use!
|
|
||||||
require('y-memory')(Y)
|
|
||||||
require('y-webrtc')(Y)
|
|
||||||
require('y-map')(Y)
|
|
||||||
// ..
|
|
||||||
```
|
|
||||||
* options.share
|
|
||||||
* Specify on `options.share[arbitraryName]` types that are shared among all
|
|
||||||
users.
|
|
||||||
* E.g. Specify `options.share[arbitraryName] = 'Array'` to require y-array and
|
|
||||||
create an y-array type on `y.share[arbitraryName]`.
|
|
||||||
* If userA doesn't specify `options.share[arbitraryName]`, it won't be
|
|
||||||
available for userA.
|
|
||||||
* If userB specifies `options.share[arbitraryName]`, it still won't be
|
|
||||||
available for userA. But all the updates are send from userB to userA.
|
|
||||||
* In contrast to y-map, types on `y.share.*` cannot be overwritten or deleted.
|
|
||||||
Instead, they are merged among all users. This feature is only available on
|
|
||||||
`y.share.*`
|
|
||||||
* Weird behavior: It is supported that two users specify different types with
|
|
||||||
the same property name.
|
|
||||||
E.g. userA specifies `options.share.x = 'Array'`, and userB specifies
|
|
||||||
`options.share.x = 'Text'`. But they only share data if they specified the
|
|
||||||
same type with the same property name
|
|
||||||
* options.type (browser only)
|
|
||||||
* Array of modules that Yjs needs to require, before instantiating a shared
|
|
||||||
type.
|
|
||||||
* By default Yjs requires the specified database adapter, the specified
|
|
||||||
connector, and all modules that are used in `options.share.*`
|
|
||||||
* Put all types here that you intend to use, but are not used in y.share.*
|
|
||||||
|
|
||||||
### Instantiated Y object (y)
|
|
||||||
`Y(options)` returns a promise that is fulfilled when..
|
|
||||||
|
|
||||||
* All modules are loaded
|
|
||||||
* The specified database adapter is loaded
|
|
||||||
* The specified connector is loaded
|
|
||||||
* All types are included
|
|
||||||
* The connector is initialized, and a unique user id is set (received from the
|
|
||||||
server)
|
|
||||||
* Note: When using y-indexeddb, a retrieved user id is stored on `localStorage`
|
|
||||||
|
|
||||||
The promise returns an instance of Y. We denote it with a lower case `y`.
|
|
||||||
|
|
||||||
* y.share.*
|
|
||||||
* Instances of the types you specified on options.share.*
|
|
||||||
* y.share.* can only be defined once when you instantiate Y!
|
|
||||||
* y.connector is an instance of Y.AbstractConnector
|
|
||||||
* y.connector.onUserEvent(function (event) {..})
|
|
||||||
* Observe user events (event.action is either 'userLeft' or 'userJoined')
|
|
||||||
* y.connector.whenSynced(listener)
|
|
||||||
* `listener` is executed when y synced with at least one user.
|
|
||||||
* `listener` is not called when no other user is in the same room.
|
|
||||||
* y-websockets-client aways waits to sync with the server
|
|
||||||
* y.connector.disconnect()
|
|
||||||
* Force to disconnect this instance from the other instances
|
|
||||||
* y.connector.connect()
|
|
||||||
* Try to reconnect to the other instances (needs to be supported by the
|
|
||||||
connector)
|
|
||||||
* Not supported by y-xmpp
|
|
||||||
* y.close()
|
|
||||||
* Destroy this object.
|
|
||||||
* Destroys all types (they will throw weird errors if you still use them)
|
|
||||||
* Disconnects from the other instances (via connector)
|
|
||||||
* Returns a promise
|
|
||||||
* y.destroy()
|
|
||||||
* calls y.close()
|
|
||||||
* Removes all data from the database
|
|
||||||
* Returns a promise
|
|
||||||
* y.db.stopGarbageCollector()
|
|
||||||
* Stop the garbage collector. Call y.db.garbageCollect() to continue garbage
|
|
||||||
collection
|
|
||||||
* y.db.gc :: Boolean
|
|
||||||
* Whether gc is turned on
|
|
||||||
* y.db.gcTimeout :: Number (defaults to 50000 ms)
|
|
||||||
* Time interval between two garbage collect cycles
|
|
||||||
* It is required that all instances exchanged all messages after two garbage
|
|
||||||
collect cycles (after 100000 ms per default)
|
|
||||||
* y.db.userId :: String
|
|
||||||
* The used user id for this client. **Never overwrite this**
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
Yjs uses [debug](https://github.com/visionmedia/debug) for logging. The flag
|
|
||||||
`y*` enables logging for all y-* components. You can selectively remove
|
|
||||||
components you are not interested in: E.g. The flag `y*,-y:connector-message`
|
|
||||||
will not log the long `y:connector-message` messages.
|
|
||||||
|
|
||||||
##### Enable logging in Node.js
|
|
||||||
```sh
|
|
||||||
DEBUG=y* node app.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Remove the colors in order to log to a file:
|
|
||||||
```sh
|
|
||||||
DEBUG_COLORS=0 DEBUG=y* node app.js > log
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Enable logging in the browser
|
|
||||||
```js
|
|
||||||
localStorage.debug = 'y*'
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
Yjs is licensed under the [MIT License](./LICENSE).
|
|
||||||
54
package-lock.json
generated
54
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.0.0",
|
"version": "13.0.5",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -98,9 +98,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"acorn": {
|
"acorn": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
|
||||||
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
|
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"acorn-jsx": {
|
"acorn-jsx": {
|
||||||
@@ -513,9 +513,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1332,9 +1332,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"isomorphic.js": {
|
"isomorphic.js": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.1.3.tgz",
|
||||||
"integrity": "sha512-Q85LNm6e50saL4EPWa0mWEYNUuV51n623gzPVNC1QiLGLmjONEtfFT3pa04OoUIYB7rzGJBpzO2iNPhV1Ib4hg=="
|
"integrity": "sha512-pabBRLDwYefSsNS+qCazJ97o7P5xDTrNoxSYFTM09JlZTxPrOEPGKekwqUy3/Np4C4PHnVUXHYsZPOix0jELsA=="
|
||||||
},
|
},
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@@ -1439,11 +1439,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib0": {
|
"lib0": {
|
||||||
"version": "0.2.7",
|
"version": "0.2.22",
|
||||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.22.tgz",
|
||||||
"integrity": "sha512-25ojhgEondpayWt2mgVTKels6R8viO27AetFgvdOk+WWGVah+q2pXouQ6dGj/gtxNwCDD2ih2B0LbDovNEtCrg==",
|
"integrity": "sha512-SFzX7/SGgrOK6VABQugczhAwaaZLW1VcrE9xG+cVG1+AMQWmcu/7SZaJq0KORnfHr1xK4P6JUBWfoxSCwBcgLA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"isomorphic.js": "^0.1.1"
|
"isomorphic.js": "^0.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"linkify-it": {
|
"linkify-it": {
|
||||||
@@ -2135,9 +2135,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"strip-json-comments": {
|
"strip-json-comments": {
|
||||||
@@ -2291,9 +2291,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rollup": {
|
"rollup": {
|
||||||
"version": "1.29.1",
|
"version": "1.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-1.30.0.tgz",
|
||||||
"integrity": "sha512-dGQ+b9d1FOX/gluiggTAVnTvzQZUEkCi/TwZcax7ujugVRHs0nkYJlV9U4hsifGEMojnO+jvEML2CJQ6qXgbHA==",
|
"integrity": "sha512-ANcmfaSQwpcJtZUTA0ZMNBtFcQ1B4A5FldlNqEK0WdWm9sHSKu93ffa2KV1ux8HA/yKIV/ZARV28m7rNdXJgEw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/estree": "*",
|
"@types/estree": "*",
|
||||||
@@ -2473,9 +2473,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2783,12 +2783,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"y-protocols": {
|
"y-protocols": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.2.3.tgz",
|
||||||
"integrity": "sha512-B9MCxMqLZCziLmQFlrXVN7MbIhXzF9bdwePcHzlVFIEHxnEvYIqAQYj4FHS376LBgfcjTUs7T614D+w0bpcJLA==",
|
"integrity": "sha512-mJ838iW7XgMQqlv+9DtH7QyLqflZoy/VvaUWRIpwawee4mQiFJcEXazCmSYUHEbXIUuVNNc70FnuNSMWDC5vKQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"lib0": "^0.2.3"
|
"lib0": "^0.2.20"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -1,21 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.0.0",
|
"version": "13.0.5",
|
||||||
"description": "Shared Editing Library",
|
"description": "Shared Editing Library",
|
||||||
"main": "./dist/yjs.cjs",
|
"main": "./dist/yjs.cjs",
|
||||||
"module": "./src/index.js",
|
"module": "./dist/yjs.mjs",
|
||||||
"types": "./dist/src/index.d.ts",
|
"types": "./dist/src/index.d.ts",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run dist && node ./dist/tests.cjs --repitition-time 50",
|
"test": "npm run dist && node ./dist/tests.cjs --repitition-time 50",
|
||||||
"test-exhaustive": "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 --repitition-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",
|
"preversion": "npm run lint && PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.cjs --repitition-time 1000",
|
||||||
"postversion": "git push && git push --tags",
|
|
||||||
"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"
|
||||||
@@ -57,7 +56,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://yjs.dev",
|
"homepage": "https://yjs.dev",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lib0": "^0.2.7"
|
"lib0": "^0.2.22"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^11.0.1",
|
"@rollup/plugin-commonjs": "^11.0.1",
|
||||||
@@ -66,11 +65,11 @@
|
|||||||
"http-server": "^0.12.1",
|
"http-server": "^0.12.1",
|
||||||
"jsdoc": "^3.6.3",
|
"jsdoc": "^3.6.3",
|
||||||
"markdownlint-cli": "^0.19.0",
|
"markdownlint-cli": "^0.19.0",
|
||||||
"rollup": "^1.29.1",
|
"rollup": "^1.30.0",
|
||||||
"rollup-cli": "^1.0.9",
|
"rollup-cli": "^1.0.9",
|
||||||
"standard": "^14.0.0",
|
"standard": "^14.0.0",
|
||||||
"tui-jsdoc-template": "^1.2.2",
|
"tui-jsdoc-template": "^1.2.2",
|
||||||
"typescript": "^3.7.5",
|
"typescript": "^3.7.5",
|
||||||
"y-protocols": "^0.2.0"
|
"y-protocols": "^0.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,15 @@ export default [{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
external: id => /^lib0\//.test(id)
|
external: id => /^lib0\//.test(id)
|
||||||
|
}, {
|
||||||
|
input: './src/index.js',
|
||||||
|
output: {
|
||||||
|
name: 'Y',
|
||||||
|
file: 'dist/yjs.mjs',
|
||||||
|
format: 'esm',
|
||||||
|
sourcemap: true
|
||||||
|
},
|
||||||
|
external: id => /^lib0\//.test(id)
|
||||||
}, {
|
}, {
|
||||||
input: './tests/index.js',
|
input: './tests/index.js',
|
||||||
output: {
|
output: {
|
||||||
@@ -62,7 +71,6 @@ export default [{
|
|||||||
plugins: [
|
plugins: [
|
||||||
debugResolve,
|
debugResolve,
|
||||||
nodeResolve({
|
nodeResolve({
|
||||||
sourcemap: true,
|
|
||||||
mainFields: ['module', 'browser', 'main']
|
mainFields: ['module', 'browser', 'main']
|
||||||
}),
|
}),
|
||||||
commonjs()
|
commonjs()
|
||||||
@@ -78,7 +86,6 @@ export default [{
|
|||||||
plugins: [
|
plugins: [
|
||||||
debugResolve,
|
debugResolve,
|
||||||
nodeResolve({
|
nodeResolve({
|
||||||
sourcemap: true,
|
|
||||||
mainFields: ['module', 'main']
|
mainFields: ['module', 'main']
|
||||||
}),
|
}),
|
||||||
commonjs()
|
commonjs()
|
||||||
|
|||||||
@@ -55,5 +55,7 @@ export {
|
|||||||
isDeleted,
|
isDeleted,
|
||||||
isParentOf,
|
isParentOf,
|
||||||
equalSnapshots,
|
equalSnapshots,
|
||||||
PermanentUserData // @TODO experimental
|
PermanentUserData, // @TODO experimental
|
||||||
|
tryGc,
|
||||||
|
transact
|
||||||
} from './internals.js'
|
} from './internals.js'
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import {
|
|||||||
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
|
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
|
||||||
import * as error from 'lib0/error.js'
|
import * as error from 'lib0/error.js'
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
export class AbstractStruct {
|
export class AbstractStruct {
|
||||||
/**
|
/**
|
||||||
* @param {ID} id
|
* @param {ID} id
|
||||||
@@ -40,7 +37,6 @@ export class AbstractStruct {
|
|||||||
* @param {encoding.Encoder} encoder The encoder to write data to.
|
* @param {encoding.Encoder} encoder The encoder to write data to.
|
||||||
* @param {number} offset
|
* @param {number} offset
|
||||||
* @param {number} encodingRef
|
* @param {number} encodingRef
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
write (encoder, offset, encodingRef) {
|
write (encoder, offset, encodingRef) {
|
||||||
throw error.methodUnimplemented()
|
throw error.methodUnimplemented()
|
||||||
@@ -54,9 +50,6 @@ export class AbstractStruct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
export class AbstractStructRef {
|
export class AbstractStructRef {
|
||||||
/**
|
/**
|
||||||
* @param {ID} id
|
* @param {ID} id
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ import {
|
|||||||
import * as encoding from 'lib0/encoding.js'
|
import * as encoding from 'lib0/encoding.js'
|
||||||
import * as decoding from 'lib0/decoding.js'
|
import * as decoding from 'lib0/decoding.js'
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
export class ContentAny {
|
export class ContentAny {
|
||||||
/**
|
/**
|
||||||
* @param {Array<any>} arr
|
* @param {Array<any>} arr
|
||||||
@@ -101,8 +98,6 @@ export class ContentAny {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* @param {decoding.Decoder} decoder
|
* @param {decoding.Decoder} decoder
|
||||||
* @return {ContentAny}
|
* @return {ContentAny}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import * as decoding from 'lib0/decoding.js'
|
|||||||
import * as buffer from 'lib0/buffer.js'
|
import * as buffer from 'lib0/buffer.js'
|
||||||
import * as error from 'lib0/error.js'
|
import * as error from 'lib0/error.js'
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
export class ContentBinary {
|
export class ContentBinary {
|
||||||
/**
|
/**
|
||||||
* @param {Uint8Array} content
|
* @param {Uint8Array} content
|
||||||
@@ -92,8 +89,6 @@ export class ContentBinary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* @param {decoding.Decoder} decoder
|
* @param {decoding.Decoder} decoder
|
||||||
* @return {ContentBinary}
|
* @return {ContentBinary}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import {
|
|||||||
import * as encoding from 'lib0/encoding.js'
|
import * as encoding from 'lib0/encoding.js'
|
||||||
import * as decoding from 'lib0/decoding.js'
|
import * as decoding from 'lib0/decoding.js'
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
export class ContentDeleted {
|
export class ContentDeleted {
|
||||||
/**
|
/**
|
||||||
* @param {number} len
|
* @param {number} len
|
||||||
|
|||||||
@@ -95,8 +95,6 @@ export class ContentFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* @param {decoding.Decoder} decoder
|
* @param {decoding.Decoder} decoder
|
||||||
* @return {ContentFormat}
|
* @return {ContentFormat}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -68,10 +68,11 @@ export const followRedone = (store, id) => {
|
|||||||
* sending it to other peers
|
* sending it to other peers
|
||||||
*
|
*
|
||||||
* @param {Item|null} item
|
* @param {Item|null} item
|
||||||
|
* @param {boolean} keep
|
||||||
*/
|
*/
|
||||||
export const keepItem = item => {
|
export const keepItem = (item, keep) => {
|
||||||
while (item !== null && !item.keep) {
|
while (item !== null && item.keep !== keep) {
|
||||||
item.keep = true
|
item.keep = keep
|
||||||
item = item.parent._item
|
item = item.parent._item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +221,7 @@ export const redoItem = (transaction, item, redoitems) => {
|
|||||||
item.content.copy()
|
item.content.copy()
|
||||||
)
|
)
|
||||||
item.redone = redoneItem.id
|
item.redone = redoneItem.id
|
||||||
keepItem(redoneItem)
|
keepItem(redoneItem, true)
|
||||||
redoneItem.integrate(transaction)
|
redoneItem.integrate(transaction)
|
||||||
return redoneItem
|
return redoneItem
|
||||||
}
|
}
|
||||||
@@ -303,7 +304,6 @@ export class Item extends AbstractStruct {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
integrate (transaction) {
|
integrate (transaction) {
|
||||||
const store = transaction.doc.store
|
const store = transaction.doc.store
|
||||||
@@ -402,7 +402,6 @@ export class Item extends AbstractStruct {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next non-deleted item
|
* Returns the next non-deleted item
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
get next () {
|
get next () {
|
||||||
let n = this.right
|
let n = this.right
|
||||||
@@ -414,7 +413,6 @@ export class Item extends AbstractStruct {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the previous non-deleted item
|
* Returns the previous non-deleted item
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
get prev () {
|
get prev () {
|
||||||
let n = this.left
|
let n = this.left
|
||||||
@@ -485,8 +483,6 @@ export class Item extends AbstractStruct {
|
|||||||
/**
|
/**
|
||||||
* @param {StructStore} store
|
* @param {StructStore} store
|
||||||
* @param {boolean} parentGCd
|
* @param {boolean} parentGCd
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
gc (store, parentGCd) {
|
gc (store, parentGCd) {
|
||||||
if (!this.deleted) {
|
if (!this.deleted) {
|
||||||
@@ -508,8 +504,6 @@ export class Item extends AbstractStruct {
|
|||||||
*
|
*
|
||||||
* @param {encoding.Encoder} encoder The encoder to write data to.
|
* @param {encoding.Encoder} encoder The encoder to write data to.
|
||||||
* @param {number} offset
|
* @param {number} offset
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
write (encoder, offset) {
|
write (encoder, offset) {
|
||||||
const origin = offset > 0 ? createID(this.id.client, this.id.clock + offset - 1) : this.origin
|
const origin = offset > 0 ? createID(this.id.client, this.id.clock + offset - 1) : this.origin
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import * as encoding from 'lib0/encoding.js' // eslint-disable-line
|
|||||||
/**
|
/**
|
||||||
* Call event listeners with an event. This will also add an event to all
|
* Call event listeners with an event. This will also add an event to all
|
||||||
* parents (for `.observeDeep` handlers).
|
* parents (for `.observeDeep` handlers).
|
||||||
* @private
|
|
||||||
*
|
*
|
||||||
* @template EventType
|
* @template EventType
|
||||||
* @param {AbstractType<EventType>} type
|
* @param {AbstractType<EventType>} type
|
||||||
@@ -54,17 +53,14 @@ export class AbstractType {
|
|||||||
*/
|
*/
|
||||||
this._item = null
|
this._item = null
|
||||||
/**
|
/**
|
||||||
* @private
|
|
||||||
* @type {Map<string,Item>}
|
* @type {Map<string,Item>}
|
||||||
*/
|
*/
|
||||||
this._map = new Map()
|
this._map = new Map()
|
||||||
/**
|
/**
|
||||||
* @private
|
|
||||||
* @type {Item|null}
|
* @type {Item|null}
|
||||||
*/
|
*/
|
||||||
this._start = null
|
this._start = null
|
||||||
/**
|
/**
|
||||||
* @private
|
|
||||||
* @type {Doc|null}
|
* @type {Doc|null}
|
||||||
*/
|
*/
|
||||||
this.doc = null
|
this.doc = null
|
||||||
@@ -90,7 +86,6 @@ export class AbstractType {
|
|||||||
*
|
*
|
||||||
* @param {Doc} y The Yjs instance
|
* @param {Doc} y The Yjs instance
|
||||||
* @param {Item|null} item
|
* @param {Item|null} item
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_integrate (y, item) {
|
_integrate (y, item) {
|
||||||
this.doc = y
|
this.doc = y
|
||||||
@@ -99,7 +94,6 @@ export class AbstractType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {AbstractType<EventType>}
|
* @return {AbstractType<EventType>}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_copy () {
|
_copy () {
|
||||||
throw error.methodUnimplemented()
|
throw error.methodUnimplemented()
|
||||||
@@ -107,7 +101,6 @@ export class AbstractType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {encoding.Encoder} encoder
|
* @param {encoding.Encoder} encoder
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_write (encoder) { }
|
_write (encoder) { }
|
||||||
|
|
||||||
@@ -128,8 +121,6 @@ export class AbstractType {
|
|||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) { /* skip if no type is specified */ }
|
_callObserver (transaction, parentSubs) { /* skip if no type is specified */ }
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ export class YArray extends AbstractType {
|
|||||||
*
|
*
|
||||||
* @param {Doc} y The Yjs instance
|
* @param {Doc} y The Yjs instance
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_integrate (y, item) {
|
_integrate (y, item) {
|
||||||
super._integrate(y, item)
|
super._integrate(y, item)
|
||||||
@@ -83,8 +81,6 @@ export class YArray extends AbstractType {
|
|||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) {
|
_callObserver (transaction, parentSubs) {
|
||||||
callTypeObservers(this, transaction, new YArrayEvent(this, transaction))
|
callTypeObservers(this, transaction, new YArrayEvent(this, transaction))
|
||||||
@@ -200,7 +196,6 @@ export class YArray extends AbstractType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {encoding.Encoder} encoder
|
* @param {encoding.Encoder} encoder
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_write (encoder) {
|
_write (encoder) {
|
||||||
encoding.writeVarUint(encoder, YArrayRefID)
|
encoding.writeVarUint(encoder, YArrayRefID)
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ export class YMap extends AbstractType {
|
|||||||
*
|
*
|
||||||
* @param {Doc} y The Yjs instance
|
* @param {Doc} y The Yjs instance
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_integrate (y, item) {
|
_integrate (y, item) {
|
||||||
super._integrate(y, item)
|
super._integrate(y, item)
|
||||||
@@ -83,8 +81,6 @@ export class YMap extends AbstractType {
|
|||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) {
|
_callObserver (transaction, parentSubs) {
|
||||||
callTypeObservers(this, transaction, new YMapEvent(this, transaction, parentSubs))
|
callTypeObservers(this, transaction, new YMapEvent(this, transaction, parentSubs))
|
||||||
@@ -215,8 +211,6 @@ export class YMap extends AbstractType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {encoding.Encoder} encoder
|
* @param {encoding.Encoder} encoder
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_write (encoder) {
|
_write (encoder) {
|
||||||
encoding.writeVarUint(encoder, YMapRefID)
|
encoding.writeVarUint(encoder, YMapRefID)
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ const insertText = (transaction, parent, left, right, currentAttributes, text, a
|
|||||||
left = insertPos.left
|
left = insertPos.left
|
||||||
right = insertPos.right
|
right = insertPos.right
|
||||||
// insert content
|
// insert content
|
||||||
const content = text.constructor === String ? new ContentString(text) : new ContentEmbed(text)
|
const content = text.constructor === String ? new ContentString(/** @type {string} */ (text)) : new ContentEmbed(text)
|
||||||
left = new Item(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, content)
|
left = new Item(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, content)
|
||||||
left.integrate(transaction)
|
left.integrate(transaction)
|
||||||
return insertNegatedAttributes(transaction, parent, left, insertPos.right, insertPos.negatedAttributes)
|
return insertNegatedAttributes(transaction, parent, left, insertPos.right, insertPos.negatedAttributes)
|
||||||
@@ -400,7 +400,6 @@ export class YTextEvent extends YEvent {
|
|||||||
constructor (ytext, transaction) {
|
constructor (ytext, transaction) {
|
||||||
super(ytext, transaction)
|
super(ytext, transaction)
|
||||||
/**
|
/**
|
||||||
* @private
|
|
||||||
* @type {Array<DeltaItem>|null}
|
* @type {Array<DeltaItem>|null}
|
||||||
*/
|
*/
|
||||||
this._delta = null
|
this._delta = null
|
||||||
@@ -612,11 +611,15 @@ export class YText extends AbstractType {
|
|||||||
/**
|
/**
|
||||||
* Array of pending operations on this type
|
* Array of pending operations on this type
|
||||||
* @type {Array<function():void>?}
|
* @type {Array<function():void>?}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this._pending = string !== undefined ? [() => this.insert(0, string)] : []
|
this._pending = string !== undefined ? [() => this.insert(0, string)] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of characters of this text type.
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
get length () {
|
get length () {
|
||||||
return this._length
|
return this._length
|
||||||
}
|
}
|
||||||
@@ -624,8 +627,6 @@ export class YText extends AbstractType {
|
|||||||
/**
|
/**
|
||||||
* @param {Doc} y
|
* @param {Doc} y
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_integrate (y, item) {
|
_integrate (y, item) {
|
||||||
super._integrate(y, item)
|
super._integrate(y, item)
|
||||||
@@ -646,8 +647,6 @@ export class YText extends AbstractType {
|
|||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_callObserver (transaction, parentSubs) {
|
_callObserver (transaction, parentSubs) {
|
||||||
callTypeObservers(this, transaction, new YTextEvent(this, transaction))
|
callTypeObservers(this, transaction, new YTextEvent(this, transaction))
|
||||||
@@ -795,12 +794,24 @@ export class YText extends AbstractType {
|
|||||||
str += /** @type {ContentString} */ (n.content).str
|
str += /** @type {ContentString} */ (n.content).str
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case ContentEmbed:
|
case ContentEmbed: {
|
||||||
packStr()
|
packStr()
|
||||||
ops.push({
|
/**
|
||||||
|
* @type {Object<string,any>}
|
||||||
|
*/
|
||||||
|
const op = {
|
||||||
insert: /** @type {ContentEmbed} */ (n.content).embed
|
insert: /** @type {ContentEmbed} */ (n.content).embed
|
||||||
})
|
}
|
||||||
|
if (currentAttributes.size > 0) {
|
||||||
|
const attrs = /** @type {Object<string,any>} */ ({})
|
||||||
|
op.attributes = attrs
|
||||||
|
for (const [key, value] of currentAttributes) {
|
||||||
|
attrs[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ops.push(op)
|
||||||
break
|
break
|
||||||
|
}
|
||||||
case ContentFormat:
|
case ContentFormat:
|
||||||
if (isVisible(n, snapshot)) {
|
if (isVisible(n, snapshot)) {
|
||||||
packStr()
|
packStr()
|
||||||
@@ -921,8 +932,6 @@ export class YText extends AbstractType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {encoding.Encoder} encoder
|
* @param {encoding.Encoder} encoder
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_write (encoder) {
|
_write (encoder) {
|
||||||
encoding.writeVarUint(encoder, YTextRefID)
|
encoding.writeVarUint(encoder, YTextRefID)
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
this.nodeName = nodeName
|
this.nodeName = nodeName
|
||||||
/**
|
/**
|
||||||
* @type {Map<string, any>|null}
|
* @type {Map<string, any>|null}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this._prelimAttrs = new Map()
|
this._prelimAttrs = new Map()
|
||||||
}
|
}
|
||||||
@@ -41,7 +40,6 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
*
|
*
|
||||||
* @param {Doc} y The Yjs instance
|
* @param {Doc} y The Yjs instance
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_integrate (y, item) {
|
_integrate (y, item) {
|
||||||
super._integrate(y, item)
|
super._integrate(y, item)
|
||||||
@@ -55,7 +53,6 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
* Creates an Item with the same effect as this Item (without position effect)
|
* Creates an Item with the same effect as this Item (without position effect)
|
||||||
*
|
*
|
||||||
* @return {YXmlElement}
|
* @return {YXmlElement}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_copy () {
|
_copy () {
|
||||||
return new YXmlElement(this.nodeName)
|
return new YXmlElement(this.nodeName)
|
||||||
@@ -184,7 +181,6 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
*
|
*
|
||||||
* This is called when this Item is sent to a remote peer.
|
* This is called when this Item is sent to a remote peer.
|
||||||
*
|
*
|
||||||
* @private
|
|
||||||
* @param {encoding.Encoder} encoder The encoder to write data to.
|
* @param {encoding.Encoder} encoder The encoder to write data to.
|
||||||
*/
|
*/
|
||||||
_write (encoder) {
|
_write (encoder) {
|
||||||
@@ -197,7 +193,6 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
* @param {decoding.Decoder} decoder
|
* @param {decoding.Decoder} decoder
|
||||||
* @return {YXmlElement}
|
* @return {YXmlElement}
|
||||||
*
|
*
|
||||||
* @private
|
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const readYXmlElement = decoder => new YXmlElement(decoding.readVarString(decoder))
|
export const readYXmlElement = decoder => new YXmlElement(decoding.readVarString(decoder))
|
||||||
|
|||||||
@@ -127,7 +127,6 @@ export class YXmlFragment extends AbstractType {
|
|||||||
super()
|
super()
|
||||||
/**
|
/**
|
||||||
* @type {Array<any>|null}
|
* @type {Array<any>|null}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this._prelimContent = []
|
this._prelimContent = []
|
||||||
}
|
}
|
||||||
@@ -141,7 +140,6 @@ export class YXmlFragment extends AbstractType {
|
|||||||
*
|
*
|
||||||
* @param {Doc} y The Yjs instance
|
* @param {Doc} y The Yjs instance
|
||||||
* @param {Item} item
|
* @param {Item} item
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_integrate (y, item) {
|
_integrate (y, item) {
|
||||||
super._integrate(y, item)
|
super._integrate(y, item)
|
||||||
@@ -224,7 +222,6 @@ export class YXmlFragment extends AbstractType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates YXmlEvent and calls observers.
|
* Creates YXmlEvent and calls observers.
|
||||||
* @private
|
|
||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
@@ -328,7 +325,6 @@ export class YXmlFragment extends AbstractType {
|
|||||||
*
|
*
|
||||||
* This is called when this Item is sent to a remote peer.
|
* This is called when this Item is sent to a remote peer.
|
||||||
*
|
*
|
||||||
* @private
|
|
||||||
* @param {encoding.Encoder} encoder The encoder to write data to.
|
* @param {encoding.Encoder} encoder The encoder to write data to.
|
||||||
*/
|
*/
|
||||||
_write (encoder) {
|
_write (encoder) {
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ export class YXmlHook extends YMap {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Item with the same effect as this Item (without position effect)
|
* Creates an Item with the same effect as this Item (without position effect)
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_copy () {
|
_copy () {
|
||||||
return new YXmlHook(this.hookName)
|
return new YXmlHook(this.hookName)
|
||||||
@@ -69,8 +67,6 @@ export class YXmlHook extends YMap {
|
|||||||
* This is called when this Item is sent to a remote peer.
|
* This is called when this Item is sent to a remote peer.
|
||||||
*
|
*
|
||||||
* @param {encoding.Encoder} encoder The encoder to write data to.
|
* @param {encoding.Encoder} encoder The encoder to write data to.
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_write (encoder) {
|
_write (encoder) {
|
||||||
super._write(encoder)
|
super._write(encoder)
|
||||||
|
|||||||
@@ -79,8 +79,6 @@ export class YXmlText extends YText {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {encoding.Encoder} encoder
|
* @param {encoding.Encoder} encoder
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
_write (encoder) {
|
_write (encoder) {
|
||||||
encoding.writeVarUint(encoder, YXmlTextRefID)
|
encoding.writeVarUint(encoder, YXmlTextRefID)
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export class DeleteSet {
|
|||||||
constructor () {
|
constructor () {
|
||||||
/**
|
/**
|
||||||
* @type {Map<number,Array<DeleteItem>>}
|
* @type {Map<number,Array<DeleteItem>>}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this.clients = new Map()
|
this.clients = new Map()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,12 @@ export class Doc extends Observable {
|
|||||||
/**
|
/**
|
||||||
* @param {Object} conf configuration
|
* @param {Object} conf configuration
|
||||||
* @param {boolean} [conf.gc] Disable garbage collection (default: gc=true)
|
* @param {boolean} [conf.gc] Disable garbage collection (default: gc=true)
|
||||||
|
* @param {function(Item):boolean} [conf.gcFilter] Will be called before an Item is garbage collected. Return false to keep the Item.
|
||||||
*/
|
*/
|
||||||
constructor ({ gc = true } = {}) {
|
constructor ({ gc = true, gcFilter = () => true } = {}) {
|
||||||
super()
|
super()
|
||||||
this.gc = gc
|
this.gc = gc
|
||||||
|
this.gcFilter = gcFilter
|
||||||
this.clientID = random.uint32()
|
this.clientID = random.uint32()
|
||||||
/**
|
/**
|
||||||
* @type {Map<string, AbstractType<YEvent>>}
|
* @type {Map<string, AbstractType<YEvent>>}
|
||||||
@@ -37,12 +39,10 @@ export class Doc extends Observable {
|
|||||||
this.store = new StructStore()
|
this.store = new StructStore()
|
||||||
/**
|
/**
|
||||||
* @type {Transaction | null}
|
* @type {Transaction | null}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this._transaction = null
|
this._transaction = null
|
||||||
/**
|
/**
|
||||||
* @type {Array<Transaction>}
|
* @type {Array<Transaction>}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this._transactionCleanups = []
|
this._transactionCleanups = []
|
||||||
}
|
}
|
||||||
@@ -103,6 +103,7 @@ export class Doc extends Observable {
|
|||||||
t._map = type._map
|
t._map = type._map
|
||||||
type._map.forEach(/** @param {Item?} n */ n => {
|
type._map.forEach(/** @param {Item?} n */ n => {
|
||||||
for (; n !== null; n = n.left) {
|
for (; n !== null; n = n.left) {
|
||||||
|
// @ts-ignore
|
||||||
n.parent = t
|
n.parent = t
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -168,8 +169,6 @@ export class Doc extends Observable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit `destroy` event and unregister all event handlers.
|
* Emit `destroy` event and unregister all event handlers.
|
||||||
*
|
|
||||||
* @protected
|
|
||||||
*/
|
*/
|
||||||
destroy () {
|
destroy () {
|
||||||
this.emit('destroyed', [true])
|
this.emit('destroyed', [true])
|
||||||
|
|||||||
@@ -28,13 +28,11 @@ export class Snapshot {
|
|||||||
constructor (ds, sv) {
|
constructor (ds, sv) {
|
||||||
/**
|
/**
|
||||||
* @type {DeleteSet}
|
* @type {DeleteSet}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this.ds = ds
|
this.ds = ds
|
||||||
/**
|
/**
|
||||||
* State Map
|
* State Map
|
||||||
* @type {Map<number,number>}
|
* @type {Map<number,number>}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this.sv = sv
|
this.sv = sv
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export class StructStore {
|
|||||||
constructor () {
|
constructor () {
|
||||||
/**
|
/**
|
||||||
* @type {Map<number,Array<GC|Item>>}
|
* @type {Map<number,Array<GC|Item>>}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this.clients = new Map()
|
this.clients = new Map()
|
||||||
/**
|
/**
|
||||||
@@ -23,19 +22,16 @@ export class StructStore {
|
|||||||
* slow in Chrome for arrays with more than 100k elements
|
* slow in Chrome for arrays with more than 100k elements
|
||||||
* @see tryResumePendingStructRefs
|
* @see tryResumePendingStructRefs
|
||||||
* @type {Map<number,{i:number,refs:Array<GCRef|ItemRef>}>}
|
* @type {Map<number,{i:number,refs:Array<GCRef|ItemRef>}>}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this.pendingClientsStructRefs = new Map()
|
this.pendingClientsStructRefs = new Map()
|
||||||
/**
|
/**
|
||||||
* Stack of pending structs waiting for struct dependencies
|
* Stack of pending structs waiting for struct dependencies
|
||||||
* Maximum length of stack is structReaders.size
|
* Maximum length of stack is structReaders.size
|
||||||
* @type {Array<GCRef|ItemRef>}
|
* @type {Array<GCRef|ItemRef>}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this.pendingStack = []
|
this.pendingStack = []
|
||||||
/**
|
/**
|
||||||
* @type {Array<decoding.Decoder>}
|
* @type {Array<decoding.Decoder>}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this.pendingDeleteReaders = []
|
this.pendingDeleteReaders = []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
findIndexSS,
|
findIndexSS,
|
||||||
callEventHandlerListeners,
|
callEventHandlerListeners,
|
||||||
Item,
|
Item,
|
||||||
ID, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line
|
StructStore, ID, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as encoding from 'lib0/encoding.js'
|
import * as encoding from 'lib0/encoding.js'
|
||||||
@@ -85,7 +85,6 @@ export class Transaction {
|
|||||||
this.changedParentTypes = new Map()
|
this.changedParentTypes = new Map()
|
||||||
/**
|
/**
|
||||||
* @type {Set<ID>}
|
* @type {Set<ID>}
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
this._mergeStructs = new Set()
|
this._mergeStructs = new Set()
|
||||||
/**
|
/**
|
||||||
@@ -145,6 +144,85 @@ export const addChangedTypeToTransaction = (transaction, type, parentSub) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<AbstractStruct>} structs
|
||||||
|
* @param {number} pos
|
||||||
|
*/
|
||||||
|
const tryToMergeWithLeft = (structs, pos) => {
|
||||||
|
const left = structs[pos - 1]
|
||||||
|
const right = structs[pos]
|
||||||
|
if (left.deleted === right.deleted && left.constructor === right.constructor) {
|
||||||
|
if (left.mergeWith(right)) {
|
||||||
|
structs.splice(pos, 1)
|
||||||
|
if (right instanceof Item && right.parentSub !== null && right.parent._map.get(right.parentSub) === right) {
|
||||||
|
right.parent._map.set(right.parentSub, /** @type {Item} */ (left))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {DeleteSet} ds
|
||||||
|
* @param {StructStore} store
|
||||||
|
* @param {function(Item):boolean} gcFilter
|
||||||
|
*/
|
||||||
|
const tryGcDeleteSet = (ds, store, gcFilter) => {
|
||||||
|
for (const [client, deleteItems] of ds.clients) {
|
||||||
|
const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(client))
|
||||||
|
for (let di = deleteItems.length - 1; di >= 0; di--) {
|
||||||
|
const deleteItem = deleteItems[di]
|
||||||
|
const endDeleteItemClock = deleteItem.clock + deleteItem.len
|
||||||
|
for (
|
||||||
|
let si = findIndexSS(structs, deleteItem.clock), struct = structs[si];
|
||||||
|
si < structs.length && struct.id.clock < endDeleteItemClock;
|
||||||
|
struct = structs[++si]
|
||||||
|
) {
|
||||||
|
const struct = structs[si]
|
||||||
|
if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (struct instanceof Item && struct.deleted && !struct.keep && gcFilter(struct)) {
|
||||||
|
struct.gc(store, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {DeleteSet} ds
|
||||||
|
* @param {StructStore} store
|
||||||
|
*/
|
||||||
|
const tryMergeDeleteSet = (ds, store) => {
|
||||||
|
// try to merge deleted / gc'd items
|
||||||
|
// merge from right to left for better efficiecy and so we don't miss any merge targets
|
||||||
|
for (const [client, deleteItems] of ds.clients) {
|
||||||
|
const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(client))
|
||||||
|
for (let di = deleteItems.length - 1; di >= 0; di--) {
|
||||||
|
const deleteItem = deleteItems[di]
|
||||||
|
// start with merging the item next to the last deleted item
|
||||||
|
const mostRightIndexToCheck = math.min(structs.length - 1, 1 + findIndexSS(structs, deleteItem.clock + deleteItem.len - 1))
|
||||||
|
for (
|
||||||
|
let si = mostRightIndexToCheck, struct = structs[si];
|
||||||
|
si > 0 && struct.id.clock >= deleteItem.clock;
|
||||||
|
struct = structs[--si]
|
||||||
|
) {
|
||||||
|
tryToMergeWithLeft(structs, si)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {DeleteSet} ds
|
||||||
|
* @param {StructStore} store
|
||||||
|
* @param {function(Item):boolean} gcFilter
|
||||||
|
*/
|
||||||
|
export const tryGc = (ds, store, gcFilter) => {
|
||||||
|
tryGcDeleteSet(ds, store, gcFilter)
|
||||||
|
tryMergeDeleteSet(ds, store)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array<Transaction>} transactionCleanups
|
* @param {Array<Transaction>} transactionCleanups
|
||||||
* @param {number} i
|
* @param {number} i
|
||||||
@@ -201,63 +279,12 @@ const cleanupTransactions = (transactionCleanups, i) => {
|
|||||||
})
|
})
|
||||||
callAll(fs, [])
|
callAll(fs, [])
|
||||||
} finally {
|
} finally {
|
||||||
/**
|
|
||||||
* @param {Array<AbstractStruct>} structs
|
|
||||||
* @param {number} pos
|
|
||||||
*/
|
|
||||||
const tryToMergeWithLeft = (structs, pos) => {
|
|
||||||
const left = structs[pos - 1]
|
|
||||||
const right = structs[pos]
|
|
||||||
if (left.deleted === right.deleted && left.constructor === right.constructor) {
|
|
||||||
if (left.mergeWith(right)) {
|
|
||||||
structs.splice(pos, 1)
|
|
||||||
if (right instanceof Item && right.parentSub !== null && right.parent._map.get(right.parentSub) === right) {
|
|
||||||
right.parent._map.set(right.parentSub, /** @type {Item} */ (left))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Replace deleted items with ItemDeleted / GC.
|
// Replace deleted items with ItemDeleted / GC.
|
||||||
// This is where content is actually remove from the Yjs Doc.
|
// This is where content is actually remove from the Yjs Doc.
|
||||||
if (doc.gc) {
|
if (doc.gc) {
|
||||||
for (const [client, deleteItems] of ds.clients) {
|
tryGcDeleteSet(ds, store, doc.gcFilter)
|
||||||
const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(client))
|
|
||||||
for (let di = deleteItems.length - 1; di >= 0; di--) {
|
|
||||||
const deleteItem = deleteItems[di]
|
|
||||||
const endDeleteItemClock = deleteItem.clock + deleteItem.len
|
|
||||||
for (
|
|
||||||
let si = findIndexSS(structs, deleteItem.clock), struct = structs[si];
|
|
||||||
si < structs.length && struct.id.clock < endDeleteItemClock;
|
|
||||||
struct = structs[++si]
|
|
||||||
) {
|
|
||||||
const struct = structs[si]
|
|
||||||
if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (struct instanceof Item && struct.deleted && !struct.keep) {
|
|
||||||
struct.gc(store, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// try to merge deleted / gc'd items
|
|
||||||
// merge from right to left for better efficiecy and so we don't miss any merge targets
|
|
||||||
for (const [client, deleteItems] of ds.clients) {
|
|
||||||
const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(client))
|
|
||||||
for (let di = deleteItems.length - 1; di >= 0; di--) {
|
|
||||||
const deleteItem = deleteItems[di]
|
|
||||||
// start with merging the item next to the last deleted item
|
|
||||||
const mostRightIndexToCheck = math.min(structs.length - 1, 1 + findIndexSS(structs, deleteItem.clock + deleteItem.len - 1))
|
|
||||||
for (
|
|
||||||
let si = mostRightIndexToCheck, struct = structs[si];
|
|
||||||
si > 0 && struct.id.clock >= deleteItem.clock;
|
|
||||||
struct = structs[--si]
|
|
||||||
) {
|
|
||||||
tryToMergeWithLeft(structs, si)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
tryMergeDeleteSet(ds, store)
|
||||||
|
|
||||||
// on all affected store.clients props, try to merge
|
// on all affected store.clients props, try to merge
|
||||||
for (const [client, clock] of transaction.afterState) {
|
for (const [client, clock] of transaction.afterState) {
|
||||||
@@ -310,7 +337,6 @@ const cleanupTransactions = (transactionCleanups, i) => {
|
|||||||
* @param {function(Transaction):void} f
|
* @param {function(Transaction):void} f
|
||||||
* @param {any} [origin=true]
|
* @param {any} [origin=true]
|
||||||
*
|
*
|
||||||
* @private
|
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const transact = (doc, f, origin = null, local = true) => {
|
export const transact = (doc, f, origin = null, local = true) => {
|
||||||
|
|||||||
@@ -199,13 +199,32 @@ export class UndoManager extends Observable {
|
|||||||
// make sure that deleted structs are not gc'd
|
// make sure that deleted structs are not gc'd
|
||||||
iterateDeletedStructs(transaction, transaction.deleteSet, /** @param {Item|GC} item */ item => {
|
iterateDeletedStructs(transaction, transaction.deleteSet, /** @param {Item|GC} item */ item => {
|
||||||
if (item instanceof Item && this.scope.some(type => isParentOf(type, item))) {
|
if (item instanceof Item && this.scope.some(type => isParentOf(type, item))) {
|
||||||
keepItem(item)
|
keepItem(item, true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.emit('stack-item-added', [{ stackItem: stack[stack.length - 1], origin: transaction.origin, type: undoing ? 'redo' : 'undo' }, this])
|
this.emit('stack-item-added', [{ stackItem: stack[stack.length - 1], origin: transaction.origin, type: undoing ? 'redo' : 'undo' }, this])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clear () {
|
||||||
|
this.doc.transact(transaction => {
|
||||||
|
/**
|
||||||
|
* @param {StackItem} stackItem
|
||||||
|
*/
|
||||||
|
const clearItem = stackItem => {
|
||||||
|
iterateDeletedStructs(transaction, stackItem.ds, item => {
|
||||||
|
if (item instanceof Item && this.scope.some(type => isParentOf(type, item))) {
|
||||||
|
keepItem(item, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.undoStack.forEach(clearItem)
|
||||||
|
this.redoStack.forEach(clearItem)
|
||||||
|
})
|
||||||
|
this.undoStack = []
|
||||||
|
this.redoStack = []
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UndoManager merges Undo-StackItem if they are created within time-gap
|
* UndoManager merges Undo-StackItem if they are created within time-gap
|
||||||
* smaller than `options.captureTimeout`. Call `um.stopCapturing()` so that the next
|
* smaller than `options.captureTimeout`. Call `um.stopCapturing()` so that the next
|
||||||
|
|||||||
@@ -158,3 +158,25 @@ export const testToJson = tc => {
|
|||||||
text0.insert(0, 'abc', { bold: true })
|
text0.insert(0, 'abc', { bold: true })
|
||||||
t.assert(text0.toJSON() === 'abc', 'toJSON returns the unformatted text')
|
t.assert(text0.toJSON() === 'abc', 'toJSON returns the unformatted text')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testToDeltaEmbedAttributes = tc => {
|
||||||
|
const { text0 } = init(tc, { users: 1 })
|
||||||
|
text0.insert(0, 'ab', { bold: true })
|
||||||
|
text0.insertEmbed(1, { image: 'imageSrc.png' }, { width: 100 })
|
||||||
|
const delta0 = text0.toDelta()
|
||||||
|
t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' }, attributes: { width: 100 } }, { insert: 'b', attributes: { bold: true } }])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testToDeltaEmbedNoAttributes = tc => {
|
||||||
|
const { text0 } = init(tc, { users: 1 })
|
||||||
|
text0.insert(0, 'ab', { bold: true })
|
||||||
|
text0.insertEmbed(1, { image: 'imageSrc.png' })
|
||||||
|
const delta0 = text0.toDelta()
|
||||||
|
t.compare(delta0, [{ insert: 'a', attributes: { bold: true } }, { insert: { image: 'imageSrc.png' } }, { insert: 'b', attributes: { bold: true } }], 'toDelta does not set attributes key when no attributes are present')
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,10 +39,7 @@
|
|||||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
"paths": {
|
"paths": {
|
||||||
"yjs": ["./src/index.js"],
|
"yjs": ["./src/index.js"]
|
||||||
"lib0/*": ["node_modules/lib0/*"],
|
|
||||||
"lib0/set.js": ["node_modules/lib0/set.js"],
|
|
||||||
"lib0/function.js": ["node_modules/lib0/function.js"]
|
|
||||||
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
@@ -60,9 +57,8 @@
|
|||||||
/* Experimental Options */
|
/* Experimental Options */
|
||||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
"maxNodeModuleJsDepth": 5,
|
// "maxNodeModuleJsDepth": 0,
|
||||||
// "types": ["./src/utils/typedefs.js"]
|
// "types": ["./src/utils/typedefs.js"]
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*.js", "./tests/**/*.js"],
|
"include": ["./src/**/*.js", "./tests/**/*.js"]
|
||||||
"exclude": ["../lib0/**/*", "node_modules/**/*", "dist", "dist/**/*.js"]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user