From 7624c42e02825e59b10861a5fe3e95929a6f688b Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Fri, 27 Mar 2026 14:05:59 +0100 Subject: [PATCH] v1.2.8 - fix url links, tab indent, todo state, graph rework, collapsible sections, nav history, page breaks, folder/tag display --- package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/commands.rs | 90 ++- src-tauri/src/lib.rs | 1 + src-tauri/src/types.rs | 1 + src-tauri/tauri.conf.json | 2 +- src/lib/api.ts | 4 + src/lib/components/AppLayout.svelte | 35 +- src/lib/components/Editor.svelte | 438 +++++++++++- src/lib/components/GraphView.svelte | 905 ++++++++++++++++-------- src/lib/components/InfoPanel.svelte | 2 +- src/lib/components/NoteList.svelte | 26 +- src/lib/components/SettingsPanel.svelte | 2 +- src/lib/components/Sidebar.svelte | 1 + src/lib/components/TitleBar.svelte | 1 + src/lib/stores/app.ts | 31 +- src/routes/+layout.svelte | 3 +- 18 files changed, 1187 insertions(+), 361 deletions(-) diff --git a/package.json b/package.json index 10cdab2..e60cfb7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "helixnotes", "private": true, "license": "AGPL-3.0-or-later", - "version": "1.2.7", + "version": "1.2.8", "type": "module", "scripts": { "dev": "vite dev", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3c7fb29..0640492 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1850,7 +1850,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helixnotes" -version = "1.2.7" +version = "1.2.8" dependencies = [ "arboard", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index cc9eaec..97fb35e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helixnotes" -version = "1.2.7" +version = "1.2.8" description = "Local markdown note-taking app" authors = ["HelixNotes"] license = "AGPL-3.0-or-later" diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index d318320..acc5262 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -430,9 +430,14 @@ pub fn get_graph_data(state: State<'_, AppState>) -> Result = HashMap::new(); + // title → list of node indices (one title can map to multiple notes in different folders) + let mut title_to_idxs: HashMap> = HashMap::new(); + // vault-relative path without extension → idx (for [[subfolder/note]] style links) + let mut relpath_to_idx: HashMap = HashMap::new(); + // absolute path → idx (for fast active-note lookup) + let mut path_to_idx: HashMap = HashMap::new(); let mut seen_paths: HashSet = HashSet::new(); let mut contents: Vec = Vec::new(); @@ -462,12 +467,18 @@ pub fn get_graph_data(state: State<'_, AppState>) -> Result) -> Result = HashSet::new(); + // edge_map: (src, tgt) → index in edges vec, used to detect reverse links + let mut edges: Vec = Vec::new(); + let mut edge_map: HashMap<(usize, usize), usize> = HashMap::new(); + + let add_edge = |edges: &mut Vec, edge_map: &mut HashMap<(usize, usize), usize>, src: usize, tgt: usize| { + if src == tgt { return; } + if edge_map.contains_key(&(src, tgt)) { return; } // exact duplicate + if let Some(&rev_idx) = edge_map.get(&(tgt, src)) { + // Reverse direction already exists — mark it as bidirectional + edges[rev_idx].bidirectional = true; + } else { + let idx = edges.len(); + edge_map.insert((src, tgt), idx); + edges.push(crate::types::GraphEdge { source: src, target: tgt, bidirectional: false }); + } + }; for (source_idx, body) in contents.iter().enumerate() { let bytes = body.as_bytes(); @@ -498,11 +523,21 @@ pub fn get_graph_data(state: State<'_, AppState>) -> Result Result<(), String> { +fn xdg_open(arg: &str) -> Result<(), String> { #[cfg(target_os = "linux")] { let mut cmd = std::process::Command::new("xdg-open"); - cmd.arg(&path); + cmd.arg(arg); // Clear AppImage environment so child processes find host binaries // (e.g. gio-launch-desktop on GNOME) if std::env::var("APPIMAGE").is_ok() { @@ -1390,25 +1424,35 @@ pub fn open_file(path: String) -> Result<(), String> { } } cmd.spawn() - .map_err(|e| format!("Failed to open {}: {}", path, e))?; + .map_err(|e| format!("Failed to open {}: {}", arg, e))?; } #[cfg(target_os = "macos")] { std::process::Command::new("open") - .arg(&path) + .arg(arg) .spawn() - .map_err(|e| format!("Failed to open {}: {}", path, e))?; + .map_err(|e| format!("Failed to open {}: {}", arg, e))?; } #[cfg(target_os = "windows")] { std::process::Command::new("cmd") - .args(["/C", "start", "", &path]) + .args(["/C", "start", "", arg]) .spawn() - .map_err(|e| format!("Failed to open {}: {}", path, e))?; + .map_err(|e| format!("Failed to open {}: {}", arg, e))?; } Ok(()) } +#[tauri::command] +pub fn open_file(path: String) -> Result<(), String> { + xdg_open(&path) +} + +#[tauri::command] +pub fn open_url(url: String) -> Result<(), String> { + xdg_open(&url) +} + #[tauri::command] pub fn copy_file_to(source: String, destination: String) -> Result<(), String> { std::fs::copy(&source, &destination).map_err(|e| format!("Failed to copy file: {}", e))?; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index b6649cf..bc33d6e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -137,6 +137,7 @@ pub fn run() { commands::get_vault_stats, commands::import_obsidian, commands::open_file, + commands::open_url, commands::copy_file_to, commands::create_backup, commands::list_backups, diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index df7b322..1b54f67 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -285,4 +285,5 @@ pub struct GraphNode { pub struct GraphEdge { pub source: usize, pub target: usize, + pub bidirectional: bool, } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d8c523a..386591b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "HelixNotes", - "version": "1.2.7", + "version": "1.2.8", "identifier": "com.helixnotes.app", "build": { "frontendDist": "../build", diff --git a/src/lib/api.ts b/src/lib/api.ts index 77794cc..b4a77d9 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -267,6 +267,10 @@ export async function openFile(path: string): Promise { return invoke("open_file", { path }); } +export async function openUrl(url: string): Promise { + return invoke("open_url", { url }); +} + export async function copyFileTo( source: string, destination: string, diff --git a/src/lib/components/AppLayout.svelte b/src/lib/components/AppLayout.svelte index 8381fda..1f80525 100644 --- a/src/lib/components/AppLayout.svelte +++ b/src/lib/components/AppLayout.svelte @@ -34,8 +34,10 @@ tags, notes, viewMode, + activeTag, updateAvailable as globalUpdateAvailable, - settingsTab + settingsTab, + navHistory } from '$lib/stores/app'; const appWindow = getCurrentWindow(); @@ -58,9 +60,7 @@ let isQuickAccess = $derived(noteRelativePath ? $quickAccessPaths.includes(noteRelativePath) : false); let backupInterval: ReturnType | null = null; - let navigatingFromHistory = false; - let noteHistory: string[] = []; - let noteHistoryIndex = -1; + function parseFrequencyMs(freq: string): number { switch (freq) { @@ -91,32 +91,19 @@ // Track note navigation in history stack $effect(() => { const path = $activeNotePath; - if (navigatingFromHistory) { - navigatingFromHistory = false; - return; - } - if (path) { - // Trim forward history and push - noteHistory = [...noteHistory.slice(0, noteHistoryIndex + 1), path]; - noteHistoryIndex = noteHistory.length - 1; - } + if (path) navHistory.push(path); }); function navigateHistory(direction: -1 | 1) { - const newIndex = noteHistoryIndex + direction; - if (newIndex < 0 || newIndex >= noteHistory.length) return; - const path = noteHistory[newIndex]; - noteHistoryIndex = newIndex; - navigatingFromHistory = true; + const path = navHistory.go(direction); + if (!path) return; readNote(path).then((content) => { editor?.flushSave(); $activeNote = content; $activeNotePath = path; $editorDirty = false; editor?.loadNote(path, content.content); - }).catch(() => { - // Note may have been deleted, ignore - }); + }).catch(() => {}); } async function handleOpenFile(filePath: string) { @@ -493,7 +480,7 @@ {#if $mobileView === 'sidebar'} HelixNotes {:else} - {$activeNotebook?.name || 'All Notes'} + {#if $viewMode === 'notebook'}{$activeNotebook?.name ?? 'Notebook'}{:else if $viewMode === 'tag'}#{$activeTag}{:else if $viewMode === 'quickaccess'}Quick Access{:else if $viewMode === 'daily'}Daily Notes{:else if $viewMode === 'trash'}Trash{:else}All Notes{/if} {/if} {#if $globalUpdateAvailable && $mobileView === 'sidebar'} @@ -597,7 +584,7 @@
- editor?.flushSave()} onNoteMoved={() => sidebar?.refresh()} /> + editor?.flushSave()} onNoteMoved={() => sidebar?.refresh()} onNoteCreated={() => editor?.focusTitle()} />
{#if !isMobile}
+ {#if $canGoBack || $canGoForward} + + {/if} {#if $editorDirty} Unsaved {/if} @@ -3545,6 +3746,31 @@ {/if}
+ {#if !$focusMode} +
+ + + {#if noteFolder} + {#each noteFolder.split('/') as segment, i} + {#if i > 0}{/if}{segment} + {/each} + {:else} + Unfiled Notes + {/if} + + {#if $activeNote.meta.tags?.length > 0} + · + {/if} + {#if $activeNote.meta.tags?.length > 0} + + {#each $activeNote.meta.tags as tag} + #{tag} + {/each} + + {/if} +
+ {/if} +
{#if noteSearchOpen}