mirror of
https://gitlab.com/ArkHost/HelixNotes.git
synced 2026-07-24 07:45:57 +02:00
v1.0.2 - Update banner, smart updater, install type detection
This commit is contained in:
+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.0.1",
|
"version": "1.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|||||||
Generated
+1
-1
@@ -1778,7 +1778,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "helixnotes"
|
name = "helixnotes"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "helixnotes"
|
name = "helixnotes"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
description = "Local-first markdown note-taking app"
|
description = "Local-first markdown note-taking app"
|
||||||
authors = ["HelixNotes"]
|
authors = ["HelixNotes"]
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
|||||||
@@ -1341,3 +1341,16 @@ fn save_app_config(config: &AppConfig) -> Result<(), String> {
|
|||||||
std::fs::write(path, data).map_err(|e| e.to_string())?;
|
std::fs::write(path, data).map_err(|e| e.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Install Type Detection ──
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_install_type() -> String {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
"windows".to_string()
|
||||||
|
} else if std::env::var("APPIMAGE").is_ok() {
|
||||||
|
"appimage".to_string()
|
||||||
|
} else {
|
||||||
|
"native".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ pub fn run() {
|
|||||||
commands::set_ai_settings,
|
commands::set_ai_settings,
|
||||||
commands::test_ai_connection,
|
commands::test_ai_connection,
|
||||||
commands::ai_ask,
|
commands::ai_ask,
|
||||||
|
commands::get_install_type,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if close_to_tray {
|
if close_to_tray {
|
||||||
|
|||||||
@@ -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.0.1",
|
"version": "1.0.2",
|
||||||
"identifier": "com.helixnotes.app",
|
"identifier": "com.helixnotes.app",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../build",
|
"frontendDist": "../build",
|
||||||
|
|||||||
@@ -309,3 +309,7 @@ export async function aiAsk(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return invoke("ai_ask", { action, text, customPrompt, requestId });
|
return invoke("ai_ask", { action, text, customPrompt, requestId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getInstallType(): Promise<string> {
|
||||||
|
return invoke("get_install_type");
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { showSettings, theme, appConfig } from '$lib/stores/app';
|
import { showSettings, theme, appConfig, updateAvailable as globalUpdateAvailable, installType, settingsTab } from '$lib/stores/app';
|
||||||
import { setTheme, setAccentColor, setFontSize, setFontFamily, setGeneralSettings, importObsidian, createBackup, listBackups, restoreBackup, deleteBackup, setBackupSettings, setAiSettings, testAiConnection } from '$lib/api';
|
import { setTheme, setAccentColor, setFontSize, setFontFamily, setGeneralSettings, importObsidian, createBackup, listBackups, restoreBackup, deleteBackup, setBackupSettings, setAiSettings, testAiConnection } from '$lib/api';
|
||||||
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
@@ -24,6 +24,23 @@
|
|||||||
}
|
}
|
||||||
loadAppVersion();
|
loadAppVersion();
|
||||||
|
|
||||||
|
// Switch to requested tab if set externally (e.g. from update badge)
|
||||||
|
$effect(() => {
|
||||||
|
const tab = $settingsTab;
|
||||||
|
if (tab) {
|
||||||
|
activeTab = tab as Tab;
|
||||||
|
$settingsTab = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pre-populate from global store if update was already detected at startup
|
||||||
|
$effect(() => {
|
||||||
|
const global = $globalUpdateAvailable;
|
||||||
|
if (global && !updateAvailable) {
|
||||||
|
updateAvailable = { version: global.version, body: global.body };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
async function handleCheckUpdate() {
|
async function handleCheckUpdate() {
|
||||||
updateChecking = true;
|
updateChecking = true;
|
||||||
updateMessage = null;
|
updateMessage = null;
|
||||||
@@ -33,6 +50,7 @@
|
|||||||
if (update) {
|
if (update) {
|
||||||
updateObj = update;
|
updateObj = update;
|
||||||
updateAvailable = { version: update.version, body: update.body, date: update.date };
|
updateAvailable = { version: update.version, body: update.body, date: update.date };
|
||||||
|
globalUpdateAvailable.set({ version: update.version, body: update.body });
|
||||||
updateMessage = { type: 'info', text: `Version ${update.version} is available!` };
|
updateMessage = { type: 'info', text: `Version ${update.version} is available!` };
|
||||||
} else {
|
} else {
|
||||||
updateMessage = { type: 'success', text: 'You are on the latest version.' };
|
updateMessage = { type: 'success', text: 'You are on the latest version.' };
|
||||||
@@ -1034,6 +1052,7 @@
|
|||||||
<div class="update-notes">{updateAvailable.body}</div>
|
<div class="update-notes">{updateAvailable.body}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if $installType === 'appimage' || $installType === 'windows'}
|
||||||
<button class="update-install-btn" onclick={handleDownloadAndInstall} disabled={updateDownloading}>
|
<button class="update-install-btn" onclick={handleDownloadAndInstall} disabled={updateDownloading}>
|
||||||
{#if updateDownloading}
|
{#if updateDownloading}
|
||||||
<svg class="spinner-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10" opacity="0.25" /><path d="M12 2a10 10 0 019.95 9" /></svg>
|
<svg class="spinner-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10" opacity="0.25" /><path d="M12 2a10 10 0 019.95 9" /></svg>
|
||||||
@@ -1050,6 +1069,14 @@
|
|||||||
<div class="update-progress-fill" style="width: {updateProgress}%"></div>
|
<div class="update-progress-fill" style="width: {updateProgress}%"></div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<a class="update-install-btn" href="https://codeberg.org/ArkHost/HelixNotes/releases" target="_blank" rel="noopener">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/>
|
||||||
|
</svg>
|
||||||
|
Download from Codeberg
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||||
import { vaultReady, focusMode } from '$lib/stores/app';
|
import { vaultReady, focusMode, updateAvailable, showSettings, settingsTab } from '$lib/stores/app';
|
||||||
|
|
||||||
let { onNewNote = () => {} }: {
|
let { onNewNote = () => {} }: {
|
||||||
onNewNote?: () => void;
|
onNewNote?: () => void;
|
||||||
@@ -71,6 +71,12 @@
|
|||||||
<line x1="29" y1="18" x2="19" y2="30" stroke="white" stroke-width="2" stroke-linecap="round" opacity="0.7" />
|
<line x1="29" y1="18" x2="19" y2="30" stroke="white" stroke-width="2" stroke-linecap="round" opacity="0.7" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="titlebar-title">HelixNotes</span>
|
<span class="titlebar-title">HelixNotes</span>
|
||||||
|
{#if $updateAvailable}
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<button class="update-badge" onmousedown={(e) => e.stopPropagation()} onclick={() => { $settingsTab = 'updates'; $showSettings = true; }}>
|
||||||
|
v{$updateAvailable.version} available
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-actions">
|
<div class="titlebar-actions">
|
||||||
<button class="switch-vault-btn" onclick={() => ($vaultReady = false)} title="Switch Vault">
|
<button class="switch-vault-btn" onclick={() => ($vaultReady = false)} title="Switch Vault">
|
||||||
@@ -144,6 +150,25 @@
|
|||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.update-badge {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--accent);
|
||||||
|
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
||||||
|
border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
transition: background 0.15s, border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-badge:hover {
|
||||||
|
background: color-mix(in srgb, var(--accent) 20%, transparent);
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 40%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
.titlebar-actions {
|
.titlebar-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const searchQuery = writable("");
|
|||||||
export const showCommandPalette = writable(false);
|
export const showCommandPalette = writable(false);
|
||||||
export const showSearch = writable(false);
|
export const showSearch = writable(false);
|
||||||
export const showSettings = writable(false);
|
export const showSettings = writable(false);
|
||||||
|
export const settingsTab = writable<string | null>(null);
|
||||||
export const showInfo = writable(false);
|
export const showInfo = writable(false);
|
||||||
export const notebookIcons = writable<Record<string, string>>({});
|
export const notebookIcons = writable<Record<string, string>>({});
|
||||||
export const quickAccessPaths = writable<string[]>([]);
|
export const quickAccessPaths = writable<string[]>([]);
|
||||||
@@ -45,6 +46,25 @@ export const focusMode = writable(false);
|
|||||||
// Theme
|
// Theme
|
||||||
export const theme = writable<string>("system");
|
export const theme = writable<string>("system");
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
export const updateAvailable = writable<{
|
||||||
|
version: string;
|
||||||
|
body?: string;
|
||||||
|
} | null>(null);
|
||||||
|
export const installType = writable<string>("native");
|
||||||
|
|
||||||
|
export async function checkForUpdate() {
|
||||||
|
try {
|
||||||
|
const { check } = await import("@tauri-apps/plugin-updater");
|
||||||
|
const update = await check();
|
||||||
|
if (update) {
|
||||||
|
updateAvailable.set({ version: update.version, body: update.body });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Silent fail — don't disrupt app startup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Derived
|
// Derived
|
||||||
export const sortedNotes = derived([notes, sortMode], ([$notes, $sortMode]) => {
|
export const sortedNotes = derived([notes, sortMode], ([$notes, $sortMode]) => {
|
||||||
const pinned = $notes.filter((n) => n.meta.pinned);
|
const pinned = $notes.filter((n) => n.meta.pinned);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import { theme, appConfig, activeNotePath } from '$lib/stores/app';
|
import { theme, appConfig, activeNotePath, installType, checkForUpdate } from '$lib/stores/app';
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||||
import { openFile } from '$lib/api';
|
import { openFile, getInstallType } from '$lib/api';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
@@ -31,6 +31,12 @@
|
|||||||
return resolved.join('/');
|
return resolved.join('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect install type and check for updates on startup
|
||||||
|
onMount(() => {
|
||||||
|
getInstallType().then(t => installType.set(t)).catch(() => {});
|
||||||
|
checkForUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
// Intercept all link clicks in capture phase to prevent webview navigation
|
// Intercept all link clicks in capture phase to prevent webview navigation
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
function handleLinkClick(e: MouseEvent) {
|
function handleLinkClick(e: MouseEvent) {
|
||||||
|
|||||||
Reference in New Issue
Block a user