Remove unneeded connector files, update connectorTypeSchemaData.js
Addresses zotero/zotero-connectors#121
This commit is contained in:
parent
dbeecb9b0a
commit
e1f09d4655
9 changed files with 91 additions and 1984 deletions
90
chrome/content/zotero/tools/build_typeSchemaData.html
Normal file
90
chrome/content/zotero/tools/build_typeSchemaData.html
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Build schemaData.js</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>This script builds schemaData.js, which contains Zotero schema information for the connector.</p>
|
||||||
|
<p id="result"></p>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
Zotero.Promise.coroutine(function* (){
|
||||||
|
// Create schema
|
||||||
|
var schema = {"itemTypes":{}, "creatorTypes":{}, "fields":{}};
|
||||||
|
var types = Zotero.ItemTypes.getTypes();
|
||||||
|
|
||||||
|
var fieldIDs = yield Zotero.DB.columnQueryAsync("SELECT fieldID FROM fieldsCombined");
|
||||||
|
var baseMappedFields = Zotero.ItemFields.getBaseMappedFields();
|
||||||
|
|
||||||
|
for (let fieldID of fieldIDs) {
|
||||||
|
var fieldObj = [/* name */Zotero.ItemFields.getName(fieldID)];
|
||||||
|
try {
|
||||||
|
fieldObj.push(/* localizedString */Zotero.getString("itemFields." + fieldObj.name));
|
||||||
|
} catch(e) {
|
||||||
|
fieldObj.push(/* name -> localizedString */fieldObj[0]);
|
||||||
|
}
|
||||||
|
fieldObj.push(/* isBaseField */ !baseMappedFields.includes(fieldID));
|
||||||
|
schema.fields[fieldID] = fieldObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// names, localizedStrings, creatorTypes, and fields for each item type
|
||||||
|
for (let type of types) {
|
||||||
|
var fieldIDs = Zotero.ItemFields.getItemTypeFields(type.id);
|
||||||
|
var baseFields = {};
|
||||||
|
for (let fieldID in fieldIDs) {
|
||||||
|
if (baseMappedFields.includes(fieldID)) {
|
||||||
|
baseFields[fieldID] = Zotero.ItemFields.getBaseIDFromTypeAndField(type.id, fieldID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var icon = Zotero.ItemTypes.getImageSrc(type.name);
|
||||||
|
icon = icon.substr(icon.lastIndexOf("/")+1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var creatorTypes = Zotero.CreatorTypes.getTypesForItemType(type.id).map((creatorType) => creatorType.id);
|
||||||
|
} catch (e) {
|
||||||
|
creatorTypes = [];
|
||||||
|
}
|
||||||
|
var primaryCreatorType = Zotero.CreatorTypes.getPrimaryIDForType(type.id);
|
||||||
|
if(creatorTypes[0] != primaryCreatorType) {
|
||||||
|
creatorTypes.splice(creatorTypes.indexOf(primaryCreatorType), 1);
|
||||||
|
creatorTypes.unshift(primaryCreatorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.itemTypes[type.id] = [
|
||||||
|
/* name */type.name,
|
||||||
|
/* localizedString */Zotero.ItemTypes.getLocalizedString(type.name),
|
||||||
|
/* creatorTypes */creatorTypes,
|
||||||
|
/* fields */ fieldIDs,
|
||||||
|
/* baseFields */baseFields,
|
||||||
|
/* icon */icon
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var types = Zotero.CreatorTypes.getTypes();
|
||||||
|
for (let type of types) {
|
||||||
|
schema.creatorTypes[type.id] = [
|
||||||
|
/* name */type.name,
|
||||||
|
/* localizedString */Zotero.CreatorTypes.getLocalizedString(type.name)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to file
|
||||||
|
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||||
|
var fp = Components.classes["@mozilla.org/filepicker;1"]
|
||||||
|
.createInstance(nsIFilePicker);
|
||||||
|
fp.init(window, Zotero.getString('dataDir.selectDir'), nsIFilePicker.modeGetFolder);
|
||||||
|
|
||||||
|
let resultElem = document.getElementById('result');
|
||||||
|
if (fp.show() != nsIFilePicker.returnOK) {
|
||||||
|
result.innerHTML = '<p>Failed.</p>';
|
||||||
|
} else {
|
||||||
|
let schemaFile = fp.file;
|
||||||
|
schemaFile.append("connectorTypeSchemaData.js");
|
||||||
|
Zotero.File.putContents(schemaFile, `Zotero.Connector_Types.schema = ${JSON.stringify(schema)}`);
|
||||||
|
result.innerHTML = `<p>Wrote ${schemaFile.path} successfully.</p>`;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,183 +0,0 @@
|
||||||
/*
|
|
||||||
***** BEGIN LICENSE BLOCK *****
|
|
||||||
|
|
||||||
Copyright © 2011 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 *****
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emulates very small parts of cachedTypes.js and itemFields.js APIs for use with connector
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @namespace
|
|
||||||
*/
|
|
||||||
Zotero.Connector_Types = new function() {
|
|
||||||
/**
|
|
||||||
* Initializes types
|
|
||||||
* @param {Object} typeSchema typeSchema generated by Zotero.Connector.GetData#_generateTypeSchema
|
|
||||||
*/
|
|
||||||
this.init = function() {
|
|
||||||
const schemaTypes = ["itemTypes", "creatorTypes", "fields"];
|
|
||||||
|
|
||||||
// attach IDs and make referenceable by either ID or name
|
|
||||||
for(var i=0; i<schemaTypes.length; i++) {
|
|
||||||
var schemaType = schemaTypes[i];
|
|
||||||
this[schemaType] = Zotero.Utilities.deepCopy(Zotero.Connector_Types.schema[schemaType]);
|
|
||||||
for(var id in Zotero.Connector_Types.schema[schemaType]) {
|
|
||||||
var entry = this[schemaType][id];
|
|
||||||
entry.unshift(parseInt(id, 10));
|
|
||||||
this[schemaType][entry[1]/* name */] = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var itemTypes = Zotero.Connector_Types["itemTypes"];
|
|
||||||
var creatorTypes = Zotero.Connector_Types["creatorTypes"];
|
|
||||||
var fields = Zotero.Connector_Types["fields"];
|
|
||||||
|
|
||||||
Zotero.CachedTypes = function() {
|
|
||||||
var thisType = Zotero.Connector_Types[this.schemaType];
|
|
||||||
|
|
||||||
this.getID = function(idOrName) {
|
|
||||||
var type = thisType[idOrName];
|
|
||||||
return (type ? type[0]/* id */ : false);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getName = function(idOrName) {
|
|
||||||
var type = thisType[idOrName];
|
|
||||||
return (type ? type[1]/* name */ : false);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getLocalizedString = function(idOrName) {
|
|
||||||
var type = thisType[idOrName];
|
|
||||||
return (type ? type[2]/* localizedString */ : false);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.ItemTypes = new function() {
|
|
||||||
this.schemaType = "itemTypes";
|
|
||||||
Zotero.CachedTypes.call(this);
|
|
||||||
|
|
||||||
this.getImageSrc = function(idOrName) {
|
|
||||||
var itemType = Zotero.Connector_Types["itemTypes"][idOrName];
|
|
||||||
var icon = itemType ? itemType[6]/* icon */ : "treeitem-"+idOrName+".png";
|
|
||||||
|
|
||||||
if(Zotero.isBookmarklet) {
|
|
||||||
return ZOTERO_CONFIG.BOOKMARKLET_URL+"images/"+icon;
|
|
||||||
} else if(Zotero.isFx) {
|
|
||||||
return "chrome://zotero/skin/"+icon;
|
|
||||||
} else if(Zotero.isBrowserExt) {
|
|
||||||
return chrome.extension.getURL("images/"+icon);
|
|
||||||
} else if(Zotero.isSafari) {
|
|
||||||
return safari.extension.baseURI+"images/"+icon;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.CreatorTypes = new function() {
|
|
||||||
this.schemaType = "creatorTypes";
|
|
||||||
Zotero.CachedTypes.call(this);
|
|
||||||
|
|
||||||
this.getTypesForItemType = function(idOrName) {
|
|
||||||
var itemType = itemTypes[idOrName];
|
|
||||||
if(!itemType) return false;
|
|
||||||
|
|
||||||
var itemCreatorTypes = itemType[3]; // creatorTypes
|
|
||||||
if (!itemCreatorTypes
|
|
||||||
// TEMP: 'note' and 'attachment' have an array containing false
|
|
||||||
|| (itemCreatorTypes.length == 1 && !itemCreatorTypes[0])) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
var n = itemCreatorTypes.length;
|
|
||||||
var outputTypes = new Array(n);
|
|
||||||
|
|
||||||
for(var i=0; i<n; i++) {
|
|
||||||
var creatorType = creatorTypes[itemCreatorTypes[i]];
|
|
||||||
outputTypes[i] = {"id":creatorType[0]/* id */,
|
|
||||||
"name":creatorType[1]/* name */};
|
|
||||||
}
|
|
||||||
return outputTypes;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getPrimaryIDForType = function(idOrName) {
|
|
||||||
var itemType = itemTypes[idOrName];
|
|
||||||
if(!itemType) return false;
|
|
||||||
return itemType[3]/* creatorTypes */[0];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.ItemFields = new function() {
|
|
||||||
this.schemaType = "fields";
|
|
||||||
Zotero.CachedTypes.call(this);
|
|
||||||
|
|
||||||
this.isValidForType = function(fieldIdOrName, typeIdOrName) {
|
|
||||||
var field = fields[fieldIdOrName], itemType = itemTypes[typeIdOrName];
|
|
||||||
|
|
||||||
// mimics itemFields.js
|
|
||||||
if(!field || !itemType) return false;
|
|
||||||
|
|
||||||
/* fields */ /* id */
|
|
||||||
return itemType[4].indexOf(field[0]) !== -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getFieldIDFromTypeAndBase = function(typeIdOrName, fieldIdOrName) {
|
|
||||||
var baseField = fields[fieldIdOrName], itemType = itemTypes[typeIdOrName];
|
|
||||||
|
|
||||||
if(!baseField || !itemType) return false;
|
|
||||||
|
|
||||||
// get as ID
|
|
||||||
baseField = baseField[0]/* id */;
|
|
||||||
|
|
||||||
// loop through base fields for item type
|
|
||||||
var baseFields = itemType[5];
|
|
||||||
for(var i in baseFields) {
|
|
||||||
if(baseFields[i] === baseField) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getBaseIDFromTypeAndField = function(typeIdOrName, fieldIdOrName) {
|
|
||||||
var field = fields[fieldIdOrName], itemType = itemTypes[typeIdOrName];
|
|
||||||
if(!field || !itemType) {
|
|
||||||
throw new Error("Invalid field or type ID");
|
|
||||||
}
|
|
||||||
|
|
||||||
var baseField = itemType[5]/* baseFields */[field[0]/* id */];
|
|
||||||
return baseField ? baseField : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.getItemTypeFields = function(typeIdOrName) {
|
|
||||||
return itemTypes[typeIdOrName][4]/* fields */.slice();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Passes schema to a callback
|
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
|
||||||
this.getSchema = function(callback) {
|
|
||||||
callback(Zotero.Connector_Types.schema);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,338 +0,0 @@
|
||||||
/*
|
|
||||||
***** BEGIN LICENSE BLOCK *****
|
|
||||||
|
|
||||||
Copyright © 2011 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 *****
|
|
||||||
*/
|
|
||||||
|
|
||||||
Zotero.Connector = new function() {
|
|
||||||
const CONNECTOR_URI = "http://127.0.0.1:23119/";
|
|
||||||
const CONNECTOR_API_VERSION = 2;
|
|
||||||
|
|
||||||
var _ieStandaloneIframeTarget, _ieConnectorCallbacks;
|
|
||||||
// As of Chrome 38 (and corresponding Opera version 24?) pages loaded over
|
|
||||||
// https (i.e. the zotero bookmarklet iframe) can not send requests over
|
|
||||||
// http, so pinging Standalone at http://127.0.0.1 fails.
|
|
||||||
// Disable for all browsers, except IE, which may be used frequently with ZSA
|
|
||||||
this.isOnline = Zotero.isBookmarklet && !Zotero.isIE ? false : null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if Zotero is online and passes current status to callback
|
|
||||||
* @param {Function} callback
|
|
||||||
*/
|
|
||||||
this.checkIsOnline = function(callback) {
|
|
||||||
// Only check once in bookmarklet
|
|
||||||
if(Zotero.isBookmarklet && this.isOnline !== null) {
|
|
||||||
callback(this.isOnline);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Zotero.isIE) {
|
|
||||||
if(window.location.protocol !== "http:") {
|
|
||||||
this.isOnline = false;
|
|
||||||
callback(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.debug("Connector: Looking for Zotero Standalone");
|
|
||||||
var me = this;
|
|
||||||
var fail = function() {
|
|
||||||
if(me.isOnline !== null) return;
|
|
||||||
Zotero.debug("Connector: Zotero Standalone is not online or cannot be contacted");
|
|
||||||
me.isOnline = false;
|
|
||||||
callback(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.setTimeout(fail, 1000);
|
|
||||||
try {
|
|
||||||
var xdr = new XDomainRequest();
|
|
||||||
xdr.timeout = 700;
|
|
||||||
xdr.open("POST", "http://127.0.0.1:23119/connector/ping", true);
|
|
||||||
xdr.onerror = function() {
|
|
||||||
Zotero.debug("Connector: XDomainRequest to Zotero Standalone experienced an error");
|
|
||||||
fail();
|
|
||||||
};
|
|
||||||
xdr.ontimeout = function() {
|
|
||||||
Zotero.debug("Connector: XDomainRequest to Zotero Standalone timed out");
|
|
||||||
fail();
|
|
||||||
};
|
|
||||||
xdr.onload = function() {
|
|
||||||
if(me.isOnline !== null) return;
|
|
||||||
me.isOnline = true;
|
|
||||||
Zotero.debug("Connector: Standalone found; trying IE hack");
|
|
||||||
|
|
||||||
_ieConnectorCallbacks = [];
|
|
||||||
var listener = function(event) {
|
|
||||||
if(event.origin !== "http://127.0.0.1:23119"
|
|
||||||
|| event.source !== iframe.contentWindow) return;
|
|
||||||
if(event.stopPropagation) {
|
|
||||||
event.stopPropagation();
|
|
||||||
} else {
|
|
||||||
event.cancelBubble = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is the first time the target was loaded, then this is a loaded
|
|
||||||
// event
|
|
||||||
if(!_ieStandaloneIframeTarget) {
|
|
||||||
Zotero.debug("Connector: Standalone loaded");
|
|
||||||
_ieStandaloneIframeTarget = iframe.contentWindow;
|
|
||||||
callback(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, this is a response event
|
|
||||||
try {
|
|
||||||
var data = JSON.parse(event.data);
|
|
||||||
} catch(e) {
|
|
||||||
Zotero.debug("Invalid JSON received: "+event.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var xhrSurrogate = {
|
|
||||||
"status":data[1],
|
|
||||||
"responseText":data[2],
|
|
||||||
"getResponseHeader":function(x) { return data[3][x.toLowerCase()] }
|
|
||||||
};
|
|
||||||
_ieConnectorCallbacks[data[0]](xhrSurrogate);
|
|
||||||
delete _ieConnectorCallbacks[data[0]];
|
|
||||||
};
|
|
||||||
|
|
||||||
if(window.addEventListener) {
|
|
||||||
window.addEventListener("message", listener, false);
|
|
||||||
} else {
|
|
||||||
window.attachEvent("onmessage", function() { listener(event); });
|
|
||||||
}
|
|
||||||
|
|
||||||
var iframe = document.createElement("iframe");
|
|
||||||
iframe.src = "http://127.0.0.1:23119/connector/ieHack";
|
|
||||||
document.documentElement.appendChild(iframe);
|
|
||||||
};
|
|
||||||
xdr.send("");
|
|
||||||
} catch(e) {
|
|
||||||
Zotero.logError(e);
|
|
||||||
fail();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Zotero.Connector.callMethod("ping", {}, function(status) {
|
|
||||||
callback(status !== false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the XHR to execute an RPC call.
|
|
||||||
*
|
|
||||||
* @param {String|Object} options - The method name as a string or an object with the
|
|
||||||
* following properties:
|
|
||||||
* method - method name
|
|
||||||
* headers - an object of HTTP headers to send
|
|
||||||
* queryString - a query string to pass on the HTTP call
|
|
||||||
* @param {Object} data - RPC data to POST. If null or undefined, a GET request is sent.
|
|
||||||
* @param {Function} callback - Function to be called when requests complete.
|
|
||||||
*/
|
|
||||||
this.callMethod = function(options, data, callback, tab) {
|
|
||||||
// Don't bother trying if not online in bookmarklet
|
|
||||||
if(Zotero.isBookmarklet && this.isOnline === false) {
|
|
||||||
callback(false, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof options == 'string') {
|
|
||||||
options = {method: options};
|
|
||||||
}
|
|
||||||
var method = options.method;
|
|
||||||
var headers = Object.assign({
|
|
||||||
"Content-Type":"application/json",
|
|
||||||
"X-Zotero-Version":Zotero.version,
|
|
||||||
"X-Zotero-Connector-API-Version":CONNECTOR_API_VERSION
|
|
||||||
}, options.headers || {});
|
|
||||||
var queryString = options.queryString ? ("?" + options.queryString) : "";
|
|
||||||
|
|
||||||
var newCallback = function(req) {
|
|
||||||
try {
|
|
||||||
var isOnline = req.status !== 0 && req.status !== 403 && req.status !== 412;
|
|
||||||
|
|
||||||
if(Zotero.Connector.isOnline !== isOnline) {
|
|
||||||
Zotero.Connector.isOnline = isOnline;
|
|
||||||
if(Zotero.Connector_Browser && Zotero.Connector_Browser.onStateChange) {
|
|
||||||
Zotero.Connector_Browser.onStateChange(isOnline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var val = null;
|
|
||||||
if(req.responseText) {
|
|
||||||
if(req.getResponseHeader("Content-Type") === "application/json") {
|
|
||||||
val = JSON.parse(req.responseText);
|
|
||||||
} else {
|
|
||||||
val = req.responseText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(req.status == 0 || req.status >= 400) {
|
|
||||||
Zotero.debug("Connector: Method "+method+" failed with status "+req.status);
|
|
||||||
if(callback) callback(false, req.status, val);
|
|
||||||
|
|
||||||
// Check for incompatible version
|
|
||||||
if(req.status === 412) {
|
|
||||||
if(Zotero.Connector_Browser && Zotero.Connector_Browser.onIncompatibleStandaloneVersion) {
|
|
||||||
var standaloneVersion = req.getResponseHeader("X-Zotero-Version");
|
|
||||||
Zotero.Connector_Browser.onIncompatibleStandaloneVersion(Zotero.version, standaloneVersion);
|
|
||||||
throw "Connector: Version mismatch: Connector version "+Zotero.version
|
|
||||||
+", Standalone version "+(standaloneVersion ? standaloneVersion : "<unknown>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Zotero.debug("Connector: Method "+method+" succeeded");
|
|
||||||
if(callback) callback(val, req.status);
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
Zotero.logError(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if(Zotero.isIE) { // IE requires XDR for CORS
|
|
||||||
if(_ieStandaloneIframeTarget) {
|
|
||||||
var requestID = Zotero.Utilities.randomString();
|
|
||||||
_ieConnectorCallbacks[requestID] = newCallback;
|
|
||||||
_ieStandaloneIframeTarget.postMessage(JSON.stringify([null, "connectorRequest",
|
|
||||||
[requestID, method, JSON.stringify(data)]]), "http://127.0.0.1:23119/connector/ieHack");
|
|
||||||
} else {
|
|
||||||
Zotero.debug("Connector: No iframe target; not sending to Standalone");
|
|
||||||
callback(false, 0);
|
|
||||||
}
|
|
||||||
} else { // Other browsers can use plain doPost
|
|
||||||
var uri = CONNECTOR_URI + "connector/" + method + queryString;
|
|
||||||
if (headers["Content-Type"] == 'application/json') {
|
|
||||||
data = JSON.stringify(data);
|
|
||||||
}
|
|
||||||
if (data == null || data == undefined) {
|
|
||||||
Zotero.HTTP.doGet(uri, newCallback, headers);
|
|
||||||
} else {
|
|
||||||
Zotero.HTTP.doPost(uri, data, newCallback, headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds detailed cookies to the data before sending "saveItems" request to
|
|
||||||
* the server/Standalone
|
|
||||||
*
|
|
||||||
* @param {Object} data RPC data. See documentation above.
|
|
||||||
* @param {Function} callback Function to be called when requests complete.
|
|
||||||
*/
|
|
||||||
this.setCookiesThenSaveItems = function(data, callback, tab) {
|
|
||||||
if(Zotero.isFx && !Zotero.isBookmarklet && data.uri) {
|
|
||||||
var host = Services.io.newURI(data.uri, null, null).host;
|
|
||||||
var cookieEnum = Services.cookies.getCookiesFromHost(host);
|
|
||||||
var cookieHeader = '';
|
|
||||||
while(cookieEnum.hasMoreElements()) {
|
|
||||||
var cookie = cookieEnum.getNext().QueryInterface(Components.interfaces.nsICookie2);
|
|
||||||
cookieHeader += '\n' + cookie.name + '=' + cookie.value
|
|
||||||
+ ';Domain=' + cookie.host
|
|
||||||
+ (cookie.path ? ';Path=' + cookie.path : '')
|
|
||||||
+ (!cookie.isDomain ? ';hostOnly' : '') //not a legit flag, but we have to use it internally
|
|
||||||
+ (cookie.isSecure ? ';secure' : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(cookieHeader) {
|
|
||||||
data.detailedCookies = cookieHeader.substr(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.callMethod("saveItems", data, callback, tab);
|
|
||||||
return;
|
|
||||||
} else if(Zotero.isBrowserExt && !Zotero.isBookmarklet) {
|
|
||||||
var self = this;
|
|
||||||
chrome.cookies.getAll({url: tab.url}, function(cookies) {
|
|
||||||
var cookieHeader = '';
|
|
||||||
for(var i=0, n=cookies.length; i<n; i++) {
|
|
||||||
cookieHeader += '\n' + cookies[i].name + '=' + cookies[i].value
|
|
||||||
+ ';Domain=' + cookies[i].domain
|
|
||||||
+ (cookies[i].path ? ';Path=' + cookies[i].path : '')
|
|
||||||
+ (cookies[i].hostOnly ? ';hostOnly' : '') //not a legit flag, but we have to use it internally
|
|
||||||
+ (cookies[i].secure ? ';secure' : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(cookieHeader) {
|
|
||||||
data.detailedCookies = cookieHeader.substr(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cookie URI needed to set up the cookie sandbox on standalone
|
|
||||||
data.uri = tab.url;
|
|
||||||
|
|
||||||
self.callMethod("saveItems", data, callback, tab);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.callMethod("saveItems", data, callback, tab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.Connector_Debug = new function() {
|
|
||||||
/**
|
|
||||||
* Call a callback depending upon whether debug output is being stored
|
|
||||||
*/
|
|
||||||
this.storing = function(callback) {
|
|
||||||
callback(Zotero.Debug.storing);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call a callback with the lines themselves
|
|
||||||
*/
|
|
||||||
this.get = function(callback) {
|
|
||||||
Zotero.Debug.get().then(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Call a callback with the number of lines of output
|
|
||||||
*/
|
|
||||||
this.count = function(callback) {
|
|
||||||
callback(Zotero.Debug.count());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submit data to the server
|
|
||||||
*/
|
|
||||||
this.submitReport = function(callback) {
|
|
||||||
Zotero.Debug.get().then(function(output){
|
|
||||||
return Zotero.HTTP.request(
|
|
||||||
ZOTERO_CONFIG.REPOSITORY_URL + "report?debug=1",
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/plain"
|
|
||||||
},
|
|
||||||
body: output,
|
|
||||||
successCodes: false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}).then(function(xmlhttp){
|
|
||||||
if (!xmlhttp.responseXML) {
|
|
||||||
callback(false, 'Invalid response from server');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var reported = xmlhttp.responseXML.getElementsByTagName('reported');
|
|
||||||
if (reported.length != 1) {
|
|
||||||
callback(false, 'The server returned an error. Please try again.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var reportID = reported[0].getAttribute('reportID');
|
|
||||||
callback(true, reportID);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
/*
|
|
||||||
***** BEGIN LICENSE BLOCK *****
|
|
||||||
|
|
||||||
Copyright © 2011 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 *****
|
|
||||||
*/
|
|
||||||
|
|
||||||
Zotero.Connector_Browser = new function() {
|
|
||||||
/**
|
|
||||||
* Called if Zotero version is determined to be incompatible with Standalone
|
|
||||||
*/
|
|
||||||
this.onIncompatibleStandaloneVersion = function(zoteroVersion, standaloneVersion) {
|
|
||||||
Zotero.startupError = 'Zotero for Firefox '+Zotero.version+' is incompatible with the running '+
|
|
||||||
'version of Zotero Standalone'+(standaloneVersion ? " ("+standaloneVersion+")" : "")+
|
|
||||||
'.\n\nPlease ensure that you have installed the latest version of these components. See '+
|
|
||||||
'http://www.zotero.org/support/standalone for more details.';
|
|
||||||
Zotero.initialized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if connector is offline. This should only happen if Zotero is getting a DB busy
|
|
||||||
* message and no connector is open, so use the DB busy error message here.
|
|
||||||
*/
|
|
||||||
this.onStateChange = function(isOnline) {
|
|
||||||
if(isOnline) return;
|
|
||||||
|
|
||||||
var msg = Zotero.localeJoin([
|
|
||||||
Zotero.getString('startupError.databaseInUse'),
|
|
||||||
Zotero.getString(Zotero.isStandalone ? 'startupError.closeFirefox' : 'startupError.closeStandalone')
|
|
||||||
]);
|
|
||||||
Zotero.startupError = msg;
|
|
||||||
Zotero.initialized = false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
/*
|
|
||||||
***** BEGIN LICENSE BLOCK *****
|
|
||||||
|
|
||||||
Copyright © 2011 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 *****
|
|
||||||
*/
|
|
||||||
|
|
||||||
const TRANSLATOR_CODE_PREFIX = "translatorCode-";
|
|
||||||
Zotero.Repo = new function() {
|
|
||||||
var _nextCheck;
|
|
||||||
var _timeoutID;
|
|
||||||
const infoRe = /^\s*{[\S\s]*?}\s*?[\r\n]/;
|
|
||||||
|
|
||||||
this.SOURCE_ZOTERO_STANDALONE = 1;
|
|
||||||
this.SOURCE_REPO = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to retrieve translator metadata from Zotero Standalone and initialize repository check
|
|
||||||
* timer
|
|
||||||
*/
|
|
||||||
this.init = function() {
|
|
||||||
// get time of next check
|
|
||||||
_nextCheck = Zotero.Prefs.get("connector.repo.lastCheck.localTime")
|
|
||||||
+ZOTERO_CONFIG.REPOSITORY_CHECK_INTERVAL*1000;
|
|
||||||
|
|
||||||
// update from standalone, but only cascade to repo if we are overdue
|
|
||||||
_updateFromStandalone(_nextCheck <= Date.now());
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force updating translators
|
|
||||||
*/
|
|
||||||
var update = this.update = function(reset) {
|
|
||||||
_updateFromStandalone(true, reset);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get translator code from repository
|
|
||||||
* @param {String} translatorID ID of the translator to retrieve code for
|
|
||||||
*/
|
|
||||||
this.getTranslatorCode = Zotero.Promise.method(function (translatorID, debugMode) {
|
|
||||||
var deferred = Zotero.Promise.defer();
|
|
||||||
|
|
||||||
// try standalone
|
|
||||||
Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":translatorID}, function(result) {
|
|
||||||
if(result) {
|
|
||||||
deferred.resolve(
|
|
||||||
Zotero.Promise.all(
|
|
||||||
[
|
|
||||||
_haveCode(result, translatorID),
|
|
||||||
Zotero.Repo.SOURCE_ZOTERO_STANDALONE
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Don't fetch from repo in debug mode
|
|
||||||
if (debugMode) {
|
|
||||||
deferred.resolve([false, Zotero.Repo.SOURCE_ZOTERO_STANDALONE]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// then try repo
|
|
||||||
Zotero.HTTP.doGet(
|
|
||||||
ZOTERO_CONFIG.REPOSITORY_URL + "code/" + translatorID + "?version=" + Zotero.version,
|
|
||||||
function(xmlhttp) {
|
|
||||||
deferred.resolve(
|
|
||||||
Zotero.Promise.all(
|
|
||||||
[
|
|
||||||
_haveCode(
|
|
||||||
xmlhttp.status === 200 ? xmlhttp.responseText : false,
|
|
||||||
translatorID
|
|
||||||
),
|
|
||||||
Zotero.Repo.SOURCE_REPO
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when code has been retrieved from standalone or repo
|
|
||||||
*/
|
|
||||||
function _haveCode(code, translatorID) {
|
|
||||||
if(!code) {
|
|
||||||
Zotero.logError(new Error("Code could not be retrieved for " + translatorID));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!Zotero.isFx) {
|
|
||||||
code = Zotero.Translators.preprocessCode(code);
|
|
||||||
|
|
||||||
var m = infoRe.exec(code);
|
|
||||||
if (!m) {
|
|
||||||
Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var metadata = JSON.parse(m[0]);
|
|
||||||
} catch(e) {
|
|
||||||
Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var translator = Zotero.Translators.getWithoutCode(translatorID);
|
|
||||||
|
|
||||||
if(metadata.lastUpdated !== translator.lastUpdated) {
|
|
||||||
if(Zotero.Date.sqlToDate(metadata.lastUpdated) > Zotero.Date.sqlToDate(translator.lastUpdated)) {
|
|
||||||
Zotero.debug("Repo: Retrieved code for "+metadata.label+" newer than stored metadata; updating");
|
|
||||||
Zotero.Translators.update([metadata]);
|
|
||||||
} else {
|
|
||||||
Zotero.debug("Repo: Retrieved code for "+metadata.label+" older than stored metadata; not caching");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve translator metadata from Zotero Standalone
|
|
||||||
* @param {Boolean} [tryRepoOnFailure] If true, run _updateFromRepo() if standalone cannot be
|
|
||||||
* contacted
|
|
||||||
*/
|
|
||||||
function _updateFromStandalone(tryRepoOnFailure, reset, callback) {
|
|
||||||
Zotero.Connector.callMethod("getTranslators", {}, function(result) {
|
|
||||||
if(!result && tryRepoOnFailure) {
|
|
||||||
_updateFromRepo(reset, callback);
|
|
||||||
} else {
|
|
||||||
// Standalone always returns all translators without .deleted property
|
|
||||||
_handleResponse(result, true);
|
|
||||||
if(callback) callback(!!result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve metadata from repository
|
|
||||||
*/
|
|
||||||
function _updateFromRepo(reset, callback) {
|
|
||||||
var url = ZOTERO_CONFIG.REPOSITORY_URL + "metadata?version=" + Zotero.version + "&last="+
|
|
||||||
(reset ? "0" : Zotero.Prefs.get("connector.repo.lastCheck.repoTime"));
|
|
||||||
|
|
||||||
Zotero.HTTP.doGet(url, function(xmlhttp) {
|
|
||||||
var success = xmlhttp.status === 200;
|
|
||||||
_handleResponse(success ? JSON.parse(xmlhttp.responseText) : false, reset);
|
|
||||||
|
|
||||||
if(success) {
|
|
||||||
var date = xmlhttp.getResponseHeader("Date");
|
|
||||||
Zotero.Prefs.set("connector.repo.lastCheck.repoTime",
|
|
||||||
Math.floor(Date.parse(date)/1000));
|
|
||||||
}
|
|
||||||
if(callback) callback(!!result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle response from Zotero Standalone or repository and set timer for next update
|
|
||||||
*/
|
|
||||||
function _handleResponse(result, reset) {
|
|
||||||
// set up timer
|
|
||||||
var now = Date.now();
|
|
||||||
|
|
||||||
if(result) {
|
|
||||||
Zotero.Translators.update(result, reset);
|
|
||||||
Zotero.Prefs.set("connector.repo.lastCheck.localTime", now);
|
|
||||||
Zotero.debug("Repo: Check succeeded");
|
|
||||||
} else {
|
|
||||||
Zotero.debug("Repo: Check failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(result || _nextCheck <= now) {
|
|
||||||
// if we failed a scheduled check, then use retry interval
|
|
||||||
_nextCheck = now+(result
|
|
||||||
? ZOTERO_CONFIG.REPOSITORY_CHECK_INTERVAL
|
|
||||||
: ZOTERO_CONFIG.REPOSITORY_RETRY_INTERVAL)*1000;
|
|
||||||
} else if(_timeoutID) {
|
|
||||||
// if we didn't fail a scheduled check and another is already scheduled, leave it
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove old timeout and create a new one
|
|
||||||
if(_timeoutID) clearTimeout(_timeoutID);
|
|
||||||
var nextCheckIn = (_nextCheck-now+2000);
|
|
||||||
_timeoutID = setTimeout(update, nextCheckIn);
|
|
||||||
Zotero.debug("Repo: Next check in "+nextCheckIn);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,726 +0,0 @@
|
||||||
/*
|
|
||||||
***** BEGIN LICENSE BLOCK *****
|
|
||||||
|
|
||||||
Copyright © 2012 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 *****
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save translator items.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} options
|
|
||||||
* <li>libraryID - ID of library in which items should be saved</li>
|
|
||||||
* <li>collections - New collections to create (used during Import translation</li>
|
|
||||||
* <li>attachmentMode - One of Zotero.Translate.ItemSaver.ATTACHMENT_* specifying how attachments should be saved</li>
|
|
||||||
* <li>forceTagType - Force tags to specified tag type</li>
|
|
||||||
* <li>cookieSandbox - Cookie sandbox for attachment requests</li>
|
|
||||||
* <li>proxy - A proxy to deproxify item URLs</li>
|
|
||||||
* <li>baseURI - URI to which attachment paths should be relative</li>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
Zotero.Translate.ItemSaver = function(options) {
|
|
||||||
this.newItems = [];
|
|
||||||
this._proxy = options.proxy;
|
|
||||||
this._baseURI = options.baseURI;
|
|
||||||
|
|
||||||
// Add listener for callbacks, but only for Safari or the bookmarklet. In Chrome, we
|
|
||||||
// (have to) save attachments from the inject page.
|
|
||||||
if(Zotero.Messaging && !Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded
|
|
||||||
&& (Zotero.isBookmarklet || Zotero.isSafari)) {
|
|
||||||
Zotero.Messaging.addMessageListener("attachmentCallback", function(data) {
|
|
||||||
var id = data[0],
|
|
||||||
status = data[1];
|
|
||||||
var callback = Zotero.Translate.ItemSaver._attachmentCallbacks[id];
|
|
||||||
if(callback) {
|
|
||||||
if(status === false || status === 100) {
|
|
||||||
delete Zotero.Translate.ItemSaver._attachmentCallbacks[id];
|
|
||||||
} else {
|
|
||||||
data[1] = 50+data[1]/2;
|
|
||||||
}
|
|
||||||
callback(data[1], data[2]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded = false;
|
|
||||||
Zotero.Translate.ItemSaver._attachmentCallbacks = {};
|
|
||||||
|
|
||||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_IGNORE = 0;
|
|
||||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD = 1;
|
|
||||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE = 2;
|
|
||||||
|
|
||||||
Zotero.Translate.ItemSaver.prototype = {
|
|
||||||
/**
|
|
||||||
* Saves items to Standalone or the server
|
|
||||||
* @param items Items in Zotero.Item.toArray() format
|
|
||||||
* @param {Function} [attachmentCallback] A callback that receives information about attachment
|
|
||||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
|
||||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
|
||||||
*/
|
|
||||||
saveItems: function (items, attachmentCallback) {
|
|
||||||
var deferred = Zotero.Promise.defer();
|
|
||||||
// first try to save items via connector
|
|
||||||
var payload = { items, uri: this._baseURI };
|
|
||||||
if (Zotero.isSafari) {
|
|
||||||
// This is the best in terms of cookies we can do in Safari
|
|
||||||
payload.cookie = document.cookie;
|
|
||||||
}
|
|
||||||
payload.proxy = this._proxy && this._proxy.toJSON();
|
|
||||||
Zotero.Connector.setCookiesThenSaveItems(payload, function(data, status) {
|
|
||||||
if(data !== false) {
|
|
||||||
Zotero.debug("Translate: Save via Standalone succeeded");
|
|
||||||
var haveAttachments = false;
|
|
||||||
if(data && data.items) {
|
|
||||||
for(var i=0; i<data.items.length; i++) {
|
|
||||||
var attachments = items[i].attachments = data.items[i].attachments;
|
|
||||||
for(var j=0; j<attachments.length; j++) {
|
|
||||||
if(attachments[j].id) {
|
|
||||||
attachmentCallback(attachments[j], 0);
|
|
||||||
haveAttachments = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deferred.resolve(items);
|
|
||||||
if (haveAttachments) this._pollForProgress(items, attachmentCallback);
|
|
||||||
} else if(Zotero.isFx) {
|
|
||||||
deferred.reject(new Error("Save via Standalone failed with " + status));
|
|
||||||
} else {
|
|
||||||
deferred.resolve(this._saveToServer(items, attachmentCallback));
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
return deferred.promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Polls for updates to attachment progress
|
|
||||||
* @param items Items in Zotero.Item.toArray() format
|
|
||||||
* @param {Function} attachmentCallback A callback that receives information about attachment
|
|
||||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
|
||||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
|
||||||
* attachmentCallback() will be called with all attachments that will be saved
|
|
||||||
*/
|
|
||||||
"_pollForProgress":function(items, attachmentCallback) {
|
|
||||||
var attachments = [];
|
|
||||||
var progressIDs = [];
|
|
||||||
var previousStatus = [];
|
|
||||||
for(var i=0; i<items.length; i++) {
|
|
||||||
var itemAttachments = items[i].attachments;
|
|
||||||
for(var j=0; j<itemAttachments.length; j++) {
|
|
||||||
if(itemAttachments[j].id) {
|
|
||||||
attachments.push(itemAttachments[j]);
|
|
||||||
progressIDs.push(itemAttachments[j].id);
|
|
||||||
previousStatus.push(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var nPolls = 0;
|
|
||||||
var poll = function() {
|
|
||||||
Zotero.Connector.callMethod("attachmentProgress", progressIDs, function(currentStatus, status) {
|
|
||||||
if(currentStatus) {
|
|
||||||
for(var i=0; i<attachments.length; i++) {
|
|
||||||
if(currentStatus[i] === 100 || currentStatus[i] === false) {
|
|
||||||
attachmentCallback(attachments[i], currentStatus[i]);
|
|
||||||
attachments.splice(i, 1);
|
|
||||||
progressIDs.splice(i, 1);
|
|
||||||
previousStatus.splice(i, 1);
|
|
||||||
currentStatus.splice(i, 1);
|
|
||||||
i--;
|
|
||||||
} else if(currentStatus[i] !== previousStatus[i]) {
|
|
||||||
attachmentCallback(attachments[i], currentStatus[i]);
|
|
||||||
previousStatus[i] = currentStatus[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(nPolls++ < 60 && attachments.length) {
|
|
||||||
setTimeout(poll, 1000);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var i=0; i<attachments.length; i++) {
|
|
||||||
attachmentCallback(attachments[i], false, "Lost connection to Zotero Standalone");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
poll();
|
|
||||||
},
|
|
||||||
|
|
||||||
// ALL CODE BELOW THIS POINT IS EXECUTED ONLY IN NON-FIREFOX ENVIRONMENTS
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves items to server
|
|
||||||
* @param items Items in Zotero.Item.toArray() format
|
|
||||||
* @param {Function} attachmentCallback A callback that receives information about attachment
|
|
||||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
|
||||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
|
||||||
* attachmentCallback() will be called with all attachments that will be saved
|
|
||||||
*/
|
|
||||||
_saveToServer: function (items, attachmentCallback) {
|
|
||||||
var newItems = [], itemIndices = [], typedArraysSupported = false;
|
|
||||||
try {
|
|
||||||
typedArraysSupported = !!(new Uint8Array(1) && new Blob());
|
|
||||||
} catch(e) {}
|
|
||||||
|
|
||||||
for(var i=0, n=items.length; i<n; i++) {
|
|
||||||
var item = items[i];
|
|
||||||
// deproxify url
|
|
||||||
if (this._proxy && item.url) {
|
|
||||||
item.url = this._proxy.toProper(item.url);
|
|
||||||
}
|
|
||||||
itemIndices[i] = newItems.length;
|
|
||||||
newItems = newItems.concat(Zotero.Utilities.itemToServerJSON(item));
|
|
||||||
if(typedArraysSupported) {
|
|
||||||
for(var j=0; j<item.attachments.length; j++) {
|
|
||||||
item.attachments[j].id = Zotero.Utilities.randomString();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
item.attachments = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var deferred = Zotero.Promise.defer();
|
|
||||||
Zotero.API.createItem({"items":newItems}, function(statusCode, response) {
|
|
||||||
if(statusCode !== 200) {
|
|
||||||
deferred.reject(new Error("Save to server failed with " + statusCode + " " + response));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var resp = JSON.parse(response);
|
|
||||||
} catch(e) {
|
|
||||||
deferred.reject(new Error("Unexpected response received from server"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for(var i in resp.failed) {
|
|
||||||
deferred.reject(new Error("Save to server failed with " + statusCode + " " + response));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.debug("Translate: Save to server complete");
|
|
||||||
Zotero.Prefs.getCallback(
|
|
||||||
["downloadAssociatedFiles", "automaticSnapshots"],
|
|
||||||
function (prefs) {
|
|
||||||
if(typedArraysSupported) {
|
|
||||||
for(var i=0; i<items.length; i++) {
|
|
||||||
var item = items[i], key = resp.success[itemIndices[i]];
|
|
||||||
if(item.attachments && item.attachments.length) {
|
|
||||||
this._saveAttachmentsToServer(key, this._getFileBaseNameFromItem(item),
|
|
||||||
item.attachments, prefs, attachmentCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deferred.resolve(items);
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
}.bind(this));
|
|
||||||
return deferred.promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves an attachment to server
|
|
||||||
* @param {String} itemKey The key of the parent item
|
|
||||||
* @param {String} baseName A string to use as the base name for attachments
|
|
||||||
* @param {Object[]} attachments An array of attachment objects
|
|
||||||
* @param {Object} prefs An object with the values of the downloadAssociatedFiles and automaticSnapshots preferences
|
|
||||||
* @param {Function} attachmentCallback A callback that receives information about attachment
|
|
||||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
|
||||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
|
||||||
*/
|
|
||||||
"_saveAttachmentsToServer":function(itemKey, baseName, attachments, prefs, attachmentCallback) {
|
|
||||||
var me = this,
|
|
||||||
uploadAttachments = [],
|
|
||||||
retrieveHeadersForAttachments = attachments.length;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates attachments on the z.org server. This is executed after we have received
|
|
||||||
* headers for all attachments to be downloaded, but before they are uploaded to
|
|
||||||
* z.org.
|
|
||||||
* @inner
|
|
||||||
*/
|
|
||||||
var createAttachments = function() {
|
|
||||||
if(uploadAttachments.length === 0) return;
|
|
||||||
var attachmentPayload = [];
|
|
||||||
for(var i=0; i<uploadAttachments.length; i++) {
|
|
||||||
var attachment = uploadAttachments[i];
|
|
||||||
// deproxify url
|
|
||||||
if (this._proxy && attachment.url) {
|
|
||||||
attachment.url = this._proxy.toProper(attachment.url);
|
|
||||||
}
|
|
||||||
attachmentPayload.push({
|
|
||||||
"itemType":"attachment",
|
|
||||||
"parentItem":itemKey,
|
|
||||||
"linkMode":attachment.linkMode,
|
|
||||||
"title":(attachment.title ? attachment.title.toString() : "Untitled Attachment"),
|
|
||||||
"accessDate":"CURRENT_TIMESTAMP",
|
|
||||||
"url":attachment.url,
|
|
||||||
"note":(attachment.note ? attachment.note.toString() : ""),
|
|
||||||
"tags":(attachment.tags && attachment.tags instanceof Array ? attachment.tags : [])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.API.createItem({"items":attachmentPayload}, function(statusCode, response) {
|
|
||||||
var resp;
|
|
||||||
if(statusCode === 200) {
|
|
||||||
try {
|
|
||||||
resp = JSON.parse(response);
|
|
||||||
if(!resp.success) resp = undefined;
|
|
||||||
} catch(e) {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.debug("Finished creating items");
|
|
||||||
for(var i=0; i<uploadAttachments.length; i++) {
|
|
||||||
var attachment = uploadAttachments[i];
|
|
||||||
if(!resp || !resp.success[i]) {
|
|
||||||
attachmentCallback(attachment, false,
|
|
||||||
new Error("Unexpected response received from server "+statusCode+" "+response));
|
|
||||||
} else {
|
|
||||||
attachment.key = resp.success[i];
|
|
||||||
|
|
||||||
if(attachment.linkMode === "linked_url") {
|
|
||||||
attachmentCallback(attachment, 100);
|
|
||||||
} else if("data" in attachment) {
|
|
||||||
me._uploadAttachmentToServer(attachment, attachmentCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
for(var i=0; i<attachments.length; i++) {
|
|
||||||
// Also begin to download attachments
|
|
||||||
(function(attachment) {
|
|
||||||
var headersValidated = null;
|
|
||||||
|
|
||||||
// Ensure these are undefined before continuing, since we'll use them to determine
|
|
||||||
// whether an attachment has been created on the Zotero server and downloaded from
|
|
||||||
// the host
|
|
||||||
delete attachment.key;
|
|
||||||
delete attachment.data;
|
|
||||||
|
|
||||||
var isSnapshot = false;
|
|
||||||
if(attachment.mimeType) {
|
|
||||||
switch(attachment.mimeType.toLowerCase()) {
|
|
||||||
case "text/html":
|
|
||||||
case "application/xhtml+xml":
|
|
||||||
isSnapshot = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if((isSnapshot && !prefs.automaticSnapshots) || (!isSnapshot && !prefs.downloadAssociatedFiles)) {
|
|
||||||
// Check preferences to see if we should download this file
|
|
||||||
if(--retrieveHeadersForAttachments === 0) createAttachments();
|
|
||||||
return;
|
|
||||||
} else if(attachment.snapshot === false && attachment.mimeType) {
|
|
||||||
// If we aren't taking a snapshot and we have the MIME type, we don't need
|
|
||||||
// to retrieve anything
|
|
||||||
attachment.linkMode = "linked_url";
|
|
||||||
uploadAttachments.push(attachment);
|
|
||||||
if(attachmentCallback) attachmentCallback(attachment, 0);
|
|
||||||
if(--retrieveHeadersForAttachments === 0) createAttachments();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks headers to ensure that they reflect our expectations. When headers have
|
|
||||||
* been checked for all attachments, creates new items on the z.org server and
|
|
||||||
* begins uploading them.
|
|
||||||
* @inner
|
|
||||||
*/
|
|
||||||
var checkHeaders = function() {
|
|
||||||
if(headersValidated !== null) return headersValidated;
|
|
||||||
|
|
||||||
retrieveHeadersForAttachments--;
|
|
||||||
headersValidated = false;
|
|
||||||
|
|
||||||
var err = null,
|
|
||||||
status = xhr.status;
|
|
||||||
|
|
||||||
// Validate status
|
|
||||||
if(status === 0 || attachment.snapshot === false) {
|
|
||||||
// Failed due to SOP, or we are supposed to be getting a snapshot
|
|
||||||
attachment.linkMode = "linked_url";
|
|
||||||
} else if(status !== 200) {
|
|
||||||
err = new Error("Server returned unexpected status code "+status);
|
|
||||||
} else {
|
|
||||||
// Validate content type
|
|
||||||
var contentType = "application/octet-stream",
|
|
||||||
charset = null,
|
|
||||||
contentTypeHeader = xhr.getResponseHeader("Content-Type");
|
|
||||||
if(contentTypeHeader) {
|
|
||||||
// See RFC 2616 sec 3.7
|
|
||||||
var m = /^[^\x00-\x1F\x7F()<>@,;:\\"\/\[\]?={} ]+\/[^\x00-\x1F\x7F()<>@,;:\\"\/\[\]?={} ]+/.exec(contentTypeHeader);
|
|
||||||
if(m) contentType = m[0].toLowerCase();
|
|
||||||
m = /;\s*charset\s*=\s*("[^"]+"|[^\x00-\x1F\x7F()<>@,;:\\"\/\[\]?={} ]+)/.exec(contentTypeHeader);
|
|
||||||
if(m) {
|
|
||||||
charset = m[1];
|
|
||||||
if(charset[0] === '"') charset = charset.substring(1, charset.length-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(attachment.mimeType
|
|
||||||
&& attachment.mimeType.toLowerCase() !== contentType.toLowerCase()) {
|
|
||||||
err = new Error("Attachment MIME type "+contentType+
|
|
||||||
" does not match specified type "+attachment.mimeType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!err) {
|
|
||||||
attachment.mimeType = contentType;
|
|
||||||
attachment.linkMode = "imported_url";
|
|
||||||
switch(contentType.toLowerCase()) {
|
|
||||||
case "application/pdf":
|
|
||||||
attachment.filename = baseName+".pdf";
|
|
||||||
break;
|
|
||||||
case "text/html":
|
|
||||||
case "application/xhtml+xml":
|
|
||||||
attachment.filename = baseName+".html";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
attachment.filename = baseName;
|
|
||||||
}
|
|
||||||
if(charset) attachment.charset = charset;
|
|
||||||
headersValidated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't validate the headers, cancel the request
|
|
||||||
if(headersValidated === false && "abort" in xhr) xhr.abort();
|
|
||||||
|
|
||||||
// Add attachments to attachment payload if there was no error
|
|
||||||
if(!err) {
|
|
||||||
uploadAttachments.push(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have retrieved the headers for all attachments, create items on z.org
|
|
||||||
// server
|
|
||||||
if(retrieveHeadersForAttachments === 0) createAttachments();
|
|
||||||
|
|
||||||
// If there was an error, throw it now
|
|
||||||
if(err) {
|
|
||||||
attachmentCallback(attachment, false, err);
|
|
||||||
Zotero.logError(err);
|
|
||||||
}
|
|
||||||
return headersValidated;
|
|
||||||
};
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open((attachment.snapshot === false ? "HEAD" : "GET"), attachment.url, true);
|
|
||||||
xhr.responseType = (isSnapshot ? "document" : "arraybuffer");
|
|
||||||
xhr.onreadystatechange = function() {
|
|
||||||
if(xhr.readyState !== 4 || !checkHeaders()) return;
|
|
||||||
|
|
||||||
attachmentCallback(attachment, 50);
|
|
||||||
attachment.data = xhr.response;
|
|
||||||
// If item already created, head to upload
|
|
||||||
if("key" in attachment) {
|
|
||||||
me._uploadAttachmentToServer(attachment, attachmentCallback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.onprogress = function(event) {
|
|
||||||
if(xhr.readyState < 2 || !checkHeaders()) return;
|
|
||||||
|
|
||||||
if(event.total && attachmentCallback) {
|
|
||||||
attachmentCallback(attachment, event.loaded/event.total*50);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhr.send();
|
|
||||||
|
|
||||||
if(attachmentCallback) attachmentCallback(attachment, 0);
|
|
||||||
})(attachments[i]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads an attachment to the Zotero server
|
|
||||||
* @param {Object} attachment Attachment object, including
|
|
||||||
* @param {Function} attachmentCallback A callback that receives information about attachment
|
|
||||||
* save progress. The callback will be called as attachmentCallback(attachment, false, error)
|
|
||||||
* on failure or attachmentCallback(attachment, progressPercent) periodically during saving.
|
|
||||||
*/
|
|
||||||
"_uploadAttachmentToServer":function(attachment, attachmentCallback) {
|
|
||||||
Zotero.debug("Uploading attachment to server");
|
|
||||||
switch(attachment.mimeType.toLowerCase()) {
|
|
||||||
case "text/html":
|
|
||||||
case "application/xhtml+xml":
|
|
||||||
// It's possible that we didn't know if this was a snapshot until after the
|
|
||||||
// download began. If this is the case, we need to convert it to a document.
|
|
||||||
if(attachment.data instanceof ArrayBuffer) {
|
|
||||||
var me = this,
|
|
||||||
blob = new Blob([attachment.data], {"type":attachment.mimeType}),
|
|
||||||
reader = new FileReader();
|
|
||||||
reader.onloadend = function() {
|
|
||||||
if(reader.error) {
|
|
||||||
attachmentCallback(attachment, false, reader.error);
|
|
||||||
} else {
|
|
||||||
// Convert to an HTML document
|
|
||||||
var result = reader.result, doc;
|
|
||||||
try {
|
|
||||||
// First try using DOMParser
|
|
||||||
doc = (new DOMParser()).parseFromString(result, "text/html");
|
|
||||||
} catch(e) {}
|
|
||||||
|
|
||||||
// If DOMParser fails, use document.implementation.createHTMLDocument,
|
|
||||||
// as documented at https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
|
|
||||||
if(!doc) {
|
|
||||||
doc = document.implementation.createHTMLDocument("");
|
|
||||||
var docEl = doc.documentElement;
|
|
||||||
// AMO reviewer: This code is not run in Firefox, and the document
|
|
||||||
// is never rendered anyway
|
|
||||||
docEl.innerHTML = result;
|
|
||||||
if(docEl.children.length === 1 && docEl.firstElementChild === "html") {
|
|
||||||
doc.replaceChild(docEl.firstElementChild, docEl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attachment.data = doc;
|
|
||||||
me._uploadAttachmentToServer(attachment, attachmentCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.readAsText(blob, attachment.charset || "iso-8859-1");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are now assured that attachment.data is an HTMLDocument, so we can
|
|
||||||
// add a base tag
|
|
||||||
|
|
||||||
// Get the head tag
|
|
||||||
var doc = attachment.data,
|
|
||||||
head = doc.getElementsByTagName("head");
|
|
||||||
if(!head.length) {
|
|
||||||
head = doc.createElement("head");
|
|
||||||
var docEl = attachment.data.documentElement;
|
|
||||||
docEl.insertBefore(head, docEl.firstChildElement);
|
|
||||||
} else {
|
|
||||||
head = head[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the base tag
|
|
||||||
var base = doc.createElement("base");
|
|
||||||
base.href = attachment.url;
|
|
||||||
head.appendChild(base);
|
|
||||||
|
|
||||||
// Remove content type tags
|
|
||||||
var metaTags = doc.getElementsByTagName("meta"), metaTag;
|
|
||||||
for(var i=0; i<metaTags.length; i++) {
|
|
||||||
metaTag = metaTags[i];
|
|
||||||
var attr = metaTag.getAttribute("http-equiv");
|
|
||||||
if(attr && attr.toLowerCase() === "content-type") {
|
|
||||||
metaTag.parentNode.removeChild(metaTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add UTF-8 content type
|
|
||||||
metaTag = doc.createElement("meta");
|
|
||||||
metaTag.setAttribute("http-equiv", "Content-Type");
|
|
||||||
metaTag.setAttribute("content", attachment.mimeType+"; charset=UTF-8");
|
|
||||||
head.insertBefore(metaTag, head.firstChild);
|
|
||||||
|
|
||||||
// Serialize document to UTF-8
|
|
||||||
var src = new XMLSerializer().serializeToString(doc),
|
|
||||||
srcLength = Zotero.Utilities.getStringByteLength(src),
|
|
||||||
srcArray = new Uint8Array(srcLength);
|
|
||||||
Zotero.Utilities.stringToUTF8Array(src, srcArray);
|
|
||||||
|
|
||||||
// Rewrite data
|
|
||||||
attachment.data = srcArray.buffer;
|
|
||||||
attachment.charset = "UTF-8";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var binaryHash = this._md5(new Uint8Array(attachment.data), 0, attachment.data.byteLength),
|
|
||||||
hash = "";
|
|
||||||
for(var i=0; i<binaryHash.length; i++) {
|
|
||||||
if(binaryHash[i] < 16) hash += "0";
|
|
||||||
hash += binaryHash[i].toString(16);
|
|
||||||
}
|
|
||||||
attachment.md5 = hash;
|
|
||||||
|
|
||||||
if(Zotero.isBrowserExt && !Zotero.isBookmarklet) {
|
|
||||||
// In Chrome, we don't use messaging for Zotero.API.uploadAttachment, since
|
|
||||||
// we can't pass ArrayBuffers to the background page
|
|
||||||
Zotero.API.uploadAttachment(attachment, attachmentCallback.bind(this, attachment));
|
|
||||||
} else {
|
|
||||||
// In Safari and the connectors, we can pass ArrayBuffers
|
|
||||||
Zotero.Translate.ItemSaver._attachmentCallbacks[attachment.id] = function(status, error) {
|
|
||||||
attachmentCallback(attachment, status, error);
|
|
||||||
};
|
|
||||||
Zotero.API.uploadAttachment(attachment);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the base name for an attachment from an item object. This mimics the default behavior
|
|
||||||
* of Zotero.Attachments.getFileBaseNameFromItem
|
|
||||||
* @param {Object} item
|
|
||||||
*/
|
|
||||||
"_getFileBaseNameFromItem":function(item) {
|
|
||||||
var parts = [];
|
|
||||||
if(item.creators && item.creators.length) {
|
|
||||||
if(item.creators.length === 1) {
|
|
||||||
parts.push(item.creators[0].lastName);
|
|
||||||
} else if(item.creators.length === 2) {
|
|
||||||
parts.push(item.creators[0].lastName+" and "+item.creators[1].lastName);
|
|
||||||
} else {
|
|
||||||
parts.push(item.creators[0].lastName+" et al.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(item.date) {
|
|
||||||
var date = Zotero.Date.strToDate(item.date);
|
|
||||||
if(date.year) parts.push(date.year);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(item.title) {
|
|
||||||
parts.push(item.title.substr(0, 50));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(parts.length) return parts.join(" - ");
|
|
||||||
return "Attachment";
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
pdf.js MD5 implementation
|
|
||||||
Copyright (c) 2011 Mozilla Foundation
|
|
||||||
|
|
||||||
Contributors: Andreas Gal <gal@mozilla.com>
|
|
||||||
Chris G Jones <cjones@mozilla.com>
|
|
||||||
Shaon Barman <shaon.barman@gmail.com>
|
|
||||||
Vivien Nicolas <21@vingtetun.org>
|
|
||||||
Justin D'Arcangelo <justindarc@gmail.com>
|
|
||||||
Yury Delendik
|
|
||||||
Kalervo Kujala
|
|
||||||
Adil Allawi <@ironymark>
|
|
||||||
Jakob Miland <saebekassebil@gmail.com>
|
|
||||||
Artur Adib <aadib@mozilla.com>
|
|
||||||
Brendan Dahl <bdahl@mozilla.com>
|
|
||||||
David Quintana <gigaherz@gmail.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the "Software"),
|
|
||||||
to deal in the Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
"_md5":(function calculateMD5Closure() {
|
|
||||||
// Don't throw if typed arrays are not supported
|
|
||||||
try {
|
|
||||||
var r = new Uint8Array([
|
|
||||||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
|
||||||
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
|
||||||
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
|
||||||
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);
|
|
||||||
|
|
||||||
var k = new Int32Array([
|
|
||||||
-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
|
|
||||||
-1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
|
|
||||||
1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
|
|
||||||
643717713, -373897302, -701558691, 38016083, -660478335, -405537848,
|
|
||||||
568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784,
|
|
||||||
1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556,
|
|
||||||
-1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222,
|
|
||||||
-722521979, 76029189, -640364487, -421815835, 530742520, -995338651,
|
|
||||||
-198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606,
|
|
||||||
-1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649,
|
|
||||||
-145523070, -1120210379, 718787259, -343485551]);
|
|
||||||
} catch(e) {};
|
|
||||||
|
|
||||||
function hash(data, offset, length) {
|
|
||||||
var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878;
|
|
||||||
// pre-processing
|
|
||||||
var paddedLength = (length + 72) & ~63; // data + 9 extra bytes
|
|
||||||
var padded = new Uint8Array(paddedLength);
|
|
||||||
var i, j, n;
|
|
||||||
if (offset || length != data.byteLength) {
|
|
||||||
padded.set(new Uint8Array(data.buffer, offset, length));
|
|
||||||
} else {
|
|
||||||
padded.set(data);
|
|
||||||
}
|
|
||||||
i = length;
|
|
||||||
padded[i++] = 0x80;
|
|
||||||
n = paddedLength - 8;
|
|
||||||
while (i < n)
|
|
||||||
padded[i++] = 0;
|
|
||||||
padded[i++] = (length << 3) & 0xFF;
|
|
||||||
padded[i++] = (length >> 5) & 0xFF;
|
|
||||||
padded[i++] = (length >> 13) & 0xFF;
|
|
||||||
padded[i++] = (length >> 21) & 0xFF;
|
|
||||||
padded[i++] = (length >>> 29) & 0xFF;
|
|
||||||
padded[i++] = 0;
|
|
||||||
padded[i++] = 0;
|
|
||||||
padded[i++] = 0;
|
|
||||||
// chunking
|
|
||||||
// TODO ArrayBuffer ?
|
|
||||||
var w = new Int32Array(16);
|
|
||||||
for (i = 0; i < paddedLength;) {
|
|
||||||
for (j = 0; j < 16; ++j, i += 4) {
|
|
||||||
w[j] = (padded[i] | (padded[i + 1] << 8) |
|
|
||||||
(padded[i + 2] << 16) | (padded[i + 3] << 24));
|
|
||||||
}
|
|
||||||
var a = h0, b = h1, c = h2, d = h3, f, g;
|
|
||||||
for (j = 0; j < 64; ++j) {
|
|
||||||
if (j < 16) {
|
|
||||||
f = (b & c) | ((~b) & d);
|
|
||||||
g = j;
|
|
||||||
} else if (j < 32) {
|
|
||||||
f = (d & b) | ((~d) & c);
|
|
||||||
g = (5 * j + 1) & 15;
|
|
||||||
} else if (j < 48) {
|
|
||||||
f = b ^ c ^ d;
|
|
||||||
g = (3 * j + 5) & 15;
|
|
||||||
} else {
|
|
||||||
f = c ^ (b | (~d));
|
|
||||||
g = (7 * j) & 15;
|
|
||||||
}
|
|
||||||
var tmp = d, rotateArg = (a + f + k[j] + w[g]) | 0, rotate = r[j];
|
|
||||||
d = c;
|
|
||||||
c = b;
|
|
||||||
b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0;
|
|
||||||
a = tmp;
|
|
||||||
}
|
|
||||||
h0 = (h0 + a) | 0;
|
|
||||||
h1 = (h1 + b) | 0;
|
|
||||||
h2 = (h2 + c) | 0;
|
|
||||||
h3 = (h3 + d) | 0;
|
|
||||||
}
|
|
||||||
return new Uint8Array([
|
|
||||||
h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF,
|
|
||||||
h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF,
|
|
||||||
h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF,
|
|
||||||
h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
})()
|
|
||||||
};
|
|
|
@ -1,473 +0,0 @@
|
||||||
/*
|
|
||||||
***** BEGIN LICENSE BLOCK *****
|
|
||||||
|
|
||||||
Copyright © 2009 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 *****
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Enumeration of types of translators
|
|
||||||
var TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Singleton to handle loading and caching of translators
|
|
||||||
* @namespace
|
|
||||||
*/
|
|
||||||
Zotero.Translators = new function() {
|
|
||||||
var _cache, _translators;
|
|
||||||
var _initialized = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes translator cache, loading all relevant translators into memory
|
|
||||||
* @param {Zotero.Translate[]} [translators] List of translators. If not specified, it will be
|
|
||||||
* retrieved from storage.
|
|
||||||
*/
|
|
||||||
this.init = function(translators) {
|
|
||||||
if(!translators) {
|
|
||||||
translators = [];
|
|
||||||
if((Zotero.isBrowserExt || Zotero.isSafari) && localStorage["translatorMetadata"]) {
|
|
||||||
try {
|
|
||||||
translators = JSON.parse(localStorage["translatorMetadata"]);
|
|
||||||
if(typeof translators !== "object") {
|
|
||||||
translators = [];
|
|
||||||
}
|
|
||||||
} catch(e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_cache = {"import":[], "export":[], "web":[], "search":[]};
|
|
||||||
_translators = {};
|
|
||||||
_initialized = true;
|
|
||||||
|
|
||||||
// Build caches
|
|
||||||
for(var i=0; i<translators.length; i++) {
|
|
||||||
try {
|
|
||||||
var translator = new Zotero.Translator(translators[i]);
|
|
||||||
_translators[translator.translatorID] = translator;
|
|
||||||
|
|
||||||
for(var type in TRANSLATOR_TYPES) {
|
|
||||||
if(translator.translatorType & TRANSLATOR_TYPES[type]) {
|
|
||||||
_cache[type].push(translator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
Zotero.logError(e);
|
|
||||||
try {
|
|
||||||
Zotero.logError("Could not load translator "+JSON.stringify(translators[i]));
|
|
||||||
} catch(e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by priority
|
|
||||||
var cmp = function (a, b) {
|
|
||||||
if (a.priority > b.priority) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if (a.priority < b.priority) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(var type in _cache) {
|
|
||||||
_cache[type].sort(cmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the translator that corresponds to a given ID, without attempting to retrieve code
|
|
||||||
* @param {String} id The ID of the translator
|
|
||||||
*/
|
|
||||||
this.getWithoutCode = function(id) {
|
|
||||||
if(!_initialized) Zotero.Translators.init();
|
|
||||||
return _translators[id] ? _translators[id] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the translator that corresponds to a given ID
|
|
||||||
*
|
|
||||||
* @param {String} id The ID of the translator
|
|
||||||
*/
|
|
||||||
this.get = Zotero.Promise.method(function (id) {
|
|
||||||
if(!_initialized) Zotero.Translators.init();
|
|
||||||
var translator = _translators[id];
|
|
||||||
if(!translator) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only need to get code if it is of some use
|
|
||||||
if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER
|
|
||||||
&& !translator.hasOwnProperty("code")) {
|
|
||||||
return translator.getCode().then(() => translator);
|
|
||||||
} else {
|
|
||||||
return translator;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets all translators for a specific type of translation
|
|
||||||
* @param {String} type The type of translators to get (import, export, web, or search)
|
|
||||||
* @param {Boolean} [debugMode] Whether to assume debugging mode. If true, code is included for
|
|
||||||
* unsupported translators, and code originally retrieved from the
|
|
||||||
* repo is re-retrieved from Zotero Standalone.
|
|
||||||
*/
|
|
||||||
this.getAllForType = Zotero.Promise.method(function (type, debugMode) {
|
|
||||||
if(!_initialized) Zotero.Translators.init()
|
|
||||||
var translators = _cache[type].slice(0);
|
|
||||||
var codeGetter = new Zotero.Translators.CodeGetter(translators, debugMode);
|
|
||||||
return codeGetter.getAll().then(function() {
|
|
||||||
return translators;
|
|
||||||
});;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets web translators for a specific location
|
|
||||||
* @param {String} uri The URI for which to look for translators
|
|
||||||
* @return {Promise<Array[]>} - A promise for a 2-item array containing an array of translators and
|
|
||||||
* an array of functions for converting URLs from proper to proxied forms
|
|
||||||
*/
|
|
||||||
this.getWebTranslatorsForLocation = Zotero.Promise.method(function (URI, rootURI) {
|
|
||||||
var isFrame = URI !== rootURI;
|
|
||||||
if(!_initialized) Zotero.Translators.init();
|
|
||||||
var allTranslators = _cache["web"];
|
|
||||||
var potentialTranslators = [];
|
|
||||||
var proxies = [];
|
|
||||||
|
|
||||||
var rootSearchURIs = Zotero.Proxies.getPotentialProxies(rootURI);
|
|
||||||
var frameSearchURIs = isFrame ? Zotero.Proxies.getPotentialProxies(URI) : rootSearchURIs;
|
|
||||||
|
|
||||||
Zotero.debug("Translators: Looking for translators for "+Object.keys(frameSearchURIs).join(', '));
|
|
||||||
|
|
||||||
for(var i=0; i<allTranslators.length; i++) {
|
|
||||||
var translator = allTranslators[i];
|
|
||||||
if (isFrame && !translator.webRegexp.all) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
rootURIsLoop:
|
|
||||||
for(var rootSearchURI in rootSearchURIs) {
|
|
||||||
var isGeneric = !allTranslators[i].webRegexp.root;
|
|
||||||
// don't attempt to use generic translators that can't be run in this browser
|
|
||||||
// since that would require transmitting every page to Zotero host
|
|
||||||
if(isGeneric && allTranslators[i].runMode !== Zotero.Translator.RUN_MODE_IN_BROWSER) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootURIMatches = isGeneric || rootSearchURI.length < 8192 && translator.webRegexp.root.test(rootSearchURI);
|
|
||||||
if (translator.webRegexp.all && rootURIMatches) {
|
|
||||||
for (var frameSearchURI in frameSearchURIs) {
|
|
||||||
var frameURIMatches = frameSearchURI.length < 8192 && translator.webRegexp.all.test(frameSearchURI);
|
|
||||||
|
|
||||||
if (frameURIMatches) {
|
|
||||||
potentialTranslators.push(translator);
|
|
||||||
proxies.push(frameSearchURIs[frameSearchURI]);
|
|
||||||
// prevent adding the translator multiple times
|
|
||||||
break rootURIsLoop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(!isFrame && (isGeneric || rootURIMatches)) {
|
|
||||||
potentialTranslators.push(translator);
|
|
||||||
proxies.push(rootSearchURIs[rootSearchURI]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var codeGetter = new Zotero.Translators.CodeGetter(potentialTranslators);
|
|
||||||
return codeGetter.getAll().then(function () {
|
|
||||||
return [potentialTranslators, proxies];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts translators to JSON-serializable objects
|
|
||||||
*/
|
|
||||||
this.serialize = function(translator, properties) {
|
|
||||||
// handle translator arrays
|
|
||||||
if(translator.length !== undefined) {
|
|
||||||
var newTranslators = new Array(translator.length);
|
|
||||||
for(var i in translator) {
|
|
||||||
newTranslators[i] = Zotero.Translators.serialize(translator[i], properties);
|
|
||||||
}
|
|
||||||
return newTranslators;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle individual translator
|
|
||||||
var newTranslator = {};
|
|
||||||
for(var i in properties) {
|
|
||||||
var property = properties[i];
|
|
||||||
newTranslator[property] = translator[property];
|
|
||||||
}
|
|
||||||
return newTranslator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves all translator metadata to localStorage
|
|
||||||
* @param {Object[]} newMetadata Metadata for new translators
|
|
||||||
* @param {Boolean} reset Whether to clear all existing translators and overwrite them with
|
|
||||||
* the specified translators.
|
|
||||||
*/
|
|
||||||
this.update = function(newMetadata, reset) {
|
|
||||||
if (!_initialized) Zotero.Translators.init();
|
|
||||||
if (!newMetadata.length) return;
|
|
||||||
var serializedTranslators = [];
|
|
||||||
|
|
||||||
if (reset) {
|
|
||||||
serializedTranslators = newMetadata.map((t) => new Zotero.Translator(t));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var hasChanged = false;
|
|
||||||
|
|
||||||
// Update translators with new metadata
|
|
||||||
for(var i in newMetadata) {
|
|
||||||
var newTranslator = newMetadata[i];
|
|
||||||
|
|
||||||
if(_translators.hasOwnProperty(newTranslator.translatorID)) {
|
|
||||||
var oldTranslator = _translators[newTranslator.translatorID];
|
|
||||||
|
|
||||||
// check whether translator has changed
|
|
||||||
if(oldTranslator.lastUpdated !== newTranslator.lastUpdated) {
|
|
||||||
// check whether newTranslator is actually newer than the existing
|
|
||||||
// translator, and if not, don't update
|
|
||||||
if(Zotero.Date.sqlToDate(newTranslator.lastUpdated) < Zotero.Date.sqlToDate(oldTranslator.lastUpdated)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.debug(`Translators: Updating ${newTranslator.label}`);
|
|
||||||
oldTranslator.init(newTranslator);
|
|
||||||
hasChanged = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Zotero.debug(`Translators: Adding ${newTranslator.label}`);
|
|
||||||
_translators[newTranslator.translatorID] = new Zotero.Translator(newTranslator);
|
|
||||||
hasChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let deletedTranslators = Object.keys(_translators).filter(id => _translators[id].deleted);
|
|
||||||
if (deletedTranslators.length) {
|
|
||||||
hasChanged = true;
|
|
||||||
for (let id of deletedTranslators) {
|
|
||||||
Zotero.debug(`Translators: Removing ${_translators[id].label}`);
|
|
||||||
delete _translators[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!hasChanged) return;
|
|
||||||
|
|
||||||
// Serialize translators
|
|
||||||
for(var i in _translators) {
|
|
||||||
var serializedTranslator = this.serialize(_translators[i], TRANSLATOR_SAVE_PROPERTIES);
|
|
||||||
|
|
||||||
// don't save run mode
|
|
||||||
delete serializedTranslator.runMode;
|
|
||||||
|
|
||||||
serializedTranslators.push(serializedTranslator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store
|
|
||||||
if (Zotero.isBrowserExt || Zotero.isSafari) {
|
|
||||||
var serialized = JSON.stringify(serializedTranslators);
|
|
||||||
localStorage["translatorMetadata"] = serialized;
|
|
||||||
Zotero.debug("Translators: Saved updated translator list ("+serialized.length+" characters)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reinitialize
|
|
||||||
Zotero.Translators.init(serializedTranslators);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preprocesses code for a translator
|
|
||||||
*/
|
|
||||||
this.preprocessCode = function(code) {
|
|
||||||
if(!Zotero.isFx) {
|
|
||||||
const foreach = /^(\s*)for each\s*\((var )?([^ ]+) in (.*?)\)(\s*){/gm;
|
|
||||||
code = code.replace(foreach, "$1var $3_zForEachSubject = $4; "+
|
|
||||||
"for(var $3_zForEachIndex in $3_zForEachSubject)$5{ "+
|
|
||||||
"$2$3 = $3_zForEachSubject[$3_zForEachIndex];", code);
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class to get the code for a set of translators at once
|
|
||||||
*
|
|
||||||
* @param {Zotero.Translator[]} translators Translators for which to retrieve code
|
|
||||||
* @param {Boolean} [debugMode] If true, include code for unsupported translators
|
|
||||||
*/
|
|
||||||
Zotero.Translators.CodeGetter = function(translators, debugMode) {
|
|
||||||
this._translators = translators;
|
|
||||||
this._debugMode = debugMode;
|
|
||||||
this._concurrency = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
Zotero.Translators.CodeGetter.prototype.getCodeFor = Zotero.Promise.method(function(i) {
|
|
||||||
let translator = this._translators[i];
|
|
||||||
// retrieve code if no code and translator is supported locally
|
|
||||||
if((translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER && !translator.hasOwnProperty("code"))
|
|
||||||
// or if debug mode is enabled (even if unsupported locally)
|
|
||||||
|| (this._debugMode && (!translator.hasOwnProperty("code")
|
|
||||||
// or if in debug mode and the code we have came from the repo (which doesn't
|
|
||||||
// include test cases)
|
|
||||||
|| (Zotero.Repo && translator.codeSource === Zotero.Repo.SOURCE_REPO)))) {
|
|
||||||
// get code
|
|
||||||
return translator.getCode().catch((e) => Zotero.debug(`Failed to retrieve code for ${translator.translatorID}`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Zotero.Translators.CodeGetter.prototype.getAll = function () {
|
|
||||||
var codes = [];
|
|
||||||
// Chain promises with some level of concurrency. If unchained, fires
|
|
||||||
// off hundreds of xhttprequests on connectors and crashes the extension
|
|
||||||
for (let i = 0; i < this._translators.length; i++) {
|
|
||||||
if (i < this._concurrency) {
|
|
||||||
codes.push(this.getCodeFor(i));
|
|
||||||
} else {
|
|
||||||
codes.push(codes[i-this._concurrency].then(() => this.getCodeFor(i)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.all(codes);
|
|
||||||
};
|
|
||||||
|
|
||||||
var TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
|
|
||||||
"priority", "lastUpdated"];
|
|
||||||
var TRANSLATOR_OPTIONAL_PROPERTIES = ["targetAll", "browserSupport", "minVersion", "maxVersion",
|
|
||||||
"inRepository", "configOptions", "displayOptions",
|
|
||||||
"hiddenPrefs", "itemType"];
|
|
||||||
var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES
|
|
||||||
.concat(["targetAll", "browserSupport", "code", "runMode", "itemType"]);
|
|
||||||
var TRANSLATOR_SAVE_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES.concat(["browserSupport", "targetAll"]);
|
|
||||||
/**
|
|
||||||
* @class Represents an individual translator
|
|
||||||
* @constructor
|
|
||||||
* @property {String} translatorID Unique GUID of the translator
|
|
||||||
* @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read)
|
|
||||||
* @property {String} label Human-readable name of the translator
|
|
||||||
* @property {String} creator Author(s) of the translator
|
|
||||||
* @property {String} target Location that the translator processes
|
|
||||||
* @property {String} minVersion Minimum Zotero version
|
|
||||||
* @property {String} maxVersion Minimum Zotero version
|
|
||||||
* @property {Integer} priority Lower-priority translators will be selected first
|
|
||||||
* @property {String} browserSupport String indicating browser supported by the translator
|
|
||||||
* g = Gecko (Firefox)
|
|
||||||
* c = Google Chrome (WebKit & V8)
|
|
||||||
* s = Safari (WebKit & Nitro/Squirrelfish Extreme)
|
|
||||||
* i = Internet Explorer
|
|
||||||
* b = Bookmarklet
|
|
||||||
* v = Server
|
|
||||||
* @property {Object} configOptions Configuration options for import/export
|
|
||||||
* @property {Object} displayOptions Display options for export
|
|
||||||
* @property {Object} hiddenPrefs Hidden preferences configurable through about:config
|
|
||||||
* @property {Boolean} inRepository Whether the translator may be found in the repository
|
|
||||||
* @property {String} lastUpdated SQL-style date and time of translator's last update
|
|
||||||
* @property {String} code The executable JavaScript for the translator
|
|
||||||
*/
|
|
||||||
Zotero.Translator = function(info) {
|
|
||||||
this.init(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a translator from a set of info, clearing code if it is set
|
|
||||||
*/
|
|
||||||
Zotero.Translator.prototype.init = function(info) {
|
|
||||||
// make sure we have all the properties
|
|
||||||
for(var i in TRANSLATOR_REQUIRED_PROPERTIES) {
|
|
||||||
var property = TRANSLATOR_REQUIRED_PROPERTIES[i];
|
|
||||||
if(info[property] === undefined) {
|
|
||||||
Zotero.logError(new Error('Missing property "'+property+'" in translator metadata JSON object in ' + info.label));
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
this[property] = info[property];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(var i=0; i<TRANSLATOR_OPTIONAL_PROPERTIES.length; i++) {
|
|
||||||
var property = TRANSLATOR_OPTIONAL_PROPERTIES[i];
|
|
||||||
if(info[property] !== undefined) {
|
|
||||||
this[property] = info[property];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g";
|
|
||||||
|
|
||||||
if(this.browserSupport.indexOf(Zotero.browser) !== -1) {
|
|
||||||
this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER;
|
|
||||||
} else {
|
|
||||||
this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
|
|
||||||
// compile import regexp to match only file extension
|
|
||||||
this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null;
|
|
||||||
} else if(this.hasOwnProperty("importRegexp")) {
|
|
||||||
delete this.importRegexp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.translatorType & TRANSLATOR_TYPES["web"]) {
|
|
||||||
// compile web regexp
|
|
||||||
this.webRegexp = {
|
|
||||||
root: this.target ? new RegExp(this.target, "i") : null,
|
|
||||||
all: this.targetAll ? new RegExp(this.targetAll, "i") : null
|
|
||||||
};
|
|
||||||
} else if(this.hasOwnProperty("webRegexp")) {
|
|
||||||
delete this.webRegexp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(info.code) {
|
|
||||||
this.code = Zotero.Translators.preprocessCode(info.code);
|
|
||||||
} else if(this.hasOwnProperty("code")) {
|
|
||||||
delete this.code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves code for this translator
|
|
||||||
*
|
|
||||||
* @return {Promise<String|false>} - Promise for translator code or false if none
|
|
||||||
*/
|
|
||||||
Zotero.Translator.prototype.getCode = function (debugMode) {
|
|
||||||
return Zotero.Repo.getTranslatorCode(this.translatorID, debugMode)
|
|
||||||
.then(function (args) {
|
|
||||||
var code = args[0];
|
|
||||||
var source = args[1];
|
|
||||||
if (!code) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cache code for session only (we have standalone anyway)
|
|
||||||
this.code = code;
|
|
||||||
this.codeSource = source;
|
|
||||||
return code;
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log a translator-related error
|
|
||||||
* @param {String} message The error message
|
|
||||||
* @param {String} [type] The error type ("error", "warning", "exception", or "strict")
|
|
||||||
* @param {String} [line] The text of the line on which the error occurred
|
|
||||||
* @param {Integer} lineNumber
|
|
||||||
* @param {Integer} colNumber
|
|
||||||
*/
|
|
||||||
Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) {
|
|
||||||
Zotero.logError(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
Zotero.Translator.RUN_MODE_IN_BROWSER = 1;
|
|
||||||
Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2;
|
|
||||||
Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4;
|
|
File diff suppressed because one or more lines are too long
1
resource/schema/connectorTypeSchemaData.js
Normal file
1
resource/schema/connectorTypeSchemaData.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue