diff --git a/.eslintrc.json b/.eslintrc.json index 793cef2f02..42bc9161b8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -150,6 +150,7 @@ "recent_senders": false, "unread_ui": false, "unread_ops": false, + "upload": false, "user_events": false, "Plotly": false, "emoji_codes": false, diff --git a/frontend_tests/node_tests/compose.js b/frontend_tests/node_tests/compose.js index 8a2ad8d691..aec44c8f5c 100644 --- a/frontend_tests/node_tests/compose.js +++ b/frontend_tests/node_tests/compose.js @@ -52,6 +52,7 @@ zrequire('stream_data'); zrequire('compose_state'); zrequire('people'); zrequire('compose'); +zrequire('upload'); page_params.use_websockets = false; var me = { @@ -1296,7 +1297,7 @@ function test_with_mock_socket(test_params) { return 'fake-html'; }; - compose.uploadStarted(); + upload.uploadStarted(); assert.equal($("#compose-send-button").attr("disabled"), ''); assert($("#send-status").hasClass("alert-info")); @@ -1311,7 +1312,7 @@ function test_with_mock_socket(test_params) { assert.equal(width_percent, '39%'); width_update_checked = true; }; - compose.progressUpdated(1, '', 39); + upload.progressUpdated(1, '', 39); assert(width_update_checked); }()); @@ -1336,7 +1337,7 @@ function test_with_mock_socket(test_params) { function test(err, file, msg) { setup_test(); - compose.uploadError(err, file); + upload.uploadError(err, file); // The text function and html function in zjquery is not in sync // with each other. QuotaExceeded changes html while all other errors // changes body. @@ -1420,7 +1421,7 @@ function test_with_mock_socket(test_params) { } setup(); - compose.uploadFinished(i, {}, response); + upload.uploadFinished(i, {}, response); assert_side_effects(); } diff --git a/static/js/compose.js b/static/js/compose.js index b5c1134c10..36504e8588 100644 --- a/static/js/compose.js +++ b/static/js/compose.js @@ -19,113 +19,11 @@ exports.uploads_path = '/user_uploads'; exports.uploads_re = new RegExp("\\]\\(" + exports.uploads_domain + "(" + exports.uploads_path + "[^\\)]+)\\)", 'g'); exports.clone_file_input = undefined; -function make_upload_absolute(uri) { - if (uri.indexOf(exports.uploads_path) === 0) { - // Rewrite the URI to a usable link - return exports.uploads_domain + uri; - } - return uri; -} - function make_uploads_relative(content) { // Rewrite uploads in markdown links back to domain-relative form return content.replace(exports.uploads_re, "]($1)"); } -// This function resets an input type="file". Pass in the -// jquery object. -function clear_out_file_list(jq_file_list) { - if (exports.clone_file_input !== undefined) { - jq_file_list.replaceWith(exports.clone_file_input.clone(true)); - } - // Hack explanation: - // IE won't let you do this (untested, but so says StackOverflow): - // $("#file_input").val(""); -} - -exports.uploadStarted = function () { - $("#compose-send-button").attr("disabled", ""); - $("#send-status").addClass("alert-info") - .show(); - $(".send-status-close").one('click', exports.abort_xhr); - $("#error-msg").html( - $("

").text(i18n.t("Uploading…")) - .after('

' + - '
' + - '
')); -}; - -exports.progressUpdated = function (i, file, progress) { - $("#upload-bar").width(progress + "%"); -}; - -exports.uploadError = function (err, file) { - var msg; - $("#send-status").addClass("alert-error") - .removeClass("alert-info"); - $("#compose-send-button").prop("disabled", false); - switch (err) { - 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': - // sanitization not needed as the file name is not potentially parsed as HTML, etc. - var context = { file_name: file.name }; - msg = i18n.t('"__file_name__" was too large; the maximum file size is 25MiB.', context); - break; - case 'REQUEST ENTITY TOO LARGE': - msg = i18n.t("Sorry, the file was too large."); - break; - case 'QuotaExceeded': - var translation_part1 = i18n.t('Upload would exceed your maximum quota. You can delete old attachments to free up space.'); - var translation_part2 = i18n.t('Click here'); - msg = translation_part1 + ' ' + translation_part2 + ''; - $("#error-msg").html(msg); - return; - default: - msg = i18n.t("An unknown error occurred."); - break; - } - $("#error-msg").text(msg); -}; - -exports.uploadFinished = function (i, file, response) { - if (response.uri === undefined) { - return; - } - var textbox = $("#new_message_content"); - var split_uri = response.uri.split("/"); - var 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 (!compose_state.composing()) { - compose_actions.start('stream'); - } - - var uri = make_upload_absolute(response.uri); - - if (i === -1) { - // This is a paste, so there's no filename. Show the image directly - textbox.val(textbox.val() + "[pasted image](" + uri + ") "); - } else { - // This is a dropped file, so make the filename a link to the image - textbox.val(textbox.val() + "[" + filename + "](" + uri + ")" + " "); - } - compose_ui.autosize_textarea(); - $("#compose-send-button").prop("disabled", false); - $("#send-status").removeClass("alert-info") - .hide(); - - // 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. This is extracted to a function - // to abstract away some IE hacks. - clear_out_file_list($("#file_input")); -}; - function show_all_everyone_warnings() { var stream_count = stream_data.get_subscriber_count(compose_state.stream_name()) || 0; @@ -872,29 +770,7 @@ exports.initialize = function () { Dropbox.choose(options); }); - $("#compose").filedrop({ - url: "/json/user_uploads", - fallback_id: "file_input", - paramname: "file", - maxfilesize: page_params.maxfilesize, - data: { - // the token isn't automatically included in filedrop's post - csrfmiddlewaretoken: csrf_token, - }, - raw_droppable: ['text/uri-list', 'text/plain'], - drop: exports.uploadStarted, - progressUpdated: exports.progressUpdated, - error: exports.uploadError, - uploadFinished: exports.uploadFinished, - rawDrop: function (contents) { - var textbox = $("#new_message_content"); - if (!compose_state.composing()) { - compose_actions.start('stream'); - } - textbox.val(textbox.val() + contents); - compose_ui.autosize_textarea(); - }, - }); + upload.initialize(); if (page_params.narrow !== undefined) { if (page_params.narrow_topic !== undefined) { diff --git a/static/js/upload.js b/static/js/upload.js new file mode 100644 index 0000000000..72a5eba320 --- /dev/null +++ b/static/js/upload.js @@ -0,0 +1,138 @@ +var upload = (function () { + +var exports = {}; + +function make_upload_absolute(uri) { + if (uri.indexOf(compose.uploads_path) === 0) { + // Rewrite the URI to a usable link + return compose.uploads_domain + uri; + } + return uri; +} + +// This function resets an input type="file". Pass in the +// jquery object. +function clear_out_file_list(jq_file_list) { + if (compose.clone_file_input !== undefined) { + jq_file_list.replaceWith(compose.clone_file_input.clone(true)); + } + // Hack explanation: + // IE won't let you do this (untested, but so says StackOverflow): + // $("#file_input").val(""); +} + +exports.uploadStarted = function () { + $("#compose-send-button").attr("disabled", ""); + $("#send-status").addClass("alert-info").show(); + $(".send-status-close").one('click', compose.abort_xhr); + $("#error-msg").html($("

").text(i18n.t("Uploading…")) + .after('

' + + '
' + + '
')); +}; + +exports.progressUpdated = function (i, file, progress) { + $("#upload-bar").width(progress + "%"); +}; + +exports.uploadError = function (err, file) { + var msg; + $("#send-status").addClass("alert-error") + .removeClass("alert-info"); + $("#compose-send-button").prop("disabled", false); + switch (err) { + 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': + // sanitization not needed as the file name is not potentially parsed as HTML, etc. + var context = { + file_name: file.name, + }; + msg = i18n.t('"__file_name__" was too large; the maximum file size is 25MiB.', context); + break; + case 'REQUEST ENTITY TOO LARGE': + msg = i18n.t("Sorry, the file was too large."); + break; + case 'QuotaExceeded': + var translation_part1 = i18n.t('Upload would exceed your maximum quota. You can delete old attachments to free up space.'); + var translation_part2 = i18n.t('Click here'); + msg = translation_part1 + ' ' + translation_part2 + ''; + $("#error-msg").html(msg); + return; + default: + msg = i18n.t("An unknown error occurred."); + break; + } + $("#error-msg").text(msg); +}; + +exports.uploadFinished = function (i, file, response) { + if (response.uri === undefined) { + return; + } + var textbox = $("#new_message_content"); + var split_uri = response.uri.split("/"); + var 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 (!compose_state.composing()) { + compose_actions.start('stream'); + } + + var uri = make_upload_absolute(response.uri); + + if (i === -1) { + // This is a paste, so there's no filename. Show the image directly + textbox.val(textbox.val() + "[pasted image](" + uri + ") "); + } else { + // This is a dropped file, so make the filename a link to the image + textbox.val(textbox.val() + "[" + filename + "](" + uri + ")" + " "); + } + compose_ui.autosize_textarea(); + $("#compose-send-button").prop("disabled", false); + $("#send-status").removeClass("alert-info") + .hide(); + + // 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. This is extracted to a function + // to abstract away some IE hacks. + clear_out_file_list($("#file_input")); +}; + +exports.initialize = function () { + $("#compose").filedrop({ + url: "/json/user_uploads", + fallback_id: "file_input", + paramname: "file", + maxfilesize: page_params.maxfilesize, + data: { + // the token isn't automatically included in filedrop's post + csrfmiddlewaretoken: csrf_token, + }, + raw_droppable: ['text/uri-list', 'text/plain'], + drop: exports.uploadStarted, + progressUpdated: exports.progressUpdated, + error: exports.uploadError, + uploadFinished: exports.uploadFinished, + rawDrop: function (contents) { + var textbox = $("#new_message_content"); + if (!compose_state.composing()) { + compose_actions.start('stream'); + } + textbox.val(textbox.val() + contents); + compose_ui.autosize_textarea(); + }, + }); +}; + +return exports; +}()); + +if (typeof module !== 'undefined') { + module.exports = upload; +} diff --git a/tools/linter_lib/custom_check.py b/tools/linter_lib/custom_check.py index 66a5a65c4b..7d063d8ce8 100644 --- a/tools/linter_lib/custom_check.py +++ b/tools/linter_lib/custom_check.py @@ -236,7 +236,7 @@ def build_custom_checkers(by_lang): 'frontend_tests/node_tests/compose.js', 'frontend_tests/node_tests/copy_and_paste.js', 'frontend_tests/node_tests/templates.js', - 'static/js/compose.js', + 'static/js/upload.js', 'static/js/dynamic_text.js', 'static/js/stream_color.js', ]), diff --git a/zproject/settings.py b/zproject/settings.py index 73e373b285..c7bcbfe387 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -977,6 +977,7 @@ JS_SPECS = { 'js/compose_state.js', 'js/compose_actions.js', 'js/compose.js', + 'js/upload.js', 'js/stream_color.js', 'js/stream_data.js', 'js/topic_data.js',