diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 027ad2b6f..37c03a26a 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -29,6 +29,7 @@ import { AnnotationBorderStyleType, + AnnotationEditorPrefix, AnnotationEditorType, AnnotationPrefix, AnnotationType, @@ -242,6 +243,16 @@ class AnnotationElement { return point; } + get commentText() { + const { data } = this; + return ( + this.annotationStorage.getRawValue(`${AnnotationEditorPrefix}${data.id}`) + ?.popup?.contents || + data.contentsObj?.str || + "" + ); + } + removePopup() { (this.#popupElement?.popup || this.popup)?.remove(); this.#popupElement = this.popup = null; @@ -2335,6 +2346,8 @@ class PopupElement { #firstElement = null; + #commentText = null; + constructor({ container, color, @@ -2495,7 +2508,16 @@ class PopupElement { } getData() { - return this.#firstElement.data; + const { richText, color, opacity, creationDate, modificationDate } = + this.#firstElement.data; + return { + contentsObj: { str: this.comment }, + richText, + color, + opacity, + creationDate, + modificationDate, + }; } get elementBeforePopup() { @@ -2503,22 +2525,35 @@ class PopupElement { } get comment() { - return this.#firstElement.data.contentsObj?.str || ""; + this.#commentText ||= this.#firstElement.commentText; + return this.#commentText; } set comment(text) { const element = this.#firstElement; - if (text) { - element.data.contentsObj = { str: text }; - // TODO: Support saving the text. - // element.annotationStorage.setValue(element.data.id, { - // popup: { contents: text }, - // }); - } else { - element.data.contentsObj = null; + 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(); } - element.data.modificationDate = new Date(); } get parentBoundingClientRect() { @@ -2707,6 +2742,10 @@ class PopupElement { } updateEdited({ rect, popup, deleted }) { + if (this.#commentManager) { + this.#commentText = deleted ? null : popup.text; + return; + } if (deleted || popup?.deleted) { this.remove(); return; diff --git a/src/display/annotation_storage.js b/src/display/annotation_storage.js index 91cdc6240..c1f21beb8 100644 --- a/src/display/annotation_storage.js +++ b/src/display/annotation_storage.js @@ -31,6 +31,8 @@ class AnnotationStorage { #modifiedIds = null; + #editorsMap = null; + #storage = new Map(); constructor() { @@ -83,6 +85,13 @@ class AnnotationStorage { * @param {string} key */ remove(key) { + const storedValue = this.#storage.get(key); + if (storedValue === undefined) { + return; + } + if (storedValue instanceof AnnotationEditor) { + this.#editorsMap.delete(storedValue.annotationElementId); + } this.#storage.delete(key); if (this.#storage.size === 0) { @@ -122,11 +131,11 @@ class AnnotationStorage { this.#setModified(); } - if ( - value instanceof AnnotationEditor && - typeof this.onAnnotationEditor === "function" - ) { - this.onAnnotationEditor(value.constructor._type); + if (value instanceof AnnotationEditor) { + (this.#editorsMap ||= new Map()).set(value.annotationElementId, value); + if (typeof this.onAnnotationEditor === "function") { + this.onAnnotationEditor(value.constructor._type); + } } } @@ -250,6 +259,15 @@ class AnnotationStorage { this.#modifiedIds = null; } + updateEditor(annotationId, data) { + const value = this.#editorsMap?.get(annotationId); + if (value) { + value.updateFromAnnotationLayer(data); + return true; + } + return false; + } + /** * @returns {{ids: Set, hash: string}} */ @@ -258,15 +276,13 @@ class AnnotationStorage { return this.#modifiedIds; } const ids = []; - for (const value of this.#storage.values()) { - if ( - !(value instanceof AnnotationEditor) || - !value.annotationElementId || - !value.serialize() - ) { - continue; + if (this.#editorsMap) { + for (const value of this.#editorsMap.values()) { + if (!value.serialize()) { + continue; + } + ids.push(value.annotationElementId); } - ids.push(value.annotationElementId); } return (this.#modifiedIds = { ids: new Set(ids), diff --git a/src/display/editor/editor.js b/src/display/editor/editor.js index f4c87425f..881c49bb5 100644 --- a/src/display/editor/editor.js +++ b/src/display/editor/editor.js @@ -1243,6 +1243,16 @@ class AnnotationEditor { } this.#comment ||= new Comment(this); this.#comment.setInitialText(comment, richText); + + if (!this.annotationElementId) { + return; + } + const storedData = this._uiManager.getAndRemoveDataFromAnnotationStorage( + this.annotationElementId + ); + if (storedData) { + this.updateFromAnnotationLayer(storedData); + } } get hasEditedComment() { @@ -1288,6 +1298,10 @@ class AnnotationEditor { } } + updateFromAnnotationLayer({ popup: { contents, deleted } }) { + this.#comment.data = deleted ? null : contents; + } + get parentBoundingClientRect() { return this.parent.boundingClientRect; } diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index 93a91f8d0..f9ac09946 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -1288,6 +1288,26 @@ class AnnotationEditorUIManager { this.#floatingToolbar.show(textLayer, boxes, this.direction === "ltr"); } + /** + * Some annotations may have been modified in the annotation layer + * (e.g. comments added or modified). + * So this function retrieves the data from the storage and removes + * them from the storage in order to be able to save them later. + * @param {string} annotationId + * @returns {Object|null} The data associated to the annotation or null. + */ + getAndRemoveDataFromAnnotationStorage(annotationId) { + if (!this.#annotationStorage) { + return null; + } + const key = `${AnnotationEditorPrefix}${annotationId}`; + const storedValue = this.#annotationStorage.getRawValue(key); + if (storedValue) { + this.#annotationStorage.remove(key); + } + return storedValue; + } + /** * Add an editor in the annotation storage. * @param {AnnotationEditor} editor