← 全部文档
协作与审阅

协作与审阅插件

六个插件,把编辑器变成一套实际可用的文档工作流:斜杠命令、@提及、修订跟踪、分线程批注、修订历史,以及基于 Yjs 的在线状态。全部包含在标准授权中——无需购买高级版本。

斜杠命令

在空行(或空白之后)输入 /,即可打开内联命令选择器。包含 15 种内置块(H1–H3、项目符号/编号列表、引用、代码、分隔线、表格、图片、链接、表情、今天的日期),以及所有已注册的 AI Toolkit 操作。

配置

{
    slashCommandEnabled: true,          // default when this plugin is loaded
    slashCommandIncludeAi: true         // pull aiToolkitActions into the menu
}

API

editor.slashCommands.register({ id, label, keywords, run });
editor.slashCommands.remove(id);
editor.slashCommands.list();
editor.slashCommands.open(); / .close();

@提及

一个触发字符框架,适用于 @、#、[[wiki]] 或任何自定义前缀。支持异步数据源(带防抖和加载状态)。生成不可编辑的原子“胶囊”——按退格键会删除整个标签。

注册触发字符

editor.mentions.register({
    trigger: "@",
    insertClass: "rte-mention-person",
    search: function (query, done) {
        fetch("/api/users?q=" + encodeURIComponent(query))
            .then(r => r.json())
            .then(done);                // Array<{ id, name, color? }>
    },
    renderItem: function (user) { return user.name; },
    renderInsert: function (user) { return "@" + user.name; }
});

修订跟踪(人工建议模式)

Word 风格的建议模式。插入的内容以当前用户的颜色加下划线显示;删除的内容加删除线(在被接受之前不会真正删除)。连续的删除会合并为一个片段;在自己刚输入的插入内容中按退格键,会使该插入内容缩短。

配置

{
    trackChangesEnabled: true,
    currentUser: { id: "maya", name: "Maya Patel", color: "#9333ea" },
    reviewCanDecide: (entry, action, user) => user.role === "editor"
}

可在审阅抽屉中按作者接受/拒绝。使用 reviewCanDecide 执行您应用的审阅人策略;被拒绝的决定会让建议保持待定状态,并触发 review_decision_denied。被接受和被拒绝的建议都会保存 decidedBy,作为审计记录。它可以与 AI 建议并存——两者最终都存放在同一个 editor.reviewLedger 存储中。

分线程批注

批注锚定到选区范围,显示为高亮加侧边栏线程。包括撰写、回复、解决和删除。

配置

{
    commentsEnabled: true,
    currentUser: { id: "alice", name: "Alice Lee", color: "#2563eb" }
}

API

editor.comments.add(range, text);
editor.comments.reply(threadId, text);
editor.comments.resolve(threadId);
editor.comments.delete(threadId);
editor.comments.list();

修订历史

快照浏览器,带有沙盒预览 iframe 和基于 LCS 的逐行差异对比。支持手动快照或防抖的自动快照。默认存储在 localStorage 中,也可以 POST 到服务器端点。

配置

{
    revisionHistoryEnabled: true,
    revisionHistoryMaxEntries: 50,
    revisionHistoryAutoSnapshotMs: 10000,
    revisionHistoryUrl: "/api/revisions",       // optional
    revisionHistoryRequestHeaders: () => ({
        "X-CSRF-Token": readCsrfToken()
    }),
    onRevisionHistorySyncError: ({ reason, status }) => {
        reportSyncProblem(reason, status);
    }
}

请使用同源端点,配合您现有的会话和 CSRF 保护。标头值可以由一个函数提供,这样就能在请求时读取短期有效的防伪造令牌。不要在编辑器配置中放置长期有效的 API 密钥。

API

editor.revisionHistory.snapshot(label?, metadata?);
editor.revisionHistory.promptAndSnapshot(defaultLabel?);   // native prompt + snapshot
editor.revisionHistory.rename(id, label);
editor.revisionHistory.list();                             // all snapshots
editor.revisionHistory.listNamed();                        // user-labeled only
editor.revisionHistory.diff(idA, idB);                     // { lines, oldText, newText }
editor.revisionHistory.restore(id);
editor.revisionHistory.delete(id);
editor.revisionHistory.openBrowser();

