diff --git a/chrome/content/zotero/xpcom/editorInstance.js b/chrome/content/zotero/xpcom/editorInstance.js index 627018814b..e2f7dcffce 100644 --- a/chrome/content/zotero/xpcom/editorInstance.js +++ b/chrome/content/zotero/xpcom/editorInstance.js @@ -25,6 +25,8 @@ Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm"); +import FilePicker from 'zotero/modules/filePicker'; + // Note: TinyMCE is automatically doing some meaningless corrections to // note-editor produced HTML. Which might result to more // conflicts, especially in group libraries @@ -105,6 +107,57 @@ class EditorInstance { return text; }; + this._iframeWindow.wrappedJSObject.zoteroCopyImage = async (dataURL) => { + let parts = dataURL.split(','); + if (!parts[0].includes('base64')) { + return; + } + let mime = parts[0].match(/:(.*?);/)[1]; + let bstr = atob(parts[1]); + let n = bstr.length; + let u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + let imgTools = Components.classes["@mozilla.org/image/tools;1"] + .getService(Components.interfaces.imgITools); + let transferable = Components.classes['@mozilla.org/widget/transferable;1'] + .createInstance(Components.interfaces.nsITransferable); + let clipboardService = Components.classes['@mozilla.org/widget/clipboard;1'] + .getService(Components.interfaces.nsIClipboard); + let imgPtr = Components.classes["@mozilla.org/supports-interface-pointer;1"] + .createInstance(Components.interfaces.nsISupportsInterfacePointer); + imgPtr.data = imgTools.decodeImageFromArrayBuffer(u8arr.buffer, mime); + transferable.init(null); + transferable.addDataFlavor(mime); + transferable.setTransferData(mime, imgPtr, 0); + clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard); + }; + + this._iframeWindow.wrappedJSObject.zoteroSaveImageAs = async (dataURL) => { + let parts = dataURL.split(','); + if (!parts[0].includes('base64')) { + return; + } + let mime = parts[0].match(/:(.*?);/)[1]; + let bstr = atob(parts[1]); + let n = bstr.length; + let u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + let ext = Zotero.MIME.getPrimaryExtension(mime, ''); + let fp = new FilePicker(); + fp.init(this._iframeWindow, Zotero.getString('noteEditor.saveImageAs'), fp.modeSave); + fp.appendFilters(fp.filterImages); + fp.defaultString = Zotero.getString('fileTypes.image').toLowerCase() + '.' + ext; + let rv = await fp.show(); + if (rv === fp.returnOK || rv === fp.returnReplace) { + let outputPath = fp.file; + await OS.File.writeAtomic(outputPath, u8arr); + } + }; + this._iframeWindow.addEventListener('message', this._messageHandler); this._iframeWindow.addEventListener('error', (event) => { Zotero.logError(event.error); diff --git a/chrome/content/zotero/xpcom/reader.js b/chrome/content/zotero/xpcom/reader.js index 5e528ad160..e5344e0605 100644 --- a/chrome/content/zotero/xpcom/reader.js +++ b/chrome/content/zotero/xpcom/reader.js @@ -23,6 +23,8 @@ ***** END LICENSE BLOCK ***** */ +import FilePicker from 'zotero/modules/filePicker'; + class ReaderInstance { constructor() { this.pdfStateFileName = '.zotero-pdf-state'; @@ -396,6 +398,54 @@ class ReaderInstance { ); return !index; }; + + this._iframeWindow.wrappedJSObject.zoteroCopyImage = async (dataURL) => { + let parts = dataURL.split(','); + if (!parts[0].includes('base64')) { + return; + } + let bstr = atob(parts[1]); + let n = bstr.length; + let u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + let imgTools = Components.classes["@mozilla.org/image/tools;1"] + .getService(Components.interfaces.imgITools); + let transferable = Components.classes['@mozilla.org/widget/transferable;1'] + .createInstance(Components.interfaces.nsITransferable); + let clipboardService = Components.classes['@mozilla.org/widget/clipboard;1'] + .getService(Components.interfaces.nsIClipboard); + let imgPtr = Components.classes["@mozilla.org/supports-interface-pointer;1"] + .createInstance(Components.interfaces.nsISupportsInterfacePointer); + let mimeType = `image/png`; + imgPtr.data = imgTools.decodeImageFromArrayBuffer(u8arr.buffer, mimeType); + transferable.init(null); + transferable.addDataFlavor(mimeType); + transferable.setTransferData(mimeType, imgPtr, 0); + clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard); + }; + + this._iframeWindow.wrappedJSObject.zoteroSaveImageAs = async (dataURL) => { + let fp = new FilePicker(); + fp.init(this._iframeWindow, Zotero.getString('pdfReader.saveImageAs'), fp.modeSave); + fp.appendFilter("PNG", "*.png"); + fp.defaultString = Zotero.getString('fileTypes.image').toLowerCase() + '.png'; + let rv = await fp.show(); + if (rv === fp.returnOK || rv === fp.returnReplace) { + let outputPath = fp.file; + let parts = dataURL.split(','); + if (parts[0].includes('base64')) { + let bstr = atob(parts[1]); + let n = bstr.length; + let u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + await OS.File.writeAtomic(outputPath, u8arr); + } + } + }; } async _setState(state) { @@ -641,6 +691,26 @@ class ReaderInstance { } // Separator popup.appendChild(this._window.document.createElement('menuseparator')); + + if (data.enableImageOptions) { + // Copy Image + menuitem = this._window.document.createElement('menuitem'); + menuitem.setAttribute('label', Zotero.getString('pdfReader.copyImage')); + menuitem.addEventListener('command', () => { + this._postMessage({ action: 'popupCmd', cmd: 'copyImage', data }); + }); + popup.appendChild(menuitem); + // Save Image As… + menuitem = this._window.document.createElement('menuitem'); + menuitem.setAttribute('label', Zotero.getString('pdfReader.saveImageAs')); + menuitem.addEventListener('command', () => { + this._postMessage({ action: 'popupCmd', cmd: 'saveImageAs', data }); + }); + popup.appendChild(menuitem); + // Separator + popup.appendChild(this._window.document.createElement('menuseparator')); + } + // Delete menuitem = this._window.document.createElement('menuitem'); menuitem.setAttribute('label', Zotero.getString('general.delete')); diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index c9cf8bcfae..b23cb46e2c 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -1345,6 +1345,8 @@ noteEditor.removeCitations = Hide Annotation Citations noteEditor.image = Image noteEditor.math = Math noteEditor.table = Table +noteEditor.copyImage = Copy Image +noteEditor.saveImageAs = Save Image As… noteEditor.insertRowBefore = Insert Row Above noteEditor.insertRowAfter = Insert Row Below noteEditor.insertColumnBefore = Insert Column Left @@ -1386,6 +1388,8 @@ pdfReader.rotateRight = Rotate Right pdfReader.rotate180 = Rotate 180° pdfReader.editPageNumber = Edit Page Number… pdfReader.editHighlightedText = Edit Highlighted Text +pdfReader.copyImage = Copy Image +pdfReader.saveImageAs = Save Image As… pdfReader.pageNumberPopupHeader = Change page number for: pdfReader.thisAnnotation = This annotation pdfReader.selectedAnnotations = Selected annotations diff --git a/note-editor b/note-editor index 658dd43e19..aa26f84708 160000 --- a/note-editor +++ b/note-editor @@ -1 +1 @@ -Subproject commit 658dd43e19e77fe89cd0e0e9342b0ab2427345a7 +Subproject commit aa26f8470849ac06a4808ccb8d0c93bac717da11 diff --git a/pdf-reader b/pdf-reader index 65d5acf2c2..2ee130e0ec 160000 --- a/pdf-reader +++ b/pdf-reader @@ -1 +1 @@ -Subproject commit 65d5acf2c26f86a29895c3d66b3292ff512c3c61 +Subproject commit 2ee130e0ecbbfe34807a5d5e2b3500ad6d99a57e