diff --git a/chrome/content/zotero/bibliography.js b/chrome/content/zotero/bibliography.js
index a673c3172..7702208d8 100644
--- a/chrome/content/zotero/bibliography.js
+++ b/chrome/content/zotero/bibliography.js
@@ -167,10 +167,23 @@ var Zotero_File_Interface_Bibliography = new function() {
document.getElementById("automaticJournalAbbreviations-checkbox").checked = true;
}
}
+ if (document.getElementById("delayCitationUpdates-checkbox")) {
+ if (_io.delayCitationUpdates) {
+ document.getElementById("delayCitationUpdates-checkbox").checked = true;
+ }
+ }
// set style to false, in case this is cancelled
_io.style = false;
});
+
+ this.openHelpLink = function() {
+ var url = "https://www.zotero.org/support/word_processor_plugin_usage";
+ var win = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator)
+ .getMostRecentWindow("navigator:browser");
+ Zotero.launchURL(url);
+ };
/*
* Called when locale is changed
@@ -211,6 +224,13 @@ var Zotero_File_Interface_Bibliography = new function() {
document.getElementById("automaticJournalAbbreviations-vbox").hidden =
!selectedStyleObj.usesAbbreviation;
}
+ // Hide the delayCitationUpdates checkbox before the prompt is shown
+ document.getElementById("delayCitationUpdates-vbox").hidden = _io.dontAskDelayCitationUpdates == undefined;
+ // Highlight delay citations checkbox after displaying the alert
+ // NOTE: Currently unused
+ if (_io.highlightDelayCitations) {
+ document.getElementById("delayCitationUpdates-vbox").style.border = "1px dashed #e52e2e"
+ }
//
// For bibliography.xul
@@ -266,6 +286,7 @@ var Zotero_File_Interface_Bibliography = new function() {
}
_io.useEndnotes = document.getElementById("displayAs").selectedIndex;
_io.fieldType = (document.getElementById("formatUsing").selectedIndex == 0 ? _io.primaryFieldType : _io.secondaryFieldType);
+ _io.delayCitationUpdates = document.getElementById("delayCitationUpdates-checkbox").checked;
}
// remember style and locale if user selected these explicitly
@@ -283,8 +304,7 @@ var Zotero_File_Interface_Bibliography = new function() {
document.documentElement.getButton('cancel').click();
var win = Zotero.Utilities.Internal.openPreferences('zotero-prefpane-cite', { tab: 'styles-tab' });
if (isDocPrefs) {
- // TODO: Move activate() code elsewhere
- Zotero.Integration.activate(win);
+ Zotero.Utilities.Internal.activate(win);
}
};
}
diff --git a/chrome/content/zotero/integration/addCitationDialog.js b/chrome/content/zotero/integration/addCitationDialog.js
index f8394d15b..3853767a1 100644
--- a/chrome/content/zotero/integration/addCitationDialog.js
+++ b/chrome/content/zotero/integration/addCitationDialog.js
@@ -59,10 +59,8 @@ var Zotero_Citation_Dialog = new function () {
this.listItemSelected = listItemSelected;
this.up = up;
this.down = down;
- this.add = add;
this.remove = remove;
this.setSortToggle = setSortToggle;
- this.citationSortUnsort = citationSortUnsort;
this.confirmRegenerate = confirmRegenerate;
this.accept = accept;
this.cancel = cancel;
@@ -373,13 +371,13 @@ var Zotero_Citation_Dialog = new function () {
/*
* Adds an item to the multipleSources list
*/
- function add(first_item) {
+ this.add = Zotero.Promise.coroutine(function* (first_item) {
var pos, len;
var item = itemsView.getSelectedItems()[0]; // treeview from xpcom/itemTreeView.js
if (!item) {
- sortCitation();
+ yield sortCitation();
_updateAccept();
_updatePreview();
return;
@@ -412,11 +410,11 @@ var Zotero_Citation_Dialog = new function () {
_citationList.ensureElementIsVisible(selectionNode);
// allow user to press OK
- selectionNode = sortCitation(selectionNode);
+ selectionNode = yield sortCitation(selectionNode);
_citationList.selectItem(selectionNode);
_updateAccept();
_updatePreview();
- }
+ });
/*
* Deletes a citation from the multipleSources list
@@ -446,11 +444,11 @@ var Zotero_Citation_Dialog = new function () {
/*
* Sorts preview citations, if preview is open.
*/
- function citationSortUnsort() {
+ this.citationSortUnsort = Zotero.Promise.coroutine(function* () {
setSortToggle();
- sortCitation();
+ yield sortCitation();
_updatePreview();
- }
+ });
/*
* Sets the current sort toggle state persistently on the citation.
@@ -468,7 +466,7 @@ var Zotero_Citation_Dialog = new function () {
/*
* Sorts the list of citations
*/
- function sortCitation(scrollToItem) {
+ var sortCitation = Zotero.Promise.coroutine(function* (scrollToItem) {
if(!_sortCheckbox) return scrollToItem;
if(!_sortCheckbox.checked) {
io.citation.properties.unsorted = true;
@@ -485,7 +483,7 @@ var Zotero_Citation_Dialog = new function () {
// run preview function to re-sort, if it hasn't already been
// run
- io.sort();
+ yield io.sort();
// add items back to list
scrollToItem = null;
@@ -502,7 +500,7 @@ var Zotero_Citation_Dialog = new function () {
if(scrollToItem) _citationList.ensureElementIsVisible(scrollToItem);
return scrollToItem;
- }
+ });
/*
* Ask whether to modifiy the preview
diff --git a/chrome/content/zotero/integration/editBibliographyDialog.js b/chrome/content/zotero/integration/editBibliographyDialog.js
index f5dfdd7ad..7c8138350 100644
--- a/chrome/content/zotero/integration/editBibliographyDialog.js
+++ b/chrome/content/zotero/integration/editBibliographyDialog.js
@@ -74,8 +74,8 @@ var Zotero_Bibliography_Dialog = new function () {
if(selectedItemIDs.length) {
for (let itemID of selectedItemIDs) {
var itemIndexToSelect = false;
- for(var i in bibEditInterface.bibliography[0].entry_ids) {
- if(bibEditInterface.bibliography[0].entry_ids[i].indexOf(itemID) !== -1) {
+ for(var i in bibEditInterface.bib[0].entry_ids) {
+ if(bibEditInterface.bib[0].entry_ids[i].indexOf(itemID) !== -1) {
itemIndexToSelect = i;
continue;
}
@@ -254,7 +254,7 @@ var Zotero_Bibliography_Dialog = new function () {
*/
function _getSelectedListItemIDs() {
return Array.from(_itemList.selectedItems)
- .map(item => bibEditInterface.bibliography[0].entry_ids[item.value][0]);
+ .map(item => bibEditInterface.bib[0].entry_ids[item.value][0]);
}
/**
@@ -287,8 +287,8 @@ var Zotero_Bibliography_Dialog = new function () {
editor.readonly = index === undefined;
if(index !== undefined) {
- var itemID = bibEditInterface.bibliography[0].entry_ids[index];
- editor.value = bibEditInterface.bibliography[1][index];
+ var itemID = bibEditInterface.bib[0].entry_ids[index];
+ editor.value = bibEditInterface.bib[1][index];
_lastSelectedIndex = index;
_lastSelectedItemID = itemID;
_lastSelectedValue = editor.value;
@@ -304,7 +304,7 @@ var Zotero_Bibliography_Dialog = new function () {
* loads items from itemSet
*/
function _loadItems() {
- var itemIDs = bibEditInterface.bibliography[0].entry_ids;
+ var itemIDs = bibEditInterface.bib[0].entry_ids;
var items = itemIDs.map(itemID => Zotero.Cite.getItem(itemID[0]));
// delete all existing items from list
diff --git a/chrome/content/zotero/integration/integrationDocPrefs.xul b/chrome/content/zotero/integration/integrationDocPrefs.xul
index 50f90205e..0d6a561f3 100644
--- a/chrome/content/zotero/integration/integrationDocPrefs.xul
+++ b/chrome/content/zotero/integration/integrationDocPrefs.xul
@@ -31,10 +31,11 @@
&zotero.integration.prefs.automaticJournalAbbeviations.caption;
+
+
+
+ &zotero.integration.prefs.delayCitationUpdates.description;
+
\ No newline at end of file
diff --git a/chrome/content/zotero/integration/quickFormat.js b/chrome/content/zotero/integration/quickFormat.js
index 3b247ba24..ffa2a8f8d 100644
--- a/chrome/content/zotero/integration/quickFormat.js
+++ b/chrome/content/zotero/integration/quickFormat.js
@@ -711,7 +711,7 @@ var Zotero_QuickFormat = new function () {
/**
* Converts the selected item to a bubble
*/
- function _bubbleizeSelected() {
+ var _bubbleizeSelected = Zotero.Promise.coroutine(function* () {
if(!referenceBox.hasChildNodes() || !referenceBox.selectedItem) return false;
var citationItem = {"id":referenceBox.selectedItem.getAttribute("zotero-item")};
@@ -734,11 +734,11 @@ var Zotero_QuickFormat = new function () {
node.nodeValue = "";
var bubble = _insertBubble(citationItem, node);
_clearEntryList();
- _previewAndSort();
+ yield _previewAndSort();
_refocusQfe();
return true;
- }
+ });
/**
* Ignores clicks (for use on separators in the rich list box)
@@ -902,13 +902,13 @@ var Zotero_QuickFormat = new function () {
/**
* Generates the preview and sorts citations
*/
- function _previewAndSort() {
+ var _previewAndSort = Zotero.Promise.coroutine(function* () {
var shouldKeepSorted = keepSorted.hasAttribute("checked"),
editorShowing = showEditor.hasAttribute("checked");
if(!shouldKeepSorted && !editorShowing) return;
_updateCitationObject();
- io.sort();
+ yield io.sort();
if(shouldKeepSorted) {
// means we need to resort citations
_clearCitation();
@@ -920,7 +920,7 @@ var Zotero_QuickFormat = new function () {
_moveCursorToEnd();
}
- }
+ });
/**
* Shows the citation properties panel for a given bubble
@@ -1071,7 +1071,7 @@ var Zotero_QuickFormat = new function () {
/**
* Handle return or escape
*/
- function _onQuickSearchKeyPress(event) {
+ var _onQuickSearchKeyPress = Zotero.Promise.coroutine(function* (event) {
// Prevent hang if another key is pressed after Enter
// https://forums.zotero.org/discussion/59157/
if (accepted) {
@@ -1083,7 +1083,7 @@ var Zotero_QuickFormat = new function () {
var keyCode = event.keyCode;
if (keyCode === event.DOM_VK_RETURN) {
event.preventDefault();
- if(!_bubbleizeSelected() && !_getEditorContent()) {
+ if(!(yield _bubbleizeSelected()) && !_getEditorContent()) {
_accept();
}
} else if(keyCode === event.DOM_VK_TAB || event.charCode === 59 /* ; */) {
@@ -1162,7 +1162,7 @@ var Zotero_QuickFormat = new function () {
} else {
_resetSearchTimer();
}
- }
+ });
/**
* Adds a dummy element to make dragging work
@@ -1190,7 +1190,7 @@ var Zotero_QuickFormat = new function () {
/**
* Replaces the dummy element with a node to make dropping work
*/
- function _onBubbleDrop(event) {
+ var _onBubbleDrop = Zotero.Promise.coroutine(function* (event) {
event.preventDefault();
event.stopPropagation();
@@ -1208,9 +1208,9 @@ var Zotero_QuickFormat = new function () {
keepSorted.removeAttribute("checked");
}
- _previewAndSort();
+ yield _previewAndSort();
_moveCursorToEnd();
- }
+ });
/**
* Handle a click on a bubble
@@ -1333,7 +1333,7 @@ var Zotero_QuickFormat = new function () {
pane.selectItem(id);
// Pull window to foreground
- Zotero.Integration.activate(pane.document.defaultView);
+ Zotero.Utilities.Internal.activate(pane.document.defaultView);
}
/**
diff --git a/chrome/content/zotero/xpcom/cite.js b/chrome/content/zotero/xpcom/cite.js
index b4ded1b30..0745c4375 100644
--- a/chrome/content/zotero/xpcom/cite.js
+++ b/chrome/content/zotero/xpcom/cite.js
@@ -16,13 +16,13 @@ Zotero.Cite = {
* Remove specified item IDs in-place from a citeproc-js bibliography object returned
* by makeBibliography()
* @param {bib} citeproc-js bibliography object
- * @param {Array} itemsToRemove Array of items to remove
+ * @param {Set} itemsToRemove Set of items to remove
*/
"removeFromBibliography":function(bib, itemsToRemove) {
var removeItems = [];
for(let i in bib[0].entry_ids) {
for(let j in bib[0].entry_ids[i]) {
- if(itemsToRemove[bib[0].entry_ids[i][j]]) {
+ if(itemsToRemove.has(`${bib[0].entry_ids[i][j]}`)) {
removeItems.push(i);
break;
}
@@ -303,7 +303,7 @@ Zotero.Cite = {
session = Zotero.Integration.sessions[sessionID],
item;
if(session) {
- item = session.embeddedZoteroItems[id.substr(slashIndex+1)];
+ item = session.embeddedItems[id.substr(slashIndex+1)];
}
if(!item) {
@@ -526,7 +526,7 @@ Zotero.Cite.System.prototype = {
}
if(!zoteroItem) {
- throw "Zotero.Cite.System.retrieveItem called on non-item "+item;
+ throw new Error("Zotero.Cite.System.retrieveItem called on non-item "+item);
}
var cslItem = Zotero.Utilities.itemToCSLJSON(zoteroItem);
diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js
index bcdea3b39..541a47516 100644
--- a/chrome/content/zotero/xpcom/integration.js
+++ b/chrome/content/zotero/xpcom/integration.js
@@ -24,9 +24,6 @@
***** END LICENSE BLOCK *****
*/
-const RESELECT_KEY_URI = 1;
-const RESELECT_KEY_ITEM_KEY = 2;
-const RESELECT_KEY_ITEM_ID = 3;
const DATA_VERSION = 3;
// Specifies that citations should only be updated if changed
@@ -37,10 +34,6 @@ const FORCE_CITATIONS_REGENERATE = 1;
// Specifies that citations should be reset regardless of whether formattedText has changed
const FORCE_CITATIONS_RESET_TEXT = 2;
-// this is used only for update checking
-const INTEGRATION_PLUGINS = ["zoteroMacWordIntegration@zotero.org",
- "zoteroOpenOfficeIntegration@zotero.org", "zoteroWinWordIntegration@zotero.org"];
-
// These must match the constants in corresponding word plugins
const DIALOG_ICON_STOP = 0;
const DIALOG_ICON_WARNING = 1;
@@ -54,24 +47,19 @@ const DIALOG_BUTTONS_YES_NO_CANCEL = 3;
const NOTE_FOOTNOTE = 1;
const NOTE_ENDNOTE = 2;
+const INTEGRATION_TYPE_ITEM = 1;
+const INTEGRATION_TYPE_BIBLIOGRAPHY = 2;
+const INTEGRATION_TYPE_TEMP = 3;
+
+const DELAY_CITATIONS_PROMPT_TIMEOUT = 5/*seconds*/;
+const DELAYED_CITATION_STYLING = "\\uldash";
+const DELAYED_CITATION_STYLING_CLEAR = "\\ulclear";
+
+
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;
-
- // these need to be global because of GC
- var _updateTimer;
-
- // For Carbon and X11
- var _carbon, ProcessSerialNumber, SetFrontProcessWithOptions;
- var _x11, _x11Display, _x11RootWindow, XClientMessageEvent, XFetchName, XFree, XQueryTree,
- XOpenDisplay, XCloseDisplay, XFlush, XDefaultRootWindow, XInternAtom, XSendEvent,
- XMapRaised, XGetWindowProperty, X11Atom, X11Bool, X11Display, X11Window, X11Status;
-
this.currentWindow = false;
this.sessions = {};
@@ -126,8 +114,6 @@ Zotero.Integration = new function() {
} catch(e) {
Zotero.logError(e);
}
-
- Zotero.Promise.delay(1000).then(_checkPluginVersions);
}
/**
@@ -178,7 +164,7 @@ Zotero.Integration = new function() {
var deletePipe = promptService.confirm(null, Zotero.getString("integration.error.title"), Zotero.getString("integration.error.deletePipe"));
if(!deletePipe) return false;
let escapedFifoFile = pipe.path.replace("'", "'\\''");
- _executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
+ Zotero.Utilities.Internal.executeAppleScript("do shell script \"rmdir '"+escapedFifoFile+"'; rm -f '"+escapedFifoFile+"'\" with administrator privileges", true);
if(pipe.exists()) return false;
} catch(e) {
Zotero.logError(e);
@@ -194,42 +180,6 @@ Zotero.Integration = new function() {
}
});
- /**
- * 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;
-
- return function _checkPluginVersions() {
- if(integrationVersionsOK) {
- if(integrationVersionsOK === true) {
- return Zotero.Promise.resolve(integrationVersionsOK);
- } else {
- return Zotero.Promise.reject(integrationVersionsOK);
- }
- }
-
- var deferred = Zotero.Promise.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;
- };
- }
-
this.getApplication = function(agent, command, docId) {
// Try to load the appropriate Zotero component; otherwise display an error
try {
@@ -254,11 +204,11 @@ Zotero.Integration = new function() {
this.execCommand = new function() {
var inProgress;
- return function execCommand(agent, command, docId) {
- var document;
+ return Zotero.Promise.coroutine(function* execCommand(agent, command, docId) {
+ var document, session;
- if(inProgress) {
- Zotero.Integration.activate();
+ if (inProgress) {
+ Zotero.Utilities.Internal.activate();
if(Zotero.Integration.currentWindow && !Zotero.Integration.currentWindow.closed) {
Zotero.Integration.currentWindow.focus();
}
@@ -266,16 +216,29 @@ Zotero.Integration = new function() {
return;
}
inProgress = true;
-
- // Check integration component versions
- return _checkPluginVersions().then(function() {
+ Zotero.debug(`Integration: ${agent}-${command}${docId ? `:'${docId}'` : ''} invoked`)
+
+ var startTime = (new Date()).getTime();
+
+ // Try to execute the command; otherwise display an error in alert service or word processor
+ // (depending on what is possible)
+ try {
+ // Word for windows throws RPC_E_CANTCALLOUT_ININPUTSYNCCALL if we invoke an OLE call in the
+ // current event loop (which.. who would have guessed would be the case?)
+ yield Zotero.Promise.delay();
var application = Zotero.Integration.getApplication(agent, command, docId);
- // 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 Zotero.Promise.resolve((new Zotero.Integration.Document(application, document))[command]());
- }).catch(function(e) {
+ Zotero.Integration.currentDoc = document = (application.getDocument && docId ? application.getDocument(docId) : application.getActiveDocument());
+ Zotero.Integration.currentSession = session = yield Zotero.Integration.getSession(application, document);
+ // TODO: this is pretty awful
+ session.fields = new Zotero.Integration.Fields(session, document);
+ session._doc = document;
+ // TODO: figure this out
+ // Zotero.Notifier.trigger('delete', 'collection', 'document');
+ yield (new Zotero.Integration.Interface(application, document, session))[command]();
+ document.setDocumentData(session.data.serialize());
+ }
+ catch (e) {
if(!(e instanceof Zotero.Exception.UserCancelled)) {
try {
var displayError = null;
@@ -303,7 +266,7 @@ Zotero.Integration = new function() {
}
if(showErrorInFirefox) {
- Zotero.Integration.activate();
+ Zotero.Utilities.Internal.activate();
Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService)
.alert(null, Zotero.getString("integration.error.title"), displayError);
@@ -312,10 +275,15 @@ Zotero.Integration = new function() {
} finally {
Zotero.logError(e);
}
+ } else {
+ // If user cancels we should still write the currently assigned session ID
+ document.setDocumentData(session.data.serialize());
}
- })
- .finally(function() {
- if(document) {
+ }
+ finally {
+ var diff = ((new Date()).getTime() - startTime)/1000;
+ Zotero.debug(`Integration: ${agent}-${command}${docId ? `:'${docId}'` : ''} complete in ${diff}s`)
+ if (document) {
try {
document.cleanup();
document.activate();
@@ -338,374 +306,13 @@ Zotero.Integration = new function() {
});
}
- inProgress = Zotero.Integration.currentWindow = false;
- });
- };
+ inProgress =
+ Zotero.Integration.currentDoc =
+ Zotero.Integration.currentWindow = false;
+ }
+ });
};
- /**
- * Activates Firefox
- */
- this.activate = function(win) {
- if(Zotero.isMac) {
- const BUNDLE_IDS = {
- "Zotero":"org.zotero.zotero",
- "Firefox":"org.mozilla.firefox",
- "Aurora":"org.mozilla.aurora",
- "Nightly":"org.mozilla.nightly"
- };
-
- if(win) {
- Components.utils.import("resource://gre/modules/ctypes.jsm");
- win.focus();
-
- if(!_carbon) {
- _carbon = ctypes.open("/System/Library/Frameworks/Carbon.framework/Carbon");
- /*
- * struct ProcessSerialNumber {
- * unsigned long highLongOfPSN;
- * unsigned long lowLongOfPSN;
- * };
- */
- ProcessSerialNumber = new ctypes.StructType("ProcessSerialNumber",
- [{"highLongOfPSN":ctypes.uint32_t}, {"lowLongOfPSN":ctypes.uint32_t}]);
-
- /*
- * OSStatus SetFrontProcessWithOptions (
- * const ProcessSerialNumber *inProcess,
- * OptionBits inOptions
- * );
- */
- SetFrontProcessWithOptions = _carbon.declare("SetFrontProcessWithOptions",
- ctypes.default_abi, ctypes.int32_t, ProcessSerialNumber.ptr,
- ctypes.uint32_t);
- }
-
- var psn = new ProcessSerialNumber();
- psn.highLongOfPSN = 0;
- psn.lowLongOfPSN = 2 // kCurrentProcess
-
- win.addEventListener("load", function() {
- var res = SetFrontProcessWithOptions(
- psn.address(),
- 1 // kSetFrontProcessFrontWindowOnly = (1 << 0)
- );
- }, false);
- } else {
- _executeAppleScript('tell application id "'+BUNDLE_IDS[Zotero.appName]+'" to activate');
- }
- } else if(!Zotero.isWin && win) {
- Components.utils.import("resource://gre/modules/ctypes.jsm");
-
- if(_x11 === false) return;
- if(!_x11) {
- try {
- _x11 = ctypes.open("libX11.so.6");
- } catch(e) {
- try {
- var libName = ctypes.libraryName("X11");
- } catch(e) {
- _x11 = false;
- Zotero.debug("Integration: Could not get libX11 name; not activating");
- Zotero.logError(e);
- return;
- }
-
- try {
- _x11 = ctypes.open(libName);
- } catch(e) {
- _x11 = false;
- Zotero.debug("Integration: Could not open "+libName+"; not activating");
- Zotero.logError(e);
- return;
- }
- }
-
- X11Atom = ctypes.unsigned_long;
- X11Bool = ctypes.int;
- X11Display = new ctypes.StructType("Display");
- X11Window = ctypes.unsigned_long;
- X11Status = ctypes.int;
-
- /*
- * typedef struct {
- * int type;
- * unsigned long serial; / * # of last request processed by server * /
- * Bool send_event; / * true if this came from a SendEvent request * /
- * Display *display; / * Display the event was read from * /
- * Window window;
- * Atom message_type;
- * int format;
- * union {
- * char b[20];
- * short s[10];
- * long l[5];
- * } data;
- * } XClientMessageEvent;
- */
- XClientMessageEvent = new ctypes.StructType("XClientMessageEvent",
- [
- {"type":ctypes.int},
- {"serial":ctypes.unsigned_long},
- {"send_event":X11Bool},
- {"display":X11Display.ptr},
- {"window":X11Window},
- {"message_type":X11Atom},
- {"format":ctypes.int},
- {"l0":ctypes.long},
- {"l1":ctypes.long},
- {"l2":ctypes.long},
- {"l3":ctypes.long},
- {"l4":ctypes.long}
- ]
- );
-
- /*
- * Status XFetchName(
- * Display* display,
- * Window w,
- * char** window_name_return
- * );
- */
- XFetchName = _x11.declare("XFetchName", ctypes.default_abi, X11Status,
- X11Display.ptr, X11Window, ctypes.char.ptr.ptr);
-
- /*
- * Status XQueryTree(
- * Display* display,
- * Window w,
- * Window* root_return,
- * Window* parent_return,
- * Window** children_return,
- * unsigned int* nchildren_return
- * );
- */
- XQueryTree = _x11.declare("XQueryTree", ctypes.default_abi, X11Status,
- X11Display.ptr, X11Window, X11Window.ptr, X11Window.ptr, X11Window.ptr.ptr,
- ctypes.unsigned_int.ptr);
-
- /*
- * int XFree(
- * void* data
- * );
- */
- XFree = _x11.declare("XFree", ctypes.default_abi, ctypes.int, ctypes.voidptr_t);
-
- /*
- * Display *XOpenDisplay(
- * _Xconst char* display_name
- * );
- */
- XOpenDisplay = _x11.declare("XOpenDisplay", ctypes.default_abi, X11Display.ptr,
- ctypes.char.ptr);
-
- /*
- * int XCloseDisplay(
- * Display* display
- * );
- */
- XCloseDisplay = _x11.declare("XCloseDisplay", ctypes.default_abi, ctypes.int,
- X11Display.ptr);
-
- /*
- * int XFlush(
- * Display* display
- * );
- */
- XFlush = _x11.declare("XFlush", ctypes.default_abi, ctypes.int, X11Display.ptr);
-
- /*
- * Window XDefaultRootWindow(
- * Display* display
- * );
- */
- XDefaultRootWindow = _x11.declare("XDefaultRootWindow", ctypes.default_abi,
- X11Window, X11Display.ptr);
-
- /*
- * Atom XInternAtom(
- * Display* display,
- * _Xconst char* atom_name,
- * Bool only_if_exists
- * );
- */
- XInternAtom = _x11.declare("XInternAtom", ctypes.default_abi, X11Atom,
- X11Display.ptr, ctypes.char.ptr, X11Bool);
-
- /*
- * Status XSendEvent(
- * Display* display,
- * Window w,
- * Bool propagate,
- * long event_mask,
- * XEvent* event_send
- * );
- */
- XSendEvent = _x11.declare("XSendEvent", ctypes.default_abi, X11Status,
- X11Display.ptr, X11Window, X11Bool, ctypes.long, XClientMessageEvent.ptr);
-
- /*
- * int XMapRaised(
- * Display* display,
- * Window w
- * );
- */
- XMapRaised = _x11.declare("XMapRaised", ctypes.default_abi, ctypes.int,
- X11Display.ptr, X11Window);
-
- /*
- * extern int XGetWindowProperty(
- * Display* display,
- * Window w,
- * Atom property,
- * long long_offset,
- * long long_length,
- * Bool delete,
- * Atom req_type,
- * Atom* actual_type_return,
- * int* actual_format_return,
- * unsigned long* nitems_return,
- * unsigned long* bytes_after_return,
- * unsigned char** prop_return
- * );
- */
- XGetWindowProperty = _x11.declare("XGetWindowProperty", ctypes.default_abi,
- ctypes.int, X11Display.ptr, X11Window, X11Atom, ctypes.long, ctypes.long,
- X11Bool, X11Atom, X11Atom.ptr, ctypes.int.ptr, ctypes.unsigned_long.ptr,
- ctypes.unsigned_long.ptr, ctypes.char.ptr.ptr);
-
-
- _x11Display = XOpenDisplay(null);
- if(!_x11Display) {
- Zotero.debug("Integration: Could not open display; not activating");
- _x11 = false;
- return;
- }
-
- Zotero.addShutdownListener(function() {
- XCloseDisplay(_x11Display);
- });
-
- _x11RootWindow = XDefaultRootWindow(_x11Display);
- if(!_x11RootWindow) {
- Zotero.debug("Integration: Could not get root window; not activating");
- _x11 = false;
- return;
- }
- }
-
- win.addEventListener("load", function() {
- var intervalID;
- intervalID = win.setInterval(function() {
- _X11BringToForeground(win, intervalID);
- }, 50);
- }, false);
- }
- }
-
- /**
- * Get a property from an X11 window
- */
- function _X11GetProperty(win, propertyName, propertyType) {
- Components.utils.import("resource://gre/modules/ctypes.jsm");
-
- var returnType = new X11Atom(),
- returnFormat = new ctypes.int(),
- nItemsReturned = new ctypes.unsigned_long(),
- nBytesAfterReturn = new ctypes.unsigned_long(),
- data = new ctypes.char.ptr();
- if(!XGetWindowProperty(_x11Display, win, XInternAtom(_x11Display, propertyName, 0), 0, 1024,
- 0, propertyType, returnType.address(), returnFormat.address(),
- nItemsReturned.address(), nBytesAfterReturn.address(), data.address())) {
- var nElements = ctypes.cast(nItemsReturned, ctypes.unsigned_int).value;
- if(nElements) return [data, nElements];
- }
- return null;
- }
-
- /**
- * Bring a window to the foreground by interfacing directly with X11
- */
- function _X11BringToForeground(win, intervalID) {
- var windowTitle = win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIBaseWindow).title;
-
- var x11Window = _X11FindWindow(_x11RootWindow, windowTitle);
- if(!x11Window) return;
- win.clearInterval(intervalID);
-
- var event = new XClientMessageEvent();
- event.type = 33; /* ClientMessage*/
- event.serial = 0;
- event.send_event = 1;
- event.message_type = XInternAtom(_x11Display, "_NET_ACTIVE_WINDOW", 0);
- event.display = _x11Display;
- event.window = x11Window;
- event.format = 32;
- event.l0 = 2;
- var mask = 1<<20 /* SubstructureRedirectMask */ | 1<<19 /* SubstructureNotifyMask */;
-
- if(XSendEvent(_x11Display, _x11RootWindow, 0, mask, event.address())) {
- XMapRaised(_x11Display, x11Window);
- XFlush(_x11Display);
- Zotero.debug("Integration: Activated successfully");
- } else {
- Zotero.debug("Integration: An error occurred activating the window");
- }
- }
-
- /**
- * Find an X11 window given a name
- */
- function _X11FindWindow(w, searchName) {
- Components.utils.import("resource://gre/modules/ctypes.jsm");
-
- var res = _X11GetProperty(w, "_NET_CLIENT_LIST", 33 /** XA_WINDOW **/)
- || _X11GetProperty(w, "_WIN_CLIENT_LIST", 6 /** XA_CARDINAL **/);
- if(!res) return false;
-
- var nClients = res[1],
- clientList = ctypes.cast(res[0], X11Window.array(nClients).ptr).contents,
- foundName = new ctypes.char.ptr();
- for(var i=0; i DATA_VERSION) && data.dataVersion != 4) {
+ throw new Zotero.Exception.Alert("integration.error.newerDocumentVersion",
+ [data.zoteroVersion, Zotero.version], "integration.error.title");
+ }
+
+ if (data.prefs.fieldType !== app.primaryFieldType
+ && data.prefs.fieldType !== app.secondaryFieldType) {
+ throw new Zotero.Exception.Alert("integration.error.fieldTypeMismatch",
+ [], "integration.error.title");
+ }
+
+ session = Zotero.Integration.sessions[data.sessionID];
+ }
+ if (!session) {
+ session = new Zotero.Integration.Session(doc, app);
+ session.reload = true;
+ }
+ try {
+ yield session.setData(data);
+ } catch(e) {
+ // make sure style is defined
+ if (e instanceof Zotero.Exception.Alert && e.name === "integration.error.invalidStyle") {
+ if (data.style.styleID) {
+ session.reload = true;
+ let trustedSource = /^https?:\/\/(www\.)?(zotero\.org|citationstyles\.org)/.test(data.style.styleID);
+ let errorString = Zotero.getString("integration.error.styleMissing", data.style.styleID);
+ if (trustedSource ||
+ doc.displayAlert(errorString, DIALOG_ICON_WARNING, DIALOG_BUTTONS_YES_NO)) {
+
+ let installed = false;
+ try {
+ yield Zotero.Styles.install(
+ {url: data.style.styleID}, data.style.styleID, true
+ );
+ installed = true;
+ }
+ catch (e) {
+ me._doc.displayAlert(
+ Zotero.getString(
+ 'integration.error.styleNotFound', data.style.styleID
+ ),
+ DIALOG_ICON_WARNING,
+ DIALOG_BUTTONS_OK
+ );
+ }
+ if (installed) {
+ yield session.setData(data, true);
+ }
+ return session;
+ }
+ }
+ yield session.setDocPrefs();
+ } else {
+ throw e;
+ }
+ }
+ return session;
+ });
+
}
/**
* An exception thrown when a document contains an item that no longer exists in the current document.
- *
- * @param reselectKeys {Array} Keys representing the missing item
- * @param reselectKeyType {Integer} The type of the keys (see RESELECT_KEY_* constants)
- * @param citationIndex {Integer} The index of the missing item within the citation cluster
- * @param citationLength {Integer} The number of items cited in this citation cluster
*/
-Zotero.Integration.MissingItemException = function(reselectKeys, reselectKeyType, citationIndex, citationLength) {
- this.reselectKeys = reselectKeys;
- this.reselectKeyType = reselectKeyType;
- this.citationIndex = citationIndex;
- this.citationLength = citationLength;
-}
+Zotero.Integration.MissingItemException = function(item) {this.item = item;};
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 Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document update"));
- } else if(result == 1) { // No
- for (let reselectKey of 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();
- return fieldGetter._processFields(fieldIndex);
- });
- }
- }
-}
-
-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, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO_CANCEL);
- if(result == 0) {
- return Zotero.Promise.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();
- });
- }
- }
+ "message":`An item in this document is missing from your Zotero library.}`,
+ "toString":function() { return this.message + `\n ${JSON.stringify(this.item)}` }
};
-/**
- * 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.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, DIALOG_ICON_CAUTION, DIALOG_BUTTONS_OK_CANCEL);
- if(result == 0) {
- return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("clearing corrupted bibliography"));
- } else {
- this.fieldGetter._bibliographyData = "";
- this.fieldGetter._session.bibliographyHasChanged = true;
- this.fieldGetter._session.bibliographyDataHasChanged = true;
- return Zotero.Promise.resolve(true);
- }
- }
-};
-
-const INTEGRATION_TYPE_ITEM = 1;
-const INTEGRATION_TYPE_BIBLIOGRAPHY = 2;
-const INTEGRATION_TYPE_TEMP = 3;
-
-// Placeholder for an empty bibliography
-const BIBLIOGRAPHY_PLACEHOLDER = "{Bibliography}";
+Zotero.Integration.NO_ACTION = 0;
+Zotero.Integration.UPDATE = 1;
+Zotero.Integration.DELETE = 2;
/**
* All methods for interacting with a document
* @constructor
*/
-Zotero.Integration.Document = function(app, doc) {
+Zotero.Integration.Interface = function(app, doc, session) {
this._app = app;
this._doc = doc;
+ this._session = session;
}
-/**
- * Creates a new session
- * @param data {Zotero.Integration.DocumentData} Document data for new session
- * @return {Zotero.Integration.Session}
- */
-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
- * @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 = Zotero.Promise.coroutine(function *(require, dontRunSetDocPrefs) {
- var dataString = this._doc.getDocumentData(),
- data,
- me = this;
-
- if(dataString) {
- try {
- data = new Zotero.Integration.DocumentData(dataString);
- } catch(e) {};
- }
-
- // If no data or corrupted data, show doc prefs window again
- if (!data || !data.prefs || !data.prefs.fieldType) {
- var haveFields = false;
- data = new Zotero.Integration.DocumentData();
-
- if(require) {
- // check to see if fields already exist
- for (let fieldType of [this._app.primaryFieldType, this._app.secondaryFieldType]) {
- var fields = this._doc.getFields(this._app.primaryFieldType);
- if(fields.hasMoreElements()) {
- data.prefs.fieldType = this._app.primaryFieldType;
- haveFields = true;
- break;
- }
- }
-
- // if no fields, throw an error
- if(!haveFields) {
- return Zotero.Promise.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");
- }
- }
-
- // Set doc prefs if no data string yet
- this._session = this._createNewSession(data);
- yield this._session.setData(data);
- if(dontRunSetDocPrefs) return Zotero.Promise.resolve(false);
-
- 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.serialize());
-
- if(haveFields) {
- me._session.reload = true;
- }
-
- return me._session;
- });
- } else {
- if(data.dataVersion < DATA_VERSION) {
- if(data.dataVersion == 1
- && data.prefs.fieldType == "Field"
- && this._app.primaryFieldType == "ReferenceMark") {
- // Converted OOo docs use ReferenceMarks, not fields
- data.prefs.fieldType = "ReferenceMark";
- }
-
- var warning = this._doc.displayAlert(Zotero.getString("integration.upgradeWarning", [Zotero.clientName, '5.0']),
- DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL);
- if(!warning) {
- return Zotero.Promise.reject(new Zotero.Exception.UserCancelled("document upgrade"));
- }
- // Don't throw for version 4(JSON) during the transition from 4.0 to 5.0
- } else if((data.dataVersion > DATA_VERSION) && data.dataVersion != 4) {
- return Zotero.Promise.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) {
- return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.fieldTypeMismatch",
- [], "integration.error.title"));
- }
-
- if (Zotero.Integration.sessions[data.sessionID]) {
- // If communication occured with this document since restart
- this._session = Zotero.Integration.sessions[data.sessionID];
- } else {
- // Document has zotero data, but has not communicated since Zotero restart
- this._session = this._createNewSession(data);
- try {
- yield this._session.setData(data);
- // this._createNewSession() updates sessionID, which we need to store back into the doc
- this._doc.setDocumentData(me._session.data.serialize())
- } catch(e) {
- // make sure style is defined
- 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)) {
-
- let installed = false;
- try {
- yield Zotero.Styles.install(
- {url: data.style.styleID}, data.style.styleID, true
- );
- installed = true;
- }
- catch (e) {
- me._doc.displayAlert(
- Zotero.getString(
- 'integration.error.styleNotFound', data.style.styleID
- ),
- DIALOG_ICON_WARNING,
- DIALOG_BUTTONS_OK
- );
- }
- if (installed) {
- yield this._session.setData(data, true);
- return Zotero.Promise.resolve(this._session);
- }
- }
- }
- return this._session.setDocPrefs(this._doc, this._app.primaryFieldType,
- this._app.secondaryFieldType).then(function(status) {
- me._doc.setDocumentData(me._session.data.serialize());
- me._session.reload = true;
- return me._session;
- });
- } else {
- return Zotero.Promise.reject(e);
- }
- }
-
- this._session.reload = true;
- }
- return Zotero.Promise.resolve(this._session);
- }
-});
/**
* Adds a citation to the current document.
* @return {Promise}
*/
-Zotero.Integration.Document.prototype.addCitation = function() {
- var me = this;
- return this._getSession(false, false).then(function() {
- return (new Zotero.Integration.Fields(me._session, me._doc)).addEditCitation(null);
- });
-}
+Zotero.Integration.Interface.prototype.addCitation = Zotero.Promise.coroutine(function* () {
+ yield this._session.init(false, false);
+
+ let [idx, field, citation] = yield this._session.fields.addEditCitation(null);
+ yield this._session.addCitation(idx, field.getNoteIndex(), citation);
+
+ if (this._session.data.prefs.delayCitationUpdates) {
+ return this._session.writeDelayedCitation(idx, field, citation);
+ } else {
+ return this._session.fields.updateDocument(FORCE_CITATIONS_FALSE, false, false);
+ }
+});
/**
* Edits the citation at the cursor position.
* @return {Promise}
*/
-Zotero.Integration.Document.prototype.editCitation = function() {
- var me = this;
- return this._getSession(true, false).then(function() {
- var field = me._doc.cursorInField(me._session.data.prefs['fieldType']);
- if(!field) {
- throw new Zotero.Exception.Alert("integration.error.notInCitation", [],
- "integration.error.title");
- }
-
- return (new Zotero.Integration.Fields(me._session, me._doc)).addEditCitation(field);
- });
-}
+Zotero.Integration.Interface.prototype.editCitation = Zotero.Promise.coroutine(function* () {
+ var docField = this._doc.cursorInField(this._session.data.prefs['fieldType']);
+ if(!docField) {
+ throw new Zotero.Exception.Alert("integration.error.notInCitation", [],
+ "integration.error.title");
+ }
+ return this.addEditCitation(docField);
+});
/**
* Edits the citation at the cursor position if one exists, or else adds a new one.
* @return {Promise}
*/
-Zotero.Integration.Document.prototype.addEditCitation = function() {
- var me = this;
- return this._getSession(false, false).then(function() {
- var field = me._doc.cursorInField(me._session.data.prefs['fieldType']);
- return (new Zotero.Integration.Fields(me._session, me._doc)).addEditCitation(field);
- });
-}
+Zotero.Integration.Interface.prototype.addEditCitation = Zotero.Promise.coroutine(function* (docField) {
+ yield this._session.init(false, false);
+ docField = docField || this._doc.cursorInField(this._session.data.prefs['fieldType']);
+
+ let [idx, field, citation] = yield this._session.fields.addEditCitation(docField);
+ yield this._session.addCitation(idx, field.getNoteIndex(), citation);
+ if (this._session.data.prefs.delayCitationUpdates) {
+ return this._session.writeDelayedCitation(idx, field, citation);
+ } else {
+ return this._session.fields.updateDocument(FORCE_CITATIONS_FALSE, false, false);
+ }
+});
/**
* Adds a bibliography to the current document.
* @return {Promise}
*/
-Zotero.Integration.Document.prototype.addBibliography = function() {
+Zotero.Integration.Interface.prototype.addBibliography = Zotero.Promise.coroutine(function* () {
var me = this;
- return this._getSession(true, false).then(function() {
- // Make sure we can have a bibliography
- if(!me._session.data.style.hasBibliography) {
- throw new Zotero.Exception.Alert("integration.error.noBibliography", [],
- "integration.error.title");
- }
-
- var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc, Zotero.Integration.onFieldError);
- return fieldGetter.addField().then(function(field) {
- field.setCode("BIBL");
- return fieldGetter.updateSession().then(function() {
- return fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, true, false);
- });
- });
- });
-}
+ yield this._session.init(true, false);
+ // Make sure we can have a bibliography
+ if(!me._session.data.style.hasBibliography) {
+ throw new Zotero.Exception.Alert("integration.error.noBibliography", [],
+ "integration.error.title");
+ }
+
+ let field = new Zotero.Integration.BibliographyField(yield this._session.fields.addField());
+ field.clearCode();
+ var citationsMode = FORCE_CITATIONS_FALSE;
+ if(this._session.data.prefs.delayCitationUpdates) {
+ // Refreshes citeproc state before proceeding
+ this._session.reload = true;
+ citationsMode = FORCE_CITATIONS_REGENERATE;
+ }
+ yield this._session.fields.updateSession(citationsMode);
+ yield this._session.fields.updateDocument(citationsMode, true, false);
+})
/**
* Edits bibliography metadata.
* @return {Promise}
*/
-Zotero.Integration.Document.prototype.editBibliography = function() {
+Zotero.Integration.Interface.prototype.editBibliography = Zotero.Promise.coroutine(function*() {
// Make sure we have a bibliography
- var me = this, fieldGetter;
- return this._getSession(true, false).then(function() {
- fieldGetter = new Zotero.Integration.Fields(me._session, me._doc, Zotero.Integration.onFieldError);
- 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;
- }
- }
-
- if(!haveBibliography) {
- throw new Zotero.Exception.Alert("integration.error.mustInsertBibliography",
- [], "integration.error.title");
- }
- return fieldGetter.updateSession();
- }).then(function() {
- return me._session.editBibliography(me._doc);
- }).then(function() {
- return fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, true, false);
- });
-}
-
-
-Zotero.Integration.Document.prototype.addEditBibliography = Zotero.Promise.coroutine(function *() {
- // Check if we have a bibliography
- var session = yield this._getSession(true, false);
+ yield this._session.init(true, false);
+ var fields = yield this._session.fields.get();
- if (!session.data.style.hasBibliography) {
- throw new Zotero.Exception.Alert("integration.error.noBibliography", [],
- "integration.error.title");
- }
-
- var fieldGetter = new Zotero.Integration.Fields(session, this._doc, Zotero.Integration.onFieldError);
- var fields = yield fieldGetter.get();
-
- 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;
+ var bibliographyField;
+ for (let i = fields.length-1; i >= 0; i--) {
+ let field = Zotero.Integration.Field.loadExisting(fields[i]);
+ if (field.type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
+ bibliographyField = field;
break;
}
}
- if (haveBibliography) {
- yield fieldGetter.updateSession();
- yield session.editBibliography(this._doc);
- } else {
- var field = yield fieldGetter.addField();
- field.setCode("BIBL");
- yield fieldGetter.updateSession();
+ if(!bibliographyField) {
+ throw new Zotero.Exception.Alert("integration.error.mustInsertBibliography",
+ [], "integration.error.title");
}
- return fieldGetter.updateDocument(FORCE_CITATIONS_FALSE, true, false);
+ let bibliography = new Zotero.Integration.Bibliography(bibliographyField);
+ var citationsMode = FORCE_CITATIONS_FALSE;
+ if(this._session.data.prefs.delayCitationUpdates) {
+ // Refreshes citeproc state before proceeding
+ this._session.reload = true;
+ citationsMode = FORCE_CITATIONS_REGENERATE;
+ }
+ yield this._session.fields.updateSession(citationsMode);
+ yield this._session.editBibliography(bibliography);
+ yield this._session.fields.updateDocument(citationsMode, true, false);
+});
+
+
+Zotero.Integration.Interface.prototype.addEditBibliography = Zotero.Promise.coroutine(function *() {
+ // Check if we have a bibliography
+ yield this._session.init(true, false);
+
+ if (!this._session.data.style.hasBibliography) {
+ throw new Zotero.Exception.Alert("integration.error.noBibliography", [],
+ "integration.error.title");
+ }
+
+ var fields = yield this._session.fields.get();
+
+ var bibliographyField;
+ for (let i = fields.length-1; i >= 0; i--) {
+ let field = Zotero.Integration.Field.loadExisting(fields[i]);
+ if (field.type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
+ bibliographyField = field;
+ break;
+ }
+ }
+
+ var newBibliography = !bibliographyField;
+ if (!bibliographyField) {
+ bibliographyField = new Zotero.Integration.BibliographyField(yield this._session.fields.addField());
+ bibliographyField.clearCode();
+ }
+
+ let bibliography = new Zotero.Integration.Bibliography(bibliographyField);
+ var citationsMode = FORCE_CITATIONS_FALSE;
+ if(this._session.data.prefs.delayCitationUpdates) {
+ // Refreshes citeproc state before proceeding
+ this._session.reload = true;
+ citationsMode = FORCE_CITATIONS_REGENERATE;
+ }
+ yield this._session.fields.updateSession(citationsMode);
+ if (!newBibliography) yield this._session.editBibliography(bibliography);
+ yield this._session.fields.updateDocument(citationsMode, true, false);
});
/**
* Updates the citation data for all citations and bibliography entries.
* @return {Promise}
*/
-Zotero.Integration.Document.prototype.refresh = function() {
- var me = this;
- 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, Zotero.Integration.onFieldError);
- return fieldGetter.updateSession().then(function() {
- return fieldGetter.updateDocument(FORCE_CITATIONS_REGENERATE, true, false);
- });
- });
+Zotero.Integration.Interface.prototype.refresh = async function() {
+ await this._session.init(true, false)
+
+ this._session.reload = this._session.data.prefs.delayCitationUpdates;
+ await this._session.fields.updateSession(FORCE_CITATIONS_REGENERATE)
+ await this._session.fields.updateDocument(FORCE_CITATIONS_REGENERATE, true, false);
}
/**
* Deletes field codes.
* @return {Promise}
*/
-Zotero.Integration.Document.prototype.removeCodes = function() {
+Zotero.Integration.Interface.prototype.removeCodes = Zotero.Promise.coroutine(function* () {
var me = this;
- return this._getSession(true, false).then(function() {
- var fieldGetter = new Zotero.Integration.Fields(me._session, me._doc);
- return fieldGetter.get()
- }).then(function(fields) {
- var result = me._doc.displayAlert(Zotero.getString("integration.removeCodesWarning"),
- DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL);
- if(result) {
- for(var i=fields.length-1; i>=0; i--) {
- fields[i].removeCode();
- }
+ yield this._session.init(true, false)
+ let fields = yield this._session.fields.get()
+ var result = me._doc.displayAlert(Zotero.getString("integration.removeCodesWarning"),
+ DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL);
+ if (result) {
+ for(var i=fields.length-1; i>=0; i--) {
+ fields[i].removeCode();
}
- });
-}
+ }
+})
/**
* Displays a dialog to set document preferences (style, footnotes/endnotes, etc.)
* @return {Promise}
*/
-Zotero.Integration.Document.prototype.setDocPrefs = function() {
- var me = this,
- fieldGetter,
- oldData;
- return this._getSession(false, true).then(function(haveSession) {
- fieldGetter = new Zotero.Integration.Fields(me._session, me._doc, Zotero.Integration.onFieldError);
- 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
- return setDocPrefs();
- } else {
- // Can get fields while dialog is open
- return Zotero.Promise.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.serialize());
-
- // 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;
+Zotero.Integration.Interface.prototype.setDocPrefs = Zotero.Promise.coroutine(function* () {
+ var oldData;
+ let haveSession = yield this._session.init(false, true);
- // 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 {});
- this._session.updateIndices = {};
- this._session.updateItemIDs = {};
- this._session.citationText = {};
- this._session.bibliographyHasChanged = false;
+ this._session.restoreProcessorState();
delete this._session.reload;
}
});
@@ -1562,59 +879,42 @@ Zotero.Integration.Fields.prototype.updateSession = Zotero.Promise.coroutine(fun
/**
* Keep processing fields until all have been processed
*/
-Zotero.Integration.Fields.prototype._processFields = Zotero.Promise.coroutine(function* (i) {
- if(!i) i = 0;
+Zotero.Integration.Fields.prototype._processFields = Zotero.Promise.coroutine(function* () {
+ if (!this._fields) {
+ throw new Error("_processFields called without fetching fields first");
+ }
- for(var n = this._fields.length; i {});
+ this._session.timer = new Zotero.Integration.Timer();
+ this._session.timer.start();
- yield Zotero.Promise.each(
- this._updateDocument(forceCitations, forceBibliography, ignoreCitationChanges), () => {});
+ yield this._session._updateCitations()
+ yield this._updateDocument(forceCitations, forceBibliography, ignoreCitationChanges)
+
+ var diff = this._session.timer.stop();
+ this._session.timer = null;
+ Zotero.debug(`Integration: updateDocument complete in ${diff}s`)
+ // If the update takes longer than 5s suggest delaying citation updates
+ if (diff > DELAY_CITATIONS_PROMPT_TIMEOUT && !this._session.data.prefs.dontAskDelayCitationUpdates && !this._session.data.prefs.delayCitationUpdates) {
+ this._doc.activate();
+ var result = this._session.displayAlert(Zotero.getString('integration.delayCitationUpdates.alert'),
+ DIALOG_ICON_WARNING, DIALOG_BUTTONS_YES_NO_CANCEL);
+ if (result == 2) {
+ this._session.data.prefs.delayCitationUpdates = true;
+ }
+ if (result) {
+ this._session.data.prefs.dontAskDelayCitationUpdates = true;
+ // yield this._session.setDocPrefs(true);
+ }
+ }
});
/**
@@ -1643,7 +958,7 @@ Zotero.Integration.Fields.prototype.updateDocument = Zotero.Promise.coroutine(fu
* @param {Boolean} [ignoreCitationChanges] Whether to ignore changes to citations that have been
* modified since they were created, instead of showing a warning
*/
-Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations, forceBibliography,
+Zotero.Integration.Fields.prototype._updateDocument = async function(forceCitations, forceBibliography,
ignoreCitationChanges) {
if(this.progressCallback) {
var nFieldUpdates = Object.keys(this._session.updateIndices).length;
@@ -1660,90 +975,88 @@ Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations,
} catch(e) {
Zotero.logError(e);
}
- yield;
}
+ // Jump to next event loop step for UI updates
+ await Zotero.Promise.delay();
var citation = this._session.citationsByIndex[i];
- var field = this._fields[i];
+ let citationField = citation._field;
- // 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];
+ if (!citation.properties.dontUpdate) {
+ var formattedCitation = citation.properties.formattedCitation && citation.properties.custom
+ ? citation.properties.custom : citation.text;
+ var plainCitation = citation.properties.plainCitation && citationField.getText();
- 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
- Zotero.debug("[_updateDocument] Attempting to update manually modified citation.\n"
- + "Original: " + citation.properties.plainCitation + "\n"
- + "Current: " + plainCitation
- );
- field.select();
- var result = this._doc.displayAlert(
- Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"),
- DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO);
- if(result) {
- citation.properties.dontUpdate = true;
- }
+ // If we're not specifically *not* trying to regen text
+ if (forceCitations != FORCE_CITATIONS_FALSE
+ // Or metadata has changed thus changing the formatted citation
+ || (citation.properties.formattedCitation !== formattedCitation)
+ // Or we shouldn't ignore citation changes and the citation text has changed
+ || (!ignoreCitationChanges && plainCitation !== citation.properties.plainCitation)) {
+
+ if (plainCitation !== citation.properties.plainCitation) {
+ // Citation manually modified; ask user if they want to save changes
+ Zotero.debug("[_updateDocument] Attempting to update manually modified citation.\n"
+ + "Original: " + citation.properties.plainCitation + "\n"
+ + "Current: " + plainCitation
+ );
+ citationField.select();
+ var result = this._session.displayAlert(
+ Zotero.getString("integration.citationChanged")+"\n\n"+Zotero.getString("integration.citationChanged.description"),
+ DIALOG_ICON_CAUTION, DIALOG_BUTTONS_YES_NO);
+ if (result) {
+ citation.properties.dontUpdate = true;
}
}
if(!citation.properties.dontUpdate) {
- field.setText(formattedCitation, isRich);
+ // Word will preserve previous text styling, so we need to force remove it
+ // for citations that were inserted with delay styling
+ if (citation.properties.formattedCitation && citation.properties.formattedCitation.includes(DELAYED_CITATION_STYLING)) {
+ isRich = citationField.setText(`${DELAYED_CITATION_STYLING_CLEAR}{${formattedCitation}}`);
+ } else {
+ isRich = citationField.setText(formattedCitation);
+ }
citation.properties.formattedCitation = formattedCitation;
- citation.properties.plainCitation = field.getText();
+ citation.properties.plainCitation = citationField.getText();
}
}
}
- var fieldCode = this._session.getCitationField(citation);
- if(fieldCode != citation.properties.field) {
- field.setCode(`ITEM CSL_CITATION ${fieldCode}`);
- if(this._session.data.prefs.fieldType === "ReferenceMark"
+ var serializedCitation = citation.serialize();
+ if (serializedCitation != citation.properties.field) {
+ citationField.setCode(serializedCitation);
+ if (this._session.data.prefs.fieldType === "ReferenceMark"
&& this._session.data.prefs.noteType != 0 && 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);
+ citationField.setText(formattedCitation, isRich);
}
}
nUpdated++;
}
// update bibliographies
- if(this._bibliographyFields.length // if bibliography exists
+ if (this._session.bibliography // 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 (let field of bibliographyFields) {
- field.setCode(`BIBL ${bibliographyData} CSL_BIBLIOGRAPHY`);
+ if (forceBibliography || this._session.bibliographyDataHasChanged) {
+ let code = this._session.bibliography.serialize();
+ for (let field of this._bibliographyFields) {
+ field.setCode(code);
}
}
// get bibliography and format as RTF
- var bib = this._session.getBibliography();
+ var bib = this._session.bibliography.getCiteprocBibliography(this._session.style);
var bibliographyText = "";
- if(bib) {
+ if (bib) {
bibliographyText = bib[0].bibstart+bib[1].join("\\\r\n")+"\\\r\n"+bib[0].bibend;
// if bibliography style not set, set it
@@ -1756,39 +1069,33 @@ Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations,
// set bibliographyStyleHasBeenSet parameter to prevent further changes
this._session.data.style.bibliographyStyleHasBeenSet = true;
- this._doc.setDocumentData(this._session.data.serialize());
}
}
// set bibliography text
- for (let field of bibliographyFields) {
+ for (let field of this._bibliographyFields) {
if(this.progressCallback) {
try {
this.progressCallback(75+(nUpdated/nFieldUpdates)*25);
} catch(e) {
Zotero.logError(e);
}
- yield;
}
+ // Jump to next event loop step for UI updates
+ await Zotero.Promise.delay();
- if(bibliographyText) {
- field.setText(bibliographyText, true);
+ if (bibliographyText) {
+ field.setText(bibliographyText);
} else {
- field.setText("{Bibliography}", false);
+ field.setText("{Bibliography}");
}
nUpdated += 5;
}
}
// 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();
- }
- }
var removeCodeFields = Object.keys(this._removeCodeFields).sort();
- for(var i=(removeCodeFields.length-1); i>=0; i--) {
+ for (var i=(removeCodeFields.length-1); i>=0; i--) {
this._fields[removeCodeFields[i]].removeCode();
}
}
@@ -1797,244 +1104,130 @@ Zotero.Integration.Fields.prototype._updateDocument = function* (forceCitations,
* Brings up the addCitationDialog, prepopulated if a citation is provided
*/
Zotero.Integration.Fields.prototype.addEditCitation = Zotero.Promise.coroutine(function* (field) {
- var newField, citation, fieldIndex, session = this._session;
+ var newField;
- // if there's already a citation, make sure we have item IDs in addition to keys
- if(field) {
- try {
- var code = field.getCode();
- } catch(e) {}
-
- if(code) {
- var [type, content] = this.getCodeTypeAndContent(code);
- if(type != INTEGRATION_TYPE_ITEM) {
- throw new Zotero.Exception.Alert("integration.error.notInCitation");
- }
-
- try {
- citation = session.unserializeCitation(content);
- } catch(e) {}
-
- if(citation) {
- try {
- yield session.lookupItems(citation);
- } catch(e) {
- if(e instanceof Zotero.Integration.MissingItemException) {
- citation.citationItems = [];
- } else {
- throw e;
- }
- }
-
- if(citation.properties.dontUpdate
- || (citation.properties.plainCitation
- && field.getText() !== citation.properties.plainCitation)) {
- this._doc.activate();
- Zotero.debug("[addEditCitation] Attempting to update manually modified citation.\n"
- + "citation.properties.dontUpdate: " + citation.properties.dontUpdate + "\n"
- + "Original: " + citation.properties.plainCitation + "\n"
- + "Current: " + field.getText()
- );
- if(!this._doc.displayAlert(Zotero.getString("integration.citationChanged.edit"),
- DIALOG_ICON_WARNING, DIALOG_BUTTONS_OK_CANCEL)) {
- throw new Zotero.Exception.UserCancelled("editing citation");
- }
- }
-
- // make sure it's going to get updated
- delete citation.properties["formattedCitation"];
- delete citation.properties["plainCitation"];
- delete citation.properties["dontUpdate"];
- }
+ if (field) {
+ field = Zotero.Integration.Field.loadExisting(field);
+
+ if (field.type != INTEGRATION_TYPE_ITEM) {
+ throw new Zotero.Exception.Alert("integration.error.notInCitation");
}
} else {
newField = true;
- field = this.addField(true);
+ field = new Zotero.Integration.CitationField(yield this.addField(true));
+ field.clearCode();
}
- var me = this;
- return Zotero.Promise.resolve(field).then(function(field) {
- if(!citation) {
- field.setCode("TEMP");
- citation = {"citationItems":[], "properties":{}};
- }
-
- var io = new Zotero.Integration.CitationEditInterface(citation, field, me, session);
-
- if(Zotero.Prefs.get("integration.useClassicAddCitationDialog")) {
- Zotero.Integration.displayDialog(me._doc,
- 'chrome://zotero/content/integration/addCitationDialog.xul', 'alwaysRaised,resizable',
- io);
- } else {
- var mode = (!Zotero.isMac && Zotero.Prefs.get('integration.keepAddCitationDialogRaised')
- ? 'popup' : 'alwaysRaised')+',resizable=false';
- Zotero.Integration.displayDialog(me._doc,
- 'chrome://zotero/content/integration/quickFormat.xul', mode, io);
- }
-
- if(newField) {
- return io.promise.catch(function(e) {
- // Try to delete new field on failure
- try {
- field.delete();
- } catch(e) {}
- throw e;
- });
- } else {
- return io.promise;
- }
- });
-});
+ var citation = new Zotero.Integration.Citation(field);
+ yield citation.prepareForEditing();
-/**
- * Citation editing functions and propertiesaccessible to quickFormat.js and addCitationDialog.js
- */
-Zotero.Integration.CitationEditInterface = function(citation, field, fieldGetter, session) {
- this.citation = citation;
- this._field = field;
- this._fieldGetter = fieldGetter;
- this._session = session;
-
- this._sessionUpdateResolveErrors = false;
- this._sessionUpdateDeferreds = [];
-
- // Needed to make this work across boundaries
- this.wrappedJSObject = this;
-
- // Determine whether citation is sortable in current style
- this.sortable = session.style.opt.sort_citations;
-
- // Citeproc-js style object for use of third-party extension
- this.style = session.style;
-
- // Start getting citation data
- this._acceptDeferred = Zotero.Promise.defer();
- this._fieldIndexPromise = fieldGetter.get().then(function(fields) {
- for(var i=0, n=fields.length; i {
return citationsByItemID[itemID]
&& citationsByItemID[itemID].length
@@ -2085,7 +1256,7 @@ Zotero.Integration.CitationEditInterface.prototype = {
});
// Sort all previously cited items at top, and all items cited later at bottom
- var fieldIndex = this._fieldIndex;
+ var fieldIndex = yield this._fieldIndexPromise;
ids.sort(function(a, b) {
var indexA = citationsByItemID[a][0].properties.zoteroIndex,
indexB = citationsByItemID[b][0].properties.zoteroIndex;
@@ -2100,22 +1271,21 @@ Zotero.Integration.CitationEditInterface.prototype = {
});
return Zotero.Cite.getItem(ids);
- }
+ }),
}
/**
* Keeps track of all session-specific variables
*/
-Zotero.Integration.Session = function(doc) {
- // holds items not in document that should be in bibliography
- this.uncitedItems = {};
- this.omittedItems = {};
+Zotero.Integration.Session = function(doc, app) {
this.embeddedItems = {};
- this.embeddedZoteroItems = {};
- this.embeddedZoteroItemsByURI = {};
- this.customBibliographyText = {};
- this.reselectedItems = {};
+ this.embeddedItemsByURI = {};
this.resetRequest(doc);
+ this.primaryFieldType = app.primaryFieldType;
+ this.secondaryFieldType = app.secondaryFieldType;
+
+ this.sessionID = Zotero.randomString();
+ Zotero.Integration.sessions[this.sessionID] = this;
}
/**
@@ -2124,22 +1294,82 @@ Zotero.Integration.Session = function(doc) {
Zotero.Integration.Session.prototype.resetRequest = function(doc) {
this.uriMap = new Zotero.Integration.URIMap(this);
- this.regenerateAll = false;
this.bibliographyHasChanged = false;
this.bibliographyDataHasChanged = false;
- this.updateItemIDs = {};
this.updateIndices = {};
this.newIndices = {};
- this.oldCitationIDs = this.citeprocCitationIDs;
-
this.citationsByItemID = {};
- this.citationsByIndex = [];
+ this.citationsByIndex = {};
this.documentCitationIDs = {};
- this.citeprocCitationIDs = {};
- this.citationText = {};
- this.doc = doc;
+ this._doc = doc;
+}
+
+/**
+ * Prepares session data and displays docPrefs dialog if needed
+ * @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 Document Preferences window if no preferences exist
+ * @return {Promise{Boolean}} true if session ready to, false if preferences dialog needs to be displayed first
+ */
+Zotero.Integration.Session.prototype.init = Zotero.Promise.coroutine(function *(require, dontRunSetDocPrefs) {
+ var data = this.data;
+ // If no data, show doc prefs window
+ if (!data.prefs.fieldType) {
+ var haveFields = false;
+ data = new Zotero.Integration.DocumentData();
+
+ if (require) {
+ // check to see if fields already exist
+ for (let fieldType of [this.primaryFieldType, this.secondaryFieldType]) {
+ var fields = this._doc.getFields(fieldType);
+ if (fields.hasMoreElements()) {
+ data.prefs.fieldType = fieldType;
+ haveFields = true;
+ break;
+ }
+ }
+
+ // if no fields, throw an error
+ if (!haveFields) {
+ return Zotero.Promise.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");
+ }
+ }
+
+ if (haveFields && !Object.keys(this.citationsByIndex).length) {
+ // We should load up the fields on the first interaction with the document, so
+ // that we can display a list of existing citations, etc.
+ yield this.fields.get();
+ yield this.fields.updateSession(FORCE_CITATIONS_FALSE);
+ }
+
+ if (dontRunSetDocPrefs) return false;
+
+ yield this.setDocPrefs();
+ }
+ if (!Object.keys(this.citationsByIndex).length) {
+ // We should load up the fields on the first interaction with the document, so
+ // that we can display a list of existing citations, etc.
+ yield this.fields.get();
+ yield this.fields.updateSession(FORCE_CITATIONS_FALSE);
+ }
+ return true;
+});
+
+Zotero.Integration.Session.prototype.displayAlert = function() {
+ if (this.timer) {
+ this.timer.pause();
+ }
+ var result = this._doc.displayAlert.apply(this._doc, arguments);
+ if (this.timer) {
+ this.timer.resume();
+ }
+ return result;
}
/**
@@ -2152,7 +1382,10 @@ Zotero.Integration.Session.prototype.resetRequest = function(doc) {
Zotero.Integration.Session.prototype.setData = Zotero.Promise.coroutine(function *(data, resetStyle) {
var oldStyle = (this.data && this.data.style ? this.data.style : false);
this.data = data;
+ this.data.sessionID = this.sessionID;
if (data.style.styleID && (!oldStyle || oldStyle.styleID != data.style.styleID || resetStyle)) {
+ // We're changing the citeproc instance, so we'll have to reinsert all citations into the registry
+ this.reload = true;
this.styleID = data.style.styleID;
try {
yield Zotero.Styles.init();
@@ -2161,7 +1394,6 @@ Zotero.Integration.Session.prototype.setData = Zotero.Promise.coroutine(function
this.style = getStyle.getCiteProc(data.style.locale, data.prefs.automaticJournalAbbreviations);
this.style.setOutputFormat("rtf");
this.styleClass = getStyle.class;
- this.dateModified = new Object();
} catch (e) {
Zotero.logError(e);
throw new Zotero.Exception.Alert("integration.error.invalidStyle");
@@ -2180,26 +1412,26 @@ Zotero.Integration.Session.prototype.setData = Zotero.Promise.coroutine(function
* if there wasn't, or rejected with Zotero.Exception.UserCancelled if the dialog was
* cancelled.
*/
-Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(function* (doc, primaryFieldType, secondaryFieldType) {
- var io = new function() {
- this.wrappedJSObject = this;
- };
+Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(function* (highlightDelayCitations=false) {
+ var io = new function() { this.wrappedJSObject = this; };
+ io.primaryFieldType = this.primaryFieldType;
+ io.secondaryFieldType = this.secondaryFieldType;
if (this.data) {
io.style = this.data.style.styleID;
io.locale = this.data.style.locale;
io.useEndnotes = this.data.prefs.noteType == 0 ? 0 : this.data.prefs.noteType-1;
io.fieldType = this.data.prefs.fieldType;
- io.primaryFieldType = primaryFieldType;
- io.secondaryFieldType = secondaryFieldType;
+ io.delayCitationUpdates = this.data.prefs.delayCitationUpdates;
+ io.dontAskDelayCitationUpdates = this.data.prefs.dontAskDelayCitationUpdates;
+ io.highlightDelayCitations = highlightDelayCitations;
io.automaticJournalAbbreviations = this.data.prefs.automaticJournalAbbreviations;
io.requireStoreReferences = !Zotero.Utilities.isEmpty(this.embeddedItems);
}
// Make sure styles are initialized for new docs
yield Zotero.Styles.init();
- yield Zotero.Integration.displayDialog(doc,
- 'chrome://zotero/content/integration/integrationDocPrefs.xul', '', io);
+ yield Zotero.Integration.displayDialog('chrome://zotero/content/integration/integrationDocPrefs.xul', '', io);
if (!io.style || !io.fieldType) {
throw new Zotero.Exception.UserCancelled("document preferences window");
@@ -2211,8 +1443,10 @@ Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(func
data.sessionID = oldData.sessionID;
data.style.styleID = io.style;
data.style.locale = io.locale;
+ data.prefs = oldData ? Object.assign({}, oldData.prefs) : {};
data.prefs.fieldType = io.fieldType;
data.prefs.automaticJournalAbbreviations = io.automaticJournalAbbreviations;
+ data.prefs.delayCitationUpdates = io.delayCitationUpdates
var forceStyleReset = oldData
&& (
@@ -2227,105 +1461,16 @@ Zotero.Integration.Session.prototype.setDocPrefs = Zotero.Promise.coroutine(func
if (!oldData || oldData.style.styleID != data.style.styleID
|| oldData.prefs.noteType != data.prefs.noteType
|| oldData.prefs.fieldType != data.prefs.fieldType
+ || (!data.prefs.delayCitationUpdates && oldData.prefs.delayCitationUpdates != data.prefs.delayCitationUpdates)
|| oldData.prefs.automaticJournalAbbreviations != data.prefs.automaticJournalAbbreviations) {
// This will cause us to regenerate all citations
- this.oldCitationIDs = {};
+ this.regenAll = true;
+ this.reload = true;
}
return oldData || null;
})
-/**
- * Reselects an item to replace a deleted item
- * @param exception {Zotero.Integration.MissingItemException}
- */
-Zotero.Integration.Session.prototype.reselectItem = function(doc, exception) {
- var io = new function() { this.wrappedJSObject = this; },
- me = this;
- io.addBorder = Zotero.isWin;
- io.singleSelection = true;
-
- 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];
-
- // add reselected item IDs to hash, so they can be used
- for (let reselectKey of exception.reselectKeys) {
- me.reselectedItems[reselectKey] = itemID;
- }
- // add old URIs to map, so that they will be included
- if(exception.reselectKeyType == RESELECT_KEY_URI) {
- me.uriMap.add(itemID, exception.reselectKeys.concat(me.uriMap.getURIsForItemID(itemID)));
- }
- // flag for update
- me.updateItemIDs[itemID] = true;
- }
- });
-}
-
-/**
- * Generates a field from a citation object
- */
-Zotero.Integration.Session.prototype.getCitationField = function(citation) {
- const saveProperties = ["custom", "unsorted", "formattedCitation", "plainCitation", "dontUpdate"];
- const saveCitationItemKeys = ["locator", "label", "suppress-author", "author-only", "prefix",
- "suffix"];
-
- var type;
- var field = [];
-
- field.push('"citationID":'+uneval(citation.citationID));
-
- var properties = JSON.stringify(citation.properties, saveProperties);
- if(properties != "{}") {
- field.push('"properties":'+properties);
- }
-
- var m = citation.citationItems.length;
- var citationItems = new Array(m);
- for(var j=0; j parseInt(i)));
-}
-
-/**
- * Refreshes updateIndices variable to include fields for modified items
- */
-Zotero.Integration.Session.prototype.updateUpdateIndices = function(regenerateAll) {
- if(regenerateAll || this.regenerateAll) {
- // update all indices
- for(var i in this.citationsByIndex) {
- this.newIndices[i] = true;
- this.updateIndices[i] = true;
- }
- } else {
- // update only item IDs
- for(var i in this.updateItemIDs) {
- if(this.citationsByItemID[i] && this.citationsByItemID[i].length) {
- for(var j=0; j [this.uriMap.getURIsForItemID(id), this.customBibliographyText[id]]);
-
-
- if(bibliographyData.uncited || bibliographyData.custom) {
- return JSON.stringify(bibliographyData);
- } else {
- return ""; // nothing
- }
-}
-
-/**
- * Returns a preview, given a citation object (whose citationItems lack item
- * and position)
- */
-Zotero.Integration.Session.prototype.previewCitation = function(citation) {
- var citationsPre, citationsPost, citationIndices;
- [citationsPre, citationsPost, citationIndices] = this._getPrePost(citation.properties.zoteroIndex);
+Zotero.Integration.Session.prototype.writeDelayedCitation = Zotero.Promise.coroutine(function* (idx, field, citation) {
try {
- return this.style.previewCitationCluster(citation, citationsPre, citationsPost, "rtf");
+ var text = citation.properties.custom || this.style.previewCitationCluster(citation, [], [], "rtf");
} catch(e) {
throw e;
}
+ text = `${DELAYED_CITATION_STYLING}{${text}}`;
+
+ // Make sure we'll prompt for manually edited citations
+ var isRich = false;
+ if(!citation.properties.dontUpdate) {
+ isRich = field.setText(text);
+
+ citation.properties.formattedCitation = text;
+ citation.properties.plainCitation = field.getText();
+ }
+
+ field.setCode(citation.serialize());
+ if (this.data.prefs.fieldType === "ReferenceMark"
+ && this.data.prefs.noteType != 0 && 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(text, isRich);
+ }
+
+ // Update bibliography with a static string
+ var fields = yield this.fields.get();
+ var bibliographyField;
+ for (let i = fields.length-1; i >= 0; i--) {
+ let field = Zotero.Integration.Field.loadExisting(fields[i]);
+ if (field.type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
+ field.setText(Zotero.getString('integration.delayCitationUpdates.bibliography'), false)
+ break;
+ }
+ }
+
+});
+
+
+Zotero.Integration.Session.prototype.getItems = function() {
+ return Zotero.Cite.getItem(Object.keys(this.citationsByItemID));
}
+
/**
* Edits integration bibliography
+ * @param {Zotero.Integration.Bibliography} bibliography
*/
-Zotero.Integration.Session.prototype.editBibliography = function(doc) {
- var bibliographyEditor = new Zotero.Integration.Session.BibliographyEditInterface(this);
- var io = new function() { this.wrappedJSObject = bibliographyEditor; }
+Zotero.Integration.Session.prototype.editBibliography = Zotero.Promise.coroutine(function *(bibliography) {
+ if (!Object.keys(this.citationsByIndex).length) {
+ throw new Error('Integration.Session.editBibliography: called without loaded citations');
+ }
+ yield bibliography.loadItemData();
+
+ var bibliographyEditor = new Zotero.Integration.BibliographyEditInterface(bibliography, this.citationsByItemID, this.style);
+
+ yield Zotero.Integration.displayDialog('chrome://zotero/content/integration/editBibliographyDialog.xul', 'resizable', bibliographyEditor);
+ if (bibliographyEditor.cancelled) throw new Zotero.Exception.UserCancelled("bibliography editing");
this.bibliographyDataHasChanged = this.bibliographyHasChanged = true;
-
- return Zotero.Integration.displayDialog(doc,
- 'chrome://zotero/content/integration/editBibliographyDialog.xul', 'resizable', io);
-}
+ this.bibliography = bibliographyEditor.bibliography;
+});
/**
* @class Interface for bibliography editor to alter document bibliography
* @constructor
* Creates a new bibliography editor interface
- * @param session {Zotero.Integration.Session}
+ * @param bibliography {Zotero.Integration.Bibliography}
*/
-Zotero.Integration.Session.BibliographyEditInterface = function(session) {
- this.session = session;
-
- this._changed = {
- "customBibliographyText":{},
- "uncitedItems":{},
- "omittedItems":{}
- }
- for(var list in this._changed) {
- for(var key in this.session[list]) {
- this._changed[list][key] = this.session[list][key];
- }
- }
-
+Zotero.Integration.BibliographyEditInterface = function(bibliography, citationsByItemID, citeproc) {
+ this.bibliography = bibliography;
+ this.citeproc = citeproc;
+ this.wrappedJSObject = this;
+ this._citationsByItemID = citationsByItemID;
this._update();
}
-/**
- * Updates stored bibliography
- */
-Zotero.Integration.Session.BibliographyEditInterface.prototype._update = function() {
- this.session.updateUncitedItems();
- this.session.style.setOutputFormat("rtf");
- this.bibliography = this.session.style.makeBibliography();
- Zotero.Cite.removeFromBibliography(this.bibliography, this.session.omittedItems);
-
- for(var i in this.bibliography[0].entry_ids) {
- if(this.bibliography[0].entry_ids[i].length != 1) continue;
- var itemID = this.bibliography[0].entry_ids[i][0];
- if(this.session.customBibliographyText[itemID]) {
- this.bibliography[1][i] = this.session.customBibliographyText[itemID];
- }
- }
-}
+Zotero.Integration.BibliographyEditInterface.prototype._update = Zotero.Promise.coroutine(function* () {
+ this.bib = this.bibliography.getCiteprocBibliography(this.citeproc);
+});
/**
* Reverts the text of an individual bibliography entry
*/
-Zotero.Integration.Session.BibliographyEditInterface.prototype.revert = function(itemID) {
- delete this.session.customBibliographyText[itemID];
- this._update();
+Zotero.Integration.BibliographyEditInterface.prototype.revert = function(itemID) {
+ delete this.bibliography.customEntryText[itemID];
+ return this._update();
}
/**
* Reverts bibliography to condition in which no edits have been made
*/
-Zotero.Integration.Session.BibliographyEditInterface.prototype.revertAll = function() {
- for(var list in this._changed) {
- this.session[list] = {};
- }
- this._update();
-}
+Zotero.Integration.BibliographyEditInterface.prototype.revertAll = Zotero.Promise.coroutine(function* () {
+ this.bibliography.customEntryText = {};
+ this.bibliography.uncitedItemIDs.clear();
+ this.bibliography.omittedItemIDs.clear();
+ return this._update();
+});
/**
* Reverts bibliography to condition before BibliographyEditInterface was opened
- * Does not run _update automatically, since this will usually only happen with a cancel request
*/
-Zotero.Integration.Session.BibliographyEditInterface.prototype.cancel = function() {
- for(var list in this._changed) {
- this.session[list] = this._changed[list];
- }
- this.session.updateUncitedItems();
-}
+Zotero.Integration.BibliographyEditInterface.prototype.cancel = function() {
+ this.cancelled = true;
+};
/**
* Checks whether a given reference is cited within the main document text
*/
-Zotero.Integration.Session.BibliographyEditInterface.prototype.isCited = function(item) {
- if(this.session.citationsByItemID[item]) return true;
+Zotero.Integration.BibliographyEditInterface.prototype.isCited = function(item) {
+ return this._citationsByItemID[item];
}
/**
* Checks whether an item ID is cited in the bibliography being edited
*/
-Zotero.Integration.Session.BibliographyEditInterface.prototype.isEdited = function(itemID) {
- if(this.session.customBibliographyText[itemID]) return true;
- return false;
+Zotero.Integration.BibliographyEditInterface.prototype.isEdited = function(itemID) {
+ return itemID in this.bibliography.customEntryText;
}
/**
* Checks whether any citations in the bibliography have been edited
*/
-Zotero.Integration.Session.BibliographyEditInterface.prototype.isAnyEdited = function() {
- for(var list in this._changed) {
- for(var a in this.session[list]) {
- return true;
- }
- }
- return false;
+Zotero.Integration.BibliographyEditInterface.prototype.isAnyEdited = function() {
+ return Object.keys(this.bibliography.customEntryText).length ||
+ this.bibliography.uncitedItemIDs.size ||
+ this.bibliography.omittedItemIDs.size;
}
/**
* Adds an item to the bibliography
*/
-Zotero.Integration.Session.BibliographyEditInterface.prototype.add = function(itemID) {
- if(this.session.omittedItems[itemID]) {
- delete this.session.omittedItems[itemID];
+Zotero.Integration.BibliographyEditInterface.prototype.add = function(itemID) {
+ if (itemID in this.bibliography.omittedItemIDs) {
+ this.bibliography.omittedItemIDs.delete(`${itemID}`);
} else {
- this.session.uncitedItems[itemID] = true;
+ this.bibliography.uncitedItemIDs.add(`${itemID}`);
}
- this._update();
+ return this._update();
}
/**
* Removes an item from the bibliography being edited
*/
-Zotero.Integration.Session.BibliographyEditInterface.prototype.remove = function(itemID) {
- if(this.session.uncitedItems[itemID]) {
- delete this.session.uncitedItems[itemID];
+Zotero.Integration.BibliographyEditInterface.prototype.remove = function(itemID) {
+ if (itemID in this.bibliography.uncitedItemIDs) {
+ this.bibliography.uncitedItemIDs.delete(`${itemID}`);
} else {
- this.session.omittedItems[itemID] = true;
+ this.bibliography.omittedItemIDs.add(`${itemID}`);
}
- this._update();
+ return this._update();
}
/**
* Sets custom bibliography text for a given item
*/
-Zotero.Integration.Session.BibliographyEditInterface.prototype.setCustomText = function(itemID, text) {
- this.session.customBibliographyText[itemID] = text;
- this._update();
+Zotero.Integration.BibliographyEditInterface.prototype.setCustomText = function(itemID, text) {
+ this.bibliography.customEntryText[itemID] = text;
+ return this._update();
}
/**
@@ -3088,7 +1767,7 @@ Zotero.Integration.DocumentData = function(string) {
this.style = {};
this.prefs = {};
this.sessionID = null;
- if(string) {
+ if (string) {
this.unserialize(string);
}
}
@@ -3114,7 +1793,8 @@ Zotero.Integration.DocumentData.prototype.serialize = function() {
}
// Otherwise default to XML for now
var prefs = "";
- for(var pref in this.prefs) {
+ for (var pref in this.prefs) {
+ if (!this.prefs[pref]) continue;
prefs += ``;
}
@@ -3247,8 +1927,8 @@ Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = Zotero.Promise.corout
var uri = uris[i];
// First try embedded URI
- if(this.session.embeddedZoteroItemsByURI[uri]) {
- embeddedItem = this.session.embeddedZoteroItemsByURI[uri];
+ if(this.session.embeddedItemsByURI[uri]) {
+ embeddedItem = this.session.embeddedItemsByURI[uri];
}
// Next try getting URI directly
@@ -3290,3 +1970,685 @@ Zotero.Integration.URIMap.prototype.getZoteroItemForURIs = Zotero.Promise.corout
return [zoteroItem, needUpdate];
});
+
+Zotero.Integration.Field = class {
+ constructor(field) {
+ if (field instanceof Zotero.Integration.Field) {
+ throw new Error("Trying to instantiate Integration.Field with Integration.Field, not doc field");
+ }
+ // This is not the best solution in terms of performance
+ for (let prop in field) {
+ if (!(prop in this)) {
+ this[prop] = field[prop].bind ? field[prop].bind(field) : field[prop];
+ }
+ }
+ this._field = field;
+ this.type = INTEGRATION_TYPE_TEMP;
+ }
+
+ setCode(code) {
+ // Boo. Inconsistent order.
+ if (this.type == INTEGRATION_TYPE_ITEM) {
+ this._field.setCode(`ITEM CSL_CITATION ${code}`);
+ } else if (this.type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
+ this._field.setCode(`BIBL ${code} CSL_BIBLIOGRAPHY`);
+ } else {
+ this._field.setCode(`TEMP`);
+ }
+ }
+
+ getCode() {
+ let code = this._field.getCode();
+ let start = code.indexOf('{');
+ if (start == -1) {
+ return '{}';
+ }
+ return code.substring(start, code.lastIndexOf('}')+1);
+ }
+
+ clearCode() {
+ this.setCode('{}');
+ }
+
+ setText(text) {
+ var isRich = false;
+ // If RTF wrap with RTF tags
+ if (text.includes("\\")) {
+ if (text.substr(0,5) != "{\\rtf") {
+ text = "{\\rtf "+text+"}";
+ }
+ isRich = true;
+ }
+ this._field.setText(text, isRich);
+ return isRich;
+ }
+};
+
+/**
+ * Load existing field in document and return correct instance of field type
+ * @param docField
+ * @param rawCode
+ * @param idx
+ * @returns {Zotero.Integration.Field|Zotero.Integration.CitationField|Zotero.Integration.BibliographyField}
+ */
+Zotero.Integration.Field.loadExisting = function(docField) {
+ var field;
+ // Already loaded
+ if (docField instanceof Zotero.Integration.Field) return docField;
+ let rawCode = docField.getCode();
+
+ // ITEM/CITATION CSL_ITEM {json: 'data'}
+ for (let type of ["ITEM", "CITATION"]) {
+ if (rawCode.substr(0, type.length) === type) {
+ field = new Zotero.Integration.CitationField(docField);
+ }
+ }
+ // BIBL {json: 'data'} CSL_BIBLIOGRAPHY
+ if (rawCode.substr(0, 4) === "BIBL") {
+ field = new Zotero.Integration.BibliographyField(docField);
+ }
+
+ if (!field) {
+ field = new Zotero.Integration.Field(docField);
+ }
+ if (field) {
+ let start = rawCode.indexOf('{');
+ if (start != -1) {
+ field._code = rawCode.substring(start, rawCode.lastIndexOf('}')+1);
+ } else {
+ field._code = rawCode.substr(rawCode.indexOf(' ')+1);
+ }
+ };
+
+ return field;
+};
+
+Zotero.Integration.CitationField = class extends Zotero.Integration.Field {
+ constructor(field) {
+ super(field);
+ this.type = INTEGRATION_TYPE_ITEM;
+ }
+
+ /**
+ * Don't be fooled, this should be as simple as JSON.parse().
+ * The schema for the code is defined @ https://raw.githubusercontent.com/citation-style-language/schema/master/csl-citation.json
+ *
+ * However, over the years and different versions of Zotero there's been changes to the schema,
+ * incorrect serialization, etc. Therefore this function is cruft-full and we can't get rid of it.
+ *
+ * @returns {{citationItems: Object[], properties: Object}}
+ */
+ unserialize() {
+ function unserialize(code) {
+ try {
+ return JSON.parse(code);
+ } catch(e) {
+ // fix for corrupted fields (corrupted by 2.1b1)
+ return JSON.parse(code.replace(/{{((?:\s*,?"unsorted":(?:true|false)|\s*,?"custom":"(?:(?:\\")?[^"]*\s*)*")*)}}/, "{$1}"));
+ }
+ }
+
+ function upgradeCruft(citation, code) {
+ // fix for uppercase citation codes
+ if(citation.CITATIONITEMS) {
+ citation.citationItems = [];
+ for (var i=0; i [Zotero.Integration.currentSession.uriMap.getURIsForItemID(id), this.customEntryText[id]]);
+
+
+ return JSON.stringify(bibliography);
+ }
+}
+
+// perhaps not the best place for a timer
+Zotero.Integration.Timer = class {
+ start() {
+ this.startTime = (new Date()).getTime();
+ }
+
+ stop() {
+ this.resume();
+ return ((new Date()).getTime() - this.startTime)/1000;
+ }
+
+ pause() {
+ this.pauseTime = (new Date()).getTime();
+ }
+
+ resume() {
+ if (this.pauseTime) {
+ this.startTime += (this.pauseTime - this.startTime);
+ this.pauseTime = null;
+ }
+ }
+}
diff --git a/chrome/content/zotero/xpcom/server_connector.js b/chrome/content/zotero/xpcom/server_connector.js
index 8fc7daf22..4d3f0b094 100644
--- a/chrome/content/zotero/xpcom/server_connector.js
+++ b/chrome/content/zotero/xpcom/server_connector.js
@@ -884,7 +884,7 @@ Zotero.Server.Connector.IncompatibleVersion.prototype = {
sendResponseCallback(404);
if(Zotero.Server.Connector.IncompatibleVersion._errorShown) return;
- Zotero.Integration.activate();
+ Zotero.Utilities.Internal.activate();
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
createInstance(Components.interfaces.nsIPromptService);
ps.alert(null,
diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js
index 68f5989af..07f7a8418 100644
--- a/chrome/content/zotero/xpcom/utilities_internal.js
+++ b/chrome/content/zotero/xpcom/utilities_internal.js
@@ -1266,6 +1266,382 @@ Zotero.Utilities.Internal = {
}
}
+/**
+ * Runs an AppleScript on OS X
+ *
+ * @param script {String}
+ * @param block {Boolean} Whether the script should block until the process is finished.
+ */
+Zotero.Utilities.Internal.executeAppleScript = new function() {
+ var _osascriptFile;
+
+ return function(script, block) {
+ if(_osascriptFile === undefined) {
+ _osascriptFile = Components.classes["@mozilla.org/file/local;1"].
+ createInstance(Components.interfaces.nsILocalFile);
+ _osascriptFile.initWithPath("/usr/bin/osascript");
+ if(!_osascriptFile.exists()) _osascriptFile = false;
+ }
+ if(_osascriptFile) {
+ var proc = Components.classes["@mozilla.org/process/util;1"].
+ createInstance(Components.interfaces.nsIProcess);
+ proc.init(_osascriptFile);
+ try {
+ proc.run(!!block, ['-e', script], 2);
+ } catch(e) {}
+ }
+ }
+}
+
+
+/**
+ * Activates Firefox
+ */
+Zotero.Utilities.Internal.activate = new function() {
+ // For Carbon and X11
+ var _carbon, ProcessSerialNumber, SetFrontProcessWithOptions;
+ var _x11, _x11Display, _x11RootWindow, XClientMessageEvent, XFetchName, XFree, XQueryTree,
+ XOpenDisplay, XCloseDisplay, XFlush, XDefaultRootWindow, XInternAtom, XSendEvent,
+ XMapRaised, XGetWindowProperty, X11Atom, X11Bool, X11Display, X11Window, X11Status;
+
+ /**
+ * Bring a window to the foreground by interfacing directly with X11
+ */
+ function _X11BringToForeground(win, intervalID) {
+ var windowTitle = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIBaseWindow).title;
+
+ var x11Window = _X11FindWindow(_x11RootWindow, windowTitle);
+ if(!x11Window) return;
+ win.clearInterval(intervalID);
+
+ var event = new XClientMessageEvent();
+ event.type = 33; /* ClientMessage*/
+ event.serial = 0;
+ event.send_event = 1;
+ event.message_type = XInternAtom(_x11Display, "_NET_ACTIVE_WINDOW", 0);
+ event.display = _x11Display;
+ event.window = x11Window;
+ event.format = 32;
+ event.l0 = 2;
+ var mask = 1<<20 /* SubstructureRedirectMask */ | 1<<19 /* SubstructureNotifyMask */;
+
+ if(XSendEvent(_x11Display, _x11RootWindow, 0, mask, event.address())) {
+ XMapRaised(_x11Display, x11Window);
+ XFlush(_x11Display);
+ Zotero.debug("Integration: Activated successfully");
+ } else {
+ Zotero.debug("Integration: An error occurred activating the window");
+ }
+ }
+
+ /**
+ * Find an X11 window given a name
+ */
+ function _X11FindWindow(w, searchName) {
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+
+ var res = _X11GetProperty(w, "_NET_CLIENT_LIST", 33 /** XA_WINDOW **/)
+ || _X11GetProperty(w, "_WIN_CLIENT_LIST", 6 /** XA_CARDINAL **/);
+ if(!res) return false;
+
+ var nClients = res[1],
+ clientList = ctypes.cast(res[0], X11Window.array(nClients).ptr).contents,
+ foundName = new ctypes.char.ptr();
+ for(var i=0; i
+
+
+
+
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index 680e10f89..e9e3ac3ad 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -870,6 +870,8 @@ integration.corruptBibliography.description = All items cited in the text will a
integration.citationChanged = You have modified this citation since Zotero generated it. Do you want to keep your modifications and prevent future updates?
integration.citationChanged.description = Clicking "Yes" will prevent Zotero from updating this citation if you add additional citations, switch styles, or modify the item to which it refers. Clicking "No" will erase your changes.
integration.citationChanged.edit = You have modified this citation since Zotero generated it. Editing will clear your modifications. Do you want to continue?
+integration.delayCitationUpdates.alert = Updating citations in this document is taking a long time. Would you like to delay citation updates until manual refresh?\n\nYou can change this setting later in the document preferences.
+integration.delayCitationUpdates.bibliography = Delayed citing mode is enabled. To see the bibliography click Refresh in Zotero plugin.
styles.install.title = Install Style
styles.install.unexpectedError = An unexpected error occurred while installing "%1$S"
diff --git a/test/tests/integrationTest.js b/test/tests/integrationTest.js
index 106bbb63c..ffe221a2a 100644
--- a/test/tests/integrationTest.js
+++ b/test/tests/integrationTest.js
@@ -2,6 +2,9 @@
describe("Zotero.Integration", function () {
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+ const INTEGRATION_TYPE_ITEM = 1;
+ const INTEGRATION_TYPE_BIBLIOGRAPHY = 2;
+ const INTEGRATION_TYPE_TEMP = 3;
/**
* To be used as a reference for Zotero-Word Integration plugins
*/
@@ -235,6 +238,7 @@ describe("Zotero.Integration", function () {
if (typeof docID === "undefined") {
throw new Error(`docID cannot be undefined`)
}
+ Zotero.debug(`execCommand '${command}': ${docID}`, 2);
return Zotero.Integration.execCommand("dummy", command, docID);
}
@@ -273,21 +277,13 @@ describe("Zotero.Integration", function () {
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", 0);
- 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;
- }
+ dialogResults.quickFormat = function(dialogName, io) {
+ io.citation.citationItems = items.map(function(item) {
+ item = Zotero.Cite.getItem(item.id);
+ return {id: item.id, uris: item.cslURIs, itemData: item.cslItemData};
+ });
+ io._acceptDeferred.resolve(() => {});
+ };
}
before(function* () {
@@ -296,7 +292,10 @@ describe("Zotero.Integration", function () {
testItems = [];
for (let i = 0; i < 5; i++) {
- testItems.push(yield createDataObject('item', {libraryID: Zotero.Libraries.userLibraryID}));
+ let testItem = yield createDataObject('item', {libraryID: Zotero.Libraries.userLibraryID});
+ testItem.setField('title', `title${1}`);
+ testItem.setCreator(0, {creatorType: 'author', name: `Author No${i}`});
+ testItems.push(testItem);
}
setAddEditItems(testItems[0]);
@@ -313,16 +312,18 @@ describe("Zotero.Integration", function () {
// possible bug that reset() erases callsFake.
// @NOTE: https://github.com/sinonjs/sinon/issues/1341
// displayDialogStub.callsFake(function(doc, dialogName, prefs, io) {
- function(doc, dialogName, prefs, io) {
+ function(dialogName, prefs, io) {
+ Zotero.debug(`Display dialog: ${dialogName}`, 2);
var ioResult = dialogResults[dialogName.substring(dialogName.lastIndexOf('/')+1, dialogName.length-4)];
if (typeof ioResult == 'function') {
- ioResult = ioResult(doc, dialogName);
+ ioResult(dialogName, io);
+ } else {
+ Object.assign(io, ioResult);
}
- Object.assign(io, ioResult);
return Zotero.Promise.resolve();
});
- addEditCitationSpy = sinon.spy(Zotero.Integration.Document.prototype, 'addEditCitation');
+ addEditCitationSpy = sinon.spy(Zotero.Integration.Interface.prototype, 'addEditCitation');
});
after(function() {
@@ -331,8 +332,8 @@ describe("Zotero.Integration", function () {
addEditCitationSpy.restore();
});
- describe('Document', function() {
- describe('#addEditCitation', function() {
+ describe('Interface', function() {
+ describe('#execCommand', function() {
var setDocumentDataSpy;
var docID = this.fullTitle();
@@ -348,18 +349,7 @@ describe("Zotero.Integration", 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 call doc.setDocumentData when document communicates for first time since restart to write new sessionID', function* () {
- Zotero.Integration.sessions = {};
+ it('should call doc.setDocumentData once', function* () {
yield execCommand('addEditCitation', docID);
assert.isTrue(setDocumentDataSpy.calledOnce);
});
@@ -390,8 +380,7 @@ describe("Zotero.Integration", function () {
});
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();
+ it('should download the style and if user clicks YES', function* () {
var styleInstallStub = sinon.stub(Zotero.Styles, "install").resolves();
var style = Zotero.Styles.get(styleID);
var styleGetCalledOnce = false;
@@ -407,7 +396,6 @@ describe("Zotero.Integration", function () {
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();
@@ -447,6 +435,194 @@ describe("Zotero.Integration", function () {
});
});
+ describe('#addEditCitation', function() {
+ var insertMultipleCitations = Zotero.Promise.coroutine(function *() {
+ var docID = this.test.fullTitle();
+ if (!(docID in applications)) initDoc(docID);
+ var doc = applications[docID].doc;
+
+ setAddEditItems(testItems[0]);
+ yield execCommand('addEditCitation', docID);
+ assert.equal(doc.fields.length, 1);
+ var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize();
+ assert.equal(citation.citationItems.length, 1);
+ assert.equal(citation.citationItems[0].id, testItems[0].id);
+
+ setAddEditItems(testItems.slice(1, 3));
+ yield execCommand('addEditCitation', docID);
+ assert.equal(doc.fields.length, 2);
+ citation = (new Zotero.Integration.CitationField(doc.fields[1])).unserialize();
+ assert.equal(citation.citationItems.length, 2);
+ for (let i = 1; i < 3; i++) {
+ assert.equal(citation.citationItems[i-1].id, testItems[i].id);
+ }
+ });
+ it('should insert citation if not in field', insertMultipleCitations);
+
+ it('should edit citation if in citation field', function* () {
+ yield insertMultipleCitations.call(this);
+ var docID = this.test.fullTitle();
+ var doc = applications[docID].doc;
+
+ sinon.stub(doc, 'cursorInField').returns(doc.fields[0]);
+ sinon.stub(doc, 'canInsertField').returns(false);
+
+ setAddEditItems(testItems.slice(3, 5));
+ yield execCommand('addEditCitation', docID);
+ assert.equal(doc.fields.length, 2);
+ var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize();
+ assert.equal(citation.citationItems.length, 2);
+ assert.equal(citation.citationItems[0].id, testItems[3].id);
+ });
+
+ it('should update bibliography if present', function* () {
+ yield insertMultipleCitations.call(this);
+ var docID = this.test.fullTitle();
+ var doc = applications[docID].doc;
+
+ let getCiteprocBibliographySpy =
+ sinon.spy(Zotero.Integration.Bibliography.prototype, 'getCiteprocBibliography');
+
+ yield execCommand('addEditBibliography', docID);
+ assert.isTrue(getCiteprocBibliographySpy.calledOnce);
+
+ assert.equal(getCiteprocBibliographySpy.lastCall.returnValue[0].entry_ids.length, 3);
+ getCiteprocBibliographySpy.reset();
+
+ setAddEditItems(testItems[3]);
+ yield execCommand('addEditCitation', docID);
+ assert.equal(getCiteprocBibliographySpy.lastCall.returnValue[0].entry_ids.length, 4);
+
+ getCiteprocBibliographySpy.restore();
+ });
+
+ describe('when original citation text has been modified', function() {
+ var displayAlertStub;
+ before(function* () {
+ displayAlertStub = sinon.stub(DocumentPluginDummy.Document.prototype, 'displayAlert').returns(0);
+ });
+ beforeEach(function() {
+ displayAlertStub.reset();
+ });
+ after(function() {
+ displayAlertStub.restore();
+ });
+ it('should keep modification if "Cancel" selected in editCitation triggered alert', async function () {
+ await insertMultipleCitations.call(this);
+ var docID = this.test.fullTitle();
+ var doc = applications[docID].doc;
+
+ doc.fields[0].text = "modified";
+ sinon.stub(doc, 'cursorInField').returns(doc.fields[0]);
+ sinon.stub(doc, 'canInsertField').returns(false);
+
+ await execCommand('addEditCitation', docID);
+ assert.equal(doc.fields.length, 2);
+ assert.equal(doc.fields[0].text, "modified");
+ });
+ it('should display citation dialog if "OK" selected in editCitation triggered alert', async function () {
+ await insertMultipleCitations.call(this);
+ var docID = this.test.fullTitle();
+ var doc = applications[docID].doc;
+
+ let origText = doc.fields[0].text;
+ doc.fields[0].text = "modified";
+ // Return OK
+ displayAlertStub.returns(1);
+ sinon.stub(doc, 'cursorInField').returns(doc.fields[0]);
+ sinon.stub(doc, 'canInsertField').returns(false);
+ setAddEditItems(testItems[0]);
+
+ await execCommand('addEditCitation', docID);
+ assert.isTrue(displayAlertStub.called);
+ assert.equal(doc.fields.length, 2);
+ assert.equal(doc.fields[0].text, origText);
+ });
+ it('should set dontUpdate: true if "yes" selected in refresh prompt', async function() {
+ await insertMultipleCitations.call(this);
+ var docID = this.test.fullTitle();
+ var doc = applications[docID].doc;
+
+ var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize();
+ assert.isNotOk(citation.properties.dontUpdate);
+ doc.fields[0].text = "modified";
+ // Return Yes
+ displayAlertStub.returns(1);
+
+ await execCommand('refresh', docID);
+ assert.isTrue(displayAlertStub.called);
+ assert.equal(doc.fields.length, 2);
+ assert.equal(doc.fields[0].text, "modified");
+ var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize();
+ assert.isOk(citation.properties.dontUpdate);
+ });
+ it('should reset citation text if "no" selected in refresh prompt', async function() {
+ await insertMultipleCitations.call(this);
+ var docID = this.test.fullTitle();
+ var doc = applications[docID].doc;
+
+ var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize();
+ assert.isNotOk(citation.properties.dontUpdate);
+ let origText = doc.fields[0].text;
+ doc.fields[0].text = "modified";
+ // Return No
+ displayAlertStub.returns(0);
+
+ await execCommand('refresh', docID);
+ assert.isTrue(displayAlertStub.called);
+ assert.equal(doc.fields.length, 2);
+ assert.equal(doc.fields[0].text, origText);
+ var citation = (new Zotero.Integration.CitationField(doc.fields[0])).unserialize();
+ assert.isNotOk(citation.properties.dontUpdate);
+ });
+ });
+
+ describe('when delayCitationUpdates is set', function() {
+ it('should insert a citation with wave underlining', function* (){
+ yield insertMultipleCitations.call(this);
+ var docID = this.test.fullTitle();
+ var doc = applications[docID].doc;
+ var data = new Zotero.Integration.DocumentData(doc.data);
+ data.prefs.delayCitationUpdates = true;
+ doc.data = data.serialize();
+
+ var setTextSpy = sinon.spy(DocumentPluginDummy.Field.prototype, 'setText');
+ setAddEditItems(testItems[3]);
+ yield execCommand('addEditCitation', docID);
+ assert.isTrue(setTextSpy.lastCall.args[0].includes('\\uldash'));
+
+ setTextSpy.restore();
+ });
+
+ it('should not write to any other fields besides the one being updated', function* () {
+ yield insertMultipleCitations.call(this);
+ var docID = this.test.fullTitle();
+ var doc = applications[docID].doc;
+ var data = new Zotero.Integration.DocumentData(doc.data);
+ data.prefs.delayCitationUpdates = true;
+ doc.data = data.serialize();
+
+ var setTextSpy = sinon.spy(DocumentPluginDummy.Field.prototype, 'setText');
+ var setCodeSpy = sinon.spy(DocumentPluginDummy.Field.prototype, 'setCode');
+
+ setAddEditItems(testItems[3]);
+ yield execCommand('addEditCitation', docID);
+ var field = setTextSpy.firstCall.thisValue;
+
+ for (let i = 0; i < setTextSpy.callCount; i++) {
+ assert.isTrue(field.equals(setTextSpy.getCall(i).thisValue));
+ }
+
+ for (let i = 0; i < setCodeSpy.callCount; i++) {
+ assert.isTrue(field.equals(setCodeSpy.getCall(i).thisValue));
+ }
+
+ setTextSpy.restore();
+ setCodeSpy.restore();
+ })
+ });
+ });
+
describe('#addEditBibliography', function() {
var docID = this.fullTitle();
beforeEach(function* () {
@@ -455,12 +631,13 @@ describe("Zotero.Integration", function () {
});
it('should insert bibliography if no bibliography field present', function* () {
+ displayDialogStub.reset();
yield execCommand('addEditBibliography', docID);
+ assert.isFalse(displayDialogStub.called);
var biblPresent = false;
for (let i = applications[docID].doc.fields.length-1; i >= 0; i--) {
- let field = applications[docID].doc.fields[i];
- Zotero.debug(field.getCode(), 1);
- if (field.getCode().includes("CSL_BIBLIOGRAPHY")) {
+ let field = Zotero.Integration.Field.loadExisting(applications[docID].doc.fields[i]);
+ if (field.type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
biblPresent = true;
break;
}
@@ -473,7 +650,7 @@ describe("Zotero.Integration", function () {
displayDialogStub.reset();
yield execCommand('addEditBibliography', docID);
assert.isTrue(displayDialogStub.calledOnce);
- assert.isTrue(displayDialogStub.lastCall.args[1].includes('editBibliographyDialog'));
+ assert.isTrue(displayDialogStub.lastCall.args[0].includes('editBibliographyDialog'));
});
});
});