Merge pull request #1476 from adomasven:feature/connector-doc-integration
Doc Integration Endpoint for the Connector
This commit is contained in:
commit
d573a5b639
14 changed files with 1104 additions and 447 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
</radiogroup>
|
||||
</groupbox>
|
||||
|
||||
<groupbox>
|
||||
<groupbox id="formatUsing-groupbox">
|
||||
<caption label="&zotero.integration.prefs.formatUsing.label;"/>
|
||||
|
||||
<radiogroup id="formatUsing" orient="vertical">
|
||||
|
|
129
chrome/content/zotero/integration/progressBar.js
Normal file
129
chrome/content/zotero/integration/progressBar.js
Normal 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);
|
51
chrome/content/zotero/integration/progressBar.xul
Normal file
51
chrome/content/zotero/integration/progressBar.xul
Normal 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>
|
|
@ -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;
|
||||
|
|
192
chrome/content/zotero/xpcom/connector/httpIntegrationClient.js
Normal file
192
chrome/content/zotero/xpcom/connector/httpIntegrationClient.js
Normal 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;
|
||||
};
|
|
@ -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
|
@ -36,6 +36,7 @@ Zotero.Server = new function() {
|
|||
412:"Precondition Failed",
|
||||
500:"Internal Server Error",
|
||||
501:"Not Implemented",
|
||||
503:"Service Unavailable",
|
||||
504:"Gateway Timeout"
|
||||
};
|
||||
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue