diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 13704edd1e..623e3e6a31 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -65,6 +65,7 @@ EXEMPT_FILES = make_set( "web/src/browser_history.ts", "web/src/buddy_list.ts", "web/src/buttons.ts", + "web/src/channel_folders_ui.ts", "web/src/click_handlers.ts", "web/src/color_picker_popover.ts", "web/src/compose.js", diff --git a/web/src/channel_folders_ui.ts b/web/src/channel_folders_ui.ts new file mode 100644 index 0000000000..25aef221ad --- /dev/null +++ b/web/src/channel_folders_ui.ts @@ -0,0 +1,164 @@ +import $ from "jquery"; +import * as z from "zod/mini"; + +import render_confirm_archive_channel_folder from "../templates/confirm_dialog/confirm_archive_channel_folder.hbs"; +import render_create_channel_folder_modal from "../templates/stream_settings/create_channel_folder_modal.hbs"; +import render_edit_channel_folder_modal from "../templates/stream_settings/edit_channel_folder_modal.hbs"; + +import * as channel from "./channel.ts"; +import * as channel_folders from "./channel_folders.ts"; +import * as confirm_dialog from "./confirm_dialog.ts"; +import * as dialog_widget from "./dialog_widget.ts"; +import {$t_html} from "./i18n.ts"; +import * as people from "./people.ts"; +import {realm} from "./state_data.ts"; +import * as ui_report from "./ui_report.ts"; + +export function add_channel_folder(): void { + const html_body = render_create_channel_folder_modal({ + max_channel_folder_name_length: realm.max_channel_folder_name_length, + max_channel_folder_description_length: realm.max_channel_folder_description_length, + }); + + function create_channel_folder(): void { + const close_on_success = true; + const data = { + name: $("input#new_channel_folder_name").val()!.trim(), + description: $("textarea#new_channel_folder_description") + .val()! + .trim(), + }; + dialog_widget.submit_api_request( + channel.post, + "/json/channel_folders/create", + data, + { + success_continuation(response_data) { + const id = z + .object({channel_folder_id: z.number()}) + .parse(response_data).channel_folder_id; + // This is a temporary channel folder object added + // to channel folders data, so that the folder is + // immediately visible in the dropdown. + // This will be replaced with the actual object once + // the client receives channel_folder/add event. + const channel_folder = { + id, + name: data.name, + description: data.description, + is_archived: false, + rendered_description: "", + date_created: 0, + creator_id: people.my_current_user_id(), + }; + channel_folders.add(channel_folder); + }, + }, + close_on_success, + ); + } + + dialog_widget.launch({ + html_heading: $t_html({defaultMessage: "Create channel folder"}), + html_body, + id: "create_channel_folder", + html_submit_button: $t_html({defaultMessage: "Create"}), + on_click: create_channel_folder, + loading_spinner: true, + on_shown: () => $("#new_channel_folder_name").trigger("focus"), + }); +} + +function archive_folder(folder_id: number): void { + const stream_ids = channel_folders.get_stream_ids_in_folder(folder_id); + let successful_requests = 0; + + function make_archive_folder_request(): void { + const url = "/json/channel_folders/" + folder_id.toString(); + const data = { + is_archived: JSON.stringify(true), + }; + dialog_widget.submit_api_request(channel.patch, url, data); + } + + if (stream_ids.length === 0) { + make_archive_folder_request(); + return; + } + + function remove_channel_from_folder(stream_id: number): void { + const url = "/json/streams/" + stream_id.toString(); + const data = { + folder_id: JSON.stringify(null), + }; + void channel.patch({ + url, + data, + success() { + successful_requests = successful_requests + 1; + + if (successful_requests === stream_ids.length) { + // Make request to archive folder only after all channels + // are removed from the folder. + make_archive_folder_request(); + } + }, + error(xhr) { + ui_report.error( + $t_html({ + defaultMessage: "Failed removing one or more channels from the folder", + }), + xhr, + $("#dialog_error"), + ); + dialog_widget.hide_dialog_spinner(); + }, + }); + } + + for (const stream_id of stream_ids) { + remove_channel_from_folder(stream_id); + } +} + +export function handle_archiving_channel_folder(folder_id: number): void { + confirm_dialog.launch({ + html_heading: $t_html({defaultMessage: "Delete channel folder?"}), + html_body: render_confirm_archive_channel_folder(), + on_click() { + archive_folder(folder_id); + }, + close_on_submit: false, + loading_spinner: true, + }); +} + +export function handle_editing_channel_folder(folder_id: number): void { + const folder = channel_folders.get_channel_folder_by_id(folder_id); + + const html_body = render_edit_channel_folder_modal({ + name: folder.name, + description: folder.description, + max_channel_folder_name_length: realm.max_channel_folder_name_length, + max_channel_folder_description_length: realm.max_channel_folder_description_length, + }); + + dialog_widget.launch({ + html_heading: $t_html({defaultMessage: "Edit channel folder"}), + html_body, + id: "edit_channel_folder", + on_click() { + const url = "/json/channel_folders/" + folder_id.toString(); + const data = { + name: $("input#edit_channel_folder_name").val()!.trim(), + description: $("textarea#edit_channel_folder_description") + .val()! + .trim(), + }; + dialog_widget.submit_api_request(channel.patch, url, data); + }, + loading_spinner: true, + on_shown: () => $("#edit_channel_folder_name").trigger("focus"), + update_submit_disabled_state_on_change: true, + }); +} diff --git a/web/src/stream_edit.ts b/web/src/stream_edit.ts index 988d60b04f..d3315967f9 100644 --- a/web/src/stream_edit.ts +++ b/web/src/stream_edit.ts @@ -10,7 +10,6 @@ import render_inline_decorated_channel_name from "../templates/inline_decorated_ import render_change_stream_info_modal from "../templates/stream_settings/change_stream_info_modal.hbs"; import render_confirm_stream_privacy_change_modal from "../templates/stream_settings/confirm_stream_privacy_change_modal.hbs"; import render_copy_email_address_modal from "../templates/stream_settings/copy_email_address_modal.hbs"; -import render_create_channel_folder_modal from "../templates/stream_settings/create_channel_folder_modal.hbs"; import render_stream_description from "../templates/stream_settings/stream_description.hbs"; import render_stream_settings from "../templates/stream_settings/stream_settings.hbs"; @@ -18,7 +17,7 @@ import * as blueslip from "./blueslip.ts"; import type {Bot} from "./bot_data.ts"; import * as browser_history from "./browser_history.ts"; import * as channel from "./channel.ts"; -import * as channel_folders from "./channel_folders.ts"; +import * as channel_folders_ui from "./channel_folders_ui.ts"; import * as confirm_dialog from "./confirm_dialog.ts"; import {show_copied_confirmation} from "./copied_tooltip.ts"; import * as dialog_widget from "./dialog_widget.ts"; @@ -914,56 +913,6 @@ export function initialize(): void { ); $("#channels_overlay_container").on("click", ".create-channel-folder-button", () => { - const html_body = render_create_channel_folder_modal({ - max_channel_folder_name_length: realm.max_channel_folder_name_length, - max_channel_folder_description_length: realm.max_channel_folder_description_length, - }); - - function create_channel_folder(): void { - const close_on_success = true; - const data = { - name: $("input#new_channel_folder_name").val()!.trim(), - description: $("textarea#new_channel_folder_description") - .val()! - .trim(), - }; - dialog_widget.submit_api_request( - channel.post, - "/json/channel_folders/create", - data, - { - success_continuation(response_data) { - const id = z - .object({channel_folder_id: z.number()}) - .parse(response_data).channel_folder_id; - // This is a temporary channel folder object added - // to channel folders data, so that the folder is - // immediately visible in the dropdown. - // This will be replaced with the actual object once - // the client receives channel_folder/add event. - const channel_folder = { - id, - name: data.name, - description: data.description, - is_archived: false, - rendered_description: "", - date_created: 0, - creator_id: people.my_current_user_id(), - }; - channel_folders.add(channel_folder); - }, - }, - close_on_success, - ); - } - dialog_widget.launch({ - html_heading: $t_html({defaultMessage: "Create channel folder"}), - html_body, - id: "create_channel_folder", - html_submit_button: $t_html({defaultMessage: "Create"}), - on_click: create_channel_folder, - loading_spinner: true, - on_shown: () => $("#new_channel_folder_name").trigger("focus"), - }); + channel_folders_ui.add_channel_folder(); }); } diff --git a/web/src/stream_settings_components.ts b/web/src/stream_settings_components.ts index a034f2870f..8d3c26a729 100644 --- a/web/src/stream_settings_components.ts +++ b/web/src/stream_settings_components.ts @@ -1,16 +1,14 @@ import $ from "jquery"; import * as z from "zod/mini"; -import render_confirm_archive_channel_folder from "../templates/confirm_dialog/confirm_archive_channel_folder.hbs"; import render_unsubscribe_private_stream_modal from "../templates/confirm_dialog/confirm_unsubscribe_private_stream.hbs"; import render_inline_decorated_channel_name from "../templates/inline_decorated_channel_name.hbs"; -import render_edit_channel_folder_modal from "../templates/stream_settings/edit_channel_folder_modal.hbs"; import render_selected_stream_title from "../templates/stream_settings/selected_stream_title.hbs"; import * as channel from "./channel.ts"; import * as channel_folders from "./channel_folders.ts"; +import * as channel_folders_ui from "./channel_folders_ui.ts"; import * as confirm_dialog from "./confirm_dialog.ts"; -import * as dialog_widget from "./dialog_widget.ts"; import type {DropdownWidget} from "./dropdown_widget.ts"; import * as dropdown_widget from "./dropdown_widget.ts"; import * as hash_util from "./hash_util.ts"; @@ -22,7 +20,7 @@ import * as resize from "./resize.ts"; import * as settings_components from "./settings_components.ts"; import * as settings_config from "./settings_config.ts"; import * as settings_data from "./settings_data.ts"; -import {current_user, realm} from "./state_data.ts"; +import {current_user} from "./state_data.ts"; import * as stream_data from "./stream_data.ts"; import * as stream_settings_containers from "./stream_settings_containers.ts"; import * as stream_settings_data from "./stream_settings_data.ts"; @@ -322,100 +320,6 @@ export function filter_includes_channel(sub: StreamSubscription): boolean { return true; } -function archive_folder(folder_id: number): void { - const stream_ids = channel_folders.get_stream_ids_in_folder(folder_id); - let successful_requests = 0; - - function make_archive_folder_request(): void { - const url = "/json/channel_folders/" + folder_id.toString(); - const data = { - is_archived: JSON.stringify(true), - }; - dialog_widget.submit_api_request(channel.patch, url, data); - } - - if (stream_ids.length === 0) { - make_archive_folder_request(); - return; - } - - function remove_channel_from_folder(stream_id: number): void { - const url = "/json/streams/" + stream_id.toString(); - const data = { - folder_id: JSON.stringify(null), - }; - void channel.patch({ - url, - data, - success() { - successful_requests = successful_requests + 1; - - if (successful_requests === stream_ids.length) { - // Make request to archive folder only after all channels - // are removed from the folder. - make_archive_folder_request(); - } - }, - error(xhr) { - ui_report.error( - $t_html({ - defaultMessage: "Failed removing one or more channels from the folder", - }), - xhr, - $("#dialog_error"), - ); - dialog_widget.hide_dialog_spinner(); - }, - }); - } - - for (const stream_id of stream_ids) { - remove_channel_from_folder(stream_id); - } -} - -function handle_archiving_channel_folder(folder_id: number): void { - confirm_dialog.launch({ - html_heading: $t_html({defaultMessage: "Delete channel folder?"}), - html_body: render_confirm_archive_channel_folder(), - on_click() { - archive_folder(folder_id); - }, - close_on_submit: false, - loading_spinner: true, - }); -} - -function handle_editing_channel_folder(folder_id: number): void { - const folder = channel_folders.get_channel_folder_by_id(folder_id); - - const html_body = render_edit_channel_folder_modal({ - name: folder.name, - description: folder.description, - max_channel_folder_name_length: realm.max_channel_folder_name_length, - max_channel_folder_description_length: realm.max_channel_folder_description_length, - }); - - dialog_widget.launch({ - html_heading: $t_html({defaultMessage: "Edit channel folder"}), - html_body, - id: "edit_channel_folder", - on_click() { - const url = "/json/channel_folders/" + folder_id.toString(); - const data = { - name: $("input#edit_channel_folder_name").val()!.trim(), - description: $("textarea#edit_channel_folder_description") - .val()! - .trim(), - }; - dialog_widget.submit_api_request(channel.patch, url, data); - }, - loading_spinner: true, - on_shown: () => $("#edit_channel_folder_name").trigger("focus"), - update_submit_disabled_state_on_change: true, - }); -} - export function set_up_folder_dropdown_widget(sub?: StreamSubscription): DropdownWidget { const folder_options = (): dropdown_widget.Option[] => { const folders = channel_folders @@ -484,7 +388,7 @@ export function set_up_folder_dropdown_widget(sub?: StreamSubscription): Dropdow $(event.target).closest(".list-item").attr("data-unique-id")!, 10, ); - handle_archiving_channel_folder(folder_id); + channel_folders_ui.handle_archiving_channel_folder(folder_id); return; } @@ -497,7 +401,7 @@ export function set_up_folder_dropdown_widget(sub?: StreamSubscription): Dropdow $(event.target).closest(".list-item").attr("data-unique-id")!, 10, ); - handle_editing_channel_folder(folder_id); + channel_folders_ui.handle_editing_channel_folder(folder_id); return; }