Yjs 在线状态与并发输入

实时光标、在线头像、共享审阅台账。Yjs 是一个对等依赖——插件本身从不包含它。您需要自行加载 Yjs 和一个提供程序(WebSocket / WebRTC / Hocuspocus),然后把它们传入。

附加

import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";

const ydoc = new Y.Doc();
const provider = new WebsocketProvider("wss://collab.example.com", docId, ydoc);

editor.collab.attach({
    doc: ydoc,
    provider: provider,
    user: { id: "alice", name: "Alice", color: "#2563eb" },
    textSync: "crdt",      // per-node Yjs CRDT; load crdt-engine.min.js first
    requireCrdt: true       // do not silently fall back to legacy snapshots
});

textSync: "crdt"。按节点的绑定会把可编辑 DOM 镜像到一个 Y.XmlFragment 中,保留并发的文本和格式更改,而无需旧方式那样重置整个正文。附加之后调用 editor.collab.getStatus(),可以记录当前生效的模式以及任何回退原因。

生产环境的 WebSocket 边缘服务

附带的协作服务器支持签名的、按房间限定的令牌、持久化的 LevelDB 状态、浏览器源允许列表,以及每个房间的连接数上限。在生产环境中请同时使用这四项控制;在代理上终止 TLS,不要暴露开放的 WebSocket 端点。

RTE_COLLAB_SECRET="use-a-long-random-secret"
RTE_COLLAB_PERSIST="/var/lib/rte-collab"
RTE_COLLAB_ALLOWED_ORIGINS="https://app.example.com,https://admin.example.com"
RTE_COLLAB_MAX_CONNECTIONS_PER_ROOM="100"
RTE_COLLAB_REDIS_URL="redis://redis:6379"
node server.js

云端令牌端点会先验证文档所有权,然后再为某个房间签发短期凭据。服务器会以 HTTP 403 拒绝缺失或不在允许列表中的浏览器源,并以 HTTP 429 拒绝某个热门房间中超出上限的对等端。在每个副本上设置相同的 Redis URL,即可在不使用粘性会话的情况下转发文档和感知更新;加入活动房间的副本会先请求一份完整的 CRDT 状态快照,再同步它的第一个客户端。如果已配置的 Redis 不可用,/healthz 会返回 HTTP 503 和 status: "degraded"。重新连接之后,就绪状态会保持降级,直到一次内部 Pub/Sub 往返证明订阅已重新开始接收消息。

每个对等端都是不可信的输入

CRDT 通过直接把节点写入实时文档来应用远程编辑。它不经过粘贴处理或 setHTMLCode,因此只过滤这两个入口的编辑器,会在其他每位参与者的浏览器中执行某个协作者的 <iframe srcdoc>,并继续把它复制出去。请像对待粘贴的 HTML 一样对待共享文档上的合著者。

sanitizer.js 封堵了这条路径:它监视可编辑区域中插入的节点和属性,在任何可执行内容运行之前将其过滤,无论它来自哪里。普通输入没有任何开销,因为文本节点会被跳过,只有出现危险内容时过滤器才会介入。如果您的同步层已经在上游进行了净化,可以用 sanitizerGuardLiveDom: false 禁用它。您可以在在线验证页面上亲自运行这项检查。

发布测试套件会针对一个隔离的、真实的 Redis 兼容进程启动两个独立的 WebSocket 服务器进程,并验证延迟加入、双向的内容和在线状态、房间/令牌隔离、后端丢失、就绪状态恢复以及恢复后的第一次编辑。这是由厂商自行运行的集成测试证据,而不是托管服务的 SLA 或外部可靠性认证。

共享审阅台账

AI 建议、人工修订建议和批注都汇集到同一个存储中:editor.reviewLedger。正因为如此,审阅抽屉才能把它们交错显示,并且无论来源如何,接受/拒绝的方式都相同。

API

editor.reviewLedger.add(entry);            // { id, changeType, author, text, replies[], ... }
editor.reviewLedger.update(id, patch);
editor.reviewLedger.remove(id);
editor.reviewLedger.get(id);
editor.reviewLedger.list();
editor.reviewLedger.refreshPanel();