Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 114 additions & 30 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
(this.#popupElement?.popup || this.popup)?.remove();
this.#popupElement = this.popup = null;
Expand All @@ -308,10 +326,8 @@ class AnnotationElement {

let popup = this.#popupElement?.popup || this.popup;
if (!popup && newPopup?.text) {
if (!this.parent._commentManager) {
this._createPopup(newPopup);
popup = this.#popupElement.popup;
}
this._createPopup(newPopup);
popup = this.#popupElement.popup;
}
if (!popup) {
return;
Expand Down Expand Up @@ -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 {
constructor(parameters, options = null) {
super(parameters, {
Expand Down Expand Up @@ -2541,7 +2607,9 @@ class PopupElement {
}

#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;
}
this.renderCommentButton();
Expand Down Expand Up @@ -2596,30 +2664,10 @@ class PopupElement {
}

set comment(text) {
const element = this.#firstElement;
const { data } = element;
if (text === this.comment) {
return;
}
const popup = { deleted: !text, contents: 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();
}
this.#firstElement.commentText = this.#commentText = text;
}

get parentBoundingClientRect() {
Expand Down Expand Up @@ -3681,10 +3729,14 @@ class AnnotationLayer {

#annotationCanvasMap = null;

#annotationStorage = null;

#editableAnnotations = new Map();

#structTreeLayer = null;

#linkService = null;

constructor({
div,
accessibilityManager,
Expand All @@ -3694,11 +3746,15 @@ class AnnotationLayer {
viewport,
structTreeLayer,
commentManager,
linkService,
annotationStorage,
}) {
this.div = div;
this.#accessibilityManager = accessibilityManager;
this.#annotationCanvasMap = annotationCanvasMap;
this.#structTreeLayer = structTreeLayer || null;
this.#linkService = linkService || null;
this.#annotationStorage = annotationStorage || new AnnotationStorage();
this.page = page;
this.viewport = viewport;
this.zIndex = 0;
Expand Down Expand Up @@ -3762,12 +3818,12 @@ class AnnotationLayer {
const elementParams = {
data: null,
layer,
linkService: params.linkService,
linkService: this.#linkService,
downloadManager: params.downloadManager,
imageResourcesPath: params.imageResourcesPath || "",
renderForms: params.renderForms !== false,
svgFactory: new DOMSVGFactory(),
annotationStorage: params.annotationStorage || new AnnotationStorage(),
annotationStorage: this.#annotationStorage,
enableComment: params.enableComment === true,
enableScripting: params.enableScripting === true,
hasJSActions: params.hasJSActions,
Expand Down Expand Up @@ -3832,11 +3888,11 @@ class AnnotationLayer {
* @param {IPDFLinkService} linkService
* @memberof AnnotationLayer
*/
async addLinkAnnotations(annotations, linkService) {
async addLinkAnnotations(annotations) {
const elementParams = {
data: null,
layer: this.div,
linkService,
linkService: this.#linkService,
svgFactory: new DOMSVGFactory(),
parent: this,
};
Expand Down Expand Up @@ -3919,6 +3975,34 @@ class AnnotationLayer {
return this.#editableAnnotations.get(id);
}

addFakeAnnotation(editor) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to have a docstring or other documentation that explains the purpose of the fake annotation (i.e., what do we need it for?).

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
*/
Expand Down
80 changes: 42 additions & 38 deletions src/display/editor/annotation_editor_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ class AnnotationEditorLayer {
this.#cleanup();
switch (mode) {
case AnnotationEditorType.NONE:
this.div.classList.toggle("nonEditing", true);
this.disableTextSelection();
this.togglePointerEvents(false);
this.toggleAnnotationLayerPointerEvents(true);
Expand All @@ -193,6 +194,7 @@ class AnnotationEditorLayer {

this.toggleAnnotationLayerPointerEvents(false);
const { classList } = this.div;
classList.toggle("nonEditing", false);
if (mode === AnnotationEditorType.POPUP) {
classList.toggle("commentEditing", true);
} else {
Expand Down Expand Up @@ -257,6 +259,7 @@ class AnnotationEditorLayer {
this.#isEnabling = true;
this.div.tabIndex = 0;
this.togglePointerEvents(true);
this.div.classList.toggle("nonEditing", false);
this.#textLayerDblClickAC?.abort();
this.#textLayerDblClickAC = null;
const annotationElementIds = new Set();
Expand All @@ -269,27 +272,24 @@ class AnnotationEditorLayer {
}
}

if (!this.#annotationLayer) {
this.#isEnabling = false;
return;
}

const editables = this.#annotationLayer.getEditableAnnotations();
for (const editable of editables) {
// The element must be hidden whatever its state is.
editable.hide();
if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) {
continue;
}
if (annotationElementIds.has(editable.data.id)) {
continue;
}
const editor = await this.deserialize(editable);
if (!editor) {
continue;
const annotationLayer = this.#annotationLayer;
if (annotationLayer) {
for (const editable of annotationLayer.getEditableAnnotations()) {
// The element must be hidden whatever its state is.
editable.hide();
if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) {
continue;
}
if (annotationElementIds.has(editable.data.id)) {
continue;
}
const editor = await this.deserialize(editable);
if (!editor) {
continue;
}
this.addOrRebuild(editor);
editor.enableEditing();
}
this.addOrRebuild(editor);
editor.enableEditing();
}
this.#isEnabling = false;
this.#uiManager._eventBus.dispatch("editorsrendered", {
Expand All @@ -305,6 +305,7 @@ class AnnotationEditorLayer {
this.#isDisabling = true;
this.div.tabIndex = -1;
this.togglePointerEvents(false);
this.div.classList.toggle("nonEditing", true);
if (this.#textLayer && !this.#textLayerDblClickAC) {
this.#textLayerDblClickAC = new AbortController();
const signal = this.#uiManager.combinedSignal(this.#textLayerDblClickAC);
Expand Down Expand Up @@ -351,26 +352,29 @@ class AnnotationEditorLayer {
{ 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);

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();
}
this.getEditableAnnotation(editor.annotationElementId)?.show();
editor.remove();
}

if (this.#annotationLayer) {
// Show the annotations that were hidden in enable().
const editables = this.#annotationLayer.getEditableAnnotations();
const editables = annotationLayer.getEditableAnnotations();
for (const editable of editables) {
const { id } = editable.data;
if (this.#uiManager.isDeletedAnnotationElement(id)) {
Expand Down Expand Up @@ -725,7 +729,7 @@ class AnnotationEditorLayer {
/**
* Create a new editor
* @param {Object} data
* @returns {AnnotationEditor | null}
* @returns {Promise<AnnotationEditor | null>}
*/
async deserialize(data) {
return (
Expand Down
Loading