pm_list: Add search to direct message section.

Fixes #22113.

The search will only be visible when in the `more conversations`
view. Once we click `back to channels` and close the
`more conversations` view, the search will clear and the search
box will disappear.

We've chosen `pm_list_data.get_conversations` as the function
to which we will pass our search term. We could have technically
found the value of the filter element via JQuery in pm_list_data,
but pm_list_data does not handle any JQuery and we should keep
it that way.

`pm_list_data.get_list_info` also accepts the search_string so that
the info it returns in expanded view is accurate.

We've also added some code to `click_handlers` to make sure that
clicking the search input does not bring us into the DM narrow.
This commit is contained in:
Shubham Padia 2024-06-06 08:35:51 +00:00 committed by Tim Abbott
parent 188dd87eec
commit 76e8ec114a
6 changed files with 147 additions and 54 deletions

View File

@ -774,7 +774,13 @@ export function initialize() {
e.preventDefault();
e.stopPropagation();
window.location.hash = "narrow/is/dm";
if (!$(e.target).hasClass("direct-messages-list-filter")) {
// Avoiding having clicks on the filter input.
//
// TODO: Refactor to use more precise selectors for this
// click handler in general; this is a fragile pattern.
window.location.hash = "narrow/is/dm";
}
});
// disable the draggability for left-sidebar components

View File

