Commit Graph
82 Commits
Author SHA1 Message Date
Steffen Becker b0ae6f09af feat: the ribbon's Insert tab
Page break, table, picture, text box, link, bookmark, cross-reference, header,
footer, page number, date, symbol and equation. The page break had a key and no
button; the rest moved up from the secondary toolbar.

The ribbon mounts its own link, bookmark, cross-reference and formula dialogs,
so the classic path keeps the ones it has and its re-fire shim untouched. Only
one chrome is mounted, so the two sets never coexist.
2026-08-12 01:40:07 +02:00
Steffen Becker ac93ba8651 feat: the ribbon's Home tab carries every formatting control
Clipboard, Font, Paragraph, a style gallery of live tiles, and Find/Replace —
which had no button in either chrome before. So did change case, underline and
strikethrough line styles: the engine has carried them all along.

Two things the fixed-height band forced. Its own overflow clips a dropdown, so
a menu leaves the flow and pins itself under its trigger. And with one shared
open-menu id, each wrapper must name the menu it owns, or every sibling closes
it on mousedown and the click on a row never lands.
2026-08-12 01:34:03 +02:00
Steffen Becker 2aeac7a574 feat: a Word-style ribbon can take the toolbar's place
A second chrome, chosen by odf-editor-chrome and switchable from either side so
the two can be compared. The shell only — tab strip, File menu, quick-access
save/undo/redo; the tab bodies follow. The classic island stays untouched.

Word's palette lives in --w-* outside the theme blocks, and .ribbon remaps the
app's own tokens onto it, so a reused picker adopts the ribbon's look without
an edit of its own.
2026-08-12 01:20:47 +02:00
Steffen Becker f055fc4850 docs: every comment back inside the three-line rule
A sweep of the whole tree: 27 blocks ran to four lines or more. Trimmed
to what the code can't say, with two of them split back onto the
function each was really about.
2026-08-11 21:22:13 +00:00
Steffen Becker 5969795d04 fix: a header or footer takes the document's font, not the editor's
The zone was pinned to Liberation Serif 12pt, so a run whose font the
file leaves to the default — every page-number field in a fixture
fixture — rendered serif inside an Arial footer. Both formats base the
Header/Footer style on the default paragraph style, so the zone now
takes its text half.
2026-08-11 20:58:29 +00:00
Steffen Becker baf0865256 fix: a section keeps its own side margins too
.tiptap's padding draws one pair of side margins for the whole document, so a
title page declaring 3.18cm left sat 3.85mm off. The difference now rides a
--sec-inset-left/-right node decoration on the section's top-level blocks, and
everything that writes a block margin adds it in — an indent, a named style's
and a table's alignment all compose with it instead of losing to it.
2026-08-11 19:03:58 +00:00
Steffen Becker 9bce48f6e0 fix: a section keeps the top and bottom margins its own page setup gives it
Page geometry was document-wide, so a title page declaring 1cm started 1cm too
low. w:pgMar / the section's ODF page layout now ride its HfSet and feed the
per-page content area; an ODF master handing over (style:next-style-name) gives
its own layout to the section's first page and the successor's to the rest.

A spacer can only push down, so a smaller margin than the document's needs the
opposite: the section's first block gets a lifting spacer where nothing was
pushed onto its page ahead of it.
2026-08-11 18:51:12 +00:00
Steffen Becker e2ee55cf35 fix: a header/footer band is as tall as its own font, not the body's
The zone is one paragraph, so its mark sizes every line of the band. Runs
that agree on a font now set that strut (applyUniformRunFont, as the body
already does), the zone schema carries blockFontSize so it renders, and
hfReachPx measures a line by the family's natural height instead of a flat
1.15. A 10pt footer reserved 12pt lines and sat 1.6mm high on every page.
2026-08-11 17:10:08 +00:00
Steffen Becker c41dce79cb fix: a zone reserves the band its own type needs
hfReachPx floored the zone's line at the 12pt body default and only ever grew
it, so a footer set throughout in 10pt still reserved a 12pt line and pushed
the body ~0.8mm too far up. The default now stands in per run, for one that
declares no size of its own.
2026-08-10 22:20:27 +00:00
Steffen Becker 0fe1a8395d fix: the header/footer band grows to hold the zone it carries
fo:min-height is a floor, not the band: LibreOffice grows it for the zone's
own lines and, in a footer, for the first paragraph's space above (probed —
a header's space below only hangs into the body). The zone paragraph now
carries those margins, so the reach counts them at its real font size and a
page with no zone of its own keeps the floor.
2026-08-10 21:22:56 +00:00
Steffen Becker d407febd4b feat: insert a chapter field, and set where a cell's content sits
Both round-tripped through save/load already but had no way in: the
header/footer bar gains a Chapter button next to page number/count, and the
table toolbar three buttons for the cell's vertical alignment.
2026-08-10 17:01:48 +00:00
Steffen Becker 3153c2f7cc feat: the running head names the chapter the page is in
A manual's footer carries text:chapter, which we imported as the string the
producer happened to cache — so every page of a fixture claimed the last chapter
of the book. It is now a live field: the layer resolves the last heading at
or above its outline level to have started by that page. Round-trips to ODF
text:chapter and Word STYLEREF; PDF keeps the cached name (Chrome's print
engine has no named strings).
2026-08-10 13:04:25 +00:00
Steffen Becker d6bc656c83 feat: the space between two blocks follows the document's own model
LibreOffice adds the upper block's space-below to the lower one's
space-above, and drops the space-above where a page break put the block
at a page top — but for a Word document it takes only the larger of the
two (all probed against soffice). CSS margins can only collapse, so
space-before rides --space-before and editor.css renders it as padding
('add') or margin ('max'); the model is read from settings.xml, assumed
for DOCX, and written back on ODF export.
2026-08-09 16:53:13 +00:00
Steffen Becker 30b5b7890e feat: a header's page background paints behind the page it belongs to
A positioned frame in a header is out of flow — the title-page background or
watermark. It keeps its wrap and its offset from the page corner, and the
layer paints it per page under the body instead of flowing it inline, where
it reserved a page-tall line of body space.

