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