diff --git a/frontend_tests/puppeteer_tests/settings.ts b/frontend_tests/puppeteer_tests/settings.ts index fc6a97f173..919583f554 100644 --- a/frontend_tests/puppeteer_tests/settings.ts +++ b/frontend_tests/puppeteer_tests/settings.ts @@ -176,7 +176,7 @@ async function test_edit_bot_form(page: Page): Promise { ); await common.fill_form(page, edit_form_selector, {bot_name: "Bot one"}); - const save_btn_selector = edit_form_selector + " .edit_bot_button"; + const save_btn_selector = "#edit_bot_modal .dialog_submit_button"; await page.click(save_btn_selector); // The form gets closed on saving. So, assert it's closed by waiting for it to be hidden. @@ -186,7 +186,7 @@ async function test_edit_bot_form(page: Page): Promise { `//*[@class="btn open_edit_bot_form" and @data-email="${bot1_email}"]/ancestor::*[@class="details"]/*[@class="name" and text()="Bot one"]`, ); - await common.wait_for_modal_to_close(page); + await common.wait_for_micromodal_to_close(page); } async function test_invalid_edit_bot_form(page: Page): Promise { @@ -203,7 +203,7 @@ async function test_invalid_edit_bot_form(page: Page): Promise { ); await common.fill_form(page, edit_form_selector, {bot_name: "Bot 2"}); - const save_btn_selector = edit_form_selector + " .edit_bot_button"; + const save_btn_selector = "#edit_bot_modal .dialog_submit_button"; await page.click(save_btn_selector); // The form should not get closed on saving. Errors should be visible on the form. @@ -213,14 +213,14 @@ async function test_invalid_edit_bot_form(page: Page): Promise { await common.get_text_from_selector(page, "div.bot_edit_errors"), "Name is already in use!", ); - await page.click("button.cancel_bot_button"); + await page.click("#edit_bot_modal .dialog_cancel_button"); await page.waitForSelector("#edit_bot_modal", {hidden: true}); await page.waitForXPath( `//*[@class="btn open_edit_bot_form" and @data-email="${bot1_email}"]/ancestor::*[@class="details"]/*[@class="name" and text()="Bot one"]`, ); - await common.wait_for_modal_to_close(page); + await common.wait_for_micromodal_to_close(page); } async function test_your_bots_section(page: Page): Promise { diff --git a/static/js/settings_bots.js b/static/js/settings_bots.js index ac059a0058..32610272cc 100644 --- a/static/js/settings_bots.js +++ b/static/js/settings_bots.js @@ -10,10 +10,10 @@ import * as avatar from "./avatar"; import * as bot_data from "./bot_data"; import * as channel from "./channel"; import {csrf_token} from "./csrf"; +import * as dialog_widget from "./dialog_widget"; import {DropdownListWidget} from "./dropdown_list_widget"; -import {$t} from "./i18n"; +import {$t, $t_html} from "./i18n"; import * as loading from "./loading"; -import * as overlays from "./overlays"; import {page_params} from "./page_params"; import * as people from "./people"; @@ -412,7 +412,6 @@ export function set_up() { $("#active_bots_list").on("click", "button.open_edit_bot_form", (e) => { e.preventDefault(); e.stopPropagation(); - overlays.open_modal("#edit_bot_modal"); const li = $(e.currentTarget).closest("li"); const bot_id = Number.parseInt(li.find(".bot_info").attr("data-user-id"), 10); const bot = bot_data.get(bot_id); @@ -422,112 +421,121 @@ export function set_up() { value: user_id.toString(), })); - $("#edit_bot_modal").empty(); - $("#edit_bot_modal").append( - render_edit_bot({ - bot, - users_list, - }), - ); - const avatar_widget = avatar.build_bot_edit_widget($("#settings_page")); - const form = $("#settings_page .edit_bot_form"); - const image = li.find(".image"); - const errors = form.find(".bot_edit_errors"); - - const opts = { - widget_name: "bot_owner", - data: users_list, - default_text: $t({defaultMessage: "No owner"}), - value: bot.owner_id, - }; - const owner_widget = new DropdownListWidget(opts); - - const service = bot_data.get_services(bot_id)[0]; - if (bot.bot_type.toString() === OUTGOING_WEBHOOK_BOT_TYPE) { - $("#service_data").append( - render_settings_edit_outgoing_webhook_service({ - service, - }), - ); - $("#edit_service_interface").val(service.interface); - } - if (bot.bot_type.toString() === EMBEDDED_BOT_TYPE) { - $("#service_data").append( - render_settings_edit_embedded_bot_service({ - service, - }), - ); - } - - avatar_widget.clear(); - - form.validate({ - errorClass: "text-error", - success() { - errors.hide(); - }, - submitHandler() { - const bot_id = Number.parseInt(form.attr("data-user-id"), 10); - const type = form.attr("data-type"); - - const full_name = form.find(".edit_bot_name").val(); - const bot_owner_id = owner_widget.value(); - const file_input = $(".edit_bot_form").find(".edit_bot_avatar_file_input"); - const spinner = form.find(".edit_bot_spinner"); - const edit_button = form.find(".edit_bot_button"); - - const formData = new FormData(); - formData.append("csrfmiddlewaretoken", csrf_token); - formData.append("full_name", full_name); - formData.append("bot_owner_id", bot_owner_id); - - if (type === OUTGOING_WEBHOOK_BOT_TYPE) { - const service_payload_url = $("#edit_service_base_url").val(); - const service_interface = $("#edit_service_interface :selected").val(); - formData.append("service_payload_url", JSON.stringify(service_payload_url)); - formData.append("service_interface", service_interface); - } else if (type === EMBEDDED_BOT_TYPE) { - const config_data = {}; - $("#config_edit_inputbox input").each(function () { - config_data[$(this).attr("name")] = $(this).val(); - }); - formData.append("config_data", JSON.stringify(config_data)); - } - for (const [i, file] of Array.prototype.entries.call(file_input[0].files)) { - formData.append("file-" + i, file); - } - loading.make_indicator(spinner, {text: "Editing bot"}); - edit_button.hide(); - channel.patch({ - url: "/json/bots/" + encodeURIComponent(bot_id), - data: formData, - cache: false, - processData: false, - contentType: false, - success(data) { - loading.destroy_indicator(spinner); - errors.hide(); - edit_button.show(); - avatar_widget.clear(); - if (data.avatar_url) { - // Note that the avatar_url won't actually change on the backend - // when the user had a previous uploaded avatar. Only the content - // changes, so we version it to get an uncached copy. - image_version += 1; - image - .find("img") - .attr("src", data.avatar_url + "&v=" + image_version.toString()); - } - overlays.close_modal("#edit_bot_modal"); - }, - error(xhr) { - loading.destroy_indicator(spinner); - edit_button.show(); - errors.text(JSON.parse(xhr.responseText).msg).show(); - }, - }); - }, + const rendered_edit_bot = render_edit_bot({ + bot, + users_list, }); + dialog_widget.launch({ + html_heading: $t_html({defaultMessage: "Edit bot"}), + html_body: rendered_edit_bot, + id: "edit_bot_modal", + on_click: () => { + $(".edit_bot_form").trigger("submit"); + }, + post_render: edit_bot_post_render, + loading_spinner: true, + }); + + function edit_bot_post_render() { + // This links the submit button to the edit bot form, so submitting this form + // by pressing Enter on an input element triggers a click on the submit button. + $("#edit_bot_modal .dialog_submit_button").attr("form", "edit_bot_form"); + + const avatar_widget = avatar.build_bot_edit_widget($(".edit_bot_form")); + const form = $(".edit_bot_form"); + const image = li.find(".image"); + const errors = form.find(".bot_edit_errors"); + + const opts = { + widget_name: "bot_owner", + data: users_list, + default_text: $t({defaultMessage: "No owner"}), + value: bot.owner_id, + }; + const owner_widget = new DropdownListWidget(opts); + + const service = bot_data.get_services(bot_id)[0]; + if (bot.bot_type.toString() === OUTGOING_WEBHOOK_BOT_TYPE) { + $("#service_data").append( + render_settings_edit_outgoing_webhook_service({ + service, + }), + ); + $("#edit_service_interface").val(service.interface); + } + if (bot.bot_type.toString() === EMBEDDED_BOT_TYPE) { + $("#service_data").append( + render_settings_edit_embedded_bot_service({ + service, + }), + ); + } + + avatar_widget.clear(); + + form.validate({ + errorClass: "text-error", + success() { + errors.hide(); + }, + submitHandler() { + const bot_id = Number.parseInt(form.attr("data-user-id"), 10); + const type = form.attr("data-type"); + + const full_name = form.find(".edit_bot_name").val(); + const bot_owner_id = owner_widget.value(); + const file_input = $(".edit_bot_form").find(".edit_bot_avatar_file_input"); + + const formData = new FormData(); + formData.append("csrfmiddlewaretoken", csrf_token); + formData.append("full_name", full_name); + formData.append("bot_owner_id", bot_owner_id); + + if (type === OUTGOING_WEBHOOK_BOT_TYPE) { + const service_payload_url = $("#edit_service_base_url").val(); + const service_interface = $("#edit_service_interface :selected").val(); + formData.append("service_payload_url", JSON.stringify(service_payload_url)); + formData.append("service_interface", service_interface); + } else if (type === EMBEDDED_BOT_TYPE) { + const config_data = {}; + $("#config_edit_inputbox input").each(function () { + config_data[$(this).attr("name")] = $(this).val(); + }); + formData.append("config_data", JSON.stringify(config_data)); + } + for (const [i, file] of Array.prototype.entries.call(file_input[0].files)) { + formData.append("file-" + i, file); + } + channel.patch({ + url: "/json/bots/" + encodeURIComponent(bot_id), + data: formData, + cache: false, + processData: false, + contentType: false, + success(data) { + avatar_widget.clear(); + if (data.avatar_url) { + // Note that the avatar_url won't actually change on the backend + // when the user had a previous uploaded avatar. Only the content + // changes, so we version it to get an uncached copy. + image_version += 1; + image + .find("img") + .attr( + "src", + data.avatar_url + "&v=" + image_version.toString(), + ); + } + dialog_widget.close_modal(); + }, + error(xhr) { + dialog_widget.hide_dialog_spinner(); + errors.text(JSON.parse(xhr.responseText).msg).show(); + }, + }); + }, + }); + } }); $("#active_bots_list").on("click", "a.download_bot_zuliprc", function () { diff --git a/static/styles/settings.css b/static/styles/settings.css index 1f86b41610..6f48b3cfb1 100644 --- a/static/styles/settings.css +++ b/static/styles/settings.css @@ -871,7 +871,7 @@ input[type="checkbox"] { } .bot_edit_errors { - margin: 20px 20px 0; + margin: 0; } #bots_lists_navbar .active a { @@ -882,8 +882,11 @@ input[type="checkbox"] { margin-top: 5px; } -.edit_bot_form_container { - padding: 15px; +#edit_bot_modal { + /* Ensure that the dropdown can overflow the modal. */ + .modal__content { + overflow: visible; + } } .edit_bot_form { diff --git a/static/templates/settings/edit_bot.hbs b/static/templates/settings/edit_bot.hbs index f8629d6d30..bf4336a95f 100644 --- a/static/templates/settings/edit_bot.hbs +++ b/static/templates/settings/edit_bot.hbs @@ -1,12 +1,8 @@ - -
-
-
+
+
{{bot.email}}
@@ -35,11 +31,4 @@
-
- -
diff --git a/static/templates/settings_overlay.hbs b/static/templates/settings_overlay.hbs index a0ceb8c748..b168b35b5d 100644 --- a/static/templates/settings_overlay.hbs +++ b/static/templates/settings_overlay.hbs @@ -197,8 +197,5 @@
-