Compare commits

..

53 Commits

Author SHA1 Message Date
Kevin Jahns
34b06b6cf9 13.6.17 2024-06-17 15:15:04 +02:00
Kevin Jahns
d4dac558c0 fix creating relative position from json when type name is the empty string 2024-06-17 15:13:07 +02:00
Kevin Jahns
a47a48b891 Merge pull request #649 from szv/readme-fixes
Readme fixes
2024-06-15 20:12:15 +02:00
Sebastian Szvetecz
2e79d0369e Fixed markdown link for ellipsus.org in README.md 2024-06-12 01:12:48 +02:00
Sebastian Szvetecz
88506f6d78 Fixed star icon in README.md 2024-06-12 01:12:13 +02:00
Kevin Jahns
fbd088ee78 13.6.16 2024-06-10 12:21:06 +02:00
Kevin Jahns
0678ed1eb5 fix event.path in observeDeep - closes #457 2024-06-10 12:18:16 +02:00
Kevin Jahns
0973e0acd4 Merge pull request #648 from ellipsus-writes/export-merge-ds
Export mergeDeleteSets
2024-06-07 17:20:20 +02:00
Fuad Saud
6932696795 Export mergeDeleteSets
Useful for comparing snapshots.
2024-06-06 13:38:08 +02:00
Kevin Jahns
a4303f914d Merge pull request #646 from MaxNoetzold/patch-1
Add y-postgresql to provider list in README
2024-06-04 13:47:02 +02:00
Max Nötzold
03593aeeb1 fix linting errors 2024-05-21 16:03:31 +02:00
Max Nötzold
d67a951104 add y-postgresql info to readme 2024-05-21 15:30:24 +02:00
Kevin Jahns
72205a688f Merge pull request #644 from i12345/patch-1
Update INTERNALS.md
2024-05-15 21:00:32 +02:00
i12345
edad668dbd Update INTERNALS.md
explained 53 bit JS numbers
2024-05-15 13:43:59 -05:00
Kevin Jahns
c264b1c291 Merge pull request #640 from malte-j/patch-1
Add y-op-sqlite to Readme
2024-05-11 12:27:35 +02:00
Kevin Jahns
cdd8e4f5fc Merge pull request #643 from jasonbw/patch-1
Fix y-websocket `server.cjs` path
2024-05-09 13:43:05 +02:00
Jason Wang
06e71f651d Fix y-websocket server path
The commmit c3d14cf07d renamed `server.js` to `server.cjs`; mirror that change here.
2024-05-09 05:55:26 -05:00
Malte Janßen
54594a2d75 Add y-op-sqlite to readme 2024-04-30 16:29:06 +02:00
Kevin Jahns
13772bf891 Merge pull request #603 from kevboh/patch-1
add screen.garden as user
2024-04-28 21:39:28 +02:00
Kevin Jahns
0896ed42b2 Merge branch 'main' into patch-1 2024-04-28 21:38:49 +02:00
Kevin Jahns
656b7e7f6a add more users 2024-04-28 21:16:58 +02:00
Kevin Jahns
0511b66346 Merge pull request #635 from synix/fix/unused-transaction
remove unused _transaction in YArrayEvent
2024-04-28 21:08:58 +02:00
Kevin Jahns
91b718cde0 13.6.15 2024-04-27 00:50:32 +02:00
Kevin Jahns
d56221b66a Merge pull request #637 from synix/fix/readme-lint
fix: markdownlint readme error
2024-04-27 00:46:42 +02:00
Kevin Jahns
ce43124ad0 [relative-positions] add option to configure whether to follow redon insertions - #638 2024-04-27 00:24:49 +02:00
synix
0af69cf6d6 fix: markdownlint readme error 2024-04-26 13:15:06 +08:00
synix
3df335cb4c update slice() function's doc 2024-04-26 12:03:28 +08:00
synix
387be70ae9 make slice() function's doc more accurate 2024-04-26 11:49:52 +08:00
Kevin Jahns
927c2369aa Merge pull request #636 from fxsalazar/patch-1
Add Hocuspocus as a backend provider
2024-04-26 00:42:32 +02:00
Felix Salazar
8270373c9f Add Hocuspocus as a backend provider 2024-04-25 19:34:05 +02:00
synix
43815d8292 fix lint error 2024-04-25 11:33:36 +08:00
synix
f0dc53f53f fix minor typos 2024-04-25 11:17:49 +08:00
synix
25ae9f3236 remove unused _transaction in YArray 2024-04-25 11:03:17 +08:00
Kevin Jahns
5e712e39b1 add ourboard as user 2024-04-24 16:03:21 +02:00
Kevin Jahns
4ffd23fd0b typo 2024-04-17 20:41:42 +02:00
Kevin Jahns
05d974cee1 Merge pull request #630 from sakihet/fix-typo
fix typo
2024-04-15 18:54:02 +02:00
saki
f1532771b7 fix typo 2024-04-16 01:15:02 +09:00
Kevin Jahns
aee9e14d09 Merge pull request #629 from synix/fix/outdated-y-instance
remove outdated Y instance in comments
2024-04-13 19:54:23 +02:00
synix
f5aa852054 remove outdated Y instance in comments 2024-04-13 21:26:44 +08:00
Kevin Jahns
b990ad9f86 Merge pull request #627 from synix/fix/INTERNALS
update search marker count in INTERNALS.md
2024-04-12 13:22:02 +02:00
synix
43e17802a6 fix: update search marker count in INTERNALS.md 2024-04-11 11:21:14 +08:00
Kevin Jahns
01c3668a0b Merge pull request #626 from satyajeetjadhav/main
Update Readme who-is-using (thinkdeli.com)
2024-04-09 18:06:25 +02:00
Satyajeet Jadhav
52b906898f Update Readme who-is-using (thinkdeli.com) 2024-04-09 16:39:18 +05:30
Kevin Jahns
d119459fad add huly as a user 2024-04-03 15:22:58 +02:00
Kevin Jahns
d730abe594 add synthesia as a user 2024-03-24 21:00:23 +01:00
Kevin Jahns
ca24f1ee76 added more sponsors 2024-03-23 14:29:23 +01:00
Kevin Jahns
dc45a8d3cf [readme] Added AppMaster to "Who is Using" 2024-03-23 12:44:01 +01:00
Kevin Jahns
2062f52a90 add reference to y-redis 2024-03-15 01:42:16 +01:00
Kevin Jahns
6e674ff5f7 add y-webxdc - related to yjs/docs#55 2024-03-14 21:09:34 +01:00
Kevin Jahns
2fba694cd4 Add documentation & clarification to clone method #622 2024-03-14 20:33:34 +01:00
Kevin Jahns
b235c57d76 add tinybase 2024-03-12 16:22:12 +01:00
Kevin Jahns
6beab79eb4 add tests for falsy formatting attributes - #619 2024-03-01 11:39:31 +01:00
Kevin Barrett
221cb81dbf add screen.garden as user 2023-11-30 16:51:23 -05:00
21 changed files with 204 additions and 51 deletions

