Compare commits

...

29 Commits

Author SHA1 Message Date
Kevin Jahns
3404d22d12 13.4.7 2020-12-12 21:41:08 +01:00
Kevin Jahns
d3b56702ad Merge pull request #267 from yjs/wishlist-259
Implements some features of wishlist #259
2020-12-12 21:38:45 +01:00
Kevin Jahns
d5e6c26420 Merge branch 'main' into wishlist-259 2020-12-12 21:36:45 +01:00
Kevin Jahns
e497f07f7a remove new pos api template 2020-12-12 21:33:14 +01:00
Kevin Jahns
510354d99f add github workflow 2020-12-12 21:22:55 +01:00
Kevin Jahns
c3342d0b34 Merge pull request #266 from yjs/circleci-project-setup
Add .circleci/config.yml
2020-12-12 21:19:41 +01:00
Kevin Jahns
45af21f31e Add .circleci/config.yml 2020-12-12 21:18:14 +01:00
Kevin Jahns
972d15dda5 Update Sponsors 2020-12-05 13:17:14 +01:00
Kevin Jahns
fdf2063943 13.4.6 2020-12-04 14:02:53 +01:00
Kevin Jahns
e81267d4df implement correct destroy event 2020-12-04 14:01:14 +01:00
Kevin Jahns
563c34f81a Update README.md 2020-12-01 15:50:58 +01:00
Kevin Jahns
ba713983e3 update sponsors 2020-12-01 15:41:45 +01:00
Kevin Jahns
bf2ee3680b 13.4.5 2020-11-21 19:28:56 +01:00
Kevin Jahns
b812a3dd6c Add getItem to the exports 2020-11-21 19:27:12 +01:00
Kevin Jahns
b3f5b50377 Merge branch 'wishlist-259' of github.com:yjs/yjs into wishlist-259 2020-11-16 12:40:27 +01:00
Kevin Jahns
7bcd4a828d Create new Pos API - #259 2020-11-16 12:40:18 +01:00
Kevin Jahns
cb705922b4 implement insertAfter - #259 2020-11-15 14:57:45 +01:00
Kevin Jahns
1ed58909d3 implement prev/nextSibling&firstChild & parent - #259 2020-11-14 13:33:43 +01:00
Kevin Jahns
0aca7bbefa implement attributes on Y.Text 2020-11-13 12:40:53 +01:00
Kevin Jahns
e1f0324840 call UndoManager pop-stack-item after transaction 2020-11-13 12:05:53 +01:00
Kevin Jahns
7bac783490 13.4.4 2020-11-08 13:09:49 +01:00
Kevin Jahns
1508c44f68 lint 2020-11-08 13:08:14 +01:00
Kevin Jahns
3dd843372f Merge pull request #254 from nornagon/array-from
add Y.Array.from
2020-11-08 02:01:48 +01:00
Kevin Jahns
d6be4d9391 Merge pull request #253 from lpmi-13/update_links
update http links, where possible, to https
2020-11-08 02:00:36 +01:00
Kevin Jahns
53f2344017 implement .clone, .slice, and yxml.get 2020-11-08 01:51:39 +01:00
Kevin Jahns
86f7631d1e 13.4.3 2020-11-04 00:37:24 +01:00
Kevin Jahns
3bb107504f fix superflous event happening in nested event system 2020-11-04 00:35:08 +01:00
Jeremy Rose
4c46ebfb45 add Y.Array.from 2020-11-01 10:01:04 -08:00
Adam Leskis
9d0d63ead7 update http links, where possible, to https
cattaz.io, unfortunately, is still only available over http, but I've raised an issue in the repo to enable https on github pages, which the site appears to be using.
2020-10-31 10:32:05 +00:00
22 changed files with 532 additions and 34 deletions

7
.circleci/config.yml Normal file
View File

@@ -0,0 +1,7 @@
version: 2.1
orbs:
node: circleci/node@3.0.0
workflows:
node-tests:
jobs:
- node/test

29
.github/workflows/node.js.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
# 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: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.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 build --if-present
- run: npm test

View File

