const render_more_topics = require('../templates/more_topics.hbs'); const render_more_topics_spinner = require('../templates/more_topics_spinner.hbs'); const render_topic_list_item = require('../templates/topic_list_item.hbs'); const topic_list_data = require('./topic_list_data'); /* Track all active widgets with a Map. (We have at max one for now, but we may eventually allow multiple streams to be expanded.) */ const active_widgets = new Map(); // We know whether we're zoomed or not. let zoomed = false; exports.update = function () { for (const widget of active_widgets.values()) { widget.build(); } }; exports.remove_expanded_topics = function () { stream_popover.hide_topic_popover(); for (const widget of active_widgets.values()) { widget.remove(); } active_widgets.clear(); }; exports.close = function () { zoomed = false; exports.remove_expanded_topics(); }; exports.zoom_out = function () { zoomed = false; const stream_ids = Array.from(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); }; exports.keyed_topic_li = (convo) => { const render = () => { return render_topic_list_item(convo); }; const eq = (other) => { return _.isEqual(convo, other.convo); }; const key = 't:' + convo.topic_name; return { key: key, render: render, convo: convo, eq: eq, }; }; exports.more_li = (more_topics_unreads) => { const render = () => { return render_more_topics({ more_topics_unreads: more_topics_unreads, }); }; const eq = (other) => { return other.more_items && more_topics_unreads === other.more_topics_unreads; }; const key = 'more'; return { key: key, more_items: true, more_topics_unreads: more_topics_unreads, render: render, eq: eq, }; }; exports.spinner_li = () => { const render = () => { return render_more_topics_spinner(); }; const eq = (other) => { return other.spinner; }; const key = 'more'; return { key: key, spinner: true, render: render, eq: eq, }; }; exports.widget = function (parent_elem, my_stream_id) { const self = {}; self.prior_dom = undefined; self.build_list = function (spinner) { const list_info = topic_list_data.get_list_info( my_stream_id, zoomed); const num_possible_topics = list_info.num_possible_topics; const more_topics_unreads = list_info.more_topics_unreads; const is_showing_all_possible_topics = list_info.items.length === num_possible_topics && topic_data.is_complete_for_stream_id(my_stream_id); const attrs = [ ['class', 'topic-list'], ]; const nodes = list_info.items.map(exports.keyed_topic_li); if (spinner) { nodes.push(exports.spinner_li()); } else if (!is_showing_all_possible_topics) { nodes.push(exports.more_li(more_topics_unreads)); } const dom = vdom.ul({ attrs: attrs, keyed_nodes: nodes, }); return dom; }; self.get_parent = function () { return parent_elem; }; self.get_stream_id = function () { return my_stream_id; }; self.remove = function () { parent_elem.find('.topic-list').remove(); self.prior_dom = undefined; }; self.build = function (spinner) { const new_dom = self.build_list(spinner); function replace_content(html) { self.remove(); parent_elem.append(html); } function find() { return parent_elem.find('.topic-list'); } vdom.update(replace_content, find, new_dom, self.prior_dom); self.prior_dom = new_dom; }; return self; }; exports.active_stream_id = function () { const stream_ids = Array.from(active_widgets.keys()); if (stream_ids.length !== 1) { return; } return stream_ids[0]; }; exports.get_stream_li = function () { const widgets = Array.from(active_widgets.values()); if (widgets.length !== 1) { return; } const stream_li = widgets[0].get_parent(); return stream_li; }; exports.rebuild = function (stream_li, stream_id) { const active_widget = active_widgets.get(stream_id); if (active_widget) { active_widget.build(); return; } exports.remove_expanded_topics(); const widget = exports.widget(stream_li, stream_id); widget.build(); active_widgets.set(stream_id, widget); }; // For zooming, we only do topic-list stuff here...let stream_list // handle hiding/showing the non-narrowed streams exports.zoom_in = function () { zoomed = true; const stream_id = exports.active_stream_id(); if (!stream_id) { blueslip.error('Cannot find widget for topic history zooming.'); return; } const active_widget = active_widgets.get(stream_id); function on_success() { if (!active_widgets.has(stream_id)) { blueslip.warn('User re-narrowed before topic history was returned.'); return; } if (!zoomed) { blueslip.warn('User zoomed out before topic history was returned.'); // Note that we could attempt to re-draw the zoomed out topic list // here, given that we have more history, but that might be more // confusing than helpful to a user who is likely trying to browse // other streams. return; } active_widget.build(); } ui.get_scroll_element($('#stream-filters-container')).scrollTop(0); const spinner = true; active_widget.build(spinner); topic_data.get_server_history(stream_id, on_success); }; exports.initialize = function () { $('#stream_filters').on('click', '.topic-box', function (e) { if (e.metaKey || e.ctrlKey) { return; } if ($(e.target).closest('.show-more-topics').length > 0) { return; } // In a more componentized world, we would delegate some // of this stuff back up to our parents. const stream_row = $(e.target).parents('.narrow-filter'); const stream_id = parseInt(stream_row.attr('data-stream-id'), 10); const sub = stream_data.get_sub_by_id(stream_id); const topic = $(e.target).parents('li').attr('data-topic-name'); narrow.activate([ {operator: 'stream', operand: sub.name}, {operator: 'topic', operand: topic}], {trigger: 'sidebar'}); e.preventDefault(); }); }; window.topic_list = exports;