ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Tolaria ADR-0033 解析:子文件夹扫描与侧边栏文件夹树导航的实现

Tolaria ADR-0033 解析:子文件夹扫描与侧边栏文件夹树导航的实现 Tolaria ADR-0033 解析子文件夹扫描与侧边栏文件夹树导航的实现【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolariaTolaria一个基于 Tauri React 的 Markdown 知识库桌面应用早期采用扁平库设计只索引库根目录下的.md文件导致使用 PARA、带文件夹的 Zettelkasten 或项目目录工作流的用户无法看到任何子目录中的笔记。本文基于决策记录 ADR-0033 完整还原这一架构决策Rust 端如何用walkdir递归扫描所有可见子目录、如何用独立的list_vault_foldersTauri 命令向侧边栏输出折叠式 FOLDERS 树以及前端如何以{ kind: folder }选择类型渲染这棵可展开的目录树。读完本文你可以理解从扁平库到任意深度目录 文件夹导航的演进逻辑、扫描与过滤的具体规则以及每个关键决策在源码中的落点。背景ADR-0006 的扁平库约束为何被取代Tolaria 最初的库结构由 ADR-0006Flat vault structure 定义所有用户笔记以扁平的.md文件存放在库根目录类型Type完全由 frontmatter 的type:字段决定从不从文件夹位置推断。其扫描约束明确写道scan_vault 只索引根级.md文件加受保护文件夹非受保护子目录会被忽略。这一设计简化了 wikilink 解析和类型变更改类型 改 frontmatter而非移动文件但付出了可用性代价按文件夹组织笔记的用户PARA、带目录的 Zettelkasten、项目子目录在侧边栏中看不到也无法按目录过滤任何子目录笔记库扫描器静默忽略所有子目录中的.md文件带有任意文件夹结构的库实际上不可用ADR-0006 自己也预设了重评估触发条件如果用户需要嵌套文件夹层级做非类型组织例如项目专属子目录。ADR-0033 正是在该触发条件被满足后做出的决策扩展 Rust 端库扫描器以索引所有可见子目录中的.md文件并通过新的list_vault_foldersTauri 命令暴露库的文件夹树使侧边栏可以渲染一个可折叠的 FOLDERS 区块。同时明确排除规则隐藏目录以.开头的目录以及.git和.laputa同时被排除在扫描和文件夹树之外。决策的三个候选方案与取舍ADR-0033 完整记录了三条路线方案内容结论Option A采纳用walkdir扫描所有子目录另暴露独立的list_vault_folders命令简单VaultEntry无 schema 变更文件夹树轻量且独立于条目缓存Option B给VaultEntry加folder字段前端派生文件夹树把文件夹元数据与条目缓存耦合仅创建/删除文件夹无文件变化时的缓存失效被复杂化Option C保持扁平扫描增加虚拟文件夹功能按 frontmatter 路径前缀分组未解决子目录笔记缺失这一核心问题选择 Option A 的关键理由在于关注点分离文件夹树与笔记条目缓存完全解耦。这一取舍在源码中清晰可见——VaultEntry结构src-tauri/src/vault/entry.rs中并不存在folder字段而文件夹树由独立的FolderNode结构承载/// 库文件夹树中的一个节点。只包含目录不含文件。 #[derive(Debug, Serialize, Deserialize, Clone)] pub struct FolderNode { /// 文件夹名路径最后一段。 pub name: String, /// 相对于库根的路径使用 / 分隔符例如 projects/laputa。 pub path: String, /// 子文件夹按字母排序。 pub children: VecFolderNode, }见 src-tauri/src/vault/entry.rs。树形递归结构与children的按字母排序注释直接对应实现细节下文会验证。Rust 端实现一递归扫描所有可见子目录ADR 承诺的扩展扫描器落地在scan_vault及其核心scan_all_files。见 src-tauri/src/vault/mod.rs/// 扫描 vault 中所有文件包括子目录。 /// 隐藏目录以 . 开头被排除。 fn scan_all_files( vault_path: Path, git_dates: HashMapString, GitDates, entries: mut VecVaultEntry, ) { let walker WalkDir::new(vault_path) .follow_links(true) .into_iter() .filter_entry(|e| { if e.file_type().is_dir() { let name e.file_name().to_string_lossy(); // 跳过 vault 根本身depth 0—— 只过滤子目录 if e.depth() 0 { return true; } return !is_hidden_dir(name); } true }); for entry in walker.filter_map(|e| e.ok()) { if entry.path().is_file() { // 跳过隐藏文件以 . 开头—— 例如 .gitignore、.DS_Store let fname entry.file_name().to_string_lossy(); if fname.starts_with(.) { continue; } try_parse_file(entry.path(), vault_path, git_dates, entries); } } }实现细节与 ADR 承诺逐条对应递归深度无限制WalkDir::new(vault_path)不加min_depth/max_depth限制任意深度的.md文件都会被解析进VaultEntrytry_parse_file按is_md_file分派到parse_md_file或parse_non_md_file解析失败仅log::warn!后跳过不会中断整体扫描。隐藏目录排除filter_entry回调对depth 0的目录节点调用is_hidden_dir判断覆盖以.开头的目录自然包含.git与.laputa。注意根节点depth 0始终保留即使库根目录本身以.开头也能被打开。隐藏文件排除.gitignore、.DS_Store等以.开头的文件被显式跳过避免进入笔记列表。符号链接跟随follow_links(true)使软链接目录也被纳入扫描——这是 ADR 未显式提及、但从源码结构看属于有意为之的行为。排序稳定scan_vault最后按modified_at降序排序条目保证侧边栏展示顺序与日期git 日期优先于文件系统日期见modified_dates_tests.rs语义一致。ADR 还提到用户未来若有需要可以加.laputaignore来缓解非笔记.md文件如node_modules产生的多余条目。当前仓库中实际的缓解机制比这更进一步扫描结果会经过 gitignore 感知过滤——filter_visible_vault_entriessrc-tauri/src/commands/vault/file_cmds.rs在返回条目前调用vault::filter_gitignored_entries其开关来自hide_gitignored_files_enabled()设置。对应测试startup_snapshot_visibility_keeps_snapshot_and_filters_ignored_entries验证了被.gitignore忽略的ignored.md不会出现在结果中而visible.md会保留。Rust 端实现二list_vault_folders命令与文件夹树构建文件夹树独立于条目缓存这一点体现为一条与list_vault平行且互不依赖的命令链路。命令注册与定义src-tauri/src/commands/vault/file_cmds.rs#[tauri::command] pub async fn list_vault(path: PathBuf) - ResultVecVaultEntry, String { tokio::task::spawn_blocking(move || { with_expanded_vault_root(path.as_path(), scan_visible_vault_entries) }) .await .map_err(|e| format!(Task panicked: {e}))? } #[tauri::command] pub async fn list_vault_folders(path: PathBuf) - ResultVecFolderNode, String { tokio::task::spawn_blocking(move || { with_expanded_vault_root(path.as_path(), scan_visible_vault_folders) }) .await .map_err(|e| format!(Task panicked: {e}))? }两个命令都在spawn_blocking中执行以避免阻塞 tokio 线程池且都先经with_expanded_vault_root展开用户输入的路径处理~等命令在 src-tauri/src/lib.rs 的 invoke 列表中注册。可见性过滤ADR 只提了隐藏目录排除gitignore 过滤是后续演进叠加的fn scan_visible_vault_folders(vault_path: Path) - ResultVecFolderNode, String { let folders vault::scan_vault_folders(vault_path)?; Ok(vault::filter_gitignored_folders( vault_path, folders, crate::settings::hide_gitignored_files_enabled(), )) }即先构建原始树再按设置裁剪被 git 忽略的分支见 src-tauri/src/commands/vault/file_cmds.rs忽略集合的计算在 src-tauri/src/vault/ignored.rs。树的构建算法src-tauri/src/vault/mod.rs/// 构建 vault 中用户创建文件夹的树。 pub fn scan_vault_folders(vault_path: Path) - ResultVecFolderNode, String { if !vault_path.is_dir() { return Err(format!(Not a directory: {}, vault_path.display())); } fn build_tree(dir: Path, vault_root: Path) - VecFolderNode { let mut nodes: VecFolderNode Vec::new(); let entries match fs::read_dir(dir) { Ok(d) d, Err(_) return nodes, }; for entry in entries.flatten() { let path entry.path(); if !path.is_dir() { continue; // 只收目录不收文件 } let name entry.file_name().to_string_lossy().to_string(); if is_folder_tree_hidden_dir(name) { continue; // 隐藏目录不入树 } let rel_path path_identity::vault_relative_path_string(vault_root, path) .unwrap_or_else(|_| { path_identity::normalize_path_for_identity(path.to_string_lossy()) }); let children build_tree(path, vault_root); nodes.push(FolderNode { name, path: rel_path, children }); } nodes.sort_by_key(|node| node.name.to_lowercase()); nodes } Ok(build_tree(vault_path, vault_path)) }值得注意的三个工程点与条目扫描不同文件夹树只用fs::read_dir递归不解析任何 frontmatter因此成本远低于scan_vault——这正是 Option A 文件夹树轻量主张的直接体现path字段是相对于库根、统一用/分隔的规范化字符串vault_relative_path_string跨平台一致前端可直接用它做过滤键和身份键同级节点按名字不区分大小写排序name.to_lowercase()与FolderNode文档注释sorted alphabetically一致。前后端协作选择模型与侧边栏 FOLDERS 区块ADR 的后果部分预言了SidebarSelection获得新的{ kind: folder; path: string }变体——所有对 selection kind 的穷尽 switch 都必须处理它。当前代码库验证了这一变体已全面铺开选择类型在 src/types.ts 中定义{ kind: folder; path: string }贯穿侧边栏、集合构建与键盘导航src/collections/collectionFromSelection.ts 会为文件夹选择构造该目录下笔记的集合测试 src/collections/collectionFromSelection.test.ts 断言collectionFromSelection({ kind: folder, path: clients, rootPath: /vault })正确生成集合前端文件夹树组件位于 src/components/folder-tree/ 目录FolderTree.tsx区块渲染、FolderTreeRow.tsx单行节点、FolderItemRow.tsx、FolderContextMenu.tsx右键操作、FolderNameInput.tsx内联重命名/新建、folderTreeLayout.ts缩进/展开布局计算以及三个自定义 hookuseFolderTreeDisclosure.ts展开/折叠状态、useFolderRowInteractions.ts行交互、useFolderContextMenu.ts上下文菜单展开状态管理展示了手动状态 必需路径的合并模式src/components/folder-tree/useFolderTreeDisclosure.ts当用户选中某个文件夹时requiredExpandedPaths会强制展开该文件夹的所有祖先路径ancestorTreePaths与用户的manualExpanded手动状态合并mergeExpandedPaths顶层节点key 以::结尾或空 key默认展开。这保证了点击侧边栏中任意层级文件夹其父链一定可见的体验测试 src/components/FolderTree.test.tsx 覆盖了区块行为渲染FOLDERS标题与顶层文件夹、点击标题可折叠整个区块、行点击回调{ kind: folder, path: projects }、多工作区下path: 的库根节点选择等场景。另外Rust 端同时提供create_vault_folder命令支持在树中直接创建文件夹src-tauri/src/commands/vault/file_cmds.rs它通过 path boundary 校验拒绝逃逸库根的路径测试commands_reject_paths_outside_requested_vault验证了../escape会报 Path must stay inside the active vault并拒绝与已有文件夹重名。文件夹创建会触发新的list_vault_folders拉取而条目缓存src-tauri/src/vault/cache.rs 的scan_vault_cached只在必要时失效——Option A 解耦收益在此兑现。决策的后果与边界条件ADR-0033 明确记录了以下后果均可在仓库中找到对应实现或测试佐证所有深度的.md文件都被索引——非笔记 Markdown如node_modules内文件会产生多余条目。当前缓解手段是隐藏目录排除 gitignore 过滤见上文ADR 中提到的.laputaignore机制是未来如需要的可选项仓库中尚未实现。扫描与 git 缓存对齐git 缓存cache.rs本就使用walkdir做变更检测本次改动使扫描语义与缓存语义一致避免缓存感知到子目录变化但扫描忽略的错位。回归测试 src-tauri/src/vault/mod_tests/real_vault_consistency.rs 用独立的WalkDir遍历真实演示库校验扫描结果一致性。SidebarSelection的穷尽 switch 扩展——已在多处落地侧边栏、集合构建、笔记列表渲染测试均处理kind: folder。ADR-0006 的扁平库原则被放松笔记现在可以放在任意子目录中但类型定义文档仍然只存在于库根的type/目录——这一约束在 ADR-0096 中延续类型文档只能由根目录创建确保类型来自 frontmatter、不来自文件夹位置这一 ADR-0006 的核心不变量没有被破坏。递归文件夹过滤被明确推迟当前选中一个文件夹时只展示其直接子级笔记非递归ADR 要求如果用户要求递归文件夹过滤再重新评估。这与collectionFromSelection中按path前缀构造集合的实现方向一致。小结ADR-0033 是一次典型的约束放松 关注点分离重构扫描器从根目录 受保护文件夹放宽到所有非隐藏子目录同时把文件夹树从笔记条目模型中彻底拆出以一条轻量、独立失效的list_vault_folders命令供给前端。三个方案对比中落选者 B 和 C 分别输在耦合缓存和没解决核心问题上。理解这条决策链对把握 Tolaria 的后续演进文件夹右键操作、集合构建、gitignore 可见性边界、非 git 库支持等非常有用——它们的共同前提都是文件夹是一等导航对象但类型语义依然只由 frontmatter 承载。【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表