/* ***** BEGIN LICENSE BLOCK ***** Copyright © 2009 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 . Based on nsChromeExtensionHandler example code by Ed Anuff at http://kb.mozillazine.org/Dev_:_Extending_the_Chrome_Protocol ***** END LICENSE BLOCK ***** */ const ZOTERO_SCHEME = "zotero"; const ZOTERO_PROTOCOL_CID = Components.ID("{9BC3D762-9038-486A-9D70-C997AF848A7C}"); const ZOTERO_PROTOCOL_CONTRACTID = "@mozilla.org/network/protocol;1?name=" + ZOTERO_SCHEME; const ZOTERO_PROTOCOL_NAME = "Zotero Chrome Extension Protocol"; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); // Dummy chrome URL used to obtain a valid chrome channel // This one was chosen at random and should be able to be substituted // for any other well known chrome URL in the browser installation const DUMMY_CHROME_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; var Zotero = Components.classes["@zotero.org/Zotero;1"] .getService(Components.interfaces.nsISupports) .wrappedJSObject; var ioService = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); function ZoteroProtocolHandler() { this.wrappedJSObject = this; this._principal = null; this._extensions = {}; /** * zotero://data/library/collection/ABCD1234/items?sort=itemType&direction=desc * zotero://data/groups/12345/collection/ABCD1234/items?sort=title&direction=asc */ var DataExtension = { loadAsChrome: false, newChannel: function (uri) { return new AsyncChannel(uri, function* () { this.contentType = 'text/plain'; path = uri.spec.match(/zotero:\/\/[^/]+(.*)/)[1]; try { return Zotero.Utilities.Internal.getAsyncInputStream( Zotero.API.Data.getGenerator(path) ); } catch (e) { if (e instanceof Zotero.Router.InvalidPathException) { return "URL could not be parsed"; } } }); } }; /* * Report generation extension for Zotero protocol */ var ReportExtension = { loadAsChrome: false, newChannel: function (uri) { return new AsyncChannel(uri, function* () { var userLibraryID = Zotero.Libraries.userLibraryID; var path = uri.path; if (!path) { return 'Invalid URL'; } // Strip leading '/' path = path.substr(1); // Proxy CSS files if (path.endsWith('.css')) { var chromeURL = 'chrome://zotero/skin/report/' + path; Zotero.debug(chromeURL); var ios = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); let uri = ios.newURI(chromeURL, null, null); var chromeReg = Components.classes["@mozilla.org/chrome/chrome-registry;1"] .getService(Components.interfaces.nsIChromeRegistry); return chromeReg.convertChromeURL(uri); } var params = { objectType: 'item', format: 'html', sort: 'title' }; var router = new Zotero.Router(params); // Items within a collection or search router.add('library/:scopeObject/:scopeObjectKey/items', function () { params.libraryID = userLibraryID; }); router.add('groups/:groupID/:scopeObject/:scopeObjectKey/items'); // All items router.add('library/items', function () { params.libraryID = userLibraryID; }); router.add('groups/:groupID/items'); // Old-style URLs router.add('collection/:id/html/report.html', function () { params.scopeObject = 'collections'; var lkh = Zotero.Collections.parseLibraryKeyHash(params.id); if (lkh) { params.libraryID = lkh.libraryID; params.scopeObjectKey = lkh.key; } else { params.scopeObjectID = params.id; } delete params.id; }); router.add('search/:id/html/report.html', function () { params.scopeObject = 'searches'; var lkh = Zotero.Searches.parseLibraryKeyHash(this.id); if (lkh) { params.libraryID = lkh.libraryID; params.scopeObjectKey = lkh.key; } else { params.scopeObjectID = this.id; } delete params.id; }); router.add('items/:ids/html/report.html', function () { var ids = this.ids.split('-'); params.libraryID = ids[0].split('_')[0]; params.itemKey = ids.map(x => x.split('_')[1]); delete params.ids; }); var parsed = router.run(path); if (!parsed) { return "URL could not be parsed"; } // TODO: support old URLs // collection // search // items // item if (params.sort.indexOf('/') != -1) { let parts = params.sort.split('/'); params.sort = parts[0]; params.direction = parts[1] == 'd' ? 'desc' : 'asc'; } try { Zotero.API.parseParams(params); var results = yield Zotero.API.getResultsFromParams(params); } catch (e) { Zotero.debug(e, 1); return e.toString(); } var mimeType, content = ''; var items = []; var itemsHash = {}; // key = itemID, val = position in |items| var searchItemIDs = {}; // hash of all selected items var searchParentIDs = {}; // hash of parents of selected child items var searchChildIDs = {}; // hash of selected chlid items var includeAllChildItems = Zotero.Prefs.get('report.includeAllChildItems'); var combineChildItems = Zotero.Prefs.get('report.combineChildItems'); var unhandledParents = {}; for (var i=0; iError generating report'; } ); } }); } }; /** * Generate MIT SIMILE Timeline * * Query string key abbreviations: intervals = i * dateType = t * timelineDate = d * * interval abbreviations: day = d | month = m | year = y | decade = e | century = c | millennium = i * dateType abbreviations: date = d | dateAdded = da | dateModified = dm * timelineDate format: shortMonthName.day.year (year is positive for A.D. and negative for B.C.) * * Defaults: intervals = month, year, decade * dateType = date * timelineDate = today's date */ var TimelineExtension = { loadAsChrome: true, newChannel: function (uri) { return new AsyncChannel(uri, function* () { var userLibraryID = Zotero.Libraries.userLibraryID; path = uri.spec.match(/zotero:\/\/[^/]+(.*)/)[1]; if (!path) { this.contentType = 'text/html'; return 'Invalid URL'; } var params = {}; var router = new Zotero.Router(params); // HTML router.add('library/:scopeObject/:scopeObjectKey', function () { params.libraryID = userLibraryID; params.controller = 'html'; }); router.add('groups/:groupID/:scopeObject/:scopeObjectKey', function () { params.controller = 'html'; }); router.add('library', function () { params.libraryID = userLibraryID; params.controller = 'html'; }); router.add('groups/:groupID', function () { params.controller = 'html'; }); // Data router.add('data/library/:scopeObject/:scopeObjectKey', function () { params.libraryID = userLibraryID; params.controller = 'data'; }); router.add('data/groups/:groupID/:scopeObject/:scopeObjectKey', function () { params.controller = 'data'; }); router.add('data/library', function () { params.libraryID = userLibraryID; params.controller = 'data'; }); router.add('data/groups/:groupID', function () { params.controller = 'data'; }); // Old-style HTML URLs router.add('collection/:id', function () { params.controller = 'html'; params.scopeObject = 'collections'; var lkh = Zotero.Collections.parseLibraryKeyHash(params.id); if (lkh) { params.libraryID = lkh.libraryID; params.scopeObjectKey = lkh.key; } else { params.scopeObjectID = params.id; } delete params.id; }); router.add('search/:id', function () { params.controller = 'html'; params.scopeObject = 'searches'; var lkh = Zotero.Searches.parseLibraryKeyHash(params.id); if (lkh) { params.libraryID = lkh.libraryID; params.scopeObjectKey = lkh.key; } else { params.scopeObjectID = params.id; } delete params.id; }); router.add('/', function () { params.controller = 'html'; params.libraryID = userLibraryID; }); var parsed = router.run(path); if (!parsed) { this.contentType = 'text/html'; return "URL could not be parsed"; } if (params.groupID) { params.libraryID = Zotero.Groups.getLibraryIDFromGroupID(params.groupID); } var intervals = params.i ? params.i : ''; var timelineDate = params.d ? params.d : ''; var dateType = params.t ? params.t : ''; // Get the collection or search object var collection, search; switch (params.scopeObject) { case 'collections': if (params.scopeObjectKey) { collection = yield Zotero.Collections.getByLibraryAndKeyAsync( params.libraryID, params.scopeObjectKey ); } else { collection = yield Zotero.Collections.getAsync(params.scopeObjectID); } if (!collection) { this.contentType = 'text/html'; return 'Invalid collection ID or key'; } 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) { return 'Invalid search ID or key'; } // FIXME: Hack to exclude group libraries for now var search = new Zotero.Search(); search.setScope(s); var groups = Zotero.Groups.getAll(); for (let group of groups) { search.addCondition('libraryID', 'isNot', group.libraryID); } break; } // // Create XML file // if (params.controller == 'data') { switch (params.scopeObject) { case 'collections': var results = collection.getChildItems(); break; case 'searches': var ids = yield search.search(); var results = yield Zotero.Items.getAsync(ids); break; default: if (params.scopeObject) { return "Invalid scope object '" + params.scopeObject + "'"; } let s = new Zotero.Search(); s.addCondition('libraryID', 'is', params.libraryID); s.addCondition('noChildren', 'true'); var ids = yield s.search(); var results = yield Zotero.Items.getAsync(ids); } var items = []; // Only include parent items for (let i=0; i