topic list: Convert module to typescript.

This commit is contained in:
evykassirer 2024-01-19 18:04:52 -08:00 committed by Tim Abbott
parent 7154539e80
commit ea04f7d27d
3 changed files with 84 additions and 49 deletions

View File

@ -244,7 +244,7 @@ EXEMPT_FILES = make_set(
"web/src/timerender.ts", "web/src/timerender.ts",
"web/src/tippyjs.ts", "web/src/tippyjs.ts",
"web/src/todo_widget.js", "web/src/todo_widget.js",
"web/src/topic_list.js", "web/src/topic_list.ts",
"web/src/topic_popover.js", "web/src/topic_popover.js",
"web/src/tutorial.js", "web/src/tutorial.js",
"web/src/types.ts", "web/src/types.ts",

View File

@ -1,5 +1,6 @@
import $ from "jquery"; import $ from "jquery";
import _ from "lodash"; import _ from "lodash";
import assert from "minimalistic-assert";
import render_more_topics from "../templates/more_topics.hbs"; import render_more_topics from "../templates/more_topics.hbs";
import render_more_topics_spinner from "../templates/more_topics_spinner.hbs"; import render_more_topics_spinner from "../templates/more_topics_spinner.hbs";
@ -11,28 +12,29 @@ import * as scroll_util from "./scroll_util";
import * as stream_topic_history from "./stream_topic_history"; import * as stream_topic_history from "./stream_topic_history";
import * as stream_topic_history_util from "./stream_topic_history_util"; import * as stream_topic_history_util from "./stream_topic_history_util";
import * as topic_list_data from "./topic_list_data"; import * as topic_list_data from "./topic_list_data";
import type {TopicInfo} from "./topic_list_data";
import * as vdom from "./vdom"; import * as vdom from "./vdom";
/* /*
Track all active widgets with a Map. Track all active widgets with a Map by stream_id.
(We have at max one for now, but we may (We have at max one for now, but we may
eventually allow multiple streams to be eventually allow multiple streams to be
expanded.) expanded.)
*/ */
const active_widgets = new Map(); const active_widgets = new Map<number, TopicListWidget>();
// We know whether we're zoomed or not. // We know whether we're zoomed or not.
let zoomed = false; let zoomed = false;
export function update() { export function update(): void {
for (const widget of active_widgets.values()) { for (const widget of active_widgets.values()) {
widget.build(); widget.build();
} }
} }
export function clear() { export function clear(): void {
popover_menus.get_topic_menu_popover()?.hide(); popover_menus.get_topic_menu_popover()?.hide();
for (const widget of active_widgets.values()) { for (const widget of active_widgets.values()) {
@ -42,12 +44,12 @@ export function clear() {
active_widgets.clear(); active_widgets.clear();
} }
export function close() { export function close(): void {
zoomed = false; zoomed = false;
clear(); clear();
} }
export function zoom_out() { export function zoom_out(): void {
zoomed = false; zoomed = false;
const stream_ids = [...active_widgets.keys()]; const stream_ids = [...active_widgets.keys()];
@ -59,75 +61,96 @@ export function zoom_out() {
const stream_id = stream_ids[0]; const stream_id = stream_ids[0];
const widget = active_widgets.get(stream_id); const widget = active_widgets.get(stream_id);
assert(widget !== undefined);
const parent_widget = widget.get_parent(); const parent_widget = widget.get_parent();
rebuild(parent_widget, stream_id); rebuild(parent_widget, stream_id);
} }
export function keyed_topic_li(conversation) { type ListInfoNodeOptions =
const render = () => render_topic_list_item(conversation); | {
type: "topic";
conversation: TopicInfo;
}
| {
type: "more_items";
more_topics_unreads: number;
}
| {
type: "spinner";
};
const eq = (other) => _.isEqual(conversation, other.conversation); type ListInfoNode = vdom.Node<ListInfoNodeOptions>;
export function keyed_topic_li(conversation: TopicInfo): ListInfoNode {
const render = (): string => render_topic_list_item(conversation);
const eq = (other: ListInfoNode): boolean =>
other.type === "topic" && _.isEqual(conversation, other.conversation);
const key = "t:" + conversation.topic_name; const key = "t:" + conversation.topic_name;
return { return {
key, key,
render, render,
type: "topic",
conversation, conversation,
eq, eq,
}; };
} }
export function more_li( export function more_li(
more_topics_unreads, more_topics_unreads: number,
more_topics_have_unread_mention_messages, more_topics_have_unread_mention_messages: boolean,
more_topics_unread_count_muted, more_topics_unread_count_muted: boolean,
) { ): ListInfoNode {
const render = () => const render = (): string =>
render_more_topics({ render_more_topics({
more_topics_unreads, more_topics_unreads,
more_topics_have_unread_mention_messages, more_topics_have_unread_mention_messages,
more_topics_unread_count_muted, more_topics_unread_count_muted,
}); });
const eq = (other) => other.more_items && more_topics_unreads === other.more_topics_unreads; const eq = (other: ListInfoNode): boolean =>
other.type === "more_items" && more_topics_unreads === other.more_topics_unreads;
const key = "more"; const key = "more";
return { return {
key, key,
more_items: true, type: "more_items",
more_topics_unreads, more_topics_unreads,
render, render,
eq, eq,
}; };
} }
export function spinner_li() { export function spinner_li(): ListInfoNode {
const render = () => render_more_topics_spinner(); const render = (): string => render_more_topics_spinner();
const eq = (other) => other.spinner; const eq = (other: ListInfoNode): boolean => other.type === "spinner";
const key = "more"; const key = "more";
return { return {
key, key,
spinner: true, type: "spinner",
render, render,
eq, eq,
}; };
} }
export class TopicListWidget { export class TopicListWidget {
prior_dom = undefined; prior_dom?: vdom.Tag<ListInfoNodeOptions> = undefined;
$parent_elem: JQuery;
my_stream_id: number;
constructor($parent_elem, my_stream_id) { constructor($parent_elem: JQuery, my_stream_id: number) {
this.$parent_elem = $parent_elem; this.$parent_elem = $parent_elem;
this.my_stream_id = my_stream_id; this.my_stream_id = my_stream_id;
} }
build_list(spinner) { build_list(spinner: boolean): vdom.Tag<ListInfoNodeOptions> {
const list_info = topic_list_data.get_list_info( const list_info = topic_list_data.get_list_info(
this.my_stream_id, this.my_stream_id,
zoomed, zoomed,
@ -143,7 +166,7 @@ export class TopicListWidget {
list_info.items.length === num_possible_topics && list_info.items.length === num_possible_topics &&
stream_topic_history.is_complete_for_stream_id(this.my_stream_id); stream_topic_history.is_complete_for_stream_id(this.my_stream_id);
const attrs = [["class", "topic-list"]]; const attrs: [string, string][] = [["class", "topic-list"]];
const nodes = list_info.items.map((conversation) => keyed_topic_li(conversation)); const nodes = list_info.items.map((conversation) => keyed_topic_li(conversation));
@ -167,28 +190,29 @@ export class TopicListWidget {
return dom; return dom;
} }
get_parent() { get_parent(): JQuery {
return this.$parent_elem; return this.$parent_elem;
} }
get_stream_id() { get_stream_id(): number {
return this.my_stream_id; return this.my_stream_id;
} }
remove() { remove(): void {
this.$parent_elem.find(".topic-list").remove(); this.$parent_elem.find(".topic-list").remove();
this.prior_dom = undefined; this.prior_dom = undefined;
} }
build(spinner) { build(spinner = false): void {
const new_dom = this.build_list(spinner); const new_dom = this.build_list(spinner);
const replace_content = (html) => { const replace_content = (html?: string): void => {
assert(html !== undefined);
this.remove(); this.remove();
this.$parent_elem.append(html); this.$parent_elem.append(html);
}; };
const find = () => this.$parent_elem.find(".topic-list"); const find = (): JQuery => this.$parent_elem.find(".topic-list");
vdom.update(replace_content, find, new_dom, this.prior_dom); vdom.update(replace_content, find, new_dom, this.prior_dom);
@ -202,7 +226,7 @@ export class TopicListWidget {
} }
} }
export function clear_topic_search(e) { export function clear_topic_search(e: JQuery.Event): void {
e.stopPropagation(); e.stopPropagation();
const $input = $("#filter-topic-input"); const $input = $("#filter-topic-input");
if ($input.length) { if ($input.length) {
@ -215,13 +239,14 @@ export function clear_topic_search(e) {
const stream_id = stream_ids[0]; const stream_id = stream_ids[0];
const widget = active_widgets.get(stream_id); const widget = active_widgets.get(stream_id);
assert(widget !== undefined);
const parent_widget = widget.get_parent(); const parent_widget = widget.get_parent();
rebuild(parent_widget, stream_id); rebuild(parent_widget, stream_id);
} }
} }
export function active_stream_id() { export function active_stream_id(): number | undefined {
const stream_ids = [...active_widgets.keys()]; const stream_ids = [...active_widgets.keys()];
if (stream_ids.length !== 1) { if (stream_ids.length !== 1) {
@ -231,7 +256,7 @@ export function active_stream_id() {
return stream_ids[0]; return stream_ids[0];
} }
export function get_stream_li() { export function get_stream_li(): JQuery | undefined {
const widgets = [...active_widgets.values()]; const widgets = [...active_widgets.values()];
if (widgets.length !== 1) { if (widgets.length !== 1) {
@ -242,7 +267,7 @@ export function get_stream_li() {
return $stream_li; return $stream_li;
} }
export function rebuild($stream_li, stream_id) { export function rebuild($stream_li: JQuery, stream_id: number): void {
const active_widget = active_widgets.get(stream_id); const active_widget = active_widgets.get(stream_id);
if (active_widget) { if (active_widget) {
@ -259,19 +284,20 @@ export function rebuild($stream_li, stream_id) {
// For zooming, we only do topic-list stuff here...let stream_list // For zooming, we only do topic-list stuff here...let stream_list
// handle hiding/showing the non-narrowed streams // handle hiding/showing the non-narrowed streams
export function zoom_in() { export function zoom_in(): void {
zoomed = true; zoomed = true;
const stream_id = active_stream_id(); const stream_id = active_stream_id();
if (!stream_id) { if (stream_id === undefined) {
blueslip.error("Cannot find widget for topic history zooming."); blueslip.error("Cannot find widget for topic history zooming.");
return; return;
} }
const active_widget = active_widgets.get(stream_id); const active_widget = active_widgets.get(stream_id);
assert(active_widget !== undefined);
function on_success() { function on_success(): void {
if (!active_widgets.has(stream_id)) { if (!active_widgets.has(stream_id!)) {
blueslip.warn("User re-narrowed before topic history was returned."); blueslip.warn("User re-narrowed before topic history was returned.");
return; return;
} }
@ -285,7 +311,7 @@ export function zoom_in() {
return; return;
} }
active_widget.build(); active_widget!.build();
} }
scroll_util.get_scroll_element($("#left_sidebar_scroll_container")).scrollTop(0); scroll_util.get_scroll_element($("#left_sidebar_scroll_container")).scrollTop(0);
@ -296,15 +322,20 @@ export function zoom_in() {
stream_topic_history_util.get_server_history(stream_id, on_success); stream_topic_history_util.get_server_history(stream_id, on_success);
} }
export function get_topic_search_term() { export function get_topic_search_term(): string {
const $filter = $("#filter-topic-input"); const $filter = $<HTMLInputElement>("input#filter-topic-input");
if ($filter.val() === undefined) { const filter_val = $filter.val();
if (filter_val === undefined) {
return ""; return "";
} }
return $filter.val().trim(); return filter_val.trim();
} }
export function initialize({on_topic_click}) { export function initialize({
on_topic_click,
}: {
on_topic_click: (stream_id: number, topic?: string) => void;
}): void {
$("#stream_filters").on( $("#stream_filters").on(
"click", "click",
".sidebar-topic-check, .topic-name, .topic-markers-and-controls", ".sidebar-topic-check, .topic-name, .topic-markers-and-controls",
@ -317,7 +348,9 @@ export function initialize({on_topic_click}) {
} }
const $stream_row = $(e.target).parents(".narrow-filter"); const $stream_row = $(e.target).parents(".narrow-filter");
const stream_id = Number.parseInt($stream_row.attr("data-stream-id"), 10); const stream_id_string = $stream_row.attr("data-stream-id");
assert(stream_id_string !== undefined);
const stream_id = Number.parseInt(stream_id_string, 10);
const topic = $(e.target).parents("li").attr("data-topic-name"); const topic = $(e.target).parents("li").attr("data-topic-name");
on_topic_click(stream_id, topic); on_topic_click(stream_id, topic);
@ -325,7 +358,9 @@ export function initialize({on_topic_click}) {
}, },
); );
$("body").on("input", "#filter-topic-input", () => { $("body").on("input", "#filter-topic-input", (): void => {
active_widgets.get(active_stream_id()).build(); const stream_id = active_stream_id();
assert(stream_id !== undefined);
active_widgets.get(stream_id)?.build();
}); });
} }

View File

@ -13,7 +13,7 @@ import * as util from "./util";
const max_topics = 8; const max_topics = 8;
const max_topics_with_unread = 12; const max_topics_with_unread = 12;
type TopicInfo = { export type TopicInfo = {
topic_name: string; topic_name: string;
topic_resolved_prefix: string; topic_resolved_prefix: string;
topic_display_name: string; topic_display_name: string;