Update zotero:// extensions (report, timeline, etc.) for async DB, and more
- Protocol handler extensions can now handle promises and can also make data available as it's ready instead of all at once (e.g., reports now output one entry at a time) - zotero:// URL syntaxes are now more consistent and closer to the web API (old URLs should work, but some may currently be broken) Also: - Code to generate server API, currently available for testing via zotero://data URLs but eventually moving to HTTP -- zotero://data URLs match web API URLs, with a different prefix for the personal library (/library vs. /users/12345) - Miscellaneous fixes to data objects Under the hood: - Extensions now return an AsyncChannel, which is an nsIChannel implementation that takes a promise-yielding generator that returns a string, nsIAsyncInputStream, or file that will be used for the channel's data - New function Zotero.Utilities.Internal.getAsyncInputStream() takes a generator that yields either promises or strings and returns an async input stream filled with the yielded strings - Zotero.Router parsers URLs and extract parameters - Zotero.Item.toResponseJSON()
This commit is contained in:
parent
c5ee3651fe
commit
755ead2119
26 changed files with 1731 additions and 900 deletions
|
@ -44,7 +44,11 @@ var ZoteroAdvancedSearch = new function() {
|
|||
|
||||
_searchBox.onLibraryChange = this.onLibraryChange;
|
||||
var io = window.arguments[0];
|
||||
_searchBox.search = io.dataIn.search;
|
||||
|
||||
io.dataIn.search.loadPrimaryData()
|
||||
.then(function () {
|
||||
_searchBox.search = io.dataIn.search;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -62,6 +66,7 @@ var ZoteroAdvancedSearch = new function() {
|
|||
// Hack to create a condition for the search's library --
|
||||
// this logic should really go in the search itself instead of here
|
||||
// and in collectionTreeView.js
|
||||
yield search.loadPrimaryData();
|
||||
var conditions = search.getSearchConditions();
|
||||
if (!conditions.some(function (condition) condition.condition == 'libraryID')) {
|
||||
yield search.addCondition('libraryID', 'is', _searchBox.search.libraryID);
|
||||
|
|
|
@ -176,8 +176,9 @@
|
|||
if (this.onLibraryChange) {
|
||||
this.onLibraryChange(libraryID);
|
||||
}
|
||||
|
||||
this.searchRef.libraryID = libraryID;
|
||||
if (!this.searchRef.id) {
|
||||
this.searchRef.libraryID = libraryID;
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
|
|
|
@ -25,47 +25,45 @@
|
|||
|
||||
|
||||
var Zotero_Report_Interface = new function() {
|
||||
this.loadCollectionReport = loadCollectionReport;
|
||||
this.loadItemReport = loadItemReport;
|
||||
this.loadItemReportByIds = loadItemReportByIds;
|
||||
|
||||
|
||||
/*
|
||||
* Load a report for the currently selected collection
|
||||
*/
|
||||
function loadCollectionReport(event) {
|
||||
var queryString = '';
|
||||
|
||||
var col = ZoteroPane_Local.getSelectedCollection();
|
||||
this.loadCollectionReport = function (event) {
|
||||
var sortColumn = ZoteroPane_Local.getSortField();
|
||||
var sortDirection = ZoteroPane_Local.getSortDirection();
|
||||
if (sortColumn != 'title' || sortDirection != 'ascending') {
|
||||
queryString = '?sort=' + sortColumn + (sortDirection == 'ascending' ? '' : '/d');
|
||||
var queryString = '?sort=' + sortColumn
|
||||
+ '&direction=' + (sortDirection == 'ascending' ? 'asc' : 'desc');
|
||||
|
||||
var url = 'zotero://report/';
|
||||
|
||||
var source = ZoteroPane_Local.getSelectedCollection();
|
||||
if (!source) {
|
||||
source = ZoteroPane_Local.getSelectedSavedSearch();
|
||||
}
|
||||
if (!source) {
|
||||
throw new Error('No collection currently selected');
|
||||
}
|
||||
|
||||
if (col) {
|
||||
ZoteroPane_Local.loadURI('zotero://report/collection/'
|
||||
+ Zotero.Collections.getLibraryKeyHash(col)
|
||||
+ '/html/report.html' + queryString, event);
|
||||
return;
|
||||
url += Zotero.API.getLibraryPrefix(source.libraryID) + '/';
|
||||
|
||||
if (source instanceof Zotero.Collection) {
|
||||
url += 'collections/' + source.key;
|
||||
}
|
||||
else {
|
||||
url += 'searches/' + source.key;
|
||||
}
|
||||
|
||||
var s = ZoteroPane_Local.getSelectedSavedSearch();
|
||||
if (s) {
|
||||
ZoteroPane_Local.loadURI('zotero://report/search/'
|
||||
+ Zotero.Searches.getLibraryKeyHash(s)
|
||||
+ '/html/report.html' + queryString, event);
|
||||
return;
|
||||
}
|
||||
url += '/items/report.html' + queryString;
|
||||
|
||||
throw ('No collection currently selected');
|
||||
ZoteroPane_Local.loadURI(url, event);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Load a report for the currently selected items
|
||||
*/
|
||||
function loadItemReport(event) {
|
||||
this.loadItemReport = function (event) {
|
||||
var libraryID = ZoteroPane_Local.getSelectedLibraryID();
|
||||
var items = ZoteroPane_Local.getSelectedItems();
|
||||
|
||||
if (!items || !items.length) {
|
||||
|
@ -77,18 +75,8 @@ var Zotero_Report_Interface = new function() {
|
|||
keyHashes.push(Zotero.Items.getLibraryKeyHash(item));
|
||||
}
|
||||
|
||||
ZoteroPane_Local.loadURI('zotero://report/items/' + keyHashes.join('-') + '/html/report.html', event);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Load a report for the specified items
|
||||
*/
|
||||
function loadItemReportByIds(ids) {
|
||||
if (!ids || !ids.length) {
|
||||
throw ('No itemIDs provided to loadItemReportByIds()');
|
||||
}
|
||||
|
||||
ZoteroPane_Local.loadURI('zotero://report/items/' + ids.join('-') + '/html/report.html');
|
||||
var url = 'zotero://report/' + Zotero.API.getLibraryPrefix(libraryID) + '/items/report.html'
|
||||
+ '?itemKey=' + items.map(item => item.key).join(',');
|
||||
ZoteroPane_Local.loadURI(url, event);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,25 +30,23 @@ var Zotero_Timeline_Interface = new function() {
|
|||
*/
|
||||
this.loadTimeline = function () {
|
||||
var uri = 'zotero://timeline/';
|
||||
|
||||
var col = ZoteroPane_Local.getSelectedCollection();
|
||||
|
||||
if (col) {
|
||||
ZoteroPane_Local.loadURI(uri + 'collection/' + Zotero.Collections.getLibraryKeyHash(col));
|
||||
return;
|
||||
uri += Zotero.API.getLibraryPrefix(col.libraryID) + '/collections/' + col.key;
|
||||
}
|
||||
|
||||
var s = ZoteroPane_Local.getSelectedSavedSearch();
|
||||
if (s) {
|
||||
ZoteroPane_Local.loadURI(uri + 'search/' + Zotero.Searches.getLibraryKeyHash(s));
|
||||
return;
|
||||
else {
|
||||
var s = ZoteroPane_Local.getSelectedSavedSearch();
|
||||
if (s) {
|
||||
uri += Zotero.API.getLibraryPrefix(s.libraryID) + '/searches/' + s.key;
|
||||
}
|
||||
else {
|
||||
let libraryID = ZoteroPane_Local.getSelectedLibraryID();
|
||||
if (libraryID) {
|
||||
uri += Zotero.API.getLibraryPrefix(libraryID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var l = ZoteroPane_Local.getSelectedLibraryID();
|
||||
if (l) {
|
||||
ZoteroPane_Local.loadURI(uri + 'library/' + l);
|
||||
return;
|
||||
}
|
||||
|
||||
ZoteroPane_Local.loadURI(uri);
|
||||
}
|
||||
}
|
||||
|
|
191
chrome/content/zotero/xpcom/api.js
Normal file
191
chrome/content/zotero/xpcom/api.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2014 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.API = {
|
||||
parseParams: function (params) {
|
||||
if (params.groupID) {
|
||||
params.libraryID = Zotero.Groups.getLibraryIDFromGroupID(params.groupID);
|
||||
}
|
||||
|
||||
if (typeof params.itemKey == 'string') {
|
||||
params.itemKey = params.itemKey.split(',');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
getResultsFromParams: Zotero.Promise.coroutine(function* (params) {
|
||||
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');
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
return results;
|
||||
}),
|
||||
|
||||
|
||||
getLibraryPrefix: function (libraryID) {
|
||||
return libraryID
|
||||
? 'groups/' + Zotero.Groups.getGroupIDFromLibraryID(libraryID)
|
||||
: 'library';
|
||||
}
|
||||
};
|
||||
|
||||
Zotero.API.Data = {
|
||||
/**
|
||||
* Parse a relative URI path and return parameters for the request
|
||||
*/
|
||||
parsePath: function (path) {
|
||||
var params = {};
|
||||
var router = new Zotero.Router(params);
|
||||
|
||||
// Top-level objects
|
||||
router.add('library/:controller/top', function () {
|
||||
params.libraryID = 0;
|
||||
params.subset = 'top';
|
||||
});
|
||||
router.add('groups/:groupID/:controller/top', function () {
|
||||
params.subset = 'top';
|
||||
});
|
||||
|
||||
router.add('library/:scopeObject/:scopeObjectKey/items/:objectKey/:subset', function () {
|
||||
params.libraryID = 0;
|
||||
params.controller = 'items';
|
||||
});
|
||||
router.add('groups/:groupID/:scopeObject/:scopeObjectKey/items/:objectKey/:subset', function () {
|
||||
params.controller = 'items';
|
||||
});
|
||||
|
||||
// All objects
|
||||
router.add('library/:controller', function () {
|
||||
params.libraryID = 0;
|
||||
});
|
||||
router.add('groups/:groupID/:controller', function () {});
|
||||
|
||||
var parsed = router.run(path);
|
||||
if (!parsed || !params.controller) {
|
||||
throw new Zotero.Router.InvalidPathException(path);
|
||||
}
|
||||
|
||||
if (params.groupID) {
|
||||
params.libraryID = Zotero.Groups.getLibraryIDFromGroupID(params.groupID);
|
||||
}
|
||||
Zotero.Router.Utilities.convertControllerToObjectType(params);
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
|
||||
getGenerator: function (path) {
|
||||
var params = this.parsePath(path);
|
||||
//Zotero.debug(params);
|
||||
|
||||
return Zotero.DataObjectUtilities.getClassForObjectType(params.objectType)
|
||||
.apiDataGenerator(params);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
|
@ -121,8 +121,7 @@ Zotero.Collection.prototype.loadPrimaryData = Zotero.Promise.coroutine(function*
|
|||
var key = this._key;
|
||||
var libraryID = this._libraryID;
|
||||
|
||||
// Should be same as query in Zotero.Collections, just with collectionID
|
||||
var sql = Zotero.Collections._getPrimaryDataSQL();
|
||||
var sql = Zotero.Collections.getPrimaryDataSQL();
|
||||
if (id) {
|
||||
sql += " AND O.collectionID=?";
|
||||
var params = id;
|
||||
|
|
|
@ -204,7 +204,7 @@ Zotero.Collections = new function() {
|
|||
}
|
||||
|
||||
|
||||
this._getPrimaryDataSQL = function () {
|
||||
this.getPrimaryDataSQL = function () {
|
||||
// This should be the same as the query in Zotero.Collection.load(),
|
||||
// just without a specific collectionID
|
||||
return "SELECT "
|
||||
|
|
|
@ -53,6 +53,12 @@ Zotero.DataObjectUtilities = {
|
|||
return key;
|
||||
},
|
||||
|
||||
|
||||
getObjectTypeSingular: function (objectTypePlural) {
|
||||
return objectTypePlural.replace(/(s|es)$/, '');
|
||||
},
|
||||
|
||||
|
||||
"getObjectTypePlural": function getObjectTypePlural(objectType) {
|
||||
return objectType == 'search' ? 'searches' : objectType + 's';
|
||||
},
|
||||
|
|
|
@ -186,17 +186,38 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated - use .libraryKey
|
||||
*/
|
||||
this.makeLibraryKeyHash = function (libraryID, key) {
|
||||
Zotero.debug("WARNING: Zotero.DataObjects.makeLibraryKeyHash() is deprecated -- use obj.libraryKey instead");
|
||||
return libraryID + '_' + key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated - use .libraryKey
|
||||
*/
|
||||
this.getLibraryKeyHash = function (obj) {
|
||||
Zotero.debug("WARNING: Zotero.DataObjects.getLibraryKeyHash() is deprecated -- use obj.libraryKey instead");
|
||||
return this.makeLibraryKeyHash(obj.libraryID, obj.key);
|
||||
}
|
||||
|
||||
|
||||
this.parseLibraryKey = function (libraryKey) {
|
||||
var [libraryID, key] = libraryKey.split('/');
|
||||
return {
|
||||
libraryID: parseInt(libraryID),
|
||||
key: key
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated - Use Zotero.DataObjects.parseLibraryKey()
|
||||
*/
|
||||
this.parseLibraryKeyHash = function (libraryKey) {
|
||||
Zotero.debug("WARNING: Zotero.DataObjects.parseLibraryKeyHash() is deprecated -- use .parseLibraryKey() instead");
|
||||
var [libraryID, key] = libraryKey.split('_');
|
||||
if (!key) {
|
||||
return false;
|
||||
|
@ -254,7 +275,8 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
|
|||
|
||||
|
||||
this.getIDFromLibraryAndKey = function (libraryID, key) {
|
||||
return this._objectIDs[libraryID][key] ? this._objectIDs[libraryID][key] : false;
|
||||
return (this._objectIDs[libraryID] && this._objectIDs[libraryID][key])
|
||||
? this._objectIDs[libraryID][key] : false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -531,8 +553,8 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
|
|||
return loaded;
|
||||
}
|
||||
|
||||
// _getPrimaryDataSQL() should use "O" for the primary table alias
|
||||
var sql = this._getPrimaryDataSQL();
|
||||
// getPrimaryDataSQL() should use "O" for the primary table alias
|
||||
var sql = this.getPrimaryDataSQL();
|
||||
var params = [];
|
||||
if (libraryID !== false) {
|
||||
sql += ' AND O.libraryID=?';
|
||||
|
|
|
@ -293,7 +293,7 @@ Zotero.Item.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (relo
|
|||
if (!columns.length) {
|
||||
return;
|
||||
}
|
||||
// This should match Zotero.Items._getPrimaryDataSQL(), but without
|
||||
// This should match Zotero.Items.getPrimaryDataSQL(), but without
|
||||
// necessarily including all columns
|
||||
var sql = "SELECT " + columns.join(", ") + Zotero.Items.primaryDataSQLFrom;
|
||||
if (id) {
|
||||
|
@ -923,9 +923,9 @@ Zotero.Item.prototype.getCreator = function (pos) {
|
|||
* @param {Integer} pos
|
||||
* @return {Object|Boolean} The API JSON creator data at the given position, or FALSE if none
|
||||
*/
|
||||
Zotero.Item.prototype.getCreatorsJSON = function (pos) {
|
||||
Zotero.Item.prototype.getCreatorJSON = function (pos) {
|
||||
this._requireData('creators');
|
||||
return this._creators[pos] ? Zotero.Creators.internalToAPIJSON(this._creators[pos]) : false;
|
||||
return this._creators[pos] ? Zotero.Creators.internalToJSON(this._creators[pos]) : false;
|
||||
}
|
||||
|
||||
|
||||
|
@ -954,7 +954,7 @@ Zotero.Item.prototype.getCreators = function () {
|
|||
*/
|
||||
Zotero.Item.prototype.getCreatorsAPIData = function () {
|
||||
this._requireData('creators');
|
||||
return this._creators.map(function (data) Zotero.Creators.internalToAPIJSON(data));
|
||||
return this._creators.map(function (data) Zotero.Creators.internalToJSON(data));
|
||||
}
|
||||
|
||||
|
||||
|
@ -2075,7 +2075,7 @@ Zotero.Item.prototype.numAttachments = function(includeTrashed) {
|
|||
/**
|
||||
* Get an nsILocalFile for the attachment, or false for invalid paths
|
||||
*
|
||||
* This no longer checks whether a file exists
|
||||
* Note: This no longer checks whether a file exists
|
||||
*
|
||||
* @return {nsILocalFile|false} An nsIFile, or false for invalid paths
|
||||
*/
|
||||
|
@ -4150,12 +4150,8 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options, patc
|
|||
}
|
||||
|
||||
var obj = {};
|
||||
if (options && options.includeKey) {
|
||||
obj.itemKey = this.key;
|
||||
}
|
||||
if (options && options.includeVersion) {
|
||||
obj.itemVersion = this.version;
|
||||
}
|
||||
obj.itemKey = this.key;
|
||||
obj.itemVersion = this.version;
|
||||
obj.itemType = Zotero.ItemTypes.getName(this.itemTypeID);
|
||||
|
||||
// Fields
|
||||
|
@ -4170,7 +4166,7 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options, patc
|
|||
// Creators
|
||||
if (this.isRegularItem()) {
|
||||
yield this.loadCreators()
|
||||
obj.creators = this.getCreators();
|
||||
obj.creators = this.getCreatorsAPIData();
|
||||
}
|
||||
else {
|
||||
var parent = this.parentKey;
|
||||
|
@ -4200,14 +4196,8 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options, patc
|
|||
obj.tags = [];
|
||||
yield this.loadTags()
|
||||
var tags = yield this.getTags();
|
||||
for each (let tag in tags) {
|
||||
let tagObj = {};
|
||||
tagObj.tag = tag.name;
|
||||
let type = tag.type;
|
||||
if (type != 0 || mode == 'full') {
|
||||
tagObj.type = tag.type;
|
||||
}
|
||||
obj.tags.push(tagObj);
|
||||
for (let i=0; i<tags.length; i++) {
|
||||
obj.tags.push(tags[i]);
|
||||
}
|
||||
|
||||
// Collections
|
||||
|
@ -4248,10 +4238,8 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options, patc
|
|||
obj.deleted = deleted;
|
||||
}
|
||||
|
||||
if (options && options.includeDate) {
|
||||
obj.dateAdded = this.dateAdded;
|
||||
obj.dateModified = this.dateModified;
|
||||
}
|
||||
obj.dateAdded = Zotero.Date.sqlToISO8601(this.dateAdded);
|
||||
obj.dateModified = Zotero.Date.sqlToISO8601(this.dateModified);
|
||||
|
||||
if (mode == 'patch') {
|
||||
for (let i in patchBase) {
|
||||
|
@ -4277,6 +4265,34 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options, patc
|
|||
});
|
||||
|
||||
|
||||
Zotero.Item.prototype.toResponseJSON = Zotero.Promise.coroutine(function* (options, patchBase) {
|
||||
var json = {
|
||||
key: this.key,
|
||||
version: this.version,
|
||||
meta: {},
|
||||
data: yield this.toJSON(options, patchBase)
|
||||
};
|
||||
|
||||
// TODO: library block?
|
||||
|
||||
// creatorSummary
|
||||
var firstCreator = this.getField('firstCreator');
|
||||
if (firstCreator) {
|
||||
json.meta.creatorSummary = firstCreator;
|
||||
}
|
||||
// parsedDate
|
||||
var parsedDate = Zotero.Date.multipartToSQL(this.getField('date', true, true));
|
||||
if (parsedDate) {
|
||||
// 0000?
|
||||
json.meta.parsedDate = parsedDate;
|
||||
}
|
||||
// numChildren
|
||||
if (this.isRegularItem()) {
|
||||
json.meta.numChildren = this.numChildren();
|
||||
}
|
||||
return json;
|
||||
})
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
|
@ -154,6 +154,67 @@ Zotero.Items = new function() {
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* Return item data in web API format
|
||||
*
|
||||
* var data = Zotero.Items.getAPIData(0, 'collections/NF3GJ38A/items');
|
||||
*
|
||||
* @param {Number} libraryID
|
||||
* @param {String} [apiPath='items'] - Web API style
|
||||
* @return {Promise<String>}.
|
||||
*/
|
||||
this.getAPIData = Zotero.Promise.coroutine(function* (libraryID, apiPath) {
|
||||
var gen = this.getAPIDataGenerator(...arguments);
|
||||
var data = "";
|
||||
while (true) {
|
||||
var result = gen.next();
|
||||
if (result.done) {
|
||||
break;
|
||||
}
|
||||
var val = yield result.value;
|
||||
if (typeof val == 'string') {
|
||||
data += val;
|
||||
}
|
||||
else if (val === undefined) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid return value from generator");
|
||||
}
|
||||
}
|
||||
return data;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Zotero.Utilities.Internal.getAsyncInputStream-compatible generator that yields item data
|
||||
* in web API format as strings
|
||||
*
|
||||
* @param {Object} params - Request parameters from Zotero.API.parsePath()
|
||||
*/
|
||||
this.apiDataGenerator = function* (params) {
|
||||
Zotero.debug(params);
|
||||
var s = new Zotero.Search;
|
||||
yield s.addCondition('libraryID', 'is', params.libraryID);
|
||||
if (params.scopeObject == 'collections') {
|
||||
yield s.addCondition('collection', 'is', params.libraryID + '/' + params.scopeObjectKey);
|
||||
}
|
||||
yield s.addCondition('title', 'contains', 'test');
|
||||
var ids = yield s.search();
|
||||
|
||||
yield '[\n';
|
||||
|
||||
for (let i=0; i<ids.length; i++) {
|
||||
let prefix = i > 0 ? ',\n' : '';
|
||||
let item = yield this.getAsync(ids[i], { noCache: true });
|
||||
var json = yield item.toResponseJSON();
|
||||
yield prefix + JSON.stringify(json, null, 4);
|
||||
}
|
||||
|
||||
yield '\n]';
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Create a new item with optional metadata and pass back the primary reference
|
||||
*
|
||||
|
@ -621,8 +682,7 @@ Zotero.Items = new function() {
|
|||
});
|
||||
|
||||
|
||||
this._getPrimaryDataSQL = function () {
|
||||
// This should match Zotero.Item.loadPrimaryData, but with all possible columns
|
||||
this.getPrimaryDataSQL = function () {
|
||||
return "SELECT "
|
||||
+ Object.keys(this._primaryDataSQLParts).map((val) => this._primaryDataSQLParts[val]).join(', ')
|
||||
+ this.primaryDataSQLFrom;
|
||||
|
|
|
@ -845,7 +845,7 @@ Zotero.Tags = new function() {
|
|||
}
|
||||
|
||||
|
||||
this._getPrimaryDataSQL = function () {
|
||||
this.getPrimaryDataSQL = function () {
|
||||
// This should be the same as the query in Zotero.Tag.load(),
|
||||
// just without a specific tagID
|
||||
return "SELECT * FROM tags O WHERE 1";
|
||||
|
|
|
@ -544,6 +544,29 @@ Zotero.Date = new function(){
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
this.sqlToISO8601 = function (sqlDate) {
|
||||
var date = sqlDate.substr(0, 10);
|
||||
var matches = date.match(/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})/);
|
||||
if (!matches) {
|
||||
return false;
|
||||
}
|
||||
date = matches[1];
|
||||
// Drop parts for reduced precision
|
||||
if (matches[2] !== "00") {
|
||||
date += "-" + matches[2];
|
||||
if (matches[3] !== "00") {
|
||||
date += "-" + matches[3];
|
||||
}
|
||||
}
|
||||
var time = sqlDate.substr(11);
|
||||
// TODO: validate times
|
||||
if (time) {
|
||||
date += "T" + time + "Z";
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
function strToMultipart(str){
|
||||
if (!str){
|
||||
return '';
|
||||
|
|
|
@ -189,7 +189,7 @@ Zotero.File = new function(){
|
|||
* @param {nsIURI|nsIFile|string spec|string path|nsIChannel|nsIInputStream} source The source to read
|
||||
* @param {String} [charset] The character set; defaults to UTF-8
|
||||
* @param {Integer} [maxLength] Maximum length to fetch, in bytes
|
||||
* @return {Promise} A Q promise that is resolved with the contents of the file
|
||||
* @return {Promise} A promise that is resolved with the contents of the file
|
||||
*/
|
||||
this.getContentsAsync = function (source, charset, maxLength) {
|
||||
Zotero.debug("Getting contents of " + source);
|
||||
|
@ -243,7 +243,7 @@ Zotero.File = new function(){
|
|||
*
|
||||
* @param {nsIURI|nsIFile|string spec|nsIChannel|nsIInputStream} source The source to read
|
||||
* @param {Integer} [maxLength] Maximum length to fetch, in bytes (unimplemented)
|
||||
* @return {Promise} A Q promise that is resolved with the contents of the source
|
||||
* @return {Promise} A promise that is resolved with the contents of the source
|
||||
*/
|
||||
this.getBinaryContentsAsync = function (source, maxLength) {
|
||||
var deferred = Zotero.Promise.defer();
|
||||
|
|
|
@ -24,57 +24,54 @@
|
|||
*/
|
||||
|
||||
|
||||
Zotero.Report = new function() {
|
||||
this.generateHTMLDetails = generateHTMLDetails;
|
||||
this.generateHTMLList = generateHTMLList;
|
||||
|
||||
var escapeXML = function (str) {
|
||||
str = str.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '\u2B1A');
|
||||
return Zotero.Utilities.htmlSpecialChars(str);
|
||||
}
|
||||
|
||||
|
||||
function generateHTMLDetails(items, combineChildItems) {
|
||||
var content = '<!DOCTYPE html>\n';
|
||||
content += '<html>\n';
|
||||
content += '<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n';
|
||||
content += '<title>' + Zotero.getString('report.title.default') + '</title>\n';
|
||||
content += '<link rel="stylesheet" type="text/css" href="zotero://report/detail.css"/>\n';
|
||||
content += '<link rel="stylesheet" type="text/css" media="screen,projection" href="zotero://report/detail_screen.css"/>\n';
|
||||
content += '<link rel="stylesheet" type="text/css" media="print" href="zotero://report/detail_print.css"/>\n';
|
||||
content += '</head>\n\n<body>\n';
|
||||
Zotero.Report = {};
|
||||
|
||||
Zotero.Report.HTML = new function () {
|
||||
this.listGenerator = function* (items, combineChildItems) {
|
||||
yield '<!DOCTYPE html>\n'
|
||||
+ '<html>\n'
|
||||
+ ' <head>\n'
|
||||
+ ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n'
|
||||
+ ' <title>' + Zotero.getString('report.title.default') + '</title>\n'
|
||||
+ ' <link rel="stylesheet" type="text/css" href="zotero://report/detail.css"/>\n'
|
||||
+ ' <link rel="stylesheet" type="text/css" media="screen,projection" href="zotero://report/detail_screen.css"/>\n'
|
||||
+ ' <link rel="stylesheet" type="text/css" media="print" href="zotero://report/detail_print.css"/>\n'
|
||||
+ ' </head>\n'
|
||||
+ ' <body>\n'
|
||||
+ ' <ul class="report' + (combineChildItems ? ' combineChildItems' : '') + '">';
|
||||
|
||||
content += '<ul class="report' + (combineChildItems ? ' combineChildItems' : '') + '">\n';
|
||||
for each(var arr in items) {
|
||||
content += '\n<li id="i' + arr.itemID + '" class="item ' + arr.itemType + '">\n';
|
||||
for (let i=0; i<items.length; i++) {
|
||||
let obj = items[i];
|
||||
|
||||
if (arr.title) {
|
||||
let content = '\n\t\t\t<li id="item_' + obj.itemKey + '" class="item ' + obj.itemType + '">\n';
|
||||
|
||||
if (obj.title) {
|
||||
// Top-level item matched search, so display title
|
||||
if (arr.reportSearchMatch) {
|
||||
content += '<h2>' + escapeXML(arr.title) + '</h2>\n';
|
||||
if (obj.reportSearchMatch) {
|
||||
content += '\t\t\t<h2>' + escapeXML(obj.title) + '</h2>\n';
|
||||
}
|
||||
// Non-matching parent, so display "Parent Item: [Title]"
|
||||
else {
|
||||
content += '<h2 class="parentItem">' + escapeXML(Zotero.getString('report.parentItem'))
|
||||
+ ' <span class="title">' + escapeXML(arr.title) + '</span></h2>';
|
||||
content += '\t\t\t<h2 class="parentItem">' + escapeXML(Zotero.getString('report.parentItem'))
|
||||
+ ' <span class="title">' + escapeXML(obj.title) + '</span></h2>\n';
|
||||
}
|
||||
}
|
||||
|
||||
// If parent matches search, display parent item metadata table and tags
|
||||
if (arr.reportSearchMatch) {
|
||||
content += _generateMetadataTable(arr);
|
||||
if (obj.reportSearchMatch) {
|
||||
content += _generateMetadataTable(obj);
|
||||
|
||||
content += _generateTagsList(arr);
|
||||
content += _generateTagsList(obj);
|
||||
|
||||
// Independent note
|
||||
if (arr['note']) {
|
||||
content += '\n';
|
||||
if (obj['note']) {
|
||||
content += '\n\t\t\t';
|
||||
|
||||
// If not valid XML, display notes with entities encoded
|
||||
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
|
||||
.createInstance(Components.interfaces.nsIDOMParser);
|
||||
var doc = parser.parseFromString('<div>'
|
||||
+ arr.note
|
||||
+ obj.note
|
||||
// isn't valid in HTML
|
||||
.replace(/ /g, " ")
|
||||
// Strip control characters (for notes that were
|
||||
|
@ -83,26 +80,26 @@ Zotero.Report = new function() {
|
|||
+ '</div>', "application/xml");
|
||||
if (doc.documentElement.tagName == 'parsererror') {
|
||||
Zotero.debug(doc.documentElement.textContent, 2);
|
||||
content += '<p class="plaintext">' + escapeXML(arr.note) + '</p>\n';
|
||||
content += '<p class="plaintext">' + escapeXML(obj.note) + '</p>\n';
|
||||
}
|
||||
// Otherwise render markup normally
|
||||
else {
|
||||
content += arr.note + '\n';
|
||||
content += obj.note + '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Children
|
||||
if (arr.reportChildren) {
|
||||
if (obj.reportChildren) {
|
||||
// Child notes
|
||||
if (arr.reportChildren.notes.length) {
|
||||
if (obj.reportChildren.notes.length) {
|
||||
// Only display "Notes:" header if parent matches search
|
||||
if (arr.reportSearchMatch) {
|
||||
content += '<h3 class="notes">' + escapeXML(Zotero.getString('report.notes')) + '</h3>\n';
|
||||
if (obj.reportSearchMatch) {
|
||||
content += '\t\t\t\t<h3 class="notes">' + escapeXML(Zotero.getString('report.notes')) + '</h3>\n';
|
||||
}
|
||||
content += '<ul class="notes">\n';
|
||||
for each(var note in arr.reportChildren.notes) {
|
||||
content += '<li id="i' + note.itemID + '">\n';
|
||||
content += '\t\t\t\t<ul class="notes">\n';
|
||||
for each(var note in obj.reportChildren.notes) {
|
||||
content += '\t\t\t\t\t<li id="item_' + note.itemKey + '">\n';
|
||||
|
||||
// If not valid XML, display notes with entities encoded
|
||||
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
|
||||
|
@ -126,114 +123,117 @@ Zotero.Report = new function() {
|
|||
// Child note tags
|
||||
content += _generateTagsList(note);
|
||||
|
||||
content += '</li>\n';
|
||||
content += '\t\t\t\t\t</li>\n';
|
||||
}
|
||||
content += '</ul>\n';
|
||||
content += '\t\t\t\t</ul>\n';
|
||||
}
|
||||
|
||||
// Chid attachments
|
||||
content += _generateAttachmentsList(arr.reportChildren);
|
||||
content += _generateAttachmentsList(obj.reportChildren);
|
||||
}
|
||||
|
||||
// Related
|
||||
if (arr.reportSearchMatch && arr.related && arr.related.length) {
|
||||
content += '<h3 class="related">' + escapeXML(Zotero.getString('itemFields.related')) + '</h3>\n';
|
||||
content += '<ul class="related">\n';
|
||||
var relateds = Zotero.Items.get(arr.related);
|
||||
for each(var related in relateds) {
|
||||
content += '<li id="i' + related.getID() + '">';
|
||||
content += escapeXML(related.getDisplayTitle());
|
||||
content += '</li>\n';
|
||||
// Related items
|
||||
if (obj.reportSearchMatch && Zotero.Relations.relatedItemPredicate in obj.relations) {
|
||||
content += '\t\t\t\t<h3 class="related">' + escapeXML(Zotero.getString('itemFields.related')) + '</h3>\n';
|
||||
content += '\t\t\t\t<ul class="related">\n';
|
||||
var rels = obj.relations[Zotero.Relations.relatedItemPredicate];
|
||||
// TEMP
|
||||
if (!Array.isArray(rels)) {
|
||||
rels = [rels];
|
||||
}
|
||||
content += '</ul>\n';
|
||||
for (let i=0; i<rels.length; i++) {
|
||||
let rel = rels[i];
|
||||
let relItem = Zotero.URI.getURIItem(rel);
|
||||
if (relItem) {
|
||||
content += '\t\t\t\t\t<li id="item_' + relItem.key + '">';
|
||||
content += escapeXML(relItem.getDisplayTitle());
|
||||
content += '</li>\n';
|
||||
}
|
||||
}
|
||||
content += '\t\t\t\t</ul>\n';
|
||||
}
|
||||
|
||||
|
||||
content += '</li>\n\n';
|
||||
content += '\t\t\t</li>\n\n';
|
||||
|
||||
yield content;
|
||||
}
|
||||
content += '</ul>\n';
|
||||
content += '</body>\n</html>';
|
||||
|
||||
return content;
|
||||
}
|
||||
yield '\t\t</ul>\n\t</body>\n</html>';
|
||||
};
|
||||
|
||||
|
||||
function generateHTMLList(items) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
function _generateMetadataTable(arr) {
|
||||
function _generateMetadataTable(obj) {
|
||||
var table = false;
|
||||
var content = '<table>\n';
|
||||
var content = '\t\t\t\t<table>\n';
|
||||
|
||||
// Item type
|
||||
content += '<tr>\n';
|
||||
content += '<th>'
|
||||
content += '\t\t\t\t\t<tr>\n';
|
||||
content += '\t\t\t\t\t\t<th>'
|
||||
+ escapeXML(Zotero.getString('itemFields.itemType'))
|
||||
+ '</th>\n';
|
||||
content += '<td>' + escapeXML(Zotero.ItemTypes.getLocalizedString(arr.itemType)) + '</td>\n';
|
||||
content += '</tr>\n';
|
||||
content += '\t\t\t\t\t\t<td>' + escapeXML(Zotero.ItemTypes.getLocalizedString(obj.itemType)) + '</td>\n';
|
||||
content += '\t\t\t\t\t</tr>\n';
|
||||
|
||||
// Creators
|
||||
if (arr['creators']) {
|
||||
if (obj['creators']) {
|
||||
table = true;
|
||||
var displayText;
|
||||
|
||||
for each(var creator in arr['creators']) {
|
||||
// Two fields
|
||||
if (creator['fieldMode']==0) {
|
||||
displayText = creator['firstName'] + ' ' + creator['lastName'];
|
||||
}
|
||||
// Single field
|
||||
else if (creator['fieldMode']==1) {
|
||||
displayText = creator['lastName'];
|
||||
for each(var creator in obj['creators']) {
|
||||
// One field
|
||||
if (creator.name !== undefined) {
|
||||
displayText = creator.name;
|
||||
}
|
||||
// Two field
|
||||
else {
|
||||
// TODO
|
||||
displayText = (creator.firstName + ' ' + creator.lastName).trim();
|
||||
}
|
||||
|
||||
content += '<tr>\n';
|
||||
content += '<th class="' + creator.creatorType + '">'
|
||||
content += '\t\t\t\t\t<tr>\n';
|
||||
content += '\t\t\t\t\t\t<th class="' + creator.creatorType + '">'
|
||||
+ escapeXML(Zotero.getString('creatorTypes.' + creator.creatorType))
|
||||
+ '</th>\n';
|
||||
content += '<td>' + escapeXML(displayText) + '</td>\n';
|
||||
content += '</tr>\n';
|
||||
content += '\t\t\t\t\t\t<td>' + escapeXML(displayText) + '</td>\n';
|
||||
content += '\t\t\t\t\t</tr>\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Move dateAdded and dateModified to the end of the array
|
||||
var da = arr['dateAdded'];
|
||||
var dm = arr['dateModified'];
|
||||
delete arr['dateAdded'];
|
||||
delete arr['dateModified'];
|
||||
arr['dateAdded'] = da;
|
||||
arr['dateModified'] = dm;
|
||||
// Move dateAdded and dateModified to the end of the objay
|
||||
var da = obj['dateAdded'];
|
||||
var dm = obj['dateModified'];
|
||||
delete obj['dateAdded'];
|
||||
delete obj['dateModified'];
|
||||
obj['dateAdded'] = da;
|
||||
obj['dateModified'] = dm;
|
||||
|
||||
for (var i in arr) {
|
||||
for (var i in obj) {
|
||||
// Skip certain fields
|
||||
switch (i) {
|
||||
case 'reportSearchMatch':
|
||||
case 'reportChildren':
|
||||
|
||||
case 'libraryID':
|
||||
case 'key':
|
||||
case 'itemKey':
|
||||
case 'itemVersion':
|
||||
case 'itemType':
|
||||
case 'itemID':
|
||||
case 'parentItemID':
|
||||
case 'title':
|
||||
case 'firstCreator':
|
||||
case 'creators':
|
||||
case 'tags':
|
||||
case 'related':
|
||||
case 'notes':
|
||||
case 'note':
|
||||
case 'attachments':
|
||||
case 'collections':
|
||||
case 'relations':
|
||||
case 'tags':
|
||||
case 'deleted':
|
||||
case 'parentItem':
|
||||
|
||||
case 'charset':
|
||||
case 'contentType':
|
||||
case 'linkMode':
|
||||
case 'path':
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
var localizedFieldName = Zotero.ItemFields.getLocalizedString(arr.itemType, i);
|
||||
var localizedFieldName = Zotero.ItemFields.getLocalizedString(obj.itemType, i);
|
||||
}
|
||||
// Skip fields we don't have a localized string for
|
||||
catch (e) {
|
||||
|
@ -241,79 +241,82 @@ Zotero.Report = new function() {
|
|||
continue;
|
||||
}
|
||||
|
||||
arr[i] = Zotero.Utilities.trim(arr[i] + '');
|
||||
obj[i] = (obj[i] + '').trim();
|
||||
|
||||
// Skip empty fields
|
||||
if (!arr[i]) {
|
||||
if (!obj[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
table = true;
|
||||
var fieldText;
|
||||
|
||||
if (i == 'url' && arr[i].match(/^https?:\/\//)) {
|
||||
fieldText = '<a href="' + escapeXML(arr[i]) + '">'
|
||||
+ escapeXML(arr[i]) + '</a>';
|
||||
if (i == 'url' && obj[i].match(/^https?:\/\//)) {
|
||||
fieldText = '<a href="' + escapeXML(obj[i]) + '">' + escapeXML(obj[i]) + '</a>';
|
||||
}
|
||||
// Hyperlink DOI
|
||||
else if (i == 'DOI') {
|
||||
fieldText = '<a href="' + escapeXML('http://doi.org/' + arr[i]) + '">'
|
||||
+ escapeXML(arr[i]) + '</a>';
|
||||
fieldText = '<a href="' + escapeXML('http://doi.org/' + obj[i]) + '">'
|
||||
+ escapeXML(obj[i]) + '</a>';
|
||||
}
|
||||
// Remove SQL date from multipart dates
|
||||
// (e.g. '2006-00-00 Summer 2006' becomes 'Summer 2006')
|
||||
else if (i=='date') {
|
||||
fieldText = escapeXML(Zotero.Date.multipartToStr(arr[i]));
|
||||
fieldText = escapeXML(Zotero.Date.multipartToStr(obj[i]));
|
||||
}
|
||||
// Convert dates to local format
|
||||
else if (i=='accessDate' || i=='dateAdded' || i=='dateModified') {
|
||||
var date = Zotero.Date.sqlToDate(arr[i], true)
|
||||
var date = Zotero.Date.isoToDate(obj[i], true)
|
||||
fieldText = escapeXML(date.toLocaleString());
|
||||
}
|
||||
else {
|
||||
fieldText = escapeXML(arr[i]);
|
||||
fieldText = escapeXML(obj[i]);
|
||||
}
|
||||
|
||||
content += '<tr>\n<th>' + escapeXML(localizedFieldName)
|
||||
+ '</th>\n<td>' + fieldText + '</td>\n</tr>\n';
|
||||
content += '\t\t\t\t\t<tr>\n\t\t\t\t\t<th>' + escapeXML(localizedFieldName)
|
||||
+ '</th>\n\t\t\t\t\t\t<td>' + fieldText + '</td>\n\t\t\t\t\t</tr>\n';
|
||||
}
|
||||
|
||||
content += '</table>';
|
||||
content += '\t\t\t\t</table>\n';
|
||||
|
||||
return table ? content : '';
|
||||
}
|
||||
|
||||
|
||||
function _generateTagsList(arr) {
|
||||
function _generateTagsList(obj) {
|
||||
var content = '';
|
||||
if (arr['tags'] && arr['tags'].length) {
|
||||
if (obj.tags && obj.tags.length) {
|
||||
var str = Zotero.getString('report.tags');
|
||||
content += '<h3 class="tags">' + escapeXML(str) + '</h3>\n';
|
||||
content += '<ul class="tags">\n';
|
||||
for each(var tag in arr.tags) {
|
||||
content += '<li>' + escapeXML(tag.fields.name) + '</li>\n';
|
||||
content += '\t\t\t\t<h3 class="tags">' + escapeXML(str) + '</h3>\n';
|
||||
content += '\t\t\t\t<ul class="tags">\n';
|
||||
for (let i=0; i<obj.tags.length; i++) {
|
||||
content += '\t\t\t\t\t<li>' + escapeXML(obj.tags[i].tag) + '</li>\n';
|
||||
}
|
||||
content += '</ul>\n';
|
||||
content += '\t\t\t\t</ul>\n';
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
function _generateAttachmentsList(arr) {
|
||||
function _generateAttachmentsList(obj) {
|
||||
var content = '';
|
||||
if (arr.attachments && arr.attachments.length) {
|
||||
content += '<h3 class="attachments">' + escapeXML(Zotero.getString('itemFields.attachments')) + '</h3>\n';
|
||||
content += '<ul class="attachments">\n';
|
||||
for each(var attachment in arr.attachments) {
|
||||
content += '<li id="i' + attachment.itemID + '">';
|
||||
content += escapeXML(attachment.title);
|
||||
if (obj.attachments && obj.attachments.length) {
|
||||
content += '\t\t\t\t<h3 class="attachments">' + escapeXML(Zotero.getString('itemFields.attachments')) + '</h3>\n';
|
||||
content += '\t\t\t\t<ul class="attachments">\n';
|
||||
for (let i=0; i<obj.attachments.length; i++) {
|
||||
let attachment = obj.attachments[i];
|
||||
|
||||
content += '\t\t\t\t\t<li id="item_' + attachment.itemKey + '">';
|
||||
if (attachment.title !== undefined) {
|
||||
content += escapeXML(attachment.title);
|
||||
}
|
||||
|
||||
// Attachment tags
|
||||
content += _generateTagsList(attachment);
|
||||
|
||||
// Attachment note
|
||||
if (attachment.note) {
|
||||
content += '<div class="note">';
|
||||
content += '\t\t\t\t\t\t<div class="note">';
|
||||
if (attachment.note.substr(0, 1024).match(/<p[^>]*>/)) {
|
||||
content += attachment.note + '\n';
|
||||
}
|
||||
|
@ -321,13 +324,19 @@ Zotero.Report = new function() {
|
|||
else {
|
||||
content += '<p class="plaintext">' + escapeXML(attachment.note) + '</p>\n';
|
||||
}
|
||||
content += '</div>';
|
||||
content += '\t\t\t\t\t</div>';
|
||||
}
|
||||
|
||||
content += '</li>\n';
|
||||
content += '\t\t\t\t\t</li>\n';
|
||||
}
|
||||
content += '</ul>\n';
|
||||
content += '\t\t\t\t</ul>\n';
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
var escapeXML = function (str) {
|
||||
str = str.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '\u2B1A');
|
||||
return Zotero.Utilities.htmlSpecialChars(str);
|
||||
}
|
||||
}
|
||||
|
|
25
chrome/content/zotero/xpcom/router.js
Normal file
25
chrome/content/zotero/xpcom/router.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
Components.utils.import("resource://zotero/pathparser.js", Zotero);
|
||||
Zotero.Router = Zotero.PathParser;
|
||||
delete Zotero.PathParser;
|
||||
|
||||
Zotero.Router.Utilities = {
|
||||
convertControllerToObjectType: function (params) {
|
||||
if (params.controller !== undefined) {
|
||||
params.objectType = Zotero.DataObjectUtilities.getObjectTypeSingular(params.controller);
|
||||
delete params.controller;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Zotero.Router.InvalidPathException = function (path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
|
||||
Zotero.Router.InvalidPathException.prototype = {
|
||||
name: "InvalidPathException",
|
||||
toString: function () {
|
||||
return "Path '" + this.path + "' could not be parsed";
|
||||
}
|
||||
};
|
|
@ -119,13 +119,17 @@ Zotero.Search.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (re
|
|||
var libraryID = this._libraryID;
|
||||
var desc = id ? id : libraryID + "/" + key;
|
||||
|
||||
var sql = "SELECT * FROM savedSearches WHERE ";
|
||||
if (!id && !key) {
|
||||
throw new Error('ID or key not set');
|
||||
}
|
||||
|
||||
var sql = Zotero.Searches.getPrimaryDataSQL()
|
||||
if (id) {
|
||||
sql += "savedSearchID=?";
|
||||
sql += " AND savedSearchID=?";
|
||||
var params = id;
|
||||
}
|
||||
else {
|
||||
sql += "key=? AND libraryID=?";
|
||||
sql += " AND key=? AND libraryID=?";
|
||||
var params = [key, libraryID];
|
||||
}
|
||||
var data = yield Zotero.DB.rowQueryAsync(sql, params);
|
||||
|
@ -323,7 +327,7 @@ Zotero.Search.prototype.clone = Zotero.Promise.coroutine(function* (libraryID) {
|
|||
|
||||
|
||||
Zotero.Search.prototype.addCondition = Zotero.Promise.coroutine(function* (condition, operator, value, required) {
|
||||
yield this.loadPrimaryData();
|
||||
this._requireData('conditions');
|
||||
|
||||
if (!Zotero.SearchConditions.hasOperator(condition, operator)){
|
||||
throw ("Invalid operator '" + operator + "' for condition " + condition);
|
||||
|
@ -483,7 +487,7 @@ Zotero.Search.prototype.removeCondition = Zotero.Promise.coroutine(function* (se
|
|||
* for the given searchConditionID
|
||||
*/
|
||||
Zotero.Search.prototype.getSearchCondition = function(searchConditionID){
|
||||
this._requireData('primaryData');
|
||||
this._requireData('conditions');
|
||||
return this._conditions[searchConditionID];
|
||||
}
|
||||
|
||||
|
@ -493,8 +497,7 @@ Zotero.Search.prototype.getSearchCondition = function(searchConditionID){
|
|||
* used in the search, indexed by searchConditionID
|
||||
*/
|
||||
Zotero.Search.prototype.getSearchConditions = function(){
|
||||
this._requireData('primaryData');
|
||||
|
||||
this._requireData('conditions');
|
||||
var conditions = [];
|
||||
for (var id in this._conditions) {
|
||||
var condition = this._conditions[id];
|
||||
|
@ -512,7 +515,7 @@ Zotero.Search.prototype.getSearchConditions = function(){
|
|||
|
||||
|
||||
Zotero.Search.prototype.hasPostSearchFilter = function() {
|
||||
this._requireData('primaryData');
|
||||
this._requireData('conditions');
|
||||
for each(var i in this._conditions){
|
||||
if (i.condition == 'fulltextContent'){
|
||||
return true;
|
||||
|
@ -531,9 +534,15 @@ Zotero.Search.prototype.hasPostSearchFilter = function() {
|
|||
Zotero.Search.prototype.search = Zotero.Promise.coroutine(function* (asTempTable) {
|
||||
var tmpTable;
|
||||
|
||||
if (this._identified) {
|
||||
yield this.loadConditions();
|
||||
}
|
||||
// Mark conditions as loaded
|
||||
else {
|
||||
this._requireData('conditions');
|
||||
}
|
||||
|
||||
try {
|
||||
yield this.loadPrimaryData();
|
||||
|
||||
if (!this._sql){
|
||||
yield this._buildQuery();
|
||||
}
|
||||
|
@ -580,6 +589,11 @@ Zotero.Search.prototype.search = Zotero.Promise.coroutine(function* (asTempTable
|
|||
|
||||
// Run a subsearch to define the superset of possible results
|
||||
if (this._scope) {
|
||||
if (this._scope._identified) {
|
||||
yield this._scope.loadPrimaryData();
|
||||
yield this._scope.loadConditions();
|
||||
}
|
||||
|
||||
// If subsearch has post-search filter, run and insert ids into temp table
|
||||
if (this._scope.hasPostSearchFilter()) {
|
||||
var ids = yield this._scope.search();
|
||||
|
@ -863,6 +877,7 @@ Zotero.Search.prototype.serialize = function() {
|
|||
*/
|
||||
Zotero.Search.prototype.getSQL = Zotero.Promise.coroutine(function* () {
|
||||
if (!this._sql) {
|
||||
yield this.loadConditions();
|
||||
yield this._buildQuery();
|
||||
}
|
||||
return this._sql;
|
||||
|
@ -871,6 +886,7 @@ Zotero.Search.prototype.getSQL = Zotero.Promise.coroutine(function* () {
|
|||
|
||||
Zotero.Search.prototype.getSQLParams = Zotero.Promise.coroutine(function* () {
|
||||
if (!this._sql) {
|
||||
yield this.loadConditions();
|
||||
yield this._buildQuery();
|
||||
}
|
||||
return this._sqlParams;
|
||||
|
@ -966,6 +982,8 @@ Zotero.Search.idsToTempTable = function (ids) {
|
|||
* Build the SQL query for the search
|
||||
*/
|
||||
Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
||||
this._requireData('conditions');
|
||||
|
||||
var sql = 'SELECT itemID FROM items';
|
||||
var sqlParams = [];
|
||||
// Separate ANY conditions for 'required' condition support
|
||||
|
@ -1171,14 +1189,16 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
|
|||
if (condition.value) {
|
||||
var lkh = Zotero.Collections.parseLibraryKeyHash(condition.value);
|
||||
if (lkh) {
|
||||
col = Zotero.Collections.getByLibraryAndKey(lkh.libraryID, lkh.key);
|
||||
col = yield Zotero.Collections.getByLibraryAndKeyAsync(lkh.libraryID, lkh.key);
|
||||
}
|
||||
}
|
||||
if (!col) {
|
||||
var msg = "Collection " + condition.value + " specified in saved search doesn't exist";
|
||||
Zotero.debug(msg, 2);
|
||||
Zotero.log(msg, 'warning', 'chrome://zotero/content/xpcom/search.js');
|
||||
continue;
|
||||
col = {
|
||||
id: 0
|
||||
};
|
||||
}
|
||||
|
||||
var q = ['?'];
|
||||
|
@ -1633,6 +1653,22 @@ Zotero.Searches = new function(){
|
|||
Zotero.DataObjects.apply(this, ['search', 'searches', 'savedSearch', 'savedSearches']);
|
||||
this.constructor.prototype = new Zotero.DataObjects();
|
||||
|
||||
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"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var _primaryDataSQLParts;
|
||||
|
||||
|
||||
this.init = Zotero.Promise.coroutine(function* () {
|
||||
yield this.constructor.prototype.init.apply(this);
|
||||
|
@ -1661,7 +1697,7 @@ Zotero.Searches = new function(){
|
|||
});
|
||||
|
||||
var searches = [];
|
||||
for (i=0; i<rows.length; i++) {
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
let search = new Zotero.Search;
|
||||
search.id = rows[i].id;
|
||||
yield search.loadPrimaryData();
|
||||
|
@ -1697,10 +1733,12 @@ Zotero.Searches = new function(){
|
|||
});
|
||||
|
||||
|
||||
this._getPrimaryDataSQL = function () {
|
||||
this.getPrimaryDataSQL = function () {
|
||||
// This should be the same as the query in Zotero.Search.loadPrimaryData(),
|
||||
// just without a specific savedSearchID
|
||||
return "SELECT O.* FROM savedSearches O WHERE 1";
|
||||
return "SELECT "
|
||||
+ Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " "
|
||||
+ "FROM savedSearches O WHERE 1";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1715,8 +1753,8 @@ Zotero.SearchConditions = new function(){
|
|||
this.parseCondition = parseCondition;
|
||||
|
||||
var _initialized = false;
|
||||
var _conditions = {};
|
||||
var _standardConditions = [];
|
||||
var _conditions;
|
||||
var _standardConditions;
|
||||
|
||||
var self = this;
|
||||
|
||||
|
@ -2180,6 +2218,7 @@ Zotero.SearchConditions = new function(){
|
|||
];
|
||||
|
||||
// Index conditions by name and aliases
|
||||
_conditions = {};
|
||||
for (var i in conditions) {
|
||||
_conditions[conditions[i]['name']] = conditions[i];
|
||||
if (conditions[i]['aliases']) {
|
||||
|
@ -2271,6 +2310,10 @@ Zotero.SearchConditions = new function(){
|
|||
function hasOperator(condition, operator){
|
||||
var [condition, mode] = this.parseCondition(condition);
|
||||
|
||||
if (!_conditions) {
|
||||
throw new Zotero.Exception.UnloadedDataException("Search conditions not yet loaded");
|
||||
}
|
||||
|
||||
if (!_conditions[condition]){
|
||||
throw ("Invalid condition '" + condition + "' in hasOperator()");
|
||||
}
|
||||
|
|
|
@ -24,32 +24,28 @@
|
|||
*/
|
||||
|
||||
|
||||
Zotero.Timeline = new function () {
|
||||
this.generateXMLDetails = generateXMLDetails;
|
||||
this.generateXMLList = generateXMLList;
|
||||
|
||||
function generateXMLDetails(items, dateType) {
|
||||
Zotero.Timeline = {
|
||||
generateXMLDetails: function* (items, dateType) {
|
||||
var escapeXML = Zotero.Utilities.htmlSpecialChars;
|
||||
|
||||
var content = '<data>\n';
|
||||
for each(var item in items) {
|
||||
yield '<data>\n';
|
||||
for (let i=0; i<items.length; i++) {
|
||||
let item = items[i];
|
||||
yield item.loadItemData();
|
||||
var date = item.getField(dateType, true, true);
|
||||
if (date) {
|
||||
var sqlDate = (dateType == 'date') ? Zotero.Date.multipartToSQL(date) : date;
|
||||
let sqlDate = (dateType == 'date') ? Zotero.Date.multipartToSQL(date) : date;
|
||||
sqlDate = sqlDate.replace("00-00", "01-01");
|
||||
content += '<event start="' + Zotero.Date.sqlToDate(sqlDate) + '" ';
|
||||
var title = item.getField('title');
|
||||
let content = '<event start="' + Zotero.Date.sqlToDate(sqlDate) + '" ';
|
||||
let title = item.getField('title');
|
||||
content += 'title=" ' + (title ? escapeXML(title) : '') + '" ';
|
||||
content += 'icon="' + item.getImageSrc() + '" ';
|
||||
content += 'color="black">';
|
||||
content += item.id;
|
||||
content += '</event>\n';
|
||||
yield content;
|
||||
}
|
||||
}
|
||||
content += '</data>';
|
||||
return content;
|
||||
yield '</data>';
|
||||
}
|
||||
|
||||
function generateXMLList(items) {
|
||||
}
|
||||
}
|
||||
};
|
101
chrome/content/zotero/xpcom/users.js
Normal file
101
chrome/content/zotero/xpcom/users.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2014 Center for History and New Media
|
||||
George Mason University, Fairfax, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
Zotero.Users = new function () {
|
||||
var _userID;
|
||||
var _libraryID;
|
||||
var _username;
|
||||
var _localUserKey;
|
||||
|
||||
this.init = Zotero.Promise.coroutine(function* () {
|
||||
var sql = "SELECT value FROM settings WHERE setting='account' AND key='userID'";
|
||||
_userID = yield Zotero.DB.valueQueryAsync(sql);
|
||||
|
||||
sql = "SELECT value FROM settings WHERE setting='account' AND key='libraryID'";
|
||||
_libraryID = yield Zotero.DB.valueQueryAsync(sql);
|
||||
|
||||
sql = "SELECT value FROM settings WHERE setting='account' AND key='username'";
|
||||
_username = yield Zotero.DB.valueQueryAsync(sql);
|
||||
|
||||
// If we don't have a global user id, generate a local user key
|
||||
if (!_userID) {
|
||||
sql = "SELECT value FROM settings WHERE setting='account' AND key='localUserKey'";
|
||||
let key = yield Zotero.DB.valueQueryAsync(sql);
|
||||
// Generate a local user key if we don't have one
|
||||
if (!key) {
|
||||
key = Zotero.randomString(8);
|
||||
sql = "INSERT INTO settings VALUES ('account', 'localUserKey', ?)";
|
||||
yield Zotero.DB.queryAsync(sql, key);
|
||||
}
|
||||
_localUserKey = key;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.getCurrentUserID = function () {
|
||||
return _userID;
|
||||
};
|
||||
|
||||
|
||||
this.setCurrentUserID = Zotero.Promise.coroutine(function* (val) {
|
||||
val = parseInt(val);
|
||||
var sql = "REPLACE INTO settings VALUES ('account', 'userID', ?)";
|
||||
Zotero.DB.queryAsync(sql, val);
|
||||
_userID = val;
|
||||
});
|
||||
|
||||
|
||||
this.getCurrentLibraryID = function () {
|
||||
return _libraryID;
|
||||
};
|
||||
|
||||
|
||||
this.setCurrentLibraryID = Zotero.Promise.coroutine(function* (val) {
|
||||
val = parseInt(val);
|
||||
var sql = "REPLACE INTO settings VALUES ('account', 'libraryID', ?)";
|
||||
Zotero.DB.queryAsync(sql, val);
|
||||
_userID = val;
|
||||
});
|
||||
|
||||
|
||||
this.getCurrentUsername = function () {
|
||||
return _username;
|
||||
};
|
||||
|
||||
|
||||
this.setCurrentUsername = Zotero.Promise.coroutine(function* (val) {
|
||||
var sql = "REPLACE INTO settings VALUES ('account', 'username', ?)";
|
||||
Zotero.DB.queryAsync(sql, val);
|
||||
_userID = val;
|
||||
});
|
||||
|
||||
|
||||
this.getLocalUserKey = function () {
|
||||
if (!_localUserKey) {
|
||||
throw new Error("Local user key not available");
|
||||
}
|
||||
return _localUserKey;
|
||||
};
|
||||
};
|
|
@ -368,6 +368,123 @@ Zotero.Utilities.Internal = {
|
|||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Return an input stream that will be filled asynchronously with strings yielded from a
|
||||
* generator. If the generator yields a promise, the promise is waited for, but its value
|
||||
* is not added to the input stream.
|
||||
*
|
||||
* @param {GeneratorFunction|Generator} gen - Promise-returning generator function or
|
||||
* generator
|
||||
* @return {nsIAsyncInputStream}
|
||||
*/
|
||||
getAsyncInputStream: function (gen, onError) {
|
||||
const funcName = 'getAsyncInputStream';
|
||||
const maxOutOfSequenceSeconds = 10;
|
||||
const outOfSequenceDelay = 50;
|
||||
|
||||
// Initialize generator if necessary
|
||||
var g = gen.next ? gen : gen();
|
||||
var seq = 0;
|
||||
|
||||
var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
|
||||
pipe.init(true, true, 0, 0, null);
|
||||
|
||||
var os = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterOutputStream);
|
||||
os.init(pipe.outputStream, 'utf-8', 0, 0x0000);
|
||||
|
||||
pipe.outputStream.asyncWait({
|
||||
onOutputStreamReady: function (aos) {
|
||||
Zotero.debug("Output stream is ready");
|
||||
|
||||
let currentSeq = seq++;
|
||||
|
||||
Zotero.spawn(function* () {
|
||||
var lastVal;
|
||||
var error = false;
|
||||
|
||||
while (true) {
|
||||
var data;
|
||||
|
||||
try {
|
||||
let result = g.next(lastVal);
|
||||
|
||||
if (result.done) {
|
||||
Zotero.debug("No more data to write");
|
||||
aos.close();
|
||||
return;
|
||||
}
|
||||
// If a promise is yielded, wait for it and pass on its value
|
||||
if (result.value.then) {
|
||||
lastVal = yield result.value;
|
||||
continue;
|
||||
}
|
||||
// Otherwise use the return value
|
||||
data = result.value;
|
||||
break;
|
||||
}
|
||||
catch (e) {
|
||||
Zotero.debug(e, 1);
|
||||
|
||||
if (onError) {
|
||||
error = e;
|
||||
data = onError();
|
||||
break;
|
||||
}
|
||||
|
||||
Zotero.debug("Closing input stream");
|
||||
aos.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data != 'string') {
|
||||
throw new Error("Yielded value is not a string or promise in " + funcName
|
||||
+ " ('" + data + "')");
|
||||
}
|
||||
|
||||
// Make sure that we're writing to the stream in order, in case
|
||||
// onOutputStreamReady is called again before the last promise completes.
|
||||
// If not in order, wait a bit and try again.
|
||||
var maxTries = Math.floor(maxOutOfSequenceSeconds * 1000 / outOfSequenceDelay);
|
||||
while (currentSeq != seq - 1) {
|
||||
if (maxTries <= 0) {
|
||||
throw new Error("Next promise took too long to finish in " + funcName);
|
||||
}
|
||||
Zotero.debug("Promise finished out of sequence in " + funcName
|
||||
+ "-- waiting " + outOfSequenceDelay + " ms");
|
||||
yield Zotero.Promise.delay(outOfSequenceDelay);
|
||||
maxTries--;
|
||||
}
|
||||
|
||||
// Write to stream
|
||||
Zotero.debug("Writing " + data.length + " characters");
|
||||
os.writeString(data);
|
||||
|
||||
if (error) {
|
||||
Zotero.debug("Closing input stream");
|
||||
aos.close();
|
||||
throw error;
|
||||
}
|
||||
|
||||
Zotero.debug("Waiting to write more");
|
||||
|
||||
// Wait until stream is ready for more
|
||||
aos.asyncWait(this, 0, 0, null);
|
||||
}, this)
|
||||
.catch(function (e) {
|
||||
Zotero.debug("Error getting data for async stream", 1);
|
||||
Components.utils.reportError(e);
|
||||
Zotero.debug(e, 1);
|
||||
os.close();
|
||||
});
|
||||
}
|
||||
}, 0, 0, null);
|
||||
|
||||
return pipe.inputStream;
|
||||
},
|
||||
|
||||
/**
|
||||
* Defines property on the object's prototype.
|
||||
* More compact way to do Object.defineProperty
|
||||
|
|
|
@ -43,7 +43,6 @@ var ZoteroPane = new function()
|
|||
this.handleKeyUp = handleKeyUp;
|
||||
this.setHighlightedRowsCallback = setHighlightedRowsCallback;
|
||||
this.handleKeyPress = handleKeyPress;
|
||||
this.editSelectedCollection = editSelectedCollection;
|
||||
this.handleSearchKeypress = handleSearchKeypress;
|
||||
this.handleSearchInput = handleSearchInput;
|
||||
this.getSelectedCollection = getSelectedCollection;
|
||||
|
@ -1781,8 +1780,7 @@ var ZoteroPane = new function()
|
|||
});
|
||||
|
||||
|
||||
function editSelectedCollection()
|
||||
{
|
||||
this.editSelectedCollection = function () {
|
||||
if (!this.canEdit()) {
|
||||
this.displayCannotEditLibraryMessage();
|
||||
return;
|
||||
|
@ -1807,11 +1805,17 @@ var ZoteroPane = new function()
|
|||
else {
|
||||
var s = new Zotero.Search();
|
||||
s.id = row.ref.id;
|
||||
var io = {dataIn: {search: s, name: row.getName()}, dataOut: null};
|
||||
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
|
||||
if (io.dataOut) {
|
||||
this.onCollectionSelected(); //reload itemsView
|
||||
}
|
||||
s.loadPrimaryData()
|
||||
.then(function () {
|
||||
return s.loadConditions();
|
||||
})
|
||||
.then(function () {
|
||||
var io = {dataIn: {search: s, name: row.getName()}, dataOut: null};
|
||||
window.openDialog('chrome://zotero/content/searchDialog.xul','','chrome,modal',io);
|
||||
if (io.dataOut) {
|
||||
this.onCollectionSelected(); //reload itemsView
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
Timeline.loadXML("zotero://timeline/data/", function(xml, url) { eventSource.loadXML(xml, url); });
|
||||
|
||||
setupFilterHighlightControls(document.getElementById("my-timeline-controls"), tl, [0,1,2], theme);
|
||||
setupOtherControls(document.getElementById("my-other-controls"), tl, document.URL);
|
||||
setupOtherControls(document.getElementById("my-other-controls"), tl, document.location.search);
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
|
|
|
@ -145,7 +145,7 @@ function checkDate(date) {
|
|||
}
|
||||
}
|
||||
}
|
||||
function changeBand(path, queryString, band, intervals, selectedIndex) {
|
||||
function changeBand(queryString, band, intervals, selectedIndex) {
|
||||
var values = new Array('d', 'm', 'y', 'e', 'c', 'i');
|
||||
|
||||
var newIntervals = '';
|
||||
|
@ -158,7 +158,7 @@ function changeBand(path, queryString, band, intervals, selectedIndex) {
|
|||
}
|
||||
}
|
||||
|
||||
window.location = path + queryString + 'i=' + newIntervals;
|
||||
window.location.search = queryString + 'i=' + newIntervals;
|
||||
}
|
||||
|
||||
function createOption(t, selected) {
|
||||
|
@ -192,7 +192,7 @@ function getFull(a) {
|
|||
}
|
||||
|
||||
function createQueryString(theQueryValue, except, timeline) {
|
||||
var temp = '?';
|
||||
var temp = '';
|
||||
for(var i in theQueryValue) {
|
||||
if(except != i) {
|
||||
temp += i + '=' + theQueryValue[i] + '&';
|
||||
|
@ -208,16 +208,9 @@ function createQueryString(theQueryValue, except, timeline) {
|
|||
return temp;
|
||||
}
|
||||
|
||||
function setupOtherControls(div, timeline, url) {
|
||||
function setupOtherControls(div, timeline, queryString) {
|
||||
var table = document.createElement("table");
|
||||
|
||||
var [path, queryString] = url.split('?');
|
||||
if(path == 'zotero://timeline') {
|
||||
path += '/';
|
||||
}
|
||||
if(path =='zotero://timeline/') {
|
||||
path += 'library';
|
||||
}
|
||||
var defaultQueryValue = new Object();
|
||||
defaultQueryValue['i'] = 'mye';
|
||||
defaultQueryValue['t'] = 'd';
|
||||
|
@ -289,7 +282,7 @@ function setupOtherControls(div, timeline, url) {
|
|||
select1.appendChild(createOption(options[i],(options[i] == selected)));
|
||||
}
|
||||
select1.onchange = function () {
|
||||
changeBand(path, createQueryString(theQueryValue, 'i', timeline), 0, intervals, table.rows[1].cells[1].firstChild.selectedIndex);
|
||||
changeBand(createQueryString(theQueryValue, 'i', timeline), 0, intervals, table.rows[1].cells[1].firstChild.selectedIndex);
|
||||
};
|
||||
td.appendChild(select1);
|
||||
|
||||
|
@ -301,7 +294,7 @@ function setupOtherControls(div, timeline, url) {
|
|||
select2.appendChild(createOption(options[i],(options[i] == selected)));
|
||||
}
|
||||
select2.onchange = function () {
|
||||
changeBand(path, createQueryString(theQueryValue, 'i', timeline), 1, intervals, table.rows[1].cells[2].firstChild.selectedIndex);
|
||||
changeBand(createQueryString(theQueryValue, 'i', timeline), 1, intervals, table.rows[1].cells[2].firstChild.selectedIndex);
|
||||
};
|
||||
td.appendChild(select2);
|
||||
|
||||
|
@ -313,7 +306,7 @@ function setupOtherControls(div, timeline, url) {
|
|||
select3.appendChild(createOption(options[i],(options[i] == selected)));
|
||||
}
|
||||
select3.onchange = function () {
|
||||
changeBand(path, createQueryString(theQueryValue, 'i', timeline), 2, intervals, table.rows[1].cells[3].firstChild.selectedIndex);
|
||||
changeBand(createQueryString(theQueryValue, 'i', timeline), 2, intervals, table.rows[1].cells[3].firstChild.selectedIndex);
|
||||
};
|
||||
td.appendChild(select3);
|
||||
|
||||
|
@ -327,7 +320,7 @@ function setupOtherControls(div, timeline, url) {
|
|||
select4.appendChild(createOption(options[i],(values[i] == dateType)));
|
||||
}
|
||||
select4.onchange = function () {
|
||||
window.location = path + createQueryString(theQueryValue, 't', timeline) + 't=' + values[table.rows[1].cells[4].firstChild.selectedIndex];
|
||||
window.location.search = createQueryString(theQueryValue, 't', timeline) + 't=' + values[table.rows[1].cells[4].firstChild.selectedIndex];
|
||||
};
|
||||
td.appendChild(select4);
|
||||
|
||||
|
@ -335,7 +328,7 @@ function setupOtherControls(div, timeline, url) {
|
|||
var fitToScreen = document.createElement("button");
|
||||
fitToScreen.innerHTML = getString("general.fitToScreen");
|
||||
Timeline.DOM.registerEvent(fitToScreen, "click", function () {
|
||||
window.location = path + createQueryString(theQueryValue, false, timeline);
|
||||
window.location.search = createQueryString(theQueryValue, false, timeline);
|
||||
});
|
||||
td.appendChild(fitToScreen);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -56,6 +56,7 @@ const xpcomFilesLocal = [
|
|||
'libraryTreeView',
|
||||
'collectionTreeView',
|
||||
'annotate',
|
||||
'api',
|
||||
'attachments',
|
||||
'cite',
|
||||
'cookieSandbox',
|
||||
|
@ -89,6 +90,7 @@ const xpcomFilesLocal = [
|
|||
'proxy',
|
||||
'quickCopy',
|
||||
'report',
|
||||
'router',
|
||||
'schema',
|
||||
'search',
|
||||
'server',
|
||||
|
|
119
resource/pathparser.js
Normal file
119
resource/pathparser.js
Normal file
|
@ -0,0 +1,119 @@
|
|||
/**
|
||||
* pathparser.js - tiny URL parser/router
|
||||
*
|
||||
* Copyright (c) 2014 Dan Stillman
|
||||
* License: MIT
|
||||
* https://github.com/dstillman/pathparser.js
|
||||
*/
|
||||
(function (factory) {
|
||||
// AMD/RequireJS
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(factory);
|
||||
// CommonJS/Node
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = factory();
|
||||
// Mozilla JSM
|
||||
} else if (~String(this).indexOf('BackstagePass')) {
|
||||
EXPORTED_SYMBOLS = ["PathParser"];
|
||||
PathParser = factory();
|
||||
// Browser global
|
||||
} else {
|
||||
PathParser = factory();
|
||||
}
|
||||
}(function () {
|
||||
"use strict";
|
||||
|
||||
var PathParser = function (params) {
|
||||
this.rules = [];
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
PathParser.prototype = (function () {
|
||||
function getParamsFromRule(rule, pathParts, queryParts) {
|
||||
var params = {};
|
||||
var missingParams = {};
|
||||
|
||||
// Parse path components
|
||||
for (var i = 0; i < rule.parts.length; i++) {
|
||||
var rulePart = rule.parts[i];
|
||||
var part = pathParts[i];
|
||||
|
||||
if (part !== undefined) {
|
||||
if (rulePart.charAt(0) == ':') {
|
||||
params[rulePart.substr(1)] = part;
|
||||
continue;
|
||||
}
|
||||
else if (rulePart !== part) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (rulePart.charAt(0) != ':') {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
missingParams[rulePart.substr(1)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse query strings
|
||||
for (var i = 0; i < queryParts.length; ++i) {
|
||||
var nameValue = queryParts[i].split('=', 2);
|
||||
var key = nameValue[0];
|
||||
// But ignore empty parameters and don't override named parameters
|
||||
if (nameValue.length == 2 && !params[key] && !missingParams[key]) {
|
||||
params[key] = nameValue[1];
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
return {
|
||||
add: function (route, handler, autoPopulateOnMatch) {
|
||||
this.rules.push({
|
||||
parts: route.replace(/^\//, '').split('/'),
|
||||
handler: handler,
|
||||
autoPopulateOnMatch: autoPopulateOnMatch === undefined || autoPopulateOnMatch
|
||||
});
|
||||
},
|
||||
|
||||
run: function (url) {
|
||||
if (url && url.length) {
|
||||
url = url
|
||||
// Remove redundant slashes
|
||||
.replace(/\/+/g, '/')
|
||||
// Strip leading and trailing '/' (at end or before query string)
|
||||
.replace(/^\/|\/($|\?)/, '')
|
||||
// Strip fragment identifiers
|
||||
.replace(/#.*$/, '');
|
||||
}
|
||||
|
||||
var urlSplit = url.split('?', 2);
|
||||
var pathParts = urlSplit[0].split('/', 50);
|
||||
var queryParts = urlSplit[1] ? urlSplit[1].split('&', 50) : [];
|
||||
|
||||
for (var i=0; i < this.rules.length; i++) {
|
||||
var rule = this.rules[i];
|
||||
var params = getParamsFromRule(rule, pathParts, queryParts);
|
||||
if (params) {
|
||||
params.url = url;
|
||||
// Automatic parameter assignment
|
||||
if (rule.autoPopulateOnMatch && this.params) {
|
||||
for (var param in params) {
|
||||
this.params[param] = params[param];
|
||||
}
|
||||
}
|
||||
// Call handler with 'this' bound to parameter object
|
||||
if (rule.handler) {
|
||||
rule.handler.call(params);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
return PathParser;
|
||||
}));
|
Loading…
Add table
Reference in a new issue