@@ -31,29 +31,30 @@ I'm currently looking for sponsors that allow me to be less dependent on
contracting work. These awesome backers already fund further development of contracting work. These awesome backers already fund further development of
Yjs: Yjs:
[![Vincent Waller](https://github.com/vwall.png?size=60)](https://github.com/vwall) [![davidhq](https://github.com/davidhq.png?size=60)](https://github.com/davidhq)
[<img src="https://user-images.githubusercontent.com/5553757/83337333-a7bcb380-a2ba-11ea-837b-e404eb35d318.png" [![Ifiok Jr.](https://github.com/ifiokjr.png?size=60)](https://github.com/ifiokjr)
height="60px" />](https://input.com/) [![Burke Libbey](https://github.com/burke.png?size=60)](https://github.com/burke)
[![Duane Johnson](https://github.com/canadaduane.png?size=60)](https://github.com/canadaduane) [![Beni Cherniavsky-Paskin](https://github.com/cben.png?size=60)](https://github.com/cben)
[![Joe Reeve](https://github.com/ISNIT0.png?size=60)](https://github.com/ISNIT0) [![Tom Moor](https://github.com/tommoor.png?size=60)](https://github.com/tommoor)
[<img src="https://room.sh/img/icons/android-chrome-192x192.png" height="60px" />](https://room.sh/) [![Michael Meyers](https://github.com/michaelemeyers.png?size=60)](https://github.com/michaelemeyers)
[![Cristiano Benjamin](https://github.com/csbenjamin.png?size=60)](https://github.com/csbenjamin)
[![Braden](https://github.com/AdventureBeard.png?size=60)](https://github.com/AdventureBeard)
[![nimbuswebinc](https://nimbusweb.me/new-style-img/note-icon.svg)](https://github.com/nimbuswebinc)
[![JourneyApps](https://github.com/journeyapps.png?size=60)](https://github.com/journeyapps) [![JourneyApps](https://github.com/journeyapps.png?size=60)](https://github.com/journeyapps)
[![Adam Brunnmeier](https://github.com/adabru.png?size=60)](https://github.com/adabru) [![Adam Brunnmeier](https://github.com/adabru.png?size=60)](https://github.com/adabru)
[![Nathanael Anderson](https://github.com/NathanaelA.png?size=60)](https://github.com/NathanaelA) [![Nathanael Anderson](https://github.com/NathanaelA.png?size=60)](https://github.com/NathanaelA)
[![Gremloon](https://github.com/gremloon.png?size=60)](https://github.com/gremloon) [<img src="https://room.sh/img/icons/android-chrome-192x192.png" height="60px" />](https://room.sh/)
[![ifiokjr](https://github.com/ifiokjr.png?size=60)](https://github.com/ifiokjr)
[![mrfambo](https://github.com/mrfambo.png?size=60)](https://github.com/mrfambo)
Sponsorship also comes with special perks! [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=d42f2d)](https://github.com/sponsors/dmonad) Sponsorship also comes with special perks! [![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=d42f2d)](https://github.com/sponsors/dmonad)
## Who is using Yjs ## Who is using Yjs
* [Relm](http://www.relm.us/) A collaborative gameworld for teamwork and * [Relm](https://www.relm.us/) A collaborative gameworld for teamwork and
community. :star2: community. :star2:
* [Input](https://input.com/) A collaborative note taking app. :star2: * [Input](https://input.com/) A collaborative note taking app. :star2:
* [Room.sh](https://room.sh/) A meeting application with integrated * [Room.sh](https://room.sh/) A meeting application with integrated
collaborative drawing, editing, and coding tools. :star: collaborative drawing, editing, and coding tools. :star:
* [http://coronavirustechhandbook.com/](https://coronavirustechhandbook.com/) * [https://coronavirustechhandbook.com/](https://coronavirustechhandbook.com/)
A collaborative wiki that is edited by thousands of different people to work A collaborative wiki that is edited by thousands of different people to work
on a rapid and sophisticated response to the coronavirus outbreak and on a rapid and sophisticated response to the coronavirus outbreak and
subsequent impacts. :star: subsequent impacts. :star:
@@ -90,10 +91,10 @@ are implemented in separate modules.
| Name | Cursors | Binding | Demo | | Name | Cursors | Binding | Demo |
|---|:-:|---|---| |---|:-:|---|---|
| [ProseMirror](https://prosemirror.net/) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | ✔ | [y-prosemirror](http://github.com/yjs/y-prosemirror) | [demo](https://demos.yjs.dev/prosemirror/prosemirror.html) | | [ProseMirror](https://prosemirror.net/) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | ✔ | [y-prosemirror](https://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://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](http://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](http://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) |
### Providers ### Providers
@@ -103,7 +104,7 @@ manage all that for you and are the perfect starting point for your
collaborative app. collaborative app.
<dl> <dl>
<dt><a href="http://github.com/yjs/y-webrtc">y-webrtc</a></dt> <dt><a href="https://github.com/yjs/y-webrtc">y-webrtc</a></dt>
<dd> <dd>
Propagates document updates peer-to-peer using WebRTC. The peers exchange Propagates document updates peer-to-peer using WebRTC. The peers exchange
signaling data over signaling servers. Publically available signaling servers signaling data over signaling servers. Publically available signaling servers
@@ -111,19 +112,19 @@ are available. Communication over the signaling servers can be encrypted by
providing a shared secret, keeping the connection information and the shared providing a shared secret, keeping the connection information and the shared
document private. document private.
</dd> </dd>
<dt><a href="http://github.com/yjs/y-websocket">y-websocket</a></dt> <dt><a href="https://github.com/yjs/y-websocket">y-websocket</a></dt>
<dd> <dd>
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> </dd>
<dt><a href="http://github.com/yjs/y-indexeddb">y-indexeddb</a></dt> <dt><a href="https://github.com/yjs/y-indexeddb">y-indexeddb</a></dt>
<dd> <dd>
Efficiently persists document updates to the browsers indexeddb database. Efficiently persists document updates to the browsers indexeddb database.
The document is immediately available and only diffs need to be synced through the The document is immediately available and only diffs need to be synced through the
network provider. network provider.
</dd> </dd>
<dt><a href="http://github.com/yjs/y-dat">y-dat</a></dt> <dt><a href="https://github.com/yjs/y-dat">y-dat</a></dt>
<dd> <dd>
[WIP] Write document updates effinciently to the dat network using [WIP] Write document updates effinciently to the dat network using
<a href="https://github.com/kappa-db/multifeed">multifeed</a>. Each client has <a href="https://github.com/kappa-db/multifeed">multifeed</a>. Each client has
@@ -241,6 +242,8 @@ necessary.
</p> </p>
<pre>const yarray = new Y.Array()</pre> <pre>const yarray = new Y.Array()</pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></dd>
<b><code>insert(index:number, content:Array&lt;object|boolean|Array|string|number|Uint8Array|Y.Type&gt;)</code></b> <b><code>insert(index:number, content:Array&lt;object|boolean|Array|string|number|Uint8Array|Y.Type&gt;)</code></b>
<dd> <dd>
Insert content at <var>index</var>. Note that content is an array of elements. Insert content at <var>index</var>. Note that content is an array of elements.
@@ -255,6 +258,8 @@ position 0.
<dd></dd> <dd></dd>
<b><code>get(index:number)</code></b> <b><code>get(index:number)</code></b>
<dd></dd> <dd></dd>
<b><code>slice(start:number, end:number):Array&lt;Object|boolean|Array|string|number|Uint8Array|Y.Type&gt;</code></b>
<dd>Retrieve a range of content</dd>
<b><code>length:number</code></b> <b><code>length:number</code></b>
<dd></dd> <dd></dd>
<b> <b>
@@ -310,6 +315,8 @@ or any of its children.
</p> </p>
<pre><code>const ymap = new Y.Map()</code></pre> <pre><code>const ymap = new Y.Map()</code></pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></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>
@@ -320,6 +327,8 @@ or any of its children.
<dd></dd> <dd></dd>
<b><code>get(index:number)</code></b> <b><code>get(index:number)</code></b>
<dd></dd> <dd></dd>
<b><code>clone():Y.Map</code></b>
<dd>Clone this type into a fresh Yjs type.</dd>
<b><code>toJSON():Object&lt;string, Object|boolean|Array|string|number|Uint8Array&gt;</code></b> <b><code>toJSON():Object&lt;string, Object|boolean|Array|string|number|Uint8Array&gt;</code></b>
<dd> <dd>
Copies the <code>[key,value]</code> pairs of this YMap to a new Object.It Copies the <code>[key,value]</code> pairs of this YMap to a new Object.It
@@ -387,6 +396,8 @@ YTextEvents compute changes as deltas.
</p> </p>
<pre>const ytext = new Y.Text()</pre> <pre>const ytext = new Y.Text()</pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></dd>
<b><code>insert(index:number, content:string, [formattingAttributes:Object&lt;string,string&gt;])</code></b> <b><code>insert(index:number, content:string, [formattingAttributes:Object&lt;string,string&gt;])</code></b>
<dd> <dd>
Insert a string at <var>index</var> and assign formatting attributes to it. Insert a string at <var>index</var> and assign formatting attributes to it.
@@ -445,14 +456,22 @@ or any of its children.
</p> </p>
<pre><code>const yxml = new Y.XmlFragment()</code></pre> <pre><code>const yxml = new Y.XmlFragment()</code></pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></dd>
<b><code>firstChild:Y.XmlElement|Y.XmlText|null</code></b>
<dd></dd>
<b><code>insert(index:number, content:Array&lt;Y.XmlElement|Y.XmlText&gt;)</code></b> <b><code>insert(index:number, content:Array&lt;Y.XmlElement|Y.XmlText&gt;)</code></b>
<dd></dd> <dd></dd>
<b><code>delete(index:number, length:number)</code></b> <b><code>delete(index:number, length:number)</code></b>
<dd></dd> <dd></dd>
<b><code>get(index:number)</code></b> <b><code>get(index:number)</code></b>
<dd></dd> <dd></dd>
<b><code>slice(start:number, end:number):Array&lt;Y.XmlElement|Y.XmlText&gt;</code></b>
<dd>Retrieve a range of content</dd>
<b><code>length:number</code></b> <b><code>length:number</code></b>
<dd></dd> <dd></dd>
<b><code>clone():Y.XmlFragment</code></b>
<dd>Clone this type into a fresh Yjs type.</dd>
<b><code>toArray():Array&lt;Y.XmlElement|Y.XmlText&gt;</code></b> <b><code>toArray():Array&lt;Y.XmlElement|Y.XmlText&gt;</code></b>
<dd>Copies the children to a new Array.</dd> <dd>Copies the children to a new Array.</dd>
<b><code>toDOM():DocumentFragment</code></b> <b><code>toDOM():DocumentFragment</code></b>
@@ -496,6 +515,14 @@ content and be actually XML compliant.
</p> </p>
<pre><code>const yxml = new Y.XmlElement()</code></pre> <pre><code>const yxml = new Y.XmlElement()</code></pre>
<dl> <dl>
<b><code>parent:Y.AbstractType|null</code></b>
<dd></dd>
<b><code>firstChild:Y.XmlElement|Y.XmlText|null</code></b>
<dd></dd>
<b><code>nextSibling:Y.XmlElement|Y.XmlText|null</code></b>
<dd></dd>
<b><code>prevSibling:Y.XmlElement|Y.XmlText|null</code></b>
<dd></dd>
<b><code>insert(index:number, content:Array&lt;Y.XmlElement|Y.XmlText&gt;)</code></b> <b><code>insert(index:number, content:Array&lt;Y.XmlElement|Y.XmlText&gt;)</code></b>
<dd></dd> <dd></dd>
<b><code>delete(index:number, length:number)</code></b> <b><code>delete(index:number, length:number)</code></b>
@@ -512,6 +539,12 @@ content and be actually XML compliant.
<dd></dd> <dd></dd>
<b><code>getAttributes(attributeName:string):Object&lt;string,string&gt;</code></b> <b><code>getAttributes(attributeName:string):Object&lt;string,string&gt;</code></b>
<dd></dd> <dd></dd>
<b><code>get(i:number):Y.XmlElement|Y.XmlText</code></b>
<dd>Retrieve the i-th element.</dd>
<b><code>slice(start:number, end:number):Array&lt;Y.XmlElement|Y.XmlText&gt;</code></b>
<dd>Retrieve a range of content</dd>
<b><code>clone():Y.XmlElement</code></b>
<dd>Clone this type into a fresh Yjs type.</dd>
<b><code>toArray():Array&lt;Y.XmlElement|Y.XmlText&gt;</code></b> <b><code>toArray():Array&lt;Y.XmlElement|Y.XmlText&gt;</code></b>
<dd>Copies the children to a new Array.</dd> <dd>Copies the children to a new Array.</dd>
<b><code>toDOM():Element</code></b> <b><code>toDOM():Element</code></b>

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.4.2", "version": "13.4.7",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.4.2", "version": "13.4.7",
"description": "Shared Editing Library", "description": "Shared Editing Library",
"main": "./dist/yjs.cjs", "main": "./dist/yjs.cjs",
"module": "./dist/yjs.mjs", "module": "./dist/yjs.mjs",

View File

@@ -45,6 +45,7 @@ export {
snapshot, snapshot,
emptySnapshot, emptySnapshot,
findRootTypeKey, findRootTypeKey,
getItem,
typeListToArraySnapshot, typeListToArraySnapshot,
typeMapGetSnapshot, typeMapGetSnapshot,
createDocFromSnapshot, createDocFromSnapshot,

View File

@@ -26,8 +26,6 @@ import {
} from '../internals.js' } from '../internals.js'
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
import * as maplib from 'lib0/map.js'
import * as set from 'lib0/set.js'
import * as binary from 'lib0/binary.js' import * as binary from 'lib0/binary.js'
/** /**
@@ -594,7 +592,7 @@ export class Item extends AbstractStruct {
} }
this.markDeleted() this.markDeleted()
addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length) addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length)
maplib.setIfUndefined(transaction.changed, parent, set.create).add(this.parentSub) addChangedTypeToTransaction(transaction, parent, this.parentSub)
this.content.delete(transaction) this.content.delete(transaction)
} }
} }

View File

@@ -287,6 +287,13 @@ export class AbstractType {
this._searchMarker = null this._searchMarker = null
} }
/**
* @return {AbstractType<any>|null}
*/
get parent () {
return this._item ? /** @type {AbstractType<any>} */ (this._item.parent) : null
}
/** /**
* Integrate this type into the Yjs instance. * Integrate this type into the Yjs instance.
* *
@@ -309,6 +316,13 @@ export class AbstractType {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/**
* @return {AbstractType<EventType>}
*/
clone () {
throw error.methodUnimplemented()
}
/** /**
* @param {AbstractUpdateEncoder} encoder * @param {AbstractUpdateEncoder} encoder
*/ */
@@ -381,6 +395,43 @@ export class AbstractType {
toJSON () {} toJSON () {}
} }
/**
* @param {AbstractType<any>} type
* @param {number} start
* @param {number} end
* @return {Array<any>}
*
* @private
* @function
*/
export const typeListSlice = (type, start, end) => {
if (start < 0) {
start = type._length + start
}
if (end < 0) {
end = type._length + end
}
let len = end - start
const cs = []
let n = type._start
while (n !== null && len > 0) {
if (n.countable && !n.deleted) {
const c = n.content.getContent()
if (c.length <= start) {
start -= c.length
} else {
for (let i = start; i < c.length && len > 0; i++) {
cs.push(c[i])
len--
}
start = 0
}
}
n = n.right
}
return cs
}
/** /**
* @param {AbstractType<any>} type * @param {AbstractType<any>} type
* @return {Array<any>} * @return {Array<any>}

View File

@@ -17,6 +17,7 @@ import {
transact, transact,
ArraySearchMarker, AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, Transaction, Item // eslint-disable-line ArraySearchMarker, AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, Transaction, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import { typeListSlice } from './AbstractType.js'
/** /**
* Event that describes the changes on a YArray * Event that describes the changes on a YArray
@@ -53,6 +54,18 @@ export class YArray extends AbstractType {
this._searchMarker = [] this._searchMarker = []
} }
/**
* Construct a new YArray containing the specified items.
* @template T
* @param {Array<T>} items
* @return {YArray<T>}
*/
static from (items) {
const a = new YArray()
a.push(items)
return a
}
/** /**
* Integrate this type into the Yjs instance. * Integrate this type into the Yjs instance.
* *
@@ -73,6 +86,17 @@ export class YArray extends AbstractType {
return new YArray() return new YArray()
} }
/**
* @return {YArray<T>}
*/
clone () {
const arr = new YArray()
arr.insert(0, this.toArray().map(el =>
el instanceof AbstractType ? el.clone() : el
))
return arr
}
get length () { get length () {
return this._prelimContent === null ? this._length : this._prelimContent.length return this._prelimContent === null ? this._length : this._prelimContent.length
} }
@@ -167,6 +191,17 @@ export class YArray extends AbstractType {
return typeListToArray(this) return typeListToArray(this)
} }
/**
* Transforms this YArray to a JavaScript Array.
*
* @param {number} [start]
* @param {number} [end]
* @return {Array<T>}
*/
slice (start = 0, end = this.length) {
return typeListSlice(this, start, end)
}
/** /**
* Transforms this Shared Type to a JSON object. * Transforms this Shared Type to a JSON object.
* *

View File

@@ -84,6 +84,17 @@ export class YMap extends AbstractType {
return new YMap() return new YMap()
} }
/**
* @return {YMap<T>}
*/
clone () {
const map = new YMap()
this.forEach((value, key) => {
map.set(key, value instanceof AbstractType ? value.clone() : value)
})
return map
}
/** /**
* Creates YMapEvent and calls observers. * Creates YMapEvent and calls observers.
* *

View File

@@ -21,6 +21,10 @@ import {
iterateDeletedStructs, iterateDeletedStructs,
iterateStructs, iterateStructs,
findMarker, findMarker,
typeMapDelete,
typeMapSet,
typeMapGet,
typeMapGetAll,
updateMarkerChanges, updateMarkerChanges,
ArraySearchMarker, AbstractUpdateDecoder, AbstractUpdateEncoder, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line ArraySearchMarker, AbstractUpdateDecoder, AbstractUpdateEncoder, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@@ -512,13 +516,32 @@ export class YTextEvent extends YEvent {
/** /**
* @param {YText} ytext * @param {YText} ytext
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {Set<any>} subs The keys that changed
*/ */
constructor (ytext, transaction) { constructor (ytext, transaction, subs) {
super(ytext, transaction) super(ytext, transaction)
/** /**
* @type {Array<DeltaItem>|null} * @type {Array<DeltaItem>|null}
*/ */
this._delta = null this._delta = null
/**
* Whether the children changed.
* @type {Boolean}
* @private
*/
this.childListChanged = false
/**
* Set of all changed attributes.
* @type {Set<string>}
*/
this.keysChanged = new Set()
subs.forEach((sub) => {
if (sub === null) {
this.childListChanged = true
} else {
this.keysChanged.add(sub)
}
})
} }
/** /**
@@ -762,6 +785,15 @@ export class YText extends AbstractType {
return new YText() return new YText()
} }
/**
* @return {YText}
*/
clone () {
const text = new YText()
text.applyDelta(this.toDelta())
return text
}
/** /**
* Creates YTextEvent and calls observers. * Creates YTextEvent and calls observers.
* *
@@ -770,7 +802,7 @@ export class YText extends AbstractType {
*/ */
_callObserver (transaction, parentSubs) { _callObserver (transaction, parentSubs) {
super._callObserver(transaction, parentSubs) super._callObserver(transaction, parentSubs)
const event = new YTextEvent(this, transaction) const event = new YTextEvent(this, transaction, parentSubs)
const doc = transaction.doc const doc = transaction.doc
// 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) {
@@ -1102,6 +1134,74 @@ export class YText extends AbstractType {
} }
} }
/**
* Removes an attribute.
*
* @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
*
* @param {String} attributeName The attribute name that is to be removed.
*
* @public
*/
removeAttribute (attributeName) {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeMapDelete(transaction, this, attributeName)
})
} else {
/** @type {Array<function>} */ (this._pending).push(() => this.removeAttribute(attributeName))
}
}
/**
* Sets or updates an attribute.
*
* @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
*
* @param {String} attributeName The attribute name that is to be set.
* @param {any} attributeValue The attribute value that is to be set.
*
* @public
*/
setAttribute (attributeName, attributeValue) {
if (this.doc !== null) {
transact(this.doc, transaction => {
typeMapSet(transaction, this, attributeName, attributeValue)
})
} else {
/** @type {Array<function>} */ (this._pending).push(() => this.setAttribute(attributeName, attributeValue))
}
}
/**
* Returns an attribute value that belongs to the attribute name.
*
* @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
*
* @param {String} attributeName The attribute name that identifies the
* queried value.
* @return {any} The queried attribute value.
*
* @public
*/
getAttribute (attributeName) {
return /** @type {any} */ (typeMapGet(this, attributeName))
}
/**
* Returns all attribute name/value pairs in a JSON Object.
*
* @note Xml-Text nodes don't have attributes. You can use this feature to assign properties to complete text-blocks.
*
* @param {Snapshot} [snapshot]
* @return {Object<string, any>} A JSON Object that describes the attributes.
*
* @public
*/
getAttributes (snapshot) {
return typeMapGetAll(this)
}
/** /**
* @param {AbstractUpdateEncoder} encoder * @param {AbstractUpdateEncoder} encoder
*/ */

View File

@@ -8,7 +8,7 @@ import {
typeMapGetAll, typeMapGetAll,
typeListForEach, typeListForEach,
YXmlElementRefID, YXmlElementRefID,
AbstractUpdateDecoder, AbstractUpdateEncoder, Snapshot, Doc, Item // eslint-disable-line YXmlText, ContentType, AbstractType, AbstractUpdateDecoder, AbstractUpdateEncoder, Snapshot, Doc, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
/** /**
@@ -28,6 +28,22 @@ export class YXmlElement extends YXmlFragment {
this._prelimAttrs = new Map() this._prelimAttrs = new Map()
} }
/**
* @type {YXmlElement|YXmlText|null}
*/
get nextSibling () {
const n = this._item ? this._item.next : null
return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
}
/**
* @type {YXmlElement|YXmlText|null}
*/
get prevSibling () {
const n = this._item ? this._item.prev : null
return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
}
/** /**
* Integrate this type into the Yjs instance. * Integrate this type into the Yjs instance.
* *
@@ -55,6 +71,20 @@ export class YXmlElement extends YXmlFragment {
return new YXmlElement(this.nodeName) return new YXmlElement(this.nodeName)
} }
/**
* @return {YXmlElement}
*/
clone () {
const el = new YXmlElement(this.nodeName)
const attrs = this.getAttributes()
for (const key in attrs) {
el.setAttribute(key, attrs[key])
}
// @ts-ignore
el.insert(0, el.toArray().map(item => item instanceof AbstractType ? item.clone() : item))
return el
}
/** /**
* Returns the XML serialization of this YXmlElement. * Returns the XML serialization of this YXmlElement.
* The attributes are ordered by attribute-name, so you can easily use this * The attributes are ordered by attribute-name, so you can easily use this

View File

@@ -1,7 +1,7 @@
import { import {
YEvent, YEvent,
YXmlElement, YXmlFragment, Transaction // eslint-disable-line YXmlText, YXmlElement, YXmlFragment, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
/** /**
@@ -9,7 +9,7 @@ import {
*/ */
export class YXmlEvent extends YEvent { export class YXmlEvent extends YEvent {
/** /**
* @param {YXmlElement|YXmlFragment} target The target on which the event is created. * @param {YXmlElement|YXmlText|YXmlFragment} target The target on which the event is created.
* @param {Set<string|null>} subs The set of changed attributes. `null` is included if the * @param {Set<string|null>} subs The set of changed attributes. `null` is included if the
* child list changed. * child list changed.
* @param {Transaction} transaction The transaction instance with wich the * @param {Transaction} transaction The transaction instance with wich the
@@ -25,7 +25,7 @@ export class YXmlEvent extends YEvent {
this.childListChanged = false this.childListChanged = false
/** /**
* Set of all changed attributes. * Set of all changed attributes.
* @type {Set<string|null>} * @type {Set<string>}
*/ */
this.attributesChanged = new Set() this.attributesChanged = new Set()
subs.forEach((sub) => { subs.forEach((sub) => {

View File

@@ -9,14 +9,19 @@ import {
typeListMap, typeListMap,
typeListForEach, typeListForEach,
typeListInsertGenerics, typeListInsertGenerics,
typeListInsertGenericsAfter,
typeListDelete, typeListDelete,
typeListToArray, typeListToArray,
YXmlFragmentRefID, YXmlFragmentRefID,
callTypeObservers, callTypeObservers,
transact, transact,
typeListGet,
typeListSlice,
AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook, Snapshot // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook, Snapshot // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as error from 'lib0/error.js'
/** /**
* Define the elements to which a set of CSS queries apply. * Define the elements to which a set of CSS queries apply.
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors}
@@ -128,6 +133,14 @@ export class YXmlFragment extends AbstractType {
this._prelimContent = [] this._prelimContent = []
} }
/**
* @type {YXmlElement|YXmlText|null}
*/
get firstChild () {
const first = this._first
return first ? first.content.getContent()[0] : null
}
/** /**
* Integrate this type into the Yjs instance. * Integrate this type into the Yjs instance.
* *
@@ -148,6 +161,16 @@ export class YXmlFragment extends AbstractType {
return new YXmlFragment() return new YXmlFragment()
} }
/**
* @return {YXmlFragment}
*/
clone () {
const el = new YXmlFragment()
// @ts-ignore
el.insert(0, el.toArray().map(item => item instanceof AbstractType ? item.clone() : item))
return el
}
get length () { get length () {
return this._prelimContent === null ? this._length : this._prelimContent.length return this._prelimContent === null ? this._length : this._prelimContent.length
} }
@@ -290,6 +313,32 @@ export class YXmlFragment extends AbstractType {
} }
} }
/**
* Inserts new content at an index.
*
* @example
* // Insert character 'a' at position 0
* xml.insert(0, [new Y.XmlText('text')])
*
* @param {null|Item|YXmlElement|YXmlText} ref The index to insert content at
* @param {Array<YXmlElement|YXmlText>} content The array of content
*/
insertAfter (ref, content) {
if (this.doc !== null) {
transact(this.doc, transaction => {
const refItem = (ref && ref instanceof AbstractType) ? ref._item : ref
typeListInsertGenericsAfter(transaction, this, refItem, content)
})
} else {
const pc = /** @type {Array<any>} */ (this._prelimContent)
const index = ref === null ? 0 : pc.findIndex(el => el === ref) + 1
if (index === 0 && ref !== null) {
throw error.create('Reference item not found')
}
pc.splice(index, 0, ...content)
}
}
/** /**
* Deletes elements starting from an index. * Deletes elements starting from an index.
* *
@@ -316,6 +365,45 @@ export class YXmlFragment extends AbstractType {
return typeListToArray(this) return typeListToArray(this)
} }
/**
* Appends content to this YArray.
*
* @param {Array<YXmlElement|YXmlText>} content Array of content to append.
*/
push (content) {
this.insert(this.length, content)
}
/**
* Preppends content to this YArray.
*
* @param {Array<YXmlElement|YXmlText>} content Array of content to preppend.
*/
unshift (content) {
this.insert(0, content)
}
/**
* Returns the i-th element from a YArray.
*
* @param {number} index The index of the element to return from the YArray
* @return {YXmlElement|YXmlText}
*/
get (index) {
return typeListGet(this, index)
}
/**
* Transforms this YArray to a JavaScript Array.
*
* @param {number} [start]
* @param {number} [end]
* @return {Array<YXmlElement|YXmlText>}
*/
slice (start = 0, end = this.length) {
return typeListSlice(this, start, end)
}
/** /**
* Transform the properties of this type to binary and write it to an * Transform the properties of this type to binary and write it to an
* BinaryEncoder. * BinaryEncoder.

View File

@@ -29,6 +29,17 @@ export class YXmlHook extends YMap {
return new YXmlHook(this.hookName) return new YXmlHook(this.hookName)
} }
/**
* @return {YXmlHook}
*/
clone () {
const el = new YXmlHook(this.hookName)
this.forEach((value, key) => {
el.set(key, value)
})
return el
}
/** /**
* Creates a Dom Element that mirrors this YXmlElement. * Creates a Dom Element that mirrors this YXmlElement.
* *

View File

@@ -2,7 +2,7 @@
import { import {
YText, YText,
YXmlTextRefID, YXmlTextRefID,
AbstractUpdateDecoder, AbstractUpdateEncoder // eslint-disable-line ContentType, YXmlElement, AbstractUpdateDecoder, AbstractUpdateEncoder // eslint-disable-line
} from '../internals.js' } from '../internals.js'
/** /**
@@ -10,10 +10,35 @@ import {
* simple formatting information like bold and italic. * simple formatting information like bold and italic.
*/ */
export class YXmlText extends YText { export class YXmlText extends YText {
/**
* @type {YXmlElement|YXmlText|null}
*/
get nextSibling () {
const n = this._item ? this._item.next : null
return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
}
/**
* @type {YXmlElement|YXmlText|null}
*/
get prevSibling () {
const n = this._item ? this._item.prev : null
return n ? /** @type {YXmlElement|YXmlText} */ (/** @type {ContentType} */ (n.content).type) : null
}
_copy () { _copy () {
return new YXmlText() return new YXmlText()
} }
/**
* @return {YXmlText}
*/
clone () {
const text = new YXmlText()
text.applyDelta(this.toDelta())
return text
}
/** /**
* Creates a Dom Element that mirrors this YXmlText. * Creates a Dom Element that mirrors this YXmlText.
* *

View File

@@ -257,6 +257,7 @@ export class Doc extends Observable {
}, null, true) }, null, true)
} }
this.emit('destroyed', [true]) this.emit('destroyed', [true])
this.emit('destroy', [this])
super.destroy() super.destroy()
} }

View File

@@ -264,6 +264,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
/** /**
* @param {RelativePosition|null} a * @param {RelativePosition|null} a
* @param {RelativePosition|null} b * @param {RelativePosition|null} b
* @return {boolean}
* *
* @function * @function
*/ */

View File

@@ -119,9 +119,6 @@ const popStackItem = (undoManager, stack, eventType) => {
} }
} }
result = stackItem result = stackItem
if (result != null) {
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
}
} }
transaction.changed.forEach((subProps, type) => { transaction.changed.forEach((subProps, type) => {
// destroy search marker if necessary // destroy search marker if necessary
@@ -130,6 +127,9 @@ const popStackItem = (undoManager, stack, eventType) => {
} }
}) })
}, undoManager) }, undoManager)
if (result != null) {
undoManager.emit('stack-item-popped', [{ stackItem: result, type: eventType }, undoManager])
}
return result return result
} }

View File

@@ -40,6 +40,8 @@ export class YEvent {
/** /**
* Computes the path from `y` to the changed type. * Computes the path from `y` to the changed type.
* *
* @todo v14 should standardize on path: Array<{parent, index}> because that is easier to work with.
*
* The following property holds: * The following property holds:
* @example * @example
* let type = y * let type = y

View File

@@ -17,6 +17,21 @@ export const testBasicUpdate = tc => {
t.compare(doc2.getArray('array').toArray(), ['hi']) t.compare(doc2.getArray('array').toArray(), ['hi'])
} }
/**
* @param {t.TestCase} tc
*/
export const testSlice = tc => {
const doc1 = new Y.Doc()
const arr = doc1.getArray('array')
arr.insert(0, [1, 2, 3])
t.compareArrays(arr.slice(0), [1, 2, 3])
t.compareArrays(arr.slice(1), [2, 3])
t.compareArrays(arr.slice(0, -1), [1, 2])
arr.insert(0, [0])
t.compareArrays(arr.slice(0), [0, 1, 2, 3])
t.compareArrays(arr.slice(0, 2), [0, 1])
}
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */

View File

@@ -73,3 +73,63 @@ export const testTreewalker = tc => {
t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1') t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1')
compare(users) compare(users)
} }
/**
* @param {t.TestCase} tc
*/
export const testYtextAttributes = tc => {
const ydoc = new Y.Doc()
const ytext = /** @type {Y.XmlText} */ (ydoc.get('', Y.XmlText))
ytext.observe(event => {
t.compare(event.changes.keys.get('test'), { action: 'add', oldValue: undefined })
})
ytext.setAttribute('test', 42)
t.compare(ytext.getAttribute('test'), 42)
t.compare(ytext.getAttributes(), { test: 42 })
}
/**
* @param {t.TestCase} tc
*/
export const testSiblings = tc => {
const ydoc = new Y.Doc()
const yxml = ydoc.getXmlFragment()
const first = new Y.XmlText()
const second = new Y.XmlElement('p')
yxml.insert(0, [first, second])
t.assert(first.nextSibling === second)
t.assert(second.prevSibling === first)
t.assert(first.parent === yxml)
t.assert(yxml.parent === null)
t.assert(yxml.firstChild === first)
}
/**
* @param {t.TestCase} tc
*/
export const testInsertafter = tc => {
const ydoc = new Y.Doc()
const yxml = ydoc.getXmlFragment()
const first = new Y.XmlText()
const second = new Y.XmlElement('p')
const third = new Y.XmlElement('p')
const deepsecond1 = new Y.XmlElement('span')
const deepsecond2 = new Y.XmlText()
second.insertAfter(null, [deepsecond1])
second.insertAfter(deepsecond1, [deepsecond2])
yxml.insertAfter(null, [first, second])
yxml.insertAfter(second, [third])
t.assert(yxml.length === 3)
t.assert(second.get(0) === deepsecond1)
t.assert(second.get(1) === deepsecond2)
t.compareArrays(yxml.toArray(), [first, second, third])
t.fails(() => {
const el = new Y.XmlElement('p')
el.insertAfter(deepsecond1, [new Y.XmlText()])
})
}