mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-07-24 07:45:57 +02:00
Fix Windows wiki-link/graph showing duplicate and old-version notes (path separators)
This commit is contained in:
@@ -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 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_path = config.active_vault.as_ref().ok_or("No active vault")?;
|
||||||
let vault = std::path::Path::new(vault_path);
|
let vault = std::path::Path::new(vault_path);
|
||||||
|
let hn_dir = operations::helixnotes_dir(vault_path);
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
for entry in walkdir::WalkDir::new(vault)
|
for entry in walkdir::WalkDir::new(vault)
|
||||||
.into_iter()
|
.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())
|
.filter_map(|e| e.ok())
|
||||||
{
|
{
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
@@ -398,9 +402,6 @@ pub fn get_all_note_titles(state: State<'_, AppState>) -> Result<Vec<NoteTitleEn
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let path_str = path.to_string_lossy();
|
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") {
|
if path.extension().and_then(|e| e.to_str()) != Some("md") {
|
||||||
continue;
|
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 seen_paths: HashSet<String> = HashSet::new();
|
||||||
let mut contents: Vec<String> = Vec::new();
|
let mut contents: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
let hn_dir = operations::helixnotes_dir(vault_path);
|
||||||
for entry in walkdir::WalkDir::new(vault)
|
for entry in walkdir::WalkDir::new(vault)
|
||||||
.into_iter()
|
.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())
|
.filter_map(|e| e.ok())
|
||||||
{
|
{
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if !path.is_file() { continue; }
|
if !path.is_file() { continue; }
|
||||||
let path_str = path.to_string_lossy().to_string();
|
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; }
|
if path.extension().and_then(|e| e.to_str()) != Some("md") { continue; }
|
||||||
// Skip Syncthing conflict files
|
// Skip Syncthing conflict files
|
||||||
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
|
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")
|
// Also index by vault-relative path without extension (e.g. "subfolder/note name")
|
||||||
if let Ok(rel) = path.strip_prefix(vault) {
|
if let Ok(rel) = path.strip_prefix(vault) {
|
||||||
let rel_no_ext = rel.with_extension("");
|
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);
|
relpath_to_idx.entry(rel_lower).or_insert(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ fn scan_dir_with_count(dir: &Path, vault_root: &str) -> (Vec<NotebookEntry>, usi
|
|||||||
(entries, note_count)
|
(entries, note_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_hidden(path: &Path) -> bool {
|
pub(crate) fn is_hidden(path: &Path) -> bool {
|
||||||
path.file_name()
|
path.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.map(|n| {
|
.map(|n| {
|
||||||
@@ -708,17 +708,20 @@ fn update_wikilinks_after_rename(
|
|||||||
new_title: &str,
|
new_title: &str,
|
||||||
) {
|
) {
|
||||||
let vault = Path::new(vault_path);
|
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
|
// Vault-relative path ref (without .md), normalized to forward slashes so it
|
||||||
let old_rel_ref = old_path.strip_prefix(&vault_prefix)
|
// matches [[folder/note]] wikilinks on Windows (OS paths use backslashes there).
|
||||||
.unwrap_or(old_path)
|
let rel_ref = |p: &str| -> String {
|
||||||
.strip_suffix(".md")
|
let rel = Path::new(p)
|
||||||
.unwrap_or(old_path);
|
.strip_prefix(vault)
|
||||||
let new_rel_ref = new_path.strip_prefix(&vault_prefix)
|
.map(|r| r.to_string_lossy().into_owned())
|
||||||
.unwrap_or(new_path)
|
.unwrap_or_else(|_| p.to_string())
|
||||||
.strip_suffix(".md")
|
.replace('\\', "/");
|
||||||
.unwrap_or(new_path);
|
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.
|
// 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,
|
// 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.
|
// vs. the other note with the same title.
|
||||||
let title_is_unique = !WalkDir::new(vault)
|
let title_is_unique = !WalkDir::new(vault)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.filter_entry(|e| !is_hidden(e.path()) && !e.path().starts_with(&hn_dir))
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
.any(|e| {
|
.any(|e| {
|
||||||
let p = e.path();
|
let p = e.path();
|
||||||
@@ -735,10 +739,6 @@ fn update_wikilinks_after_rename(
|
|||||||
if p.extension().and_then(|ext| ext.to_str()) != Some("md") {
|
if p.extension().and_then(|ext| ext.to_str()) != Some("md") {
|
||||||
return false;
|
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
|
// Check if this note's filename (without .md) matches the old title
|
||||||
p.file_stem()
|
p.file_stem()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
@@ -748,12 +748,12 @@ fn update_wikilinks_after_rename(
|
|||||||
|
|
||||||
for entry in WalkDir::new(vault)
|
for entry in WalkDir::new(vault)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.filter_entry(|e| !is_hidden(e.path()) && !e.path().starts_with(&hn_dir))
|
||||||
.filter_map(|e| e.ok())
|
.filter_map(|e| e.ok())
|
||||||
{
|
{
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if !path.is_file() { continue; }
|
if !path.is_file() { continue; }
|
||||||
let path_str = path.to_string_lossy();
|
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; }
|
if path.extension().and_then(|e| e.to_str()) != Some("md") { continue; }
|
||||||
// Skip the renamed note itself
|
// Skip the renamed note itself
|
||||||
if *path_str == *new_path { continue; }
|
if *path_str == *new_path { continue; }
|
||||||
|
|||||||
@@ -1325,7 +1325,10 @@
|
|||||||
function wikiLinkFolderPath(entry: NoteTitleEntry): string {
|
function wikiLinkFolderPath(entry: NoteTitleEntry): string {
|
||||||
const vaultRoot = $appConfig?.active_vault;
|
const vaultRoot = $appConfig?.active_vault;
|
||||||
if (!vaultRoot || !entry.path) return '';
|
if (!vaultRoot || !entry.path) return '';
|
||||||
const rel = entry.path.startsWith(vaultRoot + '/') ? entry.path.slice(vaultRoot.length + 1) : entry.path;
|
// Normalize Windows backslashes so the folder subtitle shows there too.
|
||||||
|
const path = entry.path.replace(/\\/g, '/');
|
||||||
|
const root = vaultRoot.replace(/\\/g, '/');
|
||||||
|
const rel = path.startsWith(root + '/') ? path.slice(root.length + 1) : path;
|
||||||
const parts = rel.split('/');
|
const parts = rel.split('/');
|
||||||
// Return parent folder(s), excluding the filename
|
// Return parent folder(s), excluding the filename
|
||||||
return parts.length > 1 ? parts.slice(0, -1).join('/') + '/' : '';
|
return parts.length > 1 ? parts.slice(0, -1).join('/') + '/' : '';
|
||||||
|
|||||||
@@ -76,8 +76,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getFolderKey(path: string): string {
|
function getFolderKey(path: string): string {
|
||||||
const vaultRoot = $appConfig?.active_vault || '';
|
// Normalize Windows backslashes so folder grouping/colours work there too.
|
||||||
const rel = vaultRoot && path.startsWith(vaultRoot + '/') ? path.slice(vaultRoot.length + 1) : path;
|
const p = (path || '').replace(/\\/g, '/');
|
||||||
|
const vaultRoot = ($appConfig?.active_vault || '').replace(/\\/g, '/');
|
||||||
|
const rel = vaultRoot && p.startsWith(vaultRoot + '/') ? p.slice(vaultRoot.length + 1) : p;
|
||||||
const slash = rel.indexOf('/');
|
const slash = rel.indexOf('/');
|
||||||
return slash >= 0 ? rel.slice(0, slash) : '';
|
return slash >= 0 ? rel.slice(0, slash) : '';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user