diff --git a/chrome/content/zotero/xpcom/editorInstance.js b/chrome/content/zotero/xpcom/editorInstance.js index 70957bc8a5..5ebbe6e0e3 100644 --- a/chrome/content/zotero/xpcom/editorInstance.js +++ b/chrome/content/zotero/xpcom/editorInstance.js @@ -26,10 +26,10 @@ class EditorInstance { constructor() { this.instanceID = Zotero.Utilities.randomString(); - Zotero.Notes.registerEditorInstance(this); } async init(options) { + Zotero.Notes.registerEditorInstance(this); this.onNavigate = options.onNavigate; this._item = options.item; this._readOnly = options.readOnly; @@ -135,9 +135,9 @@ class EditorInstance { } async insertAnnotations(annotations) { - let list = await this._annotationsToInsertionList(annotations); - if (list.length) { - this._postMessage({ action: 'insertAnnotationsAndCitations', list, pos: -1 }); + let html = await this._digestAnnotations(annotations); + if (html) { + this._postMessage({ action: 'insertHtml', pos: -1, html }); } } @@ -158,63 +158,125 @@ class EditorInstance { _handleFontChange = () => { this._postMessage({ action: 'updateFont', font: this._getFont() }); } - - async _digestExternalNote(itemID) { - let item = await Zotero.Items.getAsync(itemID); - item._loaded.childItems = true; - let note = item.note; - let attachments = await Zotero.Items.getAsync(item.getAttachments()); - for (let attachment of attachments) { - let path = await attachment.getFilePathAsync(); - let buf = await OS.File.read(path, {}); - buf = new Uint8Array(buf).buffer; - let blob = new this._iframeWindow.Blob([buf], { type: attachment.attachmentContentType }); - let clonedAttachment = await Zotero.Attachments.importEmbeddedImage({ - blob, - parentItemID: this._item.id, - saveOptions: { - notifierData: { - noteEditorID: this.instanceID - } - } - }); - note = note.replace(attachment.key, clonedAttachment.key); - } - return note; - } - async _annotationsToInsertionList(annotations) { - let list = []; + async _digestAnnotations(annotations) { + let html = ''; for (let annotation of annotations) { let attachmentItem = await Zotero.Items.getAsync(annotation.itemId); if (!attachmentItem) { continue; } - let item = attachmentItem.parentID && await Zotero.Items.getAsync(attachmentItem.parentID) || attachmentItem; + + let citationHTML = ''; + let imageHTML = ''; + let highlightHTML = ''; + let commentHTML = ''; + annotation.uri = Zotero.URI.getItemURI(attachmentItem); - if (item !== attachmentItem) { - annotation.parentURI = Zotero.URI.getItemURI(item); - + + // Citation + let parentItem = attachmentItem.parentID && await Zotero.Items.getAsync(attachmentItem.parentID) || attachmentItem; + if (parentItem) { + annotation.parentURI = Zotero.URI.getItemURI(parentItem); let citationItem = { - uris: [Zotero.URI.getItemURI(item)], - itemData: Zotero.Cite.System.prototype.retrieveItem(item), + uris: [Zotero.URI.getItemURI(parentItem)], + // TODO: Find a more elegant way to call this method + itemData: Zotero.Cite.System.prototype.retrieveItem(parentItem), locator: annotation.pageLabel }; - annotation.citationItem = citationItem; - let citation = { citationItems: [citationItem], properties: {} }; - let formattedCitation = (await this._getFormattedCitationParts(citation)).join(';'); - list.push({ annotation, citation, formattedCitation }); + let formatted = (await this._getFormattedCitationParts(citation)).join(';'); + citationHTML = `(${formatted})`; } - else { - list.push({ annotation }); + + // Image + if (annotation.image) { + let blob = this._dataURLtoBlob(annotation.image); + delete annotation.image; + let imageAttachment = await Zotero.Attachments.importEmbeddedImage({ + blob, + parentItemID: this._item.id, + saveOptions: { + notifierData: { + noteEditorID: this.instanceID + } + } + }); + + // Normalize image dimensions to 1.25 of the print size + let rect = annotation.position.rects[0]; + let rectWidth = rect[2] - rect[0]; + let rectHeight = rect[3] - rect[1]; + // Constants from pdf.js + const CSS_UNITS = 96.0 / 72.0; + const PDFJS_DEFAULT_SCALE = 1.25; + let width = Math.round(rectWidth * CSS_UNITS * PDFJS_DEFAULT_SCALE); + let height = Math.round(rectHeight * width / rectWidth); + imageHTML = ``; + } + + // Text + if (annotation.text) { + highlightHTML = `"${annotation.text}"`; + } + + // Note + if (annotation.comment) { + commentHTML = ' ' + annotation.comment; + } + + html += '

' + imageHTML + [highlightHTML, citationHTML, commentHTML].filter(x => x).join(' ') + '

\n'; + } + return html; + } + + async _digestItems(ids) { + let html = ''; + for (let id of ids) { + let item = await Zotero.Items.getAsync(id); + if (!item) { + continue; + } + if (item.isRegularItem()) { + let citation = { + citationItems: [{ + uris: [Zotero.URI.getItemURI(item)], + itemData: Zotero.Cite.System.prototype.retrieveItem(item) + }], + properties: {} + }; + let formatted = (await this._getFormattedCitationParts(citation)).join(';'); + html += `

(${formatted})

`; + } + else if (item.isNote()) { + // TODO: Remove when fixed + item._loaded.childItems = true; + let note = item.note; + let attachments = await Zotero.Items.getAsync(item.getAttachments()); + for (let attachment of attachments) { + let path = await attachment.getFilePathAsync(); + let buf = await OS.File.read(path, {}); + buf = new Uint8Array(buf).buffer; + let blob = new (Zotero.getMainWindow()).Blob([buf], { type: attachment.attachmentContentType }); + let clonedAttachment = await Zotero.Attachments.importEmbeddedImage({ + blob, + parentItemID: this._item.id, + saveOptions: { + notifierData: { + noteEditorID: this.instanceID + } + } + }); + note = note.replace(attachment.key, clonedAttachment.key); + } + html += `

${note}

`; } } - return list; + return html; } _messageHandler = async (e) => { @@ -225,40 +287,17 @@ class EditorInstance { switch (message.action) { case 'insertObject': { let { type, data, pos } = message; - let list = []; + let html = ''; if (type === 'zotero/item') { let ids = data.split(',').map(id => parseInt(id)); - for (let id of ids) { - let item = await Zotero.Items.getAsync(id); - if (!item) { - continue; - } - - if (item.isRegularItem()) { - let citation = { - citationItems: [{ - uris: [Zotero.URI.getItemURI(item)], - itemData: Zotero.Cite.System.prototype.retrieveItem(item) - }], - properties: {} - }; - - let formattedCitation = (await this._getFormattedCitationParts(citation)).join(';'); - - list.push({ citation, formattedCitation }); - } - else if (item.isNote()) { - let note = await this._digestExternalNote(item.id); - list.push({ note }); - } - } + html = await this._digestItems(ids); } else if (type === 'zotero/annotation') { let annotations = JSON.parse(data); - list = await this._annotationsToInsertionList(annotations); + html = await this._digestAnnotations(annotations); } - if (list.length) { - this._postMessage({ action: 'insertAnnotationsAndCitations', list, pos }); + if (html) { + this._postMessage({ action: 'insertHtml', pos, html }); } return; } @@ -329,9 +368,9 @@ class EditorInstance { } case 'generateCitation': { let { citation, pos } = message; - let formattedCitation = (await this._getFormattedCitationParts(citation)).join(';'); - let list = [{ citation, formattedCitation }]; - this._postMessage({ action: 'insertAnnotationsAndCitations', list, pos }); + let formatted = (await this._getFormattedCitationParts(citation)).join(';'); + let html = `(${formatted})`; + this._postMessage({ action: 'insertHtml', pos, html }); return; } case 'subscribeProvider': { @@ -674,7 +713,7 @@ class EditorInstance { u8arr[n] = bstr.charCodeAt(n); } - return new this._iframeWindow.Blob([u8arr], { type: mime }); + return new (Zotero.getMainWindow()).Blob([u8arr], { type: mime }); } return null; } @@ -835,6 +874,37 @@ class EditorInstance { wrappedJSObject: io }); } + + static canCreateNoteFromAnnotations(item) { + return item.parentID && item.isAttachment() && item.attachmentContentType === 'application/pdf'; + } + + static async createNoteFromAnnotations(attachmentItem) { + if (!this.canCreateNoteFromAnnotations(attachmentItem)) { + return; + } + let annotations = attachmentItem.getAnnotations(); + if (!annotations.length) { + return; + } + let note = new Zotero.Item('note'); + note.libraryID = attachmentItem.libraryID; + note.parentID = attachmentItem.parentID; + await note.saveTx(); + let editorInstance = new EditorInstance(); + editorInstance._item = note; + let jsonAnnotations = []; + for (let annotation of annotations) { + annotation._loaded.childItems = true; + let jsonAnnotation = await Zotero.Annotations.toJSON(annotation); + jsonAnnotation.itemId = attachmentItem.id; + jsonAnnotations.push(jsonAnnotation); + } + let html = `

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

\n`; + html += await editorInstance._digestAnnotations(jsonAnnotations); + note.setNote(html); + await note.saveTx(); + } } Zotero.EditorInstance = EditorInstance; diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index a3c061ccf0..ece61940d9 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -2745,7 +2745,8 @@ var ZoteroPane = new function() 'createParent', 'renameAttachments', 'reindexItem', - 'importAnnotations' + 'importAnnotations', + 'createNoteFromAnnotations' ]; var m = {}; @@ -2973,6 +2974,10 @@ var ZoteroPane = new function() if (Zotero.PDFWorker.canImport(item)) { show.push(m.importAnnotations); } + + if (Zotero.EditorInstance.canCreateNoteFromAnnotations(item)) { + show.push(m.createNoteFromAnnotations); + } } // Update attachment submenu @@ -4666,7 +4671,16 @@ var ZoteroPane = new function() } } }; - + + this.createNoteFromSelected = function () { + if (!this.canEdit()) { + this.displayCannotEditLibraryMessage(); + return; + } + + let item = this.getSelectedItems()[0]; + Zotero.EditorInstance.createNoteFromAnnotations(item); + }; this.createEmptyParent = async function (item) { await Zotero.DB.executeTransaction(async function () { diff --git a/chrome/content/zotero/zoteroPane.xul b/chrome/content/zotero/zoteroPane.xul index b2d112bda9..63704375ae 100644 --- a/chrome/content/zotero/zoteroPane.xul +++ b/chrome/content/zotero/zoteroPane.xul @@ -312,6 +312,7 @@ + diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd index 709faee992..45950a6431 100644 --- a/chrome/locale/en-US/zotero/zotero.dtd +++ b/chrome/locale/en-US/zotero/zotero.dtd @@ -101,6 +101,7 @@ + diff --git a/zotero-note-editor b/zotero-note-editor index db2bfc6cbb..73808236ba 160000 --- a/zotero-note-editor +++ b/zotero-note-editor @@ -1 +1 @@ -Subproject commit db2bfc6cbb724dbb1a22bafc324357a1bd61c3b1 +Subproject commit 73808236baa117a0e712e21f3c0ac06af1a553f0