# 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.ts` 的 `getDocumentsBaseDir()` 决定 - 如果设置了 `MNOTE_DATA_DIR`,目录是 `MNOTE_DATA_DIR/documents//` - 否则回退到 `/mnt/Data1T/mnote/wolai-frontend/public/documents//` - 兼容旧路径时,还会尝试 `getLegacyMindmapsBaseDir()`,即 `MNOTE_DATA_DIR/mindmaps//` 或 `/mnt/Data1T/mnote/wolai-frontend/public/mindmaps//` 保存格式: - 保存为 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. 后续重构建议 如果后面你要把这套能力继续收口到 `/mnt/Data1T/mnote/rust/`,最值得优先抽象的是三层: 1. 文档宿主层 - 文档页面壳 - 文档权限 - 编辑器挂载流程 2. Block 能力层 - BlockNote schema - `mindmap` / `onlineTable` / `media` 的块协议 3. 外部编辑器层 - OnlyOffice 独立页面 - 代理 / callback / sign / forcesave 这样拆之后会非常清楚: - 哪些能力应继续做成“内嵌 block” - 哪些能力应继续保留为“独立页面编辑器”