From ee217610ba929708fcc0ea5f6ac74cf4f2145d38 Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Sat, 13 Jun 2026 15:20:24 +0200 Subject: [PATCH] feat: header/footer --- CHANGELOG.md | 1 + src/App.svelte | 56 +++- src/lib/editor/Editor.svelte | 37 ++- src/lib/editor/HeaderFooterLayer.svelte | 332 ++++++++++++++++++++++++ src/lib/editor/ToolbarExpanded.svelte | 47 +++- src/lib/editor/hfExtensions.ts | 54 ++++ src/lib/editor/pageField.ts | 26 ++ src/lib/export/odt.ts | 180 ++++++++++--- src/lib/import/odt.ts | 69 ++++- src/lib/import/styleResolver.ts | 86 ++++-- src/lib/storage/headerFooter.ts | 39 +++ 11 files changed, 856 insertions(+), 71 deletions(-) create mode 100644 src/lib/editor/HeaderFooterLayer.svelte create mode 100644 src/lib/editor/hfExtensions.ts create mode 100644 src/lib/editor/pageField.ts create mode 100644 src/lib/storage/headerFooter.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 690fbe4..1a864dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - tables - undo history - ODT import / open existing file +- header/footer - ... ### Not yet implemented diff --git a/src/App.svelte b/src/App.svelte index ea60d92..9e73a5a 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -10,12 +10,24 @@ import { loadTheme, saveTheme, applyTheme, loadToolbarExpanded, saveToolbarExpanded, loadFormattingMarks, saveFormattingMarks, type ThemeMode } from './lib/storage/theme'; import { loadPageMargins, savePageMargins, type PageMargins } from './lib/storage/pageMargins'; import { loadOrientation, saveOrientation, type Orientation } from './lib/storage/pageOrientation'; + import { loadHfDoc, saveHfDoc, type HfDoc, type HfZone } from './lib/storage/headerFooter'; let editor: Editor | null = $state(null); let tick: number = $state(0); let currentPage: number = $state(1); let numPages: number = $state(1); + // Header/footer content + live-edit state. While a zone is being edited, the + // top toolbars target hfEditor instead of the body editor (activeEditor below). + let headerDoc: HfDoc = $state(loadHfDoc('header')); + let footerDoc: HfDoc = $state(loadHfDoc('footer')); + let hfEditor: Editor | null = $state(null); + let hfActive: HfZone | null = $state(null); + let hfTick: number = $state(0); + + let activeEditor = $derived(hfActive ? hfEditor : editor); + let activeTick = $derived(hfActive ? hfTick : tick); + let themeMode: ThemeMode = $state(loadTheme()); let themeOpen = $state(false); let toolbarExpanded = $state(loadToolbarExpanded()); @@ -36,6 +48,14 @@ saveOrientation(pageOrientation); }); + $effect(() => { + saveHfDoc('header', headerDoc); + }); + + $effect(() => { + saveHfDoc('footer', footerDoc); + }); + function setZoom(value: number) { zoom = Math.max(20, Math.min(300, value)); localStorage.setItem('odf-editor-zoom', String(zoom)); @@ -62,7 +82,9 @@ } async function handleExport() { - if (editor) await exportToOdt(editor, pageMargins, pageOrientation); + if (editor) { + await exportToOdt(editor, pageMargins, pageOrientation, { header: headerDoc, footer: footerDoc, pageCount: numPages }); + } } let fileInput: HTMLInputElement | null = $state(null); @@ -86,6 +108,10 @@ // Editor.svelte re-paginates. if (result.margins) pageMargins = result.margins; if (result.orientation) pageOrientation = result.orientation; + // Adopt header/footer (null clears the zone); end any active edit. + hfActive = null; + headerDoc = result.header; + footerDoc = result.footer; if (result.warnings.length) { console.warn('[import] Unsupported content in opened file:', result.warnings); @@ -123,7 +149,7 @@
- +
{#if toolbarExpanded} - + (hfActive = zone)} + /> {/if} - +