Hocuspocus 提供者示例
Tiptap
Tiptap 是一个无头文本编辑器,完全可定制,且拥有一流的协作编辑集成,兼容 Hocuspocus。
下面的示例代码展示了创建 Tiptap 实例所需的全部内容:包含所有默认扩展,启动你的 Hocuspocus 协作后端,并将一切连接起来。
在你的 HTML 文档中添加一个元素,用于初始化 Tiptap:
<div class="element"></div>安装所需扩展:
npm install @hocuspocus/provider @tiptap/core @tiptap/pm @tiptap/starter-kit @tiptap/extension-collaboration @tiptap/extension-collaboration-caret yjs y-prosemirror然后创建你的 Tiptap 实例:
import { Editor } from '@tiptap/core'
import { StarterKit } from '@tiptap/starter-kit'
import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCaret from '@tiptap/extension-collaboration-caret'
import * as Y from 'yjs'
import { HocuspocusProvider } from '@hocuspocus/provider'
const ydoc = new Y.Doc();
const provider = new HocuspocusProvider({
url: "ws://127.0.0.1",
name: "example-document",
document: ydoc,
});
new Editor({
element: document.querySelector(".element"),
extensions: [
StarterKit.configure({
undoRedo: false,
}),
Collaboration.configure({
document: ydoc,
}),
CollaborationCaret.configure({
provider,
user: { name: "John Doe", color: "#ffcc00" },
}),
],
});CodeMirror
import * as Y from "yjs";
import { CodemirrorBinding } from "y-codemirror";
import { WebsocketProvider } from "y-websocket";
import CodeMirror from "codemirror";
const ydoc = new Y.Doc();
var provider = new WebsocketProvider(
"wss://websocket.tiptap.dev",
"hocuspocus-demos-codemirror",
ydoc
);
const yText = ydoc.getText("codemirror");
const yUndoManager = new Y.UndoManager(yText);
const editor = CodeMirror(document.querySelector(".editor"), {
mode: "javascript",
lineNumbers: true,
});
const binding = new CodemirrorBinding(yText, editor, provider.awareness, { yUndoManager });了解更多:https://github.com/yjs/y-codemirror
Monaco
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
import { MonacoBinding } from "y-monaco";
import * as monaco from "monaco-editor";
window.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
if (label === "json") {
return "/monaco/dist/json.worker.bundle.js";
}
if (label === "css") {
return "/monaco/dist/css.worker.bundle.js";
}
if (label === "html") {
return "/monaco/dist/html.worker.bundle.js";
}
if (label === "typescript" || label === "javascript") {
return "/monaco/dist/ts.worker.bundle.js";
}
return "/monaco/dist/editor.worker.bundle.js";
},
};
window.addEventListener("load", () => {
const ydoc = new Y.Doc();
const provider = new WebsocketProvider(
"wss://websocket.tiptap.dev",
"hocuspocus-demos-monaco",
ydoc
);
const type = ydoc.getText("monaco");
const editor = monaco.editor.create(document.querySelector(".editor"), {
value: "",
language: "javascript",
theme: "vs-dark",
});
const monacoBinding = new MonacoBinding(
type,
editor.getModel(),
new Set([editor]),
provider.awareness
);
window.example = { provider, ydoc, type, monacoBinding };
});了解更多:https://github.com/yjs/y-monaco
Quill
import Quill from "quill";
import QuillCursors from "quill-cursors";
import * as Y from "yjs";
import { QuillBinding } from "y-quill";
import { WebsocketProvider } from "y-websocket";
Quill.register("modules/cursors", QuillCursors);
var ydoc = new Y.Doc();
var type = ydoc.getText("quill");
var provider = new WebsocketProvider("wss://websocket.tiptap.dev", "hocuspocus-demos-quill", ydoc);
var quill = new Quill(".editor", {
theme: "snow",
modules: {
cursors: true,
history: {
userOnly: true,
},
},
});
new QuillBinding(type, quill, provider.awareness);了解更多:https://github.com/yjs/y-quill
Lexical
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
import { CollaborationPlugin } from "@lexical/react/LexicalCollaborationPlugin";
import * as Y from "yjs";
import { TiptapCollabProvider } from "@hocuspocus/provider";
export default function Editor({
initialEditorState,
key
}: {
initialEditorState: string | null;
key: string;
}) {
return (
<LexicalComposer
key={key}
initialConfig={{
editorState: null,
namespace: "test",
}}
>
<PlainTextPlugin
contentEditable={<ContentEditable />}
placeholder={<div>请输入一些文本...</div>}
ErrorBoundary={LexicalErrorBoundary}
/>
<CollaborationPlugin
id={key}
providerFactory={createWebsocketProvider}
initialEditorState={initialEditorState}
shouldBootstrap={true}
/>
</LexicalComposer>
);
}
function createWebsocketProvider(
id: string,
yjsDocMap: Map<string, Y.Doc>
): Provider {
const doc = new Y.Doc();
yjsDocMap.set(id, doc);
// @TODO: 替换 APP ID
// @TODO: 填写正确的 Token
// @TODO: 或使用带 Hocuspocus URL 的 `HocuspocusProvider`
const hocuspocusProvider = new TiptapCollabProvider({
appId: 'YOUR_APP_ID',
name: `lexical-${id}`,
token: 'YOUR_TOKEN',
document: doc,
});
return hocuspocusProvider;
}Slate(草稿)
了解更多:https://github.com/BitPhinix/slate-yjs
多路复用
为了使用多路复用(即通过同一个 websocket 连接打开多个文档)与 TiptapCollab 或 Hocuspocus,你需要分别创建 socket 和 provider。
下面的示例展示了它如何与 TiptapCollab 一起工作,但你也可以将 TiptapCollabProviderWebsocket 替换为 HocuspocusProviderWebsocket,将 TiptapCollabProvider 替换为 HocuspocusProvider,以配合 Hocuspocus 使用。
请注意,认证需针对每个文档进行,因此 token 是 Provider 的一部分,而非 ProviderWebsocket。
import {
TiptapCollabProvider,
TiptapCollabProviderWebsocket
} from "@hocuspocus/provider";
const socket = new TiptapCollabProviderWebsocket({
appId: '', // 如果使用 `HocuspocusProviderWebsocket` 则填写 `url`
})
const provider1 = new TiptapCollabProvider({
websocketProvider: socket,
name: 'document1',
token: '',
})
const provider2 = new TiptapCollabProvider({
websocketProvider: socket,
name: 'document2',
token: '',
})
provider1.attach() // 手动传入 socket 时,需要显式调用 attach
provider2.attach() // 手动传入 socket 时,需要显式调用 attach