# @convex-dev/prosemirror-sync 中文文档

规范地址：https://npmcn.com/package/%40convex-dev/prosemirror-sync/

版本：0\.2\.7
npm 元数据更新时间：2026-10-07T00:51:49.061Z
中文译文更新时间：2026-11-10T17:45:49.909Z
软件包许可：Apache-2\.0

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40convex-dev/prosemirror-sync
原文对照：https://npmcn.com/package/%40convex-dev/prosemirror-sync/?lang=original
项目主页：https://github.com/get-convex/prosemirror-sync#readme
源码仓库：https://github.com/get-convex/prosemirror-sync

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：233 kB，56 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：1 个
- peer 依赖：react ^18\.3\.1 \|\| ^19\.0\.0，convex ^1\.24\.8，react-dom ^18\.3\.1 \|\| ^19\.0\.0，@tiptap/core ^2\.7\.0 \|\| ^3\.10\.7，@blocknote/core \>=0\.31\.0 \<=1\.0\.0，@blocknote/react \>=0\.31\.0 \<=1\.0\.0，@blocknote/mantine \>=0\.31\.0 \<=1\.0\.0，decode-named-character-reference ^1\.3\.0
- 首次发布：2024-12-03T19:18:37.706Z
- 最新版本发布：2026-10-02T22:49:40.708Z
- 近 30 天下载量：461418（2026-10-11 至 2026-11-09，npm 官方下载统计）
- 已发布版本数：45（近 12 个月 8 个）

## 中文简介

使用 Convex 组件同步 Tiptap 的ProseMirror 文档 .

## 安装

```sh
npm install @convex-dev/prosemirror-sync
```

也可以使用：`pnpm add @convex-dev/prosemirror-sync`、`yarn add @convex-dev/prosemirror-sync`、`bun add @convex-dev/prosemirror-sync`

## README

# Convex ProseMiror 组件

