Add context menu options to copy/save image from pdf-reader/note-editor

Fixes #2664
This commit is contained in:
Martynas Bagdonas 2022-09-05 16:59:07 +03:00
parent dbc65faecb
commit 80385ff893
5 changed files with 129 additions and 2 deletions

View file

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

View file

@ -23,6 +23,8 @@
***** END LICENSE BLOCK *****
*/
import FilePicker from 'zotero/modules/filePicker';
class ReaderInstance {
constructor() {
this.pdfStateFileName = '.zotero-pdf-state';
@ -395,6 +397,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) {
@ -647,6 +697,26 @@ class ReaderInstance {
}
// Separator
popup.appendChild(this._window.document.createXULElement('menuseparator'));
if (data.enableImageOptions) {
// Copy Image
menuitem = this._window.document.createXULElement('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.createXULElement('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.createXULElement('menuseparator'));
}
// Delete
menuitem = this._window.document.createXULElement('menuitem');
menuitem.setAttribute('label', Zotero.getString('general.delete'));

View file

@ -1340,6 +1340,8 @@ noteEditor.addCitations = Show Annotation Citations
noteEditor.removeCitations = Hide Annotation Citations
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
@ -1381,6 +1383,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

@ -1 +1 @@
Subproject commit 29372d3801665c21ebc0a8d6661df88bebd0ea29
Subproject commit aa26f8470849ac06a4808ccb8d0c93bac717da11

@ -1 +1 @@
Subproject commit 65d5acf2c26f86a29895c3d66b3292ff512c3c61
Subproject commit 2ee130e0ecbbfe34807a5d5e2b3500ad6d99a57e