From 6f9e97921d39e87637c654337c77daa809080c7b Mon Sep 17 00:00:00 2001 From: jai2201 Date: Tue, 13 Sep 2022 16:45:57 +0530 Subject: [PATCH] pm_section: Create collapsible private messages section. This commit introduces the change of rendering private messages section as collapsible, whose data-fetching logic came with zulip#21357. We now have separated out `Private messages` from `top_left_corner` section and shifted it below the `global_filters` in a different separate section along with stream list with common scroll bar in left-sidebar. The new PM section will be opened by-default on loading the page and will have a toggle-icon in its header, clicking on which makes the section collapse/expand accordingly. In default view, only recent 5 PM threads would be shown and would append the active conversation as the 6th one at last if not present in those 5, similar to how topics list work. In PM section with unreads, a maximum of 8 conversations would be shown and rest of them would be hidden behind the 'more conversations' li-item, clicking on which takes to the zoomedIn view of PM section where all the present PM threads would be visible and rest of the sections of left-sidebar will get collapsed. Fixes #20870. Co-authored-by: Aman Agrawal --- frontend_tests/node_tests/dispatch.js | 2 + frontend_tests/node_tests/pm_list.js | 8 +- frontend_tests/node_tests/vdom.js | 2 +- frontend_tests/puppeteer_tests/compose.ts | 2 +- .../puppeteer_tests/message-basics.ts | 4 +- frontend_tests/puppeteer_tests/navigation.ts | 12 +- static/js/click_handlers.js | 42 ++++ static/js/pm_list.js | 194 ++++++++++----- static/js/pm_list_dom.js | 28 ++- static/js/recent_topics_ui.js | 2 + static/js/resize.js | 11 +- static/js/stream_list.js | 35 ++- static/js/tippyjs.js | 38 ++- static/js/top_left_corner.js | 6 +- static/js/topic_list.js | 2 +- static/js/ui_init.js | 2 + static/styles/dark_theme.css | 12 +- static/styles/left_sidebar.css | 220 +++++++++++++----- static/styles/zulip.css | 8 +- static/templates/left_sidebar.hbs | 41 ++-- static/templates/more_pms.hbs | 8 + static/templates/pm_list_item.hbs | 3 +- tools/lib/capitalization.py | 3 + 23 files changed, 517 insertions(+), 168 deletions(-) create mode 100644 static/templates/more_pms.hbs diff --git a/frontend_tests/node_tests/dispatch.js b/frontend_tests/node_tests/dispatch.js index d35f691150..d6161ebf46 100644 --- a/frontend_tests/node_tests/dispatch.js +++ b/frontend_tests/node_tests/dispatch.js @@ -40,6 +40,7 @@ const message_lists = mock_esm("../../static/js/message_lists"); const muted_topics_ui = mock_esm("../../static/js/muted_topics_ui"); const muted_users_ui = mock_esm("../../static/js/muted_users_ui"); const notifications = mock_esm("../../static/js/notifications"); +const pm_list = mock_esm("../../static/js/pm_list"); const reactions = mock_esm("../../static/js/reactions"); const realm_icon = mock_esm("../../static/js/realm_icon"); const realm_logo = mock_esm("../../static/js/realm_logo"); @@ -1020,6 +1021,7 @@ run_test("user_status", ({override}) => { { const stub = make_stub(); override(activity, "redraw_user", stub.f); + override(pm_list, "update_private_messages", noop); dispatch(event); assert.equal(stub.num_calls, 1); const args = stub.get_args("user_id"); diff --git a/frontend_tests/node_tests/pm_list.js b/frontend_tests/node_tests/pm_list.js index daba91ef7d..68aba53f7b 100644 --- a/frontend_tests/node_tests/pm_list.js +++ b/frontend_tests/node_tests/pm_list.js @@ -17,14 +17,16 @@ run_test("update_dom_with_unread_counts", () => { assert.equal(narrow_state.active(), true); const $total_count = $.create("total-count-stub"); - const $private_li = $(".top_left_private_messages .private_messages_header"); + const $private_li = $( + ".private_messages_container #private_messages_section #private_messages_section_header", + ); $private_li.set_find_results(".unread_count", $total_count); counts = { private_message_count: 10, }; - pm_list.update_dom_with_unread_counts(counts); + pm_list.set_count(counts.private_message_count); assert.equal($total_count.text(), "10"); assert.ok($total_count.visible()); @@ -32,7 +34,7 @@ run_test("update_dom_with_unread_counts", () => { private_message_count: 0, }; - pm_list.update_dom_with_unread_counts(counts); + pm_list.set_count(counts.private_message_count); assert.equal($total_count.text(), ""); assert.ok(!$total_count.visible()); }); diff --git a/frontend_tests/node_tests/vdom.js b/frontend_tests/node_tests/vdom.js index 83f7dda6a3..3a6aeec4d8 100644 --- a/frontend_tests/node_tests/vdom.js +++ b/frontend_tests/node_tests/vdom.js @@ -26,7 +26,7 @@ run_test("basics", () => { run_test("attribute escaping", () => { // So far most of the time our attributes are - // hard-coded classes like "expanded_private_messages", + // hard-coded classes like "pm-list", // but we need to be defensive about future code // that might use data from possibly malicious users. const opts = { diff --git a/frontend_tests/puppeteer_tests/compose.ts b/frontend_tests/puppeteer_tests/compose.ts index c9790e9e8a..00e6a6d3b5 100644 --- a/frontend_tests/puppeteer_tests/compose.ts +++ b/frontend_tests/puppeteer_tests/compose.ts @@ -113,7 +113,7 @@ async function test_narrow_to_private_messages_with_cordelia(page: Page): Promis you_and_cordelia_selector, ); const cordelia_user_id = await common.get_user_id_from_name(page, "Cordelia, Lear's daughter"); - const pm_list_selector = `li[data-user-ids-string="${cordelia_user_id}"].expanded_private_message.active-sub-filter`; + const pm_list_selector = `li[data-user-ids-string="${cordelia_user_id}"].pm-list-item.active-sub-filter`; await page.waitForSelector(pm_list_selector, {visible: true}); await close_compose_box(page); diff --git a/frontend_tests/puppeteer_tests/message-basics.ts b/frontend_tests/puppeteer_tests/message-basics.ts index 7066190d6e..685002cd7d 100644 --- a/frontend_tests/puppeteer_tests/message-basics.ts +++ b/frontend_tests/puppeteer_tests/message-basics.ts @@ -280,7 +280,9 @@ async function test_narrow_by_clicking_the_left_sidebar(page: Page): Promise { await page.waitForSelector("#subscription_overlay", {hidden: true}); } +async function navigate_to_private_messages(page: Page): Promise { + console.log("Navigate to private messages"); + + const all_private_messages_icon = "#show_all_private_messages"; + await page.waitForSelector(all_private_messages_icon, {visible: true}); + await page.click(all_private_messages_icon); + + await page.waitForSelector("#message_view_header .fa-envelope", {visible: true}); +} + async function test_reload_hash(page: Page): Promise { const initial_page_load_time = await page.evaluate( (): number => zulip_test.page_params.page_load_time, @@ -99,7 +109,7 @@ async function navigation_tests(page: Page): Promise { await navigate_to_subscriptions(page); await navigate_using_left_sidebar(page, "all_messages", "message_feed_container"); await navigate_to_settings(page); - await navigate_using_left_sidebar(page, "narrow/is/private", "message_feed_container"); + await navigate_to_private_messages(page); await navigate_to_subscriptions(page); await navigate_using_left_sidebar(page, verona_narrow, "message_feed_container"); diff --git a/static/js/click_handlers.js b/static/js/click_handlers.js index 3cb75c7be2..0e109e9f14 100644 --- a/static/js/click_handlers.js +++ b/static/js/click_handlers.js @@ -32,6 +32,7 @@ import * as notifications from "./notifications"; import * as overlays from "./overlays"; import {page_params} from "./page_params"; import * as people from "./people"; +import * as pm_list from "./pm_list"; import * as popovers from "./popovers"; import * as reactions from "./reactions"; import * as recent_topics_ui from "./recent_topics_ui"; @@ -759,6 +760,47 @@ export function initialize() { stream_list.toggle_filter_displayed(e); }); + $("body").on( + "click", + ".private_messages_container.zoom-out #private_messages_section_header", + (e) => { + if (e.target.classList.value === "fa fa-align-right") { + // Let the browser handle the "all private messages" widget. + return; + } + + e.preventDefault(); + e.stopPropagation(); + const $left_sidebar_scrollbar = $( + "#left_sidebar_scroll_container .simplebar-content-wrapper", + ); + const scroll_position = $left_sidebar_scrollbar.scrollTop(); + + // This next bit of logic is a bit subtle; this header + // button scrolls to the top of the private messages + // section is uncollapsed but out of view; otherwise, we + // toggle its collapsed state. + if (scroll_position === 0 || pm_list.is_private_messages_collapsed()) { + pm_list.toggle_private_messages_section(); + } + $left_sidebar_scrollbar.scrollTop(0); + }, + ); + + /* The PRIVATE MESSAGES label's click behavior is complicated; + * only when zoomed in does it have a navigation effect, so we need + * this click handler rather than just a link. */ + $("body").on( + "click", + ".private_messages_container.zoom-in #private_messages_section_header", + (e) => { + e.preventDefault(); + e.stopPropagation(); + + window.location.hash = "narrow/is/private"; + }, + ); + // WEBATHENA $("body").on("click", ".webathena_login", (e) => { diff --git a/static/js/pm_list.js b/static/js/pm_list.js index a8d28e77e1..38ef458a2a 100644 --- a/static/js/pm_list.js +++ b/static/js/pm_list.js @@ -1,7 +1,6 @@ import $ from "jquery"; +import _ from "lodash"; -import * as narrow_state from "./narrow_state"; -import * as people from "./people"; import * as pm_list_data from "./pm_list_data"; import * as pm_list_dom from "./pm_list_dom"; import * as ui from "./ui"; @@ -9,99 +8,182 @@ import * as ui_util from "./ui_util"; import * as vdom from "./vdom"; let prior_dom; -let private_messages_open = false; // This module manages the "Private messages" section in the upper // left corner of the app. This was split out from stream_list.js. -function get_filter_li() { - return $(".top_left_private_messages .private_messages_header"); +let private_messages_collapsed = false; + +// The private messages section can be zoomed in to view more messages. +// This keeps track of if we're zoomed in or not. +let zoomed = false; + +function get_private_messages_section_header() { + return $( + ".private_messages_container #private_messages_section #private_messages_section_header", + ); } -function set_count(count) { - ui_util.update_unread_count_in_dom(get_filter_li(), count); +export function set_count(count) { + ui_util.update_unread_count_in_dom(get_private_messages_section_header(), count); } -function remove_expanded_private_messages() { - ui.get_content_element($("#private-container")).empty(); -} +function close() { + private_messages_collapsed = true; + $("#toggle_private_messages_section_icon").removeClass("fa-caret-down"); + $("#toggle_private_messages_section_icon").addClass("fa-caret-right"); -export function close() { - private_messages_open = false; - prior_dom = undefined; - remove_expanded_private_messages(); + update_private_messages(); } export function _build_private_messages_list() { const conversations = pm_list_data.get_conversations(); - const dom_ast = pm_list_dom.pm_ul(conversations); + const pm_list_info = pm_list_data.get_list_info(zoomed); + const conversations_to_be_shown = pm_list_info.conversations_to_be_shown; + const more_conversations_unread_count = pm_list_info.more_conversations_unread_count; + + const pm_list_nodes = conversations_to_be_shown.map((conversation) => + pm_list_dom.keyed_pm_li(conversation), + ); + + const all_conversations_shown = conversations_to_be_shown.length === conversations.length; + if (!all_conversations_shown) { + pm_list_nodes.push( + pm_list_dom.more_private_conversations_li(more_conversations_unread_count), + ); + } + const dom_ast = pm_list_dom.pm_ul(pm_list_nodes); return dom_ast; } -export function update_private_messages() { - if (!narrow_state.active()) { - return; +function set_dom_to(new_dom) { + const $container = ui.get_content_element($("#private_messages_list")); + + function replace_content(html) { + $container.html(html); } - if (private_messages_open) { - const $container = ui.get_content_element($("#private-container")); + function find() { + return $container.find("ul"); + } + + vdom.update(replace_content, find, new_dom, prior_dom); + prior_dom = new_dom; +} + +export function update_private_messages() { + if (private_messages_collapsed) { + // In the collapsed state, we will still display the current + // conversation, to preserve the UI invariant that there's + // always something highlighted in the left sidebar. + const conversations = pm_list_data.get_conversations(); + const active_conversation = conversations.find((conversation) => conversation.is_active); + + if (active_conversation) { + const node = [pm_list_dom.keyed_pm_li(active_conversation)]; + const new_dom = pm_list_dom.pm_ul(node); + set_dom_to(new_dom); + } else { + // Otherwise, empty the section. + $(".pm-list").empty(); + prior_dom = undefined; + } + } else { const new_dom = _build_private_messages_list(); - - function replace_content(html) { - $container.html(html); - } - - function find() { - return $container.find("ul"); - } - - vdom.update(replace_content, find, new_dom, prior_dom); - prior_dom = new_dom; + set_dom_to(new_dom); } } export function expand() { - private_messages_open = true; + private_messages_collapsed = false; + $("#toggle_private_messages_section_icon").addClass("fa-caret-down"); + $("#toggle_private_messages_section_icon").removeClass("fa-caret-right"); update_private_messages(); - if (pm_list_data.is_all_privates()) { - $(".top_left_private_messages").addClass("active-filter"); - } } export function update_dom_with_unread_counts(counts) { + // In theory, we could support passing the counts object through + // to pm_list_data, rather than fetching it directly there. But + // it's not an important optimization, because it's unlikely a + // user would have 10,000s of unread PMs where it could matter. update_private_messages(); + // This is just the global unread count. set_count(counts.private_message_count); } -function should_expand_pm_list(filter) { - const op_is = filter.operands("is"); +export function highlight_all_private_messages_view() { + $(".private_messages_container").addClass("active_private_messages_section"); +} - if (op_is.length >= 1 && op_is.includes("private")) { - return true; - } - - const op_pm = filter.operands("pm-with"); - - if (op_pm.length !== 1) { - return false; - } - - const emails_strings = op_pm[0]; - const emails = emails_strings.split(","); - - const has_valid_emails = people.is_valid_bulk_emails_for_compose(emails); - - return has_valid_emails; +function unhighlight_all_private_messages_view() { + $(".private_messages_container").removeClass("active_private_messages_section"); } export function handle_narrow_activated(filter) { - if (should_expand_pm_list(filter)) { + const active_filter = filter; + const is_all_private_message_view = _.isEqual(active_filter.sorted_term_types(), [ + "is-private", + ]); + const narrow_to_private_messages_section = active_filter.operands("pm-with").length !== 0; + + if (is_all_private_message_view) { + highlight_all_private_messages_view(); + } else { + unhighlight_all_private_messages_view(); + } + if (narrow_to_private_messages_section) { + update_private_messages(); + } +} + +export function handle_narrow_deactivated() { + // Since one can renarrow via the keyboard shortcut or similar, we + // avoid disturbing the zoomed state here. + unhighlight_all_private_messages_view(); + update_private_messages(); +} + +export function is_private_messages_collapsed() { + return private_messages_collapsed; +} + +export function toggle_private_messages_section() { + // change the state of PM section depending on the previous state. + if (private_messages_collapsed) { expand(); } else { close(); } } -export function handle_narrow_deactivated() { - close(); +function zoom_in() { + zoomed = true; + update_private_messages(); + $(".private_messages_container").removeClass("zoom-out").addClass("zoom-in"); + $("#streams_list").hide(); + $(".left-sidebar .right-sidebar-items").hide(); +} + +function zoom_out() { + zoomed = false; + update_private_messages(); + $(".private_messages_container").removeClass("zoom-in").addClass("zoom-out"); + $("#streams_list").show(); + $(".left-sidebar .right-sidebar-items").show(); +} + +export function initialize() { + $(".private_messages_container").on("click", "#show_more_private_messages", (e) => { + e.stopPropagation(); + e.preventDefault(); + + zoom_in(); + }); + + $(".private_messages_container").on("click", "#hide_more_private_messages", (e) => { + e.stopPropagation(); + e.preventDefault(); + + zoom_out(); + }); } diff --git a/static/js/pm_list_dom.js b/static/js/pm_list_dom.js index e3617237bb..f633b74f15 100644 --- a/static/js/pm_list_dom.js +++ b/static/js/pm_list_dom.js @@ -1,5 +1,6 @@ import _ from "lodash"; +import render_more_private_conversations from "../templates/more_pms.hbs"; import render_pm_list_item from "../templates/pm_list_item.hbs"; import * as vdom from "./vdom"; @@ -19,13 +20,34 @@ export function keyed_pm_li(conversation) { }; } -export function pm_ul(conversations) { +export function more_private_conversations_li(more_conversations_unread_count) { + const render = () => render_more_private_conversations({more_conversations_unread_count}); + + // Used in vdom.js to check if an element has changed and needs to + // be updated in the DOM. + const eq = (other) => + other.more_items && + more_conversations_unread_count === other.more_conversations_unread_count; + + // This special key must be impossible as a user_ids_string. + const key = "more_private_conversations"; + + return { + key, + more_items: true, + more_conversations_unread_count, + render, + eq, + }; +} + +export function pm_ul(nodes) { const attrs = [ - ["class", "expanded_private_messages"], + ["class", "pm-list"], ["data-name", "private"], ]; return vdom.ul({ attrs, - keyed_nodes: conversations.map((conversation) => keyed_pm_li(conversation)), + keyed_nodes: nodes, }); } diff --git a/static/js/recent_topics_ui.js b/static/js/recent_topics_ui.js index 3683b87503..9356cf6327 100644 --- a/static/js/recent_topics_ui.js +++ b/static/js/recent_topics_ui.js @@ -20,6 +20,7 @@ import * as narrow_state from "./narrow_state"; import * as navigate from "./navigate"; import {page_params} from "./page_params"; import * as people from "./people"; +import * as pm_list from "./pm_list"; import * as recent_senders from "./recent_senders"; import {get, process_message, topics} from "./recent_topics_data"; import { @@ -767,6 +768,7 @@ export function show() { narrow.set_narrow_title(recent_topics_title); message_view_header.render_title_area(); narrow.handle_middle_pane_transition(); + pm_list.handle_narrow_deactivated(); complete_rerender(); } diff --git a/static/js/resize.js b/static/js/resize.js index aaaa7823b5..29b11597c3 100644 --- a/static/js/resize.js +++ b/static/js/resize.js @@ -55,8 +55,7 @@ function get_new_heights() { Number.parseInt($("#left-sidebar").css("marginTop"), 10) - Number.parseInt($(".narrows_panel").css("marginTop"), 10) - Number.parseInt($(".narrows_panel").css("marginBottom"), 10) - - $("#global_filters").safeOuterHeight(true) - - $("#streams_header").safeOuterHeight(true); + $("#global_filters").safeOuterHeight(true); // Don't let us crush the stream sidebar completely out of view res.stream_filters_max_height = Math.max(80, res.stream_filters_max_height); @@ -100,10 +99,8 @@ function left_userlist_get_new_heights() { Number.parseInt($(".narrows_panel").css("marginTop"), 10) - Number.parseInt($(".narrows_panel").css("marginBottom"), 10) - $("#global_filters").safeOuterHeight(true) - - $("#streams_header").safeOuterHeight(true) - $("#userlist-header").safeOuterHeight(true) - - $("#user_search_section").safeOuterHeight(true) - - Number.parseInt($stream_filters.css("marginBottom"), 10); + $("#user_search_section").safeOuterHeight(true); const blocks = [ { @@ -211,7 +208,7 @@ export function resize_bottom_whitespace(h) { export function resize_stream_filters_container(h) { h = narrow_window ? left_userlist_get_new_heights() : get_new_heights(); resize_bottom_whitespace(h); - $("#stream-filters-container").css("max-height", h.stream_filters_max_height); + $("#left_sidebar_scroll_container").css("max-height", h.stream_filters_max_height); } export function resize_sidebars() { @@ -248,7 +245,7 @@ export function resize_sidebars() { const h = narrow_window ? left_userlist_get_new_heights() : get_new_heights(); $("#buddy_list_wrapper").css("max-height", h.buddy_list_wrapper_max_height); - $("#stream-filters-container").css("max-height", h.stream_filters_max_height); + $("#left_sidebar_scroll_container").css("max-height", h.stream_filters_max_height); return h; } diff --git a/static/js/stream_list.js b/static/js/stream_list.js index 936211ff95..2fc55b7656 100644 --- a/static/js/stream_list.js +++ b/static/js/stream_list.js @@ -14,6 +14,7 @@ import * as keydown_util from "./keydown_util"; import {ListCursor} from "./list_cursor"; import * as narrow from "./narrow"; import * as narrow_state from "./narrow_state"; +import * as pm_list from "./pm_list"; import * as popovers from "./popovers"; import * as resize from "./resize"; import * as scroll_util from "./scroll_util"; @@ -268,9 +269,16 @@ export function zoom_in_topics(options) { $elt.hide(); } }); + + // we also need to hide the PM section and allow + // stream list to take complete left-sidebar in zoomedIn view. + $(".private_messages_container").hide(); } export function zoom_out_topics() { + // Show PM section + $(".private_messages_container").show(); + // Show stream list titles and pinned stream splitter $(".stream-filters-label").each(function () { $(this).show(); @@ -599,16 +607,33 @@ export function set_event_handlers() { toggle_filter_displayed(e); }); + function toggle_pm_header_icon() { + if (pm_list.is_private_messages_collapsed()) { + return; + } + + const scroll_position = $( + "#left_sidebar_scroll_container .simplebar-content-wrapper", + ).scrollTop(); + const pm_list_height = $("#private_messages_list").height(); + if (scroll_position > pm_list_height) { + $("#toggle_private_messages_section_icon").addClass("fa-caret-right"); + $("#toggle_private_messages_section_icon").removeClass("fa-caret-down"); + } else { + $("#toggle_private_messages_section_icon").addClass("fa-caret-down"); + $("#toggle_private_messages_section_icon").removeClass("fa-caret-right"); + } + } + // check for user scrolls on streams list for first time - ui.get_scroll_element($("#stream-filters-container")).on("scroll", function () { + ui.get_scroll_element($("#left_sidebar_scroll_container")).on("scroll", () => { has_scrolled = true; - // remove listener once user has scrolled - $(this).off("scroll"); + toggle_pm_header_icon(); }); stream_cursor = new ListCursor({ list: { - scroll_container_sel: "#stream-filters-container", + scroll_container_sel: "#left_sidebar_scroll_container", find_li(opts) { const stream_id = opts.key; const li = get_stream_li(stream_id); @@ -722,7 +747,7 @@ export function toggle_filter_displayed(e) { } export function scroll_stream_into_view($stream_li) { - const $container = $("#stream-filters-container"); + const $container = $("#left_sidebar_scroll_container"); if ($stream_li.length !== 1) { blueslip.error("Invalid stream_li was passed in"); diff --git a/static/js/tippyjs.js b/static/js/tippyjs.js index 3fb9928f7f..c30fbae13e 100644 --- a/static/js/tippyjs.js +++ b/static/js/tippyjs.js @@ -222,7 +222,8 @@ export function initialize() { delegate("body", { target: [ ".recipient_bar_icon", - ".sidebar-title", + "#streams_header .sidebar-title", + "#userlist-title", "#user_filter_icon", "#scroll-to-bottom-button-clickable-area", ".code_external_link", @@ -396,4 +397,39 @@ export function initialize() { } }, }); + + delegate("body", { + target: "#pm_tooltip_container", + onShow(instance) { + if ($(".private_messages_container").hasClass("zoom-in")) { + return false; + } + + if ($("#toggle_private_messages_section_icon").hasClass("fa-caret-down")) { + instance.setContent( + $t({ + defaultMessage: "Collapse private messages", + }), + ); + } else { + instance.setContent($t({defaultMessage: "Expand private messages"})); + } + return true; + }, + delay: [500, 20], + appendTo: () => document.body, + }); + + delegate("body", { + target: "#show_all_private_messages", + placement: "bottom", + onShow(instance) { + instance.setContent( + $t({ + defaultMessage: "All private messages (P)", + }), + ); + }, + appendTo: () => document.body, + }); } diff --git a/static/js/top_left_corner.js b/static/js/top_left_corner.js index fa4731ff49..87fb8eacbc 100644 --- a/static/js/top_left_corner.js +++ b/static/js/top_left_corner.js @@ -1,6 +1,5 @@ import $ from "jquery"; -import * as pm_list from "./pm_list"; import * as resize from "./resize"; import * as ui_util from "./ui_util"; import * as unread_ui from "./unread_ui"; @@ -27,9 +26,8 @@ function remove($elem) { $elem.removeClass("active-filter active-sub-filter"); } -function deselect_top_left_corner_items() { +export function deselect_top_left_corner_items() { remove($(".top_left_all_messages")); - remove($(".top_left_private_messages")); remove($(".top_left_starred_messages")); remove($(".top_left_mentions")); remove($(".top_left_recent_topics")); @@ -73,11 +71,9 @@ export function handle_narrow_deactivated() { export function narrow_to_recent_topics() { remove($(".top_left_all_messages")); - remove($(".top_left_private_messages")); remove($(".top_left_starred_messages")); remove($(".top_left_mentions")); $(".top_left_recent_topics").addClass("active-filter"); - pm_list.close(); setTimeout(() => { resize.resize_stream_filters_container(); }, 0); diff --git a/static/js/topic_list.js b/static/js/topic_list.js index 3701c6a461..9e5734bf21 100644 --- a/static/js/topic_list.js +++ b/static/js/topic_list.js @@ -331,7 +331,7 @@ export function zoom_in() { active_widget.build(); } - ui.get_scroll_element($("#stream-filters-container")).scrollTop(0); + ui.get_scroll_element($("#left_sidebar_scroll_container")).scrollTop(0); const spinner = true; active_widget.build(spinner); diff --git a/static/js/ui_init.js b/static/js/ui_init.js index f87a098f18..14573ba648 100644 --- a/static/js/ui_init.js +++ b/static/js/ui_init.js @@ -59,6 +59,7 @@ import * as overlays from "./overlays"; import {page_params} from "./page_params"; import * as people from "./people"; import * as pm_conversations from "./pm_conversations"; +import * as pm_list from "./pm_list"; import * as popover_menus from "./popover_menus"; import * as presence from "./presence"; import * as realm_logo from "./realm_logo"; @@ -677,6 +678,7 @@ export function initialize_everything() { unread_ui.initialize(); activity.initialize(); emoji_picker.initialize(); + pm_list.initialize(); topic_list.initialize(); topic_zoom.initialize(); drafts.initialize(); diff --git a/static/styles/dark_theme.css b/static/styles/dark_theme.css index 011b43849e..12e69c3a55 100644 --- a/static/styles/dark_theme.css +++ b/static/styles/dark_theme.css @@ -144,7 +144,9 @@ body.dark-theme { .column-right .right-sidebar, #groups_overlay .right, #subscription_overlay .right, - #settings_page .right { + #settings_page .right, + #streams_header, + .private_messages_container { background-color: hsl(212, 28%, 18%); } @@ -225,6 +227,14 @@ body.dark-theme { background-color: hsl(208, 17%, 29%); } + .active_private_messages_section { + #private_messages_section, + #private_messages_list, + #hide_more_private_messages { + background-color: hsla(199, 33%, 46%, 0.2); + } + } + /* do not turn the .message_header .stream_label text dark on hover because they're on a dark background, and don't change the dark labels dark either. */ .message_header:not(.dark_background) diff --git a/static/styles/left_sidebar.css b/static/styles/left_sidebar.css index f82e8aa7eb..3fbd91fc05 100644 --- a/static/styles/left_sidebar.css +++ b/static/styles/left_sidebar.css @@ -8,6 +8,8 @@ $left_col_size: 19px; the above (and another 5px of padding not measured here) */ $topic_indent: calc($far_left_gutter_size + $left_col_size + 4px); $topic_resolve_width: 13px; +/* Space between section in the left sidebar. */ +$sections_vertical_gutter: 8px; #left-sidebar { #user-list { @@ -50,11 +52,6 @@ $topic_resolve_width: 13px; } } -.pm_left_col { - min-width: $left_col_size; - margin-left: 15px; -} - #stream_filters, #global_filters { margin-right: 12px; @@ -72,7 +69,7 @@ li.show-more-topics { float: right; opacity: 0.5; padding: 3px; - margin-left: 7px; + margin-left: 4px; &:hover { opacity: 1; @@ -89,7 +86,7 @@ li.show-more-topics { } #streams_inline_icon { - margin-right: 10px; + margin-right: 8px; } .tooltip { @@ -106,6 +103,10 @@ li.show-more-topics { li { a { padding: 1px 0; + + &:hover { + text-decoration: none; + } } ul { @@ -190,8 +191,8 @@ li.show-more-topics { } } -#private-container, -#stream-filters-container { +#left_sidebar_scroll_container { + outline: none; overflow-x: hidden; overflow-y: auto; position: relative; @@ -199,16 +200,111 @@ li.show-more-topics { width: 100%; } -#stream-filters-container .simplebar-content-wrapper { - outline: none; +.private_messages_container { + background: hsl(0, 0%, 100%); + margin-right: 16px; + margin-left: 6px; + z-index: 1; + + #toggle_private_messages_section_icon { + opacity: 0.7; + margin-left: -15px; + min-width: 12px; + + &.fa-caret-right { + position: relative; + left: 3px; + } + + &:hover { + opacity: 1; + } + } + + #private_messages_section_header { + cursor: pointer; + padding: 0 10px 1px 4px; + white-space: nowrap; + + #show_all_private_messages { + right: 0; + float: right; + position: absolute; + opacity: 0.7; + text-decoration: none; + color: inherit; + margin-right: 21px; + margin-top: 1px; + + &:hover { + opacity: 1; + } + } + + .unread_count { + margin-right: 16px; + margin-top: 2px; + } + } + + ul.pm-list { + list-style-type: none; + font-weight: 400; + margin-left: 0; + margin-bottom: 0; + + span.fa-group { + font-size: 90%; + } + + li.pm-list-item { + position: relative; + padding: 1px 10px 1px 4px; + margin-left: 2px; + + a { + text-decoration: none; + color: inherit; + } + + .pm_left_col { + min-width: $left_col_size; + } + } + + li#show_more_private_messages { + cursor: pointer; + padding-right: 26px; + padding-left: 6px; + + a { + font-size: 12px; + } + + .unread_count { + margin-top: 2px; + } + } + } } -#private-container { - max-height: 210px; +.active_private_messages_section { + #private_messages_section, + #private_messages_list, + #hide_more_private_messages { + background-color: hsl(202, 56%, 91%); + } - /* Match the opacity for global-filters icons. */ - span.fa-group { - opacity: 0.7; + #private_messages_section { + border-radius: 4px 4px 0 0; + } + + #private_messages_list { + border-radius: 0 0 4px 4px; + } + + #more_private_messages_sidebar_title { + font-weight: 600; } } @@ -277,7 +373,7 @@ li.active-sub-filter { } #global_filters { - margin-bottom: 16px; + margin-bottom: $sections_vertical_gutter; .filter-icon { display: inline-block; @@ -301,20 +397,12 @@ li.active-sub-filter { margin-top: 1px !important; } - .expanded_private_message .unread_count { - /* This margin accounts for the fact that the private messages - container gets a few pixels taller when expanded */ - margin: 0; - display: inline; - } - i { opacity: 0.7; } } li.top_left_all_messages, -.private_messages_header, li.top_left_mentions, li.top_left_starred_messages, li.top_left_drafts, @@ -333,12 +421,6 @@ li.top_left_recent_topics { padding-right: 10px; } -.top_left_row, -.bottom_left_row, -.top_left_private_messages { - border-radius: 4px; -} - .conversation-partners { line-height: 1.25; } @@ -348,12 +430,6 @@ li.top_left_recent_topics { font-size: 15px; } -.top_left_private_messages i.fa-envelope { - position: relative; - top: -1px; - font-size: 11px; -} - .top_left_mentions i.fa-at, .top_left_starred_messages i.fa-star { font-size: 13px; @@ -479,32 +555,11 @@ ul.topic-list { font-weight: normal; } -ul.expanded_private_messages { - list-style-type: none; - - span.fa-group { - font-size: 90%; - } - font-weight: 400; - margin-left: 0; - padding-bottom: 2px; -} - li.topic-list-item { position: relative; padding-right: 5px; } -li.expanded_private_message { - position: relative; - padding-top: 1px; - padding-bottom: 1px; - - a { - margin: 1px 0; - } -} - .show-all-streams { a { color: hsl(0, 0%, 20%); @@ -525,7 +580,7 @@ li.expanded_private_message { } .pm-box { - margin-right: 20px; + margin-right: 16px; align-items: center; .user_circle { @@ -548,6 +603,10 @@ li.expanded_private_message { #topics_header { display: none; } + + .zoom-out-hide { + display: none; + } } #topics_header { @@ -560,7 +619,7 @@ li.expanded_private_message { text-transform: uppercase; i { - margin: 0 5px 0 10px; + margin: 0 6px 0 13px; position: relative; top: 1px; } @@ -569,9 +628,12 @@ li.expanded_private_message { #streams_header { margin-right: 12px; - padding-left: $far_left_gutter_size; cursor: pointer; - margin-top: 3px; + padding: $sections_vertical_gutter 0 3px $far_left_gutter_size; + position: sticky; + top: 0; + background: hsl(0, 0%, 100%); + z-index: 1; input { padding-right: 20px; @@ -643,7 +705,41 @@ li.expanded_private_message { display: none; } + #more_private_messages_sidebar_title { + display: inline; + } + + #hide_more_private_messages { + display: block; + text-decoration: none; + color: inherit; + font-size: 12px; + + span { + display: block; + padding: 2px 0 2px 4px; + } + + &:hover { + span { + background-color: hsla(120, 12.3%, 71.4%, 0.38); + border-radius: 4px; + } + } + } + .zoom-in-hide { display: none; } + + .zoom-in-sticky { + position: sticky; + top: 0; + z-index: 1; + padding: 3px 0 3px $far_left_gutter_size; + } + + #show_all_private_messages { + margin-right: 5px !important; + } } diff --git a/static/styles/zulip.css b/static/styles/zulip.css index 44a7570591..90d052b0a2 100644 --- a/static/styles/zulip.css +++ b/static/styles/zulip.css @@ -18,7 +18,7 @@ go beneath the header. */ $sidebar_top: calc($header_height + $header_padding_bottom); -/* These need to agree with scroll_bar.js */ +$left_sidebar_collapse_widget_gutter: 10px; $left_sidebar_width: 270px; $right_sidebar_width: 250px; @@ -532,7 +532,7 @@ p.n-margin { .column-left .left-sidebar { width: $left_sidebar_width; - padding-left: 0; + padding-left: $left_sidebar_collapse_widget_gutter; } .column-right .right-sidebar { @@ -548,7 +548,9 @@ p.n-margin { .column-middle, #compose-content { margin-right: $right_sidebar_width; - margin-left: $left_sidebar_width; + margin-left: calc( + $left_sidebar_width + $left_sidebar_collapse_widget_gutter + ); position: relative; } diff --git a/static/templates/left_sidebar.hbs b/static/templates/left_sidebar.hbs index 20ba842265..042695778f 100644 --- a/static/templates/left_sidebar.hbs +++ b/static/templates/left_sidebar.hbs @@ -22,20 +22,6 @@ {{t 'Recent conversations' }} -
  • - -
    -
    -
  • @@ -69,6 +55,31 @@
  • + + + + {{~!-- squash whitespace --~}} +