@ -39,8 +39,10 @@ export function close(): void {
}
export function _build_direct_messages_list(): vdom.Tag<PMNode> {
const conversations = pm_list_data.get_conversations();
const pm_list_info = pm_list_data.get_list_info(zoomed);
const $filter = $<HTMLInputElement>(".direct-messages-list-filter").expectOne();
const search_term = $filter.val()!;
const conversations = pm_list_data.get_conversations(search_term);
const pm_list_info = pm_list_data.get_list_info(zoomed, search_term);
const conversations_to_be_shown = pm_list_info.conversations_to_be_shown;
const more_conversations_unread_count = pm_list_info.more_conversations_unread_count;
@ -55,6 +57,12 @@ export function _build_direct_messages_list(): vdom.Tag<PMNode> {
);
}
const dom_ast = pm_list_dom.pm_ul(pm_list_nodes);
if (search_term === "") {
$("#clear-direct-messages-search-button").hide();
} else {
$("#clear-direct-messages-search-button").show();
}
return dom_ast;
}
@ -197,16 +205,31 @@ function zoom_in(): void {
$(".direct-messages-container").removeClass("zoom-out").addClass("zoom-in");
$("#streams_list").hide();
$(".left-sidebar .right-sidebar-items").hide();
const $filter = $(".direct-messages-list-filter").expectOne();
$filter.trigger("focus");
}
function zoom_out(): void {
zoomed = false;
update_private_messages();
clear_search(true); // force rerender if the search is empty.
$(".direct-messages-container").removeClass("zoom-in").addClass("zoom-out");
$("#streams_list").show();
$(".left-sidebar .right-sidebar-items").show();
}
export function clear_search(force_rerender = false): void {
const $filter = $(".direct-messages-list-filter").expectOne();
if ($filter.val() !== "") {
$filter.val("");
update_private_messages();
} else if (force_rerender) {
update_private_messages();
}
}
const throttled_update_private_message = _.throttle(update_private_messages, 50);
export function initialize(): void {
$(".direct-messages-container").on("click", "#show-more-direct-messages", (e) => {
e.stopPropagation();
@ -221,4 +244,18 @@ export function initialize(): void {
zoom_out();
});
$(".direct-messages-container").on("input", ".direct-messages-list-filter", (e) => {
e.stopPropagation();
e.preventDefault();
throttled_update_private_message();
});
$(".direct-messages-container").on("click", "#clear-direct-messages-search-button", (e) => {
e.stopPropagation();
e.preventDefault();
clear_search();
});
}

View File

@ -48,7 +48,7 @@ type DisplayObject = {
is_bot: boolean;
};
export function get_conversations(): DisplayObject[] {
export function get_conversations(search_string = ""): DisplayObject[] {
const conversations = pm_conversations.recent.get();
const display_objects = [];
@ -66,6 +66,16 @@ export function get_conversations(): DisplayObject[] {
for (const conversation of conversations) {
const user_ids_string = conversation.user_ids_string;
const users = people.get_users_from_ids(
people.user_ids_string_to_ids_array(user_ids_string),
);
if (!people.dm_matches_search_string(users, search_string)) {
// Skip adding the conversation to the display_objects array if it does
// not match the search_term.
continue;
}
const reply_to = people.user_ids_string_to_emails_string(user_ids_string);
assert(reply_to !== undefined);
const recipients_string = people.get_recipients(user_ids_string);
@ -110,11 +120,14 @@ export function get_conversations(): DisplayObject[] {
}
// Designed to closely match topic_list_data.get_list_info().
export function get_list_info(zoomed: boolean): {
export function get_list_info(
zoomed: boolean,
search_term = "",
): {
conversations_to_be_shown: DisplayObject[];
more_conversations_unread_count: number;
} {
const conversations = get_conversations();
const conversations = get_conversations(search_term);
if (zoomed || conversations.length <= max_conversations_to_show) {
return {

View File

@ -1130,6 +1130,13 @@ li.topic-list-item {
}
}
/* Since direct-messages-sticky-header also has the `input-append`
class accompanying it. The display property of that class will
overwrite display: none if we don't have a more specific CSS
rule. It will also overwrite `display: none` even if `.zoom-out`
properties are declared after the `.input-append` properties since
the latter is more specific. */
#direct-messages-sticky-header.zoom-out,
.zoom-out {
#topics_header {
display: none;
@ -1225,53 +1232,6 @@ li.topic-list-item {
vertical centering. */
line-height: 20px;
white-space: nowrap;
.stream-list-filter {
/* Use the border-box model so flex
can do its thing despite whatever
padding and border we specify. */
box-sizing: border-box;
flex: 1 0 100%;
/* Match the input height exactly
with the row height for a perfect
fit and better vertical alignment. */
height: 28px;
/* Pad the entire clear-button area,
so that input text does not bleed
into there. */
padding-right: 30px;
}
.clear_search_button {
/* Use the border-box model so flex
can do its thing despite whatever
padding and border we specify. */
box-sizing: border-box;
/* Clear inherited positioning. */
position: static;
/* We're going to use flexbox, not
positioning, to get the clear button
over top of the input. This -30px
margin accomplishes that, in tandem
with the 30px width of this element,
which is shared with the ending
anchor element in left sidebar header
rows. */
width: 30px;
margin-left: -30px;
/* Flexbox respects z-index; this just
ensures the button remains over top
of the input. */
z-index: 1;
/* Make the button itself a flex container,
so we can perfectly center the X icon. */
display: flex;
justify-content: center;
align-items: center;
/* Flexbox will pull the element open
to make a generous clickable area. */
padding: 0;
}
}
&.hide_unread_counts {
@ -1307,6 +1267,57 @@ li.topic-list-item {
}
}
.stream_search_section,
.direct-messages-search-section {
.stream-list-filter,
.direct-messages-list-filter {
/* Use the border-box model so flex
can do its thing despite whatever
padding and border we specify. */
box-sizing: border-box;
flex: 1 0 100%;
/* Match the input height exactly
with the row height for a perfect
fit and better vertical alignment. */
height: 28px;
/* Pad the entire clear-button area,
so that input text does not bleed
into there. */
padding-right: 30px;
}
.clear_search_button {
/* Use the border-box model so flex
can do its thing despite whatever
padding and border we specify. */
box-sizing: border-box;
/* Clear inherited positioning. */
position: static;
/* We're going to use flexbox, not
positioning, to get the clear button
over top of the input. This -30px
margin accomplishes that, in tandem
with the 30px width of this element,
which is shared with the ending
anchor element in left sidebar header
rows. */
width: 30px;
margin-left: -30px;
/* Flexbox respects z-index; this just
ensures the button remains over top
of the input. */
z-index: 1;
/* Make the button itself a flex container,
so we can perfectly center the X icon. */
display: flex;
justify-content: center;
align-items: center;
/* Flexbox will pull the element open
to make a generous clickable area. */
padding: 0;
}
}
/* Prepare an adjusted grid for the logged-out state,
one that reassigns the vdots space to markers and
controls. */
@ -1415,6 +1426,13 @@ li.topic-list-item {
}
}
.direct-messages-search-section {
display: flex;
grid-column: row-content / markers-and-controls;
margin-top: 5px;
margin-bottom: 5px;
}
.zoom-in-hide {
display: none;
}

View File

@ -155,6 +155,12 @@
<a class="zoom-out-hide" id="hide-more-direct-messages">
<span class="hide-more-direct-messages-text"> {{t 'back to channels' }}</span>
</a>
<div class="zoom-out-hide direct-messages-search-section">
<input class="direct-messages-list-filter filter_text_input" type="text" autocomplete="off" placeholder="{{t 'Filter direct messages' }}" />
<button type="button" class="btn clear_search_button" id="clear-direct-messages-search-button">
<i class="fa fa-remove" aria-hidden="true"></i>
</button>
</div>
</div>
{{~!-- squash whitespace --~}}
<div id="left_sidebar_scroll_container" class="scrolling_list" data-simplebar data-simplebar-tab-index="-1">

View File

@ -163,6 +163,19 @@ test("get_conversations", ({override}) => {
set_pm_with_filter("iago@zulip.com");
pm_data = pm_list_data.get_conversations();
assert.deepEqual(pm_data, expected_data);
pm_data = pm_list_data.get_conversations("Ia");
assert.deepEqual(
pm_data,
expected_data.filter((item) => item.recipients === "Iago"),
);
// filter should work with email
pm_data = pm_list_data.get_conversations("me@zulip");
assert.deepEqual(
pm_data,
expected_data.filter((item) => item.recipients === "Me Myself"),
);
});
test("get_conversations bot", ({override}) => {