/
githubmirror
/
zulip
Обзор
Документация
Войти
/
githubmirror
/
zulip
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
web/src/compose_closed_ui.ts
327 строк
13 KB
0xthedance
compose_closed_ui: Restore label_text for stream/topic recipients.
31 июл 2026, 14:07
31 июл 2026, 14:07
82f119d
Код
Авторство
О чём код?
import {$} from "jquery"; import assert from "minimalistic-assert"; import render_reply_recipient_label from "../templates/reply_recipient_label.hbs"; import * as compose_actions from "./compose_actions.ts"; import {$t} from "./i18n.ts"; import * as inbox_util from "./inbox_util.ts"; import * as message_lists from "./message_lists.ts"; import * as message_store from "./message_store.ts"; import * as message_util from "./message_util.ts"; import * as narrow_state from "./narrow_state.ts"; import {page_params} from "./page_params.ts"; import * as people from "./people.ts"; import * as recent_view_util from "./recent_view_util.ts"; import * as stream_data from "./stream_data.ts"; import type {StreamSubscription} from "./sub_store.ts"; import * as util from "./util.ts"; // `label_text` is a flat, pre-formatted string used by the call-creation // paths in compose_call_ui.ts to build a meeting/room name. // The reply-button template uses the structured `stream` / `topic_display_name` // fields instead, so it can render the decorated channel icon. type RecipientLabel = { label_text: string; has_empty_string_topic?: boolean; stream?: StreamSubscription; topic_display_name?: string; is_dm_with_self?: boolean; user_ids?: number[]; }; function get_stream_recipient_label(stream_id: number, topic: string): RecipientLabel | undefined { const stream = stream_data.get_sub_by_id(stream_id); const topic_display_name = util.get_final_topic_display_name(topic); if (stream) { const recipient_label: RecipientLabel = { label_text: `#${stream.name} > ${topic_display_name}`, has_empty_string_topic: topic === "", stream, topic_display_name, }; return recipient_label; } return undefined; } function get_direct_message_recipient_label(user_ids: number[]): RecipientLabel { let label_text = ""; let is_dm_with_self = false; if (people.is_direct_message_conversation_with_self(user_ids)) { is_dm_with_self = true; } else { label_text = message_store.get_pm_full_names(user_ids); } const recipient_label: RecipientLabel = { label_text, is_dm_with_self, user_ids, }; return recipient_label; } export type ReplyRecipientInformation = { stream_id?: number | undefined; topic?: string | undefined; user_ids?: number[] | undefined; display_reply_to?: string | undefined; }; export function get_recipient_label( recipient_information?: ReplyRecipientInformation, ): RecipientLabel | undefined { if (recipient_information !== undefined) { assert(recent_view_util.is_visible() || inbox_util.is_visible()); // When we're in either the Inbox or Recent Conversations view, // we try to update the closed compose box button label with // information about the reply target from the focused row in // the view. if ( recipient_information.stream_id !== undefined && recipient_information.topic !== undefined ) { return get_stream_recipient_label( recipient_information.stream_id, recipient_information.topic, ); } if (recipient_information.user_ids !== undefined) { return get_direct_message_recipient_label(recipient_information.user_ids); } if (recipient_information.display_reply_to !== undefined) { return {label_text: recipient_information.display_reply_to}; } } // Otherwise, we check the current message list for information // about the reply target for the closed compose box button label. if (message_lists.current === undefined) { return undefined; } if (message_lists.current.visibly_empty()) { // For empty narrows where there's a clear reply target, // i.e. channel and topic or a direct message conversation, // we label the button as replying to the thread. const stream_id = narrow_state.stream_id(narrow_state.filter(), true); const topic = narrow_state.topic(); const user_ids_string = narrow_state.pm_ids_string(); if (stream_id !== undefined && topic !== undefined) { return get_stream_recipient_label(stream_id, topic); } if (user_ids_string !== undefined) { // Check for validity of user ids to avoid any errors in case user // narrowed to an incorrect URL. const user_ids = people.user_ids_string_to_ids_array(user_ids_string); if (!people.is_valid_user_ids(user_ids)) { return undefined; } return get_direct_message_recipient_label(user_ids); } // Show the standard button text for empty narrows without // a clear reply target, e.g., an empty search view. return undefined; } const selected_message = message_lists.current.selected_message(); if (selected_message !== undefined) { if (selected_message?.is_stream) { return get_stream_recipient_label(selected_message.stream_id, selected_message.topic); } const user_ids = people.user_ids_string_to_ids_array(selected_message.to_user_ids); return get_direct_message_recipient_label(user_ids); } // Fall through to show the standard button text. return undefined; } // Exported for tests export let update_reply_button_state = (): void => { const $compose_reply_button_wrapper = $( "#legacy-closed-compose-box .compose-reply-button-wrapper", ); const stream_id_str = $compose_reply_button_wrapper.attr("data-stream-id"); const user_ids_string = $compose_reply_button_wrapper.attr("data-user-ids-string"); let disable = false; if (stream_id_str !== undefined) { disable = should_disable_compose_reply_button_for_stream( Number.parseInt(stream_id_str, 10), ); } else if (user_ids_string !== undefined) { disable = should_disable_compose_reply_button_for_direct_message(user_ids_string); } $(".compose_reply_button").attr("disabled", disable ? "disabled" : null); if (disable) { if (stream_id_str !== undefined) { $compose_reply_button_wrapper.attr("data-reply-button-type", "stream_disabled"); } else { $compose_reply_button_wrapper.attr("data-reply-button-type", "direct_disabled"); } return; } if (narrow_state.is_message_feed_visible()) { $compose_reply_button_wrapper.attr("data-reply-button-type", "selected_message"); } else { $compose_reply_button_wrapper.attr("data-reply-button-type", "selected_conversation"); } }; export function rewire_update_reply_button_state(value: typeof update_reply_button_state): void { update_reply_button_state = value; } function update_new_conversation_button(data_attribute_string: "stream" | "non-specific"): void { $("#new_conversation_button").attr("data-conversation-type", data_attribute_string); } function should_disable_compose_reply_button_for_stream(stream_id: number): boolean { if (page_params.is_spectator) { return false; } const stream = stream_data.get_sub_by_id(stream_id); return stream !== undefined && !stream_data.can_post_messages_in_stream(stream); } // Exported for tests export function should_disable_compose_reply_button_for_direct_message( user_ids_string: string, ): boolean { return !message_util.user_can_send_direct_message(user_ids_string); } export function update_buttons(update_type?: string): void { update_new_conversation_button(update_type === "stream" ? "stream" : "non-specific"); // We omit recipient_information here, so update_reply_button derives // the reply target from the current message list: the selected // message, or the narrowed conversation in an empty view. (The Inbox // and Recent Conversations views instead pass the reply target from // their focused row.) update_reply_button(); } export function maybe_update_buttons_for_dm_recipient(): void { const filter = narrow_state.filter(); if (filter?.contains_only_private_messages()) { update_buttons("direct"); } } function set_reply_button_label(label: string): void { $("#left_bar_compose_reply_button_big").text(label); } export function set_standard_text_for_reply_button(): void { set_reply_button_label($t({defaultMessage: "Compose message"})); const $compose_reply_button_wrapper = $( "#legacy-closed-compose-box .compose-reply-button-wrapper", ); // Clear any stale recipient context from a previous reply target. $compose_reply_button_wrapper.removeAttr("data-stream-id"); $compose_reply_button_wrapper.removeAttr("data-user-ids-string"); $compose_reply_button_wrapper.removeAttr("data-reply-button-type"); // Reset the stale disabled state if the reply button was // previously disabled. $(".compose_reply_button").attr("disabled", null); } export function update_reply_button_with_recipient_context( recipient_information?: ReplyRecipientInformation, ): void { const recipient_label = get_recipient_label(recipient_information); const $compose_reply_button_wrapper = $( "#legacy-closed-compose-box .compose-reply-button-wrapper", ); if (recipient_label !== undefined) { const empty_string_topic_display_name = util.get_final_topic_display_name(""); const rendered_recipient_label = render_reply_recipient_label({ has_empty_string_topic: recipient_label.has_empty_string_topic, stream: recipient_label.stream, topic_display_name: recipient_label.topic_display_name, is_dm_with_self: recipient_label.is_dm_with_self, empty_string_topic_display_name, label_text: recipient_label.label_text, }); // We store the recipient state so that update_reply_button_state // can use that information without re-examining the message list // or narrow state. The state is reset/unset by // set_standard_text_for_reply_button(). if (recipient_label.stream) { // Channel message recipient. $compose_reply_button_wrapper.removeAttr("data-user-ids-string"); $compose_reply_button_wrapper.attr( "data-stream-id", recipient_label.stream.stream_id.toString(), ); } else if (recipient_label.user_ids) { // Direct message recipient. $compose_reply_button_wrapper.removeAttr("data-stream-id"); $compose_reply_button_wrapper.attr( "data-user-ids-string", recipient_label.user_ids.join(","), ); } else { // Fallback case: recipient label from display_reply_to // text when we couldn't decode user IDs from the narrow // URL. We can't check permissions, so we leave the // button enabled. $compose_reply_button_wrapper.removeAttr("data-stream-id"); $compose_reply_button_wrapper.removeAttr("data-user-ids-string"); } $("#left_bar_compose_reply_button_big").html(rendered_recipient_label); } else { set_standard_text_for_reply_button(); } } // This should be called when updating the reply button so that // update_reply_button_with_recipient_context() runs before // update_reply_button_state(), ensuring that the button wrapper // contains the relevant recipient metadata. export function update_reply_button(recipient_information?: ReplyRecipientInformation): void { update_reply_button_with_recipient_context(recipient_information); update_reply_button_state(); } export function initialize(): void { // When the message selection changes, change the label on the Reply button. $(document).on("message_selected.zulip", () => { if (narrow_state.is_message_feed_visible()) { // message_selected events can occur with Recent Conversations // open due to the combined feed view loading in the background, // so we only update if message feed is visible. update_reply_button(); } }); // Click handlers for buttons in the compose box. $("body").on("click", ".compose_new_conversation_button", () => { compose_actions.start({ message_type: "stream", trigger: "clear topic button", keep_composebox_empty: true, }); }); $("body").on("click", ".compose_mobile_button", () => { // Remove the channel and topic context, since on mobile widths, // the "+" button should open the compose box with the channel // picker open (even if the user is in a topic or channel view). compose_actions.start({ message_type: "stream", stream_id: undefined, topic: "", keep_composebox_empty: true, }); }); $("body").on("click", ".compose_new_direct_message_button", () => { compose_actions.start({ message_type: "private", trigger: "new direct message", keep_composebox_empty: true, }); }); }