diff --git a/README.md b/README.md index 3ef187a..922aff5 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ EdenText is a web-based, powerful word processor for everything from quick notes > EdenText is young, in **beta** and actively developed — more features are on > the way. It is tested — the full suite plus LibreOffice round-trip checks run > on every commit — but expect occasional bugs, and keep backups of documents -> you care about. +> you care about. The browser copy also keeps the last three versions of the open +> document — File ▸ Earlier versions puts one back. [![EdenText editing a multi-column document](docs/screenshot.png)](https://edentext.app) Sample File from https://sample-files.com/ diff --git a/src/App.svelte b/src/App.svelte index 4a63c32..9f3beb5 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -46,6 +46,7 @@ import { loadLineNumbering, saveLineNumbering, DEFAULT_LINE_NUMBERING, type LineNumbering } from './lib/storage/lineNumbering'; import { loadFoldMarks, saveFoldMarks } from './lib/storage/foldMarks'; import { printMarkup } from './lib/storage/printMarkup.svelte'; + import { snapshots, loadSnapshots, readSnapshot } from './lib/storage/snapshots.svelte'; import { commentsInPane, changesInPane, markupAttrs, setShowChanges, setShowComments } from './lib/storage/markup.svelte'; import { loadDocumentLanguage, saveDocumentLanguage, odfFromLanguage, type DocumentLanguage } from './lib/storage/documentLanguage'; import { setTableLanguage } from './lib/storage/tableOptions.svelte'; @@ -68,7 +69,7 @@ import { OPEN_THESAURUS_EVENT } from './lib/spell/thesaurus'; import StyleManagerDialog from './lib/components/StyleManagerDialog.svelte'; import NoteOptionsDialog from './lib/components/NoteOptionsDialog.svelte'; - import { t } from './lib/i18n/i18n.svelte'; + import { t, locale } from './lib/i18n/i18n.svelte'; import { withShortcut } from './lib/i18n/shortcut'; import { DEFAULT_SHORTCUTS, matchesEvent, shortcutHint } from './lib/editor/shortcuts'; import { OPEN_LINK_DIALOG_EVENT } from './lib/editor/extensions/link'; @@ -1040,6 +1041,29 @@ } } + // The versions the autosave keeps aside, and the reader's label for one. + void loadSnapshots(); + function snapshotLabel(at: number): string { + return new Date(at).toLocaleString(locale(), { dateStyle: 'short', timeStyle: 'short' }); + } + + // Put an earlier version's text back. The page setup, the styles and the zones + // belong to the open document, so only the text is replaced. + async function handleRestoreSnapshot(at: number) { + exportMenuOpen = false; + if (!editor || !confirm(t().dialogs.confirmRestore(snapshotLabel(at)))) return; + const json = await readSnapshot(at); + if (!json) { + alert(t().dialogs.snapshotGone); + void loadSnapshots(); + return; + } + loadContent(json as Content); + documentEpoch++; + resetHistory(); + editor.commands.focus(); + } + function handleForgetRecent() { forgetRecentFiles(); recentFiles = []; @@ -1317,6 +1341,8 @@ recentFiles={recentFiles} onOpenRecent={(id) => { const f = recentFiles.find((r) => r.id === id); if (f) void handleOpenRecent(f); }} onForgetRecent={handleForgetRecent} + snapshots={snapshots().map((at) => ({ at, label: snapshotLabel(at) }))} + onRestoreSnapshot={(at) => void handleRestoreSnapshot(at)} onExportPdf={handleExportPdf} onPrintPdf={handlePrintPdf} onPrint={handlePrint} @@ -1454,6 +1480,14 @@ {t().app.clearRecentFiles} {/if} + {#if snapshots().length} +
{t().app.versions}
+ {#each snapshots() as at (at)} + + {/each} + {/if} {/if} + {#if snapshots.length} +
+ {#each snapshots as s (s.at)} + + {/each} + {/if}