From 7f17fc020ffc18f074cf6cadcbd0e74b180f985d Mon Sep 17 00:00:00 2001 From: Steve Howell Date: Thu, 6 Apr 2017 13:47:27 -0700 Subject: [PATCH] Extract settings_bots.js. --- .eslintrc.json | 1 + .../{settings.js => settings_bots.js} | 10 +- static/js/settings.js | 335 +---------------- static/js/settings_bots.js | 347 ++++++++++++++++++ zproject/settings.py | 1 + 5 files changed, 353 insertions(+), 341 deletions(-) rename frontend_tests/node_tests/{settings.js => settings_bots.js} (74%) create mode 100644 static/js/settings_bots.js diff --git a/.eslintrc.json b/.eslintrc.json index 8c761025e6..77f92b4a86 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -37,6 +37,7 @@ "settings_notifications": false, "settings_muting": false, "settings_lab": false, + "settings_bots": false, "settings": false, "resize": false, "loading": false, diff --git a/frontend_tests/node_tests/settings.js b/frontend_tests/node_tests/settings_bots.js similarity index 74% rename from frontend_tests/node_tests/settings.js rename to frontend_tests/node_tests/settings_bots.js index 1826880ba7..f8fb456648 100644 --- a/frontend_tests/node_tests/settings.js +++ b/frontend_tests/node_tests/settings_bots.js @@ -1,19 +1,15 @@ -var jsdom = require("jsdom"); -var window = jsdom.jsdom().defaultView; -global.$ = require("jquery")(window); - set_global("page_params", { realm_uri: "https://chat.example.com", }); -var settings = require("js/settings.js"); +var settings_bots = require("js/settings_bots.js"); (function test_generate_zuliprc_uri() { var bot = { email: "error-bot@zulip.org", api_key: "QadL788EkiottHmukyhHgePUFHREiu8b", }; - var uri = settings.generate_zuliprc_uri(bot.email, bot.api_key); + var uri = settings_bots.generate_zuliprc_uri(bot.email, bot.api_key); var expected = "data:application/octet-stream;charset=utf-8," + encodeURIComponent( "[api]\nemail=error-bot@zulip.org\n" + "key=QadL788EkiottHmukyhHgePUFHREiu8b\n" + @@ -28,7 +24,7 @@ var settings = require("js/settings.js"); email: "admin12@chatting.net", api_key: "nSlA0mUm7G42LP85lMv7syqFTzDE2q34", }; - var content = settings.generate_zuliprc_content(user.email, user.api_key); + var content = settings_bots.generate_zuliprc_content(user.email, user.api_key); var expected = "[api]\nemail=admin12@chatting.net\n" + "key=nSlA0mUm7G42LP85lMv7syqFTzDE2q34\n" + "site=https://chat.example.com\n"; diff --git a/static/js/settings.js b/static/js/settings.js index 8d09ec9065..f90f69b7a0 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -2,60 +2,6 @@ var settings = (function () { var exports = {}; -function add_bot_row(info) { - info.id_suffix = _.uniqueId('_bot_'); - var row = $(templates.render('bot_avatar_row', info)); - if (info.is_active) { - $('#active_bots_list').append(row); - } else { - $('#inactive_bots_list').append(row); - } -} - -function is_local_part(value, element) { - // Adapted from Django's EmailValidator - return this.optional(element) || /^[\-!#$%&'*+\/=?\^_`{}|~0-9A-Z]+(\.[\-!#$%&'*+\/=?\^_`{}|~0-9A-Z]+)*$/i.test(value); -} - -function render_bots() { - $('#active_bots_list').empty(); - $('#inactive_bots_list').empty(); - - _.each(bot_data.get_all_bots_for_current_user(), function (elem) { - add_bot_row({ - name: elem.full_name, - email: elem.email, - avatar_url: elem.avatar_url, - api_key: elem.api_key, - is_active: elem.is_active, - zuliprc: 'zuliprc', // Most browsers do not allow filename starting with `.` - }); - }); - - if ($("#bots_lists_navbar .active-bots-tab").hasClass("active")) { - $("#active_bots_list").show(); - $("#inactive_bots_list").hide(); - } else { - $("#active_bots_list").hide(); - $("#inactive_bots_list").show(); - } -} - -exports.generate_zuliprc_uri = function (email, api_key) { - var data = settings.generate_zuliprc_content(email, api_key); - - return "data:application/octet-stream;charset=utf-8," + encodeURIComponent(data); -}; - -exports.generate_zuliprc_content = function (email, api_key) { - return "[api]" + - "\nemail=" + email + - "\nkey=" + api_key + - "\nsite=" + page_params.realm_uri + - // Some tools would not work in files without a trailing new line. - "\n"; -}; - $("body").ready(function () { var $sidebar = $(".form-sidebar"); var $targets = $sidebar.find("[data-target]"); @@ -125,297 +71,18 @@ function _setup_page() { settings_account.set_up(); settings_display.set_up(); settings_notifications.set_up(); + settings_bots.set_up(); settings_muting.set_up(); settings_lab.set_up(); - $("#api_key_value").text(""); - $("#get_api_key_box").hide(); - $("#show_api_key_box").hide(); - $("#api_key_button_box").show(); - if (tab) { exports.launch_page(tab); } - $('#api_key_button').click(function () { - if (page_params.password_auth_enabled !== false) { - $("#get_api_key_box").show(); - } else { - // Skip the password prompt step - $("#get_api_key_box form").submit(); - } - $("#api_key_button_box").hide(); - }); - if (!page_params.show_digest_email) { $("#other_notifications").hide(); } - $("#get_api_key_box").hide(); - $("#show_api_key_box").hide(); - $("#get_api_key_box form").ajaxForm({ - dataType: 'json', // This seems to be ignored. We still get back an xhr. - success: function (resp, statusText, xhr) { - var result = JSON.parse(xhr.responseText); - var settings_status = $('#account-settings-status').expectOne(); - - $("#get_api_key_password").val(""); - $("#api_key_value").text(result.api_key); - $("#show_api_key_box").show(); - $("#get_api_key_box").hide(); - settings_status.hide(); - }, - error: function (xhr) { - ui_report.error(i18n.t("Error getting API key"), xhr, $('#account-settings-status').expectOne()); - $("#show_api_key_box").hide(); - $("#get_api_key_box").show(); - }, - }); - - // TODO: render bots xxxx - render_bots(); - $(document).on('zulip.bot_data_changed', render_bots); - - $.validator.addMethod("bot_local_part", - function (value, element) { - return is_local_part.call(this, value + "-bot", element); - }, - "Please only use characters that are valid in an email address"); - - - var create_avatar_widget = avatar.build_bot_create_widget(); - - $('#create_bot_form').validate({ - errorClass: 'text-error', - success: function () { - $('#bot_table_error').hide(); - }, - submitHandler: function () { - var full_name = $('#create_bot_name').val(); - var short_name = $('#create_bot_short_name').val() || $('#create_bot_short_name').text(); - var formData = new FormData(); - - formData.append('csrfmiddlewaretoken', csrf_token); - formData.append('full_name', full_name); - formData.append('short_name', short_name); - jQuery.each($('#bot_avatar_file_input')[0].files, function (i, file) { - formData.append('file-'+i, file); - }); - $('#create_bot_button').val('Adding bot...').prop('disabled', true); - channel.post({ - url: '/json/bots', - data: formData, - cache: false, - processData: false, - contentType: false, - success: function () { - $('#bot_table_error').hide(); - $('#create_bot_name').val(''); - $('#create_bot_short_name').val(''); - $('#create_bot_button').show(); - create_avatar_widget.clear(); - }, - error: function (xhr) { - $('#bot_table_error').text(JSON.parse(xhr.responseText).msg).show(); - }, - complete: function () { - $('#create_bot_button').val('Create bot').prop('disabled', false); - }, - }); - }, - }); - - $("#active_bots_list").on("click", "button.delete_bot", function (e) { - var email = $(e.currentTarget).data('email'); - channel.del({ - url: '/json/bots/' + encodeURIComponent(email), - success: function () { - var row = $(e.currentTarget).closest("li"); - row.hide('slow', function () { row.remove(); }); - }, - error: function (xhr) { - $('#bot_delete_error').text(JSON.parse(xhr.responseText).msg).show(); - }, - }); - }); - - $("#inactive_bots_list").on("click", "button.reactivate_bot", function (e) { - var email = $(e.currentTarget).data('email'); - - channel.post({ - url: '/json/users/' + encodeURIComponent(email) + "/reactivate", - error: function (xhr) { - $('#bot_delete_error').text(JSON.parse(xhr.responseText).msg).show(); - }, - }); - }); - - $("#active_bots_list").on("click", "button.regenerate_bot_api_key", function (e) { - var email = $(e.currentTarget).data('email'); - channel.post({ - url: '/json/bots/' + encodeURIComponent(email) + '/api_key/regenerate', - idempotent: true, - success: function (data) { - var row = $(e.currentTarget).closest("li"); - row.find(".api_key").find(".value").text(data.api_key); - row.find("api_key_error").hide(); - }, - error: function (xhr) { - var row = $(e.currentTarget).closest("li"); - row.find(".api_key_error").text(JSON.parse(xhr.responseText).msg).show(); - }, - }); - }); - - var image_version = 0; - - var avatar_widget = avatar.build_bot_edit_widget($("#settings_page")); - - $("#active_bots_list").on("click", "button.open_edit_bot_form", function (e) { - var users_list = people.get_realm_persons().filter(function (person) { - return !person.is_bot; - }); - var li = $(e.currentTarget).closest('li'); - var edit_div = li.find('div.edit_bot'); - var form = $('#settings_page .edit_bot_form'); - var image = li.find(".image"); - var bot_info = li; - var reset_edit_bot = li.find(".reset_edit_bot"); - var owner_select = $(templates.render("bot_owner_select", {users_list:users_list})); - var old_full_name = bot_info.find(".name").text(); - var old_owner = bot_data.get(bot_info.find(".email .value").text()).owner; - var bot_email = bot_info.find(".email .value").text(); - - $("#settings_page .edit_bot .edit_bot_name").val(old_full_name); - $("#settings_page .edit_bot .select-form").text("").append(owner_select); - $("#settings_page .edit_bot .edit-bot-owner select").val(old_owner); - $("#settings_page .edit_bot_form").attr("data-email", bot_email); - $(".edit_bot_email").text(bot_email); - - avatar_widget.clear(); - - - function show_row_again() { - image.show(); - bot_info.show(); - edit_div.hide(); - } - - reset_edit_bot.click(function (event) { - form.find(".edit_bot_name").val(old_full_name); - owner_select.remove(); - show_row_again(); - $(this).off(event); - }); - - var errors = form.find('.bot_edit_errors'); - - form.validate({ - errorClass: 'text-error', - success: function () { - errors.hide(); - }, - submitHandler: function () { - var email = form.attr('data-email'); - var full_name = form.find('.edit_bot_name').val(); - var bot_owner = form.find('.edit-bot-owner select').val(); - var file_input = $(".edit_bot").find('.edit_bot_avatar_file_input'); - var spinner = form.find('.edit_bot_spinner'); - var edit_button = form.find('.edit_bot_button'); - var formData = new FormData(); - - formData.append('csrfmiddlewaretoken', csrf_token); - formData.append('full_name', full_name); - formData.append('bot_owner', bot_owner); - jQuery.each(file_input[0].files, function (i, file) { - formData.append('file-'+i, file); - }); - loading.make_indicator(spinner, {text: 'Editing bot'}); - edit_button.hide(); - channel.patch({ - url: '/json/bots/' + encodeURIComponent(email), - data: formData, - cache: false, - processData: false, - contentType: false, - success: function (data) { - loading.destroy_indicator(spinner); - errors.hide(); - edit_button.show(); - show_row_again(); - avatar_widget.clear(); - - bot_info.find('.name').text(full_name); - if (data.avatar_url) { - // Note that the avatar_url won't actually change on the back end - // 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()); - } - }, - error: function (xhr) { - loading.destroy_indicator(spinner); - edit_button.show(); - errors.text(JSON.parse(xhr.responseText).msg).show(); - }, - }); - }, - }); - - - }); - - $("#active_bots_list").on("click", "a.download_bot_zuliprc", function () { - var bot_info = $(this).closest(".bot-information-box"); - var email = bot_info.find(".email .value").text(); - var api_key = bot_info.find(".api_key .api-key-value-and-button .value").text(); - - $(this).attr("href", settings.generate_zuliprc_uri( - $.trim(email), $.trim(api_key) - )); - }); - - $("#download_zuliprc").on("click", function () { - $(this).attr("href", settings.generate_zuliprc_uri( - people.my_current_email(), - $("#api_key_value").text() - )); - }); - - $("#show_api_key_box").on("click", "button.regenerate_api_key", function () { - channel.post({ - url: '/json/users/me/api_key/regenerate', - idempotent: true, - success: function (data) { - $('#api_key_value').text(data.api_key); - }, - error: function (xhr) { - $('#user_api_key_error').text(JSON.parse(xhr.responseText).msg).show(); - }, - }); - }); - - $("#bots_lists_navbar .active-bots-tab").click(function (e) { - e.preventDefault(); - e.stopPropagation(); - - $("#bots_lists_navbar .active-bots-tab").addClass("active"); - $("#bots_lists_navbar .inactive-bots-tab").removeClass("active"); - $("#active_bots_list").show(); - $("#inactive_bots_list").hide(); - }); - - $("#bots_lists_navbar .inactive-bots-tab").click(function (e) { - e.preventDefault(); - e.stopPropagation(); - - $("#bots_lists_navbar .active-bots-tab").removeClass("active"); - $("#bots_lists_navbar .inactive-bots-tab").addClass("active"); - $("#active_bots_list").hide(); - $("#inactive_bots_list").show(); - }); - } exports.setup_page = function () { diff --git a/static/js/settings_bots.js b/static/js/settings_bots.js new file mode 100644 index 0000000000..100c7f0699 --- /dev/null +++ b/static/js/settings_bots.js @@ -0,0 +1,347 @@ +var settings_bots = (function () { + +var exports = {}; + +function add_bot_row(info) { + info.id_suffix = _.uniqueId('_bot_'); + var row = $(templates.render('bot_avatar_row', info)); + if (info.is_active) { + $('#active_bots_list').append(row); + } else { + $('#inactive_bots_list').append(row); + } +} + +function is_local_part(value, element) { + // Adapted from Django's EmailValidator + return this.optional(element) || /^[\-!#$%&'*+\/=?\^_`{}|~0-9A-Z]+(\.[\-!#$%&'*+\/=?\^_`{}|~0-9A-Z]+)*$/i.test(value); +} + +function render_bots() { + $('#active_bots_list').empty(); + $('#inactive_bots_list').empty(); + + _.each(bot_data.get_all_bots_for_current_user(), function (elem) { + add_bot_row({ + name: elem.full_name, + email: elem.email, + avatar_url: elem.avatar_url, + api_key: elem.api_key, + is_active: elem.is_active, + zuliprc: 'zuliprc', // Most browsers do not allow filename starting with `.` + }); + }); + + if ($("#bots_lists_navbar .active-bots-tab").hasClass("active")) { + $("#active_bots_list").show(); + $("#inactive_bots_list").hide(); + } else { + $("#active_bots_list").hide(); + $("#inactive_bots_list").show(); + } +} + +exports.generate_zuliprc_uri = function (email, api_key) { + var data = exports.generate_zuliprc_content(email, api_key); + + return "data:application/octet-stream;charset=utf-8," + encodeURIComponent(data); +}; + +exports.generate_zuliprc_content = function (email, api_key) { + return "[api]" + + "\nemail=" + email + + "\nkey=" + api_key + + "\nsite=" + page_params.realm_uri + + // Some tools would not work in files without a trailing new line. + "\n"; +}; + +exports.set_up = function () { + $("#api_key_value").text(""); + $("#get_api_key_box").hide(); + $("#show_api_key_box").hide(); + $("#api_key_button_box").show(); + + $('#api_key_button').click(function () { + if (page_params.password_auth_enabled !== false) { + $("#get_api_key_box").show(); + } else { + // Skip the password prompt step + $("#get_api_key_box form").submit(); + } + $("#api_key_button_box").hide(); + }); + + $("#get_api_key_box").hide(); + $("#show_api_key_box").hide(); + $("#get_api_key_box form").ajaxForm({ + dataType: 'json', // This seems to be ignored. We still get back an xhr. + success: function (resp, statusText, xhr) { + var result = JSON.parse(xhr.responseText); + var settings_status = $('#account-settings-status').expectOne(); + + $("#get_api_key_password").val(""); + $("#api_key_value").text(result.api_key); + $("#show_api_key_box").show(); + $("#get_api_key_box").hide(); + settings_status.hide(); + }, + error: function (xhr) { + ui_report.error(i18n.t("Error getting API key"), xhr, $('#account-settings-status').expectOne()); + $("#show_api_key_box").hide(); + $("#get_api_key_box").show(); + }, + }); + + // TODO: render bots xxxx + render_bots(); + $(document).on('zulip.bot_data_changed', render_bots); + + $.validator.addMethod("bot_local_part", + function (value, element) { + return is_local_part.call(this, value + "-bot", element); + }, + "Please only use characters that are valid in an email address"); + + + var create_avatar_widget = avatar.build_bot_create_widget(); + + $('#create_bot_form').validate({ + errorClass: 'text-error', + success: function () { + $('#bot_table_error').hide(); + }, + submitHandler: function () { + var full_name = $('#create_bot_name').val(); + var short_name = $('#create_bot_short_name').val() || $('#create_bot_short_name').text(); + var formData = new FormData(); + + formData.append('csrfmiddlewaretoken', csrf_token); + formData.append('full_name', full_name); + formData.append('short_name', short_name); + jQuery.each($('#bot_avatar_file_input')[0].files, function (i, file) { + formData.append('file-'+i, file); + }); + $('#create_bot_button').val('Adding bot...').prop('disabled', true); + channel.post({ + url: '/json/bots', + data: formData, + cache: false, + processData: false, + contentType: false, + success: function () { + $('#bot_table_error').hide(); + $('#create_bot_name').val(''); + $('#create_bot_short_name').val(''); + $('#create_bot_button').show(); + create_avatar_widget.clear(); + }, + error: function (xhr) { + $('#bot_table_error').text(JSON.parse(xhr.responseText).msg).show(); + }, + complete: function () { + $('#create_bot_button').val('Create bot').prop('disabled', false); + }, + }); + }, + }); + + $("#active_bots_list").on("click", "button.delete_bot", function (e) { + var email = $(e.currentTarget).data('email'); + channel.del({ + url: '/json/bots/' + encodeURIComponent(email), + success: function () { + var row = $(e.currentTarget).closest("li"); + row.hide('slow', function () { row.remove(); }); + }, + error: function (xhr) { + $('#bot_delete_error').text(JSON.parse(xhr.responseText).msg).show(); + }, + }); + }); + + $("#inactive_bots_list").on("click", "button.reactivate_bot", function (e) { + var email = $(e.currentTarget).data('email'); + + channel.post({ + url: '/json/users/' + encodeURIComponent(email) + "/reactivate", + error: function (xhr) { + $('#bot_delete_error').text(JSON.parse(xhr.responseText).msg).show(); + }, + }); + }); + + $("#active_bots_list").on("click", "button.regenerate_bot_api_key", function (e) { + var email = $(e.currentTarget).data('email'); + channel.post({ + url: '/json/bots/' + encodeURIComponent(email) + '/api_key/regenerate', + idempotent: true, + success: function (data) { + var row = $(e.currentTarget).closest("li"); + row.find(".api_key").find(".value").text(data.api_key); + row.find("api_key_error").hide(); + }, + error: function (xhr) { + var row = $(e.currentTarget).closest("li"); + row.find(".api_key_error").text(JSON.parse(xhr.responseText).msg).show(); + }, + }); + }); + + var image_version = 0; + + var avatar_widget = avatar.build_bot_edit_widget($("#settings_page")); + + $("#active_bots_list").on("click", "button.open_edit_bot_form", function (e) { + var users_list = people.get_realm_persons().filter(function (person) { + return !person.is_bot; + }); + var li = $(e.currentTarget).closest('li'); + var edit_div = li.find('div.edit_bot'); + var form = $('#settings_page .edit_bot_form'); + var image = li.find(".image"); + var bot_info = li; + var reset_edit_bot = li.find(".reset_edit_bot"); + var owner_select = $(templates.render("bot_owner_select", {users_list:users_list})); + var old_full_name = bot_info.find(".name").text(); + var old_owner = bot_data.get(bot_info.find(".email .value").text()).owner; + var bot_email = bot_info.find(".email .value").text(); + + $("#settings_page .edit_bot .edit_bot_name").val(old_full_name); + $("#settings_page .edit_bot .select-form").text("").append(owner_select); + $("#settings_page .edit_bot .edit-bot-owner select").val(old_owner); + $("#settings_page .edit_bot_form").attr("data-email", bot_email); + $(".edit_bot_email").text(bot_email); + + avatar_widget.clear(); + + + function show_row_again() { + image.show(); + bot_info.show(); + edit_div.hide(); + } + + reset_edit_bot.click(function (event) { + form.find(".edit_bot_name").val(old_full_name); + owner_select.remove(); + show_row_again(); + $(this).off(event); + }); + + var errors = form.find('.bot_edit_errors'); + + form.validate({ + errorClass: 'text-error', + success: function () { + errors.hide(); + }, + submitHandler: function () { + var email = form.attr('data-email'); + var full_name = form.find('.edit_bot_name').val(); + var bot_owner = form.find('.edit-bot-owner select').val(); + var file_input = $(".edit_bot").find('.edit_bot_avatar_file_input'); + var spinner = form.find('.edit_bot_spinner'); + var edit_button = form.find('.edit_bot_button'); + var formData = new FormData(); + + formData.append('csrfmiddlewaretoken', csrf_token); + formData.append('full_name', full_name); + formData.append('bot_owner', bot_owner); + jQuery.each(file_input[0].files, function (i, file) { + formData.append('file-'+i, file); + }); + loading.make_indicator(spinner, {text: 'Editing bot'}); + edit_button.hide(); + channel.patch({ + url: '/json/bots/' + encodeURIComponent(email), + data: formData, + cache: false, + processData: false, + contentType: false, + success: function (data) { + loading.destroy_indicator(spinner); + errors.hide(); + edit_button.show(); + show_row_again(); + avatar_widget.clear(); + + bot_info.find('.name').text(full_name); + if (data.avatar_url) { + // Note that the avatar_url won't actually change on the back end + // 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()); + } + }, + error: function (xhr) { + loading.destroy_indicator(spinner); + edit_button.show(); + errors.text(JSON.parse(xhr.responseText).msg).show(); + }, + }); + }, + }); + + + }); + + $("#active_bots_list").on("click", "a.download_bot_zuliprc", function () { + var bot_info = $(this).closest(".bot-information-box"); + var email = bot_info.find(".email .value").text(); + var api_key = bot_info.find(".api_key .api-key-value-and-button .value").text(); + + $(this).attr("href", exports.generate_zuliprc_uri( + $.trim(email), $.trim(api_key) + )); + }); + + $("#download_zuliprc").on("click", function () { + $(this).attr("href", exports.generate_zuliprc_uri( + people.my_current_email(), + $("#api_key_value").text() + )); + }); + + $("#show_api_key_box").on("click", "button.regenerate_api_key", function () { + channel.post({ + url: '/json/users/me/api_key/regenerate', + idempotent: true, + success: function (data) { + $('#api_key_value').text(data.api_key); + }, + error: function (xhr) { + $('#user_api_key_error').text(JSON.parse(xhr.responseText).msg).show(); + }, + }); + }); + + $("#bots_lists_navbar .active-bots-tab").click(function (e) { + e.preventDefault(); + e.stopPropagation(); + + $("#bots_lists_navbar .active-bots-tab").addClass("active"); + $("#bots_lists_navbar .inactive-bots-tab").removeClass("active"); + $("#active_bots_list").show(); + $("#inactive_bots_list").hide(); + }); + + $("#bots_lists_navbar .inactive-bots-tab").click(function (e) { + e.preventDefault(); + e.stopPropagation(); + + $("#bots_lists_navbar .active-bots-tab").removeClass("active"); + $("#bots_lists_navbar .inactive-bots-tab").addClass("active"); + $("#active_bots_list").hide(); + $("#inactive_bots_list").show(); + }); + +}; + +return exports; +}()); + +if (typeof module !== 'undefined') { + module.exports = settings_bots; +} diff --git a/zproject/settings.py b/zproject/settings.py index b48e590e63..e9345b5472 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -908,6 +908,7 @@ JS_SPECS = { 'js/settings_account.js', 'js/settings_display.js', 'js/settings_notifications.js', + 'js/settings_bots.js', 'js/settings_muting.js', 'js/settings_lab.js', 'js/settings.js',