mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 12:03:46 +00:00 
			
		
		
		
	Co-authored-by: Steve Howell <showell@zulip.com> Co-authored-by: Tim Abbott <tabbott@zulip.com> This commit adds the backend functionality to mark messages as unread through update_message_flags with `unread` flag and `remove` operation. We also manage incoming events in the webapp. Tweaked by tabbott to simplify the implementation and add an API feature level update to the documentation. This commit was originally drafted by showell, and showell also finalized the changes. Many thanks to Suyash here for the main work here, which was to get all the tests and documentation work moving forward.
		
			
				
	
	
		
			796 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			796 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import $ from "jquery";
 | |
| 
 | |
| import * as emoji from "../shared/js/emoji";
 | |
| 
 | |
| import * as activity from "./activity";
 | |
| import * as alert_words from "./alert_words";
 | |
| import * as alert_words_ui from "./alert_words_ui";
 | |
| import * as attachments_ui from "./attachments_ui";
 | |
| import * as blueslip from "./blueslip";
 | |
| import * as bot_data from "./bot_data";
 | |
| import {buddy_list} from "./buddy_list";
 | |
| import * as compose from "./compose";
 | |
| import * as compose_actions from "./compose_actions";
 | |
| import * as compose_fade from "./compose_fade";
 | |
| import * as compose_pm_pill from "./compose_pm_pill";
 | |
| import * as composebox_typeahead from "./composebox_typeahead";
 | |
| import * as dark_theme from "./dark_theme";
 | |
| import * as emoji_picker from "./emoji_picker";
 | |
| import * as giphy from "./giphy";
 | |
| import * as hotspots from "./hotspots";
 | |
| import * as linkifiers from "./linkifiers";
 | |
| import * as message_edit from "./message_edit";
 | |
| import * as message_events from "./message_events";
 | |
| import * as message_flags from "./message_flags";
 | |
| import * as message_list from "./message_list";
 | |
| import * as message_lists from "./message_lists";
 | |
| import * as message_live_update from "./message_live_update";
 | |
| import * as muted_topics_ui from "./muted_topics_ui";
 | |
| import * as muted_users_ui from "./muted_users_ui";
 | |
| import * as narrow_state from "./narrow_state";
 | |
| import * as navbar_alerts from "./navbar_alerts";
 | |
| import * as notifications from "./notifications";
 | |
| import * as overlays from "./overlays";
 | |
| import {page_params} from "./page_params";
 | |
| import * as peer_data from "./peer_data";
 | |
| import * as people from "./people";
 | |
| import * as pm_list from "./pm_list";
 | |
| import * as reactions from "./reactions";
 | |
| import * as realm_icon from "./realm_icon";
 | |
| import * as realm_logo from "./realm_logo";
 | |
| import * as realm_playground from "./realm_playground";
 | |
| import {realm_user_settings_defaults} from "./realm_user_settings_defaults";
 | |
| import * as reload from "./reload";
 | |
| import * as scroll_bar from "./scroll_bar";
 | |
| import * as settings_account from "./settings_account";
 | |
| import * as settings_bots from "./settings_bots";
 | |
| import * as settings_config from "./settings_config";
 | |
| import * as settings_display from "./settings_display";
 | |
| import * as settings_emoji from "./settings_emoji";
 | |
| import * as settings_exports from "./settings_exports";
 | |
| import * as settings_invites from "./settings_invites";
 | |
| import * as settings_linkifiers from "./settings_linkifiers";
 | |
| import * as settings_notifications from "./settings_notifications";
 | |
| import * as settings_org from "./settings_org";
 | |
| import * as settings_playgrounds from "./settings_playgrounds";
 | |
| import * as settings_profile_fields from "./settings_profile_fields";
 | |
| import * as settings_realm_user_settings_defaults from "./settings_realm_user_settings_defaults";
 | |
| import * as settings_streams from "./settings_streams";
 | |
| import * as settings_user_groups from "./settings_user_groups";
 | |
| import * as settings_users from "./settings_users";
 | |
| import * as starred_messages from "./starred_messages";
 | |
| import * as stream_data from "./stream_data";
 | |
| import * as stream_events from "./stream_events";
 | |
| import * as stream_list from "./stream_list";
 | |
| import * as stream_settings_ui from "./stream_settings_ui";
 | |
