mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-07-24 07:45:57 +02:00
v1.2.7 - prefix search, keyboard shortcut fixes, GNOME AppImage fix
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# HelixNotes
|
# HelixNotes
|
||||||
|
|
||||||
[](https://codeberg.org/ArkHost/HelixNotes/src/branch/main/LICENSE)
|
[](https://codeberg.org/ArkHost/HelixNotes/src/branch/main/LICENSE)
|
||||||
[](https://codeberg.org/ArkHost/HelixNotes/releases)
|
[](https://codeberg.org/ArkHost/HelixNotes/releases)
|
||||||
[](https://helixnotes.com)
|
[](https://helixnotes.com)
|
||||||
[]()
|
[]()
|
||||||
|
|
||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
Generated
+1
-1
@@ -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,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"
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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,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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user