diff --git a/chrome/content/zotero/upgrade.xul b/chrome/content/zotero/upgrade.xul index fe44b4a35f..e562da566e 100644 --- a/chrome/content/zotero/upgrade.xul +++ b/chrome/content/zotero/upgrade.xul @@ -108,9 +108,9 @@ function _setStartupError() { - Zotero.startupError = Zotero.localeJoin([ + Zotero.startupError = [ Zotero.getString('upgrade.dbUpdateRequired'), Zotero.getString('general.restartFirefox') - ]); + ].join(' '); } diff --git a/chrome/content/zotero/xpcom/annotate.js b/chrome/content/zotero/xpcom/annotate.js index ece5fe64e7..638fc1b73c 100644 --- a/chrome/content/zotero/xpcom/annotate.js +++ b/chrome/content/zotero/xpcom/annotate.js @@ -94,7 +94,7 @@ Zotero.Annotate = new function() { } return colorArray; } catch(e) { - throw "Annotate: parseColor passed invalid color"; + throw new Error("Annotate: parseColor passed invalid color"); } } @@ -173,9 +173,9 @@ Zotero.Annotate = new function() { } else if(offset < node.childNodes.length) { node = node.childNodes[offset]; } else { - throw "Annotate: dereferenceNodeOffset called with invalid offset "+offset; + throw new Error("Annotate: dereferenceNodeOffset called with invalid offset "+offset); } - if(!node) throw "Annotate: dereferenceNodeOffset resolved to invalid node"; + if(!node) throw new Error("Annotate: dereferenceNodeOffset resolved to invalid node"); } return node; @@ -308,7 +308,7 @@ Zotero.Annotate.Path = function(document, nsResolver, parent, textNode, offset) * @param {Integer} offset The text offset, if the DOM node is a text node */ Zotero.Annotate.Path.prototype.fromNode = function(node, offset) { - if(!node) throw "Annotate: Path() called with invalid node"; + if(!node) throw new Error("Annotate: Path() called with invalid node"); Zotero.debug("Annotate: Path() called with node "+node.tagName+" offset "+offset); this.parent = ""; @@ -345,7 +345,7 @@ Zotero.Annotate.Path.prototype.fromNode = function(node, offset) { this.offset = 0; } } - if(!node) throw "Annotate: Path() handled Zotero inappropriately"; + if(!node) throw new Error("Annotate: Path() handled Zotero inappropriately"); lastWasTextNode = lastWasTextNode || node.nodeType == TEXT_TYPE; @@ -387,7 +387,7 @@ Zotero.Annotate.Path.prototype.fromNode = function(node, offset) { node = node.parentNode; } - if(!node) throw "Annotate: Path() resolved text offset inappropriately"; + if(!node) throw new Error("Annotate: Path() resolved text offset inappropriately"); while(node && node !== this._document) { var number = 1; @@ -928,7 +928,7 @@ Zotero.Annotation.prototype.save = function() { * Displays annotation */ Zotero.Annotation.prototype.display = function() { - if(!this.node) throw "Annotation not initialized!"; + if(!this.node) throw new Error("Annotation not initialized!"); var x = 0, y = 0; @@ -946,7 +946,7 @@ Zotero.Annotation.prototype.display = function() { * Displays annotation given absolute coordinates for its position */ Zotero.Annotation.prototype.displayWithAbsoluteCoordinates = function(absX, absY, select) { - if(!this.node) throw "Annotation not initialized!"; + if(!this.node) throw new Error("Annotation not initialized!"); var startScroll = this.window.scrollMaxX; diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js index 51695be77f..d420829b47 100644 --- a/chrome/content/zotero/xpcom/attachments.js +++ b/chrome/content/zotero/xpcom/attachments.js @@ -431,11 +431,11 @@ Zotero.Attachments = new function(){ fileBaseName = this.getFileBaseNameFromItem(parentItem); } if (fileBaseName) { - let ext = _getExtensionFromURL(url, contentType); + let ext = this._getExtensionFromURL(url, contentType); var filename = fileBaseName + (ext != '' ? '.' + ext : ''); } else { - var filename = _getFileNameFromURL(url, contentType); + var filename = this._getFileNameFromURL(url, contentType); } // Create a temporary directory to save to within the storage directory. @@ -644,7 +644,7 @@ Zotero.Attachments = new function(){ // Override MIME type to application/pdf if extension is .pdf -- // workaround for sites that respond to the HEAD request with an // invalid MIME type (https://www.zotero.org/trac/ticket/460) - var ext = _getExtensionFromURL(url); + var ext = this._getExtensionFromURL(url); if (ext == 'pdf') { contentType = 'application/pdf'; } @@ -731,7 +731,7 @@ Zotero.Attachments = new function(){ var tmpDir = (yield this.createTemporaryStorageDirectory()).path; try { - var fileName = Zotero.File.truncateFileName(_getFileNameFromURL(url, contentType), 100); + var fileName = Zotero.File.truncateFileName(this._getFileNameFromURL(url, contentType), 100); var tmpFile = OS.Path.join(tmpDir, fileName); // If we're using the title from the document, make some adjustments @@ -742,7 +742,7 @@ Zotero.Attachments = new function(){ title = title.replace(/(.+ \([^,]+, [0-9]+x[0-9]+[^\)]+\)) - .+/, "$1" ); } // If not native type, strip mime type data in parens - else if (!Zotero.MIME.hasNativeHandler(contentType, _getExtensionFromURL(url))) { + else if (!Zotero.MIME.hasNativeHandler(contentType, this._getExtensionFromURL(url))) { title = title.replace(/(.+) \([a-z]+\/[^\)]+\)/, "$1" ); } } @@ -1816,7 +1816,7 @@ Zotero.Attachments = new function(){ }); - function _getFileNameFromURL(url, contentType){ + this._getFileNameFromURL = function(url, contentType) { var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"] .createInstance(Components.interfaces.nsIURL); nsIURL.spec = url; @@ -1867,7 +1867,7 @@ Zotero.Attachments = new function(){ } - function _getExtensionFromURL(url, contentType) { + this._getExtensionFromURL = function(url, contentType) { var nsIURL = Components.classes["@mozilla.org/network/standard-url;1"] .createInstance(Components.interfaces.nsIURL); nsIURL.spec = url; diff --git a/chrome/content/zotero/xpcom/cite.js b/chrome/content/zotero/xpcom/cite.js index a7a46e37f9..cc231316cd 100644 --- a/chrome/content/zotero/xpcom/cite.js +++ b/chrome/content/zotero/xpcom/cite.js @@ -113,7 +113,7 @@ Zotero.Cite = { output.push("}"); return output.join(""); } else { - throw "Unimplemented bibliography format "+format; + throw new Error("Unimplemented bibliography format "+format); } } else { if(format == "html") { @@ -176,9 +176,9 @@ Zotero.Cite = { var secondFieldAlign = bib[0]["second-field-align"]; // Validate input - if(maxOffset == NaN) throw "Invalid maxoffset"; - if(entrySpacing == NaN) throw "Invalid entryspacing"; - if(lineSpacing == NaN) throw "Invalid linespacing"; + if(maxOffset == NaN) throw new Error("Invalid maxoffset"); + if(entrySpacing == NaN) throw new Error("Invalid entryspacing"); + if(lineSpacing == NaN) throw new Error("Invalid linespacing"); var str; var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] @@ -283,7 +283,7 @@ Zotero.Cite = { return bib[0].bibstart+preamble+bib[1].join("\\\r\n")+"\\\r\n"+bib[0].bibend; } else { - throw "Unimplemented bibliography format "+format; + throw new Error("Unimplemented bibliography format "+format); } }, diff --git a/chrome/content/zotero/xpcom/connector/server_connector.js b/chrome/content/zotero/xpcom/connector/server_connector.js index ab5734e0a2..455ffacaa3 100644 --- a/chrome/content/zotero/xpcom/connector/server_connector.js +++ b/chrome/content/zotero/xpcom/connector/server_connector.js @@ -850,47 +850,33 @@ Zotero.Server.Connector.SaveSnapshot.prototype = { return item; } - return new Zotero.Promise((resolve, reject) => { - Zotero.Server.Connector.Data[data.url] = "" + data.html + ""; - Zotero.HTTP.loadDocuments( - ["zotero://connector/" + encodeURIComponent(data.url)], - async function (doc) { - delete Zotero.Server.Connector.Data[data.url]; - - try { - // Create new webpage item - let item = new Zotero.Item("webpage"); - item.libraryID = libraryID; - item.setField("title", doc.title); - item.setField("url", data.url); - item.setField("accessDate", "CURRENT_TIMESTAMP"); - if (collection) { - item.setCollections([collection.id]); - } - var itemID = await item.saveTx(); - - // Save snapshot - if (library.filesEditable && !data.skipSnapshot) { - await Zotero.Attachments.importFromDocument({ - document: doc, - parentItemID: itemID - }); - } - - resolve(item); - } - catch (e) { - reject(e); - } - }, - null, - null, - false, - cookieSandbox - ); - }); + var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] + .createInstance(Components.interfaces.nsIDOMParser); + var doc = parser.parseFromString(`${data.html}`, 'text/html'); + doc = Zotero.HTTP.wrapDocument(doc, data.url); + + // Create new webpage item + let item = new Zotero.Item("webpage"); + item.libraryID = libraryID; + item.setField("title", doc.title); + item.setField("url", data.url); + item.setField("accessDate", "CURRENT_TIMESTAMP"); + if (collection) { + item.setCollections([collection.id]); + } + var itemID = await item.saveTx(); + + // Save snapshot + if (library.filesEditable && !data.skipSnapshot) { + await Zotero.Attachments.importFromDocument({ + document: doc, + parentItemID: itemID + }); + } + + return item; } -} +}; /** * Handle item selection diff --git a/chrome/content/zotero/xpcom/data/feedItem.js b/chrome/content/zotero/xpcom/data/feedItem.js index dfdc236c86..4400586884 100644 --- a/chrome/content/zotero/xpcom/data/feedItem.js +++ b/chrome/content/zotero/xpcom/data/feedItem.js @@ -215,7 +215,7 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar } let deferred = Zotero.Promise.defer(); - let error = function(e) { Zotero.debug(e, 1); deferred.reject(e); }; + let error = function(e) { }; let translate = new Zotero.Translate.Web(); var win = Services.wm.getMostRecentWindow("navigator:browser"); let progressWindow = win.ZoteroPane.progressWindow; @@ -234,13 +234,12 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar } // Load document - let hiddenBrowser = Zotero.HTTP.loadDocuments( - this.getField('url'), - doc => deferred.resolve(doc), - () => {}, - error, - true - ); + try { + yield Zotero.HTTP.processDocuments(this.getField('url'), doc => deferred.resolve(doc)); + } catch (e) { + Zotero.debug(e, 1); + deferred.reject(e); + } let doc = yield deferred.promise; // Set translate document @@ -266,7 +265,6 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar if (libraryID) { let result = yield translate.translate({libraryID, collections: collectionID ? [collectionID] : false}) .then(items => items ? items[0] : false); - Zotero.Browser.deleteHiddenBrowser(hiddenBrowser); if (!result) { let item = yield this.clone(libraryID, collectionID, doc); progressWindow.Translation.itemDoneHandler()(null, null, item); @@ -285,7 +283,6 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar translate.translate({libraryID: false, saveAttachments: false}); let itemData = yield deferred.promise; - Zotero.Browser.deleteHiddenBrowser(hiddenBrowser); // clean itemData const deleteFields = ['attachments', 'notes', 'id', 'itemID', 'path', 'seeAlso', 'version', 'dateAdded', 'dateModified']; diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js index c61192e21a..e55a298726 100644 --- a/chrome/content/zotero/xpcom/data/items.js +++ b/chrome/content/zotero/xpcom/data/items.js @@ -1001,7 +1001,7 @@ Zotero.Items = function() { }); } // When no longer idle, cancel timer - else if (topic == 'back') { + else if (topic === 'active') { if (this._emptyTrashTimeoutID) { clearTimeout(this._emptyTrashTimeoutID); this._emptyTrashTimeoutID = null; diff --git a/chrome/content/zotero/xpcom/dataDirectory.js b/chrome/content/zotero/xpcom/dataDirectory.js index 136adc19b4..a845f4bdb6 100644 --- a/chrome/content/zotero/xpcom/dataDirectory.js +++ b/chrome/content/zotero/xpcom/dataDirectory.js @@ -1228,5 +1228,27 @@ Zotero.DataDirectory = { ext = ext ? '.' + ext : ''; return OS.Path.join(this.dir, name + ext); + }, + + /** + * @param {String} name - the name of the subdirectory + * @param {Boolean} createIfMissing - ensure that the directory exists + * @return {String} the path to the subdirectory + */ + getSubdirectory: function (name, createIfMissing = false) { + let dir = OS.Path.join(this.dir, name); + if (createIfMissing) { + Zotero.File.createDirectoryIfMissing(dir); + } + return dir; + }, + + /** + * @param {String} name - the name of the subdirectory + * @return {Promise} true if the subdirectory was deleted, + * or false if it did not exist + */ + removeSubdirectory: function (name) { + return OS.File.removeDir(OS.Path.join(this.dir, name), {ignoreAbsent: true}); } }; diff --git a/chrome/content/zotero/xpcom/date.js b/chrome/content/zotero/xpcom/date.js index db31cd1d1d..eb800c97c8 100644 --- a/chrome/content/zotero/xpcom/date.js +++ b/chrome/content/zotero/xpcom/date.js @@ -43,50 +43,45 @@ Zotero.Date = new function(){ var _monthsWithEnglish; this.init = function () { - if (!Zotero.isFx || Zotero.isBookmarklet) { + if (!(Zotero.isFx || Zotero.isElectron) || Zotero.isBookmarklet) { throw new Error("Unimplemented"); } - return Zotero.HTTP.request( - 'GET', 'resource://zotero/schema/dateFormats.json', { responseType: 'json' } - ).then(function(xmlhttp) { - var json = xmlhttp.response; - - var locale = Zotero.locale; - var english = locale.startsWith('en'); - // If no exact match, try first two characters ('de') - if (!json[locale]) { - locale = locale.substr(0, 2); + var json = JSON.parse(Zotero.File.getResource('resource://zotero/schema/dateFormats.json')); + var locale = Zotero.locale; + var english = locale.startsWith('en'); + // If no exact match, try first two characters ('de') + if (!json[locale]) { + locale = locale.substr(0, 2); + } + // Try first two characters repeated ('de-DE') + if (!json[locale]) { + locale = locale + "-" + locale.toUpperCase(); + } + // Look for another locale with same first two characters + if (!json[locale]) { + let sameLang = Object.keys(json).filter(l => l.startsWith(locale.substr(0, 2))); + if (sameLang.length) { + locale = sameLang[0]; } - // Try first two characters repeated ('de-DE') - if (!json[locale]) { - locale = locale + "-" + locale.toUpperCase(); - } - // Look for another locale with same first two characters - if (!json[locale]) { - let sameLang = Object.keys(json).filter(l => l.startsWith(locale.substr(0, 2))); - if (sameLang.length) { - locale = sameLang[0]; - } - } - // If all else fails, use English - if (!json[locale]) { - locale = 'en-US'; - english = true; - } - _months = json[locale]; + } + // If all else fails, use English + if (!json[locale]) { + locale = 'en-US'; + english = true; + } + _months = json[locale]; - // Add English versions if not already added - if (english) { - _monthsWithEnglish = _months; + // Add English versions if not already added + if (english) { + _monthsWithEnglish = _months; + } + else { + _monthsWithEnglish = {}; + for (let key in _months) { + _monthsWithEnglish[key] = _months[key].concat(json['en-US'][key]); } - else { - _monthsWithEnglish = {}; - for (let key in _months) { - _monthsWithEnglish[key] = _months[key].concat(json['en-US'][key]); - } - } - }); + } }; diff --git a/chrome/content/zotero/xpcom/db.js b/chrome/content/zotero/xpcom/db.js index 58c8648d0f..151886369d 100644 --- a/chrome/content/zotero/xpcom/db.js +++ b/chrome/content/zotero/xpcom/db.js @@ -126,21 +126,6 @@ Zotero.DBConnection.prototype.test = function () { return this._getConnectionAsync().then(() => {}); } -Zotero.DBConnection.prototype.getAsyncStatement = Zotero.Promise.coroutine(function* (sql) { - var conn = yield this._getConnectionAsync(); - conn = conn._connection; - - try { - this._debug(sql, 4); - return conn.createAsyncStatement(sql); - } - catch (e) { - var dberr = (conn.lastErrorString != 'not an error') - ? ' [ERROR: ' + conn.lastErrorString + ']' : ''; - throw new Error(e + ' [QUERY: ' + sql + ']' + dberr); - } -}); - Zotero.DBConnection.prototype.parseQueryAndParams = function (sql, params) { // If single scalar value, wrap in an array @@ -267,44 +252,6 @@ Zotero.DBConnection.prototype.parseQueryAndParams = function (sql, params) { }; -/** - * Execute an asynchronous statement with previously bound parameters - * - * Warning: This will freeze if used with a write statement within executeTransaction()! - * - * @param {mozIStorageAsyncStatement} statement - Statement to run - * @param {Function} [progressHandler] - Function to pass each available row to for SELECT queries - * @return {Promise} - Resolved on completion, rejected with a reason on error - */ -Zotero.DBConnection.prototype.executeAsyncStatement = Zotero.Promise.method(function (statement, progressHandler) { - var resolve; - var reject; - statement.executeAsync({ - handleResult: function (resultSet) { - if (progressHandler) { - progressHandler(resultSet.getNextRow()); - } - }, - - handleError: function (e) { - reject(e); - }, - - handleCompletion: function (reason) { - if (reason != Components.interfaces.mozIStorageStatementCallback.REASON_FINISHED) { - reject(reason); - } - resolve(); - } - }); - return new Zotero.Promise(function () { - resolve = arguments[0]; - reject = arguments[1]; - }); -}); - - - Zotero.DBConnection.prototype.addCallback = function (type, cb) { switch (type) { case 'begin': @@ -924,8 +871,7 @@ Zotero.DBConnection.prototype.checkException = function (e) { this._dbIsCorrupt = true; - var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] - .getService(Components.interfaces.nsIPromptService); + var ps = Services.prompt; var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING); @@ -939,8 +885,7 @@ Zotero.DBConnection.prototype.checkException = function (e) { null, null, {}); if (index == 0) { - var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"] - .getService(Components.interfaces.nsIAppStartup); + var appStartup = Services.startup; appStartup.quit(Components.interfaces.nsIAppStartup.eRestart); appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit); } @@ -975,8 +920,7 @@ Zotero.DBConnection.prototype.backupDatabase = Zotero.Promise.coroutine(function return false; } - var storageService = Components.classes["@mozilla.org/storage/service;1"] - .getService(Components.interfaces.mozIStorageService); + var storageService = Services.storage; if (!suffix) { var numBackups = Zotero.Prefs.get("backup.numBackups"); @@ -1183,8 +1127,7 @@ Zotero.DBConnection.prototype._getConnectionAsync = async function (options) { Zotero.debug(this._dbPath); // Get the storage service - var store = Components.classes["@mozilla.org/storage/service;1"]. - getService(Components.interfaces.mozIStorageService); + var store = Services.storage; var file = this._dbPath; var backupFile = this._dbPath + '.bak'; diff --git a/chrome/content/zotero/xpcom/file.js b/chrome/content/zotero/xpcom/file.js index 59dc2e9850..9728726106 100644 --- a/chrome/content/zotero/xpcom/file.js +++ b/chrome/content/zotero/xpcom/file.js @@ -33,6 +33,7 @@ Zotero.File = new function(){ this.getExtension = getExtension; this.getContentsFromURL = getContentsFromURL; + this.getContentsFromURLAsync = getContentsFromURLAsync; this.putContents = putContents; this.getValidFileName = getValidFileName; this.truncateFileName = truncateFileName; @@ -58,9 +59,7 @@ Zotero.File = new function(){ this.pathToFileURI = function (path) { var file = new FileUtils.File(path); - var ios = Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService); - return ios.newFileURI(file).spec; + return Services.io.newFileURI(file).spec; } @@ -107,11 +106,11 @@ Zotero.File = new function(){ } var dir = OS.Path.dirname(file); - while (dir && !await OS.File.exists(dir)) { + while (dir && dir != '/' && !await OS.File.exists(dir)) { dir = OS.Path.dirname(dir); } - return dir || false; + return (dir && dir != '/') ? dir : false; } @@ -131,6 +130,8 @@ Zotero.File = new function(){ * Get contents of a binary file */ this.getBinaryContents = function(file) { + Zotero.debug("Zotero.File.getBinaryContents() is deprecated -- " + + "use Zotero.File.getBinaryContentsAsync() when possible", 2); var iStream = Components.classes["@mozilla.org/network/file-input-stream;1"] .createInstance(Components.interfaces.nsIFileInputStream); iStream.init(file, 0x01, 0o664, 0); @@ -339,12 +340,33 @@ Zotero.File = new function(){ xmlhttp.send(null); return xmlhttp.responseText; } + + /** + * Return the contents of resource. Use this for loading + * resource/chrome URLs. + * + * @param {String} url - the resource url + * @return {String} the resource contents as a string + */ + this.getResource = function (url) { + return getContentsFromURL(url); + } + + /** + * Return a promise for the contents of resource. + * + * @param {String} url - the resource url + * @return {Promise} the resource contents as a string + */ + this.getResourceAsync = function (url) { + return getContentsFromURLAsync(url); + } /* * Return a promise for the contents of a URL as a string */ - this.getContentsFromURLAsync = function (url, options={}) { + function getContentsFromURLAsync (url, options={}) { return Zotero.HTTP.request("GET", url, Object.assign(options, { responseType: "text" })) .then(function (xmlhttp) { return xmlhttp.response; @@ -952,6 +974,7 @@ Zotero.File = new function(){ this.createDirectoryIfMissing = function (dir) { + dir = this.pathToFile(dir); if (!dir.exists() || !dir.isDirectory()) { if (dir.exists() && !dir.isDirectory()) { dir.remove(null); diff --git a/chrome/content/zotero/xpcom/http.js b/chrome/content/zotero/xpcom/http.js index 2c14390708..f011bbc3f4 100644 --- a/chrome/content/zotero/xpcom/http.js +++ b/chrome/content/zotero/xpcom/http.js @@ -785,8 +785,7 @@ Zotero.HTTP = new function() { * @type Boolean */ this.browserIsOffline = function() { - return Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService).offline; + return Services.io.offline; } diff --git a/chrome/content/zotero/xpcom/intl.js b/chrome/content/zotero/xpcom/intl.js new file mode 100644 index 0000000000..5e14f3837d --- /dev/null +++ b/chrome/content/zotero/xpcom/intl.js @@ -0,0 +1,252 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2018 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 . + + ***** END LICENSE BLOCK ***** +*/ +Zotero.Intl = new function () { + let bundle; + let collation; + let intlProps; + let pluralFormGet; + let pluralFormNumForms; + + // Get settings from language pack (extracted by zotero-build/locale/merge_mozilla_files) + this.init = function () { + Components.utils.import("resource://gre/modules/PluralForm.jsm"); + + bundle = Services.strings.createBundle('chrome://zotero/locale/zotero.properties'); + intlProps = Services.strings.createBundle('chrome://zotero/locale/mozilla/intl.properties'); + + [pluralFormGet, pluralFormNumForms] = PluralForm.makeGetter(parseInt(getIntlProp('pluralRule', 1))); + setOrClearIntlPref('intl.accept_languages', 'string'); + + Zotero.locale = getIntlProp('general.useragent.locale', 'en-US'); + + // Also load the brand as appName + Zotero.appName = Services.strings + .createBundle('chrome://branding/locale/brand.properties') + .GetStringFromName('brandShortName'); + + // Set the locale direction to Zotero.dir + Zotero.dir = Zotero.Locale.defaultScriptDirection(Zotero.locale); + Zotero.rtl = (Zotero.dir === 'rtl'); + }; + + + /** + * @param {String} name + * @param {String[]} [params=[]] - Strings to substitute for placeholders + * @param {Number} [num] - Number (also appearing in `params`) to use when determining which plural + * form of the string to use; localized strings should include all forms in the order specified + * in https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals, + * separated by semicolons + */ + this.getString = function (name, params, num) { + try { + var l10n; + if (params != undefined) { + if (typeof params != 'object'){ + params = [params]; + } + l10n = bundle.formatStringFromName(name, params, params.length); + } + else { + l10n = bundle.GetStringFromName(name); + } + if (num !== undefined) { + let availableForms = l10n.split(/;/); + // If not enough available forms, use last one -- PluralForm.get() uses first by + // default, but it's more likely that a localizer will translate the two English + // strings with some plural form as the second one, so we might as well use that + if (availableForms.length < pluralFormNumForms()) { + l10n = availableForms[availableForms.length - 1]; + } + else { + l10n = pluralFormGet(num, l10n); + } + } + } + catch (e){ + if (e.name == 'NS_ERROR_ILLEGAL_VALUE') { + Zotero.debug(params, 1); + } + else if (e.name != 'NS_ERROR_FAILURE') { + Zotero.logError(e); + } + throw new Error('Localized string not available for ' + name); + } + return l10n; + }; + + /* + * Compares two strings based on the current collator. + * @param {String} string1 + * @param {String} string2 + * @return {Number} a number indicating how string1 and string2 compare to + * each other according to the sort order of this Collator object: a + * negative value if string1 comes before string2; a positive value if + * string1 comes after string2; 0 if they are considered equal. + */ + this.compare = function (...args) { + return this.collation.compareString(1, ...args); + }; + + Object.defineProperty(this, 'collation', { + get() { + if (collation == null) { + collation = getLocaleCollation(); + } + return collation; + } + }); + + + function getIntlProp(name, fallback = null) { + try { + return intlProps.GetStringFromName(name); + } + catch (e) { + Zotero.logError(`Couldn't load ${name} from intl.properties`); + return fallback; + } + } + + function setOrClearIntlPref(name, type) { + var val = getIntlProp(name); + if (val !== null) { + if (type == 'boolean') { + val = val == 'true'; + } + Zotero.Prefs.set(name, val, true); + } + else { + Zotero.Prefs.clear(name, true); + } + } + + function getLocaleCollation() { + try { + // DEBUG: Is this necessary, or will Intl.Collator just default to the same locales we're + // passing manually? + + let locales; + // Fx55+ + if (Services.locale.getAppLocalesAsBCP47) { + locales = Services.locale.getAppLocalesAsBCP47(); + } + else { + let locale; + // Fx54 + if (Services.locale.getAppLocale) { + locale = Services.locale.getAppLocale(); + } + // Fx <=53 + else { + locale = Services.locale.getApplicationLocale(); + locale = locale.getCategory('NSILOCALE_COLLATE'); + } + + // Extract a valid language tag + try { + locale = locale.match(/^[a-z]{2}(\-[A-Z]{2})?/)[0]; + } + catch (e) { + throw new Error(`Error parsing locale ${locale}`); + } + locales = [locale]; + } + + var collator = new Intl.Collator(locales, { + numeric: true, + sensitivity: 'base' + }); + } + catch (e) { + Zotero.logError(e); + + // Fall back to en-US sorting + try { + Zotero.logError("Falling back to en-US sorting"); + collator = new Intl.Collator(['en-US'], { + numeric: true, + sensitivity: 'base' + }); + } + catch (e) { + Zotero.logError(e); + + // If there's still an error, just skip sorting + collator = { + compare: function (a, b) { + return 0; + } + }; + } + } + + // Grab all ASCII punctuation and space at the begining of string + var initPunctuationRE = /^[\x20-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/; + // Punctuation that should be ignored when sorting + var ignoreInitRE = /["'[{(]+$/; + + // Until old code is updated, pretend we're returning an nsICollation + return this.collation = { + compareString: function (_, a, b) { + if (!a && !b) return 0; + if (!a || !b) return b ? -1 : 1; + + // Compare initial punctuation + var aInitP = initPunctuationRE.exec(a) || ''; + var bInitP = initPunctuationRE.exec(b) || ''; + + var aWordStart = 0, bWordStart = 0; + if (aInitP) { + aWordStart = aInitP[0].length; + aInitP = aInitP[0].replace(ignoreInitRE, ''); + } + if (bInitP) { + bWordStart = bInitP.length; + bInitP = bInitP[0].replace(ignoreInitRE, ''); + } + + // If initial punctuation is equivalent, use collator comparison + // that ignores all punctuation + // + // Update: Intl.Collator's ignorePunctuation also ignores whitespace, so we're + // no longer using it, meaning we could take out most of the code to handle + // initial punctuation separately, unless we think we'll at some point switch to + // a collation function that ignores punctuation but not whitespace. + if (aInitP == bInitP || !aInitP && !bInitP) return collator.compare(a, b); + + // Otherwise consider "attached" words as well, e.g. the order should be + // "__ n", "__z", "_a" + // We don't actually care what the attached word is, just whether it's + // there, since at this point we're guaranteed to have non-equivalent + // initial punctuation + if (aWordStart < a.length) aInitP += 'a'; + if (bWordStart < b.length) bInitP += 'a'; + + return aInitP.localeCompare(bInitP); + } + }; + } +}; diff --git a/chrome/content/zotero/xpcom/locale.js b/chrome/content/zotero/xpcom/locale.js index 9b3b1315c0..7b1fb4c5db 100644 --- a/chrome/content/zotero/xpcom/locale.js +++ b/chrome/content/zotero/xpcom/locale.js @@ -40,5 +40,16 @@ Zotero.Locale = { 'vi-VN': 'Tiếng Việt', 'zh-CN': '中文 (简体)', 'zh-TW': '正體中文 (繁體)' - }) + }), + + defaultScriptDirection(locale) { + switch (locale.split('-')[0]) { + case 'ar': + case 'fa': + case 'he': + return 'rtl'; + default: + return 'ltr'; + } + } } diff --git a/chrome/content/zotero/xpcom/locateManager.js b/chrome/content/zotero/xpcom/locateManager.js index fa3d5a3b40..66b83913b0 100644 --- a/chrome/content/zotero/xpcom/locateManager.js +++ b/chrome/content/zotero/xpcom/locateManager.js @@ -29,16 +29,12 @@ Zotero.LocateManager = new function() { var _jsonFile; var _locateEngines; - var _ios; var _timer; /** * Read locateEngines JSON file to initialize locate manager */ this.init = async function() { - _ios = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); - _jsonFile = _getLocateFile(); try { @@ -61,7 +57,7 @@ Zotero.LocateManager = new function() { */ this.addEngine = function(engineURL, dataType, iconURL, confirm) { if(dataType !== Components.interfaces.nsISearchEngine.TYPE_OPENSEARCH) { - throw "LocateManager supports only OpenSearch engines"; + throw new Error("LocateManager supports only OpenSearch engines"); } Zotero.HTTP.doGet(engineURL, function(xmlhttp) { @@ -121,7 +117,7 @@ Zotero.LocateManager = new function() { */ this.removeEngine = function(engine) { var oldIndex = _locateEngines.indexOf(engine); - if(oldIndex === -1) throw "Engine is not currently listed"; + if(oldIndex === -1) throw new Error("Engine is not currently listed"); _locateEngines.splice(oldIndex, 1); engine._removeIcon(); _serializeLocateEngines(); @@ -271,7 +267,7 @@ Zotero.LocateManager = new function() { * Theoretically implements nsISearchSubmission */ var LocateSubmission = function(uri, postData) { - this.uri = _ios.newURI(uri, null, null); + this.uri = Services.io.newURI(uri, null, null); this.postData = postData; } @@ -316,7 +312,7 @@ Zotero.LocateManager = new function() { xns = {"s":doc.documentElement.namespaceURI, "xmlns":"http://www.w3.org/2000/xmlns"}; if(OPENSEARCH_NAMESPACES.indexOf(ns) === -1) { - throw "Invalid namespace"; + throw new Error("Invalid namespace"); } // get simple attributes @@ -331,7 +327,7 @@ Zotero.LocateManager = new function() { i = 0; while(urlTags[i].hasAttribute("rel") && urlTags[i].getAttribute("rel") != "results") { i++; - if(i == urlTags.length) throw "No Url tag found"; + if(i == urlTags.length) throw new Error("No Url tag found"); } // TODO: better error handling @@ -383,7 +379,7 @@ Zotero.LocateManager = new function() { "getItemSubmission":function(item, responseType) { if(responseType && responseType !== "text/html") { - throw "LocateManager supports only responseType text/html"; + throw new Error("LocateManager supports only responseType text/html"); } if (item.toJSON) { @@ -447,7 +443,7 @@ Zotero.LocateManager = new function() { "_removeIcon":function() { if(!this.icon) return; - var uri = _ios.newURI(this.icon, null, null); + var uri = Services.io.newURI(this.icon, null, null); var file = uri.QueryInterface(Components.interfaces.nsIFileURL).file; if(file.exists()) file.remove(null); }, @@ -492,4 +488,4 @@ Zotero.LocateManager = new function() { this.icon = OS.Path.toFileURI(iconFile); } } -} \ No newline at end of file +} diff --git a/chrome/content/zotero/xpcom/openurl.js b/chrome/content/zotero/xpcom/openurl.js index c61a5c3181..a5c206f086 100644 --- a/chrome/content/zotero/xpcom/openurl.js +++ b/chrome/content/zotero/xpcom/openurl.js @@ -52,7 +52,7 @@ Zotero.OpenURL = new function() { req.send(null); if(!req.responseXML) { - throw "Could not access resolver registry"; + throw new Error("Could not access resolver registry"); } var resolverArray = new Array(); diff --git a/chrome/content/zotero/xpcom/prefs.js b/chrome/content/zotero/xpcom/prefs.js new file mode 100644 index 0000000000..49df260b9e --- /dev/null +++ b/chrome/content/zotero/xpcom/prefs.js @@ -0,0 +1,331 @@ +/* + ***** BEGIN LICENSE BLOCK ***** + + Copyright © 2018 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 . + + ***** END LICENSE BLOCK ***** +*/ +Zotero.Prefs = new function(){ + // Privileged methods + this.init = init; + this.get = get; + this.set = set; + + this.register = register; + this.unregister = unregister; + this.observe = observe; + + // Public properties + this.prefBranch; + + function init(){ + this.prefBranch = Services.prefs.getBranch(ZOTERO_CONFIG.PREF_BRANCH); + + // Register observer to handle pref changes + this.register(); + + // Unregister observer handling pref changes + if (Zotero.addShutdownListener) { + Zotero.addShutdownListener(this.unregister.bind(this)); + } + + // Process pref version updates + var fromVersion = this.get('prefVersion'); + if (!fromVersion) { + fromVersion = 0; + } + var toVersion = 2; + if (fromVersion < toVersion) { + for (var i = fromVersion + 1; i <= toVersion; i++) { + switch (i) { + case 1: + // If a sync username is entered and ZFS is enabled, turn + // on-demand downloading off to maintain current behavior + if (this.get('sync.server.username')) { + if (this.get('sync.storage.enabled') + && this.get('sync.storage.protocol') == 'zotero') { + this.set('sync.storage.downloadMode.personal', 'on-sync'); + } + if (this.get('sync.storage.groups.enabled')) { + this.set('sync.storage.downloadMode.groups', 'on-sync'); + } + } + break; + + case 2: + // Re-show saveButton guidance panel (and clear old saveIcon pref). + // The saveButton guidance panel initially could auto-hide too easily. + this.clear('firstRunGuidanceShown.saveIcon'); + this.clear('firstRunGuidanceShown.saveButton'); + break; + } + } + this.set('prefVersion', toVersion); + } + } + + + /** + * Retrieve a preference + **/ + function get(pref, global){ + try { + if (global) { + var branch = Services.prefs.getBranch(""); + } + else { + var branch = this.prefBranch; + } + + switch (branch.getPrefType(pref)){ + case branch.PREF_BOOL: + return branch.getBoolPref(pref); + case branch.PREF_STRING: + return '' + branch.getComplexValue(pref, Components.interfaces.nsISupportsString); + case branch.PREF_INT: + return branch.getIntPref(pref); + } + } + catch (e){ + throw new Error("Invalid preference '" + pref + "'"); + } + } + + + /** + * Set a preference + **/ + function set(pref, value, global) { + try { + if (global) { + var branch = Services.prefs.getBranch(""); + } + else { + var branch = this.prefBranch; + } + + switch (branch.getPrefType(pref)) { + case branch.PREF_BOOL: + return branch.setBoolPref(pref, value); + case branch.PREF_STRING: + let str = Cc["@mozilla.org/supports-string;1"] + .createInstance(Ci.nsISupportsString); + str.data = value; + return branch.setComplexValue(pref, Ci.nsISupportsString, str); + case branch.PREF_INT: + return branch.setIntPref(pref, value); + + // If not an existing pref, create appropriate type automatically + case 0: + if (typeof value == 'boolean') { + Zotero.debug("Creating boolean pref '" + pref + "'"); + return branch.setBoolPref(pref, value); + } + if (typeof value == 'string') { + Zotero.debug("Creating string pref '" + pref + "'"); + return branch.setCharPref(pref, value); + } + if (parseInt(value) == value) { + Zotero.debug("Creating integer pref '" + pref + "'"); + return branch.setIntPref(pref, value); + } + throw new Error("Invalid preference value '" + value + "' for pref '" + pref + "'"); + } + } + catch (e) { + Zotero.logError(e); + throw new Error("Invalid preference '" + pref + "'"); + } + } + + + this.clear = function (pref, global) { + if (global) { + var branch = Services.prefs.getBranch(""); + } + else { + var branch = this.prefBranch; + } + branch.clearUserPref(pref); + } + + + this.resetBranch = function (exclude = []) { + var keys = this.prefBranch.getChildList("", {}); + for (let key of keys) { + if (this.prefBranch.prefHasUserValue(key)) { + if (exclude.includes(key)) { + continue; + } + Zotero.debug("Clearing " + key); + this.prefBranch.clearUserPref(key); + } + } + }; + + + // Import settings bundles + this.importSettings = function (str, uri) { + var ps = Services.prompt; + + if (!uri.match(/https:\/\/([^\.]+\.)?zotero.org\//)) { + Zotero.debug("Ignoring settings file not from https://zotero.org"); + return; + } + + str = Zotero.Utilities.trim(str.replace(/<\?xml.*\?>\s*/, '')); + Zotero.debug(str); + + var confirm = ps.confirm( + null, + "", + "Apply settings from zotero.org?" + ); + + if (!confirm) { + return; + } + + // TODO: parse settings XML + } + + // Handlers for some Zotero preferences + var _handlers = [ + [ "automaticScraperUpdates", function(val) { + if (val){ + Zotero.Schema.updateFromRepository(1); + } + else { + Zotero.Schema.stopRepositoryTimer(); + } + }], + ["fontSize", function (val) { + Zotero.setFontSize( + Zotero.getActiveZoteroPane().document.getElementById('zotero-pane') + ); + }], + [ "layout", function(val) { + Zotero.getActiveZoteroPane().updateLayout(); + }], + [ "note.fontSize", function(val) { + if (val < 6) { + Zotero.Prefs.set('note.fontSize', 11); + } + }], + [ "zoteroDotOrgVersionHeader", function(val) { + if (val) { + Zotero.VersionHeader.register(); + } + else { + Zotero.VersionHeader.unregister(); + } + }], + [ "sync.autoSync", function(val) { + if (val) { + Zotero.Sync.EventListeners.AutoSyncListener.register(); + Zotero.Sync.EventListeners.IdleListener.register(); + } + else { + Zotero.Sync.EventListeners.AutoSyncListener.unregister(); + Zotero.Sync.EventListeners.IdleListener.unregister(); + } + }], + [ "search.quicksearch-mode", function(val) { + var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] + .getService(Components.interfaces.nsIWindowMediator); + var enumerator = wm.getEnumerator("navigator:browser"); + while (enumerator.hasMoreElements()) { + var win = enumerator.getNext(); + if (!win.ZoteroPane) continue; + Zotero.updateQuickSearchBox(win.ZoteroPane.document); + } + + var enumerator = wm.getEnumerator("zotero:item-selector"); + while (enumerator.hasMoreElements()) { + var win = enumerator.getNext(); + if (!win.Zotero) continue; + Zotero.updateQuickSearchBox(win.document); + } + }] + ]; + + // + // Methods to register a preferences observer + // + function register(){ + this.prefBranch.addObserver("", this, false); + + // Register pre-set handlers + for (var i=0; i<_handlers.length; i++) { + this.registerObserver(_handlers[i][0], _handlers[i][1]); + } + } + + function unregister(){ + if (!this.prefBranch){ + return; + } + this.prefBranch.removeObserver("", this); + } + + /** + * @param {nsIPrefBranch} subject The nsIPrefBranch we're observing (after appropriate QI) + * @param {String} topic The string defined by NS_PREFBRANCH_PREFCHANGE_TOPIC_ID + * @param {String} data The name of the pref that's been changed (relative to subject) + */ + function observe(subject, topic, data){ + if (topic != "nsPref:changed" || !_observers[data] || !_observers[data].length) { + return; + } + + var obs = _observers[data]; + for (var i=0; i 0) - throw "missing.js: oops nnonempty dict not imp"; + throw new Error("missing.js: oops nnonempty dict not imp"); return []; } @@ -57,8 +57,8 @@ $rdf.N3Parser = function () { var assertFudge = function (condition, desc) { if(condition) return; - if(desc) throw "python Assertion failed: " + desc; - throw "(python) Assertion failed."; + if(desc) throw new Error("python Assertion failed: " + desc); + throw new Error("(python) Assertion failed."); } diff --git a/chrome/content/zotero/xpcom/rdf/serialize.js b/chrome/content/zotero/xpcom/rdf/serialize.js index 091e71cec0..863a2f538b 100644 --- a/chrome/content/zotero/xpcom/rdf/serialize.js +++ b/chrome/content/zotero/xpcom/rdf/serialize.js @@ -192,7 +192,7 @@ $rdf.Serializer = function () { if (obj.termType == 'bnode' && (subjects[sz.toStr(obj)] && (force || (rootsHash[obj.toNT()] == undefined )))) {// and there are statements if (doneBnodesNT[obj.toNT()]) { // Ah-ha! a loop - throw "Serializer: Should be no loops "+obj; + throw new Error("Serializer: Should be no loops "+obj); } doneBnodesNT[obj.toNT()] = true; return dummyPropertyTree(obj, subjects, rootsHash); @@ -258,7 +258,7 @@ $rdf.Serializer = function () { for (var i=0; i']); break; default: - throw "Can't serialize object of type " + st.object.termType + " into XML"; + throw new Error("Can't serialize object of type " + st.object.termType + " into XML"); } // switch } } @@ -830,7 +830,7 @@ $rdf.Serializer = function () { '']); break; default: - throw "Can't serialize object of type " + st.object.termType + " into XML"; + throw new Error("Can't serialize object of type " + st.object.termType + " into XML"); } // switch } diff --git a/chrome/content/zotero/xpcom/rdf/term.js b/chrome/content/zotero/xpcom/rdf/term.js index 8a3d669bc8..5d3ec66779 100644 --- a/chrome/content/zotero/xpcom/rdf/term.js +++ b/chrome/content/zotero/xpcom/rdf/term.js @@ -215,7 +215,7 @@ $rdf.Formula.prototype.add = function (subj, pred, obj, why) { // Convenience methods on a formula allow the creation of new RDF terms: $rdf.Formula.prototype.sym = function (uri, name) { if(name != null) { - throw "This feature (kb.sym with 2 args) is removed. Do not assume prefix mappings." + throw new Error("This feature (kb.sym with 2 args) is removed. Do not assume prefix mappings."); if(!$rdf.ns[uri]) throw 'The prefix "' + uri + '" is not set in the API'; uri = $rdf.ns[uri] + name } @@ -316,7 +316,7 @@ $rdf.Formula.prototype.fromNT = function (str) { if(k < len - 1) { if(str[k + 1] == '@') lang = str.slice(k + 2, len); else if(str.slice(k + 1, k + 3) == '^^') dt = $rdf.fromNT(str.slice(k + 3, len)); - else throw "Can't convert string from NT: " + str + else throw new Error("Can't convert string from NT: " + str); } var str = (str.slice(1, k)); str = str.replace(/\\"/g, '"'); // unescape quotes ' @@ -334,7 +334,7 @@ $rdf.Formula.prototype.fromNT = function (str) { var x = new $rdf.Variable(str.slice(1)); return x; } - throw "Can't convert from NT: " + str; + throw new Error("Can't convert from NT: " + str); } $rdf.fromNT = $rdf.Formula.prototype.fromNT; // Not for inexpert user diff --git a/chrome/content/zotero/xpcom/schema.js b/chrome/content/zotero/xpcom/schema.js index 36547456fd..0fab829b33 100644 --- a/chrome/content/zotero/xpcom/schema.js +++ b/chrome/content/zotero/xpcom/schema.js @@ -101,7 +101,7 @@ Zotero.Schema = new function(){ return _initializeSchema() .then(function() { (Zotero.isStandalone ? Zotero.uiReadyPromise : Zotero.initializationPromise) - .then(1000) + .delay(1000) .then(async function () { await this.updateBundledFiles(); if (Zotero.Prefs.get('automaticScraperUpdates')) { @@ -221,7 +221,7 @@ Zotero.Schema = new function(){ // In Standalone, don't load bundled files until after UI is ready. In Firefox, load them as // soon initialization is done so that translation works before the Zotero pane is opened. (Zotero.isStandalone ? Zotero.uiReadyPromise : Zotero.initializationPromise) - .then(1000) + .delay(1000) .then(async function () { await this.updateBundledFiles(); if (Zotero.Prefs.get('automaticScraperUpdates')) { @@ -574,7 +574,7 @@ Zotero.Schema = new function(){ var ModeType = Zotero.Utilities.capitalize(modeType); var Mode = Zotero.Utilities.capitalize(mode); - var repotime = yield Zotero.File.getContentsFromURLAsync("resource://zotero/schema/repotime.txt"); + var repotime = yield Zotero.File.getResourceAsync("resource://zotero/schema/repotime.txt"); var date = Zotero.Date.sqlToDate(repotime.trim(), true); repotime = Zotero.Date.toUnixTimestamp(date); @@ -1454,7 +1454,7 @@ Zotero.Schema = new function(){ throw ('Schema type not provided to _getSchemaSQL()'); } - return Zotero.File.getContentsFromURLAsync("resource://zotero/schema/" + schema + ".sql"); + return Zotero.File.getResourceAsync(`resource://zotero/schema/${schema}.sql`); } diff --git a/chrome/content/zotero/xpcom/style.js b/chrome/content/zotero/xpcom/style.js index 690b46af81..a65b949eab 100644 --- a/chrome/content/zotero/xpcom/style.js +++ b/chrome/content/zotero/xpcom/style.js @@ -37,6 +37,8 @@ Zotero.Styles = new function() { this.ns = { "csl":"http://purl.org/net/xbiblio/csl" }; + + this.CSL_VALIDATOR_URL = "resource://zotero/csl-validator.js"; /** @@ -101,8 +103,9 @@ Zotero.Styles = new function() { var localeFile = {}; var locales = {}; var primaryDialects = {}; - var localesLocation = "chrome://zotero/content/locale/csl/locales.json"; - localeFile = JSON.parse(yield Zotero.File.getContentsFromURLAsync(localesLocation)); + localeFile = JSON.parse( + yield Zotero.File.getResourceAsync("chrome://zotero/content/locale/csl/locales.json") + ); primaryDialects = localeFile["primary-dialects"]; @@ -115,19 +118,10 @@ Zotero.Styles = new function() { this.primaryDialects = primaryDialects; // Load renamed styles - _renamedStyles = {}; - var xmlhttp = yield Zotero.HTTP.request( - "GET", - "resource://zotero/schema/renamed-styles.json", - { - responseType: 'json' - } + _renamedStyles = JSON.parse( + yield Zotero.File.getResourceAsync("resource://zotero/schema/renamed-styles.json") ); - // Map some obsolete styles to current ones - if (xmlhttp.response) { - _renamedStyles = xmlhttp.response; - } - + _initializationDeferred.resolve(); _initialized = true; @@ -252,18 +246,18 @@ Zotero.Styles = new function() { * @return {Promise} A promise representing the style file. This promise is rejected * with the validation error if validation fails, or resolved if it is not. */ - this.validate = function(style) { - var deferred = Zotero.Promise.defer(), - worker = new Worker("resource://zotero/csl-validator.js"); - worker.onmessage = function(event) { - if(event.data) { - deferred.reject(event.data); - } else { - deferred.resolve(); - } - }; - worker.postMessage(style); - return deferred.promise; + this.validate = function (style) { + return new Zotero.Promise((resolve, reject) => { + let worker = new Worker(this.CSL_VALIDATOR_URL); + worker.onmessage = function (event) { + if (event.data) { + reject(event.data); + } else { + resolve(); + } + }; + worker.postMessage(style); + }); } /** @@ -493,9 +487,7 @@ Zotero.Styles = new function() { yield Zotero.Styles.reinit(); // Refresh preferences windows - var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]. - getService(Components.interfaces.nsIWindowMediator); - var enumerator = wm.getEnumerator("zotero:pref"); + var enumerator = Services.wm.getEnumerator("zotero:pref"); while(enumerator.hasMoreElements()) { var win = enumerator.getNext(); if(win.Zotero_Preferences.Cite) { @@ -682,7 +674,7 @@ Zotero.Style = function (style, path) { '/csl:style/csl:info[1]/csl:link[@rel="source" or @rel="independent-parent"][1]/@href', Zotero.Styles.ns); if(this.source === this.styleID) { - throw "Style with ID "+this.styleID+" references itself as source"; + throw new Error("Style with ID "+this.styleID+" references itself as source"); } } diff --git a/chrome/content/zotero/xpcom/sync/syncEventListeners.js b/chrome/content/zotero/xpcom/sync/syncEventListeners.js index 4e177022a4..d06b316ed4 100644 --- a/chrome/content/zotero/xpcom/sync/syncEventListeners.js +++ b/chrome/content/zotero/xpcom/sync/syncEventListeners.js @@ -224,7 +224,7 @@ Zotero.Sync.EventListeners.IdleListener = { _backObserver: { observe: function (subject, topic, data) { - if (topic != 'back') { + if (topic !== 'active') { return; } diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index 57edc321ca..aa245ad7eb 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -2752,7 +2752,7 @@ Zotero.Translate.IO = { } if(nodes.getElementsByTagName("parsererror").length) { - throw "DOMParser error: loading data into data store failed"; + throw new Error("DOMParser error: loading data into data store failed"); } if("normalize" in nodes) nodes.normalize(); diff --git a/chrome/content/zotero/xpcom/translation/translate_firefox.js b/chrome/content/zotero/xpcom/translation/translate_firefox.js index 2e66d5f9db..ff5b15b98b 100644 --- a/chrome/content/zotero/xpcom/translation/translate_firefox.js +++ b/chrome/content/zotero/xpcom/translation/translate_firefox.js @@ -129,7 +129,7 @@ Zotero.Translate.DOMWrapper = new function() { // No double wrapping. if (isWrapper(obj)) - throw "Trying to double-wrap object!"; + throw new Error("Trying to double-wrap object!"); let dummy; if (typeof obj === "function") @@ -151,7 +151,7 @@ Zotero.Translate.DOMWrapper = new function() { // If we have a wrappable type, make sure it's wrapped. if (!isWrapper(x)) - throw "Trying to unwrap a non-wrapped object!"; + throw new Error("Trying to unwrap a non-wrapped object!"); var obj = x.SpecialPowers_wrappedObject; // unwrapped. @@ -253,7 +253,7 @@ Zotero.Translate.DOMWrapper = new function() { }, defineProperty(target, prop, descriptor) { - throw "Can't call defineProperty on SpecialPowers wrapped object"; + throw new Error("Can't call defineProperty on SpecialPowers wrapped object"); }, getOwnPropertyDescriptor(target, prop) { @@ -316,7 +316,7 @@ Zotero.Translate.DOMWrapper = new function() { }, preventExtensions(target) { - throw "Can't call preventExtensions on SpecialPowers wrapped object"; + throw new Error("Can't call preventExtensions on SpecialPowers wrapped object"); } }; @@ -870,7 +870,7 @@ Zotero.Translate.IO.Read.prototype = { "setCharacterSet":function(charset) { if(typeof charset !== "string") { - throw "Translate: setCharacterSet: charset must be a string"; + throw new Error("Translate: setCharacterSet: charset must be a string"); } // seek back to the beginning @@ -966,7 +966,7 @@ Zotero.Translate.IO.Write.prototype = { "setCharacterSet":function(charset) { if(typeof charset !== "string") { - throw "Translate: setCharacterSet: charset must be a string"; + throw new Error("Translate: setCharacterSet: charset must be a string"); } if(!this.outputStream) { diff --git a/chrome/content/zotero/xpcom/translation/translator.js b/chrome/content/zotero/xpcom/translation/translator.js index c7418dd2fb..5ad6388b7d 100644 --- a/chrome/content/zotero/xpcom/translation/translator.js +++ b/chrome/content/zotero/xpcom/translation/translator.js @@ -215,4 +215,7 @@ Zotero.Translator.replaceDeprecatedStatements = function(code) { Zotero.Translator.RUN_MODE_IN_BROWSER = 1; Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2; -Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4; \ No newline at end of file +Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4; +Zotero.Translator.TRANSLATOR_TYPES = TRANSLATOR_TYPES; +Zotero.Translator.TRANSLATOR_OPTIONAL_PROPERTIES = TRANSLATOR_OPTIONAL_PROPERTIES; +Zotero.Translator.TRANSLATOR_REQUIRED_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES; diff --git a/chrome/content/zotero/xpcom/translation/translators.js b/chrome/content/zotero/xpcom/translation/translators.js index 1e7a63292b..3936466127 100644 --- a/chrome/content/zotero/xpcom/translation/translators.js +++ b/chrome/content/zotero/xpcom/translation/translators.js @@ -181,10 +181,10 @@ Zotero.Translators = new function() { // add to cache _translators[translator.translatorID] = translator; - for (let type in TRANSLATOR_TYPES) { - if (translator.translatorType & TRANSLATOR_TYPES[type]) { + for (let type in Zotero.Translator.TRANSLATOR_TYPES) { + if (translator.translatorType & Zotero.Translator.TRANSLATOR_TYPES[type]) { _cache[type].push(translator); - if ((translator.translatorType & TRANSLATOR_TYPES.web) && translator.targetAll) { + if ((translator.translatorType & Zotero.Translator.TRANSLATOR_TYPES.web) && translator.targetAll) { _cache.webWithTargetAll.push(translator); } } @@ -193,8 +193,8 @@ Zotero.Translators = new function() { if (!dbCacheEntry) { yield Zotero.Translators.cacheInDB( fileName, - translator.serialize(TRANSLATOR_REQUIRED_PROPERTIES. - concat(TRANSLATOR_OPTIONAL_PROPERTIES)), + translator.serialize(Zotero.Translator.TRANSLATOR_REQUIRED_PROPERTIES. + concat(Zotero.Translator.TRANSLATOR_OPTIONAL_PROPERTIES)), lastModifiedTime ); } @@ -270,7 +270,7 @@ Zotero.Translators = new function() { return Zotero.Translators.load(infoRe.exec(source)[0], path, source); }) .catch(function() { - throw "Invalid or missing translator metadata JSON object in " + OS.Path.basename(path); + throw new Error("Invalid or missing translator metadata JSON object in " + OS.Path.basename(path)); }); } diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js index 1bb1a567f9..0ba43b1d3f 100644 --- a/chrome/content/zotero/xpcom/utilities.js +++ b/chrome/content/zotero/xpcom/utilities.js @@ -182,7 +182,7 @@ Zotero.Utilities = { var initialRe = new RegExp('^-?[' + allCaps + ']$'); if(typeof(author) != "string") { - throw "cleanAuthor: author must be a string"; + throw new Error("cleanAuthor: author must be a string"); } author = author.replace(/^[\s\u00A0\.\,\/\[\]\:]+/, '') @@ -246,7 +246,7 @@ Zotero.Utilities = { */ "trim":function(/**String*/ s) { if (typeof(s) != "string") { - throw "trim: argument must be a string"; + throw new Error("trim: argument must be a string"); } s = s.replace(/^\s+/, ""); @@ -272,7 +272,7 @@ Zotero.Utilities = { */ "superCleanString":function(/**String*/ x) { if(typeof(x) != "string") { - throw "superCleanString: argument must be a string"; + throw new Error("superCleanString: argument must be a string"); } var x = x.replace(/^[\x00-\x27\x29-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F\s]+/, ""); @@ -289,10 +289,8 @@ Zotero.Utilities = { url = url.trim(); if (!url) return false; - var ios = Components.classes["@mozilla.org/network/io-service;1"] - .getService(Components.interfaces.nsIIOService); try { - return ios.newURI(url, null, null).spec; // Valid URI if succeeds + return Services.io.newURI(url, null, null).spec; // Valid URI if succeeds } catch (e) { if (e instanceof Components.Exception && e.result == Components.results.NS_ERROR_MALFORMED_URI @@ -300,7 +298,7 @@ Zotero.Utilities = { if (tryHttp && /\w\.\w/.test(url)) { // Assume it's a URL missing "http://" part try { - return ios.newURI('http://' + url, null, null).spec; + return Services.io.newURI('http://' + url, null, null).spec; } catch (e) {} } @@ -317,7 +315,7 @@ Zotero.Utilities = { */ "cleanTags":function(/**String*/ x) { if(typeof(x) != "string") { - throw "cleanTags: argument must be a string"; + throw new Error("cleanTags: argument must be a string"); } x = x.replace(/]*>/gi, "\n"); @@ -331,7 +329,7 @@ Zotero.Utilities = { */ "cleanDOI":function(/**String**/ x) { if(typeof(x) != "string") { - throw "cleanDOI: argument must be a string"; + throw new Error("cleanDOI: argument must be a string"); } var doi = x.match(/10(?:\.[0-9]{4,})?\/[^\s]*[^\s\.,]/); @@ -1245,7 +1243,7 @@ Zotero.Utilities = { */ "quotemeta":function(literal) { if(typeof literal !== "string") { - throw "Argument "+literal+" must be a string in Zotero.Utilities.quotemeta()"; + throw new Error("Argument "+literal+" must be a string in Zotero.Utilities.quotemeta()"); } const metaRegexp = /[-[\]{}()*+?.\\^$|,#\s]/g; return literal.replace(metaRegexp, "\\$&"); @@ -1367,6 +1365,7 @@ Zotero.Utilities = { return strings.join(delimiter !== undefined ? delimiter : ", "); }, + /** * Generate a random string of length 'len' (defaults to 8) **/ diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index eded4fe357..4a41c00a2d 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -1338,6 +1338,150 @@ Zotero.Utilities.Internal = { .join('\n'); }, + /** + * Generate a function that produces a static output + * + * Zotero.lazy(fn) returns a function. The first time this function + * is called, it calls fn() and returns its output. Subsequent + * calls return the same output as the first without calling fn() + * again. + */ + lazy: function (fn) { + var x, called = false; + return function() { + if(!called) { + x = fn.apply(this); + called = true; + } + return x; + }; + }, + + serial: function (fn) { + Components.utils.import("resource://zotero/concurrentCaller.js"); + var caller = new ConcurrentCaller({ + numConcurrent: 1, + onError: e => Zotero.logError(e) + }); + return function () { + var args = arguments; + return caller.start(function () { + return fn.apply(this, args); + }.bind(this)); + }; + }, + + spawn: function (generator, thisObject) { + if (thisObject) { + return Zotero.Promise.coroutine(generator.bind(thisObject))(); + } + return Zotero.Promise.coroutine(generator)(); + }, + + /** + * Defines property on the object + * More compact way to do Object.defineProperty + * + * @param {Object} obj Target object + * @param {String} prop Property to be defined + * @param {Object} desc Propery descriptor. If not overriden, "enumerable" is true + * @param {Object} opts Options: + * lazy {Boolean} If true, the _getter_ is intended for late + * initialization of the property. The getter is replaced with a simple + * property once initialized. + */ + defineProperty: function(obj, prop, desc, opts) { + if (typeof prop != 'string') throw new Error("Property must be a string"); + var d = { __proto__: null, enumerable: true, configurable: true }; // Enumerable by default + for (let p in desc) { + if (!desc.hasOwnProperty(p)) continue; + d[p] = desc[p]; + } + + if (opts) { + if (opts.lazy && d.get) { + let getter = d.get; + d.configurable = true; // Make sure we can change the property later + d.get = function() { + let val = getter.call(this); + + // Redefine getter on this object as non-writable value + delete d.set; + delete d.get; + d.writable = false; + d.value = val; + Object.defineProperty(this, prop, d); + + return val; + } + } + } + + Object.defineProperty(obj, prop, d); + }, + + extendClass: function(superClass, newClass) { + newClass._super = superClass; + newClass.prototype = Object.create(superClass.prototype); + newClass.prototype.constructor = newClass; + }, + + /* + * Flattens mixed arrays/values in a passed _arguments_ object and returns + * an array of values -- allows for functions to accept both arrays of + * values and/or an arbitrary number of individual values + */ + flattenArguments: function (args){ + // Put passed scalar values into an array + if (args === null || typeof args == 'string' || typeof args.length == 'undefined') { + args = [args]; + } + + var returns = []; + for (var i=0; i Zotero.Utilities.Internal.defineProperty(...args); + + this.extendClass = (...args) => Zotero.Utilities.Internal.extendClass(...args); + this.getLocaleCollation = function () { - if (this.collation) { - return this.collation; - } - - try { - // DEBUG: Is this necessary, or will Intl.Collator just default to the same locales we're - // passing manually? - - let locales; - // Fx55+ - if (Services.locale.getAppLocalesAsBCP47) { - locales = Services.locale.getAppLocalesAsBCP47(); - } - else { - let locale; - // Fx54 - if (Services.locale.getAppLocale) { - locale = Services.locale.getAppLocale(); - } - // Fx <=53 - else { - locale = Services.locale.getApplicationLocale(); - locale = locale.getCategory('NSILOCALE_COLLATE'); - } - - // Extract a valid language tag - try { - locale = locale.match(/^[a-z]{2}(\-[A-Z]{2})?/)[0]; - } - catch (e) { - throw new Error(`Error parsing locale ${locale}`); - } - locales = [locale]; - } - - var collator = new Intl.Collator(locales, { - numeric: true, - sensitivity: 'base' - }); - } - catch (e) { - Zotero.logError(e); - - // Fall back to en-US sorting - try { - Zotero.logError("Falling back to en-US sorting"); - collator = new Intl.Collator(['en-US'], { - numeric: true, - sensitivity: 'base' - }); - } - catch (e) { - Zotero.logError(e); - - // If there's still an error, just skip sorting - collator = { - compare: function (a, b) { - return 0; - } - }; - } - } - - // Grab all ASCII punctuation and space at the begining of string - var initPunctuationRE = /^[\x20-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/; - // Punctuation that should be ignored when sorting - var ignoreInitRE = /["'[{(]+$/; - - // Until old code is updated, pretend we're returning an nsICollation - return this.collation = { - compareString: function (_, a, b) { - if (!a && !b) return 0; - if (!a || !b) return b ? -1 : 1; - - // Compare initial punctuation - var aInitP = initPunctuationRE.exec(a) || ''; - var bInitP = initPunctuationRE.exec(b) || ''; - - var aWordStart = 0, bWordStart = 0; - if (aInitP) { - aWordStart = aInitP[0].length; - aInitP = aInitP[0].replace(ignoreInitRE, ''); - } - if (bInitP) { - bWordStart = bInitP.length; - bInitP = bInitP[0].replace(ignoreInitRE, ''); - } - - // If initial punctuation is equivalent, use collator comparison - // that ignores all punctuation - // - // Update: Intl.Collator's ignorePunctuation also ignores whitespace, so we're - // no longer using it, meaning we could take out most of the code to handle - // initial punctuation separately, unless we think we'll at some point switch to - // a collation function that ignores punctuation but not whitespace. - if (aInitP == bInitP || !aInitP && !bInitP) return collator.compare(a, b); - - // Otherwise consider "attached" words as well, e.g. the order should be - // "__ n", "__z", "_a" - // We don't actually care what the attached word is, just whether it's - // there, since at this point we're guaranteed to have non-equivalent - // initial punctuation - if (aWordStart < a.length) aInitP += 'a'; - if (bWordStart < b.length) bInitP += 'a'; - - return aInitP.localeCompare(bInitP); - } - }; + return Zotero.Intl.collation; + } + + this.localeCompare = function (...args) { + return Zotero.Intl.compare(...args); } - this.defineProperty(this, "localeCompare", { - get: function() { - var collation = this.getLocaleCollation(); - return collation.compareString.bind(collation, 1); - } - }, {lazy: true}); - - /* - * Sets font size based on prefs -- intended for use on root element - * (zotero-pane, note window, etc.) - */ function setFontSize(rootElement) { - var size = Zotero.Prefs.get('fontSize'); - rootElement.style.fontSize = size + 'em'; - if (size <= 1) { - size = 'small'; - } - else if (size <= 1.25) { - size = 'medium'; - } - else { - size = 'large'; - } - // Custom attribute -- allows for additional customizations in zotero.css - rootElement.setAttribute('zoteroFontSize', size); + return Zotero.Utilities.Internal.setFontSize(rootElement); } - - /* - * Flattens mixed arrays/values in a passed _arguments_ object and returns - * an array of values -- allows for functions to accept both arrays of - * values and/or an arbitrary number of individual values - */ function flattenArguments(args){ - // Put passed scalar values into an array - if (args === null || typeof args == 'string' || typeof args.length == 'undefined') { - args = [args]; - } - - var returns = []; - for (var i=0; i Zotero.logError(e) - }); - return function () { - var args = arguments; - return caller.start(function () { - return fn.apply(this, args); - }.bind(this)); - }; + return Zotero.Utilities.Internal.lazy(fn); } + this.serial = function (fn) { + return Zotero.Utilities.Internal.serial(fn); + } this.spawn = function (generator, thisObject) { - if (thisObject) { - return Zotero.Promise.coroutine(generator.bind(thisObject))(); - } - return Zotero.Promise.coroutine(generator)(); + return Zotero.Utilities.Internal.spawn(generator, thisObject); } @@ -2200,315 +1812,6 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); }; }).call(Zotero); -Zotero.Prefs = new function(){ - // Privileged methods - this.init = init; - this.get = get; - this.set = set; - - this.register = register; - this.unregister = unregister; - this.observe = observe; - - // Public properties - this.prefBranch; - - function init(){ - this.prefBranch = Services.prefs.getBranch(ZOTERO_CONFIG.PREF_BRANCH); - - // Register observer to handle pref changes - this.register(); - - // Unregister observer handling pref changes - if (Zotero.addShutdownListener) { - Zotero.addShutdownListener(this.unregister.bind(this)); - } - - // Process pref version updates - var fromVersion = this.get('prefVersion'); - if (!fromVersion) { - fromVersion = 0; - } - var toVersion = 2; - if (fromVersion < toVersion) { - for (var i = fromVersion + 1; i <= toVersion; i++) { - switch (i) { - case 1: - // If a sync username is entered and ZFS is enabled, turn - // on-demand downloading off to maintain current behavior - if (this.get('sync.server.username')) { - if (this.get('sync.storage.enabled') - && this.get('sync.storage.protocol') == 'zotero') { - this.set('sync.storage.downloadMode.personal', 'on-sync'); - } - if (this.get('sync.storage.groups.enabled')) { - this.set('sync.storage.downloadMode.groups', 'on-sync'); - } - } - break; - - case 2: - // Re-show saveButton guidance panel (and clear old saveIcon pref). - // The saveButton guidance panel initially could auto-hide too easily. - this.clear('firstRunGuidanceShown.saveIcon'); - this.clear('firstRunGuidanceShown.saveButton'); - break; - } - } - this.set('prefVersion', toVersion); - } - } - - - /** - * Retrieve a preference - **/ - function get(pref, global){ - try { - if (global) { - var branch = Services.prefs.getBranch(""); - } - else { - var branch = this.prefBranch; - } - - switch (branch.getPrefType(pref)){ - case branch.PREF_BOOL: - return branch.getBoolPref(pref); - case branch.PREF_STRING: - return '' + branch.getComplexValue(pref, Components.interfaces.nsISupportsString); - case branch.PREF_INT: - return branch.getIntPref(pref); - } - } - catch (e){ - throw ("Invalid preference '" + pref + "'"); - } - } - - - /** - * Set a preference - **/ - function set(pref, value, global) { - try { - if (global) { - var branch = Services.prefs.getBranch(""); - } - else { - var branch = this.prefBranch; - } - - switch (branch.getPrefType(pref)) { - case branch.PREF_BOOL: - return branch.setBoolPref(pref, value); - case branch.PREF_STRING: - let str = Cc["@mozilla.org/supports-string;1"] - .createInstance(Ci.nsISupportsString); - str.data = value; - return branch.setComplexValue(pref, Ci.nsISupportsString, str); - case branch.PREF_INT: - return branch.setIntPref(pref, value); - - // If not an existing pref, create appropriate type automatically - case 0: - if (typeof value == 'boolean') { - Zotero.debug("Creating boolean pref '" + pref + "'"); - return branch.setBoolPref(pref, value); - } - if (typeof value == 'string') { - Zotero.debug("Creating string pref '" + pref + "'"); - return branch.setCharPref(pref, value); - } - if (parseInt(value) == value) { - Zotero.debug("Creating integer pref '" + pref + "'"); - return branch.setIntPref(pref, value); - } - throw new Error("Invalid preference value '" + value + "' for pref '" + pref + "'"); - } - } - catch (e) { - Zotero.logError(e); - throw new Error("Invalid preference '" + pref + "'"); - } - } - - - this.clear = function (pref, global) { - if (global) { - var branch = Services.prefs.getBranch(""); - } - else { - var branch = this.prefBranch; - } - branch.clearUserPref(pref); - } - - - this.resetBranch = function (exclude = []) { - var keys = this.prefBranch.getChildList("", {}); - for (let key of keys) { - if (this.prefBranch.prefHasUserValue(key)) { - if (exclude.includes(key)) { - continue; - } - Zotero.debug("Clearing " + key); - this.prefBranch.clearUserPref(key); - } - } - }; - - - // Import settings bundles - this.importSettings = function (str, uri) { - var ps = Services.prompt; - - if (!uri.match(/https:\/\/([^\.]+\.)?zotero.org\//)) { - Zotero.debug("Ignoring settings file not from https://zotero.org"); - return; - } - - str = Zotero.Utilities.trim(str.replace(/<\?xml.*\?>\s*/, '')); - Zotero.debug(str); - - var confirm = ps.confirm( - null, - "", - "Apply settings from zotero.org?" - ); - - if (!confirm) { - return; - } - - // TODO: parse settings XML - } - - // Handlers for some Zotero preferences - var _handlers = [ - [ "automaticScraperUpdates", function(val) { - if (val){ - Zotero.Schema.updateFromRepository(1); - } - else { - Zotero.Schema.stopRepositoryTimer(); - } - }], - ["fontSize", function (val) { - Zotero.setFontSize( - Zotero.getActiveZoteroPane().document.getElementById('zotero-pane') - ); - }], - [ "layout", function(val) { - Zotero.getActiveZoteroPane().updateLayout(); - }], - [ "note.fontSize", function(val) { - if (val < 6) { - Zotero.Prefs.set('note.fontSize', 11); - } - }], - [ "zoteroDotOrgVersionHeader", function(val) { - if (val) { - Zotero.VersionHeader.register(); - } - else { - Zotero.VersionHeader.unregister(); - } - }], - [ "sync.autoSync", function(val) { - if (val) { - Zotero.Sync.EventListeners.AutoSyncListener.register(); - Zotero.Sync.EventListeners.IdleListener.register(); - } - else { - Zotero.Sync.EventListeners.AutoSyncListener.unregister(); - Zotero.Sync.EventListeners.IdleListener.unregister(); - } - }], - [ "search.quicksearch-mode", function(val) { - var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] - .getService(Components.interfaces.nsIWindowMediator); - var enumerator = wm.getEnumerator("navigator:browser"); - while (enumerator.hasMoreElements()) { - var win = enumerator.getNext(); - if (!win.ZoteroPane) continue; - Zotero.updateQuickSearchBox(win.ZoteroPane.document); - } - - var enumerator = wm.getEnumerator("zotero:item-selector"); - while (enumerator.hasMoreElements()) { - var win = enumerator.getNext(); - if (!win.Zotero) continue; - Zotero.updateQuickSearchBox(win.document); - } - }] - ]; - - // - // Methods to register a preferences observer - // - function register(){ - this.prefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2); - this.prefBranch.addObserver("", this, false); - - // Register pre-set handlers - for (var i=0; i<_handlers.length; i++) { - this.registerObserver(_handlers[i][0], _handlers[i][1]); - } - } - - function unregister(){ - if (!this.prefBranch){ - return; - } - this.prefBranch.removeObserver("", this); - } - - /** - * @param {nsIPrefBranch} subject The nsIPrefBranch we're observing (after appropriate QI) - * @param {String} topic The string defined by NS_PREFBRANCH_PREFCHANGE_TOPIC_ID - * @param {String} data The name of the pref that's been changed (relative to subject) - */ - function observe(subject, topic, data){ - if (topic != "nsPref:changed" || !_observers[data] || !_observers[data].length) { - return; - } - - var obs = _observers[data]; - for (var i=0; i|false} - */ - this.addItemFromPage = Zotero.Promise.method(function (itemType, saveSnapshot, row) { - if (row == undefined && this.collectionsView && this.collectionsView.selection) { - row = this.collectionsView.selection.currentIndex; - } - - if (row !== undefined) { - if (!this.canEdit(row)) { - this.displayCannotEditLibraryMessage(); - return false; - } - - var collectionTreeRow = this.collectionsView.getRow(row); - if (collectionTreeRow.isPublications()) { - this.displayCannotAddToMyPublicationsMessage(); - return false; - } - } - - return this.addItemFromDocument(window.content.document, itemType, saveSnapshot, row); - }); - /** * Shows progress dialog for a webpage/snapshot save request */ @@ -4061,10 +4037,6 @@ var ZoteroPane = new function() * @return {Zotero.Item|false} - The saved item, or false if item can't be saved */ this.addItemFromURL = Zotero.Promise.coroutine(function* (url, itemType, saveSnapshot, row) { - if (window.content && url == window.content.document.location.href) { - return this.addItemFromPage(itemType, saveSnapshot, row); - } - url = Zotero.Utilities.resolveIntermediateURL(url); let [mimeType, hasNativeHandler] = yield Zotero.MIME.getMIMETypeFromURL(url); @@ -4079,12 +4051,12 @@ var ZoteroPane = new function() deferred.resolve(item) }); }; - var done = function () {} - var exception = function (e) { + try { + yield Zotero.HTTP.processDocuments([url], processor); + } catch (e) { Zotero.debug(e, 1); deferred.reject(e); } - Zotero.HTTP.loadDocuments([url], processor, done, exception); return deferred.promise; } diff --git a/components/zotero-service.js b/components/zotero-service.js index 8cb26d9b2f..4f3ae0db13 100644 --- a/components/zotero-service.js +++ b/components/zotero-service.js @@ -33,10 +33,14 @@ const Ci = Components.interfaces; /** XPCOM files to be loaded for all modes **/ const xpcomFilesAll = [ 'zotero', + 'intl', + 'prefs', 'dataDirectory', 'date', 'debug', 'error', + 'utilities', + 'utilities_internal', 'file', 'http', 'mimeTypeHandler', @@ -49,9 +53,7 @@ const xpcomFilesAll = [ 'translation/translate_firefox', 'translation/translator', 'translation/tlds', - 'utilities', 'isbn', - 'utilities_internal', 'utilities_translate' ]; @@ -456,9 +458,7 @@ var _isStandalone = null; */ function isStandalone() { if(_isStandalone === null) { - var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]. - getService(Components.interfaces.nsIXULAppInfo); - _isStandalone = appInfo.ID === 'zotero@chnm.gmu.edu'; + _isStandalone = Services.appinfo.ID === 'zotero@chnm.gmu.edu'; } return _isStandalone; } diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js index beb110947b..d8b674cd38 100644 --- a/defaults/preferences/zotero.js +++ b/defaults/preferences/zotero.js @@ -7,9 +7,9 @@ pref("extensions.zotero.firstRun2", true); pref("extensions.zotero@chnm.gmu.edu.description", "chrome://zotero/locale/zotero.properties"); pref("extensions.zotero.saveRelativeAttachmentPath", false); -pref("extensions.zotero.baseAttachmentPath", ''); +pref("extensions.zotero.baseAttachmentPath", ""); pref("extensions.zotero.useDataDir", false); -pref("extensions.zotero.dataDir", ''); +pref("extensions.zotero.dataDir", ""); pref("extensions.zotero.warnOnUnsafeDataDir", true); pref("extensions.zotero.debug.log",false); pref("extensions.zotero.debug.log.slowTime", 250); @@ -23,7 +23,7 @@ pref("extensions.zotero.automaticScraperUpdates",true); pref("extensions.zotero.zoteroDotOrgVersionHeader", true); pref("extensions.zotero.triggerProxyAuthentication", true); // Proxy auth URLs should respond successfully to HEAD requests over HTTP and HTTPS (in case of forced HTTPS requests) -pref("extensions.zotero.proxyAuthenticationURLs", 'https://www.acm.org,https://www.ebscohost.com,https://www.sciencedirect.com,https://ieeexplore.ieee.org,https://www.jstor.org,http://www.ovid.com,https://link.springer.com,https://www.tandfonline.com'); +pref("extensions.zotero.proxyAuthenticationURLs", "https://www.acm.org,https://www.ebscohost.com,https://www.sciencedirect.com,https://ieeexplore.ieee.org,https://www.jstor.org,http://www.ovid.com,https://link.springer.com,https://www.tandfonline.com"); pref("extensions.zotero.browserContentContextMenu", true); pref("extensions.zotero.openURL.resolver","http://worldcatlibraries.org/registry/gateway"); pref("extensions.zotero.openURL.version","1.0"); @@ -39,7 +39,7 @@ pref("extensions.zotero.recursiveCollections", false); pref("extensions.zotero.autoRecognizeFiles", true); pref("extensions.zotero.autoRenameFiles", true); pref("extensions.zotero.autoRenameFiles.fileTypes", "application/pdf"); -pref("extensions.zotero.attachmentRenameFormatString", '{%c - }{%y - }{%t{50}}'); +pref("extensions.zotero.attachmentRenameFormatString", "{%c - }{%y - }{%t{50}}"); pref("extensions.zotero.capitalizeTitles", false); pref("extensions.zotero.launchNonNativeFiles", false); pref("extensions.zotero.sortNotesChronologically", false); @@ -69,26 +69,26 @@ pref("extensions.zotero.lastRenameAssociatedFile", false); pref("extensions.zotero.lastLongTagMode", 0); pref("extensions.zotero.lastLongTagDelimiter", ";"); -pref("extensions.zotero.fallbackSort", 'firstCreator,date,title,dateAdded'); +pref("extensions.zotero.fallbackSort", "firstCreator,date,title,dateAdded"); pref("extensions.zotero.sortCreatorAsString", false); //Tag Cloud pref("extensions.zotero.tagCloud", false); // Keyboard shortcuts -pref("extensions.zotero.keys.openZotero", 'Z'); -pref("extensions.zotero.keys.toggleFullscreen", 'F'); -pref("extensions.zotero.keys.saveToZotero", 'S'); -pref("extensions.zotero.keys.newItem", 'N'); -pref("extensions.zotero.keys.newNote", 'O'); -pref("extensions.zotero.keys.library", 'L'); -pref("extensions.zotero.keys.quicksearch", 'K'); -pref("extensions.zotero.keys.copySelectedItemCitationsToClipboard", 'A'); -pref("extensions.zotero.keys.copySelectedItemsToClipboard", 'C'); -pref("extensions.zotero.keys.toggleTagSelector", 'T'); -pref("extensions.zotero.keys.sync", 'Y'); -pref("extensions.zotero.keys.toggleAllRead", 'R'); -pref("extensions.zotero.keys.toggleRead", '`'); +pref("extensions.zotero.keys.openZotero", "Z"); +pref("extensions.zotero.keys.toggleFullscreen", "F"); +pref("extensions.zotero.keys.saveToZotero", "S"); +pref("extensions.zotero.keys.newItem", "N"); +pref("extensions.zotero.keys.newNote", "O"); +pref("extensions.zotero.keys.library", "L"); +pref("extensions.zotero.keys.quicksearch", "K"); +pref("extensions.zotero.keys.copySelectedItemCitationsToClipboard", "A"); +pref("extensions.zotero.keys.copySelectedItemsToClipboard", "C"); +pref("extensions.zotero.keys.toggleTagSelector", "T"); +pref("extensions.zotero.keys.sync", "Y"); +pref("extensions.zotero.keys.toggleAllRead", "R"); +pref("extensions.zotero.keys.toggleRead", "`"); pref("extensions.zotero.search.quicksearch-mode", "fields"); @@ -107,10 +107,10 @@ pref("extensions.zotero.report.includeAllChildItems", true); pref("extensions.zotero.report.combineChildItems", true); // Export and citation settings -pref("extensions.zotero.export.lastTranslator", '14763d24-8ba0-45df-8f52-b8d1108e7ac9'); -pref("extensions.zotero.export.translatorSettings", 'true,false'); -pref("extensions.zotero.export.lastStyle", 'http://www.zotero.org/styles/chicago-note-bibliography'); -pref("extensions.zotero.export.bibliographySettings", 'save-as-rtf'); +pref("extensions.zotero.export.lastTranslator", "14763d24-8ba0-45df-8f52-b8d1108e7ac9"); +pref("extensions.zotero.export.translatorSettings", "true,false"); +pref("extensions.zotero.export.lastStyle", "http://www.zotero.org/styles/chicago-note-bibliography"); +pref("extensions.zotero.export.bibliographySettings", "save-as-rtf"); pref("extensions.zotero.export.displayCharsetOption", true); pref("extensions.zotero.export.citePaperJournalArticleURL", false); pref("extensions.zotero.cite.automaticJournalAbbreviations", true); @@ -119,7 +119,7 @@ pref("extensions.zotero.import.createNewCollection.fromFileOpenHandler", true); pref("extensions.zotero.rtfScan.lastInputFile", ""); pref("extensions.zotero.rtfScan.lastOutputFile", ""); -pref("extensions.zotero.export.quickCopy.setting", 'bibliography=http://www.zotero.org/styles/chicago-note-bibliography'); +pref("extensions.zotero.export.quickCopy.setting", "bibliography=http://www.zotero.org/styles/chicago-note-bibliography"); pref("extensions.zotero.export.quickCopy.dragLimit", 50); pref("extensions.zotero.export.quickCopy.quoteBlockquotes.plainText", true); pref("extensions.zotero.export.quickCopy.quoteBlockquotes.richText", true); @@ -147,14 +147,14 @@ pref("extensions.zotero.streaming.enabled", true); // Sync pref("extensions.zotero.sync.autoSync", true); -pref("extensions.zotero.sync.server.username", ''); +pref("extensions.zotero.sync.server.username", ""); pref("extensions.zotero.sync.server.compressData", true); pref("extensions.zotero.sync.storage.enabled", true); pref("extensions.zotero.sync.storage.protocol", "zotero"); pref("extensions.zotero.sync.storage.verified", false); -pref("extensions.zotero.sync.storage.scheme", 'https'); -pref("extensions.zotero.sync.storage.url", ''); -pref("extensions.zotero.sync.storage.username", ''); +pref("extensions.zotero.sync.storage.scheme", "https"); +pref("extensions.zotero.sync.storage.url", ""); +pref("extensions.zotero.sync.storage.username", ""); pref("extensions.zotero.sync.storage.maxDownloads", 4); pref("extensions.zotero.sync.storage.maxUploads", 2); pref("extensions.zotero.sync.storage.deleteDelayDays", 30); @@ -177,7 +177,7 @@ pref("extensions.zotero.purge.items", false); pref("extensions.zotero.purge.tags", false); // Zotero pane persistent data -pref("extensions.zotero.pane.persist", ''); +pref("extensions.zotero.pane.persist", ""); // Domains allowed to import, separated by a semicolon pref("extensions.zotero.ingester.allowedSites", ""); diff --git a/resource/concurrentCaller.js b/resource/concurrentCaller.js index fb64934cd2..87c3d12d1e 100644 --- a/resource/concurrentCaller.js +++ b/resource/concurrentCaller.js @@ -24,9 +24,13 @@ */ EXPORTED_SYMBOLS = ["ConcurrentCaller"]; -Components.utils.import('resource://zotero/require.js'); -var Promise = require('resource://zotero/bluebird.js'); +if (!(typeof process === 'object' && process + '' === '[object process]')) { + Components.utils.import('resource://zotero/require.js'); + var Promise = require('resource://zotero/bluebird.js'); +} else { + Promise = require('bluebird'); +} /** * Call a fixed number of functions at once, queueing the rest until slots @@ -272,3 +276,7 @@ ConcurrentCaller.prototype._log = function (msg) { this._logger("[ConcurrentCaller] " + (this._id ? `[${this._id}] ` : "") + msg); } }; + +if (typeof process === 'object' && process + '' === '[object process]'){ + module.exports = ConcurrentCaller; +} diff --git a/resource/config.js b/resource/config.js index 3df75b4aca..0ed1925825 100644 --- a/resource/config.js +++ b/resource/config.js @@ -28,4 +28,8 @@ var ZOTERO_CONFIG = { CONNECTORS_URL: "https://www.zotero.org/download/connectors" }; -EXPORTED_SYMBOLS = ["ZOTERO_CONFIG"]; +if (typeof process === 'object' && process + '' === '[object process]'){ + module.exports = ZOTERO_CONFIG; +} else { + EXPORTED_SYMBOLS = ["ZOTERO_CONFIG"]; +} diff --git a/test/tests/dateTest.js b/test/tests/dateTest.js index 9c416bc4c1..4330f94f10 100644 --- a/test/tests/dateTest.js +++ b/test/tests/dateTest.js @@ -28,7 +28,7 @@ describe("Zotero.Date", function() { beforeEach(function* () { if (Zotero.locale != 'en-US') { Zotero.locale = 'en-US'; - yield Zotero.Date.init(); + Zotero.Date.init(); } }); @@ -52,7 +52,7 @@ describe("Zotero.Date", function() { it("should resolve to English from unknown locale", function* () { Zotero.locale = 'zz'; - yield Zotero.Date.init(); + Zotero.Date.init(); let months = Zotero.Date.getMonths().short; assert.lengthOf(months, 12); assert.sameMembers(months, englishShort); @@ -60,7 +60,7 @@ describe("Zotero.Date", function() { it("shouldn't repeat English with unknown locale", function* () { Zotero.locale = 'zz'; - yield Zotero.Date.init(); + Zotero.Date.init(); let months = Zotero.Date.getMonths(true).short; assert.lengthOf(months, 12); assert.sameMembers(months, englishShort); @@ -71,7 +71,7 @@ describe("Zotero.Date", function() { beforeEach(function* () { if (Zotero.locale != 'fr-FR') { Zotero.locale = 'fr-FR'; - yield Zotero.Date.init(); + Zotero.Date.init(); } }); @@ -101,7 +101,7 @@ describe("Zotero.Date", function() { it("should resolve from two-letter locale", function* () { Zotero.locale = 'fr'; - yield Zotero.Date.init(); + Zotero.Date.init(); let months = Zotero.Date.getMonths().short; assert.lengthOf(months, 12); assert.sameMembers(months, frenchShort); @@ -109,7 +109,7 @@ describe("Zotero.Date", function() { it("should resolve from unknown four-letter locale with common prefix", function* () { Zotero.locale = 'fr-ZZ'; - yield Zotero.Date.init(); + Zotero.Date.init(); let months = Zotero.Date.getMonths().short; assert.lengthOf(months, 12); assert.sameMembers(months, frenchShort); diff --git a/test/tests/librariesTest.js b/test/tests/librariesTest.js index 2a8bfccfd4..ba2da772af 100644 --- a/test/tests/librariesTest.js +++ b/test/tests/librariesTest.js @@ -62,7 +62,7 @@ describe("Zotero.Libraries", function() { }); it("should return false for a non-existing ID", function() { assert.isFalse(Zotero.Libraries.exists(-1), "returns boolean false for a negative ID"); - let badID = Zotero.Libraries.getAll().map(library => library.libraryID).sort().pop() + 1; + let badID = Zotero.Libraries.getAll().map(lib => lib.libraryID).reduce((a, b) => (a < b ? b : a)) + 1; assert.isFalse(Zotero.Libraries.exists(badID), "returns boolean false for a non-existent positive ID"); }); }); diff --git a/test/tests/styleTest.js b/test/tests/styleTest.js index 7c76716092..6ea188838f 100644 --- a/test/tests/styleTest.js +++ b/test/tests/styleTest.js @@ -27,8 +27,8 @@ describe("Zotero.Styles", function() { it("should install the style from url", function* () { var getContentsFromURLAsync = Zotero.File.getContentsFromURLAsync; - sinon.stub(Zotero.File, 'getContentsFromURLAsync').callsFake(function(style) { - if (style.url == styleID) { + sinon.stub(Zotero.File, 'getContentsFromURLAsync').callsFake(function(url) { + if (url === styleID) { return Zotero.Promise.resolve(style); } else { return getContentsFromURLAsync.apply(Zotero.File, arguments); diff --git a/test/tests/utilities_internalTest.js b/test/tests/utilities_internalTest.js index a9aaff08de..109355b82e 100644 --- a/test/tests/utilities_internalTest.js +++ b/test/tests/utilities_internalTest.js @@ -67,7 +67,7 @@ describe("Zotero.Utilities.Internal", function () { }); afterEach(function () { - spy.reset(); + spy.resetHistory(); }); after(function () { @@ -86,7 +86,7 @@ describe("Zotero.Utilities.Internal", function () { let val = yield gen.next().value; assert.isTrue(val); assert.isTrue(spy.calledWith(i)); - spy.reset(); + spy.resetHistory(); } }); @@ -102,7 +102,7 @@ describe("Zotero.Utilities.Internal", function () { let val = yield gen.next().value; assert.isTrue(val); assert.isTrue(spy.calledWith(i)); - spy.reset(); + spy.resetHistory(); } // Another interval would put us over maxTime, so return false immediately diff --git a/test/tests/zoteroIntlTest.js b/test/tests/zoteroIntlTest.js new file mode 100644 index 0000000000..62d5038544 --- /dev/null +++ b/test/tests/zoteroIntlTest.js @@ -0,0 +1,26 @@ +"use strict"; + +describe("Zotero.Intl", function() { + describe("#getString()", function () { + it("should return the right plural form", function* () { + if (Zotero.locale != 'en-US') { + this.skip(); + } + var str1 = Zotero.getString('fileInterface.itemsWereImported') + .split(/;/)[1] + .replace('%1$S', 2); + var str2 = Zotero.getString('fileInterface.itemsWereImported', 2, 2); + assert.equal(str1, str2); + }); + }); + + describe("#localeCompare", function () { + it("shouldn't ignore whitespace", function () { + assert.equal(Zotero.localeCompare("Chang", "Chan H"), 1); + }); + + it("shouldn't ignore leading punctuation", function () { + assert.equal(Zotero.localeCompare("_Abcd", "Abcd"), -1); + }); + }); +}); diff --git a/test/tests/zoteroTest.js b/test/tests/zoteroTest.js index f95cf47a92..971e91e4f3 100644 --- a/test/tests/zoteroTest.js +++ b/test/tests/zoteroTest.js @@ -1,33 +1,6 @@ "use strict"; describe("Zotero", function() { - describe("#getString()", function () { - it("should return the right plural form", function* () { - if (Zotero.locale != 'en-US') { - this.skip(); - } - Components.utils.import("resource://gre/modules/PluralForm.jsm"); - var str1 = Zotero.getString('fileInterface.itemsWereImported') - .split(/;/)[1] - .replace('%1$S', 2); - var str2 = Zotero.getString('fileInterface.itemsWereImported', 2, 2); - Zotero.debug(str1); - Zotero.debug(str2); - assert.equal(str1, str2); - }); - }); - - - describe("#localeCompare", function () { - it("shouldn't ignore whitespace", function () { - assert.equal(Zotero.localeCompare("Chang", "Chan H"), 1); - }); - - it("shouldn't ignore leading punctuation", function () { - assert.equal(Zotero.localeCompare("_Abcd", "Abcd"), -1); - }); - }); - describe("VersionHeader", function () { describe("#update()", function () { var majorMinorVersion;