From 0e31e7ca017d5d5b5fe2e42932d21883b5814cff Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Sat, 1 Nov 2014 19:43:41 -0500 Subject: [PATCH 01/26] Wait for the item pane to initialize before focusing first field --- chrome/content/zotero/zoteroPane.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 01c6918c8..09c7c28e3 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -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; }); From aca15c0d2d61d6109dddcdd5935d70db3fb32b75 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Sat, 1 Nov 2014 00:23:00 -0500 Subject: [PATCH 02/26] Fix deleting saved searches --- chrome/content/zotero/xpcom/search.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js index 0166666ad..e38f921c4 100644 --- a/chrome/content/zotero/xpcom/search.js +++ b/chrome/content/zotero/xpcom/search.js @@ -1735,6 +1735,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=?"; From d4bd0ee81170c02c0adca39e28726330c50b47c5 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 02:06:22 -0600 Subject: [PATCH 03/26] Move defineProperty from ZU.Internal to Zotero --- chrome/content/zotero/xpcom/data/dataObject.js | 8 ++++---- chrome/content/zotero/xpcom/data/libraries.js | 2 +- .../content/zotero/xpcom/utilities_internal.js | 18 ------------------ chrome/content/zotero/xpcom/zotero.js | 18 ++++++++++++++++++ 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index a7a9b7ce5..8452e9e0a 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -55,17 +55,17 @@ Zotero.DataObject = function () { Zotero.DataObject.prototype._objectType = 'dataObject'; Zotero.DataObject.prototype._dataTypes = []; -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, '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) }); diff --git a/chrome/content/zotero/xpcom/data/libraries.js b/chrome/content/zotero/xpcom/data/libraries.js index 8234f2083..828e35af7 100644 --- a/chrome/content/zotero/xpcom/data/libraries.js +++ b/chrome/content/zotero/xpcom/data/libraries.js @@ -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"); diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index f88c08f75..bbe1767c0 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -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); } } diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index cdd4bd080..0db278232 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -1400,6 +1400,24 @@ 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 + */ + this.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); + } + /* * This function should be removed * From 29e9946123fa46a8d7e9b3a74605e0d5431b8b1d Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Sun, 2 Nov 2014 13:47:52 -0600 Subject: [PATCH 04/26] Allow to define properties for late initialization via defineProperty --- chrome/content/zotero/xpcom/zotero.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 0db278232..f9be95d1e 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -1407,14 +1407,30 @@ Components.utils.import("resource://gre/modules/osfile.jsm"); * @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) { + 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 }; // 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); } From f7d5ebc3573cb19b41b87731dd522c612cfcbe8c Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Thu, 6 Nov 2014 22:02:13 -0600 Subject: [PATCH 05/26] Default to "configurable: true" in defineProperty --- chrome/content/zotero/xpcom/zotero.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index f9be95d1e..535eb2523 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -1414,7 +1414,7 @@ Components.utils.import("resource://gre/modules/osfile.jsm"); */ 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 }; // Enumerable by default + 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]; From c222a3248a94827a9c18d4c55a490acf66ba4ebc Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 02:29:54 -0600 Subject: [PATCH 06/26] Transition __defineGetter/Setter__ to Zotero.defineProperty --- .../content/zotero/xpcom/data/collection.js | 52 +- chrome/content/zotero/xpcom/data/item.js | 685 +++++++++--------- chrome/content/zotero/xpcom/search.js | 42 +- 3 files changed, 418 insertions(+), 361 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js index dc75fe18f..db205fb4b 100644 --- a/chrome/content/zotero/xpcom/data/collection.js +++ b/chrome/content/zotero/xpcom/data/collection.js @@ -48,27 +48,39 @@ Zotero.Collection.prototype._dataTypes = Zotero.Collection._super.prototype._dat '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, 'id', { + get: function() this._get('id'), + set: function(val) this._set('id', val) }); -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, '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) { diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 04b89a3b7..fecc926fb 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -106,35 +106,62 @@ Zotero.Item.prototype._dataTypes = Zotero.Item._super.prototype._dataTypes.conca 'relations' ]); -Zotero.Item.prototype.__defineGetter__('id', function () this._id); -Zotero.Item.prototype.__defineGetter__('itemID', function () { - Zotero.debug("Item.itemID is deprecated -- use Item.id"); - return this._id; +Zotero.defineProperty(Zotero.Item.prototype, 'id', { + get: function() this._id, + set: function(val) this.setField('id', val) +}); +Zotero.defineProperty(Zotero.Item.prototype, 'itemID', { + get: function() { + Zotero.debug("Item.itemID is deprecated -- use Item.id"); + return this._id; + } +}); +Zotero.defineProperty(Zotero.Item.prototype, 'libraryID', { + get: function() this._libraryID, + set: function(val) this.setField('libraryID', val) +}); +Zotero.defineProperty(Zotero.Item.prototype, 'key', { + get: function() this._key, + set: function(val) this.setField('key', val) +}); +Zotero.defineProperty(Zotero.Item.prototype, 'itemTypeID', { + get: function() this._itemTypeID +}); +Zotero.defineProperty(Zotero.Item.prototype, 'dateAdded', { + get: function() this._dateAdded +}); +Zotero.defineProperty(Zotero.Item.prototype, 'dateModified', { + get: function() this._dateModified +}); +Zotero.defineProperty(Zotero.Item.prototype, 'version', { + get: function() this._itemVersion, + set: function(val) this.setField('version', val) +}); +Zotero.defineProperty(Zotero.Item.prototype, 'synced', { + get: function() this._synced, + set: function(val) this.setField('synced', val) }); -Zotero.Item.prototype.__defineSetter__('id', function (val) { this.setField('id', val); }); -Zotero.Item.prototype.__defineGetter__('libraryID', function () this._libraryID ); -Zotero.Item.prototype.__defineSetter__('libraryID', function (val) { this.setField('libraryID', val); }); -Zotero.Item.prototype.__defineGetter__('key', function () this._key ); -Zotero.Item.prototype.__defineSetter__('key', function (val) { this.setField('key', val) }); -Zotero.Item.prototype.__defineGetter__('itemTypeID', function () this._itemTypeID); -Zotero.Item.prototype.__defineGetter__('dateAdded', function () this._dateAdded ); -Zotero.Item.prototype.__defineGetter__('dateModified', function () this._dateModified ); -Zotero.Item.prototype.__defineGetter__('version', function () this._itemVersion ); -Zotero.Item.prototype.__defineSetter__('version', function (val) { return this.setField('itemVersion', val); }); -Zotero.Item.prototype.__defineGetter__('synced', function () this._synced ); -Zotero.Item.prototype.__defineSetter__('synced', function (val) { return this.setField('synced', val); }); // .parentKey and .parentID defined in dataObject.js, but create aliases -Zotero.Item.prototype.__defineGetter__('parentItemKey', function () this._parentKey ); -Zotero.Item.prototype.__defineSetter__('parentItemKey', function (val) this._setParentKey(val) ); -Zotero.Item.prototype.__defineGetter__('parentItemID', function () this._getParentID() ); -Zotero.Item.prototype.__defineSetter__('parentItemID', function (val) this._setParentID(val) ); +Zotero.defineProperty(Zotero.Item.prototype, 'parentItemID', { + get: function() this.parentID, + set: function(val) this.parentID = val +}); +Zotero.defineProperty(Zotero.Item.prototype, 'parentItemKey', { + get: function() this.parentKey, + set: function(val) this.parentKey = val +}); -Zotero.Item.prototype.__defineGetter__('firstCreator', function () this._firstCreator ); -Zotero.Item.prototype.__defineGetter__('sortCreator', function () this._sortCreator ); - -Zotero.Item.prototype.__defineGetter__('relatedItems', function () { return this._getRelatedItems(true); }); -Zotero.Item.prototype.__defineSetter__('relatedItems', function (arr) { this._setRelatedItems(arr); }); +Zotero.defineProperty(Zotero.Item.prototype, 'firstCreator', { + get: function() this._firstCreator +}); +Zotero.defineProperty(Zotero.Item.prototype, 'sortCreator', { + get: function() this._sortCreator +}); +Zotero.defineProperty(Zotero.Item.prototype, 'relatedItems', { + get: function() this._getRelatedItems(true), + set: function(arr) this._setRelatedItems(arr) +}); Zotero.Item.prototype.getID = function() { Zotero.debug('Item.getID() is deprecated -- use Item.id'); @@ -1059,28 +1086,27 @@ Zotero.Item.prototype.removeCreator = function(orderIndex, allowMissing) { return true; } - -Zotero.Item.prototype.__defineGetter__('deleted', function () { - if (!this.id) { - return false; +Zotero.defineProperty(Zotero.Item.prototype, 'deleted', { + get: function() { + if (!this.id) { + return false; + } + if (this._deleted !== null) { + return this._deleted; + } + this._requireData('primaryData'); + }, + set: function(val) { + var deleted = !!val; + + if (this._deleted == deleted) { + Zotero.debug("Deleted state hasn't changed for item " + this.id); + return; + } + this._markFieldChange('deleted', !!this._deleted); + this._changed.deleted = true; + this._deleted = deleted; } - if (this._deleted !== null) { - return this._deleted; - } - this._requireData('primaryData'); -}); - - -Zotero.Item.prototype.__defineSetter__('deleted', function (val) { - var deleted = !!val; - - if (this._deleted == deleted) { - Zotero.debug("Deleted state hasn't changed for item " + this.id); - return; - } - this._markFieldChange('deleted', !!this._deleted); - this._changed.deleted = true; - this._deleted = deleted; }); @@ -2688,39 +2714,39 @@ Zotero.Item.prototype.getAttachmentLinkMode = function() { * Possible values specified as constants in Zotero.Attachments * (e.g. Zotero.Attachments.LINK_MODE_LINKED_FILE) */ -Zotero.Item.prototype.__defineGetter__('attachmentLinkMode', function () { - if (!this.isAttachment()) { - return undefined; +Zotero.defineProperty(Zotero.Item.prototype, 'attachmentLinkMode', { + get: function() { + if (!this.isAttachment()) { + return undefined; + } + return this._attachmentLinkMode; + }, + set: function(val) { + if (!this.isAttachment()) { + throw (".attachmentLinkMode can only be set for attachment items"); + } + + switch (val) { + case Zotero.Attachments.LINK_MODE_IMPORTED_FILE: + case Zotero.Attachments.LINK_MODE_IMPORTED_URL: + case Zotero.Attachments.LINK_MODE_LINKED_FILE: + case Zotero.Attachments.LINK_MODE_LINKED_URL: + break; + + default: + throw ("Invalid attachment link mode '" + val + + "' in Zotero.Item.attachmentLinkMode setter"); + } + + if (val === this.attachmentLinkMode) { + return; + } + if (!this._changed.attachmentData) { + this._changed.attachmentData = {}; + } + this._changed.attachmentData.linkMode = true; + this._attachmentLinkMode = val; } - return this._attachmentLinkMode; -}); - - -Zotero.Item.prototype.__defineSetter__('attachmentLinkMode', function (val) { - if (!this.isAttachment()) { - throw (".attachmentLinkMode can only be set for attachment items"); - } - - switch (val) { - case Zotero.Attachments.LINK_MODE_IMPORTED_FILE: - case Zotero.Attachments.LINK_MODE_IMPORTED_URL: - case Zotero.Attachments.LINK_MODE_LINKED_FILE: - case Zotero.Attachments.LINK_MODE_LINKED_URL: - break; - - default: - throw ("Invalid attachment link mode '" + val - + "' in Zotero.Item.attachmentLinkMode setter"); - } - - if (val === this.attachmentLinkMode) { - return; - } - if (!this._changed.attachmentData) { - this._changed.attachmentData = {}; - } - this._changed.attachmentData.linkMode = true; - this._attachmentLinkMode = val; }); @@ -2729,40 +2755,42 @@ Zotero.Item.prototype.getAttachmentMIMEType = function() { return this.attachmentContentType; }; -Zotero.Item.prototype.__defineGetter__('attachmentMIMEType', function () { - Zotero.debug(".attachmentMIMEType deprecated -- use .attachmentContentType"); - return this.attachmentContentType; +Zotero.defineProperty(Zotero.Item.prototype, 'attachmentMIMEType', { + get: function() { + Zotero.debug(".attachmentMIMEType deprecated -- use .attachmentContentType"); + return this.attachmentContentType; + } }); /** * Content type of an attachment (e.g. 'text/plain') */ -Zotero.Item.prototype.__defineGetter__('attachmentContentType', function () { - if (!this.isAttachment()) { - return undefined; +Zotero.defineProperty(Zotero.Item.prototype, 'attachmentContentType', { + get: function() { + if (!this.isAttachment()) { + return undefined; + } + return this._attachmentContentType; + }, + set: function(val) { + if (!this.isAttachment()) { + throw (".attachmentContentType can only be set for attachment items"); + } + + if (!val) { + val = ''; + } + + if (val == this.attachmentContentType) { + return; + } + + if (!this._changed.attachmentData) { + this._changed.attachmentData = {}; + } + this._changed.attachmentData.contentType = true; + this._attachmentContentType = val; } - return this._attachmentContentType; -}); - - -Zotero.Item.prototype.__defineSetter__('attachmentContentType', function (val) { - if (!this.isAttachment()) { - throw (".attachmentContentType can only be set for attachment items"); - } - - if (!val) { - val = ''; - } - - if (val == this.attachmentContentType) { - return; - } - - if (!this._changed.attachmentData) { - this._changed.attachmentData = {}; - } - this._changed.attachmentData.contentType = true; - this._attachmentContentType = val; }); @@ -2775,76 +2803,75 @@ Zotero.Item.prototype.getAttachmentCharset = function() { /** * Character set of an attachment */ -Zotero.Item.prototype.__defineGetter__('attachmentCharset', function () { - if (!this.isAttachment()) { - return undefined; +Zotero.defineProperty(Zotero.Item.prototype, 'attachmentCharset', { + get: function() { + if (!this.isAttachment()) { + return undefined; + } + return this._attachmentCharset + }, + set: function(val) { + if (!this.isAttachment()) { + throw (".attachmentCharset can only be set for attachment items"); + } + + var oldVal = this.attachmentCharset; + if (oldVal) { + oldVal = Zotero.CharacterSets.getID(oldVal); + } + if (!oldVal) { + oldVal = null; + } + + if (val) { + val = Zotero.CharacterSets.getID(val); + } + if (!val) { + val = null; + } + + if (val == oldVal) { + return; + } + + if (!this._changed.attachmentData) { + this._changed.attachmentData= {}; + } + this._changed.attachmentData.charset = true; + this._attachmentCharset = val; } - return this._attachmentCharset }); - -Zotero.Item.prototype.__defineSetter__('attachmentCharset', function (val) { - if (!this.isAttachment()) { - throw (".attachmentCharset can only be set for attachment items"); +Zotero.defineProperty(Zotero.Item.prototype, 'attachmentPath', { + get: function() { + if (!this.isAttachment()) { + return undefined; + } + return this._attachmentPath; + }, + set: function(val) { + if (!this.isAttachment()) { + throw (".attachmentPath can only be set for attachment items"); + } + + if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) { + throw ('attachmentPath cannot be set for link attachments'); + } + + if (!val) { + val = ''; + } + + if (val == this.attachmentPath) { + return; + } + + if (!this._changed.attachmentData) { + this._changed.attachmentData = {}; + } + this._changed.attachmentData.path = true; + this._attachmentPath = val; } - - var oldVal = this.attachmentCharset; - if (oldVal) { - oldVal = Zotero.CharacterSets.getID(oldVal); - } - if (!oldVal) { - oldVal = null; - } - - if (val) { - val = Zotero.CharacterSets.getID(val); - } - if (!val) { - val = null; - } - - if (val == oldVal) { - return; - } - - if (!this._changed.attachmentData) { - this._changed.attachmentData= {}; - } - this._changed.attachmentData.charset = true; - this._attachmentCharset = val; -}); - - -Zotero.Item.prototype.__defineGetter__('attachmentPath', function () { - if (!this.isAttachment()) { - return undefined; - } - return this._attachmentPath; -}); - - -Zotero.Item.prototype.__defineSetter__('attachmentPath', function (val) { - if (!this.isAttachment()) { - throw (".attachmentPath can only be set for attachment items"); - } - - if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) { - throw ('attachmentPath cannot be set for link attachments'); - } - - if (!val) { - val = ''; - } - - if (val == this.attachmentPath) { - return; - } - - if (!this._changed.attachmentData) { - this._changed.attachmentData = {}; - } - this._changed.attachmentData.path = true; - this._attachmentPath = val; }); @@ -2865,51 +2892,51 @@ Zotero.Item.prototype.updateAttachmentPath = function () { }; -Zotero.Item.prototype.__defineGetter__('attachmentSyncState', function () { - if (!this.isAttachment()) { - return undefined; +Zotero.defineProperty(Zotero.Item.prototype, 'attachmentSyncState', { + get: function() { + if (!this.isAttachment()) { + return undefined; + } + return this._attachmentSyncState; + }, + set: function(val) { + if (!this.isAttachment()) { + throw ("attachmentSyncState can only be set for attachment items"); + } + + switch (this.attachmentLinkMode) { + case Zotero.Attachments.LINK_MODE_IMPORTED_URL: + case Zotero.Attachments.LINK_MODE_IMPORTED_FILE: + break; + + default: + throw ("attachmentSyncState can only be set for snapshots and " + + "imported files"); + } + + switch (val) { + case Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD: + case Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD: + case Zotero.Sync.Storage.SYNC_STATE_IN_SYNC: + case Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD: + case Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD: + break; + + default: + throw ("Invalid sync state '" + val + + "' in Zotero.Item.attachmentSyncState setter"); + } + + if (val == this.attachmentSyncState) { + return; + } + + if (!this._changed.attachmentData) { + this._changed.attachmentData = {}; + } + this._changed.attachmentData.syncState = true; + this._attachmentSyncState = val; } - return this._attachmentSyncState; -}); - - -Zotero.Item.prototype.__defineSetter__('attachmentSyncState', function (val) { - if (!this.isAttachment()) { - throw ("attachmentSyncState can only be set for attachment items"); - } - - switch (this.attachmentLinkMode) { - case Zotero.Attachments.LINK_MODE_IMPORTED_URL: - case Zotero.Attachments.LINK_MODE_IMPORTED_FILE: - break; - - default: - throw ("attachmentSyncState can only be set for snapshots and " - + "imported files"); - } - - switch (val) { - case Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD: - case Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD: - case Zotero.Sync.Storage.SYNC_STATE_IN_SYNC: - case Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD: - case Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD: - break; - - default: - throw ("Invalid sync state '" + val - + "' in Zotero.Item.attachmentSyncState setter"); - } - - if (val == this.attachmentSyncState) { - return; - } - - if (!this._changed.attachmentData) { - this._changed.attachmentData = {}; - } - this._changed.attachmentData.syncState = true; - this._attachmentSyncState = val; }); @@ -2922,29 +2949,31 @@ Zotero.Item.prototype.__defineSetter__('attachmentSyncState', function (val) { * @return {Promise} File modification time as timestamp in milliseconds, * or undefined if no file */ -Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', Zotero.Promise.coroutine(function* () { - if (!this.isAttachment()) { - return undefined; - } - - if (!this.id) { - return undefined; - } - - var path = yield this.getFilePathAsync(); - if (!path) { - return undefined; - } - - var fmtime = OS.File.stat(path).lastModificationDate; - - if (fmtime < 1) { - Zotero.debug("File mod time " + fmtime + " is less than 1 -- interpreting as 1", 2); - fmtime = 1; - } - - return fmtime; -})); +Zotero.defineProperty(Zotero.Item.prototype, 'attachmentModificationTime', { + get: Zotero.Promise.coroutine(function* () { + if (!this.isAttachment()) { + return undefined; + } + + if (!this.id) { + return undefined; + } + + var path = yield this.getFilePathAsync(); + if (!path) { + return undefined; + } + + var fmtime = OS.File.stat(path).lastModificationDate; + + if (fmtime < 1) { + Zotero.debug("File mod time " + fmtime + " is less than 1 -- interpreting as 1", 2); + fmtime = 1; + } + + return fmtime; + }) +}); /** @@ -2955,21 +2984,23 @@ Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', Zotero.Prom * * @return {String} MD5 hash of file as hex string */ -Zotero.Item.prototype.__defineGetter__('attachmentHash', function () { - if (!this.isAttachment()) { - return undefined; +Zotero.defineProperty(Zotero.Item.prototype, 'attachmentHash', { + get: function () { + if (!this.isAttachment()) { + return undefined; + } + + if (!this.id) { + return undefined; + } + + var file = this.getFile(); + if (!file) { + return undefined; + } + + return Zotero.Utilities.Internal.md5(file) || undefined; } - - if (!this.id) { - return undefined; - } - - var file = this.getFile(); - if (!file) { - return undefined; - } - - return Zotero.Utilities.Internal.md5(file) || undefined; }); @@ -2982,84 +3013,86 @@ Zotero.Item.prototype.__defineGetter__('attachmentHash', function () { * * @return {Promise} - A promise for attachment text or empty string if unavailable */ -Zotero.Item.prototype.__defineGetter__('attachmentText', Zotero.Promise.coroutine(function* () { - if (!this.isAttachment()) { - return undefined; - } - - if (!this.id) { - return null; - } - - var file = this.getFile(); - - if (!(yield OS.File.exists(file.path))) { - file = false; - } - - var cacheFile = Zotero.Fulltext.getItemCacheFile(this); - if (!file) { - if (cacheFile.exists()) { - var str = yield Zotero.File.getContentsAsync(cacheFile); - - return str.trim(); - } - return ''; - } - - var contentType = this.attachmentContentType; - if (!contentType) { - contentType = yield Zotero.MIME.getMIMETypeFromFile(file); - if (contentType) { - this.attachmentContentType = contentType; - yield this.save(); - } - } - - var str; - if (Zotero.Fulltext.isCachedMIMEType(contentType)) { - var reindex = false; - - if (!cacheFile.exists()) { - Zotero.debug("Regenerating item " + this.id + " full-text cache file"); - reindex = true; - } - // Fully index item if it's not yet - else if (!(yield Zotero.Fulltext.isFullyIndexed(this))) { - Zotero.debug("Item " + this.id + " is not fully indexed -- caching now"); - reindex = true; +Zotero.defineProperty(Zotero.Item.prototype, 'attachmentText', { + get: Zotero.Promise.coroutine(function* () { + if (!this.isAttachment()) { + return undefined; } - if (reindex) { - if (!Zotero.Fulltext.pdfConverterIsRegistered()) { - Zotero.debug("PDF converter is unavailable -- returning empty .attachmentText", 3); - return ''; + if (!this.id) { + return null; + } + + var file = this.getFile(); + + if (!(yield OS.File.exists(file.path))) { + file = false; + } + + var cacheFile = Zotero.Fulltext.getItemCacheFile(this); + if (!file) { + if (cacheFile.exists()) { + var str = yield Zotero.File.getContentsAsync(cacheFile); + + return str.trim(); } - yield Zotero.Fulltext.indexItems(this.id, false); - } - - if (!cacheFile.exists()) { - Zotero.debug("Cache file doesn't exist after indexing -- returning empty .attachmentText"); return ''; } - str = yield Zotero.File.getContentsAsync(cacheFile); - } - - else if (contentType == 'text/html') { - str = yield Zotero.File.getContentsAsync(file); - str = Zotero.Utilities.unescapeHTML(str); - } - - else if (contentType == 'text/plain') { - str = yield Zotero.File.getContentsAsync(file); - } - - else { - return ''; - } - - return str.trim(); -})); + + var contentType = this.attachmentContentType; + if (!contentType) { + contentType = yield Zotero.MIME.getMIMETypeFromFile(file); + if (contentType) { + this.attachmentContentType = contentType; + yield this.save(); + } + } + + var str; + if (Zotero.Fulltext.isCachedMIMEType(contentType)) { + var reindex = false; + + if (!cacheFile.exists()) { + Zotero.debug("Regenerating item " + this.id + " full-text cache file"); + reindex = true; + } + // Fully index item if it's not yet + else if (!(yield Zotero.Fulltext.isFullyIndexed(this))) { + Zotero.debug("Item " + this.id + " is not fully indexed -- caching now"); + reindex = true; + } + + if (reindex) { + if (!Zotero.Fulltext.pdfConverterIsRegistered()) { + Zotero.debug("PDF converter is unavailable -- returning empty .attachmentText", 3); + return ''; + } + yield Zotero.Fulltext.indexItems(this.id, false); + } + + if (!cacheFile.exists()) { + Zotero.debug("Cache file doesn't exist after indexing -- returning empty .attachmentText"); + return ''; + } + str = yield Zotero.File.getContentsAsync(cacheFile); + } + + else if (contentType == 'text/html') { + str = yield Zotero.File.getContentsAsync(file); + str = Zotero.Utilities.unescapeHTML(str); + } + + else if (contentType == 'text/plain') { + str = yield Zotero.File.getContentsAsync(file); + } + + else { + return ''; + } + + return str.trim(); + }) +}); diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js index e38f921c4..4a81d05c8 100644 --- a/chrome/content/zotero/xpcom/search.js +++ b/chrome/content/zotero/xpcom/search.js @@ -62,21 +62,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') { From 4657c46d4b46d8224bd9837734a7bce993539dd1 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 02:34:15 -0600 Subject: [PATCH 07/26] getClassForObjectType => getObjectsClassForObjectType --- chrome/content/zotero/xpcom/api.js | 2 +- chrome/content/zotero/xpcom/data/dataObject.js | 15 ++++++--------- .../zotero/xpcom/data/dataObjectUtilities.js | 4 ++-- chrome/content/zotero/xpcom/search.js | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/chrome/content/zotero/xpcom/api.js b/chrome/content/zotero/xpcom/api.js index 2a38a569b..a732c7800 100644 --- a/chrome/content/zotero/xpcom/api.js +++ b/chrome/content/zotero/xpcom/api.js @@ -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); } }; diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 8452e9e0a..092f55461 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -70,6 +70,10 @@ Zotero.defineProperty(Zotero.DataObject.prototype, 'parentID', { set: function(v) this._setParentID(v) }); +Zotero.defineProperty(Zotero.DataObject.prototype, 'ObjectsClass', { + get: function() Zotero.DataObjectUtilities.getObjectsClassForObjectType(this.objectType) +}); + Zotero.DataObject.prototype._get = function (field) { if (this['_' + field] !== null) { @@ -135,7 +139,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 +152,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 ); } @@ -368,13 +372,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 diff --git a/chrome/content/zotero/xpcom/data/dataObjectUtilities.js b/chrome/content/zotero/xpcom/data/dataObjectUtilities.js index 854419f43..fa70c0b9c 100644 --- a/chrome/content/zotero/xpcom/data/dataObjectUtilities.js +++ b/chrome/content/zotero/xpcom/data/dataObjectUtilities.js @@ -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] diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js index 4a81d05c8..ad6073260 100644 --- a/chrome/content/zotero/xpcom/search.js +++ b/chrome/content/zotero/xpcom/search.js @@ -1201,7 +1201,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('_')) { From 56f244a4bb96d4d9a31b642755d494d8ce4a60a7 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 02:38:31 -0600 Subject: [PATCH 08/26] Add id and libraryID getters for all data objects --- chrome/content/zotero/xpcom/data/dataObject.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 092f55461..d7d928548 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -58,6 +58,12 @@ Zotero.DataObject.prototype._dataTypes = []; Zotero.defineProperty(Zotero.DataObject.prototype, 'objectType', { get: function() this._objectType }); +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, 'libraryKey', { get: function() this._libraryID + "/" + this._key }); From e02945b591b17ab9705771c3817c68e762e1b6ac Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 02:46:31 -0600 Subject: [PATCH 09/26] Add a centralized, modular .save() method to DataObject .save calls ._initSave(), _saveData(), _finalizeSave() internally passing `env` object to each to act as an environment for passing around variables * _initSave should determine if the save is possible and return a promise for either `true` or `false`. It should also set up the environment, e.g. determine if this `isNew` * _saveData performs the actual saving to the database, but should not do any terminal steps in the save process so that any extending classes could extend this method to write additional data to the database * _finalizeSave should perform any finalization before the data is committed to the database. _recoverFromSaveError is called with `env` and an error that occurred. This method should perform any recovery steps, e.g. discarding the save and reloading the item from the database into the cache. --- .../content/zotero/xpcom/data/collection.js | 272 ++-- .../content/zotero/xpcom/data/dataObject.js | 92 ++ chrome/content/zotero/xpcom/data/item.js | 1154 ++++++++--------- chrome/content/zotero/xpcom/search.js | 240 ++-- 4 files changed, 872 insertions(+), 886 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js index db205fb4b..1ae5aa4b1 100644 --- a/chrome/content/zotero/xpcom/data/collection.js +++ b/chrome/content/zotero/xpcom/data/collection.js @@ -279,168 +279,136 @@ 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'); + } + + return Zotero.Collection._super.prototype._initSave.apply(this, arguments); +}); -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); + + // Verify parent + if (this._parentKey) { + let newParent = Zotero.Collections.getByLibraryAndKey( + this.libraryID, this._parentKey + ); - if (!this.name) { - throw new Error('Collection name is empty'); + if (!newParent) { + throw new Error("Cannot set parent to invalid collection " + this._parentKey); } - if (Zotero.Utilities.isEmpty(this._changed)) { - Zotero.debug("Collection " + this.id + " has not changed"); - return false; + if (newParent.id == this.id) { + throw new Error('Cannot move collection into itself!'); } - 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; + if (this.id && (yield this.hasDescendent('collection', newParent.id))) { + throw ('Cannot move collection "' + this.name + '" into one of its own descendents'); } - 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); + var parent = newParent.id; } - catch (e) { - try { - yield this.reload(); - this._clearChanged(); - } - catch (e2) { - Zotero.debug(e2, 1); - } - - Zotero.debug(e, 1); - throw e; + 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 = env.id = 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); + } + env.parentIDs = parentIDs; + } +}); + +Zotero.Collection.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) { + var isNew = env.isNew; + 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 (env.parentIDs) { + Zotero.Collections.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; }); diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index d7d928548..a563f7e67 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -425,6 +425,98 @@ 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} 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 = { + arguments: arguments, + 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; + + 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; +}); + /** * Generates data object key * @return {String} key diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index fecc926fb..f3c57bbe0 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -503,15 +503,6 @@ Zotero.Item.prototype.loadFromRow = function(row, reload) { } -/* - * Check if any data fields have changed since last save - */ -Zotero.Item.prototype.hasChanged = function() { - Zotero.debug(this._changed); - return !!Object.keys(this._changed).filter((dataType) => this._changed[dataType]).length -} - - /* * Set or change the item's type */ @@ -1173,609 +1164,572 @@ Zotero.Item.prototype.removeRelatedItem = Zotero.Promise.coroutine(function* (it }); -/** - * Save changes to database - * - * @return {Promise} Promise for itemID of new item, - * TRUE on item update, or FALSE if item was unchanged - */ -Zotero.Item.prototype.save = Zotero.Promise.coroutine(function* (options) { - try { - if (!options) { - options = {}; - } +Zotero.Item.prototype.isEditable = function() { + var editable = Zotero.Item._super.prototype.isEditable.apply(this); + if (!editable) return false; + + // Check if we're allowed to save attachments + if (this.isAttachment() + && (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL || + this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE) + && !Zotero.Libraries.isFilesEditable(this.libraryID) + ) { + return false; + } + + return true; +} + +Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { + var isNew = env.isNew; + var options = env.options; + + var itemTypeID = this.itemTypeID; - var isNew = !this.id; - - Zotero.Items.editCheck(this); - - if (!this.hasChanged()) { - Zotero.debug('Item ' + this.id + ' has not changed', 4); - return false; - } - - // Register this item's identifiers in Zotero.DataObjects on transaction commit, - // before other callbacks run - var itemID, libraryID, key; - if (isNew) { - var transactionOptions = { - onCommit: function () { - Zotero.Items.registerIdentifiers(itemID, libraryID, key); + var sqlColumns = []; + var sqlValues = []; + var reloadParentChildItems = {}; + + // + // Primary fields + // + // If available id value, use it -- otherwise we'll use autoincrement + var itemID = env.id = this._id = this.id ? this.id : yield Zotero.ID.get('items'); + Zotero.debug('='); + var libraryID = env.libraryID = this.libraryID; + var key = env.key = this._key = this.key ? this.key : this._generateKey(); + + sqlColumns.push( + 'itemTypeID', + 'dateAdded', + 'libraryID', + 'key', + 'version', + 'synced' + ); + + sqlValues.push( + { int: itemTypeID }, + this.dateAdded ? this.dateAdded : Zotero.DB.transactionDateTime, + this.libraryID ? this.libraryID : 0, + key, + this.version ? this.version : 0, + this.synced ? 1 : 0 + ); + + if (isNew) { + sqlColumns.push('dateModified', 'clientDateModified'); + sqlValues.push(Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime); + } + else { + for each (let field in ['dateModified', 'clientDateModified']) { + switch (field) { + case 'dateModified': + case 'clientDateModified': + let skipFlag = "skip" + field[0].toUpperCase() + field.substr(1) + "Update"; + if (!options[skipFlag]) { + sqlColumns.push(field); + sqlValues.push(Zotero.DB.transactionDateTime); } - }; + break; + } + } + } + + if (isNew) { + sqlColumns.unshift('itemID'); + sqlValues.unshift(parseInt(itemID)); + + var sql = "INSERT INTO items (" + sqlColumns.join(", ") + ") " + + "VALUES (" + sqlValues.map(function () "?").join() + ")"; + var insertID = yield Zotero.DB.queryAsync(sql, sqlValues); + if (!itemID) { + itemID = env.id = insertID; + } + + Zotero.Notifier.trigger('add', 'item', itemID); + } + else { + var sql = "UPDATE items SET " + sqlColumns.join("=?, ") + "=? WHERE itemID=?"; + sqlValues.push(parseInt(itemID)); + yield Zotero.DB.queryAsync(sql, sqlValues); + + var notifierData = {}; + notifierData[itemID] = { changed: this._previousData }; + Zotero.Notifier.trigger('modify', 'item', itemID, notifierData); + } + + // + // ItemData + // + if (this._changed.itemData) { + let del = []; + + let valueSQL = "SELECT valueID FROM itemDataValues WHERE value=?"; + let insertValueSQL = "INSERT INTO itemDataValues VALUES (?,?)"; + let replaceSQL = "REPLACE INTO itemData VALUES (?,?,?)"; + + for (let fieldID in this._changed.itemData) { + fieldID = parseInt(fieldID); + let value = this.getField(fieldID, true); + + // If field changed and is empty, mark row for deletion + if (!value) { + del.push(fieldID); + continue; + } + + if (Zotero.ItemFields.getID('accessDate') == fieldID + && (this.getField(fieldID)) == 'CURRENT_TIMESTAMP') { + value = Zotero.DB.transactionDateTime; + } + + let valueID = yield Zotero.DB.valueQueryAsync(valueSQL, [value], { debug: true }) + if (!valueID) { + valueID = yield Zotero.ID.get('itemDataValues'); + yield Zotero.DB.queryAsync(insertValueSQL, [valueID, value], { debug: false }); + } + + yield Zotero.DB.queryAsync(replaceSQL, [itemID, fieldID, valueID], { debug: false }); + } + + // Delete blank fields + if (del.length) { + sql = 'DELETE from itemData WHERE itemID=? AND ' + + 'fieldID IN (' + del.map(function () '?').join() + ')'; + yield Zotero.DB.queryAsync(sql, [itemID].concat(del)); + } + } + + // + // Creators + // + if (this._changed.creators) { + for (let orderIndex in this._changed.creators) { + orderIndex = parseInt(orderIndex); + + if (isNew) { + Zotero.debug('Adding creator in position ' + orderIndex, 4); + } + else { + Zotero.debug('Creator ' + orderIndex + ' has changed', 4); + } + + let creatorData = this.getCreator(orderIndex); + // If no creator in this position, just remove the item-creator association + if (!creatorData) { + let sql = "DELETE FROM itemCreators WHERE itemID=? AND orderIndex=?"; + yield Zotero.DB.queryAsync(sql, [itemID, orderIndex]); + Zotero.Prefs.set('purge.creators', true); + continue; + } + + let previousCreatorID = this._previousData.creators[orderIndex] + ? this._previousData.creators[orderIndex].id + : false; + let newCreatorID = yield Zotero.Creators.getIDFromData(creatorData, true); + + // If there was previously a creator at this position and it's different from + // the new one, the old one might need to be purged. + if (previousCreatorID && previousCreatorID != newCreatorID) { + Zotero.Prefs.set('purge.creators', true); + } + + let sql = "INSERT OR REPLACE INTO itemCreators " + + "(itemID, creatorID, creatorTypeID, orderIndex) VALUES (?, ?, ?, ?)"; + yield Zotero.DB.queryAsync( + sql, + [ + itemID, + newCreatorID, + creatorData.creatorTypeID, + orderIndex + ] + ); + } + } + + // Parent item + let parentItem = this.parentKey; + parentItem = parentItem ? Zotero.Items.getByLibraryAndKey(this.libraryID, parentItem) : null; + if (this._changed.parentKey) { + if (isNew) { + if (!parentItem) { + // TODO: clear caches? + let msg = this._parentKey + " is not a valid item key"; + throw new Zotero.Error(msg, "MISSING_OBJECT"); + } + + let newParentItemNotifierData = {}; + //newParentItemNotifierData[newParentItem.id] = {}; + Zotero.Notifier.trigger('modify', 'item', parentItem.id, newParentItemNotifierData); + + switch (Zotero.ItemTypes.getName(itemTypeID)) { + case 'note': + case 'attachment': + reloadParentChildItems[parentItem.id] = true; + break; + } } else { - var transactionOptions = null; + let type = Zotero.ItemTypes.getName(itemTypeID); + let Type = type[0].toUpperCase() + type.substr(1); + + if (this._parentKey) { + if (!parentItem) { + // TODO: clear caches + let msg = "Cannot set source to invalid item " + this._parentKey; + throw new Zotero.Error(msg, "MISSING_OBJECT"); + } + + let newParentItemNotifierData = {}; + //newParentItemNotifierData[newParentItem.id] = {}; + Zotero.Notifier.trigger('modify', 'item', parentItem.id, newParentItemNotifierData); + } + + var oldParentKey = this._previousData.parentKey; + if (oldParentKey) { + var oldParentItem = Zotero.Items.getByLibraryAndKey(this.libraryID, oldParentKey); + if (oldParentItem) { + let oldParentItemNotifierData = {}; + //oldParentItemNotifierData[oldParentItem.id] = {}; + Zotero.Notifier.trigger('modify', 'item', oldParentItem.id, oldParentItemNotifierData); + } + else { + Zotero.debug("Old source item " + oldParentKey + + " didn't exist in Zotero.Item.save()", 2); + } + } + + // If this was an independent item, remove from any collections + // where it existed previously and add parent instead + if (!oldParentKey) { + let sql = "SELECT collectionID FROM collectionItems WHERE itemID=?"; + let changedCollections = yield Zotero.DB.columnQueryAsync(sql, this.id); + if (changedCollections) { + for (let i=0; i wrapper if not present - if (!noteText.match(/^
[\s\S]*<\/div>$/)) { - // Keep consistent with getNote() - noteText = '
' + noteText + '
'; - } - - let params = [ - parent ? parent : null, - noteText, - this._noteTitle ? this._noteTitle : '' - ]; - let sql = "SELECT COUNT(*) FROM itemNotes WHERE itemID=?"; - if (yield Zotero.DB.valueQueryAsync(sql, itemID)) { - sql = "UPDATE itemNotes SET parentItemID=?, note=?, title=? WHERE itemID=?"; - params.push(itemID); - } - else { - sql = "INSERT INTO itemNotes " - + "(itemID, parentItemID, note, title) VALUES (?,?,?,?)"; - params.unshift(itemID); - } - yield Zotero.DB.queryAsync(sql, params); - - if (parentItem) { - reloadParentChildItems[parentItem.id] = true; - } - } - - // - // Attachment - // - if (!isNew) { - // If attachment title changes, update parent attachments - if (this._changed.itemData && this._changed.itemData[110] && this.isAttachment() && parentItem) { - reloadParentChildItems[parentItem.id] = true; - } - } - - if (this.isAttachment() || this._changed.attachmentData) { - let sql = "REPLACE INTO itemAttachments (itemID, parentItemID, linkMode, " - + "contentType, charsetID, path, syncState) VALUES (?,?,?,?,?,?,?)"; - let parent = this.parentID; - let linkMode = this.attachmentLinkMode; - let contentType = this.attachmentContentType; - let charsetID = Zotero.CharacterSets.getID(this.attachmentCharset); - let path = this.attachmentPath; - let syncState = this.attachmentSyncState; - - if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) { - // Save attachment within attachment base directory as relative path - if (Zotero.Prefs.get('saveRelativeAttachmentPath')) { - path = Zotero.Attachments.getBaseDirectoryRelativePath(path); - } - // If possible, convert relative path to absolute - else { - let file = Zotero.Attachments.resolveRelativePath(path); - if (file) { - path = file.persistentDescriptor; - } - } - } - - let params = [ - itemID, - parent ? parent : null, - { int: linkMode }, - contentType ? { string: contentType } : null, - charsetID ? { int: charsetID } : null, - path ? { string: path } : null, - syncState ? { int: syncState } : 0 - ]; - yield Zotero.DB.queryAsync(sql, params); - - // Clear cached child attachments of the parent - if (!isNew && parentItem) { - reloadParentChildItems[parentItem.id] = true; - } - } - - // Tags - if (this._changed.tags) { - let oldTags = this._previousData.tags; - let newTags = this._tags; - - // Convert to individual JSON objects, diff, and convert back - let oldTagsJSON = oldTags.map(function (x) JSON.stringify(x)); - let newTagsJSON = newTags.map(function (x) JSON.stringify(x)); - let toAdd = Zotero.Utilities.arrayDiff(newTagsJSON, oldTagsJSON) - .map(function (x) JSON.parse(x)); - let toRemove = Zotero.Utilities.arrayDiff(oldTagsJSON, newTagsJSON) - .map(function (x) JSON.parse(x));; - - for (let i=0; i wrapper if not present + if (!noteText.match(/^
[\s\S]*<\/div>$/)) { + // Keep consistent with getNote() + noteText = '
' + noteText + '
'; + } + + let params = [ + parent ? parent : null, + noteText, + this._noteTitle ? this._noteTitle : '' + ]; + let sql = "SELECT COUNT(*) FROM itemNotes WHERE itemID=?"; + if (yield Zotero.DB.valueQueryAsync(sql, itemID)) { + sql = "UPDATE itemNotes SET parentItemID=?, note=?, title=? WHERE itemID=?"; + params.push(itemID); + } + else { + sql = "INSERT INTO itemNotes " + + "(itemID, parentItemID, note, title) VALUES (?,?,?,?)"; + params.unshift(itemID); + } + yield Zotero.DB.queryAsync(sql, params); + + if (parentItem) { + reloadParentChildItems[parentItem.id] = true; + } + } + + // + // Attachment + // + if (!isNew) { + // If attachment title changes, update parent attachments + if (this._changed.itemData && this._changed.itemData[110] && this.isAttachment() && parentItem) { + reloadParentChildItems[parentItem.id] = true; + } + } + + if (this.isAttachment() || this._changed.attachmentData) { + let sql = "REPLACE INTO itemAttachments (itemID, parentItemID, linkMode, " + + "contentType, charsetID, path, syncState) VALUES (?,?,?,?,?,?,?)"; + let parent = this.parentID; + let linkMode = this.attachmentLinkMode; + let contentType = this.attachmentContentType; + let charsetID = Zotero.CharacterSets.getID(this.attachmentCharset); + let path = this.attachmentPath; + let syncState = this.attachmentSyncState; + + if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) { + // Save attachment within attachment base directory as relative path + if (Zotero.Prefs.get('saveRelativeAttachmentPath')) { + path = Zotero.Attachments.getBaseDirectoryRelativePath(path); + } + // If possible, convert relative path to absolute + else { + let file = Zotero.Attachments.resolveRelativePath(path); + if (file) { + path = file.persistentDescriptor; + } + } + } + + let params = [ + itemID, + parent ? parent : null, + { int: linkMode }, + contentType ? { string: contentType } : null, + charsetID ? { int: charsetID } : null, + path ? { string: path } : null, + syncState ? { int: syncState } : 0 + ]; + yield Zotero.DB.queryAsync(sql, params); + + // Clear cached child attachments of the parent + if (!isNew && parentItem) { + reloadParentChildItems[parentItem.id] = true; + } + } + + // Tags + if (this._changed.tags) { + let oldTags = this._previousData.tags; + let newTags = this._tags; + + // Convert to individual JSON objects, diff, and convert back + let oldTagsJSON = oldTags.map(function (x) JSON.stringify(x)); + let newTagsJSON = newTags.map(function (x) JSON.stringify(x)); + let toAdd = Zotero.Utilities.arrayDiff(newTagsJSON, oldTagsJSON) + .map(function (x) JSON.parse(x)); + let toRemove = Zotero.Utilities.arrayDiff(oldTagsJSON, newTagsJSON) + .map(function (x) JSON.parse(x));; + + for (let i=0; i Date: Sat, 1 Nov 2014 19:18:14 -0500 Subject: [PATCH 10/26] Pass fixGaps to Zotero.Search.save() in an options object --- chrome/content/zotero/bindings/zoterosearch.xml | 2 +- chrome/content/zotero/xpcom/data/dataObject.js | 1 - chrome/content/zotero/xpcom/search.js | 11 +---------- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/chrome/content/zotero/bindings/zoterosearch.xml b/chrome/content/zotero/bindings/zoterosearch.xml index f93b8076c..f84f724c1 100644 --- a/chrome/content/zotero/bindings/zoterosearch.xml +++ b/chrome/content/zotero/bindings/zoterosearch.xml @@ -232,7 +232,7 @@ diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index a563f7e67..271b03b14 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -445,7 +445,6 @@ Zotero.DataObject.prototype.editCheck = function () { */ Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options) { var env = { - arguments: arguments, transactionOptions: null, options: options || {} }; diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js index f5516bf0f..c964e8c6c 100644 --- a/chrome/content/zotero/xpcom/search.js +++ b/chrome/content/zotero/xpcom/search.js @@ -181,17 +181,8 @@ Zotero.Search.prototype._initSave = Zotero.Promise.coroutine(function* (env) { 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._saveData = Zotero.Promise.coroutine(function* (env) { - var fixGaps = env.arguments[0]; + var fixGaps = env.options.fixGaps; var isNew = env.isNew; var searchID = env.id = this._id = this.id ? this.id : yield Zotero.ID.get('savedSearches'); From 9700cdde387b28678e3e9cf085b5fa44ce2da4ac Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Sun, 2 Nov 2014 13:48:45 -0600 Subject: [PATCH 11/26] Add Zotero.extendClass --- chrome/content/zotero/xpcom/zotero.js | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 535eb2523..749bf3dd0 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -1613,6 +1613,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 * From a56e800d7cf0a2c8487a425455a6f13697f97a7d Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 02:54:53 -0600 Subject: [PATCH 12/26] Add Zotero.localeCompare Can be used directly in Array.sort() as the sorting function. Uses LocaleCollateion.compareString internally --- chrome/content/zotero/xpcom/zotero.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 749bf3dd0..3501acb3b 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -1531,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 From 9f535e0e91e7707719cbe64ebf03dd147dfe6f85 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 03:02:27 -0600 Subject: [PATCH 13/26] Use Zotero.extendClass for inheritance --- chrome/content/zotero/xpcom/data/item.js | 4 +--- chrome/content/zotero/xpcom/search.js | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index f3c57bbe0..2a1e2fdc5 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -89,9 +89,7 @@ Zotero.Item = function(itemTypeOrID) { } } -Zotero.Item._super = Zotero.DataObject; -Zotero.Item.prototype = Object.create(Zotero.Item._super.prototype); -Zotero.Item.constructor = Zotero.Item; +Zotero.extendClass(Zotero.DataObject, Zotero.Item); Zotero.Item.prototype._objectType = 'item'; Zotero.Item.prototype._dataTypes = Zotero.Item._super.prototype._dataTypes.concat([ diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js index c964e8c6c..1184f7ef0 100644 --- a/chrome/content/zotero/xpcom/search.js +++ b/chrome/content/zotero/xpcom/search.js @@ -37,9 +37,7 @@ 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([ From 442388304bdf948fe5a439aa85eb2c73255dcdfb Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 05:30:14 -0600 Subject: [PATCH 14/26] Add Zotero.Libraries.isGroupLibrary --- chrome/content/zotero/xpcom/data/libraries.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/data/libraries.js b/chrome/content/zotero/xpcom/data/libraries.js index 828e35af7..9fdc5a827 100644 --- a/chrome/content/zotero/xpcom/data/libraries.js +++ b/chrome/content/zotero/xpcom/data/libraries.js @@ -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'; + } +} \ No newline at end of file From adab8e45a7e74a51ec449a12a8ca70741845436c Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 04:08:49 -0600 Subject: [PATCH 15/26] Additional tweaks to DataObject & descendents to support inheritance --- .../content/zotero/xpcom/data/collection.js | 80 ++++++++++--------- .../content/zotero/xpcom/data/dataObject.js | 9 ++- chrome/content/zotero/xpcom/data/item.js | 73 +++++++++-------- chrome/content/zotero/xpcom/search.js | 3 +- 4 files changed, 88 insertions(+), 77 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js index 1ae5aa4b1..a97a7937d 100644 --- a/chrome/content/zotero/xpcom/data/collection.js +++ b/chrome/content/zotero/xpcom/data/collection.js @@ -37,17 +37,18 @@ 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.defineProperty(Zotero.Collection.prototype, 'ChildObjects', { + get: function() Zotero.Items +}); + Zotero.defineProperty(Zotero.Collection.prototype, 'id', { get: function() this._get('id'), set: function(val) this._set('id', val) @@ -136,7 +137,7 @@ Zotero.Collection.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* var key = this._key; var libraryID = this._libraryID; - var sql = Zotero.Collections.getPrimaryDataSQL(); + var sql = this.ObjectsClass.getPrimaryDataSQL(); if (id) { sql += " AND O.collectionID=?"; var params = id; @@ -164,7 +165,7 @@ Zotero.Collection.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* 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); } @@ -284,21 +285,12 @@ Zotero.Collection.prototype._initSave = Zotero.Promise.coroutine(function* (env) throw new Error('Collection name is empty'); } - return Zotero.Collection._super.prototype._initSave.apply(this, arguments); -}); - -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(); + var proceed = yield Zotero.Collection._super.prototype._initSave.apply(this, arguments); + if (!proceed) return false; - Zotero.debug("Saving collection " + this.id); - - // Verify parent + // Verify parent if (this._parentKey) { - let newParent = Zotero.Collections.getByLibraryAndKey( + let newParent = this.ObjectsClass.getByLibraryAndKey( this.libraryID, this._parentKey ); @@ -314,12 +306,24 @@ Zotero.Collection.prototype._saveData = Zotero.Promise.coroutine(function* (env) throw ('Cannot move collection "' + this.name + '" into one of its own descendents'); } - var parent = newParent.id; + env.parent = newParent.id; } else { - var parent = null; + env.parent = null; } + return true; +}); + +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', @@ -333,7 +337,7 @@ Zotero.Collection.prototype._saveData = Zotero.Promise.coroutine(function* (env) var sqlValues = [ collectionID ? { int: collectionID } : null, { string: this.name }, - parent ? parent : null, + env.parent ? env.parent : null, Zotero.DB.transactionDateTime, this.libraryID ? this.libraryID : 0, key, @@ -362,12 +366,12 @@ Zotero.Collection.prototype._saveData = Zotero.Promise.coroutine(function* (env) if (this._changed.parentKey) { var parentIDs = []; if (this.id && this._previousData.parentKey) { - parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey( + parentIDs.push(this.ObjectsClass.getIDFromLibraryAndKey( this.libraryID, this._previousData.parentKey )); } if (this.parentKey) { - parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey( + parentIDs.push(this.ObjectsClass.getIDFromLibraryAndKey( this.libraryID, this.parentKey )); } @@ -380,7 +384,7 @@ Zotero.Collection.prototype._saveData = Zotero.Promise.coroutine(function* (env) Zotero.Collection.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) { var isNew = env.isNew; - if (isNew && this.libraryID) { + if (isNew && Zotero.Libraries.isGroupLibrary(this.libraryID)) { var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID); var group = Zotero.Groups.get(groupID); group.clearCollectionCache(); @@ -395,7 +399,7 @@ Zotero.Collection.prototype._finalizeSave = Zotero.Promise.coroutine(function* ( // Invalidate cached child collections if (env.parentIDs) { - Zotero.Collections.refreshChildCollections(env.parentIDs); + this.ObjectsClass.refreshChildCollections(env.parentIDs); } // New collections have to be reloaded via Zotero.Collections.get(), so mark them as disabled @@ -446,7 +450,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({ @@ -493,7 +497,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({ @@ -545,7 +549,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( @@ -605,7 +609,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) { @@ -641,7 +645,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() }; } @@ -655,7 +659,7 @@ Zotero.Collection.prototype.erase = function(deleteItems) { } } if (del.length) { - yield Zotero.Items.trash(del); + yield this.ChildObjects.trash(del); } // Remove relations @@ -678,9 +682,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 () { @@ -795,7 +799,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 ); @@ -851,7 +855,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; } @@ -883,7 +887,7 @@ Zotero.Collection.prototype.loadChildCollections = Zotero.Promise.coroutine(func if (ids) { 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'); } @@ -923,7 +927,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; } diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 271b03b14..f2b86ad8a 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -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,7 +55,7 @@ Zotero.DataObject = function () { }; Zotero.DataObject.prototype._objectType = 'dataObject'; -Zotero.DataObject.prototype._dataTypes = []; +Zotero.DataObject.prototype._dataTypes = ['primaryData']; Zotero.defineProperty(Zotero.DataObject.prototype, 'objectType', { get: function() this._objectType @@ -64,6 +66,9 @@ Zotero.defineProperty(Zotero.DataObject.prototype, '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 }); @@ -77,7 +82,7 @@ Zotero.defineProperty(Zotero.DataObject.prototype, 'parentID', { }); Zotero.defineProperty(Zotero.DataObject.prototype, 'ObjectsClass', { - get: function() Zotero.DataObjectUtilities.getObjectsClassForObjectType(this.objectType) + get: function() this._ObjectsClass }); diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 2a1e2fdc5..6fd8687e5 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -92,8 +92,11 @@ Zotero.Item = function(itemTypeOrID) { Zotero.extendClass(Zotero.DataObject, Zotero.Item); Zotero.Item.prototype._objectType = 'item'; +Zotero.defineProperty(Zotero.Item.prototype, 'ContainerObjectsClass', { + get: function() Zotero.Collections +}); + Zotero.Item.prototype._dataTypes = Zotero.Item._super.prototype._dataTypes.concat([ - 'primaryData', 'itemData', 'note', 'creators', @@ -173,7 +176,7 @@ Zotero.Item.prototype.getType = function() { Zotero.Item.prototype.isPrimaryField = function (fieldName) { Zotero.debug("Zotero.Item.isPrimaryField() is deprecated -- use Zotero.Items.isPrimaryField()"); - return Zotero.Items.isPrimaryField(fieldName); + return this.ObjectsClass.isPrimaryField(fieldName); } Zotero.Item.prototype._get = function (fieldName) { @@ -228,7 +231,7 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped) } else if (creators.length > 3) { return creatorsData[0].lastName + " " + Zotero.getString('general.etAl'); } - } else if (field === 'id' || Zotero.Items.isPrimaryField(field)) { + } else if (field === 'id' || this.ObjectsClass.isPrimaryField(field)) { var privField = '_' + field; //Zotero.debug('Returning ' + (this[privField] ? this[privField] : '') + ' (typeof ' + typeof this[privField] + ')'); return this[privField]; @@ -313,12 +316,12 @@ Zotero.Item.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (relo } var columns = [], join = [], where = []; - var primaryFields = Zotero.Items.primaryFields; + var primaryFields = this.ObjectsClass.primaryFields; for (let i=0; i { + var aTitle = this.ObjectsClass.getSortTitle(a.title); + var bTitle = this.ObjectsClass.getSortTitle(b.title); return collation.compareString(1, aTitle, bTitle); }); } @@ -2461,7 +2464,7 @@ Zotero.Item.prototype._updateAttachmentStates = function (exists) { } try { - var item = Zotero.Items.getByLibraryAndKey(this.libraryID, parentKey); + var item = this.ObjectsClass.getByLibraryAndKey(this.libraryID, parentKey); } catch (e) { if (e instanceof Zotero.Exception.UnloadedDataException) { @@ -3121,7 +3124,7 @@ Zotero.Item.prototype.getBestAttachments = Zotero.Promise.coroutine(function* () + "AND IA.itemID NOT IN (SELECT itemID FROM deletedItems) " + "ORDER BY contentType='application/pdf' DESC, value=? DESC, dateAdded ASC"; var itemIDs = yield Zotero.DB.columnQueryAsync(sql, [this.id, Zotero.Attachments.LINK_MODE_LINKED_URL, url]); - return Zotero.Items.get(itemIDs); + return this.ObjectsClass.get(itemIDs); }); @@ -3377,7 +3380,7 @@ Zotero.Item.prototype.setCollections = function (collectionIDsOrKeys) { var collectionIDs = collectionIDsOrKeys.map(function (val) { return parseInt(val) == val ? parseInt(val) - : Zotero.Collections.getIDFromLibraryAndKey(this.libraryID, val); + : this.ContainerObjectsClass.getIDFromLibraryAndKey(this.libraryID, val); }.bind(this)); collectionIDs = Zotero.Utilities.arrayUnique(collectionIDs); @@ -3402,7 +3405,7 @@ Zotero.Item.prototype.setCollections = function (collectionIDsOrKeys) { Zotero.Item.prototype.addToCollection = function (collectionIDOrKey) { var collectionID = parseInt(collectionIDOrKey) == collectionIDOrKey ? parseInt(collectionIDOrKey) - : Zotero.Collections.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey) + : this.ContainerObjectsClass.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey) if (!collectionID) { throw new Error("Invalid collection '" + collectionIDOrKey + "'"); @@ -3427,7 +3430,7 @@ Zotero.Item.prototype.addToCollection = function (collectionIDOrKey) { Zotero.Item.prototype.removeFromCollection = function (collectionIDOrKey) { var collectionID = parseInt(collectionIDOrKey) == collectionIDOrKey ? parseInt(collectionIDOrKey) - : Zotero.Collections.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey) + : this.ContainerObjectsClass.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey) if (!collectionID) { throw new Error("Invalid collection '" + collectionIDOrKey + "'"); @@ -3576,7 +3579,7 @@ Zotero.Item.prototype.diff = function (item, includeMatches, ignoreFields) { var thisData = this.serialize(); var otherData = item.serialize(); - var numDiffs = Zotero.Items.diff(thisData, otherData, diff, includeMatches); + var numDiffs = this.ObjectsClass.diff(thisData, otherData, diff, includeMatches); diff[0].creators = []; diff[1].creators = []; @@ -3712,7 +3715,7 @@ Zotero.Item.prototype.multiDiff = Zotero.Promise.coroutine(function* (otherItems let otherItem = otherItems[i]; let diff = []; let otherData = yield otherItem.toJSON(); - let numDiffs = Zotero.Items.diff(thisData, otherData, diff); + let numDiffs = this.ObjectsClass.diff(thisData, otherData, diff); if (numDiffs) { for (let field in diff[1]) { @@ -3846,13 +3849,13 @@ Zotero.Item.prototype.erase = Zotero.Promise.coroutine(function* () { var parentCollectionIDs = this.collections; if (parentCollectionIDs) { for (var i=0; i Date: Fri, 14 Nov 2014 04:18:23 -0600 Subject: [PATCH 16/26] Validate numeric field IDs --- chrome/content/zotero/xpcom/data/itemFields.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/data/itemFields.js b/chrome/content/zotero/xpcom/data/itemFields.js index ffd86a2e4..1ffa92216 100644 --- a/chrome/content/zotero/xpcom/data/itemFields.js +++ b/chrome/content/zotero/xpcom/data/itemFields.js @@ -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; From b83bc40426e11609ae5e3203df5134964971d652 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 04:35:26 -0600 Subject: [PATCH 17/26] Various fixes for Zotero.Item --- .../content/zotero/xpcom/data/collection.js | 2 +- chrome/content/zotero/xpcom/data/item.js | 54 +++++++++++-------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js index a97a7937d..ae9007fb1 100644 --- a/chrome/content/zotero/xpcom/data/collection.js +++ b/chrome/content/zotero/xpcom/data/collection.js @@ -885,7 +885,7 @@ Zotero.Collection.prototype.loadChildCollections = Zotero.Promise.coroutine(func this._childCollections = []; - if (ids) { + if (ids.length) { for each(var id in ids) { var col = yield this.ObjectsClass.getAsync(id); if (!col) { diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 6fd8687e5..147452fb9 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -69,9 +69,9 @@ Zotero.Item = function(itemTypeOrID) { this._attachments = null; this._notes = null; - this._tags = {}; - this._collections = {}; - this._relations = {}; + this._tags = []; + this._collections = []; + this._relations = []; this._bestAttachmentState = null; this._fileExists = null; @@ -291,13 +291,13 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped) * @param {Boolean} asNames * @return {Integer{}|String[]} */ -Zotero.Item.prototype.getUsedFields = Zotero.Promise.coroutine(function* (asNames) { +Zotero.Item.prototype.getUsedFields = function(asNames) { this._requireData('itemData'); return Object.keys(this._itemData) - .filter(id => this._itemData[id] !== false) + .filter(id => this._itemData[id] !== false && this._itemData[id] !== null) .map(id => asNames ? Zotero.ItemFields.getName(id) : parseInt(id)); -}); +}; @@ -1325,7 +1325,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { continue; } - let previousCreatorID = this._previousData.creators[orderIndex] + let previousCreatorID = !isNew && this._previousData.creators[orderIndex] ? this._previousData.creators[orderIndex].id : false; let newCreatorID = yield Zotero.Creators.getIDFromData(creatorData, true); @@ -1603,7 +1603,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { // Collections if (this._changed.collections) { - let oldCollections = this._previousData.collections; + let oldCollections = this._previousData.collections || []; let newCollections = this._collections; let toAdd = Zotero.Utilities.arrayDiff(newCollections, oldCollections); @@ -3766,6 +3766,7 @@ Zotero.Item.prototype.clone = function(libraryID, skipTags) { var sameLibrary = libraryID == this.libraryID; var newItem = new Zotero.Item; + newItem.libraryID = libraryID; newItem.setType(this.itemTypeID); var fieldIDs = this.getUsedFields(); @@ -3950,7 +3951,7 @@ Zotero.Item.prototype.fromJSON = function (json) { case 'dateAdded': case 'dateModified': - item[field] = val; + this['_'+field] = val; break; case 'tags': @@ -3999,8 +4000,9 @@ Zotero.Item.prototype.fromJSON = function (json) { if (!changedFields[field] && // Invalid fields will already have been cleared by the type change Zotero.ItemFields.isValidForType( - Zotero.ItemFields.getID(field), data.itemTypeID - )) { + Zotero.ItemFields.getID(field), this.itemTypeID + ) + ) { this.setField(field, false); } } @@ -4009,18 +4011,17 @@ Zotero.Item.prototype.fromJSON = function (json) { this.deleted = !!json.deleted; // Creators - var numCreators = 0; + let pos = 0; if (json.creators) { - for each (let creator in json.creators) { - this.setCreator(pos, creator); - numCreators++; + while (pos Date: Fri, 14 Nov 2014 04:38:59 -0600 Subject: [PATCH 18/26] Move loadPrimaryData to Zotero.DataObject --- .../content/zotero/xpcom/data/collection.js | 32 ---------- .../content/zotero/xpcom/data/dataObject.js | 54 ++++++++++++++++ .../content/zotero/xpcom/data/dataObjects.js | 4 ++ chrome/content/zotero/xpcom/data/item.js | 63 +++---------------- 4 files changed, 66 insertions(+), 87 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js index ae9007fb1..0244b578d 100644 --- a/chrome/content/zotero/xpcom/data/collection.js +++ b/chrome/content/zotero/xpcom/data/collection.js @@ -127,38 +127,6 @@ 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 = this.ObjectsClass.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 */ diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index f2b86ad8a..4ab4ba805 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -324,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 Date: Fri, 14 Nov 2014 04:42:42 -0600 Subject: [PATCH 19/26] Move erase to DataObject and make it modular --- .../content/zotero/xpcom/data/dataObject.js | 44 +++++ chrome/content/zotero/xpcom/data/item.js | 162 ++++++++---------- 2 files changed, 120 insertions(+), 86 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 4ab4ba805..010e1a9a0 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -575,6 +575,50 @@ Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env) 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 diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 5ea577abb..16e6435bc 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -3780,99 +3780,89 @@ Zotero.Item.prototype.copy = Zotero.Promise.coroutine(function* () { });; -/** - * Delete item from database and clear from Zotero.Items internal array - * - * Items.erase() should be used for multiple items - */ -Zotero.Item.prototype.erase = Zotero.Promise.coroutine(function* () { - if (!this.id) { - return false; +Zotero.Item.prototype._eraseInit = Zotero.Promise.coroutine(function* (env) { + var proceed = yield Zotero.Item._super.prototype._eraseInit.apply(this, arguments); + if (!proceed) return false; + + env.deletedItemNotifierData = {}; + env.deletedItemNotifierData[this.id] = { old: this.toJSON() }; + + return true; +}); + +Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) { + // Remove item from parent collections + var parentCollectionIDs = this.collections; + if (parentCollectionIDs) { + for (var i=0; i Date: Fri, 14 Nov 2014 04:44:23 -0600 Subject: [PATCH 20/26] Add loadAllData to Zotero.dataObject Useful when trying to duplicate items, which requires access to all data --- chrome/content/zotero/xpcom/data/dataObject.js | 8 ++++++++ chrome/content/zotero/xpcom/data/item.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 010e1a9a0..76b487777 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -447,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 Date: Fri, 14 Nov 2014 04:48:47 -0600 Subject: [PATCH 21/26] Allow bypassing library edit check when saving data objects Pass {skipEditCheck: true} option to .save() --- chrome/content/zotero/xpcom/data/dataObject.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 76b487777..4704e5e8c 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -563,7 +563,7 @@ Zotero.DataObject.prototype._recoverFromSaveError = Zotero.Promise.coroutine(fun Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env) { env.isNew = !this.id; - this.editCheck(); + if (!env.options.skipEditCheck) this.editCheck(); if (!this.hasChanged()) { Zotero.debug(this._ObjectType + ' ' + this.id + ' has not changed', 4); From 20444eceb7be7a4b07729b8e2d71127774dab8d3 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 05:11:42 -0600 Subject: [PATCH 22/26] Sanitize primary fields in setField before comparing to current value --- chrome/content/zotero/xpcom/data/item.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index b4430929d..306c5db6e 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -718,8 +718,14 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) { case 'itemTypeID': case 'dateAdded': case 'dateModified': + break; + case 'version': + value = parseInt(value); + break; + case 'synced': + value = !!value; break; default: @@ -744,15 +750,6 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) { this.setType(value, loadIn); } else { - switch (field) { - case 'version': - value = parseInt(value); - break; - - case 'synced': - value = !!value; - break; - } this['_' + field] = value; From 707a65c63e8a5ac816f38e4e175a83ce67f67609 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 05:15:45 -0600 Subject: [PATCH 23/26] Remove deprecated Zotero.Items.add --- chrome/content/zotero/xpcom/data/items.js | 69 ----------------------- 1 file changed, 69 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js index 49fb6ee64..44d52a1d1 100644 --- a/chrome/content/zotero/xpcom/data/items.js +++ b/chrome/content/zotero/xpcom/data/items.js @@ -215,75 +215,6 @@ 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.cacheFields = Zotero.Promise.coroutine(function* (libraryID, fields, items) { From 7f5555aab6efb03226f38aecf524790aea00d801 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Mon, 19 Jan 2015 11:00:11 -0600 Subject: [PATCH 24/26] Inheritance-based Zotero.DataObjects --- .../content/zotero/xpcom/data/collections.js | 139 ++- .../content/zotero/xpcom/data/dataObjects.js | 1108 ++++++++--------- chrome/content/zotero/xpcom/data/items.js | 107 +- chrome/content/zotero/xpcom/data/relations.js | 35 +- chrome/content/zotero/xpcom/search.js | 38 +- 5 files changed, 711 insertions(+), 716 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/collections.js b/chrome/content/zotero/xpcom/data/collections.js index 2377355c5..3cb752086 100644 --- a/chrome/content/zotero/xpcom/data/collections.js +++ b/chrome/content/zotero/xpcom/data/collections.js @@ -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 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))(); diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js index dff2bf4a0..2c41a458b 100644 --- a/chrome/content/zotero/xpcom/data/dataObjects.js +++ b/chrome/content/zotero/xpcom/data/dataObjects.js @@ -24,610 +24,608 @@ */ -Zotero.DataObjects = function (object, objectPlural, id, table) { - var self = this; +Zotero.DataObjects = function () { + if (!this._ZDO_object) throw new Error('this._ZDO_object must be set before calling Zotero.DataObjects constructor'); - if (!object) { - object = ''; + if (!this._ZDO_objects) { + this._ZDO_objects = Zotero.DataObjectUtilities.getObjectTypePlural(this._ZDO_object); + } + if (!this._ZDO_Object) { + this._ZDO_Object = this._ZDO_object.substr(0, 1).toUpperCase() + + this._ZDO_object.substr(1); + } + if (!this._ZDO_Objects) { + this._ZDO_Objects = this._ZDO_objects.substr(0, 1).toUpperCase() + + this._ZDO_objects.substr(1); } - // Override these variables in child objects - this._ZDO_object = object; - this._ZDO_objects = objectPlural ? objectPlural : object + 's'; - this._ZDO_Object = object.substr(0, 1).toUpperCase() + object.substr(1); - this._ZDO_Objects = this._ZDO_objects.substr(0, 1).toUpperCase() - + this._ZDO_objects.substr(1); - this._ZDO_id = (id ? id : object) + 'ID'; - this._ZDO_table = table ? table : this._ZDO_objects; - - // Certain object types don't have a libary and key and only use an id - switch (object) { - case 'relation': - this._ZDO_idOnly = true; - break; - - default: - this._ZDO_idOnly = false; + if (!this._ZDO_id) { + this._ZDO_id = this._ZDO_object + 'ID'; } - Zotero.defineProperty(this, 'idColumn', { - get: function() this._ZDO_id - }); + if (!this._ZDO_table) { + this._ZDO_table = this._ZDO_objects; + } + + if (!this.ObjectClass) { + this.ObjectClass = Zotero[this._ZDO_Object]; + } + + this.primaryDataSQLFrom = " " + this._primaryDataSQLFrom + " " + this._primaryDataSQLWhere; this._objectCache = {}; this._objectKeys = {}; this._objectIDs = {}; this._loadedLibraries = {}; this._loadPromise = null; - - // Public properties - this.table = this._ZDO_table; - - - this.init = function () { - return this._loadIDsAndKeys(); +} + +Zotero.DataObjects.prototype._ZDO_idOnly = false; + +// Public properties +Zotero.defineProperty(Zotero.DataObjects.prototype, 'idColumn', { + get: function() this._ZDO_id +}); +Zotero.defineProperty(Zotero.DataObjects.prototype, 'table', { + get: function() this._ZDO_table +}); + +Zotero.defineProperty(Zotero.DataObjects.prototype, 'primaryFields', { + get: function () Object.keys(this._primaryDataSQLParts) +}, {lazy: true}); + + +Zotero.DataObjects.prototype.init = function() { + return this._loadIDsAndKeys(); +} + + +Zotero.DataObjects.prototype.isPrimaryField = function (field) { + return this.primaryFields.indexOf(field) != -1; +} + + +/** + * Retrieves one or more already-loaded items + * + * If an item hasn't been loaded, an error is thrown + * + * @param {Array|Integer} ids An individual object id or an array of object ids + * @return {Zotero.[Object]|Array} A Zotero.[Object], if a scalar id was passed; + * otherwise, an array of Zotero.[Object] + */ +Zotero.DataObjects.prototype.get = function (ids) { + if (Array.isArray(ids)) { + var singleObject = false; + } + else { + var singleObject = true; + ids = [ids]; } + var toReturn = []; - this.__defineGetter__('primaryFields', function () { - var primaryFields = Object.keys(this._primaryDataSQLParts); - - // Once primary fields have been cached, get rid of getter for speed purposes - delete this.primaryFields; - this.primaryFields = primaryFields; - - return primaryFields; - }); - - - this.isPrimaryField = function (field) { - return this.primaryFields.indexOf(field) != -1; + for (let i=0; i} A Zotero.[Object], if a scalar id was passed; - * otherwise, an array of Zotero.[Object] - */ - this.get = function (ids) { - if (Array.isArray(ids)) { - var singleObject = false; - } - else { - var singleObject = true; - ids = [ids]; - } - - var toReturn = []; - - for (let i=0; i} A Zotero.[Object], if a scalar id was passed; + * otherwise, an array of Zotero.[Object] + */ +Zotero.DataObjects.prototype.getAsync = Zotero.Promise.coroutine(function* (ids, options) { + var toLoad = []; + var toReturn = []; + + if (!ids) { + throw new Error("No arguments provided to " + this._ZDO_Objects + ".get()"); + } + + if (Array.isArray(ids)) { + var singleObject = false; + } + else { + var singleObject = true; + ids = [ids]; + } + + for (let i=0; i} A Zotero.[Object], if a scalar id was passed; - * otherwise, an array of Zotero.[Object] - */ - this.getAsync = Zotero.Promise.coroutine(function* (ids, options) { + // New object to load + if (toLoad.length) { // Serialize loads if (this._loadPromise && this._loadPromise.isPending()) { yield this._loadPromise; } - var deferred = Zotero.Promise.defer(); + let deferred = Zotero.Promise.defer(); this._loadPromise = deferred.promise; - var toLoad = []; - var toReturn = []; - - if (!ids) { - throw new Error("No arguments provided to " + this._ZDO_Objects + ".get()"); - } - - if (Array.isArray(ids)) { - var singleObject = false; - } - else { - var singleObject = true; - ids = [ids]; - } - - for (let i=0; i} - Promise for a data object, or FALSE if not found - */ - this.getByLibraryAndKeyAsync = Zotero.Promise.coroutine(function* (libraryID, key, options) { - var id = this.getIDFromLibraryAndKey(libraryID, key); - if (!id) { - return false; - } - return Zotero[this._ZDO_Objects].getAsync(id, options); - }); - - - this.exists = function (itemID) { - return !!this.getLibraryAndKeyFromID(itemID); +} + + +/** + * @deprecated - Use Zotero.DataObjects.parseLibraryKey() + */ +Zotero.DataObjects.prototype.parseLibraryKeyHash = function (libraryKey) { + Zotero.debug("WARNING: " + this._ZDO_Objects + ".parseLibraryKeyHash() is deprecated -- use .parseLibraryKey() instead"); + var [libraryID, key] = libraryKey.split('_'); + if (!key) { + return false; + } + return { + libraryID: parseInt(libraryID), + key: key + }; +} + + +/** + * Retrieves an object by its libraryID and key + * + * @param {Integer} libraryID + * @param {String} key + * @return {Zotero.DataObject} Zotero data object, or FALSE if not found + */ +Zotero.DataObjects.prototype.getByLibraryAndKey = function (libraryID, key, options) { + var id = this.getIDFromLibraryAndKey(libraryID, key); + if (!id) { + return false; + } + return Zotero[this._ZDO_Objects].get(id, options); +}; + + +/** + * Asynchronously retrieves an object by its libraryID and key + * + * @param {Integer} - libraryID + * @param {String} - key + * @return {Promise} - Promise for a data object, or FALSE if not found + */ +Zotero.DataObjects.prototype.getByLibraryAndKeyAsync = Zotero.Promise.coroutine(function* (libraryID, key, options) { + var id = this.getIDFromLibraryAndKey(libraryID, key); + if (!id) { + return false; + } + return Zotero[this._ZDO_Objects].getAsync(id, options); +}); + + +Zotero.DataObjects.prototype.exists = function (itemID) { + return !!this.getLibraryAndKeyFromID(itemID); +} + + +/** + * @return {Array} Array with libraryID and key + */ +Zotero.DataObjects.prototype.getLibraryAndKeyFromID = function (id) { + return this._objectKeys[id] ? this._objectKeys[id] : false; +} + + +Zotero.DataObjects.prototype.getIDFromLibraryAndKey = function (libraryID, key) { + if (libraryID === null) { + throw new Error("libraryID cannot be NULL (did you mean 0?)"); + } + return (this._objectIDs[libraryID] && this._objectIDs[libraryID][key]) + ? this._objectIDs[libraryID][key] : false; +} + + +Zotero.DataObjects.prototype.getOlder = function (libraryID, date) { + if (!date || date.constructor.name != 'Date') { + throw ("date must be a JS Date in " + + "Zotero." + this._ZDO_Objects + ".getOlder()") } - - /** - * @return {Array} Array with libraryID and key - */ - this.getLibraryAndKeyFromID = function (id) { - return this._objectKeys[id] ? this._objectKeys[id] : false; + var sql = "SELECT ROWID FROM " + this._ZDO_table + + " WHERE libraryID=? AND clientDateModified?"; + if (ignoreFutureDates) { + sql += " AND clientDateModified<=CURRENT_TIMESTAMP"; } + return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]); +} + + +/** + * @param {Integer} libraryID + * @return {Promise} A promise for an array of object ids + */ +Zotero.DataObjects.prototype.getUnsynced = function (libraryID) { + var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table + + " WHERE libraryID=? AND synced=0"; + return Zotero.DB.columnQueryAsync(sql, [libraryID]); +} + + +/** + * Get JSON from the sync cache that hasn't yet been written to the + * main object tables + * + * @param {Integer} libraryID + * @return {Promise} A promise for an array of JSON objects + */ +Zotero.DataObjects.prototype.getUnwrittenData = function (libraryID) { + var sql = "SELECT data FROM syncCache SC " + + "LEFT JOIN " + this._ZDO_table + " " + + "USING (libraryID) " + + "WHERE SC.libraryID=? AND " + + "syncObjectTypeID IN (SELECT syncObjectTypeID FROM " + + "syncObjectTypes WHERE name='" + this._ZDO_object + "') " + + "AND IFNULL(O.version, 0) < SC.version"; + return Zotero.DB.columnQueryAsync(sql, [libraryID]); +} + + +/** + * Reload loaded data of loaded objects + * + * @param {Array|Number} ids - An id or array of ids + * @param {Array} [dataTypes] - Data types to reload (e.g., 'primaryData'), or all loaded + * types if not provided + * @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally. + * This should be set to true for data that was + * changed externally (e.g., globally renamed tags). + */ +Zotero.DataObjects.prototype.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) { + ids = Zotero.flattenArguments(ids); + Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '') + + this._ZDO_objects + ' ' + ids); - this.getOlder = function (libraryID, date) { - if (!date || date.constructor.name != 'Date') { - throw ("date must be a JS Date in " - + "Zotero." + this._ZDO_Objects + ".getOlder()") - } - - var sql = "SELECT ROWID FROM " + this._ZDO_table - + " WHERE libraryID=? AND clientDateModified?"; - if (ignoreFutureDates) { - sql += " AND clientDateModified<=CURRENT_TIMESTAMP"; - } - return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]); - } - - - /** - * @param {Integer} libraryID - * @return {Promise} A promise for an array of object ids - */ - this.getUnsynced = function (libraryID) { - var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table - + " WHERE libraryID=? AND synced=0"; - return Zotero.DB.columnQueryAsync(sql, [libraryID]); - } - - - /** - * Get JSON from the sync cache that hasn't yet been written to the - * main object tables - * - * @param {Integer} libraryID - * @return {Promise} A promise for an array of JSON objects - */ - this.getUnwrittenData = function (libraryID) { - var sql = "SELECT data FROM syncCache SC " - + "LEFT JOIN " + this._ZDO_table + " " - + "USING (libraryID) " - + "WHERE SC.libraryID=? AND " - + "syncObjectTypeID IN (SELECT syncObjectTypeID FROM " - + "syncObjectTypes WHERE name='" + this._ZDO_object + "') " - + "AND IFNULL(O.version, 0) < SC.version"; - return Zotero.DB.columnQueryAsync(sql, [libraryID]); - } - - - /** - * Reload loaded data of loaded objects - * - * @param {Array|Number} ids - An id or array of ids - * @param {Array} [dataTypes] - Data types to reload (e.g., 'primaryData'), or all loaded - * types if not provided - * @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally. - * This should be set to true for data that was - * changed externally (e.g., globally renamed tags). - */ - this.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) { - ids = Zotero.flattenArguments(ids); - - Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '') - + this._ZDO_objects + ' ' + ids); - - for (let i=0; i this._primaryDataSQLParts[val]).join(', ') + + this.primaryDataSQLFrom; + } +}, {lazy: true}); + +Zotero.DataObjects.prototype._primaryDataSQLWhere = "WHERE 1"; + +Zotero.DataObjects.prototype.getPrimaryDataSQLPart = function (part) { + var sql = this._primaryDataSQLParts[part]; + if (!sql) { + throw new Error("Invalid primary data SQL part '" + part + "'"); + } + return sql; +} + + +Zotero.DataObjects.prototype._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) { + var loaded = {}; + + // If library isn't an integer (presumably false or null), skip it + if (parseInt(libraryID) != libraryID) { + libraryID = false; + } + + if (libraryID === false && !ids) { + throw new Error("Either libraryID or ids must be provided"); + } + + if (libraryID !== false && this._loadedLibraries[libraryID]) { + return loaded; + } + + // getPrimaryDataSQL() should use "O" for the primary table alias + var sql = this.primaryDataSQL; + var params = []; + if (libraryID !== false) { + sql += ' AND O.libraryID=?'; + params.push(libraryID); + } + if (ids) { + sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')'; + } + + var t = new Date(); + yield Zotero.DB.queryAsync( + sql, + params, + { + onRow: function (row) { + var id = row.getResultByIndex(this._ZDO_id); + var columns = Object.keys(this._primaryDataSQLParts); + var rowObj = {}; + for (let i=0; i { if (topic == 'idle' || topic == 'timer-callback') { var days = Zotero.Prefs.get('trashAutoEmptyDays'); if (!days) { @@ -551,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 ); @@ -572,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(); } } } @@ -581,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); } @@ -624,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(); } } @@ -655,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; @@ -759,6 +743,7 @@ Zotero.Items = new function() { /* * Generate SQL to retrieve sortCreator field */ + var _sortCreatorSQL = ''; function _getSortCreatorSQL() { if (_sortCreatorSQL) { return _sortCreatorSQL; @@ -878,7 +863,7 @@ Zotero.Items = new function() { } - function getSortTitle(title) { + this.getSortTitle = function(title) { if (title === false || title === undefined) { return ''; } @@ -887,4 +872,8 @@ Zotero.Items = new function() { } return title.replace(/^[\[\'\"](.*)[\'\"\]]?$/, '$1') } -} + + Zotero.DataObjects.call(this); + + return this; +}.bind(Object.create(Zotero.DataObjects.prototype))(); diff --git a/chrome/content/zotero/xpcom/data/relations.js b/chrome/content/zotero/xpcom/data/relations.js index 310f39e97..de60de528 100644 --- a/chrome/content/zotero/xpcom/data/relations.js +++ b/chrome/content/zotero/xpcom/data/relations.js @@ -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))(); \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js index 6d67ebb26..cee68a6aa 100644 --- a/chrome/content/zotero/xpcom/search.js +++ b/chrome/content/zotero/xpcom/search.js @@ -1637,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(); }); @@ -1730,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))(); From a0a656eac0381c521498b870b1007f04911469f6 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 04:51:11 -0600 Subject: [PATCH 25/26] Allow setting a specific dateModified on item To be used in feed items to set the last _remote_ modification date --- chrome/content/zotero/xpcom/data/item.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 306c5db6e..6d8c914c2 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -132,7 +132,8 @@ Zotero.defineProperty(Zotero.Item.prototype, 'dateAdded', { get: function() this._dateAdded }); Zotero.defineProperty(Zotero.Item.prototype, 'dateModified', { - get: function() this._dateModified + get: function() this._dateModified, + set: function(val) this.setField('dateModified', val) }); Zotero.defineProperty(Zotero.Item.prototype, 'version', { get: function() this._itemVersion, @@ -717,8 +718,15 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) { switch (field) { case 'itemTypeID': case 'dateAdded': + break; + case 'dateModified': - break; + // Make sure it's valid + let date = Zotero.Date.sqlToDate(value, true); + if (!date) throw new Error("Invalid SQL date: " + value); + + value = Zotero.Date.dateToSQL(date); + break; case 'version': value = parseInt(value); @@ -1168,7 +1176,11 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { this.synced ? 1 : 0 ); - if (isNew) { + if (this._changed.primaryData && this._changed.primaryData._dateModified) { + sqlColumns.push('dateModified', 'clientDateModified'); + sqlValues.push(this.dateModified, Zotero.DB.transactionDateTime); + } + else if (isNew) { sqlColumns.push('dateModified', 'clientDateModified'); sqlValues.push(Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime); } From 5db52ac1c3c7f9fc5762a59dfbbb850d2bf1fa61 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Fri, 14 Nov 2014 05:12:34 -0600 Subject: [PATCH 26/26] Add 'year' as a gettable field for Zotero.Item --- chrome/content/zotero/xpcom/data/item.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 6d8c914c2..f13b42c05 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -200,13 +200,13 @@ Zotero.Item.prototype._setParentKey = function() { /* * Retrieves (and loads from DB, if necessary) an itemData field value * - * Field can be passed as fieldID or fieldName - * - * If |unformatted| is true, skip any special processing of DB value - * (e.g. multipart date field) (default false) - * - * If |includeBaseMapped| is true and field is a base field, returns value of - * type-specific field instead (e.g. 'label' for 'publisher' in 'audioRecording') + * @param {String|Integer} field fieldID or fieldName + * @param {Boolean} [unformatted] Skip any special processing of DB value + * (e.g. multipart date field) + * @param {Boolean} includeBaseMapped If true and field is a base field, returns + * value of type-specific field instead + * (e.g. 'label' for 'publisher' in 'audioRecording') + * @return {String} Value as string or empty string if value is not present */ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped) { // We don't allow access after saving to force use of the centrally cached @@ -236,6 +236,8 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped) var privField = '_' + field; //Zotero.debug('Returning ' + (this[privField] ? this[privField] : '') + ' (typeof ' + typeof this[privField] + ')'); return this[privField]; + } else if (field == 'year') { + return this.getField('date', true, true).substr(0,4); } if (this.isNote()) {