Skip to content

Commit 99a12d5

Browse files
author
wangc
committed
添加中文文档
1 parent fb02d51 commit 99a12d5

File tree

10 files changed

+2816
-1
lines changed

10 files changed

+2816
-1
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
node_modules
22
dist
33
.vscode
4-
docs

docs/INTERNALS.md

Lines changed: 288 additions & 0 deletions
Large diffs are not rendered by default.

docs/LICENSE

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2023
4+
- Kevin Jahns <kevin.jahns@protonmail.com>.
5+
- Chair of Computer Science 5 (Databases & Information Systems), RWTH Aachen University, Germany
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.

docs/api.md

Lines changed: 1565 additions & 0 deletions
Large diffs are not rendered by default.

docs/crdt-algorithm.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Yjs CRDT 算法
2+
3+
**Yjs CRDT Algorithm**
4+
5+
=== "中文"
6+
7+
*无冲突复制数据类型*(CRDT)用于协作编辑,是*操作变换*(OT)的替代方法。两者的简单区分在于,OT 尝试转换索引位置以确保收敛(所有客户端最终拥有相同内容),而 CRDT 则使用通常不涉及索引转换的数学模型,例如链表。OT 目前是文本共享编辑的事实标准。支持无中央真实来源(中央服务器)的共享编辑的 OT 方法在实际操作中需要过多的记录管理,因而不太可行。CRDT 更适合分布式系统,提供了额外的保证,确保文档可以与远程客户端同步,并且不需要中央真实来源。
8+
9+
Yjs 实现了该算法的修改版,详细信息可参考 [这篇论文](https://www.researchgate.net/publication/310212186_Near_Real-Time_Peer-to-Peer_Shared_Editing_on_Extensible_Data_Types)。这篇 [文章](https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/) 解释了 CRDT 模型的简单优化,并提供了有关 Yjs 性能特征的更多见解。关于具体实现的更多信息,请参见 [INTERNALS.md](./INTERNALS.md) 和 [Yjs 代码库的此指南](https://youtu.be/0l5XgnQ6rB4)。
10+
11+
适合共享文本编辑的 CRDT 存在只能不断增长的缺陷。虽然存在不增长的 CRDT,但它们不具备对共享文本编辑有利的特性(如意图保留)。Yjs 实现了许多对原始算法的改进,减轻了文档仅增长的缺陷。我们无法在确保结构唯一顺序的同时回收已删除的结构(墓碑)。但我们可以 1. 将前面的结构合并为单个结构,以减少元信息的数量,2. 如果内容被删除,则可以从结构中删除内容,3. 如果我们不再关心结构的顺序,则可以回收墓碑(例如,如果父级被删除)。
12+
13+
**示例:**
14+
15+
1. 如果用户按顺序插入元素,则结构将合并为一个单一结构。例如 `text.insert(0, 'a'), text.insert(1, 'b');` 首先表示为两个结构 (`[{id: {client, clock: 0}, content: 'a'}, {id: {client, clock: 1}, content: 'b'}]`),然后合并为一个结构:`[{id: {client, clock: 0}, content: 'ab'}]`。
16+
2. 当包含内容的结构(例如 `ItemString`)被删除时,该结构将被替换为不再包含内容的 `ItemDeleted`。
17+
3. 当类型被删除时,所有子元素都会转换为 `GC` 结构。`GC` 结构仅表示结构的存在及其被删除。`GC` 结构可以与其他 `GC` 结构合并,只要它们的 id 相邻。
18+
19+
特别是在处理结构化内容时(例如在 ProseMirror 上进行共享编辑),这些改进在 [基准测试](https://github.com/dmonad/crdt-benchmarks) 随机文档编辑时表现出良好的结果。在实践中,它们显示出更好的效果,因为用户通常按顺序编辑文本,从而生成可以轻松合并的结构。基准测试表明,即使在用户从右到左编辑文本的最坏情况下,Yjs 对于大型文档也能保持良好的性能。
20+
21+
=== "英文"
22+
23+
*Conflict-free replicated data types* (CRDT) for collaborative editing are an
24+
alternative approach to *operational transformation* (OT). A very simple
25+
differentiation between the two approaches is that OT attempts to transform
26+
index positions to ensure convergence (all clients end up with the same
27+
content), while CRDTs use mathematical models that usually do not involve index
28+
transformations, like linked lists. OT is currently the de-facto standard for
29+
shared editing on text. OT approaches that support shared editing without a
30+
central source of truth (a central server) require too much bookkeeping to be
31+
viable in practice. CRDTs are better suited for distributed systems, provide
32+
additional guarantees that the document can be synced with remote clients, and
33+
do not require a central source of truth.
34+
35+
Yjs implements a modified version of the algorithm described in [this
36+
paper](https://www.researchgate.net/publication/310212186_Near_Real-Time_Peer-to-Peer_Shared_Editing_on_Extensible_Data_Types).
37+
This [article](https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/)
38+
explains a simple optimization on the CRDT model and
39+
gives more insight about the performance characteristics in Yjs.
40+
More information about the specific implementation is available in
41+
[INTERNALS.md](./INTERNALS.md) and in
42+
[this walkthrough of the Yjs codebase](https://youtu.be/0l5XgnQ6rB4).
43+
44+
CRDTs that are suitable for shared text editing suffer from the fact that they
45+
only grow in size. There are CRDTs that do not grow in size, but they do not
46+
have the characteristics that are benificial for shared text editing (like
47+
intention preservation). Yjs implements many improvements to the original
48+
algorithm that diminish the trade-off that the document only grows in size. We
49+
can't garbage collect deleted structs (tombstones) while ensuring a unique
50+
order of the structs. But we can 1. merge preceeding structs into a single
51+
struct to reduce the amount of meta information, 2. we can delete content from
52+
the struct if it is deleted, and 3. we can garbage collect tombstones if we
53+
don't care about the order of the structs anymore (e.g. if the parent was
54+
deleted).
55+
56+
**Examples:**
57+
58+
1. If a user inserts elements in sequence, the struct will be merged into a
59+
single struct. E.g. `text.insert(0, 'a'), text.insert(1, 'b');` is
60+
first represented as two structs (`[{id: {client, clock: 0}, content: 'a'},
61+
{id: {client, clock: 1}, content: 'b'}`) and then merged into a single
62+
struct: `[{id: {client, clock: 0}, content: 'ab'}]`.
63+
2. When a struct that contains content (e.g. `ItemString`) is deleted, the
64+
struct will be replaced with an `ItemDeleted` that does not contain content
65+
anymore.
66+
3. When a type is deleted, all child elements are transformed to `GC` structs. A
67+
`GC` struct only denotes the existence of a struct and that it is deleted.
68+
`GC` structs can always be merged with other `GC` structs if the id's are
69+
adjacent.
70+
71+
Especially when working on structured content (e.g. shared editing on
72+
ProseMirror), these improvements yield very good results when
73+
[benchmarking](https://github.com/dmonad/crdt-benchmarks) random document edits.
74+
In practice they show even better results, because users usually edit text in
75+
sequence, resulting in structs that can easily be merged. The benchmarks show
76+
that even in the worst case scenario that a user edits text from right to left,
77+
Yjs achieves good performance even for huge documents.
78+
79+
## 状态向量
80+
81+
=== "中文"
82+
83+
Yjs 能够在同步两个客户端时仅交换差异。我们使用 Lamport 时间戳来标识结构,并跟踪客户端创建它们的顺序。每个结构都有一个 `struct.id = { client: number, clock: number}`,唯一标识一个结构。我们定义每个客户端的下一个预期 `clock`*状态向量*。该数据结构类似于 [版本向量](https://en.wikipedia.org/wiki/Version_vector) 数据结构。但我们使用状态向量仅用于描述本地文档的状态,以便计算远程客户端缺失的结构。我们不使用它来跟踪因果关系。
84+
85+
=== "英文"
86+
87+
Yjs has the ability to exchange only the differences when syncing two clients.
88+
We use lamport timestamps to identify structs and to track in which order a
89+
client created them. Each struct has an `struct.id = { client: number, clock:
90+
number}` that uniquely identifies a struct. We define the next expected `clock`
91+
by each client as the *state vector*. This data structure is similar to the
92+
[version vectors](https://en.wikipedia.org/wiki/Version_vector) data structure.
93+
But we use state vectors only to describe the state of the local document, so we
94+
can compute the missing struct of the remote client. We do not use it to track
95+
causality.

docs/getting-start.md

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# 入门
2+
3+
**Getting Started**
4+
5+
=== "中文"
6+
7+
使用您喜欢的包管理器安装 Yjs 和提供者:
8+
9+
```sh
10+
npm i yjs y-websocket
11+
```
12+
13+
启动 y-websocket 服务器:
14+
15+
```sh
16+
PORT=1234 node ./node_modules/y-websocket/bin/server.cjs
17+
```
18+
19+
=== "英文"
20+
21+
Install Yjs and a provider with your favorite package manager:
22+
23+
```sh
24+
npm i yjs y-websocket
25+
```
26+
27+
Start the y-websocket server:
28+
29+
```sh
30+
PORT=1234 node ./node_modules/y-websocket/bin/server.cjs
31+
```
32+
33+
## 示例:观察类型
34+
35+
**Example: Observe types**
36+
37+
38+
=== "中文"
39+
40+
```js
41+
import * as Y from 'yjs';
42+
43+
const doc = new Y.Doc();
44+
const yarray = doc.getArray('my-array')
45+
yarray.observe(event => {
46+
console.log('yarray 被修改了')
47+
})
48+
// 每当本地或远程客户端修改 yarray 时,观察者都会被调用
49+
yarray.insert(0, ['val']) // => "yarray 被修改了"
50+
```
51+
52+
=== "英文"
53+
54+
```js
55+
import * as Y from 'yjs';
56+
57+
const doc = new Y.Doc();
58+
const yarray = doc.getArray('my-array')
59+
yarray.observe(event => {
60+
console.log('yarray was modified')
61+
})
62+
// every time a local or remote client modifies yarray, the observer is called
63+
yarray.insert(0, ['val']) // => "yarray was modified"
64+
```
65+
66+
## 示例:嵌套类型
67+
68+
**Example: Nest types**
69+
70+
=== "中文"
71+
72+
请记住,共享类型只是普通的数据类型。唯一的限制是共享类型在共享文档中必须只存在一次。
73+
74+
```js
75+
const ymap = doc.getMap('map')
76+
const foodArray = new Y.Array()
77+
foodArray.insert(0, ['apple', 'banana'])
78+
ymap.set('food', foodArray)
79+
ymap.get('food') === foodArray // => true
80+
ymap.set('fruit', foodArray) // => 错误!foodArray 已经被定义
81+
```
82+
83+
现在你了解了如何在共享文档上定义类型。接下来你可以跳转到 [演示仓库](https://github.com/yjs/yjs-demos) 或继续阅读 API 文档。
84+
85+
=== "英文"
86+
87+
Remember, shared types are just plain old data types. The only limitation is
88+
that a shared type must exist only once in the shared document.
89+
90+
```js
91+
const ymap = doc.getMap('map')
92+
const foodArray = new Y.Array()
93+
foodArray.insert(0, ['apple', 'banana'])
94+
ymap.set('food', foodArray)
95+
ymap.get('food') === foodArray // => true
96+
ymap.set('fruit', foodArray) // => Error! foodArray is already defined
97+
```
98+
99+
Now you understand how types are defined on a shared document. Next you can jump
100+
to the [demo repository](https://github.com/yjs/yjs-demos) or continue reading
101+
the API docs.
102+
103+
## 示例:使用和组合提供者
104+
105+
**Example: Using and combining providers**
106+
107+
=== "中文"
108+
109+
任何 Yjs 提供者都可以相互组合。因此你可以通过不同的网络技术同步数据。
110+
111+
在大多数情况下,你希望将网络提供者(如 y-websocket 或 y-webrtc)与持久化提供者(浏览器中的 y-indexeddb)结合使用。持久化允许你更快地加载文档,并在离线时持久化创建的数据。
112+
113+
为了演示,我们将两个不同的网络提供者与一个持久化提供者结合使用。
114+
115+
```js
116+
import * as Y from 'yjs'
117+
import { WebrtcProvider } from 'y-webrtc'
118+
import { WebsocketProvider } from 'y-websocket'
119+
import { IndexeddbPersistence } from 'y-indexeddb'
120+
121+
const ydoc = new Y.Doc()
122+
123+
// 这允许你立即获取(缓存的)文档数据
124+
const indexeddbProvider = new IndexeddbPersistence('count-demo', ydoc)
125+
indexeddbProvider.whenSynced.then(() => {
126+
console.log('从索引数据库加载的数据')
127+
})
128+
129+
// 使用 y-webrtc 提供者同步客户端。
130+
const webrtcProvider = new WebrtcProvider('count-demo', ydoc)
131+
132+
// 使用 y-websocket 提供者同步客户端
133+
const websocketProvider = new WebsocketProvider(
134+
'wss://demos.yjs.dev', 'count-demo', ydoc
135+
)
136+
137+
// 生成和计算总和的数字数组
138+
const yarray = ydoc.getArray('count')
139+
140+
// 观察总和的变化
141+
yarray.observe(event => {
142+
// 当数据变化时打印更新
143+
console.log('新总和: ' + yarray.toArray().reduce((a,b) => a + b))
144+
})
145+
146+
// 将 1 加到总和中
147+
yarray.push([1]) // => "新总和: 1"
148+
```
149+
150+
=== "英文"
151+
152+
Any of the Yjs providers can be combined with each other. So you can sync data
153+
over different network technologies.
154+
155+
In most cases you want to use a network provider (like y-websocket or y-webrtc)
156+
in combination with a persistence provider (y-indexeddb in the browser).
157+
Persistence allows you to load the document faster and to persist data that is
158+
created while offline.
159+
160+
For the sake of this demo we combine two different network providers with a
161+
persistence provider.
162+
163+
```js
164+
import * as Y from 'yjs'
165+
import { WebrtcProvider } from 'y-webrtc'
166+
import { WebsocketProvider } from 'y-websocket'
167+
import { IndexeddbPersistence } from 'y-indexeddb'
168+
169+
const ydoc = new Y.Doc()
170+
171+
// this allows you to instantly get the (cached) documents data
172+
const indexeddbProvider = new IndexeddbPersistence('count-demo', ydoc)
173+
indexeddbProvider.whenSynced.then(() => {
174+
console.log('loaded data from indexed db')
175+
})
176+
177+
// Sync clients with the y-webrtc provider.
178+
const webrtcProvider = new WebrtcProvider('count-demo', ydoc)
179+
180+
// Sync clients with the y-websocket provider
181+
const websocketProvider = new WebsocketProvider(
182+
'wss://demos.yjs.dev', 'count-demo', ydoc
183+
)
184+
185+
// array of numbers which produce a sum
186+
const yarray = ydoc.getArray('count')
187+
188+
// observe changes of the sum
189+
yarray.observe(event => {
190+
// print updates when the data changes
191+
console.log('new sum: ' + yarray.toArray().reduce((a,b) => a + b))
192+
})
193+
194+
// add 1 to the sum
195+
yarray.push([1]) // => "new sum: 1"
196+
```

0 commit comments

Comments
 (0)