Merge pull request #576 from aurimasv/async_db-av2
[Async DB] Modularize Zotero.DataObject.save()
This commit is contained in:
commit
66a04a39db
|
@ -232,7 +232,7 @@
|
|||
<body>
|
||||
<![CDATA[
|
||||
this.updateSearch();
|
||||
return this.search.save(true);
|
||||
return this.search.save({fixGaps: true});
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
|
|
@ -201,7 +201,7 @@ Zotero.API.Data = {
|
|||
var params = this.parsePath(path);
|
||||
//Zotero.debug(params);
|
||||
|
||||
return Zotero.DataObjectUtilities.getClassForObjectType(params.objectType)
|
||||
return Zotero.DataObjectUtilities.getObjectsClassForObjectType(params.objectType)
|
||||
.apiDataGenerator(params);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -37,38 +37,51 @@ Zotero.Collection = function() {
|
|||
this._childItems = [];
|
||||
}
|
||||
|
||||
Zotero.Collection._super = Zotero.DataObject;
|
||||
Zotero.Collection.prototype = Object.create(Zotero.Collection._super.prototype);
|
||||
Zotero.Collection.constructor = Zotero.Collection;
|
||||
Zotero.extendClass(Zotero.DataObject, Zotero.Collection);
|
||||
|
||||
Zotero.Collection.prototype._objectType = 'collection';
|
||||
Zotero.Collection.prototype._dataTypes = Zotero.Collection._super.prototype._dataTypes.concat([
|
||||
'primaryData',
|
||||
'childCollections',
|
||||
'childItems'
|
||||
]);
|
||||
|
||||
Zotero.Collection.prototype.__defineGetter__('id', function () { return this._get('id'); });
|
||||
Zotero.Collection.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
|
||||
Zotero.Collection.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
|
||||
Zotero.Collection.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
|
||||
Zotero.Collection.prototype.__defineGetter__('key', function () { return this._get('key'); });
|
||||
Zotero.Collection.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
|
||||
Zotero.Collection.prototype.__defineGetter__('name', function () { return this._get('name'); });
|
||||
Zotero.Collection.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
|
||||
// .parentKey and .parentID defined in dataObject.js
|
||||
Zotero.Collection.prototype.__defineGetter__('version', function () { return this._get('version'); });
|
||||
Zotero.Collection.prototype.__defineSetter__('version', function (val) { this._set('version', val); });
|
||||
Zotero.Collection.prototype.__defineGetter__('synced', function () { return this._get('synced'); });
|
||||
Zotero.Collection.prototype.__defineSetter__('synced', function (val) { this._set('synced', val); });
|
||||
|
||||
Zotero.Collection.prototype.__defineGetter__('parent', function (val) {
|
||||
Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
|
||||
return this.parentID;
|
||||
Zotero.defineProperty(Zotero.Collection.prototype, 'ChildObjects', {
|
||||
get: function() Zotero.Items
|
||||
});
|
||||
Zotero.Collection.prototype.__defineSetter__('parent', function (val) {
|
||||
Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
|
||||
this.parentID = val;
|
||||
|
||||
Zotero.defineProperty(Zotero.Collection.prototype, 'id', {
|
||||
get: function() this._get('id'),
|
||||
set: function(val) this._set('id', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Collection.prototype, 'libraryID', {
|
||||
get: function() this._get('libraryID'),
|
||||
set: function(val) this._set('libraryID', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Collection.prototype, 'key', {
|
||||
get: function() this._get('key'),
|
||||
set: function(val) this._set('key', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Collection.prototype, 'name', {
|
||||
get: function() this._get('name'),
|
||||
set: function(val) this._set('name', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Collection.prototype, 'version', {
|
||||
get: function() this._get('version'),
|
||||
set: function(val) this._set('version', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Collection.prototype, 'synced', {
|
||||
get: function() this._get('synced'),
|
||||
set: function(val) this._set('synced', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Collection.prototype, 'parent', {
|
||||
get: function() {
|
||||
Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
|
||||
return this.parentID;
|
||||
},
|
||||
set: function(val) {
|
||||
Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
|
||||
this.parentID = val;
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Collection.prototype._set = function (field, value) {
|
||||
|
@ -114,45 +127,13 @@ Zotero.Collection.prototype.getName = function() {
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* Build collection from database
|
||||
*/
|
||||
Zotero.Collection.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload) {
|
||||
if (this._loaded.primaryData && !reload) return;
|
||||
|
||||
var id = this._id;
|
||||
var key = this._key;
|
||||
var libraryID = this._libraryID;
|
||||
|
||||
var sql = Zotero.Collections.getPrimaryDataSQL();
|
||||
if (id) {
|
||||
sql += " AND O.collectionID=?";
|
||||
var params = id;
|
||||
}
|
||||
else {
|
||||
sql += " AND O.libraryID=? AND O.key=?";
|
||||
var params = [libraryID, key];
|
||||
}
|
||||
var data = yield Zotero.DB.rowQueryAsync(sql, params);
|
||||
|
||||
this._loaded.primaryData = true;
|
||||
this._clearChanged('primaryData');
|
||||
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadFromRow(data);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Populate collection data from a database row
|
||||
*/
|
||||
Zotero.Collection.prototype.loadFromRow = function(row) {
|
||||
Zotero.debug("Loading collection from row");
|
||||
|
||||
for each(let col in Zotero.Collections.primaryFields) {
|
||||
for each(let col in this.ObjectsClass.primaryFields) {
|
||||
if (row[col] === undefined) {
|
||||
Zotero.debug('Skipping missing collection field ' + col);
|
||||
}
|
||||
|
@ -267,168 +248,139 @@ Zotero.Collection.prototype.getChildItems = function (asIDs, includeDeleted) {
|
|||
return objs;
|
||||
}
|
||||
|
||||
Zotero.Collection.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
|
||||
if (!this.name) {
|
||||
throw new Error('Collection name is empty');
|
||||
}
|
||||
|
||||
var proceed = yield Zotero.Collection._super.prototype._initSave.apply(this, arguments);
|
||||
if (!proceed) return false;
|
||||
|
||||
// Verify parent
|
||||
if (this._parentKey) {
|
||||
let newParent = this.ObjectsClass.getByLibraryAndKey(
|
||||
this.libraryID, this._parentKey
|
||||
);
|
||||
|
||||
if (!newParent) {
|
||||
throw new Error("Cannot set parent to invalid collection " + this._parentKey);
|
||||
}
|
||||
|
||||
if (newParent.id == this.id) {
|
||||
throw new Error('Cannot move collection into itself!');
|
||||
}
|
||||
|
||||
if (this.id && (yield this.hasDescendent('collection', newParent.id))) {
|
||||
throw ('Cannot move collection "' + this.name + '" into one of its own descendents');
|
||||
}
|
||||
|
||||
env.parent = newParent.id;
|
||||
}
|
||||
else {
|
||||
env.parent = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
|
||||
try {
|
||||
Zotero.Collections.editCheck(this);
|
||||
Zotero.Collection.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
||||
var isNew = env.isNew;
|
||||
|
||||
var collectionID = env.id = this._id = this.id ? this.id : yield Zotero.ID.get('collections');
|
||||
var libraryID = env.libraryID = this.libraryID;
|
||||
var key = env.key = this._key = this.key ? this.key : this._generateKey();
|
||||
|
||||
Zotero.debug("Saving collection " + this.id);
|
||||
|
||||
var columns = [
|
||||
'collectionID',
|
||||
'collectionName',
|
||||
'parentCollectionID',
|
||||
'clientDateModified',
|
||||
'libraryID',
|
||||
'key',
|
||||
'version',
|
||||
'synced'
|
||||
];
|
||||
var sqlValues = [
|
||||
collectionID ? { int: collectionID } : null,
|
||||
{ string: this.name },
|
||||
env.parent ? env.parent : null,
|
||||
Zotero.DB.transactionDateTime,
|
||||
this.libraryID ? this.libraryID : 0,
|
||||
key,
|
||||
this.version ? this.version : 0,
|
||||
this.synced ? 1 : 0
|
||||
];
|
||||
if (isNew) {
|
||||
var placeholders = columns.map(function () '?').join();
|
||||
|
||||
if (!this.name) {
|
||||
throw new Error('Collection name is empty');
|
||||
var sql = "REPLACE INTO collections (" + columns.join(', ') + ") "
|
||||
+ "VALUES (" + placeholders + ")";
|
||||
var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
|
||||
if (!collectionID) {
|
||||
collectionID = env.id = insertID;
|
||||
}
|
||||
|
||||
if (Zotero.Utilities.isEmpty(this._changed)) {
|
||||
Zotero.debug("Collection " + this.id + " has not changed");
|
||||
return false;
|
||||
}
|
||||
|
||||
var isNew = !this.id;
|
||||
|
||||
// Register this item's identifiers in Zotero.DataObjects on transaction commit,
|
||||
// before other callbacks run
|
||||
var collectionID, libraryID, key;
|
||||
if (isNew) {
|
||||
var transactionOptions = {
|
||||
onCommit: function () {
|
||||
Zotero.Collections.registerIdentifiers(collectionID, libraryID, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
var transactionOptions = null;
|
||||
}
|
||||
|
||||
return Zotero.DB.executeTransaction(function* () {
|
||||
// how to know if date modified changed (in server code too?)
|
||||
|
||||
collectionID = this._id = this.id ? this.id : yield Zotero.ID.get('collections');
|
||||
libraryID = this.libraryID;
|
||||
key = this._key = this.key ? this.key : this._generateKey();
|
||||
|
||||
Zotero.debug("Saving collection " + this.id);
|
||||
|
||||
// Verify parent
|
||||
if (this._parentKey) {
|
||||
let newParent = Zotero.Collections.getByLibraryAndKey(
|
||||
this.libraryID, this._parentKey
|
||||
);
|
||||
|
||||
if (!newParent) {
|
||||
throw new Error("Cannot set parent to invalid collection " + this._parentKey);
|
||||
}
|
||||
|
||||
if (newParent.id == this.id) {
|
||||
throw new Error('Cannot move collection into itself!');
|
||||
}
|
||||
|
||||
if (this.id && (yield this.hasDescendent('collection', newParent.id))) {
|
||||
throw ('Cannot move collection "' + this.name + '" into one of its own descendents');
|
||||
}
|
||||
|
||||
var parent = newParent.id;
|
||||
}
|
||||
else {
|
||||
var parent = null;
|
||||
}
|
||||
|
||||
var columns = [
|
||||
'collectionID',
|
||||
'collectionName',
|
||||
'parentCollectionID',
|
||||
'clientDateModified',
|
||||
'libraryID',
|
||||
'key',
|
||||
'version',
|
||||
'synced'
|
||||
];
|
||||
var sqlValues = [
|
||||
collectionID ? { int: collectionID } : null,
|
||||
{ string: this.name },
|
||||
parent ? parent : null,
|
||||
Zotero.DB.transactionDateTime,
|
||||
this.libraryID ? this.libraryID : 0,
|
||||
key,
|
||||
this.version ? this.version : 0,
|
||||
this.synced ? 1 : 0
|
||||
];
|
||||
if (isNew) {
|
||||
var placeholders = columns.map(function () '?').join();
|
||||
|
||||
var sql = "REPLACE INTO collections (" + columns.join(', ') + ") "
|
||||
+ "VALUES (" + placeholders + ")";
|
||||
var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
|
||||
if (!collectionID) {
|
||||
collectionID = insertID;
|
||||
}
|
||||
}
|
||||
else {
|
||||
columns.shift();
|
||||
sqlValues.push(sqlValues.shift());
|
||||
let sql = 'UPDATE collections SET '
|
||||
+ columns.map(function (x) x + '=?').join(', ')
|
||||
+ ' WHERE collectionID=?';
|
||||
yield Zotero.DB.queryAsync(sql, sqlValues);
|
||||
}
|
||||
|
||||
if (this._changed.parentKey) {
|
||||
var parentIDs = [];
|
||||
if (this.id && this._previousData.parentKey) {
|
||||
parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey(
|
||||
this.libraryID, this._previousData.parentKey
|
||||
));
|
||||
}
|
||||
if (this.parentKey) {
|
||||
parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey(
|
||||
this.libraryID, this.parentKey
|
||||
));
|
||||
}
|
||||
if (this.id) {
|
||||
Zotero.Notifier.trigger('move', 'collection', this.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (isNew && this.libraryID) {
|
||||
var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
|
||||
var group = Zotero.Groups.get(groupID);
|
||||
group.clearCollectionCache();
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
Zotero.Notifier.trigger('add', 'collection', this.id);
|
||||
}
|
||||
else {
|
||||
Zotero.Notifier.trigger('modify', 'collection', this.id, this._previousData);
|
||||
}
|
||||
|
||||
// Invalidate cached child collections
|
||||
if (parentIDs) {
|
||||
Zotero.Collections.refreshChildCollections(parentIDs);
|
||||
}
|
||||
|
||||
// New collections have to be reloaded via Zotero.Collections.get(), so mark them as disabled
|
||||
if (isNew) {
|
||||
var id = this.id;
|
||||
this._disabled = true;
|
||||
return id;
|
||||
}
|
||||
|
||||
yield this.reload();
|
||||
this._clearChanged();
|
||||
|
||||
return true;
|
||||
}.bind(this), transactionOptions);
|
||||
}
|
||||
catch (e) {
|
||||
try {
|
||||
yield this.reload();
|
||||
this._clearChanged();
|
||||
}
|
||||
catch (e2) {
|
||||
Zotero.debug(e2, 1);
|
||||
}
|
||||
|
||||
Zotero.debug(e, 1);
|
||||
throw e;
|
||||
else {
|
||||
columns.shift();
|
||||
sqlValues.push(sqlValues.shift());
|
||||
let sql = 'UPDATE collections SET '
|
||||
+ columns.map(function (x) x + '=?').join(', ')
|
||||
+ ' WHERE collectionID=?';
|
||||
yield Zotero.DB.queryAsync(sql, sqlValues);
|
||||
}
|
||||
|
||||
if (this._changed.parentKey) {
|
||||
var parentIDs = [];
|
||||
if (this.id && this._previousData.parentKey) {
|
||||
parentIDs.push(this.ObjectsClass.getIDFromLibraryAndKey(
|
||||
this.libraryID, this._previousData.parentKey
|
||||
));
|
||||
}
|
||||
if (this.parentKey) {
|
||||
parentIDs.push(this.ObjectsClass.getIDFromLibraryAndKey(
|
||||
this.libraryID, this.parentKey
|
||||
));
|
||||
}
|
||||
if (this.id) {
|
||||
Zotero.Notifier.trigger('move', 'collection', this.id);
|
||||
}
|
||||
env.parentIDs = parentIDs;
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Collection.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
|
||||
var isNew = env.isNew;
|
||||
if (isNew && Zotero.Libraries.isGroupLibrary(this.libraryID)) {
|
||||
var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
|
||||
var group = Zotero.Groups.get(groupID);
|
||||
group.clearCollectionCache();
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
Zotero.Notifier.trigger('add', 'collection', this.id);
|
||||
}
|
||||
else {
|
||||
Zotero.Notifier.trigger('modify', 'collection', this.id, this._previousData);
|
||||
}
|
||||
|
||||
// Invalidate cached child collections
|
||||
if (env.parentIDs) {
|
||||
this.ObjectsClass.refreshChildCollections(env.parentIDs);
|
||||
}
|
||||
|
||||
// New collections have to be reloaded via Zotero.Collections.get(), so mark them as disabled
|
||||
if (isNew) {
|
||||
var id = this.id;
|
||||
this._disabled = true;
|
||||
return id;
|
||||
}
|
||||
|
||||
yield this.reload();
|
||||
this._clearChanged();
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
|
@ -466,7 +418,7 @@ Zotero.Collection.prototype.addItems = Zotero.Promise.coroutine(function* (itemI
|
|||
continue;
|
||||
}
|
||||
|
||||
let item = yield Zotero.Items.getAsync(itemID);
|
||||
let item = yield this.ChildObjects.getAsync(itemID);
|
||||
yield item.loadCollections();
|
||||
item.addToCollection(this.id);
|
||||
yield item.save({
|
||||
|
@ -513,7 +465,7 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it
|
|||
continue;
|
||||
}
|
||||
|
||||
let item = yield Zotero.Items.getAsync(itemID);
|
||||
let item = yield this.ChildObjects.getAsync(itemID);
|
||||
yield item.loadCollections();
|
||||
item.removeFromCollection(this.id);
|
||||
yield item.save({
|
||||
|
@ -565,7 +517,7 @@ Zotero.Collection.prototype.diff = function (collection, includeMatches) {
|
|||
var diff = [];
|
||||
var thisData = this.serialize();
|
||||
var otherData = collection.serialize();
|
||||
var numDiffs = Zotero.Collections.diff(thisData, otherData, diff, includeMatches);
|
||||
var numDiffs = this.ObjectsClass.diff(thisData, otherData, diff, includeMatches);
|
||||
|
||||
// For the moment, just compare children and increase numDiffs if any differences
|
||||
var d1 = Zotero.Utilities.arrayDiff(
|
||||
|
@ -625,7 +577,7 @@ Zotero.Collection.prototype.clone = function (includePrimary, newCollection) {
|
|||
var sameLibrary = newCollection.libraryID == this.libraryID;
|
||||
}
|
||||
else {
|
||||
var newCollection = new Zotero.Collection;
|
||||
var newCollection = new this.constructor;
|
||||
var sameLibrary = true;
|
||||
|
||||
if (includePrimary) {
|
||||
|
@ -661,7 +613,7 @@ Zotero.Collection.prototype.erase = function(deleteItems) {
|
|||
// Descendent collections
|
||||
if (descendents[i].type == 'collection') {
|
||||
collections.push(descendents[i].id);
|
||||
var c = yield Zotero.Collections.getAsync(descendents[i].id);
|
||||
var c = yield this.ObjectsClass.getAsync(descendents[i].id);
|
||||
if (c) {
|
||||
notifierData[c.id] = { old: c.toJSON() };
|
||||
}
|
||||
|
@ -675,7 +627,7 @@ Zotero.Collection.prototype.erase = function(deleteItems) {
|
|||
}
|
||||
}
|
||||
if (del.length) {
|
||||
yield Zotero.Items.trash(del);
|
||||
yield this.ChildObjects.trash(del);
|
||||
}
|
||||
|
||||
// Remove relations
|
||||
|
@ -698,9 +650,9 @@ Zotero.Collection.prototype.erase = function(deleteItems) {
|
|||
|
||||
// TODO: Update member items
|
||||
}.bind(this))
|
||||
.then(function () {
|
||||
.then(() => {
|
||||
// Clear deleted collection from internal memory
|
||||
Zotero.Collections.unload(collections);
|
||||
this.ObjectsClass.unload(collections);
|
||||
//return Zotero.Collections.reloadAll();
|
||||
})
|
||||
.then(function () {
|
||||
|
@ -815,7 +767,7 @@ Zotero.Collection.prototype.getChildren = Zotero.Promise.coroutine(function* (re
|
|||
}
|
||||
|
||||
if (recursive) {
|
||||
let child = yield Zotero.Collections.getAsync(children[i].id);
|
||||
let child = yield this.ObjectsClass.getAsync(children[i].id);
|
||||
let descendents = yield child.getChildren(
|
||||
true, nested, type, includeDeletedItems, level+1
|
||||
);
|
||||
|
@ -871,7 +823,7 @@ Zotero.Collection.prototype.addLinkedCollection = Zotero.Promise.coroutine(funct
|
|||
var predicate = Zotero.Relations.linkedObjectPredicate;
|
||||
if ((yield Zotero.Relations.getByURIs(url1, predicate, url2)).length
|
||||
|| (yield Zotero.Relations.getByURIs(url2, predicate, url1)).length) {
|
||||
Zotero.debug("Collections " + this.key + " and " + collection.key + " are already linked");
|
||||
Zotero.debug(this._ObjectTypePlural + " " + this.key + " and " + collection.key + " are already linked");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -901,9 +853,9 @@ Zotero.Collection.prototype.loadChildCollections = Zotero.Promise.coroutine(func
|
|||
|
||||
this._childCollections = [];
|
||||
|
||||
if (ids) {
|
||||
if (ids.length) {
|
||||
for each(var id in ids) {
|
||||
var col = yield Zotero.Collections.getAsync(id);
|
||||
var col = yield this.ObjectsClass.getAsync(id);
|
||||
if (!col) {
|
||||
throw new Error('Collection ' + id + ' not found');
|
||||
}
|
||||
|
@ -943,7 +895,7 @@ Zotero.Collection.prototype.loadChildItems = Zotero.Promise.coroutine(function*
|
|||
this._childItems = [];
|
||||
|
||||
if (ids) {
|
||||
var items = yield Zotero.Items.getAsync(ids)
|
||||
var items = yield this.ChildObjects.getAsync(ids)
|
||||
if (items) {
|
||||
this._childItems = items;
|
||||
}
|
||||
|
|
|
@ -27,9 +27,10 @@
|
|||
/*
|
||||
* Primary interface for accessing Zotero collection
|
||||
*/
|
||||
Zotero.Collections = new function() {
|
||||
Zotero.DataObjects.apply(this, ['collection']);
|
||||
this.constructor.prototype = new Zotero.DataObjects();
|
||||
Zotero.Collections = function() {
|
||||
this.constructor = null;
|
||||
|
||||
this._ZDO_object = 'collection';
|
||||
|
||||
this._primaryDataSQLParts = {
|
||||
collectionID: "O.collectionID",
|
||||
|
@ -45,9 +46,13 @@ Zotero.Collections = new function() {
|
|||
hasChildCollections: "(SELECT COUNT(*) FROM collections WHERE "
|
||||
+ "parentCollectionID=O.collectionID) != 0 AS hasChildCollections",
|
||||
hasChildItems: "(SELECT COUNT(*) FROM collectionItems WHERE "
|
||||
+ "collectionID=O.collectionID) != 0 AS hasChildItems "
|
||||
+ "collectionID=O.collectionID) != 0 AS hasChildItems"
|
||||
};
|
||||
|
||||
|
||||
this._primaryDataSQLFrom = "FROM collections O "
|
||||
+ "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID)";
|
||||
|
||||
/**
|
||||
* Add new collection to DB and return Collection object
|
||||
*
|
||||
|
@ -74,55 +79,51 @@ Zotero.Collections = new function() {
|
|||
* Takes parent collectionID as optional parameter;
|
||||
* by default, returns root collections
|
||||
*/
|
||||
this.getByParent = Zotero.Promise.coroutine(function* (libraryID, parent, recursive) {
|
||||
var toReturn = [];
|
||||
this.getByParent = Zotero.Promise.coroutine(function* (libraryID, parentID, recursive) {
|
||||
let children;
|
||||
|
||||
if (!parent) {
|
||||
parent = null;
|
||||
if (parentID) {
|
||||
let parent = yield this.getAsync(parentID);
|
||||
yield parent.loadChildCollections();
|
||||
children = parent.getChildCollections();
|
||||
if (!children.length) Zotero.debug('No child collections in collection ' + parentID, 5);
|
||||
} else if (libraryID || libraryID === 0) {
|
||||
children = this.getCollectionsInLibrary(libraryID);
|
||||
if (!children.length) Zotero.debug('No child collections in library ' + libraryID, 5);
|
||||
} else {
|
||||
throw new Error("Either library ID or parent collection ID must be provided to getNumCollectionsByParent");
|
||||
}
|
||||
|
||||
var sql = "SELECT collectionID AS id, collectionName AS name FROM collections C "
|
||||
+ "WHERE libraryID=? AND parentCollectionID " + (parent ? '= ' + parent : 'IS NULL');
|
||||
var children = yield Zotero.DB.queryAsync(sql, [libraryID]);
|
||||
|
||||
if (!children) {
|
||||
Zotero.debug('No child collections of collection ' + parent, 5);
|
||||
return toReturn;
|
||||
if (!children.length) {
|
||||
return children;
|
||||
}
|
||||
|
||||
// Do proper collation sort
|
||||
var collation = Zotero.getLocaleCollation();
|
||||
children.sort(function (a, b) {
|
||||
return collation.compareString(1, a.name, b.name);
|
||||
});
|
||||
children.sort(function (a, b) Zotero.localeCompare(a.name, b.name));
|
||||
|
||||
if (!recursive) return children;
|
||||
|
||||
let toReturn = [];
|
||||
for (var i=0, len=children.length; i<len; i++) {
|
||||
var obj = yield this.getAsync(children[i].id);
|
||||
if (!obj) {
|
||||
throw ('Collection ' + children[i].id + ' not found');
|
||||
}
|
||||
|
||||
var obj = children[i];
|
||||
toReturn.push(obj);
|
||||
|
||||
// If recursive, get descendents
|
||||
if (recursive) {
|
||||
var desc = obj.getDescendents(false, 'collection');
|
||||
for (var j in desc) {
|
||||
var obj2 = yield this.getAsync(desc[j]['id']);
|
||||
if (!obj2) {
|
||||
throw new Error('Collection ' + desc[j] + ' not found');
|
||||
}
|
||||
|
||||
// TODO: This is a quick hack so that we can indent subcollections
|
||||
// in the search dialog -- ideally collections would have a
|
||||
// getLevel() method, but there's no particularly quick way
|
||||
// of calculating that without either storing it in the DB or
|
||||
// changing the schema to Modified Preorder Tree Traversal,
|
||||
// and I don't know if we'll actually need it anywhere else.
|
||||
obj2.level = desc[j].level;
|
||||
|
||||
toReturn.push(obj2);
|
||||
var desc = obj.getDescendents(false, 'collection');
|
||||
for (var j in desc) {
|
||||
var obj2 = yield this.getAsync(desc[j]['id']);
|
||||
if (!obj2) {
|
||||
throw new Error('Collection ' + desc[j] + ' not found');
|
||||
}
|
||||
|
||||
// TODO: This is a quick hack so that we can indent subcollections
|
||||
// in the search dialog -- ideally collections would have a
|
||||
// getLevel() method, but there's no particularly quick way
|
||||
// of calculating that without either storing it in the DB or
|
||||
// changing the schema to Modified Preorder Tree Traversal,
|
||||
// and I don't know if we'll actually need it anywhere else.
|
||||
obj2.level = desc[j].level;
|
||||
|
||||
toReturn.push(obj2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,6 +131,17 @@ Zotero.Collections = new function() {
|
|||
});
|
||||
|
||||
|
||||
this.getCollectionsInLibrary = Zotero.Promise.coroutine(function* (libraryID) {
|
||||
let sql = "SELECT collectionID AS id FROM collections C "
|
||||
+ "WHERE libraryID=? AND parentCollectionId IS NULL";
|
||||
let ids = yield Zotero.DB.queryAsync(sql, [libraryID]);
|
||||
let collections = yield this.getAsync(ids.map(function(row) row.id));
|
||||
if (!collections.length) return collections;
|
||||
|
||||
return collections.sort(function (a, b) Zotero.localeCompare(a.name, b.name));
|
||||
});
|
||||
|
||||
|
||||
this.getCollectionsContainingItems = function (itemIDs, asIDs) {
|
||||
// If an unreasonable number of items, don't try
|
||||
if (itemIDs.length > 100) {
|
||||
|
@ -145,8 +157,8 @@ Zotero.Collections = new function() {
|
|||
}
|
||||
sql = sql.substring(0, sql.length - 5);
|
||||
return Zotero.DB.columnQueryAsync(sql, sqlParams)
|
||||
.then(function (collectionIDs) {
|
||||
return asIDs ? collectionIDs : Zotero.Collections.get(collectionIDs);
|
||||
.then(collectionIDs => {
|
||||
return asIDs ? collectionIDs : this.get(collectionIDs);
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -186,32 +198,23 @@ Zotero.Collections = new function() {
|
|||
});
|
||||
|
||||
|
||||
this.erase = function (ids) {
|
||||
this.erase = function(ids) {
|
||||
ids = Zotero.flattenArguments(ids);
|
||||
|
||||
Zotero.DB.beginTransaction();
|
||||
for each(var id in ids) {
|
||||
var collection = this.getAsync(id);
|
||||
if (collection) {
|
||||
collection.erase();
|
||||
return Zotero.DB.executeTransaction(function* () {
|
||||
for each(var id in ids) {
|
||||
var collection = yield this.getAsync(id);
|
||||
if (collection) {
|
||||
yield collection.erase();
|
||||
}
|
||||
collection = undefined;
|
||||
}
|
||||
collection = undefined;
|
||||
}
|
||||
|
||||
this.unload(ids);
|
||||
|
||||
Zotero.DB.commitTransaction();
|
||||
}
|
||||
|
||||
this.unload(ids);
|
||||
});
|
||||
};
|
||||
|
||||
Zotero.DataObjects.call(this);
|
||||
|
||||
this.getPrimaryDataSQL = function () {
|
||||
// This should be the same as the query in Zotero.Collection.load(),
|
||||
// just without a specific collectionID
|
||||
return "SELECT "
|
||||
+ Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " "
|
||||
+ "FROM collections O "
|
||||
+ "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID) "
|
||||
+ "WHERE 1";
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}.bind(Object.create(Zotero.DataObjects.prototype))();
|
||||
|
|
|
@ -34,6 +34,8 @@ Zotero.DataObject = function () {
|
|||
let objectType = this._objectType;
|
||||
this._ObjectType = objectType[0].toUpperCase() + objectType.substr(1);
|
||||
this._objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
|
||||
this._ObjectTypePlural = this._objectTypePlural[0].toUpperCase() + this._objectTypePlural.substr(1);
|
||||
this._ObjectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
|
||||
|
||||
this._id = null;
|
||||
this._libraryID = null;
|
||||
|
@ -53,23 +55,36 @@ Zotero.DataObject = function () {
|
|||
};
|
||||
|
||||
Zotero.DataObject.prototype._objectType = 'dataObject';
|
||||
Zotero.DataObject.prototype._dataTypes = [];
|
||||
Zotero.DataObject.prototype._dataTypes = ['primaryData'];
|
||||
|
||||
Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'objectType', {
|
||||
Zotero.defineProperty(Zotero.DataObject.prototype, 'objectType', {
|
||||
get: function() this._objectType
|
||||
});
|
||||
Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'libraryKey', {
|
||||
Zotero.defineProperty(Zotero.DataObject.prototype, 'id', {
|
||||
get: function() this._id
|
||||
});
|
||||
Zotero.defineProperty(Zotero.DataObject.prototype, 'libraryID', {
|
||||
get: function() this._libraryID
|
||||
});
|
||||
Zotero.defineProperty(Zotero.DataObject.prototype, 'key', {
|
||||
get: function() this._key
|
||||
});
|
||||
Zotero.defineProperty(Zotero.DataObject.prototype, 'libraryKey', {
|
||||
get: function() this._libraryID + "/" + this._key
|
||||
});
|
||||
Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'parentKey', {
|
||||
Zotero.defineProperty(Zotero.DataObject.prototype, 'parentKey', {
|
||||
get: function() this._parentKey,
|
||||
set: function(v) this._setParentKey(v)
|
||||
});
|
||||
Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'parentID', {
|
||||
Zotero.defineProperty(Zotero.DataObject.prototype, 'parentID', {
|
||||
get: function() this._getParentID(),
|
||||
set: function(v) this._setParentID(v)
|
||||
});
|
||||
|
||||
Zotero.defineProperty(Zotero.DataObject.prototype, 'ObjectsClass', {
|
||||
get: function() this._ObjectsClass
|
||||
});
|
||||
|
||||
|
||||
Zotero.DataObject.prototype._get = function (field) {
|
||||
if (this['_' + field] !== null) {
|
||||
|
@ -135,7 +150,7 @@ Zotero.DataObject.prototype._getParentID = function () {
|
|||
if (!this._parentKey) {
|
||||
return false;
|
||||
}
|
||||
return this._parentID = this._getClass().getIDFromLibraryAndKey(this._libraryID, this._parentKey);
|
||||
return this._parentID = this.ObjectsClass.getIDFromLibraryAndKey(this._libraryID, this._parentKey);
|
||||
}
|
||||
|
||||
|
||||
|
@ -148,7 +163,7 @@ Zotero.DataObject.prototype._getParentID = function () {
|
|||
Zotero.DataObject.prototype._setParentID = function (id) {
|
||||
return this._setParentKey(
|
||||
id
|
||||
? this._getClass().getLibraryAndKeyFromID(Zotero.DataObjectUtilities.checkDataID(id))[1]
|
||||
? this.ObjectsClass.getLibraryAndKeyFromID(Zotero.DataObjectUtilities.checkDataID(id))[1]
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
@ -309,6 +324,60 @@ Zotero.DataObject.prototype._getLinkedObject = Zotero.Promise.coroutine(function
|
|||
return false;
|
||||
});
|
||||
|
||||
/*
|
||||
* Build object from database
|
||||
*/
|
||||
Zotero.DataObject.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload, failOnMissing) {
|
||||
if (this._loaded.primaryData && !reload) return;
|
||||
|
||||
var id = this.id;
|
||||
var key = this.key;
|
||||
var libraryID = this.libraryID;
|
||||
|
||||
if (!id && !key) {
|
||||
throw new Error('ID or key not set in Zotero.' + this._ObjectType + '.loadPrimaryData()');
|
||||
}
|
||||
|
||||
var columns = [], join = [], where = [];
|
||||
var primaryFields = this.ObjectsClass.primaryFields;
|
||||
var idField = this.ObjectsClass.idColumn;
|
||||
for (let i=0; i<primaryFields.length; i++) {
|
||||
let field = primaryFields[i];
|
||||
// If field not already set
|
||||
if (field == idField || this['_' + field] === null || reload) {
|
||||
columns.push(this.ObjectsClass.getPrimaryDataSQLPart(field));
|
||||
}
|
||||
}
|
||||
if (!columns.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This should match Zotero.*.primaryDataSQL, but without
|
||||
// necessarily including all columns
|
||||
var sql = "SELECT " + columns.join(", ") + this.ObjectsClass.primaryDataSQLFrom;
|
||||
if (id) {
|
||||
sql += " AND O." + idField + "=? ";
|
||||
var params = id;
|
||||
}
|
||||
else {
|
||||
sql += " AND O.key=? AND O.libraryID=? ";
|
||||
var params = [key, libraryID];
|
||||
}
|
||||
sql += (where.length ? ' AND ' + where.join(' AND ') : '');
|
||||
var row = yield Zotero.DB.rowQueryAsync(sql, params);
|
||||
|
||||
if (!row) {
|
||||
if (failOnMissing) {
|
||||
throw new Error(this._ObjectType + " " + (id ? id : libraryID + "/" + key)
|
||||
+ " not found in Zotero." + this._ObjectType + ".loadPrimaryData()");
|
||||
}
|
||||
this._loaded.primaryData = true;
|
||||
this._clearChanged('primaryData');
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadFromRow(row, reload);
|
||||
});
|
||||
|
||||
/**
|
||||
* Reloads loaded, changed data
|
||||
|
@ -368,13 +437,6 @@ Zotero.DataObject.prototype._requireData = function (dataType) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a global Zotero class object given a data object. (e.g. Zotero.Items)
|
||||
* @return {obj} One of Zotero data classes
|
||||
*/
|
||||
Zotero.DataObject.prototype._getClass = function () {
|
||||
return Zotero.DataObjectUtilities.getClassForObjectType(this._objectType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data for a given data type
|
||||
|
@ -385,6 +447,14 @@ Zotero.DataObject.prototype._loadDataType = function (dataType, reload) {
|
|||
return this["load" + dataType[0].toUpperCase() + dataType.substr(1)](reload);
|
||||
}
|
||||
|
||||
Zotero.DataObject.prototype.loadAllData = function (reload) {
|
||||
let loadPromises = new Array(this._dataTypes.length);
|
||||
for (let i=0; i<this._dataTypes.length; i++) {
|
||||
loadPromises[i] = this._loadDataType(this._dataTypes[i], reload);
|
||||
}
|
||||
|
||||
return Zotero.Promise.all(loadPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save old version of data that's being changed, to pass to the notifier
|
||||
|
@ -422,6 +492,141 @@ Zotero.DataObject.prototype._clearFieldChange = function (field) {
|
|||
delete this._previousData[field];
|
||||
}
|
||||
|
||||
|
||||
Zotero.DataObject.prototype.isEditable = function () {
|
||||
return Zotero.Libraries.isEditable(this.libraryID);
|
||||
}
|
||||
|
||||
|
||||
Zotero.DataObject.prototype.editCheck = function () {
|
||||
if (!Zotero.Sync.Server.updatesInProgress && !Zotero.Sync.Storage.updatesInProgress && !this.isEditable()) {
|
||||
throw ("Cannot edit " + this._objectType + " in read-only Zotero library");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save changes to database
|
||||
*
|
||||
* @return {Promise<Integer|Boolean>} Promise for itemID of new item,
|
||||
* TRUE on item update, or FALSE if item was unchanged
|
||||
*/
|
||||
Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options) {
|
||||
var env = {
|
||||
transactionOptions: null,
|
||||
options: options || {}
|
||||
};
|
||||
|
||||
var proceed = yield this._initSave(env);
|
||||
if (!proceed) return false;
|
||||
|
||||
if (env.isNew) {
|
||||
Zotero.debug('Saving data for new ' + this._objectType + ' to database', 4);
|
||||
}
|
||||
else {
|
||||
Zotero.debug('Updating database with new ' + this._objectType + ' data', 4);
|
||||
}
|
||||
|
||||
return Zotero.DB.executeTransaction(function* () {
|
||||
yield this._saveData(env);
|
||||
return yield this._finalizeSave(env);
|
||||
}.bind(this), env.transactionOptions)
|
||||
.catch(e => {
|
||||
return this._recoverFromSaveError(env, e)
|
||||
.catch(function(e2) {
|
||||
Zotero.debug(e2, 1);
|
||||
})
|
||||
.then(function() {
|
||||
Zotero.debug(e, 1);
|
||||
throw e;
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
Zotero.DataObject.prototype.hasChanged = function() {
|
||||
Zotero.debug(this._changed);
|
||||
return !!Object.keys(this._changed).filter(dataType => this._changed[dataType]).length
|
||||
}
|
||||
|
||||
Zotero.DataObject.prototype._saveData = function() {
|
||||
throw new Error("Zotero.DataObject.prototype._saveData is an abstract method");
|
||||
}
|
||||
|
||||
Zotero.DataObject.prototype._finalizeSave = function() {
|
||||
throw new Error("Zotero.DataObject.prototype._finalizeSave is an abstract method");
|
||||
}
|
||||
|
||||
Zotero.DataObject.prototype._recoverFromSaveError = Zotero.Promise.coroutine(function* () {
|
||||
yield this.reload(null, true);
|
||||
this._clearChanged();
|
||||
});
|
||||
|
||||
Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
|
||||
env.isNew = !this.id;
|
||||
|
||||
if (!env.options.skipEditCheck) this.editCheck();
|
||||
|
||||
if (!this.hasChanged()) {
|
||||
Zotero.debug(this._ObjectType + ' ' + this.id + ' has not changed', 4);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register this object's identifiers in Zotero.DataObjects on transaction commit,
|
||||
// before other callbacks run
|
||||
if (env.isNew) {
|
||||
env.transactionOptions = {
|
||||
onCommit: () => {
|
||||
this.ObjectsClass.registerIdentifiers(env.id, env.libraryID, env.key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete object from database
|
||||
*/
|
||||
Zotero.DataObject.prototype.erase = Zotero.Promise.coroutine(function* () {
|
||||
var env = {};
|
||||
|
||||
var proceed = yield this._eraseInit(env);
|
||||
if (!proceed) return false;
|
||||
|
||||
Zotero.debug('Deleting ' + this.objectType + ' ' + this.id);
|
||||
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
yield this._eraseData(env);
|
||||
yield this._erasePreCommit(env);
|
||||
}.bind(this))
|
||||
.catch(e => {
|
||||
return this._eraseRecoverFromFailure(env);
|
||||
});
|
||||
|
||||
return this._erasePostCommit(env);
|
||||
});
|
||||
|
||||
Zotero.DataObject.prototype._eraseInit = function(env) {
|
||||
if (!this.id) return Zotero.Promise.resolve(false);
|
||||
|
||||
return Zotero.Promise.resolve(true);
|
||||
};
|
||||
|
||||
Zotero.DataObject.prototype._eraseData = function(env) {
|
||||
throw new Error("Zotero.DataObject.prototype._eraseData is an abstract method");
|
||||
};
|
||||
|
||||
Zotero.DataObject.prototype._erasePreCommit = function(env) {
|
||||
return Zotero.Promise.resolve();
|
||||
};
|
||||
|
||||
Zotero.DataObject.prototype._erasePostCommit = function(env) {
|
||||
return Zotero.Promise.resolve();
|
||||
};
|
||||
|
||||
Zotero.DataObject.prototype._eraseRecoverFromFailure = function(env) {
|
||||
throw new Error("Zotero.DataObject.prototype._eraseRecoverFromFailure is an abstract method");
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates data object key
|
||||
* @return {String} key
|
||||
|
|
|
@ -59,12 +59,12 @@ Zotero.DataObjectUtilities = {
|
|||
},
|
||||
|
||||
|
||||
"getObjectTypePlural": function getObjectTypePlural(objectType) {
|
||||
"getObjectTypePlural": function(objectType) {
|
||||
return objectType == 'search' ? 'searches' : objectType + 's';
|
||||
},
|
||||
|
||||
|
||||
"getClassForObjectType": function getClassForObjectType(objectType) {
|
||||
"getObjectsClassForObjectType": function(objectType) {
|
||||
var objectTypePlural = this.getObjectTypePlural(objectType);
|
||||
var className = objectTypePlural[0].toUpperCase() + objectTypePlural.substr(1);
|
||||
return Zotero[className]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -103,7 +103,7 @@ Zotero.ItemFields = new function() {
|
|||
}
|
||||
|
||||
if (typeof field == 'number') {
|
||||
return field;
|
||||
return _fields[field] ? field : false;
|
||||
}
|
||||
|
||||
return _fields[field] ? _fields[field]['id'] : false;
|
||||
|
|
|
@ -27,17 +27,16 @@
|
|||
/*
|
||||
* Primary interface for accessing Zotero items
|
||||
*/
|
||||
Zotero.Items = new function() {
|
||||
Zotero.DataObjects.apply(this, ['item']);
|
||||
this.constructor.prototype = new Zotero.DataObjects();
|
||||
Zotero.Items = function() {
|
||||
this.constructor = null;
|
||||
|
||||
// Privileged methods
|
||||
this.add = add;
|
||||
this.getSortTitle = getSortTitle;
|
||||
this._ZDO_object = 'item';
|
||||
|
||||
Object.defineProperty(this, "_primaryDataSQLParts", {
|
||||
// This needs to wait until all Zotero components are loaded to initialize,
|
||||
// but otherwise it can be just a simple property
|
||||
Zotero.defineProperty(this, "_primaryDataSQLParts", {
|
||||
get: function () {
|
||||
return _primaryDataSQLParts ? _primaryDataSQLParts : (_primaryDataSQLParts = {
|
||||
return {
|
||||
itemID: "O.itemID",
|
||||
itemTypeID: "O.itemTypeID",
|
||||
dateAdded: "O.dateAdded",
|
||||
|
@ -88,18 +87,17 @@ Zotero.Items = new function() {
|
|||
attachmentContentType: "IA.contentType AS attachmentContentType",
|
||||
attachmentPath: "IA.path AS attachmentPath",
|
||||
attachmentSyncState: "IA.syncState AS attachmentSyncState"
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
}, {lazy: true});
|
||||
|
||||
// Private members
|
||||
var _primaryDataSQLParts;
|
||||
var _cachedFields = {};
|
||||
var _firstCreatorSQL = '';
|
||||
var _sortCreatorSQL = '';
|
||||
var _emptyTrashIdleObserver = null;
|
||||
var _emptyTrashTimer = null;
|
||||
|
||||
this._primaryDataSQLFrom = "FROM items O "
|
||||
+ "LEFT JOIN itemAttachments IA USING (itemID) "
|
||||
+ "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) "
|
||||
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
|
||||
+ "LEFT JOIN items INoP ON (INo.parentItemID=INoP.itemID) "
|
||||
+ "LEFT JOIN deletedItems DI ON (O.itemID=DI.itemID)";
|
||||
|
||||
/**
|
||||
* Return items marked as deleted
|
||||
|
@ -215,77 +213,7 @@ Zotero.Items = new function() {
|
|||
};
|
||||
|
||||
|
||||
/*
|
||||
* Create a new item with optional metadata and pass back the primary reference
|
||||
*
|
||||
* Using "var item = new Zotero.Item()" and "item.save()" directly results
|
||||
* in an orphaned reference to the created item. If other code retrieves the
|
||||
* new item with Zotero.Items.get() and modifies it, the original reference
|
||||
* will not reflect the changes.
|
||||
*
|
||||
* Using this method avoids the need to call Zotero.Items.get() after save()
|
||||
* in order to get the primary item reference. Since it accepts metadata
|
||||
* as a JavaScript object, it also offers a simpler syntax than
|
||||
* item.setField() and item.setCreator().
|
||||
*
|
||||
* Callers with no need for an up-to-date reference after save() (or who
|
||||
* don't mind doing an extra Zotero.Items.get()) can use Zotero.Item
|
||||
* directly if they prefer.
|
||||
*
|
||||
* Sample usage:
|
||||
*
|
||||
* var data = {
|
||||
* title: "Shakespeare: The Invention of the Human",
|
||||
* publisher: "Riverhead Hardcover",
|
||||
* date: '1998-10-26',
|
||||
* ISBN: 1573221201,
|
||||
* pages: 745,
|
||||
* creators: [
|
||||
* ['Harold', 'Bloom', 'author']
|
||||
* ]
|
||||
* };
|
||||
* var item = Zotero.Items.add('book', data);
|
||||
*/
|
||||
function add(itemTypeOrID, data) {
|
||||
var item = new Zotero.Item(itemTypeOrID);
|
||||
for (var field in data) {
|
||||
if (field == 'creators') {
|
||||
var i = 0;
|
||||
for each(var creator in data.creators) {
|
||||
// TODO: accept format from toArray()
|
||||
|
||||
var fields = {
|
||||
firstName: creator[0],
|
||||
lastName: creator[1],
|
||||
fieldMode: creator[3] ? creator[3] : 0
|
||||
};
|
||||
|
||||
var creatorDataID = Zotero.Creators.getDataID(fields);
|
||||
if (creatorDataID) {
|
||||
var linkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID);
|
||||
// TODO: identical creators?
|
||||
var creatorID = linkedCreators[0];
|
||||
}
|
||||
else {
|
||||
var creatorObj = new Zotero.Creator;
|
||||
creatorObj.setFields(fields);
|
||||
var creatorID = creatorObj.save();
|
||||
}
|
||||
|
||||
item.setCreator(i, Zotero.Creators.get(creatorID), creator[2]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
item.setField(field, data[field]);
|
||||
}
|
||||
}
|
||||
var id = item.save();
|
||||
|
||||
return this.getAsync(id);
|
||||
}
|
||||
|
||||
|
||||
this._cachedFields = {};
|
||||
this.cacheFields = Zotero.Promise.coroutine(function* (libraryID, fields, items) {
|
||||
if (items && items.length == 0) {
|
||||
return;
|
||||
|
@ -315,14 +243,14 @@ Zotero.Items = new function() {
|
|||
var fieldIDs = [];
|
||||
for each(var field in fields) {
|
||||
// Check if field already cached
|
||||
if (_cachedFields[libraryID] && _cachedFields[libraryID].indexOf(field) != -1) {
|
||||
if (this._cachedFields[libraryID] && this._cachedFields[libraryID].indexOf(field) != -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_cachedFields[libraryID]) {
|
||||
_cachedFields[libraryID] = [];
|
||||
if (!this._cachedFields[libraryID]) {
|
||||
this._cachedFields[libraryID] = [];
|
||||
}
|
||||
_cachedFields[libraryID].push(field);
|
||||
this._cachedFields[libraryID].push(field);
|
||||
|
||||
if (this.isPrimaryField(field)) {
|
||||
primaryFields.push(field);
|
||||
|
@ -472,7 +400,7 @@ Zotero.Items = new function() {
|
|||
for (let i=0; i<allItemIDs.length; i++) {
|
||||
let itemID = allItemIDs[i];
|
||||
let item = this._objectCache[itemID];
|
||||
yield this._objectCache[itemID].loadDisplayTitle()
|
||||
yield item.loadDisplayTitle()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,7 +425,7 @@ Zotero.Items = new function() {
|
|||
// 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);
|
||||
var attachment = yield this.getAsync(id);
|
||||
|
||||
// TODO: Skip identical children?
|
||||
|
||||
|
@ -549,7 +477,7 @@ Zotero.Items = new function() {
|
|||
}
|
||||
|
||||
yield item.save();
|
||||
});
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
||||
|
@ -604,9 +532,11 @@ Zotero.Items = new function() {
|
|||
/**
|
||||
* Start idle observer to delete trashed items older than a certain number of days
|
||||
*/
|
||||
this._emptyTrashIdleObserver = null;
|
||||
this._emptyTrashTimer = null;
|
||||
this.startEmptyTrashTimer = function () {
|
||||
_emptyTrashIdleObserver = {
|
||||
observe: function (subject, topic, data) {
|
||||
this._emptyTrashIdleObserver = {
|
||||
observe: (subject, topic, data) => {
|
||||
if (topic == 'idle' || topic == 'timer-callback') {
|
||||
var days = Zotero.Prefs.get('trashAutoEmptyDays');
|
||||
if (!days) {
|
||||
|
@ -620,20 +550,20 @@ Zotero.Items = new function() {
|
|||
// TODO: increase number after dealing with slow
|
||||
// tag.getLinkedItems() call during deletes
|
||||
var num = 10;
|
||||
Zotero.Items.emptyTrash(null, days, num)
|
||||
.then(function (deleted) {
|
||||
this.emptyTrash(null, days, num)
|
||||
.then(deleted => {
|
||||
if (!deleted) {
|
||||
_emptyTrashTimer = null;
|
||||
this._emptyTrashTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set a timer to do more every few seconds
|
||||
if (!_emptyTrashTimer) {
|
||||
_emptyTrashTimer = Components.classes["@mozilla.org/timer;1"]
|
||||
if (!this._emptyTrashTimer) {
|
||||
this._emptyTrashTimer = Components.classes["@mozilla.org/timer;1"]
|
||||
.createInstance(Components.interfaces.nsITimer);
|
||||
}
|
||||
_emptyTrashTimer.init(
|
||||
_emptyTrashIdleObserver.observe,
|
||||
this._emptyTrashTimer.init(
|
||||
this._emptyTrashIdleObserver.observe,
|
||||
5 * 1000,
|
||||
Components.interfaces.nsITimer.TYPE_ONE_SHOT
|
||||
);
|
||||
|
@ -641,8 +571,8 @@ Zotero.Items = new function() {
|
|||
}
|
||||
// When no longer idle, cancel timer
|
||||
else if (topic == 'back') {
|
||||
if (_emptyTrashTimer) {
|
||||
_emptyTrashTimer.cancel();
|
||||
if (this._emptyTrashTimer) {
|
||||
this._emptyTrashTimer.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -650,7 +580,7 @@ Zotero.Items = new function() {
|
|||
|
||||
var idleService = Components.classes["@mozilla.org/widget/idleservice;1"].
|
||||
getService(Components.interfaces.nsIIdleService);
|
||||
idleService.addIdleObserver(_emptyTrashIdleObserver, 305);
|
||||
idleService.addIdleObserver(this._emptyTrashIdleObserver, 305);
|
||||
}
|
||||
|
||||
|
||||
|
@ -693,28 +623,12 @@ Zotero.Items = new function() {
|
|||
});
|
||||
|
||||
|
||||
this.getPrimaryDataSQL = function () {
|
||||
return "SELECT "
|
||||
+ Object.keys(this._primaryDataSQLParts).map((val) => this._primaryDataSQLParts[val]).join(', ')
|
||||
+ this.primaryDataSQLFrom;
|
||||
};
|
||||
|
||||
|
||||
this.primaryDataSQLFrom = " FROM items O "
|
||||
+ "LEFT JOIN itemAttachments IA USING (itemID) "
|
||||
+ "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) "
|
||||
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
|
||||
+ "LEFT JOIN items INoP ON (INo.parentItemID=INoP.itemID) "
|
||||
+ "LEFT JOIN deletedItems DI ON (O.itemID=DI.itemID) "
|
||||
+ "WHERE 1";
|
||||
|
||||
|
||||
this._postLoad = function (libraryID, ids) {
|
||||
if (!ids) {
|
||||
if (!_cachedFields[libraryID]) {
|
||||
_cachedFields[libraryID] = [];
|
||||
if (!this._cachedFields[libraryID]) {
|
||||
this._cachedFields[libraryID] = [];
|
||||
}
|
||||
_cachedFields[libraryID] = this.primaryFields.concat();
|
||||
this._cachedFields[libraryID] = this.primaryFields.concat();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -724,6 +638,7 @@ Zotero.Items = new function() {
|
|||
*
|
||||
* Why do we do this entirely in SQL? Because we're crazy. Crazy like foxes.
|
||||
*/
|
||||
var _firstCreatorSQL = '';
|
||||
function _getFirstCreatorSQL() {
|
||||
if (_firstCreatorSQL) {
|
||||
return _firstCreatorSQL;
|
||||
|
@ -828,6 +743,7 @@ Zotero.Items = new function() {
|
|||
/*
|
||||
* Generate SQL to retrieve sortCreator field
|
||||
*/
|
||||
var _sortCreatorSQL = '';
|
||||
function _getSortCreatorSQL() {
|
||||
if (_sortCreatorSQL) {
|
||||
return _sortCreatorSQL;
|
||||
|
@ -947,7 +863,7 @@ Zotero.Items = new function() {
|
|||
}
|
||||
|
||||
|
||||
function getSortTitle(title) {
|
||||
this.getSortTitle = function(title) {
|
||||
if (title === false || title === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
@ -956,4 +872,8 @@ Zotero.Items = new function() {
|
|||
}
|
||||
return title.replace(/^[\[\'\"](.*)[\'\"\]]?$/, '$1')
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.DataObjects.call(this);
|
||||
|
||||
return this;
|
||||
}.bind(Object.create(Zotero.DataObjects.prototype))();
|
||||
|
|
|
@ -28,7 +28,7 @@ Zotero.Libraries = new function () {
|
|||
_userLibraryID,
|
||||
_libraryDataLoaded = false;
|
||||
|
||||
Zotero.Utilities.Internal.defineProperty(this, 'userLibraryID', {
|
||||
Zotero.defineProperty(this, 'userLibraryID', {
|
||||
get: function() {
|
||||
if (!_libraryDataLoaded) {
|
||||
throw new Error("Library data not yet loaded");
|
||||
|
@ -177,4 +177,12 @@ Zotero.Libraries = new function () {
|
|||
throw new Error("Unsupported library type '" + type + "' in Zotero.Libraries.getName()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isGroupLibrary = function (libraryID) {
|
||||
if (!_libraryDataLoaded) {
|
||||
throw new Error("Library data not yet loaded");
|
||||
}
|
||||
|
||||
return this.getType(libraryID) == 'group';
|
||||
}
|
||||
}
|
|
@ -23,18 +23,15 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Relations = new function () {
|
||||
Zotero.DataObjects.apply(this, ['relation']);
|
||||
this.constructor.prototype = new Zotero.DataObjects();
|
||||
Zotero.Relations = function () {
|
||||
this.constructor = null;
|
||||
|
||||
this.__defineGetter__('relatedItemPredicate', function () "dc:relation");
|
||||
this.__defineGetter__('linkedObjectPredicate', function () "owl:sameAs");
|
||||
this.__defineGetter__('deletedItemPredicate', function () 'dc:isReplacedBy');
|
||||
this._ZDO_object = 'relation';
|
||||
this._ZDO_idOnly = true;
|
||||
|
||||
var _namespaces = {
|
||||
dc: 'http://purl.org/dc/elements/1.1/',
|
||||
owl: 'http://www.w3.org/2002/07/owl#'
|
||||
};
|
||||
Zotero.defineProperty(this, 'relatedItemPredicate', {value: 'dc:relation'});
|
||||
Zotero.defineProperty(this, 'linkedObjectPredicate', {value: 'owl:sameAs'});
|
||||
Zotero.defineProperty(this, 'deletedItemPredicate', {value: 'dc:isReplacedBy'});
|
||||
|
||||
this.get = function (id) {
|
||||
if (typeof id != 'number') {
|
||||
|
@ -52,7 +49,7 @@ Zotero.Relations = new function () {
|
|||
*/
|
||||
this.getByURIs = Zotero.Promise.coroutine(function* (subject, predicate, object) {
|
||||
if (predicate) {
|
||||
predicate = _getPrefixAndValue(predicate).join(':');
|
||||
predicate = this._getPrefixAndValue(predicate).join(':');
|
||||
}
|
||||
|
||||
if (!subject && !predicate && !object) {
|
||||
|
@ -141,7 +138,7 @@ Zotero.Relations = new function () {
|
|||
|
||||
|
||||
this.add = Zotero.Promise.coroutine(function* (libraryID, subject, predicate, object) {
|
||||
predicate = _getPrefixAndValue(predicate).join(':');
|
||||
predicate = this._getPrefixAndValue(predicate).join(':');
|
||||
|
||||
var relation = new Zotero.Relation;
|
||||
if (!libraryID) {
|
||||
|
@ -272,11 +269,15 @@ Zotero.Relations = new function () {
|
|||
return relation;
|
||||
}
|
||||
|
||||
this._namespaces = {
|
||||
dc: 'http://purl.org/dc/elements/1.1/',
|
||||
owl: 'http://www.w3.org/2002/07/owl#'
|
||||
};
|
||||
|
||||
function _getPrefixAndValue(uri) {
|
||||
this._getPrefixAndValue = function(uri) {
|
||||
var [prefix, value] = uri.split(':');
|
||||
if (prefix && value) {
|
||||
if (!_namespaces[prefix]) {
|
||||
if (!this._namespaces[prefix]) {
|
||||
throw ("Invalid prefix '" + prefix + "' in Zotero.Relations._getPrefixAndValue()");
|
||||
}
|
||||
return [prefix, value];
|
||||
|
@ -290,4 +291,8 @@ Zotero.Relations = new function () {
|
|||
}
|
||||
throw ("Invalid namespace in URI '" + uri + "' in Zotero.Relations._getPrefixAndValue()");
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.DataObjects.call(this);
|
||||
|
||||
return this;
|
||||
}.bind(Object.create(Zotero.DataObjects.prototype))();
|
|
@ -37,13 +37,10 @@ Zotero.Search = function() {
|
|||
this._hasPrimaryConditions = false;
|
||||
}
|
||||
|
||||
Zotero.Search._super = Zotero.DataObject;
|
||||
Zotero.Search.prototype = Object.create(Zotero.Search._super.prototype);
|
||||
Zotero.Search.constructor = Zotero.Search;
|
||||
Zotero.extendClass(Zotero.DataObject, Zotero.Search);
|
||||
|
||||
Zotero.Search.prototype._objectType = 'search';
|
||||
Zotero.Search.prototype._dataTypes = Zotero.Search._super.prototype._dataTypes.concat([
|
||||
'primaryData',
|
||||
'conditions'
|
||||
]);
|
||||
|
||||
|
@ -62,21 +59,33 @@ Zotero.Search.prototype.setName = function(val) {
|
|||
this.name = val;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Search.prototype.__defineGetter__('id', function () { return this._get('id'); });
|
||||
Zotero.Search.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
|
||||
Zotero.Search.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
|
||||
Zotero.Search.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
|
||||
Zotero.Search.prototype.__defineGetter__('key', function () { return this._get('key'); });
|
||||
Zotero.Search.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
|
||||
Zotero.Search.prototype.__defineGetter__('name', function () { return this._get('name'); });
|
||||
Zotero.Search.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
|
||||
Zotero.Search.prototype.__defineGetter__('version', function () { return this._get('version'); });
|
||||
Zotero.Search.prototype.__defineSetter__('version', function (val) { this._set('version', val); });
|
||||
Zotero.Search.prototype.__defineGetter__('synced', function () { return this._get('synced'); });
|
||||
Zotero.Search.prototype.__defineSetter__('synced', function (val) { this._set('synced', val); });
|
||||
|
||||
Zotero.Search.prototype.__defineGetter__('conditions', function (arr) { this.getSearchConditions(); });
|
||||
Zotero.defineProperty(Zotero.Search.prototype, 'id', {
|
||||
get: function() this._get('id'),
|
||||
set: function(val) this._set('id', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Search.prototype, 'libraryID', {
|
||||
get: function() this._get('libraryID'),
|
||||
set: function(val) this._set('libraryID', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Search.prototype, 'key', {
|
||||
get: function() this._get('key'),
|
||||
set: function(val) this._set('key', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Search.prototype, 'name', {
|
||||
get: function() this._get('name'),
|
||||
set: function(val) this._set('name', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Search.prototype, 'version', {
|
||||
get: function() this._get('version'),
|
||||
set: function(val) this._set('version', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Search.prototype, 'synced', {
|
||||
get: function() this._get('synced'),
|
||||
set: function(val) this._set('synced', val)
|
||||
});
|
||||
Zotero.defineProperty(Zotero.Search.prototype, 'conditions', {
|
||||
get: function() this.getSearchConditions()
|
||||
});
|
||||
|
||||
Zotero.Search.prototype._set = function (field, value) {
|
||||
if (field == 'id' || field == 'libraryID' || field == 'key') {
|
||||
|
@ -161,152 +170,115 @@ Zotero.Search.prototype.loadFromRow = function (row) {
|
|||
this._identified = true;
|
||||
}
|
||||
|
||||
Zotero.Search.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
|
||||
if (!this.name) {
|
||||
throw('Name not provided for saved search');
|
||||
}
|
||||
|
||||
return Zotero.Search._super.prototype._initSave.apply(this, arguments);
|
||||
});
|
||||
|
||||
/*
|
||||
* Save the search to the DB and return a savedSearchID
|
||||
*
|
||||
* If there are gaps in the searchConditionIDs, |fixGaps| must be true
|
||||
* and the caller must dispose of the search or reload the condition ids,
|
||||
* which may change after the save.
|
||||
*
|
||||
* For new searches, name must be set called before saving
|
||||
*/
|
||||
Zotero.Search.prototype.save = Zotero.Promise.coroutine(function* (fixGaps) {
|
||||
try {
|
||||
Zotero.Searches.editCheck(this);
|
||||
|
||||
if (!this.name) {
|
||||
throw('Name not provided for saved search');
|
||||
}
|
||||
|
||||
var isNew = !this.id;
|
||||
|
||||
// Register this item's identifiers in Zotero.DataObjects on transaction commit,
|
||||
// before other callbacks run
|
||||
var searchID, libraryID, key;
|
||||
if (isNew) {
|
||||
var transactionOptions = {
|
||||
onCommit: function () {
|
||||
Zotero.Searches.registerIdentifiers(searchID, libraryID, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
var transactionOptions = null;
|
||||
}
|
||||
|
||||
return Zotero.DB.executeTransaction(function* () {
|
||||
searchID = this._id = this.id ? this.id : yield Zotero.ID.get('savedSearches');
|
||||
libraryID = this.libraryID;
|
||||
key = this._key = this.key ? this.key : this._generateKey();
|
||||
|
||||
Zotero.debug("Saving " + (isNew ? 'new ' : '') + "search " + this.id);
|
||||
|
||||
var columns = [
|
||||
'savedSearchID',
|
||||
'savedSearchName',
|
||||
'clientDateModified',
|
||||
'libraryID',
|
||||
'key',
|
||||
'version',
|
||||
'synced'
|
||||
];
|
||||
var placeholders = columns.map(function () '?').join();
|
||||
var sqlValues = [
|
||||
searchID ? { int: searchID } : null,
|
||||
{ string: this.name },
|
||||
Zotero.DB.transactionDateTime,
|
||||
this.libraryID ? this.libraryID : 0,
|
||||
key,
|
||||
this.version ? this.version : 0,
|
||||
this.synced ? 1 : 0
|
||||
];
|
||||
|
||||
var sql = "REPLACE INTO savedSearches (" + columns.join(', ') + ") "
|
||||
+ "VALUES (" + placeholders + ")";
|
||||
var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
|
||||
if (!searchID) {
|
||||
searchID = insertID;
|
||||
}
|
||||
|
||||
if (!isNew) {
|
||||
var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
|
||||
yield Zotero.DB.queryAsync(sql, this.id);
|
||||
}
|
||||
|
||||
// Close gaps in savedSearchIDs
|
||||
var saveConditions = {};
|
||||
var i = 1;
|
||||
for (var id in this._conditions) {
|
||||
if (!fixGaps && id != i) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
throw ('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' + this._id);
|
||||
}
|
||||
saveConditions[i] = this._conditions[id];
|
||||
i++;
|
||||
}
|
||||
|
||||
this._conditions = saveConditions;
|
||||
|
||||
for (var i in this._conditions){
|
||||
var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
|
||||
+ "searchConditionID, condition, operator, value, required) "
|
||||
+ "VALUES (?,?,?,?,?,?)";
|
||||
|
||||
// Convert condition and mode to "condition[/mode]"
|
||||
var condition = this._conditions[i].mode ?
|
||||
this._conditions[i].condition + '/' + this._conditions[i].mode :
|
||||
this._conditions[i].condition
|
||||
|
||||
var sqlParams = [
|
||||
searchID,
|
||||
i,
|
||||
condition,
|
||||
this._conditions[i].operator ? this._conditions[i].operator : null,
|
||||
this._conditions[i].value ? this._conditions[i].value : null,
|
||||
this._conditions[i].required ? 1 : null
|
||||
];
|
||||
yield Zotero.DB.queryAsync(sql, sqlParams);
|
||||
}
|
||||
|
||||
|
||||
if (isNew) {
|
||||
Zotero.Notifier.trigger('add', 'search', this.id);
|
||||
}
|
||||
else {
|
||||
Zotero.Notifier.trigger('modify', 'search', this.id, this._previousData);
|
||||
}
|
||||
|
||||
if (isNew && this.libraryID) {
|
||||
var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
|
||||
var group = yield Zotero.Groups.get(groupID);
|
||||
group.clearSearchCache();
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
var id = this.id;
|
||||
this._disabled = true;
|
||||
return id;
|
||||
}
|
||||
|
||||
yield this.reload();
|
||||
this._clearChanged();
|
||||
|
||||
return isNew ? this.id : true;
|
||||
}.bind(this), transactionOptions);
|
||||
Zotero.Search.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
|
||||
var fixGaps = env.options.fixGaps;
|
||||
var isNew = env.isNew;
|
||||
|
||||
var searchID = env.id = this._id = this.id ? this.id : yield Zotero.ID.get('savedSearches');
|
||||
var libraryID = env.libraryID = this.libraryID;
|
||||
var key = env.key = this._key = this.key ? this.key : this._generateKey();
|
||||
|
||||
var columns = [
|
||||
'savedSearchID',
|
||||
'savedSearchName',
|
||||
'clientDateModified',
|
||||
'libraryID',
|
||||
'key',
|
||||
'version',
|
||||
'synced'
|
||||
];
|
||||
var placeholders = columns.map(function () '?').join();
|
||||
var sqlValues = [
|
||||
searchID ? { int: searchID } : null,
|
||||
{ string: this.name },
|
||||
Zotero.DB.transactionDateTime,
|
||||
this.libraryID ? this.libraryID : 0,
|
||||
key,
|
||||
this.version ? this.version : 0,
|
||||
this.synced ? 1 : 0
|
||||
];
|
||||
|
||||
var sql = "REPLACE INTO savedSearches (" + columns.join(', ') + ") "
|
||||
+ "VALUES (" + placeholders + ")";
|
||||
var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
|
||||
if (!searchID) {
|
||||
searchID = env.id = insertID;
|
||||
}
|
||||
catch (e) {
|
||||
try {
|
||||
yield this.reload();
|
||||
this._clearChanged();
|
||||
}
|
||||
catch (e2) {
|
||||
Zotero.debug(e2, 1);
|
||||
}
|
||||
|
||||
Zotero.debug(e, 1);
|
||||
throw e;
|
||||
|
||||
if (!isNew) {
|
||||
var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
|
||||
yield Zotero.DB.queryAsync(sql, this.id);
|
||||
}
|
||||
|
||||
// Close gaps in savedSearchIDs
|
||||
var saveConditions = {};
|
||||
var i = 1;
|
||||
for (var id in this._conditions) {
|
||||
if (!fixGaps && id != i) {
|
||||
Zotero.DB.rollbackTransaction();
|
||||
throw ('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' + this._id);
|
||||
}
|
||||
saveConditions[i] = this._conditions[id];
|
||||
i++;
|
||||
}
|
||||
|
||||
this._conditions = saveConditions;
|
||||
|
||||
for (var i in this._conditions){
|
||||
var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
|
||||
+ "searchConditionID, condition, operator, value, required) "
|
||||
+ "VALUES (?,?,?,?,?,?)";
|
||||
|
||||
// Convert condition and mode to "condition[/mode]"
|
||||
var condition = this._conditions[i].mode ?
|
||||
this._conditions[i].condition + '/' + this._conditions[i].mode :
|
||||
this._conditions[i].condition
|
||||
|
||||
var sqlParams = [
|
||||
searchID,
|
||||
i,
|
||||
condition,
|
||||
this._conditions[i].operator ? this._conditions[i].operator : null,
|
||||
this._conditions[i].value ? this._conditions[i].value : null,
|
||||
this._conditions[i].required ? 1 : null
|
||||
];
|
||||
yield Zotero.DB.queryAsync(sql, sqlParams);
|
||||
}
|
||||
});
|
||||
|
||||
Zotero.Search.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
|
||||
var isNew = env.isNew;
|
||||
if (isNew) {
|
||||
Zotero.Notifier.trigger('add', 'search', this.id);
|
||||
}
|
||||
else {
|
||||
Zotero.Notifier.trigger('modify', 'search', this.id, this._previousData);
|
||||
}
|
||||
|
||||
if (isNew && Zotero.Libraries.isGroupLibrary(this.libraryID)) {
|
||||
var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
|
||||
var group = yield Zotero.Groups.get(groupID);
|
||||
group.clearSearchCache();
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
var id = this.id;
|
||||
this._disabled = true;
|
||||
return id;
|
||||
}
|
||||
|
||||
yield this.reload();
|
||||
this._clearChanged();
|
||||
|
||||
return isNew ? this.id : true;
|
||||
});
|
||||
|
||||
|
||||
|
@ -1189,7 +1161,7 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
|||
let objLibraryID;
|
||||
let objKey = condition.value;
|
||||
let objectType = condition.name == 'collection' ? 'collection' : 'search';
|
||||
let objectTypeClass = Zotero.DataObjectUtilities.getClassForObjectType(objectType);
|
||||
let objectTypeClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
|
||||
|
||||
// Old-style library-key hash
|
||||
if (objKey.contains('_')) {
|
||||
|
@ -1665,29 +1637,25 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
|||
this._sqlParams = sqlParams.length ? sqlParams : false;
|
||||
});
|
||||
|
||||
Zotero.Searches = new function(){
|
||||
Zotero.DataObjects.apply(this, ['search', 'searches', 'savedSearch', 'savedSearches']);
|
||||
this.constructor.prototype = new Zotero.DataObjects();
|
||||
Zotero.Searches = function() {
|
||||
this.constructor = null;
|
||||
|
||||
Object.defineProperty(this, "_primaryDataSQLParts", {
|
||||
get: function () {
|
||||
return _primaryDataSQLParts ? _primaryDataSQLParts : (_primaryDataSQLParts = {
|
||||
savedSearchID: "O.savedSearchID",
|
||||
name: "O.savedSearchName",
|
||||
libraryID: "O.libraryID",
|
||||
key: "O.key",
|
||||
version: "O.version",
|
||||
synced: "O.synced"
|
||||
});
|
||||
}
|
||||
});
|
||||
this._ZDO_object = 'search';
|
||||
this._ZDO_id = 'savedSearch';
|
||||
this._ZDO_table = 'savedSearches';
|
||||
|
||||
|
||||
var _primaryDataSQLParts;
|
||||
this._primaryDataSQLParts = {
|
||||
savedSearchID: "O.savedSearchID",
|
||||
name: "O.savedSearchName",
|
||||
libraryID: "O.libraryID",
|
||||
key: "O.key",
|
||||
version: "O.version",
|
||||
synced: "O.synced"
|
||||
}
|
||||
|
||||
|
||||
this.init = Zotero.Promise.coroutine(function* () {
|
||||
yield this.constructor.prototype.init.apply(this);
|
||||
yield Zotero.DataObjects.prototype.init.apply(this);
|
||||
yield Zotero.SearchConditions.init();
|
||||
});
|
||||
|
||||
|
@ -1735,6 +1703,8 @@ Zotero.Searches = new function(){
|
|||
let id = ids[i];
|
||||
var search = new Zotero.Search;
|
||||
search.id = id;
|
||||
yield search.loadPrimaryData();
|
||||
yield search.loadConditions();
|
||||
notifierData[id] = { old: search.serialize() };
|
||||
|
||||
var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
|
||||
|
@ -1756,7 +1726,11 @@ Zotero.Searches = new function(){
|
|||
+ Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " "
|
||||
+ "FROM savedSearches O WHERE 1";
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.DataObjects.call(this);
|
||||
|
||||
return this;
|
||||
}.bind(Object.create(Zotero.DataObjects.prototype))();
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -493,24 +493,6 @@ Zotero.Utilities.Internal = {
|
|||
}, 0, 0, null);
|
||||
|
||||
return pipe.inputStream;
|
||||
},
|
||||
|
||||
/**
|
||||
* Defines property on the object
|
||||
* More compact way to do Object.defineProperty
|
||||
*
|
||||
* @param {Object} obj Target object
|
||||
* @param {String} prop Property to be defined
|
||||
* @param {Object} desc Propery descriptor. If not overriden, "enumerable" is true
|
||||
*/
|
||||
"defineProperty": function(obj, prop, desc) {
|
||||
if (typeof prop != 'string') throw new Error("Property must be a string");
|
||||
var d = { __proto__: null, enumerable: true }; // Enumerable by default
|
||||
for (let p in desc) {
|
||||
if (!desc.hasOwnProperty(p)) continue;
|
||||
d[p] = desc[p];
|
||||
}
|
||||
Object.defineProperty(obj, prop, d);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1400,6 +1400,40 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines property on the object
|
||||
* More compact way to do Object.defineProperty
|
||||
*
|
||||
* @param {Object} obj Target object
|
||||
* @param {String} prop Property to be defined
|
||||
* @param {Object} desc Propery descriptor. If not overriden, "enumerable" is true
|
||||
* @param {Object} opts Options:
|
||||
* lazy {Boolean} If true, the _getter_ is intended for late
|
||||
* initialization of the property. The getter is replaced with a simple
|
||||
* property once initialized.
|
||||
*/
|
||||
this.defineProperty = function(obj, prop, desc, opts) {
|
||||
if (typeof prop != 'string') throw new Error("Property must be a string");
|
||||
var d = { __proto__: null, enumerable: true, configurable: true }; // Enumerable by default
|
||||
for (let p in desc) {
|
||||
if (!desc.hasOwnProperty(p)) continue;
|
||||
d[p] = desc[p];
|
||||
}
|
||||
|
||||
if (opts) {
|
||||
if (opts.lazy && d.get) {
|
||||
let getter = d.get;
|
||||
d.get = function() {
|
||||
var val = getter.call(this);
|
||||
this[prop] = val; // Replace getter with value
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(obj, prop, d);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function should be removed
|
||||
*
|
||||
|
@ -1497,6 +1531,12 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
};
|
||||
}
|
||||
|
||||
this.defineProperty(this, "localeCompare", {
|
||||
get: function() {
|
||||
var collation = this.getLocaleCollation();
|
||||
return collation.compareString.bind(collation, 1);
|
||||
}
|
||||
}, {lazy: true});
|
||||
|
||||
/*
|
||||
* Sets font size based on prefs -- intended for use on root element
|
||||
|
@ -1579,6 +1619,46 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines property on the object
|
||||
* More compact way to do Object.defineProperty
|
||||
*
|
||||
* @param {Object} obj Target object
|
||||
* @param {String} prop Property to be defined
|
||||
* @param {Object} desc Propery descriptor. If not overriden, "enumerable" is true
|
||||
* @param {Object} opts Options:
|
||||
* lateInit {Boolean} If true, the _getter_ is intended for late
|
||||
* initialization of the property. The getter is replaced with a simple
|
||||
* property once initialized.
|
||||
*/
|
||||
this.defineProperty = function(obj, prop, desc, opts) {
|
||||
if (typeof prop != 'string') throw new Error("Property must be a string");
|
||||
var d = { __proto__: null, enumerable: true, configurable: true }; // Enumerable by default
|
||||
for (let p in desc) {
|
||||
if (!desc.hasOwnProperty(p)) continue;
|
||||
d[p] = desc[p];
|
||||
}
|
||||
|
||||
if (opts) {
|
||||
if (opts.lateInit && d.get) {
|
||||
let getter = d.get;
|
||||
d.get = function() {
|
||||
var val = getter.call(this);
|
||||
this[prop] = val; // Replace getter with value
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(obj, prop, d);
|
||||
}
|
||||
|
||||
this.extendClass = function(superClass, newClass) {
|
||||
newClass._super = superClass;
|
||||
newClass.prototype = Object.create(superClass.prototype);
|
||||
newClass.prototype.constructor = newClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow other events (e.g., UI updates) on main thread to be processed if necessary
|
||||
*
|
||||
|
|
|
@ -1971,6 +1971,7 @@ var ZoteroPane = new function()
|
|||
}
|
||||
|
||||
var self = this;
|
||||
var deferred = Zotero.Promise.defer();
|
||||
this.collectionsView.addEventListener('load', function () {
|
||||
Zotero.spawn(function* () {
|
||||
var currentLibraryID = self.getSelectedLibraryID();
|
||||
|
@ -1993,15 +1994,22 @@ var ZoteroPane = new function()
|
|||
yield self.collectionsView.selectLibrary(item.libraryID);
|
||||
yield self.itemsView.selectItem(itemID, expand);
|
||||
}
|
||||
deferred.resolve(true);
|
||||
})
|
||||
.catch(function(e) {
|
||||
deferred.reject(e);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function(e) {
|
||||
deferred.reject(e);
|
||||
});
|
||||
});
|
||||
|
||||
// open Zotero pane
|
||||
this.show();
|
||||
|
||||
return true;
|
||||
return deferred.promise;
|
||||
});
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user