知行札记
DocFlow 实现与复用调查

DocFlow 编辑器组装与扩展

从有效入口、schema、扩展注册、图片、公式和菜单解释编辑器实现。

本页整理自 2026-10-04 的 DocFlow 静态调查材料。原材料没有提供仓库 URL、分支或提交哈希;正文中的路径、行号、计数与依赖版本均指向该未锚定快照。本轮整理读取了归档报告,未取得原工作副本、启动产品或验证外部后端。下文的“复核”指原材料记载的复核,前端路径存在与运行可用性分别判断。

编辑器架构如何组装

组装入口与生命周期

编辑器入口是 /docs/[room] 页面(apps/DocFlow/src/app/docs/[room]/page.tsx),这是一个客户端组件:

  1. 先调两个 hook 拿协作上下文:useDocumentPermission(HTTP 拉权限、创建 Y.Doc)与 useCollaboration(本地持久化 + WS provider)(page.tsx:67-77)。
  2. useEditor 创建实例(page.tsx:99-159):extensions 主体来自 ExtensionKit({ provider }) 工厂(page.tsx:101-102),再条件追加两个官方协作扩展——
    • Collaboration.configure({ document: doc, field: 'content' })(Yjs 文档的 content XmlFragment 绑到 ProseMirror,page.tsx:103-105);
    • CollaborationCaret.configure({ provider, user: currentUser })(多人光标,page.tsx:106-108)。 两者仅在 isCollaborationBootstrapReady 满足本地恢复及服务端同步标志后加入;未配置 WS 时服务端标志直接置真(useCollaboration.ts:60-65,145)。
  3. 只读权限通过 editor.setEditable(!isReadOnly, false) 动态切换而不重建实例(page.tsx:162-165);page.tsx:156-158 注释明确 isReadOnly 故意不进依赖数组,避免实例中途销毁。
  4. 页面级快捷键:Ctrl/Cmd+F 搜索(Esc 关闭)、Tab 拦截插入两空格(Shift+Tab 删行尾空格)(page.tsx:119-147,210-223)。

页面布局(docs/layout.tsx):左侧 DocumentSidebar(文档树/搜索/模板/组件/设置五个 tab),主区 DocumentHeader(标题 + 协作者头像 + 导出分享菜单)与 react-resizable-panels 分栏(编辑器 + AI 面板),悬浮 FloatingToc 与 SearchPanel。

ExtensionKit:注册表与 StarterKit 裁剪

扩展分两个来源(src/extensions/index.ts):

  • 官方包直接 re-export(index.ts:3-32):StarterKit、Highlight、Emoji(+gitHubEmojis)、Details/DetailsContent/DetailsSummary、TaskList/TaskItem、FileHandler、UniqueID、TableOfContents 等;TableKit 官方版经本地 Table 目录 re-export(Table/index.ts:1-4)。注意:Mathematics 是在 extension-kit.ts:5 直接 import 的,不在 index.ts 的 re-export 列表里(复核勘误,实质仍是官方包直接使用)。
  • 自研/二次封装(index.ts:34-69):src/extensions 下 29 个本地目录(实测目录清点 31 项,减去 extension-kit.ts 与 index.ts 两个文件恰为 29)。

extension-kit.ts:92-105 对 StarterKit 显式关闭 12 个内置项:document、dropcursor、heading、horizontalRule、undoRedo、codeBlock、paragraph、hardBreak、text、link、underline、trailingNode——由单独注册的自研扩展与 Collaboration 承担(关闭 undoRedo 是为让位给协作扩展的 UndoManager)。

实际注册的自研/二次封装扩展(ExtensionKit 数组 extension-kit.ts:73-393,复核逐一对号):Document(content 'block+' :74)、Heading 1-6(:84-86)、HorizontalRule(:87)、Selection(:83)、CodeBlock(:114)、FontSize(:116)、TrailingNode(:119)、Link(:120-122)、TableOfContentsNode(:127)、ImageUpload(:128-130)、ImageBlock(:131)、TableImage(:132)、DraggableBlock(:133)、DragHandler(:134)、MathLiveExtension(:340)、SlashCommand(:329)、Figcaption(:331)、JsonPaste(:336)、MarkdownPaste(:337)、SelectOnlyCode(:338)、ClearMarksOnEnter(:379)、Mention(:380-385)、SearchAndReplace(:386-391)、AgentSuggestion(:392)、Youtube(extend 官方 + 对话框,:371-378),以及匿名 mathMigration 扩展(:342-357)。

以上按 ExtensionKit 数组中的注册条目逐一清点为 26 项(口径可复算:Heading 1-6 计 1 项;匿名 mathMigration 计入;Youtube 为官方扩展的二次封装、计入;自研 emojiSuggestion 是官方 Emoji 扩展的 suggestion 配置而非独立注册条目(extension-kit.ts:309-312),故不在 26 之列,它就是 编辑器功能盘点"emoji(官方 Emoji + 自研建议)"中的"自研建议")。调研原文称"核心自研扩展 22 项",其清单把三个图片节点、DraggableBlock/DragHandler、JsonPaste/MarkdownPaste 按组书写且未单列 mathMigration——22 与 26 的差异来自分组书写与条目展开,两份清单指向同一注册事实;本报告统一采用可复算的"注册条目 = 26"口径,自研资产归属 同步使用。

