mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-28 18:07:21 +02:00
Merge pull request #18900 from ryzokuken/popup-undo-annotation
Annotation deletion popup (bug 1899731)
This commit is contained in:
commit
a5ce71247b
@ -1148,6 +1148,7 @@ function buildComponents(defines, dir) {
|
|||||||
"web/images/messageBar_*.svg",
|
"web/images/messageBar_*.svg",
|
||||||
"web/images/toolbarButton-{editorHighlight,menuArrow}.svg",
|
"web/images/toolbarButton-{editorHighlight,menuArrow}.svg",
|
||||||
"web/images/cursor-*.svg",
|
"web/images/cursor-*.svg",
|
||||||
|
"web/images/secondaryToolbarButton-documentProperties.svg",
|
||||||
];
|
];
|
||||||
|
|
||||||
return ordered([
|
return ordered([
|
||||||
|
|||||||
@ -503,3 +503,24 @@ pdfjs-editor-alt-text-settings-editor-title = Alt text editor
|
|||||||
pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image
|
pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image
|
||||||
pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.
|
pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.
|
||||||
pdfjs-editor-alt-text-settings-close-button = Close
|
pdfjs-editor-alt-text-settings-close-button = Close
|
||||||
|
|
||||||
|
## "Annotations removed" bar
|
||||||
|
|
||||||
|
pdfjs-editor-undo-bar-message-highlight = Highlight removed
|
||||||
|
pdfjs-editor-undo-bar-message-freetext = Text removed
|
||||||
|
pdfjs-editor-undo-bar-message-ink = Drawing removed
|
||||||
|
pdfjs-editor-undo-bar-message-stamp = Image removed
|
||||||
|
# Variables:
|
||||||
|
# $count (Number) - the number of removed annotations.
|
||||||
|
pdfjs-editor-undo-bar-message-multiple =
|
||||||
|
{ $count ->
|
||||||
|
[one] { $count } annotation removed
|
||||||
|
*[other] { $count } annotations removed
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfjs-editor-undo-bar-undo-button =
|
||||||
|
.title = Undo
|
||||||
|
pdfjs-editor-undo-bar-undo-button-label = Undo
|
||||||
|
pdfjs-editor-undo-bar-close-button =
|
||||||
|
.title = Close
|
||||||
|
pdfjs-editor-undo-bar-close-button-label = Close
|
||||||
|
|||||||
@ -676,6 +676,7 @@ class DrawingEditor extends AnnotationEditor {
|
|||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
parent.toggleDrawing();
|
parent.toggleDrawing();
|
||||||
|
uiManager._editorUndoBar?.hide();
|
||||||
|
|
||||||
if (this._currentDraw) {
|
if (this._currentDraw) {
|
||||||
parent.drawLayer.updateProperties(
|
parent.drawLayer.updateProperties(
|
||||||
|
|||||||
@ -1142,6 +1142,8 @@ class AnnotationEditor {
|
|||||||
|
|
||||||
bindEvents(this, this.div, ["pointerdown"]);
|
bindEvents(this, this.div, ["pointerdown"]);
|
||||||
|
|
||||||
|
this._uiManager._editorUndoBar?.hide();
|
||||||
|
|
||||||
return this.div;
|
return this.div;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -620,6 +620,8 @@ class AnnotationEditorUIManager {
|
|||||||
|
|
||||||
#editorsToRescale = new Set();
|
#editorsToRescale = new Set();
|
||||||
|
|
||||||
|
_editorUndoBar = null;
|
||||||
|
|
||||||
#enableHighlightFloatingButton = false;
|
#enableHighlightFloatingButton = false;
|
||||||
|
|
||||||
#enableUpdatedAddImage = false;
|
#enableUpdatedAddImage = false;
|
||||||
@ -829,7 +831,8 @@ class AnnotationEditorUIManager {
|
|||||||
enableHighlightFloatingButton,
|
enableHighlightFloatingButton,
|
||||||
enableUpdatedAddImage,
|
enableUpdatedAddImage,
|
||||||
enableNewAltTextWhenAddingImage,
|
enableNewAltTextWhenAddingImage,
|
||||||
mlManager
|
mlManager,
|
||||||
|
editorUndoBar
|
||||||
) {
|
) {
|
||||||
const signal = (this._signal = this.#abortController.signal);
|
const signal = (this._signal = this.#abortController.signal);
|
||||||
this.#container = container;
|
this.#container = container;
|
||||||
@ -864,6 +867,7 @@ class AnnotationEditorUIManager {
|
|||||||
rotation: 0,
|
rotation: 0,
|
||||||
};
|
};
|
||||||
this.isShiftKeyDown = false;
|
this.isShiftKeyDown = false;
|
||||||
|
this._editorUndoBar = editorUndoBar || null;
|
||||||
|
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||||
Object.defineProperty(this, "reset", {
|
Object.defineProperty(this, "reset", {
|
||||||
@ -904,6 +908,7 @@ class AnnotationEditorUIManager {
|
|||||||
clearTimeout(this.#translationTimeoutId);
|
clearTimeout(this.#translationTimeoutId);
|
||||||
this.#translationTimeoutId = null;
|
this.#translationTimeoutId = null;
|
||||||
}
|
}
|
||||||
|
this._editorUndoBar?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
combinedSignal(ac) {
|
combinedSignal(ac) {
|
||||||
@ -1656,6 +1661,8 @@ class AnnotationEditorUIManager {
|
|||||||
this.setEditingState(false);
|
this.setEditingState(false);
|
||||||
this.#disableAll();
|
this.#disableAll();
|
||||||
|
|
||||||
|
this._editorUndoBar?.hide();
|
||||||
|
|
||||||
this.#updateModeCapability.resolve();
|
this.#updateModeCapability.resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2038,6 +2045,7 @@ class AnnotationEditorUIManager {
|
|||||||
hasSomethingToRedo: true,
|
hasSomethingToRedo: true,
|
||||||
isEmpty: this.#isEmpty(),
|
isEmpty: this.#isEmpty(),
|
||||||
});
|
});
|
||||||
|
this._editorUndoBar?.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2099,6 +2107,10 @@ class AnnotationEditorUIManager {
|
|||||||
? [drawingEditor]
|
? [drawingEditor]
|
||||||
: [...this.#selectedEditors];
|
: [...this.#selectedEditors];
|
||||||
const cmd = () => {
|
const cmd = () => {
|
||||||
|
this._editorUndoBar?.show(
|
||||||
|
undo,
|
||||||
|
editors.length === 1 ? editors[0].editorType : editors.length
|
||||||
|
);
|
||||||
for (const editor of editors) {
|
for (const editor of editors) {
|
||||||
editor.remove();
|
editor.remove();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3670,4 +3670,125 @@ describe("FreeText Editor", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Undo deletion popup has the expected behaviour", () => {
|
||||||
|
let pages;
|
||||||
|
const editorSelector = getEditorSelector(0);
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that deleting a FreeText editor can be undone using the undo button", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToFreeText(page);
|
||||||
|
|
||||||
|
const rect = await getRect(page, ".annotationEditorLayer");
|
||||||
|
const data = "Hello PDF.js World !!";
|
||||||
|
await page.mouse.click(rect.x + 100, rect.y + 100);
|
||||||
|
await page.waitForSelector(editorSelector, {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
await page.type(`${editorSelector} .internal`, data);
|
||||||
|
|
||||||
|
// Commit.
|
||||||
|
await page.keyboard.press("Escape");
|
||||||
|
await page.waitForSelector(`${editorSelector} .overlay.enabled`);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
await page.click("#editorUndoBarUndoButton");
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the undo deletion popup displays the correct message", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToFreeText(page);
|
||||||
|
|
||||||
|
const rect = await getRect(page, ".annotationEditorLayer");
|
||||||
|
const data = "Hello PDF.js World !!";
|
||||||
|
await page.mouse.click(rect.x + 100, rect.y + 100);
|
||||||
|
await page.waitForSelector(editorSelector, {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
await page.type(`${editorSelector} .internal`, data);
|
||||||
|
|
||||||
|
// Commit.
|
||||||
|
await page.keyboard.press("Escape");
|
||||||
|
await page.waitForSelector(`${editorSelector} .overlay.enabled`);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForFunction(() => {
|
||||||
|
const messageElement = document.querySelector(
|
||||||
|
"#editorUndoBarMessage"
|
||||||
|
);
|
||||||
|
return messageElement && messageElement.textContent.trim() !== "";
|
||||||
|
});
|
||||||
|
const message = await page.waitForSelector("#editorUndoBarMessage");
|
||||||
|
const messageText = await page.evaluate(
|
||||||
|
el => el.textContent,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
expect(messageText).toContain("Text removed");
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when a new textbox is created", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToFreeText(page);
|
||||||
|
|
||||||
|
let rect = await getRect(page, ".annotationEditorLayer");
|
||||||
|
const data = "Hello PDF.js World !!";
|
||||||
|
await page.mouse.click(rect.x + 100, rect.y + 100);
|
||||||
|
await page.waitForSelector(editorSelector, {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
await page.type(`${editorSelector} .internal`, data);
|
||||||
|
|
||||||
|
await page.keyboard.press("Escape");
|
||||||
|
await page.waitForSelector(`${editorSelector} .overlay.enabled`);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
rect = await getRect(page, ".annotationEditorLayer");
|
||||||
|
const newData = "This is a new text box!";
|
||||||
|
await page.mouse.click(rect.x + 150, rect.y + 150);
|
||||||
|
await page.waitForSelector(getEditorSelector(1), {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
await page.type(`${getEditorSelector(1)} .internal`, newData);
|
||||||
|
|
||||||
|
await page.keyboard.press("Escape");
|
||||||
|
await page.waitForSelector(
|
||||||
|
`${getEditorSelector(1)} .overlay.enabled`
|
||||||
|
);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import {
|
|||||||
kbBigMoveUp,
|
kbBigMoveUp,
|
||||||
kbFocusNext,
|
kbFocusNext,
|
||||||
kbFocusPrevious,
|
kbFocusPrevious,
|
||||||
|
kbSave,
|
||||||
kbSelectAll,
|
kbSelectAll,
|
||||||
kbUndo,
|
kbUndo,
|
||||||
loadAndWait,
|
loadAndWait,
|
||||||
@ -37,6 +38,11 @@ import {
|
|||||||
waitForSelectedEditor,
|
waitForSelectedEditor,
|
||||||
waitForSerialized,
|
waitForSerialized,
|
||||||
} from "./test_utils.mjs";
|
} from "./test_utils.mjs";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
const selectAll = async page => {
|
const selectAll = async page => {
|
||||||
await kbSelectAll(page);
|
await kbSelectAll(page);
|
||||||
@ -2165,4 +2171,446 @@ describe("Highlight Editor", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Undo deletion popup has the expected behaviour", () => {
|
||||||
|
let pages;
|
||||||
|
const editorSelector = getEditorSelector(0);
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages = await loadAndWait(
|
||||||
|
"tracemonkey.pdf",
|
||||||
|
".annotationEditorLayer",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
highlightEditorColors:
|
||||||
|
"yellow=#FFFF00,green=#00FF00,blue=#0000FF,pink=#FF00FF,red=#FF0000",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that deleting a highlight can be undone using the undo button", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.click("#editorUndoBarUndoButton");
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
await page.waitForSelector(
|
||||||
|
`.page[data-page-number = "1"] svg.highlight[fill = "#FFFF00"]`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when the undo button is clicked", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.click("#editorUndoBarUndoButton");
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when the close button is clicked", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.waitForSelector("#editorUndoBarCloseButton");
|
||||||
|
await page.click("#editorUndoBarCloseButton");
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when a new annotation is created", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
const newRect = await getSpanRectFromText(page, 1, "Introduction");
|
||||||
|
const newX = newRect.x + newRect.width / 2;
|
||||||
|
const newY = newRect.y + newRect.height / 2;
|
||||||
|
await page.mouse.click(newX, newY, { count: 2, delay: 100 });
|
||||||
|
|
||||||
|
await page.waitForSelector(getEditorSelector(1));
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when the print dialog is opened", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.evaluate(() => window.print());
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when the user clicks on the print button", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.click("#printButton");
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when the save dialog is opened", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await kbSave(page);
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when an option from the secondaryToolbar is used", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.click("#secondaryToolbarToggleButton");
|
||||||
|
await page.click("#lastPage");
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when highlight mode is disabled", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await switchToHighlight(page, /* disable */ true);
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when a PDF is drag-and-dropped", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
const pdfPath = path.join(__dirname, "../pdfs/basicapi.pdf");
|
||||||
|
const pdfData = fs.readFileSync(pdfPath).toString("base64");
|
||||||
|
const dataTransfer = await page.evaluateHandle(data => {
|
||||||
|
const transfer = new DataTransfer();
|
||||||
|
const view = Uint8Array.from(atob(data), code =>
|
||||||
|
code.charCodeAt(0)
|
||||||
|
);
|
||||||
|
const file = new File([view], "basicapi.pdf", {
|
||||||
|
type: "application/pdf",
|
||||||
|
});
|
||||||
|
transfer.items.add(file);
|
||||||
|
return transfer;
|
||||||
|
}, pdfData);
|
||||||
|
|
||||||
|
const dropSelector = "#viewer";
|
||||||
|
await page.evaluate(
|
||||||
|
(transfer, selector) => {
|
||||||
|
const dropTarget = document.querySelector(selector);
|
||||||
|
const event = new DragEvent("dragstart", {
|
||||||
|
dataTransfer: transfer,
|
||||||
|
});
|
||||||
|
dropTarget.dispatchEvent(event);
|
||||||
|
},
|
||||||
|
dataTransfer,
|
||||||
|
dropSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.evaluate(
|
||||||
|
(transfer, selector) => {
|
||||||
|
const dropTarget = document.querySelector(selector);
|
||||||
|
const event = new DragEvent("drop", {
|
||||||
|
dataTransfer: transfer,
|
||||||
|
bubbles: true,
|
||||||
|
});
|
||||||
|
dropTarget.dispatchEvent(event);
|
||||||
|
},
|
||||||
|
dataTransfer,
|
||||||
|
dropSelector
|
||||||
|
);
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the undo deletion popup displays the correct message", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForFunction(() => {
|
||||||
|
const messageElement = document.querySelector(
|
||||||
|
"#editorUndoBarMessage"
|
||||||
|
);
|
||||||
|
return messageElement && messageElement.textContent.trim() !== "";
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = await page.waitForSelector("#editorUndoBarMessage");
|
||||||
|
const messageText = await page.evaluate(
|
||||||
|
el => el.textContent,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
expect(messageText).toContain("Highlight removed");
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must display correct message for multiple highlights", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
let rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
let x = rect.x + rect.width / 2;
|
||||||
|
let y = rect.y + rect.height / 2;
|
||||||
|
await page.mouse.click(x, y, { count: 2, delay: 100 });
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
|
||||||
|
rect = await getSpanRectFromText(page, 1, "Languages");
|
||||||
|
x = rect.x + rect.width / 2;
|
||||||
|
y = rect.y + rect.height / 2;
|
||||||
|
await page.mouse.click(x, y, { count: 2, delay: 100 });
|
||||||
|
await page.waitForSelector(getEditorSelector(1));
|
||||||
|
|
||||||
|
await selectAll(page);
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForFunction(() => {
|
||||||
|
const messageElement = document.querySelector(
|
||||||
|
"#editorUndoBarMessage"
|
||||||
|
);
|
||||||
|
return messageElement && messageElement.textContent.trim() !== "";
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = await page.waitForSelector("#editorUndoBarMessage");
|
||||||
|
const messageText = await page.evaluate(
|
||||||
|
el => el.textContent,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cleans the message text by removing all non-ASCII characters.
|
||||||
|
// It eliminates any invisible characters such as directional marks
|
||||||
|
// that interfere with string comparisons
|
||||||
|
const cleanMessage = messageText.replaceAll(/\P{ASCII}/gu, "");
|
||||||
|
expect(cleanMessage).toContain(`2 annotations removed`);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must work properly when selecting undo by keyboard", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.focus("#editorUndoBarUndoButton"); // we have to simulate focus like this to avoid the wait
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
await page.waitForSelector(
|
||||||
|
`.page[data-page-number = "1"] svg.highlight[fill = "#FFFF00"]`
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.focus("#editorUndoBarUndoButton"); // we have to simulate focus like this to avoid the wait
|
||||||
|
await page.keyboard.press(" ");
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
await page.waitForSelector(
|
||||||
|
`.page[data-page-number = "1"] svg.highlight[fill = "#FFFF00"]`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must dismiss itself when user presses space/enter key and undo key isn't focused", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToHighlight(page);
|
||||||
|
|
||||||
|
const rect = await getSpanRectFromText(page, 1, "Abstract");
|
||||||
|
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(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.focus("#editorUndoBar");
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -829,4 +829,130 @@ describe("Ink Editor", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Undo deletion popup has the expected behaviour", () => {
|
||||||
|
let pages;
|
||||||
|
const editorSelector = getEditorSelector(0);
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that deleting a drawing can be undone using the undo button", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToInk(page);
|
||||||
|
|
||||||
|
const rect = await getRect(page, ".annotationEditorLayer");
|
||||||
|
const xStart = rect.x + 300;
|
||||||
|
const yStart = rect.y + 300;
|
||||||
|
const clickHandle = await waitForPointerUp(page);
|
||||||
|
await page.mouse.move(xStart, yStart);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(xStart + 50, yStart + 50);
|
||||||
|
await page.mouse.up();
|
||||||
|
await awaitPromise(clickHandle);
|
||||||
|
await commit(page);
|
||||||
|
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
await page.click("#editorUndoBarUndoButton");
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the undo deletion popup displays the correct message", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToInk(page);
|
||||||
|
|
||||||
|
const rect = await getRect(page, ".annotationEditorLayer");
|
||||||
|
const xStart = rect.x + 300;
|
||||||
|
const yStart = rect.y + 300;
|
||||||
|
const clickHandle = await waitForPointerUp(page);
|
||||||
|
await page.mouse.move(xStart, yStart);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(xStart + 50, yStart + 50);
|
||||||
|
await page.mouse.up();
|
||||||
|
await awaitPromise(clickHandle);
|
||||||
|
await commit(page);
|
||||||
|
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForFunction(() => {
|
||||||
|
const messageElement = document.querySelector(
|
||||||
|
"#editorUndoBarMessage"
|
||||||
|
);
|
||||||
|
return messageElement && messageElement.textContent.trim() !== "";
|
||||||
|
});
|
||||||
|
const message = await page.waitForSelector("#editorUndoBarMessage");
|
||||||
|
const messageText = await page.evaluate(
|
||||||
|
el => el.textContent,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
expect(messageText).toContain("Drawing removed");
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when a new drawing is created", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToInk(page);
|
||||||
|
|
||||||
|
const rect = await getRect(page, ".annotationEditorLayer");
|
||||||
|
const xStart = rect.x + 300;
|
||||||
|
const yStart = rect.y + 300;
|
||||||
|
const clickHandle = await waitForPointerUp(page);
|
||||||
|
await page.mouse.move(xStart, yStart);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(xStart + 50, yStart + 50);
|
||||||
|
await page.mouse.up();
|
||||||
|
await awaitPromise(clickHandle);
|
||||||
|
await commit(page);
|
||||||
|
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
const newRect = await getRect(page, ".annotationEditorLayer");
|
||||||
|
const newXStart = newRect.x + 300;
|
||||||
|
const newYStart = newRect.y + 300;
|
||||||
|
const newClickHandle = await waitForPointerUp(page);
|
||||||
|
await page.mouse.move(newXStart, newYStart);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(newXStart + 50, newYStart + 50);
|
||||||
|
await page.mouse.up();
|
||||||
|
await awaitPromise(newClickHandle);
|
||||||
|
await commit(page);
|
||||||
|
|
||||||
|
await page.waitForSelector(getEditorSelector(1));
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
await page.waitForSelector(getEditorSelector(1));
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1530,4 +1530,98 @@ describe("Stamp Editor", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Undo deletion popup has the expected behaviour", () => {
|
||||||
|
let pages;
|
||||||
|
const editorSelector = getEditorSelector(0);
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages = await loadAndWait("tracemonkey.pdf", ".annotationEditorLayer");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that deleting an image can be undone using the undo button", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToStamp(page);
|
||||||
|
const selector = editorSelector;
|
||||||
|
|
||||||
|
await copyImage(page, "../images/firefox_logo.png", 0);
|
||||||
|
await page.waitForSelector(selector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${selector} button.delete`);
|
||||||
|
await page.click(`${selector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
|
||||||
|
await page.click("#editorUndoBarUndoButton");
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
await page.waitForSelector(editorSelector);
|
||||||
|
await page.waitForSelector(`${selector} canvas`);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the undo deletion popup displays the correct message", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToStamp(page);
|
||||||
|
const selector = editorSelector;
|
||||||
|
|
||||||
|
await copyImage(page, "../images/firefox_logo.png", 0);
|
||||||
|
await page.waitForSelector(selector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${selector} button.delete`);
|
||||||
|
await page.click(`${selector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForFunction(() => {
|
||||||
|
const messageElement = document.querySelector(
|
||||||
|
"#editorUndoBarMessage"
|
||||||
|
);
|
||||||
|
return messageElement && messageElement.textContent.trim() !== "";
|
||||||
|
});
|
||||||
|
const message = await page.waitForSelector("#editorUndoBarMessage");
|
||||||
|
const messageText = await page.evaluate(
|
||||||
|
el => el.textContent,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
expect(messageText).toContain("Image removed");
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check that the popup disappears when a new image is inserted", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await switchToStamp(page);
|
||||||
|
const selector = editorSelector;
|
||||||
|
|
||||||
|
await copyImage(page, "../images/firefox_logo.png", 0);
|
||||||
|
await page.waitForSelector(selector);
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
|
||||||
|
await page.waitForSelector(`${editorSelector} button.delete`);
|
||||||
|
await page.click(`${editorSelector} button.delete`);
|
||||||
|
await waitForSerialized(page, 0);
|
||||||
|
|
||||||
|
await page.waitForSelector("#editorUndoBar:not([hidden])");
|
||||||
|
await page.click("#editorStampAddImage");
|
||||||
|
const newInput = await page.$("#stampEditorFileInput");
|
||||||
|
await newInput.uploadFile(
|
||||||
|
`${path.join(__dirname, "../images/firefox_logo.png")}`
|
||||||
|
);
|
||||||
|
await waitForImage(page, getEditorSelector(1));
|
||||||
|
await waitForSerialized(page, 1);
|
||||||
|
await page.waitForSelector("#editorUndoBar", { hidden: true });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -756,6 +756,12 @@ async function kbFocusPrevious(page) {
|
|||||||
await awaitPromise(handle);
|
await awaitPromise(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function kbSave(page) {
|
||||||
|
await page.keyboard.down(modifier);
|
||||||
|
await page.keyboard.press("s");
|
||||||
|
await page.keyboard.up(modifier);
|
||||||
|
}
|
||||||
|
|
||||||
async function switchToEditor(name, page, disable = false) {
|
async function switchToEditor(name, page, disable = false) {
|
||||||
const modeChangedHandle = await createPromise(page, resolve => {
|
const modeChangedHandle = await createPromise(page, resolve => {
|
||||||
window.PDFViewerApplication.eventBus.on(
|
window.PDFViewerApplication.eventBus.on(
|
||||||
@ -842,6 +848,7 @@ export {
|
|||||||
kbModifierDown,
|
kbModifierDown,
|
||||||
kbModifierUp,
|
kbModifierUp,
|
||||||
kbRedo,
|
kbRedo,
|
||||||
|
kbSave,
|
||||||
kbSelectAll,
|
kbSelectAll,
|
||||||
kbUndo,
|
kbUndo,
|
||||||
loadAndWait,
|
loadAndWait,
|
||||||
|
|||||||
37
web/app.js
37
web/app.js
@ -69,6 +69,7 @@ import { AltTextManager } from "web-alt_text_manager";
|
|||||||
import { AnnotationEditorParams } from "web-annotation_editor_params";
|
import { AnnotationEditorParams } from "web-annotation_editor_params";
|
||||||
import { CaretBrowsingMode } from "./caret_browsing.js";
|
import { CaretBrowsingMode } from "./caret_browsing.js";
|
||||||
import { DownloadManager } from "web-download_manager";
|
import { DownloadManager } from "web-download_manager";
|
||||||
|
import { EditorUndoBar } from "./editor_undo_bar.js";
|
||||||
import { OverlayManager } from "./overlay_manager.js";
|
import { OverlayManager } from "./overlay_manager.js";
|
||||||
import { PasswordPrompt } from "./password_prompt.js";
|
import { PasswordPrompt } from "./password_prompt.js";
|
||||||
import { PDFAttachmentViewer } from "web-pdf_attachment_viewer";
|
import { PDFAttachmentViewer } from "web-pdf_attachment_viewer";
|
||||||
@ -192,6 +193,7 @@ const PDFViewerApplication = {
|
|||||||
_isCtrlKeyDown: false,
|
_isCtrlKeyDown: false,
|
||||||
_caretBrowsing: null,
|
_caretBrowsing: null,
|
||||||
_isScrolling: false,
|
_isScrolling: false,
|
||||||
|
editorUndoBar: null,
|
||||||
|
|
||||||
// Called once when the document is loaded.
|
// Called once when the document is loaded.
|
||||||
async initialize(appConfig) {
|
async initialize(appConfig) {
|
||||||
@ -461,6 +463,10 @@ const PDFViewerApplication = {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appConfig.editorUndoBar) {
|
||||||
|
this.editorUndoBar = new EditorUndoBar(appConfig.editorUndoBar, eventBus);
|
||||||
|
}
|
||||||
|
|
||||||
const enableHWA = AppOptions.get("enableHWA");
|
const enableHWA = AppOptions.get("enableHWA");
|
||||||
const pdfViewer = new PDFViewer({
|
const pdfViewer = new PDFViewer({
|
||||||
container,
|
container,
|
||||||
@ -470,6 +476,7 @@ const PDFViewerApplication = {
|
|||||||
linkService: pdfLinkService,
|
linkService: pdfLinkService,
|
||||||
downloadManager,
|
downloadManager,
|
||||||
altTextManager,
|
altTextManager,
|
||||||
|
editorUndoBar: this.editorUndoBar,
|
||||||
findController,
|
findController,
|
||||||
scriptingManager:
|
scriptingManager:
|
||||||
AppOptions.get("enableScripting") && pdfScriptingManager,
|
AppOptions.get("enableScripting") && pdfScriptingManager,
|
||||||
@ -2732,7 +2739,7 @@ function onTouchEnd(evt) {
|
|||||||
this._isPinching = false;
|
this._isPinching = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClick(evt) {
|
function closeSecondaryToolbar(evt) {
|
||||||
if (!this.secondaryToolbar?.isOpen) {
|
if (!this.secondaryToolbar?.isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2749,6 +2756,20 @@ function onClick(evt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeEditorUndoBar(evt) {
|
||||||
|
if (!this.editorUndoBar?.isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.appConfig.secondaryToolbar?.toolbar.contains(evt.target)) {
|
||||||
|
this.editorUndoBar.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClick(evt) {
|
||||||
|
closeSecondaryToolbar.call(this, evt);
|
||||||
|
closeEditorUndoBar.call(this, evt);
|
||||||
|
}
|
||||||
|
|
||||||
function onKeyUp(evt) {
|
function onKeyUp(evt) {
|
||||||
// evt.ctrlKey is false hence we use evt.key.
|
// evt.ctrlKey is false hence we use evt.key.
|
||||||
if (evt.key === "Control") {
|
if (evt.key === "Control") {
|
||||||
@ -2759,6 +2780,20 @@ function onKeyUp(evt) {
|
|||||||
function onKeyDown(evt) {
|
function onKeyDown(evt) {
|
||||||
this._isCtrlKeyDown = evt.key === "Control";
|
this._isCtrlKeyDown = evt.key === "Control";
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.editorUndoBar?.isOpen &&
|
||||||
|
evt.keyCode !== 9 &&
|
||||||
|
evt.keyCode !== 16 &&
|
||||||
|
!(
|
||||||
|
(evt.keyCode === 13 || evt.keyCode === 32) &&
|
||||||
|
getActiveOrFocusedElement() === this.appConfig.editorUndoBar.undoButton
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Hide undo bar on keypress except for Shift, Tab, Shift+Tab.
|
||||||
|
// Also avoid hiding if the undo button is triggered.
|
||||||
|
this.editorUndoBar.hide();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.overlayManager.active) {
|
if (this.overlayManager.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -270,29 +270,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.messageBar {
|
.messageBar {
|
||||||
--message-bar-warning-icon: url(images/messageBar_warning.svg);
|
|
||||||
--closing-button-icon: url(images/messageBar_closingButton.svg);
|
|
||||||
|
|
||||||
--message-bar-bg-color: #ffebcd;
|
--message-bar-bg-color: #ffebcd;
|
||||||
--message-bar-fg-color: #15141a;
|
--message-bar-fg-color: #15141a;
|
||||||
--message-bar-border-color: rgb(0 0 0 / 0.08);
|
--message-bar-border-color: rgb(0 0 0 / 0.08);
|
||||||
|
--message-bar-icon: url(images/messageBar_warning.svg);
|
||||||
--message-bar-icon-color: #cd411e;
|
--message-bar-icon-color: #cd411e;
|
||||||
--message-bar-close-button-border-radius: 4px;
|
|
||||||
--message-bar-close-button-border: none;
|
|
||||||
--message-bar-close-button-color: var(--text-primary-color);
|
|
||||||
--message-bar-close-button-hover-bg-color: rgb(21 20 26 / 0.14);
|
|
||||||
--message-bar-close-button-active-bg-color: rgb(21 20 26 / 0.21);
|
|
||||||
--message-bar-close-button-focus-bg-color: rgb(21 20 26 / 0.07);
|
|
||||||
--message-bar-close-button-color-hover: var(--text-primary-color);
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
--message-bar-bg-color: #5a3100;
|
--message-bar-bg-color: #5a3100;
|
||||||
--message-bar-fg-color: #fbfbfe;
|
--message-bar-fg-color: #fbfbfe;
|
||||||
--message-bar-border-color: rgb(255 255 255 / 0.08);
|
--message-bar-border-color: rgb(255 255 255 / 0.08);
|
||||||
--message-bar-icon-color: #e49c49;
|
--message-bar-icon-color: #e49c49;
|
||||||
--message-bar-close-button-hover-bg-color: rgb(251 251 254 / 0.14);
|
|
||||||
--message-bar-close-button-active-bg-color: rgb(251 251 254 / 0.21);
|
|
||||||
--message-bar-close-button-focus-bg-color: rgb(251 251 254 / 0.07);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
@ -300,43 +288,14 @@
|
|||||||
--message-bar-fg-color: CanvasText;
|
--message-bar-fg-color: CanvasText;
|
||||||
--message-bar-border-color: CanvasText;
|
--message-bar-border-color: CanvasText;
|
||||||
--message-bar-icon-color: CanvasText;
|
--message-bar-icon-color: CanvasText;
|
||||||
--message-bar-close-button-color: ButtonText;
|
|
||||||
--message-bar-close-button-border: 1px solid ButtonText;
|
|
||||||
--message-bar-close-button-hover-bg-color: ButtonText;
|
|
||||||
--message-bar-close-button-active-bg-color: ButtonText;
|
|
||||||
--message-bar-close-button-focus-bg-color: ButtonText;
|
|
||||||
--message-bar-close-button-color-hover: HighlightText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
padding: 12px 8px 12px 0;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 8px;
|
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--message-bar-border-color);
|
|
||||||
background: var(--message-bar-bg-color);
|
|
||||||
color: var(--message-bar-fg-color);
|
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
display: flex;
|
&::before,
|
||||||
padding-inline-start: 16px;
|
> div {
|
||||||
align-items: flex-start;
|
margin-block: 4px;
|
||||||
gap: 8px;
|
|
||||||
align-self: stretch;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
mask-image: var(--message-bar-warning-icon);
|
|
||||||
mask-size: cover;
|
|
||||||
background-color: var(--message-bar-icon-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
@ -356,50 +315,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.closeButton {
|
|
||||||
position: absolute;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
inset-inline-end: 8px;
|
|
||||||
inset-block-start: 8px;
|
|
||||||
background: none;
|
|
||||||
border-radius: var(--message-bar-close-button-border-radius);
|
|
||||||
border: var(--message-bar-close-button-border);
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
mask-image: var(--closing-button-icon);
|
|
||||||
mask-size: cover;
|
|
||||||
background-color: var(--message-bar-close-button-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:is(:hover, :active, :focus)::before {
|
|
||||||
background-color: var(--message-bar-close-button-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--message-bar-close-button-hover-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: var(--message-bar-close-button-active-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
background-color: var(--message-bar-close-button-focus-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
display: inline-block;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggler {
|
.toggler {
|
||||||
|
|||||||
128
web/editor_undo_bar.js
Normal file
128
web/editor_undo_bar.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/* Copyright 2024 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 { noContextMenu } from "pdfjs-lib";
|
||||||
|
|
||||||
|
class EditorUndoBar {
|
||||||
|
#closeButton = null;
|
||||||
|
|
||||||
|
#container;
|
||||||
|
|
||||||
|
#eventBus = null;
|
||||||
|
|
||||||
|
#focusTimeout = null;
|
||||||
|
|
||||||
|
#initController = null;
|
||||||
|
|
||||||
|
isOpen = false;
|
||||||
|
|
||||||
|
#message;
|
||||||
|
|
||||||
|
#showController = null;
|
||||||
|
|
||||||
|
#undoButton;
|
||||||
|
|
||||||
|
static #l10nMessages = Object.freeze({
|
||||||
|
highlight: "pdfjs-editor-undo-bar-message-highlight",
|
||||||
|
freetext: "pdfjs-editor-undo-bar-message-freetext",
|
||||||
|
stamp: "pdfjs-editor-undo-bar-message-stamp",
|
||||||
|
ink: "pdfjs-editor-undo-bar-message-ink",
|
||||||
|
_multiple: "pdfjs-editor-undo-bar-message-multiple",
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor({ container, message, undoButton, closeButton }, eventBus) {
|
||||||
|
this.#container = container;
|
||||||
|
this.#message = message;
|
||||||
|
this.#undoButton = undoButton;
|
||||||
|
this.#closeButton = closeButton;
|
||||||
|
this.#eventBus = eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.#initController?.abort();
|
||||||
|
this.#initController = null;
|
||||||
|
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
show(undoAction, messageData) {
|
||||||
|
if (!this.#initController) {
|
||||||
|
this.#initController = new AbortController();
|
||||||
|
const opts = { signal: this.#initController.signal };
|
||||||
|
const boundHide = this.hide.bind(this);
|
||||||
|
|
||||||
|
this.#container.addEventListener("contextmenu", noContextMenu, opts);
|
||||||
|
this.#closeButton.addEventListener("click", boundHide, opts);
|
||||||
|
this.#eventBus._on("beforeprint", boundHide, opts);
|
||||||
|
this.#eventBus._on("download", boundHide, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hide();
|
||||||
|
|
||||||
|
if (typeof messageData === "string") {
|
||||||
|
this.#message.setAttribute(
|
||||||
|
"data-l10n-id",
|
||||||
|
EditorUndoBar.#l10nMessages[messageData]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.#message.setAttribute(
|
||||||
|
"data-l10n-id",
|
||||||
|
EditorUndoBar.#l10nMessages._multiple
|
||||||
|
);
|
||||||
|
this.#message.setAttribute(
|
||||||
|
"data-l10n-args",
|
||||||
|
JSON.stringify({ count: messageData })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.isOpen = true;
|
||||||
|
this.#container.hidden = false;
|
||||||
|
|
||||||
|
this.#showController = new AbortController();
|
||||||
|
|
||||||
|
this.#undoButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
|
undoAction();
|
||||||
|
this.hide();
|
||||||
|
},
|
||||||
|
{ signal: this.#showController.signal }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Without the setTimeout, VoiceOver will read out the document title
|
||||||
|
// instead of the popup label.
|
||||||
|
this.#focusTimeout = setTimeout(() => {
|
||||||
|
this.#container.focus();
|
||||||
|
this.#focusTimeout = null;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
if (!this.isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isOpen = false;
|
||||||
|
this.#container.hidden = true;
|
||||||
|
|
||||||
|
this.#showController?.abort();
|
||||||
|
this.#showController = null;
|
||||||
|
|
||||||
|
if (this.#focusTimeout) {
|
||||||
|
clearTimeout(this.#focusTimeout);
|
||||||
|
this.#focusTimeout = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { EditorUndoBar };
|
||||||
221
web/message_bar.css
Normal file
221
web/message_bar.css
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/* Copyright 2024 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.messageBar {
|
||||||
|
--closing-button-icon: url(images/messageBar_closingButton.svg);
|
||||||
|
--message-bar-close-button-color: var(--text-primary-color);
|
||||||
|
--message-bar-close-button-color-hover: var(--text-primary-color);
|
||||||
|
--message-bar-close-button-border-radius: 4px;
|
||||||
|
--message-bar-close-button-border: none;
|
||||||
|
--message-bar-close-button-hover-bg-color: rgb(21 20 26 / 0.14);
|
||||||
|
--message-bar-close-button-active-bg-color: rgb(21 20 26 / 0.21);
|
||||||
|
--message-bar-close-button-focus-bg-color: rgb(21 20 26 / 0.07);
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
--message-bar-close-button-hover-bg-color: rgb(251 251 254 / 0.14);
|
||||||
|
--message-bar-close-button-active-bg-color: rgb(251 251 254 / 0.21);
|
||||||
|
--message-bar-close-button-focus-bg-color: rgb(251 251 254 / 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (forced-colors: active) {
|
||||||
|
--message-bar-close-button-color: ButtonText;
|
||||||
|
--message-bar-close-button-border: 1px solid ButtonText;
|
||||||
|
--message-bar-close-button-hover-bg-color: ButtonText;
|
||||||
|
--message-bar-close-button-active-bg-color: ButtonText;
|
||||||
|
--message-bar-close-button-focus-bg-color: ButtonText;
|
||||||
|
--message-bar-close-button-color-hover: HighlightText;
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
padding: 8px 8px 8px 16px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
border: 1px solid var(--message-bar-border-color);
|
||||||
|
background: var(--message-bar-bg-color);
|
||||||
|
color: var(--message-bar-fg-color);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
align-self: stretch;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
mask-image: var(--message-bar-icon);
|
||||||
|
mask-size: cover;
|
||||||
|
background-color: var(--message-bar-icon-color);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: var(--focus-ring-outline);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: none;
|
||||||
|
border-radius: var(--message-bar-close-button-border-radius);
|
||||||
|
border: var(--message-bar-close-button-border);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
mask-image: var(--closing-button-icon);
|
||||||
|
mask-size: cover;
|
||||||
|
background-color: var(--message-bar-close-button-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:is(:hover, :active, :focus)::before {
|
||||||
|
background-color: var(--message-bar-close-button-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--message-bar-close-button-hover-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--message-bar-close-button-active-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--message-bar-close-button-focus-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
display: inline-block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#editorUndoBar {
|
||||||
|
--text-primary-color: #15141a;
|
||||||
|
|
||||||
|
--message-bar-icon: url(images/secondaryToolbarButton-documentProperties.svg);
|
||||||
|
--message-bar-icon-color: #0060df;
|
||||||
|
--message-bar-bg-color: #deeafc;
|
||||||
|
--message-bar-fg-color: var(--text-primary-color);
|
||||||
|
--message-bar-border-color: rgb(0 0 0 / 0.08);
|
||||||
|
|
||||||
|
--undo-button-bg-color: rgb(21 20 26 / 0.07);
|
||||||
|
--undo-button-bg-color-hover: rgb(21 20 26 / 0.14);
|
||||||
|
--undo-button-bg-color-active: rgb(21 20 26 / 0.21);
|
||||||
|
|
||||||
|
--undo-button-fg-color: var(--message-bar-fg-color);
|
||||||
|
--undo-button-fg-color-hover: var(--undo-button-fg-color);
|
||||||
|
--undo-button-fg-color-active: var(--undo-button-fg-color);
|
||||||
|
|
||||||
|
--focus-ring-color: #0060df;
|
||||||
|
--focus-ring-outline: 2px solid var(--focus-ring-color);
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
--text-primary-color: #fbfbfe;
|
||||||
|
|
||||||
|
--message-bar-icon-color: #73a7f3;
|
||||||
|
--message-bar-bg-color: #003070;
|
||||||
|
--message-bar-border-color: rgb(255 255 255 / 0.08);
|
||||||
|
|
||||||
|
--undo-button-bg-color: rgb(255 255 255 / 0.08);
|
||||||
|
--undo-button-bg-color-hover: rgb(255 255 255 / 0.14);
|
||||||
|
--undo-button-bg-color-active: rgb(255 255 255 / 0.21);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (forced-colors: active) {
|
||||||
|
--text-primary-color: CanvasText;
|
||||||
|
|
||||||
|
--message-bar-icon-color: CanvasText;
|
||||||
|
--message-bar-bg-color: Canvas;
|
||||||
|
--message-bar-border-color: CanvasText;
|
||||||
|
|
||||||
|
--undo-button-bg-color: ButtonText;
|
||||||
|
--undo-button-bg-color-hover: SelectedItem;
|
||||||
|
--undo-button-bg-color-active: SelectedItem;
|
||||||
|
|
||||||
|
--undo-button-fg-color: ButtonFace;
|
||||||
|
--undo-button-fg-color-hover: SelectedItemText;
|
||||||
|
--undo-button-fg-color-active: SelectedItemText;
|
||||||
|
|
||||||
|
--focus-ring-color: CanvasText;
|
||||||
|
}
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
top: 50px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
padding-block: 8px;
|
||||||
|
padding-inline: 16px 8px;
|
||||||
|
|
||||||
|
font: menu;
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editorUndoBarUndoButton {
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 590;
|
||||||
|
line-height: 19.5px;
|
||||||
|
color: var(--undo-button-fg-color);
|
||||||
|
border: none;
|
||||||
|
padding: 4px 16px;
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
background-color: var(--undo-button-bg-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--undo-button-bg-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--undo-button-bg-color-active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
@import url(message_bar.css);
|
||||||
@import url(dialog.css);
|
@import url(dialog.css);
|
||||||
@import url(text_layer_builder.css);
|
@import url(text_layer_builder.css);
|
||||||
@import url(annotation_layer_builder.css);
|
@import url(annotation_layer_builder.css);
|
||||||
|
|||||||
@ -214,6 +214,8 @@ class PDFViewer {
|
|||||||
|
|
||||||
#containerTopLeft = null;
|
#containerTopLeft = null;
|
||||||
|
|
||||||
|
#editorUndoBar = null;
|
||||||
|
|
||||||
#enableHWA = false;
|
#enableHWA = false;
|
||||||
|
|
||||||
#enableHighlightFloatingButton = false;
|
#enableHighlightFloatingButton = false;
|
||||||
@ -281,6 +283,7 @@ class PDFViewer {
|
|||||||
this.downloadManager = options.downloadManager || null;
|
this.downloadManager = options.downloadManager || null;
|
||||||
this.findController = options.findController || null;
|
this.findController = options.findController || null;
|
||||||
this.#altTextManager = options.altTextManager || null;
|
this.#altTextManager = options.altTextManager || null;
|
||||||
|
this.#editorUndoBar = options.editorUndoBar || null;
|
||||||
|
|
||||||
if (this.findController) {
|
if (this.findController) {
|
||||||
this.findController.onIsPageVisible = pageNumber =>
|
this.findController.onIsPageVisible = pageNumber =>
|
||||||
@ -907,7 +910,8 @@ class PDFViewer {
|
|||||||
this.#enableHighlightFloatingButton,
|
this.#enableHighlightFloatingButton,
|
||||||
this.#enableUpdatedAddImage,
|
this.#enableUpdatedAddImage,
|
||||||
this.#enableNewAltTextWhenAddingImage,
|
this.#enableNewAltTextWhenAddingImage,
|
||||||
this.#mlManager
|
this.#mlManager,
|
||||||
|
this.#editorUndoBar
|
||||||
);
|
);
|
||||||
eventBus.dispatch("annotationeditoruimanager", {
|
eventBus.dispatch("annotationeditoruimanager", {
|
||||||
source: this,
|
source: this,
|
||||||
|
|||||||
@ -688,6 +688,20 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
<!--#endif-->
|
<!--#endif-->
|
||||||
</div> <!-- dialogContainer -->
|
</div> <!-- dialogContainer -->
|
||||||
|
|
||||||
|
<div id="editorUndoBar" class="messageBar" role="status" aria-labelledby="editorUndoBarMessage" tabindex="-1" hidden>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<span id="editorUndoBarMessage" class="description"></span>
|
||||||
|
</div>
|
||||||
|
<button id="editorUndoBarUndoButton" class="undoButton" type="button" tabindex="0" title="Undo" data-l10n-id="pdfjs-editor-undo-bar-undo-button">
|
||||||
|
<span data-l10n-id="pdfjs-editor-undo-bar-undo-button-label">Undo</span>
|
||||||
|
</button>
|
||||||
|
<button id="editorUndoBarCloseButton" class="closeButton" type="button" tabindex="0" title="Close" data-l10n-id="pdfjs-editor-undo-bar-close-button">
|
||||||
|
<span data-l10n-id="pdfjs-editor-undo-bar-close-button-label">Close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div> <!-- editorUndoBar -->
|
||||||
|
|
||||||
</div> <!-- outerContainer -->
|
</div> <!-- outerContainer -->
|
||||||
<div id="printContainer"></div>
|
<div id="printContainer"></div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -223,6 +223,12 @@ function getViewerConfiguration() {
|
|||||||
editorHighlightShowAll: document.getElementById("editorHighlightShowAll"),
|
editorHighlightShowAll: document.getElementById("editorHighlightShowAll"),
|
||||||
},
|
},
|
||||||
printContainer: document.getElementById("printContainer"),
|
printContainer: document.getElementById("printContainer"),
|
||||||
|
editorUndoBar: {
|
||||||
|
container: document.getElementById("editorUndoBar"),
|
||||||
|
message: document.getElementById("editorUndoBarMessage"),
|
||||||
|
undoButton: document.getElementById("editorUndoBarUndoButton"),
|
||||||
|
closeButton: document.getElementById("editorUndoBarCloseButton"),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user