v1.1.8 - Fix image paste persistence, save-flush on note switch, WebKitGTK clipboard fallback

This commit is contained in:
Yuri Karamian
2026-02-25 01:02:45 +01:00
parent 25d4cf7a9d
commit 1440bdcd90
8 changed files with 361 additions and 34 deletions
+4
View File
@@ -164,6 +164,10 @@ export async function saveVaultState(vaultState: VaultState): Promise<void> {
return invoke("save_vault_state", { vaultState });
}
export async function readClipboardImage(): Promise<number[]> {
return invoke("read_clipboard_image");
}
export async function saveImage(name: string, data: number[]): Promise<string> {
return invoke("save_image", { name, data });
}
+5 -2
View File
@@ -98,6 +98,7 @@
noteHistoryIndex = newIndex;
navigatingFromHistory = true;
readNote(path).then((content) => {
editor?.flushSave();
$activeNote = content;
$activeNotePath = path;
$editorDirty = false;
@@ -114,6 +115,7 @@
if (!vaultRoot || !filePath.startsWith(vaultRoot)) return;
try {
const content = await readNote(filePath);
editor?.flushSave();
$activeNote = content;
$activeNotePath = filePath;
$editorDirty = false;
@@ -167,6 +169,7 @@
try {
const entry = await createDailyNote();
const content = await readNote(entry.path);
editor?.flushSave();
$activeNote = content;
$activeNotePath = entry.path;
$editorDirty = false;
@@ -391,7 +394,7 @@
<Sidebar bind:this={sidebar} onViewChanged={handleViewChanged} />
</div>
<div class="mobile-panel" class:active={$mobileView === 'notelist'}>
<NoteList bind:this={noteList} onNoteSelected={handleNoteSelected} onNoteMoved={() => sidebar?.refresh()} />
<NoteList bind:this={noteList} onNoteSelected={handleNoteSelected} onBeforeNoteSwitch={() => editor?.flushSave()} onNoteMoved={() => sidebar?.refresh()} />
</div>
<div class="mobile-panel" class:active={$mobileView === 'editor'}>
<Editor bind:this={editor} />
@@ -485,7 +488,7 @@
{/if}
<div class="notelist-panel" style="width: {$notelistWidth}px">
<NoteList bind:this={noteList} onNoteSelected={handleNoteSelected} onNoteMoved={() => sidebar?.refresh()} />
<NoteList bind:this={noteList} onNoteSelected={handleNoteSelected} onBeforeNoteSwitch={() => editor?.flushSave()} onNoteMoved={() => sidebar?.refresh()} />
</div>
<ResizeHandle onResize={handleNotelistResize} />
+44 -30
View File
@@ -39,7 +39,7 @@
import { openFile, copyFileTo } from '$lib/api';
import { save as saveDialog } from '@tauri-apps/plugin-dialog';
import { activeNote, activeNotePath, appConfig, editorDirty, sourceMode, focusMode, readOnly, quickAccessPaths, notes } from '$lib/stores/app';
import { saveNote, saveImage, saveAttachment, addQuickAccess, removeQuickAccess, getQuickAccess, getNoteVersions, getNoteVersionContent, createVersion, aiAsk, getAllNoteTitles, readNote, renameNote } from '$lib/api';
import { saveNote, saveImage, saveAttachment, readClipboardImage, addQuickAccess, removeQuickAccess, getQuickAccess, getNoteVersions, getNoteVersionContent, createVersion, aiAsk, getAllNoteTitles, readNote, renameNote } from '$lib/api';
import type { VersionEntry, AiStreamEvent, NoteTitleEntry } from '$lib/types';
import { listen } from '@tauri-apps/api/event';
import { debounce } from '$lib/utils/debounce';
@@ -1203,22 +1203,25 @@
});
}
export function loadNote(path: string, content: string) {
// Flush any unsaved changes for the CURRENT note before loading the new one
if ($editorDirty && $activeNote && $activeNotePath && $activeNotePath !== path) {
try {
const body = $sourceMode
? restoreTitleH1(sourceContent)
: editorToMarkdown();
const trimmed = body.replace(/^#.*\n?/, '').trim();
if (trimmed || !$activeNote.content || $activeNote.content.trim().length <= 10) {
saveNote($activeNotePath, $activeNote.meta, body);
}
} catch (e) {
console.error('Pre-switch save failed:', e);
/** Flush unsaved editor content to disk (synchronous serialize + fire-and-forget save).
* Call BEFORE updating $activeNote/$activeNotePath stores when switching notes. */
export function flushSave() {
if (!$editorDirty || !$activeNote || !$activeNotePath) return;
try {
const body = $sourceMode
? restoreTitleH1(sourceContent)
: editorToMarkdown();
const trimmed = body.replace(/^#.*\n?/, '').trim();
if (trimmed || !$activeNote.content || $activeNote.content.trim().length <= 10) {
saveNote($activeNotePath, $activeNote.meta, body);
}
$editorDirty = false;
} catch (e) {
console.error('Pre-switch save failed:', e);
}
$editorDirty = false;
}
export function loadNote(path: string, content: string) {
loadedPath = path;
lastSourceMode = $sourceMode;
isLoadingNote = true;
@@ -2018,21 +2021,7 @@
});
function destroyEditor() {
// Flush unsaved changes before destroying
if ($editorDirty && $activeNote && $activeNotePath && editor) {
try {
const body = $sourceMode
? restoreTitleH1(sourceContent)
: editorToMarkdown();
const trimmed = body.replace(/^#.*\n?/, '').trim();
if (trimmed || !$activeNote.content || $activeNote.content.trim().length <= 10) {
saveNote($activeNotePath, $activeNote.meta, body);
}
} catch (e) {
console.error('Destroy-save failed:', e);
}
$editorDirty = false;
}
flushSave();
if (editor) {
editor.destroy();
editor = null;
@@ -2757,9 +2746,34 @@
}
return true;
}
// WebKitGTK fallback (bug #218519): older WebKitGTK versions return
// empty DataTransferItemList for image pastes. Detect this and read
// the image directly from the system clipboard via Rust/arboard.
if (items.length === 0) {
const hasText = event.clipboardData!.getData('text/plain');
const hasHtml = event.clipboardData!.getData('text/html');
if (!hasText && !hasHtml) {
event.preventDefault();
insertClipboardImage();
return true;
}
}
return false;
}
async function insertClipboardImage() {
try {
const data = await readClipboardImage();
const relativePath = await saveImage('pasted-image.png', data);
if (editor) {
const displaySrc = resolveImageSrc(relativePath);
editor.chain().focus().setImage({ src: displaySrc }).run();
}
} catch (e) {
console.error('Clipboard image fallback failed:', e);
}
}
async function insertImage(file: File) {
try {
const buffer = await file.arrayBuffer();
+3 -1
View File
@@ -35,9 +35,10 @@
import { openNoteWindow } from '$lib/utils/window';
import type { NoteEntry, SortMode } from '$lib/types';
let { onNoteSelected = (_path: string, _content: string) => {}, onNoteMoved = () => {} }: {
let { onNoteSelected = (_path: string, _content: string) => {}, onNoteMoved = () => {}, onBeforeNoteSwitch = () => {} }: {
onNoteSelected?: (path: string, content: string) => void;
onNoteMoved?: () => void;
onBeforeNoteSwitch?: () => void;
} = $props();
const modKey = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
@@ -184,6 +185,7 @@
if ($activeNotePath === note.path) return;
try {
const content = await readNote(note.path);
onBeforeNoteSwitch();
$activeNote = content;
$activeNotePath = note.path;
$editorDirty = false;