v1.2.7 - prefix search, keyboard shortcut fixes, GNOME AppImage fix

This commit is contained in:
Yuri Karamian
2026-03-19 19:19:26 +01:00
parent 4897703111
commit bc372993c4
10 changed files with 84 additions and 37 deletions
+7 -7
View File
@@ -1,7 +1,7 @@
# HelixNotes # 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) [![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) [![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)]() [![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. Your notes are stored as standard Markdown files on your local filesystem.
No cloud, no lock-in. No cloud, no lock-in.
## Download (v1.2.6) ## Download (v1.2.7)
### Linux ### 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) #### 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) #### .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 ### 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 ### 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 ### 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)
--- ---
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "helixnotes", "name": "helixnotes",
"private": true, "private": true,
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"version": "1.2.6", "version": "1.2.7",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
+1 -1
View File
@@ -1850,7 +1850,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "helixnotes" name = "helixnotes"
version = "1.2.5" version = "1.2.7"
dependencies = [ dependencies = [
"arboard", "arboard",
"chrono", "chrono",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "helixnotes" name = "helixnotes"
version = "1.2.5" version = "1.2.7"
description = "Local markdown note-taking app" description = "Local markdown note-taking app"
authors = ["HelixNotes"] authors = ["HelixNotes"]
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
+22 -3
View File
@@ -257,6 +257,14 @@ pub fn save_note(
drop(config); drop(config);
operations::save_note(&path, &meta, &body)?; 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(()) Ok(())
} }
@@ -1368,9 +1376,20 @@ fn resolve_wiki_ref(
pub fn open_file(path: String) -> Result<(), String> { pub fn open_file(path: String) -> Result<(), String> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
std::process::Command::new("xdg-open") let mut cmd = std::process::Command::new("xdg-open");
.arg(&path) cmd.arg(&path);
.spawn() // 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))?; .map_err(|e| format!("Failed to open {}: {}", path, e))?;
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
+32 -9
View File
@@ -9,9 +9,9 @@ use tantivy::collector::TopDocs;
use tantivy::directory::MmapDirectory; use tantivy::directory::MmapDirectory;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
use tantivy::directory::RamDirectory; use tantivy::directory::RamDirectory;
use tantivy::query::QueryParser; use tantivy::query::{BooleanQuery, FuzzyTermQuery, Occur, PhrasePrefixQuery, Query};
use tantivy::schema::*; use tantivy::schema::*;
use tantivy::{Index, IndexWriter, TantivyDocument}; use tantivy::{Index, IndexWriter, TantivyDocument, Term};
use walkdir::WalkDir; use walkdir::WalkDir;
pub struct SearchIndex { pub struct SearchIndex {
@@ -152,14 +152,37 @@ impl SearchIndex {
let reader = self.index.reader().map_err(|e| e.to_string())?; let reader = self.index.reader().map_err(|e| e.to_string())?;
let searcher = reader.searcher(); let searcher = reader.searcher();
let query_parser = QueryParser::for_index( let fields = vec![self.title_field, self.body_field, self.tags_field];
&self.index, let terms: Vec<String> = query_str
vec![self.title_field, self.body_field, self.tags_field], .split_whitespace()
); .map(|t| t.to_lowercase())
.collect();
let query = query_parser // For each search term, create prefix + fuzzy queries across all fields (OR),
.parse_query(query_str) // then AND all terms together
.map_err(|e| e.to_string())?; let term_queries: Vec<(Occur, Box<dyn Query>)> = terms
.iter()
.map(|term| {
let field_queries: Vec<(Occur, Box<dyn Query>)> = fields
.iter()
.flat_map(|&field| {
let prefix: Box<dyn Query> = Box::new(PhrasePrefixQuery::new(
vec![Term::from_field_text(field, term)],
));
let fuzzy: Box<dyn Query> = Box::new(FuzzyTermQuery::new(
Term::from_field_text(field, term),
1,
true,
));
vec![(Occur::Should, prefix), (Occur::Should, fuzzy)]
})
.collect();
let combined: Box<dyn Query> = Box::new(BooleanQuery::new(field_queries));
(Occur::Must, combined)
})
.collect();
let query = BooleanQuery::new(term_queries);
let top_docs = searcher let top_docs = searcher
.search(&query, &TopDocs::with_limit(limit)) .search(&query, &TopDocs::with_limit(limit))
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "HelixNotes", "productName": "HelixNotes",
"version": "1.2.6", "version": "1.2.7",
"identifier": "com.helixnotes.app", "identifier": "com.helixnotes.app",
"build": { "build": {
"frontendDist": "../build", "frontendDist": "../build",
+15 -11
View File
@@ -252,6 +252,7 @@
function handleKeydown(e: KeyboardEvent) { function handleKeydown(e: KeyboardEvent) {
const mod = e.ctrlKey || e.metaKey; const mod = e.ctrlKey || e.metaKey;
const code = e.code;
if (e.altKey && e.key === 'ArrowLeft') { if (e.altKey && e.key === 'ArrowLeft') {
e.preventDefault(); e.preventDefault();
navigateHistory(-1); navigateHistory(-1);
@@ -262,11 +263,12 @@
navigateHistory(1); navigateHistory(1);
return; return;
} }
if (mod && !e.shiftKey && e.key === 'n') { if (mod && !e.shiftKey && code === 'KeyN') {
e.preventDefault(); e.preventDefault();
createAndFocusNote(); createAndFocusNote();
return;
} }
if (mod && e.shiftKey && e.key === 'N') { if (mod && e.shiftKey && code === 'KeyN') {
e.preventDefault(); e.preventDefault();
const isDark = $theme === 'dark' || ($theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); const isDark = $theme === 'dark' || ($theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
const next = isDark ? 'light' : 'dark'; const next = isDark ? 'light' : 'dark';
@@ -274,12 +276,12 @@
setTheme(next); setTheme(next);
return; return;
} }
if (mod && e.shiftKey && e.key === 'F') { if (mod && e.shiftKey && code === 'KeyF') {
e.preventDefault(); e.preventDefault();
$showSearch = true; $showSearch = true;
return; return;
} }
if (mod && !e.shiftKey && e.key === 'f') { if (mod && !e.shiftKey && code === 'KeyF') {
e.preventDefault(); e.preventDefault();
if ($activeNotePath) { if ($activeNotePath) {
editor?.openNoteSearch(); editor?.openNoteSearch();
@@ -288,23 +290,25 @@
} }
return; return;
} }
if (mod && e.key === 'p') { if (mod && !e.shiftKey && code === 'KeyP') {
e.preventDefault(); e.preventDefault();
$showCommandPalette = true; $showCommandPalette = true;
return;
} }
if (mod && e.key === 's') { if (mod && !e.shiftKey && code === 'KeyS') {
e.preventDefault(); e.preventDefault();
editor?.forceSave(); editor?.forceSave();
return;
} }
if (mod && e.key === 'k' && !e.shiftKey && $activeNotePath && !$sourceMode) { if (mod && code === 'KeyK' && !e.shiftKey && $activeNotePath && !$sourceMode) {
e.preventDefault(); e.preventDefault();
editor?.addLinkFromToolbar(); editor?.addLinkFromToolbar();
} }
if (mod && e.shiftKey && e.key === 'M') { if (mod && e.shiftKey && code === 'KeyM') {
e.preventDefault(); e.preventDefault();
$sourceMode = !$sourceMode; $sourceMode = !$sourceMode;
} }
if (mod && e.shiftKey && e.key === 'W') { if (mod && e.shiftKey && code === 'KeyW') {
e.preventDefault(); e.preventDefault();
if ($activeNotePath && $activeNote) { if ($activeNotePath && $activeNote) {
openNoteWindow($activeNotePath, $activeNote.meta.title); openNoteWindow($activeNotePath, $activeNote.meta.title);
@@ -315,12 +319,12 @@
$sidebarCollapsed = !$sidebarCollapsed; $sidebarCollapsed = !$sidebarCollapsed;
return; return;
} }
if (mod && e.shiftKey && e.key === 'E') { if (mod && e.shiftKey && code === 'KeyE') {
e.preventDefault(); e.preventDefault();
$focusMode = !$focusMode; $focusMode = !$focusMode;
return; return;
} }
if (mod && e.shiftKey && e.key === 'R') { if (mod && e.shiftKey && code === 'KeyR') {
e.preventDefault(); e.preventDefault();
$readOnly = !$readOnly; $readOnly = !$readOnly;
return; return;
+3 -2
View File
@@ -67,11 +67,12 @@
function handleKeydown(e: KeyboardEvent) { function handleKeydown(e: KeyboardEvent) {
const mod = e.ctrlKey || e.metaKey; const mod = e.ctrlKey || e.metaKey;
if (mod && e.key === 's') { if (mod && !e.shiftKey && e.code === 'KeyS') {
e.preventDefault(); e.preventDefault();
editor?.forceSave(); editor?.forceSave();
return;
} }
if (mod && e.shiftKey && e.key === 'M') { if (mod && e.shiftKey && e.code === 'KeyM') {
e.preventDefault(); e.preventDefault();
$sourceMode = !$sourceMode; $sourceMode = !$sourceMode;
} }
+1 -1
View File
@@ -490,7 +490,7 @@
class:active={$viewMode === 'notebook' && $activeNotebook?.relative_path === ''} class:active={$viewMode === 'notebook' && $activeNotebook?.relative_path === ''}
onclick={selectUnfiled} onclick={selectUnfiled}
> >
<span class="chevron-spacer"></span> <span style="width:13px;flex-shrink:0"></span>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;opacity:0.6"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;opacity:0.6">
<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" /> <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" />
<polyline points="14 2 14 8 20 8" /> <polyline points="14 2 14 8 20 8" />