Update zotero://select to use new URLs and wait for items list load

Closes #541
This commit is contained in:
Dan Stillman 2014-09-23 01:11:41 -04:00
parent c917d9e30e
commit e2d3cc3f0d
7 changed files with 288 additions and 159 deletions

View File

@ -36,89 +36,109 @@ Zotero.API = {
getResultsFromParams: Zotero.Promise.coroutine(function* (params) { getResultsFromParams: Zotero.Promise.coroutine(function* (params) {
var results; if (!params.objectType) {
switch (params.scopeObject) { throw new Error("objectType not specified");
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');
}
// 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 + "'");
}
if (params.itemKey) {
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);
}
yield s.addCondition('blockEnd');
var ids = yield s.search();
}
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();
}
} }
if (results) { var results;
// Filter results by item key
if (params.itemKey) { if (params.objectType == 'item') {
results = results.filter(function (result) { switch (params.scopeObject) {
return params.itemKey.indexOf(result.key) !== -1; 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');
}
// 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 + "'");
}
var s = new Zotero.Search;
if (params.libraryID !== undefined) {
yield s.addCondition('libraryID', 'is', params.libraryID);
}
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 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 if (ids) { else {
// Filter results by item key throw new Error("Unsupported object type '" + params.objectType + "'");
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);
} }
return results; return results;

View File

@ -36,10 +36,11 @@
*/ */
Zotero.CollectionTreeView = function() Zotero.CollectionTreeView = function()
{ {
Zotero.LibraryTreeView.apply(this);
this.itemToSelect = null; this.itemToSelect = null;
this.hideSources = []; this.hideSources = [];
this._treebox = null;
this._highlightedRows = {}; this._highlightedRows = {};
this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'trash', 'bucket'], 'collectionTreeView'); this._unregisterID = Zotero.Notifier.registerObserver(this, ['collection', 'search', 'share', 'group', 'trash', 'bucket'], 'collectionTreeView');
this._containerState = {}; this._containerState = {};
@ -57,6 +58,8 @@ Object.defineProperty(Zotero.CollectionTreeView.prototype, "selectedTreeRow", {
} }
}); });
/* /*
* Called by the tree itself * Called by the tree itself
*/ */
@ -93,6 +96,9 @@ Zotero.CollectionTreeView.prototype.setTree = Zotero.Promise.coroutine(function*
var row = yield this.getLastViewedRow(); var row = yield this.getLastViewedRow();
this.selection.select(row); this.selection.select(row);
this._treebox.ensureRowIsVisible(row); this._treebox.ensureRowIsVisible(row);
yield this._runListeners('load');
this._initialized = true;
} }
catch (e) { catch (e) {
Zotero.debug(e, 1); Zotero.debug(e, 1);

View File

@ -35,18 +35,16 @@
* Constructor for the ItemTreeView object * Constructor for the ItemTreeView object
*/ */
Zotero.ItemTreeView = function (collectionTreeRow, sourcesOnly) { Zotero.ItemTreeView = function (collectionTreeRow, sourcesOnly) {
Zotero.LibraryTreeView.apply(this);
this.wrappedJSObject = this; this.wrappedJSObject = this;
this.rowCount = 0; this.rowCount = 0;
this.collectionTreeRow = collectionTreeRow; this.collectionTreeRow = collectionTreeRow;
this._initialized = false;
this._skipKeypress = false; this._skipKeypress = false;
this._sourcesOnly = sourcesOnly; this._sourcesOnly = sourcesOnly;
this._callbacks = [];
this._treebox = null;
this._ownerDocument = null; this._ownerDocument = null;
this._needsSort = false; this._needsSort = false;
@ -62,17 +60,6 @@ Zotero.ItemTreeView = function (collectionTreeRow, sourcesOnly) {
Zotero.ItemTreeView.prototype = Object.create(Zotero.LibraryTreeView.prototype); Zotero.ItemTreeView.prototype = Object.create(Zotero.LibraryTreeView.prototype);
Zotero.ItemTreeView.prototype.type = 'item'; 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 * Called by the tree itself
@ -251,12 +238,12 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
yield this.sort(); yield this.sort();
// Only yield if there are callbacks; otherwise, we're almost done // 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(); yield this.expandMatchParents();
//Zotero.debug('Running callbacks in itemTreeView.setTree()', 4); yield this._runListeners('load');
yield this._runCallbacks(); this._initialized = true;
if (this._ownerDocument.defaultView.ZoteroPane_Local) { if (this._ownerDocument.defaultView.ZoteroPane_Local) {
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage(); this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
@ -1805,8 +1792,7 @@ Zotero.ItemTreeView.prototype.setFilter = Zotero.Promise.coroutine(function* (ty
//this._treebox.endUpdateBatch(); //this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false; this.selection.selectEventsSuppressed = false;
//Zotero.debug('Running callbacks in itemTreeView.setFilter()', 4); yield this._runListeners('load');
yield this._runCallbacks();
}); });

View File

@ -23,8 +23,35 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
Zotero.LibraryTreeView = function () {}; Zotero.LibraryTreeView = function () {
this._initialized = false;
this._listeners = {
load: []
};
};
Zotero.LibraryTreeView.prototype = { 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 * Called while a drag is over the tree
*/ */

View File

@ -2189,6 +2189,18 @@ Zotero.SearchConditions = new function(){
noLoad: true noLoad: true
}, },
{
name: 'itemID',
operators: {
is: true,
isNot: true
},
table: 'items',
field: 'itemID',
special: true,
noLoad: true
},
{ {
name: 'annotation', name: 'annotation',
operators: { operators: {

View File

@ -32,6 +32,7 @@ var ZoteroPane = new function()
var _unserialized = false; var _unserialized = false;
this.collectionsView = false; this.collectionsView = false;
this.itemsView = false; this.itemsView = false;
this._listeners = {};
this.__defineGetter__('loaded', function () _loaded); this.__defineGetter__('loaded', function () _loaded);
//Privileged methods //Privileged methods
@ -1099,7 +1100,7 @@ var ZoteroPane = new function()
this.onCollectionSelected = Zotero.Promise.coroutine(function* () { this.onCollectionSelected = Zotero.Promise.coroutine(function* () {
var collectionTreeRow = this.getCollectionTreeRow(); var collectionTreeRow = this.getCollectionTreeRow();
if (this.itemsView.collectionTreeRow == collectionTreeRow) { if (this.itemsView && this.itemsView.collectionTreeRow == collectionTreeRow) {
Zotero.debug("Collection selection hasn't changed"); Zotero.debug("Collection selection hasn't changed");
return; return;
} }
@ -1167,7 +1168,14 @@ var ZoteroPane = new function()
this.itemsView.onError = function () { this.itemsView.onError = function () {
ZoteroPane_Local.displayErrorMessage(); 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; document.getElementById('zotero-items-tree').view = this.itemsView;
// Add events to treecolpicker to update menu before showing/hiding // Add events to treecolpicker to update menu before showing/hiding
@ -1953,34 +1961,77 @@ var ZoteroPane = new function()
return false; return false;
} }
if (!this.itemsView) { // Restore window if it's in the dock
Components.utils.reportError("Items view not set in ZoteroPane_Local.selectItem()"); if (window.windowState == Components.interfaces.nsIDOMChromeWindow.STATE_MINIMIZED) {
return false; window.restore();
} }
var currentLibraryID = this.getSelectedLibraryID(); if (!this.collectionsView) {
// If in a different library throw new Error("Collections view not loaded");
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);
} }
var selected = yield this.itemsView.selectItem(itemID, expand); var self = this;
if (!selected) { this.collectionsView.addEventListener('load', function () {
Zotero.debug("Item was not selected; switching to library"); Zotero.spawn(function* () {
yield this.collectionsView.selectLibrary(item.libraryID); var currentLibraryID = self.getSelectedLibraryID();
yield this.itemsView.selectItem(itemID, expand); // 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; 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 () { this.getSelectedLibraryID = function () {
return this.collectionsView.getSelectedLibraryID(); return this.collectionsView.getSelectedLibraryID();
} }

View File

@ -110,6 +110,7 @@ function ZoteroProtocolHandler() {
} }
var params = { var params = {
objectType: 'item',
format: 'html', format: 'html',
sort: 'title' sort: 'title'
}; };
@ -793,47 +794,68 @@ function ZoteroProtocolHandler() {
var SelectExtension = { var SelectExtension = {
newChannel: function (uri) { newChannel: function (uri) {
return new AsyncChannel(uri, function* () { return new AsyncChannel(uri, function* () {
generateContent:try { var path = uri.path;
var mimeType, content = ''; if (!path) {
return 'Invalid URL';
var [path, queryString] = uri.path.substr(1).split('?'); }
var [type, id] = path.split('/'); // Strip leading '/'
path = path.substr(1);
// currently only able to select one item var mimeType, content = '';
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator); var params = {
var win = wm.getMostRecentWindow("navigator:browser"); objectType: 'item'
};
// restore window if it's in the dock var router = new Zotero.Router(params);
if(win.windowState == Components.interfaces.nsIDOMChromeWindow.STATE_MINIMIZED) {
win.restore(); // Item within a collection or search
} router.add('library/:scopeObject/:scopeObjectKey/items/:objectKey', function () {
params.libraryID = 0;
// open Zotero pane });
win.ZoteroPane.show(); router.add('groups/:groupID/:scopeObject/:scopeObjectKey/items/:objectKey');
if(!id) return; // All items
router.add('library/items/:objectKey', function () {
var lkh = Zotero.Items.parseLibraryKeyHash(id); 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) { if (lkh) {
var item = Zotero.Items.getByLibraryAndKey(lkh.libraryID, lkh.key); params.libraryID = lkh.libraryID;
params.objectKey = lkh.key;
} }
else { else {
var item = Zotero.Items.get(id); params.objectID = params.id;
} }
if (!item) { delete params.id;
var msg = "Item " + id + " not found in zotero://select"; });
Zotero.debug(msg, 2); router.run(path);
Components.utils.reportError(msg);
return; try {
} Zotero.API.parseParams(params);
var results = yield Zotero.API.getResultsFromParams(params);
win.ZoteroPane.selectItem(item.id);
} }
catch (e){ catch (e) {
Zotero.debug(e); Zotero.debug(e, 1);
throw (e); 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; return promise;
} }
else if (data === undefined) {
this.cancel(0x804b0002); // BINDING_ABORTED
}
else { else {
throw new Error("Invalid return type (" + typeof data + ") from generator passed to AsyncChannel"); throw new Error("Invalid return type (" + typeof data + ") from generator passed to AsyncChannel");
} }
}.bind(this)) }.bind(this))
.then(function () { .then(function () {
Zotero.debug("AsyncChannel request succeeded in " + (new Date - t) + " ms"); if (this._isPending) {
channel._isPending = false; Zotero.debug("AsyncChannel request succeeded in " + (new Date - t) + " ms");
channel._isPending = false;
}
}) })
.catch(function (e) { .catch(function (e) {
Zotero.debug(e, 1); Zotero.debug(e, 1);