diff --git a/src/display/editor/freetext.js b/src/display/editor/freetext.js index b3fe34a5c..3cbfbb0d4 100644 --- a/src/display/editor/freetext.js +++ b/src/display/editor/freetext.js @@ -355,8 +355,26 @@ class FreeTextEditor extends AnnotationEditor { } this.disableEditMode(); - this.#content = this.#extractText().trimEnd(); + const savedText = this.#content; + const newText = (this.#content = this.#extractText().trimEnd()); + if (savedText === newText) { + return; + } + const setText = text => { + this.#content = text; + this.#setContent(); + this.#setEditorDimensions(); + }; + this.addCommands({ + cmd: () => { + setText(newText); + }, + undo: () => { + setText(savedText); + }, + mustExec: false, + }); this.#setEditorDimensions(); } @@ -466,14 +484,7 @@ class FreeTextEditor extends AnnotationEditor { this.height * parentHeight ); - for (const line of this.#content.split("\n")) { - const div = document.createElement("div"); - div.append( - line ? document.createTextNode(line) : document.createElement("br") - ); - this.editorDiv.append(div); - } - + this.#setContent(); this.div.draggable = true; this.editorDiv.contentEditable = false; } else { @@ -484,6 +495,20 @@ class FreeTextEditor extends AnnotationEditor { return this.div; } + #setContent() { + this.editorDiv.replaceChildren(); + if (!this.#content) { + return; + } + for (const line of this.#content.split("\n")) { + const div = document.createElement("div"); + div.append( + line ? document.createTextNode(line) : document.createElement("br") + ); + this.editorDiv.append(div); + } + } + get contentDiv() { return this.editorDiv; } diff --git a/test/integration/freetext_editor_spec.js b/test/integration/freetext_editor_spec.js index c4ff6febc..e8979e726 100644 --- a/test/integration/freetext_editor_spec.js +++ b/test/integration/freetext_editor_spec.js @@ -343,6 +343,83 @@ describe("Editor", () => { }) ); }); + + it("must check that text change can be undone/redone", async () => { + // Run sequentially to avoid clipboard issues. + for (const [browserName, page] of pages) { + const rect = await page.$eval(".annotationEditorLayer", el => { + const { x, y } = el.getBoundingClientRect(); + return { x, y }; + }); + + await page.keyboard.down("Control"); + await page.keyboard.press("a"); + await page.keyboard.up("Control"); + + await page.keyboard.down("Control"); + await page.keyboard.press("Backspace"); + await page.keyboard.up("Control"); + + await page.mouse.click(rect.x + 200, rect.y + 100); + + for (let i = 0; i < 5; i++) { + await page.type(`${getEditorSelector(9)} .internal`, "A"); + + const editorRect = await page.$eval(getEditorSelector(9), el => { + const { x, y, width, height } = el.getBoundingClientRect(); + return { x, y, width, height }; + }); + + // Commit. + await page.mouse.click( + editorRect.x, + editorRect.y + 2 * editorRect.height + ); + + if (i < 4) { + // And select it again. + await page.mouse.click( + editorRect.x + editorRect.width / 2, + editorRect.y + editorRect.height / 2, + { clickCount: 2 } + ); + } + } + + await page.keyboard.down("Control"); + await page.keyboard.press("z"); + await page.keyboard.up("Control"); + await page.waitForTimeout(10); + + let text = await page.$eval(`${getEditorSelector(9)} .internal`, el => { + return el.innerText; + }); + + expect(text).withContext(`In ${browserName}`).toEqual("AAAA"); + + await page.keyboard.down("Control"); + await page.keyboard.press("z"); + await page.keyboard.up("Control"); + await page.waitForTimeout(10); + + text = await page.$eval(`${getEditorSelector(9)} .internal`, el => { + return el.innerText; + }); + + expect(text).withContext(`In ${browserName}`).toEqual("AAA"); + + await page.keyboard.down("Control"); + await page.keyboard.press("y"); + await page.keyboard.up("Control"); + await page.waitForTimeout(10); + + text = await page.$eval(`${getEditorSelector(9)} .internal`, el => { + return el.innerText; + }); + + expect(text).withContext(`In ${browserName}`).toEqual("AAAA"); + } + }); }); describe("FreeText (multiselection)", () => {