2021-03-11 05:43:45 +01:00
|
|
|
import $ from "jquery";
|
2021-02-28 00:43:33 +01:00
|
|
|
import _ from "lodash";
|
2020-08-01 03:43:15 +02:00
|
|
|
|
2021-06-08 18:42:06 +02:00
|
|
|
import render_filter_topics from "../templates/filter_topics.hbs";
|
2021-02-28 00:43:33 +01:00
|
|
|
import render_more_topics from "../templates/more_topics.hbs";
|
|
|
|
import render_more_topics_spinner from "../templates/more_topics_spinner.hbs";
|
|
|
|
import render_topic_list_item from "../templates/topic_list_item.hbs";
|
2020-07-25 02:02:35 +02:00
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
import * as blueslip from "./blueslip";
|
2021-02-28 21:31:57 +01:00
|
|
|
import * as narrow from "./narrow";
|
2021-02-28 01:02:37 +01:00
|
|
|
import * as stream_popover from "./stream_popover";
|
2021-02-28 00:54:32 +01:00
|
|
|
import * as stream_topic_history from "./stream_topic_history";
|
2021-04-15 18:51:44 +02:00
|
|
|
import * as stream_topic_history_util from "./stream_topic_history_util";
|
2021-04-15 17:02:54 +02:00
|
|
|
import * as sub_store from "./sub_store";
|
2021-02-28 00:43:33 +01:00
|
|
|
import * as topic_list_data from "./topic_list_data";
|
2021-02-28 21:33:10 +01:00
|
|
|
import * as ui from "./ui";
|
2021-02-28 00:43:33 +01:00
|
|
|
import * as vdom from "./vdom";
|
2019-02-08 11:56:33 +01:00
|
|
|
|
2019-03-09 16:32:41 +01:00
|
|
|
/*
|
2020-02-03 09:42:50 +01:00
|
|
|
Track all active widgets with a Map.
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
(We have at max one for now, but we may
|
|
|
|
eventually allow multiple streams to be
|
|
|
|
expanded.)
|
|
|
|
*/
|
|
|
|
|
2020-02-03 09:42:50 +01:00
|
|
|
const active_widgets = new Map();
|
2016-10-27 01:36:20 +02:00
|
|
|
|
2016-11-10 16:15:12 +01:00
|
|
|
// We know whether we're zoomed or not.
|
2019-11-02 00:06:25 +01:00
|
|
|
let zoomed = false;
|
2016-11-10 16:15:12 +01:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function update() {
|
2020-01-31 20:15:46 +01:00
|
|
|
for (const widget of active_widgets.values()) {
|
|
|
|
widget.build();
|
|
|
|
}
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|
2020-01-31 20:15:46 +01:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function clear() {
|
2017-03-05 17:28:40 +01:00
|
|
|
stream_popover.hide_topic_popover();
|
2016-10-27 01:53:47 +02:00
|
|
|
|
2020-02-03 08:51:09 +01:00
|
|
|
for (const widget of active_widgets.values()) {
|
2019-03-09 16:32:41 +01:00
|
|
|
widget.remove();
|
2020-02-03 08:51:09 +01:00
|
|
|
}
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
active_widgets.clear();
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|
2016-10-27 01:36:20 +02:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function close() {
|
2017-08-11 00:30:23 +02:00
|
|
|
zoomed = false;
|
2021-02-28 00:43:33 +01:00
|
|
|
clear();
|
|
|
|
}
|
2017-08-11 00:30:23 +02:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function zoom_out() {
|
2017-08-11 00:30:23 +02:00
|
|
|
zoomed = false;
|
2019-03-09 16:32:41 +01:00
|
|
|
|
2020-02-04 23:46:56 +01:00
|
|
|
const stream_ids = Array.from(active_widgets.keys());
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
if (stream_ids.length !== 1) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("Unexpected number of topic lists to zoom out.");
|
2019-03-09 16:32:41 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_id = stream_ids[0];
|
|
|
|
const widget = active_widgets.get(stream_id);
|
|
|
|
const parent_widget = widget.get_parent();
|
2019-03-09 16:32:41 +01:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
rebuild(parent_widget, stream_id);
|
|
|
|
}
|
2017-08-11 00:30:23 +02:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function keyed_topic_li(convo) {
|
2020-07-02 01:41:40 +02:00
|
|
|
const render = () => render_topic_list_item(convo);
|
2016-11-12 01:01:20 +01:00
|
|
|
|
2020-07-02 01:41:40 +02:00
|
|
|
const eq = (other) => _.isEqual(convo, other.convo);
|
2016-11-12 01:01:20 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const key = "t:" + convo.topic_name;
|
2016-10-29 19:39:47 +02:00
|
|
|
|
2020-01-31 20:15:46 +01:00
|
|
|
return {
|
2020-07-20 22:18:43 +02:00
|
|
|
key,
|
|
|
|
render,
|
|
|
|
convo,
|
|
|
|
eq,
|
2020-01-31 20:15:46 +01:00
|
|
|
};
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|
2019-03-09 16:32:41 +01:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function more_li(more_topics_unreads) {
|
2020-07-15 00:34:28 +02:00
|
|
|
const render = () =>
|
|
|
|
render_more_topics({
|
2020-07-20 22:18:43 +02:00
|
|
|
more_topics_unreads,
|
2020-07-15 00:34:28 +02:00
|
|
|
});
|
2020-01-31 20:15:46 +01:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
const eq = (other) => other.more_items && more_topics_unreads === other.more_topics_unreads;
|
2020-01-31 20:15:46 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const key = "more";
|
2019-03-09 16:32:41 +01:00
|
|
|
|
2020-01-31 20:15:46 +01:00
|
|
|
return {
|
2020-07-20 22:18:43 +02:00
|
|
|
key,
|
2020-01-31 20:15:46 +01:00
|
|
|
more_items: true,
|
2020-07-20 22:18:43 +02:00
|
|
|
more_topics_unreads,
|
|
|
|
render,
|
|
|
|
eq,
|
2020-01-31 20:15:46 +01:00
|
|
|
};
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|
2020-01-31 20:15:46 +01:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function spinner_li() {
|
2020-07-02 01:41:40 +02:00
|
|
|
const render = () => render_more_topics_spinner();
|
2020-01-31 20:15:46 +01:00
|
|
|
|
2020-07-02 01:41:40 +02:00
|
|
|
const eq = (other) => other.spinner;
|
2020-01-31 20:15:46 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const key = "more";
|
2020-01-31 20:15:46 +01:00
|
|
|
|
|
|
|
return {
|
2020-07-20 22:18:43 +02:00
|
|
|
key,
|
2020-01-31 20:15:46 +01:00
|
|
|
spinner: true,
|
2020-07-20 22:18:43 +02:00
|
|
|
render,
|
|
|
|
eq,
|
2020-01-31 20:15:46 +01:00
|
|
|
};
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|
2016-10-27 01:36:20 +02:00
|
|
|
|
2021-06-08 18:42:06 +02:00
|
|
|
function filter_topics_li() {
|
|
|
|
const eq = (other) => other.filter_topics;
|
|
|
|
|
|
|
|
return {
|
|
|
|
key: "filter",
|
|
|
|
filter_topics: true,
|
|
|
|
render: render_filter_topics,
|
|
|
|
eq,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export class TopicListWidget {
|
2020-07-23 03:50:12 +02:00
|
|
|
prior_dom = undefined;
|
2016-10-27 02:02:40 +02:00
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
constructor(parent_elem, my_stream_id) {
|
|
|
|
this.parent_elem = parent_elem;
|
|
|
|
this.my_stream_id = my_stream_id;
|
2021-06-08 18:42:06 +02:00
|
|
|
this.topic_search_text = "";
|
2022-01-07 11:01:33 +01:00
|
|
|
this.topic_search_focused_before_build = true;
|
2020-07-23 03:50:12 +02:00
|
|
|
}
|
2020-01-31 20:15:46 +01:00
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
build_list(spinner) {
|
|
|
|
const list_info = topic_list_data.get_list_info(this.my_stream_id, zoomed);
|
2020-01-18 13:53:12 +01:00
|
|
|
|
2020-01-17 22:56:00 +01:00
|
|
|
const num_possible_topics = list_info.num_possible_topics;
|
|
|
|
const more_topics_unreads = list_info.more_topics_unreads;
|
|
|
|
|
2020-01-30 12:54:36 +01:00
|
|
|
const is_showing_all_possible_topics =
|
|
|
|
list_info.items.length === num_possible_topics &&
|
2020-07-23 03:50:12 +02:00
|
|
|
stream_topic_history.is_complete_for_stream_id(this.my_stream_id);
|
2020-01-30 12:54:36 +01:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
const attrs = [["class", "topic-list"]];
|
2020-01-17 22:56:00 +01:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
const nodes = list_info.items.map((convo) => keyed_topic_li(convo));
|
2016-10-29 20:19:25 +02:00
|
|
|
|
2020-01-31 20:15:46 +01:00
|
|
|
if (spinner) {
|
2021-02-28 00:43:33 +01:00
|
|
|
nodes.push(spinner_li());
|
2020-01-31 20:15:46 +01:00
|
|
|
} else if (!is_showing_all_possible_topics) {
|
2021-02-28 00:43:33 +01:00
|
|
|
nodes.push(more_li(more_topics_unreads));
|
2021-06-08 18:42:06 +02:00
|
|
|
} else if (zoomed) {
|
|
|
|
// In the zoomed topic view, we need to add the input
|
|
|
|
// for filtering through list of topics.
|
|
|
|
nodes.unshift(filter_topics_li());
|
2020-01-31 20:15:46 +01:00
|
|
|
}
|
2016-10-27 02:02:40 +02:00
|
|
|
|
2020-01-31 20:15:46 +01:00
|
|
|
const dom = vdom.ul({
|
2020-07-20 22:18:43 +02:00
|
|
|
attrs,
|
2020-01-31 20:15:46 +01:00
|
|
|
keyed_nodes: nodes,
|
|
|
|
});
|
2020-01-30 13:02:50 +01:00
|
|
|
|
2020-01-31 20:15:46 +01:00
|
|
|
return dom;
|
2020-07-23 03:50:12 +02:00
|
|
|
}
|
2016-11-10 20:05:14 +01:00
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
get_parent() {
|
|
|
|
return this.parent_elem;
|
|
|
|
}
|
2016-11-10 20:19:22 +01:00
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
get_stream_id() {
|
|
|
|
return this.my_stream_id;
|
|
|
|
}
|
2016-11-10 15:35:14 +01:00
|
|
|
|
2021-06-08 18:42:06 +02:00
|
|
|
update_topic_search_text(text) {
|
|
|
|
this.topic_search_text = text;
|
|
|
|
}
|
|
|
|
|
|
|
|
update_topic_search_input() {
|
|
|
|
const input = this.parent_elem.find("#filter-topic-input");
|
|
|
|
if (input.length) {
|
|
|
|
// Restore topic search text saved in remove()
|
|
|
|
// after the element was rerendered.
|
|
|
|
input.val(this.topic_search_text);
|
2022-01-07 11:01:33 +01:00
|
|
|
if (this.topic_search_focused_before_build) {
|
|
|
|
// Don't focus topic search if it wasn't focused before.
|
|
|
|
// This avoids unwanted change of focus.
|
|
|
|
input.trigger("focus");
|
|
|
|
}
|
2021-06-08 18:42:06 +02:00
|
|
|
|
2021-06-10 21:14:59 +02:00
|
|
|
// setup display of clear(x) button.
|
|
|
|
if (this.topic_search_text.length) {
|
|
|
|
$("#clear_search_topic_button").show();
|
|
|
|
} else {
|
|
|
|
$("#clear_search_topic_button").hide();
|
|
|
|
}
|
|
|
|
|
2021-06-08 18:42:06 +02:00
|
|
|
// setup event handlers.
|
|
|
|
const rebuild_list = () => this.build();
|
|
|
|
input.on("input", rebuild_list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
remove() {
|
2021-06-08 18:42:06 +02:00
|
|
|
// If text was present in the topic search filter, we store
|
|
|
|
// the input value lazily before removing old elements. This
|
|
|
|
// is a workaround for the quirk that the filter input is part
|
|
|
|
// of the region that we rerender.
|
|
|
|
const input = this.parent_elem.find("#filter-topic-input");
|
|
|
|
if (input.length) {
|
|
|
|
this.update_topic_search_text(input.val());
|
2022-01-18 07:09:35 +01:00
|
|
|
// Only set focus on search input if it was focused before the update.
|
|
|
|
this.topic_search_focused_before_build =
|
|
|
|
document.activeElement.id === "filter-topic-input";
|
2021-06-08 18:42:06 +02:00
|
|
|
} else {
|
|
|
|
// Clear the topic search input when zooming out.
|
|
|
|
this.update_topic_search_text("");
|
|
|
|
}
|
2020-07-23 03:50:12 +02:00
|
|
|
this.parent_elem.find(".topic-list").remove();
|
|
|
|
this.prior_dom = undefined;
|
|
|
|
}
|
2017-09-22 03:26:10 +02:00
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
build(spinner) {
|
|
|
|
const new_dom = this.build_list(spinner);
|
2019-08-29 01:41:23 +02:00
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
const replace_content = (html) => {
|
|
|
|
this.remove();
|
|
|
|
this.parent_elem.append(html);
|
2021-06-08 18:42:06 +02:00
|
|
|
this.update_topic_search_input();
|
2020-07-23 03:50:12 +02:00
|
|
|
};
|
2016-10-29 21:01:07 +02:00
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
const find = () => this.parent_elem.find(".topic-list");
|
2017-09-22 00:12:55 +02:00
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
vdom.update(replace_content, find, new_dom, this.prior_dom);
|
2016-11-10 20:05:14 +01:00
|
|
|
|
2020-07-23 03:50:12 +02:00
|
|
|
this.prior_dom = new_dom;
|
|
|
|
}
|
|
|
|
}
|
2016-10-27 02:02:40 +02:00
|
|
|
|
2021-06-08 18:42:06 +02:00
|
|
|
export function clear_topic_search(e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
const input = $("#filter-topic-input");
|
|
|
|
if (input.length) {
|
|
|
|
input.val("");
|
|
|
|
input.trigger("blur");
|
|
|
|
|
|
|
|
// Since this changes the contents of the search input, we
|
|
|
|
// need to rerender the topic list.
|
|
|
|
const stream_ids = Array.from(active_widgets.keys());
|
|
|
|
|
|
|
|
const stream_id = stream_ids[0];
|
|
|
|
const widget = active_widgets.get(stream_id);
|
|
|
|
const parent_widget = widget.get_parent();
|
|
|
|
|
|
|
|
rebuild(parent_widget, stream_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function active_stream_id() {
|
2020-02-04 23:46:56 +01:00
|
|
|
const stream_ids = Array.from(active_widgets.keys());
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
if (stream_ids.length !== 1) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2017-08-11 00:29:35 +02:00
|
|
|
}
|
|
|
|
|
2019-03-09 16:32:41 +01:00
|
|
|
return stream_ids[0];
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|
2017-08-11 00:29:35 +02:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function get_stream_li() {
|
2020-02-04 23:46:56 +01:00
|
|
|
const widgets = Array.from(active_widgets.values());
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
if (widgets.length !== 1) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-09-10 14:52:58 +02:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_li = widgets[0].get_parent();
|
2018-09-10 14:52:58 +02:00
|
|
|
return stream_li;
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|
2018-09-10 14:52:58 +02:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function rebuild(stream_li, stream_id) {
|
2020-01-31 20:15:46 +01:00
|
|
|
const active_widget = active_widgets.get(stream_id);
|
|
|
|
|
|
|
|
if (active_widget) {
|
|
|
|
active_widget.build();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
clear();
|
2020-07-23 03:50:12 +02:00
|
|
|
const widget = new TopicListWidget(stream_li, stream_id);
|
2020-01-17 22:41:13 +01:00
|
|
|
widget.build();
|
2019-03-09 16:32:41 +01:00
|
|
|
|
|
|
|
active_widgets.set(stream_id, widget);
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|
2016-10-27 02:33:24 +02:00
|
|
|
|
2016-11-10 16:15:12 +01:00
|
|
|
// For zooming, we only do topic-list stuff here...let stream_list
|
|
|
|
// handle hiding/showing the non-narrowed streams
|
2021-02-28 00:43:33 +01:00
|
|
|
export function zoom_in() {
|
2016-11-10 16:15:12 +01:00
|
|
|
zoomed = true;
|
2016-11-10 20:46:01 +01:00
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
const stream_id = active_stream_id();
|
2019-03-09 16:32:41 +01:00
|
|
|
if (!stream_id) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.error("Cannot find widget for topic history zooming.");
|
2016-11-10 20:46:01 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const active_widget = active_widgets.get(stream_id);
|
2019-03-09 16:32:41 +01:00
|
|
|
|
2017-08-08 20:29:15 +02:00
|
|
|
function on_success() {
|
2019-03-09 16:32:41 +01:00
|
|
|
if (!active_widgets.has(stream_id)) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.warn("User re-narrowed before topic history was returned.");
|
2017-09-22 17:34:50 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!zoomed) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.warn("User zoomed out before topic history was returned.");
|
2017-09-22 17:34:50 +02:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2020-01-31 20:15:46 +01:00
|
|
|
active_widget.build();
|
2017-08-08 20:29:15 +02:00
|
|
|
}
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
ui.get_scroll_element($("#stream-filters-container")).scrollTop(0);
|
2020-01-31 20:15:46 +01:00
|
|
|
|
|
|
|
const spinner = true;
|
|
|
|
active_widget.build(spinner);
|
|
|
|
|
2021-04-15 18:51:44 +02:00
|
|
|
stream_topic_history_util.get_server_history(stream_id, on_success);
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|
2016-11-10 16:15:12 +01:00
|
|
|
|
2021-06-08 18:42:06 +02:00
|
|
|
export function get_topic_search_term() {
|
|
|
|
const filter = $("#filter-topic-input");
|
|
|
|
if (filter.val() === undefined) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
return filter.val().trim();
|
|
|
|
}
|
|
|
|
|
2021-02-28 00:43:33 +01:00
|
|
|
export function initialize() {
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#stream_filters").on("click", ".topic-box", (e) => {
|
2016-10-27 03:47:05 +02:00
|
|
|
if (e.metaKey || e.ctrlKey) {
|
|
|
|
return;
|
|
|
|
}
|
2020-07-15 01:29:15 +02:00
|
|
|
if ($(e.target).closest(".show-more-topics").length > 0) {
|
2019-08-29 01:41:23 +02:00
|
|
|
return;
|
|
|
|
}
|
2016-10-27 03:47:05 +02:00
|
|
|
|
|
|
|
// In a more componentized world, we would delegate some
|
|
|
|
// of this stuff back up to our parents.
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const stream_row = $(e.target).parents(".narrow-filter");
|
2020-10-07 09:17:30 +02:00
|
|
|
const stream_id = Number.parseInt(stream_row.attr("data-stream-id"), 10);
|
2021-04-15 17:02:54 +02:00
|
|
|
const sub = sub_store.get(stream_id);
|
2020-07-15 01:29:15 +02:00
|
|
|
const topic = $(e.target).parents("li").attr("data-topic-name");
|
2016-10-27 03:47:05 +02:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
narrow.activate(
|
|
|
|
[
|
|
|
|
{operator: "stream", operand: sub.name},
|
|
|
|
{operator: "topic", operand: topic},
|
|
|
|
],
|
|
|
|
{trigger: "sidebar"},
|
|
|
|
);
|
2016-10-27 03:47:05 +02:00
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
2021-02-28 00:43:33 +01:00
|
|
|
}
|