<commit_before>const render_more_topics = require('../templates/more_topics.hbs');
const render_topic_list_item = require('../templates/topic_list_item.hbs');
const Dict = require('./dict').Dict;
const FoldDict = require('./fold_dict').FoldDict;

/*
    Track all active widgets with a Dict.

    (We have at max one for now, but we may
    eventually allow multiple streams to be
    expanded.)
*/

const active_widgets = new Dict();

// We know whether we're zoomed or not.
let zoomed = false;

exports.remove_expanded_topics = function () {
    stream_popover.hide_topic_popover();

    _.each(active_widgets.values(), function (widget) {
        widget.remove();
    });

    active_widgets.clear();
};

exports.close = function () {
    zoomed = false;
    exports.remove_expanded_topics();
};

exports.zoom_out = function () {
    zoomed = false;

    const stream_ids = active_widgets.keys();

    if (stream_ids.length !== 1) {
        blueslip.error('Unexpected number of topic lists to zoom out.');
        return;
    }

    const stream_id = stream_ids[0];
    const widget = active_widgets.get(stream_id);
    const parent_widget = widget.get_parent();

    exports.rebuild(parent_widget, stream_id);
};

function update_unread_count(unread_count_elem, count) {
    // unread_count_elem is a jquery element...we expect DOM
    // to look like this:
    //   <div class="topic-unread-count {{#if is_zero}}zero_count{{/if}}">
    //        <div class="value">{{unread}}</div>
    //   </div>
    const value_span = unread_count_elem.find('.value');

    if (value_span.length === 0) {
        blueslip.error('malformed dom for unread count');
        return;
    }

    if (count === 0) {
        unread_count_elem.addClass("zero_count");
        value_span.text('');
    } else {
        unread_count_elem.removeClass("zero_count");
        unread_count_elem.show();
        value_span.text(count);
    }
}

exports.set_count = function (stream_id, topic, count) {
    const widget = active_widgets.get(stream_id);

    if (widget === undefined) {
        return false;
    }

    return widget.set_count(topic, count);
};

