Copy embedded images, downloaded when needed or show a warning (#2129)

This commit is contained in:
Martynas Bagdonas 2021-07-26 13:04:38 +03:00 committed by GitHub
parent a40ac2907e
commit 3c658fc672
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 167 additions and 27 deletions

View file

@ -445,6 +445,40 @@ Zotero.Attachments = new function(){
};
/**
* Copy an image from one note to another
*
* @param {Object} params
* @param {Zotero.Item} params.attachment - Image attachment to copy
* @param {Zotero.Item} params.note - Note item to add attachment to
* @param {Object} [params.saveOptions] - Options to pass to Zotero.Item::save()
* @return {Promise<Zotero.Item>}
*/
this.copyEmbeddedImage = async function ({ attachment, note, saveOptions }) {
Zotero.DB.requireTransaction();
if (!attachment.isEmbeddedImageAttachment()) {
throw new Error("'attachment' must be an embedded image");
}
if (!await attachment.fileExists()) {
throw new Error("Image attachment file doesn't exist");
}
var newAttachment = attachment.clone(note.libraryID);
// Attachment path isn't copied over by clone() if libraryID is different
newAttachment.attachmentPath = attachment.attachmentPath;
newAttachment.parentID = note.id;
await newAttachment.save(saveOptions);
let dir = Zotero.Attachments.getStorageDirectory(attachment);
let newDir = await Zotero.Attachments.createDirectoryForItem(newAttachment);
await Zotero.File.copyDirectory(dir, newDir);
return newAttachment;
};
/**
* @param {Object} options
* @param {Integer} options.libraryID

View file

@ -2043,6 +2043,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
yield newItem.addLinkedItem(item);
if (item.isNote()) {
yield Zotero.Notes.copyEmbeddedImages(item, newItem);
return newItemID;
}
@ -2058,7 +2059,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
yield newNote.save({
skipSelect: true
})
yield Zotero.Notes.copyEmbeddedImages(note, newNote);
yield newNote.addLinkedItem(note);
}
}

View file

@ -190,6 +190,92 @@ Zotero.Notes = new function() {
return doc.body.innerHTML;
};
/**
* Download embedded images if they don't exist locally
*
* @param {Zotero.Item} item
* @returns {Promise<boolean>}
*/
this.ensureEmbeddedImagesAreAvailable = async function (item) {
var attachments = Zotero.Items.get(item.getAttachments());
for (let attachment of attachments) {
let path = await attachment.getFilePathAsync();
if (!path) {
Zotero.debug(`Image file not found for item ${attachment.key}. Trying to download`);
let fileSyncingEnabled = Zotero.Sync.Storage.Local.getEnabledForLibrary(item.libraryID);
if (!fileSyncingEnabled) {
Zotero.debug('File sync is disabled');
return false;
}
try {
let results = await Zotero.Sync.Runner.downloadFile(attachment);
if (!results || !results.localChanges) {
Zotero.debug('Download failed');
return false;
}
}
catch (e) {
Zotero.debug(e);
return false;
}
}
}
return true;
};
/**
* Copy embedded images from one note to another and update
* item keys in note HTML.
*
* Must be called after copying a note
*
* @param {Zotero.Item} fromNote
* @param {Zotero.Item} toNote
* @returns {Promise}
*/
this.copyEmbeddedImages = async function (fromNote, toNote) {
Zotero.DB.requireTransaction();
let attachments = Zotero.Items.get(fromNote.getAttachments());
if (!attachments.length) {
return;
}
let note = toNote.note;
let parser = Components.classes['@mozilla.org/xmlextras/domparser;1']
.createInstance(Components.interfaces.nsIDOMParser);
let doc = parser.parseFromString(note, 'text/html');
// Copy note image attachments and replace keys in the new note
for (let attachment of attachments) {
if (await attachment.fileExists()) {
let copiedAttachment = await Zotero.Attachments.copyEmbeddedImage({ attachment, note: toNote });
let node = doc.querySelector(`img[data-attachment-key="${attachment.key}"]`);
if (node) {
node.setAttribute('data-attachment-key', copiedAttachment.key);
}
}
}
toNote.setNote(doc.body.innerHTML);
await toNote.save({ skipDateModifiedUpdate: true });
};
this.promptToIgnoreMissingImage = function () {
let ps = Services.prompt;
let buttonFlags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING
+ ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
let index = ps.confirmEx(
null,
Zotero.getString('general.warning'),
Zotero.getString('pane.item.notes.ignoreMissingImage'),
buttonFlags,
Zotero.getString('general.continue'),
null, null, null, {}
);
return !index;
};
this.hasSchemaVersion = function (note) {
let parser = Components.classes['@mozilla.org/xmlextras/domparser;1']
.createInstance(Components.interfaces.nsIDOMParser);
@ -294,9 +380,7 @@ Zotero.Notes = new function() {
}
schemaVersion++;
metadataContainer.setAttribute('data-schema-version', schemaVersion);
note = doc.body.innerHTML;
note = note.trim();
item.setNote(note);
item.setNote(doc.body.innerHTML);
await item.saveTx({ skipDateModifiedUpdate: true });
return true;
};

View file

@ -115,6 +115,10 @@ class EditorInstance {
...Zotero.Intl.getPrefixedStrings('noteEditor.')
}
});
if (!this._item.isAttachment()) {
Zotero.Notes.ensureEmbeddedImagesAreAvailable(this._item);
}
}
uninit() {
@ -401,11 +405,16 @@ class EditorInstance {
async _digestItems(ids) {
let html = '';
for (let id of ids) {
let item = await Zotero.Items.getAsync(id);
if (!item) {
continue;
let items = await Zotero.Items.getAsync(ids);
for (let item of items) {
if (item.isNote()
&& !await Zotero.Notes.ensureEmbeddedImagesAreAvailable(item)
&& !Zotero.Notes.promptToIgnoreMissingImage()) {
return null;
}
}
for (let item of items) {
if (item.isRegularItem()) {
let itemData = Zotero.Utilities.itemToCSLJSON(item);
let citation = {
@ -483,27 +492,26 @@ class EditorInstance {
}
// Clone all note image attachments and replace keys in the new note
let attachments = await Zotero.Items.getAsync(item.getAttachments());
let attachments = Zotero.Items.get(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 });
// Image type is not additionally filtered because it was an attachment already
let clonedAttachment = await Zotero.Attachments.importEmbeddedImage({
blob,
parentItemID: this._item.id,
saveOptions: {
notifierData: {
noteEditorID: this.instanceID
if (!await attachment.fileExists()) {
continue;
}
await Zotero.DB.executeTransaction(async () => {
let copiedAttachment = await Zotero.Attachments.copyEmbeddedImage({
attachment,
note: this._item,
saveOptions: {
notifierData: {
noteEditorID: this.instanceID
}
}
});
let node = doc.querySelector(`img[data-attachment-key="${attachment.key}"]`);
if (node) {
node.setAttribute('data-attachment-key', copiedAttachment.key);
}
});
let node = doc.querySelector(`img[data-attachment-key="${attachment.key}"]`);
if (node) {
node.setAttribute('data-attachment-key', clonedAttachment.key);
}
}
html += `<p></p>${doc.body.innerHTML}<p></p>`;
@ -534,6 +542,9 @@ class EditorInstance {
if (type === 'zotero/item') {
let ids = data.split(',').map(id => parseInt(id));
html = await this._digestItems(ids);
if (!html) {
return;
}
}
else if (type === 'zotero/annotation') {
let annotations = JSON.parse(data);

View file

@ -880,8 +880,8 @@ Zotero.Sync.Runner_Module = function (options = {}) {
return false;
}
if (!item.isImportedAttachment()) {
throw new Error("Not an imported attachment");
if (!item.isStoredFileAttachment()) {
throw new Error("Not a stored file attachment");
}
if (yield item.getFilePathAsync()) {

View file

@ -1784,6 +1784,12 @@ var ZoteroPane = new function()
}
var item = self.getSelectedItems()[0];
if (item.isNote()
&& !(yield Zotero.Notes.ensureEmbeddedImagesAreAvailable(item))
&& !Zotero.Notes.promptToIgnoreMissingImage()) {
return;
}
var newItem;
yield Zotero.DB.executeTransaction(function* () {
@ -1793,6 +1799,9 @@ var ZoteroPane = new function()
newItem.setCollections([self.collectionsView.selectedTreeRow.ref.id]);
}
yield newItem.save();
if (item.isNote()) {
yield Zotero.Notes.copyEmbeddedImages(item, newItem);
}
for (let relItemKey of item.relatedItems) {
try {
let relItem = yield Zotero.Items.getByLibraryAndKeyAsync(item.libraryID, relItemKey);

View file

@ -381,6 +381,7 @@ pane.item.notes.untitled = Untitled Note
pane.item.notes.delete.confirm = Are you sure you want to delete this note?
pane.item.notes.count = %1$S note;%1$S notes
pane.item.notes.editingInWindow = Editing in separate window
pane.item.notes.ignoreMissingImage = Some note images are missing and cannot be copied.
pane.item.attachments.rename.title = New title:
pane.item.attachments.rename.renameAssociatedFile = Rename associated file
pane.item.attachments.rename.error = An error occurred while renaming the file.