Files
mnote/ARCHITECTURE.md
T
lix-2026 b33ffb99e7 feat: 收口文档桥接与 OnlyOffice/Sidebar 回归
- 为 documents.save/meta/content、blocks.patch 与 sidebar.dataset.list 补齐 Rust 协议映射、共享契约与桥接执行器

- 对齐 BlockNote、Mindmap、OnlyOffice 的保存/路由元信息,并补真实浏览器回归脚本与 OnlyOffice 部署基线

- 忽略 Rust 本地构建产物与 Harness 调试状态文件,避免临时产物进入仓库历史
2026-04-15 03:06:29 +08:00

27 KiB
Raw Blame History

MNOTE 当前架构梳理

本文只描述当前仓库中保留下来的主线结构,不再依赖已移动到 /mnt/Data1T/mnote/recycle/ 的旧设计稿。

1. 当前保留的主线目录

  • /mnt/Data1T/mnote/wolai-frontend/ 当前主前端,Next.js App Router,页面、编辑器、思维导图、OnlyOffice 页面都在这里。
  • /mnt/Data1T/mnote/wolai-backend/ 当前辅助后端,FastAPI + Celery,主要提供补充服务能力。
  • /mnt/Data1T/mnote/infra/convex/ Convex 自托管相关配置。
  • /mnt/Data1T/mnote/src/components/onlyoffice/ 根目录历史遗留,但当前仍保留,因为里面有 OnlyOffice 静态资源、插件和密钥数据。
  • /mnt/Data1T/mnote/scripts/ 启动与环境脚本。

2. 总体运行结构

