diff --git a/README.md b/README.md index 17dbb8b..487a320 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # HelixNotes [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](https://codeberg.org/ArkHost/HelixNotes/src/branch/main/LICENSE) -[![Latest Release](https://img.shields.io/badge/release-v1.2.6-green)](https://codeberg.org/ArkHost/HelixNotes/releases) +[![Latest Release](https://img.shields.io/badge/release-v1.2.7-green)](https://codeberg.org/ArkHost/HelixNotes/releases) [![Website](https://img.shields.io/badge/web-helixnotes.com-purple)](https://helixnotes.com) [![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20Windows%20%7C%20macOS%20%7C%20Android-lightgrey)]() @@ -10,7 +10,7 @@ A local markdown note-taking app built with Tauri, SvelteKit, and Rust. Your notes are stored as standard Markdown files on your local filesystem. No cloud, no lock-in. -## Download (v1.2.6) +## Download (v1.2.7) ### Linux @@ -28,23 +28,23 @@ curl -fsSL https://repo.arkhost.com/gpg.key | sudo gpg --dearmor -o /usr/share/k #### AppImage (Arch, Fedora 43+, openSUSE Tumbleweed) -[Download AppImage](https://download.helixnotes.com/releases/v1.2.6/HelixNotes_1.2.6_amd64.AppImage) +[Download AppImage](https://download.helixnotes.com/releases/v1.2.7/HelixNotes_1.2.7_amd64.AppImage) #### .deb (manual) -[Download .deb](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.2.6/HelixNotes_1.2.6_amd64.deb) (Ubuntu 22.04+) +[Download .deb](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.2.7/HelixNotes_1.2.7_amd64.deb) (Ubuntu 22.04+) ### Windows -[Download Installer](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.2.6/HelixNotes_1.2.6_x64-setup.exe) (Windows 10/11) +[Download Installer](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.2.7/HelixNotes_1.2.7_x64-setup.exe) (Windows 10/11) ### macOS -[Download .dmg](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.2.6/HelixNotes_1.2.6_x64.dmg) (Intel, runs on Apple Silicon via Rosetta) +[Download .dmg](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.2.7/HelixNotes_1.2.7_x64.dmg) (Intel, runs on Apple Silicon via Rosetta) ### Android -[Download APK](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.2.6/HelixNotes_1.2.6_android.apk) +[Download APK](https://codeberg.org/ArkHost/HelixNotes/releases/download/v1.2.7/HelixNotes_1.2.7_android.apk) --- diff --git a/package.json b/package.json index 538319c..10cdab2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "helixnotes", "private": true, "license": "AGPL-3.0-or-later", - "version": "1.2.6", + "version": "1.2.7", "type": "module", "scripts": { "dev": "vite dev", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3b7f629..3c7fb29 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1850,7 +1850,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "helixnotes" -version = "1.2.5" +version = "1.2.7" dependencies = [ "arboard", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f986ace..cc9eaec 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helixnotes" -version = "1.2.5" +version = "1.2.7" 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 3039968..d318320 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -257,6 +257,14 @@ pub fn save_note( drop(config); operations::save_note(&path, &meta, &body)?; + + // Re-index note so search picks up changes immediately + if let Ok(search_guard) = state.search_index.lock() { + if let Some(ref search) = *search_guard { + let _ = search.index_note(&path); + } + } + Ok(()) } @@ -1368,9 +1376,20 @@ fn resolve_wiki_ref( pub fn open_file(path: String) -> Result<(), String> { #[cfg(target_os = "linux")] { - std::process::Command::new("xdg-open") - .arg(&path) - .spawn() + let mut cmd = std::process::Command::new("xdg-open"); + cmd.arg(&path); + // Clear AppImage environment so child processes find host binaries + // (e.g. gio-launch-desktop on GNOME) + if std::env::var("APPIMAGE").is_ok() { + cmd.env_remove("LD_LIBRARY_PATH") + .env_remove("LD_PRELOAD") + .env_remove("GIO_LAUNCHED_DESKTOP_FILE") + .env_remove("GIO_LAUNCHED_DESKTOP_FILE_PID"); + if let Ok(original_path) = std::env::var("PATH_ORIG") { + cmd.env("PATH", original_path); + } + } + cmd.spawn() .map_err(|e| format!("Failed to open {}: {}", path, e))?; } #[cfg(target_os = "macos")] diff --git a/src-tauri/src/search/mod.rs b/src-tauri/src/search/mod.rs index 8a58f27..4454fa3 100644 --- a/src-tauri/src/search/mod.rs +++ b/src-tauri/src/search/mod.rs @@ -9,9 +9,9 @@ use tantivy::collector::TopDocs; use tantivy::directory::MmapDirectory; #[cfg(target_os = "android")] use tantivy::directory::RamDirectory; -use tantivy::query::QueryParser; +use tantivy::query::{BooleanQuery, FuzzyTermQuery, Occur, PhrasePrefixQuery, Query}; use tantivy::schema::*; -use tantivy::{Index, IndexWriter, TantivyDocument}; +use tantivy::{Index, IndexWriter, TantivyDocument, Term}; use walkdir::WalkDir; pub struct SearchIndex { @@ -152,14 +152,37 @@ impl SearchIndex { let reader = self.index.reader().map_err(|e| e.to_string())?; let searcher = reader.searcher(); - let query_parser = QueryParser::for_index( - &self.index, - vec![self.title_field, self.body_field, self.tags_field], - ); + let fields = vec![self.title_field, self.body_field, self.tags_field]; + let terms: Vec = query_str + .split_whitespace() + .map(|t| t.to_lowercase()) + .collect(); - let query = query_parser - .parse_query(query_str) - .map_err(|e| e.to_string())?; + // For each search term, create prefix + fuzzy queries across all fields (OR), + // then AND all terms together + let term_queries: Vec<(Occur, Box)> = terms + .iter() + .map(|term| { + let field_queries: Vec<(Occur, Box)> = fields + .iter() + .flat_map(|&field| { + let prefix: Box = Box::new(PhrasePrefixQuery::new( + vec![Term::from_field_text(field, term)], + )); + let fuzzy: Box = Box::new(FuzzyTermQuery::new( + Term::from_field_text(field, term), + 1, + true, + )); + vec![(Occur::Should, prefix), (Occur::Should, fuzzy)] + }) + .collect(); + let combined: Box = Box::new(BooleanQuery::new(field_queries)); + (Occur::Must, combined) + }) + .collect(); + + let query = BooleanQuery::new(term_queries); let top_docs = searcher .search(&query, &TopDocs::with_limit(limit)) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index da875fb..d8c523a 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.6", + "version": "1.2.7", "identifier": "com.helixnotes.app", "build": { "frontendDist": "../build", diff --git a/src/lib/components/AppLayout.svelte b/src/lib/components/AppLayout.svelte index 34210c9..8381fda 100644 --- a/src/lib/components/AppLayout.svelte +++ b/src/lib/components/AppLayout.svelte @@ -252,6 +252,7 @@ function handleKeydown(e: KeyboardEvent) { const mod = e.ctrlKey || e.metaKey; + const code = e.code; if (e.altKey && e.key === 'ArrowLeft') { e.preventDefault(); navigateHistory(-1); @@ -262,11 +263,12 @@ navigateHistory(1); return; } - if (mod && !e.shiftKey && e.key === 'n') { + if (mod && !e.shiftKey && code === 'KeyN') { e.preventDefault(); createAndFocusNote(); + return; } - if (mod && e.shiftKey && e.key === 'N') { + if (mod && e.shiftKey && code === 'KeyN') { e.preventDefault(); const isDark = $theme === 'dark' || ($theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); const next = isDark ? 'light' : 'dark'; @@ -274,12 +276,12 @@ setTheme(next); return; } - if (mod && e.shiftKey && e.key === 'F') { + if (mod && e.shiftKey && code === 'KeyF') { e.preventDefault(); $showSearch = true; return; } - if (mod && !e.shiftKey && e.key === 'f') { + if (mod && !e.shiftKey && code === 'KeyF') { e.preventDefault(); if ($activeNotePath) { editor?.openNoteSearch(); @@ -288,23 +290,25 @@ } return; } - if (mod && e.key === 'p') { + if (mod && !e.shiftKey && code === 'KeyP') { e.preventDefault(); $showCommandPalette = true; + return; } - if (mod && e.key === 's') { + if (mod && !e.shiftKey && code === 'KeyS') { e.preventDefault(); editor?.forceSave(); + return; } - if (mod && e.key === 'k' && !e.shiftKey && $activeNotePath && !$sourceMode) { + if (mod && code === 'KeyK' && !e.shiftKey && $activeNotePath && !$sourceMode) { e.preventDefault(); editor?.addLinkFromToolbar(); } - if (mod && e.shiftKey && e.key === 'M') { + if (mod && e.shiftKey && code === 'KeyM') { e.preventDefault(); $sourceMode = !$sourceMode; } - if (mod && e.shiftKey && e.key === 'W') { + if (mod && e.shiftKey && code === 'KeyW') { e.preventDefault(); if ($activeNotePath && $activeNote) { openNoteWindow($activeNotePath, $activeNote.meta.title); @@ -315,12 +319,12 @@ $sidebarCollapsed = !$sidebarCollapsed; return; } - if (mod && e.shiftKey && e.key === 'E') { + if (mod && e.shiftKey && code === 'KeyE') { e.preventDefault(); $focusMode = !$focusMode; return; } - if (mod && e.shiftKey && e.key === 'R') { + if (mod && e.shiftKey && code === 'KeyR') { e.preventDefault(); $readOnly = !$readOnly; return; diff --git a/src/lib/components/NoteWindow.svelte b/src/lib/components/NoteWindow.svelte index a90385a..3ac0fe9 100644 --- a/src/lib/components/NoteWindow.svelte +++ b/src/lib/components/NoteWindow.svelte @@ -67,11 +67,12 @@ function handleKeydown(e: KeyboardEvent) { const mod = e.ctrlKey || e.metaKey; - if (mod && e.key === 's') { + if (mod && !e.shiftKey && e.code === 'KeyS') { e.preventDefault(); editor?.forceSave(); + return; } - if (mod && e.shiftKey && e.key === 'M') { + if (mod && e.shiftKey && e.code === 'KeyM') { e.preventDefault(); $sourceMode = !$sourceMode; } diff --git a/src/lib/components/Sidebar.svelte b/src/lib/components/Sidebar.svelte index 0dce135..68cbb1e 100644 --- a/src/lib/components/Sidebar.svelte +++ b/src/lib/components/Sidebar.svelte @@ -490,7 +490,7 @@ class:active={$viewMode === 'notebook' && $activeNotebook?.relative_path === ''} onclick={selectUnfiled} > - +