mirror of
https://github.com/zotero/zotero.git
synced 2026-10-03 02:21:49 +00:00
Move cross-library item copying to Zotero.Items
This commit is contained in:
parent
af5ab8427b
commit
a7df23cdb8
3 changed files with 199 additions and 166 deletions
|
|
@ -2045,159 +2045,11 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a given item into another library. Used when we need to create a copy of a collection
|
||||
* in another library if collection is drag-dropped into a group it is not a part of.
|
||||
*/
|
||||
async _copyItem({ item, targetLibraryID, targetTreeRow, options }) {
|
||||
// Check if there's already a copy of this item in the library
|
||||
var linkedItem = await item.getLinkedItem(targetLibraryID, true);
|
||||
if (linkedItem) {
|
||||
return linkedItem.id;
|
||||
|
||||
/*
|
||||
// TODO: support tags, related, attachments, etc.
|
||||
|
||||
// Overlay source item fields on unsaved clone of linked item
|
||||
var newItem = item.clone(false, linkedItem.clone(true));
|
||||
newItem.setField('dateAdded', item.dateAdded);
|
||||
newItem.setField('dateModified', item.dateModified);
|
||||
|
||||
var diff = newItem.diff(linkedItem, false, ["dateAdded", "dateModified"]);
|
||||
if (!diff) {
|
||||
// Check if creators changed
|
||||
var creatorsChanged = false;
|
||||
|
||||
var creators = item.getCreators();
|
||||
var linkedCreators = linkedItem.getCreators();
|
||||
if (creators.length != linkedCreators.length) {
|
||||
Zotero.debug('Creators have changed');
|
||||
creatorsChanged = true;
|
||||
}
|
||||
else {
|
||||
for (var i=0; i<creators.length; i++) {
|
||||
if (!creators[i].ref.equals(linkedCreators[i].ref)) {
|
||||
Zotero.debug('changed');
|
||||
creatorsChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!creatorsChanged) {
|
||||
Zotero.debug("Linked item hasn't changed -- skipping conflict resolution");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
toReconcile.push([newItem, linkedItem]);
|
||||
continue;
|
||||
*/
|
||||
}
|
||||
|
||||
// Standalone attachment
|
||||
if (item.isAttachment()) {
|
||||
var linkMode = item.attachmentLinkMode;
|
||||
|
||||
// Skip linked files
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
Zotero.debug("Skipping standalone linked file attachment on drag");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!targetTreeRow.filesEditable) {
|
||||
Zotero.debug("Skipping standalone file attachment on drag");
|
||||
return false;
|
||||
}
|
||||
|
||||
let newAttachment = await Zotero.Attachments.copyAttachmentToLibrary(item, targetLibraryID);
|
||||
if (options.annotations) {
|
||||
await Zotero.Items.copyChildItems(item, newAttachment);
|
||||
}
|
||||
|
||||
return newAttachment.id;
|
||||
}
|
||||
|
||||
// Create new clone item in target library
|
||||
var newItem = item.clone(targetLibraryID, { skipTags: !options.tags });
|
||||
|
||||
var newItemID = await newItem.save({
|
||||
skipSelect: true
|
||||
});
|
||||
|
||||
// Record link
|
||||
await newItem.addLinkedItem(item);
|
||||
|
||||
if (item.isNote()) {
|
||||
if (Zotero.Libraries.get(newItem.libraryID).filesEditable) {
|
||||
await Zotero.Notes.copyEmbeddedImages(item, newItem);
|
||||
}
|
||||
return newItemID;
|
||||
}
|
||||
|
||||
// For regular items, add child items if prefs and permissions allow
|
||||
|
||||
// Child notes
|
||||
if (options.childNotes) {
|
||||
var noteIDs = item.getNotes();
|
||||
var notes = Zotero.Items.get(noteIDs);
|
||||
for (let note of notes) {
|
||||
let newNote = note.clone(targetLibraryID, { skipTags: !options.tags });
|
||||
newNote.parentID = newItemID;
|
||||
await newNote.save({
|
||||
skipSelect: true
|
||||
})
|
||||
|
||||
if (Zotero.Libraries.get(newNote.libraryID).filesEditable) {
|
||||
await Zotero.Notes.copyEmbeddedImages(note, newNote);
|
||||
}
|
||||
await newNote.addLinkedItem(note);
|
||||
}
|
||||
}
|
||||
|
||||
// Child attachments
|
||||
if (options.childLinks || options.childFileAttachments) {
|
||||
var attachmentIDs = item.getAttachments();
|
||||
var attachments = Zotero.Items.get(attachmentIDs);
|
||||
for (let attachment of attachments) {
|
||||
var linkMode = attachment.attachmentLinkMode;
|
||||
|
||||
// Skip linked files
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
Zotero.debug("Skipping child linked file attachment on drag");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip imported files if we don't have pref and permissions
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
if (!options.childLinks) {
|
||||
Zotero.debug("Skipping child link attachment on drag");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!options.childFileAttachments
|
||||
|| (!targetTreeRow.filesEditable && !targetTreeRow.isPublications())) {
|
||||
Zotero.debug("Skipping child file attachment on drag");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let newAttachment = await Zotero.Attachments.copyAttachmentToLibrary(
|
||||
attachment, targetLibraryID, newItemID
|
||||
);
|
||||
|
||||
if (options.annotations) {
|
||||
await Zotero.Items.copyChildItems(attachment, newAttachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newItemID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function used by executeCollectionCopy to recursively copy collections from one library
|
||||
* into another, or from one collection to another within the same library.
|
||||
*/
|
||||
async _copyCollections({ descendents, parentID, addItems, targetLibraryID, targetTreeRow, copyOptions }) {
|
||||
async _copyCollections({ descendents, parentID, addItems, targetLibraryID, copyOptions }) {
|
||||
for (var desc of descendents) {
|
||||
// Collections
|
||||
if (desc.type == 'collection') {
|
||||
|
|
@ -2219,7 +2071,6 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
parentID: collectionID,
|
||||
addItems,
|
||||
targetLibraryID,
|
||||
targetTreeRow,
|
||||
copyOptions
|
||||
});
|
||||
}
|
||||
|
|
@ -2230,12 +2081,10 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
let id = desc.id;
|
||||
// Actually copy items only if moving to another library
|
||||
if (item.libraryID !== targetLibraryID) {
|
||||
id = await this._copyItem({
|
||||
item,
|
||||
targetLibraryID,
|
||||
targetTreeRow,
|
||||
options: copyOptions
|
||||
});
|
||||
let copiedItem = await Zotero.Items.copyToLibrary(
|
||||
item, targetLibraryID, copyOptions
|
||||
);
|
||||
id = copiedItem && copiedItem.id;
|
||||
// Standalone attachments might not get copied
|
||||
if (!id) {
|
||||
continue;
|
||||
|
|
@ -2274,10 +2123,9 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
* @param {Zotero.Collection} collection - collection to copy
|
||||
* @param {String} targetCollectionID - id of the collection to copy to
|
||||
* @param {String} targetLibraryID - id of the library to copy to
|
||||
* @param {Zotero.CollectionTreeRow } targetTreeRow - tree row of the target
|
||||
* @param {Object} copyOptions - options how to perform the copy - see onDrop for an example
|
||||
*/
|
||||
async executeCollectionCopy({ collection, targetCollectionID, targetLibraryID, targetTreeRow, copyOptions }) {
|
||||
async executeCollectionCopy({ collection, targetCollectionID, targetLibraryID, copyOptions }) {
|
||||
await Zotero.DB.executeTransaction(async () => {
|
||||
var collections = [{
|
||||
id: collection.id,
|
||||
|
|
@ -2291,7 +2139,6 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
parentID: targetCollectionID,
|
||||
addItems,
|
||||
targetLibraryID,
|
||||
targetTreeRow,
|
||||
copyOptions
|
||||
});
|
||||
for (let [collectionID, items] of addItems.entries()) {
|
||||
|
|
@ -2388,7 +2235,6 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
collection: droppedCollection,
|
||||
targetCollectionID,
|
||||
targetLibraryID,
|
||||
targetTreeRow,
|
||||
copyOptions
|
||||
});
|
||||
}
|
||||
|
|
@ -2507,12 +2353,10 @@ var CollectionTree = class CollectionTree extends LibraryTree {
|
|||
return Zotero.DB.executeTransaction(async () => {
|
||||
let copiedItemIDs = [];
|
||||
for (let item of chunk) {
|
||||
var id = await this._copyItem({
|
||||
item,
|
||||
targetLibraryID,
|
||||
targetTreeRow,
|
||||
options: copyOptions
|
||||
});
|
||||
let copiedItem = await Zotero.Items.copyToLibrary(
|
||||
item, targetLibraryID, copyOptions
|
||||
);
|
||||
let id = copiedItem && copiedItem.id;
|
||||
// Standalone attachments might not get copied
|
||||
if (!id) {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -967,6 +967,128 @@ Zotero.Items = function () {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* Copy an item to another library, including enabled child items
|
||||
*
|
||||
* Requires a transaction
|
||||
*
|
||||
* @param {Zotero.Item} item
|
||||
* @param {Integer} targetLibraryID
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.tags]
|
||||
* @param {Boolean} [options.childNotes]
|
||||
* @param {Boolean} [options.childLinks]
|
||||
* @param {Boolean} [options.childFileAttachments]
|
||||
* @param {Boolean} [options.annotations]
|
||||
* @return {Promise<Zotero.Item|false>} The copied or existing linked item, or false if an
|
||||
* attachment was skipped
|
||||
*/
|
||||
this.copyToLibrary = async function (item, targetLibraryID, options = {}) {
|
||||
Zotero.DB.requireTransaction();
|
||||
|
||||
if (!item.isRegularItem() && !item.isNote() && !item.isAttachment()) {
|
||||
throw new Error("Only regular items, notes, and attachments can be copied between libraries");
|
||||
}
|
||||
|
||||
// Check if there's already a copy of this item in the library
|
||||
let linkedItem = await item.getLinkedItem(targetLibraryID, true);
|
||||
if (linkedItem) {
|
||||
// TODO: Support reconciling tags, related items, attachments, etc.
|
||||
return linkedItem;
|
||||
}
|
||||
|
||||
let targetLibrary = Zotero.Libraries.get(targetLibraryID);
|
||||
let {
|
||||
tags: copyTags = Zotero.Prefs.get('groups.copyTags'),
|
||||
childNotes: copyChildNotes = Zotero.Prefs.get('groups.copyChildNotes'),
|
||||
childLinks: copyChildLinks = Zotero.Prefs.get('groups.copyChildLinks'),
|
||||
childFileAttachments: copyChildFileAttachments
|
||||
= Zotero.Prefs.get('groups.copyChildFileAttachments'),
|
||||
annotations: copyAnnotations = Zotero.Prefs.get('groups.copyAnnotations'),
|
||||
} = options;
|
||||
|
||||
// Standalone attachment
|
||||
if (item.isAttachment()) {
|
||||
// Skip linked files
|
||||
if (item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
Zotero.debug("Skipping standalone linked file attachment on copy");
|
||||
return false;
|
||||
}
|
||||
if (!targetLibrary.filesEditable) {
|
||||
Zotero.debug("Skipping standalone file attachment on copy");
|
||||
return false;
|
||||
}
|
||||
let newAttachment = await Zotero.Attachments.copyAttachmentToLibrary(item, targetLibraryID);
|
||||
if (copyAnnotations) {
|
||||
await this.copyChildItems(item, newAttachment);
|
||||
}
|
||||
return newAttachment;
|
||||
}
|
||||
|
||||
// Create new clone item in target library
|
||||
let newItem = item.clone(targetLibraryID, { skipTags: !copyTags });
|
||||
await newItem.save({ skipSelect: true });
|
||||
|
||||
// Record link
|
||||
await newItem.addLinkedItem(item);
|
||||
|
||||
if (item.isNote()) {
|
||||
if (targetLibrary.filesEditable) {
|
||||
await Zotero.Notes.copyEmbeddedImages(item, newItem);
|
||||
}
|
||||
return newItem;
|
||||
}
|
||||
|
||||
// For regular items, add child items if prefs and permissions allow
|
||||
|
||||
// Child notes
|
||||
if (copyChildNotes) {
|
||||
for (let note of Zotero.Items.get(item.getNotes())) {
|
||||
let newNote = note.clone(targetLibraryID, { skipTags: !copyTags });
|
||||
newNote.parentID = newItem.id;
|
||||
await newNote.save({ skipSelect: true });
|
||||
if (targetLibrary.filesEditable) {
|
||||
await Zotero.Notes.copyEmbeddedImages(note, newNote);
|
||||
}
|
||||
await newNote.addLinkedItem(note);
|
||||
}
|
||||
}
|
||||
|
||||
// Child attachments
|
||||
if (copyChildLinks || copyChildFileAttachments) {
|
||||
for (let attachment of Zotero.Items.get(item.getAttachments())) {
|
||||
let linkMode = attachment.attachmentLinkMode;
|
||||
|
||||
// Skip linked files
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
|
||||
Zotero.debug("Skipping child linked file attachment on copy");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip imported files if we don't have pref and permissions
|
||||
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
|
||||
if (!copyChildLinks) {
|
||||
Zotero.debug("Skipping child link attachment on copy");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!copyChildFileAttachments || !targetLibrary.filesEditable) {
|
||||
Zotero.debug("Skipping child file attachment on copy");
|
||||
continue;
|
||||
}
|
||||
let newAttachment = await Zotero.Attachments.copyAttachmentToLibrary(
|
||||
attachment, targetLibraryID, newItem.id
|
||||
);
|
||||
if (copyAnnotations) {
|
||||
await this.copyChildItems(attachment, newAttachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newItem;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Copy child items from one item to another (e.g., in another library)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -153,6 +153,73 @@ describe("Zotero.Items", function () {
|
|||
});
|
||||
|
||||
|
||||
describe("#copyToLibrary()", function () {
|
||||
var group;
|
||||
var copyChildNotes;
|
||||
|
||||
before(async function () {
|
||||
group = await createGroup();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
copyChildNotes = Zotero.Prefs.get('groups.copyChildNotes');
|
||||
Zotero.Prefs.set('groups.copyChildNotes', true);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Zotero.Prefs.set('groups.copyChildNotes', copyChildNotes);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await group.eraseTx();
|
||||
});
|
||||
|
||||
it("should copy and link a regular item into a group and reuse it", async function () {
|
||||
var item = await createDataObject('item');
|
||||
var newItem;
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
newItem = await Zotero.Items.copyToLibrary(item, group.libraryID);
|
||||
});
|
||||
|
||||
assert.instanceOf(newItem, Zotero.Item);
|
||||
assert.equal(newItem.libraryID, group.libraryID);
|
||||
assert.equal(await item.getLinkedItem(group.libraryID, true), newItem);
|
||||
|
||||
var reusedItem;
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
reusedItem = await Zotero.Items.copyToLibrary(item, group.libraryID);
|
||||
});
|
||||
assert.strictEqual(reusedItem, newItem);
|
||||
});
|
||||
|
||||
it("should copy a child note", async function () {
|
||||
var item = await createDataObject('item');
|
||||
var note = await createDataObject('item', { itemType: 'note', parentID: item.id });
|
||||
var newItem;
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
newItem = await Zotero.Items.copyToLibrary(item, group.libraryID);
|
||||
});
|
||||
|
||||
var notes = Zotero.Items.get(newItem.getNotes());
|
||||
assert.lengthOf(notes, 1);
|
||||
assert.equal(await note.getLinkedItem(group.libraryID, true), notes[0]);
|
||||
});
|
||||
|
||||
it("should honor disabled child-note copying", async function () {
|
||||
var item = await createDataObject('item');
|
||||
await createDataObject('item', { itemType: 'note', parentID: item.id });
|
||||
var newItem;
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
newItem = await Zotero.Items.copyToLibrary(item, group.libraryID, {
|
||||
childNotes: false,
|
||||
});
|
||||
});
|
||||
|
||||
assert.isEmpty(newItem.getNotes());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("#copyChildItems()", function () {
|
||||
var group;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue