Merge pull request #20283 from calixteman/bug1989304
[Editor] Make sure the comment dialog is visible on the screen (bug 1989304)
This commit is contained in:
commit
225b07aa29
@ -193,12 +193,17 @@ class AnnotationEditorLayer {
|
|||||||
|
|
||||||
this.toggleAnnotationLayerPointerEvents(false);
|
this.toggleAnnotationLayerPointerEvents(false);
|
||||||
const { classList } = this.div;
|
const { classList } = this.div;
|
||||||
|
if (mode === AnnotationEditorType.POPUP) {
|
||||||
|
classList.toggle("commentEditing", true);
|
||||||
|
} else {
|
||||||
|
classList.toggle("commentEditing", false);
|
||||||
for (const editorType of AnnotationEditorLayer.#editorTypes.values()) {
|
for (const editorType of AnnotationEditorLayer.#editorTypes.values()) {
|
||||||
classList.toggle(
|
classList.toggle(
|
||||||
`${editorType._type}Editing`,
|
`${editorType._type}Editing`,
|
||||||
mode === editorType._editorType
|
mode === editorType._editorType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
this.div.hidden = false;
|
this.div.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
134
test/integration/comment_spec.mjs
Normal file
134
test/integration/comment_spec.mjs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/* Copyright 2025 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
closePages,
|
||||||
|
getEditorSelector,
|
||||||
|
getRect,
|
||||||
|
getSpanRectFromText,
|
||||||
|
loadAndWait,
|
||||||
|
scrollIntoView,
|
||||||
|
switchToEditor,
|
||||||
|
waitAndClick,
|
||||||
|
} from "./test_utils.mjs";
|
||||||
|
|
||||||
|
const switchToHighlight = switchToEditor.bind(null, "Highlight");
|
||||||
|
|
||||||
|
describe("Comment", () => {
|
||||||
|
describe("Comment edit dialog must be visible in ltr", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages = await loadAndWait(
|
||||||
|
"bug1989304.pdf",
|
||||||
|
".annotationEditorLayer",
|
||||||
|
"page-width",
|
||||||
|
null,
|
||||||
|
{ enableComment: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must set the comment dialog in the viewport (LTR)", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
await scrollIntoView(page, ".textLayer span:last-of-type");
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "...");
|
||||||
|
const x = rect.x + rect.width / 2;
|
||||||
|
const y = rect.y + rect.height / 2;
|
||||||
|
// Here and elsewhere, we add a small delay between press and release
|
||||||
|
// to make sure that a pointerup event is triggered after
|
||||||
|
// selectionchange.
|
||||||
|
// It works with a value of 1ms, but we use 100ms to be sure.
|
||||||
|
await page.mouse.click(x, y, { count: 2, delay: 100 });
|
||||||
|
await page.waitForSelector(getEditorSelector(0));
|
||||||
|
|
||||||
|
const commentButtonSelector = `${getEditorSelector(0)} button.comment`;
|
||||||
|
await waitAndClick(page, commentButtonSelector);
|
||||||
|
|
||||||
|
await page.waitForSelector("#commentManagerDialog", {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
const dialogRect = await getRect(page, "#commentManagerDialog");
|
||||||
|
const viewport = await page.evaluate(() => ({
|
||||||
|
width: document.documentElement.clientWidth,
|
||||||
|
height: document.documentElement.clientHeight,
|
||||||
|
}));
|
||||||
|
expect(dialogRect.x + dialogRect.width)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeLessThanOrEqual(viewport.width);
|
||||||
|
expect(dialogRect.y + dialogRect.height)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeLessThanOrEqual(viewport.height);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Comment edit dialog must be visible in rtl", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages = await loadAndWait(
|
||||||
|
"bug1989304.pdf",
|
||||||
|
".annotationEditorLayer",
|
||||||
|
"page-width",
|
||||||
|
null,
|
||||||
|
{ enableComment: true, localeProperties: "ar" }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must set the comment dialog in the viewport (RTL)", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
await scrollIntoView(page, ".textLayer span:nth-of-type(4)");
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "World");
|
||||||
|
const x = rect.x + rect.width / 2;
|
||||||
|
const y = rect.y + rect.height / 2;
|
||||||
|
await page.mouse.click(x, y, { count: 2, delay: 100 });
|
||||||
|
await page.waitForSelector(getEditorSelector(0));
|
||||||
|
|
||||||
|
const commentButtonSelector = `${getEditorSelector(0)} button.comment`;
|
||||||
|
await waitAndClick(page, commentButtonSelector);
|
||||||
|
|
||||||
|
await page.waitForSelector("#commentManagerDialog", {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
const dialogRect = await getRect(page, "#commentManagerDialog");
|
||||||
|
const viewport = await page.evaluate(() => ({
|
||||||
|
height: document.documentElement.clientHeight,
|
||||||
|
}));
|
||||||
|
expect(dialogRect.x + dialogRect.width)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeGreaterThanOrEqual(0);
|
||||||
|
expect(dialogRect.y + dialogRect.height)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeLessThanOrEqual(viewport.height);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -30,6 +30,7 @@ async function runTests(results) {
|
|||||||
"annotation_spec.mjs",
|
"annotation_spec.mjs",
|
||||||
"autolinker_spec.mjs",
|
"autolinker_spec.mjs",
|
||||||
"caret_browsing_spec.mjs",
|
"caret_browsing_spec.mjs",
|
||||||
|
"comment_spec.mjs",
|
||||||
"copy_paste_spec.mjs",
|
"copy_paste_spec.mjs",
|
||||||
"document_properties_spec.mjs",
|
"document_properties_spec.mjs",
|
||||||
"find_spec.mjs",
|
"find_spec.mjs",
|
||||||
|
|||||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -744,3 +744,4 @@
|
|||||||
!bug1980958.pdf
|
!bug1980958.pdf
|
||||||
!tracemonkey_annotation_on_page_8.pdf
|
!tracemonkey_annotation_on_page_8.pdf
|
||||||
!issue20232.pdf
|
!issue20232.pdf
|
||||||
|
!bug1989304.pdf
|
||||||
|
|||||||
BIN
test/pdfs/bug1989304.pdf
Executable file
BIN
test/pdfs/bug1989304.pdf
Executable file
Binary file not shown.
@ -368,6 +368,7 @@ const PDFViewerApplication = {
|
|||||||
docBaseUrl: x => x,
|
docBaseUrl: x => x,
|
||||||
enableAltText: x => x === "true",
|
enableAltText: x => x === "true",
|
||||||
enableAutoLinking: x => x === "true",
|
enableAutoLinking: x => x === "true",
|
||||||
|
enableComment: x => x === "true",
|
||||||
enableFakeMLManager: x => x === "true",
|
enableFakeMLManager: x => x === "true",
|
||||||
enableGuessAltText: x => x === "true",
|
enableGuessAltText: x => x === "true",
|
||||||
enablePermissions: x => x === "true",
|
enablePermissions: x => x === "true",
|
||||||
@ -380,6 +381,7 @@ const PDFViewerApplication = {
|
|||||||
forcePageColors: x => x === "true",
|
forcePageColors: x => x === "true",
|
||||||
pageColorsBackground: x => x,
|
pageColorsBackground: x => x,
|
||||||
pageColorsForeground: x => x,
|
pageColorsForeground: x => x,
|
||||||
|
localeProperties: x => ({ lang: x }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,8 @@
|
|||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 8px 16px 16px;
|
padding: 8px 16px 16px;
|
||||||
margin: 0;
|
margin-left: 0;
|
||||||
|
margin-top: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class CommentManager {
|
|||||||
dateStyle: "long",
|
dateStyle: "long",
|
||||||
});
|
});
|
||||||
this.dialogElement = commentDialog.dialog;
|
this.dialogElement = commentDialog.dialog;
|
||||||
this.#dialog = new CommentDialog(commentDialog, overlayManager);
|
this.#dialog = new CommentDialog(commentDialog, overlayManager, ltr);
|
||||||
this.#popup = new CommentPopup(dateFormat, ltr, this.dialogElement);
|
this.#popup = new CommentPopup(dateFormat, ltr, this.dialogElement);
|
||||||
this.#sidebar = new CommentSidebar(
|
this.#sidebar = new CommentSidebar(
|
||||||
sidebar,
|
sidebar,
|
||||||
@ -572,15 +572,19 @@ class CommentDialog {
|
|||||||
|
|
||||||
#dialogY = 0;
|
#dialogY = 0;
|
||||||
|
|
||||||
|
#isLTR;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ dialog, toolbar, title, textInput, cancelButton, saveButton },
|
{ dialog, toolbar, title, textInput, cancelButton, saveButton },
|
||||||
overlayManager
|
overlayManager,
|
||||||
|
ltr
|
||||||
) {
|
) {
|
||||||
this.#dialog = dialog;
|
this.#dialog = dialog;
|
||||||
this.#textInput = textInput;
|
this.#textInput = textInput;
|
||||||
this.#overlayManager = overlayManager;
|
this.#overlayManager = overlayManager;
|
||||||
this.#saveButton = saveButton;
|
this.#saveButton = saveButton;
|
||||||
this.#title = title;
|
this.#title = title;
|
||||||
|
this.#isLTR = ltr;
|
||||||
|
|
||||||
const finishBound = this.#finish.bind(this);
|
const finishBound = this.#finish.bind(this);
|
||||||
dialog.addEventListener("close", finishBound);
|
dialog.addEventListener("close", finishBound);
|
||||||
@ -682,7 +686,38 @@ class CommentDialog {
|
|||||||
}
|
}
|
||||||
this.#uiManager?.removeEditListeners();
|
this.#uiManager?.removeEditListeners();
|
||||||
this.#saveButton.disabled = true;
|
this.#saveButton.disabled = true;
|
||||||
|
const parentDimensions = options?.parentDimensions;
|
||||||
|
if (editor.hasDefaultPopupPosition()) {
|
||||||
|
const { dialogWidth, dialogHeight } = this._dialogDimensions;
|
||||||
|
if (parentDimensions) {
|
||||||
|
if (
|
||||||
|
this.#isLTR &&
|
||||||
|
posX + dialogWidth >
|
||||||
|
Math.min(
|
||||||
|
parentDimensions.x + parentDimensions.width,
|
||||||
|
window.innerWidth
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const buttonWidth = this.#editor.commentButtonWidth;
|
||||||
|
posX -= dialogWidth - buttonWidth * parentDimensions.width;
|
||||||
|
} else if (!this.#isLTR) {
|
||||||
|
const buttonWidth =
|
||||||
|
this.#editor.commentButtonWidth * parentDimensions.width;
|
||||||
|
if (posX - dialogWidth < Math.max(0, parentDimensions.x)) {
|
||||||
|
posX = Math.max(0, posX);
|
||||||
|
} else {
|
||||||
|
posX -= dialogWidth - buttonWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const height = Math.max(dialogHeight, options?.height || 0);
|
||||||
|
if (posY + height > window.innerHeight) {
|
||||||
|
posY = window.innerHeight - height;
|
||||||
|
}
|
||||||
|
if (posY < 0) {
|
||||||
|
posY = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.#setPosition(posX, posY);
|
this.#setPosition(posX, posY);
|
||||||
|
|
||||||
await this.#overlayManager.open(this.#dialog);
|
await this.#overlayManager.open(this.#dialog);
|
||||||
@ -694,14 +729,17 @@ class CommentDialog {
|
|||||||
this.#finish();
|
this.#finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
get _dialogWidth() {
|
get _dialogDimensions() {
|
||||||
const dialog = this.#dialog;
|
const dialog = this.#dialog;
|
||||||
const { style } = dialog;
|
const { style } = dialog;
|
||||||
style.opacity = "0";
|
style.opacity = "0";
|
||||||
style.display = "block";
|
style.display = "block";
|
||||||
const width = dialog.getBoundingClientRect().width;
|
const { width, height } = dialog.getBoundingClientRect();
|
||||||
style.opacity = style.display = "";
|
style.opacity = style.display = "";
|
||||||
return shadow(this, "_dialogWidth", width);
|
return shadow(this, "_dialogDimensions", {
|
||||||
|
dialogWidth: width,
|
||||||
|
dialogHeight: height,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#setPosition(x, y) {
|
#setPosition(x, y) {
|
||||||
@ -1021,7 +1059,7 @@ class CommentPopup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#setPosition(x, y, correctPosition = true) {
|
#setPosition(x, y, correctPosition) {
|
||||||
if (!correctPosition) {
|
if (!correctPosition) {
|
||||||
this.#editor.commentPopupPosition = [x, y];
|
this.#editor.commentPopupPosition = [x, y];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user