diff --git a/chrome/content/zotero/locale/csl b/chrome/content/zotero/locale/csl index 843dcc3b3..73f3050d7 160000 --- a/chrome/content/zotero/locale/csl +++ b/chrome/content/zotero/locale/csl @@ -1 +1 @@ -Subproject commit 843dcc3b3f1f16ab317a9551be7446e098a9a7ee +Subproject commit 73f3050d7c66caeb338a54c53d79c1b4be0ea8aa diff --git a/chrome/content/zotero/xpcom/connector/cachedTypes.js b/chrome/content/zotero/xpcom/connector/cachedTypes.js index 466eb94dc..9c7333842 100644 --- a/chrome/content/zotero/xpcom/connector/cachedTypes.js +++ b/chrome/content/zotero/xpcom/connector/cachedTypes.js @@ -78,11 +78,10 @@ Zotero.Connector_Types = new function() { this.getImageSrc = function(idOrName) { var itemType = Zotero.Connector_Types["itemTypes"][idOrName]; - if(!itemType) return false; - var icon = itemType[6]/* icon */; + var icon = itemType ? itemType[6]/* icon */ : "treeitem-"+idOrName+".png"; if(Zotero.isBookmarklet) { - return ZOTERO_CONFIG.BOOKMARKLET_URL+"icons/"+icon; + return ZOTERO_CONFIG.BOOKMARKLET_URL+"images/"+icon; } else if(Zotero.isFx) { return "chrome://zotero/skin/"+icon; } else if(Zotero.isChrome) { diff --git a/chrome/content/zotero/xpcom/connector/connector.js b/chrome/content/zotero/xpcom/connector/connector.js index f529e85e2..94717d330 100644 --- a/chrome/content/zotero/xpcom/connector/connector.js +++ b/chrome/content/zotero/xpcom/connector/connector.js @@ -27,8 +27,7 @@ Zotero.Connector = new function() { const CONNECTOR_URI = "http://127.0.0.1:23119/"; const CONNECTOR_API_VERSION = 2; - var _ieStandaloneIframeTarget; - var _ieConnectorCallbacks; + var _ieStandaloneIframeTarget, _ieConnectorCallbacks; this.isOnline = null; /** @@ -67,16 +66,26 @@ Zotero.Connector = new function() { Zotero.debug("Connector: Standalone found; trying IE hack"); _ieConnectorCallbacks = []; - Zotero.Messaging.addMessageListener("standaloneLoaded", function(data, event) { + var listener = function(event) { if(event.origin !== "http://127.0.0.1:23119") return; + event.stopPropagation(); - Zotero.debug("Connector: Standalone loaded"); - _ieStandaloneIframeTarget = iframe.contentWindow; - callback(true); - }); - Zotero.Messaging.addMessageListener("connectorResponse", function(data, event) { - if(event.origin !== "http://127.0.0.1:23119") return; + // 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], @@ -84,7 +93,13 @@ Zotero.Connector = new function() { }; _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"; @@ -169,10 +184,10 @@ Zotero.Connector = new function() { }; if(Zotero.isIE) { // IE requires XDR for CORS - if(_ieStandaloneIframeTarget !== undefined) { + if(_ieStandaloneIframeTarget) { var requestID = Zotero.Utilities.randomString(); _ieConnectorCallbacks[requestID] = newCallback; - _ieStandaloneIframeTarget.postMessage("ZOTERO_MSG "+JSON.stringify([null, "connectorRequest", + _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"); diff --git a/chrome/content/zotero/xpcom/connector/translate_item.js b/chrome/content/zotero/xpcom/connector/translate_item.js index e47c42694..b856e62df 100644 --- a/chrome/content/zotero/xpcom/connector/translate_item.js +++ b/chrome/content/zotero/xpcom/connector/translate_item.js @@ -32,7 +32,26 @@ Zotero.Translate.ItemSaver = function(libraryID, attachmentMode, forceTagType, d this._uri = document.location.toString(); this._cookie = document.cookie; } + + // Add listener for callbacks + if(!Zotero.Translate.ItemSaver._attachmentCallbackListenerAdded) { + 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]; + } + 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; @@ -41,8 +60,16 @@ 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} callback A callback to be executed when saving is complete. If saving + * succeeded, this callback will be passed true as the first argument and a list of items + * saved as the second. If saving failed, the callback will be passed false as the first + * argument and an error object as the second + * @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, callback) { + "saveItems":function(items, callback, attachmentCallback) { var me = this; // first try to save items via connector var payload = {"items":items}; @@ -58,30 +85,453 @@ Zotero.Translate.ItemSaver.prototype = { } else if(Zotero.isFx) { callback(false, new Error("Save via Standalone failed with "+status)); } else { - me._saveToServer(items, callback); + me._saveToServer(items, callback, attachmentCallback); } }); }, /** * Saves items to server + * @param items Items in Zotero.Item.toArray() format + * @param {Function} callback A callback to be executed when saving is complete. If saving + * succeeded, this callback will be passed true as the first argument and a list of items + * saved as the second. If saving failed, the callback will be passed false as the first + * argument and an error object as the second + * @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. */ - "_saveToServer":function(items, callback) { - var newItems = []; + "_saveToServer":function(items, callback, attachmentCallback) { + var newItems = [], typedArraysSupported = false; + try { + typedArraysSupported = new Uint8Array(1); + } catch(e) {} for(var i=0, n=items.length; i@,;:\\"\/\[\]?={} ]+\/[^\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); + } + } + + 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); + throw err; + } + }; + + var xhr = new XMLHttpRequest(); + xhr.open("GET", attachment.url, true); + xhr.responseType = "arraybuffer"; + xhr.onloadend = function() { + if(!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(this.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) { + var binaryHash = this._md5(new Uint8Array(attachment.data), 0, attachment.data.byteLength), + hash = ""; + for(var i=0; i + Chris G Jones + Shaon Barman + Vivien Nicolas <21@vingtetun.org> + Justin D'Arcangelo + Yury Delendik + Kalervo Kujala + Adil Allawi <@ironymark> + Jakob Miland + Artur Adib + Brendan Dahl + David Quintana + + 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; + })() }; \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js index 79c4b17b8..86403f6b6 100644 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ b/chrome/content/zotero/xpcom/translation/translate.js @@ -90,6 +90,7 @@ Zotero.Translate.Sandbox = { const allowedObjects = ["complete", "attachments", "seeAlso", "creators", "tags", "notes"]; + delete item.complete; for(var i in item) { var val = item[i]; var type = typeof val; @@ -99,7 +100,7 @@ Zotero.Translate.Sandbox = { } else if(type === "string") { // trim strings item[i] = val.trim(); - } else if((type === "object" || type === "xml") && allowedObjects.indexOf(i) === -1) { + } else if((type === "object" || type === "xml" || type === "function") && allowedObjects.indexOf(i) === -1) { // convert things that shouldn't be objecst to objects translate._debug("Translate: WARNING: typeof "+i+" is "+type+"; converting to string"); item[i] = val.toString(); @@ -144,6 +145,8 @@ Zotero.Translate.Sandbox = { translate.complete(false, data); throw data; } + }, function(arg1, arg2, arg3) { + translate._attachmentProgress(arg1, arg2, arg3); }); translate._runHandler("itemSaving", item); @@ -985,6 +988,7 @@ Zotero.Translate.Base.prototype = { this._libraryID = libraryID; this._saveAttachments = saveAttachments === undefined || saveAttachments; + this._attachmentsSaving = []; var me = this; if(typeof this.translator[0] === "object") { @@ -1035,30 +1039,6 @@ Zotero.Translate.Base.prototype = { this.decrementAsyncProcesses("Zotero.Translate#translate()"); }, - /** - * Executed when items have been saved (which may happen asynchronously, if in connector) - * - * @param {Boolean} returnValue Whether saving was successful - * @param {Zotero.Item[]|Error} data If returnValue is true, this will be an array of - * Zotero.Item objects. If returnValue is false, this will - * be a string error message. - */ - "itemsSaved":function(returnValue, data) { - if(returnValue) { - // trigger deferred itemDone events - var nItems = data.length; - for(var i=0; i= 128) { + if(val >= 2048) { + array[offset] = ((val >>> 6) | 192); + array[offset+1] = (val & 63) | 128; + offset += 2; + } else { + array[offset] = (val >>> 12) | 224; + array[offset+1] = ((val >>> 6) & 63) | 128; + array[offset+2] = (val & 63) | 128; + offset += 3; + } + } else { + array[offset++] = val; + } + } + }, + + /** + * Gets the byte length of the UTF-8 representation of a given string + * @param {String} string + * @return {Integer} + */ + "getStringByteLength":function(string) { + var length = 0, n = string.length; + for(var i=0; i= 128) { + if(val >= 2048) { + length += 3; + } else { + length += 2; + } + } else { + length += 1; + } + } + return length; } } diff --git a/chrome/skin/default/zotero/progress_arcs.png b/chrome/skin/default/zotero/progress_arcs.png new file mode 100644 index 000000000..89c5a14d5 Binary files /dev/null and b/chrome/skin/default/zotero/progress_arcs.png differ diff --git a/chrome/skin/default/zotero/treeitem-attachment-pdf.png b/chrome/skin/default/zotero/treeitem-attachment-pdf.png index 37e683166..4d76d4f6f 100644 Binary files a/chrome/skin/default/zotero/treeitem-attachment-pdf.png and b/chrome/skin/default/zotero/treeitem-attachment-pdf.png differ diff --git a/styles b/styles index cb42bb0ac..a322796a0 160000 --- a/styles +++ b/styles @@ -1 +1 @@ -Subproject commit cb42bb0ac96581e1a18737df1a105604e1b5144b +Subproject commit a322796a088e261718bc7d88e16a338be4143545 diff --git a/translators b/translators index 1ba73cb10..c4f989eb1 160000 --- a/translators +++ b/translators @@ -1 +1 @@ -Subproject commit 1ba73cb10ad0c77c171a389dd60bad78ea6a007f +Subproject commit c4f989eb17cedb10a1f0d62c601c15a22309518d