mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	message_list: Don't always cache "Combined feed" view.
Important changes in this commit: * We only cache message list for "Combined feed" if it is the default view. * We modify existing handling of home message list code so that it can be used to for any message list that we want to cache using a new `preserve_rendered_state` variable. * narrow_state.filter() returns the filter of combined feed view list instead of `undefined`. * We start fetching messages from the latest message on app load. * Messages in all messages data and Recent view are always synced. * If combined feed view list is not cached, we don't track it's last pointer, effectively sending user to the latest unread message always .
This commit is contained in:
		@@ -108,6 +108,7 @@ EXEMPT_FILES = make_set(
 | 
			
		||||
        "web/src/emojisets.ts",
 | 
			
		||||
        "web/src/favicon.ts",
 | 
			
		||||
        "web/src/feedback_widget.ts",
 | 
			
		||||
        "web/src/fetch_status.ts",
 | 
			
		||||
        "web/src/flatpickr.ts",
 | 
			
		||||
        "web/src/gear_menu.js",
 | 
			
		||||
        "web/src/giphy.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,12 @@ async function get_stream_li(page: Page, stream_name: string): Promise<string> {
 | 
			
		||||
 | 
			
		||||
async function expect_home(page: Page): Promise<void> {
 | 
			
		||||
    const message_list_id = await common.get_current_msg_list_id(page, true);
 | 
			
		||||
    await page.waitForSelector(`.message-list[data-message-list-id='${message_list_id}']`, {
 | 
			
		||||
        visible: true,
 | 
			
		||||
    });
 | 
			
		||||
    // Assert that there is only one message list.
 | 
			
		||||
    assert.equal((await page.$$(".message-list")).length, 1);
 | 
			
		||||
    assert.strictEqual(await page.title(), "Combined feed - Zulip Dev - Zulip");
 | 
			
		||||
    await common.check_messages_sent(page, message_list_id, [
 | 
			
		||||
        ["Verona > test", ["verona test a", "verona test b"]],
 | 
			
		||||
        ["Verona > other topic", ["verona other topic c"]],
 | 
			
		||||
@@ -107,10 +113,6 @@ async function un_narrow(page: Page): Promise<void> {
 | 
			
		||||
        await page.keyboard.press("Escape");
 | 
			
		||||
    }
 | 
			
		||||
    await page.click("#left-sidebar-navigation-list .top_left_all_messages");
 | 
			
		||||
    await page.waitForSelector(".message-list .message_row", {visible: true});
 | 
			
		||||
    // Assert that there is only one message list.
 | 
			
		||||
    assert.equal((await page.$$(".message-list")).length, 1);
 | 
			
		||||
    assert.strictEqual(await page.title(), "Combined feed - Zulip Dev - Zulip");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function un_narrow_by_clicking_org_icon(page: Page): Promise<void> {
 | 
			
		||||
@@ -322,6 +324,7 @@ async function test_narrow_by_clicking_the_left_sidebar(page: Page): Promise<voi
 | 
			
		||||
    await expect_all_direct_messages(page);
 | 
			
		||||
 | 
			
		||||
    await un_narrow(page);
 | 
			
		||||
    await expect_home(page);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function arrow(page: Page, direction: "Up" | "Down"): Promise<void> {
 | 
			
		||||
 
 | 
			
		||||
@@ -206,7 +206,8 @@ export function initialize(): void {
 | 
			
		||||
            const narrow_filter = narrow_state.filter();
 | 
			
		||||
            let display_current_view;
 | 
			
		||||
            if (narrow_state.is_message_feed_visible()) {
 | 
			
		||||
                if (narrow_filter === undefined) {
 | 
			
		||||
                assert(narrow_filter !== undefined);
 | 
			
		||||
                if (narrow_filter.is_in_home()) {
 | 
			
		||||
                    display_current_view = $t({
 | 
			
		||||
                        defaultMessage: "Currently viewing your combined feed.",
 | 
			
		||||
                    });
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,6 @@ import * as inbox_ui from "./inbox_ui";
 | 
			
		||||
import * as inbox_util from "./inbox_util";
 | 
			
		||||
import * as info_overlay from "./info_overlay";
 | 
			
		||||
import * as message_fetch from "./message_fetch";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
import * as message_viewport from "./message_viewport";
 | 
			
		||||
import * as modals from "./modals";
 | 
			
		||||
import * as narrow from "./narrow";
 | 
			
		||||
@@ -161,7 +160,6 @@ function do_hashchange_normal(from_reload) {
 | 
			
		||||
            if (from_reload) {
 | 
			
		||||
                blueslip.debug("We are narrowing as part of a reload.");
 | 
			
		||||
                if (message_fetch.initial_narrow_pointer !== undefined) {
 | 
			
		||||
                    message_lists.home.pre_narrow_offset = message_fetch.initial_offset;
 | 
			
		||||
                    narrow_opts.then_select_id = message_fetch.initial_narrow_pointer;
 | 
			
		||||
                    narrow_opts.then_select_offset = message_fetch.initial_narrow_offset;
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -1241,7 +1241,7 @@ export function delete_topic(stream_id, topic_name, failures = 0) {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function handle_narrow_deactivated() {
 | 
			
		||||
export function restore_edit_state_after_message_view_change() {
 | 
			
		||||
    assert(message_lists.current !== undefined);
 | 
			
		||||
    for (const [idx, elem] of currently_editing_messages) {
 | 
			
		||||
        if (message_lists.current.get(idx) !== undefined) {
 | 
			
		||||
 
 | 
			
		||||
@@ -166,7 +166,6 @@ export function insert_new_messages(messages, sent_by_this_client) {
 | 
			
		||||
    message_notifications.received_messages(messages);
 | 
			
		||||
    stream_list.update_streams_sidebar();
 | 
			
		||||
    pm_list.update_private_messages();
 | 
			
		||||
    recent_view_ui.process_messages(messages);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function update_messages(events) {
 | 
			
		||||
@@ -532,7 +531,6 @@ export function update_messages(events) {
 | 
			
		||||
    // propagated edits to be updated (since the topic edits can have
 | 
			
		||||
    // changed the correct grouping of messages).
 | 
			
		||||
    if (any_topic_edited || any_stream_changed) {
 | 
			
		||||
        message_lists.home.update_muting_and_rerender();
 | 
			
		||||
        // However, we don't need to rerender message_list if
 | 
			
		||||
        // we just changed the narrow earlier in this function.
 | 
			
		||||
        //
 | 
			
		||||
@@ -542,8 +540,15 @@ 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) {
 | 
			
		||||
            message_lists.current.update_muting_and_rerender();
 | 
			
		||||
 | 
			
		||||
        for (const list of message_lists.all_rendered_message_lists()) {
 | 
			
		||||
            if (changed_narrow && list === message_lists.current) {
 | 
			
		||||
                // Avoid updating current message list if user switched to a different narrow and
 | 
			
		||||
                // we don't want to preserver the rendered state for the current one.
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            list.view.rerender_messages(messages_to_rerender, any_message_content_edited);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        // If the content of the message was edited, we do a special animation.
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,10 @@ import $ from "jquery";
 | 
			
		||||
import {all_messages_data} from "./all_messages_data";
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import * as channel from "./channel";
 | 
			
		||||
import {Filter} from "./filter";
 | 
			
		||||
import * as huddle_data from "./huddle_data";
 | 
			
		||||
import * as message_feed_loading from "./message_feed_loading";
 | 
			
		||||
import * as message_feed_top_notices from "./message_feed_top_notices";
 | 
			
		||||
import * as message_helper from "./message_helper";
 | 
			
		||||
import * as message_list_data from "./message_list_data";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
import * as message_util from "./message_util";
 | 
			
		||||
import * as narrow_banner from "./narrow_banner";
 | 
			
		||||
@@ -19,21 +17,17 @@ import * as stream_data from "./stream_data";
 | 
			
		||||
import * as stream_list from "./stream_list";
 | 
			
		||||
import * as ui_report from "./ui_report";
 | 
			
		||||
 | 
			
		||||
export let initial_pointer;
 | 
			
		||||
export let initial_offset;
 | 
			
		||||
let first_messages_fetch = true;
 | 
			
		||||
export let initial_narrow_pointer;
 | 
			
		||||
export let initial_narrow_offset;
 | 
			
		||||
 | 
			
		||||
let is_all_messages_data_loaded = false;
 | 
			
		||||
 | 
			
		||||
const consts = {
 | 
			
		||||
    backfill_idle_time: 10 * 1000,
 | 
			
		||||
    backfill_batch_size: 1000,
 | 
			
		||||
    narrow_before: 50,
 | 
			
		||||
    narrow_after: 50,
 | 
			
		||||
    num_before_home_anchor: 200,
 | 
			
		||||
    num_after_home_anchor: 200,
 | 
			
		||||
    recent_view_initial_fetch_size: 400,
 | 
			
		||||
    initial_backfill_fetch_size: 400,
 | 
			
		||||
    maximum_initial_backfill_size: 4000,
 | 
			
		||||
    narrowed_view_backward_batch_size: 100,
 | 
			
		||||
    narrowed_view_forward_batch_size: 100,
 | 
			
		||||
    recent_view_fetch_more_batch_size: 1000,
 | 
			
		||||
@@ -54,13 +48,6 @@ function process_result(data, opts) {
 | 
			
		||||
    // messages not tracked in unread.ts during this fetching process.
 | 
			
		||||
    message_util.do_unread_count_updates(messages, true);
 | 
			
		||||
 | 
			
		||||
    // If we're loading more messages into the home view, save them to
 | 
			
		||||
    // the all_messages_data as well, as the message_lists.home is
 | 
			
		||||
    // reconstructed from all_messages_data.
 | 
			
		||||
    if (opts.msg_list === message_lists.home) {
 | 
			
		||||
        all_messages_data.add_messages(messages);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (messages.length !== 0) {
 | 
			
		||||
        if (opts.msg_list) {
 | 
			
		||||
            // Since this adds messages to the MessageList and renders MessageListView,
 | 
			
		||||
@@ -69,21 +56,6 @@ function process_result(data, opts) {
 | 
			
		||||
        } else {
 | 
			
		||||
            opts.msg_list_data.add_messages(messages);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // To avoid non-contiguous blocks of data in recent view from
 | 
			
		||||
        // message_lists.home and recent_view_message_list_data, we
 | 
			
		||||
        // only process data from message_lists.home if we have found
 | 
			
		||||
        // the newest message in message_lists.home. We check this via
 | 
			
		||||
        // is_all_messages_data_loaded, to avoid unnecessary
 | 
			
		||||
        // double-processing of the last batch of messages;
 | 
			
		||||
        // is_all_messages_data_loaded is set via opts.cont, below.
 | 
			
		||||
        if (
 | 
			
		||||
            opts.is_recent_view_data ||
 | 
			
		||||
            (opts.msg_list === message_lists.home && is_all_messages_data_loaded)
 | 
			
		||||
        ) {
 | 
			
		||||
            const msg_list_data = opts.msg_list_data ?? opts.msg_list.data;
 | 
			
		||||
            recent_view_ui.process_messages(messages, msg_list_data);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    huddle_data.process_loaded_messages(messages);
 | 
			
		||||
@@ -137,15 +109,6 @@ function get_messages_success(data, opts) {
 | 
			
		||||
            found_oldest: data.found_oldest,
 | 
			
		||||
            history_limited: data.history_limited,
 | 
			
		||||
        });
 | 
			
		||||
        if (opts.msg_list === message_lists.home) {
 | 
			
		||||
            // When we update message_lists.home, we need to also update
 | 
			
		||||
            // the fetch_status data structure for all_messages_data.
 | 
			
		||||
            all_messages_data.fetch_status.finish_older_batch({
 | 
			
		||||
                update_loading_indicator: false,
 | 
			
		||||
                found_oldest: data.found_oldest,
 | 
			
		||||
                history_limited: data.history_limited,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        message_feed_top_notices.update_top_of_narrow_notices(opts.msg_list);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -154,14 +117,6 @@ function get_messages_success(data, opts) {
 | 
			
		||||
            update_loading_indicator,
 | 
			
		||||
            found_newest: data.found_newest,
 | 
			
		||||
        });
 | 
			
		||||
        if (opts.msg_list === message_lists.home) {
 | 
			
		||||
            // When we update message_lists.home, we need to also update
 | 
			
		||||
            // the fetch_status data structure for all_messages_data.
 | 
			
		||||
            opts.fetch_again = all_messages_data.fetch_status.finish_newer_batch(data.messages, {
 | 
			
		||||
                update_loading_indicator: false,
 | 
			
		||||
                found_newest: data.found_newest,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (opts.msg_list && opts.msg_list.narrowed && opts.msg_list !== message_lists.current) {
 | 
			
		||||
@@ -255,16 +210,16 @@ export function load_messages(opts, attempt = 1) {
 | 
			
		||||
    //   data.narrow = opts.msg_list.data.filter.public_terms()
 | 
			
		||||
    //
 | 
			
		||||
    // But support for the all_messages_data sharing of data with
 | 
			
		||||
    // message_lists.home and the (hacky) page_params.narrow feature
 | 
			
		||||
    // the combined feed view and the (hacky) page_params.narrow feature
 | 
			
		||||
    // requires a somewhat ugly bundle of conditionals.
 | 
			
		||||
    if (msg_list_data.filter.is_in_home()) {
 | 
			
		||||
        if (page_params.narrow_stream !== undefined) {
 | 
			
		||||
            data.narrow = JSON.stringify(page_params.narrow);
 | 
			
		||||
        }
 | 
			
		||||
        // Otherwise, we don't pass narrow for message_lists.home; this is
 | 
			
		||||
        // required because it shares its data with all_msg_list, and
 | 
			
		||||
        // so we need the server to send us message history from muted
 | 
			
		||||
        // streams and topics even though message_lists.home's in:home
 | 
			
		||||
        // Otherwise, we don't pass narrow for the combined feed view; this is
 | 
			
		||||
        // required to display messages if their muted status changes without a new
 | 
			
		||||
        // network request, and so we need the server to send us message history from muted
 | 
			
		||||
        // streams and topics even though the combined feed view's in:home
 | 
			
		||||
        // operators will filter those.
 | 
			
		||||
    } else {
 | 
			
		||||
        let terms = msg_list_data.filter.public_terms();
 | 
			
		||||
@@ -280,11 +235,6 @@ export function load_messages(opts, attempt = 1) {
 | 
			
		||||
        msg_list_data.fetch_status.start_older_batch({
 | 
			
		||||
            update_loading_indicator,
 | 
			
		||||
        });
 | 
			
		||||
        if (opts.msg_list === message_lists.home) {
 | 
			
		||||
            all_messages_data.fetch_status.start_older_batch({
 | 
			
		||||
                update_loading_indicator,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (opts.num_after > 0) {
 | 
			
		||||
@@ -293,11 +243,6 @@ export function load_messages(opts, attempt = 1) {
 | 
			
		||||
        msg_list_data.fetch_status.start_newer_batch({
 | 
			
		||||
            update_loading_indicator,
 | 
			
		||||
        });
 | 
			
		||||
        if (opts.msg_list === message_lists.home) {
 | 
			
		||||
            all_messages_data.fetch_status.start_newer_batch({
 | 
			
		||||
                update_loading_indicator,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    data.client_gravatar = true;
 | 
			
		||||
@@ -416,12 +361,8 @@ export function load_messages_for_narrow(opts) {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function get_backfill_anchor(msg_list) {
 | 
			
		||||
    const oldest_msg =
 | 
			
		||||
        msg_list === message_lists.home
 | 
			
		||||
            ? all_messages_data.first_including_muted()
 | 
			
		||||
            : msg_list.data.first_including_muted();
 | 
			
		||||
 | 
			
		||||
export function get_backfill_anchor(msg_list_data) {
 | 
			
		||||
    const oldest_msg = msg_list_data.first_including_muted();
 | 
			
		||||
    if (oldest_msg) {
 | 
			
		||||
        return oldest_msg.id;
 | 
			
		||||
    }
 | 
			
		||||
@@ -430,10 +371,7 @@ export function get_backfill_anchor(msg_list) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function get_frontfill_anchor(msg_list) {
 | 
			
		||||
    const last_msg =
 | 
			
		||||
        msg_list === message_lists.home
 | 
			
		||||
            ? all_messages_data.last_including_muted()
 | 
			
		||||
            : msg_list.data.last_including_muted();
 | 
			
		||||
    const last_msg = msg_list.data.last_including_muted();
 | 
			
		||||
 | 
			
		||||
    if (last_msg) {
 | 
			
		||||
        return last_msg.id;
 | 
			
		||||
@@ -469,15 +407,15 @@ export function maybe_load_older_messages(opts) {
 | 
			
		||||
    // This function gets called when you scroll to the top
 | 
			
		||||
    // of your window, and you want to get messages older
 | 
			
		||||
    // than what the browsers originally fetched.
 | 
			
		||||
    const msg_list = opts.msg_list;
 | 
			
		||||
    if (!msg_list.data.fetch_status.can_load_older_messages()) {
 | 
			
		||||
    const msg_list_data = opts.msg_list_data ?? opts.msg_list.data;
 | 
			
		||||
    if (!msg_list_data.fetch_status.can_load_older_messages()) {
 | 
			
		||||
        // We may already be loading old messages or already
 | 
			
		||||
        // got the oldest one.
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    do_backfill({
 | 
			
		||||
        msg_list,
 | 
			
		||||
        ...opts,
 | 
			
		||||
        num_before: opts.recent_view
 | 
			
		||||
            ? consts.recent_view_fetch_more_batch_size
 | 
			
		||||
            : consts.narrowed_view_backward_batch_size,
 | 
			
		||||
@@ -485,14 +423,18 @@ export function maybe_load_older_messages(opts) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function do_backfill(opts) {
 | 
			
		||||
    const msg_list = opts.msg_list;
 | 
			
		||||
    const anchor = get_backfill_anchor(msg_list);
 | 
			
		||||
    const msg_list_data = opts.msg_list_data ?? opts.msg_list.data;
 | 
			
		||||
    const anchor = get_backfill_anchor(msg_list_data);
 | 
			
		||||
 | 
			
		||||
    // `load_messages` behaves differently for `msg_list` and `msg_list_data` as
 | 
			
		||||
    // parameters as which one is passed affects the behavior of the function.
 | 
			
		||||
    // So, we need to need them as they were provided to us.
 | 
			
		||||
    load_messages({
 | 
			
		||||
        anchor,
 | 
			
		||||
        num_before: opts.num_before,
 | 
			
		||||
        num_after: 0,
 | 
			
		||||
        msg_list,
 | 
			
		||||
        msg_list: opts.msg_list,
 | 
			
		||||
        msg_list_data: opts.msg_list_data,
 | 
			
		||||
        cont() {
 | 
			
		||||
            if (opts.cont) {
 | 
			
		||||
                opts.cont();
 | 
			
		||||
@@ -541,15 +483,13 @@ export function start_backfilling_messages() {
 | 
			
		||||
        onIdle() {
 | 
			
		||||
            do_backfill({
 | 
			
		||||
                num_before: consts.backfill_batch_size,
 | 
			
		||||
                msg_list: message_lists.home,
 | 
			
		||||
                msg_list_data: all_messages_data,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function set_initial_pointer_and_offset({pointer, offset, narrow_pointer, narrow_offset}) {
 | 
			
		||||
    initial_pointer = pointer;
 | 
			
		||||
    initial_offset = offset;
 | 
			
		||||
export function set_initial_pointer_and_offset({narrow_pointer, narrow_offset}) {
 | 
			
		||||
    initial_narrow_pointer = narrow_pointer;
 | 
			
		||||
    initial_narrow_offset = narrow_offset;
 | 
			
		||||
}
 | 
			
		||||
@@ -557,36 +497,19 @@ export function set_initial_pointer_and_offset({pointer, offset, narrow_pointer,
 | 
			
		||||
export function initialize(finished_initial_fetch) {
 | 
			
		||||
    // get the initial message list
 | 
			
		||||
    function load_more(data) {
 | 
			
		||||
        // If we haven't selected a message in the home view yet, and
 | 
			
		||||
        // the home view isn't empty, we select the anchor message here.
 | 
			
		||||
        if (message_lists.home.selected_id() === -1 && !message_lists.home.visibly_empty()) {
 | 
			
		||||
            // We fall back to the closest selected id, as the user
 | 
			
		||||
            // may have removed a stream from the home view while we
 | 
			
		||||
            // were loading data.
 | 
			
		||||
            message_lists.home.select_id(data.anchor, {
 | 
			
		||||
                then_scroll: true,
 | 
			
		||||
                use_closest: true,
 | 
			
		||||
                target_scroll_offset: initial_offset,
 | 
			
		||||
            });
 | 
			
		||||
        if (first_messages_fetch) {
 | 
			
		||||
            // See server_events.js for this callback.
 | 
			
		||||
            // Start processing server events.
 | 
			
		||||
            finished_initial_fetch();
 | 
			
		||||
            recent_view_ui.hide_loading_indicator();
 | 
			
		||||
            first_messages_fetch = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (data.found_newest) {
 | 
			
		||||
            // Mark that we've finishing loading all the way to the
 | 
			
		||||
            // present in the all_messages_data data set. At this
 | 
			
		||||
            // time, it's safe to call recent_view_ui.process_messages
 | 
			
		||||
            // with all the messages in our cache.
 | 
			
		||||
            is_all_messages_data_loaded = true;
 | 
			
		||||
            recent_view_ui.process_messages(all_messages_data.all_messages(), all_messages_data);
 | 
			
		||||
 | 
			
		||||
            if (page_params.is_spectator) {
 | 
			
		||||
                // Since for spectators, this is the main fetch, we
 | 
			
		||||
                // hide the Recent Conversations loading indicator here.
 | 
			
		||||
                recent_view_ui.hide_loading_indicator();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // See server_events.js for this callback.
 | 
			
		||||
            finished_initial_fetch();
 | 
			
		||||
        if (data.found_oldest) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (all_messages_data.num_items() >= consts.maximum_initial_backfill_size) {
 | 
			
		||||
            start_backfilling_messages();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@@ -596,72 +519,25 @@ export function initialize(finished_initial_fetch) {
 | 
			
		||||
        //
 | 
			
		||||
        // But we do it with a bit of delay, to reduce risk that we
 | 
			
		||||
        // hit rate limits with these backfills.
 | 
			
		||||
        const latest_id = data.messages.at(-1).id;
 | 
			
		||||
        const oldest_id = data.messages.at(0).id;
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            load_messages({
 | 
			
		||||
                anchor: latest_id,
 | 
			
		||||
                num_before: 0,
 | 
			
		||||
                num_after: consts.catch_up_batch_size,
 | 
			
		||||
                msg_list: message_lists.home,
 | 
			
		||||
                anchor: oldest_id,
 | 
			
		||||
                num_before: consts.catch_up_batch_size,
 | 
			
		||||
                num_after: 0,
 | 
			
		||||
                msg_list_data: all_messages_data,
 | 
			
		||||
                cont: load_more,
 | 
			
		||||
            });
 | 
			
		||||
        }, consts.catch_up_backfill_delay);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let anchor;
 | 
			
		||||
    if (initial_pointer !== undefined) {
 | 
			
		||||
        // If we're doing a server-initiated reload, similar to a
 | 
			
		||||
        // near: narrow query, we want to select a specific message.
 | 
			
		||||
        anchor = initial_pointer;
 | 
			
		||||
    } else {
 | 
			
		||||
        // Otherwise, we should just use the first unread message in
 | 
			
		||||
        // the user's unmuted history as our anchor.
 | 
			
		||||
        anchor = "first_unread";
 | 
			
		||||
    }
 | 
			
		||||
    load_messages({
 | 
			
		||||
        anchor,
 | 
			
		||||
        num_before: consts.num_before_home_anchor,
 | 
			
		||||
        num_after: consts.num_after_home_anchor,
 | 
			
		||||
        msg_list: message_lists.home,
 | 
			
		||||
        cont: load_more,
 | 
			
		||||
    // Since `all_messages_data` contains continuous message history
 | 
			
		||||
    // which always contains the latest message, it makes sense for
 | 
			
		||||
    // Recent view to display the same data and be in sync.
 | 
			
		||||
    all_messages_data.set_add_messages_callback((messages) => {
 | 
			
		||||
        recent_view_ui.process_messages(messages, all_messages_data);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (page_params.is_spectator) {
 | 
			
		||||
        // Since spectators never have old unreads, we can skip the
 | 
			
		||||
        // hacky fetch below for them (which would just waste resources).
 | 
			
		||||
 | 
			
		||||
        // This optimization requires a bit of duplicated loading
 | 
			
		||||
        // indicator code, here and hiding logic in hide_more.
 | 
			
		||||
        recent_view_ui.show_loading_indicator();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // In addition to the algorithm above, which is designed to ensure
 | 
			
		||||
    // that we fetch all message history eventually starting with the
 | 
			
		||||
    // first unread message, we also need to ensure that the Recent
 | 
			
		||||
    // Topics page contains the very most recent threads on page load.
 | 
			
		||||
    //
 | 
			
		||||
    // Long term, we'll want to replace this with something that's
 | 
			
		||||
    // more performant (i.e. avoids this unnecessary extra fetch the
 | 
			
		||||
    // results of which are basically discarded) and better represents
 | 
			
		||||
    // more than a few hundred messages' history, but this strategy
 | 
			
		||||
    // allows "Recent Conversations" to always show current data (with gaps)
 | 
			
		||||
    // on page load; the data will be complete once the algorithm
 | 
			
		||||
    // above catches up to present.
 | 
			
		||||
    //
 | 
			
		||||
    // (Users will see a weird artifact where Recent Conversations has a gap
 | 
			
		||||
    // between E.g. 6 days ago and 37 days ago while the catchup
 | 
			
		||||
    // process runs, so this strategy still results in problematic
 | 
			
		||||
    // visual artifacts shortly after page load; just more forgivable
 | 
			
		||||
    // ones).
 | 
			
		||||
    //
 | 
			
		||||
    // We only initialize MessageListData here, since we don't
 | 
			
		||||
    // want update the UI and confuse the functions in MessageList.
 | 
			
		||||
    // Recent view can handle the UI updates itself.
 | 
			
		||||
    const recent_view_message_list_data = new message_list_data.MessageListData({
 | 
			
		||||
        filter: new Filter([{operator: "in", operand: "home"}]),
 | 
			
		||||
        excludes_muted_topics: true,
 | 
			
		||||
    });
 | 
			
		||||
    // TODO: Ideally we'd have loading indicators for Recent Conversations
 | 
			
		||||
    // at both top and bottom be managed by load_messages, but that
 | 
			
		||||
    // likely depends on other reorganizations of the early loading
 | 
			
		||||
@@ -669,10 +545,9 @@ export function initialize(finished_initial_fetch) {
 | 
			
		||||
    recent_view_ui.show_loading_indicator();
 | 
			
		||||
    load_messages({
 | 
			
		||||
        anchor: "newest",
 | 
			
		||||
        num_before: consts.recent_view_initial_fetch_size,
 | 
			
		||||
        num_before: consts.initial_backfill_fetch_size,
 | 
			
		||||
        num_after: 0,
 | 
			
		||||
        msg_list_data: recent_view_message_list_data,
 | 
			
		||||
        is_recent_view_data: true,
 | 
			
		||||
        cont: recent_view_ui.hide_loading_indicator,
 | 
			
		||||
        msg_list_data: all_messages_data,
 | 
			
		||||
        cont: load_more,
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,10 @@ import autosize from "autosize";
 | 
			
		||||
import $ from "jquery";
 | 
			
		||||
import assert from "minimalistic-assert";
 | 
			
		||||
 | 
			
		||||
import {all_messages_data} from "./all_messages_data";
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import {Filter} from "./filter";
 | 
			
		||||
import {MessageListData} from "./message_list_data";
 | 
			
		||||
import * as message_list_tooltips from "./message_list_tooltips";
 | 
			
		||||
import {MessageListView} from "./message_list_view";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
import * as narrow_banner from "./narrow_banner";
 | 
			
		||||
import * as narrow_state from "./narrow_state";
 | 
			
		||||
import {page_params} from "./page_params";
 | 
			
		||||
@@ -59,8 +56,7 @@ export class MessageList {
 | 
			
		||||
        // DOM.
 | 
			
		||||
        this.view = new MessageListView(this, collapse_messages, opts.is_node_test);
 | 
			
		||||
 | 
			
		||||
        // Whether this is a narrowed message list. The only message
 | 
			
		||||
        // list that is not is the home_msg_list global.
 | 
			
		||||
        // If this message list is not for the global feed.
 | 
			
		||||
        this.narrowed = !this.data.filter.is_in_home();
 | 
			
		||||
 | 
			
		||||
        // Keeps track of whether the user has done a UI interaction,
 | 
			
		||||
@@ -72,6 +68,17 @@ export class MessageList {
 | 
			
		||||
        // the user. Possibly this can be unified in some nice way.
 | 
			
		||||
        this.reading_prevented = false;
 | 
			
		||||
 | 
			
		||||
        // Whether this message list's is preserved in the DOM even
 | 
			
		||||
        // when viewing other views -- a valuable optimization for
 | 
			
		||||
        // fast toggling between the combined feed and other views,
 | 
			
		||||
        // which we enable only when that is the user's home view.
 | 
			
		||||
        //
 | 
			
		||||
        // This is intentionally not live-updated when web_home_view
 | 
			
		||||
        // changes, since it's easier to reason about if this
 | 
			
		||||
        // optimization is active or not for an entire session.
 | 
			
		||||
        this.preserve_rendered_state =
 | 
			
		||||
            user_settings.web_home_view === "all_messages" && !this.narrowed;
 | 
			
		||||
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -110,14 +117,14 @@ export class MessageList {
 | 
			
		||||
            render_info = this.append_to_view(bottom_messages, opts);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.narrowed && !this.visibly_empty()) {
 | 
			
		||||
        if (!this.visibly_empty()) {
 | 
			
		||||
            // If adding some new messages to the message tables caused
 | 
			
		||||
            // our current narrow to no longer be empty, hide the empty
 | 
			
		||||
            // feed placeholder text.
 | 
			
		||||
            narrow_banner.hide_empty_narrow_message();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.narrowed && !this.visibly_empty() && this.selected_id() === -1) {
 | 
			
		||||
        if (!this.visibly_empty() && this.selected_id() === -1) {
 | 
			
		||||
            // The message list was previously empty, but now isn't
 | 
			
		||||
            // due to adding these messages, and we need to select a
 | 
			
		||||
            // message. Regardless of whether the messages are new or
 | 
			
		||||
@@ -468,19 +475,6 @@ export class MessageList {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update_muting_and_rerender() {
 | 
			
		||||
        // For the home message list, we need to re-initialize
 | 
			
		||||
        // _all_items for stream muting/topic unmuting from
 | 
			
		||||
        // all_messages_data, since otherwise unmuting a previously
 | 
			
		||||
        // muted stream won't work.
 | 
			
		||||
        //
 | 
			
		||||
        // "in-home" filter doesn't included muted stream messages, so we
 | 
			
		||||
        // need to repopulate the message list with all messages to include
 | 
			
		||||
        // the previous messages in muted streams so that update_items_for_muting works.
 | 
			
		||||
        if (this.data.filter.is_in_home()) {
 | 
			
		||||
            this.data.clear();
 | 
			
		||||
            this.data.add_messages(all_messages_data.all_messages());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.data.update_items_for_muting();
 | 
			
		||||
        // We need to rerender whether or not the narrow hides muted
 | 
			
		||||
        // topics, because we need to update recipient bars for topics
 | 
			
		||||
@@ -529,12 +523,3 @@ export class MessageList {
 | 
			
		||||
        return this.data.get_last_message_sent_by_me();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function initialize() {
 | 
			
		||||
    /* Create home_msg_list and register it. */
 | 
			
		||||
    const home_msg_list = new MessageList({
 | 
			
		||||
        filter: new Filter([{operator: "in", operand: "home"}]),
 | 
			
		||||
        excludes_muted_topics: true,
 | 
			
		||||
    });
 | 
			
		||||
    message_lists.set_home(home_msg_list);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,8 @@ export class MessageListData {
 | 
			
		||||
    // there are no messages matching the current filter.
 | 
			
		||||
    _selected_id: number;
 | 
			
		||||
    predicate?: (message: Message) => boolean;
 | 
			
		||||
    // This is a callback that is called when messages are added to the message list.
 | 
			
		||||
    add_messages_callback?: (messages: Message[]) => void;
 | 
			
		||||
 | 
			
		||||
    // MessageListData is a core data structure for keeping track of a
 | 
			
		||||
    // contiguous block of messages matching a given narrow that can
 | 
			
		||||
@@ -60,6 +62,10 @@ export class MessageListData {
 | 
			
		||||
        this._selected_id = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set_add_messages_callback(callback: () => void): void {
 | 
			
		||||
        this.add_messages_callback = callback;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    all_messages(): Message[] {
 | 
			
		||||
        return this._items;
 | 
			
		||||
    }
 | 
			
		||||
@@ -333,6 +339,10 @@ export class MessageListData {
 | 
			
		||||
            bottom_messages = this.append(bottom_messages);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.add_messages_callback) {
 | 
			
		||||
            this.add_messages_callback(messages);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const info = {
 | 
			
		||||
            top_messages,
 | 
			
		||||
            bottom_messages,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
import $ from "jquery";
 | 
			
		||||
import assert from "minimalistic-assert";
 | 
			
		||||
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import * as inbox_util from "./inbox_util";
 | 
			
		||||
@@ -33,6 +32,7 @@ export type SelectIdOpts = {
 | 
			
		||||
 | 
			
		||||
export type MessageList = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    preserve_rendered_state: boolean;
 | 
			
		||||
    view: MessageListView;
 | 
			
		||||
    selected_id: () => number;
 | 
			
		||||
    selected_row: () => JQuery;
 | 
			
		||||
@@ -52,39 +52,37 @@ export type MessageList = {
 | 
			
		||||
    ) => RenderInfo | undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export let home: MessageList | undefined;
 | 
			
		||||
export let current: MessageList | undefined;
 | 
			
		||||
export const rendered_message_lists = new Map<number, MessageList>();
 | 
			
		||||
 | 
			
		||||
export function set_current(msg_list: MessageList | undefined): void {
 | 
			
		||||
    // NOTE: Use update_current_message_list instead of this function.
 | 
			
		||||
    // NOTE: Strictly used for mocking in node tests.
 | 
			
		||||
    // Use `update_current_message_list` instead in production.
 | 
			
		||||
    current = msg_list;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function update_current_message_list(msg_list: MessageList | undefined): void {
 | 
			
		||||
    if (msg_list !== home) {
 | 
			
		||||
        home?.view.$list.removeClass("focused-message-list");
 | 
			
		||||
    if (current && !current.preserve_rendered_state) {
 | 
			
		||||
        // Remove the current message list from the DOM.
 | 
			
		||||
        current.view.$list.remove();
 | 
			
		||||
        rendered_message_lists.delete(current.id);
 | 
			
		||||
    } else {
 | 
			
		||||
        current?.view.$list.removeClass("focused-message-list");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (current !== home) {
 | 
			
		||||
        // Remove old msg list from DOM.
 | 
			
		||||
        current?.view.$list.remove();
 | 
			
		||||
    current = msg_list;
 | 
			
		||||
    if (current !== undefined) {
 | 
			
		||||
        rendered_message_lists.set(current.id, current);
 | 
			
		||||
        current.view.$list.addClass("focused-message-list");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    set_current(msg_list);
 | 
			
		||||
    current?.view.$list.addClass("focused-message-list");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function set_home(msg_list: MessageList): void {
 | 
			
		||||
    home = msg_list;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function all_rendered_message_lists(): MessageList[] {
 | 
			
		||||
    assert(home !== undefined);
 | 
			
		||||
    const rendered_message_lists = [home];
 | 
			
		||||
    if (current !== undefined && current !== home) {
 | 
			
		||||
        rendered_message_lists.push(current);
 | 
			
		||||
    }
 | 
			
		||||
    return rendered_message_lists;
 | 
			
		||||
    return [...rendered_message_lists.values()];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function add_rendered_message_list(msg_list: MessageList): void {
 | 
			
		||||
    rendered_message_lists.set(msg_list.id, msg_list);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function all_rendered_row_for_message_id(message_id: number): JQuery {
 | 
			
		||||
@@ -112,7 +110,11 @@ export function update_recipient_bar_background_color(): void {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function save_pre_narrow_offset_for_reload(): void {
 | 
			
		||||
    if (current === undefined) {
 | 
			
		||||
    // Only save the pre_narrow_offset if the message list will be cached if user
 | 
			
		||||
    // switches to a different narrow, otherwise the pre_narrow_offset would just be lost when
 | 
			
		||||
    // user switches to a different narrow. In case of a reload, offset for the current
 | 
			
		||||
    // message is captured and restored by `reload` library.
 | 
			
		||||
    if (!current?.preserve_rendered_state) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,6 @@ import * as util from "./util";
 | 
			
		||||
import * as widgetize from "./widgetize";
 | 
			
		||||
 | 
			
		||||
const LARGER_THAN_MAX_MESSAGE_ID = 10000000000000000;
 | 
			
		||||
export let has_visited_all_messages = false;
 | 
			
		||||
 | 
			
		||||
export function reset_ui_state() {
 | 
			
		||||
    // Resets the state of various visual UI elements that are
 | 
			
		||||
@@ -98,7 +97,7 @@ export function activate(raw_terms, opts) {
 | 
			
		||||
 | 
			
		||||
       raw_terms: Narrowing/search terms; used to construct
 | 
			
		||||
       a Filter object that decides which messages belong in the
 | 
			
		||||
       view.  Required (See the above note on how `message_lists.home` works)
 | 
			
		||||
       view.
 | 
			
		||||
 | 
			
		||||
       All other options are encoded via the `opts` dictionary:
 | 
			
		||||
 | 
			
		||||
@@ -128,14 +127,14 @@ export function activate(raw_terms, opts) {
 | 
			
		||||
        raw_terms = [{operator: "is", operand: "home"}];
 | 
			
		||||
    }
 | 
			
		||||
    const filter = new Filter(raw_terms);
 | 
			
		||||
 | 
			
		||||
    const is_narrowed_to_all_messages_view = narrow_state.filter()?.is_in_home();
 | 
			
		||||
    if (has_visited_all_messages && is_narrowed_to_all_messages_view && filter.is_in_home()) {
 | 
			
		||||
    const is_combined_feed_global_view = filter.is_in_home();
 | 
			
		||||
    const is_narrowed_to_combined_feed_view = narrow_state.filter()?.is_in_home();
 | 
			
		||||
    if (is_narrowed_to_combined_feed_view && is_combined_feed_global_view) {
 | 
			
		||||
        // If we're already looking at the combined feed, exit without doing any work.
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (filter.is_in_home() && message_scroll_state.actively_scrolling) {
 | 
			
		||||
    if (is_combined_feed_global_view && message_scroll_state.actively_scrolling) {
 | 
			
		||||
        // TODO: Figure out why puppeteer test for this fails when run for narrows
 | 
			
		||||
        // other than `Combined feed`.
 | 
			
		||||
 | 
			
		||||
@@ -149,8 +148,7 @@ export function activate(raw_terms, opts) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Use to determine if user read any unread messages outside the combined feed.
 | 
			
		||||
    // BUG: This doesn't check for the combined feed?
 | 
			
		||||
    const was_narrowed_already = narrow_state.filter() !== undefined;
 | 
			
		||||
    const was_narrowed_already = message_lists.current?.narrowed;
 | 
			
		||||
 | 
			
		||||
    // Since narrow.activate is called directly from various
 | 
			
		||||
    // places in our code without passing through hashchange,
 | 
			
		||||
@@ -161,7 +159,7 @@ export function activate(raw_terms, opts) {
 | 
			
		||||
        // TODO: is:home is currently not permitted for spectators
 | 
			
		||||
        // because they can't mute things; maybe that's the wrong
 | 
			
		||||
        // policy?
 | 
			
		||||
        !filter.is_in_home() &&
 | 
			
		||||
        !is_combined_feed_global_view &&
 | 
			
		||||
        raw_terms.some(
 | 
			
		||||
            (raw_term) => !hash_parser.allowed_web_public_narrows.includes(raw_term.operator),
 | 
			
		||||
        )
 | 
			
		||||
@@ -424,11 +422,21 @@ export function activate(raw_terms, opts) {
 | 
			
		||||
 | 
			
		||||
        const excludes_muted_topics = filter.excludes_muted_topics();
 | 
			
		||||
 | 
			
		||||
        // Check if we already have a rendered message list for the `filter`.
 | 
			
		||||
        // TODO: If we add a message list other than `is_in_home` to be save as rendered,
 | 
			
		||||
        // we need to add a `is_equal` function to `Filter` to compare the filters.
 | 
			
		||||
        let msg_list;
 | 
			
		||||
        if (filter.is_in_home()) {
 | 
			
		||||
            has_visited_all_messages = true;
 | 
			
		||||
            msg_list = message_lists.home;
 | 
			
		||||
        } else {
 | 
			
		||||
        let restore_rendered_list = false;
 | 
			
		||||
        for (const list of message_lists.all_rendered_message_lists()) {
 | 
			
		||||
            if (is_combined_feed_global_view && list.preserve_rendered_state) {
 | 
			
		||||
                assert(list.data.filter.is_in_home());
 | 
			
		||||
                msg_list = list;
 | 
			
		||||
                restore_rendered_list = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!restore_rendered_list) {
 | 
			
		||||
            let msg_data = new MessageListData({
 | 
			
		||||
                filter,
 | 
			
		||||
                excludes_muted_topics,
 | 
			
		||||
@@ -461,7 +469,6 @@ export function activate(raw_terms, opts) {
 | 
			
		||||
        }
 | 
			
		||||
        assert(msg_list !== undefined);
 | 
			
		||||
 | 
			
		||||
        narrow_state.set_has_shown_message_list_view();
 | 
			
		||||
        // Show the new set of messages. It is important to set message_lists.current to
 | 
			
		||||
        // the view right as it's being shown, because we rely on message_lists.current
 | 
			
		||||
        // being shown for deciding when to condense messages.
 | 
			
		||||
@@ -473,7 +480,7 @@ export function activate(raw_terms, opts) {
 | 
			
		||||
        let select_immediately;
 | 
			
		||||
        let select_opts;
 | 
			
		||||
        let then_select_offset;
 | 
			
		||||
        if (filter.is_in_home()) {
 | 
			
		||||
        if (restore_rendered_list) {
 | 
			
		||||
            select_immediately = true;
 | 
			
		||||
            select_opts = {
 | 
			
		||||
                empty_ok: true,
 | 
			
		||||
@@ -502,7 +509,7 @@ export function activate(raw_terms, opts) {
 | 
			
		||||
            message_lists.current.resume_reading();
 | 
			
		||||
            // Reset the collapsed status of messages rows.
 | 
			
		||||
            condense.condense_and_collapse(message_lists.current.view.$list.find(".message_row"));
 | 
			
		||||
            message_edit.handle_narrow_deactivated();
 | 
			
		||||
            message_edit.restore_edit_state_after_message_view_change();
 | 
			
		||||
            widgetize.set_widgets_for_list();
 | 
			
		||||
            message_feed_top_notices.update_top_of_narrow_notices(msg_list);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -92,7 +92,7 @@ function pick_empty_narrow_banner(): NarrowBannerData {
 | 
			
		||||
 | 
			
		||||
    const current_filter = narrow_state.filter();
 | 
			
		||||
 | 
			
		||||
    if (current_filter === undefined) {
 | 
			
		||||
    if (current_filter === undefined || current_filter.is_in_home()) {
 | 
			
		||||
        return default_banner;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,15 +8,9 @@ import * as stream_data from "./stream_data";
 | 
			
		||||
import type {StreamSubscription} from "./sub_store";
 | 
			
		||||
import * as unread from "./unread";
 | 
			
		||||
 | 
			
		||||
export let has_shown_message_list_view = false;
 | 
			
		||||
 | 
			
		||||
export function filter(): Filter | undefined {
 | 
			
		||||
    // `Combined feed`, `Recent Conversations` and `Inbox` return undefined;
 | 
			
		||||
    if (message_lists.current === undefined || message_lists.current.data.filter.is_in_home()) {
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return message_lists.current.data.filter;
 | 
			
		||||
    // `Recent Conversations` and `Inbox` return undefined;
 | 
			
		||||
    return message_lists.current?.data.filter;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function search_terms(current_filter: Filter | undefined = filter()): NarrowTerm[] {
 | 
			
		||||
@@ -381,7 +375,3 @@ export function is_for_stream_id(stream_id: number, filter?: Filter): boolean {
 | 
			
		||||
 | 
			
		||||
    return stream_id === narrow_sub.stream_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function set_has_shown_message_list_view(): void {
 | 
			
		||||
    has_shown_message_list_view = true;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -403,14 +403,7 @@ export function hide_loading_indicator() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function process_messages(messages, msg_list_data) {
 | 
			
		||||
    // This code path processes messages from 3 sources:
 | 
			
		||||
    // 1. Newly sent messages from the server_events system. This is safe to
 | 
			
		||||
    //    process because we always will have the latest previously sent messages.
 | 
			
		||||
    // 2. Messages in all_messages_data, the main cache of contiguous
 | 
			
		||||
    //    message history that the client maintains.
 | 
			
		||||
    // 3. Latest messages fetched specifically for Recent view when
 | 
			
		||||
    //    the browser first loads. We will be able to remove this once
 | 
			
		||||
    //    all_messages_data is fetched in a better order.
 | 
			
		||||
    // Always synced with messages in all_messages_data.
 | 
			
		||||
 | 
			
		||||
    let conversation_data_updated = false;
 | 
			
		||||
    if (messages.length > 0) {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ function call_reload_hooks() {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function preserve_state(send_after_reload, save_pointer, save_compose) {
 | 
			
		||||
function preserve_state(send_after_reload, save_compose) {
 | 
			
		||||
    if (!localstorage.supported()) {
 | 
			
		||||
        // If local storage is not supported by the browser, we can't
 | 
			
		||||
        // save the browser's position across reloads (since there's
 | 
			
		||||
@@ -68,23 +68,7 @@ function preserve_state(send_after_reload, save_pointer, save_compose) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (save_pointer) {
 | 
			
		||||
        const pointer = message_lists.home.selected_id();
 | 
			
		||||
        if (pointer !== -1) {
 | 
			
		||||
            url += "+pointer=" + pointer;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message_lists.current === message_lists.home) {
 | 
			
		||||
        const $row = message_lists.home.selected_row();
 | 
			
		||||
        if ($row.length > 0) {
 | 
			
		||||
            url += "+offset=" + $row.get_offset_to_window().top;
 | 
			
		||||
        }
 | 
			
		||||
    } else if (message_lists.current !== undefined) {
 | 
			
		||||
        url += "+offset=" + message_lists.home.pre_narrow_offset;
 | 
			
		||||
 | 
			
		||||
        // narrow_state.filter() is not undefined, so this is the current
 | 
			
		||||
        // narrowed message list.
 | 
			
		||||
    if (message_lists.current !== undefined) {
 | 
			
		||||
        const narrow_pointer = message_lists.current.selected_id();
 | 
			
		||||
        if (narrow_pointer !== -1) {
 | 
			
		||||
            url += "+narrow_pointer=" + narrow_pointer;
 | 
			
		||||
@@ -93,8 +77,6 @@ function preserve_state(send_after_reload, save_pointer, save_compose) {
 | 
			
		||||
        if ($narrow_row.length > 0) {
 | 
			
		||||
            url += "+narrow_offset=" + $narrow_row.get_offset_to_window().top;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        url += "+offset=" + message_lists.home.pre_narrow_offset;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    url += hash_util.build_reload_url();
 | 
			
		||||
@@ -146,7 +128,7 @@ function delete_stale_tokens(ls) {
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function do_reload_app(send_after_reload, save_pointer, save_compose, message_html) {
 | 
			
		||||
function do_reload_app(send_after_reload, save_compose, message_html) {
 | 
			
		||||
    if (reload_state.is_in_progress()) {
 | 
			
		||||
        blueslip.log("do_reload_app: Doing nothing since reload_in_progress");
 | 
			
		||||
        return;
 | 
			
		||||
@@ -154,7 +136,7 @@ function do_reload_app(send_after_reload, save_pointer, save_compose, message_ht
 | 
			
		||||
 | 
			
		||||
    // TODO: we should completely disable the UI here
 | 
			
		||||
    try {
 | 
			
		||||
        preserve_state(send_after_reload, save_pointer, save_compose);
 | 
			
		||||
        preserve_state(send_after_reload, save_compose);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        blueslip.error("Failed to preserve state", undefined, error);
 | 
			
		||||
    }
 | 
			
		||||
@@ -199,13 +181,12 @@ function do_reload_app(send_after_reload, save_pointer, save_compose, message_ht
 | 
			
		||||
 | 
			
		||||
export function initiate({
 | 
			
		||||
    immediate = false,
 | 
			
		||||
    save_pointer = true,
 | 
			
		||||
    save_compose = true,
 | 
			
		||||
    send_after_reload = false,
 | 
			
		||||
    message_html = "Reloading ...",
 | 
			
		||||
}) {
 | 
			
		||||
    if (immediate) {
 | 
			
		||||
        do_reload_app(send_after_reload, save_pointer, save_compose, message_html);
 | 
			
		||||
        do_reload_app(send_after_reload, save_compose, message_html);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (reload_state.is_pending() || reload_state.is_in_progress()) {
 | 
			
		||||
@@ -242,7 +223,7 @@ export function initiate({
 | 
			
		||||
    let compose_started_handler;
 | 
			
		||||
 | 
			
		||||
    function reload_from_idle() {
 | 
			
		||||
        do_reload_app(false, save_pointer, save_compose, message_html);
 | 
			
		||||
        do_reload_app(false, save_compose, message_html);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Make sure we always do a reload eventually after
 | 
			
		||||
@@ -296,7 +277,6 @@ reload_state.set_csrf_failed_handler(() => {
 | 
			
		||||
 | 
			
		||||
    initiate({
 | 
			
		||||
        immediate: true,
 | 
			
		||||
        save_pointer: true,
 | 
			
		||||
        save_compose: true,
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -68,13 +68,15 @@ export function initialize() {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const pointer = Number.parseInt(vars.pointer, 10);
 | 
			
		||||
    const offset = Number.parseInt(vars.offset, 10);
 | 
			
		||||
    // We only restore pointer and offset for the current narrow, even if there are narrows that
 | 
			
		||||
    // were cached before the reload, they are no longer cached after the reload. We could possibly
 | 
			
		||||
    // store the pointer and offset for these narrows but it might lead to a confusing experience if
 | 
			
		||||
    // user gets back to these narrow much later (maybe days) and finds them at a random position in
 | 
			
		||||
    // narrow which they didn't navigate to while they were trying to just get to the latest unread
 | 
			
		||||
    // message in that narrow which will now take more effort to find.
 | 
			
		||||
    const narrow_pointer = Number.parseInt(vars.narrow_pointer, 10);
 | 
			
		||||
    const narrow_offset = Number.parseInt(vars.narrow_offset, 10);
 | 
			
		||||
    message_fetch.set_initial_pointer_and_offset({
 | 
			
		||||
        pointer: Number.isNaN(pointer) ? undefined : pointer,
 | 
			
		||||
        offset: Number.isNaN(offset) ? undefined : offset,
 | 
			
		||||
        narrow_pointer: Number.isNaN(narrow_pointer) ? undefined : narrow_pointer,
 | 
			
		||||
        narrow_offset: Number.isNaN(narrow_offset) ? undefined : narrow_offset,
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ import * as blueslip from "./blueslip";
 | 
			
		||||
import * as channel from "./channel";
 | 
			
		||||
import * as echo from "./echo";
 | 
			
		||||
import * as message_events from "./message_events";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
import {page_params} from "./page_params";
 | 
			
		||||
import * as reload from "./reload";
 | 
			
		||||
import * as reload_state from "./reload_state";
 | 
			
		||||
@@ -128,10 +127,6 @@ function get_events_success(events) {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (message_lists.home.selected_id() === -1 && !message_lists.home.visibly_empty()) {
 | 
			
		||||
        message_lists.home.select_id(message_lists.home.first().id, {then_scroll: false});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (update_message_events.length !== 0) {
 | 
			
		||||
        try {
 | 
			
		||||
            message_events.update_messages(update_message_events);
 | 
			
		||||
@@ -221,7 +216,6 @@ function get_events({dont_block = false} = {}) {
 | 
			
		||||
                    event_queue_expired = true;
 | 
			
		||||
                    reload.initiate({
 | 
			
		||||
                        immediate: true,
 | 
			
		||||
                        save_pointer: false,
 | 
			
		||||
                        save_compose: true,
 | 
			
		||||
                    });
 | 
			
		||||
                    return;
 | 
			
		||||
 
 | 
			
		||||
@@ -175,7 +175,6 @@ export function dispatch_normal_event(event) {
 | 
			
		||||
 | 
			
		||||
        case "web_reload_client": {
 | 
			
		||||
            const reload_options = {
 | 
			
		||||
                save_pointer: true,
 | 
			
		||||
                save_compose: true,
 | 
			
		||||
                message_html: "The application has been updated; reloading!",
 | 
			
		||||
            };
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,6 @@ export function send_message(request, on_success, error) {
 | 
			
		||||
                    // The error might be due to the server changing
 | 
			
		||||
                    reload.initiate({
 | 
			
		||||
                        immediate: true,
 | 
			
		||||
                        save_pointer: true,
 | 
			
		||||
                        save_compose: true,
 | 
			
		||||
                        send_after_reload: true,
 | 
			
		||||
                    });
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import * as activity from "./activity";
 | 
			
		||||
import * as activity_ui from "./activity_ui";
 | 
			
		||||
import * as add_stream_options_popover from "./add_stream_options_popover";
 | 
			
		||||
import * as alert_words from "./alert_words";
 | 
			
		||||
import {all_messages_data} from "./all_messages_data";
 | 
			
		||||
import * as audible_notifications from "./audible_notifications";
 | 
			
		||||
import * as blueslip from "./blueslip";
 | 
			
		||||
import * as bot_data from "./bot_data";
 | 
			
		||||
@@ -63,7 +64,6 @@ import * as markdown_config from "./markdown_config";
 | 
			
		||||
import * as message_actions_popover from "./message_actions_popover";
 | 
			
		||||
import * as message_edit_history from "./message_edit_history";
 | 
			
		||||
import * as message_fetch from "./message_fetch";
 | 
			
		||||
import * as message_list from "./message_list";
 | 
			
		||||
import * as message_list_hover from "./message_list_hover";
 | 
			
		||||
import * as message_list_tooltips from "./message_list_tooltips";
 | 
			
		||||
import * as message_lists from "./message_lists";
 | 
			
		||||
@@ -726,7 +726,6 @@ export function initialize_everything(state_data) {
 | 
			
		||||
 | 
			
		||||
    realm_logo.initialize();
 | 
			
		||||
    message_lists.initialize();
 | 
			
		||||
    message_list.initialize();
 | 
			
		||||
    recent_view_ui.initialize({
 | 
			
		||||
        on_click_participant(avatar_element, participant_user_id) {
 | 
			
		||||
            const user = people.get_by_user_id(participant_user_id);
 | 
			
		||||
@@ -736,7 +735,7 @@ export function initialize_everything(state_data) {
 | 
			
		||||
        on_mark_topic_as_read: unread_ops.mark_topic_as_read,
 | 
			
		||||
        maybe_load_older_messages() {
 | 
			
		||||
            message_fetch.maybe_load_older_messages({
 | 
			
		||||
                msg_list: message_lists.home,
 | 
			
		||||
                msg_list_data: all_messages_data,
 | 
			
		||||
                recent_view: true,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -47,10 +47,11 @@ export function handle_topic_updates(user_topic_event) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setTimeout(0, () => {
 | 
			
		||||
        /* Rerender the combined feed view if necessary, but defer until after
 | 
			
		||||
         * the browser has rendered the DOM updates scheduled above. */
 | 
			
		||||
        if (message_lists.current !== message_lists.home) {
 | 
			
		||||
            message_lists.home.update_muting_and_rerender();
 | 
			
		||||
        // Defer updates for any background-rendered messages lists until the visible one has been updated.
 | 
			
		||||
        for (const list of message_lists.all_rendered_message_lists()) {
 | 
			
		||||
            if (list.preserve_rendered_state && message_lists.current !== list) {
 | 
			
		||||
                list.update_muting_and_rerender();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -72,9 +72,7 @@ export function show(opts: {
 | 
			
		||||
    complete_rerender: () => void;
 | 
			
		||||
    is_recent_view?: boolean;
 | 
			
		||||
}): void {
 | 
			
		||||
    if (narrow_state.has_shown_message_list_view) {
 | 
			
		||||
        message_lists.save_pre_narrow_offset_for_reload();
 | 
			
		||||
    }
 | 
			
		||||
    message_lists.save_pre_narrow_offset_for_reload();
 | 
			
		||||
 | 
			
		||||
    if (opts.is_visible()) {
 | 
			
		||||
        // If we're already visible, E.g. because the user hit Esc
 | 
			
		||||
 
 | 
			
		||||
@@ -92,6 +92,7 @@ const user_groups = mock_esm("../src/user_groups");
 | 
			
		||||
const user_group_edit = mock_esm("../src/user_group_edit");
 | 
			
		||||
const overlays = mock_esm("../src/overlays");
 | 
			
		||||
mock_esm("../src/giphy");
 | 
			
		||||
const {Filter} = zrequire("filter");
 | 
			
		||||
 | 
			
		||||
const electron_bridge = set_global("electron_bridge", {});
 | 
			
		||||
 | 
			
		||||
@@ -101,19 +102,17 @@ message_lists.current = {
 | 
			
		||||
    rerender_view: noop,
 | 
			
		||||
    data: {
 | 
			
		||||
        get_messages_sent_by_user: () => [],
 | 
			
		||||
        filter: {
 | 
			
		||||
            is_in_home: () => true,
 | 
			
		||||
        },
 | 
			
		||||
        filter: new Filter([]),
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
message_lists.home = {
 | 
			
		||||
const cached_message_list = {
 | 
			
		||||
    get_row: noop,
 | 
			
		||||
    rerender_view: noop,
 | 
			
		||||
    data: {
 | 
			
		||||
        get_messages_sent_by_user: () => [],
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [cached_message_list, message_lists.current];
 | 
			
		||||
 | 
			
		||||
// page_params is highly coupled to dispatching now
 | 
			
		||||
page_params.test_suite = false;
 | 
			
		||||
@@ -783,7 +782,6 @@ run_test("web_reload_client", ({override}) => {
 | 
			
		||||
    dispatch(event);
 | 
			
		||||
    assert.equal(stub.num_calls, 1);
 | 
			
		||||
    const args = stub.get_args("options");
 | 
			
		||||
    assert.equal(args.options.save_pointer, true);
 | 
			
		||||
    assert.equal(args.options.immediate, true);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -890,13 +888,16 @@ run_test("user_settings", ({override}) => {
 | 
			
		||||
    message_lists.current.rerender = () => {
 | 
			
		||||
        called = true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    override(message_lists.home, "rerender", noop);
 | 
			
		||||
    let called_for_cached_msg_list = false;
 | 
			
		||||
    cached_message_list.rerender = () => {
 | 
			
		||||
        called_for_cached_msg_list = true;
 | 
			
		||||
    };
 | 
			
		||||
    event = event_fixtures.user_settings__twenty_four_hour_time;
 | 
			
		||||
    user_settings.twenty_four_hour_time = false;
 | 
			
		||||
    dispatch(event);
 | 
			
		||||
    assert_same(user_settings.twenty_four_hour_time, true);
 | 
			
		||||
    assert_same(called, true);
 | 
			
		||||
    assert_same(called_for_cached_msg_list, true);
 | 
			
		||||
 | 
			
		||||
    event = event_fixtures.user_settings__translate_emoticons;
 | 
			
		||||
    user_settings.translate_emoticons = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -41,14 +41,15 @@ message_lists.current = {
 | 
			
		||||
    },
 | 
			
		||||
    change_message_id: noop,
 | 
			
		||||
};
 | 
			
		||||
message_lists.home = {
 | 
			
		||||
const home_msg_list = {
 | 
			
		||||
    view: {
 | 
			
		||||
        rerender_messages: noop,
 | 
			
		||||
        change_message_id: noop,
 | 
			
		||||
    },
 | 
			
		||||
    preserver_rendered_state: true,
 | 
			
		||||
    change_message_id: noop,
 | 
			
		||||
};
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [home_msg_list, message_lists.current];
 | 
			
		||||
 | 
			
		||||
const echo = zrequire("echo");
 | 
			
		||||
const people = zrequire("people");
 | 
			
		||||
@@ -76,7 +77,7 @@ run_test("process_from_server for un-echoed messages", () => {
 | 
			
		||||
run_test("process_from_server for differently rendered messages", ({override}) => {
 | 
			
		||||
    let messages_to_rerender = [];
 | 
			
		||||
 | 
			
		||||
    override(message_lists.home.view, "rerender_messages", (msgs) => {
 | 
			
		||||
    override(home_msg_list.view, "rerender_messages", (msgs) => {
 | 
			
		||||
        messages_to_rerender = msgs;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -193,13 +194,13 @@ run_test("build_display_recipient", () => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
run_test("update_message_lists", () => {
 | 
			
		||||
    message_lists.home.view = {};
 | 
			
		||||
    home_msg_list.view = {};
 | 
			
		||||
 | 
			
		||||
    const stub = make_stub();
 | 
			
		||||
    const view_stub = make_stub();
 | 
			
		||||
 | 
			
		||||
    message_lists.home.change_message_id = stub.f;
 | 
			
		||||
    message_lists.home.view.change_message_id = view_stub.f;
 | 
			
		||||
    home_msg_list.change_message_id = stub.f;
 | 
			
		||||
    home_msg_list.view.change_message_id = view_stub.f;
 | 
			
		||||
 | 
			
		||||
    echo.update_message_lists({old_id: 401, new_id: 402});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ const message_lists = mock_esm("../src/message_lists");
 | 
			
		||||
const message_notifications = mock_esm("../src/message_notifications");
 | 
			
		||||
const message_util = mock_esm("../src/message_util");
 | 
			
		||||
const pm_list = mock_esm("../src/pm_list");
 | 
			
		||||
const recent_view_data = mock_esm("../src/recent_view_data");
 | 
			
		||||
const stream_list = mock_esm("../src/stream_list");
 | 
			
		||||
const unread_ops = mock_esm("../src/unread_ops");
 | 
			
		||||
const unread_ui = mock_esm("../src/unread_ui");
 | 
			
		||||
@@ -39,8 +38,7 @@ message_lists.current = {
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
message_lists.home = message_lists.current;
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [message_lists.current];
 | 
			
		||||
 | 
			
		||||
// And we will also test some real code, of course.
 | 
			
		||||
const message_events = zrequire("message_events");
 | 
			
		||||
@@ -101,7 +99,6 @@ run_test("insert_message", ({override}) => {
 | 
			
		||||
    helper.redirect(message_notifications, "received_messages");
 | 
			
		||||
    helper.redirect(message_util, "add_new_messages_data");
 | 
			
		||||
    helper.redirect(message_util, "add_new_messages");
 | 
			
		||||
    helper.redirect(recent_view_data, "process_message");
 | 
			
		||||
    helper.redirect(stream_list, "update_streams_sidebar");
 | 
			
		||||
    helper.redirect(unread_ops, "process_visible");
 | 
			
		||||
    helper.redirect(unread_ui, "update_unread_counts");
 | 
			
		||||
@@ -116,12 +113,10 @@ run_test("insert_message", ({override}) => {
 | 
			
		||||
        [huddle_data, "process_loaded_messages"],
 | 
			
		||||
        [message_util, "add_new_messages_data"],
 | 
			
		||||
        [message_util, "add_new_messages"],
 | 
			
		||||
        [message_util, "add_new_messages"],
 | 
			
		||||
        [unread_ui, "update_unread_counts"],
 | 
			
		||||
        [unread_ops, "process_visible"],
 | 
			
		||||
        [message_notifications, "received_messages"],
 | 
			
		||||
        [stream_list, "update_streams_sidebar"],
 | 
			
		||||
        [recent_view_data, "process_message"],
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // Despite all of our stubbing/mocking, the call to
 | 
			
		||||
 
 | 
			
		||||
@@ -59,8 +59,7 @@ const message_viewport = mock_esm("../src/message_viewport");
 | 
			
		||||
const unread_ui = mock_esm("../src/unread_ui");
 | 
			
		||||
 | 
			
		||||
message_lists.current = {view: {}};
 | 
			
		||||
message_lists.home = {view: {}};
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [message_lists.current];
 | 
			
		||||
 | 
			
		||||
const message_store = zrequire("message_store");
 | 
			
		||||
const stream_data = zrequire("stream_data");
 | 
			
		||||
@@ -107,7 +106,6 @@ run_test("unread_ops", ({override}) => {
 | 
			
		||||
 | 
			
		||||
    // Ignore these interactions for now:
 | 
			
		||||
    override(message_lists.current.view, "show_message_as_read", noop);
 | 
			
		||||
    override(message_lists.home.view, "show_message_as_read", noop);
 | 
			
		||||
    override(desktop_notifications, "close_notification", noop);
 | 
			
		||||
    override(unread_ui, "update_unread_counts", noop);
 | 
			
		||||
    override(unread_ui, "notify_messages_remain_unread", noop);
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ const pm_list = mock_esm("../src/pm_list");
 | 
			
		||||
const stream_list = mock_esm("../src/stream_list");
 | 
			
		||||
const unread_ui = mock_esm("../src/unread_ui");
 | 
			
		||||
message_lists.current = {};
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [message_lists.home, message_lists.current];
 | 
			
		||||
message_lists.all_rendered_message_lists = () => [message_lists.current];
 | 
			
		||||
 | 
			
		||||
const people = zrequire("people");
 | 
			
		||||
const message_events = zrequire("message_events");
 | 
			
		||||
@@ -95,7 +95,6 @@ run_test("update_messages", () => {
 | 
			
		||||
        rendered_mgs = msgs_to_rerender;
 | 
			
		||||
        assert.equal(message_content_edited, true);
 | 
			
		||||
    };
 | 
			
		||||
    message_lists.home = message_lists.current;
 | 
			
		||||
 | 
			
		||||
    const side_effects = [
 | 
			
		||||
        [message_edit, "end_message_edit"],
 | 
			
		||||
 
 | 
			
		||||
@@ -1,490 +0,0 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
const {strict: assert} = require("assert");
 | 
			
		||||
 | 
			
		||||
const _ = require("lodash");
 | 
			
		||||
 | 
			
		||||
const {mock_esm, set_global, zrequire} = require("./lib/namespace");
 | 
			
		||||
const {run_test, noop} = require("./lib/test");
 | 
			
		||||
const $ = require("./lib/zjquery");
 | 
			
		||||
const {page_params} = require("./lib/zpage_params");
 | 
			
		||||
 | 
			
		||||
set_global("document", "document-stub");
 | 
			
		||||
 | 
			
		||||
function MessageListView() {
 | 
			
		||||
    return {
 | 
			
		||||
        maybe_rerender: noop,
 | 
			
		||||
        append: noop,
 | 
			
		||||
        prepend: noop,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
mock_esm("../src/message_list_view", {
 | 
			
		||||
    MessageListView,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
mock_esm("../src/recent_view_ui", {
 | 
			
		||||
    process_messages: noop,
 | 
			
		||||
    show_loading_indicator: noop,
 | 
			
		||||
    hide_loading_indicator: noop,
 | 
			
		||||
    set_oldest_message_date: noop,
 | 
			
		||||
});
 | 
			
		||||
mock_esm("../src/ui_report", {
 | 
			
		||||
    hide_error: noop,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const channel = mock_esm("../src/channel");
 | 
			
		||||
const message_helper = mock_esm("../src/message_helper");
 | 
			
		||||
const message_lists = mock_esm("../src/message_lists");
 | 
			
		||||
const message_util = mock_esm("../src/message_util");
 | 
			
		||||
const stream_list = mock_esm("../src/stream_list", {
 | 
			
		||||
    maybe_scroll_narrow_into_view() {},
 | 
			
		||||
});
 | 
			
		||||
mock_esm("../src/message_feed_top_notices", {
 | 
			
		||||
    update_top_of_narrow_notices() {},
 | 
			
		||||
});
 | 
			
		||||
mock_esm("../src/message_feed_loading", {
 | 
			
		||||
    show_loading_older: noop,
 | 
			
		||||
    hide_loading_older: noop,
 | 
			
		||||
    show_loading_newer: noop,
 | 
			
		||||
    hide_loading_newer: noop,
 | 
			
		||||
});
 | 
			
		||||
set_global("document", "document-stub");
 | 
			
		||||
 | 
			
		||||
const message_fetch = zrequire("message_fetch");
 | 
			
		||||
 | 
			
		||||
const {all_messages_data} = zrequire("all_messages_data");
 | 
			
		||||
const {Filter} = zrequire("../src/filter");
 | 
			
		||||
const message_list = zrequire("message_list");
 | 
			
		||||
const people = zrequire("people");
 | 
			
		||||
 | 
			
		||||
const alice = {
 | 
			
		||||
    email: "alice@example.com",
 | 
			
		||||
    user_id: 7,
 | 
			
		||||
    full_name: "Alice",
 | 
			
		||||
};
 | 
			
		||||
people.add_active_user(alice);
 | 
			
		||||
 | 
			
		||||
function make_home_msg_list() {
 | 
			
		||||
    const filter = new Filter([]);
 | 
			
		||||
 | 
			
		||||
    const list = new message_list.MessageList({
 | 
			
		||||
        filter,
 | 
			
		||||
    });
 | 
			
		||||
    return list;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function reset_lists() {
 | 
			
		||||
    message_lists.home = make_home_msg_list();
 | 
			
		||||
    message_lists.current = message_lists.home;
 | 
			
		||||
    all_messages_data.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function config_fake_channel(conf) {
 | 
			
		||||
    const self = {};
 | 
			
		||||
    let called;
 | 
			
		||||
    let called_with_newest_flag = false;
 | 
			
		||||
 | 
			
		||||
    channel.get = (opts) => {
 | 
			
		||||
        assert.equal(opts.url, "/json/messages");
 | 
			
		||||
        // There's a separate call with anchor="newest" that happens
 | 
			
		||||
        // unconditionally; do basic verification of that call.
 | 
			
		||||
        if (opts.data.anchor === "newest") {
 | 
			
		||||
            assert.ok(!called_with_newest_flag, "Only one 'newest' call allowed");
 | 
			
		||||
            called_with_newest_flag = true;
 | 
			
		||||
            assert.equal(opts.data.num_after, 0);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assert.ok(!called || conf.can_call_again, "only use this for one call");
 | 
			
		||||
        if (!conf.can_call_again) {
 | 
			
		||||
            assert.equal(self.success, undefined);
 | 
			
		||||
        }
 | 
			
		||||
        assert.deepEqual(opts.data, conf.expected_opts_data);
 | 
			
		||||
        self.success = opts.success;
 | 
			
		||||
        called = true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function config_process_results(messages) {
 | 
			
		||||
    const self = {};
 | 
			
		||||
 | 
			
		||||
    const messages_processed_for_new = [];
 | 
			
		||||
 | 
			
		||||
    message_helper.process_new_message = (message) => {
 | 
			
		||||
        messages_processed_for_new.push(message);
 | 
			
		||||
        return message;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    message_util.do_unread_count_updates = (arg) => {
 | 
			
		||||
        assert.deepEqual(arg, messages);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    message_util.add_old_messages = (new_messages, msg_list) => {
 | 
			
		||||
        assert.deepEqual(new_messages, messages);
 | 
			
		||||
        msg_list.add_messages(new_messages);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    stream_list.update_streams_sidebar = noop;
 | 
			
		||||
 | 
			
		||||
    self.verify = () => {
 | 
			
		||||
        assert.deepEqual(messages_processed_for_new, messages);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function message_range(start, end) {
 | 
			
		||||
    return _.range(start, end).map((idx) => ({
 | 
			
		||||
        id: idx,
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const initialize_data = {
 | 
			
		||||
    initial_fetch: {
 | 
			
		||||
        req: {
 | 
			
		||||
            anchor: "first_unread",
 | 
			
		||||
            num_before: 200,
 | 
			
		||||
            num_after: 200,
 | 
			
		||||
            client_gravatar: true,
 | 
			
		||||
            // Same as message_lists.home.data.public_terms() after `reset_lists` is called.
 | 
			
		||||
            narrow: JSON.stringify([]),
 | 
			
		||||
        },
 | 
			
		||||
        resp: {
 | 
			
		||||
            messages: message_range(201, 801),
 | 
			
		||||
            found_newest: false,
 | 
			
		||||
            anchor: 444,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    forward_fill: {
 | 
			
		||||
        req: {
 | 
			
		||||
            anchor: "800",
 | 
			
		||||
            num_before: 0,
 | 
			
		||||
            num_after: 1000,
 | 
			
		||||
            client_gravatar: true,
 | 
			
		||||
            narrow: JSON.stringify([]),
 | 
			
		||||
        },
 | 
			
		||||
        resp: {
 | 
			
		||||
            messages: message_range(800, 1000),
 | 
			
		||||
            found_newest: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    back_fill: {
 | 
			
		||||
        req: {
 | 
			
		||||
            anchor: "201",
 | 
			
		||||
            num_before: 1000,
 | 
			
		||||
            num_after: 0,
 | 
			
		||||
            client_gravatar: true,
 | 
			
		||||
            narrow: JSON.stringify([]),
 | 
			
		||||
        },
 | 
			
		||||
        resp: {
 | 
			
		||||
            messages: message_range(100, 200),
 | 
			
		||||
            found_oldest: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function test_fetch_success(opts) {
 | 
			
		||||
    const response = opts.response;
 | 
			
		||||
    const messages = response.messages;
 | 
			
		||||
 | 
			
		||||
    const process_results = config_process_results(messages);
 | 
			
		||||
    opts.fetch.success(response);
 | 
			
		||||
    process_results.verify();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initial_fetch_step(finished_initial_fetch) {
 | 
			
		||||
    const self = {};
 | 
			
		||||
 | 
			
		||||
    let fetch;
 | 
			
		||||
    const response = initialize_data.initial_fetch.resp;
 | 
			
		||||
 | 
			
		||||
    self.prep = () => {
 | 
			
		||||
        fetch = config_fake_channel({
 | 
			
		||||
            expected_opts_data: initialize_data.initial_fetch.req,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        message_fetch.initialize(finished_initial_fetch);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    self.finish = () => {
 | 
			
		||||
        test_fetch_success({
 | 
			
		||||
            fetch,
 | 
			
		||||
            response,
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function forward_fill_step() {
 | 
			
		||||
    const self = {};
 | 
			
		||||
 | 
			
		||||
    let fetch;
 | 
			
		||||
 | 
			
		||||
    self.prep = () => {
 | 
			
		||||
        /* Don't wait for the timeout before recursively calling `load_messages`. */
 | 
			
		||||
        const expected_delay = 150;
 | 
			
		||||
        set_global("setTimeout", (f, delay) => {
 | 
			
		||||
            assert.equal(delay, expected_delay);
 | 
			
		||||
            f();
 | 
			
		||||
        });
 | 
			
		||||
        fetch = config_fake_channel({
 | 
			
		||||
            expected_opts_data: initialize_data.forward_fill.req,
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    self.finish = () => {
 | 
			
		||||
        const response = initialize_data.forward_fill.resp;
 | 
			
		||||
 | 
			
		||||
        let idle_config;
 | 
			
		||||
        $("document-stub").idle = (config) => {
 | 
			
		||||
            idle_config = config;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        test_fetch_success({
 | 
			
		||||
            fetch,
 | 
			
		||||
            response,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        assert.equal(idle_config.idle, 10000);
 | 
			
		||||
 | 
			
		||||
        return idle_config;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function test_backfill_idle(idle_config) {
 | 
			
		||||
    const fetch = config_fake_channel({
 | 
			
		||||
        expected_opts_data: initialize_data.back_fill.req,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const response = initialize_data.back_fill.resp;
 | 
			
		||||
 | 
			
		||||
    idle_config.onIdle();
 | 
			
		||||
 | 
			
		||||
    test_fetch_success({
 | 
			
		||||
        fetch,
 | 
			
		||||
        response,
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
run_test("initialize", () => {
 | 
			
		||||
    reset_lists();
 | 
			
		||||
 | 
			
		||||
    let home_loaded = false;
 | 
			
		||||
    page_params.unread_msgs = {
 | 
			
		||||
        old_unreads_missing: false,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function finished_initial_fetch() {
 | 
			
		||||
        home_loaded = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const step1 = initial_fetch_step(finished_initial_fetch);
 | 
			
		||||
 | 
			
		||||
    step1.prep();
 | 
			
		||||
 | 
			
		||||
    const step2 = forward_fill_step();
 | 
			
		||||
 | 
			
		||||
    step2.prep();
 | 
			
		||||
    step1.finish();
 | 
			
		||||
 | 
			
		||||
    assert.ok(!home_loaded);
 | 
			
		||||
    const idle_config = step2.finish();
 | 
			
		||||
    assert.ok(home_loaded);
 | 
			
		||||
 | 
			
		||||
    test_backfill_idle(idle_config);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function simulate_narrow() {
 | 
			
		||||
    const filter = new Filter([{operator: "dm", operand: alice.email}]);
 | 
			
		||||
 | 
			
		||||
    const msg_list = new message_list.MessageList({
 | 
			
		||||
        filter,
 | 
			
		||||
    });
 | 
			
		||||
    message_lists.current = msg_list;
 | 
			
		||||
 | 
			
		||||
    return msg_list;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
run_test("loading_newer", () => {
 | 
			
		||||
    function test_dup_new_fetch(msg_list) {
 | 
			
		||||
        assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), false);
 | 
			
		||||
        message_fetch.maybe_load_newer_messages({
 | 
			
		||||
            msg_list,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function test_happy_path(opts) {
 | 
			
		||||
        const msg_list = opts.msg_list;
 | 
			
		||||
        const data = opts.data;
 | 
			
		||||
 | 
			
		||||
        const fetch = config_fake_channel({
 | 
			
		||||
            expected_opts_data: data.req,
 | 
			
		||||
            can_call_again: true,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        message_fetch.maybe_load_newer_messages({
 | 
			
		||||
            msg_list,
 | 
			
		||||
            show_loading: noop,
 | 
			
		||||
            hide_loading: noop,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        test_dup_new_fetch(msg_list);
 | 
			
		||||
 | 
			
		||||
        test_fetch_success({
 | 
			
		||||
            fetch,
 | 
			
		||||
            response: data.resp,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    (function test_narrow() {
 | 
			
		||||
        let msg_list = simulate_narrow();
 | 
			
		||||
        page_params.unread_msgs = {
 | 
			
		||||
            old_unreads_missing: true,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Test what happens when an empty list is returned with found_newest false.
 | 
			
		||||
        const empty_list_data = {
 | 
			
		||||
            req: {
 | 
			
		||||
                anchor: "oldest",
 | 
			
		||||
                num_before: 0,
 | 
			
		||||
                num_after: 100,
 | 
			
		||||
                narrow: `[{"negated":false,"operator":"dm","operand":[${alice.user_id}]}]`,
 | 
			
		||||
                client_gravatar: true,
 | 
			
		||||
            },
 | 
			
		||||
            resp: {
 | 
			
		||||
                messages: message_range(500, 600),
 | 
			
		||||
                found_newest: false,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        test_happy_path({
 | 
			
		||||
            msg_list,
 | 
			
		||||
            data: empty_list_data,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        msg_list = simulate_narrow();
 | 
			
		||||
        msg_list.append_to_view = noop;
 | 
			
		||||
        // Instead of using 444 as page_param.pointer, we
 | 
			
		||||
        // should have a message with that id in the message_list.
 | 
			
		||||
        msg_list.append(message_range(444, 445), false);
 | 
			
		||||
 | 
			
		||||
        const data = {
 | 
			
		||||
            req: {
 | 
			
		||||
                anchor: "444",
 | 
			
		||||
                num_before: 0,
 | 
			
		||||
                num_after: 100,
 | 
			
		||||
                narrow: `[{"negated":false,"operator":"dm","operand":[${alice.user_id}]}]`,
 | 
			
		||||
                client_gravatar: true,
 | 
			
		||||
            },
 | 
			
		||||
            resp: {
 | 
			
		||||
                messages: message_range(500, 600),
 | 
			
		||||
                found_newest: false,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        test_happy_path({
 | 
			
		||||
            msg_list,
 | 
			
		||||
            data,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), true);
 | 
			
		||||
 | 
			
		||||
        // The server successfully responded with messages having id's from 500-599.
 | 
			
		||||
        // We test for the case that this was the last batch of messages for the narrow
 | 
			
		||||
        // so no more fetching should occur.
 | 
			
		||||
        // And also while fetching for the above condition the server received a new message
 | 
			
		||||
        // event, updating the last message's id for that narrow to 600 from 599.
 | 
			
		||||
        data.resp.found_newest = true;
 | 
			
		||||
        msg_list.data.fetch_status.update_expected_max_message_id([{id: 600}]);
 | 
			
		||||
 | 
			
		||||
        test_happy_path({
 | 
			
		||||
            msg_list,
 | 
			
		||||
            data,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // To handle this special case we should allow another fetch to occur,
 | 
			
		||||
        // since the last message event's data had been discarded.
 | 
			
		||||
        // This fetch goes on until the newest message has been found.
 | 
			
		||||
        assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), false);
 | 
			
		||||
    })();
 | 
			
		||||
 | 
			
		||||
    (function test_home() {
 | 
			
		||||
        reset_lists();
 | 
			
		||||
        let msg_list = message_lists.home;
 | 
			
		||||
 | 
			
		||||
        // Test what happens when an empty list is returned with found_newest false.
 | 
			
		||||
        const empty_list_data = {
 | 
			
		||||
            req: {
 | 
			
		||||
                anchor: "oldest",
 | 
			
		||||
                num_before: 0,
 | 
			
		||||
                num_after: 100,
 | 
			
		||||
                client_gravatar: true,
 | 
			
		||||
                narrow: JSON.stringify([]),
 | 
			
		||||
            },
 | 
			
		||||
            resp: {
 | 
			
		||||
                messages: message_range(500, 600),
 | 
			
		||||
                found_newest: false,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        test_happy_path({
 | 
			
		||||
            msg_list,
 | 
			
		||||
            data: empty_list_data,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        reset_lists();
 | 
			
		||||
 | 
			
		||||
        const data = [
 | 
			
		||||
            {
 | 
			
		||||
                req: {
 | 
			
		||||
                    anchor: "444",
 | 
			
		||||
                    num_before: 0,
 | 
			
		||||
                    num_after: 100,
 | 
			
		||||
                    client_gravatar: true,
 | 
			
		||||
                    narrow: JSON.stringify([]),
 | 
			
		||||
                },
 | 
			
		||||
                resp: {
 | 
			
		||||
                    messages: message_range(500, 600),
 | 
			
		||||
                    found_newest: false,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                req: {
 | 
			
		||||
                    anchor: "599",
 | 
			
		||||
                    num_before: 0,
 | 
			
		||||
                    num_after: 100,
 | 
			
		||||
                    client_gravatar: true,
 | 
			
		||||
                    narrow: JSON.stringify([]),
 | 
			
		||||
                },
 | 
			
		||||
                resp: {
 | 
			
		||||
                    messages: message_range(700, 800),
 | 
			
		||||
                    found_newest: true,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        msg_list = message_lists.home;
 | 
			
		||||
        all_messages_data.append(message_range(444, 445), false);
 | 
			
		||||
 | 
			
		||||
        test_happy_path({
 | 
			
		||||
            msg_list,
 | 
			
		||||
            data: data[0],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), true);
 | 
			
		||||
 | 
			
		||||
        test_happy_path({
 | 
			
		||||
            msg_list,
 | 
			
		||||
            data: data[1],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        assert.equal(msg_list.data.fetch_status.can_load_newer_messages(), false);
 | 
			
		||||
    })();
 | 
			
		||||
});
 | 
			
		||||
@@ -21,14 +21,6 @@ const compose_recipient = mock_esm("../src/compose_recipient");
 | 
			
		||||
const message_fetch = mock_esm("../src/message_fetch");
 | 
			
		||||
const message_list = mock_esm("../src/message_list");
 | 
			
		||||
const message_lists = mock_esm("../src/message_lists", {
 | 
			
		||||
    home: {
 | 
			
		||||
        view: {
 | 
			
		||||
            $list: {
 | 
			
		||||
                removeClass: noop,
 | 
			
		||||
                addClass: noop,
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    current: {
 | 
			
		||||
        view: {
 | 
			
		||||
            $list: {
 | 
			
		||||
@@ -44,6 +36,9 @@ const message_lists = mock_esm("../src/message_lists", {
 | 
			
		||||
    update_current_message_list(msg_list) {
 | 
			
		||||
        message_lists.current = msg_list;
 | 
			
		||||
    },
 | 
			
		||||
    all_rendered_message_lists() {
 | 
			
		||||
        return [message_lists.current];
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
const message_feed_top_notices = mock_esm("../src/message_feed_top_notices");
 | 
			
		||||
const message_feed_loading = mock_esm("../src/message_feed_loading");
 | 
			
		||||
 
 | 
			
		||||
@@ -10,18 +10,11 @@ const {page_params} = require("./lib/zpage_params");
 | 
			
		||||
set_global("addEventListener", noop);
 | 
			
		||||
 | 
			
		||||
const channel = mock_esm("../src/channel");
 | 
			
		||||
const message_lists = mock_esm("../src/message_lists");
 | 
			
		||||
mock_esm("../src/reload_state", {
 | 
			
		||||
    is_in_progress() {
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
message_lists.home = {
 | 
			
		||||
    select_id: noop,
 | 
			
		||||
    selected_id() {
 | 
			
		||||
        return 1;
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
page_params.test_suite = false;
 | 
			
		||||
 | 
			
		||||
// we also directly write to pointer
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,6 @@ run_test("transmit_message_ajax_reload_pending", () => {
 | 
			
		||||
        reload_initiated = true;
 | 
			
		||||
        assert.deepEqual(opts, {
 | 
			
		||||
            immediate: true,
 | 
			
		||||
            save_pointer: true,
 | 
			
		||||
            save_compose: true,
 | 
			
		||||
            send_after_reload: true,
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user