exports.widget = function (parent_elem, my_stream_id) {
    const self = {};

    self.build_list = function () {
        self.topic_items = new FoldDict();
        let topics_selected = 0;
        let more_topics_unreads = 0;

        let active_topic = narrow_state.topic();

        if (active_topic) {
            active_topic = active_topic.toLowerCase();
        }

        const max_topics = 5;
        const max_topics_with_unread = 8;
        const topic_names = topic_data.get_recent_names(my_stream_id);

        const ul = $('<ul class="topic-list">');

        _.each(topic_names, function (topic_name, idx) {
            const num_unread = unread.num_unread_for_topic(my_stream_id, topic_name);
            const is_active_topic = active_topic === topic_name.toLowerCase();
            const is_topic_muted = muting.is_topic_muted(my_stream_id, topic_name);

            if (!zoomed) {
                function should_show_topic() {
                    // This function exists just for readability, to
                    // avoid long chained conditionals to determine
                    // which topics to include.

                    // We always show the active topic.  Ideally, this
                    // logic would first check whether the active
                    // topic is in the set of those with unreads to
                    // avoid ending up with max_topics_with_unread + 1
                    // total topics if the active topic comes after
                    // the first several topics with unread messages.
                    if (is_active_topic) {
                        return true;
                    }

                    // We unconditionally skip showing muted topics
                    // when not zoomed, even if they have unread
                    // messages.
                    if (is_topic_muted) {
                        return false;
                    }

                    // We include the most recent max_topics topics,
                    // even if there are no unread messages.
                    if (idx < max_topics) {
                        return true;
                    }

                    // We include older topics with unread messages up
                    // until max_topics_with_unread total topics have
                    // been included.
                    if (num_unread > 0 && topics_selected < max_topics_with_unread) {
                        return true;
                    }

                    // Otherwise, we don't show the topic in the
                    // unzoomed view.  We might display its unread
                    // count in in "more topics" if it is not muted.
                    return false;
                }

                const show_topic = should_show_topic();
                if (!show_topic) {
                    if (!is_topic_muted) {
                        // The "more topics" unread count, like
                        // stream-level counts, only counts messages
                        // on unmuted topics.
                        more_topics_unreads += num_unread;
                    }
                    return;
                }
                topics_selected += 1;
                // We fall through to rendering the topic, using the
                // same code we do when zoomed.
            }

            const topic_info = {
                topic_name: topic_name,
                unread: num_unread,
                is_zero: num_unread === 0,
                is_muted: is_topic_muted,
                is_active_topic: is_active_topic,
                url: hash_util.by_stream_topic_uri(my_stream_id, topic_name),
            };
            const li = $(render_topic_list_item(topic_info));
            self.topic_items.set(topic_name, li);
            ul.append(li);
        });

        // Now, we decide whether we need to show the "more topics"
        // widget.  We need it if there are at least 5 topics in the
        // frontend's cache, or if we (possibly) don't have all
        // historical topics in the browser's cache.
        const show_more = self.build_more_topics_section(more_topics_unreads);
        const sub = stream_data.get_sub_by_id(my_stream_id);

        if (topic_names.length > max_topics || !stream_data.all_topics_in_cache(sub)) {
            ul.append(show_more);
        }
        return ul;
    };

    self.build_more_topics_section = function (more_topics_unreads) {
        const show_more_html = render_more_topics({
            more_topics_unreads: more_topics_unreads,
        });
        return $(show_more_html);
    };

    self.get_parent = function () {
        return parent_elem;
    };
<commit_msg>topic_list: Extract get_list_info.<commit_after>const render_more_topics = require('../templates/more_topics.hbs');
const render_topic_list_item = require('../templates/topic_list_item.hbs');
const Dict = require('./dict').Dict;
const FoldDict = require('./fold_dict').FoldDict;

/*
    Track all active widgets with a Dict.

    (We have at max one for now, but we may
    eventually allow multiple streams to be
    expanded.)
*/

const active_widgets = new Dict();

// We know whether we're zoomed or not.
let zoomed = false;

exports.remove_expanded_topics = function () {
    stream_popover.hide_topic_popover();

    _.each(active_widgets.values(), function (widget) {
        widget.remove();
    });

    active_widgets.clear();
};

exports.close = function () {
    zoomed = false;
    exports.remove_expanded_topics();
};

exports.zoom_out = function () {
    zoomed = false;

    const stream_ids = active_widgets.keys();

    if (stream_ids.length !== 1) {
        blueslip.error('Unexpected number of topic lists to zoom out.');
        return;
    }

    const stream_id = stream_ids[0];
    const widget = active_widgets.get(stream_id);
    const parent_widget = widget.get_parent();

    exports.rebuild(parent_widget, stream_id);
};

function update_unread_count(unread_count_elem, count) {
    // unread_count_elem is a jquery element...we expect DOM
    // to look like this:
    //   <div class="topic-unread-count {{#if is_zero}}zero_count{{/if}}">
    //        <div class="value">{{unread}}</div>
    //   </div>
    const value_span = unread_count_elem.find('.value');

    if (value_span.length === 0) {
        blueslip.error('malformed dom for unread count');
        return;
    }

    if (count === 0) {
        unread_count_elem.addClass("zero_count");
        value_span.text('');
    } else {
        unread_count_elem.removeClass("zero_count");
        unread_count_elem.show();
        value_span.text(count);
    }
}

exports.set_count = function (stream_id, topic, count) {
    const widget = active_widgets.get(stream_id);

    if (widget === undefined) {
        return false;
    }

    return widget.set_count(topic, count);
};

exports.widget = function (parent_elem, my_stream_id) {
    const self = {};
    const max_topics = 5;

    self.get_list_info = function () {
        let topics_selected = 0;
        let more_topics_unreads = 0;

        let active_topic = narrow_state.topic();

        if (active_topic) {
            active_topic = active_topic.toLowerCase();
        }

        const max_topics_with_unread = 8;
        const topic_names = topic_data.get_recent_names(my_stream_id);

        const items = [];

        _.each(topic_names, function (topic_name, idx) {
            const num_unread = unread.num_unread_for_topic(my_stream_id, topic_name);
            const is_active_topic = active_topic === topic_name.toLowerCase();
            const is_topic_muted = muting.is_topic_muted(my_stream_id, topic_name);

            if (!zoomed) {
                function should_show_topic() {
                    // This function exists just for readability, to
                    // avoid long chained conditionals to determine
                    // which topics to include.

                    // We always show the active topic.  Ideally, this
                    // logic would first check whether the active
                    // topic is in the set of those with unreads to
                    // avoid ending up with max_topics_with_unread + 1
                    // total topics if the active topic comes after
                    // the first several topics with unread messages.
                    if (is_active_topic) {
                        return true;
                    }

                    // We unconditionally skip showing muted topics
                    // when not zoomed, even if they have unread
                    // messages.
                    if (is_topic_muted) {
                        return false;
                    }

                    // We include the most recent max_topics topics,
                    // even if there are no unread messages.
                    if (idx < max_topics) {
                        return true;
                    }

                    // We include older topics with unread messages up
                    // until max_topics_with_unread total topics have
                    // been included.
                    if (num_unread > 0 && topics_selected < max_topics_with_unread) {
                        return true;
                    }

                    // Otherwise, we don't show the topic in the
                    // unzoomed view.  We might display its unread
                    // count in in "more topics" if it is not muted.
                    return false;
                }

                const show_topic = should_show_topic();
                if (!show_topic) {
                    if (!is_topic_muted) {
                        // The "more topics" unread count, like
                        // stream-level counts, only counts messages
                        // on unmuted topics.
                        more_topics_unreads += num_unread;
                    }
                    return;
                }
                topics_selected += 1;
                // We fall through to rendering the topic, using the
                // same code we do when zoomed.
            }

            const topic_info = {
                topic_name: topic_name,
                unread: num_unread,
                is_zero: num_unread === 0,
                is_muted: is_topic_muted,
                is_active_topic: is_active_topic,
                url: hash_util.by_stream_topic_uri(my_stream_id, topic_name),
            };

            items.push(topic_info);
        });

        return {
            items: items,
            num_possible_topics: topic_names.length,
            more_topics_unreads: more_topics_unreads,
        };
    };

    self.build_list = function () {
        const list_info = self.get_list_info();
        const num_possible_topics = list_info.num_possible_topics;
        const more_topics_unreads = list_info.more_topics_unreads;

        const ul = $('<ul class="topic-list">');

        self.topic_items = new FoldDict();

        // This is the main list of topics:
        //    topic1
        //    topic2
        //    topic3
        _.each(list_info.items, (topic_info) => {
            const li = $(render_topic_list_item(topic_info));
            self.topic_items.set(topic_info.topic_name, li);
            ul.append(li);
        });

        // Now, we decide whether we need to show the "more topics"
        // widget.  We need it if there are at least 5 topics in the
        // frontend's cache, or if we (possibly) don't have all
        // historical topics in the browser's cache.
        const show_more = self.build_more_topics_section(more_topics_unreads);
        const sub = stream_data.get_sub_by_id(my_stream_id);

        if (num_possible_topics > max_topics || !stream_data.all_topics_in_cache(sub)) {
            ul.append(show_more);
        }
        return ul;
    };

    self.build_more_topics_section = function (more_topics_unreads) {
        const show_more_html = render_more_topics({
            more_topics_unreads: more_topics_unreads,
        });
        return $(show_more_html);
    };

    self.get_parent = function () {
        return parent_elem;
    };

{'fraction_non_alphanumeric': 0.09167073937024367, 'fraction_numerical': 0.002024715492564407, 'mean_word_length': 1.7180265654648956, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1968339', 'n_tokens_mistral': 4119, 'n_tokens_neox': 3885, 'n_words': 1017}