1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-22 16:18:08 +02:00

[Editor] In caret browsing mode, allow to select in pressing shift and arrow down (bug 1881802)

In implementing caret browsing mode in pdf.js, I didn't notice that selectstart isn't always triggered.
So this patch removes the use of selectstart and rely only on selectionchange.
In order to simplify the selection management, the selection code is moved in the AnnotationUIManager:
 - it simplifies the code;
 - it allows to have only one listener for selectionchange instead of having one by visible page
   for selectstart.
I had to add a delay in the integration tests for highlighting (there's a comment with an explanation),
it isn't really nice, but it's the only way I found and in real life there always is a delay between
press and release.
This commit is contained in:
Calixte Denizet 2024-02-23 18:53:58 +01:00
parent b8b8f1af66
commit 0520f2f0cb
4 changed files with 132 additions and 79 deletions

View file

@ -63,16 +63,12 @@ class AnnotationEditorLayer {
#boundPointerup = this.pointerup.bind(this);
#boundPointerUpAfterSelection = this.pointerUpAfterSelection.bind(this);
#boundPointerdown = this.pointerdown.bind(this);
#boundTextLayerPointerDown = this.#textLayerPointerDown.bind(this);
#editorFocusTimeoutId = null;
#boundSelectionStart = this.selectionStart.bind(this);
#editors = new Map();
#hadPointerDown = false;
@ -338,7 +334,6 @@ class AnnotationEditorLayer {
enableTextSelection() {
if (this.#textLayer?.div) {
document.addEventListener("selectstart", this.#boundSelectionStart);
this.#textLayer.div.addEventListener(
"pointerdown",
this.#boundTextLayerPointerDown
@ -349,7 +344,6 @@ class AnnotationEditorLayer {
disableTextSelection() {
if (this.#textLayer?.div) {
document.removeEventListener("selectstart", this.#boundSelectionStart);
this.#textLayer.div.removeEventListener(
"pointerdown",
this.#boundTextLayerPointerDown
@ -686,54 +680,6 @@ class AnnotationEditorLayer {
this.#uiManager.unselect(editor);
}
/**
* SelectionChange callback.
* @param {Event} event
*/
selectionStart(event) {
if (
!this.#textLayer ||
event.target.parentElement.closest(".textLayer") !== this.#textLayer.div
) {
return;
}
if (this.#uiManager.isShiftKeyDown) {
const keyup = () => {
if (this.#uiManager.isShiftKeyDown) {
return;
}
window.removeEventListener("keyup", keyup);
window.removeEventListener("blur", keyup);
this.pointerUpAfterSelection({});
};
window.addEventListener("keyup", keyup);
window.addEventListener("blur", keyup);
} else {
this.#textLayer.div.addEventListener(
"pointerup",
this.#boundPointerUpAfterSelection,
{ once: true }
);
}
}
/**
* Called when the user releases the mouse button after having selected
* some text.
* @param {PointerEvent} event
*/
pointerUpAfterSelection(event) {
const boxes = this.#uiManager.getSelectionBoxes(this.#textLayer?.div);
if (boxes) {
this.createAndAddNewEditor(event, false, {
boxes,
});
document.getSelection().empty();
}
}
/**
* Pointerup callback.
* @param {PointerEvent} event

View file

@ -57,11 +57,19 @@ function opacityToHex(opacity) {
class IdManager {
#id = 0;
constructor() {
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
Object.defineProperty(this, "reset", {
value: () => (this.#id = 0),
});
}
}
/**
* Get a unique id.
* @returns {string}
*/
getId() {
get id() {
return `${AnnotationEditorPrefix}${this.#id++}`;
}
}
@ -551,10 +559,10 @@ class AnnotationEditorUIManager {
#focusMainContainerTimeoutId = null;
#hasSelection = false;
#highlightColors = null;
#highlightWhenShiftUp = false;
#idManager = new IdManager();
#isEnabled = false;
@ -780,6 +788,16 @@ class AnnotationEditorUIManager {
rotation: 0,
};
this.isShiftKeyDown = false;
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
Object.defineProperty(this, "reset", {
value: () => {
this.selectAll();
this.delete();
this.#idManager.reset();
},
});
}
}
destroy() {
@ -958,8 +976,7 @@ class AnnotationEditorUIManager {
#selectionChange() {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
if (this.#hasSelection) {
this.#hasSelection = false;
if (this.#selectedTextNode) {
this.#selectedTextNode = null;
this.#dispatchUpdateStates({
hasSelectedText: false,
@ -977,8 +994,7 @@ class AnnotationEditorUIManager {
? anchorNode.parentElement
: anchorNode;
if (!anchorElement.closest(".textLayer")) {
if (this.#hasSelection) {
this.#hasSelection = false;
if (this.#selectedTextNode) {
this.#selectedTextNode = null;
this.#dispatchUpdateStates({
hasSelectedText: false,
@ -986,11 +1002,30 @@ class AnnotationEditorUIManager {
}
return;
}
this.#hasSelection = true;
this.#selectedTextNode = anchorNode;
this.#dispatchUpdateStates({
hasSelectedText: true,
});
if (this.#mode !== AnnotationEditorType.HIGHLIGHT) {
return;
}
this.#highlightWhenShiftUp = this.isShiftKeyDown;
if (!this.isShiftKeyDown) {
const pointerup = e => {
if (e.type === "pointerup" && e.button !== 0) {
// Do nothing on right click.
return;
}
window.removeEventListener("pointerup", pointerup);
window.removeEventListener("blur", pointerup);
if (e.type === "pointerup") {
this.highlightSelection();
}
};
window.addEventListener("pointerup", pointerup);
window.addEventListener("blur", pointerup);
}
}
#addSelectionListener() {
@ -1013,6 +1048,10 @@ class AnnotationEditorUIManager {
blur() {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
this.highlightSelection();
}
if (!this.hasSelection) {
return;
}
@ -1199,6 +1238,10 @@ class AnnotationEditorUIManager {
keyup(event) {
if (this.isShiftKeyDown && event.key === "Shift") {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
this.highlightSelection();
}
}
}
@ -1287,7 +1330,7 @@ class AnnotationEditorUIManager {
* @returns {string}
*/
getId() {
return this.#idManager.getId();
return this.#idManager.id;
}
get currentLayer() {