diff --git a/src/vault.rs b/src/vault.rs index a12a187..7373beb 100644 --- a/src/vault.rs +++ b/src/vault.rs @@ -1,5 +1,6 @@ use std::io; use std::path::{Path, PathBuf}; +use walkdir::WalkDir; // ── VaultDocument ───────────────────────────────────────────────────────────── @@ -83,6 +84,11 @@ pub fn is_within_vault(vault_path: &Path, candidate: &Path) -> bool { /// → matches "guides/getting-started.md" /// ``` pub fn resolve_wiki_link(vault_path: &Path, raw_target: &str) -> Option { + // Magic sentinel — [[Directory]] navigates to the virtual directory page + if raw_target.eq_ignore_ascii_case("directory") { + return Some(PathBuf::from("__directory__")); + } + // Split off subpath prefix if present: "guides/Getting Started" → ("guides", "Getting Started") let (subdir, name) = match raw_target.rfind('/') { Some(i) => (&raw_target[..i], &raw_target[i + 1..]), @@ -182,3 +188,66 @@ pub fn resolve_standard_link(vault_path: &Path, current_doc: &str, dest: &str) - None } + +// ── Directory listing ───────────────────────────────────────────────────────── + +/// Entry in the vault directory listing. +pub struct DirEntry { + /// Indentation depth (1-based, depth 1 = vault root children). + pub depth: usize, + /// Display name (without .md extension for files). + pub name: String, + /// True if this entry is a directory. + pub is_dir: bool, + /// Vault-relative path (only for .md files; None for directories). + pub vault_path: Option, +} + +/// List all markdown files and directories in the vault, sorted alphabetically. +/// +/// Skips hidden files/directories (starting with '.') and non-.md files. +/// Returns entries with depth for tree indentation. +pub fn list_vault_files(vault_path: &Path) -> Vec { + let mut entries = Vec::new(); + for entry in WalkDir::new(vault_path) + .sort_by_file_name() + .into_iter() + .filter_map(|e| e.ok()) + .skip(1) // skip vault root itself + { + let depth = entry.depth(); + let name = entry.file_name().to_string_lossy().to_string(); + + // Skip hidden files/dirs + if name.starts_with('.') { + continue; + } + + let is_dir = entry.file_type().is_dir(); + + if is_dir { + entries.push(DirEntry { + depth, + name, + is_dir: true, + vault_path: None, + }); + } else if name.ends_with(".md") { + let display_name = name.strip_suffix(".md").unwrap_or(&name).to_string(); + let rel = entry + .path() + .strip_prefix(vault_path) + .unwrap_or(entry.path()) + .to_string_lossy() + .to_string(); + entries.push(DirEntry { + depth, + name: display_name, + is_dir: false, + vault_path: Some(rel), + }); + } + // Skip non-.md files silently + } + entries +}