Files
zulip/static/js/server_events_dispatch.js
Steve Howell 72e0f2e901 bots: Lift render_bots calls to dispatcher code.
This is mostly a refactoring to break the unnecessary
dependency of bot_data on settings_bots.

This is a bit more than a refactoring, as I remove all
the debounced calls to render bots during the
initialization of bot_data. (The debouncing probably
meant we only rendered once, but it was still needless
work.)

We don't need to explicitly render bots during
bot_data.initialize(), which you can verify by loading
"#settings/your-bots" as the home page. It was just an
artifact of how add() was implemented.

Note that for the **admin** screen, we did not and
still do not do live updates for add/remove; we only do
it for updates. Fixing that is out of the scope of this
change. The code that was moved here affects
**personal** bot settings.

Note that the debounce code is quite fragile. See my
code comment that explains it. I don't have time to go
down the rabbit hole of a deep fix here. The puppeteer
tests would fail without the debounce, even though I
was able to eliminate the debounce in an earlier
version of this fix and see good results during manual
testing. (My testing may have just been on the "lucky"
side of the race.) I created #17743 to address this
problem.
2021-03-22 19:40:06 -07:00

603 lines
26 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 * as compose from "./compose";
import * as compose_fade from "./compose_fade";
import * as composebox_typeahead from "./composebox_typeahead";
import * as emoji_picker from "./emoji_picker";
import * as hotspots from "./hotspots";
import * as markdown from "./markdown";
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 muting_ui from "./muting_ui";
import * as narrow_state from "./narrow_state";
import * as night_mode from "./night_mode";
import * as notifications from "./notifications";
import * as overlays from "./overlays";
import * as panels from "./panels";
import * as peer_data from "./peer_data";
import * as people from "./people";
import * as reactions from "./reactions";
import * as realm_icon from "./realm_icon";
import * as realm_logo from "./realm_logo";
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_profile_fields from "./settings_profile_fields";
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_topic_history from "./stream_topic_history";
import * as submessage from "./submessage";
import * as subs from "./subs";
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 * 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.render_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":
muting_ui.handle_topic_updates(event.muted_topics);
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: "The application has been updated; reloading!",
};
if (event.immediate) {
reload_options.immediate = true;
}
reload.initiate(reload_options);
break;
}
case "reaction":
if (event.op === "add") {
reactions.add_reaction(event);
} else if (event.op === "remove") {
reactions.remove_reaction(event);
}
break;
case "realm": {
const realm_settings = {
add_emoji_by_admins_only: settings_emoji.update_custom_emoji_ui,
allow_edit_history: noop,
allow_message_deleting: noop,
allow_message_editing: noop,
allow_community_topic_editing: 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_stream_policy: noop,
invite_to_stream_policy: noop,
default_code_block_language: noop,
default_language: noop,
default_twenty_four_hour_time: 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_by_admins_only: 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,
signup_notifications_stream_id: noop,
emails_restricted_to_domains: noop,
video_chat_provider: compose.update_video_chat_button_display,
waiting_period_threshold: noop,
wildcard_mention_policy: noop,
};
if (
event.op === "update" &&
Object.prototype.hasOwnProperty.call(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 === "create_stream_policy") {
// TODO: Add waiting_period_threshold logic here.
page_params.can_create_streams =
page_params.is_admin || page_params.realm_create_stream_policy === 1;
} else if (event.property === "invite_to_stream_policy") {
// TODO: Add waiting_period_threshold logic here.
page_params.can_invite_to_stream =
page_params.is_admin || page_params.realm_invite_to_stream_policy === 1;
}
if (event.property === "name" && window.electron_bridge !== undefined) {
window.electron_bridge.send_event("realm_name", event.value);
}
} else if (event.op === "update_dict" && event.property === "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.prototype.hasOwnProperty.call(realm_settings, key)) {
settings_org.sync_realm_settings(key);
}
}
if (event.data.authentication_methods !== undefined) {
settings_org.populate_auth_methods(event.data.authentication_methods);
}
} else if (event.op === "update_dict" && event.property === "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);
}
} else if (event.op === "update_dict" && event.property === "logo") {
page_params.realm_logo_url = event.data.logo_url;
page_params.realm_logo_source = event.data.logo_source;
realm_logo.rerender();
} else if (event.op === "update_dict" && event.property === "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.rerender();
} else if (event.op === "deactivated") {
window.location.href = "/accounts/deactivated/";
}
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).
panels.show_profile_incomplete(panels.check_profile_incomplete());
}
break;
}
case "realm_bot":
if (event.op === "add") {
bot_data.add(event.bot);
} else if (event.op === "remove") {
bot_data.deactivate(event.bot.user_id);
event.bot.is_active = false;
} else if (event.op === "delete") {
blueslip.info("ignoring bot deletion for live UI update");
break;
} else if (event.op === "update") {
bot_data.update(event.bot.user_id, event.bot);
}
settings_bots.eventually_render_bots();
settings_users.update_bot_data(event.bot.user_id);
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_filters":
page_params.realm_filters = event.realm_filters;
markdown.update_linkifier_rules(page_params.realm_filters);
settings_linkifiers.populate_linkifiers(page_params.realm_filters);
break;
case "realm_domains": {
let i;
if (event.op === "add") {
page_params.realm_domains.push(event.realm_domain);
} else if (event.op === "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;
}
}
} else if (event.op === "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;
}
case "realm_user":
if (event.op === "add") {
people.add_active_user(event.person);
} else if (event.op === "remove") {
people.deactivate(event.person);
stream_events.remove_deactivated_user_from_all_streams(event.person.user_id);
} else if (event.op === "update") {
user_events.update_person(event.person);
}
break;
case "stream":
if (event.op === "update") {
// Legacy: Stream properties are still managed by subs.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,
});
settings_streams.update_default_streams_table();
} else if (event.op === "create") {
stream_data.create_streams(event.streams);
for (const stream of event.streams) {
const sub = stream_data.get_sub_by_id(stream.stream_id);
stream_data.update_calculated_fields(sub);
if (overlays.streams_open()) {
subs.add_sub_to_table(sub);
}
}
} else if (event.op === "delete") {
for (const stream of event.streams) {
const was_subscribed = stream_data.get_sub_by_id(stream.stream_id).subscribed;
const is_narrowed_to_stream = narrow_state.is_for_stream_id(stream.stream_id);
subs.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) {
current_msg_list.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;
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":
if (event.op === "add") {
for (const rec of event.subscriptions) {
const sub = stream_data.get_sub_by_id(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);
}
}
} else if (event.op === "peer_add") {
const stream_ids = stream_data.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 = stream_data.get_sub_by_id(stream_id);
subs.update_subscribers_ui(sub);
}
compose_fade.update_faded_users();
} else if (event.op === "peer_remove") {
const stream_ids = stream_data.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 = stream_data.get_sub_by_id(stream_id);
subs.update_subscribers_ui(sub);
}
compose_fade.update_faded_users();
} else if (event.op === "remove") {
for (const rec of event.subscriptions) {
const sub = stream_data.get_sub_by_id(rec.stream_id);
stream_events.mark_unsubscribed(sub);
}
} else if (event.op === "update") {
stream_events.update_property(event.stream_id, event.property, event.value);
}
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;
}
if (event.op === "start") {
typing_events.display_notification(event);
} else if (event.op === "stop") {
typing_events.hide_notification(event);
}
break;
case "update_display_settings": {
const user_display_settings = [
"color_scheme",
"default_language",
"default_view",
"demote_inactive_streams",
"dense_mode",
"emojiset",
"fluid_layout_width",
"high_contrast_mode",
"left_side_userlist",
"timezone",
"twenty_four_hour_time",
"translate_emoticons",
"starred_message_counts",
];
if (user_display_settings.includes(event.setting_name)) {
page_params[event.setting_name] = event.setting;
}
if (event.setting_name === "default_language") {
// We additionally need to set the language name.
page_params.default_language_name = event.language_name;
}
if (event.setting_name === "twenty_four_hour_time") {
// Rerender the whole message list UI
home_msg_list.rerender();
if (current_msg_list === message_list.narrowed) {
message_list.narrowed.rerender();
}
}
if (event.setting_name === "high_contrast_mode") {
$("body").toggleClass("high-contrast");
}
if (event.setting_name === "demote_inactive_streams") {
stream_list.update_streams_sidebar();
stream_data.set_filter_out_inactives();
}
if (event.setting_name === "dense_mode") {
$("body").toggleClass("less_dense_mode");
$("body").toggleClass("more_dense_mode");
}
if (event.setting_name === "color_scheme") {
$("body").fadeOut(300);
setTimeout(() => {
if (event.setting === settings_config.color_scheme_values.night.code) {
night_mode.enable();
realm_logo.rerender();
} else if (event.setting === settings_config.color_scheme_values.day.code) {
night_mode.disable();
realm_logo.rerender();
} else {
night_mode.default_preference_checker();
realm_logo.rerender();
}
$("body").fadeIn(300);
}, 300);
}
if (event.setting_name === "starred_message_counts") {
starred_messages.rerender_ui();
}
if (event.setting_name === "fluid_layout_width") {
scroll_bar.set_layout_width();
}
if (event.setting_name === "left_side_userlist") {
// TODO: Make this change the view immediately rather
// than requiring a reload or page resize.
}
if (event.setting_name === "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.setting_name === "emojiset") {
settings_display.report_emojiset_change();
// Rerender the whole message list UI
home_msg_list.rerender();
if (current_msg_list === message_list.narrowed) {
message_list.narrowed.rerender();
}
}
settings_display.update_page();
break;
}
case "update_global_notifications":
notifications.handle_global_notification_updates(
event.notification_name,
event.setting,
);
settings_notifications.update_page();
// 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;
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":
unread_ops.process_read_messages_event(event.messages);
break;
}
break;
}
case "user_group":
if (event.op === "add") {
user_groups.add(event.group);
} else if (event.op === "add_members") {
user_groups.add_members(event.group_id, event.user_ids);
} else if (event.op === "remove_members") {
user_groups.remove_members(event.group_id, event.user_ids);
} else if (event.op === "update") {
user_groups.update(event);
}
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);
}
break;
case "realm_export":
settings_exports.populate_exports_table(event.exports);
break;
}
}