diff --git a/.eslintrc.json b/.eslintrc.json index 03bacc0839..94ebfbc832 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -49,6 +49,7 @@ "compose_ui": false, "composebox_typeahead": false, "condense": false, + "confirm_dialog": false, "copy_and_paste": false, "csrf_token": false, "current_msg_list": true, diff --git a/static/js/bundles/app.js b/static/js/bundles/app.js index f87269688b..95d2c33e2e 100644 --- a/static/js/bundles/app.js +++ b/static/js/bundles/app.js @@ -164,6 +164,7 @@ import "js/upload_widget.js"; import "js/avatar.js"; import "js/realm_icon.js"; import 'js/reminder.js'; +import 'js/confirm_dialog.js'; import "js/settings_account.js"; import "js/settings_display.js"; import "js/settings_notifications.js"; diff --git a/static/js/confirm_dialog.js b/static/js/confirm_dialog.js new file mode 100644 index 0000000000..ac9ceab044 --- /dev/null +++ b/static/js/confirm_dialog.js @@ -0,0 +1,86 @@ +var confirm_dialog = (function () { + +var exports = {}; + +/* + Look for confirm_dialog in settings_user_groups + to see an example of how to use this widget. It's + pretty simple to use! + + Some things to note: + + 1) We create DOM on the fly, and we remove + the DOM once it's closed. + + 2) We attach the DOM for the modal to conf.parent, + and this temporary DOM location will influence + how styles work. + + 3) The cancel button is driven by bootstrap.js. + + 4) For settings, we have a click handler in settings.js + that will close the dialog via overlays.close_active_modal. + + 5) We assume that since this is a modal, you will + only ever have one confirm dialog active at any + time. + +*/ + +exports.launch = function (conf) { + var html = templates.render("confirm_dialog"); + var confirm_dialog = $(html); + + var conf_fields = [ + // The next three fields should be safe HTML. If callers + // interpolate user data into strings, they should use + // templates. + 'html_heading', + 'html_body', + 'html_yes_button', + 'on_click', + 'parent', + ]; + + _.each(conf_fields, function (f) { + if (!conf[f]) { + blueslip.error('programmer omitted ' + f); + } + }); + + conf.parent.append(confirm_dialog); + + // Close any existing modals--on settings screens you can + // have multiple buttons that need confirmation. + if (overlays.is_modal_open()) { + overlays.close_modal('confirm_dialog_modal'); + } + + confirm_dialog.find('.confirm_dialog_heading').html(conf.html_heading); + confirm_dialog.find('.confirm_dialog_body').html(conf.html_body); + + var yes_button = confirm_dialog.find('.confirm_dialog_yes_button'); + + yes_button.html(conf.html_yes_button); + + // Set up handlers. + yes_button.on('click', function () { + overlays.close_modal('confirm_dialog_modal'); + conf.on_click(); + }); + + confirm_dialog.on('hide', function () { + confirm_dialog.remove(); + }); + + // Open the modal + overlays.open_modal('confirm_dialog_modal'); +}; + +return exports; +}()); + +if (typeof module !== 'undefined') { + module.exports = confirm_dialog; +} +window.confirm_dialog = confirm_dialog; diff --git a/static/styles/settings.scss b/static/styles/settings.scss index 87a09fa5aa..8c42e77436 100644 --- a/static/styles/settings.scss +++ b/static/styles/settings.scss @@ -1446,6 +1446,7 @@ body:not(.night-mode) #account-settings .custom_user_field .datepicker { margin: 10px; } +#confirm_dialog_modal, #deactivation_user_modal.fade.in { top: calc(50% - 120px); } diff --git a/static/templates/confirm_dialog.handlebars b/static/templates/confirm_dialog.handlebars new file mode 100644 index 0000000000..ec92aa05c7 --- /dev/null +++ b/static/templates/confirm_dialog.handlebars @@ -0,0 +1,12 @@ + diff --git a/tools/linter_lib/custom_check.py b/tools/linter_lib/custom_check.py index db373f0d01..75d44c665e 100644 --- a/tools/linter_lib/custom_check.py +++ b/tools/linter_lib/custom_check.py @@ -194,6 +194,7 @@ def build_custom_checkers(by_lang): {'pattern': '[.]html[(]', 'exclude_pattern': '[.]html[(]("|\'|templates|html|message.content|sub.rendered_description|i18n.t|rendered_|$|[)]|error_text|widget_elem|[$]error|[$][(]"

"[)])', 'exclude': ['static/js/portico', 'static/js/lightbox.js', 'static/js/ui_report.js', + 'static/js/confirm_dialog.js', 'frontend_tests/'], 'description': 'Setting HTML content with jQuery .html() can lead to XSS security bugs. Consider .text() or using rendered_foo as a variable name if content comes from handlebars and thus is already sanitized.'}, {'pattern': '["\']json/',