Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ The release run heads these entries with the version and opens a fresh
- A `.docx` run's shading (`w:shd`) renders as its background where no
highlight paints over it.

- The document view's editor formats text under scope `document`:
`odr.editing.format({bold, italic, underline, strikethrough, highlight,
color, size})`, `odr.editing.toggle("bold")`, the ctrl/cmd+B, I and U
chords, and `odr.onSelectionChange` for a host's buttons. A toggle on a
collapsed caret marks the next typed text. Scope `paragraph` refuses it.

- A run that is both underlined and struck through renders both lines. The
page wrote two `text-decoration` declarations, and the second replaced the
first.
Expand Down
47 changes: 29 additions & 18 deletions docs/design/document-editing.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,11 @@ a path in a version-2 world names the wrong element rather than none.
| `splitParagraph` | `paragraph`, `after` (optional), `id` | the children after `after` move into a new paragraph that copies the style; no `after` moves all of them |
| `mergeParagraph` | `paragraph` | takes the children of the next sibling paragraph and removes it |
| `insertParagraph` | `after`, `id` | a fresh empty paragraph after the named one, copying its style |
| `setTextStyle` | `id`, `style` | states the listed properties on one run; see [Inline formatting](#inline-formatting) |
| `setCell` | `sheet`, `column`, `row`, `value` | unchanged; see [`spreadsheet-editing.md`](spreadsheet-editing.md) |

Every `id` field on an op that creates an element is negative (decision 4).
Every other id is one the page wrote. [Inline formatting](#inline-formatting)
adds `setTextStyle` to this table.
Every other id is one the page wrote.

### The four reader gestures, as ops

Expand Down Expand Up @@ -362,9 +362,8 @@ that names the frame rather than a range across text.

## Inline formatting

Status: **in progress.** It is the one step of [`editing.md`](editing.md)
still open; the order of work below says what is in. It covers what a reader
changes on a stretch of text without
Status: **landed**, but for the bindings; the order of work below says what
is in. It covers what a reader changes on a stretch of text without
changing the text: bold, italic, underline, strikethrough, highlight, colour
and size. Font name, superscript and subscript are not in it; nothing asked
for them, and each is the same shape once these seven are in.
Expand Down Expand Up @@ -515,26 +514,39 @@ second wins, so such a run shows one line. It has to become one declaration,
### 16. The gesture reaches the editor two ways, and both land in one function

- **The host asks.** `odr.editing.format(style)` applies a partial style to
the current selection, since a mobile host has buttons and no keyboard.
For the buttons to show state, the editor reports the style of the
selection through `odr.onSelectionChange(style)`, one key per property and
a key left out where the selection is mixed.
the current selection, since a mobile host has buttons and no keyboard; it
answers false where the editor refused, and the refusal channel says why.
For the buttons to show state, the editor reports the computed style of
the selection through `odr.onSelectionChange(style)` as it moves, one key
per property and none where the covered runs differ.
- **The browser asks.** `formatBold`, `formatItalic`, `formatUnderline` and
`formatStrikeThrough` leave the refused list of decision 13 in
[`editing.md`](editing.md) and join the whitelist. Chrome raises them for
ctrl/cmd+B, I and U. They are chords, so they are the *shortcuts* class of
decision 12, and a host that keeps that class keeps these too.
decision 12: where a host keeps that class the editor cancels the
browser's own mark and does nothing else. A chord toggles, and a mixed
selection turns on, as Word does.

Formatting sits behind the scope gate, decision 14 of
[`editing.md`](editing.md): under `paragraph` every formatting gesture
refuses with `outOfScope`, whatever it covers, and only `document` takes it.
A host that offers the narrow scope today keeps offering exactly what it
tested. A collapsed caret inside a word marks the word, as Word does; a
collapsed caret at a word boundary refuses with `range`, and the open
questions hold what it should do instead.
tested.

`odr.editing.toggle(property)` is the chord's rule for a host's button: a
mixed selection turns on, as Word does. A collapsed caret inside a word marks
the word. At a word boundary, or in a paragraph holding no run, the mark is
**pending**: nothing changes until the next typed text, which is cut into a
run of its own and marked, so what follows stays marked on its own.
`onSelectionChange` reports the pending mark meanwhile, and a caret that moves
away drops it. There is no empty run for the caret to sit in, which is what
Chrome places a caret in unreliably.

Undo needs nothing new. A step already holds its ops and the two halves of
taking it back; here the halves are the runs' old and new `style` attributes.
Two marks on one run fold into one op where the later keys win, and so do
two texts, each stepping over the other's kind; any other operation naming
the run stops the fold, since a run put beside it takes what it holds then.

### The adapter surface

Expand Down Expand Up @@ -567,14 +579,10 @@ Each step is a pull request that builds and tests on its own.
**Landed.**
4. **The browser.** `format()`, `onSelectionChange`, the four input types,
the word rule for a collapsed caret, and a check page in
`test/browser/text` asserting the log of each gesture.
`test/browser/text` asserting the log of each gesture. **Landed.**

### Open questions

- **A pending mark.** A caret between words that toggles bold means "what I
type next is bold". That needs an empty run the caret can sit in, and
Chrome places a caret in an empty inline unreliably. Refusing it is
honest and not what a reader expects.
- **Colour back to automatic.** docx has `w:color w:val="auto"`; ODF has
nothing but removal, which decision 9 forbids. Until this is answered a
colour once set can only become another colour.
Expand All @@ -584,6 +592,9 @@ Each step is a pull request that builds and tests on its own.
- **Where the size list comes from.** A host offers sizes; the editor takes
any length. Whether the ODF percentage sizes the reader resolves are ever
written back as absolute is a question the fixtures answer.
- **A line under a wrapper.** `text-decoration` is drawn through every
descendant, so an underline taken off a run inside an underlined span
still shows in the page and in a fresh render; the saved file is right.
- **`transparent` is read as unstated.** `read_color` answers nothing for
`fo:background-color="transparent"`, so a highlight taken away on a run
inside a highlighted paragraph still shows in our render, not in
Expand Down
40 changes: 20 additions & 20 deletions docs/design/editing.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ file — with no live connection between the browser and C++.
format's editor attaches one editor to it (decision 9):
`frontend/sheet-editing.js`, `frontend/document.js`, `frontend/text.js`.
- `Document::edit` replays the envelope: `setCell` for a sheet, and `setText`,
`insertText`, `removeElement`, `splitParagraph`, `mergeParagraph` and
`insertParagraph` for a document. `TextFile::write_edited` is the plain-text
counterpart, since a `.txt` is not a document.
`setTextStyle`, `insertText`, `removeElement`, `splitParagraph`,
`mergeParagraph` and `insertParagraph` for a document.
`TextFile::write_edited` is the plain-text counterpart, since a `.txt` is
not a document.
- `back_translate` CLI replays an envelope onto a source document and `save`s it.

What is **not** done is inline formatting — bold, italic, underline,
strikethrough, highlight, colour and size. Decision 5 of
[`document-editing.md`](document-editing.md) says why the schema takes it
without changing, and its [Inline formatting](document-editing.md#inline-formatting)
section is the plan.
Inline formatting — bold, italic, underline, strikethrough, highlight, colour
and size — is `setTextStyle` on the wire and `odr.editing.format` in the page;
the [Inline formatting](document-editing.md#inline-formatting) section of
[`document-editing.md`](document-editing.md) holds its decisions.

## Decisions

Expand Down Expand Up @@ -189,6 +189,9 @@ runs or paragraphs. It owns:
the wording and not the numbers;
- the **log** — `getOperations()`, `undo()`, `redo()`, `committed()`, and the
`dirty` / `canUndo` / `canRedo` state `odr.onEditChange` reports;
- the **formatting seam** — `format(style)` and `toggle(property)` hand a
host's button to the editor that has one, and `odr.onSelectionChange` is
where that editor reports what the selection shows;
- the **keyboard classes** the page may take (decision 12).

A format's editor is a second script that **attaches** one editor to the mode:
Expand Down Expand Up @@ -348,11 +351,12 @@ then intercepts `beforeinput` and takes the edits it can express as operations:
| Enter | taken: the paragraph splits where the caret sits |
| Backspace at the start of a paragraph | taken: the paragraph merges into the one before it |
| a paste of plain text, over as many lines as it holds | taken: each line after the first opens a paragraph |
| a mark - ctrl/cmd+B, I, U, or `odr.editing.format` - under scope `document` | taken: a run covered in part is cut, and the covered runs are restyled |
| a composition (CJK, autocorrect, dictation) | let through and reconciled on `compositionend` |
| a soft line break (`insertLineBreak`) | refused, reason `newLine` - no operation carries one |
| a range reaching over a picture | taken: the frame carries an address, so the picture goes with the text |
| a range reaching over a text box or a table | refused, reason `range` - it holds text of its own, which the reader did not mean to lose |
| anything else the browser offers (a mark, a list, a drop) | refused, reason `unsupportedEdit` |
| anything else the browser offers (a list, a rule, a drop) | refused, reason `unsupportedEdit` |
| an edit landing outside every run | refused, reason `range` |

**Why the whole view rather than a run at a time:** `contenteditable` per run
Expand Down Expand Up @@ -439,17 +443,13 @@ Each per-editor document holds what its own step decided.
| The op envelope and a replay that dispatches over it | landed |
| The write side of the engines — odf, ooxml text, ooxml presentation | landed |
| The browser editor, owning the edit and its own undo | landed |
| **Inline formatting** — bold, italic, underline, strikethrough, highlight, colour, size | **not started**; planned in [`document-editing.md`](document-editing.md#inline-formatting) |

Formatting is the one left, and decision 5 of
[`document-editing.md`](document-editing.md) is why the schema takes it without
changing: toggling a mark on part of a run is, in both formats, "split the run,
restyle the middle one", and the split is already two operations we have. What
it needs is one op, `setTextStyle {id, style}`, carrying values rather than
toggles, and two things on the write side: a container of the run's own,
because a run shares its `text:span` / `w:r` / `a:r` with its siblings, and for
ODF the automatic style a mark is reached through. Decisions 8 to 16 there
hold the rest.
| **Inline formatting** — bold, italic, underline, strikethrough, highlight, colour, size | landed; [`document-editing.md`](document-editing.md#inline-formatting) |

Formatting landed last, and decision 5 of
[`document-editing.md`](document-editing.md) is why the schema took it without
changing: a mark on part of a run is "split the run, restyle the middle one",
and the split was already two operations. It added one op, `setTextStyle
{id, style}`; decisions 8 to 16 there hold the rest.

The **conformance corpus** decision 7 asks for is still not built. What stands
in for it is that both sides pin the same operation shapes: the browser check
Expand Down
Loading
Loading