Merge pull request #1476 from adomasven:feature/connector-doc-integration

Doc Integration Endpoint for the Connector
This commit is contained in:
Dan Stillman 2018-04-24 01:37:48 -04:00
commit d573a5b639
14 changed files with 1104 additions and 447 deletions

View file

@ -14,6 +14,10 @@ body[multiline="true"] {
width: 800px;
}
#quick-format-dialog.progress-bar #quick-format-deck {
height: 37px;
}
#quick-format-search {
background: white;
-moz-appearance: searchfield;

View file

@ -147,26 +147,33 @@ var Zotero_File_Interface_Bibliography = new function() {
let dialog = document.getElementById("zotero-doc-prefs-dialog");
dialog.setAttribute('title', `${Zotero.clientName} - ${dialog.getAttribute('title')}`);
if(_io.fieldType == "Bookmark") document.getElementById("formatUsing").selectedIndex = 1;
var formatOption = (_io.primaryFieldType == "ReferenceMark" ? "referenceMarks" : "fields");
document.getElementById("fields").label =
Zotero.getString("integration."+formatOption+".label");
document.getElementById("fields-caption").textContent =
Zotero.getString("integration."+formatOption+".caption");
document.getElementById("fields-file-format-notice").textContent =
Zotero.getString("integration."+formatOption+".fileFormatNotice");
document.getElementById("bookmarks-file-format-notice").textContent =
Zotero.getString("integration.fields.fileFormatNotice");
if(_io.automaticJournalAbbreviations === undefined) {
_io.automaticJournalAbbreviations = Zotero.Prefs.get("cite.automaticJournalAbbreviations");
if (document.getElementById("formatUsing-groupbox")) {
if (["Field", "ReferenceMark"].includes(_io.primaryFieldType)) {
if(_io.fieldType == "Bookmark") document.getElementById("formatUsing").selectedIndex = 1;
var formatOption = (_io.primaryFieldType == "ReferenceMark" ? "referenceMarks" : "fields");
document.getElementById("fields").label =
Zotero.getString("integration."+formatOption+".label");
document.getElementById("fields-caption").textContent =
Zotero.getString("integration."+formatOption+".caption");
document.getElementById("fields-file-format-notice").textContent =
Zotero.getString("integration."+formatOption+".fileFormatNotice");
document.getElementById("bookmarks-file-format-notice").textContent =
Zotero.getString("integration.fields.fileFormatNotice");
} else {
document.getElementById("formatUsing-groupbox").style.display = "none";
_io.fieldType = _io.primaryFieldType;
}
}
if(_io.automaticJournalAbbreviations) {
document.getElementById("automaticJournalAbbreviations-checkbox").checked = true;
if(document.getElementById("automaticJournalAbbreviations-checkbox")) {
if(_io.automaticJournalAbbreviations === undefined) {
_io.automaticJournalAbbreviations = Zotero.Prefs.get("cite.automaticJournalAbbreviations");
}
if(_io.automaticJournalAbbreviations) {
document.getElementById("automaticJournalAbbreviations-checkbox").checked = true;
}
document.getElementById("automaticCitationUpdates-checkbox").checked = !_io.delayCitationUpdates;
}
document.getElementById("automaticCitationUpdates-checkbox").checked = !_io.delayCitationUpdates;
}
// set style to false, in case this is cancelled
@ -204,7 +211,8 @@ var Zotero_File_Interface_Bibliography = new function() {
if (isDocPrefs) {
// update status of displayAs box based on style class
var isNote = selectedStyleObj.class == "note";
document.getElementById("displayAs-groupbox").hidden = !isNote;
var multipleNotesSupported = _io.supportedNotes.length > 1;
document.getElementById("displayAs-groupbox").hidden = !isNote || !multipleNotesSupported;
// update status of formatUsing box based on style class
if(isNote) document.getElementById("formatUsing").selectedIndex = 0;

View file

@ -68,7 +68,7 @@
</radiogroup>
</groupbox>
<groupbox>
<groupbox id="formatUsing-groupbox">
<caption label="&zotero.integration.prefs.formatUsing.label;"/>
<radiogroup id="formatUsing" orient="vertical">

View file

@ -0,0 +1,129 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2018 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
Components.utils.import("resource://gre/modules/Services.jsm");
var Zotero_ProgressBar = new function () {
var initialized, io;
/**
* Pre-initialization, when the dialog has loaded but has not yet appeared
*/
this.onDOMContentLoaded = function(event) {
if(event.target === document) {
initialized = true;
io = window.arguments[0].wrappedJSObject;
if (io.onLoad) {
io.onLoad(_onProgress);
}
// Only hide chrome on Windows or Mac
if(Zotero.isMac) {
document.documentElement.setAttribute("drawintitlebar", true);
} else if(Zotero.isWin) {
document.documentElement.setAttribute("hidechrome", true);
}
new WindowDraggingElement(document.getElementById("quick-format-dialog"), window);
}
};
/**
* Initialize add citation dialog
*/
this.onLoad = function(event) {
if(event.target !== document) return;
// make sure we are visible
window.setTimeout(function() {
// window.resizeTo(window.outerWidth, window.outerHeight);
var screenX = window.screenX;
var screenY = window.screenY;
var xRange = [window.screen.availLeft, window.screen.width-window.outerWidth];
var yRange = [window.screen.availTop, window.screen.height-window.outerHeight];
if (screenX < xRange[0] || screenX > xRange[1] || screenY < yRange[0] || screenY > yRange[1]) {
var targetX = Math.max(Math.min(screenX, xRange[1]), xRange[0]);
var targetY = Math.max(Math.min(screenY, yRange[1]), yRange[0]);
Zotero.debug("Moving window to "+targetX+", "+targetY);
window.moveTo(targetX, targetY);
}
}, 0);
window.focus();
};
/**
* Called when progress changes
*/
function _onProgress(percent) {
var meter = document.getElementById("quick-format-progress-meter");
if(percent === null) {
meter.mode = "undetermined";
} else {
meter.mode = "determined";
meter.value = Math.round(percent);
}
}
/**
* Resizes windows
* @constructor
*/
var Resizer = function(panel, targetWidth, targetHeight, pixelsPerStep, stepsPerSecond) {
this.panel = panel;
this.curWidth = panel.clientWidth;
this.curHeight = panel.clientHeight;
this.difX = (targetWidth ? targetWidth - this.curWidth : 0);
this.difY = (targetHeight ? targetHeight - this.curHeight : 0);
this.step = 0;
this.steps = Math.ceil(Math.max(Math.abs(this.difX), Math.abs(this.difY))/pixelsPerStep);
this.timeout = (1000/stepsPerSecond);
var me = this;
this._animateCallback = function() { me.animate() };
};
/**
* Performs a step of the animation
*/
Resizer.prototype.animate = function() {
if(this.stopped) return;
this.step++;
this.panel.sizeTo(this.curWidth+Math.round(this.step*this.difX/this.steps),
this.curHeight+Math.round(this.step*this.difY/this.steps));
if(this.step !== this.steps) {
window.setTimeout(this._animateCallback, this.timeout);
}
};
/**
* Halts resizing
*/
Resizer.prototype.stop = function() {
this.stopped = true;
};
}
window.addEventListener("DOMContentLoaded", Zotero_ProgressBar.onDOMContentLoaded, false);
window.addEventListener("load", Zotero_ProgressBar.onLoad, false);

View file

@ -0,0 +1,51 @@
<?xml version="1.0"?>
<!--
***** BEGIN LICENSE BLOCK *****
Copyright © 2018 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
-->
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/zotero.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero/skin/integration.css" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/integration.css" type="text/css"?>
<!DOCTYPE window SYSTEM "chrome://zotero/locale/zotero.dtd">
<window
id="quick-format-dialog"
class="progress-bar"
orient="vertical"
title="&zotero.progress.title;"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
persist="screenX screenY">
<script src="../include.js"/>
<script src="windowDraggingUtils.js" type="text/javascript"/>
<script src="progressBar.js" type="text/javascript"/>
<box orient="horizontal" id="quick-format-entry">
<deck id="quick-format-deck" selectedIndex="0" flex="1">
<progressmeter id="quick-format-progress-meter" mode="undetermined" value="0" flex="1"/>
</deck>
</box>
</window>

View file

@ -179,6 +179,7 @@ var Zotero_QuickFormat = new function () {
*/
function _getCurrentEditorTextNode() {
var selection = qfiWindow.getSelection();
if (!selection) return false;
var range = selection.getRangeAt(0);
var node = range.startContainer;

View file

@ -0,0 +1,192 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2017 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
/**
* This is a HTTP-based integration interface for Zotero. The actual
* heavy lifting occurs in the connector and/or wherever the connector delegates the heavy
* lifting to.
*/
Zotero.HTTPIntegrationClient = {
deferredResponse: null,
sendCommandPromise: Zotero.Promise.resolve(),
sendCommand: async function(command, args=[]) {
let payload = JSON.stringify({command, arguments: args});
function sendCommand() {
Zotero.HTTPIntegrationClient.deferredResponse = Zotero.Promise.defer();
Zotero.HTTPIntegrationClient.sendResponse.apply(Zotero.HTTPIntegrationClient,
[200, 'application/json', payload]);
return Zotero.HTTPIntegrationClient.deferredResponse.promise;
}
// Force issued commands to occur sequentially, since these are really just
// a sequence of HTTP requests and responses.
// We might want to consider something better later, but this has the advantage of
// being easy to interface with as a Client, as you don't need SSE or WS.
if (command != 'Document.complete') {
Zotero.HTTPIntegrationClient.sendCommandPromise =
Zotero.HTTPIntegrationClient.sendCommandPromise.then(sendCommand, sendCommand);
} else {
await Zotero.HTTPIntegrationClient.sendCommandPromise;
sendCommand();
}
return Zotero.HTTPIntegrationClient.sendCommandPromise;
}
};
Zotero.HTTPIntegrationClient.Application = function() {
this.primaryFieldType = "Http";
this.secondaryFieldType = "Http";
this.outputFormat = 'html';
this.supportedNotes = ['footnotes'];
};
Zotero.HTTPIntegrationClient.Application.prototype = {
getActiveDocument: async function() {
let result = await Zotero.HTTPIntegrationClient.sendCommand('Application.getActiveDocument');
this.outputFormat = result.outputFormat || this.outputFormat;
this.supportedNotes = result.supportedNotes || this.supportedNotes;
return new Zotero.HTTPIntegrationClient.Document(result.documentID);
}
};
/**
* See integrationTests.js
*/
Zotero.HTTPIntegrationClient.Document = function(documentID) {
this._documentID = documentID;
};
for (let method of ["activate", "canInsertField", "displayAlert", "getDocumentData",
"setDocumentData", "setBibliographyStyle"]) {
Zotero.HTTPIntegrationClient.Document.prototype[method] = async function() {
return Zotero.HTTPIntegrationClient.sendCommand("Document."+method,
[this._documentID].concat(Array.prototype.slice.call(arguments)));
};
}
// @NOTE Currently unused, prompts are done using the connector
Zotero.HTTPIntegrationClient.Document.prototype._displayAlert = async function(dialogText, icon, buttons) {
var ps = Services.prompt;
var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_OK)
+ (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
switch (buttons) {
case DIALOG_BUTTONS_OK:
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.BUTTON_POS_0 * ps.BUTTON_TITLE_OK; break;
case DIALOG_BUTTONS_OK_CANCEL:
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.STD_OK_CANCEL_BUTTONS; break;
case DIALOG_BUTTONS_YES_NO:
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.STD_YES_NO_BUTTONS; break;
case DIALOG_BUTTONS_YES_NO_CANCEL:
buttonFlags = ps.BUTTON_POS_0_DEFAULT + ps.BUTTON_POS_0 * ps.BUTTON_TITLE_YES +
ps.BUTTON_POS_1 * ps.BUTTON_TITLE_NO +
ps.BUTTON_POS_2 * ps.BUTTON_TITLE_CANCEL; break;
}
var result = ps.confirmEx(
null,
"Zotero",
dialogText,
buttonFlags,
null, null, null,
null,
{}
);
switch (buttons) {
default:
break;
case DIALOG_BUTTONS_OK_CANCEL:
case DIALOG_BUTTONS_YES_NO:
result = (result+1)%2; break;
case DIALOG_BUTTONS_YES_NO_CANCEL:
result = result == 0 ? 2 : result == 2 ? 0 : 1; break;
}
await this.activate();
return result;
}
Zotero.HTTPIntegrationClient.Document.prototype.cleanup = async function() {};
Zotero.HTTPIntegrationClient.Document.prototype.cursorInField = async function(fieldType) {
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.cursorInField", [this._documentID, fieldType]);
if (!retVal) return null;
return new Zotero.HTTPIntegrationClient.Field(this._documentID, retVal);
};
Zotero.HTTPIntegrationClient.Document.prototype.insertField = async function(fieldType, noteType) {
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.insertField", [this._documentID, fieldType, parseInt(noteType) || 0]);
return new Zotero.HTTPIntegrationClient.Field(this._documentID, retVal);
};
Zotero.HTTPIntegrationClient.Document.prototype.getFields = async function(fieldType) {
var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.getFields", [this._documentID, fieldType]);
return retVal.map(field => new Zotero.HTTPIntegrationClient.Field(this._documentID, field));
};
Zotero.HTTPIntegrationClient.Document.prototype.convert = async function(fields, fieldType, noteTypes) {
fields = fields.map((f) => f._id);
await Zotero.HTTPIntegrationClient.sendCommand("Field.convert", [this._documentID, fields, fieldType, noteTypes]);
};
Zotero.HTTPIntegrationClient.Document.prototype.complete = async function() {
Zotero.HTTPIntegrationClient.inProgress = false;
Zotero.HTTPIntegrationClient.sendCommand("Document.complete", [this._documentID]);
};
/**
* See integrationTests.js
*/
Zotero.HTTPIntegrationClient.Field = function(documentID, json) {
this._documentID = documentID;
this._id = json.id;
this._code = json.code;
this._text = json.text;
this._noteIndex = json.noteIndex;
};
Zotero.HTTPIntegrationClient.Field.prototype = {};
for (let method of ["delete", "select", "removeCode"]) {
Zotero.HTTPIntegrationClient.Field.prototype[method] = async function() {
return Zotero.HTTPIntegrationClient.sendCommand("Field."+method,
[this._documentID, this._id].concat(Array.prototype.slice.call(arguments)));
};
}
Zotero.HTTPIntegrationClient.Field.prototype.getText = async function() {
return this._text;
};
Zotero.HTTPIntegrationClient.Field.prototype.setText = async function(text, isRich) {
// The HTML will be stripped by Google Docs and and since we're
// caching this value, we need to strip it ourselves
var wm = Services.wm;
var win = wm.getMostRecentWindow('navigator:browser');
var doc = new win.DOMParser().parseFromString(text, "text/html");
this._text = doc.documentElement.textContent;
return Zotero.HTTPIntegrationClient.sendCommand("Field.setText", [this._documentID, this._id, text, isRich]);
};
Zotero.HTTPIntegrationClient.Field.prototype.getCode = async function() {
return this._code;
};
Zotero.HTTPIntegrationClient.Field.prototype.setCode = async function(code) {
this._code = code;
return Zotero.HTTPIntegrationClient.sendCommand("Field.setCode", [this._documentID, this._id, code]);
};
Zotero.HTTPIntegrationClient.Field.prototype.getNoteIndex = async function() {
return this._noteIndex;
};
Zotero.HTTPIntegrationClient.Field.prototype.equals = async function(arg) {
return this._id === arg._id;
};

View file

@ -0,0 +1,72 @@
/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2017 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://zotero.org
This file is part of Zotero.
Zotero is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zotero is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
***** END LICENSE BLOCK *****
*/
/**
* Adds integration endpoints related to doc integration via HTTP/connector.
*
* document/execCommand initiates an integration command and responds with the
* next request for the http client (e.g. 'Application.getDocument').
* The client should respond to document/respond with the payload and expect
* another response with the next request, until it receives 'Document.complete'
* at which point the integration transaction is considered complete.
*/
Zotero.Server.Endpoints['/connector/document/execCommand'] = function() {};
Zotero.Server.Endpoints['/connector/document/execCommand'].prototype = {
supportedMethods: ["POST"],
supportedDataTypes: ["application/json"],
permitBookmarklet: true,
init: function(data, sendResponse) {
if (Zotero.HTTPIntegrationClient.inProgress) {
// This will focus the last integration window if present
Zotero.Integration.execCommand('http', data.command, data.docId);
sendResponse(503, 'text/plain', 'Integration transaction is already in progress')
return;
}
Zotero.HTTPIntegrationClient.inProgress = true;
Zotero.HTTPIntegrationClient.sendResponse = sendResponse;
Zotero.Integration.execCommand('http', data.command, data.docId);
},
};
Zotero.Server.Endpoints['/connector/document/respond'] = function() {};
Zotero.Server.Endpoints['/connector/document/respond'].prototype = {
supportedMethods: ["POST"],
supportedDataTypes: ["application/json"],
permitBookmarklet: true,
init: function(data, sendResponse) {
data = JSON.parse(data);
if (data && data.error) {
// Apps Script stack is a JSON object
if (typeof data.stack != "string") {
data.stack = JSON.stringify(data.stack);
}
Zotero.HTTPIntegrationClient.deferredResponse.reject(data);
} else {
Zotero.HTTPIntegrationClient.deferredResponse.resolve(data);
}
Zotero.HTTPIntegrationClient.sendResponse = sendResponse;
}
};

File diff suppressed because it is too large Load diff

View file

@ -36,6 +36,7 @@ Zotero.Server = new function() {
412:"Precondition Failed",
500:"Internal Server Error",
501:"Not Implemented",
503:"Service Unavailable",
504:"Gateway Timeout"
};

View file

@ -1660,6 +1660,18 @@ Zotero.Utilities.Internal.activate = new function() {
}
};
Zotero.Utilities.Internal.sendToBack = function() {
if (Zotero.isMac) {
Zotero.Utilities.Internal.executeAppleScript(`
tell application "System Events"
if frontmost of application id "org.zotero.zotero" then
set visible of process "Zotero" to false
end if
end tell
`);
}
}
/**
* Base64 encode / decode
* From http://www.webtoolkit.info/

View file

@ -132,18 +132,9 @@ const xpcomFilesLocal = [
'users',
'translation/translate_item',
'translation/translators',
'server_connector'
];
/** XPCOM files to be loaded only for connector translation and DB access **/
const xpcomFilesConnector = [
'connector/translate_item',
'connector/translator',
'connector/connector',
'connector/connector_firefox',
'connector/cachedTypes',
'connector/repo',
'connector/typeSchemaData'
'connector/httpIntegrationClient',
'connector/server_connector',
'connector/server_connectorIntegration',
];
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

View file

@ -7,6 +7,9 @@ describe("Zotero.Integration", function () {
const INTEGRATION_TYPE_TEMP = 3;
/**
* To be used as a reference for Zotero-Word Integration plugins
*
* NOTE: Functions must return promises instead of values!
* The functions defined for the dummy are promisified below
*/
var DocumentPluginDummy = {};
@ -17,6 +20,7 @@ describe("Zotero.Integration", function () {
this.doc = new DocumentPluginDummy.Document();
this.primaryFieldType = "Field";
this.secondaryFieldType = "Bookmark";
this.supportedNotes = ['footnotes', 'endnotes'];
this.fields = [];
};
DocumentPluginDummy.Application.prototype = {
@ -87,26 +91,15 @@ describe("Zotero.Integration", function () {
throw new Error("noteType must be an integer");
}
var field = new DocumentPluginDummy.Field(this);
this.fields.push(field);
return field
this.fields.push(field);
return field;
},
/**
* Gets all fields present in the document.
* @param {String} fieldType
* @returns {DocumentPluginDummy.FieldEnumerator}
* @returns {DocumentPluginDummy.Field[]}
*/
getFields: function(fieldType) {return new DocumentPluginDummy.FieldEnumerator(this)},
/**
* Gets all fields present in the document. The observer will receive notifications for two
* topics: "fields-progress", with the document as the subject and percent progress as data, and
* "fields-available", with an nsISimpleEnumerator of fields as the subject and the length as
* data
* @param {String} fieldType
* @param {nsIObserver} observer
*/
getFieldsAsync: function(fieldType, observer) {
observer.observe(this.getFields(fieldType), 'fields-available', null)
},
getFields: function(fieldType) {return Array.from(this.fields)},
/**
* Sets the bibliography style, overwriting the current values for this document
*/
@ -114,7 +107,7 @@ describe("Zotero.Integration", function () {
tabStops, tabStopsCount) => 0,
/**
* Converts all fields in a document to a different fieldType or noteType
* @params {DocumentPluginDummy.FieldEnumerator} fields
* @params {DocumentPluginDummy.Field[]} fields
*/
convert: (fields, toFieldType, toNoteType, count) => 0,
/**
@ -128,14 +121,6 @@ describe("Zotero.Integration", function () {
complete: () => 0,
};
DocumentPluginDummy.FieldEnumerator = function(doc) {this.doc = doc; this.idx = 0};
DocumentPluginDummy.FieldEnumerator.prototype = {
hasMoreElements: function() {return this.idx < this.doc.fields.length;},
getNext: function() {return this.doc.fields[this.idx++]},
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
Components.interfaces.nsISimpleEnumerator])
};
/**
* The Field class corresponds to a field containing an individual citation
* or bibliography
@ -197,11 +182,12 @@ describe("Zotero.Integration", function () {
getNoteIndex: () => 0,
};
for (let cls of ['Application', 'Document', 'FieldEnumerator', 'Field']) {
// Processing functions for logging and promisification
for (let cls of ['Application', 'Document', 'Field']) {
for (let methodName in DocumentPluginDummy[cls].prototype) {
if (methodName !== 'QueryInterface') {
let method = DocumentPluginDummy[cls].prototype[methodName];
DocumentPluginDummy[cls].prototype[methodName] = function() {
DocumentPluginDummy[cls].prototype[methodName] = async function() {
try {
Zotero.debug(`DocumentPluginDummy: ${cls}.${methodName} invoked with args ${JSON.stringify(arguments)}`, 2);
} catch (e) {
@ -250,7 +236,7 @@ describe("Zotero.Integration", function () {
editBibliographyDialog: {}
};
function initDoc(docID, options={}) {
async function initDoc(docID, options={}) {
applications[docID] = new DocumentPluginDummy.Application();
var data = new Zotero.Integration.DocumentData();
data.prefs = {
@ -261,7 +247,7 @@ describe("Zotero.Integration", function () {
data.style = {styleID, locale: 'en-US', hasBibliography: true, bibliographyStyleHasBeenSet: true};
data.sessionID = Zotero.Utilities.randomString(10);
Object.assign(data, options);
applications[docID].getActiveDocument().setDocumentData(data.serialize());
await (await applications[docID].getDocument(docID)).setDocumentData(data.serialize());
}
function setDefaultIntegrationDocPrefs() {
@ -319,9 +305,12 @@ describe("Zotero.Integration", function () {
});
addEditCitationSpy = sinon.spy(Zotero.Integration.Interface.prototype, 'addEditCitation');
sinon.stub(Zotero.Integration.Progress.prototype, 'show');
});
after(function() {
Zotero.Integration.Progress.prototype.show.restore();
Zotero.Integration.getApplication.restore();
displayDialogStub.restore();
addEditCitationSpy.restore();
@ -354,10 +343,10 @@ describe("Zotero.Integration", function () {
var displayAlertStub;
var style;
before(function* () {
displayAlertStub = sinon.stub(DocumentPluginDummy.Document.prototype, 'displayAlert').returns(0);
displayAlertStub = sinon.stub(DocumentPluginDummy.Document.prototype, 'displayAlert').resolves(0);
});
beforeEach(function() {
beforeEach(async function () {
// 🦉birds?
style = {styleID: "http://www.example.com/csl/waterbirds", locale: 'en-US'};
@ -365,7 +354,7 @@ describe("Zotero.Integration", function () {
try {
Zotero.Styles.get(style.styleID).remove();
} catch (e) {}
initDoc(docID, {style});
await initDoc(docID, {style});
displayDialogStub.resetHistory();
displayAlertStub.reset();
});
@ -386,7 +375,7 @@ describe("Zotero.Integration", function () {
}
return style;
});
displayAlertStub.returns(1);
displayAlertStub.resolves(1);
yield execCommand('addEditCitation', docID);
assert.isTrue(displayAlertStub.calledOnce);
assert.isFalse(displayDialogStub.calledWith(applications[docID].doc, 'chrome://zotero/content/integration/integrationDocPrefs.xul'));
@ -397,7 +386,7 @@ describe("Zotero.Integration", function () {
});
it('should prompt with the document preferences dialog if user clicks NO', function* () {
displayAlertStub.returns(0);
displayAlertStub.resolves(0);
yield execCommand('addEditCitation', docID);
assert.isTrue(displayAlertStub.calledOnce);
// Prefs to select a new style and quickFormat
@ -407,7 +396,7 @@ describe("Zotero.Integration", function () {
});
it('should download the style without prompting if it is from zotero.org', function* (){
initDoc(docID, {styleID: "http://www.zotero.org/styles/waterbirds", locale: 'en-US'});
yield initDoc(docID, {styleID: "http://www.zotero.org/styles/waterbirds", locale: 'en-US'});
var styleInstallStub = sinon.stub(Zotero.Styles, "install").resolves();
var style = Zotero.Styles.get(styleID);
var styleGetCalledOnce = false;
@ -418,7 +407,7 @@ describe("Zotero.Integration", function () {
}
return style;
});
displayAlertStub.returns(1);
displayAlertStub.resolves(1);
yield execCommand('addEditCitation', docID);
assert.isFalse(displayAlertStub.called);
assert.isFalse(displayDialogStub.calledWith(applications[docID].doc, 'chrome://zotero/content/integration/integrationDocPrefs.xul'));
@ -433,20 +422,20 @@ describe("Zotero.Integration", function () {
describe('#addEditCitation', function() {
var insertMultipleCitations = Zotero.Promise.coroutine(function *() {
var docID = this.test.fullTitle();
if (!(docID in applications)) initDoc(docID);
if (!(docID in applications)) yield 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], doc.fields[0].code)).unserialize();
var citation = yield (new Zotero.Integration.CitationField(doc.fields[0], doc.fields[0].code)).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], doc.fields[1].code)).unserialize();
citation = yield (new Zotero.Integration.CitationField(doc.fields[1], doc.fields[1].code)).unserialize();
assert.equal(citation.citationItems.length, 2);
for (let i = 1; i < 3; i++) {
assert.equal(citation.citationItems[i-1].id, testItems[i].id);
@ -459,13 +448,13 @@ describe("Zotero.Integration", function () {
var docID = this.test.fullTitle();
var doc = applications[docID].doc;
sinon.stub(doc, 'cursorInField').returns(doc.fields[0]);
sinon.stub(doc, 'canInsertField').returns(false);
sinon.stub(doc, 'cursorInField').resolves(doc.fields[0]);
sinon.stub(doc, 'canInsertField').resolves(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], doc.fields[0].code)).unserialize();
var citation = yield (new Zotero.Integration.CitationField(doc.fields[0], doc.fields[0].code)).unserialize();
assert.equal(citation.citationItems.length, 2);
assert.equal(citation.citationItems[0].id, testItems[3].id);
});
@ -494,7 +483,7 @@ describe("Zotero.Integration", function () {
describe('when original citation text has been modified', function() {
var displayAlertStub;
before(function* () {
displayAlertStub = sinon.stub(DocumentPluginDummy.Document.prototype, 'displayAlert').returns(0);
displayAlertStub = sinon.stub(DocumentPluginDummy.Document.prototype, 'displayAlert').resolves(0);
});
beforeEach(function() {
displayAlertStub.reset();
@ -508,8 +497,8 @@ describe("Zotero.Integration", function () {
var doc = applications[docID].doc;
doc.fields[0].text = "modified";
sinon.stub(doc, 'cursorInField').returns(doc.fields[0]);
sinon.stub(doc, 'canInsertField').returns(false);
sinon.stub(doc, 'cursorInField').resolves(doc.fields[0]);
sinon.stub(doc, 'canInsertField').resolves(false);
await execCommand('addEditCitation', docID);
assert.equal(doc.fields.length, 2);
@ -523,9 +512,9 @@ describe("Zotero.Integration", function () {
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);
displayAlertStub.resolves(1);
sinon.stub(doc, 'cursorInField').resolves(doc.fields[0]);
sinon.stub(doc, 'canInsertField').resolves(false);
setAddEditItems(testItems[0]);
await execCommand('addEditCitation', docID);
@ -538,17 +527,17 @@ describe("Zotero.Integration", function () {
var docID = this.test.fullTitle();
var doc = applications[docID].doc;
var citation = (new Zotero.Integration.CitationField(doc.fields[0], doc.fields[0].code)).unserialize();
var citation = await (new Zotero.Integration.CitationField(doc.fields[0], doc.fields[0].code)).unserialize();
assert.isNotOk(citation.properties.dontUpdate);
doc.fields[0].text = "modified";
// Return Yes
displayAlertStub.returns(1);
displayAlertStub.resolves(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], doc.fields[0].code)).unserialize();
var citation = await (new Zotero.Integration.CitationField(doc.fields[0], doc.fields[0].code)).unserialize();
assert.isOk(citation.properties.dontUpdate);
});
it('should reset citation text if "no" selected in refresh prompt', async function() {
@ -556,18 +545,18 @@ describe("Zotero.Integration", function () {
var docID = this.test.fullTitle();
var doc = applications[docID].doc;
var citation = (new Zotero.Integration.CitationField(doc.fields[0], doc.fields[0].code)).unserialize();
var citation = await (new Zotero.Integration.CitationField(doc.fields[0], doc.fields[0].code)).unserialize();
assert.isNotOk(citation.properties.dontUpdate);
let origText = doc.fields[0].text;
doc.fields[0].text = "modified";
// Return No
displayAlertStub.returns(0);
displayAlertStub.resolves(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], doc.fields[0].code)).unserialize();
var citation = await (new Zotero.Integration.CitationField(doc.fields[0], doc.fields[0].code)).unserialize();
assert.isNotOk(citation.properties.dontUpdate);
});
});
@ -673,11 +662,11 @@ describe("Zotero.Integration", function () {
var field = setTextSpy.firstCall.thisValue;
for (let i = 0; i < setTextSpy.callCount; i++) {
assert.isTrue(field.equals(setTextSpy.getCall(i).thisValue));
assert.isTrue(yield field.equals(setTextSpy.getCall(i).thisValue));
}
for (let i = 0; i < setCodeSpy.callCount; i++) {
assert.isTrue(field.equals(setCodeSpy.getCall(i).thisValue));
assert.isTrue(yield field.equals(setCodeSpy.getCall(i).thisValue));
}
setTextSpy.restore();
@ -689,7 +678,7 @@ describe("Zotero.Integration", function () {
describe('#addEditBibliography', function() {
var docID = this.fullTitle();
beforeEach(function* () {
initDoc(docID);
yield initDoc(docID);
yield execCommand('addEditCitation', docID);
});
@ -699,7 +688,7 @@ describe("Zotero.Integration", function () {
assert.isFalse(displayDialogStub.called);
var biblPresent = false;
for (let i = applications[docID].doc.fields.length-1; i >= 0; i--) {
let field = Zotero.Integration.Field.loadExisting(applications[docID].doc.fields[i]);
let field = yield Zotero.Integration.Field.loadExisting(applications[docID].doc.fields[i]);
if (field.type == INTEGRATION_TYPE_BIBLIOGRAPHY) {
biblPresent = true;
break;