2728 lines
No EOL
73 KiB
JavaScript
2728 lines
No EOL
73 KiB
JavaScript
/*
|
||
***** 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 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 General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
***** END LICENSE BLOCK *****
|
||
*/
|
||
|
||
const ZOTERO_CONFIG = {
|
||
GUID: 'zotero@chnm.gmu.edu',
|
||
DB_REBUILD: false, // erase DB and recreate from schema
|
||
REPOSITORY_URL: 'https://api.zotero.org/repo',
|
||
REPOSITORY_CHECK_INTERVAL: 86400, // 24 hours
|
||
REPOSITORY_RETRY_INTERVAL: 3600, // 1 hour
|
||
BASE_URI: 'http://zotero.org/',
|
||
WWW_BASE_URL: 'http://www.zotero.org/',
|
||
SYNC_URL: 'https://sync.zotero.org/',
|
||
API_URL: 'https://api.zotero.org/',
|
||
PREF_BRANCH: 'extensions.zotero.'
|
||
};
|
||
|
||
/*
|
||
* Core functions
|
||
*/
|
||
var Zotero = new function(){
|
||
// Privileged (public) methods
|
||
this.init = init;
|
||
this.stateCheck = stateCheck;
|
||
this.getProfileDirectory = getProfileDirectory;
|
||
this.getInstallDirectory = getInstallDirectory;
|
||
this.getZoteroDirectory = getZoteroDirectory;
|
||
this.getStorageDirectory = getStorageDirectory;
|
||
this.getZoteroDatabase = getZoteroDatabase;
|
||
this.chooseZoteroDirectory = chooseZoteroDirectory;
|
||
this.debug = debug;
|
||
this.log = log;
|
||
this.getErrors = getErrors;
|
||
this.getSystemInfo = getSystemInfo;
|
||
this.varDump = varDump;
|
||
this.safeDebug = safeDebug;
|
||
this.getString = getString;
|
||
this.localeJoin = localeJoin;
|
||
this.getLocaleCollation = getLocaleCollation;
|
||
this.setFontSize = setFontSize;
|
||
this.flattenArguments = flattenArguments;
|
||
this.getAncestorByTagName = getAncestorByTagName;
|
||
this.join = join;
|
||
this.inArray = inArray;
|
||
this.arraySearch = arraySearch;
|
||
this.arrayToHash = arrayToHash;
|
||
this.hasValues = hasValues;
|
||
this.randomString = randomString;
|
||
this.moveToUnique = moveToUnique;
|
||
|
||
// Public properties
|
||
this.initialized = false;
|
||
this.skipLoading = false;
|
||
this.startupError;
|
||
this.__defineGetter__("startupErrorHandler", function() { return _startupErrorHandler; });
|
||
this.version;
|
||
this.platform;
|
||
this.locale;
|
||
this.dir; // locale direction: 'ltr' or 'rtl'
|
||
this.isMac;
|
||
this.isWin;
|
||
this.initialURL; // used by Schema to show the changelog on upgrades
|
||
|
||
|
||
this.__defineGetter__('userID', function () {
|
||
var sql = "SELECT value FROM settings WHERE "
|
||
+ "setting='account' AND key='userID'";
|
||
return Zotero.DB.valueQuery(sql);
|
||
});
|
||
|
||
this.__defineSetter__('userID', function (val) {
|
||
var sql = "REPLACE INTO settings VALUES ('account', 'userID', ?)";
|
||
Zotero.DB.query(sql, parseInt(val));
|
||
});
|
||
|
||
this.__defineGetter__('libraryID', function () {
|
||
var sql = "SELECT value FROM settings WHERE "
|
||
+ "setting='account' AND key='libraryID'";
|
||
return Zotero.DB.valueQuery(sql);
|
||
});
|
||
|
||
this.__defineSetter__('libraryID', function (val) {
|
||
var sql = "REPLACE INTO settings VALUES ('account', 'libraryID', ?)";
|
||
Zotero.DB.query(sql, parseInt(val));
|
||
});
|
||
|
||
this.__defineGetter__('username', function () {
|
||
var sql = "SELECT value FROM settings WHERE "
|
||
+ "setting='account' AND key='username'";
|
||
return Zotero.DB.valueQuery(sql);
|
||
});
|
||
|
||
this.__defineSetter__('username', function (val) {
|
||
var sql = "REPLACE INTO settings VALUES ('account', 'username', ?)";
|
||
Zotero.DB.query(sql, val);
|
||
});
|
||
|
||
this.getLocalUserKey = function (generate) {
|
||
if (_localUserKey) {
|
||
return _localUserKey;
|
||
}
|
||
|
||
var sql = "SELECT value FROM settings WHERE "
|
||
+ "setting='account' AND key='localUserKey'";
|
||
var key = Zotero.DB.valueQuery(sql);
|
||
|
||
// Generate a local user key if we don't have a global library id
|
||
if (!key && generate) {
|
||
key = Zotero.randomString(8);
|
||
var sql = "INSERT INTO settings VALUES ('account', 'localUserKey', ?)";
|
||
Zotero.DB.query(sql, key);
|
||
}
|
||
_localUserKey = key;
|
||
return key;
|
||
};
|
||
|
||
/**
|
||
* @property {Boolean} waiting Whether Zotero is waiting for other
|
||
* main thread events to be processed
|
||
*/
|
||
this.__defineGetter__('waiting', function () _waiting);
|
||
|
||
/**
|
||
* @property {Boolean} locked Whether all Zotero panes are locked
|
||
* with an overlay
|
||
*/
|
||
this.__defineGetter__('locked', function () _locked);
|
||
|
||
/**
|
||
* @property {Boolean} suppressUIUpdates Don't update UI on Notifier triggers
|
||
*/
|
||
this.suppressUIUpdates = false;
|
||
|
||
var _startupErrorHandler;
|
||
var _zoteroDirectory = false;
|
||
var _localizedStringBundle;
|
||
var _localUserKey;
|
||
var _waiting;
|
||
|
||
var _locked;
|
||
var _unlockCallbacks = [];
|
||
var _progressMeters;
|
||
var _lastPercentage;
|
||
|
||
/*
|
||
* Initialize the extension
|
||
*/
|
||
function init(){
|
||
if (this.initialized || this.skipLoading) {
|
||
return false;
|
||
}
|
||
|
||
var start = (new Date()).getTime()
|
||
|
||
// Register shutdown handler to call Zotero.shutdown()
|
||
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||
.getService(Components.interfaces.nsIObserverService);
|
||
observerService.addObserver({
|
||
observe: Zotero.shutdown
|
||
}, "quit-application", false);
|
||
|
||
// Load in the preferences branch for the extension
|
||
Zotero.Prefs.init();
|
||
|
||
Zotero.Debug.init();
|
||
|
||
this.mainThread = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
|
||
|
||
var appInfo =
|
||
Components.classes["@mozilla.org/xre/app-info;1"].
|
||
getService(Components.interfaces.nsIXULAppInfo);
|
||
this.appName = appInfo.name;
|
||
this.isFx3 = appInfo.platformVersion.indexOf('1.9') === 0;
|
||
this.isFx35 = appInfo.platformVersion.indexOf('1.9.1') === 0;
|
||
this.isFx31 = this.isFx35;
|
||
this.isFx36 = appInfo.platformVersion.indexOf('1.9.2') === 0;
|
||
this.isFx4 = appInfo.platformVersion[0] == 2;
|
||
|
||
this.isStandalone = appInfo.ID == ZOTERO_CONFIG['GUID'];
|
||
if(this.isStandalone) {
|
||
this.version = appInfo.version;
|
||
} else {
|
||
// Load in the extension version from the extension manager
|
||
if(this.isFx4) {
|
||
AddonManager.getAddonByID(ZOTERO_CONFIG['GUID'],
|
||
function(addon) { Zotero.version = addon.version; Zotero.addon = addon; });
|
||
} else {
|
||
var gExtensionManager =
|
||
Components.classes["@mozilla.org/extensions/manager;1"]
|
||
.getService(Components.interfaces.nsIExtensionManager);
|
||
this.version
|
||
= gExtensionManager.getItemForID(ZOTERO_CONFIG['GUID']).version;
|
||
}
|
||
}
|
||
|
||
// OS platform
|
||
var win = Components.classes["@mozilla.org/appshell/appShellService;1"]
|
||
.getService(Components.interfaces.nsIAppShellService)
|
||
.hiddenDOMWindow;
|
||
this.platform = win.navigator.platform;
|
||
this.isMac = (this.platform.substr(0, 3) == "Mac");
|
||
this.isWin = (this.platform.substr(0, 3) == "Win");
|
||
this.isLinux = (this.platform.substr(0, 5) == "Linux");
|
||
this.oscpu = win.navigator.oscpu;
|
||
|
||
// Installed extensions (Fx4 only)
|
||
if(this.isFx4) {
|
||
AddonManager.getAllAddons(function(addonList) {
|
||
Zotero.addons = addonList;
|
||
});
|
||
}
|
||
|
||
// Locale
|
||
var ph = Components.classes["@mozilla.org/network/protocol;1?name=http"].
|
||
getService(Components.interfaces.nsIHttpProtocolHandler);
|
||
if (ph.language.length == 2) {
|
||
this.locale = ph.language + '-' + ph.language.toUpperCase();
|
||
}
|
||
else {
|
||
this.locale = ph.language;
|
||
}
|
||
|
||
// Load in the localization stringbundle for use by getString(name)
|
||
var src = 'chrome://zotero/locale/zotero.properties';
|
||
var localeService = Components.classes['@mozilla.org/intl/nslocaleservice;1'].
|
||
getService(Components.interfaces.nsILocaleService);
|
||
var appLocale = localeService.getApplicationLocale();
|
||
|
||
var stringBundleService =
|
||
Components.classes["@mozilla.org/intl/stringbundle;1"]
|
||
.getService(Components.interfaces.nsIStringBundleService);
|
||
|
||
_localizedStringBundle = stringBundleService.createBundle(src, appLocale);
|
||
|
||
// Set the locale direction to Zotero.dir
|
||
// DEBUG: is there a better way to get the entity from JS?
|
||
var xmlhttp = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||
.createInstance();
|
||
xmlhttp.open('GET', 'chrome://global/locale/global.dtd', false);
|
||
xmlhttp.overrideMimeType('text/plain');
|
||
xmlhttp.send(null);
|
||
var matches = xmlhttp.responseText.match(/(ltr|rtl)/);
|
||
if (matches && matches[0] == 'rtl') {
|
||
this.dir = 'rtl';
|
||
}
|
||
else {
|
||
this.dir = 'ltr';
|
||
}
|
||
|
||
try {
|
||
var dataDir = this.getZoteroDirectory();
|
||
}
|
||
catch (e) {
|
||
// Zotero dir not found
|
||
if (e.name == 'NS_ERROR_FILE_NOT_FOUND') {
|
||
this.startupError = Zotero.getString('dataDir.notFound');
|
||
_startupErrorHandler = function() {
|
||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||
.getService(Components.interfaces.nsIWindowMediator);
|
||
var win = wm.getMostRecentWindow('navigator:browser');
|
||
|
||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
|
||
createInstance(Components.interfaces.nsIPromptService);
|
||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_OK)
|
||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING)
|
||
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
|
||
var index = ps.confirmEx(win,
|
||
Zotero.getString('general.error'),
|
||
this.startupError + '\n\n' +
|
||
Zotero.getString('dataDir.previousDir') + ' '
|
||
+ Zotero.Prefs.get('lastDataDir'),
|
||
buttonFlags, null,
|
||
Zotero.getString('dataDir.useProfileDir'),
|
||
Zotero.getString('general.locate'),
|
||
null, {});
|
||
|
||
// Revert to profile directory
|
||
if (index == 1) {
|
||
Zotero.chooseZoteroDirectory(false, true);
|
||
}
|
||
// Locate data directory
|
||
else if (index == 2) {
|
||
Zotero.chooseZoteroDirectory();
|
||
}
|
||
}
|
||
return;
|
||
} else if(e.name == "ZOTERO_DIR_MAY_EXIST") {
|
||
var app = Zotero.isStandalone ? Zotero.getString('app.standalone') : Zotero.getString('app.firefox');
|
||
var altApp = !Zotero.isStandalone ? Zotero.getString('app.standalone') : Zotero.getString('app.firefox');
|
||
|
||
var message = Zotero.getString("dataDir.standaloneMigration.description", [app, altApp]);
|
||
if(e.multipleProfiles) {
|
||
message += "\n\n"+Zotero.getString("dataDir.standaloneMigration.multipleProfiles", [app, altApp]);
|
||
}
|
||
|
||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
|
||
createInstance(Components.interfaces.nsIPromptService);
|
||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_YES)
|
||
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_NO)
|
||
+ (ps.BUTTON_POS_2) * (ps.BUTTON_TITLE_IS_STRING);
|
||
var index = ps.confirmEx(null, Zotero.getString("dataDir.standaloneMigration.title"), message,
|
||
buttonFlags, null, null,
|
||
Zotero.getString('dataDir.standaloneMigration.selectCustom'),
|
||
null, {});
|
||
|
||
// Migrate data directory
|
||
if (index == 0) {
|
||
// copy prefs
|
||
var prefsFile = e.profile.clone();
|
||
prefsFile.append("prefs.js");
|
||
if(prefsFile.exists()) {
|
||
// build sandbox
|
||
var sandbox = new Components.utils.Sandbox("http://www.example.com/");
|
||
Components.utils.evalInSandbox(
|
||
"var prefs = {};"+
|
||
"function user_pref(key, val) {"+
|
||
"prefs[key] = val;"+
|
||
"}"
|
||
, sandbox);
|
||
|
||
// remove comments
|
||
var prefsJs = Zotero.File.getContents(prefsFile);
|
||
prefsJs = prefsJs.replace(/^#[^\r\n]*$/mg, "");
|
||
|
||
// evaluate
|
||
Components.utils.evalInSandbox(prefsJs, sandbox);
|
||
var prefs = new XPCSafeJSObjectWrapper(sandbox.prefs);
|
||
for(var key in prefs) {
|
||
if(key.substr(0, ZOTERO_CONFIG.PREF_BRANCH.length) == ZOTERO_CONFIG.PREF_BRANCH) {
|
||
Zotero.Prefs.set(key.substr(ZOTERO_CONFIG.PREF_BRANCH.length), prefs[key]);
|
||
}
|
||
}
|
||
}
|
||
|
||
// also set data dir if no custom data dir is now defined
|
||
if(!Zotero.Prefs.get("useDataDir")) {
|
||
var dir = e.dir.QueryInterface(Components.interfaces.nsILocalFile);
|
||
Zotero.Prefs.set('dataDir', dir.persistentDescriptor);
|
||
Zotero.Prefs.set('lastDataDir', dir.path);
|
||
Zotero.Prefs.set('useDataDir', true);
|
||
}
|
||
}
|
||
// Create new data directory
|
||
else if (index == 1) {
|
||
Zotero.File.createDirectoryIfMissing(e.curDir);
|
||
}
|
||
// Locate new data directory
|
||
else if (index == 2) {
|
||
Zotero.chooseZoteroDirectory();
|
||
}
|
||
var dataDir = this.getZoteroDirectory();
|
||
}
|
||
// DEBUG: handle more startup errors
|
||
else {
|
||
throw (e);
|
||
return;
|
||
}
|
||
}
|
||
|
||
Zotero.VersionHeader.init();
|
||
|
||
// Check for DB restore
|
||
var restoreFile = dataDir.clone();
|
||
restoreFile.append('restore-from-server');
|
||
if (restoreFile.exists()) {
|
||
try {
|
||
// TODO: better error handling
|
||
|
||
// TODO: prompt for location
|
||
// TODO: Back up database
|
||
|
||
restoreFile.remove(false);
|
||
|
||
var dbfile = Zotero.getZoteroDatabase();
|
||
dbfile.remove(false);
|
||
|
||
// Recreate database with no quick start guide
|
||
Zotero.Schema.skipDefaultData = true;
|
||
Zotero.Schema.updateSchema();
|
||
|
||
this.restoreFromServer = true;
|
||
}
|
||
catch (e) {
|
||
// Restore from backup?
|
||
alert(e);
|
||
}
|
||
}
|
||
|
||
try {
|
||
Zotero.DB.test();
|
||
}
|
||
catch (e) {
|
||
if (e.name == 'NS_ERROR_FILE_ACCESS_DENIED') {
|
||
var msg = Zotero.localeJoin([
|
||
Zotero.getString('startupError.databaseCannotBeOpened'),
|
||
Zotero.getString('startupError.checkPermissions')
|
||
]);
|
||
this.startupError = msg;
|
||
}
|
||
|
||
Components.utils.reportError(e);
|
||
this.skipLoading = true;
|
||
Zotero.DB.skipBackup = true;
|
||
return;
|
||
}
|
||
|
||
// Add notifier queue callbacks to the DB layer
|
||
Zotero.DB.addCallback('begin', Zotero.Notifier.begin);
|
||
Zotero.DB.addCallback('commit', Zotero.Notifier.commit);
|
||
Zotero.DB.addCallback('rollback', Zotero.Notifier.reset);
|
||
|
||
Zotero.Fulltext.init();
|
||
|
||
// Trigger updating of schema and scrapers
|
||
if (Zotero.Schema.userDataUpgradeRequired()) {
|
||
var upgraded = Zotero.Schema.showUpgradeWizard();
|
||
if (!upgraded) {
|
||
this.skipLoading = true;
|
||
Zotero.DB.skipBackup = true;
|
||
return false;
|
||
}
|
||
}
|
||
// If no userdata upgrade, still might need to process system/scrapers
|
||
else {
|
||
try {
|
||
var updated = Zotero.Schema.updateSchema();
|
||
}
|
||
catch (e) {
|
||
if (typeof e == 'string' && e.match('newer than SQL file')) {
|
||
var kbURL = "http://zotero.org/support/kb/newer_db_version";
|
||
var msg = Zotero.localeJoin([
|
||
Zotero.getString('startupError.zoteroVersionIsOlder'),
|
||
Zotero.getString('startupError.zoteroVersionIsOlder.upgrade')
|
||
]) + "\n\n"
|
||
+ Zotero.getString('startupError.zoteroVersionIsOlder.current', Zotero.version) + "\n\n"
|
||
+ Zotero.getString('general.seeForMoreInformation', kbURL);
|
||
this.startupError = msg;
|
||
}
|
||
else {
|
||
this.startupError = Zotero.getString('startupError.databaseUpgradeError');
|
||
}
|
||
Components.utils.reportError(e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Zotero.DB.startDummyStatement();
|
||
Zotero.Schema.updateFromRepository();
|
||
|
||
// Populate combined tables for custom types and fields -- this is likely temporary
|
||
if (!upgraded && !updated) {
|
||
Zotero.Schema.updateCustomTables();
|
||
}
|
||
|
||
// Initialize various services
|
||
Zotero.Integration.init();
|
||
|
||
if(Zotero.isStandalone) {
|
||
Zotero.Connector.init();
|
||
}
|
||
|
||
Zotero.Zeroconf.init();
|
||
|
||
Zotero.Sync.init();
|
||
Zotero.Sync.Runner.init();
|
||
|
||
Zotero.MIMETypeHandler.init();
|
||
Zotero.Proxies.init();
|
||
|
||
// Initialize keyboard shortcuts
|
||
Zotero.Keys.init();
|
||
|
||
this.initialized = true;
|
||
Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
/*
|
||
* Check if a DB transaction is open and, if so, disable Zotero
|
||
*/
|
||
function stateCheck() {
|
||
if (Zotero.DB.transactionInProgress()) {
|
||
this.initialized = false;
|
||
this.skipLoading = true;
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
this.shutdown = function (subject, topic, data) {
|
||
Zotero.debug("Shutting down Zotero");
|
||
Zotero.removeTempDirectory();
|
||
return true;
|
||
}
|
||
|
||
|
||
function getProfileDirectory(){
|
||
return Components.classes["@mozilla.org/file/directory_service;1"]
|
||
.getService(Components.interfaces.nsIProperties)
|
||
.get("ProfD", Components.interfaces.nsIFile);
|
||
}
|
||
|
||
|
||
function getInstallDirectory() {
|
||
if(this.isStandalone) {
|
||
var dir = Components.classes["@mozilla.org/file/directory_service;1"]
|
||
.getService(Components.interfaces.nsIProperties)
|
||
.get("CurProcD", Components.interfaces.nsILocalFile);
|
||
return dir;
|
||
} else {
|
||
if(this.isFx4) {
|
||
while(!Zotero.addon) Zotero.mainThread.processNextEvent(true);
|
||
var resourceURI = Zotero.addon.getResourceURI();
|
||
return resourceURI.QueryInterface(Components.interfaces.nsIFileURL).file;
|
||
} else {
|
||
var id = ZOTERO_CONFIG.GUID;
|
||
var em = Components.classes["@mozilla.org/extensions/manager;1"].
|
||
getService(Components.interfaces.nsIExtensionManager);
|
||
return em.getInstallLocation(id).getItemLocation(id);
|
||
}
|
||
}
|
||
}
|
||
|
||
function getDefaultProfile(prefDir) {
|
||
// find profiles.ini file
|
||
var profilesIni = prefDir.clone();
|
||
profilesIni.append("profiles.ini");
|
||
if(!profilesIni.exists()) return false;
|
||
var iniContents = Zotero.File.getContents(profilesIni);
|
||
|
||
// cheap and dirty ini parser
|
||
var curSection = null;
|
||
var defaultSection = null;
|
||
var nSections = 0;
|
||
for each(var line in iniContents.split(/(?:\r?\n|\r)/)) {
|
||
let tline = line.trim();
|
||
if(tline[0] == "[" && tline[tline.length-1] == "]") {
|
||
curSection = {};
|
||
if(tline != "[General]") nSections++;
|
||
} else if(curSection && tline != "") {
|
||
let equalsIndex = tline.indexOf("=");
|
||
let key = tline.substr(0, equalsIndex);
|
||
let val = tline.substr(equalsIndex+1);
|
||
curSection[key] = val;
|
||
if(key == "Default" && val == "1") {
|
||
defaultSection = curSection;
|
||
}
|
||
}
|
||
}
|
||
if(!defaultSection && curSection) defaultSection = curSection;
|
||
|
||
// parse out ini to reveal profile
|
||
if(!defaultSection || !defaultSection.Path) return false;
|
||
if(defaultSection.IsRelative) {
|
||
var defaultProfile = prefDir.clone();
|
||
defaultProfile.QueryInterface(Components.interfaces.nsILocalFile).appendRelativePath(defaultSection.Path);
|
||
} else {
|
||
var defaultProfile = Components.classes["@mozilla.org/file/local;1"]
|
||
.createInstance(Components.interfaces.nsILocalFile);
|
||
defaultProfile.initWithPath(defaultSection.Path);
|
||
}
|
||
|
||
if(!defaultProfile.exists()) return false;
|
||
return [defaultProfile, nSections > 1];
|
||
}
|
||
|
||
function getZoteroDirectory(){
|
||
if (_zoteroDirectory != false) {
|
||
// Return a clone of the file pointer so that callers can modify it
|
||
return _zoteroDirectory.clone();
|
||
}
|
||
|
||
if (Zotero.Prefs.get('useDataDir')) {
|
||
var file = Components.classes["@mozilla.org/file/local;1"].
|
||
createInstance(Components.interfaces.nsILocalFile);
|
||
try {
|
||
file.persistentDescriptor = Zotero.Prefs.get('dataDir');
|
||
}
|
||
catch (e) {
|
||
Zotero.debug("Persistent descriptor in extensions.zotero.dataDir did not resolve", 1);
|
||
e = { name: "NS_ERROR_FILE_NOT_FOUND" };
|
||
throw (e);
|
||
}
|
||
if (!file.exists()) {
|
||
var e = { name: "NS_ERROR_FILE_NOT_FOUND" };
|
||
throw (e);
|
||
}
|
||
}
|
||
else {
|
||
var file = Zotero.getProfileDirectory();
|
||
file.append('zotero');
|
||
|
||
// if standalone and no directory yet, check Firefox directory
|
||
// or if in Firefox and no directory yet, check standalone Zotero directory
|
||
if(!file.exists()) {
|
||
if(Zotero.isStandalone) {
|
||
if(Zotero.isWin) {
|
||
var path = "../Mozilla/Firefox";
|
||
} else if(Zotero.isMac) {
|
||
var path = "../Firefox";
|
||
} else {
|
||
var path = "../.mozilla/firefox";
|
||
}
|
||
} else {
|
||
if(Zotero.isWin) {
|
||
var path = "../../Zotero";
|
||
} else if(Zotero.isMac) {
|
||
var path = "../Zotero";
|
||
} else {
|
||
var path = "../../.zotero";
|
||
}
|
||
}
|
||
|
||
// get Firefox directory
|
||
var prefDir = Components.classes["@mozilla.org/file/directory_service;1"]
|
||
.getService(Components.interfaces.nsIProperties)
|
||
.get("DefProfRt", Components.interfaces.nsILocalFile).parent
|
||
.QueryInterface(Components.interfaces.nsILocalFile);
|
||
prefDir.appendRelativePath(path);
|
||
|
||
// get default profile
|
||
var defProfile = getDefaultProfile(prefDir);
|
||
if(defProfile) {
|
||
// get Zotero directory
|
||
var zoteroDir = defProfile[0].clone();
|
||
zoteroDir.append("zotero");
|
||
|
||
if(zoteroDir.exists()) {
|
||
// if Zotero directory exists in default profile for alternative app, ask
|
||
// whether to use
|
||
var e = { name:"ZOTERO_DIR_MAY_EXIST", curDir:file, profile:defProfile[0], dir:zoteroDir, multipleProfiles:defProfile[1] };
|
||
throw (e);
|
||
}
|
||
}
|
||
}
|
||
|
||
Zotero.File.createDirectoryIfMissing(file);
|
||
}
|
||
Zotero.debug("Using data directory " + file.path);
|
||
|
||
_zoteroDirectory = file;
|
||
return file.clone();
|
||
}
|
||
|
||
|
||
function getStorageDirectory(){
|
||
var file = Zotero.getZoteroDirectory();
|
||
|
||
file.append('storage');
|
||
Zotero.File.createDirectoryIfMissing(file);
|
||
return file;
|
||
}
|
||
|
||
function getZoteroDatabase(name, ext){
|
||
name = name ? name + '.sqlite' : 'zotero.sqlite';
|
||
ext = ext ? '.' + ext : '';
|
||
|
||
var file = Zotero.getZoteroDirectory();
|
||
file.append(name + ext);
|
||
return file;
|
||
}
|
||
|
||
|
||
/**
|
||
* @return {nsIFile}
|
||
*/
|
||
this.getTempDirectory = function () {
|
||
var tmp = this.getZoteroDirectory();
|
||
tmp.append('tmp');
|
||
Zotero.File.createDirectoryIfMissing(tmp);
|
||
return tmp;
|
||
}
|
||
|
||
|
||
this.removeTempDirectory = function () {
|
||
var tmp = this.getZoteroDirectory();
|
||
tmp.append('tmp');
|
||
if (tmp.exists()) {
|
||
try {
|
||
tmp.remove(true);
|
||
}
|
||
catch (e) {}
|
||
}
|
||
}
|
||
|
||
|
||
this.getStylesDirectory = function () {
|
||
var dir = this.getZoteroDirectory();
|
||
dir.append('styles');
|
||
Zotero.File.createDirectoryIfMissing(dir);
|
||
return dir;
|
||
}
|
||
|
||
|
||
this.getTranslatorsDirectory = function () {
|
||
var dir = this.getZoteroDirectory();
|
||
dir.append('translators');
|
||
Zotero.File.createDirectoryIfMissing(dir);
|
||
return dir;
|
||
}
|
||
|
||
|
||
function chooseZoteroDirectory(forceRestartNow, useProfileDir) {
|
||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||
.getService(Components.interfaces.nsIWindowMediator);
|
||
var win = wm.getMostRecentWindow('navigator:browser');
|
||
|
||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||
.getService(Components.interfaces.nsIPromptService);
|
||
|
||
if (useProfileDir) {
|
||
Zotero.Prefs.set('useDataDir', false);
|
||
}
|
||
else {
|
||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||
while (true) {
|
||
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||
.createInstance(nsIFilePicker);
|
||
fp.init(win, Zotero.getString('dataDir.selectDir'), nsIFilePicker.modeGetFolder);
|
||
fp.appendFilters(nsIFilePicker.filterAll);
|
||
if (fp.show() == nsIFilePicker.returnOK) {
|
||
var file = fp.file;
|
||
|
||
if (file.directoryEntries.hasMoreElements()) {
|
||
var dbfile = file.clone();
|
||
dbfile.append('zotero.sqlite');
|
||
|
||
// Warn if non-empty and no zotero.sqlite
|
||
if (!dbfile.exists()) {
|
||
var buttonFlags = ps.STD_YES_NO_BUTTONS;
|
||
var index = ps.confirmEx(win,
|
||
Zotero.getString('dataDir.selectedDirNonEmpty.title'),
|
||
Zotero.getString('dataDir.selectedDirNonEmpty.text'),
|
||
buttonFlags, null, null, null, null, {});
|
||
|
||
// Not OK -- return to file picker
|
||
if (index == 1) {
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
var buttonFlags = ps.STD_YES_NO_BUTTONS;
|
||
var index = ps.confirmEx(win,
|
||
//Zotero.getString('dataDir.selectedDirEmpty.title'),
|
||
//Zotero.getString('dataDir.selectedDirEmpty.text'),
|
||
'Directory Empty',
|
||
'The directory you selected is empty. To move an existing Zotero data directory, '
|
||
+ 'you will need to manually copy files from the existing data directory to the new location. '
|
||
+ 'See http://zotero.org/support/zotero_data for more information.\n\nUse the new directory?',
|
||
|
||
buttonFlags, null, null, null, null, {});
|
||
|
||
// Not OK -- return to file picker
|
||
if (index == 1) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
|
||
// Set new data directory
|
||
Zotero.Prefs.set('dataDir', file.persistentDescriptor);
|
||
Zotero.Prefs.set('lastDataDir', file.path);
|
||
Zotero.Prefs.set('useDataDir', true);
|
||
break;
|
||
}
|
||
else {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING);
|
||
if (!forceRestartNow) {
|
||
buttonFlags += (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
|
||
}
|
||
var index = ps.confirmEx(win,
|
||
Zotero.getString('general.restartRequired'),
|
||
Zotero.getString('general.restartRequiredForChange'),
|
||
buttonFlags,
|
||
Zotero.getString('general.restartNow'),
|
||
forceRestartNow ? null : Zotero.getString('general.restartLater'),
|
||
null, null, {});
|
||
|
||
if (index == 0) {
|
||
var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
|
||
.getService(Components.interfaces.nsIAppStartup);
|
||
appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit
|
||
| Components.interfaces.nsIAppStartup.eRestart);
|
||
}
|
||
|
||
return useProfileDir ? true : file;
|
||
}
|
||
|
||
|
||
/*
|
||
* Debug logging function
|
||
*
|
||
* Uses prefs e.z.debug.log and e.z.debug.level (restart required)
|
||
*
|
||
* Defaults to log level 3 if level not provided
|
||
*/
|
||
function debug(message, level) {
|
||
Zotero.Debug.log(message, level);
|
||
}
|
||
|
||
|
||
/*
|
||
* Log a message to the Mozilla JS error console
|
||
*
|
||
* |type| is a string with one of the flag types in nsIScriptError:
|
||
* 'error', 'warning', 'exception', 'strict'
|
||
*/
|
||
function log(message, type, sourceName, sourceLine, lineNumber, columnNumber) {
|
||
var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
|
||
.getService(Components.interfaces.nsIConsoleService);
|
||
var scriptError = Components.classes["@mozilla.org/scripterror;1"]
|
||
.createInstance(Components.interfaces.nsIScriptError);
|
||
|
||
if (!type) {
|
||
type = 'warning';
|
||
}
|
||
var flags = scriptError[type + 'Flag'];
|
||
|
||
scriptError.init(
|
||
message,
|
||
sourceName ? sourceName : null,
|
||
sourceLine != undefined ? sourceLine : null,
|
||
lineNumber != undefined ? lineNumber : null,
|
||
columnNumber != undefined ? columnNumber : null,
|
||
flags,
|
||
'component javascript'
|
||
);
|
||
consoleService.logMessage(scriptError);
|
||
}
|
||
|
||
|
||
function getErrors(asStrings) {
|
||
var errors = [];
|
||
var cs = Components.classes["@mozilla.org/consoleservice;1"].
|
||
getService(Components.interfaces.nsIConsoleService);
|
||
var messages = {};
|
||
cs.getMessageArray(messages, {})
|
||
|
||
var skip = ['CSS Parser', 'content javascript'];
|
||
|
||
msgblock:
|
||
for each(var msg in messages.value) {
|
||
//Zotero.debug(msg);
|
||
try {
|
||
msg.QueryInterface(Components.interfaces.nsIScriptError);
|
||
//Zotero.debug(msg);
|
||
if (skip.indexOf(msg.category) != -1 || msg.flags & msg.warningFlag) {
|
||
continue;
|
||
}
|
||
}
|
||
catch (e) { }
|
||
|
||
var blacklist = [
|
||
"No chrome package registered for chrome://communicator",
|
||
'[JavaScript Error: "Components is not defined" {file: "chrome://nightly/content/talkback/talkback.js',
|
||
'[JavaScript Error: "document.getElementById("sanitizeItem")',
|
||
'No chrome package registered for chrome://piggy-bank',
|
||
'[JavaScript Error: "[Exception... "\'Component is not available\' when calling method: [nsIHandlerService::getTypeFromExtension',
|
||
'[JavaScript Error: "this._uiElement is null',
|
||
'Error: a._updateVisibleText is not a function',
|
||
'[JavaScript Error: "Warning: unrecognized command line flag ',
|
||
'[JavaScript Error: "Warning: unrecognized command line flag -foreground',
|
||
'LibX:',
|
||
'function skype_',
|
||
'[JavaScript Error: "uncaught exception: Permission denied to call method Location.toString"]',
|
||
'CVE-2009-3555'
|
||
];
|
||
|
||
for (var i=0; i<blacklist.length; i++) {
|
||
if (msg.message.indexOf(blacklist[i]) != -1) {
|
||
//Zotero.debug("Skipping blacklisted error: " + msg.message);
|
||
continue msgblock;
|
||
}
|
||
}
|
||
|
||
// Remove password in malformed XML messages
|
||
if (msg.category == 'malformed-xml') {
|
||
try {
|
||
// msg.message is read-only, so store separately
|
||
var altMessage = msg.message.replace(/(file: "https?:\/\/[^:]+:)([^@]+)(@[^"]+")/, "$1********$3");
|
||
}
|
||
catch (e) {}
|
||
}
|
||
|
||
if (asStrings) {
|
||
errors.push(altMessage ? altMessage : msg.message)
|
||
}
|
||
else {
|
||
errors.push(msg);
|
||
}
|
||
}
|
||
return errors;
|
||
}
|
||
|
||
|
||
function getSystemInfo() {
|
||
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].
|
||
getService(Components.interfaces.nsIXULAppInfo);
|
||
|
||
var info = {
|
||
version: Zotero.version,
|
||
platform: Zotero.platform,
|
||
oscpu: Zotero.oscpu,
|
||
locale: Zotero.locale,
|
||
appName: appInfo.name,
|
||
appVersion: appInfo.version,
|
||
extensions: this.getInstalledExtensions().join(', ')
|
||
};
|
||
|
||
var str = '';
|
||
for (var key in info) {
|
||
str += key + ' => ' + info[key] + ', ';
|
||
}
|
||
str = str.substr(0, str.length - 2);
|
||
return str;
|
||
}
|
||
|
||
|
||
/**
|
||
* @return {String[]} Array of extension names and versions
|
||
*/
|
||
this.getInstalledExtensions = function () {
|
||
if(this.isFx4) {
|
||
while(!Zotero.addons) Zotero.mainThread.processNextEvent(true);
|
||
var installed = Zotero.addons;
|
||
} else {
|
||
var em = Components.classes["@mozilla.org/extensions/manager;1"].
|
||
getService(Components.interfaces.nsIExtensionManager);
|
||
var installed = em.getItemList(
|
||
Components.interfaces.nsIUpdateItem.TYPE_ANY, {}
|
||
);
|
||
}
|
||
|
||
var addons = [];
|
||
for each(var addon in installed) {
|
||
switch (addon.id) {
|
||
case "zotero@chnm.gmu.edu":
|
||
case "{972ce4c6-7e08-4474-a285-3208198ce6fd}": // Default theme
|
||
continue;
|
||
}
|
||
|
||
addons.push(addon.name + " (" + addon.version
|
||
+ (addon.type != 2 ? ", " + addon.type : "") + ")");
|
||
}
|
||
return addons;
|
||
}
|
||
|
||
|
||
/**
|
||
* PHP var_dump equivalent for JS
|
||
*
|
||
* Adapted from http://binnyva.blogspot.com/2005/10/dump-function-javascript-equivalent-of.html
|
||
*/
|
||
function varDump(arr,level) {
|
||
var dumped_text = "";
|
||
if (!level){
|
||
level = 0;
|
||
}
|
||
|
||
// The padding given at the beginning of the line.
|
||
var level_padding = "";
|
||
for (var j=0;j<level+1;j++){
|
||
level_padding += " ";
|
||
}
|
||
|
||
if (typeof(arr) == 'object') { // Array/Hashes/Objects
|
||
for (var item in arr) {
|
||
var value = arr[item];
|
||
|
||
if (typeof(value) == 'object') { // If it is an array,
|
||
dumped_text += level_padding + "'" + item + "' ...\n";
|
||
dumped_text += arguments.callee(value,level+1);
|
||
}
|
||
else {
|
||
if (typeof value == 'function'){
|
||
dumped_text += level_padding + "'" + item + "' => function(...){...} \n";
|
||
}
|
||
else if (typeof value == 'number') {
|
||
dumped_text += level_padding + "'" + item + "' => " + value + "\n";
|
||
}
|
||
else {
|
||
dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else { // Stings/Chars/Numbers etc.
|
||
dumped_text = "===>"+arr+"<===("+typeof(arr)+")";
|
||
}
|
||
return dumped_text;
|
||
}
|
||
|
||
|
||
function safeDebug(obj){
|
||
for (var i in obj){
|
||
try {
|
||
Zotero.debug(i + ': ' + obj[i]);
|
||
}
|
||
catch (e){
|
||
try {
|
||
Zotero.debug(i + ': ERROR');
|
||
}
|
||
catch (e){}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
function getString(name, params){
|
||
try {
|
||
if (params != undefined){
|
||
if (typeof params != 'object'){
|
||
params = [params];
|
||
}
|
||
var l10n = _localizedStringBundle.formatStringFromName(name, params, params.length);
|
||
}
|
||
else {
|
||
var l10n = _localizedStringBundle.GetStringFromName(name);
|
||
}
|
||
}
|
||
catch (e){
|
||
throw ('Localized string not available for ' + name);
|
||
}
|
||
return l10n;
|
||
}
|
||
|
||
|
||
/*
|
||
* Join the elements of an array into a string using the appropriate
|
||
* locale direction
|
||
*
|
||
* |separator| defaults to a space (not a comma like Array.join()) if
|
||
* not specified
|
||
*
|
||
* TODO: Substitute localized characters (e.g. Arabic comma and semicolon)
|
||
*/
|
||
function localeJoin(arr, separator) {
|
||
if (typeof separator == 'undefined') {
|
||
separator = ' ';
|
||
}
|
||
if (this.dir == 'rtl') {
|
||
arr.reverse();
|
||
separator.split('').reverse().join('');
|
||
}
|
||
return arr.join(separator);
|
||
}
|
||
|
||
|
||
function getLocaleCollation() {
|
||
var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
|
||
.getService(Components.interfaces.nsILocaleService);
|
||
var collationFactory = Components.classes["@mozilla.org/intl/collation-factory;1"]
|
||
.getService(Components.interfaces.nsICollationFactory);
|
||
return collationFactory.CreateCollation(localeService.getApplicationLocale());
|
||
}
|
||
|
||
|
||
/*
|
||
* 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);
|
||
}
|
||
|
||
|
||
/*
|
||
* 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){
|
||
var isArguments = args.callee && args.length;
|
||
|
||
// Put passed scalar values into an array
|
||
if (args === null || (args.constructor.name != 'Array' && !isArguments)) {
|
||
args = [args];
|
||
}
|
||
|
||
var returns = [];
|
||
for (var i=0; i<args.length; i++){
|
||
if (!args[i]) {
|
||
continue;
|
||
}
|
||
if (args[i].constructor.name == 'Array') {
|
||
for (var j=0; j<args[i].length; j++){
|
||
returns.push(args[i][j]);
|
||
}
|
||
}
|
||
else {
|
||
returns.push(args[i]);
|
||
}
|
||
}
|
||
|
||
return returns;
|
||
}
|
||
|
||
|
||
function getAncestorByTagName(elem, tagName){
|
||
while (elem.parentNode){
|
||
elem = elem.parentNode;
|
||
if (elem.localName == tagName) {
|
||
return elem;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
/*
|
||
* A version of join() that operates externally for use on objects other
|
||
* than arrays (e.g. _arguments_)
|
||
*
|
||
* Note that this is safer than extending Object()
|
||
*/
|
||
function join(obj, delim){
|
||
var a = [];
|
||
for (var i=0, len=obj.length; i<len; i++){
|
||
a.push(obj[i]);
|
||
}
|
||
return a.join(delim);
|
||
}
|
||
|
||
|
||
/*
|
||
* PHP's in_array() for JS -- returns true if a value is contained in
|
||
* an array, false otherwise
|
||
*/
|
||
function inArray(needle, haystack){
|
||
for (var i in haystack){
|
||
if (haystack[i]==needle){
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
/*
|
||
* PHP's array_search() for JS -- searches an array for a value and
|
||
* returns the key if found, false otherwise
|
||
*/
|
||
function arraySearch(needle, haystack){
|
||
for (var i in haystack){
|
||
if (haystack[i]==needle){
|
||
return i;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
function arrayToHash(array){
|
||
var hash = {};
|
||
|
||
for each(var val in array){
|
||
hash[val] = true;
|
||
}
|
||
|
||
return hash;
|
||
}
|
||
|
||
|
||
/*
|
||
* Returns true if an object (or associative array) has at least one value
|
||
*/
|
||
function hasValues(obj) {
|
||
Zotero.debug("WARNING: Zotero.isEmpty() is deprecated! Use Zotero.Utilities.isEmpty(obj)", 2);
|
||
|
||
for (var i in obj) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
|
||
/**
|
||
* Generate a random string of length 'len' (defaults to 8)
|
||
**/
|
||
function randomString(len, chars) {
|
||
if (!chars) {
|
||
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
|
||
}
|
||
if (!len) {
|
||
len = 8;
|
||
}
|
||
var randomstring = '';
|
||
for (var i=0; i<len; i++) {
|
||
var rnum = Math.floor(Math.random() * chars.length);
|
||
randomstring += chars.substring(rnum,rnum+1);
|
||
}
|
||
return randomstring;
|
||
}
|
||
|
||
|
||
function moveToUnique(file, newFile){
|
||
newFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
|
||
var newName = newFile.leafName;
|
||
newFile.remove(null);
|
||
|
||
// Move file to unique name
|
||
file.moveTo(newFile.parent, newName);
|
||
return file;
|
||
}
|
||
|
||
|
||
/**
|
||
* Sleep for a given amount of time, allowing other events on main thread to be processed
|
||
*
|
||
* @param {Integer} ms Milliseconds to wait
|
||
*/
|
||
this.sleep = function (ms) {
|
||
var mainThread = Zotero.mainThread;
|
||
var endTime = Date.now() + ms;
|
||
do {
|
||
mainThread.processNextEvent(false);
|
||
} while (Date.now() < endTime);
|
||
|
||
return;
|
||
};
|
||
|
||
|
||
/**
|
||
* Allow other events (e.g., UI updates) on main thread to be processed if necessary
|
||
*
|
||
* @param {Integer} [timeout=50] Maximum number of milliseconds to wait
|
||
*/
|
||
this.wait = function (timeout) {
|
||
if (!timeout) {
|
||
timeout = 50;
|
||
}
|
||
var mainThread = Zotero.mainThread;
|
||
var endTime = Date.now() + timeout;
|
||
var more;
|
||
//var cycles = 0;
|
||
|
||
_waiting = true;
|
||
|
||
do {
|
||
more = mainThread.processNextEvent(false);
|
||
//cycles++;
|
||
} while (more && Date.now() < endTime);
|
||
|
||
_waiting = false;
|
||
|
||
//Zotero.debug("Waited " + cycles + " cycles");
|
||
return;
|
||
};
|
||
|
||
|
||
/**
|
||
* Show Zotero pane overlay and progress bar in all windows
|
||
*
|
||
* @param {String} msg
|
||
* @param {Boolean} [determinate=false]
|
||
* @return void
|
||
*/
|
||
this.showZoteroPaneProgressMeter = function (msg, determinate) {
|
||
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||
.getService(Components.interfaces.nsIWindowMediator);
|
||
var enumerator = wm.getEnumerator("navigator:browser");
|
||
var progressMeters = [];
|
||
while (enumerator.hasMoreElements()) {
|
||
var win = enumerator.getNext();
|
||
|
||
win.document.getElementById('zotero-pane-progress-label').value = msg;
|
||
var progressMeter = win.document.getElementById('zotero-pane-progressmeter')
|
||
if (determinate) {
|
||
progressMeter.mode = 'determined';
|
||
progressMeter.value = 0;
|
||
progressMeter.max = 1000;
|
||
}
|
||
else {
|
||
progressMeter.mode = 'undetermined';
|
||
}
|
||
|
||
_showWindowZoteroPaneOverlay(win);
|
||
win.document.getElementById('zotero-pane-overlay-deck').selectedIndex = 0;
|
||
|
||
progressMeters.push(progressMeter);
|
||
}
|
||
_locked = true;
|
||
_progressMeters = progressMeters;
|
||
}
|
||
|
||
|
||
/**
|
||
* @param {Number} percentage Percentage complete as integer or float
|
||
*/
|
||
this.updateZoteroPaneProgressMeter = function (percentage) {
|
||
if (percentage < 0 || percentage > 100) {
|
||
Zotero.debug("Invalid percentage value '" + percentage + "' in Zotero.updateZoteroPaneProgressMeter()");
|
||
return;
|
||
}
|
||
percentage = Math.round(percentage * 10);
|
||
if (percentage == _lastPercentage) {
|
||
return;
|
||
}
|
||
for each(var pm in _progressMeters) {
|
||
if (pm.mode == 'undetermined') {
|
||
pm.max = 1000;
|
||
pm.mode = 'determined';
|
||
}
|
||
pm.value = percentage;
|
||
}
|
||
_lastPercentage = percentage;
|
||
}
|
||
|
||
|
||
/**
|
||
* Hide Zotero pane overlay in all windows
|
||
*/
|
||
this.hideZoteroPaneOverlay = function () {
|
||
// Run any queued callbacks
|
||
if (_unlockCallbacks.length) {
|
||
var func;
|
||
while (func = _unlockCallbacks.shift()) {
|
||
func();
|
||
}
|
||
}
|
||
|
||
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();
|
||
_hideWindowZoteroPaneOverlay(win);
|
||
}
|
||
_locked = false;
|
||
_progressMeters = [];
|
||
_lastPercentage = null;
|
||
}
|
||
|
||
|
||
/**
|
||
* Adds a callback to be called when the Zotero pane overlay closes
|
||
*
|
||
* @param {Boolean} TRUE if added, FALSE if not locked
|
||
*/
|
||
this.addUnlockCallback = function (callback) {
|
||
if (!_locked) {
|
||
return false;
|
||
}
|
||
_unlockCallbacks.push(callback);
|
||
return true;
|
||
}
|
||
|
||
|
||
function _showWindowZoteroPaneOverlay(win) {
|
||
win.document.getElementById('zotero-collections-tree').disabled = true;
|
||
win.document.getElementById('zotero-items-tree').disabled = true;
|
||
win.document.getElementById('zotero-pane-tab-catcher-top').hidden = false;
|
||
win.document.getElementById('zotero-pane-tab-catcher-bottom').hidden = false;
|
||
win.document.getElementById('zotero-pane-overlay').hidden = false;
|
||
}
|
||
|
||
|
||
function _hideWindowZoteroPaneOverlay(win) {
|
||
win.document.getElementById('zotero-collections-tree').disabled = false;
|
||
win.document.getElementById('zotero-items-tree').disabled = false;
|
||
win.document.getElementById('zotero-pane-tab-catcher-top').hidden = true;
|
||
win.document.getElementById('zotero-pane-tab-catcher-bottom').hidden = true;
|
||
win.document.getElementById('zotero-pane-overlay').hidden = true;
|
||
}
|
||
|
||
|
||
/*
|
||
* Clear entries that no longer exist from various tables
|
||
*/
|
||
this.purgeDataObjects = function (skipStoragePurge) {
|
||
Zotero.Creators.purge();
|
||
Zotero.Tags.purge();
|
||
Zotero.Fulltext.purgeUnusedWords();
|
||
Zotero.Items.purge();
|
||
// DEBUG: this might not need to be permanent
|
||
Zotero.Relations.purge();
|
||
|
||
if (!skipStoragePurge && Zotero.Utilities.prototype.probability(10)) {
|
||
Zotero.Sync.Storage.purgeDeletedStorageFiles('zfs');
|
||
Zotero.Sync.Storage.purgeDeletedStorageFiles('webdav');
|
||
}
|
||
}
|
||
|
||
|
||
this.reloadDataObjects = function () {
|
||
Zotero.Tags.reloadAll();
|
||
Zotero.Collections.reloadAll();
|
||
Zotero.Creators.reloadAll();
|
||
Zotero.Items.reloadAll();
|
||
}
|
||
};
|
||
|
||
|
||
|
||
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(){
|
||
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||
.getService(Components.interfaces.nsIPrefService);
|
||
this.prefBranch = prefs.getBranch(ZOTERO_CONFIG.PREF_BRANCH);
|
||
|
||
// Register observer to handle pref changes
|
||
this.register();
|
||
}
|
||
|
||
|
||
/**
|
||
* Retrieve a preference
|
||
**/
|
||
function get(pref, global){
|
||
try {
|
||
if (global) {
|
||
var service = Components.classes["@mozilla.org/preferences-service;1"]
|
||
.getService(Components.interfaces.nsIPrefService);
|
||
}
|
||
else {
|
||
var service = this.prefBranch;
|
||
}
|
||
|
||
switch (this.prefBranch.getPrefType(pref)){
|
||
case this.prefBranch.PREF_BOOL:
|
||
return this.prefBranch.getBoolPref(pref);
|
||
case this.prefBranch.PREF_STRING:
|
||
return this.prefBranch.getCharPref(pref);
|
||
case this.prefBranch.PREF_INT:
|
||
return this.prefBranch.getIntPref(pref);
|
||
}
|
||
}
|
||
catch (e){
|
||
throw ("Invalid preference '" + pref + "'");
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Set a preference
|
||
**/
|
||
function set(pref, value){
|
||
try {
|
||
switch (this.prefBranch.getPrefType(pref)){
|
||
case this.prefBranch.PREF_BOOL:
|
||
return this.prefBranch.setBoolPref(pref, value);
|
||
case this.prefBranch.PREF_STRING:
|
||
return this.prefBranch.setCharPref(pref, value);
|
||
case this.prefBranch.PREF_INT:
|
||
return this.prefBranch.setIntPref(pref, value);
|
||
}
|
||
}
|
||
catch (e){
|
||
throw ("Invalid preference '" + pref + "'");
|
||
}
|
||
}
|
||
|
||
|
||
this.clear = function (pref) {
|
||
try {
|
||
this.prefBranch.clearUserPref(pref);
|
||
}
|
||
catch (e) {
|
||
throw ("Invalid preference '" + pref + "'");
|
||
}
|
||
}
|
||
|
||
|
||
// Import settings bundles
|
||
this.importSettings = function (str, uri) {
|
||
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
||
.getService(Components.interfaces.nsIPromptService);
|
||
|
||
if (!uri.match(/https:\/\/([^\.]+\.)?zotero.org\//)) {
|
||
Zotero.debug("Ignoring settings file not from https://zotero.org");
|
||
return;
|
||
}
|
||
|
||
str = Zotero.Utilities.prototype.trim(str.replace(/<\?xml.*\?>\s*/, ''));
|
||
Zotero.debug(str);
|
||
|
||
var confirm = ps.confirm(
|
||
null,
|
||
"",
|
||
"Apply settings from zotero.org?"
|
||
);
|
||
|
||
if (!confirm) {
|
||
return;
|
||
}
|
||
|
||
var xml = new XML(str);
|
||
|
||
var commonsEnable = xml.setting.(@id == 'commons-enable');
|
||
if (commonsEnable == 'true') {
|
||
Zotero.Commons.enabled = true;
|
||
Zotero.Commons.accessKey = xml.setting.(@id == 'commons-accessKey').toString();
|
||
Zotero.Commons.secretKey = xml.setting.(@id == 'commons-secretKey').toString();
|
||
}
|
||
else if (commonsEnable == 'false') {
|
||
Zotero.Commons.enabled = false;
|
||
Zotero.Commons.accessKey = '';
|
||
Zotero.Commons.secretKey = '';
|
||
}
|
||
// This is kind of a hack
|
||
Zotero.Notifier.trigger('refresh', 'collection', []);
|
||
}
|
||
|
||
|
||
//
|
||
// Methods to register a preferences observer
|
||
//
|
||
function register(){
|
||
this.prefBranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
|
||
this.prefBranch.addObserver("", this, false);
|
||
}
|
||
|
||
function unregister(){
|
||
if (!this.prefBranch){
|
||
return;
|
||
}
|
||
this.prefBranch.removeObserver("", this);
|
||
}
|
||
|
||
function observe(subject, topic, data){
|
||
if(topic!="nsPref:changed"){
|
||
return;
|
||
}
|
||
// subject is the nsIPrefBranch we're observing (after appropriate QI)
|
||
// data is the name of the pref that's been changed (relative to subject)
|
||
switch (data){
|
||
case "automaticScraperUpdates":
|
||
if (this.get('automaticScraperUpdates')){
|
||
Zotero.Schema.updateFromRepository();
|
||
}
|
||
else {
|
||
Zotero.Schema.stopRepositoryTimer();
|
||
}
|
||
break;
|
||
|
||
case "zoteroDotOrgVersionHeader":
|
||
if (this.get("zoteroDotOrgVersionHeader")) {
|
||
Zotero.VersionHeader.register();
|
||
}
|
||
else {
|
||
Zotero.VersionHeader.unregister();
|
||
}
|
||
break;
|
||
|
||
case "sync.autoSync":
|
||
if (this.get("sync.autoSync")) {
|
||
Zotero.Sync.Runner.IdleListener.register();
|
||
}
|
||
else {
|
||
Zotero.Sync.Runner.IdleListener.unregister();
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
* Handles keyboard shortcut initialization from preferences, optionally
|
||
* overriding existing global shortcuts
|
||
*
|
||
* Actions are configured in ZoteroPane.handleKeyPress()
|
||
*/
|
||
Zotero.Keys = new function() {
|
||
this.init = init;
|
||
this.windowInit = windowInit;
|
||
this.getCommand = getCommand;
|
||
|
||
var _keys = {};
|
||
|
||
|
||
/*
|
||
* Called by Zotero.init()
|
||
*/
|
||
function init() {
|
||
var actions = Zotero.Prefs.prefBranch.getChildList('keys', {}, {});
|
||
|
||
// Get the key=>command mappings from the prefs
|
||
for each(var action in actions) {
|
||
var action = action.substr(5); // strips 'keys.'
|
||
if (action == 'overrideGlobal') {
|
||
continue;
|
||
}
|
||
_keys[Zotero.Prefs.get('keys.' + action)] = action;
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
* Called by ZoteroPane.onLoad()
|
||
*/
|
||
function windowInit(document) {
|
||
var useShift = Zotero.isMac;
|
||
|
||
// Zotero pane shortcut
|
||
var zKey = Zotero.Prefs.get('keys.openZotero');
|
||
var keyElem = document.getElementById('key_openZotero');
|
||
// Only override the default with the pref if the <key> hasn't been manually changed
|
||
// and the pref has been
|
||
if (keyElem.getAttribute('key') == 'Z' && keyElem.getAttribute('modifiers') == 'accel alt'
|
||
&& (zKey != 'Z' || useShift)) {
|
||
keyElem.setAttribute('key', zKey);
|
||
if (useShift) {
|
||
keyElem.setAttribute('modifiers', 'accel shift');
|
||
}
|
||
}
|
||
|
||
if (Zotero.Prefs.get('keys.overrideGlobal')) {
|
||
var keys = document.getElementsByTagName('key');
|
||
for each(var key in keys) {
|
||
try {
|
||
var id = key.getAttribute('id');
|
||
}
|
||
// A couple keys are always invalid
|
||
catch (e) {
|
||
continue;
|
||
}
|
||
|
||
if (id == 'key_openZotero') {
|
||
continue;
|
||
}
|
||
|
||
var mods = key.getAttribute('modifiers').split(/[\,\s]/);
|
||
var second = useShift ? 'shift' : 'alt';
|
||
// Key doesn't match a Zotero shortcut
|
||
if (mods.length != 2 || !((mods[0] == 'accel' && mods[1] == second) ||
|
||
(mods[0] == second && mods[1] == 'accel'))) {
|
||
continue;
|
||
}
|
||
|
||
if (_keys[key.getAttribute('key')] || key.getAttribute('key') == zKey) {
|
||
// Don't override Redo on Fx3 Mac, since Redo and Zotero can coexist
|
||
if (zKey == 'Z' && key.getAttribute('key') == 'Z'
|
||
&& id == 'key_redo' && Zotero.isFx3 && Zotero.isMac) {
|
||
continue;
|
||
}
|
||
|
||
Zotero.debug('Removing key ' + id + ' with accesskey ' + key.getAttribute('key'));
|
||
key.parentNode.removeChild(key);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
function getCommand(key) {
|
||
return _keys[key] ? _keys[key] : false;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Add X-Zotero-Version header to HTTP requests to zotero.org
|
||
*
|
||
* @namespace
|
||
*/
|
||
Zotero.VersionHeader = {
|
||
init: function () {
|
||
if (Zotero.Prefs.get("zoteroDotOrgVersionHeader")) {
|
||
this.register();
|
||
}
|
||
},
|
||
|
||
// Called from this.init() and Zotero.Prefs.observe()
|
||
register: function () {
|
||
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||
.getService(Components.interfaces.nsIObserverService);
|
||
observerService.addObserver(this, "http-on-modify-request", false);
|
||
},
|
||
|
||
observe: function (subject, topic, data) {
|
||
try {
|
||
var channel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
|
||
if (channel.URI.host.match(/zotero\.org$/)) {
|
||
channel.setRequestHeader("X-Zotero-Version", Zotero.version, false);
|
||
}
|
||
}
|
||
catch (e) {
|
||
Zotero.debug(e);
|
||
}
|
||
},
|
||
|
||
unregister: function () {
|
||
var observerService = Components.classes["@mozilla.org/observer-service;1"]
|
||
.getService(Components.interfaces.nsIObserverService);
|
||
observerService.removeObserver(this, "http-on-modify-request");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Class for creating hash arrays that behave a bit more sanely
|
||
*
|
||
* Hashes can be created in the constructor by alternating key and val:
|
||
*
|
||
* var hasharray = new Zotero.Hash('foo','foovalue','bar','barvalue');
|
||
*
|
||
* Or using hasharray.set(key, val)
|
||
*
|
||
* _val_ defaults to true if not provided
|
||
*
|
||
* If using foreach-style looping, be sure to use _for (i in arr.items)_
|
||
* rather than just _for (i in arr)_, or else you'll end up with the
|
||
* methods and members instead of the hash items
|
||
*
|
||
* Most importantly, hasharray.length will work as expected, even with
|
||
* non-numeric keys
|
||
*
|
||
* Adapated from http://www.mojavelinux.com/articles/javascript_hashes.html
|
||
* (c) Mojavelinux, Inc.
|
||
* License: Creative Commons
|
||
**/
|
||
Zotero.Hash = function(){
|
||
this.length = 0;
|
||
this.items = {};
|
||
|
||
// Public methods defined on prototype below
|
||
|
||
for (var i = 0; i < arguments.length; i += 2) {
|
||
if (typeof(arguments[i + 1]) != 'undefined') {
|
||
this.items[arguments[i]] = arguments[i + 1];
|
||
this.length++;
|
||
}
|
||
}
|
||
}
|
||
|
||
Zotero.Hash.prototype.get = function(in_key){
|
||
return this.items[in_key] ? this.items[in_key] : false;
|
||
}
|
||
|
||
Zotero.Hash.prototype.set = function(in_key, in_value){
|
||
// Default to a boolean hash if value not provided
|
||
if (typeof(in_value) == 'undefined'){
|
||
in_value = true;
|
||
}
|
||
|
||
if (typeof(this.items[in_key]) == 'undefined') {
|
||
this.length++;
|
||
}
|
||
|
||
this.items[in_key] = in_value;
|
||
|
||
return in_value;
|
||
}
|
||
|
||
Zotero.Hash.prototype.remove = function(in_key){
|
||
var tmp_value;
|
||
if (typeof(this.items[in_key]) != 'undefined') {
|
||
this.length--;
|
||
var tmp_value = this.items[in_key];
|
||
delete this.items[in_key];
|
||
}
|
||
|
||
return tmp_value;
|
||
}
|
||
|
||
Zotero.Hash.prototype.has = function(in_key){
|
||
return typeof(this.items[in_key]) != 'undefined';
|
||
}
|
||
|
||
/**
|
||
* Singleton for common text formatting routines
|
||
**/
|
||
Zotero.Text = new function() {
|
||
this.titleCase = titleCase;
|
||
|
||
var skipWords = ["but", "or", "yet", "so", "for", "and", "nor", "a", "an",
|
||
"the", "at", "by", "from", "in", "into", "of", "on", "to", "with", "up",
|
||
"down", "as"];
|
||
// this may only match a single character
|
||
var delimiterRegexp = /([ \/\-–—])/;
|
||
|
||
function titleCase(string) {
|
||
if (!string) {
|
||
return "";
|
||
}
|
||
|
||
// split words
|
||
var words = string.split(delimiterRegexp);
|
||
var isUpperCase = string.toUpperCase() == string;
|
||
|
||
var newString = "";
|
||
var delimiterOffset = words[0].length;
|
||
var lastWordIndex = words.length-1;
|
||
var previousWordIndex = -1;
|
||
for(var i=0; i<=lastWordIndex; i++) {
|
||
// only do manipulation if not a delimiter character
|
||
if(words[i].length != 0 && (words[i].length != 1 || !delimiterRegexp.test(words[i]))) {
|
||
var upperCaseVariant = words[i].toUpperCase();
|
||
var lowerCaseVariant = words[i].toLowerCase();
|
||
|
||
// only use if word does not already possess some capitalization
|
||
if(isUpperCase || words[i] == lowerCaseVariant) {
|
||
if(
|
||
// a skip word
|
||
skipWords.indexOf(lowerCaseVariant.replace(/[^a-zA-Z]+/, "")) != -1
|
||
// not first or last word
|
||
&& i != 0 && i != lastWordIndex
|
||
// does not follow a colon
|
||
&& (previousWordIndex == -1 || words[previousWordIndex][words[previousWordIndex].length-1] != ":")
|
||
) {
|
||
words[i] = lowerCaseVariant;
|
||
} else {
|
||
// this is not a skip word or comes after a colon;
|
||
// we must capitalize
|
||
words[i] = upperCaseVariant[0] + lowerCaseVariant.substr(1);
|
||
}
|
||
}
|
||
|
||
previousWordIndex = i;
|
||
}
|
||
|
||
newString += words[i];
|
||
}
|
||
|
||
return newString;
|
||
}
|
||
}
|
||
|
||
Zotero.Date = new function(){
|
||
this.sqlToDate = sqlToDate;
|
||
this.dateToSQL = dateToSQL;
|
||
this.strToDate = strToDate;
|
||
this.formatDate = formatDate;
|
||
this.strToISO = strToISO;
|
||
this.strToMultipart = strToMultipart;
|
||
this.isMultipart = isMultipart;
|
||
this.multipartToSQL = multipartToSQL;
|
||
this.multipartToStr = multipartToStr;
|
||
this.isSQLDate = isSQLDate;
|
||
this.isSQLDateTime = isSQLDateTime;
|
||
this.sqlHasYear = sqlHasYear;
|
||
this.sqlHasMonth = sqlHasMonth;
|
||
this.sqlHasDay = sqlHasDay;
|
||
this.getUnixTimestamp = getUnixTimestamp;
|
||
this.toUnixTimestamp = toUnixTimestamp;
|
||
this.getFileDateString = getFileDateString;
|
||
this.getFileTimeString = getFileTimeString;
|
||
this.getLocaleDateOrder = getLocaleDateOrder;
|
||
|
||
var _localeDateOrder = null;
|
||
|
||
|
||
/**
|
||
* Convert an SQL date in the form '2006-06-13 11:03:05' into a JS Date object
|
||
*
|
||
* Can also accept just the date part (e.g. '2006-06-13')
|
||
**/
|
||
function sqlToDate(sqldate, isUTC){
|
||
try {
|
||
var datetime = sqldate.split(' ');
|
||
var dateparts = datetime[0].split('-');
|
||
if (datetime[1]){
|
||
var timeparts = datetime[1].split(':');
|
||
}
|
||
else {
|
||
timeparts = [false, false, false];
|
||
}
|
||
|
||
// Invalid date part
|
||
if (dateparts.length==1){
|
||
return false;
|
||
}
|
||
|
||
if (isUTC){
|
||
return new Date(Date.UTC(dateparts[0], dateparts[1]-1, dateparts[2],
|
||
timeparts[0], timeparts[1], timeparts[2]));
|
||
}
|
||
|
||
return new Date(dateparts[0], dateparts[1]-1, dateparts[2],
|
||
timeparts[0], timeparts[1], timeparts[2]);
|
||
}
|
||
catch (e){
|
||
Zotero.debug(sqldate + ' is not a valid SQL date', 2)
|
||
return false;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Convert a JS Date object to an SQL date in the form '2006-06-13 11:03:05'
|
||
*
|
||
* If _toUTC_ is true, creates a UTC date
|
||
**/
|
||
function dateToSQL(date, toUTC)
|
||
{
|
||
try {
|
||
if (toUTC){
|
||
var year = date.getUTCFullYear();
|
||
var month = date.getUTCMonth();
|
||
var day = date.getUTCDate();
|
||
var hours = date.getUTCHours();
|
||
var minutes = date.getUTCMinutes();
|
||
var seconds = date.getUTCSeconds();
|
||
}
|
||
else {
|
||
return date.toLocaleFormat('%Y-%m-%d %H:%M:%S');
|
||
}
|
||
|
||
var utils = new Zotero.Utilities();
|
||
year = utils.lpad(year, '0', 4);
|
||
month = utils.lpad(month + 1, '0', 2);
|
||
day = utils.lpad(day, '0', 2);
|
||
hours = utils.lpad(hours, '0', 2);
|
||
minutes = utils.lpad(minutes, '0', 2);
|
||
seconds = utils.lpad(seconds, '0', 2);
|
||
|
||
return year + '-' + month + '-' + day + ' '
|
||
+ hours + ':' + minutes + ':' + seconds;
|
||
}
|
||
catch (e){
|
||
Zotero.debug(date + ' is not a valid JS date', 2);
|
||
return '';
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Convert a JS Date object to an ISO 8601 UTC date/time
|
||
*
|
||
* @param {Date} date JS Date object
|
||
* @return {String} ISO 8601 UTC date/time
|
||
* e.g. 2008-08-15T20:00:00Z
|
||
*/
|
||
this.dateToISO = function (date) {
|
||
var year = date.getUTCFullYear();
|
||
var month = date.getUTCMonth();
|
||
var day = date.getUTCDate();
|
||
var hours = date.getUTCHours();
|
||
var minutes = date.getUTCMinutes();
|
||
var seconds = date.getUTCSeconds();
|
||
|
||
var utils = new Zotero.Utilities();
|
||
year = utils.lpad(year, '0', 4);
|
||
month = utils.lpad(month + 1, '0', 2);
|
||
day = utils.lpad(day, '0', 2);
|
||
hours = utils.lpad(hours, '0', 2);
|
||
minutes = utils.lpad(minutes, '0', 2);
|
||
seconds = utils.lpad(seconds, '0', 2);
|
||
|
||
return year + '-' + month + '-' + day + 'T'
|
||
+ hours + ':' + minutes + ':' + seconds + 'Z';
|
||
}
|
||
|
||
|
||
/**
|
||
* Convert an ISO 8601–formatted UTC date/time to a JS Date
|
||
*
|
||
* Adapted from http://delete.me.uk/2005/03/iso8601.html (AFL-licensed)
|
||
*
|
||
* @param {String} isoDate ISO 8601 date
|
||
* @return {Date} JS Date
|
||
*/
|
||
this.isoToDate = function (isoDate) {
|
||
var re8601 = /([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?/;
|
||
var d = isoDate.match(re8601);
|
||
|
||
var offset = 0;
|
||
var date = new Date(d[1], 0, 1);
|
||
|
||
if (d[3]) { date.setMonth(d[3] - 1); }
|
||
if (d[5]) { date.setDate(d[5]); }
|
||
if (d[7]) { date.setHours(d[7]); }
|
||
if (d[8]) { date.setMinutes(d[8]); }
|
||
if (d[10]) { date.setSeconds(d[10]); }
|
||
if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
|
||
if (d[14]) {
|
||
offset = (Number(d[16]) * 60) + Number(d[17]);
|
||
offset *= ((d[15] == '-') ? 1 : -1);
|
||
}
|
||
|
||
offset -= date.getTimezoneOffset();
|
||
var time = (Number(date) + (offset * 60 * 1000));
|
||
return new Date(time);
|
||
}
|
||
|
||
|
||
/*
|
||
* converts a string to an object containing:
|
||
* day: integer form of the day
|
||
* month: integer form of the month (indexed from 0, not 1)
|
||
* year: 4 digit year (or, year + BC/AD/etc.)
|
||
* part: anything that does not fall under any of the above categories
|
||
* (e.g., "Summer," etc.)
|
||
*
|
||
* Note: the returned object is *not* a JS Date object
|
||
*/
|
||
var _slashRe = /^(.*?)\b([0-9]{1,4})(?:([\-\/\.\u5e74])([0-9]{1,2}))?(?:([\-\/\.\u6708])([0-9]{1,4}))?((?:\b|[^0-9]).*?)$/
|
||
var _yearRe = /^(.*?)\b((?:circa |around |about |c\.? ?)?[0-9]{1,4}(?: ?B\.? ?C\.?(?: ?E\.?)?| ?C\.? ?E\.?| ?A\.? ?D\.?)|[0-9]{3,4})\b(.*?)$/i;
|
||
var _monthRe = null;
|
||
var _dayRe = null;
|
||
|
||
function strToDate(string) {
|
||
// Parse 'yesterday'/'today'/'tomorrow'
|
||
var lc = (string + '').toLowerCase();
|
||
if (lc == 'yesterday' || lc == Zotero.getString('date.yesterday')) {
|
||
string = Zotero.Date.dateToSQL(new Date(new Date().getTime() - 86400000)).substr(0, 10);
|
||
}
|
||
else if (lc == 'today' || lc == Zotero.getString('date.today')) {
|
||
string = Zotero.Date.dateToSQL(new Date()).substr(0, 10);
|
||
}
|
||
else if (lc == 'tomorrow' || lc == Zotero.getString('date.tomorrow')) {
|
||
string = Zotero.Date.dateToSQL(new Date(new Date().getTime() + 86400000)).substr(0, 10);
|
||
}
|
||
|
||
var date = new Object();
|
||
|
||
// skip empty things
|
||
if(!string) {
|
||
return date;
|
||
}
|
||
|
||
string = string.toString().replace(/^\s+/, "").replace(/\s+$/, "").replace(/\s+/, " ");
|
||
|
||
// first, directly inspect the string
|
||
var m = _slashRe.exec(string);
|
||
if(m &&
|
||
(!m[5] || m[3] == m[5] || (m[3] == "\u5e74" && m[5] == "\u6708")) && // require sane separators
|
||
((m[2] && m[4] && m[6]) || (!m[1] && !m[7]))) { // require that either all parts are found,
|
||
// or else this is the entire date field
|
||
// figure out date based on parts
|
||
if(m[2].length == 3 || m[2].length == 4 || m[3] == "\u5e74") {
|
||
// ISO 8601 style date (big endian)
|
||
date.year = m[2];
|
||
date.month = m[4];
|
||
date.day = m[6];
|
||
} else {
|
||
// local style date (middle or little endian)
|
||
date.year = m[6];
|
||
var country = Zotero.locale.substr(3);
|
||
if(country == "US" || // The United States
|
||
country == "FM" || // The Federated States of Micronesia
|
||
country == "PW" || // Palau
|
||
country == "PH") { // The Philippines
|
||
date.month = m[2];
|
||
date.day = m[4];
|
||
} else {
|
||
date.month = m[4];
|
||
date.day = m[2];
|
||
}
|
||
}
|
||
|
||
if(date.year) date.year = parseInt(date.year, 10);
|
||
if(date.day) date.day = parseInt(date.day, 10);
|
||
if(date.month) {
|
||
date.month = parseInt(date.month, 10);
|
||
|
||
if(date.month > 12) {
|
||
// swap day and month
|
||
var tmp = date.day;
|
||
date.day = date.month
|
||
date.month = tmp;
|
||
}
|
||
}
|
||
|
||
if((!date.month || date.month <= 12) && (!date.day || date.day <= 31)) {
|
||
if(date.year && date.year < 100) { // for two digit years, determine proper
|
||
// four digit year
|
||
var today = new Date();
|
||
var year = today.getFullYear();
|
||
var twoDigitYear = year % 100;
|
||
var century = year - twoDigitYear;
|
||
|
||
if(date.year <= twoDigitYear) {
|
||
// assume this date is from our century
|
||
date.year = century + date.year;
|
||
} else {
|
||
// assume this date is from the previous century
|
||
date.year = century - 100 + date.year;
|
||
}
|
||
}
|
||
|
||
if(date.month) date.month--; // subtract one for JS style
|
||
Zotero.debug("DATE: retrieved with algorithms: "+date.toSource());
|
||
|
||
date.part = m[1]+m[7];
|
||
} else {
|
||
// give up; we failed the sanity check
|
||
Zotero.debug("DATE: algorithms failed sanity check");
|
||
date = {"part":string};
|
||
}
|
||
} else {
|
||
Zotero.debug("DATE: could not apply algorithms");
|
||
date.part = string;
|
||
}
|
||
|
||
// couldn't find something with the algorithms; use regexp
|
||
// YEAR
|
||
if(!date.year) {
|
||
var m = _yearRe.exec(date.part);
|
||
if(m) {
|
||
date.year = m[2];
|
||
date.part = m[1]+m[3];
|
||
Zotero.debug("DATE: got year ("+date.year+", "+date.part+")");
|
||
}
|
||
}
|
||
|
||
// MONTH
|
||
if(!date.month) {
|
||
// compile month regular expression
|
||
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
|
||
'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||
// If using a non-English bibliography locale, try those too
|
||
if (Zotero.locale != 'en-US') {
|
||
months = months.concat(Zotero.Cite.getMonthStrings("short"));
|
||
}
|
||
if(!_monthRe) {
|
||
_monthRe = new RegExp("^(.*)\\b("+months.join("|")+")[^ ]*(?: (.*)$|$)", "i");
|
||
}
|
||
|
||
var m = _monthRe.exec(date.part);
|
||
if(m) {
|
||
// Modulo 12 in case we have multiple languages
|
||
date.month = months.indexOf(m[2][0].toUpperCase()+m[2].substr(1).toLowerCase()) % 12;
|
||
date.part = m[1]+m[3];
|
||
Zotero.debug("DATE: got month ("+date.month+", "+date.part+")");
|
||
}
|
||
}
|
||
|
||
// DAY
|
||
if(!date.day) {
|
||
// compile day regular expression
|
||
if(!_dayRe) {
|
||
var daySuffixes = Zotero.getString("date.daySuffixes").replace(/, ?/g, "|");
|
||
_dayRe = new RegExp("\\b([0-9]{1,2})(?:"+daySuffixes+")?\\b(.*)", "i");
|
||
}
|
||
|
||
var m = _dayRe.exec(date.part);
|
||
if(m) {
|
||
var day = parseInt(m[1], 10);
|
||
// Sanity check
|
||
if (day <= 31) {
|
||
date.day = day;
|
||
if(m.index > 0) {
|
||
date.part = date.part.substr(0, m.index);
|
||
if(m[2]) {
|
||
date.part += " "+m[2];;
|
||
}
|
||
} else {
|
||
date.part = m[2];
|
||
}
|
||
|
||
Zotero.debug("DATE: got day ("+date.day+", "+date.part+")");
|
||
}
|
||
}
|
||
}
|
||
|
||
// clean up date part
|
||
if(date.part) {
|
||
date.part = date.part.replace(/^[^A-Za-z0-9]+/, "").replace(/[^A-Za-z0-9]+$/, "");
|
||
if(!date.part.length) {
|
||
date.part = undefined;
|
||
}
|
||
}
|
||
|
||
return date;
|
||
}
|
||
|
||
/**
|
||
* does pretty formatting of a date object returned by strToDate()
|
||
*
|
||
* @param {Object} date A date object, as returned from strToDate()
|
||
* @param {Boolean} shortFormat Whether to return a short (12/1/95) date
|
||
* @return A formatted date string
|
||
* @type String
|
||
**/
|
||
function formatDate(date, shortFormat) {
|
||
if(shortFormat) {
|
||
var localeDateOrder = getLocaleDateOrder();
|
||
var string = localeDateOrder[0]+"/"+localeDateOrder[1]+"/"+localeDateOrder[2];
|
||
return string.replace("y", (date.year !== undefined ? date.year : "00"))
|
||
.replace("m", (date.month !== undefined ? 1+date.month : "0"))
|
||
.replace("d", (date.day !== undefined ? date.day : "0"));
|
||
} else {
|
||
var string = "";
|
||
|
||
if(date.part) {
|
||
string += date.part+" ";
|
||
}
|
||
|
||
var months = Zotero.Cite.getMonthStrings("long");
|
||
if(date.month != undefined && months[date.month]) {
|
||
// get short month strings from CSL interpreter
|
||
string += months[date.month];
|
||
if(date.day) {
|
||
string += " "+date.day+", ";
|
||
} else {
|
||
string += " ";
|
||
}
|
||
}
|
||
|
||
if(date.year) {
|
||
string += date.year;
|
||
}
|
||
}
|
||
|
||
return string;
|
||
}
|
||
|
||
function strToISO(str) {
|
||
var date = Zotero.Date.strToDate(str);
|
||
|
||
if(date.year) {
|
||
var dateString = Zotero.Utilities.prototype.lpad(date.year, "0", 4);
|
||
if(date.month) {
|
||
dateString += "-"+Zotero.Utilities.prototype.lpad(date.month+1, "0", 2);
|
||
if(date.day) {
|
||
dateString += "-"+Zotero.Utilities.prototype.lpad(date.day, "0", 2);
|
||
}
|
||
}
|
||
return dateString;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function strToMultipart(str){
|
||
if (!str){
|
||
return '';
|
||
}
|
||
|
||
var utils = new Zotero.Utilities();
|
||
|
||
var parts = strToDate(str);
|
||
|
||
// FIXME: Until we have a better BCE date solution,
|
||
// remove year value if not between 1 and 9999
|
||
if (parts.year) {
|
||
var year = parts.year + '';
|
||
if (!year.match(/^[0-9]{1,4}$/)) {
|
||
delete parts.year;
|
||
}
|
||
}
|
||
|
||
parts.month = typeof parts.month != "undefined" ? parts.month + 1 : '';
|
||
|
||
var multi = (parts.year ? utils.lpad(parts.year, '0', 4) : '0000') + '-'
|
||
+ utils.lpad(parts.month, '0', 2) + '-'
|
||
+ (parts.day ? utils.lpad(parts.day, '0', 2) : '00')
|
||
+ ' '
|
||
+ str;
|
||
return multi;
|
||
}
|
||
|
||
// Regexes for multipart and SQL dates
|
||
// Allow zeroes in multipart dates
|
||
// TODO: Allow negative multipart in DB and here with \-?
|
||
var _multipartRE = /^[0-9]{4}\-(0[0-9]|10|11|12)\-(0[0-9]|[1-2][0-9]|30|31) /;
|
||
var _sqldateRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31)$/;
|
||
var _sqldateWithZeroesRE = /^\-?[0-9]{4}\-(0[0-9]|10|11|12)\-(0[0-9]|[1-2][0-9]|30|31)$/;
|
||
var _sqldatetimeRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31) ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])$/;
|
||
|
||
/**
|
||
* Tests if a string is a multipart date string
|
||
* e.g. '2006-11-03 November 3rd, 2006'
|
||
*/
|
||
function isMultipart(str){
|
||
if (isSQLDateTime(str)) {
|
||
return false;
|
||
}
|
||
return _multipartRE.test(str);
|
||
}
|
||
|
||
|
||
/**
|
||
* Returns the SQL part of a multipart date string
|
||
* (e.g. '2006-11-03 November 3rd, 2006' returns '2006-11-03')
|
||
*/
|
||
function multipartToSQL(multi){
|
||
if (!multi){
|
||
return '';
|
||
}
|
||
|
||
if (!isMultipart(multi)){
|
||
return '0000-00-00';
|
||
}
|
||
|
||
return multi.substr(0, 10);
|
||
}
|
||
|
||
|
||
/**
|
||
* Returns the user part of a multipart date string
|
||
* (e.g. '2006-11-03 November 3rd, 2006' returns 'November 3rd, 2006')
|
||
*/
|
||
function multipartToStr(multi){
|
||
if (!multi){
|
||
return '';
|
||
}
|
||
|
||
if (!isMultipart(multi)){
|
||
return multi;
|
||
}
|
||
|
||
return multi.substr(11);
|
||
}
|
||
|
||
|
||
function isSQLDate(str, allowZeroes) {
|
||
if (allowZeroes) {
|
||
return _sqldateWithZeroesRE.test(str);
|
||
}
|
||
return _sqldateRE.test(str);
|
||
}
|
||
|
||
|
||
function isSQLDateTime(str){
|
||
return _sqldatetimeRE.test(str);
|
||
}
|
||
|
||
|
||
function sqlHasYear(sqldate){
|
||
return isSQLDate(sqldate, true) && sqldate.substr(0,4)!='0000';
|
||
}
|
||
|
||
|
||
function sqlHasMonth(sqldate){
|
||
return isSQLDate(sqldate, true) && sqldate.substr(5,2)!='00';
|
||
}
|
||
|
||
|
||
function sqlHasDay(sqldate){
|
||
return isSQLDate(sqldate, true) && sqldate.substr(8,2)!='00';
|
||
}
|
||
|
||
|
||
function getUnixTimestamp() {
|
||
return Math.round(Date.now() / 1000);
|
||
}
|
||
|
||
|
||
function toUnixTimestamp(date) {
|
||
if (date === null || typeof date != 'object' ||
|
||
date.constructor.name != 'Date') {
|
||
throw ('Not a valid date in Zotero.Date.toUnixTimestamp()');
|
||
}
|
||
return Math.round(date.getTime() / 1000);
|
||
}
|
||
|
||
|
||
/**
|
||
* Convert a JS Date to a relative date (e.g., "5 minutes ago")
|
||
*
|
||
* Adapted from http://snipplr.com/view/10290/javascript-parse-relative-date/
|
||
*
|
||
* @param {Date} date
|
||
* @return {String}
|
||
*/
|
||
this.toRelativeDate = function (date) {
|
||
var str;
|
||
var now = new Date();
|
||
var timeSince = now.getTime() - date;
|
||
var inSeconds = timeSince / 1000;
|
||
var inMinutes = timeSince / 1000 / 60;
|
||
var inHours = timeSince / 1000 / 60 / 60;
|
||
var inDays = timeSince / 1000 / 60 / 60 / 24;
|
||
var inYears = timeSince / 1000 / 60 / 60 / 24 / 365;
|
||
|
||
var n;
|
||
|
||
// in seconds
|
||
if (Math.round(inSeconds) == 1) {
|
||
var key = "secondsAgo";
|
||
}
|
||
else if (inMinutes < 1.01) {
|
||
var key = "secondsAgo";
|
||
n = Math.round(inSeconds);
|
||
}
|
||
|
||
// in minutes
|
||
else if (Math.round(inMinutes) == 1) {
|
||
var key = "minutesAgo";
|
||
}
|
||
else if (inHours < 1.01) {
|
||
var key = "minutesAgo";
|
||
n = Math.round(inMinutes);
|
||
}
|
||
|
||
// in hours
|
||
else if (Math.round(inHours) == 1) {
|
||
var key = "hoursAgo";
|
||
}
|
||
else if (inDays < 1.01) {
|
||
var key = "hoursAgo";
|
||
n = Math.round(inHours);
|
||
}
|
||
|
||
// in days
|
||
else if (Math.round(inDays) == 1) {
|
||
var key = "daysAgo";
|
||
}
|
||
else if (inYears < 1.01) {
|
||
var key = "daysAgo";
|
||
n = Math.round(inDays);
|
||
}
|
||
|
||
// in years
|
||
else if (Math.round(inYears) == 1) {
|
||
var key = "yearsAgo";
|
||
}
|
||
else {
|
||
var key = "yearsAgo";
|
||
var n = Math.round(inYears);
|
||
}
|
||
|
||
return Zotero.getString("date.relative." + key + "." + (n ? "multiple" : "one"), n);
|
||
}
|
||
|
||
|
||
function getFileDateString(file){
|
||
var date = new Date();
|
||
date.setTime(file.lastModifiedTime);
|
||
return date.toLocaleDateString();
|
||
}
|
||
|
||
|
||
function getFileTimeString(file){
|
||
var date = new Date();
|
||
date.setTime(file.lastModifiedTime);
|
||
return date.toLocaleTimeString();
|
||
}
|
||
|
||
/**
|
||
* Figure out the date order from the output of toLocaleDateString()
|
||
*
|
||
* Returns a string with y, m, and d (e.g. 'ymd', 'mdy')
|
||
*/
|
||
function getLocaleDateOrder(){
|
||
if (_localeDateOrder) {
|
||
return _localeDateOrder;
|
||
}
|
||
|
||
var date = new Date("October 5, 2006");
|
||
var parts = date.toLocaleDateString().match(/([0-9]+)[^0-9]+([0-9]+)[^0-9]+([0-9]+)/);
|
||
|
||
// The above only works on OS X and Linux,
|
||
// where toLocaleDateString() produces "10/05/2006"
|
||
if (!parts) {
|
||
var country = Zotero.locale.substr(3);
|
||
switch (country) {
|
||
// I don't know where this country list came from, but these
|
||
// are little-endian in Zotero.strToDate()
|
||
case 'US': // The United States
|
||
case 'FM': // The Federated States of Micronesia
|
||
case 'PW': // Palau
|
||
case 'PH': // The Philippines
|
||
return 'mdy';
|
||
break;
|
||
|
||
default:
|
||
return 'dmy';
|
||
}
|
||
}
|
||
|
||
switch (parseInt(parts[1])){
|
||
case 2006:
|
||
var order = 'y';
|
||
break;
|
||
case 10:
|
||
var order = 'm';
|
||
break;
|
||
case 5:
|
||
var order = 'd';
|
||
break;
|
||
}
|
||
switch (parseInt(parts[2])){
|
||
case 2006:
|
||
order += 'y';
|
||
break;
|
||
case 10:
|
||
order += 'm';
|
||
break;
|
||
case 5:
|
||
order += 'd';
|
||
break;
|
||
}
|
||
switch (parseInt(parts[3])){
|
||
case 2006:
|
||
order += 'y';
|
||
break;
|
||
case 10:
|
||
order += 'm';
|
||
break;
|
||
case 5:
|
||
order += 'd';
|
||
break;
|
||
}
|
||
|
||
_localeDateOrder = order;
|
||
|
||
return order;
|
||
}
|
||
}
|
||
|
||
|
||
Zotero.DragDrop = {
|
||
currentDataTransfer: null,
|
||
|
||
getDragData: function (element, firstOnly) {
|
||
var dragData = {
|
||
dataType: '',
|
||
data: []
|
||
};
|
||
|
||
var dt = this.currentDataTransfer;
|
||
if (!dt) {
|
||
Zotero.debug("Drag data not available");
|
||
return false;
|
||
}
|
||
|
||
var len = firstOnly ? 1 : dt.mozItemCount;
|
||
|
||
if (dt.types.contains('zotero/collection')) {
|
||
dragData.dataType = 'zotero/collection';
|
||
var ids = dt.getData('zotero/collection').split(",");
|
||
dragData.data = ids;
|
||
}
|
||
else if (dt.types.contains('zotero/item')) {
|
||
dragData.dataType = 'zotero/item';
|
||
var ids = dt.getData('zotero/item').split(",");
|
||
dragData.data = ids;
|
||
}
|
||
else if (dt.types.contains('application/x-moz-file')) {
|
||
dragData.dataType = 'application/x-moz-file';
|
||
var files = [];
|
||
for (var i=0; i<len; i++) {
|
||
var file = dt.mozGetDataAt("application/x-moz-file", i);
|
||
file.QueryInterface(Components.interfaces.nsIFile);
|
||
// Don't allow folder drag
|
||
if (file.isDirectory()) {
|
||
continue;
|
||
}
|
||
files.push(file);
|
||
}
|
||
dragData.data = files;
|
||
}
|
||
else if (dt.types.contains('text/x-moz-url')) {
|
||
dragData.dataType = 'text/x-moz-url';
|
||
var urls = [];
|
||
for (var i=0; i<len; i++) {
|
||
var url = dt.getData("text/x-moz-url").split("\n")[0];
|
||
urls.push(url);
|
||
}
|
||
dragData.data = urls;
|
||
}
|
||
|
||
return dragData;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Functions for creating and destroying hidden browser objects
|
||
**/
|
||
Zotero.Browser = new function() {
|
||
this.createHiddenBrowser = createHiddenBrowser;
|
||
this.deleteHiddenBrowser = deleteHiddenBrowser;
|
||
|
||
function createHiddenBrowser(win) {
|
||
if (!win) {
|
||
var win = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||
.getService(Components.interfaces.nsIWindowMediator)
|
||
.getMostRecentWindow("navigator:browser");
|
||
if(!win) {
|
||
var win = Components.classes["@mozilla.org/appshell/window-mediator;1"]
|
||
.getService(Components.interfaces.nsIWindowWatcher)
|
||
.activeWindow;
|
||
}
|
||
}
|
||
|
||
// Create a hidden browser
|
||
var hiddenBrowser = win.document.createElement("browser");
|
||
hiddenBrowser.setAttribute('type', 'content');
|
||
hiddenBrowser.setAttribute('disablehistory', 'true');
|
||
win.document.documentElement.appendChild(hiddenBrowser);
|
||
// Disable some features
|
||
hiddenBrowser.docShell.allowImages = false;
|
||
hiddenBrowser.docShell.allowJavascript = false;
|
||
hiddenBrowser.docShell.allowMetaRedirects = false;
|
||
hiddenBrowser.docShell.allowPlugins = false;
|
||
Zotero.debug("created hidden browser ("
|
||
+ (win.document.getElementsByTagName('browser').length - 1) + ")");
|
||
return hiddenBrowser;
|
||
}
|
||
|
||
function deleteHiddenBrowser(myBrowser) {
|
||
myBrowser.stop();
|
||
myBrowser.parentNode.removeChild(myBrowser);
|
||
myBrowser = null;
|
||
Zotero.debug("deleted hidden browser");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Functions for disabling and enabling the unresponsive script indicator
|
||
**/
|
||
Zotero.UnresponsiveScriptIndicator = new function() {
|
||
this.disable = disable;
|
||
this.enable = enable;
|
||
|
||
// stores the state of the unresponsive script preference prior to disabling
|
||
var _unresponsiveScriptPreference, _isDisabled;
|
||
|
||
/**
|
||
* disables the "unresponsive script" warning; necessary for import and
|
||
* export, which can take quite a while to execute
|
||
**/
|
||
function disable() {
|
||
// don't do anything if already disabled
|
||
if (_isDisabled) {
|
||
return false;
|
||
}
|
||
|
||
var prefService = Components.classes["@mozilla.org/preferences-service;1"].
|
||
getService(Components.interfaces.nsIPrefBranch);
|
||
_unresponsiveScriptPreference = prefService.getIntPref("dom.max_chrome_script_run_time");
|
||
prefService.setIntPref("dom.max_chrome_script_run_time", 0);
|
||
|
||
_isDisabled = true;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* restores the "unresponsive script" warning
|
||
**/
|
||
function enable() {
|
||
var prefService = Components.classes["@mozilla.org/preferences-service;1"].
|
||
getService(Components.interfaces.nsIPrefBranch);
|
||
prefService.setIntPref("dom.max_chrome_script_run_time", _unresponsiveScriptPreference);
|
||
|
||
_isDisabled = false;
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
* Implements nsIWebProgressListener
|
||
*/
|
||
Zotero.WebProgressFinishListener = function(onFinish) {
|
||
this.onStateChange = function(wp, req, stateFlags, status) {
|
||
//Zotero.debug('onStageChange: ' + stateFlags);
|
||
if ((stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
|
||
&& (stateFlags & Components.interfaces.nsIWebProgressListener.STATE_IS_NETWORK)) {
|
||
onFinish();
|
||
}
|
||
}
|
||
|
||
this.onProgressChange = function(wp, req, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
|
||
//Zotero.debug('onProgressChange');
|
||
//Zotero.debug('Current: ' + curTotalProgress);
|
||
//Zotero.debug('Max: ' + maxTotalProgress);
|
||
}
|
||
|
||
this.onLocationChange = function(wp, req, location) {}
|
||
this.onSecurityChange = function(wp, req, stateFlags, status) {}
|
||
this.onStatusChange = function(wp, req, status, msg) {}
|
||
}
|
||
|
||
/*
|
||
* Saves or loads JSON objects.
|
||
*/
|
||
Zotero.JSON = new function() {
|
||
var nativeJSON = Components.classes["@mozilla.org/dom/json;1"].createInstance(Components.interfaces.nsIJSON);
|
||
|
||
this.serialize = function(arg) {
|
||
return nativeJSON.encode(arg);
|
||
}
|
||
|
||
this.unserialize = function(arg) {
|
||
return nativeJSON.decode(arg);
|
||
}
|
||
} |