diff --git a/web/src/click_handlers.js b/web/src/click_handlers.js index 36acd73ab2..a91f4bf954 100644 --- a/web/src/click_handlers.js +++ b/web/src/click_handlers.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import tippy from "tippy.js"; // You won't find every click handler here, but it's a good place to start! @@ -60,6 +61,7 @@ export function initialize() { if (!id) { return; } + assert(message_lists.current !== undefined); message_lists.current.select_id(id); setTimeout(() => { // The algorithm to trigger long tap is that first, we check @@ -179,6 +181,7 @@ export function initialize() { const $row = $(this).closest(".message_row"); const id = rows.id($row); + assert(message_lists.current !== undefined); message_lists.current.select_id(id); if (message_edit.is_editing(id)) { @@ -259,6 +262,7 @@ export function initialize() { }); $("body").on("click", ".reveal_hidden_message", (e) => { + assert(message_lists.current !== undefined); const message_id = rows.id($(e.currentTarget).closest(".message_row")); message_lists.current.view.reveal_hidden_message(message_id); e.stopPropagation(); @@ -287,12 +291,14 @@ export function initialize() { // MESSAGE EDITING $("body").on("click", ".edit_content_button, .view_source_button", function (e) { + assert(message_lists.current !== undefined); const $row = message_lists.current.get_row(rows.id($(this).closest(".message_row"))); message_lists.current.select_id(rows.id($row)); message_edit.start($row); e.stopPropagation(); }); $("body").on("click", ".move_message_button", function (e) { + assert(message_lists.current !== undefined); const $row = message_lists.current.get_row(rows.id($(this).closest(".message_row"))); const message_id = rows.id($row); const message = message_lists.current.get(message_id); @@ -447,6 +453,7 @@ export function initialize() { const $group = rows.get_closest_group(narrow_link_elem); const msg_id = rows.id_for_recipient_row($group); + assert(message_lists.current !== undefined); const nearest = message_lists.current.get(msg_id); const selected = message_lists.current.selected_message(); if (util.same_recipient(nearest, selected)) { diff --git a/web/src/compose_actions.js b/web/src/compose_actions.js index 5370c60af7..5675e3b682 100644 --- a/web/src/compose_actions.js +++ b/web/src/compose_actions.js @@ -131,6 +131,10 @@ export function maybe_scroll_up_selected_message(opts) { return; } + if (message_lists.current === undefined) { + return; + } + // If the compose box is obscuring the currently selected message, // scroll up until the message is no longer occluded. if (message_lists.current.selected_id() === -1) { diff --git a/web/src/compose_closed_ui.js b/web/src/compose_closed_ui.js index 30dec9e0a2..261f318b37 100644 --- a/web/src/compose_closed_ui.js +++ b/web/src/compose_closed_ui.js @@ -14,6 +14,10 @@ export function get_recipient_label(message) { // couple fields, both those constructed here and potentially // passed in. + if (message_lists.current === undefined) { + return ""; + } + if (message === undefined) { if (message_lists.current.visibly_empty()) { // For empty narrows where there's a clear reply target, diff --git a/web/src/compose_fade.js b/web/src/compose_fade.js index 3994497c9d..a41a966ceb 100644 --- a/web/src/compose_fade.js +++ b/web/src/compose_fade.js @@ -40,7 +40,7 @@ export function set_focused_recipient(msg_type) { } function display_messages_normally() { - message_lists.current.view.$list.find(".recipient_row").removeClass("message-fade"); + message_lists.current?.view.$list.find(".recipient_row").removeClass("message-fade"); normal_display = true; } @@ -54,6 +54,10 @@ function change_fade_state($elt, should_fade_group) { } function fade_messages() { + if (message_lists.current === undefined) { + return; + } + let i; let first_message; let $first_row; @@ -74,8 +78,6 @@ function fade_messages() { // Defer updating all message groups so that the compose box can open sooner setTimeout( (expected_msg_list, expected_recipient) => { - const $all_groups = message_lists.current.view.$list.find(".recipient_row"); - if ( message_lists.current !== expected_msg_list || !compose_state.composing() || @@ -85,7 +87,7 @@ function fade_messages() { } should_fade_group = false; - + const $all_groups = message_lists.current.view.$list.find(".recipient_row"); // Note: The below algorithm relies on the fact that all_elts is // sorted as it would be displayed in the message view for (i = 0; i < $all_groups.length; i += 1) { diff --git a/web/src/compose_notifications.ts b/web/src/compose_notifications.ts index 7dd079c8ec..725a925cff 100644 --- a/web/src/compose_notifications.ts +++ b/web/src/compose_notifications.ts @@ -115,7 +115,11 @@ export function get_muted_narrow(message: Message): string | undefined { } export function get_local_notify_mix_reason(message: Message): string | undefined { - assert(message_lists.current !== undefined); + if (message_lists.current === undefined) { + // For non-message list views like Inbox, the message is not visible after sending it. + return undefined; + } + const $row = message_lists.current.get_row(message.id); if ($row.length > 0) { // If our message is in the current message list, we do diff --git a/web/src/compose_reply.js b/web/src/compose_reply.js index 87fe7b051c..b343b7fef5 100644 --- a/web/src/compose_reply.js +++ b/web/src/compose_reply.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import * as fenced_code from "../shared/src/fenced_code"; @@ -42,6 +43,8 @@ export function respond_to_message(opts) { } message = message_opts.message; } else { + assert(message_lists.current !== undefined); + message = message_lists.current.get(opts.message_id) || message_lists.current.selected_message(); @@ -119,6 +122,7 @@ export function respond_to_message(opts) { } export function reply_with_mention(opts) { + assert(message_lists.current !== undefined); respond_to_message(opts); const message = message_lists.current.selected_message(); const mention = people.get_mention_syntax(message.sender_full_name, message.sender_id); @@ -126,6 +130,7 @@ export function reply_with_mention(opts) { } export function quote_and_reply(opts) { + assert(message_lists.current !== undefined); const message_id = opts.message_id || message_lists.current.selected_id(); const message = message_lists.current.get(message_id); const quoting_placeholder = $t({defaultMessage: "[Quoting…]"}); diff --git a/web/src/condense.js b/web/src/condense.js index 06fc7791e6..29ba385df9 100644 --- a/web/src/condense.js +++ b/web/src/condense.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import * as message_flags from "./message_flags"; import * as message_lists from "./message_lists"; @@ -40,6 +41,8 @@ function uncondense_row($row) { } export function uncollapse($row) { + assert(message_lists.current !== undefined); + // Uncollapse a message, restoring the condensed message "Show more" or // "Show less" button if necessary. const message = message_lists.current.get(rows.id($row)); @@ -75,6 +78,7 @@ export function uncollapse($row) { } export function collapse($row) { + assert(message_lists.current !== undefined); // Collapse a message, hiding the condensed message [More] or // [Show less] link if necessary. const message = message_lists.current.get(rows.id($row)); @@ -118,6 +122,7 @@ export function toggle_collapse(message) { // * If the message is fully visible, either because it's too short to // condense or because it's already uncondensed, collapse it + assert(message_lists.current !== undefined); const $row = message_lists.current.get_row(message.id); if (!$row) { return; @@ -178,6 +183,10 @@ export function show_message_condenser($row) { } export function condense_and_collapse(elems) { + if (message_lists.current === undefined) { + return; + } + const height_cutoff = message_viewport.max_message_height(); const rows_to_resize = []; @@ -259,6 +268,7 @@ export function initialize() { // uncondensing it. const $row = $(this).closest(".message_row"); const id = rows.id($row); + assert(message_lists.current !== undefined); const message = message_lists.current.get(id); // Focus on the expanded message. message_lists.current.select_id(id); @@ -281,6 +291,7 @@ export function initialize() { const $row = $(this).closest(".message_row"); const id = rows.id($row); // Focus on the condensed message. + assert(message_lists.current !== undefined); message_lists.current.select_id(id); message_lists.current.get(id).condensed = true; condense_row($row); diff --git a/web/src/copy_and_paste.js b/web/src/copy_and_paste.js index 4bb5a0517b..8802487e67 100644 --- a/web/src/copy_and_paste.js +++ b/web/src/copy_and_paste.js @@ -66,6 +66,9 @@ how modern browsers deal with copy/paste. Just test your changes carefully. */ function construct_copy_div($div, start_id, end_id) { + if (message_lists.current === undefined) { + return; + } const copy_rows = rows.visible_range(start_id, end_id); const $start_row = copy_rows[0]; diff --git a/web/src/echo.js b/web/src/echo.js index b2f47ef435..07a737ca56 100644 --- a/web/src/echo.js +++ b/web/src/echo.js @@ -210,6 +210,10 @@ export function is_slash_command(content) { } export function try_deliver_locally(message_request, insert_new_messages) { + if (message_lists.current === undefined) { + return undefined; + } + if (markdown.contains_backend_only_syntax(message_request.content)) { return undefined; } @@ -453,7 +457,7 @@ export function message_send_error(message_id, error_response) { function abort_message(message) { // Remove in all lists in which it exists all_messages_data.remove([message.id]); - for (const msg_list of [message_lists.home, message_lists.current]) { + for (const msg_list of message_lists.all_rendered_message_lists()) { msg_list.remove_and_rerender([message.id]); } } diff --git a/web/src/hotkey.js b/web/src/hotkey.js index 514cf3b620..4176bc7399 100644 --- a/web/src/hotkey.js +++ b/web/src/hotkey.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import * as activity from "./activity"; import * as activity_ui from "./activity_ui"; @@ -65,6 +66,10 @@ import {user_settings} from "./user_settings"; import * as user_topics_ui from "./user_topics_ui"; function do_narrow_action(action) { + if (message_lists.current === undefined) { + return false; + } + action(message_lists.current.selected_id(), {trigger: "hotkey"}); return true; } @@ -558,6 +563,7 @@ export function process_enter_key(e) { // conversation. const current_filter = narrow_state.filter(); if (current_filter !== undefined && !current_filter.supports_collapsing_recipients()) { + assert(message_lists.current !== undefined); const message = message_lists.current.selected_message(); if (message === undefined) { @@ -998,7 +1004,7 @@ export function process_hotkey(e, hotkey) { // Hotkeys below this point are for the message feed, and so // should only function if the message feed is visible and nonempty. - if (!narrow_state.is_message_feed_visible() || message_lists.current.visibly_empty()) { + if (message_lists.current === undefined || message_lists.current.visibly_empty()) { return false; } diff --git a/web/src/message_actions_popover.js b/web/src/message_actions_popover.js index 38f9125525..e9aa1b6771 100644 --- a/web/src/message_actions_popover.js +++ b/web/src/message_actions_popover.js @@ -1,5 +1,6 @@ import ClipboardJS from "clipboard"; import $ from "jquery"; +import assert from "minimalistic-assert"; import render_actions_popover from "../templates/popovers/actions_popover.hbs"; @@ -114,6 +115,7 @@ export function initialize() { $popper.one("click", ".popover_edit_message, .popover_view_source", (e) => { const message_id = $(e.currentTarget).data("message-id"); + assert(message_lists.current !== undefined); const $row = message_lists.current.get_row(message_id); message_edit.start($row); e.preventDefault(); @@ -123,6 +125,7 @@ export function initialize() { $popper.one("click", ".popover_move_message", (e) => { const message_id = $(e.currentTarget).data("message-id"); + assert(message_lists.current !== undefined); message_lists.current.select_id(message_id); const message = message_lists.current.get(message_id); stream_popover.build_move_topic_to_stream_popover( @@ -146,6 +149,7 @@ export function initialize() { $popper.one("click", ".popover_toggle_collapse", (e) => { const message_id = $(e.currentTarget).data("message-id"); + assert(message_lists.current !== undefined); const $row = message_lists.current.get_row(message_id); const message = message_lists.current.get(rows.id($row)); if ($row) { @@ -162,6 +166,7 @@ export function initialize() { $popper.one("click", ".rehide_muted_user_message", (e) => { const message_id = $(e.currentTarget).data("message-id"); + assert(message_lists.current !== undefined); const $row = message_lists.current.get_row(message_id); const message = message_lists.current.get(rows.id($row)); const message_container = message_lists.current.view.message_containers.get( diff --git a/web/src/message_edit.js b/web/src/message_edit.js index a6414e4271..a1895e900d 100644 --- a/web/src/message_edit.js +++ b/web/src/message_edit.js @@ -1,5 +1,6 @@ import ClipboardJS from "clipboard"; import $ from "jquery"; +import assert from "minimalistic-assert"; import * as resolved_topic from "../shared/src/resolved_topic"; import render_wildcard_mention_not_allowed_error from "../templates/compose_banner/wildcard_mention_not_allowed_error.hbs"; @@ -429,13 +430,13 @@ function create_copy_to_clipboard_handler($row, source, message_id) { } function edit_message($row, raw_content) { + assert(message_lists.current !== undefined); + const message = message_lists.current.get(rows.id($row)); $row.find(".message_reactions").hide(); condense.hide_message_expander($row); condense.hide_message_condenser($row); const content_top = $row.find(".message_controls")[0].getBoundingClientRect().top; - const message = message_lists.current.get(rows.id($row)); - // We potentially got to this function by clicking a button that implied the // user would be able to edit their message. Give a little bit of buffer in // case the button has been around for a bit, e.g. we show the @@ -584,6 +585,7 @@ function start_edit_with_content($row, content, edit_box_open_callback) { } export function start($row, edit_box_open_callback) { + assert(message_lists.current !== undefined); const message = message_lists.current.get(rows.id($row)); if (message === undefined) { blueslip.error("Couldn't find message ID for edit", {row_id: rows.id($row)}); @@ -758,6 +760,7 @@ export function toggle_resolve_topic( } export function start_inline_topic_edit($recipient_row) { + assert(message_lists.current !== undefined); const $form = $( render_topic_edit_form({ max_topic_length: realm.max_topic_length, @@ -787,10 +790,12 @@ export function is_editing(id) { } export function end_inline_topic_edit($row) { + assert(message_lists.current !== undefined); message_lists.current.hide_edit_topic_on_recipient_row($row); } export function end_message_row_edit($row) { + assert(message_lists.current !== undefined); const row_id = rows.id($row); // Clean up the upload handler @@ -833,8 +838,8 @@ export function end_message_row_edit($row) { } export function end_message_edit(message_id) { - const $row = message_lists.current.get_row(message_id); - if ($row.length > 0) { + const $row = message_lists.current?.get_row(message_id); + if (message_lists.current !== undefined && $row.length > 0) { end_message_row_edit($row); } else if (currently_editing_messages.has(message_id)) { // We should delete the message_id from currently_editing_messages @@ -844,6 +849,7 @@ export function end_message_edit(message_id) { } export function try_save_inline_topic_edit($row) { + assert(message_lists.current !== undefined); const message_id = rows.id_for_recipient_row($row); const message = message_lists.current.get(message_id); @@ -944,6 +950,7 @@ export function do_save_inline_topic_edit($row, message, new_topic) { } export function save_message_row_edit($row) { + assert(message_lists.current !== undefined); const $banner_container = compose_banner.get_compose_banner_container( $row.find(".message_edit_form textarea"), ); @@ -1118,12 +1125,20 @@ export function save_message_row_edit($row) { } export function maybe_show_edit($row, id) { + if (message_lists.current === undefined) { + return; + } + if (currently_editing_messages.has(id)) { message_lists.current.show_edit_message($row, currently_editing_messages.get(id)); } } export function edit_last_sent_message() { + if (message_lists.current === undefined) { + return; + } + const msg = message_lists.current.get_last_message_sent_by_me(); if (!msg) { @@ -1223,6 +1238,7 @@ export function delete_topic(stream_id, topic_name, failures = 0) { } export function handle_narrow_deactivated() { + assert(message_lists.current !== undefined); for (const [idx, elem] of currently_editing_messages) { if (message_lists.current.get(idx) !== undefined) { const $row = message_lists.current.get_row(idx); diff --git a/web/src/message_events.js b/web/src/message_events.js index a8d371b070..f0410dad0e 100644 --- a/web/src/message_events.js +++ b/web/src/message_events.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import * as alert_words from "./alert_words"; import {all_messages_data} from "./all_messages_data"; @@ -273,8 +274,10 @@ export function update_messages(events) { const orig_topic = util.get_edit_event_orig_topic(event); const current_filter = narrow_state.filter(); - const current_selected_id = message_lists.current.selected_id(); - const selection_changed_topic = event.message_ids.includes(current_selected_id); + const current_selected_id = message_lists.current?.selected_id(); + const selection_changed_topic = + message_lists.current !== undefined && + event.message_ids.includes(current_selected_id); const event_messages = []; for (const message_id of event.message_ids) { // We don't need to concern ourselves updating data structures @@ -450,9 +453,10 @@ export function update_messages(events) { // list and then pass these to the remove messages codepath. // While we can pass all our messages to the add messages // codepath as the filtering is done within the method. + assert(message_lists.current !== undefined); message_lists.current.remove_and_rerender(message_ids_to_remove); message_lists.current.add_messages(event_messages); - } else { + } else if (message_lists.current !== undefined) { // Remove existing message that were updated, since // they may not be a part of the filter now. Also, // this will help us rerender them via @@ -538,7 +542,7 @@ export function update_messages(events) { // edit. Doing so could save significant work, since most // topic edits will not match the current topic narrow in // large organizations. - if (!changed_narrow && message_lists.current.narrowed) { + if (!changed_narrow && message_lists.current?.narrowed) { message_lists.current.update_muting_and_rerender(); } } else { diff --git a/web/src/message_feed_top_notices.ts b/web/src/message_feed_top_notices.ts index 11e5c1f03d..43a7992190 100644 --- a/web/src/message_feed_top_notices.ts +++ b/web/src/message_feed_top_notices.ts @@ -37,7 +37,7 @@ function show_end_of_results_notice(): void { export function update_top_of_narrow_notices(msg_list: MessageList): void { // Assumes that the current state is all notices hidden (i.e. this // will not hide a notice that should not be there) - if (msg_list !== message_lists.current) { + if (message_lists.current === undefined || msg_list !== message_lists.current) { return; } diff --git a/web/src/message_fetch.js b/web/src/message_fetch.js index e2b7185768..91ff117a41 100644 --- a/web/src/message_fetch.js +++ b/web/src/message_fetch.js @@ -91,6 +91,7 @@ function process_result(data, opts) { stream_list.maybe_scroll_narrow_into_view(); if ( + message_lists.current !== undefined && opts.msg_list === message_lists.current && opts.msg_list.narrowed && opts.msg_list.visibly_empty() @@ -121,7 +122,8 @@ function process_result(data, opts) { } function get_messages_success(data, opts) { - const update_loading_indicator = opts.msg_list === message_lists.current; + const update_loading_indicator = + message_lists.current !== undefined && opts.msg_list === message_lists.current; const msg_list_data = opts.msg_list_data ?? opts.msg_list.data; if (opts.num_before > 0) { msg_list_data.fetch_status.finish_older_batch({ @@ -266,7 +268,8 @@ export function load_messages(opts, attempt = 1) { data.narrow = JSON.stringify(terms); } - let update_loading_indicator = opts.msg_list === message_lists.current; + let update_loading_indicator = + message_lists.current !== undefined && opts.msg_list === message_lists.current; if (opts.num_before > 0) { msg_list_data.fetch_status.start_older_batch({ update_loading_indicator, @@ -356,6 +359,7 @@ export function load_messages(opts, attempt = 1) { message_feed_loading.hide_indicators(); if ( + message_lists.current !== undefined && opts.msg_list === message_lists.current && opts.msg_list.narrowed && opts.msg_list.visibly_empty() @@ -506,7 +510,11 @@ export function maybe_load_newer_messages(opts) { const anchor = get_frontfill_anchor(msg_list); function load_more(_data, args) { - if (args.fetch_again && args.msg_list === message_lists.current) { + if ( + args.fetch_again && + message_lists.current !== undefined && + args.msg_list === message_lists.current + ) { maybe_load_newer_messages({msg_list: message_lists.current}); } } diff --git a/web/src/message_list.js b/web/src/message_list.js index e332ddb7a8..f14b9bbc13 100644 --- a/web/src/message_list.js +++ b/web/src/message_list.js @@ -537,5 +537,4 @@ export function initialize() { excludes_muted_topics: true, }); message_lists.set_home(home_msg_list); - message_lists.update_current_message_list(home_msg_list); } diff --git a/web/src/message_list_hover.js b/web/src/message_list_hover.js index 6d7d32a7f6..a4de964204 100644 --- a/web/src/message_list_hover.js +++ b/web/src/message_list_hover.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import render_edit_content_button from "../templates/edit_content_button.hbs"; @@ -17,6 +18,7 @@ export function message_unhover() { export function message_hover($message_row) { const id = rows.id($message_row); + assert(message_lists.current !== undefined); const message = message_lists.current.get(id); if ($current_message_hover && rows.id($current_message_hover) === id) { diff --git a/web/src/message_list_tooltips.js b/web/src/message_list_tooltips.js index 78332500b2..d2ad430eff 100644 --- a/web/src/message_list_tooltips.js +++ b/web/src/message_list_tooltips.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import {delegate} from "tippy.js"; import render_change_visibility_policy_button_tooltip from "../templates/change_visibility_policy_button_tooltip.hbs"; @@ -210,6 +211,7 @@ export function initialize() { onShow(instance) { const $time_elem = $(instance.reference); const $row = $time_elem.closest(".message_row"); + assert(message_lists.current !== undefined); const message = message_lists.current.get(rows.id($row)); // Don't show time tooltip for locally echoed message. if (message.locally_echoed) { diff --git a/web/src/message_lists.ts b/web/src/message_lists.ts index f9c1edc5f6..20b8c88729 100644 --- a/web/src/message_lists.ts +++ b/web/src/message_lists.ts @@ -5,7 +5,6 @@ import * as blueslip from "./blueslip"; import * as inbox_util from "./inbox_util"; import type {MessageListData} from "./message_list_data"; import type {Message} from "./message_store"; -import * as recent_view_util from "./recent_view_util"; import * as ui_util from "./ui_util"; // TODO(typescript): Move this to message_list_view when it's @@ -55,12 +54,12 @@ export type MessageList = { export let home: MessageList | undefined; export let current: MessageList | undefined; -function set_current(msg_list: MessageList): void { +function set_current(msg_list: MessageList | undefined): void { // NOTE: Use update_current_message_list instead of this function. current = msg_list; } -export function update_current_message_list(msg_list: MessageList): void { +export function update_current_message_list(msg_list: MessageList | undefined): void { if (msg_list !== home) { home?.view.$list.removeClass("focused-message-list"); } @@ -80,9 +79,8 @@ export function set_home(msg_list: MessageList): void { export function all_rendered_message_lists(): MessageList[] { assert(home !== undefined); - assert(current !== undefined); const rendered_message_lists = [home]; - if (current !== home && !recent_view_util.is_visible()) { + if (current !== undefined && current !== home) { rendered_message_lists.push(current); } return rendered_message_lists; @@ -97,7 +95,10 @@ export function all_rendered_row_for_message_id(message_id: number): JQuery { } export function all_current_message_rows(): JQuery { - assert(current !== undefined); + if (current === undefined) { + return $(); + } + return current.view.$list.find(".message_row"); } @@ -109,7 +110,10 @@ export function update_recipient_bar_background_color(): void { } export function save_pre_narrow_offset_for_reload(): void { - assert(current !== undefined); + if (current === undefined) { + return; + } + if (current.selected_id() !== -1) { if (current.selected_row().length === 0) { const current_message = current.get(current.selected_id()); diff --git a/web/src/message_scroll.js b/web/src/message_scroll.js index 6a5dabdb0d..44884885e0 100644 --- a/web/src/message_scroll.js +++ b/web/src/message_scroll.js @@ -13,6 +13,10 @@ import * as unread_ui from "./unread_ui"; let hide_scroll_to_bottom_timer; export function hide_scroll_to_bottom() { + if (message_lists.current === undefined) { + return; + } + const $show_scroll_to_bottom_button = $("#scroll-to-bottom-button-container"); if ( message_viewport.bottom_rendered_message_visible() || @@ -60,13 +64,16 @@ $(document).on("keydown", (e) => { export function scroll_finished() { message_scroll_state.set_actively_scrolling(false); - message_lists.current.view.update_sticky_recipient_headers(); hide_scroll_to_bottom(); - if (!narrow_state.is_message_feed_visible()) { + if (message_lists.current === undefined) { return; } + // It's possible that we are in transit and message_lists.current is not defined. + // We still want the rest of the code to run but it's fine to skip this. + message_lists.current.view.update_sticky_recipient_headers(); + if (compose_banner.scroll_to_message_banner_message_id !== null) { const $message_row = message_lists.current.get_row( compose_banner.scroll_to_message_banner_message_id, @@ -127,7 +134,7 @@ export function initialize() { "scroll", _.throttle(() => { unread_ops.process_visible(); - message_lists.current.view.update_sticky_recipient_headers(); + message_lists.current?.view.update_sticky_recipient_headers(); scroll_finish(); }, 50), ); diff --git a/web/src/muted_users_ui.js b/web/src/muted_users_ui.js index 9f5dc75a24..9aa71ffa2a 100644 --- a/web/src/muted_users_ui.js +++ b/web/src/muted_users_ui.js @@ -8,9 +8,8 @@ import * as recent_view_ui from "./recent_view_ui"; import * as settings_muted_users from "./settings_muted_users"; export function rerender_for_muted_user() { - message_lists.current.update_muting_and_rerender(); - if (message_lists.current !== message_lists.home) { - message_lists.home.update_muting_and_rerender(); + for (const msg_list of message_lists.all_rendered_message_lists()) { + msg_list.update_muting_and_rerender(); } if (overlays.settings_open() && settings_muted_users.loaded) { diff --git a/web/src/narrow.js b/web/src/narrow.js index c2bfc2b73e..daeee6980d 100644 --- a/web/src/narrow.js +++ b/web/src/narrow.js @@ -364,7 +364,7 @@ export function activate(raw_terms, opts) { blueslip.debug("Narrowed", { operators: terms.map((e) => e.operator), trigger: opts ? opts.trigger : undefined, - previous_id: message_lists.current.selected_id(), + previous_id: message_lists.current?.selected_id(), }); if (opts.then_select_id > 0) { @@ -372,7 +372,7 @@ export function activate(raw_terms, opts) { // having a near: narrow auto-reloaded. id_info.target_id = opts.then_select_id; // Position selected row to not scroll off-screen. - if (opts.then_select_offset === undefined) { + if (opts.then_select_offset === undefined && message_lists.current !== undefined) { const $row = message_lists.current.get_row(opts.then_select_id); if ($row.length > 0) { const row_props = $row.get_offset_to_window(); @@ -727,7 +727,7 @@ export function maybe_add_local_messages(opts) { } export function render_message_list_with_selected_message(opts) { - if (message_lists.current !== opts.msg_list) { + if (message_lists.current !== undefined && message_lists.current !== opts.msg_list) { // If we navigated away from a view while we were fetching // messages for it, don't attempt to move the currently // selected message. @@ -1102,13 +1102,14 @@ export function deactivate() { narrow_state.set_has_shown_message_list_view(); message_lists.update_current_message_list(message_lists.home); + assert(message_lists.current === message_lists.home); message_lists.current.resume_reading(); condense.condense_and_collapse(message_lists.home.view.$list.find(".message_row")); reset_ui_state(); compose_recipient.handle_middle_pane_transition(); - if (message_lists.current.selected_id() !== -1) { + if (message_lists.current !== undefined && message_lists.current.selected_id() !== -1) { const preserve_pre_narrowing_screen_position = message_lists.current.selected_row().length > 0 && message_lists.current.pre_narrow_offset !== undefined; diff --git a/web/src/navigate.js b/web/src/navigate.js index c635d99b06..eb877e3652 100644 --- a/web/src/navigate.js +++ b/web/src/navigate.js @@ -1,12 +1,16 @@ +import assert from "minimalistic-assert"; + import * as message_lists from "./message_lists"; import * as message_viewport from "./message_viewport"; import * as unread_ops from "./unread_ops"; function go_to_row(msg_id) { + assert(message_lists.current !== undefined); message_lists.current.select_id(msg_id, {then_scroll: true, from_scroll: true}); } export function up() { + assert(message_lists.current !== undefined); message_viewport.set_last_movement_direction(-1); const msg_id = message_lists.current.prev(); if (msg_id === undefined) { @@ -16,6 +20,7 @@ export function up() { } export function down(with_centering) { + assert(message_lists.current !== undefined); message_viewport.set_last_movement_direction(1); if (message_lists.current.is_at_end()) { @@ -41,12 +46,14 @@ export function down(with_centering) { } export function to_home() { + assert(message_lists.current !== undefined); message_viewport.set_last_movement_direction(-1); const first_id = message_lists.current.first().id; message_lists.current.select_id(first_id, {then_scroll: true, from_scroll: true}); } export function to_end() { + assert(message_lists.current !== undefined); const next_id = message_lists.current.last().id; message_viewport.set_last_movement_direction(1); message_lists.current.select_id(next_id, {then_scroll: true, from_scroll: true}); @@ -100,6 +107,7 @@ export function page_down_the_right_amount() { } export function page_up() { + assert(message_lists.current !== undefined); if (message_viewport.at_rendered_top() && !message_lists.current.visibly_empty()) { if (message_lists.current.view.is_fetched_start_rendered()) { message_lists.current.select_id(message_lists.current.first().id, {then_scroll: false}); @@ -117,6 +125,7 @@ export function page_up() { } export function page_down() { + assert(message_lists.current !== undefined); if (message_viewport.at_rendered_bottom() && !message_lists.current.visibly_empty()) { if (message_lists.current.view.is_fetched_end_rendered()) { message_lists.current.select_id(message_lists.current.last().id, {then_scroll: false}); diff --git a/web/src/popover_menus_data.js b/web/src/popover_menus_data.js index fca8c2e33e..5dcba1b6ba 100644 --- a/web/src/popover_menus_data.js +++ b/web/src/popover_menus_data.js @@ -1,6 +1,8 @@ /* This module provides relevant data to render popovers that require multiple args. This helps keep the popovers code small and keep it focused on rendering side of things. */ +import assert from "minimalistic-assert"; + import * as resolved_topic from "../shared/src/resolved_topic"; import * as buddy_data from "./buddy_data"; @@ -23,6 +25,7 @@ import * as user_status from "./user_status"; import * as user_topics from "./user_topics"; export function get_actions_popover_content_context(message_id) { + assert(message_lists.current !== undefined); const message = message_lists.current.get(message_id); const message_container = message_lists.current.view.message_containers.get(message.id); const not_spectator = !page_params.is_spectator; diff --git a/web/src/reload.js b/web/src/reload.js index a253e2cd18..618dd79b41 100644 --- a/web/src/reload.js +++ b/web/src/reload.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import * as blueslip from "./blueslip"; import * as compose_state from "./compose_state"; @@ -85,6 +86,7 @@ function preserve_state(send_after_reload, save_pointer, save_narrow, save_compo url += "+offset=" + $row.get_offset_to_window().top; } } else { + assert(message_lists.current !== undefined); url += "+offset=" + message_lists.home.pre_narrow_offset; // narrow_state.active() is true, so this is the current diff --git a/web/src/resize_handler.js b/web/src/resize_handler.js index 743e8dbc37..9ac04c6f9c 100644 --- a/web/src/resize_handler.js +++ b/web/src/resize_handler.js @@ -33,7 +33,7 @@ export function handler() { // This function might run onReady (if we're in a narrow window), // but before we've loaded in the messages; in that case, don't // try to scroll to one. - if (message_lists.current.selected_id() !== -1) { + if (message_lists.current !== undefined && message_lists.current.selected_id() !== -1) { message_viewport.scroll_to_selected(); } } diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index d035f62dae..2ab5479583 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import * as activity_ui from "./activity_ui"; import * as alert_words from "./alert_words"; @@ -547,6 +548,7 @@ export function dispatch_normal_event(event) { stream.stream_id, ); if (is_narrowed_to_stream) { + assert(message_lists.current !== undefined); message_lists.current.update_trailing_bookend(); } stream_data.delete_sub(stream.stream_id); diff --git a/web/src/stream_events.js b/web/src/stream_events.js index 8932a3209c..abbdf4e56e 100644 --- a/web/src/stream_events.js +++ b/web/src/stream_events.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import * as activity_ui from "./activity_ui"; import * as blueslip from "./blueslip"; @@ -146,6 +147,7 @@ export function mark_subscribed(sub, subscribers, color) { message_view_header.maybe_rerender_title_area_for_stream(sub); if (narrow_state.is_for_stream_id(sub.stream_id)) { + assert(message_lists.current !== undefined); message_lists.current.update_trailing_bookend(); activity_ui.build_user_sidebar(); } @@ -178,6 +180,7 @@ export function mark_unsubscribed(sub) { if (narrow_state.is_for_stream_id(sub.stream_id)) { // Update UI components if we just unsubscribed from the // currently viewed stream. + assert(message_lists.current !== undefined); message_lists.current.update_trailing_bookend(); // This update would likely be better implemented by having it diff --git a/web/src/stream_popover.js b/web/src/stream_popover.js index 7653d035c4..a8d7cf2abc 100644 --- a/web/src/stream_popover.js +++ b/web/src/stream_popover.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import render_inline_decorated_stream_name from "../templates/inline_decorated_stream_name.hbs"; import render_move_topic_to_stream from "../templates/move_topic_to_stream.hbs"; @@ -226,6 +227,7 @@ async function get_message_placement_in_conversation( topic_name, current_message_id, ) { + assert(message_lists.current !== undefined); // First we check if the placement of the message can be determined // in the current message list. This allows us to avoid a server call // in most cases. diff --git a/web/src/stream_settings_ui.js b/web/src/stream_settings_ui.js index ee5bdcb7fa..d6c718693e 100644 --- a/web/src/stream_settings_ui.js +++ b/web/src/stream_settings_ui.js @@ -145,7 +145,7 @@ export function update_stream_privacy(slim_sub, values) { // Update UI elements update_left_panel_row(sub); - message_lists.current.update_trailing_bookend(); + message_lists.current?.update_trailing_bookend(); stream_ui_updates.update_setting_element(sub, "stream_privacy"); stream_ui_updates.enable_or_disable_permission_settings_in_edit_panel(sub); stream_ui_updates.update_stream_privacy_icon_in_settings(sub); diff --git a/web/src/ui_init.js b/web/src/ui_init.js index 02fc4f889e..c9bce23715 100644 --- a/web/src/ui_init.js +++ b/web/src/ui_init.js @@ -282,9 +282,10 @@ export function initialize_kitchen_sink_stuff() { }); $(document).on("message_selected.zulip", (event) => { - if (message_lists.current !== event.msg_list) { + if (message_lists.current !== undefined && message_lists.current !== event.msg_list) { return; } + if (event.id === -1) { // If the message list is empty, don't do anything return; diff --git a/web/src/unread_ops.js b/web/src/unread_ops.js index cbb9dd1a55..a29048f194 100644 --- a/web/src/unread_ops.js +++ b/web/src/unread_ops.js @@ -1,5 +1,6 @@ import $ from "jquery"; import _ from "lodash"; +import assert from "minimalistic-assert"; import render_confirm_mark_all_as_read from "../templates/confirm_dialog/confirm_mark_all_as_read.hbs"; @@ -15,7 +16,6 @@ import * as message_lists from "./message_lists"; import * as message_store from "./message_store"; import * as message_viewport from "./message_viewport"; import * as modals from "./modals"; -import * as narrow_state from "./narrow_state"; import * as overlays from "./overlays"; import * as people from "./people"; import * as recent_view_ui from "./recent_view_ui"; @@ -201,6 +201,7 @@ export function mark_as_unread_from_here( num_after = INITIAL_BATCH_SIZE - 1, narrow, ) { + assert(message_lists.current !== undefined); if (narrow === undefined) { narrow = JSON.stringify(message_lists.current.data.filter.terms()); } @@ -312,7 +313,7 @@ export function process_read_messages_event(message_ids) { } for (const message_id of message_ids) { - if (message_lists.current.narrowed) { + if (message_lists.current?.narrowed) { // I'm not sure this entirely makes sense for all server // notifications. unread.set_messages_read_in_narrow(true); @@ -340,7 +341,7 @@ export function process_unread_messages_event({message_ids, message_details}) { return; } - if (message_lists.current.narrowed) { + if (message_lists.current?.narrowed) { unread.set_messages_read_in_narrow(false); } @@ -400,6 +401,7 @@ export function process_unread_messages_event({message_ids, message_details}) { recent_view_ui.complete_rerender(); if ( + message_lists.current !== undefined && !message_lists.current.can_mark_messages_read() && message_lists.current.has_unread_messages() ) { @@ -420,7 +422,7 @@ export function notify_server_messages_read(messages, options = {}) { message_flags.send_read(messages); for (const message of messages) { - if (message_lists.current.narrowed) { + if (message_lists.current?.narrowed) { unread.set_messages_read_in_narrow(true); } @@ -436,8 +438,8 @@ export function notify_server_message_read(message, options) { } function process_scrolled_to_bottom() { - if (!narrow_state.is_message_feed_visible()) { - // First, verify the current message list is visible. + if (message_lists.current === undefined) { + // First, verify that user is narrowed to a list of messages. return; } @@ -465,6 +467,7 @@ function process_scrolled_to_bottom() { // may need to update message_notifications.received_messages as well. export function process_visible() { if ( + message_lists.current !== undefined && viewport_is_visible_and_focused() && message_viewport.bottom_rendered_message_visible() && message_lists.current.view.is_fetched_end_rendered() diff --git a/web/src/unread_ui.ts b/web/src/unread_ui.ts index 06ce371435..7903558507 100644 --- a/web/src/unread_ui.ts +++ b/web/src/unread_ui.ts @@ -24,6 +24,10 @@ export function register_update_unread_counts_hook(f: UpdateUnreadCountsHook): v } export function update_unread_banner(): void { + if (message_lists.current === undefined) { + return; + } + const filter = narrow_state.filter(); const is_conversation_view = filter === undefined ? false : filter.is_conversation_view(); if ( @@ -40,7 +44,6 @@ export function update_unread_banner(): void { render_mark_as_read_only_in_conversation_view(), ); } else { - assert(message_lists.current !== undefined); $("#mark_read_on_scroll_state_banner").html(render_mark_as_read_turned_off_banner()); if (message_lists.current.can_mark_messages_read_without_setting()) { hide_unread_banner(); diff --git a/web/src/upload.js b/web/src/upload.js index 8afe34af37..84c07f41c9 100644 --- a/web/src/upload.js +++ b/web/src/upload.js @@ -1,6 +1,7 @@ import {Uppy} from "@uppy/core"; import XHRUpload from "@uppy/xhr-upload"; import $ from "jquery"; +import assert from "minimalistic-assert"; import render_upload_banner from "../templates/compose_banner/upload_banner.hbs"; @@ -122,6 +123,7 @@ export function get_item(key, config, file_id) { case "source": return "message-edit-file-input"; case "drag_drop_container": + assert(message_lists.current !== undefined); return $( `#message-row-${message_lists.current.id}-${CSS.escape( config.row, @@ -532,7 +534,7 @@ export function initialize() { const edit_upload_object = upload_objects_by_message_edit_row.get(row_id); upload_files(edit_upload_object, {mode: "edit", row: row_id}, files); - } else if (message_lists.current.selected_message()) { + } else if (message_lists.current?.selected_message()) { // Start a reply to selected message, if viewing a message feed. compose_reply.respond_to_message({trigger: "drag_drop_file"}); upload_files(compose_upload_object, {mode: "compose"}, files); diff --git a/web/src/user_card_popover.js b/web/src/user_card_popover.js index 052e444a72..f7e1b18165 100644 --- a/web/src/user_card_popover.js +++ b/web/src/user_card_popover.js @@ -1,6 +1,7 @@ import ClipboardJS from "clipboard"; import {parseISO} from "date-fns"; import $ from "jquery"; +import assert from "minimalistic-assert"; import tippy from "tippy.js"; import render_confirm_mute_user from "../templates/confirm_dialog/confirm_mute_user.hbs"; @@ -569,6 +570,7 @@ export function toggle_sender_info() { $sender = $message.find(".message_sender"); } + assert(message_lists.current !== undefined); const message = message_lists.current.get(rows.id($message)); const user = people.get_by_user_id(message.sender_id); toggle_user_card_popover_for_message($sender[0], user, message, () => { @@ -644,6 +646,7 @@ function register_click_handlers() { $("#main_div").on("click", ".sender_name, .inline_profile_picture", function (e) { const $row = $(this).closest(".message_row"); e.stopPropagation(); + assert(message_lists.current !== undefined); const message = message_lists.current.get(rows.id($row)); const user = people.get_by_user_id(message.sender_id); toggle_user_card_popover_for_message(this, user, message); @@ -659,6 +662,7 @@ function register_click_handlers() { } const $row = $(this).closest(".message_row"); e.stopPropagation(); + assert(message_lists.current !== undefined); const message = message_lists.current.get(rows.id($row)); let user; if (id_string) { diff --git a/web/src/user_topics_ui.js b/web/src/user_topics_ui.js index a7ee3c7cf6..ef17e729cf 100644 --- a/web/src/user_topics_ui.js +++ b/web/src/user_topics_ui.js @@ -17,7 +17,7 @@ export function handle_topic_updates(user_topic_event) { stream_list.update_streams_sidebar(); unread_ui.update_unread_counts(); - message_lists.current.update_muting_and_rerender(); + message_lists.current?.update_muting_and_rerender(); recent_view_ui.update_topic_visibility_policy( user_topic_event.stream_id, user_topic_event.topic_name, diff --git a/web/src/views_util.js b/web/src/views_util.js index 615128fab4..8219986ea1 100644 --- a/web/src/views_util.js +++ b/web/src/views_util.js @@ -78,6 +78,7 @@ export function show(opts) { // a messages narrow. We hide it and show the view. $("#message_feed_container").hide(); opts.$view.show(); + message_lists.update_current_message_list(undefined); opts.set_visible(true); unread_ui.hide_unread_banner(); diff --git a/web/src/widgetize.js b/web/src/widgetize.js index eac464c0bc..671bdd836e 100644 --- a/web/src/widgetize.js +++ b/web/src/widgetize.js @@ -88,7 +88,7 @@ export function activate(in_opts) { export function set_widgets_for_list() { for (const [idx, $widget_elem] of widget_contents) { - if (message_lists.current.get(idx) !== undefined) { + if (message_lists.current?.get(idx) !== undefined) { const $row = message_lists.current.get_row(idx); set_widget_in_message($row, $widget_elem); } diff --git a/web/tests/activity.test.js b/web/tests/activity.test.js index 39de6e2ed6..d55b2c7758 100644 --- a/web/tests/activity.test.js +++ b/web/tests/activity.test.js @@ -47,8 +47,8 @@ const {buddy_list} = zrequire("buddy_list"); const activity = zrequire("activity"); const activity_ui = zrequire("activity_ui"); const stream_data = zrequire("stream_data"); -const narrow_state = zrequire("narrow_state"); const peer_data = zrequire("peer_data"); +const message_lists = zrequire("message_lists"); const util = zrequire("util"); const {Filter} = zrequire("../src/filter"); @@ -105,7 +105,12 @@ const $fred_stub = $.create("fred stub"); const rome_sub = {name: "Rome", subscribed: true, stream_id: 1001}; function add_sub_and_set_as_current_narrow(sub) { stream_data.add_sub(sub); - narrow_state.set_current_filter(new Filter([{operator: "stream", operand: sub.name}])); + const filter = new Filter([{operator: "stream", operand: sub.name}]); + message_lists.set_current({ + data: { + filter, + }, + }); } function test(label, f) { diff --git a/web/tests/dispatch.test.js b/web/tests/dispatch.test.js index 849ef80a1f..409bcf7ad7 100644 --- a/web/tests/dispatch.test.js +++ b/web/tests/dispatch.test.js @@ -100,6 +100,9 @@ message_lists.current = { rerender_view: noop, data: { get_messages_sent_by_user: () => [], + filter: { + is_in_home: () => true, + }, }, }; message_lists.home = { diff --git a/web/tests/hotkey.test.js b/web/tests/hotkey.test.js index bc3b51a4b6..c77efd2b26 100644 --- a/web/tests/hotkey.test.js +++ b/web/tests/hotkey.test.js @@ -49,9 +49,7 @@ const message_edit = mock_esm("../src/message_edit"); const message_lists = mock_esm("../src/message_lists"); const user_topics_ui = mock_esm("../src/user_topics_ui"); const narrow = mock_esm("../src/narrow"); -const narrow_state = mock_esm("../src/narrow_state", { - is_message_feed_visible: () => true, -}); +const narrow_state = mock_esm("../src/narrow_state"); const navigate = mock_esm("../src/navigate"); const modals = mock_esm("../src/modals", { any_active: () => false, diff --git a/web/tests/stream_events.test.js b/web/tests/stream_events.test.js index be0e07e5e5..586dd3aa5f 100644 --- a/web/tests/stream_events.test.js +++ b/web/tests/stream_events.test.js @@ -19,7 +19,7 @@ const stream_settings_ui = mock_esm("../src/stream_settings_ui", { }); const unread_ui = mock_esm("../src/unread_ui"); const message_lists = mock_esm("../src/message_lists", { - current: {}, + current: undefined, }); const message_view_header = mock_esm("../src/message_view_header", { maybe_rerender_title_area_for_stream() {},