With revision recording on, setContent inserted the whole file in one
transaction, so the recorder marked every run as the reader's own
insertion — the document then rendered underlined in the author colour
(#c69200) from the first character. Measured: 19 843 of 19 843 characters
on an 11-page import, 436 revision spans.
Both loads (new document, import) now carry the RECORDING meta; what is
typed after the open is still recorded, as in both word processors.
View ▸ Split (Ctrl+Alt+S): a second live view of the editor's state, stacked
below the first and editable the same way. The panes share a width, so the
second renders the pagination the first measures — pageBreaks, columnsFlow
and tabStops skip the pass on a split pane rather than run it twice.
Every widget decoration now builds its DOM in a function: two views render
one decoration set, and a shared node is taken back and forth between the
two documents until the tab locks up (measured on a two-page document).
The three attrs LibreOffice's Format… dialog is mostly about, on both
sides of the bar. They live together on one TextStyle mark and a mark of
one type replaces that type whole, so each run's own attrs are the base of
the new one — recolouring leaves the font alone.
F3 was bound twice from here on: it now expands an AutoText shortcut where
the caret has one, as it does in both word processors, and finds the next
match otherwise.
LibreOffice's Tools ▸ AutoText, in the ribbon where Word keeps its Quick
Parts: store the selection under a name and a shortcut, insert it from the
library or by typing the shortcut and pressing F3 — LibreOffice's key for
it.
An entry is HTML parsed back through the schema, so it keeps its marks and
blocks; the library is app-level, as it is in both products, so no file
carries it.
Every recorded change in document order beside the comments pane: author,
date, the text, accept or reject in place. A change split by a paragraph
boundary is one row, so acceptRevision/rejectRevision take an id. The
author colour is a decoration, since a mark cannot know the document's
other authors; it nests inside the mark's span and carries the under/
strikethrough with it. shortcutHint no longer throws on an id nothing
binds, which heading 7-10 had just made reachable.
The manifest registers .odt/.ott/.docx/.dotx as file handlers and a
launchQueue consumer opens what the OS passes, keeping the handle so the
first Save writes that file. A .dotx now reads like an .ott: Word's
importer, but never bound as the file to save over.
Two faults on one picture. A block after a float keeps its box over
the float — only its lines move — so the caption on top swallowed
every click meant for the picture; the frame is lifted above the
in-flow boxes. And a caption can only sit under a side-wrapped frame
if it is boxed to it, so its span in the column is measured and
becomes the caption's indent, which also drops it clear of the float.
Measured at 100% and 150%: caption under the picture, its width, in
all four wrap modes.
A picture with "above and below" wrap is a frame centred in the
column while its paragraph carries no alignment at all, so the
caption still went to the left margin. framePlacement reads the
place off the frame: centred, the side it is set against, or its
offset as the caption's indent. The debug dump gains a frames
section — every other one measures the flow downwards, and this
is a sideways question.
The Navigator was still listed as missing; only split view is. Formula
recalculation stops the walk at the blocks a table cannot be in, or it
visits every run of the document per keystroke. One dead CSS rule goes,
so svelte-check reports nothing at all.
The formula rides the cell, as ODF writes it; Word's field syntax is what
is stored, since it is the wider language. `ooow:` in the ODF attribute
value is a namespace prefix — undeclared, LibreOffice reads it as part of
the formula and prints "Expression is faulty".
Sort takes one key and detects the type per cell where both dialogs make
you pick. Number recognition is off by default, as it is in LibreOffice,
and a formula parses a cell's text either way — exactly as there.
The page grid was one repeating cycle, so every page had to be the same
size. It is now a table of height runs the placement pass grows as each
section's first page becomes known, and the sheets are one box per page
instead of a repeating gradient — .paper reserves the widest section's
paper, a narrower one takes the difference as a right inset.
Layout ▸ Orientation offers "this section"; the size stays document-wide
in the ribbon but round-trips per section from a file.
F5 (LibreOffice's key) opens an outline of the document's headings:
click one to jump, and move it up or down or promote/demote its level,
each carrying everything under the heading. Below it the tables,
pictures and bookmarks LibreOffice's Navigator also lists.
A move swaps with the adjacent chapter at the same level and stops at a
heading above it, so a subheading never reparents itself. The pane docks
right beside the comments one — the classic chrome's Tools chip owns the
left edge.
A .ott/.dotx is the built document with one label changed — ODF's
package media type, Word's content type for the document part — so the
picker offers both and the chosen extension decides which exporter runs.
Opening or saving also records the file in a recent list. The name is in
localStorage so the menu draws at once; the handle that reopens it is in
IndexedDB beside it. A file that has moved, or whose permission the
browser drops on reload, is reported and dropped from the list.
Continuous or per page, every nth line, empty lines counted or not —
LibreOffice's Tools ▸ Line Numbering, round-tripping as its
text:linenumbering-configuration and Word's w:lnNumType. CSS exposes no line
boxes, so the layer Range-selects each block and takes the distinct tops of
its client rects as its lines; LibreOffice renders the exported file with the
numbers in the same places.
LibreOffice's Format ▸ Page Style ▸ Area/Borders and its Format ▸ Watermark,
all three round-tripping. Probed placements that are not guessable: ODF reads
a watermark only out of the master page's header (and only with xmlns:draw
declared), Word's w:sectPr wants pgBorders after pgMar or it loses the header
references, and the VML shapetype has to travel or LibreOffice cannot resolve
it. On screen the watermark is an SVG textLength, since a fontwork shape
stretches its text to its box rather than setting a font size.
The comment is a mark carrying its own author, date, body and resolved
flag — the bookmark pattern, so no second store and no schema node. The
pane lists them in document order and jumps to the text each marks.
ODF gets office:annotation + office:annotation-end and LibreOffice's
loext:resolved, DOCX the range pair plus word/comments.xml; a point
comment, which LibreOffice writes without a range, attaches to the run
before it. Ctrl+M no longer strips a comment, a bookmark or a link.
Layout > Page numbers: 1 / i / I / a / A and a first-page number, both
round-tripping. ODF has no document-level start — probed, LibreOffice
puts style:page-number on the first paragraph's own style — so the
export mints one there and the page layout carries style:num-format;
Word takes both in the first section's w:pgNumType. The page *count*
field stays decimal, as the one both of them write does.
Layout > Hyphenation, off by default as in both word processors. The
browser hyphenates in the document's own language (the body host gets
lang and hyphens: auto) — measured, a justified German paragraph takes
15 lines instead of 17. ODF keeps fo:hyphenate in the base style's TEXT
properties: probed, in paragraph-properties LibreOffice ignores it and
drops it on the next save. Word takes w:autoHyphenation.
LibreOffice's rule set: typographic quotes per document language, its
dash matrix, its replacement table, sentence capitalization with an
abbreviation exception list, and TWo INitials. One handleTextInput
plugin listed last, so every input rule still fires first; the rule
table itself is a pure function under test. Auto-URL and auto-list were
already TipTap's and now answer to the same checkboxes.
LibreOffice's File > Properties / Word's File > Info, reachable from
both chromes' file menus. ODF writes them into the meta.xml odf-kit
already emits (one meta:keyword each, generator EdenText); DOCX passes
them to the package's core properties. Both importers read them back,
and LibreOffice shows every field from either format.
Per cell the attr beside the computed value, cell and row height, the gap above
and below the content, and each block's margins — plus the style sheet and the
spacing model, which is what decides whether the content fills the box.
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.
Numbering format, start value, restart, position, prefix/suffix and the two
styles per class, plus the separator's length, thickness, spacing, alignment
and colour. The separator rides :root custom properties, so editor.css draws
the line and pageBreaks.ts reserves its band from the same values. A settings
change is no document change, so it carries a resync meta — without it the
numbering kept the format it was typed under.
Each class gets its own part via the docx package's footnotes/endnotes maps,
counted from 1, and the anchor becomes a reference run. Import reads both
parts, skipping the separator entries Word references from nowhere, and
flattens a multi-paragraph note to hard breaks. The numbering settings ride
w:footnotePr/w:endnotePr in settings.xml — a post-pack pass, as the package
exposes neither.
Anchors become an FNT sentinel and each note is hoisted to a top-level
paragraph, so its text rides every existing pass — marks, character styles,
links, bookmarks and formulas included; applyNotes cuts the region back out
into <text:note> at the anchor. styles.xml gains LibreOffice's own Footnote/
Endnote styles, the notes-configuration per class and the separator, and the
resolver reads all of it back. The importer builds the section instead of
warning that notes were dropped.
ODF style:writing-mode="rl-tb" and Word's section-level w:bidi were not
read, so a Hebrew two-column page put column 1 on the left where
LibreOffice puts it on the right — checked against pdftoppm of its own
PDF, since the harness bands both columns into one line and never gets
that far. It is a document setting now, applied as `dir` on the body
host, which is all CSS multi-column needs; it also gives bidi the base
direction the file means. Round-trips through both formats. The
header/footer band keeps its own direction, as LibreOffice lays it out.
A pagination pass measures the DOM the last one changed, so a fresh
document searched for its layout in public — 12 of them over 550ms on a
two-column file, a phantom third page among them. Two real causes: the
flow's overflow test had no headroom, so it read its own last answer
(880px fits undecorated, 913px overflows once the decoration pins the
column height), and a split dropped the height decorations without
clearing their cache key. The rest is inherent to measuring rendered
DOM, so the page stays hidden until its layout repeats: one visible
layout instead of 12, and 21-139ms where there are no columns.
The hidden input sat inside the classic header, so where the File System
Access API is missing handleOpen clicked nothing at all. It moves out of
the chrome fork, which both paths route through.
The label drops the extension while it is there: the input has always
taken .odt, .ott and .docx.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.