/
githubmirror
/
zulip
Обзор
Документация
Войти
/
githubmirror
/
zulip
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
web/src/stream_settings_components.ts
547 строк
20 KB
Anders Kaseorg
web: Use named import for jQuery.
29 июл 2026, 02:35
29 июл 2026, 02:35
52a10e8
Код
Авторство
О чём код?
import {$} from "jquery"; import * as z from "zod/mini"; import render_unsubscribe_private_stream_modal from "../templates/confirm_dialog/confirm_unsubscribe_private_stream.hbs"; import render_decorated_channel_name from "../templates/decorated_channel_name.hbs"; import render_new_channel_members_title from "../templates/stream_settings/new_channel_members_title.hbs"; import render_selected_stream_title from "../templates/stream_settings/selected_stream_title.hbs"; import * as blueslip from "./blueslip.ts"; import * as buttons from "./buttons.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 type {DropdownWidget} from "./dropdown_widget.ts"; import * as dropdown_widget from "./dropdown_widget.ts"; import * as folder_dropdown_widget from "./folder_dropdown_widget.ts"; import * as hash_util from "./hash_util.ts"; import {$t, $t_html} from "./i18n.ts"; import * as loading from "./loading.ts"; import * as overlays from "./overlays.ts"; import * as peer_data from "./peer_data.ts"; 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} 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"; import type {StreamSubscription} from "./sub_store.ts"; import type {GroupSettingPillContainer} from "./typeahead_helper.ts"; import * as ui_report from "./ui_report.ts"; import * as user_groups from "./user_groups.ts"; export let archived_status_filter_dropdown_widget: DropdownWidget; export let channel_creation_privacy_widget: DropdownWidget; let folder_filter_dropdown_widget: DropdownWidget; function set_visibility_for_stream_title_buttons(sub: StreamSubscription): void { // This is for the Archive/Unarchive button in the right panel. const $archive_button = $( `.stream-title-buttons[data-stream-id='${CSS.escape(sub.stream_id.toString())}'] .deactivate`, ); const $unarchive_button = $( `.stream-title-buttons[data-stream-id='${CSS.escape(sub.stream_id.toString())}'] .reactivate`, ); const $edit_stream_button = $( `.stream-title-buttons[data-stream-id='${CSS.escape(sub.stream_id.toString())}'] #channel_title_open_channel_info_modal`, ); if (!stream_data.can_administer_channel(sub)) { $archive_button.hide(); $unarchive_button.hide(); $edit_stream_button.hide(); return; } if (sub.is_archived) { $archive_button.hide(); $unarchive_button.show(); } else { $unarchive_button.hide(); $archive_button.show(); } } export function set_right_panel_title(sub: StreamSubscription): void { let title_icon_color = "#333333"; if (settings_data.using_dark_theme()) { title_icon_color = "#dddeee"; } const preview_url = hash_util.channel_url_by_user_setting(sub.stream_id); const settings_sub = stream_settings_data.get_sub_for_settings(sub); $("#subscription_overlay .stream-info-title") .html( render_selected_stream_title({ sub: settings_sub, title_icon_color, preview_url, }), ) .toggleClass("new-channel-members-title", false); set_visibility_for_stream_title_buttons(sub); } export const show_subs_pane = { nothing_selected(): void { $(".settings, #stream-creation").hide(); $(".nothing-selected").show(); $("#subscription_overlay .stream-info-title") .text($t({defaultMessage: "Channel settings"})) .toggleClass("new-channel-members-title", false); resize.resize_settings_overlay($("#channels_overlay_container")); }, settings(sub: StreamSubscription): void { $(".settings, #stream-creation").hide(); $(".settings").show(); set_right_panel_title(sub); resize.resize_settings_overlay($("#channels_overlay_container")); }, create_stream( container_name = "configure_channel_settings", sub?: { name: string; invite_only: boolean; is_web_public: boolean; }, ): void { $(".stream_creation_container").hide(); if (container_name === "configure_channel_settings") { $("#subscription_overlay .stream-info-title") .text($t({defaultMessage: "Configure new channel settings"})) .toggleClass("new-channel-members-title", false); } else { $("#subscription_overlay .stream-info-title") .html( render_new_channel_members_title({ sub: sub ?? { name: "", invite_only: false, is_web_public: false, can_change_name_description: false, }, }), ) .toggleClass("new-channel-members-title", true); } update_footer_buttons(container_name); $(`.${CSS.escape(container_name)}`).show(); $(".nothing-selected, .settings, #stream-creation").hide(); $("#stream-creation").show(); resize.resize_settings_overlay($("#channels_overlay_container")); resize.resize_settings_creation_overlay($("#channels_overlay_container")); $("#stream_creation_form .two-pane-settings-creation-simplebar-container").toggleClass( "subscribers-visible", container_name === "subscribers_container", ); }, }; export function update_footer_buttons(container_name: string): void { if (container_name === "subscribers_container") { // Hide stream creation containers and show add subscriber container $(".finalize_create_stream").show(); $("#stream_creation_go_to_subscribers").hide(); $("#stream_creation_go_to_configure_channel_settings").show(); } else { // Hide add subscriber container and show stream creation containers $(".finalize_create_stream").hide(); $("#stream_creation_go_to_subscribers").show(); $("#stream_creation_go_to_configure_channel_settings").hide(); } } export function get_active_data(): { $row: JQuery; id: number; $tabs: JQuery; } { const $active_row = $("div.stream-row.active"); const valid_active_id = Number.parseInt($active_row.attr("data-stream-id")!, 10); const $active_tabs = $("#subscription_overlay .two-pane-settings-container").find( "div.ind-tab.selected", ); return { $row: $active_row, id: valid_active_id, $tabs: $active_tabs, }; } /* For the given stream_row, remove the tick and replace by a spinner. */ function display_subscribe_toggle_spinner($stream_row: JQuery): void { /* Prevent sending multiple requests by removing the button class. */ $stream_row.find(".check").removeClass("sub_unsub_button"); /* Hide the tick. */ const $tick = $stream_row.find(".sub-unsub-icon"); $tick.addClass("hide"); /* Add a spinner to show the request is in process. */ const $spinner = $stream_row.find(".sub_unsub_status").expectOne(); $spinner.show(); loading.make_indicator($spinner); } /* For the given stream_row, add the tick and delete the spinner. */ function hide_subscribe_toggle_spinner($stream_row: JQuery): void { /* Re-enable the button to handle requests. */ $stream_row.find(".check").addClass("sub_unsub_button"); /* Show the tick. */ const $tick = $stream_row.find(".sub-unsub-icon"); $tick.removeClass("hide"); /* Destroy the spinner. */ const $spinner = $stream_row.find(".sub_unsub_status").expectOne(); loading.destroy_indicator($spinner); } export function ajaxSubscribe( stream: string, color: string, $stream_row: JQuery | undefined, $button_elem: JQuery | undefined, ): void { // Subscribe yourself to a single stream. if ($stream_row !== undefined) { display_subscribe_toggle_spinner($stream_row); } if ($button_elem !== undefined) { buttons.show_button_loading_indicator($button_elem); } void channel.post({ url: "/json/users/me/subscriptions", data: {subscriptions: JSON.stringify([{name: stream, color}])}, success(_resp, _statusText, xhr) { if (overlays.streams_open()) { $("#create_stream_name").val(""); } const res = z .object({ already_subscribed: z.record(z.string(), z.array(z.string())), }) .parse(xhr.responseJSON); if (Object.keys(res.already_subscribed).length > 0) { ui_report.success( $t_html({defaultMessage: "Already subscribed"}), $(".stream_change_property_info"), ); } // The rest of the work is done via the subscribe event we will get if ($stream_row !== undefined) { hide_subscribe_toggle_spinner($stream_row); } if ($button_elem !== undefined) { buttons.hide_button_loading_indicator($button_elem); } }, error(xhr) { if ($stream_row !== undefined) { hide_subscribe_toggle_spinner($stream_row); } if ($button_elem !== undefined) { buttons.hide_button_loading_indicator($button_elem); } ui_report.error( $t_html({defaultMessage: "Error subscribing"}), xhr, $(".stream_change_property_info"), ); }, }); } function ajaxUnsubscribe( sub: StreamSubscription, $stream_row: JQuery | undefined, $button_elem: JQuery | undefined, ): void { // TODO: use stream_id when backend supports it if ($stream_row !== undefined) { display_subscribe_toggle_spinner($stream_row); } if ($button_elem !== undefined) { buttons.show_button_loading_indicator($button_elem); } void channel.del({ url: "/json/users/me/subscriptions", data: {subscriptions: JSON.stringify([sub.name])}, success() { $(".stream_change_property_info").hide(); // The rest of the work is done via the unsubscribe event we will get if ($stream_row !== undefined) { hide_subscribe_toggle_spinner($stream_row); } if ($button_elem !== undefined) { buttons.hide_button_loading_indicator($button_elem); } }, error(xhr) { if ($stream_row !== undefined) { hide_subscribe_toggle_spinner($stream_row); } if ($button_elem !== undefined) { buttons.hide_button_loading_indicator($button_elem); } ui_report.error( $t_html({defaultMessage: "Error unsubscribing"}), xhr, $(".stream_change_property_info"), ); }, }); } export function unsubscribe_from_private_stream( sub: StreamSubscription, $stream_row: JQuery | undefined, $button_elem: JQuery | undefined, ): void { const invite_only = sub.invite_only; const sub_count = peer_data.get_subscriber_count(sub.stream_id); const stream_name_with_privacy_symbol_html = render_decorated_channel_name({ inline_with_text: true, stream: sub, }); const modal_content_html = render_unsubscribe_private_stream_modal({ unsubscribing_other_user: false, organization_will_lose_content_access: sub_count === 1 && invite_only && user_groups.is_setting_group_set_to_nobody_group(sub.can_subscribe_group) && user_groups.is_setting_group_set_to_nobody_group(sub.can_add_subscribers_group), }); function unsubscribe_from_stream(): void { ajaxUnsubscribe(sub, $stream_row, $button_elem); } confirm_dialog.launch({ modal_title_html: $t_html( {defaultMessage: "Unsubscribe from <z-link></z-link>?"}, {"z-link": () => stream_name_with_privacy_symbol_html}, ), modal_content_html, on_click: unsubscribe_from_stream, }); } export function sub_or_unsub( sub: StreamSubscription, $stream_row?: JQuery, $button_elem?: JQuery, ): void { if (sub.subscribed) { // TODO: This next line should allow guests to access web-public streams. if ( (sub.invite_only && !stream_data.has_content_access_via_group_permissions(sub)) || current_user.is_guest ) { unsubscribe_from_private_stream(sub, $stream_row, $button_elem); return; } ajaxUnsubscribe(sub, $stream_row, $button_elem); } else { ajaxSubscribe(sub.name, sub.color, $stream_row, $button_elem); } } export function set_archived_status_filter_dropdown_widget(widget: DropdownWidget): void { archived_status_filter_dropdown_widget = widget; } export function get_archived_status_filter_dropdown_value(): string { return z.string().parse(archived_status_filter_dropdown_widget.value()); } export function set_archived_status_filter_dropdown_value(value: string): void { archived_status_filter_dropdown_widget.render(value); } export function set_archived_status_filters_for_tests(filter_widget: DropdownWidget): void { archived_status_filter_dropdown_widget = filter_widget; } export function set_folder_filter_dropdown_widget(widget: DropdownWidget): void { folder_filter_dropdown_widget = widget; } export function get_folder_filter_dropdown_value(): number { return z.number().parse(folder_filter_dropdown_widget.value()); } export function set_folder_filter_dropdown_value(value: number): void { folder_filter_dropdown_widget.render(value); } export function set_folder_filter_for_tests(filter_widget: DropdownWidget): void { folder_filter_dropdown_widget = filter_widget; } export function archived_status_filter_includes_channel(sub: StreamSubscription): boolean { const filter_value = get_archived_status_filter_dropdown_value(); const FILTERS = stream_settings_data.ARCHIVED_STATUS_FILTERS; if ( (filter_value === FILTERS.NON_ARCHIVED_CHANNELS && sub.is_archived) || (filter_value === FILTERS.ARCHIVED_CHANNELS && !sub.is_archived) ) { return false; } return true; } export function folder_filter_includes_channel(sub: StreamSubscription): boolean { const filters = folder_dropdown_widget.FOLDER_FILTERS; const filter_value = get_folder_filter_dropdown_value(); if (filter_value === filters.ANY_FOLDER_DROPDOWN_OPTION) { return true; } if (filter_value === filters.UNCATEGORIZED_DROPDOWN_OPTION) { return sub.folder_id === null; } return filter_value === sub.folder_id; } export function set_up_folder_dropdown_widget(sub?: StreamSubscription): DropdownWidget { const folder_options = (): dropdown_widget.Option[] => { const folders = channel_folders.get_channel_folders(); const can_manage_folder = current_user.is_admin; const manage_folder_icon_label = can_manage_folder ? $t({defaultMessage: "Manage folder"}) : $t({defaultMessage: "Preview folder"}); const options: dropdown_widget.Option[] = folders.map((folder) => ({ name: folder.name, unique_id: folder.id, has_delete_icon: can_manage_folder, manage_folder_icon: can_manage_folder ? "folder-cog" : "preview", has_manage_folder_icon: true, delete_icon_label: $t({defaultMessage: "Delete folder"}), manage_folder_icon_label, })); const disabled_option = { make_italic: true, unique_id: settings_config.no_folder_selected, name: $t({defaultMessage: "None"}), }; options.unshift(disabled_option); return options; }; const default_id = sub?.folder_id ?? settings_config.no_folder_selected; let widget_name = "folder_id"; if (sub === undefined) { widget_name = "new_channel_folder_id"; } let $events_container = $("#stream_settings .subscription_settings"); if (sub === undefined) { $events_container = $("#stream_creation_form"); } const folder_widget = new dropdown_widget.DropdownWidget({ widget_name, get_options: folder_options, $events_container, item_click_callback(event, dropdown, this_widget) { dropdown.hide(); event.preventDefault(); event.stopPropagation(); this_widget.render(); if (sub !== undefined) { const $edit_container = stream_settings_containers.get_edit_container(sub); settings_components.save_discard_stream_settings_widget_status_handler( $edit_container.find(".stream-settings-subsection"), stream_data.get_sub_by_id(sub.stream_id), ); } }, item_button_click_callback(event) { event.preventDefault(); event.stopPropagation(); if ( $(event.target).closest( `.${CSS.escape(widget_name)}-dropdown-list-container .dropdown-list-delete`, ).length > 0 ) { const folder_id = Number.parseInt( $(event.target).closest(".list-item").attr("data-unique-id")!, 10, ); channel_folders_ui.handle_archiving_channel_folder(folder_id); return; } if ( $(event.target).closest( `.${CSS.escape(widget_name)}-dropdown-list-container .dropdown-list-manage-folder`, ).length > 0 ) { const folder_id = Number.parseInt( $(event.target).closest(".list-item").attr("data-unique-id")!, 10, ); channel_folders_ui.handle_editing_channel_folder(folder_id); return; } }, default_id, unique_id_type: "number", }); if (sub !== undefined) { settings_components.set_dropdown_setting_widget("folder_id", folder_widget); } folder_widget.setup(); return folder_widget; } export function set_channel_creation_privacy_widget(widget: DropdownWidget): void { channel_creation_privacy_widget = widget; } const new_stream_group_setting_widget_map = new Map<string, GroupSettingPillContainer | null>([ ["can_add_subscribers_group", null], ["can_administer_channel_group", null], ["can_create_topic_group", null], ["can_delete_any_message_group", null], ["can_delete_own_message_group", null], ["can_move_messages_out_of_channel_group", null], ["can_move_messages_within_channel_group", null], ["can_remove_subscribers_group", null], ["can_resolve_topics_group", null], ["can_send_message_group", null], ]); export function get_group_setting_widget_for_new_stream( setting_name: string, ): GroupSettingPillContainer | null { const pill_widget = new_stream_group_setting_widget_map.get(setting_name); if (pill_widget === undefined) { blueslip.error("No group setting pill widget for property", {setting_name}); return null; } return pill_widget; } export function set_group_setting_widget_for_new_stream( setting_name: string, widget: GroupSettingPillContainer, ): void { new_stream_group_setting_widget_map.set(setting_name, widget); }