支持的全部块类型(slash 菜单 SlashCommand/groups.ts:3-195 与 kit 注册交叉验证,原材料复核):标题 1-6、有序/无序列表、任务清单(nested)、折叠块 Details、引用、代码块、表格(resizable)、水平线、图片、数学公式、YouTube、可插入 TOC、emoji、@Mention;没有流程图/甘特图/音频/mermaid 等编辑器块。

图片体系:三节点 + FileHandler 上传流

三个节点各司其职(原材料复核):

  • ImageUpload(ImageUpload/ImageUpload.ts:13-52):点击/拖放上传的占位块节点,命令插入 <div data-type="imageUpload">;视图用 useDropZone + accept=".jpg,.jpeg,.png,.webp,.gif"(view/ImageUploader.tsx:30,81)。
  • ImageBlock(ImageBlock/ImageBlock.ts:45):基于官方 Image.extend(基础 Image 来自 Image/Image.ts:3 的 BaseImage.extend,group 改为 'block'),带 src/width/align/alt 四属性(:56-87)与 setImageBlockAlign/setImageBlockWidth(width clamp 0-100,:129-143)。
  • TableImage(TableImage/TableImage.ts:31-41):表格内自动改用的缩略节点,isTableImage 恒 true "始终保持缩略图尺寸"(:61-67)。

上传流由官方 FileHandler 统一承载(extension-kit.ts:135-307,原材料复核):onDrop 时 resolve(pos) 逐层判断是否在 table/tableCell/tableHeader 内(:142-156),据此走 setTableImageAt 或 setImageBlockAt(:158-162);onPaste 时先 readFileAsDataURL 插入 base64 预览(:190-206),随后 uploadImage 上传(:209),完成后按 src 回查节点(findImageNodeByUrl/findTableImageNodeByUrl,:214-226)并以 setNodeMarkup 替换为服务器 URL——即"先预览后替换"的体验。provider 的 clientID 还被传入 ImageUpload 用于区分多客户端上传(extension-kit.ts:128-130)。

数学公式:三层实现

原调查记录的公式链路由三部分组合:官方 Mathematics 使用 KaTeX,katexOptions 自定义 \R → \mathbb{R} 及 \N/\Z/\Q/\C 宏(extension-kit.ts:359-370);自研 MathLiveExtension 用 Ctrl/Cmd+M 打开 MathLivePopover,插入 {type:'inlineMath'} 节点(MathLiveExtension.ts:33-74、MathLivePopover.tsx:22-54);匿名 mathMigration 扩展在创建和更新后调用官方 migrateMathStrings,把旧 $...$ 文本迁移为数学节点(extension-kit.ts:342-357)。其中创建延迟 100ms,更新防抖 500ms,均为原快照参数。

块拖拽:两套体系并存

  • 编辑器内块排序用官方 @tiptap/extension-drag-handle-react 的 DragHandle 组件包住自研 ContentItemMenu(原材料复核):工具条提供 +(handleAdd)、AI 续写(handleAIContinue)、GripVertical(Popover 内含清除格式/复制/复制块/删除四项),ContentItemMenu.tsx:37-90。ImageBlock/TableImage/DraggableBlock/ImageUpload 四个节点视图标 data-drag-handle(grep 命中:TableImageView.tsx:32、DraggableBlockView.tsx:9、imageBlockView.tsx:37、ImageUpload/view/ImageUpload.tsx:14)。
  • 侧栏组件库 → 编辑器用原生 HTML5 DnD:BlocksTab handleDragStart setData('application/x-block-type', blockType)(BlocksTab.tsx:204-207);自研 DragHandler ProseMirror 插件在 handleDOMEvents 里检查 dataTransfer 类型、posAtCoords 定位,经 BlockContentStrategyFactory(注册 text/image/list/table/codeblock/todolist/emoji/audio/divider/ai 十个策略,DragHandler.ts:258-269)生成 JSON 后 insertContentAt(:34-96)。注意 audio/ai 策略生成的节点类型 schema 中不存在(见 实现缺口)。
  • dnd-kit 仅用于侧栏文件树排序(DocumentSidebar/folder/index.tsx:5,342,原调查搜索记录中 @dnd-kit 只出现在 folder 一族 8 个文件),不参与编辑器内块排序。

DraggableBlock 已注册但 setDraggableBlock 命令无任何调用方(未接线)。

气泡菜单与 slash 菜单

文本工具栏是自研 CustomBubbleMenu(原材料复核):TextMenu/BubbleMenu.tsx 用 floating-ui 的 computePosition + offset/flip/shift 中间件(:121-141)与 autoUpdate 持续跟踪(:170-203),提供块类型切换、字体/字号、加粗/斜体/下划线/删除线/行内代码/代码块、链接编辑、多色高亮、文字颜色、上下标、四种对齐(TextMenu.tsx:88-247)。LinkMenu 与 ImageBlockMenu(对齐 + 宽度滑杆)则用官方 BubbleMenu(@tiptap/react/menus)。

slash 菜单为自研实现(未逐条复核):基于 @tiptap/suggestion,char '/'、allowSpaces、startOfLine,仅允许根深度段落且以 / 开头触发(SlashCommand.ts:16-36);分组 + 别名过滤(:53-93);ReactRenderer 渲染 Popover 并处理滚动重定位与 Escape/上下/Enter 键盘导航(:94-246)。

最后更新于

本页目录