diff --git a/docs/THIRDPARTY b/docs/THIRDPARTY index 4d74968269..248dbc3908 100644 --- a/docs/THIRDPARTY +++ b/docs/THIRDPARTY @@ -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 diff --git a/frontend_tests/node_tests/compose.js b/frontend_tests/node_tests/compose.js index cf6e9a234c..daddd2a02a 100644 --- a/frontend_tests/node_tests/compose.js +++ b/frontend_tests/node_tests/compose.js @@ -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', () => { diff --git a/frontend_tests/node_tests/compose_actions.js b/frontend_tests/node_tests/compose_actions.js index 13dac8319e..ae23fb63d3 100644 --- a/frontend_tests/node_tests/compose_actions.js +++ b/frontend_tests/node_tests/compose_actions.js @@ -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'); diff --git a/frontend_tests/node_tests/ui_init.js b/frontend_tests/node_tests/ui_init.js index bbb1e2c14e..edb3f179ed 100644 --- a/frontend_tests/node_tests/ui_init.js +++ b/frontend_tests/node_tests/ui_init.js @@ -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; diff --git a/frontend_tests/node_tests/upload.js b/frontend_tests/node_tests/upload.js index 75944f1daa..926459d9a5 100644 --- a/frontend_tests/node_tests/upload.js +++ b/frontend_tests/node_tests/upload.js @@ -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 = '
").text(), 'translated: Uploading…'); + assert(compose_ui_insert_syntax_and_focus_called); + assert(compose_ui_autosize_textarea_called); + assert(uppy_add_file_called); + + 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); }); -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; +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, + }, }; - upload_opts.progressUpdated(1, {trackingId: "1549958107000"}, 39); - assert(width_update_checked); + 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('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(''); +run_test('file_drop', () => { + set_global('$', global.make_zjquery()); - $("#compose-upload-bar-1549958107000").parent = function () { - return { remove: function () {} }; + 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; + }; + + paste_handler(event); + assert(get_as_file_called); + assert(upload_files_called); + + 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); - 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); - } + 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); - function test(err, msg, server_response = null, file = {}) { - setup_test(); - file.trackingId = "1549958107000"; - upload_opts.error(err, server_response, file); - assert_side_effects(msg); - } + 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 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.'; + 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); - 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); + 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); - // 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) { - func(); - }); - - $("#compose-upload-bar-1549958107000").width = function (width_percent) { - assert.equal(width_percent, '100%'); - }; - - setup(); - upload_opts.uploadFinished(i, { - trackingId: "1549958107000", - name: 'some-file', - }, response); - upload_opts.progressUpdated(1, {trackingId: "1549958107000"}, 100); - assert_side_effects(); - } - - 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); + 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); }); diff --git a/package.json b/package.json index 8afa51e41d..76d7f78821 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/static/js/bundles/app.js b/static/js/bundles/app.js index d5e4fcbb40..761d14a727 100644 --- a/static/js/bundles/app.js +++ b/static/js/bundles/app.js @@ -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"; diff --git a/static/js/bundles/common.js b/static/js/bundles/common.js index fca9df10f2..1d68200dde 100644 --- a/static/js/bundles/common.js +++ b/static/js/bundles/common.js @@ -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"; diff --git a/static/js/compose.js b/static/js/compose.js index 30a374fabd..774b084d1c 100644 --- a/static/js/compose.js +++ b/static/js/compose.js @@ -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 = { diff --git a/static/js/copy_and_paste.js b/static/js/copy_and_paste.js index c2562e0584..e1e7c7494f 100644 --- a/static/js/copy_and_paste.js +++ b/static/js/copy_and_paste.js @@ -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(); diff --git a/static/js/message_edit.js b/static/js/message_edit.js index 74ad618aaf..47e7b51721 100644 --- a/static/js/message_edit.js +++ b/static/js/message_edit.js @@ -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({ - mode: 'edit', - row: rows.id(row), - }) - ); + upload.setup_upload({ + mode: 'edit', + row: rows.id(row), + }); } exports.start = function (row, edit_box_open_callback) { diff --git a/static/js/upload.js b/static/js/upload.js index 2d30894ba2..12019fb9e4 100644 --- a/static/js/upload.js +++ b/static/js/upload.js @@ -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; - default: +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(); - }); - }; +exports.show_error_message = function (config, message) { + if (!message) { + message = i18n.t("An unknown error occurred."); + } + 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 uploadStarted = function (i, file) { - error_msg.html($("
").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.upload_files = function (uppy, config, files) { + if (files.length === 0) { + uppy.cancelAll(); + exports.hide_upload_status(config); + return; + } + 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; + } + 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($("
").text(i18n.t("Uploading…"))); + exports.get_item("send_status_close_button", config).one('click', function () { + uppy.cancelAll(); + setTimeout(function () { + exports.hide_upload_status(config); + }, 500); + }); + + 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. } - send_status.append('