diff --git a/src/lib/MarkdownViewer.svelte b/src/lib/MarkdownViewer.svelte index d4e3de2..cf02e95 100644 --- a/src/lib/MarkdownViewer.svelte +++ b/src/lib/MarkdownViewer.svelte @@ -1311,67 +1311,26 @@ import { t } from './utils/i18n.js'; return true; } - async function toggleEdit(silentSave = false) { + async function toggleEdit() { const tab = tabManager.activeTab; if (!tab || tab.path === undefined) return; if (isEditing) { - // Switch back to view - if (tab.isDirty && tab.path !== '') { - // `confirmBeforeSave` always wins: when the user has asked - // for confirmation, every dirty toggle must show the modal, - // even if the caller passed `silentSave=true` (hotkey path). - const shouldSilent = - !settings.confirmBeforeSave && (silentSave || settings.autoSave); - if (shouldSilent) { - cancelPendingAutoSave(tab.id); - const success = await saveContent(tab.id); - if (!success) { - addToast(t('toast.autoSaveFailed', settings.language), 'error'); - return; // If save fails, stay in edit mode - } - } else { - const response = await askCustom(t('modal.youHaveUnsavedChangesBeforeReturning', settings.language), { - title: t('modal.unsavedChanges', settings.language), - kind: 'warning', - showSave: true, - }); - - // Cancel only happens on save / discard. If user picks - // Cancel, the pending auto-save timer keeps running. - if (response === 'cancel') return; - if (response === 'save') { - cancelPendingAutoSave(tab.id); - const success = await saveContent(tab.id); - if (!success) return; - } else if (response === 'discard') { - cancelPendingAutoSave(tab.id); - tab.rawContent = tab.originalContent; - tab.isDirty = false; - } - } - } - // If `saveContent` left `tab.isDirty=true` (TOCTOU — user typed - // during the await), staying in edit mode is the safe default: - // a non-editable dirty tab disables auto-save, blocks Cmd+S, - // and risks getting clobbered by the next disk reload. Surface - // a hint and keep the tab editable. - if (tab.path !== '' && tab.isDirty) { - addToast(t('toast.savedNewerEdits', settings.language), 'info'); - return; - } - + // Switch to view — never auto-save; render in-memory content so + // unsaved edits are visible and the file stays dirty until the + // user explicitly saves with Ctrl+S. tab.isEditing = false; - if (tab.path !== '') { + if (tab.path !== '' && !tab.isDirty) { + // Clean saved file: load from disk (handles large-file chunking). await loadMarkdown(tab.path, { preserveEditState: true }); } else { - // Untitled: render the in-memory buffer for the preview. + // Dirty or untitled: render the current in-memory buffer. try { const html = (await invoke('render_markdown', { content: tab.rawContent })) as string; - const processedInfo = processMarkdownHtml(html, '', collapsedHeaders); + const processedInfo = processMarkdownHtml(html, tab.path, collapsedHeaders); tabManager.updateTabContent(tab.id, processedInfo); } catch (e) { - console.error('Failed to render markdown', e); + console.error('Failed to render markdown for preview', e); } } } else { @@ -2051,13 +2010,11 @@ import { t } from './utils/i18n.js'; } if (cmdOrCtrl && key === 'e') { e.preventDefault(); - if (!isSplit) toggleEdit(true); + if (!isSplit) toggleEdit(); } if (cmdOrCtrl && key === 's') { - if (isEditing || isSplit) { - e.preventDefault(); - saveContent(); - } + e.preventDefault(); + saveContent(); } if (cmdOrCtrl && e.shiftKey && key === 't') { @@ -2395,16 +2352,12 @@ import { t } from './utils/i18n.js'; ); // Native macOS menubar — Markpad ▸ Quit and File ▸ * — bridged // to the same handlers the in-window burger button uses, so the - // menu and the burger stay behaviourally identical. Save mirrors - // the keydown guard (`isEditing || isSplit`) so menu ⌘S in pure - // view mode is a no-op, matching the keyboard shortcut. + // menu and the burger stay behaviourally identical. unlisteners.push(await listen('menu-app-quit', () => appExit())); unlisteners.push(await listen('menu-file-new', () => handleNewFile())); unlisteners.push(await listen('menu-file-open', () => selectFile())); unlisteners.push(await listen('menu-file-close', () => closeFile())); - unlisteners.push(await listen('menu-file-save', () => { - if (isEditing || tabManager.activeTab?.isSplit) saveContent(); - })); + unlisteners.push(await listen('menu-file-save', () => saveContent())); unlisteners.push(await listen('menu-file-save-as', () => saveContentAs())); unlisteners.push(await listen('menu-file-export-html', () => exportAsHtml())); unlisteners.push(await listen('menu-file-export-pdf', () => exportAsPdf())); diff --git a/src/lib/components/HomePage.svelte b/src/lib/components/HomePage.svelte index 0f043fd..a198b35 100644 --- a/src/lib/components/HomePage.svelte +++ b/src/lib/components/HomePage.svelte @@ -58,6 +58,7 @@ + {#if settings.showRecentFiles}

{t('home.recentFiles', settings.language)}

{#if recentFiles.length > 0} @@ -108,6 +109,7 @@

{t('home.noRecentFiles', settings.language)}

{/if}
+ {/if}
v{version}
diff --git a/src/lib/components/Settings.svelte b/src/lib/components/Settings.svelte index b3ecf59..3b6a2ae 100644 --- a/src/lib/components/Settings.svelte +++ b/src/lib/components/Settings.svelte @@ -654,6 +654,13 @@ +
+ + +