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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ The release run heads these entries with the version and opens a fresh
- `TextFile::edit`, `save` and `save_to_memory`, also in Python, Java,
Objective-C and Swift. `write_edited` is deprecated.

- **Fix**: one undo in the document view's editor took back only part of an
edit that took several steps, such as Enter inside a run, and showed states
between the steps that the reader never made. `odr.editing.undo()` and
`redo()` now take back and replay the whole edit.

## v7.0.0 - 2026-09-13

- **Breaking**: `DocumentPath`, `Element::document_path()` and
Expand Down
25 changes: 19 additions & 6 deletions src/odr/internal/html/frontend/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,14 @@
var done = [];
var undone = [];

// undo and redo take all steps of one gesture together
var gesture = 0;

function perform(step) {
if (step === null) {
return null;
}
step.gesture = gesture;
step.apply();
done.push(step);
undone.length = 0;
Expand Down Expand Up @@ -745,6 +749,7 @@
/// paragraph holding no run, the mark waits for the next typed text.
/// Formatting sits behind the scope gate whole.
function format(style, at) {
gesture += 1;
if (!odr.editing.isEnabled()) {
refuse(null, "readOnly", at);
return false;
Expand Down Expand Up @@ -1334,6 +1339,7 @@
});

root.addEventListener("compositionend", function () {
gesture += 1;
var run = composing;
composing = null;
if (!odr.editing.isEnabled()) {
Expand All @@ -1354,6 +1360,7 @@
});

root.addEventListener("beforeinput", function (event) {
gesture += 1;
var type = event.inputType;
var at = rangeOf(event);

Expand Down Expand Up @@ -1520,19 +1527,25 @@
if (done.length === 0) {
return false;
}
var step = done.pop();
step.revert();
undone.push(step);
var taken = done[done.length - 1].gesture;
while (done.length > 0 && done[done.length - 1].gesture === taken) {
var step = done.pop();
step.revert();
undone.push(step);
}
odr.editing.changed();
return true;
},
redo: function () {
if (undone.length === 0) {
return false;
}
var step = undone.pop();
step.apply();
done.push(step);
var given = undone[undone.length - 1].gesture;
while (undone.length > 0 && undone[undone.length - 1].gesture === given) {
var step = undone.pop();
step.apply();
done.push(step);
}
odr.editing.changed();
return true;
},
Expand Down
14 changes: 11 additions & 3 deletions test/browser/text/tests.html
Original file line number Diff line number Diff line change
Expand Up @@ -375,11 +375,19 @@
"and the paragraph is one again",
texts() === "first run a link and a tail|bold|third|"
);
check("undo goes on to the run that was cut", odr.editing.undo() === true);
check("and to the run that was shortened", odr.editing.undo() === true);
check("until there is nothing left", odr.editing.undo() === false);
check("in one step, so nothing is left", odr.editing.undo() === false);
check("with the page as the renderer wrote it", texts() === "first run a link and a tail|bold|third|");
check("and the log empty", ops().length === 0);
check("redo splits it again", odr.editing.redo() === true && ops().length > 0);
check("in one step too", odr.editing.redo() === false);

reset();
select(run(11).firstChild, 5);
input("insertText", "A");
select(run(11).firstChild, 6);
input("insertText", "B");
check("two edits are two steps", odr.editing.undo() === true);
check("and the first one stays", run(11).textContent === "firstA run ");

reset();
select(run(21).firstChild, 0);
Expand Down
Loading