Compare commits

..

11 Commits

Author SHA1 Message Date
Kevin Jahns
4c87f9a021 13.0.6 2020-05-08 14:50:53 +02:00
Kevin Jahns
4b08c67e06 bump lib0 to fix critical encoding issue in safari 2020-05-08 14:49:50 +02:00
Kevin Jahns
9f5bc9ddfe change client id when duplicate content is detected 2020-05-03 16:10:58 +02:00
Kevin Jahns
b399ffa765 add gc information to API docs 2020-04-26 13:24:18 +02:00
Kevin Jahns
180f4667c1 Readme correction: UndoManager accepts options 2020-04-17 02:02:09 +02:00
Kevin Jahns
9455373611 Merge branch 'master' of github.com:yjs/yjs 2020-04-15 20:50:29 +02:00
Kevin Jahns
aa804d89c0 update now.sh links 2020-04-15 19:52:34 +02:00
Kevin Jahns
3ef51a5d1a run test-exhaustive 2020-04-03 12:11:25 +02:00
Kevin Jahns
e61089c659 npm ci before workflow start 2020-04-03 12:09:13 +02:00
Kevin Jahns
97625cf29b fix workflow 2020-04-03 12:05:43 +02:00
Kevin Jahns
a5dc6c27aa Setup github workflow 2020-04-03 12:02:37 +02:00
9 changed files with 91 additions and 24 deletions

31
.github/workflows/nodejs.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 13.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run lint
- run: npm run test-extensive
env:
CI: true

View File

