Files
mnote/recycle/design/file-tree-vscode-checklist.md
T
2026-04-13 19:21:42 +08:00

168 lines
8.8 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.
# 文件树(类 VSCode Explorer)实现顺序 + 对应文件 + 必测用例
> 目标:在「文件模式」页面树中实现接近 VSCode Explorer 的体验:点击行高亮选择(支持多选/范围选)、`Ctrl+C/Ctrl+V` 复制粘贴(真实文件/文件夹复制)、拖拽移动/按修饰键拖拽复制。
>
> 非目标:键盘导航(↑↓←→)与编辑器正文内复制粘贴行为。
## 0. 参考代码位置(VSCode
说明:VSCode Explorer 不是独立 React 组件,依赖 Workbench 服务体系;这里用于“行为/结构参考”,不直接搬运实现。
- [x] VSCode 参考仓库:`cankao/vscode`
- [x] 关键入口与文件(详见 `design/vscodetree`):
- [x] `cankao/vscode/src/vs/workbench/contrib/files/browser/files.contribution.ts`
- [x] `cankao/vscode/src/vs/workbench/contrib/files/browser/views/explorerView.ts`
- [x] `cankao/vscode/src/vs/workbench/contrib/files/common/explorerModel.ts`
- [x] `cankao/vscode/src/vs/base/common/resourceTree.ts`
- [x] `cankao/vscode/src/vs/workbench/contrib/files/browser/fileCommands.ts`
## 1. MNOTE 当前实现位置(改造落点)
- [x] 文件树(文件模式):`wolai-frontend/src/components/sidebar/file-tree.tsx`
- [x] 侧边栏状态/数据/选择/复制/拖拽集成:`wolai-frontend/src/components/sidebar/sidebar.tsx`
- [x] 文档树扁平化:`wolai-frontend/src/lib/sidebar-tree.ts`(已有 `flattenDocumentTree`
- [x] 复制页面(保留单页复制):`wolai-frontend/src/app/api/documents/duplicate/route.ts`
- [x] 递归复制入口:`wolai-frontend/src/app/api/documents/copy-tree/route.ts`
- [x] 附件 copy/move/delete/rename`wolai-frontend/src/app/api/media/batch/route.ts`
## 2. 实现顺序(建议小步提交,每步能回归)
### Step 0:建立“可见行模型”(TreeRow)——多选/复制/拖拽的共同基础
**要做**
- [x] 抽象 `visibleRows: FileTreeRow[]`(按展开状态 flattendoc + index.md + assets + 子 doc
- [x] `rowId` 全局唯一:`doc:<id>` / `index:<docId>` / `asset:<assetId>`
**新增/改造文件**
- [x] `wolai-frontend/src/lib/file-tree/types.ts`
- [x] `wolai-frontend/src/lib/file-tree/rows.ts`
**必测用例(Vitest:纯函数)**
- [x] `buildVisibleRows` 顺序稳定(展开/折叠后符合预期)
- [x] `rowId` 唯一且可逆解析
---
### Step 1:选择模型(Selection Model)——对齐 VSCode 的点击/多选/范围选/右键语义
**要做**
- [x] 去除复选框,改为“点击行高亮选择”
- [x] 多选覆盖 doc/index.md/asset 同一集合
- [x] 状态字段:
- [x] `selectedRowIds: Set<string>`
- [x] `anchorRowId: string | null`Shift 范围起点)
- [x] `focusedRowId: string | null`(最后交互行,用于粘贴目标推断)
**新增文件**
- [x] `wolai-frontend/src/lib/file-tree/selection.ts`(把交互写成纯函数,便于测)
**必测用例(Vitest:纯函数)**
- [x] 单击:清空并仅选中当前;更新 anchor/focus
- [x] Ctrl/Cmd+单击:切换选中;不清空其他
- [x] Shift+单击:按 `visibleRows` 做区间选择(覆盖式)
- [x] 右键:未选中项右键 → 先切为单选再弹菜单;已选中项 → 保持多选集合
- [x] 空白处单击清空选择
---
### Step 2`Ctrl+C / Ctrl+V`(仅文件树区域生效)
**要做**
- [x] 只在“文件树区域激活”时响应 `Ctrl+C/V`,不影响编辑器输入框/正文
- [x] 复制时写入自定义剪贴板 payload(同时提供内存 fallback
- [x] 粘贴目标推断:
- [x] focused 为 doc → 贴入其下
- [x] focused 为 index/asset → 贴入其所属 doc
- [x] 无 focused → fallback activeDocId
**新增文件**
- [x] `wolai-frontend/src/lib/file-tree/clipboard.ts`
**必测用例(Vitest**
- [x] payload 版本/字段校验(`type/version/action/rowIds`
- [x] 目标推断 3 分支覆盖(doc / index|asset / null
- [x]`input/textarea/contenteditable` 内不拦截 `Ctrl+C/V`
---
### Step 3:“真实文件/文件夹复制”后端能力(对齐 VSCode 语义)
**要做**
- [x] doc(文件夹/页面)复制:支持递归复制其内容(子页面 + 正文 + 附件)
- [x] asset(真实文件)复制:复制存储对象并创建新记录(`media/batch copy` + 重名策略)
- [x] 命名冲突:自动生成不冲突名称(文件夹与文件分别处理)
- [x] 虚拟附件(mindmap.json 等)不参与复制/拖拽(仅保留打开/下载逻辑)
**新增/改造 API**
- [x] 新增:`wolai-frontend/src/app/api/documents/copy-tree/route.ts`(递归复制入口,返回新旧 id 映射)
- [x] 保持:`wolai-frontend/src/app/api/documents/duplicate/route.ts`(现有单页复制不破坏)
- [x] 补齐:`wolai-frontend/src/app/api/media/upload/route.ts`(写入 `bucket/storage_path`,便于后续 copy/move
- [x] 补齐:`wolai-frontend/src/app/api/media/batch/route.ts`copy/move 支持 `storage_path` 或可解析的 `file_url`;重名策略)
**纯函数工具与测试**
- [x] 命名:`wolai-frontend/src/lib/file-tree/naming.ts`
- [x] 测试:`wolai-frontend/src/lib/file-tree/naming.test.ts`
---
### Step 4:拖拽移动/复制(含多选拖拽)
**要做**
- [x] 拖拽默认移动;按修饰键(`Alt`)为复制(浏览器层面 dropEffect 已设置;Alt-copy 建议人工补测一次)
- [x] 起拖行在选择集中 → 拖整个选择集;否则仅拖当前行并先切为单选
- [ ] 拖拽悬停反馈(drop feedback):
- [ ] 悬停到“收起的 doc(文件夹)行”时,仅该行变灰
- [ ] 悬停到“展开的 doc(文件夹)行”时,该 doc 的可见子节点范围一起变灰(类似 VSCode Explorer
- [x] drop 目标:
- [x] drop 到 doc 行:贴入其下
- [x] drop 到 index/asset 行:等同贴入其所属 doc
- [x] 禁止把 doc 拖到自身或后代(含多选去重/去后代)
- [x] 与“拖拽上传文件到页面”的 drop 行为区分(payload 不同)
**新增文件**
- [x] `wolai-frontend/src/lib/file-tree/dnd.ts`
- [x] `wolai-frontend/src/lib/file-tree/asset.ts`(判断“真实文件”附件)
**必测用例(Vitest:纯函数)**
- [x] `inferDropTargetDocId`doc / index|asset / null 推断正确(`wolai-frontend/src/lib/file-tree/dnd.test.ts`
- [x] `isInvalidDocDrop`:自拖/后代拖拦截(`wolai-frontend/src/lib/file-tree/dnd.test.ts`
- [x] `isRealFileAsset`:排除 mindmap 等虚拟附件(`wolai-frontend/src/lib/file-tree/asset.test.ts`
## 3. 手工验收(每步至少跑一遍)
- [x] 点击行高亮;active(当前打开页)与 selected(选中)同时可识别
- [x] `Ctrl/Cmd+单击` 多选;`Shift+单击` 连续范围选;右键语义正确
- [ ] `Ctrl+C/Ctrl+V`:仅树区域生效;编辑器正文不受影响(MCP 按键模拟不稳定,建议人工复核一次)
- [x] 复制 doc`/api/documents/copy-tree` 已验证返回 200(页面端 `fetch`
- [ ] 复制 asset:生成新的存储对象(不是引用),新文件可独立下载(需要至少 1 个真实附件用例)
- [x] 拖拽移动 doc:已通过页面拖拽触发 `/api/documents/move` 并验证 200
- [x] 从系统拖拽文件到文件树:落点为目标页面(doc 行 / index 行 / asset 行),触发 `/api/media/upload` 上传并作为“真实文件”附件挂到该页面下
- [ ] 拖拽悬停反馈:收起 doc 行仅该行变灰;展开 doc 行则其可见范围一起变灰
- [ ] 从系统拖拽单个文件:只创建 1 份附件记录(不应出现两个同名文件)
- [ ] 从系统拖拽文件到“当前打开的页面”:主编辑区自动插入 1 个附件/媒体块(可在页面正文中看到)
- [ ] Alt+拖拽复制:逻辑已接入(`event.altKey`),需要人工补测一次(MCP 暂无法“按住 Alt 拖拽”)
---
### Step 5:多选删除(右键删除作用于选择集)
**要做**
- [x] 右键删除:若右键对象在 `selectedRowIds` 内,则对整个选择集执行删除
- [x] index 行视作其所属页面(去重后再删)
- [x] 页面删除:移动到垃圾桶(复用 `/api/documents/delete`
- [x] 附件删除:沿用当前逻辑(`/api/media/batch delete`mindmap 走 `/api/mindmap/:docId`
- [x] 若页面被删除,则跳过其页面内已选附件删除(避免重复/无效操作)
**新增/改造文件**
- [x] `wolai-frontend/src/lib/file-tree/delete.ts`
- [x] `wolai-frontend/src/components/sidebar/sidebar.tsx`
**必测用例(Vitest:纯函数)**
- [x] 选中父/子页面时,仅删除父页面(去后代)
- [x] index 行与 doc 行同时选中时,页面 id 去重
- [x] 页面被删时,其下已选附件不参与附件删除
**手工验收**
- [x] 多选后右键任一已选行 → 点击“删除到垃圾桶”,应删除整个选择集(而不是仅最后右键项)
- [ ] 多选包含「页面 + 附件」时:无论最后右键落在页面行还是附件行,“删除”都应按选择集执行