Skip to content

API

import * as Y from 'yjs'

共享类型

Shared Types

Y.Array

一种可共享的类数组类型,支持在任何位置高效地插入/删除元素。内部使用链表的数组,在必要时进行拆分。

const yarray = new Y.Array()
Y.Array.from(Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>): Y.Array
基于现有内容创建 Y.Array 的替代工厂函数。
parent:Y.AbstractType|null
insert(index:number, content:Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
index 插入内容。注意,内容是元素的数组。 即 array.insert(0, [1]) 会在位置 0 插入 1。
push(Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
unshift(Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
delete(index:number, length:number)
get(index:number)
slice(start:number, end:number):Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>
检索内容的范围
length:number
forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type, index:number, array: Y.Array))
map(function(T, number, YArray):M):Array<M>
clone(): Y.Array
将所有值克隆到一个新的 Y.Array 实例中。返回的类型可以包含到 Yjs 文档中。
toArray():Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>
将此 YArray 的内容复制到一个新数组中。
toJSON():Array<Object|boolean|Array|string|number|null>
将此 YArray 的内容复制到一个新数组中。它使用其 toJSON 方法将所有子类型转换为 JSON。
[Symbol.Iterator]
返回一个 YArray 迭代器,包含数组中每个索引的值。
for (let value of yarray) { .. }
observe(function(YArrayEvent, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。
unobserve(function(YArrayEvent, Transaction):void)
从此类型移除 observe 事件监听器。
observeDeep(function(Array<YEvent>, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型或其任何子项时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。事件监听器接收由自身或任何子项创建的所有事件。
unobserveDeep(function(Array<YEvent>, Transaction):void)
从此类型移除 observeDeep 事件监听器。

A shareable Array-like type that supports efficient insert/delete of elements at any position. Internally it uses a linked list of Arrays that is split when necessary.

const yarray = new Y.Array()
Y.Array.from(Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>): Y.Array
An alternative factory function to create a Y.Array based on existing content.
parent:Y.AbstractType|null
insert(index:number, content:Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
Insert content at index. Note that content is an array of elements. I.e. array.insert(0, [1]) splices the list and inserts 1 at position 0.
push(Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
unshift(Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
delete(index:number, length:number)
get(index:number)
slice(start:number, end:number):Array<Object|boolean|Array|string|number|null|Uint8Array|Y.Type>
Retrieve a range of content
length:number
forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type, index:number, array: Y.Array))
map(function(T, number, YArray):M):Array<M>
clone(): Y.Array
Clone all values into a fresh Y.Array instance. The returned type can be included into the Yjs document.
toArray():Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>
Copies the content of this YArray to a new Array.
toJSON():Array<Object|boolean|Array|string|number|null>
Copies the content of this YArray to a new Array. It transforms all child types to JSON using their toJSON method.
[Symbol.Iterator]
Returns an YArray Iterator that contains the values for each index in the array.
for (let value of yarray) { .. }
observe(function(YArrayEvent, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns.
unobserve(function(YArrayEvent, Transaction):void)
Removes an observe event listener from this type.
observeDeep(function(Array<YEvent>, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type or any of its children is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns. The event listener receives all Events created by itself or any of its children.
unobserveDeep(function(Array<YEvent>, Transaction):void)
Removes an observeDeep event listener from this type.

Y.Map

一种可共享的 Map 类型。

const ymap = new Y.Map()
parent:Y.AbstractType|null
size: number
键/值对的总数。
get(key:string):object|boolean|string|number|null|Uint8Array|Y.Type
set(key:string, value:object|boolean|string|number|null|Uint8Array|Y.Type)
delete(key:string)
has(key:string):boolean
clear()
从此 YMap 中移除所有元素。
clone():Y.Map
将此类型克隆为一个新的 Yjs 类型。
toJSON():Object<string, Object|boolean|Array|string|number|null|Uint8Array>
将此 YMap 的 [key,value] 对复制到一个新对象。它使用其 toJSON 方法将所有子类型转换为 JSON。
forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type, key:string, map: Y.Map))
对每个键值对执行提供的函数一次。
[Symbol.Iterator]
返回一个 [key, value] 对的迭代器。
for (let [key, value] of ymap) { .. }
entries()
返回一个 [key, value] 对的迭代器。
values()
返回所有值的迭代器。
keys()
返回所有键的迭代器。
observe(function(YMapEvent, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。
unobserve(function(YMapEvent, Transaction):void)
从此类型移除 observe 事件监听器。
observeDeep(function(Array<YEvent>, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型或其任何子项时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。事件监听器接收由自身或任何子项创建的所有事件。
unobserveDeep(function(Array<YEvent>, Transaction):void)
从此类型移除 observeDeep 事件监听器。

A shareable Map type.

const ymap = new Y.Map()
parent:Y.AbstractType|null
size: number
Total number of key/value pairs.
get(key:string):object|boolean|string|number|null|Uint8Array|Y.Type
set(key:string, value:object|boolean|string|number|null|Uint8Array|Y.Type)
delete(key:string)
has(key:string):boolean
clear()
Removes all elements from this YMap.
clone():Y.Map
Clone this type into a fresh Yjs type.
toJSON():Object<string, Object|boolean|Array|string|number|null|Uint8Array>
Copies the [key,value] pairs of this YMap to a new Object.It transforms all child types to JSON using their toJSON method.
forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type, key:string, map: Y.Map))
Execute the provided function once for every key-value pair.
[Symbol.Iterator]
Returns an Iterator of [key, value] pairs.
for (let [key, value] of ymap) { .. }
entries()
Returns an Iterator of [key, value] pairs.
values()
Returns an Iterator of all values.
keys()
Returns an Iterator of all keys.
observe(function(YMapEvent, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns.
unobserve(function(YMapEvent, Transaction):void)
Removes an observe event listener from this type.
observeDeep(function(Array<YEvent>, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type or any of its children is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns. The event listener receives all Events created by itself or any of its children.
unobserveDeep(function(Array<YEvent>, Transaction):void)
Removes an observeDeep event listener from this type.

Y.Text

一种可共享的类型,专为文本的共享编辑而优化。它允许为文本中的范围分配属性。这使得实现丰富文本绑定成为可能。

该类型还可以转换为 delta 格式。类似地,YTextEvents 计算变化为增量。

const ytext = new Y.Text()
parent:Y.AbstractType|null
insert(index:number, content:string, [formattingAttributes:Object<string,string>])
index 插入字符串并为其分配格式属性。
ytext.insert(0, 'bold text', { bold: true })
delete(index:number, length:number)
format(index:number, length:number, formattingAttributes:Object<string,string>)
为文本中的范围分配格式属性
applyDelta(delta: Delta, opts:Object<string,any>)
参见 Quill Delta 可以设置选项以防止移除结尾的新行,默认值为 true。
ytext.applyDelta(delta, { sanitize: false })
length:number
toString():string
将此类型(不带格式选项)转换为字符串。
toJSON():string
参见 toString
toDelta():Delta
将此类型转换为 Quill Delta
observe(function(YTextEvent, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。
unobserve(function(YTextEvent, Transaction):void)
从此类型移除 observe 事件监听器。
observeDeep(function(Array<YEvent>, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型或其任何子项时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。事件监听器接收由自身或任何子项创建的所有事件。
unobserveDeep(function(Array<YEvent>, Transaction):void)
从此类型移除 observeDeep 事件监听器。

A shareable type that is optimized for shared editing on text. It allows to assign properties to ranges in the text. This makes it possible to implement rich-text bindings to this type.

This type can also be transformed to the delta format. Similarly the YTextEvents compute changes as deltas.

const ytext = new Y.Text()
parent:Y.AbstractType|null
insert(index:number, content:string, [formattingAttributes:Object<string,string>])
Insert a string at index and assign formatting attributes to it.
ytext.insert(0, 'bold text', { bold: true })
delete(index:number, length:number)
format(index:number, length:number, formattingAttributes:Object<string,string>)
Assign formatting attributes to a range in the text
applyDelta(delta: Delta, opts:Object<string,any>)
See Quill Delta Can set options for preventing remove ending newLines, default is true.
ytext.applyDelta(delta, { sanitize: false })
length:number
toString():string
Transforms this type, without formatting options, into a string.
toJSON():string
See toString
toDelta():Delta
Transforms this type to a Quill Delta
observe(function(YTextEvent, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns.
unobserve(function(YTextEvent, Transaction):void)
Removes an observe event listener from this type.
observeDeep(function(Array<YEvent>, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type or any of its children is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns. The event listener receives all Events created by itself or any of its children.
unobserveDeep(function(Array<YEvent>, Transaction):void)
Removes an observeDeep event listener from this type.

Y.XmlFragment

一个包含 Y.XmlElements 数组的容器。

const yxml = new Y.XmlFragment()
parent:Y.AbstractType|null
firstChild:Y.XmlElement|Y.XmlText|null
insert(index:number, content:Array<Y.XmlElement|Y.XmlText>)
delete(index:number, length:number)
get(index:number)
slice(start:number, end:number):Array<Y.XmlElement|Y.XmlText>
检索内容范围
length:number
clone():Y.XmlFragment
将此类型克隆为一个新的 Yjs 类型。
toArray():Array<Y.XmlElement|Y.XmlText>
将子项复制到一个新的数组中。
toDOM():DocumentFragment
将此类型及所有子项转换为新的 DOM 元素。
toString():string
获取所有后代的 XML 序列化。
toJSON():string
参见 toString
createTreeWalker(filter: function(AbstractType<any>):boolean):Iterable
创建一个可迭代对象,以遍历子项。
observe(function(YXmlEvent, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。
unobserve(function(YXmlEvent, Transaction):void)
从此类型移除 observe 事件监听器。
observeDeep(function(Array<YEvent>, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型或其任何子项时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。事件监听器接收由自身或任何子项创建的所有事件。
unobserveDeep(function(Array<YEvent>, Transaction):void)
从此类型移除 observeDeep 事件监听器。

A container that holds an Array of Y.XmlElements.

const yxml = new Y.XmlFragment()
parent:Y.AbstractType|null
firstChild:Y.XmlElement|Y.XmlText|null
insert(index:number, content:Array<Y.XmlElement|Y.XmlText>)
delete(index:number, length:number)
get(index:number)
slice(start:number, end:number):Array<Y.XmlElement|Y.XmlText>
Retrieve a range of content
length:number
clone():Y.XmlFragment
Clone this type into a fresh Yjs type.
toArray():Array<Y.XmlElement|Y.XmlText>
Copies the children to a new Array.
toDOM():DocumentFragment
Transforms this type and all children to new DOM elements.
toString():string
Get the XML serialization of all descendants.
toJSON():string
See toString.
createTreeWalker(filter: function(AbstractType<any>):boolean):Iterable
Create an Iterable that walks through the children.
observe(function(YXmlEvent, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns.
unobserve(function(YXmlEvent, Transaction):void)
Removes an observe event listener from this type.
observeDeep(function(Array<YEvent>, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type or any of its children is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns. The event listener receives all Events created by itself or any of its children.
unobserveDeep(function(Array<YEvent>, Transaction):void)
Removes an observeDeep event listener from this type.

Y.XmlElement

一个可共享的类型,表示一个 XML 元素。它具有 nodeName、属性和子项列表,但并不努力验证其内容或确保符合 XML 标准。

const yxml = new Y.XmlElement()
parent:Y.AbstractType|null
firstChild:Y.XmlElement|Y.XmlText|null
nextSibling:Y.XmlElement|Y.XmlText|null
prevSibling:Y.XmlElement|Y.XmlText|null
insert(index:number, content:Array<Y.XmlElement|Y.XmlText>)
delete(index:number, length:number)
get(index:number)
length:number
setAttribute(attributeName:string, attributeValue:string)
removeAttribute(attributeName:string)
getAttribute(attributeName:string):string
getAttributes():Object<string,string>
get(i:number):Y.XmlElement|Y.XmlText
检索第 i 个元素。
slice(start:number, end:number):Array<Y.XmlElement|Y.XmlText>
检索内容范围
clone():Y.XmlElement
将此类型克隆为一个新的 Yjs 类型。
toArray():Array<Y.XmlElement|Y.XmlText>
将子项复制到一个新的数组中。
toDOM():Element
将此类型及所有子项转换为一个新的 DOM 元素。
toString():string
获取所有后代的 XML 序列化。
toJSON():string
参见 toString
observe(function(YXmlEvent, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。
unobserve(function(YXmlEvent, Transaction):void)
从此类型移除 observe 事件监听器。
observeDeep(function(Array<YEvent>, Transaction):void)
为此类型添加一个事件监听器,每次修改此类型或其任何子项时都会同步调用。若在事件监听器中修改了此类型,当前事件监听器返回后会再次调用该事件监听器。事件监听器接收由自身或任何子项创建的所有事件。
unobserveDeep(function(Array<YEvent>, Transaction):void)
从此类型移除 observeDeep 事件监听器。

A shareable type that represents an XML Element. It has a nodeName, attributes, and a list of children. But it makes no effort to validate its content and be actually XML compliant.

const yxml = new Y.XmlElement()
parent:Y.AbstractType|null
firstChild:Y.XmlElement|Y.XmlText|null
nextSibling:Y.XmlElement|Y.XmlText|null
prevSibling:Y.XmlElement|Y.XmlText|null
insert(index:number, content:Array<Y.XmlElement|Y.XmlText>)
delete(index:number, length:number)
get(index:number)
length:number
setAttribute(attributeName:string, attributeValue:string)
removeAttribute(attributeName:string)
getAttribute(attributeName:string):string
getAttributes():Object<string,string>
get(i:number):Y.XmlElement|Y.XmlText
Retrieve the i-th element.
slice(start:number, end:number):Array<Y.XmlElement|Y.XmlText>
Retrieve a range of content
clone():Y.XmlElement
Clone this type into a fresh Yjs type.
toArray():Array<Y.XmlElement|Y.XmlText>
Copies the children to a new Array.
toDOM():Element
Transforms this type and all children to a new DOM element.
toString():string
Get the XML serialization of all descendants.
toJSON():string
See toString.
observe(function(YXmlEvent, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns.
unobserve(function(YXmlEvent, Transaction):void)
Removes an observe event listener from this type.
observeDeep(function(Array<YEvent>, Transaction):void)
Adds an event listener to this type that will be called synchronously every time this type or any of its children is modified. In the case this type is modified in the event listener, the event listener will be called again after the current event listener returns. The event listener receives all Events created by itself or any of its children.
unobserveDeep(function(Array<YEvent>, Transaction):void)
Removes an observeDeep event listener from this type.

Y.Doc

const doc = new Y.Doc()

clientID
一个唯一的 ID,用于标识此客户端。(只读)
gc
是否在此文档实例上启用垃圾回收。设置 doc.gc = false 以禁用垃圾回收并能够恢复旧内容。有关 Yjs 中垃圾回收的更多信息,请参见 https://github.com/yjs/yjs#yjs-crdt-algorithm。
transact(function(Transaction):void [, origin:any])
每次对共享文档的更改都发生在一个事务中。观察者调用和 update 事件在每个事务后被调用。您应该将更改打包到一个单独的事务中,以减少事件调用的数量。即 doc.transact(() => { yarray.insert(..); ymap.set(..) }) 触发一个单一的更改事件。
您可以指定一个可选的 origin 参数,该参数存储在 transaction.originon('update', (update, origin) => ..) 中。
toJSON():any
已弃用:建议直接在共享类型上调用 toJSON。将整个文档转换为 js 对象,递归遍历每个 yjs 类型。不会记录未定义的类型(使用 ydoc.getType(..))。
get(string, Y.[TypeClass]):[Type]
定义一个共享类型。
getArray(string):Y.Array
定义一个共享的 Y.Array 类型。相当于 y.get(string, Y.Array)
getMap(string):Y.Map
定义一个共享的 Y.Map 类型。相当于 y.get(string, Y.Map)
getText(string):Y.Text
定义一个共享的 Y.Text 类型。相当于 y.get(string, Y.Text)
getXmlElement(string, string):Y.XmlElement
定义一个共享的 Y.XmlElement 类型。相当于 y.get(string, Y.XmlElement)
getXmlFragment(string):Y.XmlFragment
定义一个共享的 Y.XmlFragment 类型。相当于 y.get(string, Y.XmlFragment)
on(string, function)
在共享类型上注册一个事件监听器
off(string, function)
从共享类型中注销一个事件监听器

const doc = new Y.Doc()

clientID
A unique id that identifies this client. (readonly)
gc
Whether garbage collection is enabled on this doc instance. Set doc.gc = false in order to disable gc and be able to restore old content. See https://github.com/yjs/yjs#yjs-crdt-algorithm for more information about gc in Yjs.
transact(function(Transaction):void [, origin:any])
Every change on the shared document happens in a transaction. Observer calls and the update event are called after each transaction. You should bundle changes into a single transaction to reduce the amount of event calls. I.e. doc.transact(() => { yarray.insert(..); ymap.set(..) }) triggers a single change event.
You can specify an optional origin parameter that is stored on transaction.origin and on('update', (update, origin) => ..).
toJSON():any
Deprecated: It is recommended to call toJSON directly on the shared types. Converts the entire document into a js object, recursively traversing each yjs type. Doesn't log types that have not been defined (using ydoc.getType(..)).
get(string, Y.[TypeClass]):[Type]
Define a shared type.
getArray(string):Y.Array
Define a shared Y.Array type. Is equivalent to y.get(string, Y.Array).
getMap(string):Y.Map
Define a shared Y.Map type. Is equivalent to y.get(string, Y.Map).
getText(string):Y.Text
Define a shared Y.Text type. Is equivalent to y.get(string, Y.Text).
getXmlElement(string, string):Y.XmlElement
Define a shared Y.XmlElement type. Is equivalent to y.get(string, Y.XmlElement).
getXmlFragment(string):Y.XmlFragment
Define a shared Y.XmlFragment type. Is equivalent to y.get(string, Y.XmlFragment).
on(string, function)
Register an event listener on the shared type
off(string, function)
Unregister an event listener from the shared type

Y.Doc 事件

on('update', function(updateMessage:Uint8Array, origin:any, Y.Doc):void)
监听文档更新。文档更新必须传递给所有其他对等方。您可以以任何顺序和多次应用文档更新。使用 updateV2 接收 V2 事件。
on('beforeTransaction', function(Y.Transaction, Y.Doc):void)
在每个事务之前发出。
on('afterTransaction', function(Y.Transaction, Y.Doc):void)
在每个事务之后发出。
on('beforeAllTransactions', function(Y.Doc):void)
事务可以嵌套(例如,当一个事务中的事件调用另一个事务时)。在第一个事务之前发出。
on('afterAllTransactions', function(Y.Doc, Array<Y.Transaction>):void)
在最后一个事务清理后发出。

on('update', function(updateMessage:Uint8Array, origin:any, Y.Doc):void)
Listen to document updates. Document updates must be transmitted to all other peers. You can apply document updates in any order and multiple times. Use updateV2 to receive V2 events.
on('beforeTransaction', function(Y.Transaction, Y.Doc):void)
Emitted before each transaction.
on('afterTransaction', function(Y.Transaction, Y.Doc):void)
Emitted after each transaction.
on('beforeAllTransactions', function(Y.Doc):void)
Transactions can be nested (e.g. when an event within a transaction calls another transaction). Emitted before the first transaction.
on('afterAllTransactions', function(Y.Doc, Array<Y.Transaction>):void)
Emitted after the last transaction is cleaned up.

文档更新

Document Updates

对共享文档的更改被编码为 文档更新。文档更新是 交换律幂等 的。这意味着它们可以以任何顺序和多次应用。

Changes on the shared document are encoded into document updates. Document updates are commutative and idempotent. This means that they can be applied in any order and multiple times.

示例:监听更新事件并在远程客户端上应用

Example: Sync two clients by exchanging the complete document structure

const doc1 = new Y.Doc()
const doc2 = new Y.Doc()

doc1.on('update', update => {
  Y.applyUpdate(doc2, update)
})

doc2.on('update', update => {
  Y.applyUpdate(doc1, update)
})

// 所有更改也应用于另一个文档
doc1.getArray('myarray').insert(0, ['Hello doc2, you got this?'])
doc2.getArray('myarray').get(0) // => 'Hello doc2, you got this?'

Yjs 内部维护一个 状态向量,表示每个客户端期望的下一个时钟。在不同的解释中,它保存由每个客户端创建的结构数量。当两个客户端同步时,您可以选择交换完整的文档结构或仅发送状态向量以计算差异。

const doc1 = new Y.Doc()
const doc2 = new Y.Doc()

doc1.on('update', update => {
  Y.applyUpdate(doc2, update)
})

doc2.on('update', update => {
  Y.applyUpdate(doc1, update)
})

// All changes are also applied to the other document
doc1.getArray('myarray').insert(0, ['Hello doc2, you got this?'])
doc2.getArray('myarray').get(0) // => 'Hello doc2, you got this?'

Yjs internally maintains a state vector that denotes the next expected clock from each client. In a different interpretation it holds the number of structs created by each client. When two clients sync, you can either exchange the complete document structure or only the differences by sending the state vector to compute the differences.

示例:通过交换完整文档结构同步两个客户端

Example: Sync two clients by exchanging the complete document structure

const state1 = Y.encodeStateAsUpdate(ydoc1)
const state2 = Y.encodeStateAsUpdate(ydoc2)
Y.applyUpdate(ydoc1, state2)
Y.applyUpdate(ydoc2, state1)

示例:通过计算差异同步两个客户端

Example: Sync two clients by computing the differences

此示例演示如何通过仅使用远程客户端的状态向量计算差异,以最小的数据交换量同步两个客户端。使用状态向量同步客户端需要额外的往返,但可以节省大量带宽。

const stateVector1 = Y.encodeStateVector(ydoc1)
const stateVector2 = Y.encodeStateVector(ydoc2)
const diff1 = Y.encodeStateAsUpdate(ydoc1, stateVector2)
const diff2 = Y.encodeStateAsUpdate(ydoc2, stateVector1)
Y.applyUpdate(ydoc1, diff2)
Y.applyUpdate(ydoc2, diff1)

This example shows how to sync two clients with the minimal amount of exchanged data by computing only the differences using the state vector of the remote client. Syncing clients using the state vector requires another roundtrip, but can save a lot of bandwidth.

const stateVector1 = Y.encodeStateVector(ydoc1)
const stateVector2 = Y.encodeStateVector(ydoc2)
const diff1 = Y.encodeStateAsUpdate(ydoc1, stateVector2)
const diff2 = Y.encodeStateAsUpdate(ydoc2, stateVector1)
Y.applyUpdate(ydoc1, diff2)
Y.applyUpdate(ydoc2, diff1)

示例:在不加载 Y.Doc 的情况下同步客户端

Example: Syncing clients without loading the Y.Doc

可以在不将 Yjs 文档加载到内存中的情况下同步客户端并计算增量更新。Yjs 提供一个 API 以直接在二进制文档更新上计算差异。

// 将当前状态编码为二进制缓冲区
let currentState1 = Y.encodeStateAsUpdate(ydoc1)
let currentState2 = Y.encodeStateAsUpdate(ydoc2)
// 现在我们可以继续使用状态向量同步客户端,而不使用 Y.Doc
ydoc1.destroy()
ydoc2.destroy()

const stateVector1 = Y.encodeStateVectorFromUpdate(currentState1)
const stateVector2 = Y.encodeStateVectorFromUpdate(currentState2)
const diff1 = Y.diffUpdate(currentState1, stateVector2)
const diff2 = Y.diffUpdate(currentState2, stateVector1)

// 同步客户端
currentState1 = Y.mergeUpdates([currentState1, diff2])
currentState2 = Y.mergeUpdates([currentState2, diff1])

It is possible to sync clients and compute delta updates without loading the Yjs document to memory. Yjs exposes an API to compute the differences directly on the binary document updates.

// encode the current state as a binary buffer
let currentState1 = Y.encodeStateAsUpdate(ydoc1)
let currentState2 = Y.encodeStateAsUpdate(ydoc2)
// now we can continue syncing clients using state vectors without using the Y.Doc
ydoc1.destroy()
ydoc2.destroy()

const stateVector1 = Y.encodeStateVectorFromUpdate(currentState1)
const stateVector2 = Y.encodeStateVectorFromUpdate(currentState2)
const diff1 = Y.diffUpdate(currentState1, stateVector2)
const diff2 = Y.diffUpdate(currentState2, stateVector1)

// sync clients
currentState1 = Y.mergeUpdates([currentState1, diff2])
currentState2 = Y.mergeUpdates([currentState2, diff1])

混淆更新

Obfuscating Updates

如果您的用户遇到奇怪的错误(例如,富文本编辑器抛出错误消息),您不必请求用户的完整文档。相反,他们可以在发送给您之前混淆文档(即,用无意义的生成内容替换内容)。请注意,某人可能仍会通过查看文档的一般结构来推断内容的类型。但这比请求原始文档要好得多。

混淆更新包含合并所需的所有与 CRDT 相关的数据。因此,合并混淆更新是安全的。

const ydoc = new Y.Doc()
// 进行一些更改..
ydoc.getText().insert(0, 'hello world')
const update = Y.encodeStateAsUpdate(ydoc)
// 以下更新包含混乱的数据
const obfuscatedUpdate = Y.obfuscateUpdate(update)
const ydoc2 = new Y.Doc()
Y.applyUpdate(ydoc2, obfuscatedUpdate)
ydoc2.getText().toString() // => "00000000000"

If one of your users runs into a weird bug (e.g. the rich-text editor throws error messages), then you don't have to request the full document from your user. Instead, they can obfuscate the document (i.e. replace the content with meaningless generated content) before sending it to you. Note that someone might still deduce the type of content by looking at the general structure of the document. But this is much better than requesting the original document.

Obfuscated updates contain all the CRDT-related data that is required for merging. So it is safe to merge obfuscated updates.

const ydoc = new Y.Doc()
// perform some changes..
ydoc.getText().insert(0, 'hello world')
const update = Y.encodeStateAsUpdate(ydoc)
// the below update contains scrambled data
const obfuscatedUpdate = Y.obfuscateUpdate(update)
const ydoc2 = new Y.Doc()
Y.applyUpdate(ydoc2, obfuscatedUpdate)
ydoc2.getText().toString() // => "00000000000"

使用 V2 更新格式

Using V2 update format

Yjs 实现了两种更新格式。默认情况下,您使用的是 V1 更新格式。您可以选择使用 V2 更新格式,该格式提供了更好的压缩效果。并非所有提供者都使用它。不过,如果您正在构建自己的提供者,您已经可以使用它。所有以下函数都有后缀 "V2"。例如 Y.applyUpdateY.applyUpdateV2。此外,在监听更新时,您需要特别监听 V2 事件,例如 yDoc.on('updateV2', …)。我们还支持两种格式之间的转换函数:Y.convertUpdateFormatV1ToV2Y.convertUpdateFormatV2ToV1

Yjs implements two update formats. By default you are using the V1 update format. You can opt-in into the V2 update format which provides much better compression. It is not yet used by all providers. However, you can already use it if you are building your own provider. All below functions are available with the suffix "V2". E.g. Y.applyUpdateY.applyUpdateV2. Also when listening to updates you need to specifically need listen for V2 events e.g. yDoc.on('updateV2', …). We also support conversion functions between both formats: Y.convertUpdateFormatV1ToV2 & Y.convertUpdateFormatV2ToV1.

更新 API

Update API

Y.applyUpdate(Y.Doc, update:Uint8Array, [transactionOrigin:any])
在共享文档上应用文档更新。您可以选择指定 transactionOrigin,该值将存储在 transaction.originydoc.on('update', (update, origin) => ..) 中。
Y.encodeStateAsUpdate(Y.Doc, [encodedTargetStateVector:Uint8Array]):Uint8Array
将文档状态编码为可以应用于远程文档的单个更新消息。可以选择指定目标状态向量,仅将差异写入更新消息。
Y.encodeStateVector(Y.Doc):Uint8Array
计算状态向量并将其编码为 Uint8Array。
Y.mergeUpdates(Array<Uint8Array>)
将多个文档更新合并为单个文档更新,同时删除重复信息。合并后的文档更新始终比单独的更新更小,因为采用了压缩编码。
Y.encodeStateVectorFromUpdate(Uint8Array): Uint8Array
从文档更新计算状态向量并将其编码为 Uint8Array。
Y.diffUpdate(update: Uint8Array, stateVector: Uint8Array): Uint8Array
将缺失的差异编码为另一个更新消息。此函数类似于 Y.encodeStateAsUpdate(ydoc, stateVector),但适用于更新。
convertUpdateFormatV1ToV2
将 V1 更新格式转换为 V2 更新格式。
convertUpdateFormatV2ToV1
将 V2 更新格式转换为 V1 更新格式。

Y.applyUpdate(Y.Doc, update:Uint8Array, [transactionOrigin:any])
Apply a document update on the shared document. Optionally you can specify transactionOrigin that will be stored on transaction.origin and ydoc.on('update', (update, origin) => ..).
Y.encodeStateAsUpdate(Y.Doc, [encodedTargetStateVector:Uint8Array]):Uint8Array
Encode the document state as a single update message that can be applied on the remote document. Optionally specify the target state vector to only write the differences to the update message.
Y.encodeStateVector(Y.Doc):Uint8Array
Computes the state vector and encodes it into an Uint8Array.
Y.mergeUpdates(Array<Uint8Array>)
Merge several document updates into a single document update while removing duplicate information. The merged document update is always smaller than the separate updates because of the compressed encoding.
Y.encodeStateVectorFromUpdate(Uint8Array): Uint8Array
Computes the state vector from a document update and encodes it into an Uint8Array.
Y.diffUpdate(update: Uint8Array, stateVector: Uint8Array): Uint8Array
Encode the missing differences to another update message. This function works similarly to Y.encodeStateAsUpdate(ydoc, stateVector) but works on updates instead.
convertUpdateFormatV1ToV2
Convert V1 update format to the V2 update format.
convertUpdateFormatV2ToV1
Convert V2 update format to the V1 update format.

相对位置

Relative Positions

在处理协作文档时,我们经常需要处理位置。位置可以表示光标位置、选择范围,甚至将评论分配给一段文本。正常的索引位置(以整数表示)不方便使用,因为一旦远程更改操作文档,索引范围就会失效。相对位置为您提供了一个强大的 API 来表达位置。

相对位置固定于共享文档中的一个元素,并且不受远程更改的影响。即给定文档 "a|c",相对位置附加到 c。当远程用户通过在光标之前插入一个字符来修改文档时,光标将保持附加在字符 c 上。insert(1, 'x')("a|c") = "ax|c"。当相对位置设置在文档末尾时,它将保持附加在文档的末尾。

When working with collaborative documents, we often need to work with positions. Positions may represent cursor locations, selection ranges, or even assign a comment to a range of text. Normal index-positions (expressed as integers) are not convenient to use because the index-range is invalidated as soon as a remote change manipulates the document. Relative positions give you a powerful API to express positions.

A relative position is fixated to an element in the shared document and is not affected by remote changes. I.e. given the document "a|c", the relative position is attached to c. When a remote user modifies the document by inserting a character before the cursor, the cursor will stay attached to the character c. insert(1, 'x')("a|c") = "ax|c". When the relative position is set to the end of the document, it will stay attached to the end of the document.

示例:转换为相对位置并返回

Example: Transform to RelativePosition and back

const relPos = Y.createRelativePositionFromTypeIndex(ytext, 2)
const pos = Y.createAbsolutePositionFromRelativePosition(relPos, doc)
pos.type === ytext // => true
pos.index === 2 // => true

示例:将相对位置发送给远程客户端(json)

Example: Send relative position to remote client (json)

const relPos = Y.createRelativePositionFromTypeIndex(ytext, 2)
const encodedRelPos = JSON.stringify(relPos)
// 将 encodedRelPos 发送给远程客户端..
const parsedRelPos = JSON.parse(encodedRelPos)
const pos = Y.createAbsolutePositionFromRelativePosition(parsedRelPos, remoteDoc)
pos.type === remoteytext // => true
pos.index === 2 // => true

示例:将相对位置发送给远程客户端(Uint8Array)

const relPos = Y.createRelativePositionFromTypeIndex(ytext, 2)
const encodedRelPos = Y.encodeRelativePosition(relPos)
// 将 encodedRelPos 发送给远程客户端..
const parsedRelPos = Y.decodeRelativePosition(encodedRelPos)
const pos = Y.createAbsolutePositionFromRelativePosition(parsedRelPos, remoteDoc)
pos.type === remoteytext // => true
pos.index === 2 // => true

Y.createRelativePositionFromTypeIndex(type:Uint8Array|Y.Type, index: number [, assoc=0])
创建一个相对位置,固定于任何序列类型共享的第 i 个元素(如果 assoc >= 0)。默认情况下,该位置与指定索引位置之后的字符关联。如果 assoc < 0,则相对位置与指定索引位置之前的字符关联。
Y.createAbsolutePositionFromRelativePosition(RelativePosition, Y.Doc): { type: Y.AbstractType, index: number, assoc: number } | null
从相对位置创建绝对位置。如果相对位置无法引用,或者类型已被删除,则结果为 null。
Y.encodeRelativePosition(RelativePosition):Uint8Array
将相对位置编码为 Uint8Array。二进制数据是文档更新的首选编码格式。如果您更喜欢 JSON 编码,可以简单地使用 JSON.stringify / JSON.parse 相对位置。
Y.decodeRelativePosition(Uint8Array):RelativePosition
将二进制编码的相对位置解码为 RelativePosition 对象。

const relPos = Y.createRelativePositionFromTypeIndex(ytext, 2)
const encodedRelPos = Y.encodeRelativePosition(relPos)
// send encodedRelPos to remote client..
const parsedRelPos = Y.decodeRelativePosition(encodedRelPos)
const pos = Y.createAbsolutePositionFromRelativePosition(parsedRelPos, remoteDoc)
pos.type === remoteytext // => true
pos.index === 2 // => true

Y.createRelativePositionFromTypeIndex(type:Uint8Array|Y.Type, index: number [, assoc=0])
Create a relative position fixated to the i-th element in any sequence-like shared type (if assoc >= 0). By default, the position associates with the character that comes after the specified index position. If assoc < 0, then the relative position associates with the character before the specified index position.
Y.createAbsolutePositionFromRelativePosition(RelativePosition, Y.Doc): { type: Y.AbstractType, index: number, assoc: number } | null
Create an absolute position from a relative position. If the relative position cannot be referenced, or the type is deleted, then the result is null.
Y.encodeRelativePosition(RelativePosition):Uint8Array
Encode a relative position to an Uint8Array. Binary data is the preferred encoding format for document updates. If you prefer JSON encoding, you can simply JSON.stringify / JSON.parse the relative position instead.
Y.decodeRelativePosition(Uint8Array):RelativePosition
Decode a binary-encoded relative position to a RelativePositon object.

Y.UndoManager

Yjs 附带一个用于选择性撤销/重做 Yjs 类型更改的撤销/重做管理器。这些更改可以选择性地限制在事务来源上。

const ytext = doc.getText('text')
const undoManager = new Y.UndoManager(ytext)

ytext.insert(0, 'abc')
undoManager.undo()
ytext.toString() // => ''
undoManager.redo()
ytext.toString() // => 'abc'

constructor(scope:Y.AbstractType|Array<Y.AbstractType> [, {captureTimeout:number,trackedOrigins:Set<any>,deleteFilter:function(item):boolean}])
接受单个类型作为范围或类型数组。
undo()
redo()
stopCapturing()
on('stack-item-added', { stackItem: { meta: Map<any,any> }, type: 'undo' | 'redo' })
注册一个事件,当一个 StackItem 被添加到撤销或重做栈时被调用。
on('stack-item-updated', { stackItem: { meta: Map<any,any> }, type: 'undo' | 'redo' })
注册一个事件,当现有的 StackItem 被更新时被调用。这在“捕获间隔”内发生两个更改时发生。
on('stack-item-popped', { stackItem: { meta: Map<any,any> }, type: 'undo' | 'redo' })
注册一个事件,当一个 StackItem 从撤销或重做栈中弹出时被调用。
on('stack-cleared', { undoStackCleared: boolean, redoStackCleared: boolean })
注册一个事件,当撤销和/或重做栈被清除时被调用。

Yjs ships with an Undo/Redo manager for selective undo/redo of changes on a Yjs type. The changes can be optionally scoped to transaction origins.

const ytext = doc.getText('text')
const undoManager = new Y.UndoManager(ytext)

ytext.insert(0, 'abc')
undoManager.undo()
ytext.toString() // => ''
undoManager.redo()
ytext.toString() // => 'abc'

constructor(scope:Y.AbstractType|Array<Y.AbstractType> [, {captureTimeout:number,trackedOrigins:Set<any>,deleteFilter:function(item):boolean}])
Accepts either single type as scope or an array of types.
undo()
redo()
stopCapturing()
on('stack-item-added', { stackItem: { meta: Map<any,any> }, type: 'undo' | 'redo' })
Register an event that is called when a StackItem is added to the undo- or the redo-stack.
on('stack-item-updated', { stackItem: { meta: Map<any,any> }, type: 'undo' | 'redo' })
Register an event that is called when an existing StackItem is updated. This happens when two changes happen within a "captureInterval".
on('stack-item-popped', { stackItem: { meta: Map<any,any> }, type: 'undo' | 'redo' })
Register an event that is called when a StackItem is popped from the undo- or the redo-stack.
on('stack-cleared', { undoStackCleared: boolean, redoStackCleared: boolean })
Register an event that is called when the undo- and/or the redo-stack is cleared.

示例:停止捕获

Example: Stop Capturing

UndoManager 会合并在小于 options.captureTimeout 的时间间隔内创建的撤销栈项。调用 um.stopCapturing() 以便下一个栈项不会被合并。

// 未停止捕获
ytext.insert(0, 'a')
ytext.insert(1, 'b')
undoManager.undo()
ytext.toString() // => '' (注意 'ab' 被删除)
// 停止捕获
ytext.insert(0, 'a')
undoManager.stopCapturing()
ytext.insert(0, 'b')
undoManager.undo()
ytext.toString() // => 'a' (注意只有 'b' 被删除)

UndoManager merges Undo-StackItems if they are created within time-gap smaller than options.captureTimeout. Call um.stopCapturing() so that the next StackItem won't be merged.

// without stopCapturing
ytext.insert(0, 'a')
ytext.insert(1, 'b')
undoManager.undo()
ytext.toString() // => '' (note that 'ab' was removed)
// with stopCapturing
ytext.insert(0, 'a')
undoManager.stopCapturing()
ytext.insert(0, 'b')
undoManager.undo()
ytext.toString() // => 'a' (note that only 'b' was removed)

示例:指定跟踪来源

Example: Specify tracked origins

共享文档上的每个更改都有一个来源。如果未指定来源,则默认为 null。通过指定 trackedOrigins,您可以选择性地指定哪些更改应该被 UndoManager 跟踪。UndoManager 实例始终会被添加到 trackedOrigins 中。

class CustomBinding {}

const ytext = doc.getText('text')
const undoManager = new Y.UndoManager(ytext, {
  trackedOrigins: new Set([42, CustomBinding])
})

ytext.insert(0, 'abc')
undoManager.undo()
ytext.toString() // => 'abc' (未跟踪,因为来源为 `null`,并且不属于
                 //           `trackedTransactionOrigins`)
ytext.delete(0, 3) // 撤销更改

doc.transact(() => {
  ytext.insert(0, 'abc')
}, 42)
undoManager.undo()
ytext.toString() // => '' (被跟踪,因为来源是 `trackedTransactionorigins` 的一个实例)

doc.transact(() => {
  ytext.insert(0, 'abc')
}, 41)
undoManager.undo()
ytext.toString() // => 'abc' (未被跟踪,因为 41 不是 `trackedTransactionorigins` 的一个实例)
ytext.delete(0, 3) // 撤销更改

doc.transact(() => {
  ytext.insert(0, 'abc')
}, new CustomBinding())
undoManager.undo()
ytext.toString() // => '' (被跟踪,因为来源是 `CustomBinding`,并且
                 //        `CustomBinding` 在 `trackedTransactionorigins` 中)

Every change on the shared document has an origin. If no origin was specified, it defaults to null. By specifying trackedOrigins you can selectively specify which changes should be tracked by UndoManager. The UndoManager instance is always added to trackedOrigins.

class CustomBinding {}

const ytext = doc.getText('text')
const undoManager = new Y.UndoManager(ytext, {
  trackedOrigins: new Set([42, CustomBinding])
})

ytext.insert(0, 'abc')
undoManager.undo()
ytext.toString() // => 'abc' (does not track because origin `null` and not part
                 //           of `trackedTransactionOrigins`)
ytext.delete(0, 3) // revert change

doc.transact(() => {
  ytext.insert(0, 'abc')
}, 42)
undoManager.undo()
ytext.toString() // => '' (tracked because origin is an instance of `trackedTransactionorigins`)

doc.transact(() => {
  ytext.insert(0, 'abc')
}, 41)
undoManager.undo()
ytext.toString() // => 'abc' (not tracked because 41 is not an instance of
                 //        `trackedTransactionorigins`)
ytext.delete(0, 3) // revert change

doc.transact(() => {
  ytext.insert(0, 'abc')
}, new CustomBinding())
undoManager.undo()
ytext.toString() // => '' (tracked because origin is a `CustomBinding` and
                 //        `CustomBinding` is in `trackedTransactionorigins`)

示例:向 StackItems 添加额外信息

Example: Add additional information to the StackItems

在撤销或重做之前的操作时,通常期望恢复额外的元信息,如光标位置或文档视图。您可以将元信息分配给撤销/重做栈项。

const ytext = doc.getText('text')
const undoManager = new Y.UndoManager(ytext, {
  trackedOrigins: new Set([42, CustomBinding])
})

undoManager.on('stack-item-added', event => {
  // 将当前光标位置保存到栈项中
  event.stackItem.meta.set('cursor-location', getRelativeCursorLocation())
})

undoManager.on('stack-item-popped', event => {
  // 恢复栈项中的当前光标位置
  restoreCursorLocation(event.stackItem.meta.get('cursor-location'))
})

When undoing or redoing a previous action, it is often expected to restore additional meta information like the cursor location or the view on the document. You can assign meta-information to Undo-/Redo-StackItems.

const ytext = doc.getText('text')
const undoManager = new Y.UndoManager(ytext, {
  trackedOrigins: new Set([42, CustomBinding])
})

undoManager.on('stack-item-added', event => {
  // save the current cursor location on the stack-item
  event.stackItem.meta.set('cursor-location', getRelativeCursorLocation())
})

undoManager.on('stack-item-popped', event => {
  // restore the current cursor location on the stack-item
  restoreCursorLocation(event.stackItem.meta.get('cursor-location'))
})