The page surface moves from .tiptap to .paper so the image has a layer
between the sheet and the text; ODF export writes it back page-anchored.
2026-08-09 15:13:46 +00:00
Steffen Becker 28a39e017a feat: a document that blocks the start is not loaded twice
An autosaved document that hangs or throws while the editor comes up was
reloaded from localStorage on every refresh, leaving no way back into the app.
Loading now raises a boot flag that only a completed startup clears; finding it
still set means the last attempt never finished, so the document moves to
odf-editor-doc-broken and the app starts empty with a message.
2026-08-09 14:13:17 +00:00
Steffen Becker 78e35cef2c feat: a reference finds the text it points at, and follows it
Bookmarks are a mark, not a point: both formats store a range, and only a
range lets a REF recompute its text. A cross-reference caches its display
like a Word field and resolves live on each pagination settle — the
bookmark's text, or the page it renders on — keeping the cached value when
the target is gone. Ctrl+click jumps to it, internal #name links too.

ODF splices its sentinels into the run text so cells and custom paragraphs
need no separate emitter; DOCX needs no post-pass at all.
2026-08-09 13:56:22 +00:00
Steffen Becker ee56a348ee feat: formulas can be inserted and edited, and survive both formats
A formula node stores only its LaTeX source; the MathML on screen, the ODF
formula object and Word's OMML are derived from it, and both importers parse
foreign markup back into that source, so an imported formula stays editable.

Word documents were losing their formulas outright: m:oMath sits beside the w:r
runs in its own namespace, past convertInline's `namespaceURI !== W` guard.
2026-08-08 21:50:36 +00:00
Steffen Becker aad4e7a3e4 feat: a later section's header and footer can be edited
The zones already imported, rendered and exported per section, but only
section 1 was editable state — the rest were marked read-only.

Editing now targets the section of the page it starts on: a double-click
edits the zone under the pointer, the Layout-panel buttons the current
page's. zoneKey/writeZone route the single live editor's read and its
write-back to either the six per-zone props or the extraHfSections entry.
2026-08-08 17:00:49 +00:00
Steffen Becker ae2290e22c feat: a header or footer keeps its own tab stops
The left⇥centre⇥right layout every letterhead uses rides on them, and a
zone had none: Word keeps them on the Header style, ODF on the zone
paragraph's, and neither was read or written.

An inactive zone is static generateHTML output no plugin reaches, so it
is measured straight on the DOM. Two bugs surfaced doing it: a segment
its own advance had pushed onto the next line measured as wrapped, and
the alignment then had no way back — it takes the sum of the segment's
client rects now. And the scale came from offsetHeight, which rounds
half a percent away on a zone one line tall.
2026-08-08 15:13:58 +00:00
Steffen Becker ea39dde58b feat: a tab lands on the file's own grid, and can draw its leader
The default tab interval was fixed at 1.25cm, so a file's own
w:defaultTabStop / style:tab-stop-distance was ignored and the DOCX
export wrote 1.25 back over Word's 1.27. It is a document setting now.

A stop can also carry the character it repeats across its gap. odf-kit
has no leader option and mints one automatic style per option set, so
the leader rides style:type behind a sentinel — matching on position
and alignment alone gave every stop at 6cm the same leader, which
LibreOffice drew back plain as day.
2026-08-08 14:40:26 +00:00
Steffen Becker 6accc7268f feat: every section keeps its own header and footer
A document had one set for all of it, taken from whichever section
declared one last — so a fixture lost its letterhead entirely
and page 1 got the empty header its final section asked for.