当前主链路是:

  1. 前端页面由 /mnt/Data1T/mnote/wolai-frontend/src/app/** 提供。
  2. 文档主页面进入 DocumentShell -> DocumentContent -> BlockNoteEditor,对应源码分别位于 /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx
  3. BlockNoteEditor 负责承载普通文档块、自定义块、思维导图块、在线表格块等。
  4. 思维导图有两种形态:
    • 作为 BlockNote 内嵌块存在于文档页面
    • 作为独立全屏页面存在于路由 /mindmap/[docId]/[mindmapId],对应源码 /mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx
  5. OnlyOffice 不嵌在 BlockNote 内部编辑,而是通过独立页面路由 /onlyoffice 打开,对应源码 /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx
  6. 文档中的附件块如果识别为 Office 文档,会跳转到路由 /onlyoffice 页面进行编辑,对应页面实现 /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx

3. 前端主入口

3.1 根布局

  • /mnt/Data1T/mnote/wolai-frontend/src/app/layout.tsx

职责:

  • 注入运行时配置到 window.__MNOTE_RUNTIME_CONFIG__
  • 挂载 Convex Provider、Query Provider
  • 作为整个前端 App Router 的根布局

3.2 文档页面入口

  • /mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx

职责:

  • 根据文档 id 读取元数据
  • 组织只读、禁下载、禁复制等页面权限
  • 把文档上下文传给 DocumentShell,对应源码 /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx

文档页真正的编辑器链路是:

/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx -> /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx -> /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx -> /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx

4. BlockNote 位置与嵌入形式

4.1 BlockNote 主组件

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx

这是当前文档编辑核心。

它负责:

  • 创建 BlockNote 实例
  • 加载自定义 schema
  • 挂接 Slash Menu、Side Menu、TOC、评论、搜索引用、全屏表格桥接等
  • 承载普通文本块和自定义块

4.2 文档壳与内容层

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx

嵌入方式:

  • DocumentShell 用动态导入方式挂载 DocumentContent
  • DocumentContent 再动态导入 BlockNoteEditor
  • 所以 BlockNote 是“文档页面中的主编辑器内核”,不是一个独立页面

4.3 BlockNote schema

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/schema.ts

当前自定义 block 已明确注册在这里,包括:

  • mindmap
  • onlineTable
  • media
  • pageReference
  • blockReference
  • advancedTodo
  • progressMeter

也就是说,思维导图和在线表格本质上都是 BlockNote 的自定义 block。

5. Mindmap 位置与嵌入形式

5.1 主组件

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx

这是思维导图前端主实现。

它同时承担:

  • 内嵌块视图
  • 本地全屏视图
  • 独立页面全屏视图复用
  • 侧栏、工具栏、导航器、缩略图、计数器、右键菜单等整套 UI

5.2 作为 BlockNote 内嵌块

注册位置:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/schema.ts

插入位置:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx

嵌入方式:

  • 在文档页面中,思维导图作为 type: "mindmap" 的 block 插入到 BlockNote 内容流里
  • 渲染时由 mindmapBlock() 对应到 MindmapBlockView
  • 这个块是“嵌在页面正文里的一大块交互式画布”

视觉与交互形态:

  • 默认是页面中的内嵌卡片画布
  • 双击后可进入局部全屏模式

5.3 作为独立全屏页面

  • /mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx

嵌入方式:

  • 这个页面不是重新实现一个思维导图编辑器
  • 而是直接复用 MindmapBlockView
  • 只用一个假的 editorStub 代替 BlockNote 编辑器上下文
  • 因此“文档内嵌思维导图”和“独立思维导图页”本质是同一套前端组件

5.4 Mindmap 相关子组件

主要都在:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/

关键文件包括:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapToolbar.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapSidebar.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapNavigator.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapMiniMap.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapCount.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapContextMenu.tsx

5.5 当前结论

Mindmap 的前端定位不是独立编辑器系统,而是:

  • 以 BlockNote 自定义块为主形态
  • 以独立全屏页为辅助形态
  • 两者共用同一核心组件 MindmapBlockView

6. OnlyOffice 位置与嵌入形式

6.1 OnlyOffice 页面入口

  • /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx

当前 OnlyOffice 不是 BlockNote 内嵌编辑器,而是单独页面。

嵌入方式:

  • 打开路由 /onlyoffice,对应入口文件 /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx
  • 页面内部加载 OnlyOffice 文档编辑器脚本
  • 通过 URL 参数和运行时配置决定打开哪个文档、哪种模式

6.2 页面职责

page.tsx 负责:

  • 在最早阶段注入 DOM 补丁
  • 防止 OnlyOffice 某些 NotFoundError 导致整页白屏

OnlyOfficeClientPage.tsx 负责:

  • 真正加载 OnlyOffice 编辑器
  • 改写内部请求
  • 处理代理地址
  • 配置 callback、签名、强制保存
  • 挂载 OnlyOfficeAiAgentPanel

6.3 与正文的关系

OnlyOffice 当前不是“BlockNote 中的一块可直接编辑的 Office 文档”。

当前模式是:

  1. 文档正文里放的是附件块 MediaBlock
  2. 如果附件扩展名属于 Office 文档类型
  3. 点击后新开路由 /onlyoffice 页面,对应实现 /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx
  4. 在独立页面内进行 OnlyOffice 编辑

对应主文件:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MediaBlock.tsx

这意味着:

  • BlockNote 中只嵌入“文件入口”
  • OnlyOffice 编辑器本身在独立页面运行

6.4 OnlyOffice 资源位置

当前仍保留的历史资源目录:

  • /mnt/Data1T/mnote/src/components/onlyoffice/onlyoffice-web-apps
  • /mnt/Data1T/mnote/src/components/onlyoffice/onlyoffice-plugins
  • /mnt/Data1T/mnote/src/components/onlyoffice/onlyoffice-data

这里不是主 React 页面代码,但对 OnlyOffice 运行仍有意义,因为里面保留了:

  • web apps 静态资源
  • 插件
  • key / data

6.5 OnlyOffice 代理与 API

关键接口:

  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/proxy/route.ts
  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts
  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/forcesave/route.ts
  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/sign/route.ts

当前角色分工:

  • proxy 负责把浏览器不可直达的 OnlyOffice / 文件回源 URL 包成同源代理,避免跨域、内网地址、token 参数冲突
  • callback 接收 OnlyOffice 保存回调,再把内容写回存储
  • forcesave 主动触发保存
  • sign 提供签名能力

6.6 当前结论

OnlyOffice 的前端定位是:

  • 独立页面型编辑器
  • 不直接嵌入 BlockNote 编辑画布
  • 通过附件块从文档页跳转进入

7. Online Table 的位置与嵌入形式

虽然你这次重点是 BlockNote / Mindmap / OnlyOffice,但当前结构里在线表格和它们关系紧密,也需要一起记住。

主文件:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/OnlineTableBlock.tsx

嵌入方式:

  • 在线表格是 BlockNote 的自定义 block
  • 在正文中显示的是紧凑预览块
  • 双击或操作按钮后,通过 editor bridge 打开全屏表格编辑器

所以它和 Mindmap 类似,都是:

  • 正文里先以内嵌 block 形式出现
  • 再切换到更大编辑视图

但它和 OnlyOffice 不同:

  • Online Table 仍然属于 BlockNote 自定义 block 体系
  • OnlyOffice 是独立页面体系

8. 四层结构图

下面用“组件层 / 页面层 / API 层 / 存储层”重新梳理一次当前主线。

8.1 组件层

组件层主要位于:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/

其中本次最关键的组件分组是:

  • 文档编辑核心
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx
  • BlockNote 自定义块
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/OnlineTableBlock.tsx
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MediaBlock.tsx
  • OnlyOffice 页面侧组件
    • /mnt/Data1T/mnote/wolai-frontend/src/components/onlyoffice/OnlyOfficeAiAgentPanel.tsx

这一层的职责是:

  • 渲染交互界面
  • 承载编辑器实例
  • 把页面数据转成可操作 UI
  • 管理局部交互状态

8.2 页面层

页面层主要位于:

  • /mnt/Data1T/mnote/wolai-frontend/src/app/

关键页面关系是:

  1. 文档页
    • 路由:/documents/[id]
    • 源码:/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx
    • 负责把文档元数据交给文档编辑器宿主
  2. 思维导图全屏页
    • 路由:/mindmap/[docId]/[mindmapId]
    • 源码:/mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx
    • 负责把 MindmapBlock 复用为独立编辑页面
  3. OnlyOffice 页
    • 路由:/onlyoffice
    • 源码:/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx
    • 客户端实现:/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx
    • 负责承载独立的文档编辑器

这一层的职责是:

  • 定义访问路由
  • 组织页面级权限和参数
  • 决定使用哪套组件组合
  • 把独立编辑器和正文编辑器区分开

8.3 API 层

API 层主要位于:

  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/

和本次主题最相关的 API 组是:

  • 文档内容与页面数据
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/
  • 思维导图
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/mindmap/
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/mindmap-ai/
  • 媒体与附件
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/media/
  • OnlyOffice
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/proxy/route.ts
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/forcesave/route.ts
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/sign/route.ts

这一层的职责是:

  • 做 BFF 转换
  • 统一权限校验
  • 代理前端和 Convex / 外部编辑器之间的请求
  • 处理 OnlyOffice 的保存回写与代理回源

8.4 存储层

存储层当前不是单一系统,而是三部分组合:

  1. Convex 主数据层
    • 位置:/mnt/Data1T/mnote/wolai-frontend/convex/
    • 负责文档元数据、业务记录、任务编排等主链路数据
  2. 本地 / 历史文件资源层
    • 位置:/mnt/Data1T/mnote/src/components/onlyoffice/
    • 负责保留 OnlyOffice 相关静态资源、插件和密钥数据
  3. 辅助后端层
    • 位置:/mnt/Data1T/mnote/wolai-backend/app/
    • 负责补充服务能力与异步处理配合

这一层的职责是:

  • 保存业务主数据
  • 保存编辑器所需静态资源
  • 承载需要服务端参与的补充逻辑

8.5 三个核心能力在四层中的落点

A. BlockNote

  • 组件层:/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx
  • 页面层:/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx
  • API 层:/mnt/Data1T/mnote/wolai-frontend/src/app/api/documents//mnt/Data1T/mnote/wolai-frontend/src/app/api/blocks/
  • 存储层:/mnt/Data1T/mnote/wolai-frontend/convex/

B. Mindmap

  • 组件层:/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx
  • 页面层:
    • 文档内嵌页面宿主:/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx
    • 独立页:/mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx
  • API 层:
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/mindmap/
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/mindmap-ai/
  • 存储层:
    • Convex 主数据:/mnt/Data1T/mnote/wolai-frontend/convex/
    • 本地 mindmap 辅助逻辑:/mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/

C. OnlyOffice

  • 组件层:
    • /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx
    • /mnt/Data1T/mnote/wolai-frontend/src/components/onlyoffice/OnlyOfficeAiAgentPanel.tsx
  • 页面层:
    • /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx
  • API 层:
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/
  • 存储层:
    • OnlyOffice 静态资源:/mnt/Data1T/mnote/src/components/onlyoffice/
    • 业务回写与数据协作:/mnt/Data1T/mnote/wolai-frontend/convex//mnt/Data1T/mnote/wolai-backend/app/

9. 页面树与文件树

这一部分专门说明当前左侧结构区里的两套树:页面树和文件树。

9.1 位置

它们当前都属于侧边栏系统,主要位于:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx

其中:

  • 页面树组件:
    • /mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/private-tree.tsx
  • 文件树组件:
    • /mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/file-tree.tsx

也就是说,两者都在“组件层”的侧边栏模块里,但不是同一个组件。

9.2 来源

当前两套树的数据源都先汇总到侧边栏数据模型,再做不同投影。

主要数据入口是:

  • /mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts

这个 hook 会从 Convex 拉取:

  • 文档列表
  • 回收站文档
  • 思维导图记录
  • 媒体资产
  • 回收站媒体资产
  • 在线表格记录
  • 工作空间摘要

然后把这些数据组合成统一的 SidebarInitialData 形状,交给侧边栏 UI。

9.3 页面树

页面树的核心特点是:

  • 只关心“文档节点”本身
  • 表示页面之间的父子结构
  • 不直接把附件、思维导图文件夹、在线表格文件作为树节点展开

主要实现链路:

  1. /mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts 提供文档数据源
  2. /mnt/Data1T/mnote/wolai-frontend/src/lib/documents.ts 负责把文档记录构造成 DocumentNode
  3. /mnt/Data1T/mnote/wolai-frontend/src/lib/sidebar-tree.ts 负责 flattenDocumentTree 和 section 投影
  4. /mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/private-tree.tsx 负责真正渲染页面树

页面树所在层次:

  • 组件层:/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/private-tree.tsx
  • 页面层宿主:/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx
  • 数据来源:/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts
  • 存储来源:/mnt/Data1T/mnote/wolai-frontend/convex/

页面树更像“文档导航树”。

9.4 文件树

文件树的核心特点是:

  • 以文档为根
  • 但会把文档下的索引页、附件、思维导图资产、在线表格资产一起展开
  • 更像“资源管理器视图”

主要实现链路:

  1. /mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts 提供文档、媒体、思维导图、表格等混合数据
  2. /mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx 把多类资源整理到 file-tree 视图所需的输入结构
  3. /mnt/Data1T/mnote/wolai-frontend/src/lib/file-tree/rows.ts 把文档节点 + 资产组合成 FileTreeRow[]
  4. /mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/file-tree.tsx 负责真正渲染文件树

文件树所在层次:

  • 组件层:/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/file-tree.tsx
  • 页面层宿主:/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx
  • 数据来源:/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts
  • 存储来源:/mnt/Data1T/mnote/wolai-frontend/convex/

文件树更像“页面 + 资源”的混合管理视图。

9.5 二者的关系

页面树和文件树不是两套独立后端数据模型,而是:

  • 共用同一套侧边栏主数据源
  • 在前端侧做两种不同投影

区别可以简单记成:

  • 页面树:只看页面层级
  • 文件树:看页面层级 + 页面下资源

9.6 当前结论

如果后续继续把这套能力收口到主仓 Rust 协议层,这两套树不应该混为一个组件重写,而应该拆成:

  1. 统一侧边栏数据聚合层
  2. 页面树投影层
  3. 文件树投影层
  4. 各自独立渲染组件

这样最符合当前实现,也最容易保持行为一致。

10. 数据保存与读写入口

10.1 BlockNote

核心文件:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/save/route.ts

保存位置:

  • 主保存目标是 Convex 文档内容字段,对应后端 mutation 为 api.documents.updateContent
  • 实现入口位于 /mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/save/route.ts

保存格式:

  • 前端提交字段为 { documentId, content }
  • 其中 content 是 BlockNote 的 JSON 块数组
  • 如果初始值不是纯数组,也会在 /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx 中先提取 blocks

读写入口:

  • 读入口:
    • /mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx
  • 写入口:
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx
    • 其中 saveContent()POST /api/documents/save
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/save/route.ts 再调用 api.documents.updateContent

补充说明:

  • BlockNote 主内容走 Convex 持久化
  • 同文件里还保留了 HocuspocusProvider + Y.Doc 协作接入,说明实时协作层与最终持久化层是分开的
  • 思维导图自动保存缓存也会临时写到浏览器 localStorage,但那不是文档主存储

10.2 Mindmap

核心文件:

  • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/mindmapLocalStore.ts
  • /mnt/Data1T/mnote/wolai-frontend/src/lib/server/local-paths.ts

保存位置:

  • 首选目录由 /mnt/Data1T/mnote/wolai-frontend/src/lib/server/local-paths.tsgetDocumentsBaseDir() 决定
  • 如果设置了 MNOTE_DATA_DIR,目录是 MNOTE_DATA_DIR/documents/<docId>/
  • 否则回退到 /mnt/Data1T/mnote/wolai-frontend/public/documents/<docId>/
  • 兼容旧路径时,还会尝试 getLegacyMindmapsBaseDir(),即 MNOTE_DATA_DIR/mindmaps/<docId>//mnt/Data1T/mnote/wolai-frontend/public/mindmaps/<docId>/

保存格式:

  • 保存为 JSON 文件
  • 文件名规则在 /mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/mindmapLocalStore.ts
    • mindmap.json
    • mindmap-${mindmapId}.json
  • 实际写入使用 JSON.stringify(data, null, 2),即格式化 JSON 文本

读写入口:

  • 读入口:
    • /mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/mindmapLocalStore.tsreadMindmapLocal(docId, mindmapId)
  • 写入口:
    • /mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/mindmapLocalStore.tswriteMindmapLocal(docId, mindmapId, data, docTitle)
  • 相关服务侧复用:
    • /mnt/Data1T/mnote/wolai-frontend/src/lib/ai-agent/tools/builtins/mindmap/mindmapServerTools.ts

补充说明:

  • Mindmap 当前不是像 BlockNote 正文那样直接持久化到 Convex 内容字段
  • 它更像“文档目录下的配套 JSON 资产”
  • 页面中的思维导图块与独立全屏思维导图页,共用同一份数据结构

10.3 OnlyOffice

核心文件:

  • /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx
  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts
  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/sign/route.ts

保存位置:

  • 当前保存目标不是 BlockNote 内容,而是附件资产本身
  • Convex 模式下,OnlyOffice 回调会把编辑后的二进制文件重新上传到 Convex Files
  • 然后更新 media_assets.storage_id / file_url 之类的附件存储指针

保存格式:

  • 保存内容是 Office 原始文件二进制,不是 BlockNote JSON
  • 文档格式取决于当前附件类型,例如 docx / xlsx / pptx / pdf
  • 前端传给 OnlyOffice 的 document.url 是可下载原文件的地址,OnlyOffice 保存后再通过回调传回新的下载地址

读写入口:

  • 读入口:
    • /mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocks/MediaBlock.tsx 负责从正文附件入口跳转
    • /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx
    • /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx
  • 写入口:
    • OnlyOfficeClientPage.tsx 里组装 callbackUrl
    • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts 接收 status 2/6
    • 回调路由下载 OnlyOffice 生成的新文件,再上传到 Convex Files,并调用 api.mediaAssets.replaceStorageFromUpload

补充说明:

  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/forcesave/route.ts 用于主动触发保存
  • /mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/proxy/route.ts 用于同源代理与回源下载
  • 根目录 /mnt/Data1T/mnote/src/components/onlyoffice/ 保留的是 OnlyOffice 静态资源、插件与历史数据,不是正文主数据存储

10.4 OnlyOffice key 生成规则

这里要区分两种“key / 密钥”。

第一种是前端传给 OnlyOffice 的 document.key

  • 定义位置:
    • /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx
  • 生成规则:
    • assetId 时,优先生成 assetId + "_" + hashKey(assetStorageId)
    • 如果还没拿到 assetStorageId,先退化为 assetId
    • 没有 assetId 时,回退为 hashKey(\${effectiveFileUrl}-${fileName}`)`
  • 设计目的:
    • 让同一附件多次打开时 document.key 尽量稳定,提升 OnlyOffice 内部缓存命中
    • 当附件回写后 storage_id 变化,key 会自然变化,避免旧缓存污染新文件
  • 字符集限制:
    • 代码注释明确说明 OnlyOffice document.key 允许字符集接近 [0-9a-zA-Z_.=-]
    • 所以这里对 storage_id 做 hash,而不是直接原样拼接

第二种是服务端签名与回调校验所需的密钥:

  • ONLYOFFICE_JWT_SECRET

    • 读取位置:/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/sign/route.ts
    • 用途:给整体 config、documenteditorConfig 生成 HS256 JWT
    • 获得方式:这是你自己部署 OnlyOffice 时,在服务端环境变量里配置的一段共享密钥,不是前端算出来的值
    • 如果未配置,当前实现会返回 token: null,兼容“服务端未开启 JWT 校验”的场景
  • ONLYOFFICE_CALLBACK_SECRET

    • 读取位置:/mnt/Data1T/mnote/wolai-frontend/src/app/api/onlyoffice/callback/route.ts
    • 用途:校验 OnlyOffice 文档服务器回调到 /api/onlyoffice/callback 时附带的共享密钥
    • 获得方式:同样是你在本项目环境变量和 OnlyOffice 侧约定的一段共享字符串,不是系统自动下发
    • 如果未配置,当前实现保持兼容,不强制校验

当前结论:

  • 如果你问“OnlyOffice 的 key 如何获得”,前端运行期真正用来区分文档实例的 key,是代码按 assetId / storage_id / fileUrl / fileName 现场生成的
  • 如果你问“OnlyOffice 的密钥如何获得”,那指的是 ONLYOFFICE_JWT_SECRETONLYOFFICE_CALLBACK_SECRET,它们都应由部署者在环境变量中自行生成并在双方配置保持一致

11. 三者关系总结

11.1 BlockNote

定位:

  • 主文档编辑器内核
  • 页面正文的承载容器

表现形式:

  • 文档主页面核心区域
  • 挂载各种自定义 block

11.2 Mindmap

定位:

  • BlockNote 内部的高级自定义块

表现形式:

  • 文档页中可内嵌
  • 也可进入全屏
  • 独立路由 /mindmap/[docId]/[mindmapId] 页面与内嵌块共用同一核心组件,对应页面文件 /mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx

11.3 OnlyOffice

定位:

  • 独立页面型文档编辑器

表现形式:

  • 从附件块跳转进入
  • 运行在路由 /onlyoffice,对应页面文件 /mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx
  • 不直接嵌入 BlockNote 正文编辑画布

12. 一句话记忆版

可以把当前架构记成下面这三句:

  • BlockNote 是主页面正文编辑器容器
  • Mindmap 是挂在 BlockNote 里的重交互 block,并可切到全屏
  • OnlyOffice 是通过附件入口跳转出去的独立编辑页面

13. 后续重构建议

如果后面你要把这套能力继续收口到 /mnt/Data1T/mnote/rust/,最值得优先抽象的是三层:

  1. 文档宿主层
    • 文档页面壳
    • 文档权限
    • 编辑器挂载流程
  2. Block 能力层
    • BlockNote schema
    • mindmap / onlineTable / media 的块协议
  3. 外部编辑器层
    • OnlyOffice 独立页面
    • 代理 / callback / sign / forcesave

这样拆之后会非常清楚:

  • 哪些能力应继续做成“内嵌 block”
  • 哪些能力应继续保留为“独立页面编辑器”