commit d550ac92b4362b91d317beb0b3d6e88a481cec9f
parent 755db116cc1a5fda27cb3233dde9fdc49457934f
Author: Simon Kornblith <simon@simonster.com>
Date: Mon, 16 Jul 2012 21:50:14 -0400
Q-ize integration.js
Adds a new function, Zotero.promiseGenerator, that returns a promise that is fulfilled by the last thing yielded by a generator, or rejected with an error.
Diffstat:
4 files changed, 955 insertions(+), 969 deletions(-)
diff --git a/chrome/content/zotero/integration/addCitationDialog.js b/chrome/content/zotero/integration/addCitationDialog.js
@@ -565,10 +565,10 @@ var Zotero_Citation_Dialog = new function () {
if(_previewShown) {
document.documentElement.getButton("extra2").label = Zotero.getString("citation.hideEditor");
if(text) {
- io.preview(function(preview) {
+ io.preview().then(function(preview) {
_originalHTML = preview;
editor.value = text;
- });
+ }).end();
} else {
_updatePreview();
}
@@ -581,9 +581,7 @@ var Zotero_Citation_Dialog = new function () {
* called when accept button is clicked
*/
function accept() {
- Zotero.debug("Trying to accept");
_getCitation();
- Zotero.debug("got citation");
var isCustom = _previewShown && io.citation.citationItems.length // if a citation is selected
&& _originalHTML
&& document.getElementById('editor').value != _originalHTML // and citation has been edited
@@ -623,7 +621,7 @@ var Zotero_Citation_Dialog = new function () {
editor.readonly = !io.citation.citationItems.length;
if(io.citation.citationItems.length) {
- io.preview(function(preview) {
+ io.preview().then(function(preview) {
editor.value = preview;
if(editor.initialized) {
diff --git a/chrome/content/zotero/integration/quickFormat.js b/chrome/content/zotero/integration/quickFormat.js
@@ -278,7 +278,7 @@ var Zotero_QuickFormat = new function () {
// Save current search so that when we get items, we know whether it's too late to
// process them or not
var lastSearchTime = currentSearchTime = Date.now();
- io.getItems(function(citedItems) {
+ io.getItems().then(function(citedItems) {
// Don't do anything if panel is already closed
if(isAsync &&
((referencePanel.state !== "open" && referencePanel.state !== "showing")
@@ -314,7 +314,7 @@ var Zotero_QuickFormat = new function () {
}
_updateItemList(citedItems, citedItemsMatchingSearch, searchResultIDs, isAsync);
- });
+ }).end();
if(!completed) {
// We are going to have to wait until items have been retrieved from the document.
diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js
@@ -24,8 +24,6 @@
***** END LICENSE BLOCK *****
*/
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-
const RESELECT_KEY_URI = 1;
const RESELECT_KEY_ITEM_KEY = 2;
const RESELECT_KEY_ITEM_ID = 3;
@@ -44,11 +42,13 @@ const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
Zotero.Integration = new function() {
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Components.utils.import("resource://gre/modules/AddonManager.jsm");
+
const INTEGRATION_MIN_VERSIONS = ["3.1.7.SOURCE", "3.5b2.SOURCE", "3.1.3.SOURCE"];
var _tmpFile = null;
var _osascriptFile;
- var _integrationVersionsOK = null;
// these need to be global because of GC
var _updateTimer;
@@ -59,7 +59,6 @@ Zotero.Integration = new function() {
XOpenDisplay, XCloseDisplay, XFlush, XDefaultRootWindow, XInternAtom, XSendEvent,
XMapRaised, XGetWindowProperty, X11Atom, X11Bool, X11Display, X11Window, X11Status;
- var _inProgress = false;
this.currentWindow = false;
this.sessions = {};
@@ -110,15 +109,25 @@ Zotero.Integration = new function() {
// try to initialize pipe
try {
- Zotero.IPC.Pipe.initPipeListener(pipe, _parseIntegrationPipeCommand);
+ Zotero.IPC.Pipe.initPipeListener(pipe, function(string) {
+ if(string != "") {
+ // exec command if possible
+ var parts = string.match(/^([^ \n]*) ([^ \n]*)(?: ([^\n]*))?\n?$/);
+ if(parts) {
+ var agent = parts[1].toString();
+ var cmd = parts[2].toString();
+ var document = parts[3] ? parts[3].toString() : null;
+ Zotero.Integration.execCommand(agent, cmd, document);
+ } else {
+ Components.utils.reportError("Zotero: Invalid integration input received: "+string);
+ }
+ }
+ });
} catch(e) {
Zotero.logError(e);
}
- _updateTimer = Components.classes["@mozilla.org/timer;1"].
- createInstance(Components.interfaces.nsITimer);
- _updateTimer.initWithCallback({"notify":function() { _checkPluginVersions() }}, 1000,
- Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+ Q.delay(1000).then(_checkPluginVersions);
}
/**
@@ -157,121 +166,144 @@ Zotero.Integration = new function() {
}
}
- function _checkPluginVersions(callback) {
- if(_updateTimer) _updateTimer = undefined;
-
- if(_integrationVersionsOK !== null) {
- if(callback) callback(_integrationVersionsOK);
- return;
- }
+ /**
+ * Checks to see that plugin versions are up to date.
+ * @return {Promise} Promise that is resolved with true if versions are up to date
+ * or with false if they are not.
+ */
+ var _checkPluginVersions = new function () {
+ var integrationVersionsOK;
- var verComp = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
- .getService(Components.interfaces.nsIVersionComparator);
- var addonsChecked = false;
- var success = true;
- function _checkAddons(addons) {
- addonsChecked = true;
- for(var i in addons) {
- var addon = addons[i];
- if(!addon) continue;
- if(addon.userDisabled) continue;
-
- if(verComp.compare(INTEGRATION_MIN_VERSIONS[i], addon.version) > 0) {
- _integrationVersionsOK = false;
- Zotero.Integration.activate();
- var msg = Zotero.getString(
- "integration.error.incompatibleVersion2",
- [Zotero.version, addon.name, INTEGRATION_MIN_VERSIONS[i]]
- );
- Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService)
- .alert(null, Zotero.getString("integration.error.title"), msg);
- throw msg;
+ return function _checkPluginVersions() {
+ if(integrationVersionsOK) {
+ if(integrationVersionsOK === true) {
+ return Q.resolve(integrationVersionsOK);
+ } else {
+ return Q.reject(integrationVersionsOK);
}
}
- _integrationVersionsOK = true;
- if(callback) callback(_integrationVersionsOK);
- }
-
- Components.utils.import("resource://gre/modules/AddonManager.jsm");
- AddonManager.getAddonsByIDs(INTEGRATION_PLUGINS, _checkAddons);
+ var deferred = Q.defer();
+ AddonManager.getAddonsByIDs(INTEGRATION_PLUGINS, function(addons) {
+ for(var i in addons) {
+ var addon = addons[i];
+ if(!addon || addon.userDisabled) continue;
+
+ if(Services.vc.compare(INTEGRATION_MIN_VERSIONS[i], addon.version) > 0) {
+ deferred.reject(integrationVersionsOK = new Zotero.Exception.Alert(
+ "integration.error.incompatibleVersion2",
+ [Zotero.version, addon.name, INTEGRATION_MIN_VERSIONS[i]],
+ "integration.error.title"));
+ }
+ }
+ deferred.resolve(integrationVersionsOK = true);
+ });
+ return deferred.promise;
+ };
}
/**
* Executes an integration command, first checking to make sure that versions are compatible
*/
- this.execCommand = function execCommand(agent, command, docId) {
- if(_inProgress) {
- Zotero.Integration.activate();
- if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
- Zotero.Integration.currentWindow.focus();
- }
- Zotero.debug("Integration: Request already in progress; not executing "+agent+" "+command);
- return;
- }
- _inProgress = true;
+ this.execCommand = new function() {
+ var inProgress;
- // Check integration component versions
- _checkPluginVersions(function(success) {
- if(success) {
- _callIntegration(agent, command, docId);
- } else {
- _inProgress = false;
- }
- });
- }
-
- /**
- * Parses a command received from the integration pipe
- */
- function _parseIntegrationPipeCommand(string) {
- if(string != "") {
- // exec command if possible
- var parts = string.match(/^([^ \n]*) ([^ \n]*)(?: ([^\n]*))?\n?$/);
- if(parts) {
- var agent = parts[1].toString();
- var cmd = parts[2].toString();
- var document = parts[3] ? parts[3].toString() : null;
- Zotero.Integration.execCommand(agent, cmd, document);
- } else {
- Components.utils.reportError("Zotero: Invalid integration input received: "+string);
+ return function execCommand(agent, command, docId) {
+ var document;
+
+ if(inProgress) {
+ Zotero.Integration.activate();
+ if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
+ Zotero.Integration.currentWindow.focus();
+ }
+ Zotero.debug("Integration: Request already in progress; not executing "+agent+" "+command);
+ return;
}
- }
- }
-
- /**
- * Calls the Integration applicatoon
- */
- function _callIntegration(agent, command, docId) {
- // Try to load the appropriate Zotero component; otherwise display an error using the alert
- // service
- try {
- var componentClass = "@zotero.org/Zotero/integration/application?agent="+agent+";1";
- Zotero.debug("Integration: Instantiating "+componentClass+" for command "+command+(docId ? " with doc "+docId : ""));
- var application = Components.classes[componentClass]
- .getService(Components.interfaces.zoteroIntegrationApplication);
- } catch(e) {
- _inProgress = false;
- Zotero.Integration.activate();
- Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService)
- .alert(null, Zotero.getString("integration.error.title"),
- Zotero.getString("integration.error.notInstalled"));
- throw e;
- }
-
- // Try to execute the command; otherwise display an error in alert service or word processor
- // (depending on what is possible)
- var integration, document;
- try {
- document = (application.getDocument && docId ? application.getDocument(docId) : application.getActiveDocument());
- integration = new Zotero.Integration.Document(application, document);
- integration[command]();
- } catch(e) {
- Zotero.Integration.handleError(e, document);
- }
- }
+ inProgress = true;
+
+ // Check integration component versions
+ _checkPluginVersions().then(function() {
+ // Try to load the appropriate Zotero component; otherwise display an error
+ try {
+ var componentClass = "@zotero.org/Zotero/integration/application?agent="+agent+";1";
+ Zotero.debug("Integration: Instantiating "+componentClass+" for command "+command+(docId ? " with doc "+docId : ""));
+ var application = Components.classes[componentClass]
+ .getService(Components.interfaces.zoteroIntegrationApplication);
+ } catch(e) {
+ throw new Zotero.Exception.Alert("integration.error.notInstalled",
+ [], "integration.error.title");
+ }
+
+ // Try to execute the command; otherwise display an error in alert service or word processor
+ // (depending on what is possible)
+ document = (application.getDocument && docId ? application.getDocument(docId) : application.getActiveDocument());
+ return Q.resolve((new Zotero.Integration.Document(application, document))[command]());
+ }).fail(function(e) {
+ if(!(e instanceof Zotero.Exception.UserCancelled)) {
+ try {
+ var displayError = null;
+ if(e instanceof Zotero.Exception.Alert) {
+ displayError = e.message;
+ } else {
+ if(e.toString().indexOf("ExceptionAlreadyDisplayed") === -1) {
+ displayError = Zotero.getString("integration.error.generic")+"\n\n"+(e.message || e.toString());
+ }
+ if(e.stack) {
+ Zotero.debug(e.stack);
+ }
+ }
+
+ if(displayError) {
+ var showErrorInFirefox = !document;
+
+ if(document) {
+ try {
+ document.activate();
+ document.displayAlert(displayError,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
+ } catch(e) {
+ showErrorInFirefox = true;
+ }
+ }
+
+ if(showErrorInFirefox) {
+ Zotero.Integration.activate();
+ Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService)
+ .alert(null, Zotero.getString("integration.error.title"), displayError);
+ }
+ }
+ } finally {
+ Zotero.logError(e);
+ }
+ }
+ }).fin(function() {
+ if(document) {
+ try {
+ document.cleanup();
+ document.activate();
+
+ // Call complete function if one exists
+ if(document.wrappedJSObject && document.wrappedJSObject.complete) {
+ document.wrappedJSObject.complete();
+ }
+ } catch(e) {
+ Zotero.logError(e);
+ }
+ }
+
+ if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
+ var oldWindow = Zotero.Integration.currentWindow;
+ Q.delay(100).then(function() {
+ oldWindow.close();
+ });
+ }
+
+ inProgress = Zotero.Integration.currentWindow = false;
+ }).end();
+ };
+ };
/**
* Activates Firefox
@@ -583,7 +615,7 @@ Zotero.Integration = new function() {
if(XSendEvent(_x11Display, _x11RootWindow, 0, mask, event.address())) {
XMapRaised(_x11Display, x11Window);
XFlush(_x11Display);
- Zotero.debug("Activated successfully");
+ Zotero.debug("Integration: Activated successfully");
} else {
Zotero.debug("Integration: An error occurred activating the window");
}
@@ -619,103 +651,6 @@ Zotero.Integration = new function() {
}
/**
- * Show appropriate dialogs for an integration error
- */
- this.handleError = function(e, document) {
- if(!(e instanceof Zotero.Exception.UserCancelled)) {
- try {
- var displayError = null;
- if(e instanceof Zotero.Integration.DisplayException) {
- displayError = e.toString();
- } else {
- // check to see whether there's a pyxpcom error in the console, since it doesn't
- // get thrown directly
- var message = "";
-
- var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
- .getService(Components.interfaces.nsIConsoleService);
-
- var messages = {};
- consoleService.getMessageArray(messages, {});
- messages = messages.value;
- if(messages && messages.length) {
- var lastMessage = messages[messages.length-1];
- try {
- var error = lastMessage.QueryInterface(Components.interfaces.nsIScriptError);
- } catch(e2) {
- if(lastMessage.message && lastMessage.message.substr(0, 12) == "ERROR:xpcom:") {
- // print just the last line of the message, but re-throw the rest
- message = lastMessage.message.substr(0, lastMessage.message.length-1);
- message = "\n"+message.substr(message.lastIndexOf("\n"))
- }
- }
- }
-
- if(!message && typeof(e) == "object") message = "\n\n"+e.toString();
-
- if(message.indexOf("ExceptionAlreadyDisplayed") === -1) {
- displayError = Zotero.getString("integration.error.generic")+message;
- }
- Zotero.debug(e);
- }
-
- if(displayError) {
- var showErrorInFirefox = !document;
-
- if(document) {
- try {
- document.activate();
- document.displayAlert(displayError,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
- } catch(e) {
- showErrorInFirefox = true;
- }
- }
-
- if(showErrorInFirefox) {
- Zotero.Integration.activate();
- Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- .getService(Components.interfaces.nsIPromptService)
- .alert(null, Zotero.getString("integration.error.title"), displayError);
- }
- }
- } finally {
- Zotero.logError(e);
- }
- }
-
- this.complete(document);
- }
-
- /**
- * Called when integration is complete
- */
- this.complete = function(doc) {
- if(doc) {
- try {
- doc.cleanup();
- doc.activate();
-
- // Call complete function if one exists
- if(doc.wrappedJSObject && doc.wrappedJSObject.complete) {
- doc.wrappedJSObject.complete();
- }
- } catch(e) {
- Zotero.logError(e);
- }
- }
-
- if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
- var oldWindow = Zotero.Integration.currentWindow;
- Zotero.setTimeout(function() {
- oldWindow.close();
- }, 100, true);
- }
- _inProgress = Zotero.Integration.currentWindow = false;
- }
-
- /**
* Runs an AppleScript on OS X
*
* @param script {String}
@@ -743,18 +678,15 @@ Zotero.Integration = new function() {
* @param {String} url The chrome:// URI of the window
* @param {String} [options] Options to pass to the window
* @param {String} [io] Data to pass to the window
- * @param {Function|Boolean} [async] Function to call when window is closed. If not specified,
- * function waits to return until the window has been closed. If "true", the function returns
- * immediately.
+ * @return {Promise} Promise resolved when the window is closed
*/
- this.displayDialog = function(doc, url, options, io, async) {
+ this.displayDialog = function displayDialog(doc, url, options, io) {
doc.cleanup();
var allOptions = 'chrome,centerscreen';
// without this, Firefox gets raised with our windows under Compiz
if(Zotero.isLinux) allOptions += ',dialog=no';
if(options) allOptions += ','+options;
- if(!async) allOptions += ',modal=yes';
var window = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher)
@@ -762,6 +694,7 @@ Zotero.Integration = new function() {
Zotero.Integration.currentWindow = window;
Zotero.Integration.activate(window);
+ var deferred = Q.defer();
var listener = function() {
if(window.location.toString() === "about:blank") return;
@@ -773,15 +706,22 @@ Zotero.Integration = new function() {
}
Zotero.Integration.currentWindow = false;
- if(async instanceof Function) {
- try {
- async();
- } catch(e) {
- Zotero.Integration.handleError(e, doc);
- }
- }
+ deferred.resolve();
}
window.addEventListener("unload", listener, false);
+
+ return deferred.promise;
+ };
+
+ /**
+ * Default callback for field-related errors. All functions that do not define their
+ * own handlers for field-related errors should use this one.
+ */
+ this.onFieldError = function onFieldError(err) {
+ if(err.attemptToResolve) {
+ return err.attemptToResolve();
+ }
+ throw err;
}
}
@@ -799,22 +739,159 @@ Zotero.Integration.MissingItemException = function(reselectKeys, reselectKeyType
this.citationIndex = citationIndex;
this.citationLength = citationLength;
}
-Zotero.Integration.MissingItemException.prototype.name = "MissingItemException";
-Zotero.Integration.MissingItemException.prototype.message = "An item in this document is missing from your Zotero library.";
-Zotero.Integration.MissingItemException.prototype.toString = function() { return this.message; };
+Zotero.Integration.MissingItemException.prototype = {
+ "name":"MissingItemException",
+ "message":"An item in this document is missing from your Zotero library.",
+ "toString":function() { return this.message },
+ "setContext":function(fieldGetter, fieldIndex) {
+ this.fieldGetter = fieldGetter;
+ this.fieldIndex = fieldIndex;
+ },
+
+ "attemptToResolve":function() {
+ Zotero.logError(this);
+ if(!this.fieldGetter) {
+ throw new Error("Could not resolve "+this.name+": setContext not called");
+ }
+
+ // Ask user what to do with this item
+ if(this.citationLength == 1) {
+ var msg = Zotero.getString("integration.missingItem.single");
+ } else {
+ var msg = Zotero.getString("integration.missingItem.multiple", (this.citationIndex+1).toString());
+ }
+ msg += '\n\n'+Zotero.getString('integration.missingItem.description');
+ this.fieldGetter._fields[this.fieldIndex].select();
+ this.fieldGetter._doc.activate();
+ var result = this.fieldGetter._doc.displayAlert(msg, 1, 3);
+ if(result == 0) { // Cancel
+ return Q.reject(new Zotero.Exception.UserCancelled("document update"));
+ } else if(result == 1) { // No
+ for each(var reselectKey in this.reselectKeys) {
+ this.fieldGetter._removeCodeKeys[reselectKey] = true;
+ }
+ this.fieldGetter._removeCodeFields[this.fieldIndex] = true;
+ return this.fieldGetter._processFields(this.fieldIndex+1);
+ } else { // Yes
+ // Display reselect item dialog
+ var fieldGetter = this.fieldGetter,
+ fieldIndex = this.fieldIndex,
+ oldCurrentWindow = Zotero.Integration.currentWindow;
+ return fieldGetter._session.reselectItem(fieldGetter._doc, this)
+ .then(function() {
+ // Now try again
+ Zotero.Integration.currentWindow = oldCurrentWindow;
+ fieldGetter._doc.activate();
+ try {
+ fieldGetter._processFields(fieldIndex);
+ } catch(e) {
+ return Zotero.Integration.onFieldError(e);
+ }
+ });
+ return false;
+ }
+ }
+}
-Zotero.Integration.DisplayException = function(name, params) {
- this.name = name;
- this.params = params ? params : [];
+Zotero.Integration.CorruptFieldException = function(code, cause) {
+ this.code = code;
+ this.cause = cause;
+};
+Zotero.Integration.CorruptFieldException.prototype = {
+ "name":"CorruptFieldException",
+ "message":"A field code in this document is corrupted.",
+ "toString":function() { return this.cause.toString()+"\n\n"+this.code.toSource(); },
+ "setContext":function(fieldGetter, fieldIndex, field) {
+ this.fieldGetter = fieldGetter;
+ this.fieldIndex = fieldIndex;
+ },
+
+ /**
+ * Tries to resolve the CorruptFieldException
+ * @return {Promise} A promise that is either resolved with true or rejected with
+ * Zotero.Exception.UserCancelled
+ */
+ "attemptToResolve":function() {
+ Zotero.logError(this.cause);
+ if(!this.fieldGetter) {
+ throw new Error("Could not resolve "+this.name+": setContext not called");
+ }
+
+ var msg = Zotero.getString("integration.corruptField")+'\n\n'+
+ Zotero.getString('integration.corruptField.description'),
+ field = this.fieldGetter._fields[this.fieldIndex];
+ field.select();
+ this.fieldGetter._doc.activate();
+ var result = this.fieldGetter._doc.displayAlert(msg,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO_CANCEL);
+
+ if(result == 0) {
+ return Q.reject(new Zotero.Exception.UserCancelled("document update"));
+ } else if(result == 1) { // No
+ this.fieldGetter._removeCodeFields[this.fieldIndex] = true;
+ return this.fieldGetter._processFields(this.fieldIndex+1);
+ } else {
+ // Display reselect edit citation dialog
+ var fieldGetter = this.fieldGetter,
+ oldWindow = Zotero.Integration.currentWindow,
+ oldProgressCallback = this.progressCallback;
+ return fieldGetter.addEditCitation(field).then(function() {
+ if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
+ Zotero.Integration.currentWindow.close();
+ }
+ Zotero.Integration.currentWindow = oldWindow;
+ fieldGetter.progressCallback = oldProgressCallback;
+ return fieldGetter.updateSession().fail(Zotero.Integration.onFieldError);
+ });
+ }
+ }
};
-Zotero.Integration.DisplayException.prototype.toString = function() { return Zotero.getString("integration.error."+this.name, this.params); };
-Zotero.Integration.CorruptFieldException = function(corruptFieldString) {
- this.corruptFieldString = corruptFieldString;
+/**
+ * An exception to encapsulate the case where bibliography data is invalid.
+ * @class
+ */
+Zotero.Integration.CorruptBibliographyException = function(code, cause) {
+ this.code = code;
+ this.cause = cause;
}
-Zotero.Integration.CorruptFieldException.prototype.name = "CorruptFieldException";
-Zotero.Integration.CorruptFieldException.prototype.message = "A field code in this document is corrupted.";
-Zotero.Integration.CorruptFieldException.prototype.toString = function() { return this.message+" "+this.corruptFieldString.toSource(); }
+Zotero.Integration.CorruptBibliographyException.prototype = {
+ "name":"CorruptBibliographyException",
+ "message":"A bibliography in this document is corrupted.",
+ "toString":function() { return this.cause.toString()+"\n\n"+this.code },
+
+ "setContext":function(fieldGetter) {
+ this.fieldGetter = fieldGetter;
+ },
+
+ /**
+ * Tries to resolve the CorruptBibliographyException
+ * @return {Promise} A promise that is either resolved with true or rejected with
+ * Zotero.Exception.UserCancelled
+ */
+ "attemptToResolve":function() {
+ Zotero.debug("Attempting to resolve")
+ Zotero.logError(this.cause);
+ if(!this.fieldGetter) {
+ throw new Error("Could not resolve "+this.name+": setContext not called");
+ }
+
+ var msg = Zotero.getString("integration.corruptBibliography")+'\n\n'+
+ Zotero.getString('integration.corruptBibliography.description');
+ var result = this.fieldGetter._doc.displayAlert(msg,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
+ if(result == 0) {
+ return Q.reject(new Zotero.Exception.UserCancelled("clearing corrupted bibliography"));
+ } else {
+ this.fieldGetter._bibliographyData = "";
+ this.fieldGetter._session.bibliographyHasChanged = true;
+ this.fieldGetter._session.bibliographyDataHasChanged = true;
+ return Q.resolve(true);
+ }
+ }
+};
const INTEGRATION_TYPE_ITEM = 1;
const INTEGRATION_TYPE_BIBLIOGRAPHY = 2;
@@ -831,25 +908,28 @@ Zotero.Integration.Document = function(app, doc) {
this._app = app;
this._doc = doc;
}
-
/**
* Creates a new session
* @param data {Zotero.Integration.DocumentData} Document data for new session
+ * @return {Zotero.Integration.Session}
*/
-Zotero.Integration.Document.prototype._createNewSession = function(data) {
+Zotero.Integration.Document.prototype._createNewSession = function _createNewSession(data) {
data.sessionID = Zotero.randomString();
var session = Zotero.Integration.sessions[data.sessionID] = new Zotero.Integration.Session(this._doc);
return session;
-}
+};
/**
* Gets preferences for a document
- * @param require {Boolean} Whether an error should be thrown if no preferences or fields exist
- * (otherwise, the set doc prefs dialog is shown)
- * @param dontRunSetDocPrefs {Boolean} Whether to show the Set Document Preferences window if no
- * preferences exist
- */
-Zotero.Integration.Document.prototype._getSession = function(require, dontRunSetDocPrefs, callback) {
+ * @param require {Boolean} Whether an error should be thrown if no preferences or fields
+ * exist (otherwise, the set doc prefs dialog is shown)
+ * @param dontRunSetDocPrefs {Boolean} Whether to show the Set Document Preferences
+ * window if no preferences exist
+ * @return {Promise} Promise resolved with true if a session was found or false if
+ * dontRunSetDocPrefs is true and no session was found, or rejected with
+ * Zotero.Exception.UserCancelled if the document preferences window was cancelled.
+ */
+Zotero.Integration.Document.prototype._getSession = function _getSession(require, dontRunSetDocPrefs) {
var dataString = this._doc.getDocumentData(),
data,
me = this;
@@ -877,7 +957,9 @@ Zotero.Integration.Document.prototype._getSession = function(require, dontRunSet
// if no fields, throw an error
if(!haveFields) {
- throw new Zotero.Integration.DisplayException("mustInsertCitation");
+ return Q.reject(new Zotero.Exception.Alert(
+ "integration.error.mustInsertCitation",
+ [], "integration.error.title"));
} else {
Zotero.debug("Integration: No document preferences found, but found "+data.prefs.fieldType+" fields");
}
@@ -886,23 +968,18 @@ Zotero.Integration.Document.prototype._getSession = function(require, dontRunSet
// Set doc prefs if no data string yet
this._session = this._createNewSession(data);
this._session.setData(data);
- if(dontRunSetDocPrefs) {
- callback(false);
- return;
- }
+ if(dontRunSetDocPrefs) return Q.resolve(false);
- this._session.setDocPrefs(this._doc, this._app.primaryFieldType, this._app.secondaryFieldType, function(status) {
- if(status === false) {
- throw new Zotero.Exception.UserCancelled("document preferences update");
- }
-
+ return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
+ this._app.secondaryFieldType).then(function(status) {
// save doc prefs in doc
me._doc.setDocumentData(me._session.data.serializeXML());
if(haveFields) {
me._session.reload = true;
}
- callback(true);
+
+ return me._session;
});
} else {
if(data.dataVersion < DATA_VERSION) {
@@ -916,14 +993,18 @@ Zotero.Integration.Document.prototype._getSession = function(require, dontRunSet
var warning = this._doc.displayAlert(Zotero.getString("integration.upgradeWarning"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
- if(!warning) throw new Zotero.Exception.UserCancelled("document upgrade");
+ if(!warning) {
+ return Q.reject(new Zotero.Exception.UserCancelled("document upgrade"));
+ }
} else if(data.dataVersion > DATA_VERSION) {
- throw new Zotero.Integration.DisplayException("newerDocumentVersion", [data.zoteroVersion, Zotero.version]);
+ return Q.reject(new Zotero.Exception.Alert("integration.error.newerDocumentVersion",
+ [data.zoteroVersion, Zotero.version], "integration.error.title"));
}
if(data.prefs.fieldType !== this._app.primaryFieldType
&& data.prefs.fieldType !== this._app.secondaryFieldType) {
- throw new Zotero.Integration.DisplayException("fieldTypeMismatch");
+ return Q.reject(new Zotero.Exception.Alert("integration.error.fieldTypeMismatch",
+ [], "integration.error.title"));
}
if(Zotero.Integration.sessions[data.sessionID]) {
@@ -935,230 +1016,227 @@ Zotero.Integration.Document.prototype._getSession = function(require, dontRunSet
} catch(e) {
// make sure style is defined
if(e instanceof Zotero.Integration.DisplayException && e.name === "invalidStyle") {
- this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
- this._app.secondaryFieldType, function(status) {
- if(status === false) {
- throw new Zotero.Exception.UserCancelled("document preferences update");
- }
-
+ return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
+ this._app.secondaryFieldType).then(function(status) {
me._doc.setDocumentData(me._session.data.serializeXML());
me._session.reload = true;
- callback(true);
+ return me._session;
});
- return;
} else {
- throw e;
+ return Q.reject(e);
}
}
this._doc.setDocumentData(this._session.data.serializeXML());
this._session.reload = true;
}
- callback(true);
+ return Q.resolve(this._session);
}
-}
+};
/**
* Adds a citation to the current document.
+ * @return {Promise}
*/
Zotero.Integration.Document.prototype.addCitation = function() {
var me = this;
- this._getSession(false, false, function() {
- var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
- fieldGetter.addEditCitation(null, function() {
- Zotero.Integration.complete(me._doc);
- });
+ return this._getSession(false, false).then(function() {
+ return (new Zotero.Integration.Fields(me._session, me._doc)).addEditCitation(null);
});
}
/**
* Edits the citation at the cursor position.
+ * @return {Promise}
*/
Zotero.Integration.Document.prototype.editCitation = function() {
var me = this;
- this._getSession(true, false, function() {
+ return this._getSession(true, false).then(function() {
var field = me._doc.cursorInField(me._session.data.prefs['fieldType'])
if(!field) {
- throw new Zotero.Integration.DisplayException("notInCitation");
+ throw new Zotero.Exception.Alert("integration.error.notInCitation", [],
+ "integration.error.title");
}
- var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
- fieldGetter.addEditCitation(field, function() {
- Zotero.Integration.complete(me._doc);
- });
+ return (new Zotero.Integration.Fields(me._session, me._doc)).addEditCitation(field);
});
}
/**
* Adds a bibliography to the current document.
+ * @return {Promise}
*/
Zotero.Integration.Document.prototype.addBibliography = function() {
var me = this;
- this._getSession(true, false, function() {
+ return this._getSession(true, false).then(function() {
// Make sure we can have a bibliography
if(!me._session.data.style.hasBibliography) {
- throw new Zotero.Integration.DisplayException("noBibliography");
+ throw new Zotero.Exception.Alert("integration.error.noBibliography", [],
+ "integration.error.title");
}
var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc),
field = fieldGetter.addField();
field.setCode("BIBL");
- fieldGetter.updateSession(function() {
- fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, true, false, function() {
- Zotero.Integration.complete(me._doc);
- });
- });
+ return fieldGetter.updateSession().fail(Zotero.Integration.onFieldError)
+ .then(function() {
+ return fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, true, false);
+ })
});
}
/**
* Edits bibliography metadata.
+ * @return {Promise}
*/
-Zotero.Integration.Document.prototype.editBibliography = function(callback) {
+Zotero.Integration.Document.prototype.editBibliography = function() {
// Make sure we have a bibliography
- var me = this;
- this._getSession(true, false, function() {
- var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
- fieldGetter.get(function(fields) {
- var haveBibliography = false;
- for(var i=fields.length-1; i>=0; i--) {
- var code = fields[i].getCode();
- var [type, content] = fieldGetter.getCodeTypeAndContent(code);
- if(type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
- haveBibliography = true;
- break;
- }
- }
-
- if(!haveBibliography) {
- throw new Zotero.Integration.DisplayException("mustInsertBibliography");
+ var me = this, fieldGetter;
+ return this._getSession(true, false).then(function() {
+ fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
+ return fieldGetter.get();
+ }).then(function(fields) {
+ var haveBibliography = false;
+ for(var i=fields.length-1; i>=0; i--) {
+ var code = fields[i].getCode();
+ var [type, content] = fieldGetter.getCodeTypeAndContent(code);
+ if(type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
+ haveBibliography = true;
+ break;
}
-
- fieldGetter.updateSession(function() {
- me._session.editBibliography(me._doc, function() {
- me._doc.activate();
- fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, true, false, function() {
- Zotero.Integration.complete(me._doc);
- });
- });
- });
- });
+ }
+
+ if(!haveBibliography) {
+ throw new Zotero.Exception.Alert("integration.error.mustInsertBibliography",
+ [], "integration.error.title");
+ }
+
+ return fieldGetter.updateSession().fail(Zotero.Integration.onFieldError);
+ }).then(function() {
+ return me._session.editBibliography(me._doc);
+ }).then(function() {
+ return fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, true, false);
});
}
/**
* Updates the citation data for all citations and bibliography entries.
+ * @return {Promise}
*/
Zotero.Integration.Document.prototype.refresh = function() {
var me = this;
- this._getSession(true, false, function() {
+ return this._getSession(true, false).then(function() {
// Send request, forcing update of citations and bibliography
var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
- fieldGetter.updateSession(function() {
- fieldGetter.updateDocument(FORCE_CITATIONS_REGENERATE, true, false, function() {
- Zotero.Integration.complete(me._doc);
- });
+ return fieldGetter.updateSession().fail(Zotero.Integration.onFieldError)
+ .then(function() {
+ return fieldGetter.updateDocument(FORCE_CITATIONS_REGENERATE, true, false);
});
});
}
/**
* Deletes field codes.
+ * @return {Promise}
*/
Zotero.Integration.Document.prototype.removeCodes = function() {
var me = this;
- this._getSession(true, false, function() {
+ return this._getSession(true, false).then(function() {
var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
- fieldGetter.get(function(fields) {
- var result = me._doc.displayAlert(Zotero.getString("integration.removeCodesWarning"),
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
- if(result) {
- for(var i=fields.length-1; i>=0; i--) {
- fields[i].removeCode();
- }
+ return fieldGetter.get()
+ }).then(function(fields) {
+ var result = me._doc.displayAlert(Zotero.getString("integration.removeCodesWarning"),
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
+ if(result) {
+ for(var i=fields.length-1; i>=0; i--) {
+ fields[i].removeCode();
}
-
- Zotero.Integration.complete(me._doc);
- });
+ }
});
}
/**
* Displays a dialog to set document preferences (style, footnotes/endnotes, etc.)
+ * @return {Promise}
*/
Zotero.Integration.Document.prototype.setDocPrefs = function() {
- var me = this;
- this._getSession(false, true, function(haveSession) {
- var setDocPrefs = function() {
- me._session.setDocPrefs(me._doc, me._app.primaryFieldType, me._app.secondaryFieldType,
- function(oldData) {
- if(oldData || oldData === null) {
- me._doc.setDocumentData(me._session.data.serializeXML());
- if(oldData === null) return;
-
- fieldGetter.get(function(fields) {
- if(fields && fields.length) {
- // if there are fields, we will have to convert some things; get a list of what we need to deal with
- var convertBibliographies = oldData === true || oldData.prefs.fieldType != me._session.data.prefs.fieldType;
- var convertItems = convertBibliographies || oldData.prefs.noteType != me._session.data.prefs.noteType;
- var fieldsToConvert = new Array();
- var fieldNoteTypes = new Array();
- for(var i=0, n=fields.length; i<n; i++) {
- var field = fields[i],
- fieldCode = field.getCode(),
- [type, content] = fieldGetter.getCodeTypeAndContent(fieldCode);
-
- if(convertItems && type === INTEGRATION_TYPE_ITEM) {
- var citation = me._session.unserializeCitation(fieldCode);
- if(!citation.properties.dontUpdate) {
- fieldsToConvert.push(field);
- fieldNoteTypes.push(me._session.data.prefs.noteType);
- }
- } else if(convertBibliographies && type === INTEGRATION_TYPE_BIBLIOGRAPHY) {
- fieldsToConvert.push(field);
- fieldNoteTypes.push(0);
- }
- }
-
- if(fieldsToConvert.length) {
- // pass to conversion function
- me._doc.convert(new Zotero.Integration.Document.JSEnumerator(fieldsToConvert),
- me._session.data.prefs.fieldType, fieldNoteTypes, fieldNoteTypes.length);
- }
-
- // refresh contents
- fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
- fieldGetter.updateSession(function() {
- fieldGetter.updateDocument(FORCE_CITATIONS_RESET_TEXT, true, true,
- function() {
- Zotero.Integration.complete(me._doc);
- });
- });
- } else {
- Zotero.Integration.complete(me._doc);
- }
- });
- } else {
- Zotero.Integration.complete(me._doc);
- }
- });
- };
-
- var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
-
+ var me = this,
+ fieldGetter,
+ oldData;
+ return this._getSession(false, true).then(function(haveSession) {
+ fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
+ var setDocPrefs = me._session.setDocPrefs.bind(me._session, me._doc,
+ me._app.primaryFieldType, me._app.secondaryFieldType);
if(!haveSession) {
// This is a brand new document; don't try to get fields
- setDocPrefs();
+ return setDocPrefs();
} else if(me._session.reload) {
// Always reload before setDocPrefs so we can permit/deny unchecking storeReferences as
// appropriate
- fieldGetter.updateSession(setDocPrefs);
+ return fieldGetter.updateSession().fail(Zotero.Integration.onFieldError)
+ .then(setDocPrefs);
} else {
// Can get fields while dialog is open
- fieldGetter.get();
- setDocPrefs();
+ return Q.all([
+ fieldGetter.get(),
+ setDocPrefs()
+ ]).spread(function (fields, setDocPrefs) {
+ // Only return value from setDocPrefs
+ return setDocPrefs;
+ });
+ }
+ }).then(function(aOldData) { // After setDocPrefs call
+ oldData = aOldData;
+
+ // Write document data to document
+ me._doc.setDocumentData(me._session.data.serializeXML());
+
+ // If oldData is null, then there was no document data, so we don't need to update
+ // fields
+ if(!oldData) return false;
+ return fieldGetter.get();
+ }).then(function(fields) {
+ if(!fields || !fields.length) return;
+
+ // If there are fields, we will have to convert some things; get a list of what
+ // we need to deal with
+ var convertBibliographies = oldData === true
+ || oldData.prefs.fieldType != me._session.data.prefs.fieldType;
+ var convertItems = convertBibliographies
+ || oldData.prefs.noteType != me._session.data.prefs.noteType;
+ var fieldsToConvert = new Array();
+ var fieldNoteTypes = new Array();
+ for(var i=0, n=fields.length; i<n; i++) {
+ var field = fields[i],
+ fieldCode = field.getCode(),
+ [type, content] = fieldGetter.getCodeTypeAndContent(fieldCode);
+
+ if(convertItems && type === INTEGRATION_TYPE_ITEM) {
+ var citation = me._session.unserializeCitation(fieldCode);
+ if(!citation.properties.dontUpdate) {
+ fieldsToConvert.push(field);
+ fieldNoteTypes.push(me._session.data.prefs.noteType);
+ }
+ } else if(convertBibliographies
+ && type === INTEGRATION_TYPE_BIBLIOGRAPHY) {
+ fieldsToConvert.push(field);
+ fieldNoteTypes.push(0);
+ }
}
+
+ if(fieldsToConvert.length) {
+ // Pass to conversion function
+ me._doc.convert(new Zotero.Integration.Document.JSEnumerator(fieldsToConvert),
+ me._session.data.prefs.fieldType, fieldNoteTypes,
+ fieldNoteTypes.length);
+ }
+
+ // Refresh contents
+ fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
+ return fieldGetter.updateSession().fail(Zotero.Integration.onFieldError)
+ .then(fieldGetter.updateDocument.bind(
+ fieldGetter, FORCE_CITATIONS_RESET_TEXT, true, true));
});
}
@@ -1182,7 +1260,6 @@ Zotero.Integration.Document.JSEnumerator.prototype.getNext = function() {
Zotero.Integration.Fields = function(session, doc) {
this._session = session;
this._doc = doc;
- this._callbacks = [];
}
/**
@@ -1192,8 +1269,8 @@ Zotero.Integration.Fields = function(session, doc) {
Zotero.Integration.Fields.prototype.addField = function(note) {
// Get citation types if necessary
if(!this._doc.canInsertField(this._session.data.prefs['fieldType'])) {
- throw new Zotero.Integration.DisplayException("cannotInsertHere");
- return false;
+ return Q.reject(new Zotero.Exception.Alert("integration.error.cannotInsertHere",
+ [], "integration.error.title"));
}
var field = this._doc.cursorInField(this._session.data.prefs['fieldType']);
@@ -1201,7 +1278,7 @@ Zotero.Integration.Fields.prototype.addField = function(note) {
if(!this._doc.displayAlert(Zotero.getString("integration.replace"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL)) {
- throw new Zotero.Exception.UserCancelled("inserting citation");
+ return Q.reject(new Zotero.Exception.UserCancelled("inserting citation"));
}
}
@@ -1236,262 +1313,155 @@ Zotero.Integration.Fields.prototype.getCodeTypeAndContent = function(rawCode) {
/**
* Gets all fields for a document
+ * @return {Promise} Promise resolved with field list.
*/
-Zotero.Integration.Fields.prototype.get = function(callback) {
+Zotero.Integration.Fields.prototype.get = function get() {
+ // If we already have fields, just return them
if(this._fields) {
- try {
- if(callback) {
- callback(this._fields);
- }
- } catch(e) {
- Zotero.Integration.handleError(e, this._doc);
- }
- return;
+ return Q.resolve(this._fields);
}
- if(callback) {
- this._callbacks.push(callback);
+ // Create a new promise and add it to promise list
+ var deferred = Q.defer();
+
+ // If already getting fields, just return the promise
+ if(this._deferreds) {
+ this._deferreds.push(deferred);
+ return deferred;
+ } else {
+ this._deferreds = [deferred];
}
- this._retrieveFields();
-}
-
-/**
- * Actually do the work of retrieving fields
- */
-Zotero.Integration.Fields.prototype._retrieveFields = function() {
- if(this._retrievingFields) return;
- this._retrievingFields = true;
- var getFieldsTime = (new Date()).getTime();
- var me = this;
- this._doc.getFieldsAsync(this._session.data.prefs['fieldType'], {"observe":function(subject, topic, data) {
+ // Otherwise, start getting fields
+ var getFieldsTime = (new Date()).getTime(),
+ me = this;
+ this._doc.getFieldsAsync(this._session.data.prefs['fieldType'],
+ {"observe":function(subject, topic, data) {
if(topic === "fields-available") {
- if(me.progressCallback) me.progressCallback(75);
-
- // Add fields to fields array
- var fieldsEnumerator = subject.QueryInterface(Components.interfaces.nsISimpleEnumerator);
- var fields = me._fields = [];
- while(fieldsEnumerator.hasMoreElements()) {
- fields.push(fieldsEnumerator.getNext().QueryInterface(Components.interfaces.zoteroIntegrationField));
- }
-
- if(Zotero.Debug.enabled) {
- var endTime = (new Date()).getTime();
- Zotero.debug("Integration: Retrieved "+fields.length+" fields in "+
- (endTime-getFieldsTime)/1000+"; "+
- 1000/((endTime-getFieldsTime)/fields.length)+" fields/second");
+ if(me.progressCallback) {
+ try {
+ me.progressCallback(75);
+ } catch(e) {
+ Zotero.logError(e);
+ };
}
- // Run callbacks
try {
- for(var i=0, n=me._callbacks.length; i<n; i++) {
- me._callbacks[i](fields);
+ // Add fields to fields array
+ var fieldsEnumerator = subject.QueryInterface(Components.interfaces.nsISimpleEnumerator);
+ var fields = me._fields = [];
+ while(fieldsEnumerator.hasMoreElements()) {
+ fields.push(fieldsEnumerator.getNext().QueryInterface(Components.interfaces.zoteroIntegrationField));
+ }
+
+ if(Zotero.Debug.enabled) {
+ var endTime = (new Date()).getTime();
+ Zotero.debug("Integration: Retrieved "+fields.length+" fields in "+
+ (endTime-getFieldsTime)/1000+"; "+
+ 1000/((endTime-getFieldsTime)/fields.length)+" fields/second");
}
} catch(e) {
- Zotero.Integration.handleError(e, me._doc);
+ // Reject promises
+ for(var i=0, n=me._deferreds.length; i<n; i++) {
+ me._deferreds[i].reject(e);
+ }
+ me._deferreds = [];
+ return;
+ }
+
+ // Resolve promises
+ for(var i=0, n=me._deferreds.length; i<n; i++) {
+ me._deferreds[i].resolve(fields);
+ }
+ me._deferreds = [];
+ } else if(topic === "fields-progress") {
+ if(me.progressCallback) {
+ try {
+ me.progressCallback((data ? parseInt(data, 10)*(3/4) : null));
+ } catch(e) {
+ Zotero.logError(e);
+ };
}
- } else if(topic === "fields-progress" && me.progressCallback) {
- me.progressCallback((data ? parseInt(data, 10)*(3/4) : null));
} else if(topic === "fields-error") {
- Zotero.Integration.handleError(data, me._doc);
+ for(var i=0, n=me._deferreds.length; i<n; i++) {
+ me._deferreds[i].reject(data);
+ }
+ me._deferreds = [];
}
}, QueryInterface:XPCOMUtils.generateQI([Components.interfaces.nsIObserver, Components.interfaces.nsISupports])});
-}
-
-/**
- * Shows an error if a field code is corrupted
- * @param {Exception} e The exception thrown
- * @param {Field} field The Zotero field object
- * @param {Function} callback The callback passed to updateSession
- * @param {Function} errorCallback The error callback passed to updateSession
- * @param {Integer} i The field index
- * @return {Boolean} Whether to continue updating the session
- */
-Zotero.Integration.Fields.prototype._showCorruptFieldError = function(e, field, callback, errorCallback, i) {
- Zotero.logError(e);
-
- var msg = Zotero.getString("integration.corruptField")+'\n\n'+
- Zotero.getString('integration.corruptField.description');
- field.select();
- this._doc.activate();
- var result = this._doc.displayAlert(msg,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO_CANCEL);
-
- if(result == 0) {
- throw new Zotero.Exception.UserCancelled("document update");
- } else if(result == 1) { // No
- this._removeCodeFields.push(i);
- return true;
- } else {
- // Display reselect edit citation dialog
- var me = this;
- var oldWindow = Zotero.Integration.currentWindow;
- var oldProgressCallback = me.progressCallback;
- this.addEditCitation(field, function() {
- if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
- Zotero.Integration.currentWindow.close();
- }
- Zotero.Integration.currentWindow = oldWindow;
- me.progressCallback = oldProgressCallback;
- me.updateSession(callback, errorCallback);
- });
- return false;
- }
-}
-
-/**
- * Shows an error if a field code is missing
- * @param {Exception} e The exception thrown
- * @param {Exception} e The exception thrown
- * @param {Field} field The Zotero field object
- * @param {Function} callback The callback passed to updateSession
- * @param {Function} errorCallback The error callback passed to updateSession
- * @param {Integer} i The field index
- * @return {Boolean} Whether to continue updating the session
- */
-Zotero.Integration.Fields.prototype._showMissingItemError = function(e, field, callback, errorCallback, i) {
- // First, check if we've already decided to remove field codes from these
- var reselect = true;
- for each(var reselectKey in e.reselectKeys) {
- if(this._deleteKeys[reselectKey]) {
- this._removeCodeFields.push(i);
- return true;
- }
- }
-
- // Ask user what to do with this item
- if(e.citationLength == 1) {
- var msg = Zotero.getString("integration.missingItem.single");
- } else {
- var msg = Zotero.getString("integration.missingItem.multiple", (e.citationIndex+1).toString());
- }
- msg += '\n\n'+Zotero.getString('integration.missingItem.description');
- field.select();
- this._doc.activate();
- var result = this._doc.displayAlert(msg, 1, 3);
- if(result == 0) { // Cancel
- throw new Zotero.Exception.UserCancelled("document update");
- } else if(result == 1) { // No
- for each(var reselectKey in e.reselectKeys) {
- this._deleteKeys[reselectKey] = true;
- }
- this._removeCodeFields.push(i);
- return true;
- } else { // Yes
- // Display reselect item dialog
- var me = this;
- var oldCurrentWindow = Zotero.Integration.currentWindow;
- this._session.reselectItem(this._doc, e, function() {
- // Now try again
- Zotero.Integration.currentWindow = oldCurrentWindow;
- me._doc.activate();
- me._processFields(me._fields, callback, errorCallback, i);
- });
- return false;
- }
+ return deferred.promise;
}
/**
* Updates Zotero.Integration.Session attached to Zotero.Integration.Fields in line with document
*/
-Zotero.Integration.Fields.prototype.updateSession = function(callback, errorCallback) {
- var me = this;
- this.get(function(fields) {
+Zotero.Integration.Fields.prototype.updateSession = function() {
+ var me = this, collectFieldsTime;
+ return this.get().then(function() {
me._session.resetRequest(me._doc);
- me._deleteKeys = {};
- me._deleteFields = [];
- me._removeCodeFields = [];
+ me._removeCodeKeys = {};
+ me._removeCodeFields = {};
me._bibliographyFields = [];
me._bibliographyData = "";
- var collectFieldsTime = (new Date()).getTime();
- me._processFields(fields, function() {
- var endTime = (new Date()).getTime();
- if(Zotero.Debug.enabled) {
- Zotero.debug("Integration: Updated session data for "+fields.length+" fields in "+
- (endTime-collectFieldsTime)/1000+"; "+
- 1000/((endTime-collectFieldsTime)/fields.length)+" fields/second");
- }
-
- // load uncited items from bibliography
- if(me._bibliographyData && !me._session.bibliographyData) {
- try {
- me._session.loadBibliographyData(me._bibliographyData);
- } catch(e) {
- var defaultHandler = function() {
- if(e instanceof Zotero.Integration.CorruptFieldException) {
- var msg = Zotero.getString("integration.corruptBibliography")+'\n\n'+
- Zotero.getString('integration.corruptBibliography.description');
- var result = me._doc.displayAlert(msg,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
- if(result == 0) {
- throw e;
- } else {
- me._bibliographyData = "";
- me._session.bibliographyHasChanged = true;
- me._session.bibliographyDataHasChanged = true;
- }
- } else {
- throw e;
- }
- };
- if(errorCallback) {
- if(!errorCallback(e, defaultHandler)) return;
- } else if(!defaultHandler()) {
- return;
- }
- }
- }
-
- // if we are reloading this session, assume no item IDs to be updated except for edited items
- if(me._session.reload) {
- //this._session.restoreProcessorState(); TODO doesn't appear to be working properly
- me._session.updateUpdateIndices();
- Zotero.pumpGenerator(me._session.updateCitations(function(deleteCitations) {
- me._deleteFields = me._deleteFields.concat([i for(i in deleteCitations)]);
- me._session.updateIndices = {};
- me._session.updateItemIDs = {};
- me._session.citationText = {};
- me._session.bibliographyHasChanged = false;
- delete me._session.reload;
- if(callback) callback(me._session);
- }));
- } else {
- if(callback) callback(me._session);
- }
- }, errorCallback);
+ collectFieldsTime = (new Date()).getTime();
+ return me._processFields();
+ }).then(function() {
+ var endTime = (new Date()).getTime();
+ if(Zotero.Debug.enabled) {
+ Zotero.debug("Integration: Updated session data for "+me._fields.length+" fields in "+
+ (endTime-collectFieldsTime)/1000+"; "+
+ 1000/((endTime-collectFieldsTime)/me._fields.length)+" fields/second");
+ }
+
+ // Load uncited items from bibliography
+ if(me._bibliographyData && !me._session.bibliographyData) {
+ try {
+ me._session.loadBibliographyData(me._bibliographyData);
+ } catch(e) {
+ var exception = new Zotero.Integration.CorruptBibliographyException(me, e);
+ exception.setContext(me);
+ throw exception;
+ }
+ }
+
+ // if we are reloading this session, assume no item IDs to be updated except for
+ // edited items
+ if(me._session.reload) {
+ //this._session.restoreProcessorState(); TODO doesn't appear to be working properly
+ me._session.updateUpdateIndices();
+ return Zotero.promiseGenerator(me._session._updateCitations())
+ .then(function() {
+ me._session.updateIndices = {};
+ me._session.updateItemIDs = {};
+ me._session.citationText = {};
+ me._session.bibliographyHasChanged = false;
+ delete me._session.reload;
+ });
+ } else {
+ return;
+ }
});
}
/**
* Keep processing fields until all have been processed
*/
-Zotero.Integration.Fields.prototype._processFields = function(fields, callback, errorCallback, i) {
+Zotero.Integration.Fields.prototype._processFields = function(i) {
if(!i) i = 0;
var me = this;
- for(var n = fields.length; i<n; i++) {
- var field = fields[i];
+ for(var n = this._fields.length; i<n; i++) {
+ var field = this._fields[i];
try {
var fieldCode = field.getCode();
} catch(e) {
- var defaultHandler = function() {
- return me._showCorruptFieldError(e, field, callback, errorCallback, i);
- };
-
- if(errorCallback) {
- if(errorCallback(e, defaultHandler)) {
- continue;
- } else {
- return;
- }
- } else if(!defaultHandler()) {
- return;
- }
+ var corruptFieldException = new Zotero.Integration.CorruptFieldException(
+ "Field code not retrievable", e);
+ corruptFieldException.setContext(this, i);
+ throw corruptFieldException;
}
var [type, content] = this.getCodeTypeAndContent(fieldCode);
@@ -1500,24 +1470,23 @@ Zotero.Integration.Fields.prototype._processFields = function(fields, callback,
try {
this._session.addCitation(i, noteIndex, content);
} catch(e) {
- var defaultHandler = function() {
- if(e instanceof Zotero.Integration.MissingItemException) {
- return me._showMissingItemError(e, field, callback, errorCallback, i);
- } else if(e instanceof Zotero.Integration.CorruptFieldException) {
- return me._showCorruptFieldError(e, field, callback, errorCallback, i);
- } else {
- throw e;
- }
- };
+ var removeCode = false;
- if(errorCallback) {
- if(errorCallback(e, defaultHandler)) {
- continue;
- } else {
- return;
+ if(e instanceof Zotero.Integration.CorruptFieldException) {
+ e.setContext(this, i)
+ } else if(e instanceof Zotero.Integration.MissingItemException) {
+ // Check if we've already decided to remove this field code
+ for each(var reselectKey in e.reselectKeys) {
+ if(this._removeCodeKeys[reselectKey]) {
+ this._removeCodeFields[i] = true;
+ removeCode = true;
+ }
}
- } if(!defaultHandler()) {
- return;
+ if(!removeCode) e.setContext(this, i);
+ }
+
+ if(!removeCode) {
+ throw e;
}
}
} else if(type === INTEGRATION_TYPE_BIBLIOGRAPHY) {
@@ -1527,29 +1496,24 @@ Zotero.Integration.Fields.prototype._processFields = function(fields, callback,
}
}
}
-
- if(callback) callback();
}
/**
* Updates bibliographies and fields within a document
* @param {Boolean} forceCitations Whether to regenerate all citations
* @param {Boolean} forceBibliography Whether to regenerate all bibliography entries
* @param {Boolean} [ignoreCitationChanges] Whether to ignore changes to citations that have been
- * modified since they were created, instead of showing a warning
+ * modified since they were created, instead of showing a warning
+ * @return {Promise} A promise resolved when the document is updated
*/
Zotero.Integration.Fields.prototype.updateDocument = function(forceCitations, forceBibliography,
- ignoreCitationChanges, callback) {
- // update citations
- try {
- this._session.updateUpdateIndices(forceCitations);
- var me = this;
- var deleteCitations = Zotero.pumpGenerator(this._session.updateCitations(function(deleteCitations) {
- Zotero.pumpGenerator(me._updateDocument(forceCitations, forceBibliography,
- ignoreCitationChanges, deleteCitations, callback));
- }));
- } catch(e) {
- Zotero.Integration.handleError(e, this._doc);
- }
+ ignoreCitationChanges) {
+ // Update citations
+ this._session.updateUpdateIndices(forceCitations);
+ var me = this;
+ return Zotero.promiseGenerator(this._session._updateCitations()).then(function() {
+ return Zotero.promiseGenerator(me._updateDocument(forceCitations, forceBibliography,
+ ignoreCitationChanges));
+ });
}
/**
@@ -1560,161 +1524,160 @@ Zotero.Integration.Fields.prototype.updateDocument = function(forceCitations, fo
* modified since they were created, instead of showing a warning
*/
Zotero.Integration.Fields.prototype._updateDocument = function(forceCitations, forceBibliography,
- ignoreCitationChanges, deleteCitations, callback) {
- try {
- // update citations
- this._deleteFields = this._deleteFields.concat([i for(i in deleteCitations)]);
-
- if(this.progressCallback) {
- var nFieldUpdates = [i for(i in this._session.updateIndices)].length;
- if(this._session.bibliographyHasChanged || forceBibliography) {
- nFieldUpdates += this._bibliographyFields.length*5;
- }
+ ignoreCitationChanges) {
+ if(this.progressCallback) {
+ var nFieldUpdates = [i for(i in this._session.updateIndices)].length;
+ if(this._session.bibliographyHasChanged || forceBibliography) {
+ nFieldUpdates += this._bibliographyFields.length*5;
}
-
- var nUpdated=0;
- for(var i in this._session.updateIndices) {
- if(this.progressCallback && nUpdated % 10 == 0) {
+ }
+
+ var nUpdated=0;
+ for(var i in this._session.updateIndices) {
+ if(this.progressCallback && nUpdated % 10 == 0) {
+ try {
this.progressCallback(75+(nUpdated/nFieldUpdates)*25);
- yield true;
+ } catch(e) {
+ Zotero.logError(e);
}
+ yield;
+ }
+
+ var citation = this._session.citationsByIndex[i];
+ var field = this._fields[i];
+
+ // If there is no citation, we're deleting it, or we shouldn't update it, ignore
+ // it
+ if(!citation || citation.properties.delete) continue;
+ var isRich = false;
+
+ if(!citation.properties.dontUpdate) {
+ var formattedCitation = citation.properties.custom
+ ? citation.properties.custom : this._session.citationText[i];
- var citation = this._session.citationsByIndex[i];
- var field = this._fields[i];
-
- // If there is no citation, we're deleting it, or we shouldn't update it, ignore it
- if(!citation || deleteCitations[i]) continue;
- var isRich = false;
+ if(formattedCitation.indexOf("\\") !== -1) {
+ // need to set text as RTF
+ formattedCitation = "{\\rtf "+formattedCitation+"}"
+ isRich = true;
+ }
- if(!citation.properties.dontUpdate) {
- var formattedCitation = citation.properties.custom
- ? citation.properties.custom : this._session.citationText[i];
-
- if(formattedCitation.indexOf("\\") !== -1) {
- // need to set text as RTF
- formattedCitation = "{\\rtf "+formattedCitation+"}"
- isRich = true;
- }
-
- if(forceCitations === FORCE_CITATIONS_RESET_TEXT
- || citation.properties.formattedCitation !== formattedCitation) {
- // Check if citation has been manually modified
- if(!ignoreCitationChanges && citation.properties.plainCitation) {
- var plainCitation = field.getText();
- if(plainCitation !== citation.properties.plainCitation) {
- // Citation manually modified; ask user if they want to save changes
- field.select();
- var result = this._doc.displayAlert(
- Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"),
- Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
- Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO);
- if(result) {
- citation.properties.dontUpdate = true;
- }
+ if(forceCitations === FORCE_CITATIONS_RESET_TEXT
+ || citation.properties.formattedCitation !== formattedCitation) {
+ // Check if citation has been manually modified
+ if(!ignoreCitationChanges && citation.properties.plainCitation) {
+ var plainCitation = field.getText();
+ if(plainCitation !== citation.properties.plainCitation) {
+ // Citation manually modified; ask user if they want to save changes
+ field.select();
+ var result = this._doc.displayAlert(
+ Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"),
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
+ Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO);
+ if(result) {
+ citation.properties.dontUpdate = true;
}
}
-
- if(!citation.properties.dontUpdate) {
- field.setText(formattedCitation, isRich);
-
- citation.properties.formattedCitation = formattedCitation;
- citation.properties.plainCitation = field.getText();
- }
}
- }
-
- var fieldCode = this._session.getCitationField(citation);
- if(fieldCode != citation.properties.field) {
- field.setCode(
- (this._session.data.prefs.storeReferences ? "ITEM CSL_CITATION" : "ITEM")
- +" "+fieldCode);
- if(this._session.data.prefs.fieldType === "ReferenceMark" && isRich
- && !citation.properties.dontUpdate) {
- // For ReferenceMarks with formatting, we need to set the text again, because
- // setting the field code removes formatting from the mark. I don't like this.
+ if(!citation.properties.dontUpdate) {
field.setText(formattedCitation, isRich);
+
+ citation.properties.formattedCitation = formattedCitation;
+ citation.properties.plainCitation = field.getText();
}
}
- nUpdated++;
}
- // update bibliographies
- if(this._bibliographyFields.length // if bibliography exists
- && (this._session.bibliographyHasChanged // and bibliography changed
- || forceBibliography)) { // or if we should generate regardless of
- // changes
- var bibliographyFields = this._bibliographyFields;
+ var fieldCode = this._session.getCitationField(citation);
+ if(fieldCode != citation.properties.field) {
+ field.setCode(
+ (this._session.data.prefs.storeReferences ? "ITEM CSL_CITATION" : "ITEM")
+ +" "+fieldCode);
- if(forceBibliography || this._session.bibliographyDataHasChanged) {
- var bibliographyData = this._session.getBibliographyData();
- for each(var field in bibliographyFields) {
- field.setCode("BIBL "+bibliographyData
- +(this._session.data.prefs.storeReferences ? " CSL_BIBLIOGRAPHY" : ""));
- }
+ if(this._session.data.prefs.fieldType === "ReferenceMark" && isRich
+ && !citation.properties.dontUpdate) {
+ // For ReferenceMarks with formatting, we need to set the text again, because
+ // setting the field code removes formatting from the mark. I don't like this.
+ field.setText(formattedCitation, isRich);
}
-
- // get bibliography and format as RTF
- var bib = this._session.getBibliography();
-
- var bibliographyText = "";
- if(bib) {
- bibliographyText = bib[0].bibstart+bib[1].join("\\\r\n")+"\\\r\n"+bib[0].bibend;
-
- // if bibliography style not set, set it
- if(!this._session.data.style.bibliographyStyleHasBeenSet) {
- var bibStyle = Zotero.Cite.getBibliographyFormatParameters(bib);
-
- // set bibliography style
- this._doc.setBibliographyStyle(bibStyle.firstLineIndent, bibStyle.indent,
- bibStyle.lineSpacing, bibStyle.entrySpacing, bibStyle.tabStops, bibStyle.tabStops.length);
-
- // set bibliographyStyleHasBeenSet parameter to prevent further changes
- this._session.data.style.bibliographyStyleHasBeenSet = true;
- this._doc.setDocumentData(this._session.data.serializeXML());
- }
+ }
+ nUpdated++;
+ }
+
+ // update bibliographies
+ if(this._bibliographyFields.length // if bibliography exists
+ && (this._session.bibliographyHasChanged // and bibliography changed
+ || forceBibliography)) { // or if we should generate regardless of
+ // changes
+ var bibliographyFields = this._bibliographyFields;
+
+ if(forceBibliography || this._session.bibliographyDataHasChanged) {
+ var bibliographyData = this._session.getBibliographyData();
+ for each(var field in bibliographyFields) {
+ field.setCode("BIBL "+bibliographyData
+ +(this._session.data.prefs.storeReferences ? " CSL_BIBLIOGRAPHY" : ""));
}
+ }
+
+ // get bibliography and format as RTF
+ var bib = this._session.getBibliography();
+
+ var bibliographyText = "";
+ if(bib) {
+ bibliographyText = bib[0].bibstart+bib[1].join("\\\r\n")+"\\\r\n"+bib[0].bibend;
- // set bibliography text
- for each(var field in bibliographyFields) {
- if(this.progressCallback) {
- this.progressCallback(75+(nUpdated/nFieldUpdates)*25);
- yield true;
- }
+ // if bibliography style not set, set it
+ if(!this._session.data.style.bibliographyStyleHasBeenSet) {
+ var bibStyle = Zotero.Cite.getBibliographyFormatParameters(bib);
- if(bibliographyText) {
- field.setText(bibliographyText, true);
- } else {
- field.setText("{Bibliography}", false);
- }
- nUpdated += 5;
+ // set bibliography style
+ this._doc.setBibliographyStyle(bibStyle.firstLineIndent, bibStyle.indent,
+ bibStyle.lineSpacing, bibStyle.entrySpacing, bibStyle.tabStops, bibStyle.tabStops.length);
+
+ // set bibliographyStyleHasBeenSet parameter to prevent further changes
+ this._session.data.style.bibliographyStyleHasBeenSet = true;
+ this._doc.setDocumentData(this._session.data.serializeXML());
}
}
- // do this operations in reverse in case plug-ins care about order
- var sortClosure = function(a, b) { return a-b; };
- this._deleteFields.sort(sortClosure);
- for(var i=(this._deleteFields.length-1); i>=0; i--) {
- this._fields[this._deleteFields[i]].delete();
- }
- this._removeCodeFields.sort(sortClosure);
- for(var i=(this._removeCodeFields.length-1); i>=0; i--) {
- this._fields[this._removeCodeFields[i]].removeCode();
+ // set bibliography text
+ for each(var field in bibliographyFields) {
+ if(this.progressCallback) {
+ try {
+ this.progressCallback(75+(nUpdated/nFieldUpdates)*25);
+ } catch(e) {
+ Zotero.logError(e);
+ }
+ yield;
+ }
+
+ if(bibliographyText) {
+ field.setText(bibliographyText, true);
+ } else {
+ field.setText("{Bibliography}", false);
+ }
+ nUpdated += 5;
}
-
- if(callback) {
- callback();
+ }
+
+ // Do these operations in reverse in case plug-ins care about order
+ for(var i=this._session.citationsByIndex.length-1; i>=0; i--) {
+ if(this._session.citationsByIndex[i] &&
+ this._session.citationsByIndex[i].properties.delete) {
+ this._fields[i].delete();
}
- } catch(e) {
- Zotero.Integration.handleError(e, this._doc);
+ }
+ var removeCodeFields = Object.keys(this._removeCodeFields).sort();
+ for(var i=(this._removeCodeFields.length-1); i>=0; i--) {
+ this._fields[this._removeCodeFields[i]].removeCode();
}
}
/**
* Brings up the addCitationDialog, prepopulated if a citation is provided
*/
-Zotero.Integration.Fields.prototype.addEditCitation = function(field, callback) {
- var newField, citation, fieldIndex, session = this._session, me = this, loadFirst;
+Zotero.Integration.Fields.prototype.addEditCitation = function(field) {
+ var newField, citation, fieldIndex, session = this._session;
// if there's already a citation, make sure we have item IDs in addition to keys
if(field) {
@@ -1723,7 +1686,7 @@ Zotero.Integration.Fields.prototype.addEditCitation = function(field, callback)
} catch(e) {}
if(code) {
- [type, content] = this.getCodeTypeAndContent(code);
+ var [type, content] = this.getCodeTypeAndContent(code);
if(type != INTEGRATION_TYPE_ITEM) {
throw new Zotero.Integration.DisplayException("notInCitation");
}
@@ -1770,33 +1733,44 @@ Zotero.Integration.Fields.prototype.addEditCitation = function(field, callback)
citation = {"citationItems":[], "properties":{}};
}
- var io = new Zotero.Integration.CitationEditInterface(citation, field, this, session, newField, callback);
+ var io = new Zotero.Integration.CitationEditInterface(citation, field, this, session);
if(Zotero.Prefs.get("integration.useClassicAddCitationDialog")) {
Zotero.Integration.displayDialog(this._doc,
- 'chrome://zotero/content/integration/addCitationDialog.xul', 'alwaysRaised,resizable',
- io, true);
+ 'chrome://zotero/content/integration/addCitationDialog.xul', 'alwaysRaised,resizable',
+ io);
} else {
var mode = (!Zotero.isMac && Zotero.Prefs.get('integration.keepAddCitationDialogRaised')
? 'popup' : 'alwaysRaised')
Zotero.Integration.displayDialog(this._doc,
- 'chrome://zotero/content/integration/quickFormat.xul', mode, io, true);
+ 'chrome://zotero/content/integration/quickFormat.xul', mode, io);
+ }
+
+ if(newField) {
+ var me = this;
+ return io.promise.fail(function(e) {
+ // Try to delete new field on failure
+ try {
+ field.delete();
+ } catch(e) {}
+ throw e;
+ });
+ } else {
+ return io.promise;
}
}
/**
* Citation editing functions and propertiesaccessible to quickFormat.js and addCitationDialog.js
*/
-Zotero.Integration.CitationEditInterface = function(citation, field, fields, session, deleteOnCancel, doneCallback) {
+Zotero.Integration.CitationEditInterface = function(citation, field, fieldGetter, session) {
this.citation = citation;
this._field = field;
- this._fields = fields;
+ this._fieldGetter = fieldGetter;
this._session = session;
- this._deleteOnCancel = deleteOnCancel;
- this._doneCallback = doneCallback;
- this._sessionUpdated = false;
- this._sessionCallbackQueue = false;
+ this._sessionUpdateResolveErrors = false;
+ this._sessionUpdateDeferreds = [];
// Needed to make this work across boundaries
this.wrappedJSObject = this;
@@ -1808,73 +1782,119 @@ Zotero.Integration.CitationEditInterface = function(citation, field, fields, ses
this.style = session.style;
// Start getting citation data
- var me = this;
- fields.get(function(fields) {
+ this._acceptDeferred = Q.defer();
+ this._fieldIndexPromise = fieldGetter.get().then(function(fields) {
for(var i=0, n=fields.length; i<n; i++) {
if(fields[i].equals(field)) {
- me._fieldIndex = i;
- return;
+ return i;
+ }
+ }
+ });
+
+ var me = this;
+ this.promise = this._fieldIndexPromise.then(function(fieldIndex) {
+ me._fieldIndex = fieldIndex;
+ return me._acceptDeferred.promise;
+ }).then(function(progressCallback) {
+ me._fieldGetter.progressCallback = progressCallback;
+ return me._updateSession(true);
+ }).then(function() {
+ // Add new citation
+ me._session.addCitation(me._fieldIndex, me._field.getNoteIndex(), me.citation);
+ me._session.updateIndices[me._fieldIndex] = true;
+
+ // Check if bibliography changed
+ if(!me._session.bibliographyHasChanged) {
+ var citationItems = me.citation.citationItems;
+ for(var i=0, n=citationItems.length; i<n; i++) {
+ if(me._session.citationsByItemID[citationItems[i].itemID] &&
+ me._session.citationsByItemID[citationItems[i].itemID].length == 1) {
+ me._session.bibliographyHasChanged = true;
+ break;
+ }
}
}
+
+ // Update document
+ return me._fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, false, false);
});
}
Zotero.Integration.CitationEditInterface.prototype = {
/**
- * Handles an error in updateSession
+ * Run a function when the session information has been updated
+ * @param {Boolean} [resolveErrors] Whether to attempt to resolve errors that occur
+ * while session information is being updated, e.g. by showing a dialog to the
+ * user.
+ * @return {Promise} A promise resolved when session information has been updated
*/
- "_errorHandler":function(e, defaultHandler) {
- Zotero.debug('Integration.CitationEditInterface: Error "'+e.toString()+'" caught by handler');
- if(this._haveAccepted) {
- try {
- return defaultHandler();
- } catch(e) {
- if(e instanceof Zotero.Exception.UserCancelled) {
- this._field.delete();
+ "_updateSession":function _updateSession(resolveErrors) {
+ var me = this;
+ if(this._sessionUpdatePromise && this._sessionUpdatePromise.isResolved()) {
+ // Session has already been updated. If we were deferring resolving an error,
+ // and we are supposed to resolve it now, then do that
+ if(this._sessionUpdateError) {
+ if(resolveErrors && this._sessionUpdateError.attemptToResolve) {
+ return this._sessionUpdateError.attemptToResolve().then(function() {
+ delete me._sessionUpdateError;
+ });
+ } else {
+ return Q.reject(this._sessionUpdateError);
}
- throw e;
+ } else {
+ return Q.resolve(true);
}
} else {
- this._errorOccurred = true;
- return true;
- }
- },
-
- /**
- * Run a function when the session information has been updated
- * @param {Function} sessionUpdatedCallback
- */
- "_runWhenSessionUpdated":function runWhenSessionUpdated(sessionUpdatedCallback) {
- if(this._sessionUpdated) {
- // session has been updated; run callback
- sessionUpdatedCallback();
- } else if(this._sessionCallbackQueue) {
- // session is being updated; add to queue
- this._sessionCallbackQueue.push(sessionUpdatedCallback);
- } else {
- // session is not yet updated; start update
- this._sessionCallbackQueue = [sessionUpdatedCallback];
- var me = this;
- me._fields.updateSession(function() {
- for(var i=0, n=me._sessionCallbackQueue.length; i<n; i++) {
- me._sessionCallbackQueue[i]();
- }
- me._sessionUpdated = true;
- delete me._sessionCallbackQueue;
- }, function(e, defaultHandler) { return me._errorHandler(e, defaultHandler) });
+ var deferred = Q.defer();
+
+ this._sessionUpdateResolveErrors = this._sessionUpdateResolveErrors || resolveErrors;
+ this._sessionUpdateDeferreds.push(deferred);
+
+ if(!this._sessionUpdatePromise) {
+ // Add deferred to queue
+
+ var me = this;
+ this._sessionUpdatePromise = this._fieldGetter.updateSession().fail(function(err) {
+ // If an error occurred, either try to resolve it or reject it
+ // depending on whether anyone has called _updateSession with
+ // resolveErrors set to true. This is necessary to prevent field code
+ // errors from appearing while the user interacts with the QuickFormat
+ // dialog, since some people find this very confusing.
+ if(me._sessionUpdateResolveErrors && err.attemptToResolve) {
+ return err.attemptToResolve();
+ } else {
+ throw err;
+ }
+ }).then(function() {
+ // If no errors occurred, or errors were resolved, resolve promises
+ for(var i=0; i<me._sessionUpdateDeferreds.length; i++) {
+ me._sessionUpdateDeferreds[i].resolve(true);
+ }
+ }, function(err) {
+ // Error propagates if attemptToResolve failed or wasn't called to
+ // begin with
+ me._sessionUpdateError = err;
+ for(var i=0; i<me._sessionUpdateDeferreds.length; i++) {
+ me._sessionUpdateDeferreds[i].reject(err);
+ }
+ throw err;
+ }).end();
+ }
+
+ return deferred.promise;
}
},
/**
* Execute a callback with a preview of the given citation
- * @param {Function} previewCallback
+ * @return {Promise} A promise resolved with the previewed citation string
*/
- "preview":function preview(previewCallback) {
+ "preview":function preview() {
var me = this;
- this._runWhenSessionUpdated(function() {
+ return this._updateSession().then(function() {
me.citation.properties.zoteroIndex = me._fieldIndex;
me.citation.properties.noteIndex = me._field.getNoteIndex();
- previewCallback(me._session.previewCitation(me.citation));
+ return me._session.previewCitation(me.citation);
});
},
@@ -1882,8 +1902,8 @@ Zotero.Integration.CitationEditInterface.prototype = {
* Sort the citation
*/
"sort":function() {
- // Unlike above, we can do the previewing here without waiting for all the fields to load,
- // since they won't change the sorting (I don't think)
+ // Unlike above, we can do the previewing here without waiting for all the fields
+ // to load, since they won't change the sorting (I don't think)
this._session.previewCitation(this.citation);
},
@@ -1891,70 +1911,32 @@ Zotero.Integration.CitationEditInterface.prototype = {
* Accept changes to the citation
* @param {Function} [progressCallback] A callback to be run when progress has changed.
* Receives a number from 0 to 100 indicating current status.
- * @param {Boolean} [force] Whether to run accept even if it has been run previously.
*/
- "accept":function(progressCallback, force) {
- var me = this;
-
- // Don't allow accept to be called multiple times
- if(!force && this._haveAccepted) return;
- this._haveAccepted = true;
-
- this._fields.progressCallback = progressCallback;
-
- if(this._errorOccurred) {
- // If an error occurred updating the session, update it again, this time letting the
- // error get displayed
- Zotero.setTimeout(function() {
- me._fields.updateSession(function() {
- me._errorOccurred = false;
- me._sessionUpdated = true;
- me.accept(progressCallback, true);
- }, function(e, defaultHandler) { return me._errorHandler(e, defaultHandler) });
- }, 0);
- return;
- }
-
- if(this.citation.citationItems.length) {
- // Citation
- this._runWhenSessionUpdated(function() {
- me._session.addCitation(me._fieldIndex, me._field.getNoteIndex(), me.citation);
- me._session.updateIndices[me._fieldIndex] = true;
-
- if(!me._session.bibliographyHasChanged) {
- var citationItems = me.citation.citationItems;
- for(var i=0, n=citationItems.length; i<n; i++) {
- if(me._session.citationsByItemID[citationItems[i].itemID] &&
- me._session.citationsByItemID[citationItems[i].itemID].length == 1) {
- me._session.bibliographyHasChanged = true;
- break;
- }
- }
- }
-
- me._fields.updateDocument(FORCE_CITATIONS_FALSE, false, false, me._doneCallback);
- });
- } else {
- if(this._deleteOnCancel) this._field.delete();
- if(this._doneCallback) this._doneCallback();
+ "accept":function(progressCallback) {
+ if(!this._acceptDeferred.promise.isResolved()) {
+ this._acceptDeferred.resolve(progressCallback);
}
},
/**
* Get a list of items used in the current document
- * @param {Function} [itemsCallback] A callback to be run with item objects when items have been
- * retrieved.
+ * @return {Promise} A promise resolved by the items
*/
- "getItems":function(itemsCallback) {
- if(this._fieldIndex || Zotero.Utilities.isEmpty(this._session.citationsByItemID)) {
+ "getItems":function() {
+ if(this._fieldIndexPromise.isFulfilled()
+ || Zotero.Utilities.isEmpty(this._session.citationsByItemID)) {
// Either we already have field data for this run or we have no item data at all.
// Update session before continuing.
var me = this;
- this._runWhenSessionUpdated(function() { me._getItems(itemsCallback); });
+ return this._updateSession().then(function() {
+ return me._getItems();
+ }, function() {
+ return [];
+ });
} else {
// We have item data left over from a previous run with this document, so we don't need
// to wait.
- this._getItems(itemsCallback);
+ return Q.resolve(this._getItems());
}
},
@@ -1962,11 +1944,11 @@ Zotero.Integration.CitationEditInterface.prototype = {
* Helper function for getItems. Does the same thing, but this can assume that the session data
* has already been updated if it should be.
*/
- "_getItems":function(itemsCallback) {
+ "_getItems":function() {
var citationsByItemID = this._session.citationsByItemID;
var ids = [itemID for(itemID in citationsByItemID)
if(citationsByItemID[itemID] && citationsByItemID[itemID].length
- // Exclude this item
+ // Exclude the present item
&& (citationsByItemID[itemID].length > 1
|| citationsByItemID[itemID][0].properties.zoteroIndex !== this._fieldIndex))];
@@ -1985,7 +1967,7 @@ Zotero.Integration.CitationEditInterface.prototype = {
return indexB - indexA;
});
- itemsCallback(Zotero.Cite.getItem(ids));
+ return Zotero.Cite.getItem(ids);
}
}
@@ -2059,9 +2041,11 @@ Zotero.Integration.Session.prototype.setData = function(data) {
/**
* Displays a dialog to set document preferences
- * @return {oldData|null|false} Old document data, if there was any; null, if there wasn't; false if cancelled
+ * @return {Promise} A promise resolved with old document data, if there was any or null,
+ * if there wasn't, or rejected with Zotero.Exception.UserCancelled if the dialog was
+ * cancelled.
*/
-Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldType, secondaryFieldType, callback) {
+Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldType, secondaryFieldType) {
var io = new function() {
this.wrappedJSObject = this;
};
@@ -2077,11 +2061,11 @@ Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldTyp
}
var me = this;
- Zotero.Integration.displayDialog(doc,
- 'chrome://zotero/content/integration/integrationDocPrefs.xul', '', io, function() {
+ return Zotero.Integration.displayDialog(doc,
+ 'chrome://zotero/content/integration/integrationDocPrefs.xul', '', io)
+ .then(function() {
if(!io.style) {
- callback(false);
- return;
+ throw Zotero.Exception.UserCancelled("document preferences window");
}
// set data
@@ -2102,7 +2086,7 @@ Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldTyp
me.oldCitationIDs = {};
}
- callback(oldData ? oldData : null);
+ return oldData || null;
});
}
@@ -2110,16 +2094,14 @@ Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldTyp
* Reselects an item to replace a deleted item
* @param exception {Zotero.Integration.MissingItemException}
*/
-Zotero.Integration.Session.prototype.reselectItem = function(doc, exception, callback) {
- var io = new function() {
- this.wrappedJSObject = this;
- },
+Zotero.Integration.Session.prototype.reselectItem = function(doc, exception) {
+ var io = new function() { this.wrappedJSObject = this; },
me = this;
io.addBorder = Zotero.isWin;
io.singleSelection = true;
- Zotero.Integration.displayDialog(doc, 'chrome://zotero/content/selectItemsDialog.xul',
- 'resizable', io, function() {
+ return Zotero.Integration.displayDialog(doc, 'chrome://zotero/content/selectItemsDialog.xul',
+ 'resizable', io).then(function() {
if(io.dataOut && io.dataOut.length) {
var itemID = io.dataOut[0];
@@ -2134,8 +2116,6 @@ Zotero.Integration.Session.prototype.reselectItem = function(doc, exception, cal
// flag for update
me.updateItemIDs[itemID] = true;
}
-
- callback();
});
}
@@ -2397,7 +2377,7 @@ Zotero.Integration.Session.prototype.unserializeCitation = function(arg, index)
try {
var citation = JSON.parse(arg.replace(/{{((?:\s*,?"unsorted":(?:true|false)|\s*,?"custom":"(?:(?:\\")?[^"]*\s*)*")*)}}/, "{$1}"));
} catch(e) {
- throw new Zotero.Integration.CorruptFieldException(arg);
+ throw new Zotero.Integration.CorruptFieldException(arg, e);
}
}
}
@@ -2492,7 +2472,7 @@ Zotero.Integration.Session.prototype.unserializeCitation = function(arg, index)
}
/**
- * marks a citation for removal
+ * Marks a citation for removal
*/
Zotero.Integration.Session.prototype.deleteCitation = function(index) {
var oldCitation = (this.citationsByIndex[index] ? this.citationsByIndex[index] : false);
@@ -2618,68 +2598,58 @@ Zotero.Integration.Session.prototype.formatCitation = function(index, citation)
/**
* Updates the list of citations to be serialized to the document
*/
-Zotero.Integration.Session.prototype.updateCitations = function(callback) {
- try {
- /*var allUpdatesForced = false;
- var forcedUpdates = {};
- if(force) {
- allUpdatesForced = true;
- // make sure at least one citation gets updated
- updateLoop: for each(var indexList in [this.newIndices, this.updateIndices]) {
- for(var i in indexList) {
- if(!this.citationsByIndex[i].properties.delete) {
- allUpdatesForced = false;
- break updateLoop;
- }
+Zotero.Integration.Session.prototype._updateCitations = function() {
+ /*var allUpdatesForced = false;
+ var forcedUpdates = {};
+ if(force) {
+ allUpdatesForced = true;
+ // make sure at least one citation gets updated
+ updateLoop: for each(var indexList in [this.newIndices, this.updateIndices]) {
+ for(var i in indexList) {
+ if(!this.citationsByIndex[i].properties.delete) {
+ allUpdatesForced = false;
+ break updateLoop;
}
}
-
- if(allUpdatesForced) {
- for(i in this.citationsByIndex) {
- if(this.citationsByIndex[i] && !this.citationsByIndex[i].properties.delete) {
- forcedUpdates[i] = true;
- break;
- }
- }
- }
- }*/
-
- if(Zotero.Debug.enabled) {
- Zotero.debug("Integration: Indices of new citations");
- Zotero.debug([key for(key in this.newIndices)]);
- Zotero.debug("Integration: Indices of updated citations");
- Zotero.debug([key for(key in this.updateIndices)]);
}
- var deleteCitations = {};
- for each(var indexList in [this.newIndices, this.updateIndices]) {
- for(var index in indexList) {
- index = parseInt(index);
-
- var citation = this.citationsByIndex[index];
- if(!citation) continue;
- if(citation.properties.delete) {
- deleteCitations[index] = true;
- continue;
- }
- if(this.formatCitation(index, citation)) {
- this.bibliographyHasChanged = true;
+ if(allUpdatesForced) {
+ for(i in this.citationsByIndex) {
+ if(this.citationsByIndex[i] && !this.citationsByIndex[i].properties.delete) {
+ forcedUpdates[i] = true;
+ break;
}
- this.citeprocCitationIDs[citation.citationID] = true;
- delete this.newIndices[index];
- yield true;
}
}
-
- /*if(allUpdatesForced) {
- this.newIndices = {};
- this.updateIndices = {};
- }*/
-
- callback(deleteCitations);
- } catch(e) {
- Zotero.Integration.handleError(e, this._doc);
+ }*/
+
+ if(Zotero.Debug.enabled) {
+ Zotero.debug("Integration: Indices of new citations");
+ Zotero.debug([key for(key in this.newIndices)]);
+ Zotero.debug("Integration: Indices of updated citations");
+ Zotero.debug([key for(key in this.updateIndices)]);
}
+
+
+ for each(var indexList in [this.newIndices, this.updateIndices]) {
+ for(var index in indexList) {
+ index = parseInt(index);
+
+ var citation = this.citationsByIndex[index];
+ if(!citation) continue;
+ if(this.formatCitation(index, citation)) {
+ this.bibliographyHasChanged = true;
+ }
+ this.citeprocCitationIDs[citation.citationID] = true;
+ delete this.newIndices[index];
+ yield;
+ }
+ }
+
+ /*if(allUpdatesForced) {
+ this.newIndices = {};
+ this.updateIndices = {};
+ }*/
}
/**
@@ -2838,15 +2808,14 @@ Zotero.Integration.Session.prototype.previewCitation = function(citation) {
/**
* Edits integration bibliography
*/
-Zotero.Integration.Session.prototype.editBibliography = function(doc, callback) {
+Zotero.Integration.Session.prototype.editBibliography = function(doc) {
var bibliographyEditor = new Zotero.Integration.Session.BibliographyEditInterface(this);
var io = new function() { this.wrappedJSObject = bibliographyEditor; }
this.bibliographyDataHasChanged = this.bibliographyHasChanged = true;
- Zotero.Integration.displayDialog(doc,
- 'chrome://zotero/content/integration/editBibliographyDialog.xul', 'resizable', io,
- callback);
+ return Zotero.Integration.displayDialog(doc,
+ 'chrome://zotero/content/integration/editBibliographyDialog.xul', 'resizable', io);
}
/**
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
@@ -38,6 +38,11 @@ const ZOTERO_CONFIG = {
VERSION: "3.0.8.SOURCE"
};
+// Commonly used imports accessible anywhere
+Components.utils.import("resource://zotero/q.js");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
/*
* Core functions
*/
@@ -1465,16 +1470,17 @@ const ZOTERO_CONFIG = {
* If errorHandler is specified, exceptions in the generator will be caught
* and passed to the callback
*/
- this.pumpGenerator = function(generator, ms, errorHandler) {
+ this.pumpGenerator = function(generator, ms, errorHandler, doneHandler) {
_waiting++;
var timer = Components.classes["@mozilla.org/timer;1"].
- createInstance(Components.interfaces.nsITimer);
+ createInstance(Components.interfaces.nsITimer),
+ yielded;
var timerCallback = {"notify":function() {
var err = false;
_waiting--;
try {
- if(generator.next()) {
+ if((yielded = generator.next()) !== false) {
_waiting++;
return;
}
@@ -1500,12 +1506,25 @@ const ZOTERO_CONFIG = {
} else {
throw err;
}
+ } else if(doneHandler) {
+ doneHandler(yielded);
}
}}
timer.initWithCallback(timerCallback, ms ? ms : 0, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
// add timer to global scope so that it doesn't get garbage collected before it completes
_runningTimers.push(timer);
- }
+ };
+
+ /**
+ * Pumps a generator until it yields false. Unlike the above, this returns a promise.
+ */
+ this.promiseGenerator = function(generator, ms) {
+ var deferred = Q.defer();
+ this.pumpGenerator(generator, ms,
+ function(e) { deferred.reject(e); },
+ function(data) { deferred.resolve(data) });
+ return deferred.promise;
+ };
/**
* Emulates the behavior of window.setTimeout, but ensures that callbacks do not get called