View File

@@ -26,7 +26,7 @@ article](https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/).
Each client is assigned a unique *clientID* property on first insert. This is a
random 53-bit integer (53 bits because that fits in the javascript safe integer
range).
range \[JavaScript uses IEEE 754 floats\]).
## List items
@@ -88,7 +88,7 @@ When a local insert happens, Yjs needs to map the insert position in the
document (eg position 1000) to an ID. With just the linked list, this would
require a slow O(n) linear scan of the list. But when editing a document, most
inserts are either at the same position as the last insert, or nearby. To
improve performance, Yjs stores a cache of the 10 most recently looked up
improve performance, Yjs stores a cache of the 80 most recently looked up
insert positions in the document. This is consulted and updated when a position
is looked up to improve performance in the average case. The cache is updated
using a heuristic that is still changing (currently, it is updated when a new

View File

@@ -49,12 +49,16 @@ Showcase](https://yjs-diagram.synergy.codes/).
## Who is using Yjs
* [AFFiNE](https://affine.pro/) A local-first, privacy-first, open source
knowledge base. 🏅
knowledge base. :star2:
* [Huly](https://huly.io/) - Open Source All-in-One Project Management Platform :star2:
* [Cargo](https://cargo.site/) Site builder for designers and artists :star2:
* [Gitbook](https://gitbook.com) Knowledge management for technical teams :star2:
* [Evernote](https://evernote.com) Note-taking app :star2:
* [Lessonspace](https://thelessonspace.com) Enterprise platform for virtual
classrooms and online training :star2:
* [Ellipsus](ellipsus.com) - Collaborative writing app for storytelling etc.
Supports versioning, change attribution, and "blame". A solution for the whole
publishing process (also selling) :star:
* [Dynaboard](https://dynaboard.com/) Build web apps collaboratively. :star:
* [Relm](https://www.relm.us/) A collaborative gameworld for teamwork and
community. :star:
@@ -64,11 +68,15 @@ Showcase](https://yjs-diagram.synergy.codes/).
Nimbus Web. :star:
* [Pluxbox RadioManager](https://getradiomanager.com/) A web-based app to
collaboratively organize radio broadcasts. :star:
* [modyfi](https://www.modyfi.com) - Modyfi is the design platform built for
multidisciplinary designers. Design, generate, animate, and more — without
switching between apps. :star:
* [Sana](https://sanalabs.com/) A learning platform with collaborative text
editing powered by Yjs.
* [Serenity Notes](https://www.serenity.re/en/notes) End-to-end encrypted
collaborative notes app.
* [PRSM](https://prsm.uk/) Collaborative mind-mapping and system visualisation. *[(source)](https://github.com/micrology/prsm)*
* [PRSM](https://prsm.uk/) Collaborative mind-mapping and system visualisation.
*[(source)](https://github.com/micrology/prsm)*
* [Alldone](https://alldone.app/) A next-gen project management and
collaboration platform.
* [Living Spec](https://livingspec.com/) A modern way for product teams to collaborate.
@@ -91,6 +99,21 @@ Showcase](https://yjs-diagram.synergy.codes/).
* [AWS SageMaker](https://aws.amazon.com/sagemaker/) Tools for building Machine
Learning Models
* [linear](https://linear.app) Streamline issues, projects, and product roadmaps.
* [btw](https://www.btw.so) - Personal website builder
* [AWS SageMaker](https://aws.amazon.com/sagemaker/) - Machine Learning Service
* [Arkiter](https://www.arkiter.com/) - Live interview software
* [Appflowy](https://www.appflowy.io/) - They use Yrs
* [Multi.app](https://multi.app) - Multiplayer app sharing: Point, draw and edit
in shared apps as if they're on your computer. They are using Yrs.
* [AppMaster](https://appmaster.io) A No-Code platform for creating
production-ready applications with source code generation.
* [Synthesia](https://www.synthesia.io) - Collaborative Video Editor
* [thinkdeli](https://thinkdeli.com) - A fast and simple notes app powered by AI
* [ourboard](https://github.com/raimohanska/ourboard) - A collaborative whiteboard
applicaiton
* [Ellie.ai](https://ellie.ai) - Data Product Design and Collaboration
* [GoPeer](https://gopeer.org/) - Collaborative tutoring
* [screen.garden](https://screen.garden) Collaborative backend for PKM apps.
## Table of Contents
@@ -145,9 +168,10 @@ collaborative app.
<dt><a href="https://github.com/yjs/y-websocket">y-websocket</a></dt>
<dd>
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
leveldb database. <b>y-sweet</b> and <b>ypy-websocket</b> (see below) are
compatible to the y-wesocket protocol.
connects to that backend. <a href="https://github.com/yjs/y-redis/"><b>y-redis</b></a>,
<b>y-sweet</b>, <b>ypy-websocket</b> and <a href="https://tiptap.dev/docs/hocuspocus/introduction">
<b>Hocuspocus</b></a> (see below) are alternative
backends to y-websocket.
</dd>
<dt><a href="https://github.com/yjs/y-webrtc">y-webrtc</a></dt>
<dd>
@@ -169,6 +193,10 @@ browser DevTools extension.
<dd>
A standalone yjs server with persistence to S3 or filesystem. They offer a
<a href="https://y-sweet.cloud">cloud service</a> as well.
</dd>
<dt><a href="https://github.com/ueberdosis/hocuspocus">Hocuspocus</a></dt>
<dd>
A standalone extensible yjs server with sqlite persistence, webhooks, auth and more.
</dd>
<dt><a href="https://docs.partykit.io/reference/y-partykit-api/">PartyKit</a></dt>
<dd>
@@ -205,6 +233,15 @@ An ActionCable companion for Yjs clients. There is a fitting
<dd>
Websocket backend, written in Python.
</dd>
<dt><a href="https://tinybase.org/">Tinybase</a></dt>
<dd>
The reactive data store for local-first apps. They support multiple CRDTs and
different network technologies.
</dd>
<dt><a href="https://codeberg.org/webxdc/y-webxdc">y-webxdc</a></dt>
<dd>
Provider for sharing data in <a href="https://webxdc.org">webxdc chat apps</a>.
</dd>
</dl>
#### Persistence Providers
@@ -230,6 +267,17 @@ Like y-indexeddb, but with sub-documents support and fully TypeScript.
<dd>
A database and connection provider for Yjs based on Firestore.
</dd>
<dt><a href="https://github.com/malte-j/y-op-sqlite">y-op-sqlite</a></dt>
<dd>
Persist YJS updates in your React Native app using
<a href="https://github.com/OP-Engineering/op-sqlite">op-sqlite</a>
, the fastest SQLite library for React Native.
</dd>
<dt><a href="https://github.com/MaxNoetzold/y-postgresql">y-postgresql</a></dt>
<dd>
Provides persistent storage for a web server using PostgreSQL and
is easily compatible with y-websocket.
</dd>
</dl>
# Ports
@@ -259,7 +307,7 @@ npm i yjs y-websocket
Start the y-websocket server:
```sh
PORT=1234 node ./node_modules/y-websocket/bin/server.js
PORT=1234 node ./node_modules/y-websocket/bin/server.cjs
```
### Example: Observe types

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "yjs",
"version": "13.6.14",
"version": "13.6.17",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "yjs",
"version": "13.6.14",
"version": "13.6.17",
"license": "MIT",
"dependencies": {
"lib0": "^0.2.86"

View File

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

View File

@@ -100,6 +100,7 @@ export {
UpdateDecoderV1,
UpdateDecoderV2,
equalDeleteSets,
mergeDeleteSets,
snapshotContainsUpdate
} from './internals.js'

View File

@@ -316,6 +316,10 @@ export class AbstractType {
}
/**
* Makes a copy of this data type that can be included somewhere else.
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {AbstractType<EventType>}
*/
clone () {
@@ -477,7 +481,7 @@ export const typeListToArraySnapshot = (type, snapshot) => {
}
/**
* Executes a provided function on once on overy element of this YArray.
* Executes a provided function on once on every element of this YArray.
*
* @param {AbstractType<any>} type
* @param {function(any,number,any):void} f A function to execute on every element of this YArray.
@@ -569,7 +573,7 @@ export const typeListCreateIterator = type => {
}
/**
* Executes a provided function on once on overy element of this YArray.
* Executes a provided function on once on every element of this YArray.
* Operates on a snapshotted state of the document.
*
* @param {AbstractType<any>} type

View File

@@ -25,16 +25,7 @@ import { typeListSlice } from './AbstractType.js'
* @template T
* @extends YEvent<YArray<T>>
*/
export class YArrayEvent extends YEvent {
/**
* @param {YArray<T>} yarray The changed type
* @param {Transaction} transaction The transaction object
*/
constructor (yarray, transaction) {
super(yarray, transaction)
this._transaction = transaction
}
}
export class YArrayEvent extends YEvent {}
/**
* A shared Array implementation.
@@ -95,6 +86,10 @@ export class YArray extends AbstractType {
}
/**
* Makes a copy of this data type that can be included somewhere else.
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {YArray<T>}
*/
clone () {
@@ -167,9 +162,9 @@ export class YArray extends AbstractType {
}
/**
* Preppends content to this YArray.
* Prepends content to this YArray.
*
* @param {Array<T>} content Array of content to preppend.
* @param {Array<T>} content Array of content to prepend.
*/
unshift (content) {
this.insert(0, content)
@@ -211,7 +206,8 @@ export class YArray extends AbstractType {
}
/**
* Transforms this YArray to a JavaScript Array.
* Returns a portion of this YArray into a JavaScript Array selected
* from start to end (end not included).
*
* @param {number} [start]
* @param {number} [end]
@@ -244,7 +240,7 @@ export class YArray extends AbstractType {
}
/**
* Executes a provided function once on overy element of this YArray.
* Executes a provided function once on every element of this YArray.
*
* @param {function(T,number,YArray<T>):void} f A function to execute on every element of this YArray.
*/

View File

@@ -88,6 +88,10 @@ export class YMap extends AbstractType {
}
/**
* Makes a copy of this data type that can be included somewhere else.
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {YMap<MapType>}
*/
clone () {

View File

@@ -897,6 +897,10 @@ export class YText extends AbstractType {
}
/**
* Makes a copy of this data type that can be included somewhere else.
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {YText}
*/
clone () {

View File

@@ -81,6 +81,10 @@ export class YXmlElement extends YXmlFragment {
}
/**
* Makes a copy of this data type that can be included somewhere else.
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {YXmlElement<KV>}
*/
clone () {

View File

@@ -163,6 +163,10 @@ export class YXmlFragment extends AbstractType {
}
/**
* Makes a copy of this data type that can be included somewhere else.
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {YXmlFragment}
*/
clone () {
@@ -376,9 +380,9 @@ export class YXmlFragment extends AbstractType {
}
/**
* Preppends content to this YArray.
* Prepends content to this YArray.
*
* @param {Array<YXmlElement|YXmlText>} content Array of content to preppend.
* @param {Array<YXmlElement|YXmlText>} content Array of content to prepend.
*/
unshift (content) {
this.insert(0, content)
@@ -395,7 +399,8 @@ export class YXmlFragment extends AbstractType {
}
/**
* Transforms this YArray to a JavaScript Array.
* Returns a portion of this YXmlFragment into a JavaScript Array selected
* from start to end (end not included).
*
* @param {number} [start]
* @param {number} [end]
@@ -406,7 +411,7 @@ export class YXmlFragment extends AbstractType {
}
/**
* Executes a provided function on once on overy child element.
* Executes a provided function on once on every child element.
*
* @param {function(YXmlElement|YXmlText,number, typeof self):void} f A function to execute on every element of this YArray.
*/

View File

@@ -29,6 +29,10 @@ export class YXmlHook extends YMap {
}
/**
* Makes a copy of this data type that can be included somewhere else.
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {YXmlHook}
*/
clone () {

View File

@@ -30,6 +30,10 @@ export class YXmlText extends YText {
}
/**
* Makes a copy of this data type that can be included somewhere else.
*
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
*
* @return {YXmlText}
*/
clone () {

View File

@@ -187,22 +187,22 @@ export class Doc extends ObservableV2 {
/**
* Define a shared data type.
*
* Multiple calls of `y.get(name, TypeConstructor)` yield the same result
* Multiple calls of `ydoc.get(name, TypeConstructor)` yield the same result
* and do not overwrite each other. I.e.
* `y.define(name, Y.Array) === y.define(name, Y.Array)`
* `ydoc.get(name, Y.Array) === ydoc.get(name, Y.Array)`
*
* After this method is called, the type is also available on `y.share.get(name)`.
* After this method is called, the type is also available on `ydoc.share.get(name)`.
*
* *Best Practices:*
* Define all types right after the Yjs instance is created and store them in a separate object.
* Define all types right after the Y.Doc instance is created and store them in a separate object.
* Also use the typed methods `getText(name)`, `getArray(name)`, ..
*
* @template {typeof AbstractType<any>} Type
* @example
* const y = new Y(..)
* const ydoc = new Y.Doc(..)
* const appState = {
* document: y.getText('document')
* comments: y.getArray('comments')
* document: ydoc.getText('document')
* comments: ydoc.getArray('comments')
* }
*
* @param {string} name

View File

@@ -8,6 +8,7 @@ import {
createID,
ContentType,
followRedone,
getItem,
ID, Doc, AbstractType // eslint-disable-line
} from '../internals.js'
@@ -101,7 +102,7 @@ export const relativePositionToJSON = rpos => {
*
* @function
*/
export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname || null, json.item == null ? null : createID(json.item.client, json.item.clock), json.assoc == null ? 0 : json.assoc)
export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname ?? null, json.item == null ? null : createID(json.item.client, json.item.clock), json.assoc == null ? 0 : json.assoc)
export class AbsolutePosition {
/**
@@ -256,13 +257,24 @@ export const readRelativePosition = decoder => {
export const decodeRelativePosition = uint8Array => readRelativePosition(decoding.createDecoder(uint8Array))
/**
* Transform a relative position to an absolute position.
*
* If you want to share the relative position with other users, you should set
* `followUndoneDeletions` to false to get consistent results across all clients.
*
* When calculating the absolute position, we try to follow the "undone deletions". This yields
* better results for the user who performed undo. However, only the user who performed the undo
* will get the better results, the other users don't know which operations recreated a deleted
* range of content. There is more information in this ticket: https://github.com/yjs/yjs/issues/638
*
* @param {RelativePosition} rpos
* @param {Doc} doc
* @param {boolean} followUndoneDeletions - whether to follow undone deletions - see https://github.com/yjs/yjs/issues/638
* @return {AbsolutePosition|null}
*
* @function
*/
export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndoneDeletions = true) => {
const store = doc.store
const rightID = rpos.item
const typeID = rpos.type
@@ -274,7 +286,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
if (getState(store, rightID.client) <= rightID.clock) {
return null
}
const res = followRedone(store, rightID)
const res = followUndoneDeletions ? followRedone(store, rightID) : { item: getItem(store, rightID), diff: 0 }
const right = res.item
if (!(right instanceof Item)) {
return null
@@ -298,7 +310,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
// type does not exist yet
return null
}
const { item } = followRedone(store, typeID)
const { item } = followUndoneDeletions ? followRedone(store, typeID) : { item: getItem(store, typeID) }
if (item instanceof Item && item.content instanceof ContentType) {
type = item.content.type
} else {

View File

@@ -28,7 +28,8 @@ import { callAll } from 'lib0/function'
* possible. Here is an example to illustrate the advantages of bundling:
*
* @example
* const map = y.define('map', YMap)
* const ydoc = new Y.Doc()
* const map = ydoc.getMap('map')
* // Log content when change is triggered
* map.observe(() => {
* console.log('change triggered')
@@ -37,7 +38,7 @@ import { callAll } from 'lib0/function'
* map.set('a', 0) // => "change triggered"
* map.set('b', 0) // => "change triggered"
* // When put in a transaction, it will trigger the log after the transaction:
* y.transact(() => {
* ydoc.transact(() => {
* map.set('a', 1)
* map.set('b', 1)
* }) // => "change triggered"
@@ -224,7 +225,7 @@ const tryGcDeleteSet = (ds, store, gcFilter) => {
*/
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
// merge from right to left for better efficiency and so we don't miss any merge targets
ds.clients.forEach((deleteItems, client) => {
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
for (let di = deleteItems.length - 1; di >= 0; di--) {

View File

@@ -264,8 +264,8 @@ const getPathTo = (parent, child) => {
let i = 0
let c = /** @type {AbstractType<any>} */ (child._item.parent)._start
while (c !== child._item && c !== null) {
if (!c.deleted) {
i++
if (!c.deleted && c.countable) {
i += c.length
}
c = c.right
}

View File

@@ -154,7 +154,7 @@ export const readClientsStructRefs = (decoder, doc) => {
// @type {string|null}
const struct = new Item(
createID(client, clock),
null, // leftd
null, // left
(info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null, // origin
null, // right
(info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null, // right origin
@@ -178,7 +178,7 @@ export const readClientsStructRefs = (decoder, doc) => {
const struct = new Item(
createID(client, clock),
null, // leftd
null, // left
origin, // origin
null, // right
rightOrigin, // right origin
@@ -370,7 +370,7 @@ export const writeStructsFromTransaction = (encoder, transaction) => writeClient
/**
* Read and apply a document update.
*
* This function has the same effect as `applyUpdate` but accepts an decoder.
* This function has the same effect as `applyUpdate` but accepts a decoder.
*
* @param {decoding.Decoder} decoder
* @param {Doc} ydoc
@@ -451,7 +451,7 @@ export const readUpdateV2 = (decoder, ydoc, transactionOrigin, structDecoder = n
/**
* Read and apply a document update.
*
* This function has the same effect as `applyUpdate` but accepts an decoder.
* This function has the same effect as `applyUpdate` but accepts a decoder.
*
* @param {decoding.Decoder} decoder
* @param {Doc} ydoc

View File

@@ -101,3 +101,25 @@ export const testRelativePositionAssociationDifference = tc => {
t.assert(posRight != null && posRight.index === 2)
t.assert(posLeft != null && posLeft.index === 1)
}
/**
* @param {t.TestCase} tc
*/
export const testRelativePositionWithUndo = tc => {
const ydoc = new Y.Doc()
const ytext = ydoc.getText()
ytext.insert(0, 'hello world')
const rpos = Y.createRelativePositionFromTypeIndex(ytext, 1)
const um = new Y.UndoManager(ytext)
ytext.delete(0, 6)
t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydoc)?.index === 0)
um.undo()
t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydoc)?.index === 1)
const posWithoutFollow = Y.createAbsolutePositionFromRelativePosition(rpos, ydoc, false)
console.log({ posWithoutFollow })
t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydoc, false)?.index === 6)
const ydocClone = new Y.Doc()
Y.applyUpdate(ydocClone, Y.encodeStateAsUpdate(ydoc))
t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydocClone)?.index === 6)
t.assert(Y.createAbsolutePositionFromRelativePosition(rpos, ydocClone, false)?.index === 6)
}

View File

@@ -330,6 +330,29 @@ export const testObserveDeepEventOrder = tc => {
}
}
/**
* Correct index when computing event.path in observeDeep - https://github.com/yjs/yjs/issues/457
*
* @param {t.TestCase} _tc
*/
export const testObservedeepIndexes = _tc => {
const doc = new Y.Doc()
const map = doc.getMap()
// Create a field with the array as value
map.set('my-array', new Y.Array())
// Fill the array with some strings and our Map
map.get('my-array').push(['a', 'b', 'c', new Y.Map()])
/**
* @type {Array<any>}
*/
let eventPath = []
map.observeDeep((events) => { eventPath = events[0].path })
// set a value on the map inside of our array
map.get('my-array').get(3).set('hello', 'world')
console.log(eventPath)
t.compare(eventPath, ['my-array', 3])
}
/**
* @param {t.TestCase} tc
*/

View File

@@ -1746,6 +1746,27 @@ export const testBasicFormat = tc => {
compare(users)
}
/**
* @param {t.TestCase} tc
*/
export const testFalsyFormats = tc => {
const { users, text0 } = init(tc, { users: 2 })
let delta
text0.observe(event => {
delta = event.delta
})
text0.insert(0, 'abcde', { falsy: false })
t.compare(text0.toDelta(), [{ insert: 'abcde', attributes: { falsy: false } }])
t.compare(delta, [{ insert: 'abcde', attributes: { falsy: false } }])
text0.format(1, 3, { falsy: true })
t.compare(text0.toDelta(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'bcd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
t.compare(delta, [{ retain: 1 }, { retain: 3, attributes: { falsy: true } }])
text0.format(2, 1, { falsy: false })
t.compare(text0.toDelta(), [{ insert: 'a', attributes: { falsy: false } }, { insert: 'b', attributes: { falsy: true } }, { insert: 'c', attributes: { falsy: false } }, { insert: 'd', attributes: { falsy: true } }, { insert: 'e', attributes: { falsy: false } }])
t.compare(delta, [{ retain: 2 }, { retain: 1, attributes: { falsy: false } }])
compare(users)
}
/**
* @param {t.TestCase} _tc
*/