[Annotation] In reading mode with new commment stuff enabled, use the comment popup for annotations without a popup but with some contents (bug 1991029)

This commit is contained in:
Calixte Denizet 2025-09-26 18:23:55 +02:00
parent 91384738f8
commit 7fc7b79cd0
3 changed files with 151 additions and 57 deletions

View File

@ -718,9 +718,6 @@ class AnnotationElement {
* @memberof AnnotationElement * @memberof AnnotationElement
*/ */
_createPopup(popupData = null) { _createPopup(popupData = null) {
if (this.parent._commentManager) {
return;
}
const { data } = this; const { data } = this;
let contentsObj, modificationDate; let contentsObj, modificationDate;
@ -750,13 +747,19 @@ class AnnotationElement {
parent: this.parent, parent: this.parent,
elements: [this], elements: [this],
})); }));
this.parent.div.append(popup.render()); if (!this.parent._commentManager) {
this.parent.div.append(popup.render());
}
} }
get hasPopupElement() { get hasPopupElement() {
return !!(this.#popupElement || this.popup || this.data.popupRef); return !!(this.#popupElement || this.popup || this.data.popupRef);
} }
get extraPopupElement() {
return this.#popupElement;
}
/** /**
* Render the annotation's HTML element(s). * Render the annotation's HTML element(s).
* *
@ -2410,10 +2413,12 @@ class PopupElement {
// consistent with other viewers such as Adobe Acrobat. // consistent with other viewers such as Adobe Acrobat.
this.#dateObj = PDFDateString.toDateObject(modificationDate); this.#dateObj = PDFDateString.toDateObject(modificationDate);
// The elements that will trigger the popup.
this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup());
if (commentManager) { if (commentManager) {
this.#renderCommentButton(); this.renderCommentButton();
} else { } else {
this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup());
this.#addEventListeners(); this.#addEventListeners();
this.#container.hidden = true; this.#container.hidden = true;
@ -2443,8 +2448,8 @@ class PopupElement {
// Attach the event listeners to the trigger element. // Attach the event listeners to the trigger element.
for (const element of this.trigger) { for (const element of this.trigger) {
element.addEventListener("click", this.#boundToggle, { signal }); element.addEventListener("click", this.#boundToggle, { signal });
element.addEventListener("mouseenter", this.#boundShow, { signal }); element.addEventListener("pointerenter", this.#boundShow, { signal });
element.addEventListener("mouseleave", this.#boundHide, { signal }); element.addEventListener("pointerleave", this.#boundHide, { signal });
element.classList.add("popupTriggerArea"); element.classList.add("popupTriggerArea");
} }
@ -2466,7 +2471,7 @@ class PopupElement {
); );
} }
#renderCommentButton() { renderCommentButton() {
if (this.#commentButton) { if (this.#commentButton) {
return; return;
} }
@ -2479,53 +2484,67 @@ class PopupElement {
return; return;
} }
const button = (this.#commentButton = document.createElement("button"));
button.className = "annotationCommentButton";
const parentContainer = this.#firstElement.container;
button.style.zIndex = parentContainer.style.zIndex + 1;
button.tabIndex = 0;
button.ariaHasPopup = "dialog";
button.ariaControls = "commentPopup";
button.setAttribute("data-l10n-id", "pdfjs-show-comment-button");
const { signal } = (this.#popupAbortController = new AbortController()); const { signal } = (this.#popupAbortController = new AbortController());
button.addEventListener("keydown", this.#boundKeyDown, { signal }); const hasOwnButton = !!this.#firstElement.extraPopupElement;
button.addEventListener( const togglePopup = () => {
"click", this.#commentManager.toggleCommentPopup(
() => { this,
this.#commentManager.toggleCommentPopup(this, /* isSelected = */ true); /* isSelected = */ true,
}, /* visibility = */ undefined,
{ signal } /* isEditable = */ !hasOwnButton
); );
button.addEventListener( };
"pointerenter", const showPopup = () => {
() => { this.#commentManager.toggleCommentPopup(
this.#commentManager.toggleCommentPopup( this,
this, /* isSelected = */ false,
/* isSelected = */ false, /* visibility = */ true,
/* visibility = */ true /* isEditable = */ !hasOwnButton
); );
}, };
{ signal } const hidePopup = () => {
); this.#commentManager.toggleCommentPopup(
button.addEventListener( this,
"pointerleave", /* isSelected = */ false,
() => { /* visibility = */ false
this.#commentManager.toggleCommentPopup( );
this, };
/* isSelected = */ false,
/* visibility = */ false if (!hasOwnButton) {
); const button = (this.#commentButton = document.createElement("button"));
}, button.className = "annotationCommentButton";
{ signal } const parentContainer = this.#firstElement.container;
); button.style.zIndex = parentContainer.style.zIndex + 1;
this.#updateColor(); button.tabIndex = 0;
this.#updateCommentButtonPosition(); button.ariaHasPopup = "dialog";
parentContainer.after(button); button.ariaControls = "commentPopup";
button.setAttribute("data-l10n-id", "pdfjs-show-comment-button");
this.#updateColor();
this.#updateCommentButtonPosition();
button.addEventListener("keydown", this.#boundKeyDown, { signal });
button.addEventListener("click", togglePopup, { signal });
button.addEventListener("pointerenter", showPopup, { signal });
button.addEventListener("pointerleave", hidePopup, { signal });
parentContainer.after(button);
} else {
this.#commentButton = this.#firstElement.container;
for (const element of this.trigger) {
element.ariaHasPopup = "dialog";
element.ariaControls = "commentPopup";
element.addEventListener("keydown", this.#boundKeyDown, { signal });
element.addEventListener("click", togglePopup, { signal });
element.addEventListener("pointerenter", showPopup, { signal });
element.addEventListener("pointerleave", hidePopup, { signal });
element.classList.add("popupTriggerArea");
}
}
} }
#updateCommentButtonPosition() { #updateCommentButtonPosition() {
this.#renderCommentButton(); if (this.#firstElement.extraPopupElement) {
return;
}
this.renderCommentButton();
const [x, y] = this.#commentButtonPosition; const [x, y] = this.#commentButtonPosition;
const { style } = this.#commentButton; const { style } = this.#commentButton;
style.left = `calc(${x}%)`; style.left = `calc(${x}%)`;
@ -2533,7 +2552,10 @@ class PopupElement {
} }
#updateColor() { #updateColor() {
this.#renderCommentButton(); if (this.#firstElement.extraPopupElement) {
return;
}
this.renderCommentButton();
this.#commentButton.style.backgroundColor = this.commentButtonColor || ""; this.#commentButton.style.backgroundColor = this.commentButtonColor || "";
} }
@ -3792,6 +3814,7 @@ class AnnotationLayer {
rendered.style.visibility = "hidden"; rendered.style.visibility = "hidden";
} }
await this.#appendElement(rendered, data.id, elementParams.elements); await this.#appendElement(rendered, data.id, elementParams.elements);
element.extraPopupElement?.popup?.renderCommentButton();
if (element._isEditable) { if (element._isEditable) {
this.#editableAnnotations.set(element.data.id, element); this.#editableAnnotations.set(element.data.id, element);

View File

@ -810,4 +810,72 @@ a dynamic compiler for JavaScript based on our`);
}); });
}); });
}); });
describe("Annotation without popup and enableComment set to true", () => {
describe("annotation-text-without-popup.pdf", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"annotation-text-without-popup.pdf",
getAnnotationSelector("4R"),
"page-fit",
null,
{ enableComment: true }
);
});
afterEach(async () => {
await closePages(pages);
});
it("must check that the popup is shown", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const rect = await getRect(page, getAnnotationSelector("4R"));
// Hover the annotation, the popup should be visible.
let promisePopup = page.waitForSelector("#commentPopup", {
visible: true,
});
await page.mouse.move(
rect.x + rect.width / 2,
rect.y + rect.height / 2
);
await promisePopup;
// Move the mouse away, the popup should be hidden.
promisePopup = page.waitForSelector("#commentPopup", {
visible: false,
});
await page.mouse.move(
rect.x - rect.width / 2,
rect.y - rect.height / 2
);
await promisePopup;
// Click the annotation, the popup should be visible.
promisePopup = page.waitForSelector("#commentPopup", {
visible: true,
});
await page.mouse.click(
rect.x + rect.width / 2,
rect.y + rect.height / 2
);
await promisePopup;
// Click again, the popup should be hidden.
promisePopup = page.waitForSelector("#commentPopup", {
visible: false,
});
await page.mouse.click(
rect.x + rect.width / 2,
rect.y + rect.height / 2
);
await promisePopup;
})
);
});
});
});
}); });

View File

@ -92,11 +92,11 @@ class CommentManager {
this.#sidebar.updateComment(annotation); this.#sidebar.updateComment(annotation);
} }
toggleCommentPopup(editor, isSelected, visibility) { toggleCommentPopup(editor, isSelected, visibility, isEditable) {
if (isSelected) { if (isSelected) {
this.selectComment(editor.uid); this.selectComment(editor.uid);
} }
this.#popup.toggle(editor, isSelected, visibility); this.#popup.toggle(editor, isSelected, visibility, isEditable);
} }
destroyPopup() { destroyPopup() {
@ -880,6 +880,8 @@ class CommentDialog {
} }
class CommentPopup { class CommentPopup {
#buttonsContainer = null;
#commentDialog; #commentDialog;
#dateFormat; #dateFormat;
@ -954,7 +956,7 @@ class CommentPopup {
const time = (this.#time = document.createElement("time")); const time = (this.#time = document.createElement("time"));
time.className = "commentPopupTime"; time.className = "commentPopupTime";
const buttons = document.createElement("div"); const buttons = (this.#buttonsContainer = document.createElement("div"));
buttons.className = "commentPopupButtons"; buttons.className = "commentPopupButtons";
const edit = document.createElement("button"); const edit = document.createElement("button");
edit.classList.add("commentPopupEdit", "toolbarButton"); edit.classList.add("commentPopupEdit", "toolbarButton");
@ -1089,7 +1091,7 @@ class CommentPopup {
this.sidebar.selectComment(null); this.sidebar.selectComment(null);
} }
toggle(editor, isSelected, visibility = undefined) { toggle(editor, isSelected, visibility = undefined, isEditable = true) {
if (!editor) { if (!editor) {
this.destroy(); this.destroy();
return; return;
@ -1119,6 +1121,7 @@ class CommentPopup {
} }
const container = this.#createPopup(); const container = this.#createPopup();
this.#buttonsContainer.classList.toggle("hidden", !isEditable);
container.classList.toggle("hidden", false); container.classList.toggle("hidden", false);
container.classList.toggle("selected", isSelected); container.classList.toggle("selected", isSelected);
this.#selected = isSelected; this.#selected = isSelected;