From ddaff4cd2ae8f32c2807a340edac7bb6bdebbbe2 Mon Sep 17 00:00:00 2001 From: Tommy Ip Date: Thu, 23 Nov 2017 14:00:05 +0000 Subject: [PATCH] refactor: Extract upload mechanics to new JS module. Tweaked by tabbott to move changes from the next commit that are required for this to pass tests into this commit. Note that this exports a few items that were not previously exported. --- .eslintrc.json | 1 + frontend_tests/node_tests/compose.js | 9 +- static/js/compose.js | 126 +----------------------- static/js/upload.js | 138 +++++++++++++++++++++++++++ tools/linter_lib/custom_check.py | 2 +- zproject/settings.py | 1 + 6 files changed, 147 insertions(+), 130 deletions(-) create mode 100644 static/js/upload.js 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',