@@ -52,13 +52,10 @@ are implemented in separate modules.
| Name | Cursors | Binding | Demo | | Name | Cursors | Binding | Demo |
|---|:-:|---|---| |---|:-:|---|---|
| [ProseMirror](https://prosemirror.net/)                                                   | ✔ | [y-prosemirror](http://github.com/yjs/y-prosemirror) | [demo](https://yjs-demos.now.sh/prosemirror/) | | [ProseMirror](https://prosemirror.net/)                                                   | ✔ | [y-prosemirror](http://github.com/yjs/y-prosemirror) | [demo](https://demos.yjs.dev/prosemirror/prosemirror.html) |
| [Quill](https://quilljs.com/) | ✔ | [y-quill](http://github.com/yjs/y-quill) | [demo](https://yjs-demos.now.sh/quill/) | | [Quill](https://quilljs.com/) | ✔ | [y-quill](http://github.com/yjs/y-quill) | [demo](https://demos.yjs.dev/quill/quill.html) |
| [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](http://github.com/yjs/y-codemirror) | [demo](https://yjs-demos.now.sh/codemirror/) | | [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](http://github.com/yjs/y-codemirror) | [demo](https://demos.yjs.dev/codemirror/codemirror.html) |
| [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](http://github.com/yjs/y-monaco) | [demo](https://yjs-demos.now.sh/monaco/) | | [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](http://github.com/yjs/y-monaco) | [demo](https://demos.yjs.dev/monaco/monaco.html) |
| [Ace](https://ace.c9.io/) | | [y-ace](http://github.com/yjs/y-ace) | [demo](https://yjs-demos.now.sh/ace/) |
| [Textarea](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) | | [y-textarea](http://github.com/yjs/y-textarea) | [demo](https://yjs-demos.now.sh/textarea/) |
| [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) | | [y-dom](http://github.com/yjs/y-dom) | [demo](https://yjs-demos.now.sh/dom/) |
### Providers ### Providers
@@ -466,6 +463,12 @@ const doc = new Y.Doc()
<dl> <dl>
<b><code>clientID</code></b> <b><code>clientID</code></b>
<dd>A unique id that identifies this client. (readonly)</dd> <dd>A unique id that identifies this client. (readonly)</dd>
<b><code>gc</code></b>
<dd>
Whether garbage collection is enabled on this doc instance. Set `doc.gc = false`
in order to disable gc and be able to restore old content. See https://github.com/yjs/yjs#yjs-crdt-algorithm
for more information about gc in Yjs.
</dd>
<b><code>transact(function(Transaction):void [, origin:any])</code></b> <b><code>transact(function(Transaction):void [, origin:any])</code></b>
<dd> <dd>
Every change on the shared document happens in a transaction. Observer calls and Every change on the shared document happens in a transaction. Observer calls and
@@ -653,8 +656,8 @@ ytext.toString() // => 'abc'
``` ```
<dl> <dl>
<b><code>constructor(scope:Y.AbstractType|Array&lt;Y.AbstractType&gt;, <b><code>constructor(scope:Y.AbstractType|Array&lt;Y.AbstractType&gt;
[[{captureTimeout:number,trackedOrigins:Set&lt;any&gt;,deleteFilter:function(item):boolean}]])</code></b> [, {captureTimeout:number,trackedOrigins:Set&lt;any&gt;,deleteFilter:function(item):boolean}])</code></b>
<dd>Accepts either single type as scope or an array of types.</dd> <dd>Accepts either single type as scope or an array of types.</dd>
<b><code>undo()</code></b> <b><code>undo()</code></b>
<dd></dd> <dd></dd>
@@ -694,28 +697,30 @@ StackItem won't be merged.
// without stopCapturing // without stopCapturing
ytext.insert(0, 'a') ytext.insert(0, 'a')
ytext.insert(1, 'b') ytext.insert(1, 'b')
um.undo() undoManager.undo()
ytext.toString() // => '' (note that 'ab' was removed) ytext.toString() // => '' (note that 'ab' was removed)
// with stopCapturing // with stopCapturing
ytext.insert(0, 'a') ytext.insert(0, 'a')
um.stopCapturing() undoManager.stopCapturing()
ytext.insert(0, 'b') ytext.insert(0, 'b')
um.undo() undoManager.undo()
ytext.toString() // => 'a' (note that only 'b' was removed) ytext.toString() // => 'a' (note that only 'b' was removed)
``` ```
#### Example: Specify tracked origins #### Example: Specify tracked origins
Every change on the shared document has an origin. If no origin was specified, Every change on the shared document has an origin. If no origin was specified,
it defaults to `null`. By specifying `trackedTransactionOrigins` you can it defaults to `null`. By specifying `trackedOrigins` you can
selectively specify which changes should be tracked by `UndoManager`. The selectively specify which changes should be tracked by `UndoManager`. The
UndoManager instance is always added to `trackedTransactionOrigins`. UndoManager instance is always added to `trackedOrigins`.
```js ```js
class CustomBinding {} class CustomBinding {}
const ytext = doc.getArray('array') const ytext = doc.getArray('array')
const undoManager = new Y.UndoManager(ytext, new Set([42, CustomBinding])) const undoManager = new Y.UndoManager(ytext, {
trackedOrigins: new Set([42, CustomBinding])
})
ytext.insert(0, 'abc') ytext.insert(0, 'abc')
undoManager.undo() undoManager.undo()
@@ -753,7 +758,9 @@ document. You can assign meta-information to Undo-/Redo-StackItems.
```js ```js
const ytext = doc.getArray('array') const ytext = doc.getArray('array')
const undoManager = new Y.UndoManager(ytext, new Set([42, CustomBinding])) const undoManager = new Y.UndoManager(ytext, {
trackedOrigins: new Set([42, CustomBinding])
})
undoManager.on('stack-item-added', event => { undoManager.on('stack-item-added', event => {
// save the current cursor location on the stack-item // save the current cursor location on the stack-item

8
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.5", "version": "13.0.6",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -1439,9 +1439,9 @@
} }
}, },
"lib0": { "lib0": {
"version": "0.2.22", "version": "0.2.26",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.22.tgz", "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.26.tgz",
"integrity": "sha512-SFzX7/SGgrOK6VABQugczhAwaaZLW1VcrE9xG+cVG1+AMQWmcu/7SZaJq0KORnfHr1xK4P6JUBWfoxSCwBcgLA==", "integrity": "sha512-DTf0VmFNi/eT+3Q+6rNHYdIAx69ROpvQkpnplpDoErW8NeRwjPwoIKjCF3rKebsMrQoxH4tFD1bvMQb4CUzcFg==",
"requires": { "requires": {
"isomorphic.js": "^0.1.3" "isomorphic.js": "^0.1.3"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.5", "version": "13.0.6",
"description": "Shared Editing Library", "description": "Shared Editing Library",
"main": "./dist/yjs.cjs", "main": "./dist/yjs.cjs",
"module": "./dist/yjs.mjs", "module": "./dist/yjs.mjs",
@@ -56,7 +56,7 @@
}, },
"homepage": "https://yjs.dev", "homepage": "https://yjs.dev",
"dependencies": { "dependencies": {
"lib0": "^0.2.22" "lib0": "^0.2.26"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^11.0.1", "@rollup/plugin-commonjs": "^11.0.1",

View File

@@ -47,6 +47,7 @@ export {
typeMapGetSnapshot, typeMapGetSnapshot,
iterateDeletedStructs, iterateDeletedStructs,
applyUpdate, applyUpdate,
readUpdate,
encodeStateAsUpdate, encodeStateAsUpdate,
encodeStateVector, encodeStateVector,
UndoManager, UndoManager,

View File

@@ -17,6 +17,8 @@ import { Observable } from 'lib0/observable.js'
import * as random from 'lib0/random.js' import * as random from 'lib0/random.js'
import * as map from 'lib0/map.js' import * as map from 'lib0/map.js'
export const generateNewClientId = random.uint32
/** /**
* A Yjs instance handles the state of shared data. * A Yjs instance handles the state of shared data.
* @extends Observable<string> * @extends Observable<string>
@@ -31,7 +33,7 @@ export class Doc extends Observable {
super() super()
this.gc = gc this.gc = gc
this.gcFilter = gcFilter this.gcFilter = gcFilter
this.clientID = random.uint32() this.clientID = generateNewClientId()
/** /**
* @type {Map<string, AbstractType<YEvent>>} * @type {Map<string, AbstractType<YEvent>>}
*/ */

View File

@@ -10,6 +10,7 @@ import {
findIndexSS, findIndexSS,
callEventHandlerListeners, callEventHandlerListeners,
Item, Item,
generateNewClientId,
StructStore, ID, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line StructStore, ID, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@@ -17,6 +18,7 @@ import * as encoding from 'lib0/encoding.js'
import * as map from 'lib0/map.js' import * as map from 'lib0/map.js'
import * as math from 'lib0/math.js' import * as math from 'lib0/math.js'
import * as set from 'lib0/set.js' import * as set from 'lib0/set.js'
import * as logging from 'lib0/logging.js'
import { callAll } from 'lib0/function.js' import { callAll } from 'lib0/function.js'
/** /**
@@ -313,6 +315,10 @@ const cleanupTransactions = (transactionCleanups, i) => {
tryToMergeWithLeft(structs, replacedStructPos) tryToMergeWithLeft(structs, replacedStructPos)
} }
} }
if (!transaction.local && transaction.afterState.get(doc.clientID) !== transaction.beforeState.get(doc.clientID)) {
doc.clientID = generateNewClientId()
logging.print(logging.ORANGE, logging.BOLD, '[yjs] ', logging.UNBOLD, logging.RED, 'Changed the client-id because another client seems to be using it.')
}
// @todo Merge all the transactions into one and provide send the data as a single update message // @todo Merge all the transactions into one and provide send the data as a single update message
doc.emit('afterTransactionCleanup', [transaction, doc]) doc.emit('afterTransactionCleanup', [transaction, doc])
if (doc._observers.has('update')) { if (doc._observers.has('update')) {

View File

@@ -0,0 +1,19 @@
import * as Y from '../src/index.js'
import * as t from 'lib0/testing.js'
/**
* Client id should be changed when an instance receives updates from another client using the same client id.
*
* @param {t.TestCase} tc
*/
export const testClientIdDuplicateChange = tc => {
const doc1 = new Y.Doc()
doc1.clientID = 0
const doc2 = new Y.Doc()
doc2.clientID = 0
t.assert(doc2.clientID === doc1.clientID)
doc1.getArray('a').insert(0, [1, 2])
Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1))
t.assert(doc2.clientID !== doc1.clientID)
}

View File

@@ -5,6 +5,7 @@ import * as text from './y-text.tests.js'
import * as xml from './y-xml.tests.js' import * as xml from './y-xml.tests.js'
import * as encoding from './encoding.tests.js' import * as encoding from './encoding.tests.js'
import * as undoredo from './undo-redo.tests.js' import * as undoredo from './undo-redo.tests.js'
import * as consistency from './consistency.tests.js'
import { runTests } from 'lib0/testing.js' import { runTests } from 'lib0/testing.js'
import { isBrowser, isNode } from 'lib0/environment.js' import { isBrowser, isNode } from 'lib0/environment.js'
@@ -14,7 +15,7 @@ if (isBrowser) {
log.createVConsole(document.body) log.createVConsole(document.body)
} }
runTests({ runTests({
map, array, text, xml, encoding, undoredo map, array, text, xml, consistency, encoding, undoredo
}).then(success => { }).then(success => {
/* istanbul ignore next */ /* istanbul ignore next */
if (isNode) { if (isNode) {