Files
zulip/web/src/server_events_dispatch.js
2025-08-04 18:33:36 -07:00

1206 lines
55 KiB
JavaScript

import $ from "jquery";
import * as activity_ui from "./activity_ui.ts";
import * as alert_words from "./alert_words.ts";
import * as alert_words_ui from "./alert_words_ui.ts";
import * as attachments_ui from "./attachments_ui.ts";
import * as audible_notifications from "./audible_notifications.ts";
import * as blueslip from "./blueslip.ts";
import * as bot_data from "./bot_data.ts";
import * as browser_history from "./browser_history.ts";
import {buddy_list} from "./buddy_list.ts";
import * as channel_folders from "./channel_folders.ts";
import * as compose_call from "./compose_call.ts";
import * as compose_call_ui from "./compose_call_ui.ts";
import * as compose_closed_ui from "./compose_closed_ui.ts";
import * as compose_pm_pill from "./compose_pm_pill.ts";
import * as compose_recipient from "./compose_recipient.ts";
import * as compose_state from "./compose_state.ts";
import * as compose_validate from "./compose_validate.ts";
import {electron_bridge} from "./electron_bridge.ts";
import * as emoji from "./emoji.ts";
import * as emoji_picker from "./emoji_picker.ts";
import * as gear_menu from "./gear_menu.ts";
import * as giphy from "./giphy.ts";
import * as inbox_ui from "./inbox_ui.ts";
import * as information_density from "./information_density.ts";
import * as left_sidebar_navigation_area from "./left_sidebar_navigation_area.ts";
import * as linkifiers from "./linkifiers.ts";
import * as message_edit from "./message_edit.ts";
import * as message_events from "./message_events.ts";
import * as message_lists from "./message_lists.ts";
import * as message_live_update from "./message_live_update.ts";
import * as message_reminder from "./message_reminder.ts";
import * as message_store from "./message_store.ts";
import * as message_view from "./message_view.ts";
import * as muted_users_ui from "./muted_users_ui.ts";
import * as narrow_title from "./narrow_title.ts";
import * as navbar_alerts from "./navbar_alerts.ts";
import * as navigation_views from "./navigation_views.ts";
import * as onboarding_steps from "./onboarding_steps.ts";
import * as overlays from "./overlays.ts";
import * as peer_data from "./peer_data.ts";
import * as people from "./people.ts";
import * as pm_list from "./pm_list.ts";
import * as reactions from "./reactions.ts";
import * as realm_icon from "./realm_icon.ts";
import * as realm_logo from "./realm_logo.ts";
import * as realm_playground from "./realm_playground.ts";
import {realm_user_settings_defaults} from "./realm_user_settings_defaults.ts";
import * as recent_view_ui from "./recent_view_ui.ts";
import * as reload from "./reload.ts";
import * as reminders_overlay_ui from "./reminders_overlay_ui.ts";
import * as saved_snippets from "./saved_snippets.ts";
import * as saved_snippets_ui from "./saved_snippets_ui.ts";
import * as scheduled_messages from "./scheduled_messages.ts";
import * as scheduled_messages_feed_ui from "./scheduled_messages_feed_ui.ts";
import * as scheduled_messages_overlay_ui from "./scheduled_messages_overlay_ui.ts";
import * as scheduled_messages_ui from "./scheduled_messages_ui.ts";
import * as scroll_bar from "./scroll_bar.ts";
import * as settings_account from "./settings_account.ts";
import * as settings_bots from "./settings_bots.ts";
import * as settings_components from "./settings_components.ts";
import * as settings_config from "./settings_config.ts";
import * as settings_emoji from "./settings_emoji.ts";
import * as settings_exports from "./settings_exports.ts";
import * as settings_invites from "./settings_invites.ts";
import * as settings_linkifiers from "./settings_linkifiers.ts";
import * as settings_notifications from "./settings_notifications.ts";
import * as settings_org from "./settings_org.ts";
import * as settings_playgrounds from "./settings_playgrounds.ts";
import * as settings_preferences from "./settings_preferences.ts";
import * as settings_profile_fields from "./settings_profile_fields.ts";
import * as settings_realm_domains from "./settings_realm_domains.ts";
import * as settings_realm_user_settings_defaults from "./settings_realm_user_settings_defaults.ts";
import * as settings_streams from "./settings_streams.ts";
import * as settings_users from "./settings_users.ts";
import * as sidebar_ui from "./sidebar_ui.ts";
import * as starred_messages from "./starred_messages.ts";
import * as starred_messages_ui from "./starred_messages_ui.ts";
import {current_user, realm} from "./state_data.ts";
import * as stream_data from "./stream_data.ts";
import * as stream_events from "./stream_events.ts";
import * as stream_list from "./stream_list.ts";
import * as stream_list_sort from "./stream_list_sort.ts";
import * as stream_settings_components from "./stream_settings_components.ts";
import * as stream_settings_data from "./stream_settings_data.ts";
import * as stream_settings_ui from "./stream_settings_ui.ts";
import * as stream_topic_history from "./stream_topic_history.ts";
import * as stream_ui_updates from "./stream_ui_updates.ts";
import * as sub_store from "./sub_store.ts";
import * as submessage from "./submessage.ts";
import * as theme from "./theme.ts";
import {group_setting_value_schema} from "./types.ts";
import * as typing_events from "./typing_events.ts";
import * as unread_ops from "./unread_ops.ts";
import * as unread_ui from "./unread_ui.ts";
import * as user_events from "./user_events.ts";
import * as user_group_edit from "./user_group_edit.ts";
import * as user_groups from "./user_groups.ts";
import {user_settings} from "./user_settings.ts";
import * as user_status from "./user_status.ts";
import * as user_topics from "./user_topics.ts";
import * as user_topics_ui from "./user_topics_ui.ts";
export function dispatch_normal_event(event) {
const noop = function () {
// Do nothing
};
switch (event.type) {
case "alert_words":
alert_words.set_words(event.alert_words);
alert_words_ui.rerender_alert_words_ui();
break;
case "attachment":
attachments_ui.update_attachments(event);
break;
case "channel_folder":
switch (event.op) {
case "add": {
channel_folders.add(event.channel_folder);
inbox_ui.complete_rerender();
break;
}
case "update":
channel_folders.update(event);
if (event.data.name !== undefined) {
inbox_ui.complete_rerender();
stream_list.update_streams_sidebar();
stream_ui_updates.update_channel_folder_name(event.channel_folder_id);
}
if (event.data.is_archived !== undefined) {
stream_settings_ui.reset_dropdown_set_to_archived_folder(
event.channel_folder_id,
);
}
break;
default:
blueslip.error("Unexpected event type channel_folder/" + event.op);
break;
}
break;
case "custom_profile_fields":
realm.custom_profile_fields = event.fields;
settings_profile_fields.populate_profile_fields(realm.custom_profile_fields);
settings_account.add_custom_profile_fields_to_settings();
navbar_alerts.maybe_toggle_empty_required_profile_fields_banner();
break;
case "default_streams":
stream_data.set_realm_default_streams(event.default_streams);
settings_streams.update_default_streams_table();
stream_settings_ui.update_is_default_stream();
break;
case "delete_message": {
const msg_ids = event.message_ids;
// message is passed to unread.get_unread_messages,
// which returns all the unread messages out of a given list.
// So double marking something as read would not occur
unread_ops.process_read_messages_event(msg_ids);
// This methods updates message_list too and since stream_topic_history relies on it
// this method should be called first.
message_events.remove_messages(msg_ids);
if (event.message_type === "stream") {
stream_topic_history.remove_messages({
stream_id: event.stream_id,
topic_name: event.topic,
num_messages: msg_ids.length,
max_removed_msg_id: Math.max(...msg_ids),
});
stream_list.update_streams_sidebar();
}
break;
}
case "has_zoom_token":
current_user.has_zoom_token = event.value;
if (event.value) {
for (const callback of compose_call.zoom_token_callbacks.values()) {
callback();
}
compose_call.zoom_token_callbacks.clear();
}
break;
case "onboarding_steps":
onboarding_steps.update_onboarding_steps_to_display(event.onboarding_steps);
break;
case "invites_changed":
if ($("#admin-invites-list").length > 0) {
settings_invites.set_up(false);
}
break;
case "muted_users":
muted_users_ui.handle_user_updates(event.muted_users);
break;
case "navigation_view":
switch (event.op) {
case "add":
navigation_views.add_navigation_view(event.navigation_view);
break;
case "update":
navigation_views.update_navigation_view(event.fragment, event.data);
break;
case "remove":
navigation_views.remove_navigation_view(event.fragment);
break;
}
break;
case "presence":
activity_ui.update_presence_info(event.user_id, event.presence, event.server_timestamp);
break;
case "restart":
realm.zulip_version = event.zulip_version;
realm.zulip_merge_base = event.zulip_merge_base;
break;
case "web_reload_client": {
const reload_options = {
save_compose: true,
reason: "update",
};
if (event.immediate) {
reload_options.immediate = true;
}
reload.initiate(reload_options);
break;
}
case "reaction":
switch (event.op) {
case "add":
reactions.add_reaction(event);
break;
case "remove":
reactions.remove_reaction(event);
break;
default:
blueslip.error("Unexpected event type reaction/" + event.op);
break;
}
message_events.update_views_filtered_on_message_property(
[event.message_id],
"has-reaction",
event.op === "add",
);
break;
case "realm": {
const realm_settings = {
allow_message_editing: noop,
avatar_changes_disabled: settings_account.update_avatar_change_display,
can_access_all_users_group: noop,
can_add_custom_emoji_group: settings_emoji.update_custom_emoji_ui,
can_add_subscribers_group: noop,
can_create_bots_group: settings_bots.update_bot_permissions_ui,
can_create_groups: user_group_edit.update_group_creation_ui,
can_create_private_channel_group: noop,
can_create_public_channel_group: noop,
can_create_web_public_channel_group: noop,
can_create_write_only_bots_group: settings_bots.update_bot_permissions_ui,
can_delete_any_message_group: noop,
can_delete_own_message_group: noop,
can_invite_users_group: noop,
can_manage_all_groups: user_group_edit.update_group_management_ui,
can_manage_billing_group: noop,
can_mention_many_users_group: noop,
can_move_messages_between_channels_group: noop,
can_move_messages_between_topics_group: noop,
can_resolve_topics_group: noop,
can_set_delete_message_policy_group: noop,
can_set_topics_policy_group: noop,
can_summarize_topics_group: noop,
create_multiuse_invite_group: noop,
default_code_block_language: noop,
default_language: noop,
description: noop,
digest_emails_enabled: noop,
digest_weekday: noop,
direct_message_initiator_group: noop,
direct_message_permission_group: noop,
email_changes_disabled: settings_account.update_email_change_display,
disallow_disposable_email_addresses: noop,
inline_image_preview: noop,
inline_url_embed_preview: noop,
invite_required: noop,
message_content_edit_limit_seconds: noop,
message_content_delete_limit_seconds: noop,
message_edit_history_visibility_policy: noop,
move_messages_between_streams_limit_seconds: noop,
move_messages_within_stream_limit_seconds: message_edit.update_inline_topic_edit_ui,
message_retention_days: noop,
name: narrow_title.redraw_title,
name_changes_disabled: settings_account.update_name_change_display,
new_stream_announcements_stream_id: stream_ui_updates.update_announce_stream_option,
org_type: noop,
push_notifications_enabled: noop,
require_unique_names: noop,
send_welcome_emails: noop,
topics_policy: noop,
require_e2ee_push_notifications: noop,
message_content_allowed_in_email_notifications: noop,
enable_spectator_access: noop,
signup_announcements_stream_id: noop,
zulip_update_announcements_stream_id: noop,
emails_restricted_to_domains: noop,
video_chat_provider: compose_call_ui.update_audio_and_video_chat_button_display,
jitsi_server_url: compose_call_ui.update_audio_and_video_chat_button_display,
giphy_rating: giphy.update_giphy_rating,
waiting_period_threshold: noop,
want_advertise_in_communities_directory: noop,
enable_read_receipts: settings_account.update_send_read_receipts_tooltip,
enable_guest_user_dm_warning: compose_validate.warn_if_guest_in_dm_recipient,
enable_guest_user_indicator: noop,
};
switch (event.op) {
case "update":
if (Object.hasOwn(realm_settings, event.property)) {
realm["realm_" + event.property] = event.value;
realm_settings[event.property]();
settings_org.sync_realm_settings(event.property);
if (event.property === "name") {
electron_bridge?.send_event("realm_name", event.value);
}
if (event.property === "enable_spectator_access") {
stream_ui_updates.update_stream_privacy_choices(
"can_create_web_public_channel_group",
);
}
}
break;
case "update_dict":
switch (event.property) {
case "default":
for (const [key, value] of Object.entries(event.data)) {
if (key === "max_file_upload_size_mib") {
realm[key] = value;
} else {
realm["realm_" + key] = value;
}
if (key === "topics_policy") {
compose_recipient.update_topic_inputbox_on_topics_policy_change();
compose_recipient.update_compose_area_placeholder_text();
}
if (Object.hasOwn(realm_settings, key)) {
settings_org.sync_realm_settings(key);
realm_settings[key]();
}
if (
Object.keys(
realm.server_supported_permission_settings.realm,
).includes(key)
) {
user_group_edit.update_realm_setting_in_permissions_panel(
key,
group_setting_value_schema.parse(value),
);
}
if (
key === "create_multiuse_invite_group" ||
key === "can_invite_users_group"
) {
settings_invites.update_invite_user_panel();
sidebar_ui.update_invite_user_option();
gear_menu.rerender();
}
if (
key === "can_create_public_channel_group" ||
key === "can_create_private_channel_group" ||
key === "can_create_web_public_channel_group"
) {
stream_ui_updates.update_stream_privacy_choices(key);
}
if (
key === "direct_message_initiator_group" ||
key === "direct_message_permission_group"
) {
settings_org.check_disable_direct_message_initiator_group_widget();
compose_closed_ui.update_buttons_for_private();
compose_recipient.check_posting_policy_for_compose_box();
}
if (
key === "can_move_messages_between_topics_group" ||
key === "can_resolve_topics_group"
) {
// Technically we just need to rerender the message recipient
// bars to update the buttons for editing or resolving a topic,
// but because these policies are changed rarely, it's fine to
// rerender the entire message feed.
message_live_update.rerender_messages_view();
}
if (key === "plan_type") {
gear_menu.rerender();
}
if (
key === "can_add_subscribers_group" &&
overlays.streams_open()
) {
const active_stream_id =
stream_settings_components.get_active_data().id;
if (active_stream_id !== undefined) {
const slim_sub = sub_store.get(active_stream_id);
const sub =
stream_settings_data.get_sub_for_settings(slim_sub);
stream_ui_updates.update_add_subscriptions_elements(sub);
}
}
}
if (event.data.authentication_methods !== undefined) {
settings_org.populate_auth_methods(
settings_components.realm_authentication_methods_to_boolean_dict(),
);
}
break;
case "icon":
realm.realm_icon_url = event.data.icon_url;
realm.realm_icon_source = event.data.icon_source;
realm_icon.rerender();
electron_bridge?.send_event("realm_icon_url", event.data.icon_url);
break;
case "logo":
realm.realm_logo_url = event.data.logo_url;
realm.realm_logo_source = event.data.logo_source;
realm_logo.render();
break;
case "night_logo":
realm.realm_night_logo_url = event.data.night_logo_url;
realm.realm_night_logo_source = event.data.night_logo_source;
realm_logo.render();
break;
default:
blueslip.error(
"Unexpected event type realm/update_dict/" + event.property,
);
break;
}
break;
case "deactivated":
// This handler is likely unnecessary, in that if we
// did nothing here, we'd reload and end up at the
// same place when we attempt the next `GET /events`
// and get an error. Some clients will do that even
// with this code, if they didn't have an active
// longpoll waiting at the moment the realm was
// deactivated.
window.location.href = "/accounts/deactivated/";
break;
}
if (current_user.is_admin) {
// Update the UI notice about the user's profile being
// incomplete, as we might have filled in the missing field(s).
navbar_alerts.toggle_organization_profile_incomplete_banner();
}
break;
}
case "realm_bot":
switch (event.op) {
case "add":
bot_data.add(event.bot);
settings_bots.render_bots();
break;
case "delete":
bot_data.del(event.bot.user_id);
settings_bots.render_bots();
break;
case "update":
bot_data.update(event.bot.user_id, event.bot);
settings_bots.render_bots();
break;
default:
blueslip.error("Unexpected event type realm_bot/" + event.op);
break;
}
break;
case "realm_emoji":
// The authoritative data source is here.
emoji.update_emojis(event.realm_emoji);
// And then let other widgets know.
settings_emoji.populate_emoji();
emoji_picker.rebuild_catalog();
break;
case "realm_export":
settings_exports.populate_exports_table(event.exports);
break;
case "realm_export_consent":
settings_exports.update_export_consent_data_and_redraw({
user_id: event.user_id,
consented: event.consented,
});
break;
case "realm_linkifiers":
realm.realm_linkifiers = event.realm_linkifiers;
linkifiers.update_linkifier_rules(realm.realm_linkifiers);
settings_linkifiers.populate_linkifiers(realm.realm_linkifiers);
break;
case "realm_playgrounds":
realm.realm_playgrounds = event.realm_playgrounds;
realm_playground.update_playgrounds(realm.realm_playgrounds);
settings_playgrounds.populate_playgrounds(realm.realm_playgrounds);
break;
case "realm_domains":
{
let i;
switch (event.op) {
case "add":
realm.realm_domains.push(event.realm_domain);
settings_org.populate_realm_domains_label(realm.realm_domains);
settings_realm_domains.populate_realm_domains_table(realm.realm_domains);
break;
case "change":
for (i = 0; i < realm.realm_domains.length; i += 1) {
if (realm.realm_domains[i].domain === event.realm_domain.domain) {
realm.realm_domains[i].allow_subdomains =
event.realm_domain.allow_subdomains;
break;
}
}
settings_org.populate_realm_domains_label(realm.realm_domains);
settings_realm_domains.populate_realm_domains_table(realm.realm_domains);
break;
case "remove":
for (i = 0; i < realm.realm_domains.length; i += 1) {
if (realm.realm_domains[i].domain === event.domain) {
realm.realm_domains.splice(i, 1);
break;
}
}
settings_org.populate_realm_domains_label(realm.realm_domains);
settings_realm_domains.populate_realm_domains_table(realm.realm_domains);
break;
default:
blueslip.error("Unexpected event type realm_domains/" + event.op);
break;
}
}
break;
case "realm_user_settings_defaults": {
realm_user_settings_defaults[event.property] = event.value;
settings_realm_user_settings_defaults.update_page(event.property);
if (event.property === "notification_sound") {
audible_notifications.update_notification_sound_source(
$("audio#realm-default-notification-sound-audio"),
realm_user_settings_defaults,
);
}
break;
}
case "realm_user":
switch (event.op) {
case "add": {
// There may be presence data we already received from the server
// before getting this event. Check if we need to redraw.
const should_redraw = activity_ui.check_should_redraw_new_user(
event.person.user_id,
);
people.add_active_user(event.person, "server_events");
settings_account.maybe_update_deactivate_account_button();
if (event.person.is_bot) {
settings_users.redraw_bots_list();
}
if (should_redraw) {
activity_ui.redraw_user(event.person.user_id);
}
if (!event.person.is_bot) {
settings_exports.update_export_consent_data_and_redraw({
user_id: event.person.user_id,
consented: false,
});
}
break;
}
case "update":
user_events.update_person(event.person);
settings_account.maybe_update_deactivate_account_button();
if (people.is_valid_bot_user(event.person.user_id)) {
settings_users.update_bot_data(event.person.user_id);
}
break;
case "remove": {
const user_id = event.person.user_id;
people.remove_inaccessible_user(user_id);
buddy_list.maybe_remove_user_id({user_id});
message_live_update.update_user_full_name(
user_id,
people.INACCESSIBLE_USER_NAME,
);
break;
}
default:
blueslip.error("Unexpected event type realm_user/" + event.op);
break;
}
break;
case "saved_snippets":
switch (event.op) {
case "add":
saved_snippets.update_saved_snippet_dict(event.saved_snippet);
saved_snippets_ui.rerender_dropdown_widget();
break;
case "remove":
saved_snippets.remove_saved_snippet(event.saved_snippet_id);
saved_snippets_ui.rerender_dropdown_widget();
break;
case "update":
saved_snippets.update_saved_snippet_dict(event.saved_snippet);
saved_snippets_ui.rerender_dropdown_widget();
break;
}
break;
case "scheduled_messages":
switch (event.op) {
case "add": {
scheduled_messages.add_scheduled_messages(event.scheduled_messages);
scheduled_messages_feed_ui.update_schedule_message_indicator();
scheduled_messages_overlay_ui.rerender();
left_sidebar_navigation_area.update_scheduled_messages_row();
break;
}
case "remove": {
scheduled_messages.remove_scheduled_message(event.scheduled_message_id);
scheduled_messages_feed_ui.update_schedule_message_indicator();
scheduled_messages_ui.hide_scheduled_message_success_compose_banner(
event.scheduled_message_id,
);
scheduled_messages_overlay_ui.remove_scheduled_message_id(
event.scheduled_message_id,
);
left_sidebar_navigation_area.update_scheduled_messages_row();
break;
}
case "update": {
scheduled_messages.update_scheduled_message(event.scheduled_message);
scheduled_messages_overlay_ui.rerender();
left_sidebar_navigation_area.update_scheduled_messages_row();
break;
}
// No default
}
break;
case "reminders":
switch (event.op) {
case "add": {
message_reminder.add_reminders(event.reminders);
reminders_overlay_ui.rerender();
left_sidebar_navigation_area.update_reminders_row();
break;
}
case "remove": {
message_reminder.remove_reminder(event.reminder_id);
reminders_overlay_ui.remove_reminder_id(event.reminder_id);
left_sidebar_navigation_area.update_reminders_row();
break;
}
// No default
}
break;
case "stream":
switch (event.op) {
case "update":
stream_events.update_property(event.stream_id, event.property, event.value, {
rendered_description: event.rendered_description,
history_public_to_subscribers: event.history_public_to_subscribers,
is_web_public: event.is_web_public,
});
settings_streams.update_default_streams_table();
stream_list.update_subscribe_to_more_streams_link();
break;
case "create":
stream_data.create_streams(event.streams);
for (const stream of event.streams) {
const sub = sub_store.get(stream.stream_id);
if (overlays.streams_open()) {
stream_settings_ui.add_sub_to_table(sub);
}
}
stream_list.update_subscribe_to_more_streams_link();
break;
case "delete":
for (const stream_id of event.stream_ids) {
const was_subscribed = sub_store.get(stream_id).subscribed;
stream_data.delete_sub(stream_id);
stream_settings_ui.remove_stream(stream_id);
if (was_subscribed) {
stream_list.remove_sidebar_row(stream_id);
if (stream_id === compose_state.selected_recipient_id) {
compose_state.set_selected_recipient_id("");
compose_recipient.on_compose_select_recipient_update();
}
}
settings_streams.update_default_streams_table();
stream_data.remove_default_stream(stream_id);
if (realm.realm_new_stream_announcements_stream_id === stream_id) {
settings_org.sync_realm_settings("new_stream_announcements_stream_id");
}
if (realm.realm_signup_announcements_stream_id === stream_id) {
settings_org.sync_realm_settings("signup_announcements_stream_id");
}
if (realm.realm_zulip_update_announcements_stream_id === stream_id) {
settings_org.sync_realm_settings(
"zulip_update_announcements_stream_id",
);
}
const message_ids = message_store.get_message_ids_in_stream(stream_id);
unread_ops.process_read_messages_event(message_ids);
message_events.remove_messages(message_ids);
stream_topic_history.remove_history_for_stream(stream_id);
}
stream_list.update_subscribe_to_more_streams_link();
break;
default:
blueslip.error("Unexpected event type stream/" + event.op);
break;
}
break;
case "submessage": {
// The fields in the event don't quite exactly
// match the layout of a submessage, since there's
// an event id. We also want to be explicit here.
const submsg = {
id: event.submessage_id,
sender_id: event.sender_id,
msg_type: event.msg_type,
message_id: event.message_id,
content: event.content,
};
submessage.handle_event(submsg);
break;
}
case "subscription":
switch (event.op) {
case "add":
for (const rec of event.subscriptions) {
const sub = sub_store.get(rec.stream_id);
if (sub) {
stream_events.mark_subscribed(sub, rec.subscribers, rec.color);
} else {
blueslip.error("Subscribing to unknown stream", {
stream_id: rec.stream_id,
});
}
}
break;
case "peer_add": {
const stream_ids = sub_store.validate_stream_ids(event.stream_ids);
const user_ids = people.validate_user_ids(event.user_ids);
peer_data.bulk_add_subscribers({stream_ids, user_ids});
stream_events.process_subscriber_update(user_ids, stream_ids);
break;
}
case "peer_remove": {
const stream_ids = sub_store.validate_stream_ids(event.stream_ids);
const user_ids = people.validate_user_ids(event.user_ids);
peer_data.bulk_remove_subscribers({stream_ids, user_ids});
stream_events.process_subscriber_update(user_ids, stream_ids);
break;
}
case "remove":
for (const rec of event.subscriptions) {
const sub = sub_store.get(rec.stream_id);
stream_events.mark_unsubscribed(sub);
}
break;
case "update":
stream_events.update_property(event.stream_id, event.property, event.value);
break;
default:
blueslip.error("Unexpected event type subscription/" + event.op);
break;
}
break;
case "typing":
if (event.sender.user_id === current_user.user_id) {
// typing notifications are sent to the user who is typing
// as well as recipients; we ignore such self-generated events.
return;
}
switch (event.op) {
case "start":
typing_events.display_notification(event);
break;
case "stop":
typing_events.hide_notification(event);
break;
default:
blueslip.error("Unexpected event type typing/" + event.op);
break;
}
break;
case "typing_edit_message":
if (event.sender_id === current_user.user_id) {
// typing edit message notifications are sent to the user who is typing
// as well as recipients; we ignore such self-generated events.
return;
}
switch (event.op) {
case "start":
typing_events.display_message_edit_notification(event);
break;
case "stop":
typing_events.hide_message_edit_notification(event);
break;
default:
blueslip.error("Unexpected event type typing_edit_message/" + event.op);
break;
}
break;
case "user_settings": {
const notification_name = event.property;
if (settings_config.all_notification_settings.includes(notification_name)) {
// Update the global settings checked when determining if we should notify
// for a given message. These settings do not affect whether or not a
// particular stream should receive notifications.
user_settings[notification_name] = event.value;
if (settings_config.stream_notification_settings.includes(notification_name)) {
stream_ui_updates.update_notification_setting_checkbox(
settings_config.specialize_stream_notification_setting[notification_name],
);
}
if (notification_name === "notification_sound") {
// Change the sound source with the new page `notification_sound`.
audible_notifications.update_notification_sound_source(
$("audio#user-notification-sound-audio"),
user_settings,
);
}
settings_notifications.update_page(settings_notifications.user_settings_panel);
break;
}
// TODO/typescript: Move privacy_setting_name_schema and PrivacySettingName
// here from `settings_account` when this file is converted to typescript,
// and use them instead of `privacy_settings`.
const privacy_settings = [
"allow_private_data_export",
"email_address_visibility",
"presence_enabled",
"send_private_typing_notifications",
"send_read_receipts",
"send_stream_typing_notifications",
];
if (privacy_settings.includes(event.property)) {
user_settings[event.property] = event.value;
settings_account.update_privacy_settings_box(event.property);
if (event.property === "presence_enabled") {
activity_ui.redraw_user(current_user.user_id);
}
if (event.property === "allow_private_data_export") {
settings_exports.refresh_allow_private_data_export_banner();
}
break;
}
const user_preferences = [
"color_scheme",
"default_language",
"demote_inactive_streams",
"display_emoji_reaction_users",
"emojiset",
"enter_sends",
"fluid_layout_width",
"hide_ai_features",
"high_contrast_mode",
"receives_typing_notifications",
"resolved_topic_notice_auto_read_policy",
"starred_message_counts",
"timezone",
"translate_emoticons",
"twenty_four_hour_time",
"user_list_style",
"web_animate_image_previews",
"web_channel_default_view",
"web_escape_navigates_to_home_view",
"web_home_view",
"web_mark_read_on_scroll_policy",
"web_navigate_to_sent_message",
"web_stream_unreads_count_display_policy",
"web_suggest_update_timezone",
"web_left_sidebar_unreads_count_summary",
"web_left_sidebar_show_channel_folders",
];
const original_home_view = user_settings.web_home_view;
if (user_preferences.includes(event.property)) {
user_settings[event.property] = event.value;
}
if (event.property === "default_language") {
// We additionally need to set the language name.
//
// Note that this does not change translations at all;
// a reload is fundamentally required because we
// cannot rerender with the new language the strings
// present in the backend/Jinja2 templates.
settings_preferences.set_default_language_name(event.language_name);
}
if (event.property === "web_home_view") {
left_sidebar_navigation_area.handle_home_view_changed(event.value);
// If current hash is empty (home view), and the
// user changes the home view while in settings,
// then going back to an empty hash on closing the
// overlay will not match the view currently displayed
// under settings, so we set the hash to the previous
// value of the home view.
if (!browser_history.state.hash_before_overlay && overlays.settings_open()) {
browser_history.state.hash_before_overlay =
"#" +
(original_home_view === "recent_topics" ? "recent" : original_home_view);
}
}
if (event.property === "twenty_four_hour_time") {
// Recalculate timestamp column width
information_density.calculate_timestamp_widths();
// Rerender the whole message list UI
for (const msg_list of message_lists.all_rendered_message_lists()) {
msg_list.rerender();
}
}
if (event.property === "high_contrast_mode") {
$("body").toggleClass("high-contrast", event.value);
}
if (event.property === "demote_inactive_streams") {
stream_list_sort.set_filter_out_inactives();
stream_list.update_streams_sidebar();
}
if (event.property === "web_animate_image_previews") {
// Rerender the whole message list UI
for (const msg_list of message_lists.all_rendered_message_lists()) {
msg_list.rerender();
}
}
if (event.property === "web_stream_unreads_count_display_policy") {
stream_list.build_stream_list(true);
}
if (event.property === "user_list_style") {
settings_preferences.report_user_list_style_change(
settings_preferences.user_settings_panel,
);
activity_ui.build_user_sidebar();
}
if (
event.property === "web_font_size_px" ||
event.property === "web_line_height_percent"
) {
// We just ignore events for web_font_size_px"
// and "web_line_height_percent" settings as we
// are fine with a window not being updated due
// to changes being done from another window and
// also helps in avoiding weird issues on clicking
// the "+"/"-" buttons multiple times quickly when
// updating these settings.
}
if (event.property === "web_mark_read_on_scroll_policy") {
unread_ui.update_unread_banner();
}
if (event.property === "color_scheme") {
requestAnimationFrame(() => {
theme.set_theme_and_update(event.value);
});
}
if (event.property === "starred_message_counts") {
starred_messages_ui.rerender_ui();
}
if (event.property === "web_left_sidebar_unreads_count_summary") {
sidebar_ui.update_unread_counts_visibility();
}
if (event.property === "web_left_sidebar_show_channel_folders") {
stream_list.build_stream_list(true);
}
if (
event.property === "receives_typing_notifications" &&
!user_settings.receives_typing_notifications
) {
typing_events.disable_typing_notification();
}
if (event.property === "fluid_layout_width") {
scroll_bar.set_layout_width();
}
if (event.property === "default_language") {
// TODO: Make this change the view immediately rather than
// requiring a reload. This is likely fairly difficult,
// because various i18n strings are rendered by the
// server; we may want to instead just trigger a page
// reload.
}
if (event.property === "emojiset") {
settings_preferences.report_emojiset_change(
settings_preferences.user_settings_panel,
);
// Rerender the whole message list UI
for (const msg_list of message_lists.all_rendered_message_lists()) {
msg_list.rerender();
}
// Rerender buddy list status emoji
activity_ui.build_user_sidebar();
}
if (event.property === "display_emoji_reaction_users") {
message_live_update.rerender_messages_view();
}
if (event.property === "web_escape_navigates_to_home_view") {
$("#go-to-home-view-hotkey-help").toggleClass("notdisplayed", !event.value);
}
if (event.property === "web_suggest_update_timezone") {
$("#automatically_offer_update_time_zone").prop("checked", event.value);
}
if (event.property === "web_channel_default_view") {
// We need to rerender wherever `channel_url_by_user_setting` is used in the DOM.
// Left sidebar
const force_rerender = true;
stream_list.create_initial_sidebar_rows(force_rerender);
stream_list.update_streams_sidebar(force_rerender);
// Inbox View
inbox_ui.complete_rerender();
// Recent View
recent_view_ui.complete_rerender();
// Message feed
for (const msg_list of message_lists.all_rendered_message_lists()) {
msg_list.rerender();
}
}
settings_preferences.update_page(event.property);
break;
}
case "update_message_flags": {
const new_value = event.op === "add";
switch (event.flag) {
case "starred":
for (const message_id of event.messages) {
starred_messages_ui.update_starred_flag(message_id, new_value);
}
if (event.op === "add") {
starred_messages.add(event.messages);
starred_messages_ui.rerender_ui();
} else {
starred_messages.remove(event.messages);
starred_messages_ui.rerender_ui();
}
message_events.update_views_filtered_on_message_property(
event.messages,
"is-starred",
new_value,
);
break;
case "read":
if (event.op === "add") {
unread_ops.process_read_messages_event(event.messages);
} else {
unread_ops.process_unread_messages_event({
message_ids: event.messages,
message_details: event.message_details,
});
}
message_events.update_views_filtered_on_message_property(
event.messages,
"is-unread",
new_value,
);
break;
}
break;
}
case "user_group":
switch (event.op) {
case "add": {
const user_group = user_groups.add(event.group);
if (overlays.groups_open()) {
user_group_edit.add_group_to_table(user_group);
}
break;
}
case "add_members":
user_groups.add_members(event.group_id, event.user_ids);
user_group_edit.handle_member_edit_event(event.group_id, event.user_ids);
break;
case "remove_members":
user_groups.remove_members(event.group_id, event.user_ids);
user_group_edit.handle_member_edit_event(event.group_id, event.user_ids);
break;
case "add_subgroups":
user_groups.add_subgroups(event.group_id, event.direct_subgroup_ids);
user_group_edit.handle_subgroup_edit_event(
event.group_id,
event.direct_subgroup_ids,
);
break;
case "remove_subgroups":
user_groups.remove_subgroups(event.group_id, event.direct_subgroup_ids);
user_group_edit.handle_subgroup_edit_event(
event.group_id,
event.direct_subgroup_ids,
);
break;
case "update": {
const group_id = event.group_id;
const group = user_groups.get_user_group_from_id(group_id);
user_groups.update(event, group);
user_group_edit.update_group(event, group);
break;
}
default:
blueslip.error("Unexpected event type user_group/" + event.op);
break;
}
break;
case "user_status":
if (event.status_text !== undefined) {
user_status.set_status_text({
user_id: event.user_id,
status_text: event.status_text,
});
activity_ui.redraw_user(event.user_id);
// Update the status text in compose box placeholder when opened to self.
if (compose_pm_pill.get_user_ids().includes(event.user_id)) {
compose_recipient.update_compose_area_placeholder_text();
}
}
if (event.emoji_name !== undefined) {
user_status.set_status_emoji(event);
activity_ui.redraw_user(event.user_id);
pm_list.update_private_messages();
message_live_update.update_user_status_emoji(
event.user_id,
user_status.get_status_emoji(event.user_id),
);
}
break;
case "user_topic": {
const previous_topic_visibility = user_topics.get_topic_visibility_policy(
event.stream_id,
event.topic_name,
);
user_topics_ui.handle_topic_updates(
event,
message_events.update_current_view_for_topic_visibility(),
message_view.rerender_combined_feed,
);
// Discard cached message lists if `event` topic was / is followed.
if (
event.visibility_policy === user_topics.all_visibility_policies.FOLLOWED ||
previous_topic_visibility === user_topics.all_visibility_policies.FOLLOWED
) {
message_events.discard_cached_lists_with_term_type("is-followed");
}
break;
}
}
}