Files
mnote/design/s1-core-plan.md
T

8.6 KiB
Raw Blame History

S1 核心内核实施方案(Mindmap 内核对标 KMind

目标:在不改变现有 Next.js + BlockNote + Yjs + Supabase 架构的前提下,完成 Stage S1 中的 5 个核心能力,形成可落地的代码骨架与开发清单。


1. MindmapCanvas(画布容器 / 多根模式 / 懒渲染)

对标 MNOTE 方案
design/Kmind/js/0.js:719-1160MindMap 类,负责容器初始化、SVG 分层、大小检测、延迟渲染、多根导图 src/components/mindmap/canvas/MindmapCanvas.tsx(新建)中封装一个 React 组件,内部通过 @xyflow/reactReactFlowProvider + 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;需要添加自定义背景(连线)、节点容器及“其他层”(如选框、直觉按钮)。做法:通过 ReactFlownodeTypes + edgeTypes + 自定义 Background/Controls 组件来模拟 lineDraw/nodeDraw/otherDraw
  4. 画布状态管理:使用 Zustandsrc/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,负责布局计算)和 MindmapRendererReact hook,负责 diff & 渲染)两部分。

关键设计

  1. 数据模型:使用 MindmapNode(包含 id, parentId, children, data, layout)与 MindmapTree(单根/多根)。使用 zod 校验。
  2. 布局策略:引入 elkjs(cjs 版本)或自己实现逻辑结构布局。第一阶段至少实现 LOGICAL_STRUCTUREMIND_MAP,接口定义:
    interface LayoutEngine {
      name: 'logical' | 'mind' | ...;
      compute(tree: MindmapTree, options: LayoutOptions): LayoutResult;
    }
    
  3. 节点缓存:维护 nodeCache: Map<string, LayoutResultNode>,当节点数据未变化时复用位置。通过 useMemo + JSON.stringify diff 或者基于 hash.
  4. 命令注册:结合 MindmapCommandBusZustand 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.tsHook 化 keydown 捕获、实例隔离、编辑模式白名单。

关键设计

  1. 多实例隔离:用一个全域 activeInstanceIdref + 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/coreInlineContent 或 mini TipTap 实例。若沿用 Quill,需要自定义 bubble toolbar + history 模块。
  2. 实时测量:使用 ResizeObserver 或 canvas measure,模拟 KMind 的 node.createTextNode -> getNodeRect,在编辑时更新 width/height 并触发布局。
  3. 粘贴清洗:复用 KMind 的 handleInputPasteTextdefenseXSS(位置 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

对标 MNOTE 方案
checkSiyuanLinkFormatData / checkSiyuanPdfUrlFormatData (design/Kmind/js/0.js:12040-12380) src/lib/mindmap/parser/linkParser.ts 中封装同功能模块,输出标准化 MindmapLink.

关键设计

  1. 类型定义
    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. 粘贴入口MindmapTextEditoronPaste 中调用 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 -> 重新渲染。

ChecklistS1 完成判定)

  • MindmapCanvas 支持多根、懒渲染、画布缩放状态。
  • MindLayoutEngine 能输出 LOGICAL_STRUCTURE 布局并驱动 React Flow。
  • MindmapCommandBus + ShortcutController 能响应基础快捷键(Enter, Tab, Delete, Ctrl+Z)。
  • MindmapTextEditor 可打开/编辑节点文本,支持撤销/粘贴清洗。
  • linkParser 能识别 siyuan 链接、PDF 标注并写入节点数据。

建议的开发顺序与依赖

  1. 基础设施:搭建 useMindmapStoreMindmapCommandBus、类型定义(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/reactReact Flow 11+
  • elkjs(或其他布局库)——生成自动布局
  • zustand, immer(状态管理)
  • @tiptap/react 或 Quill(富文本)
  • vitest, testing-library/react

下一步:按照本方案拆解具体任务,在 mindlist.md 的 S1 项目上依次打勾,并在备注表记录进度与提交。