Attachment progress notifications. These are already hooked up to the UI in the connector, but still need to be hooked up to the UI in Firefox.
Addresses #3
This commit is contained in:
parent
f2d03014b0
commit
221d1da340
|
@ -551,9 +551,9 @@ var Zotero_Browser = new function() {
|
|||
function _constructLookupFunction(tab, success) {
|
||||
return function(e) {
|
||||
tab.page.translate.setTranslator(tab.page.translators[0]);
|
||||
tab.page.translate.clearHandlers("done");
|
||||
tab.page.translate.clearHandlers("itemsDone");
|
||||
tab.page.translate.clearHandlers("itemDone");
|
||||
tab.page.translate.setHandler("done", function(obj, status) {
|
||||
tab.page.translate.setHandler("itemsDone", function(obj, status) {
|
||||
if(status) {
|
||||
success(e, obj);
|
||||
Zotero_Browser.progress.close();
|
||||
|
@ -730,10 +730,10 @@ Zotero_Browser.Tab.prototype.translate = function(libraryID, collectionID, trans
|
|||
// use first translator available
|
||||
this.page.translate.setTranslator(translator ? translator : this.page.translators[0]);
|
||||
|
||||
this.page.translate.clearHandlers("done");
|
||||
this.page.translate.clearHandlers("itemsDone");
|
||||
this.page.translate.clearHandlers("itemDone");
|
||||
|
||||
this.page.translate.setHandler("done", function(obj, item) { Zotero_Browser.finishScraping(obj, item) });
|
||||
this.page.translate.setHandler("itemsDone", function(obj, item) { Zotero_Browser.finishScraping(obj, item) });
|
||||
this.page.translate.setHandler("itemDone", function(obj, dbItem, item) { Zotero_Browser.itemDone(obj, dbItem, item, collection) });
|
||||
|
||||
this.page.translate.translate(libraryID);
|
||||
|
|
|
@ -220,6 +220,7 @@ Zotero.Attachments = new function(){
|
|||
var urlRe = /^https?:\/\/[^\s]*$/;
|
||||
var matches = urlRe.exec(url);
|
||||
if (!matches) {
|
||||
callback(false);
|
||||
throw ("Invalid URL '" + url + "' in Zotero.Attachments.importFromURL()");
|
||||
}
|
||||
|
||||
|
@ -297,9 +298,11 @@ Zotero.Attachments = new function(){
|
|||
|
||||
if (mimeType == 'application/pdf' &&
|
||||
Zotero.MIME.sniffForMIMEType(str) != 'application/pdf') {
|
||||
Zotero.debug("Downloaded PDF did not have MIME type "
|
||||
+ "'application/pdf' in Attachments.importFromURL()", 2);
|
||||
var errString = "Downloaded PDF did not have MIME type "
|
||||
+ "'application/pdf' in Attachments.importFromURL()";
|
||||
Zotero.debug(errString, 2);
|
||||
attachmentItem.erase();
|
||||
callback(false, new Error(errString));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -311,6 +314,8 @@ Zotero.Attachments = new function(){
|
|||
|
||||
Zotero.Notifier.trigger('add', 'item', itemID);
|
||||
Zotero.Notifier.trigger('modify', 'item', sourceItemID);
|
||||
|
||||
if(callback) callback(attachmentItem);
|
||||
|
||||
// We don't have any way of knowing that the file
|
||||
// is flushed to disk, so we just wait a second
|
||||
|
@ -325,6 +330,7 @@ Zotero.Attachments = new function(){
|
|||
catch (e) {
|
||||
// Clean up
|
||||
attachmentItem.erase();
|
||||
callback(false, e);
|
||||
|
||||
throw (e);
|
||||
}
|
||||
|
@ -346,8 +352,6 @@ Zotero.Attachments = new function(){
|
|||
nsIURL.spec = url;
|
||||
wbp.saveURI(nsIURL, null, null, null, null, file);
|
||||
|
||||
if(callback) callback(attachmentItem);
|
||||
|
||||
return attachmentItem;
|
||||
}
|
||||
catch (e){
|
||||
|
@ -553,7 +557,7 @@ Zotero.Attachments = new function(){
|
|||
Zotero.Fulltext.indexDocument(document, itemID);
|
||||
Zotero.Notifier.trigger('refresh', 'item', itemID);
|
||||
if (callback) {
|
||||
callback();
|
||||
callback(attachmentItem);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -612,6 +616,7 @@ Zotero.Attachments = new function(){
|
|||
// Clean up
|
||||
var item = Zotero.Items.get(itemID);
|
||||
item.erase();
|
||||
callback(false, e);
|
||||
|
||||
throw (e);
|
||||
}
|
||||
|
|
|
@ -79,10 +79,23 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
payload.cookie = this._cookie;
|
||||
}
|
||||
|
||||
Zotero.Connector.callMethod("saveItems", payload, function(success, status) {
|
||||
if(success !== false) {
|
||||
Zotero.Connector.callMethod("saveItems", payload, function(data, status) {
|
||||
if(data !== false) {
|
||||
Zotero.debug("Translate: Save via Standalone succeeded");
|
||||
var haveAttachments = false;
|
||||
if(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
callback(true, items);
|
||||
if(haveAttachments) me._pollForProgress(items, attachmentCallback);
|
||||
} else if(Zotero.isFx) {
|
||||
callback(false, new Error("Save via Standalone failed with "+status));
|
||||
} else {
|
||||
|
@ -91,6 +104,60 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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();
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves items to server
|
||||
* @param items Items in Zotero.Item.toArray() format
|
||||
|
@ -101,6 +168,7 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
* @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, callback, attachmentCallback) {
|
||||
var newItems = [], typedArraysSupported = false;
|
||||
|
|
|
@ -27,6 +27,31 @@ const CONNECTOR_API_VERSION = 2;
|
|||
Zotero.Server.Connector = function() {};
|
||||
Zotero.Server.Connector._waitingForSelection = {};
|
||||
Zotero.Server.Connector.Data = {};
|
||||
Zotero.Server.Connector.AttachmentProgressManager = new function() {
|
||||
var attachmentsInProgress = new WeakMap(),
|
||||
attachmentProgress = {},
|
||||
i = 1;
|
||||
|
||||
/**
|
||||
* Called on attachment progress
|
||||
*/
|
||||
this.onProgress = function(attachment, progress, error) {
|
||||
var progressID = attachmentsInProgress.get(attachment);
|
||||
if(!progressID) {
|
||||
progressID = attachment.id = i++;
|
||||
attachmentsInProgress.set(attachment, progressID);
|
||||
}
|
||||
|
||||
attachmentProgress[progressID] = progress;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets progress for a given progressID
|
||||
*/
|
||||
this.getProgressForID = function(progressID) {
|
||||
return progressID in attachmentProgress ? attachmentProgress[progressID] : 0;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Lists all available translators, including code for translators that should be run on every page
|
||||
|
@ -169,7 +194,7 @@ Zotero.Server.Connector.Detect.prototype = {
|
|||
* cookie - document.cookie or equivalent
|
||||
*
|
||||
* Returns:
|
||||
* If a single item, sends response code 201 with no body.
|
||||
* If a single item, sends response code 201 with item in body.
|
||||
* If multiple items, sends response code 300 with the following content:
|
||||
* items - list of items in the format typically passed to the selectItems handler
|
||||
* instanceID - an ID that must be maintained for the subsequent Zotero.Connector.Select call
|
||||
|
@ -246,9 +271,13 @@ Zotero.Server.Connector.SavePage.prototype = {
|
|||
if(collection) {
|
||||
collection.addItem(item.id);
|
||||
}
|
||||
|
||||
jsonItems.push(jsonItem);
|
||||
});
|
||||
translate.setHandler("done", function(obj, item) {
|
||||
translate.setHandler("attachmentProgress", function(obj, attachment, progress, error) {
|
||||
Zotero.Server.Connector.AttachmentProgressManager.onProgress(attachment, progress, error);
|
||||
});
|
||||
translate.setHandler("itemsDone", function(obj, item) {
|
||||
Zotero.Browser.deleteHiddenBrowser(me._browser);
|
||||
if(jsonItems.length || me.selectedItems === false) {
|
||||
me.sendResponse(201, "application/json", JSON.stringify({"items":jsonItems}));
|
||||
|
@ -269,7 +298,7 @@ Zotero.Server.Connector.SavePage.prototype = {
|
|||
* Accepts:
|
||||
* items - an array of JSON format items
|
||||
* Returns:
|
||||
* 201 response code with empty body
|
||||
* 201 response code with item in body.
|
||||
*/
|
||||
Zotero.Server.Connector.SaveItem = function() {};
|
||||
Zotero.Server.Endpoints["/connector/saveItems"] = Zotero.Server.Connector.SaveItem;
|
||||
|
@ -299,22 +328,23 @@ Zotero.Server.Connector.SaveItem.prototype = {
|
|||
// save items
|
||||
var itemSaver = new Zotero.Translate.ItemSaver(libraryID,
|
||||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD, 1, undefined, cookieSandbox);
|
||||
itemSaver.saveItems(data.items, function(returnValue, data) {
|
||||
itemSaver.saveItems(data.items, function(returnValue, newItems) {
|
||||
if(returnValue) {
|
||||
try {
|
||||
for each(var item in data) {
|
||||
for each(var item in newItems) {
|
||||
if(collection) collection.addItem(item.id);
|
||||
}
|
||||
sendResponseCallback(201);
|
||||
|
||||
sendResponseCallback(201, "application/json", JSON.stringify({"items":data.items}));
|
||||
} catch(e) {
|
||||
Zotero.logError(e);
|
||||
sendResponseCallback(500);
|
||||
}
|
||||
} else {
|
||||
sendResponseCallback(500);
|
||||
throw data;
|
||||
throw newItems;
|
||||
}
|
||||
});
|
||||
}, Zotero.Server.Connector.AttachmentProgressManager.onProgress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -434,6 +464,31 @@ Zotero.Server.Connector.SelectItems.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets progress for an attachment that is currently being saved
|
||||
*
|
||||
* Accepts:
|
||||
* Array of attachment IDs returned by savePage, saveItems, or saveSnapshot
|
||||
* Returns:
|
||||
* 200 response code with current progress in body. Progress is either a number
|
||||
* between 0 and 100 or "false" to indicate that saving failed.
|
||||
*/
|
||||
Zotero.Server.Connector.Progress = function() {};
|
||||
Zotero.Server.Endpoints["/connector/attachmentProgress"] = Zotero.Server.Connector.Progress;
|
||||
Zotero.Server.Connector.Progress.prototype = {
|
||||
"supportedMethods":["POST"],
|
||||
"supportedDataTypes":["application/json"],
|
||||
|
||||
/**
|
||||
* @param {String} data POST data or GET query string
|
||||
* @param {Function} sendResponseCallback function to send HTTP response
|
||||
*/
|
||||
"init":function(data, sendResponseCallback) {
|
||||
sendResponseCallback(200, "application/json",
|
||||
JSON.stringify([Zotero.Server.Connector.AttachmentProgressManager.getProgressForID(id) for each(id in data)]));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get code for a translator
|
||||
*
|
||||
|
|
|
@ -87,7 +87,18 @@ Zotero.Translate.ItemSaver.ATTACHMENT_MODE_DOWNLOAD = 1;
|
|||
Zotero.Translate.ItemSaver.ATTACHMENT_MODE_FILE = 2;
|
||||
|
||||
Zotero.Translate.ItemSaver.prototype = {
|
||||
"saveItems":function(items, callback) {
|
||||
/**
|
||||
* 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, attachmentCallback) {
|
||||
// if no open transaction, open a transaction and add a timer call to close it
|
||||
var openedTransaction = false;
|
||||
if(!Zotero.DB.transactionInProgress()) {
|
||||
|
@ -110,8 +121,8 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
newItem = Zotero.Items.get(myID);
|
||||
} else {
|
||||
if(type == "attachment") { // handle attachments differently
|
||||
newItem = this._saveAttachment(item);
|
||||
if(!newItem) return;
|
||||
newItem = this._saveAttachment(item, null, attachmentCallback);
|
||||
if(!newItem) continue;
|
||||
var myID = newItem.id;
|
||||
} else {
|
||||
var typeID = Zotero.ItemTypes.getID(type);
|
||||
|
@ -137,8 +148,12 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
// handle attachments
|
||||
if(item.attachments) {
|
||||
for(var i=0; i<item.attachments.length; i++) {
|
||||
var newAttachment = this._saveAttachment(item.attachments[i], myID);
|
||||
if(newAttachment) this._saveTags(item.attachments[i], newAttachment);
|
||||
var newAttachment = this._saveAttachment(item.attachments[i], myID, attachmentCallback);
|
||||
if(typeof newAttachment === "object") {
|
||||
this._saveTags(item.attachments[i], newAttachment);
|
||||
} else if(!newAttachment) {
|
||||
item.attachments.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +224,7 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
|
||||
if(!attachment.url && !attachment.path) {
|
||||
Zotero.debug("Translate: Ignoring attachment: no path or URL specified", 2);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!attachment.path) {
|
||||
|
@ -221,34 +236,40 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
attachment.url = false;
|
||||
} else if(protocol != "http" && protocol != "https") {
|
||||
Zotero.debug("Translate: Unrecognized protocol "+protocol, 2);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!attachment.path) {
|
||||
// create from URL
|
||||
attachment.linkMode = "linked_file";
|
||||
try {
|
||||
var myID = Zotero.Attachments.linkFromURL(attachment.url, parentID,
|
||||
(attachment.mimeType ? attachment.mimeType : undefined),
|
||||
(attachment.title ? attachment.title : undefined));
|
||||
} catch(e) {
|
||||
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
|
||||
return;
|
||||
attachmentCallback(attachment, false, e);
|
||||
return false;
|
||||
}
|
||||
Zotero.debug("Translate: Created attachment; id is "+myID, 4);
|
||||
attachmentCallback(attachment, 100);
|
||||
var newItem = Zotero.Items.get(myID);
|
||||
} else {
|
||||
var file = this._parsePath(attachment.path);
|
||||
if(!file || !file.exists()) return;
|
||||
|
||||
if (attachment.url) {
|
||||
attachment.linkMode = "imported_url";
|
||||
var myID = Zotero.Attachments.importSnapshotFromFile(file,
|
||||
attachment.url, attachment.title, attachment.mimeType, attachment.charset,
|
||||
parentID);
|
||||
}
|
||||
else {
|
||||
attachment.linkMode = "imported_file";
|
||||
var myID = Zotero.Attachments.importFromFile(file, parentID);
|
||||
}
|
||||
attachmentCallback(attachment, 100);
|
||||
}
|
||||
|
||||
var newItem = Zotero.Items.get(myID);
|
||||
|
@ -306,7 +327,7 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
return file;
|
||||
},
|
||||
|
||||
"_saveAttachmentDownload":function(attachment, parentID) {
|
||||
"_saveAttachmentDownload":function(attachment, parentID, attachmentCallback) {
|
||||
Zotero.debug("Translate: Adding attachment", 4);
|
||||
|
||||
// determine whether to save attachments at all
|
||||
|
@ -319,7 +340,7 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
var shouldAttach = ((attachment.document
|
||||
|| (attachment.mimeType && attachment.mimeType == "text/html")) && automaticSnapshots)
|
||||
|| downloadAssociatedFiles;
|
||||
if(!shouldAttach) return;
|
||||
if(!shouldAttach) return false;
|
||||
|
||||
if(attachment.document && "__wrappedDOMObject" in attachment.document) {
|
||||
attachment.document = attachment.document.__wrappedDOMObject;
|
||||
|
@ -327,10 +348,18 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
|
||||
if(attachment.snapshot === false || !this._saveFiles) {
|
||||
// if snapshot is explicitly set to false, attach as link
|
||||
attachment.linkMode = "linked_url";
|
||||
if(attachment.document) {
|
||||
Zotero.Attachments.linkFromURL(attachment.document.location.href, parentID,
|
||||
(attachment.mimeType ? attachment.mimeType : attachment.document.contentType),
|
||||
(attachment.title ? attachment.title : attachment.document.title));
|
||||
try {
|
||||
Zotero.Attachments.linkFromURL(attachment.document.location.href, parentID,
|
||||
(attachment.mimeType ? attachment.mimeType : attachment.document.contentType),
|
||||
(attachment.title ? attachment.title : attachment.document.title));
|
||||
attachmentCallback(attachment, 100);
|
||||
} catch(e) {
|
||||
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
|
||||
attachmentCallback(attachment, false, e);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if(!attachment.mimeType || !attachment.title) {
|
||||
Zotero.debug("Translate: Either mimeType or title is missing; attaching file will be slower", 3);
|
||||
|
@ -340,19 +369,33 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
Zotero.Attachments.linkFromURL(attachment.url, parentID,
|
||||
(attachment.mimeType ? attachment.mimeType : undefined),
|
||||
(attachment.title ? attachment.title : undefined));
|
||||
attachmentCallback(attachment, 100);
|
||||
} catch(e) {
|
||||
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
|
||||
attachmentCallback(attachment, false, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// if snapshot is not explicitly set to false, retrieve snapshot
|
||||
if(attachment.document) {
|
||||
if(automaticSnapshots) {
|
||||
try {
|
||||
Zotero.Attachments.importFromDocument(attachment.document, parentID, attachment.title);
|
||||
attachment.linkMode = "imported_url";
|
||||
Zotero.Attachments.importFromDocument(attachment.document,
|
||||
parentID, attachment.title, function(status, err) {
|
||||
if(status) {
|
||||
attachmentCallback(attachment, 100);
|
||||
} else {
|
||||
attachmentCallback(attachment, false, err);
|
||||
}
|
||||
}, this._libraryID);
|
||||
attachmentCallback(attachment, 0);
|
||||
} catch(e) {
|
||||
Zotero.debug("Translate: Error attaching document", 2);
|
||||
attachmentCallback(attachment, false, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Save attachment if snapshot pref enabled or not HTML
|
||||
// (in which case downloadAssociatedFiles applies)
|
||||
|
@ -364,14 +407,27 @@ Zotero.Translate.ItemSaver.prototype = {
|
|||
var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(parentID);
|
||||
try {
|
||||
Zotero.debug('Importing attachment from URL');
|
||||
attachment.linkMode = "imported_url";
|
||||
Zotero.Attachments.importFromURL(attachment.url, parentID, title,
|
||||
fileBaseName, null, mimeType, this._libraryID, null, this._cookieSandbox);
|
||||
fileBaseName, null, mimeType, this._libraryID, function(status, err) {
|
||||
// TODO: actually indicate progress during download
|
||||
if(status) {
|
||||
attachmentCallback(attachment, 100);
|
||||
} else {
|
||||
attachmentCallback(attachment, false, err);
|
||||
}
|
||||
}, this._cookieSandbox);
|
||||
attachmentCallback(attachment, 0);
|
||||
} catch(e) {
|
||||
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
|
||||
attachmentCallback(attachment, false, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
"_saveFields":function(item, newItem) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user