From 5ee40f6601415c61a9ed61a7ee283640905524f5 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Mon, 6 Oct 2014 21:38:14 -0500 Subject: [PATCH 1/3] Add ZU.dom2text and Zotero.Utilities.Internal.getDOMDocument * getDomDocument: returns a detached DOMDocument object * dom2text (TODO): Currently just returns Node.textContent, but is intended to return Zotero-formatted string based on text formatting in the DOM and the Zotero.Item field that the text is meant for --- chrome/content/zotero/xpcom/utilities.js | 20 ++++++++++++++----- .../zotero/xpcom/utilities_internal.js | 10 ++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js index 990a7a7a25..a3b1571974 100644 --- a/chrome/content/zotero/xpcom/utilities.js +++ b/chrome/content/zotero/xpcom/utilities.js @@ -411,11 +411,7 @@ Zotero.Utilities = { // Create a node and use the textContent property to do unescaping where // possible, because this approach preserves line endings in the HTML if(node === undefined) { - var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] - .createInstance(Components.interfaces.nsIDOMParser); - var domDocument = parser.parseFromString("", - "text/html"); - node = domDocument.createElement("div"); + node = Zotero.Utilities.Internal.getDOMDocument().createElement("div"); } node.innerHTML = str; @@ -440,6 +436,20 @@ Zotero.Utilities = { }; }, + /** + * Converts text inside a DOM object to plain text preserving text formatting + * appropriate for given field + * + * @param {DOMNode} rootNode Node containing all the text that needs to be extracted + * @param {String} targetField Zotero item field that the text is meant for + * + * @return {String} Zotero formatted string + */ + "dom2text": function(rootNode, targetField) { + // TODO: actually do this + return Zotero.Utilities.trimInternal(rootNode.textContent); + }, + /** * Wrap URLs and DOIs in links in plain text * diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index 887874c362..18f0ae1f15 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -345,6 +345,16 @@ Zotero.Utilities.Internal = { }, + /** + * Returns a DOMDocument object not attached to any window + */ + "getDOMDocument": function() { + return Components.classes["@mozilla.org/xmlextras/domparser;1"] + .createInstance(Components.interfaces.nsIDOMParser) + .parseFromString("", "text/html"); + }, + + /** * A generator that yields promises that delay for the given intervals * From bc8a340c30be53177af0fe27e26d621a745fae36 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Wed, 29 Oct 2014 19:22:52 -0500 Subject: [PATCH 2/3] Virtualize data objects --- .../content/zotero/xpcom/data/collection.js | 17 ++++---- .../content/zotero/xpcom/data/dataObject.js | 21 +++------- chrome/content/zotero/xpcom/data/item.js | 41 ++++++++++++------- chrome/content/zotero/xpcom/search.js | 15 ++++--- 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js index 64b0a83520..dc75fe18f2 100644 --- a/chrome/content/zotero/xpcom/data/collection.js +++ b/chrome/content/zotero/xpcom/data/collection.js @@ -24,12 +24,7 @@ */ Zotero.Collection = function() { - let dataTypes = [ - 'primaryData', - 'childCollections', - 'childItems' - ]; - Zotero.DataObject.apply(this, ['collection', dataTypes]); + Zotero.Collection._super.apply(this); this._name = null; this._parentID = null; @@ -42,9 +37,17 @@ Zotero.Collection = function() { this._childItems = []; } -Zotero.Collection.prototype = Object.create(Zotero.DataObject.prototype); +Zotero.Collection._super = Zotero.DataObject; +Zotero.Collection.prototype = Object.create(Zotero.Collection._super.prototype); Zotero.Collection.constructor = Zotero.Collection; +Zotero.Collection.prototype._objectType = 'collection'; +Zotero.Collection.prototype._dataTypes = Zotero.Collection._super.prototype._dataTypes.concat([ + 'primaryData', + 'childCollections', + 'childItems' +]); + Zotero.Collection.prototype.__defineGetter__('id', function () { return this._get('id'); }); Zotero.Collection.prototype.__defineSetter__('id', function (val) { this._set('id', val); }); Zotero.Collection.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); }); diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 9d0f6d78fe..9b2dabb89b 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -24,21 +24,16 @@ */ /** - * - * @param {String} objectType - * @param {String[]} dataTypes A set of data types that can be loaded for this data object - * * @property {String} (readOnly) objectType * @property {String} (readOnly) libraryKey * @property {String|null} parentKey Null if no parent * @property {Integer|false|undefined} parentID False if no parent. Undefined if not applicable (e.g. search objects) */ -Zotero.DataObject = function (objectType, dataTypes) { - this._objectType = objectType; +Zotero.DataObject = function () { + let objectType = this._objectType; this._ObjectType = objectType[0].toUpperCase() + objectType.substr(1); this._objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType); - this._dataTypes = dataTypes; this._id = null; this._libraryID = null; @@ -57,6 +52,9 @@ Zotero.DataObject = function (objectType, dataTypes) { this._clearChanged(); }; +Zotero.DataObject.prototype._objectType = 'dataObject'; +Zotero.DataObject.prototype._dataTypes = []; + Zotero.Utilities.Internal.defineProperty(Zotero.DataObject, 'objectType', { get: function() this._objectType }); @@ -74,10 +72,6 @@ Zotero.Utilities.Internal.defineProperty(Zotero.DataObject, 'parentID', { Zotero.DataObject.prototype._get = function (field) { - if (this._objectType == 'item') { - throw new Error("_get is not valid for items"); - } - if (this['_' + field] !== null) { return this['_' + field]; } @@ -166,11 +160,6 @@ Zotero.DataObject.prototype._setParentID = function (id) { * @return {Boolean} True if changed, false if stayed the same */ Zotero.DataObject.prototype._setParentKey = function(key) { - if (this._objectType == 'item') { - if (!this.isNote() && !this.isAttachment()) { - throw new Error("_setParentKey() can only be called on items of type 'note' or 'attachment'"); - } - } key = Zotero.DataObjectUtilities.checkKey(key); if (this._parentKey == key) { diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index bf4a9f43d9..04b89a3b7f 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -32,18 +32,7 @@ Zotero.Item = function(itemTypeOrID) { throw ("Zotero.Item constructor only takes one parameter"); } - var dataTypes = [ - 'primaryData', - 'itemData', - 'note', - 'creators', - 'childItems', - 'relatedItems', // TODO: remove - 'tags', - 'collections', - 'relations' - ]; - Zotero.DataObject.apply(this, ['item', dataTypes]); + Zotero.Item._super.apply(this); this._disabled = false; @@ -100,10 +89,23 @@ Zotero.Item = function(itemTypeOrID) { } } -Zotero.Item.prototype = Object.create(Zotero.DataObject.prototype); +Zotero.Item._super = Zotero.DataObject; +Zotero.Item.prototype = Object.create(Zotero.Item._super.prototype); Zotero.Item.constructor = Zotero.Item; -Zotero.Item.prototype.__defineGetter__('objectType', function () { return 'item'; }); +Zotero.Item.prototype._objectType = 'item'; +Zotero.Item.prototype._dataTypes = Zotero.Item._super.prototype._dataTypes.concat([ + 'primaryData', + 'itemData', + 'note', + 'creators', + 'childItems', + 'relatedItems', // TODO: remove + 'tags', + 'collections', + 'relations' +]); + Zotero.Item.prototype.__defineGetter__('id', function () this._id); Zotero.Item.prototype.__defineGetter__('itemID', function () { Zotero.debug("Item.itemID is deprecated -- use Item.id"); @@ -149,6 +151,17 @@ Zotero.Item.prototype.isPrimaryField = function (fieldName) { return Zotero.Items.isPrimaryField(fieldName); } +Zotero.Item.prototype._get = function (fieldName) { + throw new Error("_get is not valid for items"); +} + +Zotero.Item.prototype._setParentKey = function() { + if (!this.isNote() && !this.isAttachment()) { + throw new Error("_setParentKey() can only be called on items of type 'note' or 'attachment'"); + } + + Zotero.Item._super.prototype._setParentKey.apply(this, arguments); +} ////////////////////////////////////////////////////////////////////////////// // diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js index ebeae812c4..0166666ade 100644 --- a/chrome/content/zotero/xpcom/search.js +++ b/chrome/content/zotero/xpcom/search.js @@ -24,11 +24,7 @@ */ Zotero.Search = function() { - var dataTypes = [ - 'primaryData', - 'conditions' - ]; - Zotero.DataObject.apply(this, ['search', dataTypes]); + Zotero.Search._super.apply(this); this._name = null; @@ -41,9 +37,16 @@ Zotero.Search = function() { this._hasPrimaryConditions = false; } -Zotero.Search.prototype = Object.create(Zotero.DataObject.prototype); +Zotero.Search._super = Zotero.DataObject; +Zotero.Search.prototype = Object.create(Zotero.Search._super.prototype); Zotero.Search.constructor = Zotero.Search; +Zotero.Search.prototype._objectType = 'search'; +Zotero.Search.prototype._dataTypes = Zotero.Search._super.prototype._dataTypes.concat([ + 'primaryData', + 'conditions' +]); + Zotero.Search.prototype.getID = function(){ Zotero.debug('Zotero.Search.getName() is deprecated -- use Search.id'); return this._id; From 3f85ee73b33ff5757a0fa0ecef3983d69fb24a67 Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Wed, 29 Oct 2014 19:21:36 -0500 Subject: [PATCH 3/3] Don't count on 0 being the user libraryID in Zotero.Libraries Some other minor tweaks --- .../content/zotero/xpcom/data/dataObject.js | 8 +-- chrome/content/zotero/xpcom/data/libraries.js | 69 ++++++++++++------- .../zotero/xpcom/utilities_internal.js | 4 +- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 9b2dabb89b..a7a9b7ce52 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, 'objectType', { +Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'objectType', { get: function() this._objectType }); -Zotero.Utilities.Internal.defineProperty(Zotero.DataObject, 'libraryKey', { +Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'libraryKey', { get: function() this._libraryID + "/" + this._key }); -Zotero.Utilities.Internal.defineProperty(Zotero.DataObject, 'parentKey', { +Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'parentKey', { get: function() this._parentKey, set: function(v) this._setParentKey(v) }); -Zotero.Utilities.Internal.defineProperty(Zotero.DataObject, 'parentID', { +Zotero.Utilities.Internal.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 15659af806..8234f2083d 100644 --- a/chrome/content/zotero/xpcom/data/libraries.js +++ b/chrome/content/zotero/xpcom/data/libraries.js @@ -24,7 +24,18 @@ */ Zotero.Libraries = new function () { - var _libraryData = {}; + let _libraryData = {}, + _userLibraryID, + _libraryDataLoaded = false; + + Zotero.Utilities.Internal.defineProperty(this, 'userLibraryID', { + get: function() { + if (!_libraryDataLoaded) { + throw new Error("Library data not yet loaded"); + } + return _userLibraryID; + } + }); this.init = Zotero.Promise.coroutine(function* () { // Library data @@ -36,25 +47,28 @@ Zotero.Libraries = new function () { type: row.libraryType, version: row.version }; + if (row.libraryType == 'user') { + _userLibraryID = row.libraryID; + } } + _libraryDataLoaded = true; }); - this.exists = function (libraryID) { - // Until there are other library types, this can just check groups, - // which already preload ids at startup - try { - return !!Zotero.Groups.getGroupIDFromLibraryID(libraryID); - } - catch (e) { - if (e.getMessage().indexOf("does not exist") != -1) { - return false; - } - throw e; - } + function _getLibraryDataFromDB (libraryID) { + var sql = "SELECT * FROM libraries WHERE libraryID=?"; + return Zotero.DB.queryAsync(sql, [libraryID]) + .then(function(rows) { + return rows[0]; + }); } - this.add = function (libraryID, type) { + this.exists = function (libraryID) { + return _libraryData[libraryID] !== undefined; + } + + + this.add = Zotero.Promise.coroutine(function* (libraryID, type) { switch (type) { case 'group': break; @@ -64,9 +78,16 @@ Zotero.Libraries = new function () { } var sql = "INSERT INTO libraries (libraryID, libraryType) VALUES (?, ?)"; - Zotero.DB.query(sql, [libraryID, type]); - } - + yield Zotero.DB.queryAsync(sql, [libraryID, type]); + // Re-fetch from DB to get auto-filled defaults + var newData = yield _getLibraryDataFromDB(libraryID); + _libraryData[newData.libraryID] = { + type: newData.libraryType, + version: newData.version + }; + + return newData; + }); this.dbLibraryID = function (libraryID) { return (libraryID == Zotero.Users.getCurrentLibraryID()) ? 0 : libraryID; @@ -74,12 +95,10 @@ Zotero.Libraries = new function () { this.getName = function (libraryID) { - if (!libraryID) { - return Zotero.getString('pane.collections.library'); - } - var type = this.getType(libraryID); switch (type) { + case 'user': + return Zotero.getString('pane.collections.library'); case 'group': var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID); var group = Zotero.Groups.get(groupID); @@ -92,10 +111,10 @@ Zotero.Libraries = new function () { this.getType = function (libraryID) { - if (this.dbLibraryID(libraryID) === 0) { + if (libraryID === Zotero.Libraries.userLibraryID) { return 'user'; } - if (!_libraryData[libraryID]) { + if (!this.exists(libraryID)) { throw new Error("Library data not loaded for library " + libraryID); } return _libraryData[libraryID].type; @@ -106,7 +125,7 @@ Zotero.Libraries = new function () { * @return {Integer} */ this.getVersion = function (libraryID) { - if (!_libraryData[libraryID]) { + if (!this.exists(libraryID)) { throw new Error("Library data not loaded for library " + libraryID); } return _libraryData[libraryID].version; @@ -122,7 +141,7 @@ Zotero.Libraries = new function () { version = parseInt(version); var sql = "UPDATE libraries SET version=? WHERE libraryID=?"; yield Zotero.DB.queryAsync(sql, [version, libraryID]); - _libraryData[libraryID] = version; + _libraryData[libraryID].version = version; }); diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index 18f0ae1f15..f88c08f752 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -496,7 +496,7 @@ Zotero.Utilities.Internal = { }, /** - * Defines property on the object's prototype. + * Defines property on the object * More compact way to do Object.defineProperty * * @param {Object} obj Target object @@ -510,7 +510,7 @@ Zotero.Utilities.Internal = { if (!desc.hasOwnProperty(p)) continue; d[p] = desc[p]; } - Object.defineProperty(obj.prototype, prop, d); + Object.defineProperty(obj, prop, d); } }