fix: a document that hung the editor is offered again before it is parked
A reload that cut a slow start short looked like a freeze, and the parked document was reachable only through the browser's storage tools. The user is asked for another try; only declining parks it.
This commit is contained in:
1 parent
bc8f8d89d1
commit
0435fdf285
5 files changed
+17
-7
No files matched your search
@@ -1095,7 +1095,7 @@ const de: Messages = {
|
||||
autosaveQuota:
|
||||
'Das Dokument ist zu groß, um automatisch gespeichert zu werden (Speicherlimit des Browsers erreicht). Das passiert meist bei großen eingebetteten Bildern. Speichere es als .odt-Datei, um deine Arbeit zu sichern.',
|
||||
documentNotLoaded:
|
||||
'Das zuletzt bearbeitete Dokument hat den Start des Editors blockiert und wurde deshalb nicht geladen. Du arbeitest in einem neuen, leeren Dokument; das alte bleibt im Browser-Speicher unter „edentext-doc-broken" erhalten.',
|
||||
'Das zuletzt bearbeitete Dokument hat den Start des Editors blockiert. Trotzdem noch einmal laden?\n\nAbbrechen startet mit einem neuen, leeren Dokument; das alte bleibt im Browser-Speicher unter „edentext-doc-broken“ erhalten.',
|
||||
picturesNotRestored: (n) =>
|
||||
`${n} ${n === 1 ? 'Bild konnte' : 'Bilder konnten'} nicht aus dem Browser-Speicher wiederhergestellt werden und ${n === 1 ? 'wird' : 'werden'} als defekt angezeigt. Öffne die gespeicherte Datei des Dokuments, um ${n === 1 ? 'es' : 'sie'} zurückzuholen.`,
|
||||
},
|
||||
|
||||
@@ -1101,7 +1101,7 @@ const en = {
|
||||
autosaveQuota:
|
||||
'The document is too large to save automatically (browser storage limit reached). This usually happens with large embedded images. Save it as an .odt file to keep your work.',
|
||||
documentNotLoaded:
|
||||
'The last document stopped the editor from starting, so it was not loaded. You are working in a new, empty document; the old one is kept in the browser storage under "edentext-doc-broken".',
|
||||
'The last document stopped the editor from starting. Load it again anyway?\n\nCancel starts a new, empty document; the old one is kept in the browser storage under "edentext-doc-broken".',
|
||||
picturesNotRestored: (n: number) =>
|
||||
`${n} picture${n === 1 ? '' : 's'} could not be restored from the browser storage and ${n === 1 ? 'is' : 'are'} shown as broken. Open the document's saved file to get ${n === 1 ? 'it' : 'them'} back.`,
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@ Both margins and orientation are passed into the ODT export so the exported docu
|
||||
|
||||
All keys share the `edentext-` prefix — a namespace, not a description: on GitHub Pages every repo of an account shares one origin, so a generic prefix would collide.
|
||||
|
||||
- **Document:** `edentext-doc` — TipTap JSON, debounced 1 s on every `onUpdate` (`autosave.ts`); hiding the page (`visibilitychange`/`pagehide`) writes what is pending at once, and writes queue behind each other so a slow picture round trip never stores an older document over a newer one. Loading it raises `edentext-doc-loading`, which only a completed startup clears (`markDocumentLoaded`, two frames after `Editor.svelte`'s mount). Still set at the next load = that document hung or crashed the editor: it moves to `edentext-doc-broken` and the app starts empty, so a reload escapes the freeze instead of repeating it. **Pictures do not live in it:** `imageStore.ts` swaps every data-URI past 4 KB for an `idb:<key>` before the write and keeps the bytes in the `edentext-images` IndexedDB store (a handful of photos is the whole localStorage quota). The key is a content hash of the data-URI's head, tail and length — sampled, because this runs on every autosave — so a picture used twice is stored once, and each save sweeps the keys the document no longer names. Where IndexedDB is unavailable the data-URIs stay inline, exactly as before; a key the store has lost keeps its `idb:` src and is reported, rather than quietly dropping the reader's content. `loadDocument` is therefore **async**, and `Editor.svelte` awaits it before building the editor.
|
||||
- **Document:** `edentext-doc` — TipTap JSON, debounced 1 s on every `onUpdate` (`autosave.ts`); hiding the page (`visibilitychange`/`pagehide`) writes what is pending at once, and writes queue behind each other so a slow picture round trip never stores an older document over a newer one. Loading it raises `edentext-doc-loading`, which only a completed startup clears (`markDocumentLoaded`, two frames after `Editor.svelte`'s mount). Still set at the next load = that document hung or crashed the editor, or a reload cut its start short: the user is asked whether to try again; declined, it moves to `edentext-doc-broken` and the app starts empty, so a freeze is escaped instead of repeated. **Pictures do not live in it:** `imageStore.ts` swaps every data-URI past 4 KB for an `idb:<key>` before the write and keeps the bytes in the `edentext-images` IndexedDB store (a handful of photos is the whole localStorage quota). The key is a content hash of the data-URI's head, tail and length — sampled, because this runs on every autosave — so a picture used twice is stored once, and each save sweeps the keys the document no longer names. Where IndexedDB is unavailable the data-URIs stay inline, exactly as before; a key the store has lost keeps its `idb:` src and is reported, rather than quietly dropping the reader's content. `loadDocument` is therefore **async**, and `Editor.svelte` awaits it before building the editor.
|
||||
- **Recent files:** `edentext-recent-files` — `{id, name, at}` per entry, newest first, eight kept (`recentFiles.ts`). Only the label lives here: the `FileSystemFileHandle` that reopens one is structured-cloneable but not JSON, so it sits in the `edentext-recent` IndexedDB store under the same id, and each write sweeps the handles no entry names. Two handles compare only through `isSameEntry`, which is async — hence the walk when recording a file already in the list. A file without a handle is never recorded (nothing could reopen it — no File System Access API, or a template), so the list simply stays empty in Firefox/Safari/Brave; a startup prune drops entries whose handle is gone. Reopening asks for `read` permission again (the API drops it on reload) — a declined prompt keeps the entry for the next try, only a moved or deleted file drops it (with a message). Save re-prompts for write on its own (Save-As fallback in `App.svelte`).
|
||||
- **Document format:** `edentext-doc-format` — `'docx'` while the open document round-trips as .docx; absent = .odt (`documentName.ts`).
|
||||
- **Theme:** `edentext-theme` — `'light' | 'dark' | 'allBlack' | 'auto'`.
|
||||
|
||||
@@ -72,13 +72,13 @@ export async function loadDocument(): Promise<object | null> {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (localStorage.getItem(BOOT_KEY)) {
|
||||
localStorage.removeItem(BOOT_KEY);
|
||||
if (raw) {
|
||||
// The user decides: a reload that cut a slow start short is the usual cause, and
|
||||
// another try costs nothing a reload would not fix. Given up, the document is parked.
|
||||
if (raw && !confirm(t().dialogs.documentNotLoaded)) {
|
||||
localStorage.setItem(BROKEN_KEY, raw);
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
// After paint, so the message doesn't land on a blank app.
|
||||
requestAnimationFrame(() => alert(t().dialogs.documentNotLoaded));
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!raw) return null;
|
||||
let doc: object;
|
||||
|
||||
@@ -9,6 +9,7 @@ describe('autosave crash recovery', () => {
|
||||
localStorage.clear();
|
||||
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => { void cb; return 0; });
|
||||
vi.stubGlobal('alert', () => {});
|
||||
vi.stubGlobal('confirm', () => false);
|
||||
});
|
||||
|
||||
it('loads the document again after a startup that completed', async () => {
|
||||
@@ -26,6 +27,15 @@ describe('autosave crash recovery', () => {
|
||||
expect(localStorage.getItem('edentext-doc')).toBeNull();
|
||||
});
|
||||
|
||||
it('loads the document again when the user asks for another try', async () => {
|
||||
vi.stubGlobal('confirm', () => true);
|
||||
localStorage.setItem('edentext-doc', '{"type":"doc"}');
|
||||
await loadDocument();
|
||||
expect(await loadDocument()).toEqual({ type: 'doc' });
|
||||
expect(localStorage.getItem('edentext-doc-broken')).toBeNull();
|
||||
expect(localStorage.getItem('edentext-doc-loading')).toBe('1');
|
||||
});
|
||||
|
||||
it('keeps the next document after a recovery', async () => {
|
||||
localStorage.setItem('edentext-doc', '{"type":"doc","content":[]}');
|
||||
await loadDocument();
|
||||
|
||||
Reference in new issue
Block a user