left-sidebar: Add topic filter input in zoomed topic view.

Fixes: #18505.
This commit is contained in:
m-e-l-u-h-a-n 2021-06-08 22:12:06 +05:30 committed by Tim Abbott
parent 59f8466de3
commit 3f0ed46fa2
7 changed files with 137 additions and 5 deletions

View File

@ -16,6 +16,10 @@ const narrow_state = mock_esm("../../static/js/narrow_state", {
topic() {}, topic() {},
}); });
const topic_list = mock_esm("../../static/js/topic_list", {
get_topic_search_term() {},
});
const stream_data = zrequire("stream_data"); const stream_data = zrequire("stream_data");
const stream_topic_history = zrequire("stream_topic_history"); const stream_topic_history = zrequire("stream_topic_history");
const topic_list_data = zrequire("topic_list_data"); const topic_list_data = zrequire("topic_list_data");
@ -50,14 +54,16 @@ test("get_list_info w/real stream_topic_history", (override) => {
num_possible_topics: 0, num_possible_topics: 0,
}); });
for (const i of _.range(7)) { function add_topic_message(topic_name, message_id) {
const topic_name = "topic " + i;
stream_topic_history.add_message({ stream_topic_history.add_message({
stream_id: general.stream_id, stream_id: general.stream_id,
topic_name, topic_name,
message_id: 1000 + i, message_id,
}); });
} }
for (const i of _.range(7)) {
add_topic_message("topic " + i, 1000 + i);
}
override(narrow_state, "topic", () => "topic 6"); override(narrow_state, "topic", () => "topic 6");
@ -75,12 +81,24 @@ test("get_list_info w/real stream_topic_history", (override) => {
url: "#narrow/stream/556-general/topic/topic.206", url: "#narrow/stream/556-general/topic/topic.206",
}); });
// If we zoom in, we'll show all 7 topics. // If we zoom in, our results based on topic filter.
// If topic search input is empty, we show all 7 topics.
const zoomed = true; const zoomed = true;
override(topic_list, "get_topic_search_term", () => "");
list_info = get_list_info(zoomed); list_info = get_list_info(zoomed);
assert.equal(list_info.items.length, 7); assert.equal(list_info.items.length, 7);
assert.equal(list_info.more_topics_unreads, 0); assert.equal(list_info.more_topics_unreads, 0);
assert.equal(list_info.num_possible_topics, 7); assert.equal(list_info.num_possible_topics, 7);
add_topic_message("After Brooklyn", 1008);
add_topic_message("Catering", 1009);
// when topic search is open then we list topics based on search term.
override(topic_list, "get_topic_search_term", () => "b,c");
list_info = get_list_info(zoomed);
assert.equal(list_info.items.length, 2);
assert.equal(list_info.more_topics_unreads, 0);
assert.equal(list_info.num_possible_topics, 2);
}); });
test("get_list_info unreads", (override) => { test("get_list_info unreads", (override) => {

View File

@ -40,6 +40,7 @@ import * as settings_toggle from "./settings_toggle";
import * as stream_edit from "./stream_edit"; import * as stream_edit from "./stream_edit";
import * as stream_list from "./stream_list"; import * as stream_list from "./stream_list";
import * as stream_popover from "./stream_popover"; import * as stream_popover from "./stream_popover";
import * as topic_list from "./topic_list";
import * as ui_util from "./ui_util"; import * as ui_util from "./ui_util";
import * as unread_ops from "./unread_ops"; import * as unread_ops from "./unread_ops";
import * as user_status_ui from "./user_status_ui"; import * as user_status_ui from "./user_status_ui";
@ -694,6 +695,8 @@ export function initialize() {
// LEFT SIDEBAR // LEFT SIDEBAR
$("body").on("click", "#clear_search_topic_button", topic_list.clear_topic_search);
$(".streams_filter_icon").on("click", (e) => { $(".streams_filter_icon").on("click", (e) => {
e.stopPropagation(); e.stopPropagation();
stream_list.toggle_filter_displayed(e); stream_list.toggle_filter_displayed(e);

View File

@ -1,6 +1,7 @@
import $ from "jquery"; import $ from "jquery";
import _ from "lodash"; import _ from "lodash";
import render_filter_topics from "../templates/filter_topics.hbs";
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";
import render_topic_list_item from "../templates/topic_list_item.hbs"; import render_topic_list_item from "../templates/topic_list_item.hbs";
@ -115,12 +116,24 @@ export function spinner_li() {
}; };
} }
function filter_topics_li() {
const eq = (other) => other.filter_topics;
return {
key: "filter",
filter_topics: true,
render: render_filter_topics,
eq,
};
}
export class TopicListWidget { export class TopicListWidget {
prior_dom = undefined; prior_dom = undefined;
constructor(parent_elem, my_stream_id) { constructor(parent_elem, my_stream_id) {
this.parent_elem = parent_elem; this.parent_elem = parent_elem;
this.my_stream_id = my_stream_id; this.my_stream_id = my_stream_id;
this.topic_search_text = "";
} }
build_list(spinner) { build_list(spinner) {
@ -141,6 +154,10 @@ export class TopicListWidget {
nodes.push(spinner_li()); nodes.push(spinner_li());
} else if (!is_showing_all_possible_topics) { } else if (!is_showing_all_possible_topics) {
nodes.push(more_li(more_topics_unreads)); nodes.push(more_li(more_topics_unreads));
} 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());
} }
const dom = vdom.ul({ const dom = vdom.ul({
@ -159,7 +176,36 @@ export class TopicListWidget {
return this.my_stream_id; return this.my_stream_id;
} }
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);
input.trigger("focus");
// setup event handlers.
const rebuild_list = () => this.build();
input.on("input", rebuild_list);
}
}
remove() { remove() {
// 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());
} else {
// Clear the topic search input when zooming out.
this.update_topic_search_text("");
}
this.parent_elem.find(".topic-list").remove(); this.parent_elem.find(".topic-list").remove();
this.prior_dom = undefined; this.prior_dom = undefined;
} }
@ -170,6 +216,7 @@ export class TopicListWidget {
const replace_content = (html) => { const replace_content = (html) => {
this.remove(); this.remove();
this.parent_elem.append(html); this.parent_elem.append(html);
this.update_topic_search_input();
}; };
const find = () => this.parent_elem.find(".topic-list"); const find = () => this.parent_elem.find(".topic-list");
@ -180,6 +227,25 @@ export class TopicListWidget {
} }
} }
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);
}
}
export function active_stream_id() { export function active_stream_id() {
const stream_ids = Array.from(active_widgets.keys()); const stream_ids = Array.from(active_widgets.keys());
@ -255,6 +321,14 @@ 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() {
const filter = $("#filter-topic-input");
if (filter.val() === undefined) {
return "";
}
return filter.val().trim();
}
export function initialize() { export function initialize() {
$("#stream_filters").on("click", ".topic-box", (e) => { $("#stream_filters").on("click", ".topic-box", (e) => {
if (e.metaKey || e.ctrlKey) { if (e.metaKey || e.ctrlKey) {

View File

@ -2,7 +2,9 @@ import * as hash_util from "./hash_util";
import * as muting from "./muting"; import * as muting from "./muting";
import * as narrow_state from "./narrow_state"; import * as narrow_state from "./narrow_state";
import * as stream_topic_history from "./stream_topic_history"; import * as stream_topic_history from "./stream_topic_history";
import * as topic_list from "./topic_list";
import * as unread from "./unread"; import * as unread from "./unread";
import * as util from "./util";
const max_topics = 5; const max_topics = 5;
const max_topics_with_unread = 8; const max_topics_with_unread = 8;
@ -17,7 +19,11 @@ export function get_list_info(stream_id, zoomed) {
active_topic = active_topic.toLowerCase(); active_topic = active_topic.toLowerCase();
} }
const topic_names = stream_topic_history.get_recent_topic_names(stream_id); let topic_names = stream_topic_history.get_recent_topic_names(stream_id);
if (zoomed) {
const search_term = topic_list.get_topic_search_term();
topic_names = util.filter_by_word_prefix_match(topic_names, search_term, (item) => item);
}
const items = []; const items = [];

View File

@ -413,6 +413,12 @@ div.overlay {
width: calc(100% - 34px); width: calc(100% - 34px);
margin-left: 10px; margin-left: 10px;
} }
.input-append .topic-list-filter {
/* Input width = 100% - 11px margin x2 - 6px padding x2 - 1px border x2. */
width: calc(100% - 36px);
margin-left: 11px;
margin-bottom: 5px;
}
} }
.stream-selection-header-colorblock { .stream-selection-header-colorblock {

View File

@ -100,6 +100,15 @@ li.show-more-topics {
padding-right: 0; padding-right: 0;
padding-bottom: 2px; padding-bottom: 2px;
padding-left: $topic_indent; padding-left: $topic_indent;
&.filter-topics {
padding-bottom: 0;
}
.input-append.topic_search_section {
margin-bottom: 3px;
margin-left: 3px;
}
} }
} }
} }
@ -515,6 +524,14 @@ li.expanded_private_message {
width: 236px; width: 236px;
} }
.topic-list-filter {
/* Input width = 100% - 30px right-margin - 6px right-padding */
/* To keep the right edge of input along with its borders inline with other
topic items we consider to subtract the space given for right margin of
other items, and right padding of input element. */
width: calc(100% - 36px);
}
.zero_count { .zero_count {
visibility: hidden; visibility: hidden;
} }

View File

@ -0,0 +1,8 @@
<li class="topic-list-item filter-topics">
<div class="input-append topic_search_section">
<input class="topic-list-filter home-page-input" id="filter-topic-input" type="text" autocomplete="off" placeholder="{{t 'Filter topics'}}" />
<button type="button" class="btn clear_search_button" id="clear_search_topic_button">
<i class="fa fa-remove" aria-hidden="true"></i>
</button>
</div>
</li>