[![npm version](https://badge.fury.io/js/@convex-dev%2Fprosemirror-sync.svg)](https://badge.fury.io/js/@convex-dev%2Fprosemirror-sync)

这是一个 [Convex 组件](https://convex.dev/components) 同步一个 [表达器](https://prosemirror.net/) 客户之间的文档通过 a [提示](https://tiptap.dev/) 扩展( 也与 [块注](https://blocknotejs.org/)) (中文(简体) ).

添加同步到云中的协作编辑器 。 使用此组件的用户 可以在多个标签或设备中编辑同一文档,更改将 与云相同步. 数据存在于您的 Convex 数据库中, 可以存储 和其余的应用程序数据一起.

只要配置您的编辑器特性, 将此组件添加到您的 Convex 后端, 并使用提供的同步反应钩。 读这个 [堆叠站](https://stack.convex.dev/add-a-collaborative-document-editor-to-your-app) 更多细节.

[![Example of editing](https://npmcn.com/package/%40convex-dev/prosemirror-sync/blocknote.gif)](http://www.youtube.com/watch?v=TGd-Nl7PBYQ "Collaborative editing syncing")

示例用途,见 [下级](https://npmcn.com/package/%40convex-dev/prosemirror-sync/#readme-usage) 详情:

```tsx
function CollaborativeEditor() {
  const sync = useBlockNoteSync(api.prosemirrorSync, "some-id");
  return sync.isLoading ? (
    <p>Loading...</p>
  ) : sync.editor ? (
    <BlockNoteView editor={sync.editor} />
  ) : (
    <button onClick={() => sync.create(EMPTY_DOC)}>Create document</button>
  );
}
```

对于编辑器,您可以选择使用Tiptap或BlockNote. 随块注去 除非您想要大量定制编辑器.

-   Tiptap 是基于ProseMirror 的, 并保存了很多配置比较 用于直接使用ProseMiror编辑器。 它拥有丰富的生态系统 扩展, 而且非常定制 .
-   BlockNote基于Tiptap 并且有一个更好的UI 和经验出盒子, 其代价是更难扩展和定制高级使用箱。 若为 你正在寻找最简单的方式 获得所有的花哨的格式选择, 这就是它.
-   不幸的是,虽然两者都基于ProseMirror,数据模型 不同的是,所以以后在不迁移的情况下切换编辑器并非微不足道 所以在公开发射前 可以先进行实验.

特征 :

-   通过业务转换(OT)安全地合并客户之间的变化.
-   简单的回放钩钩以获取初始文档并通过一个文档保持同步 Tittap 扩展名 .
-   支持 Tiptap 和 BlockNote 两种模式 .
-   服务器侧切入点用于授权读取、写取和快照.
-   创建在线或离线的新文档.
-   被跳出快照可以让新客户端避免阅读完整历史.
-   删除旧快照和步骤的 API .
-   转换文档服务器侧面, 方便的 AI 操作 .
-   关闭带有未符号更改的标签时警告( 默认启用) .

见 [下级](https://npmcn.com/package/%40convex-dev/prosemirror-sync/#readme-future-features) 未来专题构想 [捐款额](https://npmcn.com/package/%40convex-dev/prosemirror-sync/CONTRIBUTING.md) 如何贡献。 找到窃听器了吗? 特性 请求? [放在这](https://github.com/get-convex/prosemirror-sync/issues)。 。 。 .

## 预先要求: Convex

你需要一个现有的 Convex 项目来使用组件。 Convex 是一个主机 后端平台,包括数据库、无服务器功能和多一个吨 可以学习 [这里](https://docs.convex.dev/get-started)。 。 。 .

运行 `npm create convex` 或遵循其中任何一条 [快速启动](https://docs.convex.dev/home) 来设置一个.

## 安装

安装组件包 :

```ts
npm install @convex-dev/prosemirror-sync
```

创建一个 `convex.config.ts` 应用程序中的文件 `convex/` 文件夹并安装 通过调用 `use`数字 :

```ts
// convex/convex.config.ts
import { defineApp } from "convex/server";
import prosemirrorSync from "@convex-dev/prosemirror-sync/convex.config.js";

const app = defineApp();
app.use(prosemirrorSync);

export default app;
```

## 使用量

要使用该组件,请在您的文件中显示 API `convex/` 文件夹,和 使用特定编辑器的同步反应钩,在引用时通过 API 您 定义。 举例来说,我们将在 `convex/example.ts`。 。 。 .

```ts
// convex/example.ts
import { components } from "./_generated/api";
import { ProsemirrorSync } from "@convex-dev/prosemirror-sync";

const prosemirrorSync = new ProsemirrorSync(components.prosemirrorSync);
export const {
  getSnapshot,
  submitSnapshot,
  latestVersion,
  getSteps,
  submitSteps,
} = prosemirrorSync.syncApi({
  // ...
});
```

在您的 React 组件中,您可以使用编辑器特定的钩子获取 通过 Tiptap 扩展文档并保持同步. **说明**: 这需要 [`ConvexProvider`](https://docs.convex.dev/quickstart/react#:~:text=Connect%20the%20app%20to%20your%20backend) 在组件树上.

### 块注编辑器

**重要性**: BlockNote 目前不支持 `<StrictMode>` 在反应19. 如果你在使用反弹19, 确保你删除任何 `<React.StrictMode>` 块 设置 `reactStrictMode: false` 在你身边 `next.config.ts` 在适用的情况下. [文档](https://www.blocknotejs.org/docs/advanced/nextjs#react-19--next-15-strictmode)

```tsx
// src/MyComponent.tsx
import { useBlockNoteSync } from "@convex-dev/prosemirror-sync/blocknote";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { api } from "../convex/_generated/api";
import { BlockNoteEditor } from "@blocknote/core";

export function MyComponent() {
  const sync = useBlockNoteSync<BlockNoteEditor>(api.example, "some-id");
  return sync.isLoading ? (
    <p>Loading...</p>
  ) : sync.editor ? (
    <BlockNoteView editor={sync.editor} />
  ) : (
    <button onClick={() => sync.create({ type: "doc", content: [] })}>
      Create document
    </button>
  );
}
```

### Tiptap 编辑器

在反应组件中,您可以使用 `useTiptapSync` 钩取 初始文档,并通过 Tiptap 扩展名保持同步 . **说明**: 这个 需要一个 [`ConvexProvider`](https://docs.convex.dev/quickstart/react#:~:text=Connect%20the%20app%20to%20your%20backend) 在组件树上.

```tsx
// src/MyComponent.tsx
import { useTiptapSync } from "@convex-dev/prosemirror-sync/tiptap";
import { EditorContent, EditorProvider } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import { api } from "../convex/_generated/api";

export function MyComponent() {
  const sync = useTiptapSync(api.example, "some-id");
  return sync.isLoading ? (
    <p>Loading...</p>
  ) : sync.initialContent !== null ? (
    <EditorProvider
      content={sync.initialContent}
      extensions={[StarterKit, sync.extension]}
    >
      <EditorContent editor={null} />
    </EditorProvider>
  ) : (
    <button onClick={() => sync.create({ type: "doc", content: [] })}>
      Create document
    </button>
  );
}
```

见以下实例: [实例.ts](https://npmcn.com/package/%40convex-dev/prosemirror-sync/example/convex/example.ts) 财务报告和审定财务报表 [应用程序x](https://npmcn.com/package/%40convex-dev/prosemirror-sync/example/src/App.tsx)。 。 。 .

## 页:1

### 配置快照解弹间隔

快照解弹间隔默认为一秒. 您可以指定 与 `snapshotDebounceMs` 选项 `useTiptapSync` 或者说 `useBlockNoteSync`。 。 。 .

除非这两样都是真的,否则不会发出快照:

-   文档已空闲到弹出间隔 .
-   目前用户是最后一个进行更改的用户.

可能会有种族,但是由于每个客户都会提交自己的快照 改变,它们不会相互冲突 并且是安全的应用.

### 无符号更改警告

默认情况下,同步扩展显示用户时的浏览器确认对话框 尝试关闭未发送到服务器的本地更改标签 . 可禁用此选项 :

```ts
const sync = useTiptapSync(api.example, "some-id", {
  warnOnUnsyncedClose: false,
});
```

### 处理同步错误

过 `onSyncError` 当同步失败时被告知 :

```ts
const sync = useTiptapSync(api.example, "some-id", {
  onSyncError: (error) => {
    console.error("Sync failed", error);
  },
});
```

这还包括在背景中进行的工作,没有呼叫者 宣传失败: 解开的快照提交和冲出 编辑器被破坏时未确认的步骤(见下文).

大多数错误是信息——扩展持续同步,并重试于 服务器上的下一个本地更改或更新。 无法提交快照 特别成本无:快照只保存回放步骤,而下一个步骤 代替它.

例外的是 `"Unsynced steps could not be flushed after destroy"`,哪个 表示本地步骤在编辑器离开前从未到达服务器. 若为 你还没通过呢 `onSyncError`,这个表面 是一个无手的拒绝 而不是被无声地扔下.

### 卸载未显示的更改

本地步骤是真理的来源,直到服务器确认为止,所以当 编辑器被销毁, 步骤仍在飞行中 您的应用程序在回合中行驶, 例如, 扩展在 而不是丢弃它们. `warnOnUnsyncedClose` 只覆盖关闭 选项卡,浏览器允许我们屏蔽该选项卡;无法屏蔽一个内置的未挂载.

冲出是最佳的: 如果请求失败( 离线, 说), 步骤是 损失和 `onSyncError` 名为. 为了完全避免这种情况 编辑器挂载到 `collab.sendableSteps(editor.state)` 返回无效.

### 创建新文档

您可以通过调用从客户端创建新文档 `sync.create(content)`, (中文(简体) ), 或通过呼叫在服务器上 `prosemirrorSync.create(ctx, id, content)`。 。 。 .

内容应该是匹配 JSON 对象 [司马](https://tiptap.dev/docs/editor/core-concepts/schema)。如果你在使用 BlockNote,它需要成为 BlockNote 的 ProseMirror JSON 代表 块。 查看存储在 `snapshots` 在数据库中的表格 一个例子。 两者都可以使用此值: `{ type: "doc", content: [] }`

对于客户端文档创建:

-   在服务器确认文件不存在之前最安全 迄今(`!sync.isLoading`),您可以选择在下线时使用新 创建 ID 以在您重新连接前开始编辑新文档.
-   当客户端下次连接并同步文档时,它将提交 初始版本和所有本地更改作为步骤.
-   如果多个客户端创建同一文档, 它会因提交而失败 不同的初始内容.
-   注:如果在线时不打开文档,则不会同步.

### 转换文档服务器侧

您可以转换文档服务器侧. 它会给你最新版本 文档,您返回 [螺旋变形](https://prosemirror.net/docs/ref/#transform.Transform)。 。 。 .

您可以通过 `new Transform(doc)` 或者,如果你正在水分 一种 满 `EditorState`,您可以使用 `Editor.create({doc}).tr` 创建新的 `Transaction` 这是一个子类 `Transform`) (中文(简体) ).

例如:

```ts
import { getSchema } from "@tiptap/core";
import { EditorState } from "@tiptap/pm/state";

export const transformExample = action({
  args: {
    id: v.string(),
  },
  handler: async (ctx, { id }) => {
    const schema = getSchema(extensions);
    await prosemirrorSync.transform(ctx, id, schema, (doc) => {
      const tr = EditorState.create({ doc }).tr;
      tr.insertText("Hello, world!", 0);
      return tr;
    });
  },
});
```

-   这个 `extensions` 应该和你的客户端编辑所用的一样, 用于任何影响计划(而不是同步扩展)的扩展.
-   这个 `transform` 如果文档是 同时修改。 理想的情况是,这种回调不会做任何缓慢的操作 内部。 相反,要事先做.
-   这个 `doc` 可能不同于返回的 `getDoc`您可以比较 `version` 从 `getDoc` 将第二个论点改为 `transform` 函数以查看文档是否更改.
-   这个 `transform` 函数可以返回一个无效值来中止更改.
-   如果你正在传递一个位置 插入文本, 注意修改 平行发生可能会导致情况发生变化。 还可以再过一次 或使用ProseMirror绘图能力绘制 通过取取不同版本之间的步骤在不同的版本之间位置 以 `(component).lib.getSteps`。 。 。 .
-   如果你使用 BlockNote, 你需要转换 BlockNote 的块之间 BlockNote区块和ProseMirror节点. 见 [块去节点和节点去Block](https://github.com/TypeCellOS/BlockNote/tree/main/packages/core/src/api/nodeConversions)。 。 。 .

```ts
import { Transform } from "@tiptap/pm/transform";

// An example of doing slower AI operations beforehand:
const schema = getSchema(extensions);
const { doc, version } = await prosemirrorSync.getDoc(ctx, id, schema);
const content = await generateAIContent(doc);
await prosemirrorSync.transform(ctx, id, schema, (doc, v) => {
  if (v !== version) {
    // Decide what to do if the document changes.
  }
  // An example of using Transform directly.
  const tr = new Transform(doc);
  tr.insert(0, schema.text(content));
  return tr;
});
```

## 未来特点

以后可以添加的特性 :

-   离线编辑支持: 缓存文档和本地更改 `sessionStorage` 和返回在线时同步(仅用于活动浏览器标签).
    -   同时保存快照(但不是本地编辑)到 `localStorage` 因此新标签可以 查看并编辑下线的文档(但直到 他们回到了网上.
-   调试同步步骤的配置( 减少函数调用) .
-   每次提交三角洲时可选择写出具体值.
-   用于 ReactNative 的插件存储,假设为单会话.
-   将其转换为ProseMirror插件, 而不是 Tiptap 扩展名, 所以是原始的 ProseMirror用例也可以使用.
-   处理边缘案件,如旧客户在较年长者身上出现局部变化 文件的版本,其中说明它们为重新定位所必须采取的步骤 从那时起,变化就被真空了.
-   为使用 Convex 的用户输入更多类型安全的文档ID 参数 身份证作为他们的证件身份证。 这在服务器上可以使用,但不能浏览器.
-   完全丢弃客户端ID, 只要在本地使用两个UUID来区分我们 从服务器应用的更改中更改.
-   在数据模型中添加一个可选的 projectId来跟踪谁做了哪些修改 .

目前没有计划的缺失特征:

-   支持大于 1兆字节的文档 .
-   离线支持在浏览器标签或对等点之间同步更改.
-   同步 Yjs 文档而不是ProseMirror 步骤 。 将由一个 不同Yjs特定成分.
-   同步存在(例如在UI中显示其他用户名和光标). 这个 以Yjs为导向的ProseMiror 组件可以解决的另外一件事.
-   回调以确认客户端的再基准和处理故障(同步期间).
-   当一个老客户端时,优化以同步一个快照而不是许多三角洲 重新连接且没有本地化的更改.
-   处理多个从同一云中还原的合成实例 备份,可导致多个客户端使用同一客户端ID. 现在,我们将 假设 AsyncStorage 只用于一个客户端一次.

## 本地运行示例

在一个终端,运行:

```sh
npm run setup
npm run example
```

要修改组件并自动重建:

```sh
npm run setup
npm run dev
```

## 常见问题

### @convex-dev/prosemirror-sync 是什么？

使用 Convex 组件同步 Tiptap 的ProseMirror 文档 \.

### 如何安装 @convex-dev/prosemirror-sync？

在项目目录运行 npm install @convex-dev/prosemirror-sync，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：react（^18\.3\.1 \|\| ^19\.0\.0）、convex（^1\.24\.8）、react-dom（^18\.3\.1 \|\| ^19\.0\.0）、@tiptap/core（^2\.7\.0 \|\| ^3\.10\.7）、decode-named-character-reference（^1\.3\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @convex-dev/prosemirror-sync 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @convex-dev/prosemirror-sync 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @convex-dev/prosemirror-sync 可以商用吗？

@convex-dev/prosemirror-sync 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### @convex-dev/prosemirror-sync 的下载量有多少？

根据 npm 官方下载统计，@convex-dev/prosemirror-sync 在 2026/10/11 – 2026/11/09 这 30 天内被下载 461,418 次，日均约 15,381 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @convex-dev/prosemirror-sync 还在维护吗？

最新版本 0\.2\.7 发布于 2026/10/03（1 个月前）；至今共发布 45 个版本，其中过去 12 个月发布了 8 个；首次发布于 2024/12/04。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

