mirror of https://github.com/zulip/zulip.git
upload: Replace jQuery filedrop with Uppy.
This commit is contained in:
parent
25bfe135b8
commit
5bab2a3762
|
@ -107,10 +107,6 @@ Files: static/generated/emoji/images/emoji/unicode/*
|
|||
Copyright: Google, Inc.
|
||||
License: Apache-2.0
|
||||
|
||||
Files: static/third/jquery-filedrop/jquery.filedrop.js
|
||||
Copyright: Resopollution
|
||||
License: Expat
|
||||
|
||||
Files: static/third/jquery-idle/jquery.idle.js
|
||||
Copyright: 2011-2013 Henrique Boaventura
|
||||
License: Expat
|
||||
|
|
|
@ -812,65 +812,6 @@ run_test('finish', () => {
|
|||
}());
|
||||
});
|
||||
|
||||
run_test('abort_xhr', () => {
|
||||
$("#compose-send-button").attr('disabled', 'disabled');
|
||||
let compose_removedata_checked = false;
|
||||
$('#compose').removeData = function (sel) {
|
||||
assert.equal(sel, 'filedrop_xhr');
|
||||
compose_removedata_checked = true;
|
||||
};
|
||||
let xhr_abort_checked = false;
|
||||
$("#compose").data = function (sel) {
|
||||
assert.equal(sel, 'filedrop_xhr');
|
||||
return {
|
||||
abort: function () {
|
||||
xhr_abort_checked = true;
|
||||
},
|
||||
};
|
||||
};
|
||||
compose.abort_xhr();
|
||||
assert.equal($("#compose-send-button").attr(), undefined);
|
||||
assert(xhr_abort_checked);
|
||||
assert(compose_removedata_checked);
|
||||
});
|
||||
|
||||
function verify_filedrop_payload(payload) {
|
||||
assert.equal(payload.url, '/json/user_uploads');
|
||||
assert.equal(payload.fallback_id, 'file_input');
|
||||
assert.equal(payload.paramname, 'file');
|
||||
assert.equal(payload.max_file_upload_size, 512);
|
||||
assert.equal(payload.data.csrfmiddlewaretoken, 'fake-csrf-token');
|
||||
assert.deepEqual(payload.raw_droppable, ['text/uri-list', 'text/plain']);
|
||||
assert.equal(typeof payload.drop, 'function');
|
||||
assert.equal(typeof payload.progressUpdated, 'function');
|
||||
assert.equal(typeof payload.error, 'function');
|
||||
assert.equal(typeof payload.uploadFinished, 'function');
|
||||
assert.equal(typeof payload.rawDrop, 'function');
|
||||
}
|
||||
|
||||
function test_raw_file_drop(raw_drop_func) {
|
||||
compose_state.set_message_type(false);
|
||||
let compose_actions_start_checked = false;
|
||||
global.compose_actions = {
|
||||
start: function (msg_type) {
|
||||
assert.equal(msg_type, 'stream');
|
||||
compose_actions_start_checked = true;
|
||||
},
|
||||
};
|
||||
$("#compose-textarea").val('Old content ');
|
||||
let compose_ui_autosize_textarea_checked = false;
|
||||
compose_ui.autosize_textarea = function () {
|
||||
compose_ui_autosize_textarea_checked = true;
|
||||
};
|
||||
|
||||
// Call the method here!
|
||||
raw_drop_func('new contents');
|
||||
|
||||
assert(compose_actions_start_checked);
|
||||
assert.equal($("#compose-textarea").val(), 'Old content new contents');
|
||||
assert(compose_ui_autosize_textarea_checked);
|
||||
}
|
||||
|
||||
run_test('warn_if_private_stream_is_linked', () => {
|
||||
stream_data.add_sub({
|
||||
name: compose_state.stream_name(),
|
||||
|
@ -954,13 +895,18 @@ run_test('initialize', () => {
|
|||
global.document = 'document-stub';
|
||||
global.csrf_token = 'fake-csrf-token';
|
||||
|
||||
let filedrop_in_compose_checked = false;
|
||||
page_params.max_file_upload_size = 512;
|
||||
$("#compose").filedrop = function (payload) {
|
||||
verify_filedrop_payload(payload);
|
||||
test_raw_file_drop(payload.rawDrop);
|
||||
|
||||
filedrop_in_compose_checked = true;
|
||||
let setup_upload_called = false;
|
||||
let uppy_cancel_all_called = false;
|
||||
upload.setup_upload = function (config) {
|
||||
assert.equal(config.mode, "compose");
|
||||
setup_upload_called = true;
|
||||
return {
|
||||
cancelAll: () => {
|
||||
uppy_cancel_all_called = true;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
compose.initialize();
|
||||
|
@ -968,14 +914,11 @@ run_test('initialize', () => {
|
|||
assert(resize_watch_manual_resize_checked);
|
||||
assert(xmlhttprequest_checked);
|
||||
assert(!$("#compose #attach_files").hasClass("notdisplayed"));
|
||||
assert(filedrop_in_compose_checked);
|
||||
assert(setup_upload_called);
|
||||
|
||||
function reset_jquery() {
|
||||
// Avoid leaks.
|
||||
set_global('$', global.make_zjquery());
|
||||
|
||||
// Bypass filedrop (we already tested it above).
|
||||
$("#compose").filedrop = noop;
|
||||
}
|
||||
|
||||
let compose_actions_start_checked;
|
||||
|
@ -1013,6 +956,18 @@ run_test('initialize', () => {
|
|||
|
||||
assert(compose_actions_start_checked);
|
||||
}());
|
||||
|
||||
(function test_abort_xhr() {
|
||||
$("#compose-send-button").attr('disabled', 'disabled');
|
||||
|
||||
reset_jquery();
|
||||
compose.initialize();
|
||||
|
||||
compose.abort_xhr();
|
||||
|
||||
assert.equal($("#compose-send-button").attr(), undefined);
|
||||
assert(uppy_cancel_all_called);
|
||||
}());
|
||||
});
|
||||
|
||||
run_test('update_fade', () => {
|
||||
|
|
|
@ -212,13 +212,18 @@ run_test('start', () => {
|
|||
|
||||
// Cancel compose.
|
||||
let pill_cleared;
|
||||
|
||||
compose_pm_pill.clear = function () {
|
||||
pill_cleared = true;
|
||||
};
|
||||
|
||||
let abort_xhr_called = false;
|
||||
compose.abort_xhr = () => {
|
||||
abort_xhr_called = true;
|
||||
};
|
||||
|
||||
assert_hidden('#compose_controls');
|
||||
cancel();
|
||||
assert(abort_xhr_called);
|
||||
assert(pill_cleared);
|
||||
assert_visible('#compose_controls');
|
||||
assert_hidden('#private-message');
|
||||
|
|
|
@ -162,7 +162,7 @@ page_params.starred_messages = [];
|
|||
page_params.presences = [];
|
||||
|
||||
$('#tab_bar').append = () => {};
|
||||
$('#compose').filedrop = () => {};
|
||||
upload.setup_upload = () => {};
|
||||
|
||||
server_events.home_view_loaded = () => true;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const rewiremock = require("rewiremock/node");
|
||||
|
||||
set_global('$', global.make_zjquery());
|
||||
set_global('document', {
|
||||
location: { },
|
||||
|
@ -9,7 +11,7 @@ set_global('i18n', global.stub_i18n);
|
|||
set_global('page_params', {
|
||||
max_file_upload_size: 25,
|
||||
});
|
||||
set_global('csrf_token', { });
|
||||
set_global('csrf_token', "csrf_token");
|
||||
set_global('bridge', false);
|
||||
|
||||
// Setting these up so that we can test that links to uploads within messages are
|
||||
|
@ -20,177 +22,500 @@ global.document.location.host = 'foo.com';
|
|||
zrequire('compose_ui');
|
||||
zrequire('compose_state');
|
||||
zrequire('compose');
|
||||
zrequire('compose_actions');
|
||||
|
||||
const plugin_stub = {
|
||||
prototype: {
|
||||
constructor: null,
|
||||
},
|
||||
};
|
||||
|
||||
zrequire('upload');
|
||||
|
||||
const upload_opts = upload.options({ mode: "compose" });
|
||||
run_test('make_upload_absolute', () => {
|
||||
let uri = "/user_uploads/5/d4/6lSlfIPIg9nDI2Upj0Mq_EbE/kerala.png";
|
||||
const expected_uri = "https://foo.com/user_uploads/5/d4/6lSlfIPIg9nDI2Upj0Mq_EbE/kerala.png";
|
||||
assert.equal(upload.make_upload_absolute(uri), expected_uri);
|
||||
|
||||
run_test('upload_started', () => {
|
||||
$("#compose-send-button").prop('disabled', false);
|
||||
uri = "https://foo.com/user_uploads/5/d4/6lSlfIPIg9nDI2Upj0Mq_EbE/alappuzha.png";
|
||||
assert.equal(upload.make_upload_absolute(uri), uri);
|
||||
});
|
||||
|
||||
run_test('get_item', () => {
|
||||
assert.equal(upload.get_item("textarea", {mode: "compose"}), $('#compose-textarea'));
|
||||
assert.equal(upload.get_item("send_status_message", {mode: "compose"}), $('#compose-error-msg'));
|
||||
assert.equal(upload.get_item("file_input_identifier", {mode: "compose"}), "#file_input");
|
||||
assert.equal(upload.get_item("source", {mode: "compose"}), "compose-file-input");
|
||||
assert.equal(upload.get_item("drag_drop_container", {mode: "compose"}), $('#compose'));
|
||||
|
||||
assert.equal(upload.get_item("textarea", {mode: "edit", row: 1}), $('#message_edit_content_1'));
|
||||
|
||||
$('#message_edit_content_2').closest = () => {
|
||||
$('#message_edit_form').set_find_results('.message_edit_save', $('.message_edit_save'));
|
||||
return $('#message_edit_form');
|
||||
};
|
||||
assert.equal(upload.get_item("send_button", {mode: "edit", row: 2}), $('.message_edit_save'));
|
||||
|
||||
assert.equal(upload.get_item("send_status_identifier", {mode: "edit", row: 11}), "#message-edit-send-status-11");
|
||||
assert.equal(upload.get_item("send_status", {mode: "edit", row: 75}), $("#message-edit-send-status-75"));
|
||||
|
||||
$('#message-edit-send-status-2').set_find_results('.send-status-close', $('.send-status-close'));
|
||||
assert.equal(upload.get_item("send_status_close_button", {mode: "edit", row: 2}), $('.send-status-close'));
|
||||
|
||||
$('#message-edit-send-status-22').set_find_results('.error-msg', $('.error-msg'));
|
||||
assert.equal(upload.get_item("send_status_message", {mode: "edit", row: 22}), $('.error-msg'));
|
||||
|
||||
assert.equal(upload.get_item("file_input_identifier", {mode: "edit", row: 123}), "#message_edit_file_input_123");
|
||||
assert.equal(upload.get_item("source", {mode: "edit", row: 123}), "message-edit-file-input");
|
||||
assert.equal(upload.get_item("drag_drop_container", {mode: "edit", row: 1}), $("#message_edit_form"));
|
||||
|
||||
assert.throws(
|
||||
() => {
|
||||
upload.get_item("textarea");
|
||||
},
|
||||
{
|
||||
name: "Error",
|
||||
message: "Missing config",
|
||||
}
|
||||
);
|
||||
assert.throws(
|
||||
() => {
|
||||
upload.get_item("textarea", {mode: "edit"});
|
||||
},
|
||||
{
|
||||
name: "Error",
|
||||
message: "Missing row in config",
|
||||
}
|
||||
);
|
||||
assert.throws(
|
||||
() => {
|
||||
upload.get_item("textarea", {mode: "blah"});
|
||||
},
|
||||
{
|
||||
name: "Error",
|
||||
message: "Invalid upload mode!",
|
||||
}
|
||||
);
|
||||
assert.throws(
|
||||
() => {
|
||||
upload.get_item("invalid", {mode: "compose"});
|
||||
},
|
||||
{
|
||||
name: "Error",
|
||||
message: 'Invalid key name for mode "compose"',
|
||||
}
|
||||
);
|
||||
assert.throws(
|
||||
() => {
|
||||
upload.get_item("invalid", {mode: "edit", row: 20});
|
||||
},
|
||||
{
|
||||
name: "Error",
|
||||
message: 'Invalid key name for mode "edit"',
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
run_test('hide_upload_status', () => {
|
||||
$('#compose-send-button').prop("disabled", "");
|
||||
$('#compose-send-status').addClass("alert-info").show();
|
||||
|
||||
upload.hide_upload_status({mode: "compose"});
|
||||
|
||||
assert.equal($('#compose-send-button').prop("disabled"), false);
|
||||
assert.equal($('#compose-send-button').hasClass("alert-info"), false);
|
||||
assert.equal($('#compose-send-button').visible(), false);
|
||||
});
|
||||
|
||||
run_test('show_error_message', () => {
|
||||
$('#compose-send-button').prop("disabled", "");
|
||||
$('#compose-send-status').addClass("alert-info").removeClass("alert-error").hide();
|
||||
$('#compose-error-msg').text("");
|
||||
$('#compose-error-msg').hide();
|
||||
|
||||
upload.show_error_message({mode: "compose"}, "Error message");
|
||||
assert.equal($('#compose-send-button').prop("disabled"), false);
|
||||
assert($('#compose-send-status').hasClass("alert-error"));
|
||||
assert.equal($('#compose-send-status').hasClass("alert-info"), false);
|
||||
assert($('#compose-send-status').visible());
|
||||
assert.equal($('#compose-error-msg').text(), "Error message");
|
||||
|
||||
upload.show_error_message({mode: "compose"});
|
||||
assert.equal($('#compose-error-msg').text(), "translated: An unknown error occurred.");
|
||||
|
||||
});
|
||||
|
||||
run_test('upload_files', () => {
|
||||
let cancel_all_counter = 0;
|
||||
const files = [
|
||||
{
|
||||
name: "budapest.png",
|
||||
type: "image/png",
|
||||
},
|
||||
];
|
||||
let uppy_add_file_called = false;
|
||||
const uppy = {
|
||||
cancelAll: () => {
|
||||
cancel_all_counter += 1;
|
||||
},
|
||||
addFile: (params) => {
|
||||
uppy_add_file_called = true;
|
||||
assert.equal(params.source, "compose-file-input");
|
||||
assert.equal(params.name, "budapest.png");
|
||||
assert.equal(params.type, "image/png");
|
||||
assert.equal(params.data, files[0]);
|
||||
},
|
||||
};
|
||||
let hide_upload_status_called = false;
|
||||
upload.hide_upload_status = (config) => {
|
||||
hide_upload_status_called = true;
|
||||
assert(config.mode, "compose");
|
||||
};
|
||||
const config = {mode: "compose"};
|
||||
|
||||
upload.upload_files(uppy, config, []);
|
||||
assert.equal(cancel_all_counter, 1);
|
||||
assert(hide_upload_status_called);
|
||||
|
||||
page_params.max_file_upload_size = 0;
|
||||
let show_error_message_called = false;
|
||||
upload.show_error_message = (config, message) => {
|
||||
show_error_message_called = true;
|
||||
assert.equal(config.mode, "compose");
|
||||
assert.equal(message, "translated: File and image uploads have been disabled for this organization.");
|
||||
};
|
||||
upload.upload_files(uppy, config, files);
|
||||
assert(show_error_message_called);
|
||||
|
||||
page_params.max_file_upload_size = 25;
|
||||
let on_click_close_button_callback;
|
||||
$(".compose-send-status-close").one = (event, callback) => {
|
||||
assert.equal(event, "click");
|
||||
on_click_close_button_callback = callback;
|
||||
};
|
||||
let compose_ui_insert_syntax_and_focus_called = false;
|
||||
compose_ui.insert_syntax_and_focus = (syntax, textarea) => {
|
||||
assert.equal(syntax, "[Uploading budapest.png…]()");
|
||||
assert.equal(textarea, $("#compose-textarea"));
|
||||
compose_ui_insert_syntax_and_focus_called = true;
|
||||
};
|
||||
let compose_ui_autosize_textarea_called = false;
|
||||
compose_ui.autosize_textarea = () => {
|
||||
compose_ui_autosize_textarea_called = true;
|
||||
};
|
||||
$("#compose-send-button").attr("disabled", false);
|
||||
$("#compose-send-status").removeClass("alert-info").hide();
|
||||
$(".compose-send-status-close").one = function (ev_name, handler) {
|
||||
assert.equal(ev_name, 'click');
|
||||
assert(handler);
|
||||
};
|
||||
$("#compose-error-msg").html('');
|
||||
const test_html = '<div class="progress active">' +
|
||||
'<div class="bar" id="compose-upload-bar-1549958107000" style="width: 0"></div>' +
|
||||
'</div>';
|
||||
$("#compose-send-status").append = function (html) {
|
||||
assert.equal(html, test_html);
|
||||
};
|
||||
$('#compose-textarea').caret = function () {
|
||||
return 0;
|
||||
};
|
||||
document.execCommand = function (command, show_default, value) {
|
||||
assert.equal(value, "[Uploading some-file…]() ");
|
||||
};
|
||||
|
||||
upload_opts.drop();
|
||||
upload_opts.uploadStarted(0, {
|
||||
trackingId: "1549958107000",
|
||||
name: 'some-file',
|
||||
}, 1);
|
||||
|
||||
upload.upload_files(uppy, config, files);
|
||||
assert.equal($("#compose-send-button").attr("disabled"), '');
|
||||
assert($("#compose-send-status").hasClass("alert-info"));
|
||||
assert($("#compose-send-status").visible());
|
||||
assert.equal($("<p>").text(), 'translated: Uploading…');
|
||||
});
|
||||
assert(compose_ui_insert_syntax_and_focus_called);
|
||||
assert(compose_ui_autosize_textarea_called);
|
||||
assert(uppy_add_file_called);
|
||||
|
||||
run_test('progress_updated', () => {
|
||||
let width_update_checked = false;
|
||||
$("#compose-upload-bar-1549958107000").width = function (width_percent) {
|
||||
assert.equal(width_percent, '39%');
|
||||
width_update_checked = true;
|
||||
};
|
||||
upload_opts.progressUpdated(1, {trackingId: "1549958107000"}, 39);
|
||||
assert(width_update_checked);
|
||||
});
|
||||
|
||||
run_test('upload_error', () => {
|
||||
function setup_test() {
|
||||
$("#compose-send-status").removeClass("alert-error");
|
||||
$("#compose-send-status").addClass("alert-info");
|
||||
$("#compose-send-button").attr("disabled", 'disabled');
|
||||
$("#compose-error-msg").text('');
|
||||
|
||||
$("#compose-upload-bar-1549958107000").parent = function () {
|
||||
return { remove: function () {} };
|
||||
};
|
||||
}
|
||||
|
||||
function assert_side_effects(msg) {
|
||||
assert($("#compose-send-status").hasClass("alert-error"));
|
||||
assert(!$("#compose-send-status").hasClass("alert-info"));
|
||||
assert.equal($("#compose-send-button").prop("disabled"), false);
|
||||
assert.equal($("#compose-error-msg").text(), msg);
|
||||
}
|
||||
|
||||
function test(err, msg, server_response = null, file = {}) {
|
||||
setup_test();
|
||||
file.trackingId = "1549958107000";
|
||||
upload_opts.error(err, server_response, file);
|
||||
assert_side_effects(msg);
|
||||
}
|
||||
|
||||
const msg_prefix = 'translated: ';
|
||||
const msg_1 = 'File upload is not yet available for your browser.';
|
||||
const msg_2 = 'Unable to upload that many files at once.';
|
||||
const msg_3 = '"foobar.txt" was too large; the maximum file size is 25MB.';
|
||||
const msg_4 = 'Sorry, the file was too large.';
|
||||
const msg_5 = 'An unknown error occurred.';
|
||||
const msg_6 = 'File and image uploads have been disabled for this organization.';
|
||||
|
||||
test('BrowserNotSupported', msg_prefix + msg_1);
|
||||
test('TooManyFiles', msg_prefix + msg_2);
|
||||
test('FileTooLarge', msg_prefix + msg_3, null, {name: 'foobar.txt'});
|
||||
test(413, msg_prefix + msg_4);
|
||||
test(400, 'ちょっと…', {msg: 'ちょっと…'});
|
||||
test('Do-not-match-any-case', msg_prefix + msg_5);
|
||||
|
||||
// If uploading files has been disabled, then a different error message is
|
||||
// displayed when a user tries to paste or drag a file onto the UI.
|
||||
page_params.max_file_upload_size = 0;
|
||||
test('FileTooLarge', msg_prefix + msg_6, null);
|
||||
});
|
||||
|
||||
run_test('upload_finish', () => {
|
||||
function test(i, response, textbox_val) {
|
||||
let compose_ui_autosize_textarea_checked = false;
|
||||
let compose_actions_start_checked = false;
|
||||
let syntax_to_replace;
|
||||
let syntax_to_replace_with;
|
||||
let file_input_clear = false;
|
||||
|
||||
function setup() {
|
||||
$("#compose-textarea").val('');
|
||||
compose_ui.autosize_textarea = function () {
|
||||
compose_ui_autosize_textarea_checked = true;
|
||||
};
|
||||
compose_ui.replace_syntax = function (old_syntax, new_syntax) {
|
||||
syntax_to_replace = old_syntax;
|
||||
syntax_to_replace_with = new_syntax;
|
||||
};
|
||||
compose_state.set_message_type();
|
||||
global.compose_actions = {
|
||||
start: function (msg_type) {
|
||||
assert.equal(msg_type, 'stream');
|
||||
compose_actions_start_checked = true;
|
||||
},
|
||||
};
|
||||
$("#compose-send-button").attr('disabled', 'disabled');
|
||||
$("#compose-send-status").addClass("alert-info");
|
||||
$("#compose-send-status").show();
|
||||
|
||||
$('#file_input').clone = function (param) {
|
||||
assert(param);
|
||||
return $('#file_input');
|
||||
};
|
||||
|
||||
$('#file_input').replaceWith = function (elem) {
|
||||
assert.equal(elem, $('#file_input'));
|
||||
file_input_clear = true;
|
||||
};
|
||||
|
||||
$("#compose-upload-bar-1549958107000").parent = function () {
|
||||
return { remove: function () {$('div.progress.active').length = 0;} };
|
||||
};
|
||||
}
|
||||
|
||||
function assert_side_effects() {
|
||||
if (response.uri) {
|
||||
assert.equal(syntax_to_replace, '[Uploading some-file…]()');
|
||||
assert.equal(syntax_to_replace_with, textbox_val);
|
||||
assert(compose_actions_start_checked);
|
||||
assert(compose_ui_autosize_textarea_checked);
|
||||
assert.equal($("#compose-send-button").prop('disabled'), false);
|
||||
assert(!$('#compose-send-status').hasClass('alert-info'));
|
||||
assert(!$('#compose-send-status').visible());
|
||||
assert(file_input_clear);
|
||||
}
|
||||
}
|
||||
|
||||
global.patch_builtin('setTimeout', function (func) {
|
||||
global.patch_builtin("setTimeout", (func) => {
|
||||
func();
|
||||
});
|
||||
hide_upload_status_called = false;
|
||||
on_click_close_button_callback();
|
||||
assert.equal(cancel_all_counter, 2);
|
||||
assert(hide_upload_status_called);
|
||||
});
|
||||
|
||||
$("#compose-upload-bar-1549958107000").width = function (width_percent) {
|
||||
assert.equal(width_percent, '100%');
|
||||
run_test('uppy_config', () => {
|
||||
let uppy_stub_called = false;
|
||||
let uppy_set_meta_called = false;
|
||||
let uppy_used_xhrupload = false;
|
||||
let uppy_used_progressbar = false;
|
||||
|
||||
function uppy_stub(config) {
|
||||
uppy_stub_called = true;
|
||||
assert.equal(config.debug, false);
|
||||
assert.equal(config.autoProceed, true);
|
||||
assert.equal(config.restrictions.maxFileSize, 25 * 1024 * 1024);
|
||||
assert.equal(Object.keys(config.locale.strings).length, 2);
|
||||
assert("exceedsSize" in config.locale.strings);
|
||||
|
||||
return {
|
||||
setMeta: (params) => {
|
||||
uppy_set_meta_called = true;
|
||||
assert.equal(params.csrfmiddlewaretoken, 'csrf_token');
|
||||
},
|
||||
use: (func, params) => {
|
||||
const func_name = func.name;
|
||||
if (func_name === "XHRUpload") {
|
||||
uppy_used_xhrupload = true;
|
||||
assert.equal(params.endpoint, '/json/user_uploads');
|
||||
assert.equal(params.formData, true);
|
||||
assert.equal(params.fieldName, 'file');
|
||||
assert.equal(params.limit, 5);
|
||||
assert.equal(Object.keys(params.locale.strings).length, 1);
|
||||
assert("timedOut" in params.locale.strings);
|
||||
} else if (func_name === "ProgressBar") {
|
||||
uppy_used_progressbar = true;
|
||||
assert.equal(params.target, '#compose-send-status');
|
||||
assert.equal(params.hideAfterFinish, false);
|
||||
} else {
|
||||
/* istanbul ignore next */
|
||||
assert.fail(`Missing tests for ${func_name}`);
|
||||
}
|
||||
},
|
||||
on: () => {},
|
||||
};
|
||||
}
|
||||
uppy_stub.Plugin = plugin_stub;
|
||||
rewiremock.proxy(() => require("../../static/js/upload"), {'@uppy/core': uppy_stub});
|
||||
upload.setup_upload({mode: "compose"});
|
||||
|
||||
assert.equal(uppy_stub_called, true);
|
||||
assert.equal(uppy_set_meta_called, true);
|
||||
assert.equal(uppy_used_xhrupload, true);
|
||||
assert.equal(uppy_used_progressbar, true);
|
||||
|
||||
});
|
||||
|
||||
run_test('file_input', () => {
|
||||
set_global('$', global.make_zjquery());
|
||||
|
||||
upload.setup_upload({mode: "compose"});
|
||||
|
||||
const change_handler = $("body").get_on_handler("change", "#file_input");
|
||||
const files = ["file1", "file2"];
|
||||
const event = {
|
||||
target: {
|
||||
files: files,
|
||||
},
|
||||
};
|
||||
let upload_files_called = false;
|
||||
upload.upload_files = (uppy, config, files) => {
|
||||
assert.equal(config.mode, "compose");
|
||||
assert.equal(files, files);
|
||||
upload_files_called = true;
|
||||
};
|
||||
change_handler(event);
|
||||
assert(upload_files_called);
|
||||
});
|
||||
|
||||
run_test('file_drop', () => {
|
||||
set_global('$', global.make_zjquery());
|
||||
|
||||
upload.setup_upload({mode: "compose"});
|
||||
|
||||
let prevent_default_counter = 0;
|
||||
const drag_event = {
|
||||
preventDefault: () => {
|
||||
prevent_default_counter += 1;
|
||||
},
|
||||
};
|
||||
const dragover_handler = $("#compose").get_on_handler("dragover");
|
||||
dragover_handler(drag_event);
|
||||
assert.equal(prevent_default_counter, 1);
|
||||
|
||||
const dragenter_handler = $("#compose").get_on_handler("dragenter");
|
||||
dragenter_handler(drag_event);
|
||||
assert.equal(prevent_default_counter, 2);
|
||||
|
||||
const files = ["file1", "file2"];
|
||||
const drop_event = {
|
||||
preventDefault: () => {
|
||||
prevent_default_counter += 1;
|
||||
},
|
||||
originalEvent: {
|
||||
dataTransfer: {
|
||||
files: files,
|
||||
},
|
||||
},
|
||||
};
|
||||
const drop_handler = $("#compose").get_on_handler("drop");
|
||||
let upload_files_called = false;
|
||||
upload.upload_files = () => {upload_files_called = true;};
|
||||
drop_handler(drop_event);
|
||||
assert.equal(prevent_default_counter, 3);
|
||||
assert.equal(upload_files_called, true);
|
||||
});
|
||||
|
||||
run_test('copy_paste', () => {
|
||||
set_global('$', global.make_zjquery());
|
||||
|
||||
upload.setup_upload({mode: "compose"});
|
||||
|
||||
const paste_handler = $("#compose").get_on_handler("paste");
|
||||
let get_as_file_called = false;
|
||||
let event = {
|
||||
originalEvent: {
|
||||
clipboardData: {
|
||||
items: [
|
||||
{
|
||||
kind: "file",
|
||||
getAsFile: () => {
|
||||
get_as_file_called = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "notfile",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
let upload_files_called = false;
|
||||
upload.upload_files = () => {
|
||||
upload_files_called = true;
|
||||
};
|
||||
|
||||
setup();
|
||||
upload_opts.uploadFinished(i, {
|
||||
trackingId: "1549958107000",
|
||||
name: 'some-file',
|
||||
}, response);
|
||||
upload_opts.progressUpdated(1, {trackingId: "1549958107000"}, 100);
|
||||
assert_side_effects();
|
||||
}
|
||||
paste_handler(event);
|
||||
assert(get_as_file_called);
|
||||
assert(upload_files_called);
|
||||
|
||||
const msg_1 = '[pasted image](https://foo.com/uploads/122456)';
|
||||
const msg_2 = '[foobar.jpeg](https://foo.com/user_uploads/foobar.jpeg)';
|
||||
|
||||
test(-1, {}, '');
|
||||
test(-1, {uri: 'https://foo.com/uploads/122456'}, msg_1);
|
||||
test(1, {uri: '/user_uploads/foobar.jpeg'}, msg_2);
|
||||
upload_files_called = false;
|
||||
event = {
|
||||
originalEvent: {},
|
||||
};
|
||||
paste_handler(event);
|
||||
assert.equal(upload_files_called, false);
|
||||
});
|
||||
|
||||
run_test('uppy_events', () => {
|
||||
set_global('$', global.make_zjquery());
|
||||
const callbacks = {};
|
||||
let uppy_cancel_all_counter = 0;
|
||||
let state = {};
|
||||
|
||||
|
||||
function uppy_stub() {
|
||||
return {
|
||||
setMeta: () => {},
|
||||
use: () => {},
|
||||
cancelAll: () => {
|
||||
uppy_cancel_all_counter += 1;
|
||||
},
|
||||
on: (event_name, callback) => {
|
||||
callbacks[event_name] = callback;
|
||||
},
|
||||
getState: () => {
|
||||
return {
|
||||
info: {
|
||||
type: state.type,
|
||||
details: state.details,
|
||||
message: state.message,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
uppy_stub.Plugin = plugin_stub;
|
||||
rewiremock.proxy(() => require("../../static/js/upload"), {'@uppy/core': uppy_stub});
|
||||
upload.setup_upload({mode: "compose"});
|
||||
assert.equal(Object.keys(callbacks).length, 4);
|
||||
|
||||
const on_upload_success_callback = callbacks["upload-success"];
|
||||
const file = {
|
||||
name: "copenhagen.png",
|
||||
};
|
||||
let response = {
|
||||
body: {
|
||||
uri: "/user_uploads/4/cb/rue1c-MlMUjDAUdkRrEM4BTJ/copenhagen.png",
|
||||
},
|
||||
};
|
||||
let compose_actions_start_called = false;
|
||||
compose_actions.start = () => {
|
||||
compose_actions_start_called = true;
|
||||
};
|
||||
let compose_ui_replace_syntax_called = false;
|
||||
compose_ui.replace_syntax = (old_syntax, new_syntax, textarea) => {
|
||||
compose_ui_replace_syntax_called = true;
|
||||
assert.equal(old_syntax, "[Uploading copenhagen.png…]()");
|
||||
assert.equal(new_syntax, "[copenhagen.png](https://foo.com/user_uploads/4/cb/rue1c-MlMUjDAUdkRrEM4BTJ/copenhagen.png)");
|
||||
assert.equal(textarea, $('#compose-textarea'));
|
||||
};
|
||||
let compose_ui_autosize_textarea_called = false;
|
||||
compose_ui.autosize_textarea = () => {
|
||||
compose_ui_autosize_textarea_called = true;
|
||||
};
|
||||
on_upload_success_callback(file, response);
|
||||
assert(compose_actions_start_called);
|
||||
assert(compose_ui_replace_syntax_called);
|
||||
assert(compose_ui_autosize_textarea_called);
|
||||
|
||||
response = {
|
||||
body: {
|
||||
uri: undefined,
|
||||
},
|
||||
};
|
||||
compose_actions_start_called = false;
|
||||
compose_ui_replace_syntax_called = false;
|
||||
compose_ui_autosize_textarea_called = false;
|
||||
on_upload_success_callback(file, response);
|
||||
assert.equal(compose_actions_start_called, false);
|
||||
assert.equal(compose_ui_replace_syntax_called, false);
|
||||
assert.equal(compose_ui_autosize_textarea_called, false);
|
||||
|
||||
const on_complete_callback = callbacks.complete;
|
||||
global.patch_builtin('setTimeout', (func) => {
|
||||
func();
|
||||
});
|
||||
let hide_upload_status_called = false;
|
||||
upload.hide_upload_status = () => {
|
||||
hide_upload_status_called = true;
|
||||
};
|
||||
assert.equal(uppy_cancel_all_counter, 0);
|
||||
on_complete_callback();
|
||||
assert.equal(uppy_cancel_all_counter, 1);
|
||||
assert(hide_upload_status_called);
|
||||
|
||||
state = {
|
||||
type: "error",
|
||||
details: "Some Error",
|
||||
message: "Some error message",
|
||||
};
|
||||
const on_info_visible_callback = callbacks["info-visible"];
|
||||
let show_error_message_called = false;
|
||||
upload.show_error_message = (config, message) => {
|
||||
show_error_message_called = true;
|
||||
assert.equal(config.mode, "compose");
|
||||
assert.equal(message, "Some error message");
|
||||
};
|
||||
on_info_visible_callback();
|
||||
assert.equal(uppy_cancel_all_counter, 2);
|
||||
assert(show_error_message_called);
|
||||
|
||||
state = {
|
||||
type: "error",
|
||||
message: "No Internet connection",
|
||||
};
|
||||
on_info_visible_callback();
|
||||
assert.equal(uppy_cancel_all_counter, 2);
|
||||
|
||||
state = {
|
||||
type: "error",
|
||||
details: "Upload Error",
|
||||
};
|
||||
on_info_visible_callback();
|
||||
assert.equal(uppy_cancel_all_counter, 2);
|
||||
|
||||
const on_upload_error_callback = callbacks["upload-error"];
|
||||
show_error_message_called = false;
|
||||
upload.show_error_message = (config, message) => {
|
||||
show_error_message_called = true;
|
||||
assert.equal(config.mode, "compose");
|
||||
assert.equal(message, "Response message");
|
||||
};
|
||||
response = {
|
||||
body: {
|
||||
msg: "Response message",
|
||||
},
|
||||
};
|
||||
on_upload_error_callback(null, null, response);
|
||||
assert.equal(uppy_cancel_all_counter, 3);
|
||||
assert(show_error_message_called);
|
||||
|
||||
upload.show_error_message = (config, message) => {
|
||||
show_error_message_called = true;
|
||||
assert.equal(config.mode, "compose");
|
||||
assert.equal(message, null);
|
||||
};
|
||||
on_upload_error_callback(null, null);
|
||||
assert.equal(uppy_cancel_all_counter, 4);
|
||||
assert(show_error_message_called);
|
||||
});
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
"@babel/preset-env": "^7.5.5",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@babel/register": "^7.6.2",
|
||||
"@uppy/core": "^1.7.1",
|
||||
"@uppy/progress-bar": "^1.3.4",
|
||||
"@uppy/xhr-upload": "^1.4.2",
|
||||
"autoprefixer": "^9.6.1",
|
||||
"autosize": "^4.0.2",
|
||||
"babel-loader": "^8.0.6",
|
||||
|
|
|
@ -3,7 +3,6 @@ import "./common.js";
|
|||
// Import Third party libraries
|
||||
import "../../third/bootstrap-notify/js/bootstrap-notify.js";
|
||||
import "../../third/bootstrap-typeahead/typeahead.js";
|
||||
import "../../third/jquery-filedrop/jquery.filedrop.js";
|
||||
import "jquery-caret-plugin/src/jquery.caret.js";
|
||||
import "../../third/jquery-idle/jquery.idle.js";
|
||||
import "spectrum-colorpicker";
|
||||
|
|
|
@ -18,3 +18,5 @@ import "font-awesome/css/font-awesome.css";
|
|||
import "../../assets/icons/zulip-icons.font.js";
|
||||
import "source-sans-pro/source-sans-pro.css";
|
||||
import "../../styles/pygments.scss";
|
||||
import "@uppy/core/dist/style.css";
|
||||
import "@uppy/progress-bar/dist/style.css";
|
||||
|
|
|
@ -17,6 +17,7 @@ const render_compose_private_stream_alert = require("../templates/compose_privat
|
|||
let user_acknowledged_all_everyone;
|
||||
let user_acknowledged_announce;
|
||||
let wildcard_mention;
|
||||
let uppy;
|
||||
|
||||
exports.all_everyone_warn_threshold = 15;
|
||||
exports.announce_warn_threshold = 60;
|
||||
|
@ -146,11 +147,7 @@ function update_fade() {
|
|||
|
||||
exports.abort_xhr = function () {
|
||||
$("#compose-send-button").prop("disabled", false);
|
||||
const xhr = $("#compose").data("filedrop_xhr");
|
||||
if (xhr !== undefined) {
|
||||
xhr.abort();
|
||||
$("#compose").removeData("filedrop_xhr");
|
||||
}
|
||||
uppy.cancelAll();
|
||||
};
|
||||
|
||||
exports.empty_topic_placeholder = function () {
|
||||
|
@ -1090,11 +1087,9 @@ exports.initialize = function () {
|
|||
exports.clear_preview_area();
|
||||
});
|
||||
|
||||
$("#compose").filedrop(
|
||||
upload.options({
|
||||
mode: 'compose',
|
||||
})
|
||||
);
|
||||
uppy = upload.setup_upload({
|
||||
mode: "compose",
|
||||
});
|
||||
|
||||
$("#compose-textarea").focus(function () {
|
||||
const opts = {
|
||||
|
|
|
@ -316,8 +316,7 @@ exports.paste_handler = function (event) {
|
|||
const mdImageRegex = /^!\[.*\]\(.*\)$/;
|
||||
if (text.match(mdImageRegex)) {
|
||||
// This block catches cases where we are pasting an
|
||||
// image into Zulip, which should be handled by the
|
||||
// jQuery filedrop library, not this code path.
|
||||
// image into Zulip, which is handled by upload.js.
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
|
|
@ -375,12 +375,10 @@ function start_edit_with_content(row, content, edit_box_open_callback) {
|
|||
edit_box_open_callback();
|
||||
}
|
||||
|
||||
row.find('#message_edit_form').filedrop(
|
||||
upload.options({
|
||||
upload.setup_upload({
|
||||
mode: 'edit',
|
||||
row: rows.id(row),
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
exports.start = function (row, edit_box_open_callback) {
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
function make_upload_absolute(uri) {
|
||||
const Uppy = require('@uppy/core');
|
||||
const XHRUpload = require('@uppy/xhr-upload');
|
||||
const ProgressBar = require('@uppy/progress-bar');
|
||||
|
||||
exports.make_upload_absolute = function (uri) {
|
||||
if (uri.startsWith(compose.uploads_path)) {
|
||||
// Rewrite the URI to a usable link
|
||||
return compose.uploads_domain + uri;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
};
|
||||
|
||||
// Show the upload button only if the browser supports it.
|
||||
exports.feature_check = function (upload_button) {
|
||||
|
@ -13,187 +17,235 @@ exports.feature_check = function (upload_button) {
|
|||
}
|
||||
};
|
||||
|
||||
exports.options = function (config) {
|
||||
let textarea;
|
||||
let send_button;
|
||||
let send_status;
|
||||
let send_status_close;
|
||||
let error_msg;
|
||||
let upload_bar;
|
||||
let file_input;
|
||||
|
||||
switch (config.mode) {
|
||||
case 'compose':
|
||||
textarea = $('#compose-textarea');
|
||||
send_button = $('#compose-send-button');
|
||||
send_status = $('#compose-send-status');
|
||||
send_status_close = $('.compose-send-status-close');
|
||||
error_msg = $('#compose-error-msg');
|
||||
upload_bar = 'compose-upload-bar';
|
||||
file_input = 'file_input';
|
||||
break;
|
||||
case 'edit':
|
||||
textarea = $('#message_edit_content_' + config.row);
|
||||
send_button = textarea.closest('#message_edit_form').find('.message_edit_save');
|
||||
send_status = $('#message-edit-send-status-' + config.row);
|
||||
send_status_close = send_status.find('.send-status-close');
|
||||
error_msg = send_status.find('.error-msg');
|
||||
upload_bar = 'message-edit-upload-bar-' + config.row;
|
||||
file_input = 'message_edit_file_input_' + config.row;
|
||||
break;
|
||||
exports.get_item = function (key, config) {
|
||||
if (!config) {
|
||||
throw Error("Missing config");
|
||||
}
|
||||
if (config.mode === "compose") {
|
||||
switch (key) {
|
||||
case "textarea":
|
||||
return $('#compose-textarea');
|
||||
case "send_button":
|
||||
return $('#compose-send-button');
|
||||
case "send_status_identifier":
|
||||
return '#compose-send-status';
|
||||
case "send_status":
|
||||
return $('#compose-send-status');
|
||||
case "send_status_close_button":
|
||||
return $('.compose-send-status-close');
|
||||
case "send_status_message":
|
||||
return $('#compose-error-msg');
|
||||
case "file_input_identifier":
|
||||
return "#file_input";
|
||||
case "source":
|
||||
return "compose-file-input";
|
||||
case "drag_drop_container":
|
||||
return $("#compose");
|
||||
default:
|
||||
throw Error(`Invalid key name for mode "${config.mode}"`);
|
||||
}
|
||||
} else if (config.mode === "edit") {
|
||||
if (!config.row) {
|
||||
throw Error("Missing row in config");
|
||||
}
|
||||
switch (key) {
|
||||
case "textarea":
|
||||
return $('#message_edit_content_' + config.row);
|
||||
case "send_button":
|
||||
return $('#message_edit_content_' + config.row).closest('#message_edit_form').find('.message_edit_save');
|
||||
case "send_status_identifier":
|
||||
return '#message-edit-send-status-' + config.row;
|
||||
case "send_status":
|
||||
return $('#message-edit-send-status-' + config.row);
|
||||
case "send_status_close_button":
|
||||
return $('#message-edit-send-status-' + config.row).find('.send-status-close');
|
||||
case "send_status_message":
|
||||
return $('#message-edit-send-status-' + config.row).find('.error-msg');
|
||||
case "file_input_identifier":
|
||||
return '#message_edit_file_input_' + config.row;
|
||||
case "source":
|
||||
return "message-edit-file-input";
|
||||
case "drag_drop_container":
|
||||
return $("#message_edit_form");
|
||||
default:
|
||||
throw Error(`Invalid key name for mode "${config.mode}"`);
|
||||
}
|
||||
} else {
|
||||
throw Error("Invalid upload mode!");
|
||||
}
|
||||
};
|
||||
|
||||
const hide_upload_status = function () {
|
||||
send_button.prop("disabled", false);
|
||||
send_status.removeClass("alert-info").hide();
|
||||
$('div.progress.active').remove();
|
||||
};
|
||||
exports.hide_upload_status = function (config) {
|
||||
exports.get_item("send_button", config).prop("disabled", false);
|
||||
exports.get_item("send_status", config).removeClass("alert-info").hide();
|
||||
};
|
||||
|
||||
const drop = function () {
|
||||
send_button.attr("disabled", "");
|
||||
send_status.addClass("alert-info").show();
|
||||
send_status_close.one('click', function () {
|
||||
setTimeout(function () {
|
||||
hide_upload_status();
|
||||
}, 500);
|
||||
compose.abort_xhr();
|
||||
});
|
||||
};
|
||||
|
||||
const uploadStarted = function (i, file) {
|
||||
error_msg.html($("<p>").text(i18n.t("Uploading…")));
|
||||
// file.lastModified is unique for each upload, and was previously used to track each
|
||||
// upload. But, when an image is pasted into Safari, it looks like the lastModified time
|
||||
// gets changed by the time the image upload is finished, and we lose track of the
|
||||
// uploaded images. Instead, we set a random ID for each image, to track it.
|
||||
if (!file.trackingId) { // The conditional check is present to make this easy to test
|
||||
file.trackingId = Math.random().toString().substring(2); // Use digits after the `.`
|
||||
exports.show_error_message = function (config, message) {
|
||||
if (!message) {
|
||||
message = i18n.t("An unknown error occurred.");
|
||||
}
|
||||
send_status.append('<div class="progress active">' +
|
||||
'<div class="bar" id="' + upload_bar + '-' + file.trackingId + '" style="width: 0"></div>' +
|
||||
'</div>');
|
||||
compose_ui.insert_syntax_and_focus("[Uploading " + file.name + "…]()", textarea);
|
||||
};
|
||||
exports.get_item("send_button", config).prop("disabled", false);
|
||||
exports.get_item("send_status", config).addClass("alert-error").removeClass("alert-info").show();
|
||||
exports.get_item("send_status_message", config).text(message);
|
||||
};
|
||||
|
||||
const progressUpdated = function (i, file, progress) {
|
||||
$("#" + upload_bar + '-' + file.trackingId).width(progress + "%");
|
||||
};
|
||||
|
||||
const uploadError = function (error_code, server_response, file) {
|
||||
let msg;
|
||||
send_status.addClass("alert-error").removeClass("alert-info");
|
||||
send_button.prop("disabled", false);
|
||||
if (file !== undefined) {
|
||||
$("#" + upload_bar + '-' + file.trackingId).parent().remove();
|
||||
}
|
||||
|
||||
switch (error_code) {
|
||||
case 'BrowserNotSupported':
|
||||
msg = i18n.t("File upload is not yet available for your browser.");
|
||||
break;
|
||||
case 'TooManyFiles':
|
||||
msg = i18n.t("Unable to upload that many files at once.");
|
||||
break;
|
||||
case 'FileTooLarge':
|
||||
if (page_params.max_file_upload_size > 0) {
|
||||
// sanitization not needed as the file name is not potentially parsed as HTML, etc.
|
||||
const context = {
|
||||
file_name: file.name,
|
||||
file_size: page_params.max_file_upload_size,
|
||||
};
|
||||
msg = i18n.t('"__file_name__" was too large; the maximum file size is __file_size__MB.',
|
||||
context);
|
||||
} else {
|
||||
// If uploading files has been disabled.
|
||||
msg = i18n.t('File and image uploads have been disabled for this organization.');
|
||||
}
|
||||
break;
|
||||
case 413: // HTTP status "Request Entity Too Large"
|
||||
msg = i18n.t("Sorry, the file was too large.");
|
||||
break;
|
||||
case 400: {
|
||||
const server_message = server_response && server_response.msg;
|
||||
msg = server_message || i18n.t("An unknown error occurred.");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
msg = i18n.t("An unknown error occurred.");
|
||||
break;
|
||||
}
|
||||
error_msg.text(msg);
|
||||
};
|
||||
|
||||
const uploadFinished = function (i, file, response) {
|
||||
if (response.uri === undefined) {
|
||||
exports.upload_files = function (uppy, config, files) {
|
||||
if (files.length === 0) {
|
||||
uppy.cancelAll();
|
||||
exports.hide_upload_status(config);
|
||||
return;
|
||||
}
|
||||
const split_uri = response.uri.split("/");
|
||||
const filename = split_uri[split_uri.length - 1];
|
||||
// Urgh, yet another hack to make sure we're "composing"
|
||||
// when text gets added into the composebox.
|
||||
if (config.mode === 'compose' && !compose_state.composing()) {
|
||||
compose_actions.start('stream');
|
||||
} else if (config.mode === 'edit' && document.activeElement !== textarea) {
|
||||
// If we are editing, focus on the edit message box
|
||||
textarea.focus();
|
||||
if (page_params.max_file_upload_size === 0) {
|
||||
exports.show_error_message(config, i18n.t('File and image uploads have been disabled for this organization.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const uri = make_upload_absolute(response.uri);
|
||||
|
||||
if (i === -1) {
|
||||
// This is a paste, so there's no filename. Show the image directly
|
||||
const pasted_image_uri = "[pasted image](" + uri + ")";
|
||||
compose_ui.replace_syntax("[Uploading " + file.name + "…]()", pasted_image_uri, textarea);
|
||||
} else {
|
||||
// This is a dropped file, so make the filename a link to the image
|
||||
const filename_uri = "[" + filename + "](" + uri + ")";
|
||||
compose_ui.replace_syntax("[Uploading " + file.name + "…]()", filename_uri, textarea);
|
||||
}
|
||||
compose_ui.autosize_textarea();
|
||||
|
||||
exports.get_item("send_button", config).attr("disabled", "");
|
||||
exports.get_item("send_status", config).addClass("alert-info").removeClass("alert-error").show();
|
||||
exports.get_item("send_status_message", config).html($("<p>").text(i18n.t("Uploading…")));
|
||||
exports.get_item("send_status_close_button", config).one('click', function () {
|
||||
uppy.cancelAll();
|
||||
setTimeout(function () {
|
||||
$("#" + upload_bar + '-' + file.trackingId).parent().remove();
|
||||
if ($('div.progress.active').length === 0) {
|
||||
hide_upload_status(file);
|
||||
}
|
||||
exports.hide_upload_status(config);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// In order to upload the same file twice in a row, we need to clear out
|
||||
// the file input element, so that the next time we use the file dialog,
|
||||
// an actual change event is fired. IE doesn't allow .val('') so we
|
||||
// need to clone it. (Taken from the jQuery form plugin)
|
||||
if (/MSIE/.test(navigator.userAgent)) {
|
||||
$('#' + file_input).replaceWith($('#' + file_input).clone(true));
|
||||
} else {
|
||||
$('#' + file_input).val('');
|
||||
for (const file of files) {
|
||||
try {
|
||||
compose_ui.insert_syntax_and_focus("[Uploading " + file.name + "…]()", exports.get_item("textarea", config));
|
||||
compose_ui.autosize_textarea();
|
||||
uppy.addFile({
|
||||
source: exports.get_item("source", config),
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
data: file,
|
||||
});
|
||||
} catch (error) {
|
||||
// Errors are handled by info-visible and upload-error event callbacks.
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
url: "/json/user_uploads",
|
||||
fallback_id: file_input, // Target for standard file dialog
|
||||
paramname: "file",
|
||||
max_file_upload_size: page_params.max_file_upload_size,
|
||||
data: {
|
||||
// the token isn't automatically included in filedrop's post
|
||||
csrfmiddlewaretoken: csrf_token,
|
||||
exports.setup_upload = function (config) {
|
||||
const uppy = Uppy({
|
||||
debug: false,
|
||||
autoProceed: true,
|
||||
restrictions: {
|
||||
maxFileSize: page_params.max_file_upload_size * 1024 * 1024,
|
||||
},
|
||||
raw_droppable: ['text/uri-list', 'text/plain'],
|
||||
drop: drop,
|
||||
uploadStarted: uploadStarted,
|
||||
progressUpdated: progressUpdated,
|
||||
error: uploadError,
|
||||
uploadFinished: uploadFinished,
|
||||
rawDrop: function (contents) {
|
||||
locale: {
|
||||
strings: {
|
||||
exceedsSize: i18n.t('This file exceeds maximum allowed size of'),
|
||||
failedToUpload: i18n.t('Failed to upload %{file}'),
|
||||
},
|
||||
},
|
||||
});
|
||||
uppy.setMeta({
|
||||
csrfmiddlewaretoken: csrf_token,
|
||||
});
|
||||
uppy.use(
|
||||
XHRUpload, {
|
||||
endpoint: '/json/user_uploads',
|
||||
formData: true,
|
||||
fieldName: 'file',
|
||||
// Number of concurrent uploads
|
||||
limit: 5,
|
||||
locale: {
|
||||
strings: {
|
||||
timedOut: i18n.t('Upload stalled for %{seconds} seconds, aborting.'),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
uppy.use(ProgressBar, {
|
||||
target: exports.get_item("send_status_identifier", config),
|
||||
hideAfterFinish: false,
|
||||
});
|
||||
|
||||
$("body").on("change", exports.get_item("file_input_identifier", config), (event) => {
|
||||
const files = event.target.files;
|
||||
exports.upload_files(uppy, config, files);
|
||||
});
|
||||
|
||||
const drag_drop_container = exports.get_item("drag_drop_container", config);
|
||||
drag_drop_container.on("dragover", (event) => event.preventDefault());
|
||||
drag_drop_container.on("dragenter", (event) => event.preventDefault());
|
||||
|
||||
drag_drop_container.on("drop", (event) => {
|
||||
event.preventDefault();
|
||||
const files = event.originalEvent.dataTransfer.files;
|
||||
exports.upload_files(uppy, config, files);
|
||||
});
|
||||
|
||||
drag_drop_container.on("paste", (event) => {
|
||||
const clipboard_data = event.clipboardData || event.originalEvent.clipboardData;
|
||||
if (!clipboard_data) {
|
||||
return;
|
||||
}
|
||||
const items = clipboard_data.items;
|
||||
const files = [];
|
||||
for (const item of items) {
|
||||
if (item.kind !== "file") {
|
||||
continue;
|
||||
}
|
||||
const file = item.getAsFile();
|
||||
files.push(file);
|
||||
}
|
||||
exports.upload_files(uppy, config, files);
|
||||
});
|
||||
|
||||
uppy.on('upload-success', (file, response) => {
|
||||
const uri = response.body.uri;
|
||||
if (uri === undefined) {
|
||||
return;
|
||||
}
|
||||
const split_uri = uri.split("/");
|
||||
const filename = split_uri[split_uri.length - 1];
|
||||
if (!compose_state.composing()) {
|
||||
compose_actions.start('stream');
|
||||
}
|
||||
textarea.val(textarea.val() + contents);
|
||||
const absolute_uri = upload.make_upload_absolute(uri);
|
||||
const filename_uri = "[" + filename + "](" + absolute_uri + ")";
|
||||
compose_ui.replace_syntax("[Uploading " + file.name + "…]()", filename_uri, exports.get_item("textarea", config));
|
||||
compose_ui.autosize_textarea();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
uppy.on('complete', () => {
|
||||
setTimeout(function () {
|
||||
uppy.cancelAll();
|
||||
exports.hide_upload_status(config);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
uppy.on('info-visible', () => {
|
||||
const info = uppy.getState().info;
|
||||
if (info.type === "error" && info.message === "No Internet connection") {
|
||||
// server_events already handles the case of no internet.
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.type === "error" && info.details === "Upload Error") {
|
||||
// The server errors come under 'Upload Error'. But we can't handle them
|
||||
// here because info object don't contain response.body.msg received from
|
||||
// the server. Server errors are hence handled by on('upload-error').
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.type === "error") {
|
||||
// The remaining errors are mostly frontend errors like file being too large
|
||||
// for upload.
|
||||
uppy.cancelAll();
|
||||
exports.show_error_message(config, info.message);
|
||||
}
|
||||
});
|
||||
|
||||
uppy.on('upload-error', (file, error, response) => {
|
||||
const message = response ? response.body.msg : null;
|
||||
uppy.cancelAll();
|
||||
exports.show_error_message(config, message);
|
||||
});
|
||||
|
||||
return uppy;
|
||||
};
|
||||
|
||||
window.upload = exports;
|
||||
|
|
|
@ -1,680 +0,0 @@
|
|||
/*global jQuery:false, alert:false */
|
||||
|
||||
/** @preserve
|
||||
Software from "jQuery Filedrop 0.1.0", a jQuery plugin for html5 dragging files,
|
||||
is Copyright (c) Resopollution and is provided under the following license:
|
||||
--
|
||||
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.
|
||||
--
|
||||
*/
|
||||
|
||||
/*
|
||||
* Default text - jQuery plugin for html5 dragging files from desktop to browser
|
||||
*
|
||||
* Author: Weixi Yen
|
||||
*
|
||||
* Email: [Firstname][Lastname]@gmail.com
|
||||
*
|
||||
* Copyright (c) 2010 Resopollution
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Project home:
|
||||
* http://www.github.com/weixiyen/jquery-filedrop
|
||||
*
|
||||
* Version: 0.1.0
|
||||
*
|
||||
* Features:
|
||||
* Allows sending of extra parameters with file.
|
||||
* Works with Firefox 3.6+
|
||||
* Future-compliant with HTML5 spec (will work with Webkit browsers and IE9)
|
||||
* Usage:
|
||||
* See README at project homepage
|
||||
*
|
||||
*/
|
||||
;(function($) {
|
||||
|
||||
var default_opts = {
|
||||
fallback_id: '',
|
||||
url: '',
|
||||
refresh: 1000,
|
||||
paramname: 'userfile',
|
||||
allowedfiletypes:[],
|
||||
raw_droppable:[],
|
||||
maxfiles: 25, // Ignored if queuefiles is set > 0
|
||||
max_file_upload_size: 1, // MB file size limit
|
||||
queuefiles: 0, // Max files before queueing (for large volume uploads)
|
||||
queuewait: 200, // Queue wait time if full
|
||||
data: {},
|
||||
headers: {},
|
||||
drop: empty,
|
||||
rawDrop: empty,
|
||||
dragStart: empty,
|
||||
dragEnter: empty,
|
||||
dragOver: empty,
|
||||
dragLeave: empty,
|
||||
docEnter: empty,
|
||||
docOver: empty,
|
||||
docLeave: empty,
|
||||
beforeEach: empty,
|
||||
afterAll: empty,
|
||||
rename: empty,
|
||||
error: function(err, response, file, i) {
|
||||
alert(err);
|
||||
},
|
||||
uploadStarted: empty,
|
||||
uploadFinished: empty,
|
||||
progressUpdated: empty,
|
||||
globalProgressUpdated: empty,
|
||||
speedUpdated: empty
|
||||
},
|
||||
errors = ["BrowserNotSupported", "TooManyFiles", "FileTooLarge", "FileTypeNotAllowed", "NotFound", "NotReadable", "AbortError", "ReadError"],
|
||||
doc_leave_timer, stop_loop = false,
|
||||
files_count = 0,
|
||||
files;
|
||||
|
||||
$.fn.filedrop = function(options) {
|
||||
var opts = $.extend({}, default_opts, options),
|
||||
global_progress = []
|
||||
// Zulip modification: keep a pointer to the object that the function
|
||||
// was invoked on.
|
||||
var caller = this;
|
||||
|
||||
this.on('drop', drop).on('dragstart', opts.dragStart).on('dragenter', dragEnter).on('dragover', dragOver).on('dragleave', dragLeave);
|
||||
this.on('paste', paste);
|
||||
this.on('imagedata-upload.zulip', uploadRawImageData);
|
||||
|
||||
$(document).on('drop', docDrop).on('dragenter', docEnter).on('dragover', docOver).on('dragleave', docLeave);
|
||||
|
||||
$('#' + opts.fallback_id).change(function(e) {
|
||||
opts.drop(e);
|
||||
files = e.target.files;
|
||||
files_count = files.length;
|
||||
upload();
|
||||
});
|
||||
|
||||
function drop(e) {
|
||||
if (!e.originalEvent.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
files = e.originalEvent.dataTransfer.files;
|
||||
|
||||
function has_type(dom_stringlist, type) {
|
||||
var j;
|
||||
for (j = 0; j < dom_stringlist.length; j++) {
|
||||
if (dom_stringlist[j] === type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (files.length === 0) {
|
||||
var i;
|
||||
for (i = 0; i < opts.raw_droppable.length; i++) {
|
||||
var type = opts.raw_droppable[i];
|
||||
if (has_type(e.originalEvent.dataTransfer.types, type)) {
|
||||
opts.rawDrop(e.originalEvent.dataTransfer.getData(type));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( opts.drop.call(this, e) === false ) return false;
|
||||
if (files === null || files === undefined || files.length === 0) {
|
||||
opts.error(errors[0]);
|
||||
return false;
|
||||
}
|
||||
files_count = files.length;
|
||||
upload();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
function sendRawImageData(event, image) {
|
||||
function finished_callback(serverResponse, timeDiff, xhr) {
|
||||
return opts.uploadFinished(-1, image.file, serverResponse, timeDiff, xhr);
|
||||
}
|
||||
|
||||
var url_params = "?mimetype=" + encodeURIComponent(image.type);
|
||||
do_xhr("pasted_image", image.data, image.type, {file: image.file}, url_params, finished_callback, function () {});
|
||||
}
|
||||
|
||||
function uploadRawImageData(event, image) {
|
||||
// Call the user callback to initialize the drop event
|
||||
if( opts.drop.call(this, undefined) === false ) return false;
|
||||
sendRawImageData(event, image);
|
||||
}
|
||||
|
||||
function dataIsImage(data) {
|
||||
// Check if the clipboard data is actually an image or a thumbnail of the
|
||||
// copied text.
|
||||
|
||||
var text = data.getData('text/html');
|
||||
|
||||
if (!text) {
|
||||
// No html is present, when pasting from image viewers or a screenshot
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
var html = $.parseHTML(text);
|
||||
} catch(e) {
|
||||
// This is really a problem with the software, where we copied the text
|
||||
// from - but we just let the default browser behavior prevail
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some software like MS Word adds an image thumbnail, when text is
|
||||
// copied. We would like to paste the actual text, instead of the
|
||||
// thumbnail image in this case.
|
||||
|
||||
// When an image copied in a (modern?) Browser, a 'text/html' item is
|
||||
// present in the clipboard, which has an img tag for the copied image
|
||||
// (along with may be a meta tag)
|
||||
|
||||
var allowedTags = ["META", "IMG"];
|
||||
for (var i=0; i < html.length; i += 1){
|
||||
if (allowedTags.indexOf(html[i].nodeName) < 0){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function paste(event) {
|
||||
if (event.originalEvent.clipboardData === undefined ||
|
||||
event.originalEvent.clipboardData.items === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the data in the clipboard is really an image, or just a
|
||||
// thumbnail of the copied text.
|
||||
if (!dataIsImage(event.originalEvent.clipboardData)){
|
||||
return;
|
||||
}
|
||||
|
||||
// Take the first image pasted in the clipboard
|
||||
var match_re = /image.*/;
|
||||
var item;
|
||||
$.each(event.originalEvent.clipboardData.items, function (idx, this_event) {
|
||||
if (this_event.type.match(match_re)) {
|
||||
item = this_event;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (item === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the user callback to initialize the drop event
|
||||
if( opts.drop.call(this, event) === false ) return false;
|
||||
|
||||
// Read the data of the drop in as binary data, and send it to the server
|
||||
var data = item.getAsFile();
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
sendRawImageData(event, {type: data.type, data: event.target.result, file: data});
|
||||
};
|
||||
reader.readAsBinaryString(data);
|
||||
opts.uploadStarted(undefined, data);
|
||||
|
||||
// Once the upload has started, the event needn't be processed further. This seems to be required on Safari to
|
||||
// prevent the Copied Image URL from being pasted along with the uploaded image URL.
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
function getBuilder(filename, filedata, mime, boundary) {
|
||||
var dashdash = '--',
|
||||
crlf = '\r\n',
|
||||
builder = '';
|
||||
|
||||
if (opts.data) {
|
||||
var params = $.param(opts.data).replace(/\+/g, '%20').split(/&/);
|
||||
|
||||
$.each(params, function() {
|
||||
var pair = this.split("=", 2),
|
||||
name = decodeURIComponent(pair[0]),
|
||||
val = decodeURIComponent(pair[1]);
|
||||
|
||||
builder += dashdash;
|
||||
builder += boundary;
|
||||
builder += crlf;
|
||||
builder += 'Content-Disposition: form-data; name="' + name + '"';
|
||||
builder += crlf;
|
||||
builder += crlf;
|
||||
builder += val;
|
||||
builder += crlf;
|
||||
});
|
||||
}
|
||||
|
||||
builder += dashdash;
|
||||
builder += boundary;
|
||||
builder += crlf;
|
||||
builder += 'Content-Disposition: form-data; name="' + opts.paramname + '"';
|
||||
builder += '; filename="' + encodeURIComponent(filename) + '"';
|
||||
builder += crlf;
|
||||
|
||||
builder += 'Content-Type: ' + mime;
|
||||
builder += crlf;
|
||||
builder += crlf;
|
||||
|
||||
builder += filedata;
|
||||
builder += crlf;
|
||||
|
||||
builder += dashdash;
|
||||
builder += boundary;
|
||||
builder += dashdash;
|
||||
builder += crlf;
|
||||
return builder;
|
||||
}
|
||||
|
||||
function progress(e) {
|
||||
if (e.lengthComputable) {
|
||||
var percentage = Math.round((e.loaded * 100) / e.total);
|
||||
if (this.currentProgress !== percentage) {
|
||||
|
||||
this.currentProgress = percentage;
|
||||
opts.progressUpdated(this.index, this.file, this.currentProgress);
|
||||
|
||||
global_progress[this.global_progress_index] = this.currentProgress;
|
||||
globalProgress();
|
||||
|
||||
var elapsed = new Date().getTime();
|
||||
var diffTime = elapsed - this.currentStart;
|
||||
if (diffTime >= opts.refresh) {
|
||||
var diffData = e.loaded - this.startData;
|
||||
var speed = diffData / diffTime; // KB per second
|
||||
opts.speedUpdated(this.index, this.file, speed);
|
||||
this.startData = e.loaded;
|
||||
this.currentStart = elapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function globalProgress() {
|
||||
if (global_progress.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var total = 0, index;
|
||||
for (index in global_progress) {
|
||||
if(global_progress.hasOwnProperty(index)) {
|
||||
total = total + global_progress[index];
|
||||
}
|
||||
}
|
||||
|
||||
opts.globalProgressUpdated(Math.round(total / global_progress.length));
|
||||
}
|
||||
|
||||
function do_xhr(filename, filedata, mime, upload_args, extra_url_args, finished_callback, on_error) {
|
||||
var xhr = new XMLHttpRequest(),
|
||||
start_time = new Date().getTime(),
|
||||
global_progress_index = global_progress.length,
|
||||
boundary = '------multipartformboundary' + (new Date()).getTime(),
|
||||
upload = xhr.upload;
|
||||
|
||||
// Zulip modification: Shunt the XHR into the parent object so we
|
||||
// can interrupt it later.
|
||||
caller.data("filedrop_xhr", xhr);
|
||||
|
||||
if (opts.withCredentials) {
|
||||
xhr.withCredentials = opts.withCredentials;
|
||||
}
|
||||
|
||||
var builder = builder = getBuilder(filename, filedata, mime, boundary);
|
||||
|
||||
upload = $.extend(upload, upload_args);
|
||||
upload.downloadStartTime = start_time;
|
||||
upload.currentStart = start_time;
|
||||
upload.currentProgress = 0;
|
||||
upload.global_progress_index = global_progress_index;
|
||||
upload.startData = 0;
|
||||
upload.addEventListener("progress", progress, false);
|
||||
|
||||
// Allow url to be a method
|
||||
if (jQuery.isFunction(opts.url)) {
|
||||
xhr.open("POST", opts.url() + extra_url_args, true);
|
||||
} else {
|
||||
xhr.open("POST", opts.url + extra_url_args, true);
|
||||
}
|
||||
|
||||
xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' + boundary);
|
||||
|
||||
// Add headers
|
||||
$.each(opts.headers, function(k, v) {
|
||||
xhr.setRequestHeader(k, v);
|
||||
});
|
||||
|
||||
xhr.sendAsBinary(builder);
|
||||
|
||||
global_progress[global_progress_index] = 0;
|
||||
globalProgress();
|
||||
|
||||
xhr.onload = function() {
|
||||
var serverResponse = null;
|
||||
|
||||
if (xhr.responseText) {
|
||||
try {
|
||||
serverResponse = JSON.parse(xhr.responseText);
|
||||
}
|
||||
catch (e) {
|
||||
serverResponse = xhr.responseText;
|
||||
}
|
||||
}
|
||||
|
||||
var now = new Date().getTime(),
|
||||
timeDiff = now - start_time,
|
||||
result = finished_callback(serverResponse, timeDiff, xhr);
|
||||
|
||||
// Make sure the global progress is updated
|
||||
global_progress[global_progress_index] = 100;
|
||||
globalProgress();
|
||||
|
||||
if (result === false) {
|
||||
stop_loop = true;
|
||||
}
|
||||
|
||||
// Pass any errors to the error option
|
||||
if (xhr.status < 200 || xhr.status > 299) {
|
||||
on_error(xhr.status, serverResponse);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Respond to an upload
|
||||
function upload() {
|
||||
stop_loop = false;
|
||||
|
||||
if (!files) {
|
||||
opts.error(errors[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.allowedfiletypes.push && opts.allowedfiletypes.length) {
|
||||
for(var fileIndex = files.length;fileIndex--;) {
|
||||
if(!files[fileIndex].type || $.inArray(files[fileIndex].type, opts.allowedfiletypes) < 0) {
|
||||
opts.error(errors[3], null, files[fileIndex], fileIndex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var filesDone = 0,
|
||||
filesRejected = 0;
|
||||
|
||||
if (files_count > opts.maxfiles && opts.queuefiles === 0) {
|
||||
opts.error(errors[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Define queues to manage upload process
|
||||
var workQueue = [];
|
||||
var processingQueue = [];
|
||||
var doneQueue = [];
|
||||
|
||||
// Add everything to the workQueue
|
||||
for (var i = 0; i < files_count; i++) {
|
||||
workQueue.push(i);
|
||||
}
|
||||
|
||||
// Helper function to enable pause of processing to wait
|
||||
// for in process queue to complete
|
||||
var pause = function(timeout) {
|
||||
setTimeout(process, timeout);
|
||||
return;
|
||||
};
|
||||
|
||||
// Process an upload, recursive
|
||||
var process = function() {
|
||||
|
||||
var fileIndex;
|
||||
|
||||
if (stop_loop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check to see if are in queue mode
|
||||
if (opts.queuefiles > 0 && processingQueue.length >= opts.queuefiles) {
|
||||
return pause(opts.queuewait);
|
||||
} else {
|
||||
// Take first thing off work queue
|
||||
fileIndex = workQueue[0];
|
||||
workQueue.splice(0, 1);
|
||||
|
||||
// Add to processing queue
|
||||
processingQueue.push(fileIndex);
|
||||
}
|
||||
|
||||
try {
|
||||
if (beforeEach(files[fileIndex]) !== false) {
|
||||
if (fileIndex === files_count) {
|
||||
return;
|
||||
}
|
||||
var reader = new FileReader(),
|
||||
max_file_size = 1048576 * opts.max_file_upload_size;
|
||||
|
||||
reader.index = fileIndex;
|
||||
if (files[fileIndex].size > max_file_size) {
|
||||
opts.error(errors[2], null, files[fileIndex], fileIndex);
|
||||
// Remove from queue
|
||||
processingQueue.forEach(function(value, key) {
|
||||
if (value === fileIndex) {
|
||||
processingQueue.splice(key, 1);
|
||||
}
|
||||
});
|
||||
filesRejected++;
|
||||
return true;
|
||||
}
|
||||
|
||||
reader.onerror = function(e) {
|
||||
switch(e.target.error.code) {
|
||||
case e.target.error.NOT_FOUND_ERR:
|
||||
opts.error(errors[4]);
|
||||
return false;
|
||||
case e.target.error.NOT_READABLE_ERR:
|
||||
opts.error(errors[5]);
|
||||
return false;
|
||||
case e.target.error.ABORT_ERR:
|
||||
opts.error(errors[6]);
|
||||
return false;
|
||||
default:
|
||||
opts.error(errors[7]);
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
reader.onloadend = !opts.beforeSend ? send : function (e) {
|
||||
opts.beforeSend(files[fileIndex], fileIndex, function () { send(e); });
|
||||
};
|
||||
|
||||
reader.readAsBinaryString(files[fileIndex]);
|
||||
|
||||
} else {
|
||||
filesRejected++;
|
||||
}
|
||||
} catch (err) {
|
||||
// Remove from queue
|
||||
processingQueue.forEach(function(value, key) {
|
||||
if (value === fileIndex) {
|
||||
processingQueue.splice(key, 1);
|
||||
}
|
||||
});
|
||||
opts.error(errors[0]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we still have work to do,
|
||||
if (workQueue.length > 0) {
|
||||
process();
|
||||
}
|
||||
};
|
||||
|
||||
var send = function(e) {
|
||||
|
||||
var fileIndex = ((typeof(e.srcElement) === "undefined") ? e.target : e.srcElement).index;
|
||||
|
||||
// Sometimes the index is not attached to the
|
||||
// event object. Find it by size. Hack for sure.
|
||||
if (e.target.index === undefined) {
|
||||
e.target.index = getIndexBySize(e.total);
|
||||
}
|
||||
|
||||
var file = files[e.target.index],
|
||||
index = e.target.index;
|
||||
|
||||
function finished_callback(serverResponse, timeDiff, xhr) {
|
||||
filesDone++;
|
||||
var result = opts.uploadFinished(index, file, serverResponse, timeDiff, xhr);
|
||||
|
||||
if (filesDone === (files_count - filesRejected)) {
|
||||
afterAll();
|
||||
}
|
||||
|
||||
// Remove from processing queue
|
||||
processingQueue.forEach(function(value, key) {
|
||||
if (value === fileIndex) {
|
||||
processingQueue.splice(key, 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Add to donequeue
|
||||
doneQueue.push(fileIndex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function on_error(status_code, response) {
|
||||
opts.error(status_code, response, file, fileIndex);
|
||||
}
|
||||
|
||||
var fileName,
|
||||
fileData = e.target.result;
|
||||
if (typeof newName === "string") {
|
||||
fileName = newName;
|
||||
} else {
|
||||
fileName = file.name;
|
||||
}
|
||||
|
||||
var extra_opts = { file: files[e.target.index],
|
||||
index: e.target.index };
|
||||
|
||||
do_xhr(fileName, fileData, file.type, extra_opts, "", finished_callback, on_error);
|
||||
opts.uploadStarted(index, file, files_count);
|
||||
};
|
||||
|
||||
// Initiate the processing loop
|
||||
process();
|
||||
}
|
||||
|
||||
function getIndexBySize(size) {
|
||||
for (var i = 0; i < files_count; i++) {
|
||||
if (files[i].size === size) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function rename(name) {
|
||||
return opts.rename(name);
|
||||
}
|
||||
|
||||
function beforeEach(file) {
|
||||
return opts.beforeEach(file);
|
||||
}
|
||||
|
||||
function afterAll() {
|
||||
return opts.afterAll();
|
||||
}
|
||||
|
||||
function dragEnter(e) {
|
||||
clearTimeout(doc_leave_timer);
|
||||
e.preventDefault();
|
||||
opts.dragEnter.call(this, e);
|
||||
}
|
||||
|
||||
function dragOver(e) {
|
||||
clearTimeout(doc_leave_timer);
|
||||
e.preventDefault();
|
||||
opts.docOver.call(this, e);
|
||||
opts.dragOver.call(this, e);
|
||||
}
|
||||
|
||||
function dragLeave(e) {
|
||||
clearTimeout(doc_leave_timer);
|
||||
opts.dragLeave.call(this, e);
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
function docDrop(e) {
|
||||
e.preventDefault();
|
||||
opts.docLeave.call(this, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
function docEnter(e) {
|
||||
clearTimeout(doc_leave_timer);
|
||||
e.preventDefault();
|
||||
opts.docEnter.call(this, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
function docOver(e) {
|
||||
clearTimeout(doc_leave_timer);
|
||||
e.preventDefault();
|
||||
opts.docOver.call(this, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
function docLeave(e) {
|
||||
doc_leave_timer = setTimeout((function(_this) {
|
||||
return function() {
|
||||
opts.docLeave.call(_this, e);
|
||||
};
|
||||
})(this), 200);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
function empty() {}
|
||||
|
||||
try {
|
||||
if (XMLHttpRequest.prototype.sendAsBinary) {
|
||||
return;
|
||||
}
|
||||
XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
|
||||
function byteValue(x) {
|
||||
return x.charCodeAt(0) & 0xff;
|
||||
}
|
||||
var ords = Array.prototype.map.call(datastr, byteValue);
|
||||
var ui8a = new Uint8Array(ords);
|
||||
this.send(ui8a.buffer);
|
||||
};
|
||||
} catch (e) {}
|
||||
|
||||
})(jQuery);
|
|
@ -154,7 +154,6 @@ EXEMPT_FILES = {
|
|||
'static/js/ui_util.js',
|
||||
'static/js/unread_ops.js',
|
||||
'static/js/unread_ui.js',
|
||||
'static/js/upload.js',
|
||||
'static/js/upload_widget.js',
|
||||
'static/js/user_status_ui.js',
|
||||
'static/js/zcommand.js',
|
||||
|
|
|
@ -26,4 +26,4 @@ LATEST_RELEASE_ANNOUNCEMENT = "https://blog.zulip.org/2019/12/13/zulip-2-1-relea
|
|||
# historical commits sharing the same major version, in which case a
|
||||
# minor version bump suffices.
|
||||
|
||||
PROVISION_VERSION = '73.0'
|
||||
PROVISION_VERSION = '73.1'
|
||||
|
|
76
yarn.lock
76
yarn.lock
|
@ -1247,6 +1247,55 @@
|
|||
semver "^6.3.0"
|
||||
tsutils "^3.17.1"
|
||||
|
||||
"@uppy/companion-client@^1.4.1":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@uppy/companion-client/-/companion-client-1.4.1.tgz#138032c145ef0961f7f3a047b36b593d6fc772d2"
|
||||
integrity sha512-ZQpEibQMDRwCzp3zugRHlCl/ne7UpCF+4ZfayhspGt7nz8tuUZXuDH15LhyMS06Y9S/kXTRMrA/w5bY42QtHDw==
|
||||
dependencies:
|
||||
namespace-emitter "^2.0.1"
|
||||
|
||||
"@uppy/core@^1.7.1":
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@uppy/core/-/core-1.7.1.tgz#cfedd5787a8d5feca604230732617fd9ca454362"
|
||||
integrity sha512-6ZT3gGMp74lAf+y2VYBWaMZvpo5CSh5HQJW3R2rUcBD9VV+IDyX+Kbt86vGL9bOBOpSCl4BOzZqfLe7kT7nrMg==
|
||||
dependencies:
|
||||
"@uppy/store-default" "^1.2.0"
|
||||
"@uppy/utils" "^2.1.2"
|
||||
cuid "^2.1.1"
|
||||
lodash.throttle "^4.1.1"
|
||||
mime-match "^1.0.2"
|
||||
namespace-emitter "^2.0.1"
|
||||
preact "8.2.9"
|
||||
|
||||
"@uppy/progress-bar@^1.3.4":
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@uppy/progress-bar/-/progress-bar-1.3.4.tgz#b818f18cf11db5e06fb9edb8ba86cc9b99855cc8"
|
||||
integrity sha512-gpVNpVpUjT7eCVrmK0Z6lKRH5hfAyiwcoT+L6EOoVPW2XnNEKG5jcxIJYqeiSD6OCJoptK4H3fxvzwcQWdNEIA==
|
||||
dependencies:
|
||||
"@uppy/utils" "^2.1.2"
|
||||
preact "8.2.9"
|
||||
|
||||
"@uppy/store-default@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@uppy/store-default/-/store-default-1.2.0.tgz#4007b84e6eef24b3f07b0fe5457548386cea77d9"
|
||||
integrity sha512-mnkxdX4DJMP2nrBklh5MXdn31bAyBSlCcp4+BZanFwv4WiCEpg/ruYzNzaJ1nVyuINJEDj2nx/DWxo+1F6WuWw==
|
||||
|
||||
"@uppy/utils@^2.1.2":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@uppy/utils/-/utils-2.1.2.tgz#979570c895d2d51a6afd329714714fab1957bbba"
|
||||
integrity sha512-lMcqHQq1KLm3NcmjOVoVIBuyfw7P8qp2/g8LwvFgAbvCMOYh5DbZWJ7W1FrLFpyAQ4FxXHCbah0m+ZChBC2nzg==
|
||||
dependencies:
|
||||
lodash.throttle "^4.1.1"
|
||||
|
||||
"@uppy/xhr-upload@^1.4.2":
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@uppy/xhr-upload/-/xhr-upload-1.4.2.tgz#d14485f76c897ba6cd99e0238bc82547bb536961"
|
||||
integrity sha512-32vqhODv4RVJZ1J6El/iFYa7sY27oubEuT2k1vsuqbsRRXSBHeraqxRyn10RoFuAB+aUmG1u9281LR7Y5YA0Pg==
|
||||
dependencies:
|
||||
"@uppy/companion-client" "^1.4.1"
|
||||
"@uppy/utils" "^2.1.2"
|
||||
cuid "^2.1.1"
|
||||
|
||||
"@vusion/webfonts-generator@^0.6.0":
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vusion/webfonts-generator/-/webfonts-generator-0.6.0.tgz#fb904121bcdd4bf31e0980742fc822b2f6cc0c23"
|
||||
|
@ -3342,6 +3391,11 @@ cubic2quad@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/cubic2quad/-/cubic2quad-1.1.1.tgz#69b19c61a3f5b41ecf2f1d5fae8fb03415aa8b15"
|
||||
integrity sha1-abGcYaP1tB7PLx1fro+wNBWqixU=
|
||||
|
||||
cuid@^2.1.1:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/cuid/-/cuid-2.1.8.tgz#cbb88f954171e0d5747606c0139fb65c5101eac0"
|
||||
integrity sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==
|
||||
|
||||
cwise-compiler@^1.0.0, cwise-compiler@^1.1.1, cwise-compiler@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/cwise-compiler/-/cwise-compiler-1.1.3.tgz#f4d667410e850d3a313a7d2db7b1e505bb034cc5"
|
||||
|
@ -7380,6 +7434,13 @@ mime-db@1.43.0, "mime-db@>= 1.43.0 < 2":
|
|||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
|
||||
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
|
||||
|
||||
mime-match@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/mime-match/-/mime-match-1.0.2.tgz#3f87c31e9af1a5fd485fb9db134428b23bbb7ba8"
|
||||
integrity sha1-P4fDHprxpf1IX7nbE0Qosju7e6g=
|
||||
dependencies:
|
||||
wildcard "^1.1.0"
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
version "2.1.26"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
|
||||
|
@ -7648,6 +7709,11 @@ mute-stream@0.0.8:
|
|||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
|
||||
|
||||
namespace-emitter@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz#978d51361c61313b4e6b8cf6f3853d08dfa2b17c"
|
||||
integrity sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==
|
||||
|
||||
nan@^2.10.0, nan@^2.12.1:
|
||||
version "2.14.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
||||
|
@ -9239,6 +9305,11 @@ potpack@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.1.tgz#d1b1afd89e4c8f7762865ec30bd112ab767e2ebf"
|
||||
integrity sha512-15vItUAbViaYrmaB/Pbw7z6qX2xENbFSTA7Ii4tgbPtasxm5v6ryKhKtL91tpWovDJzTiZqdwzhcFBCwiMVdVw==
|
||||
|
||||
preact@8.2.9:
|
||||
version "8.2.9"
|
||||
resolved "https://registry.yarnpkg.com/preact/-/preact-8.2.9.tgz#813ba9dd45e5d97c5ea0d6c86d375b3be711cc40"
|
||||
integrity sha512-ThuGXBmJS3VsT+jIP+eQufD3L8pRw/PY3FoCys6O9Pu6aF12Pn9zAJDX99TfwRAFOCEKm/P0lwiPTbqKMJp0fA==
|
||||
|
||||
prelude-ls@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
|
@ -12617,6 +12688,11 @@ wide-align@^1.1.0:
|
|||
dependencies:
|
||||
string-width "^1.0.2 || 2"
|
||||
|
||||
wildcard@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-1.1.2.tgz#a7020453084d8cd2efe70ba9d3696263de1710a5"
|
||||
integrity sha1-pwIEUwhNjNLv5wup02liY94XEKU=
|
||||
|
||||
winchan@^0.2.1:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/winchan/-/winchan-0.2.2.tgz#6766917b88e5e1cb75f455ffc7cc13f51e5c834e"
|
||||
|
|
Loading…
Reference in New Issue