Merge pull request #1202 from adomasven/fix/doc-pref-window-unloaded-styles

Fix document preferences dialog failing when styles unloaded.
This commit is contained in:
Adomas Ven 2017-04-12 11:52:58 +03:00 committed by GitHub
commit 2a5dbaa204
8 changed files with 620 additions and 107 deletions

View File

@ -45,7 +45,7 @@ var Zotero_File_Interface_Bibliography = new function() {
* Initialize some variables and prepare event listeners for when chrome is done * Initialize some variables and prepare event listeners for when chrome is done
* loading * loading
*/ */
this.init = function () { this.init = Zotero.Promise.coroutine(function* () {
// Set font size from pref // Set font size from pref
// Affects bibliography.xul and integrationDocPrefs.xul // Affects bibliography.xul and integrationDocPrefs.xul
var bibContainer = document.getElementById("zotero-bibliography-container"); var bibContainer = document.getElementById("zotero-bibliography-container");
@ -67,21 +67,22 @@ var Zotero_File_Interface_Bibliography = new function() {
_io.style = Zotero.Prefs.get("export.lastStyle"); _io.style = Zotero.Prefs.get("export.lastStyle");
} }
// Initialize styles
yield Zotero.Styles.init();
// add styles to list // add styles to list
var styles = Zotero.Styles.getVisible(); var styles = Zotero.Styles.getVisible();
var index = 0;
var nStyles = styles.length;
var selectIndex = null; var selectIndex = null;
for(var i=0; i<nStyles; i++) { for (let i=0; i < styles.length; i++) {
var itemNode = document.createElement("listitem"); var itemNode = document.createElement("listitem");
itemNode.setAttribute("value", styles[i].styleID); itemNode.setAttribute("value", styles[i].styleID);
itemNode.setAttribute("label", styles[i].title); itemNode.setAttribute("label", styles[i].title);
listbox.appendChild(itemNode); listbox.appendChild(itemNode);
if(styles[i].styleID == _io.style) { if(styles[i].styleID == _io.style) {
selectIndex = index; selectIndex = i;
} }
index++;
} }
let requestedLocale; let requestedLocale;
@ -171,7 +172,7 @@ var Zotero_File_Interface_Bibliography = new function() {
// set style to false, in case this is cancelled // set style to false, in case this is cancelled
_io.style = false; _io.style = false;
}; });
/* /*
* Called when locale is changed * Called when locale is changed

View File

@ -335,8 +335,8 @@ Zotero.File = new function(){
/* /*
* Return a promise for the contents of a URL as a string * Return a promise for the contents of a URL as a string
*/ */
this.getContentsFromURLAsync = function (url) { this.getContentsFromURLAsync = function (url, options={}) {
return Zotero.HTTP.request("GET", url, { responseType: "text" }) return Zotero.HTTP.request("GET", url, Object.assign(options, { responseType: "text" }))
.then(function (xmlhttp) { .then(function (xmlhttp) {
return xmlhttp.response; return xmlhttp.response;
}); });

View File

@ -40,6 +40,19 @@ const FORCE_CITATIONS_RESET_TEXT = 2;
// this is used only for update checking // this is used only for update checking
const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org", const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
"zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"]; "zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
// These must match the constants defined in zoteroIntegration.idl
const DIALOG_ICON_STOP = 0;
const DIALOG_ICON_WARNING = 1;
const DIALOG_ICON_CAUTION = 2;
const DIALOG_BUTTONS_OK = 0;
const DIALOG_BUTTONS_OK_CANCEL = 1;
const DIALOG_BUTTONS_YES_NO = 2;
const DIALOG_BUTTONS_YES_NO_CANCEL = 3;
const NOTE_FOOTNOTE = 1;
const NOTE_ENDNOTE = 2;
Zotero.Integration = new function() { Zotero.Integration = new function() {
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
@ -210,6 +223,19 @@ Zotero.Integration = new function() {
}; };
} }
this.getApplication = function(agent, command, docId) {
// 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 : ""));
return Components.classes[componentClass]
.getService(Components.interfaces.zoteroIntegrationApplication);
} catch(e) {
throw new Zotero.Exception.Alert("integration.error.notInstalled",
[], "integration.error.title");
}
},
/** /**
* Executes an integration command, first checking to make sure that versions are compatible * Executes an integration command, first checking to make sure that versions are compatible
*/ */
@ -230,17 +256,8 @@ Zotero.Integration = new function() {
inProgress = true; inProgress = true;
// Check integration component versions // Check integration component versions
_checkPluginVersions().then(function() { return _checkPluginVersions().then(function() {
// Try to load the appropriate Zotero component; otherwise display an error var application = Zotero.Integration.getApplication(agent, command, docId);
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 // Try to execute the command; otherwise display an error in alert service or word processor
// (depending on what is possible) // (depending on what is possible)
@ -267,9 +284,7 @@ Zotero.Integration = new function() {
if(document) { if(document) {
try { try {
document.activate(); document.activate();
document.displayAlert(displayError, document.displayAlert(displayError, DIALOG_ICON_STOP, DIALOG_BUTTONS_OK);
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK);
} catch(e) { } catch(e) {
showErrorInFirefox = true; showErrorInFirefox = true;
} }
@ -821,10 +836,7 @@ Zotero.Integration.CorruptFieldException.prototype = {
field = this.fieldGetter._fields[this.fieldIndex]; field = this.fieldGetter._fields[this.fieldIndex];
field.select(); field.select();
this.fieldGetter._doc.activate(); this.fieldGetter._doc.activate();
var result = this.fieldGetter._doc.displayAlert(msg, var result = this.fieldGetter._doc.displayAlert(msg, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO_CANCEL);
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO_CANCEL);
if(result == 0) { if(result == 0) {
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document update")); return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document update"));
} else if(result == 1) { // No } else if(result == 1) { // No
@ -878,9 +890,7 @@ Zotero.Integration.CorruptBibliographyException.prototype = {
var msg = Zotero.getString("integration.corruptBibliography")+'\n\n'+ var msg = Zotero.getString("integration.corruptBibliography")+'\n\n'+
Zotero.getString('integration.corruptBibliography.description'); Zotero.getString('integration.corruptBibliography.description');
var result = this.fieldGetter._doc.displayAlert(msg, var result = this.fieldGetter._doc.displayAlert(msg, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_OK_CANCEL);
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(result == 0) { if(result == 0) {
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("clearing corrupted bibliography")); return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("clearing corrupted bibliography"));
} else { } else {
@ -928,7 +938,7 @@ Zotero.Integration.Document.prototype._createNewSession = function _createNewSes
* dontRunSetDocPrefs is true and no session was found, or rejected with * dontRunSetDocPrefs is true and no session was found, or rejected with
* Zotero.Exception.UserCancelled if the document preferences window was cancelled. * Zotero.Exception.UserCancelled if the document preferences window was cancelled.
*/ */
Zotero.Integration.Document.prototype._getSession = function _getSession(require, dontRunSetDocPrefs) { Zotero.Integration.Document.prototype._getSession = Zotero.Promise.coroutine(function *(require, dontRunSetDocPrefs) {
var dataString = this._doc.getDocumentData(), var dataString = this._doc.getDocumentData(),
data, data,
me = this; me = this;
@ -967,7 +977,7 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
// Set doc prefs if no data string yet // Set doc prefs if no data string yet
this._session = this._createNewSession(data); this._session = this._createNewSession(data);
this._session.setData(data); yield this._session.setData(data);
if(dontRunSetDocPrefs) return Zotero.Promise.resolve(false); if(dontRunSetDocPrefs) return Zotero.Promise.resolve(false);
return this._session.setDocPrefs(this._doc, this._app.primaryFieldType, return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
@ -991,8 +1001,7 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
} }
var warning = this._doc.displayAlert(Zotero.getString("integration.upgradeWarning"), var warning = this._doc.displayAlert(Zotero.getString("integration.upgradeWarning"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING, DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL);
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(!warning) { if(!warning) {
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document upgrade")); return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document upgrade"));
} }
@ -1007,15 +1016,27 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
[], "integration.error.title")); [], "integration.error.title"));
} }
if(Zotero.Integration.sessions[data.sessionID]) { if (Zotero.Integration.sessions[data.sessionID]) {
// If communication occured with this document since restart
this._session = Zotero.Integration.sessions[data.sessionID]; this._session = Zotero.Integration.sessions[data.sessionID];
} else { } else {
// Document has zotero data, but has not communicated since Zotero restart
this._session = this._createNewSession(data); this._session = this._createNewSession(data);
try { try {
this._session.setData(data); yield this._session.setData(data);
} catch(e) { } catch(e) {
// make sure style is defined // make sure style is defined
if(e instanceof Zotero.Exception.Alert && e.name === "integration.error.invalidStyle") { if(e instanceof Zotero.Exception.Alert && e.name === "integration.error.invalidStyle") {
if (data.style.styleID) {
let displayError = Zotero.getString("integration.error.styleMissing", data.style.styleID);
if (/^https?:\/\/(www\.)?(zotero\.org|citationstyles\.org)/.test(data.style.styleID) ||
me._doc.displayAlert(displayError, DIALOG_ICON_WARNING, DIALOG_BUTTONS_YES_NO)) {
yield Zotero.Styles.install({url: data.style.styleID}, data.style.styleID, true);
yield this._session.setData(data, true);
return Zotero.Promise.resolve(this._session);
}
}
return this._session.setDocPrefs(this._doc, this._app.primaryFieldType, return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
this._app.secondaryFieldType).then(function(status) { this._app.secondaryFieldType).then(function(status) {
me._doc.setDocumentData(me._session.data.serializeXML()); me._doc.setDocumentData(me._session.data.serializeXML());
@ -1027,12 +1048,11 @@ Zotero.Integration.Document.prototype._getSession = function _getSession(require
} }
} }
this._doc.setDocumentData(this._session.data.serializeXML());
this._session.reload = true; this._session.reload = true;
} }
return Zotero.Promise.resolve(this._session); return Zotero.Promise.resolve(this._session);
} }
}; });
/** /**
* Adds a citation to the current document. * Adds a citation to the current document.
@ -1156,8 +1176,7 @@ Zotero.Integration.Document.prototype.removeCodes = function() {
return fieldGetter.get() return fieldGetter.get()
}).then(function(fields) { }).then(function(fields) {
var result = me._doc.displayAlert(Zotero.getString("integration.removeCodesWarning"), var result = me._doc.displayAlert(Zotero.getString("integration.removeCodesWarning"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING, DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL);
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL);
if(result) { if(result) {
for(var i=fields.length-1; i>=0; i--) { for(var i=fields.length-1; i>=0; i--) {
fields[i].removeCode(); fields[i].removeCode();
@ -1301,8 +1320,8 @@ Zotero.Integration.Fields.prototype.addField = function(note) {
var field = this._doc.cursorInField(this._session.data.prefs['fieldType']); var field = this._doc.cursorInField(this._session.data.prefs['fieldType']);
if(field) { if(field) {
if(!this._doc.displayAlert(Zotero.getString("integration.replace"), if(!this._doc.displayAlert(Zotero.getString("integration.replace"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_STOP, DIALOG_ICON_STOP,
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL)) { DIALOG_BUTTONS_OK_CANCEL)) {
return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("inserting citation")); return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("inserting citation"));
} }
} }
@ -1607,8 +1626,7 @@ Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations,
field.select(); field.select();
var result = this._doc.displayAlert( var result = this._doc.displayAlert(
Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"), Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_CAUTION, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO);
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_YES_NO);
if(result) { if(result) {
citation.properties.dontUpdate = true; citation.properties.dontUpdate = true;
} }
@ -1752,8 +1770,7 @@ Zotero.Integration.Fields.prototype.addEditCitation = Zotero.Promise.coroutine(f
+ "Current: " + field.getText() + "Current: " + field.getText()
); );
if(!this._doc.displayAlert(Zotero.getString("integration.citationChanged.edit"), if(!this._doc.displayAlert(Zotero.getString("integration.citationChanged.edit"),
Components.interfaces.zoteroIntegrationDocument.DIALOG_ICON_WARNING, DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL)) {
Components.interfaces.zoteroIntegrationDocument.DIALOG_BUTTONS_OK_CANCEL)) {
throw new Zotero.Exception.UserCancelled("editing citation"); throw new Zotero.Exception.UserCancelled("editing citation");
} }
} }
@ -2066,30 +2083,30 @@ Zotero.Integration.Session.prototype.resetRequest = function(doc) {
* regardless of whether it has changed. This is desirable if the * regardless of whether it has changed. This is desirable if the
* automaticJournalAbbreviations or locale has changed. * automaticJournalAbbreviations or locale has changed.
*/ */
Zotero.Integration.Session.prototype.setData = function(data, resetStyle) { Zotero.Integration.Session.prototype.setData = Zotero.Promise.coroutine(function *(data, resetStyle) {
var oldStyle = (this.data && this.data.style ? this.data.style : false); var oldStyle = (this.data && this.data.style ? this.data.style : false);
this.data = data; this.data = data;
if(data.style.styleID && (!oldStyle || oldStyle.styleID != data.style.styleID || resetStyle)) { if (data.style.styleID && (!oldStyle || oldStyle.styleID != data.style.styleID || resetStyle)) {
this.styleID = data.style.styleID; this.styleID = data.style.styleID;
try { try {
yield Zotero.Styles.init();
var getStyle = Zotero.Styles.get(data.style.styleID); var getStyle = Zotero.Styles.get(data.style.styleID);
data.style.hasBibliography = getStyle.hasBibliography; data.style.hasBibliography = getStyle.hasBibliography;
this.style = getStyle.getCiteProc(data.style.locale, data.prefs.automaticJournalAbbreviations); this.style = getStyle.getCiteProc(data.style.locale, data.prefs.automaticJournalAbbreviations);
this.style.setOutputFormat("rtf"); this.style.setOutputFormat("rtf");
this.styleClass = getStyle.class; this.styleClass = getStyle.class;
this.dateModified = new Object(); this.dateModified = new Object();
} catch(e) { } catch (e) {
Zotero.logError(e); Zotero.logError(e);
data.style.styleID = undefined;
throw new Zotero.Exception.Alert("integration.error.invalidStyle"); throw new Zotero.Exception.Alert("integration.error.invalidStyle");
} }
return true; return true;
} else if(oldStyle) { } else if (oldStyle) {
data.style = oldStyle; data.style = oldStyle;
} }
return false; return false;
} });
/** /**
* Displays a dialog to set document preferences * Displays a dialog to set document preferences
@ -2097,12 +2114,12 @@ Zotero.Integration.Session.prototype.setData = function(data, resetStyle) {
* if there wasn't, or rejected with Zotero.Exception.UserCancelled if the dialog was * if there wasn't, or rejected with Zotero.Exception.UserCancelled if the dialog was
* cancelled. * cancelled.
*/ */
Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldType, secondaryFieldType) { Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(function* (doc, primaryFieldType, secondaryFieldType) {
var io = new function() { var io = new function() {
this.wrappedJSObject = this; this.wrappedJSObject = this;
}; };
if(this.data) { if (this.data) {
io.style = this.data.style.styleID; io.style = this.data.style.styleID;
io.locale = this.data.style.locale; io.locale = this.data.style.locale;
io.useEndnotes = this.data.prefs.noteType == 0 ? 0 : this.data.prefs.noteType-1; io.useEndnotes = this.data.prefs.noteType == 0 ? 0 : this.data.prefs.noteType-1;
@ -2114,45 +2131,43 @@ Zotero.Integration.Session.prototype.setDocPrefs = function(doc, primaryFieldTyp
io.requireStoreReferences = !Zotero.Utilities.isEmpty(this.embeddedItems); io.requireStoreReferences = !Zotero.Utilities.isEmpty(this.embeddedItems);
} }
var me = this; yield Zotero.Integration.displayDialog(doc,
return Zotero.Integration.displayDialog(doc, 'chrome://zotero/content/integration/integrationDocPrefs.xul', '', io);
'chrome://zotero/content/integration/integrationDocPrefs.xul', '', io)
.then(function() { if (!io.style || !io.fieldType) {
if (!io.style || !io.fieldType) { throw new Zotero.Exception.UserCancelled("document preferences window");
throw new Zotero.Exception.UserCancelled("document preferences window"); }
}
// set data
// set data var oldData = this.data;
var oldData = me.data; var data = new Zotero.Integration.DocumentData();
var data = new Zotero.Integration.DocumentData(); data.sessionID = oldData.sessionID;
data.sessionID = oldData.sessionID; data.style.styleID = io.style;
data.style.styleID = io.style; data.style.locale = io.locale;
data.style.locale = io.locale; data.prefs.fieldType = io.fieldType;
data.prefs.fieldType = io.fieldType; data.prefs.storeReferences = io.storeReferences;
data.prefs.storeReferences = io.storeReferences; data.prefs.automaticJournalAbbreviations = io.automaticJournalAbbreviations;
data.prefs.automaticJournalAbbreviations = io.automaticJournalAbbreviations;
var forceStyleReset = oldData
var forceStyleReset = oldData && (
&& ( oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations
oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations || oldData.style.locale != io.locale
|| oldData.style.locale != io.locale );
); yield this.setData(data, forceStyleReset);
me.setData(data, forceStyleReset);
// need to do this after setting the data so that we know if it's a note style // need to do this after setting the data so that we know if it's a note style
me.data.prefs.noteType = me.style && me.styleClass == "note" ? io.useEndnotes+1 : 0; this.data.prefs.noteType = this.style && this.styleClass == "note" ? io.useEndnotes+1 : 0;
if(!oldData || oldData.style.styleID != data.style.styleID if (!oldData || oldData.style.styleID != data.style.styleID
|| oldData.prefs.noteType != data.prefs.noteType || oldData.prefs.noteType != data.prefs.noteType
|| oldData.prefs.fieldType != data.prefs.fieldType || oldData.prefs.fieldType != data.prefs.fieldType
|| oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations) { || oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations) {
// This will cause us to regenerate all citations // This will cause us to regenerate all citations
me.oldCitationIDs = {}; this.oldCitationIDs = {};
} }
return oldData || null; return oldData || null;
}); })
}
/** /**
* Reselects an item to replace a deleted item * Reselects an item to replace a deleted item
@ -2348,9 +2363,9 @@ Zotero.Integration.Session.prototype.lookupItems = Zotero.Promise.coroutine(func
this.updateIndices[index] = true; this.updateIndices[index] = true;
} }
} else { } else {
if(citationItem.key) { if(citationItem.key && citationItem.libraryID) {
// DEBUG: why no library id? // DEBUG: why no library id?
zoteroItem = Zotero.Items.getByLibraryAndKey(0, citationItem.key); zoteroItem = Zotero.Items.getByLibraryAndKey(citationItem.libraryID, citationItem.key);
} else if(citationItem.itemID) { } else if(citationItem.itemID) {
zoteroItem = Zotero.Items.get(citationItem.itemID); zoteroItem = Zotero.Items.get(citationItem.itemID);
} else if(citationItem.id) { } else if(citationItem.id) {
@ -3082,9 +3097,9 @@ Zotero.Integration.DocumentData.prototype.unserialize = function(input) {
"storeReferences":false}; "storeReferences":false};
if(prefParameters[2] == "note") { if(prefParameters[2] == "note") {
if(prefParameters[4] == "1" || prefParameters[4] == "True") { if(prefParameters[4] == "1" || prefParameters[4] == "True") {
this.prefs.noteType = Components.interfaces.zoteroIntegrationDocument.NOTE_ENDNOTE; this.prefs.noteType = NOTE_ENDNOTE;
} else { } else {
this.prefs.noteType = Components.interfaces.zoteroIntegrationDocument.NOTE_FOOTNOTE; this.prefs.noteType = NOTE_FOOTNOTE;
} }
} else { } else {
this.prefs.noteType = 0; this.prefs.noteType = 0;

View File

@ -49,7 +49,6 @@ Zotero.Styles = new function() {
this.reinit = Zotero.Promise.coroutine(function* () { this.reinit = Zotero.Promise.coroutine(function* () {
Zotero.debug("Initializing styles"); Zotero.debug("Initializing styles");
var start = new Date; var start = new Date;
_initialized = true;
// Upgrade style locale prefs for 4.0.27 // Upgrade style locale prefs for 4.0.27
var bibliographyLocale = Zotero.Prefs.get("export.bibliographyLocale"); var bibliographyLocale = Zotero.Prefs.get("export.bibliographyLocale");
@ -114,6 +113,7 @@ Zotero.Styles = new function() {
_renamedStyles = xmlhttp.response; _renamedStyles = xmlhttp.response;
} }
}) })
_initialized = true;
}); });
this.init = Zotero.lazy(this.reinit); this.init = Zotero.lazy(this.reinit);
@ -190,7 +190,7 @@ Zotero.Styles = new function() {
} }
return _styles[id] || false; return _styles[id] || false;
} };
/** /**
* Gets all visible styles * Gets all visible styles
@ -238,24 +238,36 @@ Zotero.Styles = new function() {
/** /**
* Installs a style file, getting the contents of an nsIFile and showing appropriate * Installs a style file, getting the contents of an nsIFile and showing appropriate
* error messages * error messages
* @param {String|nsIFile} style An nsIFile representing a style on disk, or a string * @param {Object} style An object with one of the following properties
* containing the style data * - file: An nsIFile representing a style on disk
* - url: A url of the location of the style (local or remote)
* - string: A string containing the style data
* @param {String} origin The origin of the style, either a filename or URL, to be * @param {String} origin The origin of the style, either a filename or URL, to be
* displayed in dialogs referencing the style * displayed in dialogs referencing the style
* @param {Boolean} [silent=false] Skip prompts * @param {Boolean} [silent=false] Skip prompts
*/ */
this.install = Zotero.Promise.coroutine(function* (style, origin, silent=false) { this.install = Zotero.Promise.coroutine(function* (style, origin, silent=false) {
var styleTitle; var styleTitle;
var warnDeprecated;
if (style instanceof Components.interfaces.nsIFile) {
warnDeprecated = true;
style = {file: style};
} else if (typeof style == 'string') {
warnDeprecated = true;
style = {string: style};
}
if (warnDeprecated) {
Zotero.debug("Zotero.Styles.install() now takes a style object as first argument -- update your code", 2);
}
try { try {
if (style instanceof Components.interfaces.nsIFile) { if (style.file) {
// handle nsIFiles style.string = yield Zotero.File.getContentsAsync(style.file);
var url = Services.io.newFileURI(style);
var xmlhttp = yield Zotero.HTTP.request("GET", url.spec);
styleTitle = yield _install(xmlhttp.responseText, style.leafName, false, silent);
} else {
styleTitle = yield _install(style, origin, false, silent);
} }
else if (style.url) {
style.string = yield Zotero.File.getContentsFromURLAsync(style.url);
}
styleTitle = yield _install(style.string, origin, false, silent);
} }
catch (error) { catch (error) {
// Unless user cancelled, show an alert with the error // Unless user cancelled, show an alert with the error

View File

@ -818,6 +818,7 @@ integration.error.noBibliography = The current bibliographic style does not def
integration.error.deletePipe = The pipe that Zotero uses to communicate with the word processor could not be initialized. Would you like Zotero to attempt to correct this error? You will be prompted for your password. integration.error.deletePipe = The pipe that Zotero uses to communicate with the word processor could not be initialized. Would you like Zotero to attempt to correct this error? You will be prompted for your password.
integration.error.invalidStyle = The style you have selected does not appear to be valid. If you have created this style yourself, please ensure that it passes validation as described at https://github.com/citation-style-language/styles/wiki/Validation. Alternatively, try selecting another style. integration.error.invalidStyle = The style you have selected does not appear to be valid. If you have created this style yourself, please ensure that it passes validation as described at https://github.com/citation-style-language/styles/wiki/Validation. Alternatively, try selecting another style.
integration.error.fieldTypeMismatch = Zotero cannot update this document because it was created by a different word processing application with an incompatible field encoding. In order to make a document compatible with both Word and LibreOffice, open the document in the word processor with which it was originally created and switch the field type to Bookmarks in the Zotero Document Preferences. integration.error.fieldTypeMismatch = Zotero cannot update this document because it was created by a different word processing application with an incompatible field encoding. In order to make a document compatible with both Word and LibreOffice, open the document in the word processor with which it was originally created and switch the field type to Bookmarks in the Zotero Document Preferences.
integration.error.styleMissing = The citation style used in this document is missing. Would you like to install it from %S?
integration.replace = Replace this Zotero field? integration.replace = Replace this Zotero field?
integration.missingItem.single = The highlighted citation no longer exists in your Zotero database. Do you want to select a substitute item? integration.missingItem.single = The highlighted citation no longer exists in your Zotero database. Do you want to select a substitute item?

116
test/tests/data/cell.csl Normal file
View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl" version="1.0" class="in-text" default-locale="en-US" demote-non-dropping-particle="sort-only" page-range-format="expanded">
<info>
<title>Cell</title>
<id>http://www.zotero.org/styles/cell</id>
<link href="http://www.zotero.org/styles/cell" rel="self"/>
<link href="http://www.cell.com/authors" rel="documentation"/>
<author>
<name>Adam Mark</name>
<email>a.mark@uoguelph.ca</email>
</author>
<contributor>
<name>Julian Onions</name>
<email>julian.onions@gmail.com</email>
</contributor>
<contributor>
<name>Aurimas Vinckevicius</name>
<email>aurimas.dev@gmail.com</email>
</contributor>
<category citation-format="author-date"/>
<category field="biology"/>
<issn>0092-8674</issn>
<eissn>1097-4172</eissn>
<summary>The Cell journal style. Original by Julian Onions.</summary>
<updated>2014-09-06T22:02:33+00:00</updated>
<rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
</info>
<macro name="author-short">
<names variable="author">
<name form="short" and="text"/>
</names>
</macro>
<macro name="author-count">
<names variable="author">
<name form="count"/>
</names>
</macro>
<macro name="author">
<names variable="author">
<name name-as-sort-order="all" initialize-with="." and="text" delimiter-precedes-last="always"/>
</names>
</macro>
<macro name="issued">
<date variable="issued">
<date-part name="year"/>
</date>
</macro>
<macro name="publisher">
<group prefix="(" delimiter=": " suffix=")">
<text variable="publisher-place"/>
<text variable="publisher"/>
</group>
</macro>
<macro name="editor">
<names variable="editor">
<name initialize-with="." and="text" delimiter-precedes-last="always"/>
<label form="short" prefix=", "/>
</names>
</macro>
<citation et-al-min="3" et-al-use-first="1" disambiguate-add-year-suffix="true" collapse="year">
<sort>
<key macro="author-short" names-min="1" names-use-first="1"/>
<key macro="author-count" names-min="3" names-use-first="3"/>
<key macro="author" names-min="3" names-use-first="1"/>
<key macro="issued"/>
<key variable="title"/>
</sort>
<layout prefix="(" suffix=")" delimiter="; ">
<group delimiter=", ">
<text macro="author-short"/>
<text macro="issued"/>
</group>
</layout>
</citation>
<bibliography et-al-min="11" et-al-use-first="10">
<sort>
<key macro="author-short" names-min="1" names-use-first="1"/>
<key macro="author-count" names-min="3" names-use-first="3"/>
<key macro="author" names-min="3" names-use-first="1"/>
<key macro="issued"/>
</sort>
<layout suffix=".">
<group delimiter=" ">
<text macro="author"/>
<text macro="issued" prefix="(" suffix=")."/>
<choose>
<if type="article article-magazine article-newspaper article-journal review" match="any">
<text variable="title" suffix="."/>
<text variable="container-title" form="short" text-case="title"/>
<group delimiter=", ">
<text variable="volume" font-style="italic"/>
<text variable="page"/>
</group>
</if>
<else-if type="chapter paper-conference" match="any">
<text variable="title" suffix="."/>
<text variable="container-title" prefix="In " suffix="," text-case="title"/>
<text macro="editor"/>
<text macro="publisher" suffix=","/>
<label variable="page" form="short"/>
<text variable="page"/>
</else-if>
<else-if type="thesis">
<text variable="title" suffix="."/>
<text variable="genre" suffix="."/>
<text variable="publisher"/>
</else-if>
<else>
<text variable="title"/>
<text macro="publisher"/>
</else>
</choose>
</group>
</layout>
</bibliography>
</style>

View File

@ -0,0 +1,323 @@
"use strict";
describe("Zotero.Integration", function () {
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
// Fully functional document plugin dummy based on word-for-windows-integration
// Functions should be stubbed when testing as needed
var DocumentPluginDummy = {};
DocumentPluginDummy.Application = function() {
this.doc = new DocumentPluginDummy.Document();
this.primaryFieldType = "Field";
this.secondaryFieldType = "Bookmark";
this.fields = [];
};
DocumentPluginDummy.Application.prototype = {
getActiveDocument: function() {return this.doc},
getDocument: function() {return this.doc},
QueryInterface: function() {return this},
};
DocumentPluginDummy.Document = function() {this.fields = []};
DocumentPluginDummy.Document.prototype = {
// Needs to be stubbed for expected return values depending on prompt type
// - Yes: 2, No: 1, Cancel: 0
// - Yes: 1, No: 0
// - Ok: 1, Cancel: 0
// - Ok: 0
displayAlert: () => 0,
activate: () => 0,
canInsertField: () => true,
cursorInField: () => false,
getDocumentData: function() {return this.data},
setDocumentData: function(data) {this.data = data},
insertField: function() { var field = new DocumentPluginDummy.Field(this); this.fields.push(field); return field },
getFields: function() {return new DocumentPluginDummy.FieldEnumerator(this)},
getFieldsAsync: function(fieldType, observer) {
observer.observe(this.getFields(fieldType), 'fields-available', null)
},
setBibliographyStyle: () => 0,
convert: () => 0,
cleanup: () => 0,
complete: () => 0,
QueryInterface: function() {return this},
};
DocumentPluginDummy.FieldEnumerator = function(doc) {this.doc = doc; this.idx = 0};
DocumentPluginDummy.FieldEnumerator.prototype = {
hasMoreElements: function() {return this.idx < this.doc.fields.length;},
getNext: function() {return this.doc.fields[this.idx++]},
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
Components.interfaces.nsISimpleEnumerator])
};
DocumentPluginDummy.Field = function(doc) {
this.doc = doc;
this.code = this.text = '';
this.noteIndex = DocumentPluginDummy.Field.noteIndex++;
this.wrappedJSObject = this;
};
DocumentPluginDummy.Field.noteIndex = 0;
DocumentPluginDummy.Field.prototype = {
delete: function() {this.doc.fields.filter((field) => field != this)},
removeCode: function() {this.code = ""},
select: () => 0,
setText: function(text) {this.text = text},
getText: function() {return this.text},
setCode: function(code) {this.code = code},
getCode: function() {return this.code},
equals: function(field) {return this == field},
getNoteIndex: function() {return this.noteIndex},
QueryInterface: function() {return this},
};
for (let cls of ['Application', 'Document', 'FieldEnumerator', 'Field']) {
for (let methodName in DocumentPluginDummy[cls].prototype) {
if (methodName !== 'QueryInterface') {
let method = DocumentPluginDummy[cls].prototype[methodName];
DocumentPluginDummy[cls].prototype[methodName] = function() {
try {
Zotero.debug(`DocumentPluginDummy: ${cls}.${methodName} invoked with args ${JSON.stringify(arguments)}`, 2);
} catch (e) {
Zotero.debug(`DocumentPluginDummy: ${cls}.${methodName} invoked with args ${arguments}`, 2);
}
var result = method.apply(this, arguments);
try {
Zotero.debug(`Result: ${JSON.stringify(result)}`, 2);
} catch (e) {
Zotero.debug(`Result: ${result}`, 2);
}
return result;
}
}
}
}
var testItems;
var applications = {};
var addEditCitationSpy, displayDialogStub;
var styleID = "http://www.zotero.org/styles/cell";
var stylePath = OS.Path.join(getTestDataDirectory().path, 'cell.csl');
var commandList = [
'addCitation', 'editCitation', 'addEditCitation',
'addBibliography', 'editBibliography', 'addEditBibliography',
'refresh', 'removeCodes', 'setDocPrefs'
];
function execCommand(command, docID) {
if (! commandList.includes(command)) {
throw new Error(`${command} is not a valid document command`);
}
if (typeof docID === "undefined") {
throw new Error(`docID cannot be undefined`)
}
return Zotero.Integration.execCommand("dummy", command, docID);
}
var dialogResults = {
addCitationDialog: {},
quickFormat: {},
integrationDocPrefs: {},
selectItemsDialog: {},
editBibliographyDialog: {}
};
function initDoc(docID, options={}) {
applications[docID] = new DocumentPluginDummy.Application();
var data = new Zotero.Integration.DocumentData();
data.prefs = {
noteType: 0,
fieldType: "Field",
storeReferences: true,
automaticJournalAbbreviations: true
};
data.style = {styleID, locale: 'en-US', hasBibliography: true, bibliographyStyleHasBeenSet: true};
data.sessionID = Zotero.Utilities.randomString(10);
Object.assign(data, options);
applications[docID].getActiveDocument().setDocumentData(data.serializeXML());
}
function setDefaultIntegrationDocPrefs() {
dialogResults.integrationDocPrefs = {
style: "http://www.zotero.org/styles/cell",
locale: 'en-US',
fieldType: 'Field',
storeReferences: true,
automaticJournalAbbreviations: false,
useEndnotes: 0
};
}
setDefaultIntegrationDocPrefs();
function setAddEditItems(items) {
if (items.length == undefined) items = [items];
dialogResults.quickFormat = function(doc, dialogName) {
var citationItems = items.map((i) => {return {id: i.id} });
var field = doc.insertField();
field.setCode('TEMP');
var integrationDoc = addEditCitationSpy.lastCall.thisValue;
var fieldGetter = new Zotero.Integration.Fields(integrationDoc._session, integrationDoc._doc, () => 0);
var io = new Zotero.Integration.CitationEditInterface(
{ citationItems, properties: {} },
field,
fieldGetter,
integrationDoc._session
);
io._acceptDeferred.resolve();
return io;
}
}
before(function* () {
yield Zotero.Styles.init();
yield Zotero.Styles.install({file: stylePath}, styleID, true);
testItems = [];
for (let i = 0; i < 5; i++) {
testItems.push(yield createDataObject('item', {libraryID: Zotero.Libraries.userLibraryID}));
}
setAddEditItems(testItems[0]);
sinon.stub(Zotero.Integration, 'getApplication', function(agent, command, docID) {
if (!applications[docID]) {
applications[docID] = new DocumentPluginDummy.Application();
}
return applications[docID];
});
displayDialogStub = sinon.stub(Zotero.Integration, 'displayDialog', function(doc, dialogName, prefs, io) {
var ioResult = dialogResults[dialogName.substring(dialogName.lastIndexOf('/')+1, dialogName.length-4)];
if (typeof ioResult == 'function') {
ioResult = ioResult(doc, dialogName);
}
Object.assign(io, ioResult);
return Zotero.Promise.resolve();
});
addEditCitationSpy = sinon.spy(Zotero.Integration.Document.prototype, 'addEditCitation');
});
after(function() {
Zotero.Integration.getApplication.restore();
displayDialogStub.restore();
addEditCitationSpy.restore();
});
describe('Document', function() {
describe('#addEditCitation', function() {
var setDocumentDataSpy;
var docID = this.fullTitle();
before(function() {
setDocumentDataSpy = sinon.spy(DocumentPluginDummy.Document.prototype, 'setDocumentData');
});
afterEach(function() {
setDocumentDataSpy.reset();
});
after(function() {
setDocumentDataSpy.restore();
});
it('should call doc.setDocumentData on a fresh document', function* () {
yield execCommand('addEditCitation', docID);
assert.isTrue(setDocumentDataSpy.calledOnce);
});
it('should not call doc.setDocumentData on subsequent invocations', function* () {
yield execCommand('addEditCitation', docID);
assert.isFalse(setDocumentDataSpy.called);
});
it('should not call doc.setDocumentData when document communicates for first time since restart, but has data', function* () {
Zotero.Integration.sessions = {};
yield execCommand('addEditCitation', docID);
assert.isFalse(setDocumentDataSpy.called);
});
describe('when style used in the document does not exist', function() {
var docID = this.fullTitle();
var displayAlertStub;
var style;
before(function* () {
displayAlertStub = sinon.stub(DocumentPluginDummy.Document.prototype, 'displayAlert').returns(0);
});
beforeEach(function() {
// 🦉birds?
style = {styleID: "http://www.example.com/csl/waterbirds", locale: 'en-US'};
// Make sure style not in library
try {
Zotero.Styles.get(style.styleID).remove();
} catch (e) {}
initDoc(docID, {style});
displayDialogStub.reset();
displayAlertStub.reset();
});
after(function* () {
displayAlertStub.restore();
});
describe('when the style is not from a trusted source', function() {
it('should download the style and not call doc.setDocumentData if user clicks YES', function* () {
setDocumentDataSpy.reset();
var styleInstallStub = sinon.stub(Zotero.Styles, "install").resolves();
var style = Zotero.Styles.get(styleID);
var styleGetCalledOnce = false;
var styleGetStub = sinon.stub(Zotero.Styles, 'get', function() {
if (!styleGetCalledOnce) {
styleGetCalledOnce = true;
return false;
}
return style;
});
displayAlertStub.returns(1);
yield execCommand('addEditCitation', docID);
assert.isTrue(displayAlertStub.calledOnce);
assert.isFalse(displayDialogStub.calledWith(applications[docID].doc, 'chrome://zotero/content/integration/integrationDocPrefs.xul'));
assert.isTrue(styleInstallStub.calledOnce);
assert.isFalse(setDocumentDataSpy.called);
assert.isOk(Zotero.Styles.get(style.styleID));
styleInstallStub.restore();
styleGetStub.restore();
});
it('should prompt with the document preferences dialog if user clicks NO', function* () {
displayAlertStub.returns(0);
yield execCommand('addEditCitation', docID);
assert.isTrue(displayAlertStub.calledOnce);
// Prefs to select a new style and quickFormat
assert.isTrue(displayDialogStub.calledTwice);
assert.isNotOk(Zotero.Styles.get(style.styleID));
});
});
it('should download the style without prompting if it is from zotero.org', function* (){
initDoc(docID, {styleID: "http://www.zotero.org/styles/waterbirds", locale: 'en-US'});
var styleInstallStub = sinon.stub(Zotero.Styles, "install").resolves();
var style = Zotero.Styles.get(styleID);
var styleGetCalledOnce = false;
var styleGetStub = sinon.stub(Zotero.Styles, 'get', function() {
if (!styleGetCalledOnce) {
styleGetCalledOnce = true;
return false;
}
return style;
});
displayAlertStub.returns(1);
yield execCommand('addEditCitation', docID);
assert.isFalse(displayAlertStub.called);
assert.isFalse(displayDialogStub.calledWith(applications[docID].doc, 'chrome://zotero/content/integration/integrationDocPrefs.xul'));
assert.isTrue(styleInstallStub.calledOnce);
assert.isOk(Zotero.Styles.get(style.styleID));
styleInstallStub.restore();
styleGetStub.restore();
});
});
});
});
});

45
test/tests/styleTest.js Normal file
View File

@ -0,0 +1,45 @@
"use strict";
describe("Zotero.Styles", function() {
var styleID = "http://www.zotero.org/styles/cell";
var stylePath = OS.Path.join(getTestDataDirectory().path, 'cell.csl');
var styleFile = Zotero.File.pathToFile(stylePath);
var style;
before(function* () {
yield Zotero.Styles.init();
style = yield Zotero.File.getContentsAsync(stylePath);
});
describe("Zotero.Styles.install", function() {
afterEach(`${styleID} style should be installed`, function* (){
assert.isOk(Zotero.Styles.get(styleID));
yield Zotero.Styles.get(styleID).remove();
});
it("should install the style from string", function* () {
yield Zotero.Styles.install(style, styleID, true);
});
it("should install the style from nsIFile", function* () {
yield Zotero.Styles.install(styleFile, styleID, true);
});
it("should install the style from url", function* () {
var getContentsFromURLAsync = Zotero.File.getContentsFromURLAsync;
sinon.stub(Zotero.File, 'getContentsFromURLAsync', function(style) {
if (style.url == styleID) {
return Zotero.Promise.resolve(style);
} else {
return getContentsFromURLAsync.apply(Zotero.File, arguments);
}
});
yield Zotero.Styles.install({url: styleID}, styleID, true);
Zotero.File.getContentsFromURLAsync.restore();
});
it("should install the style from file path", function* () {
yield Zotero.Styles.install({file: stylePath}, styleID, true);
})
});
});