Copy embedded images, downloaded when needed or show a warning (#2129)
This commit is contained in:
parent
a40ac2907e
commit
3c658fc672
7 changed files with 167 additions and 27 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue