recent_view: Add drodown widget to filter topics.

This commit is contained in:
Aman Agrawal 2023-11-23 05:35:15 +00:00 committed by Tim Abbott
parent de767cc9ad
commit 1e7b4ae160
6 changed files with 203 additions and 219 deletions

View File

@ -284,6 +284,9 @@ EXEMPT_FILES = make_set(
"web/tests/lib/real_jquery.js",
"web/tests/lib/zjquery_element.js",
"web/tests/lib/zpage_billing_params.js",
# There are some important functions which are not called right now but will
# be reused when we add tests for dropdown widget so it doesn't make sense to remove them.
"web/tests/recent_view.test.js",
]
)

View File

@ -10,6 +10,7 @@ import * as blueslip from "./blueslip";
import * as buddy_data from "./buddy_data";
import * as compose_closed_ui from "./compose_closed_ui";
import * as compose_state from "./compose_state";
import * as dropdown_widget from "./dropdown_widget";
import * as hash_util from "./hash_util";
import {$t} from "./i18n";
import * as left_sidebar_navigation_area from "./left_sidebar_navigation_area";
@ -39,6 +40,7 @@ import * as user_topics from "./user_topics";
import * as views_util from "./views_util";
let topics_widget;
let filters_dropdown_widget;
// Sets the number of avatars to display.
// Rest of the avatars, if present, are displayed as {+x}
const MAX_AVATAR = 4;
@ -81,20 +83,24 @@ const MAX_SELECTABLE_DIRECT_MESSAGE_COLS = 3;
// we use localstorage to persist the recent topic filters
const ls_key = "recent_topic_filters";
const ls_dropdown_key = "recent_topic_dropdown_filters";
const ls = localstorage();
let filters = new Set();
let dropdown_filters = new Set();
const recent_conversation_key_prefix = "recent_conversation:";
export function clear_for_tests() {
filters.clear();
dropdown_filters.clear();
recent_view_data.conversations.clear();
topics_widget = undefined;
}
export function save_filters() {
ls.set(ls_key, [...filters]);
ls.set(ls_dropdown_key, [...dropdown_filters]);
}
export function is_in_focus() {
@ -362,6 +368,11 @@ export function revive_current_focus() {
return true;
}
if ($current_focus_elem.hasClass("dropdown-widget-button")) {
$("#recent-view-filter_widget").trigger("focus");
return true;
}
const filter_button = $current_focus_elem.data("filter");
if (!filter_button) {
set_default_focus();
@ -655,7 +666,7 @@ export function filters_should_hide_topic(topic_data) {
return true;
}
if (!filters.has("include_muted") && topic_data.type === "stream") {
if (dropdown_filters.has(views_util.FILTERS.UNMUTED_TOPICS) && topic_data.type === "stream") {
// We want to show the unmuted or followed topics within muted
// streams in Recent Conversations.
const topic_unmuted_or_followed = Boolean(
@ -680,6 +691,24 @@ export function filters_should_hide_topic(topic_data) {
}
}
if (
dropdown_filters.has(views_util.FILTERS.FOLLOWED_TOPICS) &&
topic_data.type === "stream" &&
!user_topics.is_topic_followed(msg.stream_id, msg.topic)
) {
return true;
}
if (
dropdown_filters.has(views_util.FILTERS.UNMUTED_TOPICS) &&
topic_data.type === "stream" &&
(user_topics.is_topic_muted(msg.stream_id, msg.topic) ||
stream_data.is_muted(msg.stream_id)) &&
!user_topics.is_topic_unmuted_or_followed(msg.stream_id, msg.topic)
) {
return true;
}
const search_keyword = $("#recent_view_search").val();
const stream_name = stream_data.get_stream_name_from_id(msg.stream_id);
if (!topic_in_search_results(search_keyword, stream_name, msg.topic)) {
@ -793,18 +822,28 @@ function show_selected_filters() {
function get_recent_view_filters_params() {
return {
filter_participated: filters.has("participated"),
filter_unread: filters.has("unread"),
filter_muted: filters.has("include_muted"),
filter_pm: filters.has("include_private"),
is_spectator: page_params.is_spectator,
};
}
function setup_dropdown_filters_widget() {
filters_dropdown_widget = new dropdown_widget.DropdownWidget({
...views_util.COMMON_DROPDOWN_WIDGET_PARAMS,
widget_name: "recent-view-filter",
item_click_callback: filter_click_handler,
$events_container: $("#recent_view_filter_buttons"),
default_id: dropdown_filters.values().next().value,
});
filters_dropdown_widget.setup();
}
export function update_filters_view() {
const rendered_filters = render_recent_view_filters(get_recent_view_filters_params());
$("#recent_filters_group").html(rendered_filters);
show_selected_filters();
filters_dropdown_widget.render();
topics_widget.hard_redraw();
}
@ -939,6 +978,25 @@ function callback_after_render() {
setTimeout(revive_current_focus, 0);
}
function filter_click_handler(event, dropdown, widget) {
event.preventDefault();
event.stopPropagation();
if (page_params.is_spectator) {
// Filter buttons are disabled for spectator.
return;
}
const filter_id = $(event.currentTarget).attr("data-unique-id");
// We don't support multiple filters yet, so we clear existing and add the new filter.
dropdown_filters = new Set([filter_id]);
dropdown.hide();
widget.render();
save_filters();
topics_widget.hard_redraw();
}
export function complete_rerender() {
if (!recent_view_util.is_visible()) {
return;
@ -996,6 +1054,7 @@ export function complete_rerender() {
},
get_min_load_count,
});
setup_dropdown_filters_widget();
}
export function show() {
@ -1217,7 +1276,7 @@ export function change_focused_element($elt, input_key) {
set_table_focus(row_focus, col_focus);
return true;
}
} else if ($elt.hasClass("btn-recent-filters")) {
} else if ($elt.hasClass("btn-recent-filters") || $elt.hasClass("dropdown-widget-button")) {
switch (input_key) {
case "click":
$current_focus_elem = $elt;
@ -1329,19 +1388,31 @@ export function change_focused_element($elt, input_key) {
return false;
}
export function initialize({
on_click_participant,
on_mark_pm_as_read,
on_mark_topic_as_read,
maybe_load_older_messages,
}) {
function load_filters() {
// load filters from local storage.
if (!page_params.is_spectator) {
// A user may have a stored filter and can log out
// to see web public view. This ensures no filters are
// selected for spectators.
filters = new Set(ls.get(ls_key));
dropdown_filters = new Set(ls.get(ls_dropdown_key));
}
// Verify that the dropdown_filters are valid.
const valid_filters = new Set(Object.values(views_util.FILTERS));
// If saved filters are not in the list of valid filters, we reset to default.
const is_subset = [...dropdown_filters].every((filter) => valid_filters.has(filter));
if (dropdown_filters.size === 0 || !is_subset) {
dropdown_filters = new Set([views_util.FILTERS.UNMUTED_TOPICS]);
}
}
export function initialize({
on_click_participant,
on_mark_pm_as_read,
on_mark_topic_as_read,
maybe_load_older_messages,
}) {
load_filters();
$("body").on("click", "#recent_view_table .recent_view_participant_avatar", function (e) {
const participant_user_id = Number.parseInt($(this).parent().attr("data-user-id"), 10);
@ -1445,6 +1516,15 @@ export function initialize({
revive_current_focus();
});
$("body").on("click", "#recent-view-filter_widget", (e) => {
if (page_params.is_spectator) {
// Filter buttons are disabled for spectator.
return;
}
change_focused_element($(e.currentTarget), "click");
});
$("body").on("click", "td.recent_topic_stream", (e) => {
e.stopPropagation();
const topic_row_index = $(e.target).closest("tr").index();

View File

@ -551,6 +551,7 @@
}
}
#recent-view-filter_widget .dropdown_widget_value,
#inbox-filter_widget .dropdown_widget_value {
text-overflow: ellipsis;
white-space: nowrap;
@ -559,30 +560,45 @@
text-align: left;
}
#recent-view-filter_widget .fa-chevron-down,
#inbox-filter_widget .fa-chevron-down {
color: var(--color-icons-inbox);
opacity: 0.4;
}
.inbox-filter-dropdown-list-container .dropdown-list-wrapper {
width: 100%;
min-width: unset;
.dropdown-list-item-common-styles .dropdown-list-bold-selected {
font-weight: 700;
}
.recent-view-filter-dropdown-list-container .dropdown-list-wrapper,
.inbox-filter-dropdown-list-container .dropdown-list-wrapper {
width: 220px;
}
.recent-view-filter-dropdown-list-container .dropdown-list-item-common-styles,
.inbox-filter-dropdown-list-container .dropdown-list-item-common-styles {
padding: 5px 10px;
display: flex;
flex-direction: column;
}
.recent-view-filter-dropdown-list-container .dropdown-list-item-name,
.inbox-filter-dropdown-list-container .dropdown-list-item-name {
white-space: nowrap;
font-weight: 500;
padding: 0;
margin: 0;
text-overflow: ellipsis;
overflow: hidden;
}
.recent-view-filter-dropdown-list-container .dropdown-list-item-description,
.inbox-filter-dropdown-list-container .dropdown-list-item-description {
white-space: nowrap;
font-weight: 400;
font-size: 13px;
opacity: 0.8;
padding: 0;
text-overflow: ellipsis;
overflow: hidden;
}

View File

@ -543,3 +543,17 @@
display: none;
position: relative;
}
#recent-view-filter_widget {
display: inline-flex;
width: 150px;
margin: 0 5px 10px 0;
&:hover {
background-color: var(--color-background-inbox-search-hover);
}
&:focus {
outline: 2px solid var(--color-outline-focus);
}
}

View File

@ -1,3 +1,4 @@
{{> ./dropdown_widget widget_name="recent-view-filter"}}
<button data-filter="include_private" type="button" class="btn btn-default btn-recent-filters {{#if is_spectator}}fake_disabled_button{{/if}}" role="checkbox" aria-checked="true">
{{#if filter_pm}}
<i class="fa fa-check-square-o"></i>
@ -6,14 +7,6 @@
{{/if}}
{{t 'Include DMs' }}
</button>
<button data-filter="include_muted" type="button" class="btn btn-default btn-recent-filters {{#if is_spectator}}fake_disabled_button{{/if}}" role="checkbox" aria-checked="false">
{{#if filter_muted }}
<i class="fa fa-check-square-o"></i>
{{else}}
<i class="fa fa-square-o"></i>
{{/if}}
{{t 'Include muted' }}
</button>
<button data-filter="unread" type="button" class="btn btn-default btn-recent-filters {{#if is_spectator}}fake_disabled_button{{/if}}" role="checkbox" aria-checked="false">
{{#if filter_unread}}
<i class="fa fa-check-square-o"></i>

View File

@ -181,6 +181,11 @@ mock_esm("../src/unread", {
mock_esm("../src/resize", {
update_recent_view_filters_height: noop,
});
const dropdown_widget = zrequire("../src/dropdown_widget");
dropdown_widget.DropdownWidget = function DropdownWidget() {
this.setup = noop;
this.render = noop;
};
const {all_messages_data} = zrequire("all_messages_data");
const people = zrequire("people");
@ -444,7 +449,6 @@ test("test_recent_view_show", ({mock_template}) => {
page_params.is_spectator = false;
const expected = {
filter_participated: false,
filter_unread: false,
filter_muted: false,
filter_pm: false,
search_val: "",
@ -475,7 +479,6 @@ test("test_filter_is_spectator", ({mock_template}) => {
page_params.is_spectator = true;
const expected = {
filter_participated: false,
filter_unread: false,
filter_muted: false,
filter_pm: false,
search_val: "",
@ -508,7 +511,6 @@ test("test_no_filter", ({mock_template}) => {
page_params.is_spectator = false;
const expected = {
filter_participated: false,
filter_unread: false,
filter_muted: false,
filter_pm: false,
search_val: "",
@ -539,80 +541,81 @@ test("test_no_filter", ({mock_template}) => {
false,
);
expected_data_to_replace_in_list_widget = [
{last_msg_id: 10, participated: true, type: "stream"},
{last_msg_id: 1, participated: true, type: "stream"},
];
// TODO: Modify this test to work with dropdown widget.
// expected_data_to_replace_in_list_widget = [
// {last_msg_id: 10, participated: true, type: "stream"},
// {last_msg_id: 1, participated: true, type: "stream"},
// ];
// topic is muted
row_data = [
...row_data,
...generate_topic_data([[1, "topic-7", 1, all_visibility_policies.MUTED]]),
];
i = row_data.length;
stub_out_filter_buttons();
rt.process_messages([messages[9]]);
assert.equal(
rt.filters_should_hide_topic({last_msg_id: 10, participated: true, type: "stream"}),
true,
);
// // topic is muted
// row_data = [
// ...row_data,
// ...generate_topic_data([[1, "topic-7", 1, all_visibility_policies.MUTED]]),
// ];
// i = row_data.length;
// stub_out_filter_buttons();
// rt.process_messages([messages[9]]);
// assert.equal(
// rt.filters_should_hide_topic({last_msg_id: 10, participated: true, type: "stream"}),
// true,
// );
expected_data_to_replace_in_list_widget = [
{last_msg_id: 12, participated: true, type: "stream"},
{last_msg_id: 10, participated: true, type: "stream"},
{last_msg_id: 1, participated: true, type: "stream"},
];
// normal topic in muted stream
row_data = [
...row_data,
...generate_topic_data([[6, "topic-8", 1, all_visibility_policies.INHERIT]]),
];
i = row_data.length;
stub_out_filter_buttons();
rt.process_messages([messages[11]]);
assert.equal(
rt.filters_should_hide_topic({last_msg_id: 12, participated: true, type: "stream"}),
true,
);
// expected_data_to_replace_in_list_widget = [
// {last_msg_id: 12, participated: true, type: "stream"},
// {last_msg_id: 10, participated: true, type: "stream"},
// {last_msg_id: 1, participated: true, type: "stream"},
// ];
// // normal topic in muted stream
// row_data = [
// ...row_data,
// ...generate_topic_data([[6, "topic-8", 1, all_visibility_policies.INHERIT]]),
// ];
// i = row_data.length;
// stub_out_filter_buttons();
// rt.process_messages([messages[11]]);
// assert.equal(
// rt.filters_should_hide_topic({last_msg_id: 12, participated: true, type: "stream"}),
// true,
// );
expected_data_to_replace_in_list_widget = [
{last_msg_id: 13, participated: true, type: "stream"},
{last_msg_id: 12, participated: true, type: "stream"},
{last_msg_id: 10, participated: true, type: "stream"},
{last_msg_id: 1, participated: true, type: "stream"},
];
// unmuted topic in muted stream
row_data = [
...row_data,
...generate_topic_data([[6, "topic-11", 1, all_visibility_policies.UNMUTED]]),
];
i = row_data.length;
stub_out_filter_buttons();
rt.process_messages([messages[12]]);
assert.equal(
rt.filters_should_hide_topic({last_msg_id: 13, participated: true, type: "stream"}),
false,
);
// expected_data_to_replace_in_list_widget = [
// {last_msg_id: 13, participated: true, type: "stream"},
// {last_msg_id: 12, participated: true, type: "stream"},
// {last_msg_id: 10, participated: true, type: "stream"},
// {last_msg_id: 1, participated: true, type: "stream"},
// ];
// // unmuted topic in muted stream
// row_data = [
// ...row_data,
// ...generate_topic_data([[6, "topic-11", 1, all_visibility_policies.UNMUTED]]),
// ];
// i = row_data.length;
// stub_out_filter_buttons();
// rt.process_messages([messages[12]]);
// assert.equal(
// rt.filters_should_hide_topic({last_msg_id: 13, participated: true, type: "stream"}),
// false,
// );
expected_data_to_replace_in_list_widget = [
{last_msg_id: 14, participated: true, type: "stream"},
{last_msg_id: 13, participated: true, type: "stream"},
{last_msg_id: 12, participated: true, type: "stream"},
{last_msg_id: 10, participated: true, type: "stream"},
{last_msg_id: 1, participated: true, type: "stream"},
];
// followed topic in muted stream
row_data = [
...row_data,
...generate_topic_data([[6, "topic-12", 1, all_visibility_policies.FOLLOWED]]),
];
i = row_data.length;
stub_out_filter_buttons();
rt.process_messages([messages[13]]);
assert.equal(
rt.filters_should_hide_topic({last_msg_id: 14, participated: true, type: "stream"}),
false,
);
// expected_data_to_replace_in_list_widget = [
// {last_msg_id: 14, participated: true, type: "stream"},
// {last_msg_id: 13, participated: true, type: "stream"},
// {last_msg_id: 12, participated: true, type: "stream"},
// {last_msg_id: 10, participated: true, type: "stream"},
// {last_msg_id: 1, participated: true, type: "stream"},
// ];
// // followed topic in muted stream
// row_data = [
// ...row_data,
// ...generate_topic_data([[6, "topic-12", 1, all_visibility_policies.FOLLOWED]]),
// ];
// i = row_data.length;
// stub_out_filter_buttons();
// rt.process_messages([messages[13]]);
// assert.equal(
// rt.filters_should_hide_topic({last_msg_id: 14, participated: true, type: "stream"}),
// false,
// );
// Test search
expected.search_val = "topic-1";
@ -630,7 +633,6 @@ test("test_filter_pm", ({mock_template}) => {
page_params.is_spectator = false;
const expected = {
filter_participated: false,
filter_unread: false,
filter_muted: false,
filter_pm: true,
search_val: "",
@ -672,128 +674,6 @@ test("test_filter_pm", ({mock_template}) => {
assert.deepEqual(rt.filters_should_hide_topic({type: "private", last_msg_id: 17}), false);
});
test("test_filter_unread", ({mock_template}) => {
let expected_filter_unread = false;
page_params.is_spectator = false;
mock_template("recent_view_table.hbs", false, (data) => {
assert.deepEqual(data, {
filter_participated: false,
filter_unread: expected_filter_unread,
filter_muted: false,
filter_pm: false,
search_val: "",
is_spectator: false,
});
});
mock_template("recent_view_filters.hbs", false, (data) => {
assert.equal(data.filter_unread, expected_filter_unread);
assert.equal(data.filter_participated, false);
return "<recent_view table stub>";
});
let i = 0;
const row_data = generate_topic_data([
// stream_id, topic, unread_count, visibility_policy
[6, "topic-12", 1, all_visibility_policies.FOLLOWED],
[6, "topic-11", 1, all_visibility_policies.UNMUTED],
[6, "topic-8", 1, all_visibility_policies.INHERIT],
[4, "topic-10", 1, all_visibility_policies.INHERIT],
[1, "topic-7", 1, all_visibility_policies.MUTED],
[1, "topic-6", 1, all_visibility_policies.INHERIT],
[1, "topic-5", 1, all_visibility_policies.INHERIT],
[1, "topic-4", 1, all_visibility_policies.INHERIT],
[1, "topic-3", 1, all_visibility_policies.INHERIT],
[1, "topic-2", 1, all_visibility_policies.INHERIT],
[1, "topic-1", 0, all_visibility_policies.INHERIT],
]);
mock_template("recent_view_row.hbs", false, (data) => {
// All the row will be processed.
if (row_data[i]) {
assert.deepEqual(data, row_data[i]);
i += 1;
}
return "<recent_view row stub>";
});
rt.clear_for_tests();
recent_view_util.set_visible(true);
rt.set_default_focus();
stub_out_filter_buttons();
rt.process_messages(messages);
$(".home-page-input").trigger("focus");
assert.equal(
rt.filters_should_hide_topic({last_msg_id: 1, participated: true, type: "stream"}),
false,
);
$("#recent_view_filter_buttons").removeClass("btn-recent-selected");
expected_filter_unread = true;
rt.set_filter("unread");
rt.update_filters_view();
expected_data_to_replace_in_list_widget = [
{
last_msg_id: 11,
participated: true,
type: "stream",
},
{
last_msg_id: 10,
participated: true,
type: "stream",
},
{
last_msg_id: 9,
participated: true,
type: "stream",
},
{
last_msg_id: 7,
participated: true,
type: "stream",
},
{
last_msg_id: 5,
participated: false,
type: "stream",
},
{
last_msg_id: 4,
participated: false,
type: "stream",
},
{
last_msg_id: 3,
participated: true,
type: "stream",
},
{
last_msg_id: 1,
participated: true,
type: "stream",
},
];
rt.process_messages([messages[0]]);
// Unselect "unread" filter by clicking twice.
expected_filter_unread = false;
$("#recent_view_filter_buttons").addClass("btn-recent-selected");
rt.set_filter("unread");
assert.equal(i, row_data.length);
$("#recent_view_filter_buttons").removeClass("btn-recent-selected");
// reselect "unread" filter
rt.set_filter("unread");
});
test("test_filter_participated", ({mock_template}) => {
let expected_filter_participated;
@ -801,7 +681,6 @@ test("test_filter_participated", ({mock_template}) => {
mock_template("recent_view_table.hbs", false, (data) => {
assert.deepEqual(data, {
filter_participated: expected_filter_participated,
filter_unread: false,
filter_muted: false,
filter_pm: false,
search_val: "",
@ -810,7 +689,6 @@ test("test_filter_participated", ({mock_template}) => {
});
mock_template("recent_view_filters.hbs", false, (data) => {
assert.equal(data.filter_unread, false);
assert.equal(data.filter_participated, expected_filter_participated);
return "<recent_view table stub>";
});