From d0b0064643aa629e19ec7a09a19b5c8e3b4c631d Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Thu, 19 Jun 2025 21:15:06 +0200 Subject: [PATCH] [Editor] When editing mode is disabled, allow to double click on a added annotation to edit it It's already possible but only when double clicking outside the the text layer. This patach adds the possibility to click on the text layer. --- src/display/editor/annotation_editor_layer.js | 58 ++++++++++++++++++- test/integration/freetext_editor_spec.mjs | 52 +++++++++++++++++ web/annotation_editor_layer_builder.css | 7 +++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/display/editor/annotation_editor_layer.js b/src/display/editor/annotation_editor_layer.js index a7c45d562..796819061 100644 --- a/src/display/editor/annotation_editor_layer.js +++ b/src/display/editor/annotation_editor_layer.js @@ -25,7 +25,11 @@ // eslint-disable-next-line max-len /** @typedef {import("../src/display/struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */ -import { AnnotationEditorType, FeatureTest } from "../../shared/util.js"; +import { + AnnotationEditorPrefix, + AnnotationEditorType, + FeatureTest, +} from "../../shared/util.js"; import { AnnotationEditor } from "./editor.js"; import { FreeTextEditor } from "./freetext.js"; import { HighlightEditor } from "./highlight.js"; @@ -85,6 +89,10 @@ class AnnotationEditorLayer { #textSelectionAC = null; + #textLayerDblClickAC = null; + + #lastPointerDownTimestamp = -1; + #uiManager; static _initialized = false; @@ -238,6 +246,8 @@ class AnnotationEditorLayer { this.#isEnabling = true; this.div.tabIndex = 0; this.togglePointerEvents(true); + this.#textLayerDblClickAC?.abort(); + this.#textLayerDblClickAC = null; const annotationElementIds = new Set(); for (const editor of this.#editors.values()) { editor.enableEditing(); @@ -280,6 +290,52 @@ class AnnotationEditorLayer { this.#isDisabling = true; this.div.tabIndex = -1; this.togglePointerEvents(false); + if (this.#textLayer && !this.#textLayerDblClickAC) { + this.#textLayerDblClickAC = new AbortController(); + const signal = this.#uiManager.combinedSignal(this.#textLayerDblClickAC); + this.#textLayer.div.addEventListener( + "pointerdown", + e => { + // It's the default value in Fenix: + // https://searchfox.org/mozilla-central/rev/beba5cde846f944c4d709e75cbe499d17af880a4/modules/libpref/init/StaticPrefList.yaml#19064 + // and in Chrome and Windows: + // https://source.chromium.org/chromium/chromium/src/+/main:ui/events/event_constants.h;drc=f0f5f3ceebb00da9363ccc7a1e2c0f17b6b383ba;l=115 + const DBL_CLICK_THRESHOLD = 500; + const { clientX, clientY, timeStamp } = e; + const lastPointerDownTimestamp = this.#lastPointerDownTimestamp; + if (timeStamp - lastPointerDownTimestamp > DBL_CLICK_THRESHOLD) { + this.#lastPointerDownTimestamp = timeStamp; + return; + } + this.#lastPointerDownTimestamp = -1; + const { classList } = this.div; + classList.toggle("getElements", true); + const elements = document.elementsFromPoint(clientX, clientY); + classList.toggle("getElements", false); + if (!this.div.contains(elements[0])) { + return; + } + let id; + const regex = new RegExp(`^${AnnotationEditorPrefix}[0-9]+$`); + for (const element of elements) { + if (regex.test(element.id)) { + id = element.id; + break; + } + } + if (!id) { + return; + } + const editor = this.#editors.get(id); + if (editor?.annotationElementId === null) { + e.stopPropagation(); + e.preventDefault(); + editor.dblclick(); + } + }, + { signal, capture: true } + ); + } const changedAnnotations = new Map(); const resetAnnotations = new Map(); for (const editor of this.#editors.values()) { diff --git a/test/integration/freetext_editor_spec.mjs b/test/integration/freetext_editor_spec.mjs index 342fc41d3..fada6eeb9 100644 --- a/test/integration/freetext_editor_spec.mjs +++ b/test/integration/freetext_editor_spec.mjs @@ -3357,4 +3357,56 @@ describe("FreeText Editor", () => { ); }); }); + + describe("Edit added Freetext annotation", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer"); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("must check that an added Freetext can be edited in double clicking on it", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await switchToFreeText(page); + + const rect = await getRect(page, ".annotationEditorLayer"); + const editorSelector = getEditorSelector(0); + + const data = "Hello PDF.js World !!"; + await page.mouse.click( + rect.x + rect.width / 2, + rect.y + rect.height / 2 + ); + await page.waitForSelector(editorSelector, { visible: true }); + await page.type(`${editorSelector} .internal`, data); + await commit(page); + await waitForSerialized(page, 1); + + await switchToFreeText(page, /* disable */ true); + + const modeChangedHandle = await createPromise(page, resolve => { + window.PDFViewerApplication.eventBus.on( + "annotationeditormodechanged", + resolve, + { once: true } + ); + }); + const editorRect = await getRect(page, editorSelector); + await page.mouse.click( + editorRect.x + editorRect.width / 2, + editorRect.y + editorRect.height / 2, + { count: 2 } + ); + + await page.waitForSelector(".annotationEditorLayer.freetextEditing"); + await awaitPromise(modeChangedHandle); + }) + ); + }); + }); }); diff --git a/web/annotation_editor_layer_builder.css b/web/annotation_editor_layer_builder.css index 7a915b059..b29873edd 100644 --- a/web/annotation_editor_layer_builder.css +++ b/web/annotation_editor_layer_builder.css @@ -134,6 +134,13 @@ &.drawing * { pointer-events: none !important; } + + &.getElements { + pointer-events: auto !important; + > div { + pointer-events: auto !important; + } + } } .annotationEditorLayer.waiting {