Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8308d89e8 | ||
|
|
0e5b380f8c | ||
|
|
e76919ab5c | ||
|
|
84ab90fd3f | ||
|
|
87b191eb63 | ||
|
|
0def9d6d6d | ||
|
|
e708d8ee63 | ||
|
|
461001442a | ||
|
|
6b5e923e8e | ||
|
|
289606a746 |
@@ -1,7 +1,7 @@
|
||||
# HelixNotes
|
||||
|
||||
[](https://gitlab.com/ArkHost/HelixNotes/-/blob/main/LICENSE)
|
||||
[](https://gitlab.com/ArkHost/HelixNotes/-/releases)
|
||||
[](https://gitlab.com/ArkHost/HelixNotes/-/releases/v1.3.4)
|
||||
[](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.
|
||||
No cloud, no lock-in.
|
||||
|
||||
## Download (v1.3.3)
|
||||
## Download (v1.3.4)
|
||||
|
||||
### Linux
|
||||
|
||||
@@ -92,21 +92,23 @@ sudo eopkg it helixnotes
|
||||
|
||||
#### AppImage (Arch, Fedora 43+, openSUSE Tumbleweed)
|
||||
|
||||
[Download AppImage](https://download.helixnotes.com/releases/v1.3.3/HelixNotes_1.3.3_amd64.AppImage)
|
||||
[Download AppImage](https://download.helixnotes.com/releases/v1.3.4/HelixNotes_1.3.4_amd64.AppImage)
|
||||
|
||||
#### .deb (manual)
|
||||
|
||||
[Download .deb](https://gitlab.com/ArkHost/HelixNotes/-/releases/v1.3.3/downloads/HelixNotes_1.3.3_amd64.deb) (Ubuntu 22.04+)
|
||||
[Download .deb](https://download.helixnotes.com/releases/v1.3.4/HelixNotes_1.3.4_amd64.deb) (Ubuntu 22.04+)
|
||||
|
||||
#### .rpm (manual)
|
||||
|
||||
[Download .rpm](https://download.helixnotes.com/releases/v1.3.4/HelixNotes-1.3.4-1.x86_64.rpm)
|
||||
|
||||
### Windows
|
||||
|
||||
[Download Installer](https://gitlab.com/ArkHost/HelixNotes/-/releases/v1.3.3/downloads/HelixNotes_1.3.3_x64-setup.exe) (Windows 10/11)
|
||||
[Download Installer](https://download.helixnotes.com/releases/v1.3.4/HelixNotes_1.3.4_x64-setup.exe) (Windows 10/11)
|
||||
|
||||
### macOS
|
||||
|
||||
[Download .dmg (Apple Silicon)](https://gitlab.com/ArkHost/HelixNotes/-/releases/v1.3.3/downloads/HelixNotes_1.3.3_aarch64.dmg) (M-series Macs)
|
||||
|
||||
[Download .dmg (Intel)](https://gitlab.com/ArkHost/HelixNotes/-/releases/v1.3.3/downloads/HelixNotes_1.3.3_x64.dmg) (older Macs, or Apple Silicon via Rosetta)
|
||||
[Download .dmg (Apple Silicon)](https://download.helixnotes.com/releases/v1.3.4/HelixNotes_1.3.4_aarch64.dmg) (M-series Macs)
|
||||
|
||||
> **"HelixNotes is damaged and can't be opened"?** The app isn't damaged. The macOS build isn't notarized by Apple yet, so Gatekeeper blocks it on Apple Silicon. Run this once in Terminal, then open it normally (you'll need to redo it after each update):
|
||||
>
|
||||
@@ -116,7 +118,7 @@ sudo eopkg it helixnotes
|
||||
|
||||
### Android
|
||||
|
||||
[Download APK](https://gitlab.com/ArkHost/HelixNotes/-/releases/v1.3.3/downloads/HelixNotes_1.3.3_android.apk)
|
||||
[Download APK](https://download.helixnotes.com/releases/v1.3.4/HelixNotes_1.3.4_android.apk)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "helixnotes",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"version": "1.3.3",
|
||||
"version": "1.3.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
||||
@@ -1850,7 +1850,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "helixnotes"
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"chrono",
|
||||
@@ -1860,6 +1860,7 @@ dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"notify",
|
||||
"objc2-app-kit",
|
||||
"png 0.17.16",
|
||||
"quick-xml 0.36.2",
|
||||
"rayon",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "helixnotes"
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
description = "Local markdown note-taking app"
|
||||
authors = ["HelixNotes"]
|
||||
license = "AGPL-3.0-or-later"
|
||||
@@ -42,6 +42,9 @@ quick-xml = "0.36"
|
||||
urlencoding = "2"
|
||||
sha2 = "0.10"
|
||||
rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] }
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc2-app-kit = "0.3.2"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
tauri-plugin-ios-vault-access = { path = "plugins/ios-vault-access" }
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 5.2 KiB |
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#fff</color>
|
||||
<color name="ic_launcher_background">#626ED4</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(85.333 85.333) scale(0.666667)">
|
||||
<line x1="178.45584" y1="178.45584" x2="333.54416" y2="333.54416" stroke="#ffffff" stroke-width="25" stroke-opacity="0.5" stroke-linecap="round"/>
|
||||
<line x1="337.53708" y1="174.46292" x2="174.46294" y2="337.53708" stroke="#ffffff" stroke-width="25" stroke-opacity="0.5" stroke-linecap="round"/>
|
||||
<circle cx="153" cy="153" r="36" fill="#ffffff"/>
|
||||
<circle cx="359" cy="153" r="36" fill="#ffffff"/>
|
||||
<circle cx="153" cy="359" r="36" fill="#ffffff"/>
|
||||
<circle cx="359" cy="359" r="36" fill="#ffffff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 706 B |
@@ -1,7 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{
|
||||
plugin::{Builder, PluginApi, PluginHandle, TauriPlugin},
|
||||
AppHandle, Manager, Runtime,
|
||||
plugin::{Builder, PluginHandle, TauriPlugin},
|
||||
Manager, Runtime,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
@@ -73,19 +73,11 @@ impl<R: Runtime, T: Manager<R>> IosVaultAccessExt<R> for T {
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize<R: Runtime>(
|
||||
_app: &AppHandle<R>,
|
||||
api: PluginApi<R, ()>,
|
||||
) -> tauri::Result<IosVaultAccess<R>> {
|
||||
let handle = api.register_ios_plugin(init_plugin_ios_vault_access)?;
|
||||
Ok(IosVaultAccess(handle))
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("ios-vault-access")
|
||||
.setup(|app, api| {
|
||||
let access = initialize(app, api)?;
|
||||
app.manage(access);
|
||||
let handle = api.register_ios_plugin(init_plugin_ios_vault_access)?;
|
||||
app.manage(IosVaultAccess(handle));
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
|
||||
@@ -392,11 +392,15 @@ pub fn import_custom_themes(state: State<'_, AppState>, path: String) -> Result<
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_font_size(state: State<'_, AppState>, size: u32) -> Result<(), String> {
|
||||
pub fn set_font_size(app: AppHandle, state: State<'_, AppState>, size: u32) -> Result<(), String> {
|
||||
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
||||
config.font_size = Some(size);
|
||||
save_app_config(&config)?;
|
||||
Ok(())
|
||||
drop(config);
|
||||
|
||||
use tauri::Emitter;
|
||||
app.emit("editor-font-size-changed", size)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -56,6 +56,23 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.on_page_load(|webview, payload| {
|
||||
#[cfg(target_os = "macos")]
|
||||
if webview.label() == "main"
|
||||
&& matches!(
|
||||
payload.event(),
|
||||
tauri::webview::PageLoadEvent::Finished
|
||||
)
|
||||
{
|
||||
let window = webview.window();
|
||||
let window_on_main = window.clone();
|
||||
let _ = window.run_on_main_thread(move || {
|
||||
fix_macos_traffic_lights(&window_on_main);
|
||||
});
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let _ = (webview, payload);
|
||||
})
|
||||
.setup(move |app| {
|
||||
#[cfg(target_os = "ios")]
|
||||
app.handle().plugin(tauri_plugin_ios_vault_access::init())?;
|
||||
@@ -88,13 +105,6 @@ pub fn run() {
|
||||
setup_tray(app)?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
fix_macos_traffic_lights(&window);
|
||||
}
|
||||
}
|
||||
|
||||
// Check CLI args for a .md file path on initial launch
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
@@ -401,44 +411,49 @@ fn percent_decode(input: &str) -> String {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn fix_macos_traffic_lights(window: &tauri::WebviewWindow) {
|
||||
fn fix_macos_traffic_lights(window: &tauri::Window) {
|
||||
use objc2_app_kit::{NSView, NSWindow, NSWindowButton};
|
||||
|
||||
const TITLEBAR_HEIGHT: f64 = 34.0;
|
||||
const LEFT_INSET: f64 = 12.0;
|
||||
|
||||
let ns_window_ptr = match window.ns_window() {
|
||||
Ok(ptr) => ptr,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let ns_window: &NSWindow = unsafe { &*(ns_window_ptr as *const NSWindow) };
|
||||
|
||||
let close = match ns_window.standardWindowButton(NSWindowButton::CloseButton) {
|
||||
Some(btn) => btn,
|
||||
Some(button) => button,
|
||||
None => return,
|
||||
};
|
||||
let miniaturize = match ns_window.standardWindowButton(NSWindowButton::MiniaturizeButton) {
|
||||
Some(btn) => btn,
|
||||
Some(button) => button,
|
||||
None => return,
|
||||
};
|
||||
let zoom = match ns_window.standardWindowButton(NSWindowButton::ZoomButton) {
|
||||
Some(btn) => btn,
|
||||
Some(button) => button,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let superview = match close.superview() {
|
||||
Some(sv) => sv,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let sv_height = NSView::frame(&superview).size.height;
|
||||
let btn_height = NSView::frame(&close).size.height;
|
||||
if sv_height <= 0.0 || btn_height <= 0.0 {
|
||||
let window_height = ns_window.frame().size.height;
|
||||
if window_height <= 0.0 {
|
||||
return;
|
||||
}
|
||||
let centered_y = (sv_height - btn_height) / 2.0;
|
||||
|
||||
for btn in [&close, &miniaturize, &zoom] {
|
||||
let mut frame = NSView::frame(btn);
|
||||
frame.origin.y = centered_y;
|
||||
btn.setFrameOrigin(frame.origin);
|
||||
let close_frame = NSView::frame(&close);
|
||||
let button_spacing = NSView::frame(&miniaturize).origin.x - close_frame.origin.x;
|
||||
for (index, button) in [&close, &miniaturize, &zoom].into_iter().enumerate() {
|
||||
let button_superview = match unsafe { button.superview() } {
|
||||
Some(view) => view,
|
||||
None => return,
|
||||
};
|
||||
let mut frame = NSView::frame(button);
|
||||
let top_inset = ((TITLEBAR_HEIGHT - frame.size.height) / 2.0).max(0.0);
|
||||
let mut target = frame.origin;
|
||||
target.y = window_height - top_inset - frame.size.height;
|
||||
frame.origin.y = button_superview.convertPoint_fromView(target, None).y;
|
||||
frame.origin.x = LEFT_INSET + index as f64 * button_spacing;
|
||||
button.setFrameOrigin(frame.origin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "HelixNotes",
|
||||
"version": "1.3.3",
|
||||
"version": "1.3.4",
|
||||
"identifier": "com.helixnotes.app",
|
||||
"build": {
|
||||
"frontendDist": "../build",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"hiddenTitle": true,
|
||||
"trafficLightPosition": {
|
||||
"x": 12,
|
||||
"y": 12
|
||||
"y": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -614,7 +614,9 @@
|
||||
// Run sidebar and note list refresh in parallel
|
||||
await Promise.all([sidebar?.refresh(), noteList?.refresh()]);
|
||||
|
||||
// Auto-cleanup orphaned attachments in the background
|
||||
// Full-vault attachment scans cause navigation stalls on mobile storage.
|
||||
// Mobile users can run the same cleanup explicitly from the Info panel.
|
||||
if (!isMobile) {
|
||||
setTimeout(async () => {
|
||||
if (get(editorDirty)) return; // skip if user is actively editing
|
||||
try {
|
||||
@@ -633,6 +635,7 @@
|
||||
}
|
||||
} catch (_) {}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Restore the last session (view + open note) if enabled.
|
||||
if ($appConfig?.restore_last_session) {
|
||||
|
||||
@@ -1112,7 +1112,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="list-content" bind:this={listContainer} onscroll={onListScroll} ondblclick={handleListDoubleClick}>
|
||||
<div class="list-content" class:tasks-mode={$viewMode === 'tasks'} bind:this={listContainer} onscroll={onListScroll} ondblclick={handleListDoubleClick}>
|
||||
{#if $viewMode === 'tasks'}
|
||||
<TasksView onOpenTask={openTask} onToggleTask={onToggleTask} onSetTaskPriority={onSetTaskPriority} onSetTaskDue={onSetTaskDue} />
|
||||
{/if}
|
||||
@@ -2213,6 +2213,11 @@
|
||||
padding: 4px 8px 180px;
|
||||
}
|
||||
|
||||
.note-list.mobile .list-content.tasks-mode {
|
||||
padding-bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.note-list.mobile .note-item {
|
||||
padding: 14px 16px;
|
||||
min-height: 56px;
|
||||
|
||||
@@ -807,6 +807,7 @@
|
||||
class="notebook-item"
|
||||
class:active={$viewMode === 'notebook' && $activeNotebook?.relative_path === ''}
|
||||
onclick={selectUnfiled}
|
||||
style="padding-left: 4px"
|
||||
>
|
||||
<span style="width:14px;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">
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { getAppConfig, openVault, restoreExternalVault, setFontSize } from '$lib/api';
|
||||
import { darkThemes, isIOS } from '$lib/platform';
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import VaultPicker from '$lib/components/VaultPicker.svelte';
|
||||
import AppLayout from '$lib/components/AppLayout.svelte';
|
||||
import NoteWindow from '$lib/components/NoteWindow.svelte';
|
||||
@@ -13,6 +14,7 @@
|
||||
let startupVaultError = $state('');
|
||||
let fontSizeSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let removeEditorZoomShortcuts: (() => void) | null = null;
|
||||
let removeFontSizeListener: (() => void) | null = null;
|
||||
|
||||
const defaultEditorFontSize = 14;
|
||||
const minEditorFontSize = 10;
|
||||
@@ -56,12 +58,17 @@
|
||||
};
|
||||
}
|
||||
|
||||
function setEditorFontSize(value: number) {
|
||||
function applyEditorFontSize(value: number) {
|
||||
const nextSize = Math.max(minEditorFontSize, Math.min(maxEditorFontSize, Math.round(value)));
|
||||
const restoreEditorViewport = preserveEditorViewportAfterLayout(getEditorScrollSurface());
|
||||
if ($appConfig) $appConfig.font_size = nextSize;
|
||||
document.documentElement.style.setProperty('--editor-font-size', `${nextSize}px`);
|
||||
restoreEditorViewport();
|
||||
return nextSize;
|
||||
}
|
||||
|
||||
function setEditorFontSize(value: number) {
|
||||
const nextSize = applyEditorFontSize(value);
|
||||
if (fontSizeSaveTimer) clearTimeout(fontSizeSaveTimer);
|
||||
fontSizeSaveTimer = setTimeout(() => {
|
||||
setFontSize(nextSize).catch((e) => console.error('Failed to save font size:', e));
|
||||
@@ -117,6 +124,9 @@
|
||||
try {
|
||||
const config = await getAppConfig();
|
||||
$appConfig = config;
|
||||
removeFontSizeListener = await listen<number>('editor-font-size-changed', (event) => {
|
||||
if ($appConfig?.font_size !== event.payload) applyEditorFontSize(event.payload);
|
||||
});
|
||||
$theme = config.theme || 'system';
|
||||
|
||||
// Apply theme immediately to prevent flash
|
||||
@@ -133,9 +143,7 @@
|
||||
}
|
||||
|
||||
// Apply saved font settings
|
||||
if (config.font_size) {
|
||||
document.documentElement.style.setProperty('--editor-font-size', `${config.font_size}px`);
|
||||
}
|
||||
if (config.font_size) applyEditorFontSize(config.font_size);
|
||||
if (config.line_height) {
|
||||
document.documentElement.style.setProperty('--editor-line-height', String(config.line_height));
|
||||
}
|
||||
@@ -193,15 +201,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Apply saved interface scale (desktop main window only; no-ops gracefully elsewhere)
|
||||
if (!noteWindowPath && config.ui_scale && config.ui_scale !== 1) {
|
||||
// Apply saved interface scale to every desktop window; no-ops gracefully elsewhere.
|
||||
if (config.ui_scale && config.ui_scale !== 1) {
|
||||
try {
|
||||
await getCurrentWebview().setZoom(config.ui_scale);
|
||||
} catch (e) {
|
||||
console.error('Failed to apply interface scale:', e);
|
||||
}
|
||||
}
|
||||
if (!noteWindowPath) removeEditorZoomShortcuts = installEditorZoomShortcuts();
|
||||
removeEditorZoomShortcuts = installEditorZoomShortcuts();
|
||||
|
||||
// Auto-open last vault if available
|
||||
if (config.active_vault) {
|
||||
@@ -240,6 +248,7 @@
|
||||
|
||||
onDestroy(() => {
|
||||
removeEditorZoomShortcuts?.();
|
||||
removeFontSizeListener?.();
|
||||
if (fontSizeSaveTimer) clearTimeout(fontSizeSaveTimer);
|
||||
});
|
||||
</script>
|
||||
|
||||