Compare commits

..

29 Commits

Author SHA1 Message Date
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
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
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 Jahns
1e69d650b8 13.6.14 2024-03-01 11:31:21 +01:00
Kevin Jahns
133cfc9cdc allow falsy values in formatting attributes 2024-03-01 11:29:14 +01:00
Kevin Jahns
83db6c814c Merge pull request #619 from jul13579/allow-falsy-attribute-values
Allow falsy attribute values
2024-03-01 11:23:04 +01:00
Julian Lehrhuber
cdbb55818d Allow falsy attribute values 2024-03-01 10:37:51 +01:00
17 changed files with 152 additions and 32 deletions

View File

@@ -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 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 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 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 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 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 using a heuristic that is still changing (currently, it is updated when a new

View File

@@ -49,7 +49,9 @@ Showcase](https://yjs-diagram.synergy.codes/).
## Who is using Yjs ## Who is using Yjs
* [AFFiNE](https://affine.pro/) A local-first, privacy-first, open source * [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: * [Cargo](https://cargo.site/) Site builder for designers and artists :star2:
* [Gitbook](https://gitbook.com) Knowledge management for technical teams :star2: * [Gitbook](https://gitbook.com) Knowledge management for technical teams :star2:
* [Evernote](https://evernote.com) Note-taking app :star2: * [Evernote](https://evernote.com) Note-taking app :star2:
@@ -64,11 +66,15 @@ Showcase](https://yjs-diagram.synergy.codes/).
Nimbus Web. :star: Nimbus Web. :star:
* [Pluxbox RadioManager](https://getradiomanager.com/) A web-based app to * [Pluxbox RadioManager](https://getradiomanager.com/) A web-based app to
collaboratively organize radio broadcasts. :star: 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 * [Sana](https://sanalabs.com/) A learning platform with collaborative text
editing powered by Yjs. editing powered by Yjs.
* [Serenity Notes](https://www.serenity.re/en/notes) End-to-end encrypted * [Serenity Notes](https://www.serenity.re/en/notes) End-to-end encrypted
collaborative notes app. 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 * [Alldone](https://alldone.app/) A next-gen project management and
collaboration platform. collaboration platform.
* [Living Spec](https://livingspec.com/) A modern way for product teams to collaborate. * [Living Spec](https://livingspec.com/) A modern way for product teams to collaborate.
@@ -91,6 +97,18 @@ Showcase](https://yjs-diagram.synergy.codes/).
* [AWS SageMaker](https://aws.amazon.com/sagemaker/) Tools for building Machine * [AWS SageMaker](https://aws.amazon.com/sagemaker/) Tools for building Machine
Learning Models Learning Models
* [linear](https://linear.app) Streamline issues, projects, and product roadmaps. * [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
## Table of Contents ## Table of Contents
@@ -145,9 +163,10 @@ collaborative app.
<dt><a href="https://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. <a href="https://github.com/yjs/y-redis/"><b>y-redis</b></a>,
leveldb database. <b>y-sweet</b> and <b>ypy-websocket</b> (see below) are <b>y-sweet</b>, <b>ypy-websocket</b> and <a href="https://tiptap.dev/docs/hocuspocus/introduction">
compatible to the y-wesocket protocol. <b>Hocuspocus</b></a> (see below) are alternative
backends to y-websocket.
</dd> </dd>
<dt><a href="https://github.com/yjs/y-webrtc">y-webrtc</a></dt> <dt><a href="https://github.com/yjs/y-webrtc">y-webrtc</a></dt>
<dd> <dd>
@@ -169,6 +188,10 @@ browser DevTools extension.
<dd> <dd>
A standalone yjs server with persistence to S3 or filesystem. They offer a A standalone yjs server with persistence to S3 or filesystem. They offer a
<a href="https://y-sweet.cloud">cloud service</a> as well. <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> </dd>
<dt><a href="https://docs.partykit.io/reference/y-partykit-api/">PartyKit</a></dt> <dt><a href="https://docs.partykit.io/reference/y-partykit-api/">PartyKit</a></dt>
<dd> <dd>
@@ -205,6 +228,15 @@ An ActionCable companion for Yjs clients. There is a fitting
<dd> <dd>
Websocket backend, written in Python. Websocket backend, written in Python.
</dd> </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> </dl>
#### Persistence Providers #### Persistence Providers

4
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.6.13", "version": "13.6.15",
"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

@@ -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>} * @return {AbstractType<EventType>}
*/ */
clone () { 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 {AbstractType<any>} type
* @param {function(any,number,any):void} f A function to execute on every element of this YArray. * @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. * Operates on a snapshotted state of the document.
* *
* @param {AbstractType<any>} type * @param {AbstractType<any>} type

View File

@@ -95,6 +95,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>} * @return {YArray<T>}
*/ */
clone () { clone () {
@@ -244,7 +248,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. * @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>} * @return {YMap<MapType>}
*/ */
clone () { clone () {

View File

@@ -201,7 +201,7 @@ const minimizeAttributeChanges = (currPos, attributes) => {
while (true) { while (true) {
if (currPos.right === null) { if (currPos.right === null) {
break break
} else if (currPos.right.deleted || (currPos.right.content.constructor === ContentFormat && equalAttrs(attributes[(/** @type {ContentFormat} */ (currPos.right.content)).key] || null, /** @type {ContentFormat} */ (currPos.right.content).value))) { } else if (currPos.right.deleted || (currPos.right.content.constructor === ContentFormat && equalAttrs(attributes[(/** @type {ContentFormat} */ (currPos.right.content)).key] ?? null, /** @type {ContentFormat} */ (currPos.right.content).value))) {
// //
} else { } else {
break break
@@ -227,7 +227,7 @@ const insertAttributes = (transaction, parent, currPos, attributes) => {
// insert format-start items // insert format-start items
for (const key in attributes) { for (const key in attributes) {
const val = attributes[key] const val = attributes[key]
const currentVal = currPos.currentAttributes.get(key) || null const currentVal = currPos.currentAttributes.get(key) ?? null
if (!equalAttrs(currentVal, val)) { if (!equalAttrs(currentVal, val)) {
// save negated attribute (set null if currentVal undefined) // save negated attribute (set null if currentVal undefined)
negatedAttributes.set(key, currentVal) negatedAttributes.set(key, currentVal)
@@ -389,12 +389,12 @@ const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAtt
switch (content.constructor) { switch (content.constructor) {
case ContentFormat: { case ContentFormat: {
const { key, value } = /** @type {ContentFormat} */ (content) const { key, value } = /** @type {ContentFormat} */ (content)
const startAttrValue = startAttributes.get(key) || null const startAttrValue = startAttributes.get(key) ?? null
if (endFormats.get(key) !== content || startAttrValue === value) { if (endFormats.get(key) !== content || startAttrValue === value) {
// Either this format is overwritten or it is not necessary because the attribute already existed. // Either this format is overwritten or it is not necessary because the attribute already existed.
start.delete(transaction) start.delete(transaction)
cleanups++ cleanups++
if (!reachedCurr && (currAttributes.get(key) || null) === value && startAttrValue !== value) { if (!reachedCurr && (currAttributes.get(key) ?? null) === value && startAttrValue !== value) {
if (startAttrValue === null) { if (startAttrValue === null) {
currAttributes.delete(key) currAttributes.delete(key)
} else { } else {
@@ -769,12 +769,12 @@ export class YTextEvent extends YEvent {
const { key, value } = /** @type {ContentFormat} */ (item.content) const { key, value } = /** @type {ContentFormat} */ (item.content)
if (this.adds(item)) { if (this.adds(item)) {
if (!this.deletes(item)) { if (!this.deletes(item)) {
const curVal = currentAttributes.get(key) || null const curVal = currentAttributes.get(key) ?? null
if (!equalAttrs(curVal, value)) { if (!equalAttrs(curVal, value)) {
if (action === 'retain') { if (action === 'retain') {
addOp() addOp()
} }
if (equalAttrs(value, (oldAttributes.get(key) || null))) { if (equalAttrs(value, (oldAttributes.get(key) ?? null))) {
delete attributes[key] delete attributes[key]
} else { } else {
attributes[key] = value attributes[key] = value
@@ -785,7 +785,7 @@ export class YTextEvent extends YEvent {
} }
} else if (this.deletes(item)) { } else if (this.deletes(item)) {
oldAttributes.set(key, value) oldAttributes.set(key, value)
const curVal = currentAttributes.get(key) || null const curVal = currentAttributes.get(key) ?? null
if (!equalAttrs(curVal, value)) { if (!equalAttrs(curVal, value)) {
if (action === 'retain') { if (action === 'retain') {
addOp() addOp()
@@ -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} * @return {YText}
*/ */
clone () { 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>} * @return {YXmlElement<KV>}
*/ */
clone () { 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} * @return {YXmlFragment}
*/ */
clone () { clone () {
@@ -406,7 +410,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. * @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} * @return {YXmlHook}
*/ */
clone () { 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} * @return {YXmlText}
*/ */
clone () { clone () {

View File

@@ -187,22 +187,22 @@ export class Doc extends ObservableV2 {
/** /**
* Define a shared data type. * 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. * 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:* * *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)`, .. * Also use the typed methods `getText(name)`, `getArray(name)`, ..
* *
* @template {typeof AbstractType<any>} Type * @template {typeof AbstractType<any>} Type
* @example * @example
* const y = new Y(..) * const ydoc = new Y.Doc(..)
* const appState = { * const appState = {
* document: y.getText('document') * document: ydoc.getText('document')
* comments: y.getArray('comments') * comments: ydoc.getArray('comments')
* } * }
* *
* @param {string} name * @param {string} name

View File

@@ -8,6 +8,7 @@ import {
createID, createID,
ContentType, ContentType,
followRedone, followRedone,
getItem,
ID, Doc, AbstractType // eslint-disable-line ID, Doc, AbstractType // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@@ -256,13 +257,24 @@ export const readRelativePosition = decoder => {
export const decodeRelativePosition = uint8Array => readRelativePosition(decoding.createDecoder(uint8Array)) 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 {RelativePosition} rpos
* @param {Doc} doc * @param {Doc} doc
* @param {boolean} followUndoneDeletions - whether to follow undone deletions - see https://github.com/yjs/yjs/issues/638
* @return {AbsolutePosition|null} * @return {AbsolutePosition|null}
* *
* @function * @function
*/ */
export const createAbsolutePositionFromRelativePosition = (rpos, doc) => { export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndoneDeletions = true) => {
const store = doc.store const store = doc.store
const rightID = rpos.item const rightID = rpos.item
const typeID = rpos.type const typeID = rpos.type
@@ -274,7 +286,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
if (getState(store, rightID.client) <= rightID.clock) { if (getState(store, rightID.client) <= rightID.clock) {
return null return null
} }
const res = followRedone(store, rightID) const res = followUndoneDeletions ? followRedone(store, rightID) : { item: getItem(store, rightID), diff: 0 }
const right = res.item const right = res.item
if (!(right instanceof Item)) { if (!(right instanceof Item)) {
return null return null
@@ -298,7 +310,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
// type does not exist yet // type does not exist yet
return null 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) { if (item instanceof Item && item.content instanceof ContentType) {
type = item.content.type type = item.content.type
} else { } else {

View File

@@ -28,7 +28,8 @@ import { callAll } from 'lib0/function'
* possible. Here is an example to illustrate the advantages of bundling: * possible. Here is an example to illustrate the advantages of bundling:
* *
* @example * @example
* const map = y.define('map', YMap) * const ydoc = new Y.Doc()
* const map = ydoc.getMap('map')
* // Log content when change is triggered * // Log content when change is triggered
* map.observe(() => { * map.observe(() => {
* console.log('change triggered') * console.log('change triggered')
@@ -37,7 +38,7 @@ import { callAll } from 'lib0/function'
* map.set('a', 0) // => "change triggered" * map.set('a', 0) // => "change triggered"
* map.set('b', 0) // => "change triggered" * map.set('b', 0) // => "change triggered"
* // When put in a transaction, it will trigger the log after the transaction: * // When put in a transaction, it will trigger the log after the transaction:
* y.transact(() => { * ydoc.transact(() => {
* map.set('a', 1) * map.set('a', 1)
* map.set('b', 1) * map.set('b', 1)
* }) // => "change triggered" * }) // => "change triggered"

View File

@@ -101,3 +101,25 @@ export const testRelativePositionAssociationDifference = tc => {
t.assert(posRight != null && posRight.index === 2) t.assert(posRight != null && posRight.index === 2)
t.assert(posLeft != null && posLeft.index === 1) 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

@@ -1746,6 +1746,27 @@ export const testBasicFormat = tc => {
compare(users) 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 * @param {t.TestCase} _tc
*/ */