Fix Windows wiki-link/graph showing duplicate and old-version notes (path separators)

This commit is contained in:
Yuri Karamian
2026-06-07 18:55:12 +02:00
parent 7c94370e48
commit a28e514ed4
4 changed files with 34 additions and 25 deletions
+10 -6
View File
@@ -387,10 +387,14 @@ pub fn get_all_note_titles(state: State<'_, AppState>) -> Result<Vec<NoteTitleEn
let config = state.config.lock().map_err(|e| e.to_string())?;
let vault_path = config.active_vault.as_ref().ok_or("No active vault")?;
let vault = std::path::Path::new(vault_path);
let hn_dir = operations::helixnotes_dir(vault_path);
let mut entries = Vec::new();
for entry in walkdir::WalkDir::new(vault)
.into_iter()
// Cross-platform exclusion (string "/.helixnotes/" checks miss Windows
// backslash paths, which leaked .helixnotes/history snapshots in as dupes).
.filter_entry(|e| !operations::is_hidden(e.path()) && !e.path().starts_with(&hn_dir))
.filter_map(|e| e.ok())
{
let path = entry.path();
@@ -398,9 +402,6 @@ pub fn get_all_note_titles(state: State<'_, AppState>) -> Result<Vec<NoteTitleEn
continue;
}
let path_str = path.to_string_lossy();
if path_str.contains("/.helixnotes/") || path_str.contains("/.trash/") {
continue;
}
if path.extension().and_then(|e| e.to_str()) != Some("md") {
continue;
}
@@ -449,15 +450,17 @@ pub fn get_graph_data(state: State<'_, AppState>) -> Result<crate::types::GraphD
let mut seen_paths: HashSet<String> = HashSet::new();
let mut contents: Vec<String> = Vec::new();
let hn_dir = operations::helixnotes_dir(vault_path);
for entry in walkdir::WalkDir::new(vault)
.into_iter()
// Cross-platform exclusion (string "/.helixnotes/" checks miss Windows
// backslash paths; is_hidden also covers .stversions/.stfolder/.trash/.git).
.filter_entry(|e| !operations::is_hidden(e.path()) && !e.path().starts_with(&hn_dir))
.filter_map(|e| e.ok())
{
let path = entry.path();
if !path.is_file() { continue; }
let path_str = path.to_string_lossy().to_string();
if path_str.contains("/.helixnotes/") || path_str.contains("/.trash/")
|| path_str.contains("/.stversions/") || path_str.contains("/.stfolder") { continue; }
if path.extension().and_then(|e| e.to_str()) != Some("md") { continue; }
// Skip Syncthing conflict files
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
@@ -483,7 +486,8 @@ pub fn get_graph_data(state: State<'_, AppState>) -> Result<crate::types::GraphD
// Also index by vault-relative path without extension (e.g. "subfolder/note name")
if let Ok(rel) = path.strip_prefix(vault) {
let rel_no_ext = rel.with_extension("");
let rel_lower = rel_no_ext.to_string_lossy().to_lowercase();
// Normalize Windows backslashes so [[folder/note]] links resolve cross-platform.
let rel_lower = rel_no_ext.to_string_lossy().replace('\\', "/").to_lowercase();
relpath_to_idx.entry(rel_lower).or_insert(idx);
}
+16 -16
View File
@@ -154,7 +154,7 @@ fn scan_dir_with_count(dir: &Path, vault_root: &str) -> (Vec<NotebookEntry>, usi
(entries, note_count)
}
fn is_hidden(path: &Path) -> bool {
pub(crate) fn is_hidden(path: &Path) -> bool {
path.file_name()
.and_then(|n| n.to_str())
.map(|n| {
@@ -708,17 +708,20 @@ fn update_wikilinks_after_rename(
new_title: &str,
) {
let vault = Path::new(vault_path);
let vault_prefix = format!("{}/", vault_path);
let hn_dir = helixnotes_dir(vault_path);
// Compute vault-relative path refs (without .md) for path-based wikilink refs
let old_rel_ref = old_path.strip_prefix(&vault_prefix)
.unwrap_or(old_path)
.strip_suffix(".md")
.unwrap_or(old_path);
let new_rel_ref = new_path.strip_prefix(&vault_prefix)
.unwrap_or(new_path)
.strip_suffix(".md")
.unwrap_or(new_path);
// Vault-relative path ref (without .md), normalized to forward slashes so it
// matches [[folder/note]] wikilinks on Windows (OS paths use backslashes there).
let rel_ref = |p: &str| -> String {
let rel = Path::new(p)
.strip_prefix(vault)
.map(|r| r.to_string_lossy().into_owned())
.unwrap_or_else(|_| p.to_string())
.replace('\\', "/");
rel.strip_suffix(".md").unwrap_or(&rel).to_string()
};
let old_rel_ref = rel_ref(old_path);
let new_rel_ref = rel_ref(new_path);
// Check if another note with the same old_title exists in the vault.
// If so, title-based replacements (rules 1-3) are ambiguous and must be skipped,
@@ -726,6 +729,7 @@ fn update_wikilinks_after_rename(
// vs. the other note with the same title.
let title_is_unique = !WalkDir::new(vault)
.into_iter()
.filter_entry(|e| !is_hidden(e.path()) && !e.path().starts_with(&hn_dir))
.filter_map(|e| e.ok())
.any(|e| {
let p = e.path();
@@ -735,10 +739,6 @@ fn update_wikilinks_after_rename(
if p.extension().and_then(|ext| ext.to_str()) != Some("md") {
return false;
}
let p_str = p.to_string_lossy();
if p_str.contains("/.helixnotes/") || p_str.contains("/.trash/") {
return false;
}
// Check if this note's filename (without .md) matches the old title
p.file_stem()
.and_then(|s| s.to_str())
@@ -748,12 +748,12 @@ fn update_wikilinks_after_rename(
for entry in WalkDir::new(vault)
.into_iter()
.filter_entry(|e| !is_hidden(e.path()) && !e.path().starts_with(&hn_dir))
.filter_map(|e| e.ok())
{
let path = entry.path();
if !path.is_file() { continue; }
let path_str = path.to_string_lossy();
if path_str.contains("/.helixnotes/") || path_str.contains("/.trash/") { continue; }
if path.extension().and_then(|e| e.to_str()) != Some("md") { continue; }
// Skip the renamed note itself
if *path_str == *new_path { continue; }