Auto-save entries and lists on navigate-away and app close
- Add saveCurrentDocument() that persists entries (via saveEntryFromStore) and lists (via updateListByStoreId) when switching documents or sections - Register flush callback in session store so +layout.svelte can trigger a save before vault rebuild on window close - Remove explicit Save button from MarkdownEditor (no longer needed) - Simplify MarkdownEditor props (removed activeSection, onOpenDocument, onDeleteDocument since save is now handled by the parent) - Todos already save instantly; fragments keep their form-based workflow Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
parent
c7933aeeec
commit
58f9f46cb9
@ -36,13 +36,10 @@
|
|||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
{activeSection}
|
|
||||||
{openDocumentId}
|
{openDocumentId}
|
||||||
{openDocumentName}
|
{openDocumentName}
|
||||||
{openDocumentContent}
|
{openDocumentContent}
|
||||||
{onDocumentContentChange}
|
{onDocumentContentChange}
|
||||||
{onOpenDocument}
|
|
||||||
{onDeleteDocument}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -1,23 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
|
||||||
saveEntryFromStore,
|
|
||||||
type EntryItem,
|
|
||||||
} from "$lib/stores/entries";
|
|
||||||
import { renderMarkdown, extractEditorTitle } from "$lib/utils/markdown";
|
import { renderMarkdown, extractEditorTitle } from "$lib/utils/markdown";
|
||||||
|
|
||||||
export let activeSection = "entries";
|
|
||||||
export let openDocumentId = "";
|
export let openDocumentId = "";
|
||||||
export let openDocumentName = "";
|
export let openDocumentName = "";
|
||||||
export let openDocumentContent = "";
|
export let openDocumentContent = "";
|
||||||
export let onDocumentContentChange: (content: string) => void = () => {};
|
export let onDocumentContentChange: (content: string) => void = () => {};
|
||||||
export let onOpenDocument: (doc: { id: string; label: string; initialContent: string }) => void = () => {};
|
|
||||||
export let onDeleteDocument: (id: string) => void = () => {};
|
|
||||||
|
|
||||||
let markdownText = openDocumentContent;
|
let markdownText = openDocumentContent;
|
||||||
let lastOpenDocumentId = openDocumentId;
|
let lastOpenDocumentId = openDocumentId;
|
||||||
let previewOnly = true;
|
let previewOnly = true;
|
||||||
let editorInput: HTMLTextAreaElement | null = null;
|
let editorInput: HTMLTextAreaElement | null = null;
|
||||||
let entrySaveBusy = false;
|
|
||||||
|
|
||||||
function updateDraft(value: string) {
|
function updateDraft(value: string) {
|
||||||
markdownText = value;
|
markdownText = value;
|
||||||
@ -69,28 +61,6 @@
|
|||||||
applyWrap("[", "](https://example.com)");
|
applyWrap("[", "](https://example.com)");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveEntryDocument() {
|
|
||||||
if (activeSection !== "entries") return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
entrySaveBusy = true;
|
|
||||||
const previousId = openDocumentId;
|
|
||||||
const saved: EntryItem | null = await saveEntryFromStore(previousId, markdownText, "Overwrite");
|
|
||||||
if (!saved) return;
|
|
||||||
|
|
||||||
if (saved.id !== previousId) {
|
|
||||||
onDeleteDocument(previousId);
|
|
||||||
}
|
|
||||||
|
|
||||||
onOpenDocument(saved);
|
|
||||||
onDocumentContentChange(saved.initialContent);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[editor] entries:save:error", error);
|
|
||||||
} finally {
|
|
||||||
entrySaveBusy = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (openDocumentId !== lastOpenDocumentId) {
|
$: if (openDocumentId !== lastOpenDocumentId) {
|
||||||
markdownText = openDocumentContent;
|
markdownText = openDocumentContent;
|
||||||
lastOpenDocumentId = openDocumentId;
|
lastOpenDocumentId = openDocumentId;
|
||||||
@ -102,11 +72,6 @@
|
|||||||
<header class="editor-header">
|
<header class="editor-header">
|
||||||
<h1>{editorTitle}</h1>
|
<h1>{editorTitle}</h1>
|
||||||
<div class="editor-actions">
|
<div class="editor-actions">
|
||||||
{#if activeSection === "entries"}
|
|
||||||
<button type="button" on:click={saveEntryDocument} disabled={entrySaveBusy}>
|
|
||||||
{entrySaveBusy ? "Saving..." : "Save"}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
<button type="button" class:primary={!previewOnly} on:click={() => (previewOnly = false)}>Write</button>
|
<button type="button" class:primary={!previewOnly} on:click={() => (previewOnly = false)}>Write</button>
|
||||||
<button type="button" class:primary={previewOnly} on:click={() => (previewOnly = true)}>Preview</button>
|
<button type="button" class:primary={previewOnly} on:click={() => (previewOnly = true)}>Preview</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -22,3 +22,13 @@ export function clearVaultSession(): void {
|
|||||||
_password.set(null);
|
_password.set(null);
|
||||||
_unlocked.set(false);
|
_unlocked.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _flushCallback: (() => Promise<void>) | null = null;
|
||||||
|
|
||||||
|
export function setFlushCallback(fn: () => Promise<void>): void {
|
||||||
|
_flushCallback = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function flushBeforeClose(): Promise<void> {
|
||||||
|
if (_flushCallback) await _flushCallback();
|
||||||
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { getSessionPassword, clearVaultSession } from "$lib/stores/session";
|
import { getSessionPassword, clearVaultSession, flushBeforeClose } from "$lib/stores/session";
|
||||||
import { persistAndClearVault } from "$lib/backend/auth";
|
import { persistAndClearVault } from "$lib/backend/auth";
|
||||||
|
|
||||||
let closeInProgress = false;
|
let closeInProgress = false;
|
||||||
@ -14,6 +14,8 @@
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
closeInProgress = true;
|
closeInProgress = true;
|
||||||
|
|
||||||
|
try { await flushBeforeClose(); } catch {}
|
||||||
|
|
||||||
const password = getSessionPassword();
|
const password = getSessionPassword();
|
||||||
if (password) {
|
if (password) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -2,10 +2,10 @@
|
|||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { unlockVaultWorkspace } from "$lib/backend/auth";
|
import { unlockVaultWorkspace } from "$lib/backend/auth";
|
||||||
import AppModal from "$lib/components/AppModal.svelte";
|
import AppModal from "$lib/components/AppModal.svelte";
|
||||||
import { entriesStore, getDefaultEntry, hydrateEntries, loadEntryByStoreId } from "$lib/stores/entries";
|
import { entriesStore, getDefaultEntry, hydrateEntries, loadEntryByStoreId, saveEntryFromStore } from "$lib/stores/entries";
|
||||||
import { hydrateFragments } from "$lib/stores/fragments";
|
import { hydrateFragments } from "$lib/stores/fragments";
|
||||||
import { hydrateLists } from "$lib/stores/lists";
|
import { hydrateLists, updateListByStoreId } from "$lib/stores/lists";
|
||||||
import { isVaultReady, setVaultSession } from "$lib/stores/session";
|
import { isVaultReady, setFlushCallback, setVaultSession } from "$lib/stores/session";
|
||||||
import { hydrateTodos } from "$lib/stores/todos";
|
import { hydrateTodos } from "$lib/stores/todos";
|
||||||
import Navbar from "$lib/components/Navbar.svelte";
|
import Navbar from "$lib/components/Navbar.svelte";
|
||||||
import SidePanel from "$lib/components/SidePanel.svelte";
|
import SidePanel from "$lib/components/SidePanel.svelte";
|
||||||
@ -195,6 +195,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveCurrentDocument() {
|
||||||
|
if (!activeDocumentId) return;
|
||||||
|
const content = openDocuments[activeDocumentId];
|
||||||
|
if (!content?.trim()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (selectedSection === "entries") {
|
||||||
|
const saved = await saveEntryFromStore(activeDocumentId, content, "Overwrite");
|
||||||
|
if (saved && saved.id !== activeDocumentId) {
|
||||||
|
const { [activeDocumentId]: _, ...rest } = openDocuments;
|
||||||
|
openDocuments = { ...rest, [saved.id]: saved.initialContent };
|
||||||
|
activeDocumentId = saved.id;
|
||||||
|
activeDocumentLabel = saved.label;
|
||||||
|
}
|
||||||
|
} else if (selectedSection === "lists" && activeDocumentId.startsWith("lists/") && !activeDocumentId.startsWith("lists/draft-")) {
|
||||||
|
await updateListByStoreId(activeDocumentId, undefined, content);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// best-effort save
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleSelect(id: string) {
|
function handleSelect(id: string) {
|
||||||
if (id === "account" || id === "settings") {
|
if (id === "account" || id === "settings") {
|
||||||
goto(`/${id}`);
|
goto(`/${id}`);
|
||||||
@ -219,6 +241,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveCurrentDocument();
|
||||||
selectedSection = id;
|
selectedSection = id;
|
||||||
panelOpen = true;
|
panelOpen = true;
|
||||||
activeDocumentId = "";
|
activeDocumentId = "";
|
||||||
@ -226,6 +249,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleOpenDocument(doc: OpenDocument) {
|
async function handleOpenDocument(doc: OpenDocument) {
|
||||||
|
await saveCurrentDocument();
|
||||||
let resolvedDoc = doc;
|
let resolvedDoc = doc;
|
||||||
if (selectedSection === "entries" && doc.id.startsWith("entries/file/") && !doc.initialContent) {
|
if (selectedSection === "entries" && doc.id.startsWith("entries/file/") && !doc.initialContent) {
|
||||||
try {
|
try {
|
||||||
@ -255,6 +279,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
setFlushCallback(saveCurrentDocument);
|
||||||
bootstrapFragmentsWithUnlock();
|
bootstrapFragmentsWithUnlock();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user