27 KiB
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. 总体运行结构
当前主链路是:
- 前端页面由
/mnt/Data1T/mnote/wolai-frontend/src/app/**提供。 - 文档主页面进入
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。 BlockNoteEditor负责承载普通文档块、自定义块、思维导图块、在线表格块等。- 思维导图有两种形态:
- 作为 BlockNote 内嵌块存在于文档页面
- 作为独立全屏页面存在于路由
/mindmap/[docId]/[mindmapId],对应源码/mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx
- OnlyOffice 不嵌在 BlockNote 内部编辑,而是通过独立页面路由
/onlyoffice打开,对应源码/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/page.tsx。 - 文档中的附件块如果识别为 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用动态导入方式挂载DocumentContentDocumentContent再动态导入BlockNoteEditor- 所以 BlockNote 是“文档页面中的主编辑器内核”,不是一个独立页面
4.3 BlockNote schema
/mnt/Data1T/mnote/wolai-frontend/src/components/editor/schema.ts
当前自定义 block 已明确注册在这里,包括:
mindmaponlineTablemediapageReferenceblockReferenceadvancedTodoprogressMeter
也就是说,思维导图和在线表格本质上都是 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 文档”。
当前模式是:
- 文档正文里放的是附件块
MediaBlock - 如果附件扩展名属于 Office 文档类型
- 点击后新开路由
/onlyoffice页面,对应实现/mnt/Data1T/mnote/wolai-frontend/src/app/onlyoffice/OnlyOfficeClientPage.tsx - 在独立页面内进行 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/
关键页面关系是:
- 文档页
- 路由:
/documents/[id] - 源码:
/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx - 负责把文档元数据交给文档编辑器宿主
- 路由:
- 思维导图全屏页
- 路由:
/mindmap/[docId]/[mindmapId] - 源码:
/mnt/Data1T/mnote/wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx - 负责把 MindmapBlock 复用为独立编辑页面
- 路由:
- 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 存储层
存储层当前不是单一系统,而是三部分组合:
- Convex 主数据层
- 位置:
/mnt/Data1T/mnote/wolai-frontend/convex/ - 负责文档元数据、业务记录、任务编排等主链路数据
- 位置:
- 本地 / 历史文件资源层
- 位置:
/mnt/Data1T/mnote/src/components/onlyoffice/ - 负责保留 OnlyOffice 相关静态资源、插件和密钥数据
- 位置:
- 辅助后端层
- 位置:
/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/
- Convex 主数据:
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/
- OnlyOffice 静态资源:
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 页面树
页面树的核心特点是:
- 只关心“文档节点”本身
- 表示页面之间的父子结构
- 不直接把附件、思维导图文件夹、在线表格文件作为树节点展开
主要实现链路:
/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts提供文档数据源/mnt/Data1T/mnote/wolai-frontend/src/lib/documents.ts负责把文档记录构造成DocumentNode/mnt/Data1T/mnote/wolai-frontend/src/lib/sidebar-tree.ts负责flattenDocumentTree和 section 投影/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 文件树
文件树的核心特点是:
- 以文档为根
- 但会把文档下的索引页、附件、思维导图资产、在线表格资产一起展开
- 更像“资源管理器视图”
主要实现链路:
/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-convex-sidebar-data.ts提供文档、媒体、思维导图、表格等混合数据/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx把多类资源整理到 file-tree 视图所需的输入结构/mnt/Data1T/mnote/wolai-frontend/src/lib/file-tree/rows.ts把文档节点 + 资产组合成FileTreeRow[]/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,这两套树不应该混为一个组件重写,而应该拆成:
- 统一侧边栏数据聚合层
- 页面树投影层
- 文件树投影层
- 各自独立渲染组件
这样最符合当前实现,也最容易保持行为一致。
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.tsmindmap.jsonmindmap-${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接收 status2/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
第二种是服务端签名与回调校验所需的密钥:
-
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,最值得优先抽象的是三层:
- 文档宿主层
- 文档页面壳
- 文档权限
- 编辑器挂载流程
- Block 能力层
- BlockNote schema
mindmap/onlineTable/media的块协议
- 外部编辑器层
- OnlyOffice 独立页面
- 代理 / callback / sign / forcesave
这样拆之后会非常清楚:
- 哪些能力应继续做成“内嵌 block”
- 哪些能力应继续保留为“独立页面编辑器”