diff --git a/web/src/click_handlers.js b/web/src/click_handlers.js index e6b529b616..ace2eda075 100644 --- a/web/src/click_handlers.js +++ b/web/src/click_handlers.js @@ -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 diff --git a/web/src/pm_list.ts b/web/src/pm_list.ts index ae228eb879..371c9c5d2b 100644 --- a/web/src/pm_list.ts +++ b/web/src/pm_list.ts @@ -39,8 +39,10 @@ export function close(): void { } export function _build_direct_messages_list(): vdom.Tag { - const conversations = pm_list_data.get_conversations(); - const pm_list_info = pm_list_data.get_list_info(zoomed); + const $filter = $(".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 { ); } 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(); + }); } diff --git a/web/src/pm_list_data.ts b/web/src/pm_list_data.ts index b0b8299711..b877eff5e1 100644 --- a/web/src/pm_list_data.ts +++ b/web/src/pm_list_data.ts @@ -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 { diff --git a/web/styles/left_sidebar.css b/web/styles/left_sidebar.css index 569f7be2a2..0f594d370f 100644 --- a/web/styles/left_sidebar.css +++ b/web/styles/left_sidebar.css @@ -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; } diff --git a/web/templates/left_sidebar.hbs b/web/templates/left_sidebar.hbs index 3968105ffc..0575247594 100644 --- a/web/templates/left_sidebar.hbs +++ b/web/templates/left_sidebar.hbs @@ -155,6 +155,12 @@ {{t 'back to channels' }} +
+ + +
{{~!-- squash whitespace --~}}