具有强大的共享数据抽象的 CRDT (conflict-free replicated data type 无冲突复制数据类型) 框架。它将其内部数据结构公开为共享类型。
共享类型是常见的数据类型,如具有超能力的 Map 或 Array:更改会自动分发到其他对等体,并在没有合并冲突的情况下进行合并。(带有数据劫持的 shareArray?)
支持许多现有的富文本编辑器、脱机编辑、版本快照、撤消/重做和共享光标。
// demo from https://docs.yjs.dev/
import * as Y from 'yjs'
const ydoc = new Y.Doc()
const ymap = ydoc.getMap()
ymap.set('keyA', 'valueA')
// 比如服务器?
const ydocRemote = new Y.Doc()
const ymapRemote = ydocRemote.getMap()
ymapRemote.set('keyB', 'valueB')
// Merge changes from remote
const update = Y.encodeStateAsUpdate(ydocRemote)
Y.applyUpdate(ydoc, update)
// Observe that the changes have merged
console.log(ymap.toJSON()) // => { keyA: 'valueA', keyB: 'valueB' }
共享类型
-
Y.Array - 支持在任何位置高效插入/删除元素。内部使用一个链表数组,并在必要时进行拆分。
length:number
insert(index:number, content:Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>)
push(Array<同 insert 类型>)
unshift(Array<同 insert 类型>)
delete(index:number, length:number)
get(index:number)
slice(start:number, end:number):Array<同 insert 类型>
forEach(function(value: 同 insert 类型, index:number, array: Y.Array))
map(function(T, number, YArray):M):Array<M>
toJSON
observe(function(YArrayEvent, Transaction):void)
unobserve
observeDeep
unobserveDeep
-
Y.Map
size: number
set(key:string, value:object|boolean|string|number|null|Uint8Array|Y.Type)
get(key:string | index:number)
delete(key)
has(key)
clear()
clone():Y.Map
toJSON
forEach
entries
values
keys
observe(function(YMapEvent, Transaction):void)
unobserve
observeDeep
unobserveDeep
-
Y.Text - 服务于富文本,可以转换为 delta 格式。
length:number
-
insert(index:number, content:string, [formattingAttributes:Object<string,string>])
=> eg: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>)
toString
toJSON
toDelta
observe(function(YTextEvent, Transaction):void)
unobserve
observeDeep
unobserveDeep
-
Y.XmlFragment
-
Y.XmlElement
Y.Doc : const doc = new Y.Doc()
-
clientID
- readonly unique id -
gc
- 是否启用垃圾收集。似乎与 undo 相关? -
transact(function(Transaction):void [, origin:any])
- ?? get(string, Y.[TypeClass]):[Type]
getArray(string):Y.Array
getMap
getText
getXmlFragment
on
-
off
on('update', function(updateMessage:Uint8Array, origin:any, Y.Doc):void)
on('beforeTransaction', function(Y.Transaction, Y.Doc):void)
on('afterTransaction',
on('beforeAllTransactions', function(Y.Doc):void)
on('afterAllTransactions', function(Y.Doc, Array<Y.Transaction>):void)
- 合并或更新方法
Y.applyUpdate(Y.Doc, update:Uint8Array, [transactionOrigin:any])
Y.mergeUpdates(Array<Uint8Array>)
Y.diffUpdate(update: Uint8Array, stateVector: Uint8Array): Uint8Array
- Y.UndoManager
- ...
y-websocket
maybe https://github.com/y-js/y-websockets-client 、 https://github.com/y-js/y-websockets-server ?
-
支持跨选项卡通信。当您在同一浏览器中打开同一文档时,文档上的更改将通过跨选项卡通信(Broadcast Channel 和 localStorage 作为回退)进行交换。
-
支持意识信息的交换(例如光标)。
// Extension for tiptap
import { keymap } from "prosemirror-keymap";
import { Extension } from "tiptap";
import {
redo,
undo,
yCursorPlugin,
ySyncPlugin,
yUndoPlugin,
} from "y-prosemirror";
import { WebsocketProvider } from "y-websocket";
import * as Y from "yjs";
const ydoc = new Y.Doc();
const roomname = location.href.includes("uuu") ? "tiptap-demo2" : "tiptap-demo";
const provider = new WebsocketProvider("ws://localhost:8080", roomname, ydoc);
const type = ydoc.getXmlFragment("prosemirror");
provider.on("sync", (isSynced) => {
console.log("======= sync", isSynced, window.ee.getHTML()); // logs "connected" or "disconnected"
// 现在没值时进行插入
if (window.ee.getText() === "")
window.ee.commands.setContent("<p>Example Text</p>");
});
export default class RealtimeExtension extends Extension {
get name() {
return "realtime";
}
get plugins() {
return [
ySyncPlugin(type),
yCursorPlugin(provider.awareness),
yUndoPlugin(),
keymap({
"Mod-z": undo,
"Mod-y": redo,
"Mod-Shift-z": redo,
}),
];
}
}
// server by node
const WebSocket = require("ws");
const http = require("http");
const StaticServer = require("node-static").Server;
const setupWSConnection = require("y-websocket/bin/utils.js").setupWSConnection;
const production = process.env.PRODUCTION != null;
const port = process.env.PORT || 8080;
const staticServer = new StaticServer("../", {
cache: production ? 3600 : false,
gzip: production,
});
const server = http.createServer((request, response) => {
request
.addListener("end", () => {
staticServer.serve(request, response);
})
.resume();
});
const wss = new WebSocket.Server({ server });
wss.on("connection", (conn, req) => setupWSConnection(conn, req, { gc: true }));
server.listen(port);
console.log(
`Listening to http://localhost:${port} ${production ? "(production)" : ""}`
);
网友评论