Tiptap 富文本与块编辑
从 schema、命令、视图和保存链路理解富文本编辑器,并接入图片与自定义节点。
内容模型先于工具栏
Tiptap 在 ProseMirror 之上提供扩展、命令和事件接口,编辑器 UI 由应用组合。应用应确定正文需要什么节点、属性和标记,再配置工具栏。一个“插入图片”按钮需要对应已注册节点、合法属性、上传结果和持久化表示;按钮本身不能建立文档能力。Tiptap 概览
节点表达段落、标题、列表、图片等结构,mark 表达加粗、链接等附着于内容的属性。schema 定义允许的父子关系和 mark 组合,例如文档根包含一个或多个块,段落包含行内内容。解析外部 HTML 时,不符合已启用 schema 的结构可能被丢弃;导入流程应检查实际保留的内容。Schema
块编辑建立在节点模型上。块菜单改变的是节点或对应范围,拖拽改变的是树的位置。菜单、节点视图和浮动定位应使用编辑器当前 selection 与位置映射,避免把屏幕坐标、DOM 偏移和文档位置当成同一种位置。
一条最小编辑链路
以下是单用户 React 接线示意,使用 Tiptap 3 的核心、React 绑定与 StarterKit。项目需要对齐 Tiptap 包的兼容版本;未包含权限、网络保存和图片上传。
import { EditorContent, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
export function NoteEditor() {
const editor = useEditor({
extensions: [StarterKit],
content: {
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: '正文' }] }],
},
immediatelyRender: false,
});
return (
<div>
<button
type="button"
disabled={!editor}
onClick={() => editor?.chain().focus().toggleBold().run()}
>
加粗
</button>
<EditorContent editor={editor} />
</div>
);
}UI 触发命令,命令生成编辑变换,编辑器状态更新后通知视图及保存层。只读状态可以关闭编辑能力,服务端还必须独立拒绝无权限写入。文档 ID 变化时应结束旧实例及其订阅,按新身份加载;自动保存需要携带文档 ID 与版本,防止旧请求写入新文档。
SSR 页面应把依赖 DOM 的编辑器初始化放在客户端生命周期。immediatelyRender: false 用于避免提前创建需要浏览器环境的视图;这不等同于禁止页面所有服务端渲染。协同场景还需要先处理本地恢复及 provider 同步,避免启动时重复写入默认内容。
保存什么以及何时保存
editor.getJSON() 取得结构化内容,getHTML() 取得用于呈现的 HTML。Tiptap 官方推荐以 JSON 保存编辑内容,它更便于结构处理;应用仍需自己保存 schema 版本、文档身份和修订版本。Persistence
一条可靠的保存链路可以这样安排:编辑变更产生待保存版本,防抖只合并提交频率;请求携带预期的服务端版本;保存响应标记已经持久化的那一版;后续本地改动保持待保存状态。保存按钮、自动保存和离线恢复共同使用这个事实来源。请求开始时的快照与响应到达时的当前内容可能不同,不能仅因请求成功就清除所有未保存标志。
服务端验证节点类型、属性范围、文档大小和写权限。链接协议、嵌入来源、外部图片地址和 HTML 渲染有不同信任边界;通过 JSON 校验不自动保证生成 HTML 可以安全放入所有页面。未知节点应明确拒绝、降级或保存原始信息,避免一次旧客户端保存删除新节点。
图片上传由临时状态走向持久资源
可先插入上传占位节点或本地预览,再上传文件,成功后把相同节点的资源属性改为持久地址。占位项应有稳定身份;用全局 src 查找会在多张相同图片或并发操作时失去唯一性。
上传期间用户可能删除、移动节点或切换文档。响应处理先核对文档身份和节点仍然存在,再更新属性。失败保留可重试状态,取消及卸载释放 Object URL 和订阅。base64 预览便于立即展示,但不应无条件永久存入每次保存的正文;大文件会增加请求、历史和协同负担。
图片节点的宽度、对齐、替代文本和资源 ID 需要分别定义。下载或导出时还要处理跨域、失效地址和权限,不把浏览器曾经看见图片当成转换器必然能读取图片。
扩展要在整个文档链路中成立
自定义节点至少需要名称、属性、内容规则、解析和序列化,以及插入或修改命令。NodeView 负责编辑状态中的交互,它的 React DOM 不必等于最终 HTML;服务端和导出器应使用同一节点语义生成各自表示。
例如提示块保存提示类型及内容,编辑视图显示可交互类型选择,HTML 输出使用明确标记,Word 转换器再映射到段落和边框。表格、公式、引用、嵌入视频同样需要检查复制、粘贴、保存、恢复、协同和导出。扩展目录存在、被 re-export、进入注册数组和用户可以触发,是不同接线阶段。
StarterKit 已提供若干节点和能力,重复注册同名扩展会造成冲突。替换某个能力时明确关闭旧实现,协同撤销尤其需要采用协同扩展的历史机制。BlockNote、Lexical、Slate 等路线提供不同块模型或编辑框架;迁移成本主要来自内容 schema、交互及插件行为,不能只比较工具栏样式。
验证编辑后的内容与边界
结构层检查命令前后的文档、非法节点、空内容、嵌套及转换损失;集成层检查保存版本、重复响应及上传节点被删除;实际交互检查输入法、selection、键盘菜单、焦点、拖拽和可访问名称。前三类证据不能互相代替。
本页整合归档富文本实现材料,示例为接线示意。许可、Pro 扩展及托管能力应按实际采用的组件逐项核对,不将开源核心许可推广到全部附加产品。
最后更新于