Commit Graph
93 Commits
Author SHA1 Message Date
Steffen Becker e770d3317f fix: the style gallery fills the band
The group centres its controls, so the gallery sat at content height — 36px
of tile in a 67px row. The wrap opts out with align-self, and the sample
takes the height the name leaves over.

Two letters fewer buy the preview room to scale: 1px per pt with 21 as the
tile's ceiling, measured against its inner width. Heading 1 now reads a
third larger than Heading 5 instead of a pixel.
2026-08-12 19:51:35 +02:00
Steffen Becker fa2726a1df fix: Word's pi for the Equation button
Word's Equation button is a pi, as its Symbol button beside it is an omega.
Same canvas, baseline and weight as that omega, at font-size 20 rather than
its 14: pi is lowercase, so at one size its x-height measures 1.44x smaller.
2026-08-12 19:51:35 +02:00
Steffen Becker ab7d8ddb01 fix: a page number that reads as a 1, and Word's stacked split button
The glyph was a stem on a base with no flag, so it read as a down-tack rather
than the digit it stands for.

A caret welded to the right of a big button floated at icon height, beside the
label instead of with it, on every menu button of the Layout tab. Big split
buttons stack now, as in Word: the icon runs the command, the label and its
caret open the menu. Measured: every label in a group still tops at 87px.
2026-08-12 13:40:12 +02:00
Steffen Becker 7db9cc96b3 fix: unclip the ribbon's picker panels and size their triggers
Every reused picker anchors its panel to its own trigger, so the band clipped
it: the table grid showed one row of eight. `pinPanels`, one action on the
band, pins whatever panel mounts inside it, and panels now drop below the whole
band instead of over the controls under them.

Their triggers also kept the classic toolbar's compact box, so Table, Symbol
and Date & time were half the size of the buttons beside them. `.rb-captioned`
moves to global.css, replacing four copies, and gives the trigger the big
button's 34px box, a 28px glyph and the ribbon's own hover and open tints.
2026-08-12 13:35:07 +02:00
Steffen Becker ba99f20cfc fix: one baseline for the Font group's letter buttons
Sub- and superscript were SVG paths whose X sat on its own baseline, two
units apart from each other and a size larger than the B, I, U and S beside
them. They are text now, in the same font and size, with the digit on a
relative offset because a flex item ignores vertical-align. Clear formatting
stays a path, redrawn upright with its T on the measured letter baseline.

Ink top and baseline agree within 0.15px, measured in the browser.
2026-08-12 13:13:32 +02:00
Steffen Becker c60cb4e0de fix: one button height across the ribbon, and the Paragraph dialog's grid
The shared pickers frame their trigger for the classic toolbar's island; in a
ribbon row of borderless buttons that reads as a different control, and at
27-28px it broke the row's line. Every trigger is 30px there now. Redrew the
change-case and highlighter glyphs, which were malformed and off-centre.

The dialog sized each row on its own, so a long label wrapped and "pt" vs "cm"
shifted the field beside it - fixed unit width, no wrap, one 2x3 grid.
2026-08-12 13:03:59 +02:00
Steffen Becker 23f234a237 feat: the Paragraph and Tabs dialogs, and a picture's alt text
Word puts the deep paragraph settings behind a dialog, and so do we — it is the
only place the four text-flow attrs can be set: widow/orphan control, keep with
next, keep lines together, page break before. All four already round-tripped
with nothing to change them.

The Tabs dialog types a stop exactly where the ruler can only drag one, and is
the only way to give it a leader. Alt text becomes editable rather than just
inherited from the file name.
2026-08-12 02:00:11 +02:00
Steffen Becker 0d8f396c23 docs: where the ribbon lives and why it is built that way
The palette outside the theme system, the token remap that lets a reused picker
adopt it, and the two things the fixed-height band forces — a menu that leaves
the flow, and a wrapper that names the menu it owns.

Also the deletion the ribbon would unlock if classic were ever retired, so the
option stays visible without anything being removed now.
2026-08-12 01:55:48 +02:00
Steffen Becker ad3f0507fd feat: the ribbon's contextual tabs for a table, a picture and a shape
They appear while the caret is in the object and go with it, handing the strip
back to Home. Table Design carries the style gallery, shading, borders and the
style options; Table Layout the rows, columns, merge, split, alignment — and
cell margins, which round-tripped with nothing to set them.

The floating toolbars stay: a mini-toolbar beside the object and a contextual
tab above the document are both Word's, and both reuse the same pickers.
2026-08-12 01:54:49 +02:00
Steffen Becker 0f53571b1f feat: the ribbon's References, Review and View tabs
References inserts the table of contents and sets how deep it goes — the depth
attribute round-tripped with no way to change it. Review holds the statistics
panel and the spell-check language, View the ruler, formatting marks and zoom;
each of those had lived only in the status bar or the secondary toolbar.

That is all seven fixed tabs.
2026-08-12 01:48:32 +02:00
Steffen Becker 5600e84605 feat: the ribbon's Layout tab
Margins, orientation, page size, columns and breaks as menus; indent and spacing
as typed fields. Three of these had no control at all: a section break, and the
absolute left and right indent the ruler could only drag.

Each margin preset is labelled with the value it sets, so none of them hides a
default — and Normal is LibreOffice's 2 cm, the one this editor already uses.
2026-08-12 01:45:24 +02:00
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