diff --git a/src/App.svelte b/src/App.svelte index 9feb32e..3dbb4a5 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -39,7 +39,7 @@ import { DEFAULT_NOTE_SETTINGS } from './lib/storage/noteSettings'; import { builtinStyleSheet, type StyleFamily } from './lib/styles/styleSheet'; import { loadHfDoc, saveHfDoc, loadHfDistances, saveHfDistances, loadDifferentFirstPage, saveDifferentFirstPage, loadDifferentOddEven, saveDifferentOddEven, hfIsEmpty, DEFAULT_HF_DISTANCES, loadExtraHfSections, saveExtraHfSections, type HfDoc, type HfZone, type HfDistances, type HfSet } from './lib/storage/headerFooter'; - import { loadDocName, saveDocName, stripOdtExtension, sanitizeNameForFile } from './lib/storage/documentName'; + import { loadDocName, saveDocName, loadDocFormat, saveDocFormat, stripOdtExtension, sanitizeNameForFile, type DocumentFormat } from './lib/storage/documentName'; import { loadDocProperties, saveDocProperties, EMPTY_DOC_PROPERTIES, type DocProperties } from './lib/storage/docProperties'; import { loadHyphenation, saveHyphenation } from './lib/storage/hyphenation'; import { loadPageNumbering, savePageNumbering, DEFAULT_PAGE_NUMBERING, type PageNumbering } from './lib/storage/pageNumbering'; @@ -184,7 +184,7 @@ let documentName: string = $state(loadDocName()); // The loaded file's format; drives the title's extension label and which // exporter "Save" round-trips through (odt-opened saves odt, docx-opened saves docx). - let documentFormat: 'odt' | 'docx' = $state('odt'); + let documentFormat: DocumentFormat = $state(loadDocFormat()); let docProps: DocProperties = $state(loadDocProperties()); let docPropsOpen = $state(false); let hyphenate = $state(loadHyphenation()); @@ -331,6 +331,7 @@ $effect(() => { saveDocName(documentName); + saveDocFormat(documentFormat); }); function setZoom(value: number) { diff --git a/src/lib/storage/CLAUDE.md b/src/lib/storage/CLAUDE.md index 92fe3d4..b382de9 100644 --- a/src/lib/storage/CLAUDE.md +++ b/src/lib/storage/CLAUDE.md @@ -40,6 +40,7 @@ All keys share the `edentext-` prefix — a namespace, not a description: on Git - **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:` 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'`. - **Toolbar expanded:** `edentext-toolbar-expanded` — boolean string. - **Chrome:** `edentext-chrome` — `'modern' | 'ribbon'` ('classic' read as the modern island's legacy stored name); absent = ribbon. diff --git a/src/lib/storage/documentName.ts b/src/lib/storage/documentName.ts index 66d65c8..9494d1f 100644 --- a/src/lib/storage/documentName.ts +++ b/src/lib/storage/documentName.ts @@ -22,3 +22,18 @@ export function sanitizeNameForFile(name: string): string { // eslint-disable-next-line no-control-regex return name.replace(/[<>:"/\\|?*\x00-\x1f]/g, '').trim(); } + +export type DocumentFormat = 'odt' | 'docx'; + +// The format the open document round-trips in. Absent at the .odt default, so only a +// document that came in as .docx writes anything. +const FORMAT_KEY = 'edentext-doc-format'; + +export function loadDocFormat(): DocumentFormat { + return localStorage.getItem(FORMAT_KEY) === 'docx' ? 'docx' : 'odt'; +} + +export function saveDocFormat(format: DocumentFormat): void { + if (format === 'docx') localStorage.setItem(FORMAT_KEY, format); + else localStorage.removeItem(FORMAT_KEY); +}