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:
1 parent
1018380f88
commit
0da75ed456
37 files changed
+76
-74
No files matched your search
@@ -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
@@ -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,6 +1,6 @@
|
||||
# Commercial License
|
||||
|
||||
Web ODF Editor is **dual-licensed**.
|
||||
EdenText is **dual-licensed**.
|
||||
|
||||
## 1. Open-source license (default)
|
||||
|
||||
|
||||
@@ -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)).
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
Generated
+2
-2
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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[]>([]);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 } } : {}),
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,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
@@ -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`)
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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) ?? '';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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';
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user