mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
[Editor] Add a floating button close to the selected text to highlight it (bug 1867742)
For now keep this feature behind a pref in order to make some experiments before deciding to enable it.
This commit is contained in:
parent
eb160726ee
commit
b4267cd294
9 changed files with 233 additions and 21 deletions
|
@ -135,4 +135,80 @@ class EditorToolbar {
|
|||
}
|
||||
}
|
||||
|
||||
export { EditorToolbar };
|
||||
class HighlightToolbar {
|
||||
#buttons = null;
|
||||
|
||||
#toolbar = null;
|
||||
|
||||
#uiManager;
|
||||
|
||||
constructor(uiManager) {
|
||||
this.#uiManager = uiManager;
|
||||
}
|
||||
|
||||
#render() {
|
||||
const editToolbar = (this.#toolbar = document.createElement("div"));
|
||||
editToolbar.className = "editToolbar";
|
||||
editToolbar.setAttribute("role", "toolbar");
|
||||
editToolbar.addEventListener("contextmenu", noContextMenu);
|
||||
|
||||
const buttons = (this.#buttons = document.createElement("div"));
|
||||
buttons.className = "buttons";
|
||||
editToolbar.append(buttons);
|
||||
|
||||
this.#addHighlightButton();
|
||||
|
||||
return editToolbar;
|
||||
}
|
||||
|
||||
#getLastPoint(boxes, isLTR) {
|
||||
let lastY = 0;
|
||||
let lastX = 0;
|
||||
for (const box of boxes) {
|
||||
const y = box.y + box.height;
|
||||
if (y < lastY) {
|
||||
continue;
|
||||
}
|
||||
const x = box.x + (isLTR ? box.width : 0);
|
||||
if (y > lastY) {
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
continue;
|
||||
}
|
||||
if (isLTR) {
|
||||
if (x > lastX) {
|
||||
lastX = x;
|
||||
}
|
||||
} else if (x < lastX) {
|
||||
lastX = x;
|
||||
}
|
||||
}
|
||||
return [isLTR ? 1 - lastX : lastX, lastY];
|
||||
}
|
||||
|
||||
show(parent, boxes, isLTR) {
|
||||
const [x, y] = this.#getLastPoint(boxes, isLTR);
|
||||
const { style } = (this.#toolbar ||= this.#render());
|
||||
parent.append(this.#toolbar);
|
||||
style.insetInlineEnd = `${100 * x}%`;
|
||||
style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.#toolbar.remove();
|
||||
}
|
||||
|
||||
#addHighlightButton() {
|
||||
const button = document.createElement("button");
|
||||
button.className = "highlightButton";
|
||||
button.tabIndex = 0;
|
||||
button.setAttribute("data-l10n-id", `pdfjs-highlight-floating-button`);
|
||||
button.addEventListener("contextmenu", noContextMenu);
|
||||
button.addEventListener("click", () => {
|
||||
this.#uiManager.highlightSelection("floating_button");
|
||||
});
|
||||
this.#buttons.append(button);
|
||||
}
|
||||
}
|
||||
|
||||
export { EditorToolbar, HighlightToolbar };
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
getRGB,
|
||||
PixelsPerInch,
|
||||
} from "../display_utils.js";
|
||||
import { HighlightToolbar } from "./toolbar.js";
|
||||
|
||||
function bindEvents(obj, element, names) {
|
||||
for (const name of names) {
|
||||
|
@ -555,6 +556,8 @@ class AnnotationEditorUIManager {
|
|||
|
||||
#editorsToRescale = new Set();
|
||||
|
||||
#enableHighlightFloatingButton = false;
|
||||
|
||||
#filterFactory = null;
|
||||
|
||||
#focusMainContainerTimeoutId = null;
|
||||
|
@ -563,6 +566,8 @@ class AnnotationEditorUIManager {
|
|||
|
||||
#highlightWhenShiftUp = false;
|
||||
|
||||
#highlightToolbar = null;
|
||||
|
||||
#idManager = new IdManager();
|
||||
|
||||
#isEnabled = false;
|
||||
|
@ -771,6 +776,7 @@ class AnnotationEditorUIManager {
|
|||
pdfDocument,
|
||||
pageColors,
|
||||
highlightColors,
|
||||
enableHighlightFloatingButton,
|
||||
mlManager
|
||||
) {
|
||||
this.#container = container;
|
||||
|
@ -782,10 +788,12 @@ class AnnotationEditorUIManager {
|
|||
this._eventBus._on("scalechanging", this.#boundOnScaleChanging);
|
||||
this._eventBus._on("rotationchanging", this.#boundOnRotationChanging);
|
||||
this.#addSelectionListener();
|
||||
this.#addKeyboardManager();
|
||||
this.#annotationStorage = pdfDocument.annotationStorage;
|
||||
this.#filterFactory = pdfDocument.filterFactory;
|
||||
this.#pageColors = pageColors;
|
||||
this.#highlightColors = highlightColors || null;
|
||||
this.#enableHighlightFloatingButton = enableHighlightFloatingButton;
|
||||
this.#mlManager = mlManager || null;
|
||||
this.viewParameters = {
|
||||
realScale: PixelsPerInch.PDF_TO_CSS_UNITS,
|
||||
|
@ -821,6 +829,8 @@ class AnnotationEditorUIManager {
|
|||
this.#selectedEditors.clear();
|
||||
this.#commandManager.destroy();
|
||||
this.#altTextManager?.destroy();
|
||||
this.#highlightToolbar?.hide();
|
||||
this.#highlightToolbar = null;
|
||||
if (this.#focusMainContainerTimeoutId) {
|
||||
clearTimeout(this.#focusMainContainerTimeoutId);
|
||||
this.#focusMainContainerTimeoutId = null;
|
||||
|
@ -946,6 +956,12 @@ class AnnotationEditorUIManager {
|
|||
this.viewParameters.rotation = pagesRotation;
|
||||
}
|
||||
|
||||
#getAnchorElementForSelection({ anchorNode }) {
|
||||
return anchorNode.nodeType === Node.TEXT_NODE
|
||||
? anchorNode.parentElement
|
||||
: anchorNode;
|
||||
}
|
||||
|
||||
highlightSelection(methodOfCreation = "") {
|
||||
const selection = document.getSelection();
|
||||
if (!selection || selection.isCollapsed) {
|
||||
|
@ -953,18 +969,19 @@ class AnnotationEditorUIManager {
|
|||
}
|
||||
const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;
|
||||
const text = selection.toString();
|
||||
const anchorElement =
|
||||
anchorNode.nodeType === Node.TEXT_NODE
|
||||
? anchorNode.parentElement
|
||||
: anchorNode;
|
||||
const anchorElement = this.#getAnchorElementForSelection(selection);
|
||||
const textLayer = anchorElement.closest(".textLayer");
|
||||
const boxes = this.getSelectionBoxes(textLayer);
|
||||
if (!boxes) {
|
||||
return;
|
||||
}
|
||||
selection.empty();
|
||||
if (this.#mode === AnnotationEditorType.NONE) {
|
||||
this._eventBus.dispatch("showannotationeditorui", {
|
||||
source: this,
|
||||
mode: AnnotationEditorType.HIGHLIGHT,
|
||||
});
|
||||
this.showAllEditors("highlight", true, /* updateButton = */ true);
|
||||
}
|
||||
for (const layer of this.#allLayers.values()) {
|
||||
if (layer.hasTextLayer(textLayer)) {
|
||||
|
@ -982,6 +999,21 @@ class AnnotationEditorUIManager {
|
|||
}
|
||||
}
|
||||
|
||||
#displayHighlightToolbar() {
|
||||
const selection = document.getSelection();
|
||||
if (!selection || selection.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
const anchorElement = this.#getAnchorElementForSelection(selection);
|
||||
const textLayer = anchorElement.closest(".textLayer");
|
||||
const boxes = this.getSelectionBoxes(textLayer);
|
||||
if (!boxes) {
|
||||
return;
|
||||
}
|
||||
this.#highlightToolbar ||= new HighlightToolbar(this);
|
||||
this.#highlightToolbar.show(textLayer, boxes, this.direction === "ltr");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an editor in the annotation storage.
|
||||
* @param {AnnotationEditor} editor
|
||||
|
@ -1000,6 +1032,7 @@ class AnnotationEditorUIManager {
|
|||
const selection = document.getSelection();
|
||||
if (!selection || selection.isCollapsed) {
|
||||
if (this.#selectedTextNode) {
|
||||
this.#highlightToolbar?.hide();
|
||||
this.#selectedTextNode = null;
|
||||
this.#dispatchUpdateStates({
|
||||
hasSelectedText: false,
|
||||
|
@ -1012,12 +1045,11 @@ class AnnotationEditorUIManager {
|
|||
return;
|
||||
}
|
||||
|
||||
const anchorElement =
|
||||
anchorNode.nodeType === Node.TEXT_NODE
|
||||
? anchorNode.parentElement
|
||||
: anchorNode;
|
||||
if (!anchorElement.closest(".textLayer")) {
|
||||
const anchorElement = this.#getAnchorElementForSelection(selection);
|
||||
const textLayer = anchorElement.closest(".textLayer");
|
||||
if (!textLayer) {
|
||||
if (this.#selectedTextNode) {
|
||||
this.#highlightToolbar?.hide();
|
||||
this.#selectedTextNode = null;
|
||||
this.#dispatchUpdateStates({
|
||||
hasSelectedText: false,
|
||||
|
@ -1025,16 +1057,22 @@ class AnnotationEditorUIManager {
|
|||
}
|
||||
return;
|
||||
}
|
||||
this.#highlightToolbar?.hide();
|
||||
this.#selectedTextNode = anchorNode;
|
||||
this.#dispatchUpdateStates({
|
||||
hasSelectedText: true,
|
||||
});
|
||||
|
||||
if (this.#mode !== AnnotationEditorType.HIGHLIGHT) {
|
||||
if (
|
||||
this.#mode !== AnnotationEditorType.HIGHLIGHT &&
|
||||
this.#mode !== AnnotationEditorType.NONE
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showAllEditors("highlight", true, /* updateButton = */ true);
|
||||
if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
|
||||
this.showAllEditors("highlight", true, /* updateButton = */ true);
|
||||
}
|
||||
|
||||
this.#highlightWhenShiftUp = this.isShiftKeyDown;
|
||||
if (!this.isShiftKeyDown) {
|
||||
|
@ -1046,7 +1084,7 @@ class AnnotationEditorUIManager {
|
|||
window.removeEventListener("pointerup", pointerup);
|
||||
window.removeEventListener("blur", pointerup);
|
||||
if (e.type === "pointerup") {
|
||||
this.highlightSelection("main_toolbar");
|
||||
this.#onSelectEnd("main_toolbar");
|
||||
}
|
||||
};
|
||||
window.addEventListener("pointerup", pointerup);
|
||||
|
@ -1054,6 +1092,14 @@ class AnnotationEditorUIManager {
|
|||
}
|
||||
}
|
||||
|
||||
#onSelectEnd(methodOfCreation = "") {
|
||||
if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
|
||||
this.highlightSelection(methodOfCreation);
|
||||
} else if (this.#enableHighlightFloatingButton) {
|
||||
this.#displayHighlightToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
#addSelectionListener() {
|
||||
document.addEventListener("selectionchange", this.#boundSelectionChange);
|
||||
}
|
||||
|
@ -1076,7 +1122,7 @@ class AnnotationEditorUIManager {
|
|||
this.isShiftKeyDown = false;
|
||||
if (this.#highlightWhenShiftUp) {
|
||||
this.#highlightWhenShiftUp = false;
|
||||
this.highlightSelection("main_toolbar");
|
||||
this.#onSelectEnd("main_toolbar");
|
||||
}
|
||||
if (!this.hasSelection) {
|
||||
return;
|
||||
|
@ -1252,7 +1298,10 @@ class AnnotationEditorUIManager {
|
|||
if (!this.isShiftKeyDown && event.key === "Shift") {
|
||||
this.isShiftKeyDown = true;
|
||||
}
|
||||
if (!this.isEditorHandlingKeyboard) {
|
||||
if (
|
||||
this.#mode !== AnnotationEditorType.NONE &&
|
||||
!this.isEditorHandlingKeyboard
|
||||
) {
|
||||
AnnotationEditorUIManager._keyboardManager.exec(this, event);
|
||||
}
|
||||
}
|
||||
|
@ -1266,7 +1315,7 @@ class AnnotationEditorUIManager {
|
|||
this.isShiftKeyDown = false;
|
||||
if (this.#highlightWhenShiftUp) {
|
||||
this.#highlightWhenShiftUp = false;
|
||||
this.highlightSelection("main_toolbar");
|
||||
this.#onSelectEnd("main_toolbar");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1335,7 +1384,6 @@ class AnnotationEditorUIManager {
|
|||
setEditingState(isEditing) {
|
||||
if (isEditing) {
|
||||
this.#addFocusManager();
|
||||
this.#addKeyboardManager();
|
||||
this.#addCopyPasteListeners();
|
||||
this.#dispatchUpdateStates({
|
||||
isEditing: this.#mode !== AnnotationEditorType.NONE,
|
||||
|
@ -1346,7 +1394,6 @@ class AnnotationEditorUIManager {
|
|||
});
|
||||
} else {
|
||||
this.#removeFocusManager();
|
||||
this.#removeKeyboardManager();
|
||||
this.#removeCopyPasteListeners();
|
||||
this.#dispatchUpdateStates({
|
||||
isEditing: false,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue