Files
mnote/ARCHITECTURE.md
T
2026-04-13 19:21:42 +08:00

761 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 当前结论
如果后续迁移到 `mnote-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.ts``getDocumentsBaseDir()` 决定
- 如果设置了 `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.ts``readMindmapLocal(docId, mindmapId)`
- 写入口:
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/mindmap/mindmapLocalStore.ts``writeMindmapLocal(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、`document``editorConfig` 生成 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_SECRET``ONLYOFFICE_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. 后续重构建议
如果后面你要把这套能力迁到 `mnote-rust`,最值得优先抽象的是三层:
1. 文档宿主层
- 文档页面壳
- 文档权限
- 编辑器挂载流程
2. Block 能力层
- BlockNote schema
- `mindmap` / `onlineTable` / `media` 的块协议
3. 外部编辑器层
- OnlyOffice 独立页面
- 代理 / callback / sign / forcesave
这样拆之后会非常清楚:
- 哪些能力应继续做成“内嵌 block”
- 哪些能力应继续保留为“独立页面编辑器”