| import * as stream_topic_history from "./stream_topic_history";
 | |
| import * as sub_store from "./sub_store";
 | |
| import * as submessage from "./submessage";
 | |
| import * as typing_events from "./typing_events";
 | |
| import * as unread_ops from "./unread_ops";
 | |
| import * as user_events from "./user_events";
 | |
| import * as user_groups from "./user_groups";
 | |
| import {user_settings} from "./user_settings";
 | |
| import * as user_status from "./user_status";
 | |
| 
 | |
| export function dispatch_normal_event(event) {
 | |
|     const noop = function () {};
 | |
|     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 "custom_profile_fields":
 | |
|             page_params.custom_profile_fields = event.fields;
 | |
|             settings_profile_fields.populate_profile_fields(page_params.custom_profile_fields);
 | |
|             settings_account.add_custom_profile_fields_to_settings();
 | |
|             break;
 | |
| 
 | |
|         case "default_streams":
 | |
|             stream_data.set_realm_default_streams(event.default_streams);
 | |
|             settings_streams.update_default_streams_table();
 | |
|             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":
 | |
|             page_params.has_zoom_token = event.value;
 | |
|             if (event.value) {
 | |
|                 for (const callback of compose.zoom_token_callbacks.values()) {
 | |
|                     callback();
 | |
|                 }
 | |
|                 compose.zoom_token_callbacks.clear();
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case "hotspots":
 | |
|             hotspots.load_new(event.hotspots);
 | |
|             page_params.hotspots = page_params.hotspots
 | |
|                 ? page_params.hotspots.concat(event.hotspots)
 | |
|                 : event.hotspots;
 | |
|             break;
 | |
| 
 | |
|         case "invites_changed":
 | |
|             if ($("#admin-invites-list").length) {
 | |
|                 settings_invites.set_up(false);
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case "muted_topics":
 | |
|             muted_topics_ui.handle_topic_updates(event.muted_topics);
 | |
|             break;
 | |
| 
 | |
|         case "muted_users":
 | |
|             muted_users_ui.handle_user_updates(event.muted_users);
 | |
|             break;
 | |
| 
 | |
|         case "presence":
 | |
|             activity.update_presence_info(event.user_id, event.presence, event.server_timestamp);
 | |
|             break;
 | |
| 
 | |
|         case "restart": {
 | |
|             const reload_options = {
 | |
|                 save_pointer: true,
 | |
|                 save_narrow: true,
 | |
|                 save_compose: true,
 | |
|                 message_html: "The application has been updated; reloading!",
 | |
|             };
 | |
|             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;
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case "realm": {
 | |
|             const realm_settings = {
 | |
|                 add_custom_emoji_policy: settings_emoji.update_custom_emoji_ui,
 | |
|                 allow_edit_history: noop,
 | |
|                 allow_message_editing: noop,
 | |
|                 edit_topic_policy: noop,
 | |
|                 user_group_edit_policy: noop,
 | |
|                 avatar_changes_disabled: settings_account.update_avatar_change_display,
 | |
|                 bot_creation_policy: settings_bots.update_bot_permissions_ui,
 | |
|                 create_public_stream_policy: noop,
 | |
|                 create_private_stream_policy: noop,
 | |
|                 create_web_public_stream_policy: noop,
 | |
|                 invite_to_stream_policy: noop,
 | |
|                 default_code_block_language: noop,
 | |
|                 default_language: noop,
 | |
|                 delete_own_message_policy: noop,
 | |
|                 description: noop,
 | |
|                 digest_emails_enabled: noop,
 | |
|                 digest_weekday: noop,
 | |
|                 email_address_visibility: 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_to_realm_policy: noop,
 | |
|                 invite_required: noop,
 | |
|                 mandatory_topics: noop,
 | |
|                 message_content_edit_limit_seconds: noop,
 | |
|                 message_content_delete_limit_seconds: noop,
 | |
|                 message_retention_days: noop,
 | |
|                 name: notifications.redraw_title,
 | |
|                 name_changes_disabled: settings_account.update_name_change_display,
 | |
|                 notifications_stream_id: noop,
 | |
|                 private_message_policy: noop,
 | |
|                 send_welcome_emails: noop,
 | |
|                 message_content_allowed_in_email_notifications: noop,
 | |
|                 enable_spectator_access: noop,
 | |
|                 signup_notifications_stream_id: noop,
 | |
|                 emails_restricted_to_domains: noop,
 | |
|                 video_chat_provider: compose.update_video_chat_button_display,
 | |
|                 giphy_rating: giphy.update_giphy_rating,
 | |
|                 waiting_period_threshold: noop,
 | |
|                 wildcard_mention_policy: noop,
 | |
|             };
 | |
|             switch (event.op) {
 | |
|                 case "update":
 | |
|                     if (Object.hasOwn(realm_settings, event.property)) {
 | |
|                         page_params["realm_" + event.property] = event.value;
 | |
|                         realm_settings[event.property]();
 | |
|                         settings_org.sync_realm_settings(event.property);
 | |
| 
 | |
|                         if (event.property === "name" && window.electron_bridge !== undefined) {
 | |
|                             window.electron_bridge.send_event("realm_name", event.value);
 | |
|                         }
 | |
| 
 | |
|                         const stream_creation_settings = [
 | |
|                             "create_private_stream_policy",
 | |
|                             "create_public_stream_policy",
 | |
|                             "create_web_public_stream_policy",
 | |
|                         ];
 | |
|                         if (stream_creation_settings.includes(event.property)) {
 | |
|                             stream_settings_ui.update_stream_privacy_choices(event.property);
 | |
|                         }
 | |
| 
 | |
|                         if (event.property === "enable_spectator_access") {
 | |
|                             stream_settings_ui.update_stream_privacy_choices(
 | |
|                                 "create_web_public_stream_policy",
 | |
|                             );
 | |
|                         }
 | |
|                     }
 | |
|                     break;
 | |
|                 case "update_dict":
 | |
|                     switch (event.property) {
 | |
|                         case "default":
 | |
|                             for (const [key, value] of Object.entries(event.data)) {
 | |
|                                 page_params["realm_" + key] = value;
 | |
|                                 if (key === "allow_message_editing") {
 | |
|                                     message_edit.update_message_topic_editing_pencil();
 | |
|                                 }
 | |
|                                 if (Object.hasOwn(realm_settings, key)) {
 | |
|                                     settings_org.sync_realm_settings(key);
 | |
|                                 }
 | |
|                             }
 | |
|                             if (event.data.authentication_methods !== undefined) {
 | |
|                                 settings_org.populate_auth_methods(
 | |
|                                     event.data.authentication_methods,
 | |
|                                 );
 | |
|                             }
 | |
|                             break;
 | |
|                         case "icon":
 | |
|                             page_params.realm_icon_url = event.data.icon_url;
 | |
|                             page_params.realm_icon_source = event.data.icon_source;
 | |
|                             realm_icon.rerender();
 | |
|                             {
 | |
|                                 const electron_bridge = window.electron_bridge;
 | |
|                                 if (electron_bridge !== undefined) {
 | |
|                                     electron_bridge.send_event(
 | |
|                                         "realm_icon_url",
 | |
|                                         event.data.icon_url,
 | |
|                                     );
 | |
|                                 }
 | |
|                             }
 | |
|                             break;
 | |
|                         case "logo":
 | |
|                             page_params.realm_logo_url = event.data.logo_url;
 | |
|                             page_params.realm_logo_source = event.data.logo_source;
 | |
|                             realm_logo.render();
 | |
|                             break;
 | |
|                         case "night_logo":
 | |
|                             page_params.realm_night_logo_url = event.data.night_logo_url;
 | |
|                             page_params.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 (page_params.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.show_profile_incomplete(navbar_alerts.check_profile_incomplete());
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         case "realm_bot":
 | |
|             switch (event.op) {
 | |
|                 case "add":
 | |
|                     bot_data.add(event.bot);
 | |
|                     settings_bots.render_bots();
 | |
|                     settings_users.update_bot_data(event.bot.user_id);
 | |
|                     break;
 | |
|                 case "remove":
 | |
|                     bot_data.deactivate(event.bot.user_id);
 | |
|                     event.bot.is_active = false;
 | |
|                     settings_bots.render_bots();
 | |
|                     settings_users.update_bot_data(event.bot.user_id);
 | |
|                     break;
 | |
|                 case "delete":
 | |
|                     blueslip.info("ignoring bot deletion for live UI update");
 | |
|                     break;
 | |
|                 case "update":
 | |
|                     bot_data.update(event.bot.user_id, event.bot);
 | |
|                     settings_bots.render_bots();
 | |
|                     settings_users.update_bot_data(event.bot.user_id);
 | |
|                     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();
 | |
|             composebox_typeahead.update_emoji_data();
 | |
|             break;
 | |
| 
 | |
|         case "realm_linkifiers":
 | |
|             page_params.realm_linkifiers = event.realm_linkifiers;
 | |
|             linkifiers.update_linkifier_rules(page_params.realm_linkifiers);
 | |
|             settings_linkifiers.populate_linkifiers(page_params.realm_linkifiers);
 | |
|             break;
 | |
| 
 | |
|         case "realm_playgrounds":
 | |
|             page_params.realm_playgrounds = event.realm_playgrounds;
 | |
|             realm_playground.update_playgrounds(page_params.realm_playgrounds);
 | |
|             settings_playgrounds.populate_playgrounds(page_params.realm_playgrounds);
 | |
|             break;
 | |
| 
 | |
|         case "realm_domains":
 | |
|             {
 | |
|                 let i;
 | |
|                 switch (event.op) {
 | |
|                     case "add":
 | |
|                         page_params.realm_domains.push(event.realm_domain);
 | |
|                         settings_org.populate_realm_domains(page_params.realm_domains);
 | |
|                         break;
 | |
|                     case "change":
 | |
|                         for (i = 0; i < page_params.realm_domains.length; i += 1) {
 | |
|                             if (page_params.realm_domains[i].domain === event.realm_domain.domain) {
 | |
|                                 page_params.realm_domains[i].allow_subdomains =
 | |
|                                     event.realm_domain.allow_subdomains;
 | |
|                                 break;
 | |
|                             }
 | |
|                         }
 | |
|                         settings_org.populate_realm_domains(page_params.realm_domains);
 | |
|                         break;
 | |
|                     case "remove":
 | |
|                         for (i = 0; i < page_params.realm_domains.length; i += 1) {
 | |
|                             if (page_params.realm_domains[i].domain === event.domain) {
 | |
|                                 page_params.realm_domains.splice(i, 1);
 | |
|                                 break;
 | |
|                             }
 | |
|                         }
 | |
|                         settings_org.populate_realm_domains(page_params.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") {
 | |
|                 notifications.update_notification_sound_source(
 | |
|                     $("#realm-default-notification-sound-audio"),
 | |
|                     realm_user_settings_defaults,
 | |
|                 );
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         case "realm_user":
 | |
|             switch (event.op) {
 | |
|                 case "add":
 | |
|                     people.add_active_user(event.person);
 | |
|                     break;
 | |
|                 case "remove":
 | |
|                     people.deactivate(event.person);
 | |
|                     stream_events.remove_deactivated_user_from_all_streams(event.person.user_id);
 | |
|                     settings_users.update_view_on_deactivate(event.person.user_id);
 | |
|                     buddy_list.maybe_remove_key({key: event.person.user_id});
 | |
|                     break;
 | |
|                 case "update":
 | |
|                     user_events.update_person(event.person);
 | |
|                     break;
 | |
|                 default:
 | |
|                     blueslip.error("Unexpected event type realm_user/" + event.op);
 | |
|                     break;
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case "stream":
 | |
|             switch (event.op) {
 | |
|                 case "update":
 | |
|                     // Legacy: Stream properties are still managed by stream_settings_ui.js on the client side.
 | |
|                     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();
 | |
|                     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);
 | |
|                         }
 | |
|                     }
 | |
|                     break;
 | |
|                 case "delete":
 | |
|                     for (const stream of event.streams) {
 | |
|                         const was_subscribed = sub_store.get(stream.stream_id).subscribed;
 | |
|                         const is_narrowed_to_stream = narrow_state.is_for_stream_id(
 | |
|                             stream.stream_id,
 | |
|                         );
 | |
|                         stream_settings_ui.remove_stream(stream.stream_id);
 | |
|                         stream_data.delete_sub(stream.stream_id);
 | |
|                         if (was_subscribed) {
 | |
|                             stream_list.remove_sidebar_row(stream.stream_id);
 | |
|                         }
 | |
|                         settings_streams.update_default_streams_table();
 | |
|                         stream_data.remove_default_stream(stream.stream_id);
 | |
|                         if (is_narrowed_to_stream) {
 | |
|                             message_lists.current.update_trailing_bookend();
 | |
|                         }
 | |
|                         if (page_params.realm_notifications_stream_id === stream.stream_id) {
 | |
|                             page_params.realm_notifications_stream_id = -1;
 | |
|                             settings_org.sync_realm_settings("notifications_stream_id");
 | |
|                         }
 | |
|                         if (page_params.realm_signup_notifications_stream_id === stream.stream_id) {
 | |
|                             page_params.realm_signup_notifications_stream_id = -1;
 | |
|                             settings_org.sync_realm_settings("signup_notifications_stream_id");
 | |
|                         }
 | |
|                     }
 | |
|                     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_data.update_stream_email_address(sub, rec.email_address);
 | |
|                             stream_events.mark_subscribed(sub, rec.subscribers, rec.color);
 | |
|                         } else {
 | |
|                             blueslip.error(
 | |
|                                 "Subscribing to unknown stream with 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});
 | |
| 
 | |
|                     for (const stream_id of stream_ids) {
 | |
|                         const sub = sub_store.get(stream_id);
 | |
|                         stream_settings_ui.update_subscribers_ui(sub);
 | |
|                     }
 | |
| 
 | |
|                     compose_fade.update_faded_users();
 | |
|                     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});
 | |
| 
 | |
|                     for (const stream_id of stream_ids) {
 | |
|                         const sub = sub_store.get(stream_id);
 | |
|                         stream_settings_ui.update_subscribers_ui(sub);
 | |
|                     }
 | |
| 
 | |
|                     compose_fade.update_faded_users();
 | |
|                     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 === page_params.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 "user_settings": {
 | |
|             if (settings_config.all_notification_settings.includes(event.property)) {
 | |
|                 notifications.handle_global_notification_updates(event.property, event.value);
 | |
|                 settings_notifications.update_page(settings_notifications.user_settings_panel);
 | |
|                 // TODO: This should also do a refresh of the stream_edit UI
 | |
|                 // if it's currently displayed, possibly reusing some code
 | |
|                 // from stream_events.js
 | |
|                 // (E.g. update_stream_push_notifications).
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             const user_display_settings = [
 | |
|                 "color_scheme",
 | |
|                 "default_language",
 | |
|                 "default_view",
 | |
|                 "demote_inactive_streams",
 | |
|                 "dense_mode",
 | |
|                 "emojiset",
 | |
|                 "escape_navigates_to_default_view",
 | |
|                 "fluid_layout_width",
 | |
|                 "high_contrast_mode",
 | |
|                 "left_side_userlist",
 | |
|                 "timezone",
 | |
|                 "twenty_four_hour_time",
 | |
|                 "translate_emoticons",
 | |
|                 "starred_message_counts",
 | |
|                 "send_stream_typing_notifications",
 | |
|                 "send_private_typing_notifications",
 | |
|                 "send_read_receipts",
 | |
|             ];
 | |
| 
 | |
|             if (user_display_settings.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_display.set_default_language_name(event.language_name);
 | |
|             }
 | |
|             if (event.property === "twenty_four_hour_time") {
 | |
|                 // Rerender the whole message list UI
 | |
|                 message_lists.home.rerender();
 | |
|                 if (message_lists.current === message_list.narrowed) {
 | |
|                     message_list.narrowed.rerender();
 | |
|                 }
 | |
|             }
 | |
|             if (event.property === "high_contrast_mode") {
 | |
|                 $("body").toggleClass("high-contrast");
 | |
|             }
 | |
|             if (event.property === "demote_inactive_streams") {
 | |
|                 stream_list.update_streams_sidebar();
 | |
|                 stream_data.set_filter_out_inactives();
 | |
|             }
 | |
|             if (event.property === "dense_mode") {
 | |
|                 $("body").toggleClass("less_dense_mode");
 | |
|                 $("body").toggleClass("more_dense_mode");
 | |
|             }
 | |
|             if (event.property === "color_scheme") {
 | |
|                 $("body").fadeOut(300);
 | |
|                 setTimeout(() => {
 | |
|                     if (event.value === settings_config.color_scheme_values.night.code) {
 | |
|                         dark_theme.enable();
 | |
|                         realm_logo.render();
 | |
|                     } else if (event.value === settings_config.color_scheme_values.day.code) {
 | |
|                         dark_theme.disable();
 | |
|                         realm_logo.render();
 | |
|                     } else {
 | |
|                         dark_theme.default_preference_checker();
 | |
|                         realm_logo.render();
 | |
|                     }
 | |
|                     $("body").fadeIn(300);
 | |
|                 }, 300);
 | |
|             }
 | |
|             if (event.property === "starred_message_counts") {
 | |
|                 starred_messages.rerender_ui();
 | |
|             }
 | |
|             if (event.property === "fluid_layout_width") {
 | |
|                 scroll_bar.set_layout_width();
 | |
|             }
 | |
|             if (event.property === "left_side_userlist") {
 | |
|                 // TODO: Make this change the view immediately rather
 | |
|                 // than requiring a reload or page resize.
 | |
|             }
 | |
|             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_display.report_emojiset_change(settings_display.user_settings_panel);
 | |
| 
 | |
|                 // Rerender the whole message list UI
 | |
|                 message_lists.home.rerender();
 | |
|                 if (message_lists.current === message_list.narrowed) {
 | |
|                     message_list.narrowed.rerender();
 | |
|                 }
 | |
|                 // Rerender buddy list status emoji
 | |
|                 activity.build_user_sidebar();
 | |
|             }
 | |
|             if (event.property === "escape_navigates_to_default_view") {
 | |
|                 $("#go-to-default-view-hotkey-help").toggleClass("notdisplayed", !event.value);
 | |
|             }
 | |
|             if (event.property === "enter_sends") {
 | |
|                 user_settings.enter_sends = event.value;
 | |
|                 $(`.enter_sends_${!user_settings.enter_sends}`).hide();
 | |
|                 $(`.enter_sends_${user_settings.enter_sends}`).show();
 | |
|                 break;
 | |
|             }
 | |
|             if (event.property === "presence_enabled") {
 | |
|                 user_settings.presence_enabled = event.value;
 | |
|                 $("#user_presence_enabled").prop("checked", user_settings.presence_enabled);
 | |
|                 activity.redraw_user(page_params.user_id);
 | |
|                 break;
 | |
|             }
 | |
|             settings_display.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) {
 | |
|                         message_flags.update_starred_flag(message_id, new_value);
 | |
|                     }
 | |
| 
 | |
|                     if (event.op === "add") {
 | |
|                         starred_messages.add(event.messages);
 | |
|                     } else {
 | |
|                         starred_messages.remove(event.messages);
 | |
|                     }
 | |
|                     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,
 | |
|                         });
 | |
|                     }
 | |
|                     break;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         case "user_group":
 | |
|             switch (event.op) {
 | |
|                 case "add":
 | |
|                     user_groups.add(event.group);
 | |
|                     break;
 | |
|                 case "remove":
 | |
|                     user_groups.remove(user_groups.get_user_group_from_id(event.group_id));
 | |
|                     break;
 | |
|                 case "add_members":
 | |
|                     user_groups.add_members(event.group_id, event.user_ids);
 | |
|                     break;
 | |
|                 case "remove_members":
 | |
|                     user_groups.remove_members(event.group_id, event.user_ids);
 | |
|                     break;
 | |
|                 case "update":
 | |
|                     user_groups.update(event);
 | |
|                     break;
 | |
|                 default:
 | |
|                     blueslip.error("Unexpected event type user_group/" + event.op);
 | |
|                     break;
 | |
|             }
 | |
|             settings_user_groups.reload();
 | |
|             break;
 | |
| 
 | |
|         case "user_status":
 | |
|             if (event.away !== undefined) {
 | |
|                 if (event.away) {
 | |
|                     activity.on_set_away(event.user_id);
 | |
|                 } else {
 | |
|                     activity.on_revoke_away(event.user_id);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (event.status_text !== undefined) {
 | |
|                 user_status.set_status_text({
 | |
|                     user_id: event.user_id,
 | |
|                     status_text: event.status_text,
 | |
|                 });
 | |
|                 activity.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_actions.update_placeholder_text();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (event.emoji_name !== undefined) {
 | |
|                 user_status.set_status_emoji(event);
 | |
|                 activity.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 "realm_export":
 | |
|             settings_exports.populate_exports_table(event.exports);
 | |
|             break;
 | |
|     }
 | |
| }
 |