refactor: the project is called edentext

Renames the product in the docs, the page title and the DOCX creator, and moves
the whole storage namespace from odf-editor- to edentext-. The prefix is a
per-origin namespace, so it stays the product name rather than a generic one.
This commit is contained in:
Steffen Becker committed 2026-08-13 21:43:12 +02:00
1 parent 1018380f88
commit 0da75ed456
37 files changed
+76 -74

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Contributor License Agreement (CLA)
**Project:** Web ODF Editor
**Project:** EdenText
**Maintainer / Licensor:** Steffen Becker <your-contact-email> (the "Maintainer")
> ⚠️ **Note:** This is a plain-language template adapted from common individual CLAs
+1 -1
View File
@@ -1,4 +1,4 @@
# Contributing to Web ODF Editor
# Contributing to EdenText
Thanks for your interest in contributing! This document explains how to get set up
and the one legal requirement for getting a change merged.
+1 -1
View File
@@ -1,6 +1,6 @@
# Commercial License
Web ODF Editor is **dual-licensed**.
EdenText is **dual-licensed**.
## 1. Open-source license (default)
+1 -1
View File
@@ -1,6 +1,6 @@
# Third-Party Notices
Web ODF Editor as a whole is licensed under the GNU Affero General Public License
EdenText as a whole is licensed under the GNU Affero General Public License
v3.0 (see [`LICENSE`](./LICENSE)), with a separate commercial license available
(see [`LICENSE.commercial.md`](./LICENSE.commercial.md)).
+2 -2
View File
@@ -7,7 +7,7 @@ compared. Everything the ribbon drives is the same editor, through the same comm
## The mode switch
`chromeMode: 'classic' | 'ribbon'` lives in `storage/theme.ts` beside the other UI-chrome prefs
(`odf-editor-chrome`, default `'classic'`). `App.svelte` forks on it: the ribbon docks as a plain
(`edentext-chrome`, default `'classic'`). `App.svelte` forks on it: the ribbon docks as a plain
flex child, the island keeps its absolute overlay. Reachable from both sides — the theme dropdown
in classic, the strip's appearance menu in the ribbon.
@@ -58,7 +58,7 @@ group footer and the band's padding, so no tab carries dead space above its butt
feeding that sum is pinned in px, line-heights included: a ratio hands the band's height to
whatever font resolves, and the group footers then hang out of the band.
A chevron in the tab strip drops the band entirely, leaving the strip (`odf-editor-ribbon-collapsed`).
A chevron in the tab strip drops the band entirely, leaving the strip (`edentext-ribbon-collapsed`).
Clicking any tab brings it back — Word instead floats it over the document until the next click
lands in the text, which is the part not built.
+1 -1
View File
@@ -5,6 +5,6 @@ With no test suite, the way to verify rendering, layout, or interaction is to dr
- **Don't** use `puppeteer` / `npx @puppeteer/browsers install chrome-headless-shell`: they fetch an x86-64 Chrome that can't run here (`rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2`). No arm64 chrome-headless-shell build exists.
- **Use Playwright's Chromium** (native arm64): `npm install --no-save playwright-core && npx playwright-core install chromium` → binary at `~/.cache/ms-playwright/chromium-*/chrome-linux/chrome`. Playwright's `install-deps` host check errors out, but the libs install via apt: `apt-get install -y libdbus-1-3 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 libatspi2.0-0 libpango-1.0-0 libcairo2`. Launch with playwright-core, `executablePath` at that binary, `args: ['--no-sandbox']`.
Then load `npm run dev`, inject a document into `localStorage['odf-editor-doc']`, reload, and read live DOM geometry (`getBoundingClientRect`, `Range.getClientRects`) or screenshot. This is the only way to exercise the editor's live ProseMirror NodeViews (e.g. the image node), which `generateHTML` can't reproduce. The `debug/pagebreak-debug-*.json` snapshots carry the live `doc` JSON, handy to inject.
Then load `npm run dev`, inject a document into `localStorage['edentext-doc']`, reload, and read live DOM geometry (`getBoundingClientRect`, `Range.getClientRects`) or screenshot. This is the only way to exercise the editor's live ProseMirror NodeViews (e.g. the image node), which `generateHTML` can't reproduce. The `debug/pagebreak-debug-*.json` snapshots carry the live `doc` JSON, handy to inject.
For PDF-export repros specifically: replicate `pdf.ts`'s clone + `html2canvas(...)` inside `page.evaluate` and read the canvas back as a PNG — capturing the real jsPDF `doc.save()` download tends to hang in headless. Inspect output PDFs with poppler-utils (`apt-get install -y poppler-utils`: `pdftoppm`, `pdfimages`, `pdftotext`).
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ODF Editor</title>
<title>EdenText</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
</head>
<body>
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "web-odf-editor",
"name": "edentext",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "web-odf-editor",
"name": "edentext",
"version": "0.1.0",
"license": "AGPL-3.0-only",
"dependencies": {
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "web-odf-editor",
"name": "edentext",
"version": "0.1.0",
"type": "module",
"description": "A fully client-side, serverless rich-text editor that saves documents as .odt",
+2 -2
View File
@@ -127,7 +127,7 @@
let chromeMode: ChromeMode = $state(loadChromeMode());
let showFormattingMarks = $state(loadFormattingMarks());
let showRuler = $state(loadRuler());
let zoom = $state(clampZoom(parseInt(localStorage.getItem('odf-editor-zoom') ?? '100', 10)));
let zoom = $state(clampZoom(parseInt(localStorage.getItem('edentext-zoom') ?? '100', 10)));
let pageMargins: PageMargins = $state(loadPageMargins());
let pageOrientation: Orientation = $state(loadOrientation());
let pageFormat: PageFormat = $state(loadPageFormat());
@@ -262,7 +262,7 @@
function setZoom(value: number) {
zoom = clampZoom(value);
localStorage.setItem('odf-editor-zoom', String(zoom));
localStorage.setItem('edentext-zoom', String(zoom));
}
function selectTheme(m: ThemeMode) {
+3 -3
View File
@@ -30,7 +30,7 @@ App.svelte – owns app-level state (theme, zoom, margins, or
│ │ markers (first line, left and right, via setIndent/setIndentRight/setIndentFirst). Sticky
│ │ inside .editor, so it clears the toolbar island and scrolls
│ │ horizontally with the page; positions are scaled by zoom while the
│ │ chrome keeps its size. Toggled in ToolbarExpanded (odf-editor-ruler).
│ │ chrome keeps its size. Toggled in ToolbarExpanded (edentext-ruler).
│ └─ TableToolbar.svelte – floating row/column/table actions, shown above the active table
└─ footer statusbar – "Page X of Y" + zoom controls (20–300%)
```
@@ -50,7 +50,7 @@ at most `SETTLE_SAMPLES` blocks: reading every one each frame starves the layout
## Zoom (`Editor.svelte`)
Zoom is a CSS `transform: scale()` on `.paper` (layout and pagination always run at 100%, so they stay stable across zoom — this replaced an earlier CSS `zoom` approach that re-ran layout at every scale). A transform reserves no layout space, so `.paper-scaler` reserves the scaled footprint to drive scrollbars and horizontal centering — it's sized in the `$effect.pre`, so it reaches the DOM in the same flush as the transform and the anchor pass below measures the right geometry. The applied zoom is throttled to one DOM write per animation frame. Range 20–300% (`MIN_ZOOM`/`MAX_ZOOM`/`clampZoom` in `utils/zoom.ts`), persisted in `localStorage['odf-editor-zoom']`.
Zoom is a CSS `transform: scale()` on `.paper` (layout and pagination always run at 100%, so they stay stable across zoom — this replaced an earlier CSS `zoom` approach that re-ran layout at every scale). A transform reserves no layout space, so `.paper-scaler` reserves the scaled footprint to drive scrollbars and horizontal centering — it's sized in the `$effect.pre`, so it reaches the DOM in the same flush as the transform and the anchor pass below measures the right geometry. The applied zoom is throttled to one DOM write per animation frame. Range 20–300% (`MIN_ZOOM`/`MAX_ZOOM`/`clampZoom` in `utils/zoom.ts`), persisted in `localStorage['edentext-zoom']`.
`zoom` lives in `App.svelte`; `setZoom` is the only writer (clamps + persists) and reaches `Editor.svelte` as the `onZoom` prop, so a gesture there routes back through it and the status-bar slider follows for free. Inputs beyond the slider:
@@ -61,7 +61,7 @@ Both anchors differ: a wheel zoom holds the **point under the cursor** fixed (`p
## Headers & footers (`HeaderFooterLayer.svelte`, `storage/headerFooter.ts`)
One header and one footer (`HfDoc` = a single-paragraph TipTap doc per zone, persisted to `odf-editor-header`/`-footer`), repeated on every page. Double-clicking a page margin — or the Layout-panel "Edit header/footer" buttons — opens the zone for editing; clicking back into the body (`onFocus`) or Escape ends it. The edge→zone distance (header from top, footer from bottom) is user-configurable in cm via the Layout panel (`HfDistances`, persisted to `odf-editor-hf-distances`, default `HF_DISTANCE_CM` 1.27cm, clamped 0–10 and below the body margin).
One header and one footer (`HfDoc` = a single-paragraph TipTap doc per zone, persisted to `edentext-header`/`-footer`), repeated on every page. Double-clicking a page margin — or the Layout-panel "Edit header/footer" buttons — opens the zone for editing; clicking back into the body (`onFocus`) or Escape ends it. The edge→zone distance (header from top, footer from bottom) is user-configurable in cm via the Layout panel (`HfDistances`, persisted to `edentext-hf-distances`, default `HF_DISTANCE_CM` 1.27cm, clamped 0–10 and below the body margin).
- **Fields:** the bar beside the active zone inserts the `pageField.ts` atoms — page number, page count, chapter (the running head, level 1). Each is patched per page, so the same zone shows a different value on every page.
- **Rendering:** the zone's own font is the document's **default paragraph style** (`styleCss` gives `.paper .hf-layer .hf-zone` its text half): both formats base the Header/Footer style on it, so a file whose body is Arial 10pt has an Arial footer, not the editor's serif. `HeaderFooterLayer.svelte` mounts inside the scaled `.paper` (like `.band-layer`), positioning per-page zone boxes in unscaled doc px. A zone's out-of-flow frame (`wrap` ≠ inline) is hidden in the box and painted per page by `.hf-bg-layer` at `z-index: -1` — hence `.paper`, not `.tiptap`, paints the page surface, so the image lands between the sheet and the body text. Inactive zones render as static HTML (`generateHTML` + `hfExtensions`); the active zone hosts the single live TipTap editor. `App.svelte`'s `activeEditor`/`activeTick` route the top toolbars to `hfEditor` while a zone is active, so all body formatting works on the header/footer with no toolbar changes.
+1 -1
View File
@@ -22,7 +22,7 @@
onInsert: (char: string, range: { from: number; to: number }) => void;
} = $props();
const RECENT_CHARS_KEY = 'odf-editor-recent-chars';
const RECENT_CHARS_KEY = 'edentext-recent-chars';
const MAX_RECENT = 12;
// De-duplicated flat index across all categories, for search and name lookup.
+1 -1
View File
@@ -45,7 +45,7 @@
];
const WEB_SAFE_SET = new Set<string>(WEB_SAFE_FONTS);
const RECENT_FONTS_KEY = 'odf-editor-recent-fonts';
const RECENT_FONTS_KEY = 'edentext-recent-fonts';
const MAX_RECENT_FONTS = 5;
let recentFonts = $state<string[]>([]);
+1 -1
View File
@@ -8,7 +8,7 @@ export const WEB_SAFE_FONTS: readonly string[] = [
];
const WEB_SAFE_SET = new Set<string>(WEB_SAFE_FONTS);
const RECENT_KEY = 'odf-editor-recent-fonts';
const RECENT_KEY = 'edentext-recent-fonts';
const MAX_RECENT = 5;
let recents = $state<string[]>(load());
+1 -1
View File
@@ -1381,7 +1381,7 @@ export async function buildDocx(
};
const doc = new Document({
creator: 'Web ODF Editor',
creator: 'EdenText',
defaultTabStop: cmToTwip(tabIntervalCm),
...(differentOddEven ? { evenAndOddHeaderAndFooters: true } : {}),
...(hasToc ? { features: { updateFields: true } } : {}),
+1 -1
View File
@@ -656,7 +656,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 „odf-editor-doc-broken" erhalten.',
'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.',
},
importWarn: {
imagesSkipped: 'Einige Bilder konnten nicht gelesen werden und wurden übersprungen',
+1 -1
View File
@@ -660,7 +660,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 "odf-editor-doc-broken".',
'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".',
},
// Values mirror the canonical English messages the import parsers emit; the UI
// maps a parser message to its localized form at display time (importMessages.ts).
+1 -1
View File
@@ -1,7 +1,7 @@
import { loadChecker, type Checker } from './dictionary';
import { loadDocumentLanguage, type DocumentLanguage } from '../storage/documentLanguage';
const PERSONAL_KEY = 'odf-editor-user-dictionary';
const PERSONAL_KEY = 'edentext-user-dictionary';
function loadPersonal(): string[] {
try {
+19 -17
View File
@@ -10,29 +10,31 @@
(`AddParaTableSpacing`), always `'max'` on DOCX import, and written back on ODF export.
`editor.css` turns `--space-before` into padding or margin accordingly (CSS margins can
only collapse, so `'add'` cannot be a margin).
- **`writingMode.ts`** — the page's text direction (`loadPageRtl`/`savePageRtl`, `odf-editor-page-rtl`), from ODF `style:writing-mode="rl-tb"` or Word's section-level `w:bidi`. `Editor.svelte` puts it on the body host as `dir`, which is what makes a multi-column page fill from the right; the header/footer band is left alone (LibreOffice lays those out unchanged — measured). The vertical modes are not read.
- **`writingMode.ts`** — the page's text direction (`loadPageRtl`/`savePageRtl`, `edentext-page-rtl`), from ODF `style:writing-mode="rl-tb"` or Word's section-level `w:bidi`. `Editor.svelte` puts it on the body host as `dir`, which is what makes a multi-column page fill from the right; the header/footer band is left alone (LibreOffice lays those out unchanged — measured). The vertical modes are not read.
- **`pageOrientation.ts`** — `'portrait' | 'landscape'`. `applyOrientationVars` sets `--user-page-{width,height}`; landscape swaps the A4 dimensions (matching odf-kit's automatic swap).
Both margins and orientation are passed into the ODT export so the exported document's geometry/line-wrapping matches the on-screen preview.
## localStorage keys
- **Document:** `odf-editor-doc` — TipTap JSON, debounced 1 s on every `onUpdate` (`autosave.ts`). Loading it raises `odf-editor-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 `odf-editor-doc-broken` and the app starts empty, so a reload escapes the freeze instead of repeating it.
- **Theme:** `odf-editor-theme` — `'light' | 'dark' | 'allBlack' | 'auto'`.
- **Toolbar expanded:** `odf-editor-toolbar-expanded` — boolean string.
- **Chrome:** `odf-editor-chrome` — `'classic' | 'ribbon'`; absent = classic.
- **Ribbon collapsed:** `odf-editor-ribbon-collapsed` — boolean string; absent = expanded.
- **Formatting marks:** `odf-editor-formatting-marks` — boolean string.
- **Ruler:** `odf-editor-ruler` — boolean string; absent = on.
- **Zoom:** `odf-editor-zoom` — integer percent.
- **Page margins:** `odf-editor-page-margins` — JSON cm values.
- **Page orientation:** `odf-editor-page-orientation` — `'portrait' | 'landscape'`.
- **Tab interval:** `odf-editor-tab-interval` — cm as a decimal string.
- **Spacing model:** `odf-editor-spacing-model` — `'add' | 'max'`.
- **Recent fonts:** `odf-editor-recent-fonts` — JSON string array (ToolbarExpanded).
- **Header/footer:** `odf-editor-header` / `-footer` (HfDoc), `odf-editor-hf-distances`.
- **Styles:** `odf-editor-styles` — the style registry (`styles/sheet.svelte.ts`).
- **Notes:** `odf-editor-notes` — footnote/endnote numbering and the separator (`noteSettings.ts`, reactive singleton in `notes.svelte.ts`); `applyNoteVars` puts the separator on `:root`, where `editor.css` draws it and `pageBreaks.ts` reserves its band.
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`). 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.
- **Theme:** `edentext-theme` — `'light' | 'dark' | 'allBlack' | 'auto'`.
- **Toolbar expanded:** `edentext-toolbar-expanded` — boolean string.
- **Chrome:** `edentext-chrome` — `'classic' | 'ribbon'`; absent = classic.
- **Ribbon collapsed:** `edentext-ribbon-collapsed` — boolean string; absent = expanded.
- **Formatting marks:** `edentext-formatting-marks` — boolean string.
- **Ruler:** `edentext-ruler` — boolean string; absent = on.
- **Zoom:** `edentext-zoom` — integer percent.
- **Page margins:** `edentext-page-margins` — JSON cm values.
- **Page orientation:** `edentext-page-orientation` — `'portrait' | 'landscape'`.
- **Tab interval:** `edentext-tab-interval` — cm as a decimal string.
- **Spacing model:** `edentext-spacing-model` — `'add' | 'max'`.
- **Recent fonts:** `edentext-recent-fonts` — JSON string array (ToolbarExpanded).
- **Header/footer:** `edentext-header` / `-footer` (HfDoc), `edentext-hf-distances`.
- **Styles:** `edentext-styles` — the style registry (`styles/sheet.svelte.ts`).
- **Notes:** `edentext-notes` — footnote/endnote numbering and the separator (`noteSettings.ts`, reactive singleton in `notes.svelte.ts`); `applyNoteVars` puts the separator on `:root`, where `editor.css` draws it and `pageBreaks.ts` reserves its band.
## Themes (`theme.ts`, `styles/global.css`)
+1 -1
View File
@@ -3,7 +3,7 @@
import { isLocale, resolveBrowserLocale, type Locale } from '../i18n/config';
const KEY = 'odf-editor-app-language';
const KEY = 'edentext-app-language';
export function loadAppLanguage(): Locale {
const saved = localStorage.getItem(KEY);
+3 -3
View File
@@ -1,13 +1,13 @@
import { t } from '../i18n/i18n.svelte';
const STORAGE_KEY = 'odf-editor-doc';
const STORAGE_KEY = 'edentext-doc';
// Set while a stored document is being handed to the editor, cleared once the editor
// has actually come up (markDocumentLoaded). Finding it still set at load time means
// the last attempt hung or threw — reloading would only freeze again.
const BOOT_KEY = 'odf-editor-doc-loading';
const BOOT_KEY = 'edentext-doc-loading';
// Where such a document is parked instead of being loaded, so nothing is lost and
// it can still be pulled out of localStorage.
const BROKEN_KEY = 'odf-editor-doc-broken';
const BROKEN_KEY = 'edentext-doc-broken';
const DEBOUNCE_MS = 1000;
let timeout: ReturnType<typeof setTimeout> | null = null;
+1 -1
View File
@@ -22,7 +22,7 @@ export const LANGUAGES: LanguageDef[] = [
export const DEFAULT_LANGUAGE: DocumentLanguage = 'en';
const KEY = 'odf-editor-doc-language';
const KEY = 'edentext-doc-language';
export function findLanguage(code: DocumentLanguage): LanguageDef | undefined {
return LANGUAGES.find((l) => l.code === code);
+1 -1
View File
@@ -1,7 +1,7 @@
// The user-visible document name (without the .odt extension). Drives the
// suggested filename on save; empty falls back to the heading-derived name.
const KEY = 'odf-editor-doc-name';
const KEY = 'edentext-doc-name';
export function loadDocName(): string {
return localStorage.getItem(KEY) ?? '';
+1 -1
View File
@@ -3,7 +3,7 @@ import type { EmbeddedFont } from '../fonts/embeddedFonts';
// The current document's embedded fonts, persisted in IndexedDB (not localStorage, whose
// ~5 MB quota is already tight for images). One record, replaced on each open, so storage
// stays bounded to the open document; on app start it is re-registered via FontFace.
const DB_NAME = 'odf-editor-fonts';
const DB_NAME = 'edentext-fonts';
const STORE = 'fonts';
const KEY = 'current';
+6 -6
View File
@@ -41,7 +41,7 @@ export function hfSetIsEmpty(s: HfSet): boolean {
// Sections past the first, in order. Persisted whole rather than per zone like
// section 1's, whose six docs each have their own key.
const EXTRA_KEY = 'odf-editor-hf-sections';
const EXTRA_KEY = 'edentext-hf-sections';
export function loadExtraHfSections(): HfSet[] {
const raw = localStorage.getItem(EXTRA_KEY);
@@ -60,14 +60,14 @@ export function saveExtraHfSections(sections: HfSet[]): void {
}
const KEYS: Record<HfZone, Record<HfVariant, string>> = {
header: { default: 'odf-editor-header', first: 'odf-editor-header-first', even: 'odf-editor-header-even' },
footer: { default: 'odf-editor-footer', first: 'odf-editor-footer-first', even: 'odf-editor-footer-even' },
header: { default: 'edentext-header', first: 'edentext-header-first', even: 'edentext-header-even' },
footer: { default: 'edentext-footer', first: 'edentext-footer-first', even: 'edentext-footer-even' },
};
// Whether page 1 uses its own header/footer (Word w:titlePg / ODF header-first).
const DIFFERENT_FIRST_KEY = 'odf-editor-hf-different-first';
const DIFFERENT_FIRST_KEY = 'edentext-hf-different-first';
// Whether even pages use their own header/footer (Word w:evenAndOddHeaders / ODF header-left).
const DIFFERENT_ODD_EVEN_KEY = 'odf-editor-hf-odd-even';
const DIFFERENT_ODD_EVEN_KEY = 'edentext-hf-odd-even';
export function loadDifferentFirstPage(): boolean {
return localStorage.getItem(DIFFERENT_FIRST_KEY) === 'true';
@@ -98,7 +98,7 @@ export type HfDistances = { header: number; footer: number };
export const DEFAULT_HF_DISTANCES: HfDistances = { header: HF_DISTANCE_CM, footer: HF_DISTANCE_CM };
const DIST_KEY = 'odf-editor-hf-distances';
const DIST_KEY = 'edentext-hf-distances';
const DIST_MIN = 0;
const DIST_MAX = 10;
+1 -1
View File
@@ -1,6 +1,6 @@
import type { OrderedTypeDef } from '../utils/orderedListTypes';
const KEY = 'odf-editor-notes';
const KEY = 'edentext-notes';
// How footnotes and endnotes are numbered and where they sit. Every default here was
// read out of a document LibreOffice saved (docs/architecture/notes.md) — an
+1 -1
View File
@@ -7,7 +7,7 @@ export type PageFormat =
| 'jisB4' | 'jisB5'
| 'letter' | 'legal' | 'tabloid' | 'executive' | 'folio' | 'statement';
const KEY = 'odf-editor-page-format';
const KEY = 'edentext-page-format';
// Portrait page dimensions in cm per format (width < height). Landscape swaps them.
// Grouped A / ISO-B / JIS-B / US, matching the Word/LibreOffice paper menus.
+1 -1
View File
@@ -4,7 +4,7 @@ export type PageMargins = {
top: number; bottom: number; left: number; right: number; mirrored?: boolean;
};
const KEY = 'odf-editor-page-margins';
const KEY = 'edentext-page-margins';
// LibreOffice Writer's default page margins (Word uses 2.54cm all round). Only a new
// document gets these — an imported one always adopts its own page geometry.
+1 -1
View File
@@ -1,6 +1,6 @@
export type Orientation = 'portrait' | 'landscape';
const KEY = 'odf-editor-page-orientation';
const KEY = 'edentext-page-orientation';
export function loadOrientation(): Orientation {
return localStorage.getItem(KEY) === 'landscape' ? 'landscape' : 'portrait';
+1 -1
View File
@@ -3,7 +3,7 @@
// only the larger of the two for a Word document ('max') — probed, and per document.
export type SpacingModel = 'add' | 'max';
const KEY = 'odf-editor-spacing-model';
const KEY = 'edentext-spacing-model';
export function loadSpacingModel(): SpacingModel {
return localStorage.getItem(KEY) === 'max' ? 'max' : 'add';
+1 -1
View File
@@ -1,4 +1,4 @@
const KEY = 'odf-editor-tab-interval';
const KEY = 'edentext-tab-interval';
// The grid every tab past the last custom stop falls on (Word w:defaultTabStop, ODF
// style:tab-stop-distance). LibreOffice's default for a new document.
+6 -6
View File
@@ -4,12 +4,12 @@ export type ThemeMode = 'light' | 'dark' | 'allBlack' | 'auto';
// Word-style ribbon. Both drive the same editor; only one is mounted at a time.
export type ChromeMode = 'classic' | 'ribbon';
const THEME_KEY = 'odf-editor-theme';
const TOOLBAR_KEY = 'odf-editor-toolbar-expanded';
const CHROME_KEY = 'odf-editor-chrome';
const RIBBON_COLLAPSED_KEY = 'odf-editor-ribbon-collapsed';
const FORMATTING_MARKS_KEY = 'odf-editor-formatting-marks';
const RULER_KEY = 'odf-editor-ruler';
const THEME_KEY = 'edentext-theme';
const TOOLBAR_KEY = 'edentext-toolbar-expanded';
const CHROME_KEY = 'edentext-chrome';
const RIBBON_COLLAPSED_KEY = 'edentext-ribbon-collapsed';
const FORMATTING_MARKS_KEY = 'edentext-formatting-marks';
const RULER_KEY = 'edentext-ruler';
export function loadTheme(): ThemeMode {
const saved = localStorage.getItem(THEME_KEY);
+1 -1
View File
@@ -2,7 +2,7 @@
// right-to-left page fills its columns from the right and sets the body's base
// direction, so bidi resolves a Hebrew or Arabic line the way the file means it.
const KEY = 'odf-editor-page-rtl';
const KEY = 'edentext-page-rtl';
export function loadPageRtl(): boolean {
return localStorage.getItem(KEY) === 'true';
+1 -1
View File
@@ -10,7 +10,7 @@ LibreOffice's model: a style has a name, a **parent** it inherits from, a follow
property groups (`ParaProps` layout / `TextProps` text). `styleSheet.ts` is framework-free — the
built-ins (Standard → Heading → Heading 1–5 / Title / Subtitle, plus Quotations), `resolveStyle`
(parent chain root-first, nearest wins, cycle-safe), `styleOrder`, and `styleCss`. `sheet.svelte.ts`
holds it as a reactive singleton persisted to `odf-editor-styles` (same shape as `i18n.svelte.ts`).
holds it as a reactive singleton persisted to `edentext-styles` (same shape as `i18n.svelte.ts`).
- **Pair kerning** (`TextProps.kerning` → `font-kerning`) is where the two formats
disagree: ODF kerns unless `style:letter-kerning="false"`, Word kerns **nothing** unless
+1 -1
View File
@@ -5,7 +5,7 @@
import { builtinStyleSheet, DEFAULT_STYLE, mergeStoredSheet, STYLE_SHEET_VERSION, type Style, type StyleFamily, type StyleSheet } from './styleSheet';
import type { TableStyle } from './tableStyles';
const STORAGE_KEY = 'odf-editor-styles';
const STORAGE_KEY = 'edentext-styles';
function load(): StyleSheet {
const raw = localStorage.getItem(STORAGE_KEY);
+1 -1
View File
@@ -62,7 +62,7 @@ async function editorRender(browser, file) {
await page.goto(`http://localhost:${PORT}/`, { waitUntil: 'load' });
await page.evaluate(() => {
localStorage.clear();
localStorage.setItem('odf-editor-zoom', '100'); // no transform scale => rects are doc px
localStorage.setItem('edentext-zoom', '100'); // no transform scale => rects are doc px
});
await page.reload({ waitUntil: 'load' });
await page.waitForSelector('.tiptap');
+5 -5
View File
@@ -11,22 +11,22 @@ describe('autosave crash recovery', () => {
});
it('loads the document again after a startup that completed', () => {
localStorage.setItem('odf-editor-doc', '{"type":"doc"}');
localStorage.setItem('edentext-doc', '{"type":"doc"}');
expect(loadDocument()).toEqual({ type: 'doc' });
markDocumentLoaded();
expect(loadDocument()).toEqual({ type: 'doc' });
});
it('parks the document and starts empty when the last startup never finished', () => {
localStorage.setItem('odf-editor-doc', '{"type":"doc"}');
localStorage.setItem('edentext-doc', '{"type":"doc"}');
loadDocument(); // raises the boot flag, then the "editor hangs"
expect(loadDocument()).toBeNull();
expect(localStorage.getItem('odf-editor-doc-broken')).toBe('{"type":"doc"}');
expect(localStorage.getItem('odf-editor-doc')).toBeNull();
expect(localStorage.getItem('edentext-doc-broken')).toBe('{"type":"doc"}');
expect(localStorage.getItem('edentext-doc')).toBeNull();
});
it('keeps the next document after a recovery', () => {
localStorage.setItem('odf-editor-doc', '{"type":"doc","content":[]}');
localStorage.setItem('edentext-doc', '{"type":"doc","content":[]}');
loadDocument();
expect(loadDocument()).toBeNull(); // recovered: boot flag cleared, doc parked
vi.useFakeTimers();