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.)
This commit is contained in:
Dan Stillman 2021-02-24 01:38:15 -05:00
parent 3e37d31a49
commit 2c66133c7e
6 changed files with 114 additions and 25 deletions

View file

@ -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);

View file

@ -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<Zotero.Item|undefined>}
* @returns {Promise<Zotero.Item>}
*/
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 = `<p>(${(new Date()).toLocaleString()})</p>\n`;
html += await editorInstance._digestAnnotations(jsonAnnotations);
let html = `<h1>${Zotero.getString('note.annotationsWithDate', new Date().toLocaleString())}</h1>\n`;
html += await editorInstance._serializeAnnotations(jsonAnnotations);
note.setNote(html);
await note.saveTx();
return note;

View file

@ -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) {

View file

@ -280,6 +280,15 @@
<menuseparator/>
<!-- with icon: <menuitem class="menuitem-iconic" id="zotero-menuitem-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemID'))"/>-->
<menuitem class="menuitem-iconic zotero-menuitem-attach-note" label="&zotero.items.menu.attach.note;" oncommand="ZoteroPane_Local.newNote(false, this.parentNode.getAttribute('itemKey'))"/>
<menuitem class="menuitem-iconic zotero-menuitem-create-note-from-annotations"
label="&zotero.items.menu.attach.noteFromAnnotations;"
oncommand="ZoteroPane.createNoteFromAnnotationsFromSelected()"/>
<menu class="menuitem-iconic zotero-menuitem-create-note-from-annotations"
label="&zotero.items.menu.attach.noteFromAnnotations;">
<menupopup id="create-note-from-annotations-popup"/>
</menu>
<menu class="menu-iconic zotero-menuitem-attach" label="&zotero.items.menu.attach;">
<menupopup id="zotero-add-attachment-popup">
<menuitem class="menuitem-iconic zotero-menuitem-attachments-web-link" label="&zotero.items.menu.attach.link.uri;" oncommand="var itemID = parseInt(this.parentNode.parentNode.parentNode.getAttribute('itemID')); ZoteroPane_Local.addAttachmentFromURI(true, itemID);"/>
@ -308,7 +317,6 @@
<menuitem class="menuitem-iconic zotero-menuitem-create-parent" oncommand="ZoteroPane_Local.createParentItemsFromSelected();"/>
<menuitem class="menuitem-iconic zotero-menuitem-rename-from-parent" oncommand="ZoteroPane_Local.renameSelectedAttachmentsFromParents()"/>
<menuitem class="menuitem-iconic zotero-menuitem-reindex" oncommand="ZoteroPane_Local.reindexItem();"/>
<menuitem class="menuitem-iconic zotero-menuitem-create-note-from-annotations" label="&zotero.items.menu.createNoteFromAnnotations;" oncommand="ZoteroPane.createNoteFromSelected()"/>
</menupopup>
<tooltip id="fake-tooltip"/>

View file

@ -90,6 +90,7 @@
<!ENTITY zotero.items.menu.showInLibrary "Show in Library">
<!ENTITY zotero.items.menu.attach.note "Add Note">
<!ENTITY zotero.items.menu.attach.noteFromAnnotations "Add Note from Annotations">
<!ENTITY zotero.items.menu.attach "Add Attachment">
<!ENTITY zotero.items.menu.attach.link.uri "Attach Link to URI…">
<!ENTITY zotero.items.menu.attach.file "Attach Stored Copy of File…">
@ -101,7 +102,6 @@
<!ENTITY zotero.items.menu.unrecognize "Undo Retrieve Metadata">
<!ENTITY zotero.items.menu.reportMetadata "Report Incorrect Metadata">
<!ENTITY zotero.items.menu.importAnnotations "Import Annotations">
<!ENTITY zotero.items.menu.createNoteFromAnnotations "Create Note from Annotations">
<!ENTITY zotero.duplicatesMerge.versionSelect "Choose the version of the item to use as the master item:">
<!ENTITY zotero.duplicatesMerge.fieldSelect "Select fields to keep from other versions of the item:">

View file

@ -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