commit e2d3cc3f0d95ede10bfbe778e312f649f1b8007d
parent c917d9e30e186718387850aa33d6d964c965ec6c
Author: Dan Stillman <dstillman@zotero.org>
Date: Tue, 23 Sep 2014 01:11:41 -0400
Update zotero://select to use new URLs and wait for items list load
Closes #541
Diffstat:
7 files changed, 282 insertions(+), 153 deletions(-)
diff --git a/chrome/content/zotero/xpcom/api.js b/chrome/content/zotero/xpcom/api.js
@@ -36,89 +36,109 @@ Zotero.API = {
getResultsFromParams: Zotero.Promise.coroutine(function* (params) {
+ if (!params.objectType) {
+ throw new Error("objectType not specified");
+ }
+
var results;
- switch (params.scopeObject) {
- case 'collections':
- if (params.scopeObjectKey) {
- var col = yield Zotero.Collections.getByLibraryAndKeyAsync(
- params.libraryID, params.scopeObjectKey
- );
- }
- else {
- var col = yield Zotero.Collections.getAsync(params.scopeObjectID);
- }
- if (!col) {
- throw new Error('Invalid collection ID or key');
- }
- yield col.loadChildItems();
- results = col.getChildItems();
- break;
-
- case 'searches':
- if (params.scopeObjectKey) {
- var s = yield Zotero.Searches.getByLibraryAndKeyAsync(
- params.libraryID, params.scopeObjectKey
- );
- }
- else {
- var s = yield Zotero.Searches.getAsync(params.scopeObjectID);
- }
- if (!s) {
- throw new Error('Invalid search ID or key');
- }
+
+ if (params.objectType == 'item') {
+ switch (params.scopeObject) {
+ case 'collections':
+ if (params.scopeObjectKey) {
+ var col = yield Zotero.Collections.getByLibraryAndKeyAsync(
+ params.libraryID, params.scopeObjectKey
+ );
+ }
+ else {
+ var col = yield Zotero.Collections.getAsync(params.scopeObjectID);
+ }
+ if (!col) {
+ throw new Error('Invalid collection ID or key');
+ }
+ yield col.loadChildItems();
+ results = col.getChildItems();
+ break;
- // FIXME: Hack to exclude group libraries for now
- var s2 = new Zotero.Search();
- s2.setScope(s);
- var groups = Zotero.Groups.getAll();
- for each(var group in groups) {
- yield s2.addCondition('libraryID', 'isNot', group.libraryID);
- }
- var ids = yield s2.search();
- break;
-
- default:
- if (params.scopeObject) {
- throw new Error("Invalid scope object '" + params.scopeObject + "'");
- }
+ case 'searches':
+ if (params.scopeObjectKey) {
+ var s = yield Zotero.Searches.getByLibraryAndKeyAsync(
+ params.libraryID, params.scopeObjectKey
+ );
+ }
+ else {
+ var s = yield Zotero.Searches.getAsync(params.scopeObjectID);
+ }
+ if (!s) {
+ throw new Error('Invalid search ID or key');
+ }
+
+ // FIXME: Hack to exclude group libraries for now
+ var s2 = new Zotero.Search();
+ s2.setScope(s);
+ var groups = Zotero.Groups.getAll();
+ for each(var group in groups) {
+ yield s2.addCondition('libraryID', 'isNot', group.libraryID);
+ }
+ var ids = yield s2.search();
+ break;
- if (params.itemKey) {
+ default:
+ if (params.scopeObject) {
+ throw new Error("Invalid scope object '" + params.scopeObject + "'");
+ }
+
var s = new Zotero.Search;
- yield s.addCondition('libraryID', 'is', params.libraryID);
- yield s.addCondition('blockStart');
- for (let i=0; i<params.itemKey.length; i++) {
- let itemKey = params.itemKey[i];
- yield s.addCondition('key', 'is', itemKey);
+ if (params.libraryID !== undefined) {
+ yield s.addCondition('libraryID', 'is', params.libraryID);
}
- yield s.addCondition('blockEnd');
+
+ if (params.objectKey) {
+ yield s.addCondition('key', 'is', params.objectKey);
+ }
+ else if (params.objectID) {
+ Zotero.debug('adding ' + params.objectID);
+ yield s.addCondition('itemID', 'is', params.objectID);
+ }
+
+ if (params.itemKey) {
+ yield s.addCondition('blockStart');
+ for (let i=0; i<params.itemKey.length; i++) {
+ let itemKey = params.itemKey[i];
+ yield s.addCondition('key', 'is', itemKey);
+ }
+ yield s.addCondition('blockEnd');
+ }
+
+ // Display all top-level items
+ /*if (params.onlyTopLevel) {
+ yield s.addCondition('noChildren', 'true');
+ }*/
+
var ids = yield s.search();
+ }
+
+ if (results) {
+ // Filter results by item key
+ if (params.itemKey) {
+ results = results.filter(function (result) {
+ return params.itemKey.indexOf(result.key) !== -1;
+ });
}
- else {
- // Display all items
- var s = new Zotero.Search();
- yield s.addCondition('libraryID', 'is', params.libraryID);
- yield s.addCondition('noChildren', 'true');
- var ids = yield s.search();
+ }
+ else if (ids) {
+ // Filter results by item key
+ if (params.itemKey) {
+ ids = ids.filter(function (id) {
+ var [libraryID, key] = Zotero.Items.getLibraryAndKeyFromID(id);
+ return params.itemKey.indexOf(key) !== -1;
+ });
}
- }
-
- if (results) {
- // Filter results by item key
- if (params.itemKey) {
- results = results.filter(function (result) {
- return params.itemKey.indexOf(result.key) !== -1;
- });
+ results = yield Zotero.Items.getAsync(ids);
}
}
- else if (ids) {
- // Filter results by item key
- if (params.itemKey) {
- ids = ids.filter(function (id) {
- var [libraryID, key] = Zotero.Items.getLibraryAndKeyFromID(id);
- return params.itemKey.indexOf(key) !== -1;
- });
- }
- results = yield Zotero.Items.getAsync(ids);
+ else {
+ throw new Error("Unsupported object type '" + params.objectType + "'");
}
return results;
diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js
@@ -36,10 +36,11 @@
*/
Zotero.CollectionTreeView = function()
{
+ Zotero.LibraryTreeView.apply(this);
+
this.itemToSelect = null;
this.hideSources = [];
- this._treebox = null;
this._highlightedRows = {};
this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'trash', 'bucket'], 'collectionTreeView');
this._containerState = {};
@@ -57,6 +58,8 @@ Object.defineProperty(Zotero.CollectionTreeView.prototype, "selectedTreeRow", {
}
});
+
+
/*
* Called by the tree itself
*/
@@ -93,6 +96,9 @@ Zotero.CollectionTreeView.prototype.setTree = Zotero.Promise.coroutine(function*
var row = yield this.getLastViewedRow();
this.selection.select(row);
this._treebox.ensureRowIsVisible(row);
+
+ yield this._runListeners('load');
+ this._initialized = true;
}
catch (e) {
Zotero.debug(e, 1);
diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js
@@ -35,18 +35,16 @@
* Constructor for the ItemTreeView object
*/
Zotero.ItemTreeView = function (collectionTreeRow, sourcesOnly) {
+ Zotero.LibraryTreeView.apply(this);
+
this.wrappedJSObject = this;
this.rowCount = 0;
this.collectionTreeRow = collectionTreeRow;
- this._initialized = false;
this._skipKeypress = false;
this._sourcesOnly = sourcesOnly;
- this._callbacks = [];
-
- this._treebox = null;
this._ownerDocument = null;
this._needsSort = false;
@@ -62,17 +60,6 @@ Zotero.ItemTreeView = function (collectionTreeRow, sourcesOnly) {
Zotero.ItemTreeView.prototype = Object.create(Zotero.LibraryTreeView.prototype);
Zotero.ItemTreeView.prototype.type = 'item';
-Zotero.ItemTreeView.prototype.addCallback = function(callback) {
- this._callbacks.push(callback);
-}
-
-
-Zotero.ItemTreeView.prototype._runCallbacks = Zotero.Promise.coroutine(function* () {
- for each(var cb in this._callbacks) {
- yield Zotero.Promise.resolve(cb());
- }
-});
-
/**
* Called by the tree itself
@@ -251,12 +238,12 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
yield this.sort();
// Only yield if there are callbacks; otherwise, we're almost done
- if(this._callbacks.length && this._waitAfter && Date.now() > this._waitAfter) yield Zotero.Promise.resolve();
+ if (this._listeners.load.length && this._waitAfter && Date.now() > this._waitAfter) yield Zotero.Promise.resolve();
yield this.expandMatchParents();
- //Zotero.debug('Running callbacks in itemTreeView.setTree()', 4);
- yield this._runCallbacks();
+ yield this._runListeners('load');
+ this._initialized = true;
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
@@ -1805,8 +1792,7 @@ Zotero.ItemTreeView.prototype.setFilter = Zotero.Promise.coroutine(function* (ty
//this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
- //Zotero.debug('Running callbacks in itemTreeView.setFilter()', 4);
- yield this._runCallbacks();
+ yield this._runListeners('load');
});
diff --git a/chrome/content/zotero/xpcom/libraryTreeView.js b/chrome/content/zotero/xpcom/libraryTreeView.js
@@ -23,8 +23,35 @@
***** END LICENSE BLOCK *****
*/
-Zotero.LibraryTreeView = function () {};
+Zotero.LibraryTreeView = function () {
+ this._initialized = false;
+ this._listeners = {
+ load: []
+ };
+};
+
Zotero.LibraryTreeView.prototype = {
+ addEventListener: function(event, listener) {
+ if (event == 'load') {
+ // If already initialized run now
+ if (this._initialized) {
+ listener();
+ }
+ else {
+ this._listeners[event].push(listener);
+ }
+ }
+ },
+
+
+ _runListeners: Zotero.Promise.coroutine(function* (event) {
+ var listener;
+ while (listener = this._listeners[event].shift()) {
+ yield Zotero.Promise.resolve(listener());
+ }
+ }),
+
+
/**
* Called while a drag is over the tree
*/
diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js
@@ -2190,6 +2190,18 @@ Zotero.SearchConditions = new function(){
},
{
+ name: 'itemID',
+ operators: {
+ is: true,
+ isNot: true
+ },
+ table: 'items',
+ field: 'itemID',
+ special: true,
+ noLoad: true
+ },
+
+ {
name: 'annotation',
operators: {
contains: true,
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
@@ -32,6 +32,7 @@ var ZoteroPane = new function()
var _unserialized = false;
this.collectionsView = false;
this.itemsView = false;
+ this._listeners = {};
this.__defineGetter__('loaded', function () _loaded);
//Privileged methods
@@ -1099,7 +1100,7 @@ var ZoteroPane = new function()
this.onCollectionSelected = Zotero.Promise.coroutine(function* () {
var collectionTreeRow = this.getCollectionTreeRow();
- if (this.itemsView.collectionTreeRow == collectionTreeRow) {
+ if (this.itemsView && this.itemsView.collectionTreeRow == collectionTreeRow) {
Zotero.debug("Collection selection hasn't changed");
return;
}
@@ -1167,7 +1168,14 @@ var ZoteroPane = new function()
this.itemsView.onError = function () {
ZoteroPane_Local.displayErrorMessage();
};
- this.itemsView.addCallback(this.setTagScope);
+ // If any queued load listeners, set them to run when the tree is ready
+ if (this._listeners.itemsLoaded) {
+ let listener;
+ while (listener = this._listeners.itemsLoaded.shift()) {
+ this.itemsView.addEventListener('load', listener);
+ }
+ }
+ this.itemsView.addEventListener('load', this.setTagScope);
document.getElementById('zotero-items-tree').view = this.itemsView;
// Add events to treecolpicker to update menu before showing/hiding
@@ -1953,34 +1961,77 @@ var ZoteroPane = new function()
return false;
}
- if (!this.itemsView) {
- Components.utils.reportError("Items view not set in ZoteroPane_Local.selectItem()");
- return false;
+ // Restore window if it's in the dock
+ if (window.windowState == Components.interfaces.nsIDOMChromeWindow.STATE_MINIMIZED) {
+ window.restore();
}
- var currentLibraryID = this.getSelectedLibraryID();
- // If in a different library
- if (item.libraryID != currentLibraryID) {
- Zotero.debug("Library ID differs; switching library");
- yield this.collectionsView.selectLibrary(item.libraryID);
- }
- // Force switch to library view
- else if (!this.collectionsView.selectedTreeRow.isLibrary() && inLibrary) {
- Zotero.debug("Told to select in library; switching to library");
- yield this.collectionsView.selectLibrary(item.libraryID);
+ if (!this.collectionsView) {
+ throw new Error("Collections view not loaded");
}
- var selected = yield this.itemsView.selectItem(itemID, expand);
- if (!selected) {
- Zotero.debug("Item was not selected; switching to library");
- yield this.collectionsView.selectLibrary(item.libraryID);
- yield this.itemsView.selectItem(itemID, expand);
- }
+ var self = this;
+ this.collectionsView.addEventListener('load', function () {
+ Zotero.spawn(function* () {
+ var currentLibraryID = self.getSelectedLibraryID();
+ // If in a different library
+ if (item.libraryID != currentLibraryID) {
+ Zotero.debug("Library ID differs; switching library");
+ yield self.collectionsView.selectLibrary(item.libraryID);
+ }
+ // Force switch to library view
+ else if (!self.collectionsView.selectedTreeRow.isLibrary() && inLibrary) {
+ Zotero.debug("Told to select in library; switching to library");
+ yield self.collectionsView.selectLibrary(item.libraryID);
+ }
+
+ self.addEventListener('itemsLoaded', function () {
+ Zotero.spawn(function* () {
+ var selected = yield self.itemsView.selectItem(itemID, expand);
+ if (!selected) {
+ Zotero.debug("Item was not selected; switching to library");
+ yield self.collectionsView.selectLibrary(item.libraryID);
+ yield self.itemsView.selectItem(itemID, expand);
+ }
+ });
+ });
+ });
+ });
+
+ // open Zotero pane
+ this.show();
return true;
});
+ this.addEventListener = function (event, listener) {
+ if (event == 'itemsLoaded') {
+ if (this.itemsView) {
+ this.itemsView.addEventListener('load', listener);
+ }
+ else {
+ if (!this._listeners.itemsLoaded) {
+ this._listeners.itemsLoaded = [];
+ }
+ this._listeners.itemsLoaded.push(listener);
+ }
+ }
+ };
+
+
+ this._runListeners = Zotero.Promise.coroutine(function* (event) {
+ if (!this._listeners[event]) {
+ return;
+ }
+
+ var listener;
+ while (listener = this._listeners[event].shift()) {
+ yield Zotero.Promise.resolve(listener());
+ }
+ });
+
+
this.getSelectedLibraryID = function () {
return this.collectionsView.getSelectedLibraryID();
}
diff --git a/components/zotero-protocol-handler.js b/components/zotero-protocol-handler.js
@@ -110,6 +110,7 @@ function ZoteroProtocolHandler() {
}
var params = {
+ objectType: 'item',
format: 'html',
sort: 'title'
};
@@ -793,47 +794,68 @@ function ZoteroProtocolHandler() {
var SelectExtension = {
newChannel: function (uri) {
return new AsyncChannel(uri, function* () {
- generateContent:try {
- var mimeType, content = '';
-
- var [path, queryString] = uri.path.substr(1).split('?');
- var [type, id] = path.split('/');
-
- // currently only able to select one item
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
- var win = wm.getMostRecentWindow("navigator:browser");
-
- // restore window if it's in the dock
- if(win.windowState == Components.interfaces.nsIDOMChromeWindow.STATE_MINIMIZED) {
- win.restore();
- }
-
- // open Zotero pane
- win.ZoteroPane.show();
-
- if(!id) return;
-
- var lkh = Zotero.Items.parseLibraryKeyHash(id);
+ var path = uri.path;
+ if (!path) {
+ return 'Invalid URL';
+ }
+ // Strip leading '/'
+ path = path.substr(1);
+ var mimeType, content = '';
+
+ var params = {
+ objectType: 'item'
+ };
+ var router = new Zotero.Router(params);
+
+ // Item within a collection or search
+ router.add('library/:scopeObject/:scopeObjectKey/items/:objectKey', function () {
+ params.libraryID = 0;
+ });
+ router.add('groups/:groupID/:scopeObject/:scopeObjectKey/items/:objectKey');
+
+ // All items
+ router.add('library/items/:objectKey', function () {
+ params.libraryID = 0;
+ });
+ router.add('groups/:groupID/items/:objectKey');
+
+ // Old-style URLs
+ router.add('item/:id', function () {
+ var lkh = Zotero.Items.parseLibraryKeyHash(params.id);
if (lkh) {
- var item = Zotero.Items.getByLibraryAndKey(lkh.libraryID, lkh.key);
+ params.libraryID = lkh.libraryID;
+ params.objectKey = lkh.key;
}
else {
- var item = Zotero.Items.get(id);
+ params.objectID = params.id;
}
- if (!item) {
- var msg = "Item " + id + " not found in zotero://select";
- Zotero.debug(msg, 2);
- Components.utils.reportError(msg);
- return;
- }
-
- win.ZoteroPane.selectItem(item.id);
+ delete params.id;
+ });
+ router.run(path);
+
+ try {
+ Zotero.API.parseParams(params);
+ var results = yield Zotero.API.getResultsFromParams(params);
}
- catch (e){
- Zotero.debug(e);
- throw (e);
+ catch (e) {
+ Zotero.debug(e, 1);
+ return e.toString();
+ }
+
+
+ if (!results.length) {
+ var msg = "Selected items not found";
+ Zotero.debug(msg, 2);
+ Components.utils.reportError(msg);
+ return;
}
+
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("navigator:browser");
+
+ // TODO: Currently only able to select one item
+ yield win.ZoteroPane.selectItem(results[0].id);
});
}
};
@@ -1226,13 +1248,18 @@ AsyncChannel.prototype = {
});
return promise;
}
+ else if (data === undefined) {
+ this.cancel(0x804b0002); // BINDING_ABORTED
+ }
else {
throw new Error("Invalid return type (" + typeof data + ") from generator passed to AsyncChannel");
}
}.bind(this))
.then(function () {
- Zotero.debug("AsyncChannel request succeeded in " + (new Date - t) + " ms");
- channel._isPending = false;
+ if (this._isPending) {
+ Zotero.debug("AsyncChannel request succeeded in " + (new Date - t) + " ms");
+ channel._isPending = false;
+ }
})
.catch(function (e) {
Zotero.debug(e, 1);