A body block now carries `sectionBreak`, ordinal rather than indexed so
editing cannot desync it. Import resolves Word's "Link to Previous" per
reference type; export writes a set per sectPr, and on the ODF side each
section past the first gets its own master page, which the block switches
to. The layer picks a page's set from where pageBreaks reports the
sections starting, and each section's zone reaches ride
`--pb-section-reach` so a later section's letterhead makes room for
itself: page 10 of a fixture was 24.9mm out, now 1.1mm.

Editing still reaches section 1 only; the rest render read-only.
2026-08-08 14:02:36 +00:00
Steffen Becker 60980fa4d6 docs: load CLAUDE.md detail only where it is needed
The root file is read at every session start and had grown to 87 KB, so every
session paid for knowledge it did not use. Move the per-directory detail into
nested CLAUDE.md files, which load when a file in that directory is touched,
and the five deep-dive topics into docs/. The root keeps the commands, the hard
rules and an index of where the rest lives.

Content moved verbatim — the probed Word/LibreOffice behaviour, sentinel order
and shared constants are what the code cannot say. CI fails the root over 120
lines, since a prose budget alone is what let it grow.
2026-08-08 15:33:46 +02:00
Steffen Becker f62559707c fix: a header/footer zone keeps its blank lines and its logo's height
A letterhead header is usually a couple of empty lines, the logo, then one
more — the empty leading ones were dropped on import, and the body's content
area counted every line as one text line, logo included. Page 1 of a contract
form started 19mm too high.
2026-08-08 08:30:06 +00:00
Steffen Becker be96ee626c docs: apply rules for comments 2026-08-08 08:28:55 +00:00
Steffen Becker 67e2a00bf0 feat: add a right paragraph indent
The mirror of the left indent (fo:margin-right, w:ind w:right) with its own
marker on the ruler. ODF export rides the PBX sentinel: odf-kit has a paragraph
option for the left indent but none for the right one.

Clearing direct formatting now resets the first-line and right indents as well —
no named style governs either, so both are always direct formatting.
2026-08-07 16:34:42 +00:00
Steffen Becker 7c09727cda docs: remove word as placeholder for general text editor 2026-08-07 18:08:20 +02:00
Steffen Becker ad38994c65 feat: add ruler for tab stops and indents
Click the strip to place a stop of the selected type, drag to move it, drag it
off to drop it; the two triangles set the paragraph's first-line and left
indent. Sticky inside the scroll area, so it clears the toolbar island and
stays aligned with the page as it scrolls or zooms.
2026-08-07 17:47:30 +02:00
Steffen Becker d608771105 feat: add table options 2026-08-06 20:01:44 +02:00
Steffen Becker 1ed9f3d172 feat: table-styles 2026-08-05 19:01:25 +02:00
Steffen Becker 82699e653b feat: add table header column 2026-08-03 22:47:44 +02:00
Steffen Becker 359915d6b2 fix: toolbar issues 2026-08-03 22:38:04 +02:00
Steffen Becker 308a80867c feat: add increase/decrease text size buttons 2026-08-03 16:41:17 +02:00
Steffen Becker 5b11cdf3f6 fix: styles manager 2026-08-03 09:42:07 +02:00
Steffen Becker 51584a98ea fix: color picker in style manager 2026-08-03 08:55:13 +02:00
Steffen Becker 24621b1450 feat: add rgb and hsl to colorpicker 2026-08-03 00:00:27 +02:00
Steffen Becker 8cdd487172 feat: right-click context-menu 2026-08-02 23:06:18 +02:00
Steffen Becker 9483d598fc feat: keyboard shortcuts 2026-07-31 20:43:48 +02:00
Steffen Becker 445d1c505f feat: editable font field 2026-07-31 18:59:38 +02:00
Steffen Becker 7cc9a36d18 feat: touchpad and keyboard scale document instad of browser ui 2026-07-31 18:37:17 +02:00
Steffen Becker f7c0e1ed1e fix: scroll bounce 2026-07-31 00:00:00 +02:00
Steffen Becker ec0cfb76fd feat: add character style editor 2026-07-30 23:34:36 +02:00
Steffen Becker 3dcae6f640 feat: named character styles 2026-07-30 23:34:23 +02:00
Steffen Becker 603f4df905 feat: paragraph style manager ui 2026-07-30 19:58:47 +02:00
Steffen Becker ac9d6cdcff feat: named paragraph styles with inheritance 2026-07-28 23:53:00 +02:00
Steffen Becker d461ce09a6 fix: align styles and headings to libreoffice 2026-07-28 22:45:24 +02:00
Steffen Becker f4973caade test: add unit test for display of font size 2026-07-27 20:38:13 +02:00
Steffen Becker 56f884a412 feat: dropdown for text styles 2026-07-27 20:35:55 +02:00
Steffen Becker f1ba14c948 feat: add paragraph frames and background 2026-07-19 16:34:23 +02:00
Steffen Becker 2a7abed917 feat: odd/even variants for footer/header 2026-07-18 16:42:23 +02:00
Steffen Becker da1bcd8794 feat: images in header/footer 2026-07-18 15:50:26 +02:00