[Editor] Add a fake annotation (in the annotation layer) associated with an editor in order to be able to show the comment button (bug 1989420)
This commit is contained in:
parent
e3a5f61234
commit
12066af578
@ -284,6 +284,24 @@ class AnnotationElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set commentText(text) {
|
||||||
|
const { data } = this;
|
||||||
|
const popup = { deleted: !text, contents: text || "" };
|
||||||
|
if (!this.annotationStorage.updateEditor(data.id, { popup })) {
|
||||||
|
this.annotationStorage.setValue(`${AnnotationEditorPrefix}${data.id}`, {
|
||||||
|
id: data.id,
|
||||||
|
annotationType: data.annotationType,
|
||||||
|
pageIndex: this.parent.page._pageIndex,
|
||||||
|
popup,
|
||||||
|
popupRef: data.popupRef,
|
||||||
|
modificationDate: new Date(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!text) {
|
||||||
|
this.removePopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
removePopup() {
|
removePopup() {
|
||||||
(this.#popupElement?.popup || this.popup)?.remove();
|
(this.#popupElement?.popup || this.popup)?.remove();
|
||||||
this.#popupElement = this.popup = null;
|
this.#popupElement = this.popup = null;
|
||||||
@ -308,10 +326,8 @@ class AnnotationElement {
|
|||||||
|
|
||||||
let popup = this.#popupElement?.popup || this.popup;
|
let popup = this.#popupElement?.popup || this.popup;
|
||||||
if (!popup && newPopup?.text) {
|
if (!popup && newPopup?.text) {
|
||||||
if (!this.parent._commentManager) {
|
this._createPopup(newPopup);
|
||||||
this._createPopup(newPopup);
|
popup = this.#popupElement.popup;
|
||||||
popup = this.#popupElement.popup;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!popup) {
|
if (!popup) {
|
||||||
return;
|
return;
|
||||||
@ -882,6 +898,56 @@ class AnnotationElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EditorAnnotationElement extends AnnotationElement {
|
||||||
|
constructor(parameters) {
|
||||||
|
super(parameters, { isRenderable: true, ignoreBorder: true });
|
||||||
|
this.editor = parameters.editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.container.className = "editorAnnotation";
|
||||||
|
return this.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
createOrUpdatePopup() {
|
||||||
|
const { editor } = this;
|
||||||
|
if (!editor.hasComment) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._createPopup(editor.comment);
|
||||||
|
this.extraPopupElement.popup.renderCommentButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasCommentButton() {
|
||||||
|
return this.enableComment && this.editor.hasComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
get commentButtonPosition() {
|
||||||
|
return this.editor.commentButtonPositionInPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
get commentText() {
|
||||||
|
return this.editor.comment.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
set commentText(text) {
|
||||||
|
this.editor.comment = text;
|
||||||
|
if (!text) {
|
||||||
|
this.removePopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get commentData() {
|
||||||
|
return this.editor.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
this.container.remove();
|
||||||
|
this.container = null;
|
||||||
|
this.removePopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class LinkAnnotationElement extends AnnotationElement {
|
class LinkAnnotationElement extends AnnotationElement {
|
||||||
constructor(parameters, options = null) {
|
constructor(parameters, options = null) {
|
||||||
super(parameters, {
|
super(parameters, {
|
||||||
@ -2541,7 +2607,9 @@ class PopupElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#updateCommentButtonPosition() {
|
#updateCommentButtonPosition() {
|
||||||
if (this.#firstElement.extraPopupElement) {
|
if (this.#firstElement.extraPopupElement && !this.#firstElement.editor) {
|
||||||
|
// If there's no editor associated with the annotation then the comment
|
||||||
|
// button position can't be changed.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.renderCommentButton();
|
this.renderCommentButton();
|
||||||
@ -2596,30 +2664,10 @@ class PopupElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set comment(text) {
|
set comment(text) {
|
||||||
const element = this.#firstElement;
|
|
||||||
const { data } = element;
|
|
||||||
if (text === this.comment) {
|
if (text === this.comment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const popup = { deleted: !text, contents: text || "" };
|
this.#firstElement.commentText = this.#commentText = text;
|
||||||
if (!element.annotationStorage.updateEditor(data.id, { popup })) {
|
|
||||||
element.annotationStorage.setValue(
|
|
||||||
`${AnnotationEditorPrefix}${data.id}`,
|
|
||||||
{
|
|
||||||
id: data.id,
|
|
||||||
annotationType: data.annotationType,
|
|
||||||
pageIndex: element.parent.page._pageIndex,
|
|
||||||
popup,
|
|
||||||
popupRef: data.popupRef,
|
|
||||||
modificationDate: new Date(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#commentText = text;
|
|
||||||
if (!text) {
|
|
||||||
element.removePopup();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get parentBoundingClientRect() {
|
get parentBoundingClientRect() {
|
||||||
@ -3681,10 +3729,14 @@ class AnnotationLayer {
|
|||||||
|
|
||||||
#annotationCanvasMap = null;
|
#annotationCanvasMap = null;
|
||||||
|
|
||||||
|
#annotationStorage = null;
|
||||||
|
|
||||||
#editableAnnotations = new Map();
|
#editableAnnotations = new Map();
|
||||||
|
|
||||||
#structTreeLayer = null;
|
#structTreeLayer = null;
|
||||||
|
|
||||||
|
#linkService = null;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
div,
|
div,
|
||||||
accessibilityManager,
|
accessibilityManager,
|
||||||
@ -3694,11 +3746,15 @@ class AnnotationLayer {
|
|||||||
viewport,
|
viewport,
|
||||||
structTreeLayer,
|
structTreeLayer,
|
||||||
commentManager,
|
commentManager,
|
||||||
|
linkService,
|
||||||
|
annotationStorage,
|
||||||
}) {
|
}) {
|
||||||
this.div = div;
|
this.div = div;
|
||||||
this.#accessibilityManager = accessibilityManager;
|
this.#accessibilityManager = accessibilityManager;
|
||||||
this.#annotationCanvasMap = annotationCanvasMap;
|
this.#annotationCanvasMap = annotationCanvasMap;
|
||||||
this.#structTreeLayer = structTreeLayer || null;
|
this.#structTreeLayer = structTreeLayer || null;
|
||||||
|
this.#linkService = linkService || null;
|
||||||
|
this.#annotationStorage = annotationStorage || new AnnotationStorage();
|
||||||
this.page = page;
|
this.page = page;
|
||||||
this.viewport = viewport;
|
this.viewport = viewport;
|
||||||
this.zIndex = 0;
|
this.zIndex = 0;
|
||||||
@ -3762,12 +3818,12 @@ class AnnotationLayer {
|
|||||||
const elementParams = {
|
const elementParams = {
|
||||||
data: null,
|
data: null,
|
||||||
layer,
|
layer,
|
||||||
linkService: params.linkService,
|
linkService: this.#linkService,
|
||||||
downloadManager: params.downloadManager,
|
downloadManager: params.downloadManager,
|
||||||
imageResourcesPath: params.imageResourcesPath || "",
|
imageResourcesPath: params.imageResourcesPath || "",
|
||||||
renderForms: params.renderForms !== false,
|
renderForms: params.renderForms !== false,
|
||||||
svgFactory: new DOMSVGFactory(),
|
svgFactory: new DOMSVGFactory(),
|
||||||
annotationStorage: params.annotationStorage || new AnnotationStorage(),
|
annotationStorage: this.#annotationStorage,
|
||||||
enableComment: params.enableComment === true,
|
enableComment: params.enableComment === true,
|
||||||
enableScripting: params.enableScripting === true,
|
enableScripting: params.enableScripting === true,
|
||||||
hasJSActions: params.hasJSActions,
|
hasJSActions: params.hasJSActions,
|
||||||
@ -3832,11 +3888,11 @@ class AnnotationLayer {
|
|||||||
* @param {IPDFLinkService} linkService
|
* @param {IPDFLinkService} linkService
|
||||||
* @memberof AnnotationLayer
|
* @memberof AnnotationLayer
|
||||||
*/
|
*/
|
||||||
async addLinkAnnotations(annotations, linkService) {
|
async addLinkAnnotations(annotations) {
|
||||||
const elementParams = {
|
const elementParams = {
|
||||||
data: null,
|
data: null,
|
||||||
layer: this.div,
|
layer: this.div,
|
||||||
linkService,
|
linkService: this.#linkService,
|
||||||
svgFactory: new DOMSVGFactory(),
|
svgFactory: new DOMSVGFactory(),
|
||||||
parent: this,
|
parent: this,
|
||||||
};
|
};
|
||||||
@ -3919,6 +3975,34 @@ class AnnotationLayer {
|
|||||||
return this.#editableAnnotations.get(id);
|
return this.#editableAnnotations.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFakeAnnotation(editor) {
|
||||||
|
const { div } = this;
|
||||||
|
const { id, rotation } = editor;
|
||||||
|
const element = new EditorAnnotationElement({
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
rect: editor.getPDFRect(),
|
||||||
|
rotation,
|
||||||
|
},
|
||||||
|
editor,
|
||||||
|
layer: div,
|
||||||
|
parent: this,
|
||||||
|
enableComment: !!this._commentManager,
|
||||||
|
linkService: this.#linkService,
|
||||||
|
annotationStorage: this.#annotationStorage,
|
||||||
|
});
|
||||||
|
const htmlElement = element.render();
|
||||||
|
div.append(htmlElement);
|
||||||
|
this.#accessibilityManager?.moveElementInDOM(
|
||||||
|
div,
|
||||||
|
htmlElement,
|
||||||
|
htmlElement,
|
||||||
|
/* isRemovable = */ false
|
||||||
|
);
|
||||||
|
element.createOrUpdatePopup();
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -170,6 +170,7 @@ class AnnotationEditorLayer {
|
|||||||
this.#cleanup();
|
this.#cleanup();
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case AnnotationEditorType.NONE:
|
case AnnotationEditorType.NONE:
|
||||||
|
this.div.classList.toggle("nonEditing", true);
|
||||||
this.disableTextSelection();
|
this.disableTextSelection();
|
||||||
this.togglePointerEvents(false);
|
this.togglePointerEvents(false);
|
||||||
this.toggleAnnotationLayerPointerEvents(true);
|
this.toggleAnnotationLayerPointerEvents(true);
|
||||||
@ -193,6 +194,7 @@ class AnnotationEditorLayer {
|
|||||||
|
|
||||||
this.toggleAnnotationLayerPointerEvents(false);
|
this.toggleAnnotationLayerPointerEvents(false);
|
||||||
const { classList } = this.div;
|
const { classList } = this.div;
|
||||||
|
classList.toggle("nonEditing", false);
|
||||||
if (mode === AnnotationEditorType.POPUP) {
|
if (mode === AnnotationEditorType.POPUP) {
|
||||||
classList.toggle("commentEditing", true);
|
classList.toggle("commentEditing", true);
|
||||||
} else {
|
} else {
|
||||||
@ -257,6 +259,7 @@ class AnnotationEditorLayer {
|
|||||||
this.#isEnabling = true;
|
this.#isEnabling = true;
|
||||||
this.div.tabIndex = 0;
|
this.div.tabIndex = 0;
|
||||||
this.togglePointerEvents(true);
|
this.togglePointerEvents(true);
|
||||||
|
this.div.classList.toggle("nonEditing", false);
|
||||||
this.#textLayerDblClickAC?.abort();
|
this.#textLayerDblClickAC?.abort();
|
||||||
this.#textLayerDblClickAC = null;
|
this.#textLayerDblClickAC = null;
|
||||||
const annotationElementIds = new Set();
|
const annotationElementIds = new Set();
|
||||||
@ -269,27 +272,24 @@ class AnnotationEditorLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.#annotationLayer) {
|
const annotationLayer = this.#annotationLayer;
|
||||||
this.#isEnabling = false;
|
if (annotationLayer) {
|
||||||
return;
|
for (const editable of annotationLayer.getEditableAnnotations()) {
|
||||||
}
|
// The element must be hidden whatever its state is.
|
||||||
|
editable.hide();
|
||||||
const editables = this.#annotationLayer.getEditableAnnotations();
|
if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) {
|
||||||
for (const editable of editables) {
|
continue;
|
||||||
// The element must be hidden whatever its state is.
|
}
|
||||||
editable.hide();
|
if (annotationElementIds.has(editable.data.id)) {
|
||||||
if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) {
|
continue;
|
||||||
continue;
|
}
|
||||||
|
const editor = await this.deserialize(editable);
|
||||||
|
if (!editor) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.addOrRebuild(editor);
|
||||||
|
editor.enableEditing();
|
||||||
}
|
}
|
||||||
if (annotationElementIds.has(editable.data.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const editor = await this.deserialize(editable);
|
|
||||||
if (!editor) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.addOrRebuild(editor);
|
|
||||||
editor.enableEditing();
|
|
||||||
}
|
}
|
||||||
this.#isEnabling = false;
|
this.#isEnabling = false;
|
||||||
this.#uiManager._eventBus.dispatch("editorsrendered", {
|
this.#uiManager._eventBus.dispatch("editorsrendered", {
|
||||||
@ -305,6 +305,7 @@ class AnnotationEditorLayer {
|
|||||||
this.#isDisabling = true;
|
this.#isDisabling = true;
|
||||||
this.div.tabIndex = -1;
|
this.div.tabIndex = -1;
|
||||||
this.togglePointerEvents(false);
|
this.togglePointerEvents(false);
|
||||||
|
this.div.classList.toggle("nonEditing", true);
|
||||||
if (this.#textLayer && !this.#textLayerDblClickAC) {
|
if (this.#textLayer && !this.#textLayerDblClickAC) {
|
||||||
this.#textLayerDblClickAC = new AbortController();
|
this.#textLayerDblClickAC = new AbortController();
|
||||||
const signal = this.#uiManager.combinedSignal(this.#textLayerDblClickAC);
|
const signal = this.#uiManager.combinedSignal(this.#textLayerDblClickAC);
|
||||||
@ -351,26 +352,29 @@ class AnnotationEditorLayer {
|
|||||||
{ signal, capture: true }
|
{ signal, capture: true }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const changedAnnotations = new Map();
|
|
||||||
const resetAnnotations = new Map();
|
|
||||||
for (const editor of this.#allEditorsIterator) {
|
|
||||||
editor.disableEditing();
|
|
||||||
if (!editor.annotationElementId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (editor.serialize() !== null) {
|
|
||||||
changedAnnotations.set(editor.annotationElementId, editor);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
resetAnnotations.set(editor.annotationElementId, editor);
|
|
||||||
}
|
|
||||||
this.getEditableAnnotation(editor.annotationElementId)?.show();
|
|
||||||
editor.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.#annotationLayer) {
|
const annotationLayer = this.#annotationLayer;
|
||||||
|
if (annotationLayer) {
|
||||||
|
const changedAnnotations = new Map();
|
||||||
|
const resetAnnotations = new Map();
|
||||||
|
for (const editor of this.#allEditorsIterator) {
|
||||||
|
editor.disableEditing();
|
||||||
|
if (!editor.annotationElementId) {
|
||||||
|
editor.updateFakeAnnotationElement(annotationLayer);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (editor.serialize() !== null) {
|
||||||
|
changedAnnotations.set(editor.annotationElementId, editor);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
resetAnnotations.set(editor.annotationElementId, editor);
|
||||||
|
}
|
||||||
|
this.getEditableAnnotation(editor.annotationElementId)?.show();
|
||||||
|
editor.remove();
|
||||||
|
}
|
||||||
|
|
||||||
// Show the annotations that were hidden in enable().
|
// Show the annotations that were hidden in enable().
|
||||||
const editables = this.#annotationLayer.getEditableAnnotations();
|
const editables = annotationLayer.getEditableAnnotations();
|
||||||
for (const editable of editables) {
|
for (const editable of editables) {
|
||||||
const { id } = editable.data;
|
const { id } = editable.data;
|
||||||
if (this.#uiManager.isDeletedAnnotationElement(id)) {
|
if (this.#uiManager.isDeletedAnnotationElement(id)) {
|
||||||
@ -725,7 +729,7 @@ class AnnotationEditorLayer {
|
|||||||
/**
|
/**
|
||||||
* Create a new editor
|
* Create a new editor
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
* @returns {AnnotationEditor | null}
|
* @returns {Promise<AnnotationEditor | null>}
|
||||||
*/
|
*/
|
||||||
async deserialize(data) {
|
async deserialize(data) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -69,6 +69,8 @@ class AnnotationEditor {
|
|||||||
|
|
||||||
#savedDimensions = null;
|
#savedDimensions = null;
|
||||||
|
|
||||||
|
#fakeAnnotation = null;
|
||||||
|
|
||||||
#focusAC = null;
|
#focusAC = null;
|
||||||
|
|
||||||
#focusedResizerName = "";
|
#focusedResizerName = "";
|
||||||
@ -382,6 +384,10 @@ class AnnotationEditor {
|
|||||||
} else {
|
} else {
|
||||||
// The editor is being removed from the DOM, so we need to stop resizing.
|
// The editor is being removed from the DOM, so we need to stop resizing.
|
||||||
this.#stopResizing();
|
this.#stopResizing();
|
||||||
|
|
||||||
|
// Remove the fake annotation in the annotation layer.
|
||||||
|
this.#fakeAnnotation?.remove();
|
||||||
|
this.#fakeAnnotation = null;
|
||||||
}
|
}
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
@ -1172,7 +1178,9 @@ class AnnotationEditor {
|
|||||||
|
|
||||||
addStandaloneCommentButton() {
|
addStandaloneCommentButton() {
|
||||||
if (this.#commentStandaloneButton) {
|
if (this.#commentStandaloneButton) {
|
||||||
this.#commentStandaloneButton.classList.remove("hidden");
|
if (this._uiManager.isEditingMode()) {
|
||||||
|
this.#commentStandaloneButton.classList.remove("hidden");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.hasComment) {
|
if (!this.hasComment) {
|
||||||
@ -2338,6 +2346,24 @@ class AnnotationEditor {
|
|||||||
this.#disabled = true;
|
this.#disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFakeAnnotationElement(annotationLayer) {
|
||||||
|
if (!this.#fakeAnnotation && !this.deleted) {
|
||||||
|
this.#fakeAnnotation = annotationLayer.addFakeAnnotation(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.deleted) {
|
||||||
|
this.#fakeAnnotation.remove();
|
||||||
|
this.#fakeAnnotation = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.hasEditedComment || this._hasBeenMoved || this._hasBeenResized) {
|
||||||
|
this.#fakeAnnotation.updateEdited({
|
||||||
|
rect: this.getPDFRect(),
|
||||||
|
popup: this.comment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render an annotation in the annotation layer.
|
* Render an annotation in the annotation layer.
|
||||||
* @param {Object} annotation
|
* @param {Object} annotation
|
||||||
|
|||||||
@ -2668,6 +2668,10 @@ class AnnotationEditorUIManager {
|
|||||||
return this.#mode;
|
return this.#mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEditingMode() {
|
||||||
|
return this.#mode !== AnnotationEditorType.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
get imageManager() {
|
get imageManager() {
|
||||||
return shadow(this, "imageManager", new ImageManager());
|
return shadow(this, "imageManager", new ImageManager());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,21 @@ const highlightSpan = async (page, pageIndex, text) => {
|
|||||||
await page.waitForSelector(getEditorSelector(0));
|
await page.waitForSelector(getEditorSelector(0));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const editComment = async (page, editorSelector, comment) => {
|
||||||
|
const commentButtonSelector = `${editorSelector} button.comment`;
|
||||||
|
await waitAndClick(page, commentButtonSelector);
|
||||||
|
|
||||||
|
const textInputSelector = "#commentManagerTextInput";
|
||||||
|
await page.waitForSelector(textInputSelector, {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
await page.type(textInputSelector, comment);
|
||||||
|
await waitAndClick(page, "#commentManagerSaveButton");
|
||||||
|
await page.waitForSelector("#commentManagerDialog", {
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
describe("Comment", () => {
|
describe("Comment", () => {
|
||||||
describe("Comment edit dialog must be visible in ltr", () => {
|
describe("Comment edit dialog must be visible in ltr", () => {
|
||||||
let pages;
|
let pages;
|
||||||
@ -88,10 +103,10 @@ describe("Comment", () => {
|
|||||||
}));
|
}));
|
||||||
expect(dialogRect.x + dialogRect.width)
|
expect(dialogRect.x + dialogRect.width)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toBeLessThanOrEqual(viewport.width);
|
.toBeLessThanOrEqual(viewport.width + 1);
|
||||||
expect(dialogRect.y + dialogRect.height)
|
expect(dialogRect.y + dialogRect.height)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toBeLessThanOrEqual(viewport.height);
|
.toBeLessThanOrEqual(viewport.height + 1);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -134,14 +149,14 @@ describe("Comment", () => {
|
|||||||
});
|
});
|
||||||
const dialogRect = await getRect(page, "#commentManagerDialog");
|
const dialogRect = await getRect(page, "#commentManagerDialog");
|
||||||
const viewport = await page.evaluate(() => ({
|
const viewport = await page.evaluate(() => ({
|
||||||
height: document.documentElement.clientHeight,
|
height: window.innerHeight,
|
||||||
}));
|
}));
|
||||||
expect(dialogRect.x + dialogRect.width)
|
expect(dialogRect.x + dialogRect.width)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toBeGreaterThanOrEqual(0);
|
.toBeGreaterThanOrEqual(-1);
|
||||||
expect(dialogRect.y + dialogRect.height)
|
expect(dialogRect.y + dialogRect.height)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toBeLessThanOrEqual(viewport.height);
|
.toBeLessThanOrEqual(viewport.height + 1);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -309,6 +324,55 @@ describe("Comment", () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("must check that the comment button is added in the annotation layer", 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(getEditorSelector(0));
|
||||||
|
|
||||||
|
const comment = "Hello world!";
|
||||||
|
await editComment(page, getEditorSelector(0), comment);
|
||||||
|
await page.hover("#editorHighlightButton");
|
||||||
|
let buttonSelector =
|
||||||
|
".annotationEditorLayer .annotationCommentButton";
|
||||||
|
await page.waitForSelector(buttonSelector, { visible: true });
|
||||||
|
await page.hover(buttonSelector);
|
||||||
|
const popupSelector = "#commentPopup";
|
||||||
|
await page.waitForSelector(popupSelector, {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
let popupText = await page.evaluate(
|
||||||
|
selector => document.querySelector(selector).textContent,
|
||||||
|
`${popupSelector} .commentPopupText`
|
||||||
|
);
|
||||||
|
expect(popupText).withContext(`In ${browserName}`).toEqual(comment);
|
||||||
|
|
||||||
|
await page.hover("#editorHighlightButton");
|
||||||
|
await switchToHighlight(page, /* disable = */ true);
|
||||||
|
|
||||||
|
buttonSelector = ".annotationLayer .annotationCommentButton";
|
||||||
|
await page.waitForSelector(buttonSelector, {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
await page.hover(buttonSelector);
|
||||||
|
|
||||||
|
await page.waitForSelector(popupSelector, {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
popupText = await page.evaluate(
|
||||||
|
selector => document.querySelector(selector).textContent,
|
||||||
|
`${popupSelector} .commentPopupText`
|
||||||
|
);
|
||||||
|
expect(popupText).withContext(`In ${browserName}`).toEqual(comment);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Focused element after editing", () => {
|
describe("Focused element after editing", () => {
|
||||||
|
|||||||
@ -69,6 +69,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page:has(.annotationEditorLayer.nonEditing)
|
||||||
|
.annotationLayer
|
||||||
|
.editorAnnotation {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
#viewerContainer.pdfPresentationMode:fullscreen,
|
#viewerContainer.pdfPresentationMode:fullscreen,
|
||||||
.annotationEditorLayer.disabled {
|
.annotationEditorLayer.disabled {
|
||||||
.noAltTextBadge {
|
.noAltTextBadge {
|
||||||
|
|||||||
@ -64,13 +64,6 @@ import { PresentationModeState } from "./ui_utils.js";
|
|||||||
* @property {StructTreeLayerBuilder} [structTreeLayer]
|
* @property {StructTreeLayerBuilder} [structTreeLayer]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} InjectLinkAnnotationsOptions
|
|
||||||
* @property {Array<Object>} inferredLinks
|
|
||||||
* @property {PageViewport} viewport
|
|
||||||
* @property {StructTreeLayerBuilder} [structTreeLayer]
|
|
||||||
*/
|
|
||||||
|
|
||||||
class AnnotationLayerBuilder {
|
class AnnotationLayerBuilder {
|
||||||
#annotations = null;
|
#annotations = null;
|
||||||
|
|
||||||
@ -158,23 +151,19 @@ class AnnotationLayerBuilder {
|
|||||||
const div = (this.div = document.createElement("div"));
|
const div = (this.div = document.createElement("div"));
|
||||||
div.className = "annotationLayer";
|
div.className = "annotationLayer";
|
||||||
this.#onAppend?.(div);
|
this.#onAppend?.(div);
|
||||||
|
this.#initAnnotationLayer(viewport, structTreeLayer);
|
||||||
|
|
||||||
if (annotations.length === 0) {
|
if (annotations.length === 0) {
|
||||||
this.#annotations = annotations;
|
this.#annotations = annotations;
|
||||||
|
setLayerDimensions(this.div, viewport);
|
||||||
this.hide(/* internal = */ true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#initAnnotationLayer(viewport, structTreeLayer);
|
|
||||||
|
|
||||||
await this.annotationLayer.render({
|
await this.annotationLayer.render({
|
||||||
annotations,
|
annotations,
|
||||||
imageResourcesPath: this.imageResourcesPath,
|
imageResourcesPath: this.imageResourcesPath,
|
||||||
renderForms: this.renderForms,
|
renderForms: this.renderForms,
|
||||||
linkService: this.linkService,
|
|
||||||
downloadManager: this.downloadManager,
|
downloadManager: this.downloadManager,
|
||||||
annotationStorage: this.annotationStorage,
|
|
||||||
enableComment: this.enableComment,
|
enableComment: this.enableComment,
|
||||||
enableScripting: this.enableScripting,
|
enableScripting: this.enableScripting,
|
||||||
hasJSActions,
|
hasJSActions,
|
||||||
@ -207,10 +196,12 @@ class AnnotationLayerBuilder {
|
|||||||
accessibilityManager: this._accessibilityManager,
|
accessibilityManager: this._accessibilityManager,
|
||||||
annotationCanvasMap: this._annotationCanvasMap,
|
annotationCanvasMap: this._annotationCanvasMap,
|
||||||
annotationEditorUIManager: this._annotationEditorUIManager,
|
annotationEditorUIManager: this._annotationEditorUIManager,
|
||||||
|
annotationStorage: this.annotationStorage,
|
||||||
page: this.pdfPage,
|
page: this.pdfPage,
|
||||||
viewport: viewport.clone({ dontFlip: true }),
|
viewport: viewport.clone({ dontFlip: true }),
|
||||||
structTreeLayer,
|
structTreeLayer,
|
||||||
commentManager: this.#commentManager,
|
commentManager: this.#commentManager,
|
||||||
|
linkService: this.linkService,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,15 +225,11 @@ class AnnotationLayerBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {InjectLinkAnnotationsOptions} options
|
* @param {Array<Object>} inferredLinks
|
||||||
* @returns {Promise<void>} A promise that is resolved when the inferred links
|
* @returns {Promise<void>} A promise that is resolved when the inferred links
|
||||||
* are added to the annotation layer.
|
* are added to the annotation layer.
|
||||||
*/
|
*/
|
||||||
async injectLinkAnnotations({
|
async injectLinkAnnotations(inferredLinks) {
|
||||||
inferredLinks,
|
|
||||||
viewport,
|
|
||||||
structTreeLayer = null,
|
|
||||||
}) {
|
|
||||||
if (this.#annotations === null) {
|
if (this.#annotations === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"`render` method must be called before `injectLinkAnnotations`."
|
"`render` method must be called before `injectLinkAnnotations`."
|
||||||
@ -261,12 +248,7 @@ class AnnotationLayerBuilder {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.annotationLayer) {
|
await this.annotationLayer.addLinkAnnotations(newLinks);
|
||||||
this.#initAnnotationLayer(viewport, structTreeLayer);
|
|
||||||
setLayerDimensions(this.div, viewport);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.annotationLayer.addLinkAnnotations(newLinks, this.linkService);
|
|
||||||
// Don't show the annotation layer if it was explicitly hidden previously.
|
// Don't show the annotation layer if it was explicitly hidden previously.
|
||||||
if (!this.#externalHide) {
|
if (!this.#externalHide) {
|
||||||
this.div.hidden = false;
|
this.div.hidden = false;
|
||||||
|
|||||||
@ -521,11 +521,9 @@ class PDFPageView extends BasePDFPageView {
|
|||||||
if (!this.annotationLayer) {
|
if (!this.annotationLayer) {
|
||||||
return; // Rendering was cancelled while the textLayerPromise resolved.
|
return; // Rendering was cancelled while the textLayerPromise resolved.
|
||||||
}
|
}
|
||||||
await this.annotationLayer.injectLinkAnnotations({
|
await this.annotationLayer.injectLinkAnnotations(
|
||||||
inferredLinks: Autolinker.processLinks(this),
|
Autolinker.processLinks(this)
|
||||||
viewport: this.viewport,
|
);
|
||||||
structTreeLayer: this.structTreeLayer,
|
|
||||||
});
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.error("#injectLinkAnnotations:", ex);
|
console.error("#injectLinkAnnotations:", ex);
|
||||||
error = ex;
|
error = ex;
|
||||||
@ -1120,20 +1118,25 @@ class PDFPageView extends BasePDFPageView {
|
|||||||
await this.#renderDrawLayer();
|
await this.#renderDrawLayer();
|
||||||
this.drawLayer.setParent(canvasWrapper);
|
this.drawLayer.setParent(canvasWrapper);
|
||||||
|
|
||||||
this.annotationEditorLayer ||= new AnnotationEditorLayerBuilder({
|
if (
|
||||||
uiManager: annotationEditorUIManager,
|
this.annotationLayer ||
|
||||||
pdfPage,
|
this.#annotationMode === AnnotationMode.DISABLE
|
||||||
l10n,
|
) {
|
||||||
structTreeLayer: this.structTreeLayer,
|
this.annotationEditorLayer ||= new AnnotationEditorLayerBuilder({
|
||||||
accessibilityManager: this._accessibilityManager,
|
uiManager: annotationEditorUIManager,
|
||||||
annotationLayer: this.annotationLayer?.annotationLayer,
|
pdfPage,
|
||||||
textLayer: this.textLayer,
|
l10n,
|
||||||
drawLayer: this.drawLayer.getDrawLayer(),
|
structTreeLayer: this.structTreeLayer,
|
||||||
onAppend: annotationEditorLayerDiv => {
|
accessibilityManager: this._accessibilityManager,
|
||||||
this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer");
|
annotationLayer: this.annotationLayer?.annotationLayer,
|
||||||
},
|
textLayer: this.textLayer,
|
||||||
});
|
drawLayer: this.drawLayer.getDrawLayer(),
|
||||||
this.#renderAnnotationEditorLayer();
|
onAppend: annotationEditorLayerDiv => {
|
||||||
|
this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.#renderAnnotationEditorLayer();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pdfPage.isPureXfa) {
|
if (pdfPage.isPureXfa) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user