diff --git a/tools/test-js-with-node b/tools/test-js-with-node
index 43198932b4..7e6334bc68 100755
--- a/tools/test-js-with-node
+++ b/tools/test-js-with-node
@@ -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",
]
)
diff --git a/web/src/recent_view_ui.js b/web/src/recent_view_ui.js
index 302265c273..985cc1ec8d 100644
--- a/web/src/recent_view_ui.js
+++ b/web/src/recent_view_ui.js
@@ -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();
diff --git a/web/styles/inbox.css b/web/styles/inbox.css
index bf59b97097..5c560353a7 100644
--- a/web/styles/inbox.css
+++ b/web/styles/inbox.css
@@ -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;
}
diff --git a/web/styles/recent_view.css b/web/styles/recent_view.css
index 6575c82f4a..6016a8ad0f 100644
--- a/web/styles/recent_view.css
+++ b/web/styles/recent_view.css
@@ -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);
+ }
+}
diff --git a/web/templates/recent_view_filters.hbs b/web/templates/recent_view_filters.hbs
index e76c27c560..420ec1c00c 100644
--- a/web/templates/recent_view_filters.hbs
+++ b/web/templates/recent_view_filters.hbs
@@ -1,3 +1,4 @@
+{{> ./dropdown_widget widget_name="recent-view-filter"}}
-