152 lines
8.6 KiB
Markdown
152 lines
8.6 KiB
Markdown
# S1 核心内核实施方案(Mindmap 内核对标 KMind)
|
||
|
||
目标:在不改变现有 Next.js + BlockNote + Yjs + Supabase 架构的前提下,完成 Stage S1 中的 5 个核心能力,形成可落地的代码骨架与开发清单。
|
||
|
||
---
|
||
|
||
## 1. MindmapCanvas(画布容器 / 多根模式 / 懒渲染)
|
||
|
||
| 对标 | MNOTE 方案 |
|
||
|------|-----------|
|
||
| `design/Kmind/js/0.js:719-1160` 的 `MindMap` 类,负责容器初始化、SVG 分层、大小检测、延迟渲染、多根导图 | 在 `src/components/mindmap/canvas/MindmapCanvas.tsx`(新建)中封装一个 React 组件,内部通过 `@xyflow/react` 的 `ReactFlowProvider` + `useReactFlow` 控制视图。|
|
||
|
||
### 关键设计
|
||
1. **多根模式**:允许 `props.roots` 为数组;在 BlockNote ↔ mindmap 同步时,若一个文档存在多个 mindmap,即 `mindmap_meta` 行 >1,则对每个根节点创建独立的 React Flow `Node` 子树,并在 `MindmapCanvas` 中渲染多个 root。
|
||
2. **容器重试/懒加载**:借鉴 KMind 的 `containerSizeRetryConfig`,封装 `useElementRect` hook,支持 `IntersectionObserver` 判断是否进入视窗,在进入前仅挂起渲染。
|
||
3. **SVG 分层**:React Flow 默认使用单一 `svg`;需要添加自定义背景(连线)、节点容器及“其他层”(如选框、直觉按钮)。做法:通过 `ReactFlow` 的 `nodeTypes` + `edgeTypes` + 自定义 `Background`/`Controls` 组件来模拟 `lineDraw`/`nodeDraw`/`otherDraw`。
|
||
4. **画布状态管理**:使用 Zustand(`src/store/useMindmapStore.ts` 新建)保存 `scale`, `translate`, `activeNodes`, `multiRootMode` 等状态,方便其他模块(快捷键、TextEdit)订阅。
|
||
|
||
### 输出物
|
||
- `src/components/mindmap/canvas/MindmapCanvas.tsx`
|
||
- `src/components/mindmap/canvas/useElementRect.ts`
|
||
- `src/store/useMindmapStore.ts`
|
||
|
||
---
|
||
|
||
## 2. MindLayoutEngine(渲染管线与多布局)
|
||
|
||
| 对标 | MNOTE 方案 |
|
||
|------|-----------|
|
||
| `Render` 类(`design/Kmind/js/0.js:11753-15120`)整合布局、懒渲染、节点缓存和命令 | 拆成 `MindLayoutEngine`(纯 TS,负责布局计算)和 `MindmapRenderer`(React hook,负责 diff & 渲染)两部分。|
|
||
|
||
### 关键设计
|
||
1. **数据模型**:使用 `MindmapNode`(包含 `id`, `parentId`, `children`, `data`, `layout`)与 `MindmapTree`(单根/多根)。使用 `zod` 校验。
|
||
2. **布局策略**:引入 elkjs(cjs 版本)或自己实现逻辑结构布局。第一阶段至少实现 `LOGICAL_STRUCTURE` 与 `MIND_MAP`,接口定义:
|
||
```ts
|
||
interface LayoutEngine {
|
||
name: 'logical' | 'mind' | ...;
|
||
compute(tree: MindmapTree, options: LayoutOptions): LayoutResult;
|
||
}
|
||
```
|
||
3. **节点缓存**:维护 `nodeCache: Map<string, LayoutResultNode>`,当节点数据未变化时复用位置。通过 `useMemo` + `JSON.stringify` diff 或者基于 `hash`.
|
||
4. **命令注册**:结合 `MindmapCommandBus`(Zustand store + typed events),将 `INSERT_NODE` / `SET_NODE_STYLE` 等命令映射到 `MindLayoutEngine` -> `MindmapRenderer`.
|
||
5. **Generalization & multi-root**:沿用 KMind 的 `generalization` 数据结构:每个节点 `data.generalization?: { range: [start,end]; text: string }[]`,在布局时对“概要节点”作为独立 branch 处理。
|
||
|
||
### 输出物
|
||
- `src/lib/mindmap/layout/MindLayoutEngine.ts`
|
||
- `src/lib/mindmap/layout/engines/logicalStructure.ts`
|
||
- `src/lib/mindmap/layout/engines/mindMap.ts`
|
||
- `src/lib/mindmap/command/MindmapCommandBus.ts`
|
||
|
||
---
|
||
|
||
## 3. MindmapShortcutController(快捷键与命令路由)
|
||
|
||
| 对标 | MNOTE 方案 |
|
||
|------|-----------|
|
||
| `KeyCommand` (`design/Kmind/js/0.js:11709-11890`) | 创建 `src/components/mindmap/shortcut/MindmapShortcutController.ts`,Hook 化 `keydown` 捕获、实例隔离、编辑模式白名单。|
|
||
|
||
### 关键设计
|
||
1. **多实例隔离**:用一个全域 `activeInstanceId`(ref) + `MindmapCanvas` `useEffect` 控制,仅当前 canvas 响应快捷键。
|
||
2. **捕获阶段阻断**:在 `document` capture 阶段阻止 `Ctrl+Z`, `Ctrl+V` 等冒泡,若不在 mindmap 内部则放行。
|
||
3. **映射**:提供 `registerShortcut(keyCombo, handler, options)` API,内部用 `Map<string, ShortcutEntry[]>`,默认注册:新增节点、删除、展开、直觉按钮等。
|
||
4. **自定义检查**:暴露 `shouldHandleEvent(e)` 回调,允许 BlockNote/其它面板禁用快捷键。
|
||
|
||
### 输出物
|
||
- `src/components/mindmap/shortcut/MindmapShortcutController.ts`
|
||
- `src/types/mindmap/shortcuts.ts`
|
||
|
||
---
|
||
|
||
## 4. TextEdit 内嵌富文本
|
||
|
||
| 对标 | MNOTE 方案 |
|
||
|------|-----------|
|
||
| Quill `history/input/keyboard` + `render/TextEdit` | 在 `src/components/mindmap/text` 下实现 `MindmapTextEditor`(挂载于节点上方)与 `useMindmapTextEdit` Hook。|
|
||
|
||
### 关键设计
|
||
1. **编辑器选择**:为了兼容 BlockNote,可采用 `@blocknote/core` 的 `InlineContent` 或 mini TipTap 实例。若沿用 Quill,需要自定义 bubble toolbar + history 模块。
|
||
2. **实时测量**:使用 `ResizeObserver` 或 canvas measure,模拟 KMind 的 `node.createTextNode` -> `getNodeRect`,在编辑时更新 `width/height` 并触发布局。
|
||
3. **粘贴清洗**:复用 KMind 的 `handleInputPasteText`、`defenseXSS`(位置 `design/Kmind/js/0.js` utils 部分),转成 TypeScript。
|
||
4. **撤销栈**:复刻 `history` 模块逻辑,处理 `undo/redo` 按键,保持与 `MindmapShortcutController` 协调。
|
||
|
||
### 输出物
|
||
- `src/components/mindmap/text/MindmapTextEditor.tsx`
|
||
- `src/components/mindmap/text/useMindmapTextEdit.ts`
|
||
- `src/lib/mindmap/text/pasteSanitizer.ts`
|
||
|
||
---
|
||
|
||
## 5. Link / Attachment 解析层
|
||
|
||
| 对标 | MNOTE 方案 |
|
||
|------|-----------|
|
||
| `checkSiyuanLinkFormatData` / `checkSiyuanPdfUrlFormatData` (`design/Kmind/js/0.js:12040-12380`) | 在 `src/lib/mindmap/parser/linkParser.ts` 中封装同功能模块,输出标准化 `MindmapLink`.|
|
||
|
||
### 关键设计
|
||
1. **类型定义**:
|
||
```ts
|
||
type MindmapLink =
|
||
| { type: 'block'; blockId: string; title?: string }
|
||
| { type: 'document'; documentId: string; title?: string }
|
||
| { type: 'mindmap-node'; mindmapId: string; nodeId: string }
|
||
| { type: 'pdf'; path: string; id: string; imageUrl?: string; title?: string }
|
||
| { type: 'url'; url: string; title?: string };
|
||
```
|
||
2. **粘贴入口**:`MindmapTextEditor` 在 `onPaste` 中调用 parser,将结果写入 `node.data.link` 并触发 Command `SET_NODE_HYPERLINK`。
|
||
3. **附件扩展**:在 parser 中同时识别 `siyuan://plugins/kmind-plugin?data=...`、`siyuan://blocks/`、`((id 'title'))`、PDF 标注、普通 URL。
|
||
|
||
### 输出物
|
||
- `src/lib/mindmap/parser/linkParser.ts`
|
||
- `src/types/mindmap/link.ts`
|
||
|
||
---
|
||
|
||
## 6. 调试与验证
|
||
|
||
- 单元测试:使用 Vitest 对 `MindLayoutEngine`, `linkParser`, `MindmapCommandBus` 编写测试用例(位于 `src/lib/mindmap/__tests__`)。
|
||
- Storybook/Playground:创建 `src/components/mindmap/dev/MindmapPlayground.tsx`,用于手动测试多根、快捷键、TextEdit。
|
||
- 集成测试:待 P4 同步控制器完成后,在 Playwright 场景中验证:新增节点 -> 更新 BlockNote -> 重新渲染。
|
||
|
||
---
|
||
|
||
## Checklist(S1 完成判定)
|
||
|
||
- [ ] `MindmapCanvas` 支持多根、懒渲染、画布缩放状态。
|
||
- [ ] `MindLayoutEngine` 能输出 `LOGICAL_STRUCTURE` 布局并驱动 React Flow。
|
||
- [ ] `MindmapCommandBus` + `ShortcutController` 能响应基础快捷键(Enter, Tab, Delete, Ctrl+Z)。
|
||
- [ ] `MindmapTextEditor` 可打开/编辑节点文本,支持撤销/粘贴清洗。
|
||
- [ ] `linkParser` 能识别 siyuan 链接、PDF 标注并写入节点数据。
|
||
|
||
---
|
||
|
||
## 建议的开发顺序与依赖
|
||
|
||
1. **基础设施**:搭建 `useMindmapStore`、`MindmapCommandBus`、类型定义(`MindmapNode`, `MindmapTree`, `MindmapLink`)。
|
||
2. **布局引擎**:实现 `MindLayoutEngine` + `logicalStructure` 布局,并在 Vitest 中用静态树校验输出。
|
||
3. **Canvas 集成**:创建 `MindmapCanvas`,接入 `React Flow`,将布局结果映射成节点/连线,接通 store。
|
||
4. **快捷键/命令**:实现 `MindmapShortcutController`,注册插入/删除/折叠等命令,验证命令总线。
|
||
5. **TextEdit & Link Parser**:落地 `MindmapTextEditor` 和粘贴解析,把链接写入节点数据。
|
||
6. **调试工具**:搭建 `MindmapPlayground` + Vitest/Storybook 场景,验证多根与富文本。
|
||
|
||
### 外部依赖/资产
|
||
- `@xyflow/react`(React Flow 11+)
|
||
- `elkjs`(或其他布局库)——生成自动布局
|
||
- `zustand`, `immer`(状态管理)
|
||
- `@tiptap/react` 或 Quill(富文本)
|
||
- `vitest`, `testing-library/react`
|
||
|
||
---
|
||
|
||
> 下一步:按照本方案拆解具体任务,在 `mindlist.md` 的 S1 项目上依次打勾,并在备注表记录进度与提交。
|