[Editor] Add the possibility to move the caret with the keyboard once an highlight has been made (bug 1881684)

This commit is contained in:
Calixte Denizet 2024-02-26 10:39:44 +01:00
parent 9600c48d4f
commit a9f47e87d5
3 changed files with 146 additions and 2 deletions

View File

@ -16,11 +16,12 @@
import {
AnnotationEditorParamsType,
AnnotationEditorType,
shadow,
Util,
} from "../../shared/util.js";
import { bindEvents, KeyboardManager } from "./tools.js";
import { FreeOutliner, Outliner } from "./outliner.js";
import { AnnotationEditor } from "./editor.js";
import { bindEvents } from "./tools.js";
import { ColorPicker } from "./color_picker.js";
import { noContextMenu } from "../display_utils.js";
@ -28,6 +29,10 @@ import { noContextMenu } from "../display_utils.js";
* Basic draw editor in order to generate an Highlight annotation.
*/
class HighlightEditor extends AnnotationEditor {
#anchorNode = null;
#anchorOffset = 0;
#boxes;
#clipPathId = null;
@ -36,6 +41,10 @@ class HighlightEditor extends AnnotationEditor {
#focusOutlines = null;
#focusNode = null;
#focusOffset = 0;
#highlightDiv = null;
#highlightOutlines = null;
@ -44,6 +53,8 @@ class HighlightEditor extends AnnotationEditor {
#isFreeHighlight = false;
#boundKeydown = this.#keydown.bind(this);
#lastPoint = null;
#opacity;
@ -72,6 +83,20 @@ class HighlightEditor extends AnnotationEditor {
static _freeHighlightClipId = "";
static get _keyboardManager() {
const proto = HighlightEditor.prototype;
return shadow(
this,
"_keyboardManager",
new KeyboardManager([
[["ArrowLeft", "mac+ArrowLeft"], proto._moveCaret, { args: [0] }],
[["ArrowRight", "mac+ArrowRight"], proto._moveCaret, { args: [1] }],
[["ArrowUp", "mac+ArrowUp"], proto._moveCaret, { args: [2] }],
[["ArrowDown", "mac+ArrowDown"], proto._moveCaret, { args: [3] }],
])
);
}
constructor(params) {
super({ ...params, name: "highlightEditor" });
this.color = params.color || HighlightEditor._defaultColor;
@ -86,6 +111,10 @@ class HighlightEditor extends AnnotationEditor {
this.#createFreeOutlines(params);
this.#addToDrawLayer();
} else {
this.#anchorNode = params.anchorNode;
this.#anchorOffset = params.anchorOffset;
this.#focusNode = params.focusNode;
this.#focusOffset = params.focusOffset;
this.#createOutlines();
this.#addToDrawLayer();
this.rotate(this.rotation);
@ -530,6 +559,8 @@ class HighlightEditor extends AnnotationEditor {
const div = super.render();
if (this.#isFreeHighlight) {
div.classList.add("free");
} else {
this.div.addEventListener("keydown", this.#boundKeydown);
}
const highlightDiv = (this.#highlightDiv = document.createElement("div"));
div.append(highlightDiv);
@ -552,6 +583,36 @@ class HighlightEditor extends AnnotationEditor {
this.parent.drawLayer.removeClass(this.#outlineId, "hovered");
}
#keydown(event) {
HighlightEditor._keyboardManager.exec(this, event);
}
_moveCaret(direction) {
this.parent.unselect(this);
switch (direction) {
case 0 /* left */:
case 2 /* up */:
this.#setCaret(/* start = */ true);
break;
case 1 /* right */:
case 3 /* down */:
this.#setCaret(/* start = */ false);
break;
}
}
#setCaret(start) {
if (!this.#anchorNode) {
return;
}
const selection = window.getSelection();
if (start) {
selection.setPosition(this.#anchorNode, this.#anchorOffset);
} else {
selection.setPosition(this.#focusNode, this.#focusOffset);
}
}
/** @inheritdoc */
select() {
super.select();
@ -563,6 +624,9 @@ class HighlightEditor extends AnnotationEditor {
unselect() {
super.unselect();
this.parent?.drawLayer.removeClass(this.#outlineId, "selected");
if (!this.#isFreeHighlight) {
this.#setCaret(/* start = */ false);
}
}
#getRotation() {

View File

@ -947,7 +947,7 @@ class AnnotationEditorUIManager {
if (!selection || selection.isCollapsed) {
return;
}
const { anchorNode } = selection;
const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;
const anchorElement =
anchorNode.nodeType === Node.TEXT_NODE
? anchorNode.parentElement
@ -966,6 +966,10 @@ class AnnotationEditorUIManager {
layer.createAndAddNewEditor({ x: 0, y: 0 }, false, {
methodOfCreation,
boxes,
anchorNode,
anchorOffset,
focusNode,
focusOffset,
});
break;
}

View File

@ -1154,5 +1154,81 @@ describe("Highlight Editor", () => {
})
);
});
it("must check that an highlight can be left with the keyboard", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
await page.click("#editorHighlight");
await page.waitForSelector(".annotationEditorLayer.highlightEditing");
if (browserName === "chrome") {
// Unfortunately, we can't test this on Chrome because we can't set
// the caret browsing mode to true.
return;
}
let rect = await getSpanRectFromText(
page,
1,
"Dynamic languages such as JavaScript are more difficult to com-"
);
await page.mouse.click(rect.x + 5, rect.y + rect.height / 2);
await page.keyboard.down("Shift");
for (let i = 0; i < 10; i++) {
await page.keyboard.press("ArrowRight");
}
const focusOffset = await page.evaluate(
() => window.getSelection().focusOffset
);
await page.keyboard.up("Shift");
await page.waitForSelector(getEditorSelector(0));
for (let i = 0; i < 5; i++) {
await page.keyboard.press("ArrowRight");
}
let offset = await page.evaluate(
() => window.getSelection().anchorOffset
);
expect(offset)
.withContext(`In ${browserName}`)
.toEqual(focusOffset + 4);
rect = await getSpanRectFromText(
page,
1,
"experience and enable a new generation of applications, virtual ma-"
);
await page.mouse.click(
rect.x + rect.width / 2,
rect.y + rect.height / 2
);
await page.keyboard.down("Shift");
for (let i = 0; i < 10; i++) {
await page.keyboard.press("ArrowRight");
}
const anchorOffset = await page.evaluate(
() => window.getSelection().anchorOffset
);
await page.keyboard.up("Shift");
await page.waitForSelector(getEditorSelector(1));
for (let i = 0; i < 5; i++) {
await page.keyboard.press("ArrowLeft");
}
offset = await page.evaluate(
() => window.getSelection().anchorOffset
);
expect(offset)
.withContext(`In ${browserName}`)
.toEqual(anchorOffset - 4);
})
);
});
});
});