From 2c66133c7eed4ebba08fb4c69b26562139abba96 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Wed, 24 Feb 2021 01:38:15 -0500 Subject: [PATCH] Add "Add Note from Annotations" menu option on items Shows as a menu option when there's a single PDF attachment with annotations and as a submenu containing titles when there are multiple (even if there's only one attachment that actually has annotations) Also removes "Create Note from Annotations" on attachment items for now. (It might make sense to add that back, though "Add" would be a little weird for creating a sibling note, while using a different label would be confusing.) --- chrome/content/zotero/xpcom/data/item.js | 36 ++++++++-- chrome/content/zotero/xpcom/editorInstance.js | 21 +++--- chrome/content/zotero/zoteroPane.js | 68 ++++++++++++++++--- chrome/content/zotero/zoteroPane.xul | 10 ++- chrome/locale/en-US/zotero/zotero.dtd | 2 +- chrome/locale/en-US/zotero/zotero.properties | 2 + 6 files changed, 114 insertions(+), 25 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index c603ac312f..94ea640532 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -2302,7 +2302,7 @@ Zotero.Item.prototype.isEmbeddedImageAttachment = function() { * @return {Boolean} - Returns true if item is a stored or linked PDF attachment */ Zotero.Item.prototype.isPDFAttachment = function () { - return this.isAttachment() && this.attachmentContentType == 'application/pdf'; + return this.isFileAttachment() && this.attachmentContentType == 'application/pdf'; }; @@ -3769,6 +3769,34 @@ Zotero.Item.prototype.isImageAnnotation = function() { } +Zotero.Item.prototype.numAnnotations = function (includeTrashed) { + if (!this.isFileAttachment()) { + throw new Error("numAnnotations() can only be called on file attachments"); + } + + this._requireData('childItems'); + + if (!this._annotations) { + return 0; + } + + var cacheKey = 'with' + (includeTrashed ? '' : 'out') + 'Trashed'; + + if (this._annotations[cacheKey]) { + return this._annotations[cacheKey].length + } + + var rows = this._annotations.rows; + // Remove trashed items if necessary + if (!includeTrashed) { + rows = rows.filter(row => !row.trashed); + } + var ids = rows.map(row => row.itemID); + this._annotations[cacheKey] = ids; + return rows.length; +}; + + /** * Returns child annotations for an attachment item * @@ -3776,8 +3804,8 @@ Zotero.Item.prototype.isImageAnnotation = function() { * @return {Zotero.Item[]} */ Zotero.Item.prototype.getAnnotations = function (includeTrashed) { - if (!this.isAttachment()) { - throw new Error("getAnnotations() can only be called on attachment items"); + if (!this.isFileAttachment()) { + throw new Error("getAnnotations() can only be called on file attachments"); } this._requireData('childItems'); @@ -3792,7 +3820,7 @@ Zotero.Item.prototype.getAnnotations = function (includeTrashed) { return Zotero.Items.get([...this._annotations[cacheKey]]); } - var rows = this._annotations.rows.concat(); + var rows = this._annotations.rows; // Remove trashed items if necessary if (!includeTrashed) { rows = rows.filter(row => !row.trashed); diff --git a/chrome/content/zotero/xpcom/editorInstance.js b/chrome/content/zotero/xpcom/editorInstance.js index f8e4b6f08d..e502804017 100644 --- a/chrome/content/zotero/xpcom/editorInstance.js +++ b/chrome/content/zotero/xpcom/editorInstance.js @@ -153,7 +153,7 @@ class EditorInstance { async insertAnnotations(annotations) { await this._ensureNoteCreated(); - let html = await this._digestAnnotations(annotations); + let html = await this._serializeAnnotations(annotations); if (html) { this._postMessage({ action: 'insertHTML', pos: -1, html }); } @@ -176,8 +176,13 @@ class EditorInstance { _handleFontChange = () => { this._postMessage({ action: 'updateFont', font: this._getFont() }); } - - async _digestAnnotations(annotations) { + + + /** + * @param {Zotero.Item[]} annotations + * @return {String} - HTML string + */ + async _serializeAnnotations(annotations) { let html = ''; for (let annotation of annotations) { let attachmentItem = await Zotero.Items.getAsync(annotation.attachmentItemID); @@ -316,7 +321,7 @@ class EditorInstance { } else if (type === 'zotero/annotation') { let annotations = JSON.parse(data); - html = await this._digestAnnotations(annotations); + html = await this._serializeAnnotations(annotations); } if (html) { this._postMessage({ action: 'insertHTML', pos, html }); @@ -966,11 +971,11 @@ class EditorInstance { * * @param {Zotero.Item[]} annotations * @param {Integer} parentID Creates standalone note if not provided - * @returns {Promise} + * @returns {Promise} */ static async createNoteFromAnnotations(annotations, parentID) { if (!annotations.length) { - return; + throw new Error("No annotations provided"); } let note = new Zotero.Item('note'); note.libraryID = annotations[0].libraryID; @@ -985,8 +990,8 @@ class EditorInstance { jsonAnnotation.attachmentItemID = attachmentItem.id; jsonAnnotations.push(jsonAnnotation); } - let html = `

(${(new Date()).toLocaleString()})

\n`; - html += await editorInstance._digestAnnotations(jsonAnnotations); + let html = `

${Zotero.getString('note.annotationsWithDate', new Date().toLocaleString())}

\n`; + html += await editorInstance._serializeAnnotations(jsonAnnotations); note.setNote(html); await note.saveTx(); return note; diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 168ace5ceb..cada77da4c 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -2735,6 +2735,8 @@ var ZoteroPane = new function() 'showInLibrary', 'sep1', 'addNote', + 'createNoteFromAnnotations', + 'createNoteFromAnnotationsMenu', 'addAttachments', 'sep2', 'findPDF', @@ -2757,7 +2759,6 @@ var ZoteroPane = new function() 'createParent', 'renameAttachments', 'reindexItem', - 'createNoteFromAnnotations' ]; var m = {}; @@ -2922,6 +2923,33 @@ var ZoteroPane = new function() if (item.isRegularItem() && !item.isFeedItem) { show.push(m.addNote, m.addAttachments, m.sep2); + + // Create Note from Annotations + let popup = document.getElementById('create-note-from-annotations-popup'); + popup.textContent = ''; + let eligibleAttachments = Zotero.Items.get(item.getAttachments()) + .filter(item => item.isPDFAttachment()); + let attachmentsWithAnnotations = eligibleAttachments.filter(x => x.numAnnotations()); + if (attachmentsWithAnnotations.length) { + // Display submenu if there's more than one PDF attachment, even if + // there's only attachment with annotations, so it's clear which one + // the annotations are coming from + if (eligibleAttachments.length > 1) { + show.push(m.createNoteFromAnnotationsMenu); + for (let attachment of attachmentsWithAnnotations) { + let menuitem = document.createElement('menuitem'); + menuitem.setAttribute('label', attachment.getDisplayTitle()); + menuitem.onclick = () => { + ZoteroPane.createNoteFromAnnotationsForAttachment(attachment); + }; + popup.appendChild(menuitem); + } + } + // Single attachment with annotations + else { + show.push(m.createNoteFromAnnotations); + } + } } if (Zotero.Attachments.canFindPDFForItem(item)) { @@ -2973,11 +3001,6 @@ var ZoteroPane = new function() else if (!collectionTreeRow.isPublications()) { show.push(m.duplicateItem); } - - - if (item.isPDFAttachment()) { - show.push(m.createNoteFromAnnotations); - } } // Update attachment submenu @@ -4679,15 +4702,38 @@ var ZoteroPane = new function() } } }; - - this.createNoteFromSelected = function () { + + + this.createNoteFromAnnotationsForAttachment = async function (attachment) { if (!this.canEdit()) { this.displayCannotEditLibraryMessage(); return; } - - let item = this.getSelectedItems()[0]; - Zotero.EditorInstance.createNoteFromAnnotations(item.getAnnotations(), item.parentID); + var note = await Zotero.EditorInstance.createNoteFromAnnotations( + attachment.getAnnotations(), attachment.parentID + ); + await this.selectItem(note.id); + }; + + + this.createNoteFromAnnotationsFromSelected = async function () { + if (!this.canEdit()) { + this.displayCannotEditLibraryMessage(); + return; + } + var item = this.getSelectedItems()[0]; + var attachment; + if (item.isRegularItem()) { + attachment = Zotero.Items.get(item.getAttachments()) + .find(x => x.isPDFAttachment() && x.numAnnotations()); + } + else if (item.isFileAttachment()) { + attachment = item; + } + else { + throw new Error("Not a regular item or file attachment"); + } + return this.createNoteFromAnnotationsForAttachment(attachment); }; this.createEmptyParent = async function (item) { diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul index ed07b6968d..e18f392a64 100644 --- a/chrome/content/zotero/zoteroPane.xul +++ b/chrome/content/zotero/zoteroPane.xul @@ -280,6 +280,15 @@ + + + + + + @@ -308,7 +317,6 @@ - diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd index 45950a6431..18c4e31a2f 100644 --- a/chrome/locale/en-US/zotero/zotero.dtd +++ b/chrome/locale/en-US/zotero/zotero.dtd @@ -90,6 +90,7 @@ + @@ -101,7 +102,6 @@ - diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index 358c28ef02..113f817555 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -620,6 +620,8 @@ findPDF.pdfWithMethod = PDF (%S) findPDF.noPDFsFound = No PDFs found findPDF.noPDFFound = No PDF found +note.annotationsWithDate = Annotations (%S) + attachment.fullText = Full Text attachment.acceptedVersion = Accepted Version attachment.submittedVersion = Submitted Version