Fixes duplicates view for async DB
It's way too slow, though, since the whole list is regenerated after merging. Fixes #519 Also: - The arguments to Zotero.Item.prototype.clone() have changed, and it no longer takes an existing item or copies primary data. To create an in-memory copy of an item, use the new Zotero.Item.prototype.copy(). - Zotero.Item.prototype.getUsedFields() now gets in-memory fields rather than the fields in the database
This commit is contained in:
parent
15d28014ed
commit
5c94119c70
|
@ -71,7 +71,7 @@
|
|||
}
|
||||
|
||||
// Clone object so changes in merge pane don't affect it
|
||||
this._leftpane.ref = val.clone(true);
|
||||
this._leftpane.ref = val.copy();
|
||||
this._leftpane.original = val;
|
||||
]]>
|
||||
</setter>
|
||||
|
@ -97,7 +97,7 @@
|
|||
}
|
||||
|
||||
// Clone object so changes in merge pane don't affect it
|
||||
this._rightpane.ref = val.clone(true);
|
||||
this._rightpane.ref = val.copy();
|
||||
this._rightpane.original = val;
|
||||
}
|
||||
|
||||
|
|
|
@ -130,16 +130,21 @@ var Zotero_Duplicates_Pane = new function () {
|
|||
// Add master item's values to the beginning of each set of
|
||||
// alternative values so that they're still available if the item box
|
||||
// modifies the item
|
||||
var diff = item.multiDiff(_otherItems, _ignoreFields);
|
||||
if (diff) {
|
||||
var itemValues = item.serialize()
|
||||
for (var i in diff) {
|
||||
diff[i].unshift(itemValues.fields[i]);
|
||||
Zotero.spawn(function* () {
|
||||
var diff = yield item.multiDiff(_otherItems, _ignoreFields);
|
||||
if (diff) {
|
||||
let itemValues = yield item.toJSON();
|
||||
for (let i in diff) {
|
||||
diff[i].unshift(itemValues[i] !== undefined ? itemValues[i] : '');
|
||||
}
|
||||
itembox.fieldAlternatives = diff;
|
||||
}
|
||||
itembox.fieldAlternatives = diff;
|
||||
}
|
||||
|
||||
itembox.item = item.clone(true);
|
||||
|
||||
var newItem = yield item.copy();
|
||||
yield newItem.loadItemData();
|
||||
yield newItem.loadCreators();
|
||||
itembox.item = newItem;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1067,22 +1067,15 @@ Zotero.Attachments = new function(){
|
|||
throw ("Attachment is already in library " + libraryID);
|
||||
}
|
||||
|
||||
var newAttachment = new Zotero.Item('attachment');
|
||||
newAttachment.libraryID = libraryID;
|
||||
// Link mode needs to be set when saving new attachment
|
||||
newAttachment.attachmentLinkMode = linkMode;
|
||||
var newAttachment = attachment.clone(libraryID);
|
||||
if (attachment.isImportedAttachment()) {
|
||||
// Attachment path isn't copied over by clone() if libraryID is different
|
||||
newAttachment.attachmentPath = attachment.attachmentPath;
|
||||
}
|
||||
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
|
||||
var id = newAttachment.save();
|
||||
newAttachment = Zotero.Items.get(id);
|
||||
attachment.clone(false, newAttachment);
|
||||
if (parentItemID) {
|
||||
newAttachment.setSource(parentItemID);
|
||||
newAttachment.parentID = parentItemID;
|
||||
}
|
||||
newAttachment.save();
|
||||
yield newAttachment.save();
|
||||
|
||||
// Copy over files if they exist
|
||||
if (newAttachment.isImportedAttachment() && attachment.getFile()) {
|
||||
|
@ -1091,7 +1084,7 @@ Zotero.Attachments = new function(){
|
|||
Zotero.File.copyDirectory(dir, newDir);
|
||||
}
|
||||
|
||||
newAttachment.addLinkedItem(attachment);
|
||||
yield newAttachment.addLinkedItem(attachment);
|
||||
return newAttachment.id;
|
||||
});
|
||||
|
||||
|
|
|
@ -1567,23 +1567,15 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
|
|||
return Zotero.Attachments.copyAttachmentToLibrary(item, targetLibraryID);
|
||||
}
|
||||
|
||||
// Create new unsaved clone item in target library
|
||||
var newItem = new Zotero.Item(item.itemTypeID);
|
||||
newItem.libraryID = targetLibraryID;
|
||||
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
|
||||
var id = yield newItem.save();
|
||||
newItem = yield Zotero.Items.getAsync(id);
|
||||
yield item.clone(false, newItem, false, !Zotero.Prefs.get('groups.copyTags'));
|
||||
yield newItem.save();
|
||||
//var id = newItem.save();
|
||||
//var newItem = Zotero.Items.get(id);
|
||||
// Create new clone item in target library
|
||||
var newItem = item.clone(targetLibraryID, false, !Zotero.Prefs.get('groups.copyTags'));
|
||||
var newItemID = yield newItem.save();
|
||||
|
||||
// Record link
|
||||
yield newItem.addLinkedItem(item);
|
||||
var newID = id;
|
||||
|
||||
if (item.isNote()) {
|
||||
return newID;
|
||||
return newItemID;
|
||||
}
|
||||
|
||||
// For regular items, add child items if prefs and permissions allow
|
||||
|
@ -1593,14 +1585,9 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
|
|||
var noteIDs = item.getNotes();
|
||||
var notes = yield Zotero.Items.getAsync(noteIDs);
|
||||
for each(var note in notes) {
|
||||
var newNote = new Zotero.Item('note');
|
||||
newNote.libraryID = targetLibraryID;
|
||||
// DEBUG: save here because clone() doesn't currently work on unsaved tagged items
|
||||
var id = yield newNote.save();
|
||||
newNote = yield Zotero.Items.getAsync(id);
|
||||
yield note.clone(false, newNote);
|
||||
newNote.parentID = newItem.id;
|
||||
yield newNote.save();
|
||||
let newNote = note.clone(targetLibraryID);
|
||||
newNote.parentID = newItemID;
|
||||
yield newNote.save()
|
||||
|
||||
yield newNote.addLinkedItem(note);
|
||||
}
|
||||
|
|
|
@ -421,73 +421,64 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
|
|||
|
||||
|
||||
/**
|
||||
* @param {Object} data1 Serialized copy of first object
|
||||
* @param {Object} data2 Serialized copy of second object
|
||||
* @param {Array} diff Empty array to put diff data in
|
||||
* @param {Boolean} [includeMatches=false] Include all fields, even those
|
||||
* that aren't different
|
||||
* @param {Object} data1 - JSON of first object
|
||||
* @param {Object} data2 - JSON of second object
|
||||
* @param {Array} diff - Empty array to put diff data in
|
||||
* @param {Boolean} [includeMatches=false] - Include all fields, even those
|
||||
* that aren't different
|
||||
*/
|
||||
this.diff = function (data1, data2, diff, includeMatches) {
|
||||
diff.push({}, {});
|
||||
var numDiffs = 0;
|
||||
|
||||
var subs = ['primary', 'fields'];
|
||||
var skipFields = ['collectionID', 'creatorID', 'itemID', 'searchID', 'tagID', 'libraryID', 'key'];
|
||||
var skipFields = ['collectionKey', 'itemKey', 'searchKey'];
|
||||
|
||||
for each(var sub in subs) {
|
||||
diff[0][sub] = {};
|
||||
diff[1][sub] = {};
|
||||
for (var field in data1[sub]) {
|
||||
if (skipFields.indexOf(field) != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!data1[sub][field] && !data2[sub][field]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var changed = !data1[sub][field] || !data2[sub][field] ||
|
||||
data1[sub][field] != data2[sub][field];
|
||||
|
||||
if (includeMatches || changed) {
|
||||
diff[0][sub][field] = data1[sub][field] ?
|
||||
data1[sub][field] : '';
|
||||
diff[1][sub][field] = data2[sub][field] ?
|
||||
data2[sub][field] : '';
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
numDiffs++;
|
||||
}
|
||||
for (var field in data1) {
|
||||
if (skipFields.indexOf(field) != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// DEBUG: some of this is probably redundant
|
||||
for (var field in data2[sub]) {
|
||||
if (skipFields.indexOf(field) != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (diff[0][sub][field] != undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!data1[sub][field] && !data2[sub][field]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var changed = !data1[sub][field] || !data2[sub][field] ||
|
||||
data1[sub][field] != data2[sub][field];
|
||||
|
||||
if (includeMatches || changed) {
|
||||
diff[0][sub][field] = data1[sub][field] ?
|
||||
data1[sub][field] : '';
|
||||
diff[1][sub][field] = data2[sub][field] ?
|
||||
data2[sub][field] : '';
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
numDiffs++;
|
||||
}
|
||||
if (data1[field] === false && (data2[field] === false || data2[field] === undefined)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var changed = data1[field] !== data2[field];
|
||||
|
||||
if (includeMatches || changed) {
|
||||
diff[0][field] = data1[field] !== false ? data1[field] : '';
|
||||
diff[1][field] = (data2[field] !== false && data2[field] !== undefined)
|
||||
? data2[field] : '';
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
numDiffs++;
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG: some of this is probably redundant
|
||||
for (var field in data2) {
|
||||
if (skipFields.indexOf(field) != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (diff[0][field] !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data2[field] === false && (data1[field] === false || data1[field] === undefined)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var changed = data1[field] !== data2[field];
|
||||
|
||||
if (includeMatches || changed) {
|
||||
diff[0][field] = (data1[field] !== false && data1[field] !== undefined)
|
||||
? data1[field] : '';
|
||||
diff[1][field] = data2[field] !== false ? data2[field] : '';
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
numDiffs++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -232,7 +232,7 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
|
|||
);
|
||||
}
|
||||
|
||||
var value = value ? value : '';
|
||||
value = value ? value : '';
|
||||
|
||||
if (!unformatted) {
|
||||
// Multipart date fields
|
||||
|
@ -251,18 +251,11 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
|
|||
* @return {Integer{}|String[]}
|
||||
*/
|
||||
Zotero.Item.prototype.getUsedFields = Zotero.Promise.coroutine(function* (asNames) {
|
||||
if (!this.id) {
|
||||
return [];
|
||||
}
|
||||
var sql = "SELECT fieldID FROM itemData WHERE itemID=?";
|
||||
if (asNames) {
|
||||
sql = "SELECT fieldName FROM fields WHERE fieldID IN (" + sql + ")";
|
||||
}
|
||||
var fields = yield Zotero.DB.columnQueryAsync(sql, this._id);
|
||||
if (!fields) {
|
||||
return [];
|
||||
}
|
||||
return fields;
|
||||
this._requireData('itemData');
|
||||
|
||||
return Object.keys(this._itemData)
|
||||
.filter(id => this._itemData[id] !== false)
|
||||
.map(id => asNames ? Zotero.ItemFields.getName(id) : parseInt(id));
|
||||
});
|
||||
|
||||
|
||||
|
@ -1581,7 +1574,8 @@ Zotero.Item.prototype.save = Zotero.Promise.coroutine(function* (options) {
|
|||
for (let i=0; i<toAdd.length; i++) {
|
||||
let tag = toAdd[i];
|
||||
let tagID = yield Zotero.Tags.getIDFromName(this.libraryID, tag.tag, true);
|
||||
let sql = "INSERT INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)";
|
||||
// "OR REPLACE" allows changing type
|
||||
let sql = "INSERT OR REPLACE INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)";
|
||||
yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
|
||||
Zotero.Notifier.trigger('add', 'item-tag', this.id + '-' + tag.tag);
|
||||
}
|
||||
|
@ -3248,31 +3242,35 @@ Zotero.Item.prototype.setTags = function (tags) {
|
|||
|
||||
|
||||
/**
|
||||
* Add a single manual tag to the item. If an automatic tag with the same name already exists,
|
||||
* replace it with a manual one.
|
||||
* Add a single tag to the item. If type is 1 and an automatic tag with the same name already
|
||||
* exists, replace it with a manual one.
|
||||
*
|
||||
* A separate save() is required to update the database.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Number} [type=0]
|
||||
*/
|
||||
Zotero.Item.prototype.addTag = function (name) {
|
||||
Zotero.Item.prototype.addTag = function (name, type) {
|
||||
type = type ? parseInt(type) : 0;
|
||||
|
||||
var changed = false;
|
||||
var tags = this.getTags();
|
||||
for (let i=0; i<tags.length; i++) {
|
||||
let tag = tags[i];
|
||||
if (tag.tag === name) {
|
||||
if (!tag.type) {
|
||||
if (tag.type == type) {
|
||||
Zotero.debug("Tag '" + name + "' already exists on item " + this.libraryKey);
|
||||
return false;
|
||||
}
|
||||
tag.type = 0;
|
||||
tag.type = type;
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!changed) {
|
||||
tags.push({
|
||||
tag: name
|
||||
tag: name,
|
||||
type: type
|
||||
});
|
||||
}
|
||||
this.setTags(tags);
|
||||
|
@ -3706,24 +3704,25 @@ Zotero.Item.prototype.diff = function (item, includeMatches, ignoreFields) {
|
|||
*
|
||||
* Currently compares only item data, not primary fields
|
||||
*/
|
||||
Zotero.Item.prototype.multiDiff = function (otherItems, ignoreFields) {
|
||||
var thisData = this.serialize();
|
||||
Zotero.Item.prototype.multiDiff = Zotero.Promise.coroutine(function* (otherItems, ignoreFields) {
|
||||
var thisData = yield this.toJSON();
|
||||
|
||||
var alternatives = {};
|
||||
var hasDiffs = false;
|
||||
|
||||
for each(var otherItem in otherItems) {
|
||||
var diff = [];
|
||||
var otherData = otherItem.serialize();
|
||||
var numDiffs = Zotero.Items.diff(thisData, otherData, diff);
|
||||
for (let i = 0; i < otherItems.length; i++) {
|
||||
let otherItem = otherItems[i];
|
||||
let diff = [];
|
||||
let otherData = yield otherItem.toJSON();
|
||||
let numDiffs = Zotero.Items.diff(thisData, otherData, diff);
|
||||
|
||||
if (numDiffs) {
|
||||
for (var field in diff[1].fields) {
|
||||
for (let field in diff[1]) {
|
||||
if (ignoreFields && ignoreFields.indexOf(field) != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = diff[1].fields[field];
|
||||
var value = diff[1][field];
|
||||
|
||||
if (!alternatives[field]) {
|
||||
hasDiffs = true;
|
||||
|
@ -3742,143 +3741,41 @@ Zotero.Item.prototype.multiDiff = function (otherItems, ignoreFields) {
|
|||
}
|
||||
|
||||
return alternatives;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Returns an unsaved copy of the item
|
||||
*
|
||||
* @param {Boolean} [includePrimary=false]
|
||||
* @param {Zotero.Item} [newItem=null] Target item for clone (used to pass a saved
|
||||
* item for duplicating items with tags)
|
||||
* @param {Boolean} [unsaved=false] Skip properties that require a saved object (e.g., tags)
|
||||
* @param {Boolean} [skipTags=false] Skip tags (implied by 'unsaved')
|
||||
* @param {Number} [libraryID] - libraryID of the new item, or the same as original if omitted
|
||||
* @param {Boolean} [skipTags=false] - Skip tags
|
||||
*/
|
||||
Zotero.Item.prototype.clone = function(includePrimary, newItem, unsaved, skipTags) {
|
||||
Zotero.Item.prototype.clone = function(libraryID, skipTags) {
|
||||
Zotero.debug('Cloning item ' + this.id);
|
||||
|
||||
if (includePrimary && newItem) {
|
||||
throw ("includePrimary and newItem parameters are mutually exclusive in Zotero.Item.clone()");
|
||||
if (libraryID !== undefined && libraryID !== null && typeof libraryID !== 'number') {
|
||||
throw new Error("libraryID must be null or an integer");
|
||||
}
|
||||
|
||||
if (unsaved) {
|
||||
skipTags = true;
|
||||
this._requireData('primaryData');
|
||||
|
||||
if (libraryID === undefined || libraryID === null) {
|
||||
libraryID = this.libraryID;
|
||||
}
|
||||
var sameLibrary = libraryID == this.libraryID;
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
var newItem = new Zotero.Item;
|
||||
newItem.setType(this.itemTypeID);
|
||||
|
||||
// TODO: get rid of serialize() call
|
||||
var obj = this.serialize();
|
||||
|
||||
var itemTypeID = this.itemTypeID;
|
||||
|
||||
if (newItem) {
|
||||
var sameLibrary = newItem.libraryID == this.libraryID;
|
||||
}
|
||||
else {
|
||||
var newItem = new Zotero.Item;
|
||||
var sameLibrary = true;
|
||||
|
||||
if (includePrimary) {
|
||||
newItem.id = this.id;
|
||||
newItem.libraryID = this.libraryID;
|
||||
newItem.key = this.key;
|
||||
newItem.setType(itemTypeID);
|
||||
for (var field in obj.primary) {
|
||||
switch (field) {
|
||||
case 'itemID':
|
||||
case 'itemType':
|
||||
case 'libraryID':
|
||||
case 'key':
|
||||
continue;
|
||||
}
|
||||
newItem.setField(field, obj.primary[field]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
newItem.setType(itemTypeID);
|
||||
}
|
||||
}
|
||||
|
||||
var changedFields = {};
|
||||
for (var field in obj.fields) {
|
||||
var fieldID = Zotero.ItemFields.getID(field);
|
||||
if (fieldID && Zotero.ItemFields.isValidForType(fieldID, itemTypeID)) {
|
||||
newItem.setField(field, obj.fields[field]);
|
||||
changedFields[field] = true;
|
||||
}
|
||||
}
|
||||
// If modifying an existing item, clear other fields not in the cloned item
|
||||
if (newItem) {
|
||||
var previousFields = this.getUsedFields(true);
|
||||
for each(var field in previousFields) {
|
||||
if (!changedFields[field] && Zotero.ItemFields.isValidForType(field, itemTypeID)) {
|
||||
newItem.setField(field, false);
|
||||
}
|
||||
}
|
||||
var fieldIDs = this.getUsedFields();
|
||||
for (let i = 0; i < fieldIDs.length; i++) {
|
||||
let fieldID = fieldIDs[i];
|
||||
newItem.setField(fieldID, this.getField(fieldID));
|
||||
}
|
||||
|
||||
// Regular item
|
||||
if (this.isRegularItem()) {
|
||||
if (includePrimary) {
|
||||
// newItem = loaded from db
|
||||
// obj = in-memory
|
||||
var max = Math.max(newItem.numCreators(), this.numCreators());
|
||||
var deleteOffset = 0;
|
||||
for (var i=0; i<max; i++) {
|
||||
var newIndex = i - deleteOffset;
|
||||
|
||||
// Remove existing creators (loaded because we set the itemID
|
||||
// above) not in the in-memory version
|
||||
if (!obj.creators[i]) {
|
||||
if (newItem.getCreator(newIndex)) {
|
||||
newItem.removeCreator(newIndex);
|
||||
deleteOffset++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Add in-memory creators
|
||||
newItem.setCreator(
|
||||
newIndex, this.getCreator(i).ref, obj.creators[i].creatorTypeID
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If overwriting an existing item, clear existing creators
|
||||
if (newItem) {
|
||||
for (var i=newItem.numCreators()-1; i>=0; i--) {
|
||||
if (newItem.getCreator(i)) {
|
||||
newItem.removeCreator(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
for (var c in obj.creators) {
|
||||
var creator = this.getCreator(c).ref;
|
||||
var creatorTypeID = this.getCreator(c).creatorTypeID;
|
||||
|
||||
if (!sameLibrary) {
|
||||
var creatorDataID = Zotero.Creators.getDataID(this.getCreator(c).ref);
|
||||
var creatorIDs = Zotero.Creators.getCreatorsWithData(creatorDataID, newItem.libraryID);
|
||||
if (creatorIDs) {
|
||||
// TODO: support multiple creators?
|
||||
var creator = Zotero.Creators.get(creatorIDs[0]);
|
||||
}
|
||||
else {
|
||||
var newCreator = new Zotero.Creator;
|
||||
newCreator.libraryID = newItem.libraryID;
|
||||
newCreator.setFields(creator);
|
||||
var creator = newCreator;
|
||||
}
|
||||
|
||||
var creatorTypeID = this.getCreator(c).creatorTypeID;
|
||||
}
|
||||
|
||||
newItem.setCreator(i, creator, creatorTypeID);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
newItem.setCreators(newItem.getCreators());
|
||||
}
|
||||
else {
|
||||
newItem.setNote(this.getNote());
|
||||
|
@ -3904,28 +3801,30 @@ Zotero.Item.prototype.clone = function(includePrimary, newItem, unsaved, skipTag
|
|||
}
|
||||
}
|
||||
|
||||
if (!skipTags && obj.tags) {
|
||||
for each(var tag in obj.tags) {
|
||||
if (sameLibrary) {
|
||||
newItem.addTagByID(tag.primary.tagID);
|
||||
}
|
||||
else {
|
||||
newItem.addTag(tag.fields.name, tag.fields.type);
|
||||
}
|
||||
}
|
||||
if (!skipTags) {
|
||||
newItem.setTags(this.getTags());
|
||||
}
|
||||
|
||||
if (obj.related && sameLibrary) {
|
||||
if (sameLibrary) {
|
||||
// DEBUG: this will add reverse-only relateds too
|
||||
newItem.relatedItems = obj.related;
|
||||
newItem.setRelations(this.getRelations());
|
||||
}
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
|
||||
return newItem;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {Promise<Zotero.Item>} - A copy of the item with primary data loaded
|
||||
*/
|
||||
Zotero.Item.prototype.copy = Zotero.Promise.coroutine(function* () {
|
||||
var newItem = new Zotero.Item;
|
||||
newItem.id = this.id;
|
||||
yield newItem.loadPrimaryData();
|
||||
return newItem;
|
||||
});;
|
||||
|
||||
|
||||
/**
|
||||
* Delete item from database and clear from Zotero.Items internal array
|
||||
*
|
||||
|
|
|
@ -481,65 +481,76 @@ Zotero.Items = new function() {
|
|||
|
||||
|
||||
this.merge = function (item, otherItems) {
|
||||
Zotero.DB.beginTransaction();
|
||||
|
||||
var otherItemIDs = [];
|
||||
var itemURI = Zotero.URI.getItemURI(item);
|
||||
|
||||
for each(var otherItem in otherItems) {
|
||||
// Move child items to master
|
||||
var ids = otherItem.getAttachments(true).concat(otherItem.getNotes(true));
|
||||
for each(var id in ids) {
|
||||
var attachment = Zotero.Items.get(id);
|
||||
return Zotero.DB.executeTransaction(function* () {
|
||||
var otherItemIDs = [];
|
||||
var itemURI = Zotero.URI.getItemURI(item);
|
||||
|
||||
yield item.loadTags();
|
||||
yield item.loadRelations();
|
||||
|
||||
for each(var otherItem in otherItems) {
|
||||
yield otherItem.loadChildItems();
|
||||
yield otherItem.loadCollections();
|
||||
yield otherItem.loadTags();
|
||||
yield otherItem.loadRelations();
|
||||
|
||||
// TODO: Skip identical children?
|
||||
// Move child items to master
|
||||
var ids = otherItem.getAttachments(true).concat(otherItem.getNotes(true));
|
||||
for each(var id in ids) {
|
||||
var attachment = yield Zotero.Items.getAsync(id);
|
||||
|
||||
// TODO: Skip identical children?
|
||||
|
||||
attachment.parentID = item.id;
|
||||
yield attachment.save();
|
||||
}
|
||||
|
||||
attachment.parentID = item.id;
|
||||
attachment.save();
|
||||
// All other operations are additive only and do not affect the,
|
||||
// old item, which will be put in the trash
|
||||
|
||||
// Add collections to master
|
||||
var collectionIDs = otherItem.getCollections();
|
||||
for each(var collectionID in collectionIDs) {
|
||||
let collection = yield Zotero.Collections.getAsync(collectionID);
|
||||
yield collection.addItem(item.id);
|
||||
}
|
||||
|
||||
// Add tags to master
|
||||
var tags = otherItem.getTags();
|
||||
for (let j = 0; j < tags.length; j++) {
|
||||
item.addTag(tags[j].tag);
|
||||
}
|
||||
|
||||
// Related items
|
||||
var relatedItems = otherItem.relatedItems;
|
||||
for each(var relatedItemID in relatedItems) {
|
||||
yield item.addRelatedItem(relatedItemID);
|
||||
}
|
||||
|
||||
// Relations
|
||||
yield Zotero.Relations.copyURIs(
|
||||
item.libraryID,
|
||||
Zotero.URI.getItemURI(otherItem),
|
||||
Zotero.URI.getItemURI(item)
|
||||
);
|
||||
|
||||
// Add relation to track merge
|
||||
var otherItemURI = Zotero.URI.getItemURI(otherItem);
|
||||
yield Zotero.Relations.add(
|
||||
item.libraryID,
|
||||
otherItemURI,
|
||||
Zotero.Relations.deletedItemPredicate,
|
||||
itemURI
|
||||
);
|
||||
|
||||
// Trash other item
|
||||
otherItem.deleted = true;
|
||||
yield otherItem.save();
|
||||
}
|
||||
|
||||
// All other operations are additive only and do not affect the,
|
||||
// old item, which will be put in the trash
|
||||
|
||||
// Add collections to master
|
||||
var collectionIDs = otherItem.getCollections();
|
||||
for each(var collectionID in collectionIDs) {
|
||||
var collection = Zotero.Collections.get(collectionID);
|
||||
collection.addItem(item.id);
|
||||
}
|
||||
|
||||
// Add tags to master
|
||||
var tags = otherItem.getTags();
|
||||
for each(var tag in tags) {
|
||||
item.addTagByID(tag.id);
|
||||
}
|
||||
|
||||
// Related items
|
||||
var relatedItems = otherItem.relatedItems;
|
||||
for each(var relatedItemID in relatedItems) {
|
||||
item.addRelatedItem(relatedItemID);
|
||||
}
|
||||
|
||||
// Relations
|
||||
Zotero.Relations.copyURIs(
|
||||
item.libraryID,
|
||||
Zotero.URI.getItemURI(otherItem),
|
||||
Zotero.URI.getItemURI(item)
|
||||
);
|
||||
|
||||
// Add relation to track merge
|
||||
var otherItemURI = Zotero.URI.getItemURI(otherItem);
|
||||
Zotero.Relations.add(item.libraryID, otherItemURI, Zotero.Relations.deletedItemPredicate, itemURI);
|
||||
|
||||
// Trash other item
|
||||
otherItem.deleted = true;
|
||||
otherItem.save();
|
||||
}
|
||||
|
||||
item.save();
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
yield item.save();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
this.trash = function (ids) {
|
||||
|
|
|
@ -79,11 +79,14 @@ Zotero.Relations = new function () {
|
|||
}
|
||||
|
||||
var toReturn = [];
|
||||
var loads = [];
|
||||
for (let i=0; i<rows.length; i++) {
|
||||
var relation = new Zotero.Relation;
|
||||
relation.id = rows[i];
|
||||
relation.id = rows[i];
|
||||
loads.push(relation.load());
|
||||
toReturn.push(relation);
|
||||
}
|
||||
yield Zotero.Promise.all(loads);
|
||||
return toReturn;
|
||||
});
|
||||
|
||||
|
|
|
@ -54,13 +54,15 @@ Zotero.Duplicates.prototype.getSearchObject = Zotero.Promise.coroutine(function*
|
|||
+ "(id INTEGER PRIMARY KEY)";
|
||||
yield Zotero.DB.queryAsync(sql);
|
||||
|
||||
this._findDuplicates();
|
||||
yield this._findDuplicates();
|
||||
var ids = this._sets.findAll(true);
|
||||
|
||||
Zotero.debug("Inserting rows into temp table");
|
||||
sql = "INSERT INTO tmpDuplicates VALUES (?)";
|
||||
for (let i=0; i<ids.length; i++) {
|
||||
yield Zotero.DB.queryAsync(sql, [ids[i]], { debug: false })
|
||||
}
|
||||
Zotero.debug("Done");
|
||||
}.bind(this));
|
||||
|
||||
var s = new Zotero.Search;
|
||||
|
@ -88,7 +90,7 @@ Zotero.Duplicates.prototype._getObjectFromID = function (id) {
|
|||
}
|
||||
|
||||
|
||||
Zotero.Duplicates.prototype._findDuplicates = function () {
|
||||
Zotero.Duplicates.prototype._findDuplicates = Zotero.Promise.coroutine(function* () {
|
||||
var start = Date.now();
|
||||
|
||||
var self = this;
|
||||
|
@ -136,8 +138,8 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
* set and the next start row would be a
|
||||
* different title.
|
||||
*/
|
||||
function processRows(compareRows, reprocessMatches) {
|
||||
if (!rows) {
|
||||
function processRows(rows, compareRows, reprocessMatches) {
|
||||
if (!rows.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -182,7 +184,7 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
+ "JOIN itemDataValues USING (valueID) "
|
||||
+ "WHERE libraryID=? AND itemTypeID=? AND fieldID=? "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems)";
|
||||
var rows = Zotero.DB.query(
|
||||
var rows = yield Zotero.DB.queryAsync(
|
||||
sql,
|
||||
[
|
||||
this._libraryID,
|
||||
|
@ -191,29 +193,43 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
]
|
||||
);
|
||||
var isbnCache = {};
|
||||
if (rows) {
|
||||
for each(var row in rows) {
|
||||
row.value = (row.value+'').replace(/[^\dX]+/ig, '').toUpperCase(); //ignore formatting
|
||||
isbnCache[row.itemID] = row.value;
|
||||
if (rows.length) {
|
||||
let newRows = [];
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
// Ignore formatting
|
||||
let newVal = (row.value + '').replace(/[^\dX]+/ig, '').toUpperCase();
|
||||
isbnCache[row.itemID] = newVal;
|
||||
newRows.push({
|
||||
itemID: row.itemID,
|
||||
value: newVal
|
||||
});
|
||||
}
|
||||
rows.sort(sortByValue);
|
||||
processRows();
|
||||
newRows.sort(sortByValue);
|
||||
processRows(newRows);
|
||||
}
|
||||
|
||||
// DOI
|
||||
var sql = "SELECT itemID, value FROM items JOIN itemData USING (itemID) "
|
||||
+ "JOIN itemDataValues USING (valueID) "
|
||||
+ "WHERE libraryID=? AND fieldID=? AND REGEXP('^10\\.', value) "
|
||||
+ "WHERE libraryID=? AND fieldID=? AND value LIKE '10\\.%' "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems)";
|
||||
var rows = Zotero.DB.query(sql, [this._libraryID, Zotero.ItemFields.getID('DOI')]);
|
||||
var rows = yield Zotero.DB.queryAsync(sql, [this._libraryID, Zotero.ItemFields.getID('DOI')]);
|
||||
var doiCache = {};
|
||||
if (rows) {
|
||||
for each(var row in rows) {
|
||||
row.value = (row.value+'').trim().toUpperCase(); //DOIs are case insensitive
|
||||
doiCache[row.itemID] = row.value;
|
||||
if (rows.length) {
|
||||
let newRows = [];
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
// DOIs are case insensitive
|
||||
let newVal = (row.value + '').trim().toUpperCase();
|
||||
doiCache[row.itemID] = newVal;
|
||||
newRows.push({
|
||||
itemID: row.itemID,
|
||||
value: newVal
|
||||
});
|
||||
}
|
||||
rows.sort(sortByValue);
|
||||
processRows();
|
||||
newRows.sort(sortByValue);
|
||||
processRows(newRows);
|
||||
}
|
||||
|
||||
// Get years
|
||||
|
@ -228,33 +244,70 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
+ "AND SUBSTR(value, 1, 4) != '0000' "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
|
||||
+ "ORDER BY value";
|
||||
var rows = Zotero.DB.query(sql, [this._libraryID].concat(dateFields));
|
||||
var rows = yield Zotero.DB.queryAsync(sql, [this._libraryID].concat(dateFields));
|
||||
var yearCache = {};
|
||||
if (rows) {
|
||||
for each(var row in rows) {
|
||||
yearCache[row.itemID] = row.year;
|
||||
}
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
yearCache[row.itemID] = row.year;
|
||||
}
|
||||
|
||||
var creatorRowsCache = {};
|
||||
|
||||
// Match on normalized title
|
||||
var titleIDs = Zotero.ItemFields.getTypeFieldsFromBase('title');
|
||||
titleIDs.push(Zotero.ItemFields.getID('title'));
|
||||
var sql = "SELECT itemID, value FROM items JOIN itemData USING (itemID) "
|
||||
+ "JOIN itemDataValues USING (valueID) "
|
||||
+ "WHERE libraryID=? AND fieldID BETWEEN 110 AND 113 "
|
||||
+ "WHERE libraryID=? AND fieldID IN "
|
||||
+ "(" + titleIDs.join(', ') + ") "
|
||||
+ "AND itemTypeID NOT IN (1, 14) "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems)";
|
||||
var rows = Zotero.DB.query(sql, [this._libraryID]);
|
||||
if(rows) {
|
||||
var rows = yield Zotero.DB.queryAsync(sql, [this._libraryID]);
|
||||
if (rows.length) {
|
||||
//normalize all values ahead of time
|
||||
rows = rows.map(function(row) {
|
||||
row.value = normalizeString(row.value);
|
||||
return row;
|
||||
return {
|
||||
itemID: row.itemID,
|
||||
value: normalizeString(row.value)
|
||||
};
|
||||
});
|
||||
//sort rows by normalized values
|
||||
rows.sort(sortByValue);
|
||||
|
||||
processRows(function (a, b) {
|
||||
// Get all creators and separate by itemID
|
||||
//
|
||||
// We won't need all of these, but otherwise we would have to make processRows()
|
||||
// asynchronous, which would be too slow
|
||||
let creatorRowsCache = {};
|
||||
let sql = "SELECT itemID, lastName, firstName, fieldMode FROM items "
|
||||
+ "JOIN itemCreators USING (itemID) "
|
||||
+ "JOIN creators USING (creatorID) "
|
||||
+ "WHERE libraryID=? AND itemTypeID NOT IN (1, 14) AND "
|
||||
+ "itemID NOT IN (SELECT itemID FROM deletedItems)"
|
||||
+ "ORDER BY itemID, orderIndex";
|
||||
let creatorRows = yield Zotero.DB.queryAsync(sql, this._libraryID);
|
||||
let lastItemID;
|
||||
let itemCreators = [];
|
||||
for (let i = 0; i < creatorRows.length; i++) {
|
||||
let row = creatorRows[i];
|
||||
if (lastItemID && row.itemID != lastItemID) {
|
||||
if (itemCreators.length) {
|
||||
creatorRowsCache[lastItemID] = itemCreators;
|
||||
itemCreators = [];
|
||||
}
|
||||
}
|
||||
else {
|
||||
itemCreators.push({
|
||||
lastName: normalizeString(row.lastName),
|
||||
firstInitial: row.fieldMode == 0 ? normalizeString(row.firstName).charAt(0) : false
|
||||
});
|
||||
}
|
||||
lastItemID = row.itemID;
|
||||
}
|
||||
// Add final item creators
|
||||
if (itemCreators.length) {
|
||||
creatorRowsCache[lastItemID] = itemCreators;
|
||||
}
|
||||
|
||||
processRows(rows, function (a, b) {
|
||||
var aTitle = a.value;
|
||||
var bTitle = b.value;
|
||||
|
||||
|
@ -293,25 +346,9 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
if (typeof creatorRowsCache[a.itemID] != 'undefined') {
|
||||
aCreatorRows = creatorRowsCache[a.itemID];
|
||||
}
|
||||
else {
|
||||
var sql = "SELECT lastName, firstName, fieldMode FROM itemCreators "
|
||||
+ "JOIN creators USING (creatorID) "
|
||||
+ "WHERE itemID=? ORDER BY orderIndex LIMIT 10";
|
||||
aCreatorRows = Zotero.DB.query(sql, a.itemID);
|
||||
creatorRowsCache[a.itemID] = aCreatorRows;
|
||||
}
|
||||
|
||||
// Check for at least one match on last name + first initial of first name
|
||||
if (typeof creatorRowsCache[b.itemID] != 'undefined') {
|
||||
bCreatorRows = creatorRowsCache[b.itemID];
|
||||
}
|
||||
else {
|
||||
var sql = "SELECT lastName, firstName, fieldMode FROM itemCreators "
|
||||
+ "JOIN creators USING (creatorID) "
|
||||
+ "WHERE itemID=? ORDER BY orderIndex LIMIT 10";
|
||||
bCreatorRows = Zotero.DB.query(sql, b.itemID);
|
||||
creatorRowsCache[b.itemID] = bCreatorRows;
|
||||
}
|
||||
|
||||
// Match if no creators
|
||||
if (!aCreatorRows && !bCreatorRows) {
|
||||
|
@ -322,13 +359,15 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
return 0;
|
||||
}
|
||||
|
||||
for each(var aCreatorRow in aCreatorRows) {
|
||||
var aLastName = normalizeString(aCreatorRow.lastName);
|
||||
var aFirstInitial = aCreatorRow.fieldMode == 0 ? normalizeString(aCreatorRow.firstName).charAt(0) : false;
|
||||
for (let i = 0; i < aCreatorRows.length; i++) {
|
||||
let aCreatorRow = aCreatorRows[i];
|
||||
let aLastName = aCreatorRow.lastName;
|
||||
let aFirstInitial = aCreatorRow.firstInitial;
|
||||
|
||||
for each(var bCreatorRow in bCreatorRows) {
|
||||
var bLastName = normalizeString(bCreatorRow.lastName);
|
||||
var bFirstInitial = bCreatorRow.fieldMode == 0 ? normalizeString(bCreatorRow.firstName).charAt(0) : false;
|
||||
for (let j = 0; j < bCreatorRows.length; j++) {
|
||||
let bCreatorRow = bCreatorRows[j];
|
||||
let bLastName = bCreatorRow.lastName;
|
||||
let bFirstInitial = bCreatorRow.firstInitial;
|
||||
|
||||
if (aLastName === bLastName && aFirstInitial === bFirstInitial) {
|
||||
return 1;
|
||||
|
@ -348,12 +387,12 @@ Zotero.Duplicates.prototype._findDuplicates = function () {
|
|||
+ "WHERE libraryID=? AND fieldID=? "
|
||||
+ "AND itemID NOT IN (SELECT itemID FROM deletedItems) "
|
||||
+ "ORDER BY value";
|
||||
var rows = Zotero.DB.query(sql, [this._libraryID, Zotero.ItemFields.getID(field)]);
|
||||
processRows();
|
||||
var rows = yield Zotero.DB.queryAsync(sql, [this._libraryID, Zotero.ItemFields.getID(field)]);
|
||||
processRows(rows);
|
||||
}*/
|
||||
|
||||
Zotero.debug("Found duplicates in " + (Date.now() - start) + " ms");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -869,7 +869,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
|
|||
// Mirror ZoteroPane.onTreeMouseDown behavior
|
||||
var itemID = this._rows[previousRow].ref.id;
|
||||
var setItemIDs = collectionTreeRow.ref.getSetItemsByItemID(itemID);
|
||||
yield this.selectItems(setItemIDs);
|
||||
this.selectItems(setItemIDs);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -1387,7 +1387,7 @@ var ZoteroPane = new function()
|
|||
this.itemsView._itemSelectedPromiseResolver.reject(e);
|
||||
}
|
||||
throw e;
|
||||
}.bind(this));;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user