From 6b165059cc8b3249335f8260e770ce47c968ba7d Mon Sep 17 00:00:00 2001 From: Yuri Karamian Date: Mon, 3 Aug 2026 02:03:58 +0200 Subject: [PATCH] Refine note info path and history --- src-tauri/src/commands.rs | 17 +++ src-tauri/src/lib.rs | 1 + src/lib/api.ts | 4 + src/lib/components/Editor.svelte | 226 ++++++++++++++-------------- src/lib/components/InfoPanel.svelte | 125 +-------------- 5 files changed, 136 insertions(+), 237 deletions(-) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index d4535e2..84bd924 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1231,6 +1231,23 @@ pub fn save_vault_state(state: State<'_, AppState>, vault_state: VaultState) -> // ── Clipboard ── +/// Copy text to the system clipboard through the native backend. +#[cfg(desktop)] +#[tauri::command] +pub fn copy_text_to_clipboard(text: String) -> Result<(), String> { + let mut clipboard = + arboard::Clipboard::new().map_err(|e| format!("Clipboard init failed: {}", e))?; + clipboard + .set_text(text) + .map_err(|e| format!("Failed to copy text: {}", e)) +} + +#[cfg(mobile)] +#[tauri::command] +pub fn copy_text_to_clipboard(_text: String) -> Result<(), String> { + Err("Text clipboard copy is only supported on desktop".to_string()) +} + /// Read image from system clipboard (bypasses WebKitGTK clipboard bug). /// Returns PNG bytes as Vec, or error if no image on clipboard. #[cfg(desktop)] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e0af3ab..9f81867 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -170,6 +170,7 @@ pub fn run() { commands::empty_trash, commands::load_vault_state, commands::save_vault_state, + commands::copy_text_to_clipboard, commands::read_clipboard_image, commands::copy_image_to_clipboard, commands::save_image, diff --git a/src/lib/api.ts b/src/lib/api.ts index 935ab14..8cf0ba3 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -217,6 +217,10 @@ export async function saveVaultState(vaultState: VaultState): Promise { return invoke("save_vault_state", { vaultState }); } +export async function copyTextToClipboard(text: string): Promise { + return invoke("copy_text_to_clipboard", { text }); +} + export async function readClipboardImage(): Promise { return invoke("read_clipboard_image"); } diff --git a/src/lib/components/Editor.svelte b/src/lib/components/Editor.svelte index 79ccc78..6227666 100644 --- a/src/lib/components/Editor.svelte +++ b/src/lib/components/Editor.svelte @@ -37,7 +37,7 @@ import { convertFileSrc } from '@tauri-apps/api/core'; import { getCurrentWindow } from '@tauri-apps/api/window'; import { readFile } from '@tauri-apps/plugin-fs'; - import { openFile, openUrl, copyFileTo, copyImageToClipboard as copyImageToClipboardCmd, writeBytesTo, copyPngToClipboard } from '$lib/api'; + import { openFile, openUrl, copyFileTo, copyImageToClipboard as copyImageToClipboardCmd, writeBytesTo, copyPngToClipboard, copyTextToClipboard } from '$lib/api'; import { save as saveDialog } from '@tauri-apps/plugin-dialog'; import { activeNote, activeNotePath, appConfig, editorDirty, sourceMode, focusMode, readOnly, quickAccessPaths, notes, navHistory, canGoBack, canGoForward, viewerNote, notebooks, outlineWidth } from '$lib/stores/app'; import { saveNote, saveImage, saveAttachment, readClipboardImage, addQuickAccess, removeQuickAccess, getQuickAccess, getNoteVersions, getNoteVersionContent, createVersion, aiAsk, getAllNoteTitles, readNote, renameNote } from '$lib/api'; @@ -331,6 +331,15 @@ let infoToggleBtnEl = $state(null); let wordCount = $state(0); let charCount = $state(0); + let infoPathCopyState = $state<'idle' | 'copied' | 'error'>('idle'); + let infoPathCopyTimer: ReturnType | null = null; + + $effect(() => { + $activeNotePath; + infoPathCopyState = 'idle'; + if (infoPathCopyTimer) clearTimeout(infoPathCopyTimer); + infoPathCopyTimer = null; + }); // In-note search let noteSearchOpen = $state(false); @@ -3066,19 +3075,27 @@ const scheduleCounts = debounce(updateCounts, 250); - async function toggleInfo() { - if (!showInfo && $activeNote) { - historyLoading = true; - try { - historyVersions = await getNoteVersions($activeNote.meta.id); - } catch { - historyVersions = []; - } - historyLoading = false; - } + function toggleInfo() { showInfo = !showInfo; } + async function copyActiveNotePath() { + const path = $activeNotePath; + if (!path) return; + if (infoPathCopyTimer) clearTimeout(infoPathCopyTimer); + try { + await copyTextToClipboard(path); + infoPathCopyState = 'copied'; + } catch (e) { + console.error('Failed to copy note path:', e); + infoPathCopyState = 'error'; + } + infoPathCopyTimer = setTimeout(() => { + infoPathCopyState = 'idle'; + infoPathCopyTimer = null; + }, 1600); + } + async function toggleHistory() { showHistory = !showHistory; historyPreview = null; @@ -5806,6 +5823,7 @@ clearTaskReveal(); destroyEditor(); unlistenFileChange?.(); + if (infoPathCopyTimer) clearTimeout(infoPathCopyTimer); }); @@ -6401,37 +6419,35 @@ {/if} - - -
-
- - -
- {#if historyLoading} -
Loading...
- {:else if historyVersions.length === 0} -
No snapshots yet. They're created automatically as you edit.
- {:else} -
- {#each historyVersions as v} - - {/each} + {#if !isMobile && $activeNotePath} +
+
+ Path on disk +
+ {$activeNotePath} +
{/if}
+
{/if} @@ -11402,13 +11418,6 @@ flex: 1; } - .info-section-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 8px; - } - .info-section-label { font-size: 11px; font-weight: 600; @@ -11418,28 +11427,6 @@ margin-bottom: 8px; } - .info-section-header .info-section-label { - margin-bottom: 0; - } - - .info-snapshot-btn { - display: flex; - align-items: center; - gap: 4px; - background: none; - border: 1px solid var(--border-color); - border-radius: 4px; - color: var(--text-secondary); - cursor: pointer; - padding: 3px 7px; - font-size: 11px; - } - - .info-snapshot-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); - } - .info-row { display: flex; align-items: baseline; @@ -11472,6 +11459,64 @@ max-width: 140px; } + .info-note-path { + min-width: 0; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--border-light); + } + + .info-note-path-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + } + + .info-copy-path-btn { + display: inline-flex; + align-items: center; + gap: 4px; + margin: -3px -4px -3px 0; + padding: 3px 5px; + border: none; + border-radius: 4px; + background: transparent; + color: var(--accent); + font-size: 11px; + font-weight: 600; + cursor: pointer; + } + + .info-copy-path-btn:hover { + background: var(--accent-light); + } + + .info-copy-path-btn:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 1px; + } + + .info-copy-path-btn.copied { + color: var(--success); + } + + .info-copy-path-btn.error { + color: var(--danger); + } + + .info-note-path-value { + display: block; + margin-top: 4px; + overflow: hidden; + color: var(--text-secondary); + font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Consolas, monospace; + font-size: 10px; + line-height: 1.4; + text-overflow: ellipsis; + white-space: nowrap; + } + .info-tags { display: flex; flex-wrap: wrap; @@ -11487,51 +11532,6 @@ padding: 1px 5px; } - .info-empty { - font-size: 12px; - color: var(--text-tertiary); - line-height: 1.5; - padding: 4px 0; - } - - .info-versions-list { - display: flex; - flex-direction: column; - gap: 1px; - margin-top: 4px; - } - - .info-version-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 8px; - border-radius: 5px; - background: none; - border: none; - cursor: pointer; - text-align: left; - width: 100%; - } - - .info-version-item:hover { - background: var(--bg-hover); - } - - .info-version-item.active { - background: var(--bg-active); - } - - .info-version-date { - font-size: 12px; - color: var(--text-primary); - } - - .info-version-size { - font-size: 11px; - color: var(--text-tertiary); - } - .editor-container.mobile .editor-body-row:has(.info-panel) > .editor-body { display: none; } diff --git a/src/lib/components/InfoPanel.svelte b/src/lib/components/InfoPanel.svelte index 1d0b6b1..f744b8e 100644 --- a/src/lib/components/InfoPanel.svelte +++ b/src/lib/components/InfoPanel.svelte @@ -1,5 +1,5 @@