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 <amanagr@zulip.com>
This commit is contained in:
jai2201 2022-09-13 16:45:57 +05:30 committed by Tim Abbott
parent 15b9e9c7cc
commit 6f9e97921d
23 changed files with 517 additions and 168 deletions

View File

@ -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_topics_ui = mock_esm("../../static/js/muted_topics_ui");
const muted_users_ui = mock_esm("../../static/js/muted_users_ui"); const muted_users_ui = mock_esm("../../static/js/muted_users_ui");
const notifications = mock_esm("../../static/js/notifications"); const notifications = mock_esm("../../static/js/notifications");
const pm_list = mock_esm("../../static/js/pm_list");
const reactions = mock_esm("../../static/js/reactions"); const reactions = mock_esm("../../static/js/reactions");
const realm_icon = mock_esm("../../static/js/realm_icon"); const realm_icon = mock_esm("../../static/js/realm_icon");
const realm_logo = mock_esm("../../static/js/realm_logo"); const realm_logo = mock_esm("../../static/js/realm_logo");
@ -1020,6 +1021,7 @@ run_test("user_status", ({override}) => {
{ {
const stub = make_stub(); const stub = make_stub();
override(activity, "redraw_user", stub.f); override(activity, "redraw_user", stub.f);
override(pm_list, "update_private_messages", noop);
dispatch(event); dispatch(event);
assert.equal(stub.num_calls, 1); assert.equal(stub.num_calls, 1);
const args = stub.get_args("user_id"); const args = stub.get_args("user_id");

View File

@ -17,14 +17,16 @@ run_test("update_dom_with_unread_counts", () => {
assert.equal(narrow_state.active(), true); assert.equal(narrow_state.active(), true);
const $total_count = $.create("total-count-stub"); 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); $private_li.set_find_results(".unread_count", $total_count);
counts = { counts = {
private_message_count: 10, 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.equal($total_count.text(), "10");
assert.ok($total_count.visible()); assert.ok($total_count.visible());
@ -32,7 +34,7 @@ run_test("update_dom_with_unread_counts", () => {
private_message_count: 0, 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.equal($total_count.text(), "");
assert.ok(!$total_count.visible()); assert.ok(!$total_count.visible());
}); });

View File

@ -26,7 +26,7 @@ run_test("basics", () => {
run_test("attribute escaping", () => { run_test("attribute escaping", () => {
// So far most of the time our attributes are // 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 // but we need to be defensive about future code
// that might use data from possibly malicious users. // that might use data from possibly malicious users.
const opts = { const opts = {

View File

@ -113,7 +113,7 @@ async function test_narrow_to_private_messages_with_cordelia(page: Page): Promis
you_and_cordelia_selector, you_and_cordelia_selector,
); );
const cordelia_user_id = await common.get_user_id_from_name(page, "Cordelia, Lear's daughter"); 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 page.waitForSelector(pm_list_selector, {visible: true});
await close_compose_box(page); await close_compose_box(page);

View File

@ -280,7 +280,9 @@ async function test_narrow_by_clicking_the_left_sidebar(page: Page): Promise<voi
await page.click(".top_left_all_messages a"); await page.click(".top_left_all_messages a");
await expect_home(page); await expect_home(page);
await page.click(".top_left_private_messages a"); 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 expect_all_pm(page); await expect_all_pm(page);
await un_narrow(page); await un_narrow(page);

View File

@ -64,6 +64,16 @@ async function navigate_to_subscriptions(page: Page): Promise<void> {
await page.waitForSelector("#subscription_overlay", {hidden: true}); await page.waitForSelector("#subscription_overlay", {hidden: true});
} }
async function navigate_to_private_messages(page: Page): Promise<void> {
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<void> { async function test_reload_hash(page: Page): Promise<void> {
const initial_page_load_time = await page.evaluate( const initial_page_load_time = await page.evaluate(
(): number => zulip_test.page_params.page_load_time, (): number => zulip_test.page_params.page_load_time,
@ -99,7 +109,7 @@ async function navigation_tests(page: Page): Promise<void> {
await navigate_to_subscriptions(page); await navigate_to_subscriptions(page);
await navigate_using_left_sidebar(page, "all_messages", "message_feed_container"); await navigate_using_left_sidebar(page, "all_messages", "message_feed_container");
await navigate_to_settings(page); 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_to_subscriptions(page);
await navigate_using_left_sidebar(page, verona_narrow, "message_feed_container"); await navigate_using_left_sidebar(page, verona_narrow, "message_feed_container");

View File

@ -32,6 +32,7 @@ import * as notifications from "./notifications";
import * as overlays from "./overlays"; import * as overlays from "./overlays";
import {page_params} from "./page_params"; import {page_params} from "./page_params";
import * as people from "./people"; import * as people from "./people";
import * as pm_list from "./pm_list";
import * as popovers from "./popovers"; import * as popovers from "./popovers";
import * as reactions from "./reactions"; import * as reactions from "./reactions";
import * as recent_topics_ui from "./recent_topics_ui"; import * as recent_topics_ui from "./recent_topics_ui";
@ -759,6 +760,47 @@ export function initialize() {
stream_list.toggle_filter_displayed(e); 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 // WEBATHENA
$("body").on("click", ".webathena_login", (e) => { $("body").on("click", ".webathena_login", (e) => {

View File

@ -1,7 +1,6 @@
import $ from "jquery"; 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_data from "./pm_list_data";
import * as pm_list_dom from "./pm_list_dom"; import * as pm_list_dom from "./pm_list_dom";
import * as ui from "./ui"; import * as ui from "./ui";
@ -9,99 +8,182 @@ import * as ui_util from "./ui_util";
import * as vdom from "./vdom"; import * as vdom from "./vdom";
let prior_dom; let prior_dom;
let private_messages_open = false;
// This module manages the "Private messages" section in the upper // This module manages the "Private messages" section in the upper
// left corner of the app. This was split out from stream_list.js. // left corner of the app. This was split out from stream_list.js.
function get_filter_li() { let private_messages_collapsed = false;
return $(".top_left_private_messages .private_messages_header");
// 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) { export function set_count(count) {
ui_util.update_unread_count_in_dom(get_filter_li(), count); ui_util.update_unread_count_in_dom(get_private_messages_section_header(), count);
} }
function remove_expanded_private_messages() { function close() {
ui.get_content_element($("#private-container")).empty(); 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() { update_private_messages();
private_messages_open = false;
prior_dom = undefined;
remove_expanded_private_messages();
} }
export function _build_private_messages_list() { export function _build_private_messages_list() {
const conversations = pm_list_data.get_conversations(); 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; return dom_ast;
} }
export function update_private_messages() { function set_dom_to(new_dom) {
if (!narrow_state.active()) { const $container = ui.get_content_element($("#private_messages_list"));
return;
function replace_content(html) {
$container.html(html);
} }
if (private_messages_open) { function find() {
const $container = ui.get_content_element($("#private-container")); 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(); const new_dom = _build_private_messages_list();
set_dom_to(new_dom);
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;
} }
} }
export function expand() { 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(); 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) { 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(); update_private_messages();
// This is just the global unread count.
set_count(counts.private_message_count); set_count(counts.private_message_count);
} }
function should_expand_pm_list(filter) { export function highlight_all_private_messages_view() {
const op_is = filter.operands("is"); $(".private_messages_container").addClass("active_private_messages_section");
}
if (op_is.length >= 1 && op_is.includes("private")) { function unhighlight_all_private_messages_view() {
return true; $(".private_messages_container").removeClass("active_private_messages_section");
}
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;
} }
export function handle_narrow_activated(filter) { 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(); expand();
} else { } else {
close(); close();
} }
} }
export function handle_narrow_deactivated() { function zoom_in() {
close(); 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();
});
} }

View File

@ -1,5 +1,6 @@
import _ from "lodash"; 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 render_pm_list_item from "../templates/pm_list_item.hbs";
import * as vdom from "./vdom"; 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 = [ const attrs = [
["class", "expanded_private_messages"], ["class", "pm-list"],
["data-name", "private"], ["data-name", "private"],
]; ];
return vdom.ul({ return vdom.ul({
attrs, attrs,
keyed_nodes: conversations.map((conversation) => keyed_pm_li(conversation)), keyed_nodes: nodes,
}); });
} }

View File

@ -20,6 +20,7 @@ import * as narrow_state from "./narrow_state";
import * as navigate from "./navigate"; import * as navigate from "./navigate";
import {page_params} from "./page_params"; import {page_params} from "./page_params";
import * as people from "./people"; import * as people from "./people";
import * as pm_list from "./pm_list";
import * as recent_senders from "./recent_senders"; import * as recent_senders from "./recent_senders";
import {get, process_message, topics} from "./recent_topics_data"; import {get, process_message, topics} from "./recent_topics_data";
import { import {
@ -767,6 +768,7 @@ export function show() {
narrow.set_narrow_title(recent_topics_title); narrow.set_narrow_title(recent_topics_title);
message_view_header.render_title_area(); message_view_header.render_title_area();
narrow.handle_middle_pane_transition(); narrow.handle_middle_pane_transition();
pm_list.handle_narrow_deactivated();
complete_rerender(); complete_rerender();
} }

View File

@ -55,8 +55,7 @@ function get_new_heights() {
Number.parseInt($("#left-sidebar").css("marginTop"), 10) - Number.parseInt($("#left-sidebar").css("marginTop"), 10) -
Number.parseInt($(".narrows_panel").css("marginTop"), 10) - Number.parseInt($(".narrows_panel").css("marginTop"), 10) -
Number.parseInt($(".narrows_panel").css("marginBottom"), 10) - Number.parseInt($(".narrows_panel").css("marginBottom"), 10) -
$("#global_filters").safeOuterHeight(true) - $("#global_filters").safeOuterHeight(true);
$("#streams_header").safeOuterHeight(true);
// Don't let us crush the stream sidebar completely out of view // 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); 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("marginTop"), 10) -
Number.parseInt($(".narrows_panel").css("marginBottom"), 10) - Number.parseInt($(".narrows_panel").css("marginBottom"), 10) -
$("#global_filters").safeOuterHeight(true) - $("#global_filters").safeOuterHeight(true) -
$("#streams_header").safeOuterHeight(true) -
$("#userlist-header").safeOuterHeight(true) - $("#userlist-header").safeOuterHeight(true) -
$("#user_search_section").safeOuterHeight(true) - $("#user_search_section").safeOuterHeight(true);
Number.parseInt($stream_filters.css("marginBottom"), 10);
const blocks = [ const blocks = [
{ {
@ -211,7 +208,7 @@ export function resize_bottom_whitespace(h) {
export function resize_stream_filters_container(h) { export function resize_stream_filters_container(h) {
h = narrow_window ? left_userlist_get_new_heights() : get_new_heights(); h = narrow_window ? left_userlist_get_new_heights() : get_new_heights();
resize_bottom_whitespace(h); 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() { export function resize_sidebars() {
@ -248,7 +245,7 @@ export function resize_sidebars() {
const h = narrow_window ? left_userlist_get_new_heights() : get_new_heights(); const h = narrow_window ? left_userlist_get_new_heights() : get_new_heights();
$("#buddy_list_wrapper").css("max-height", h.buddy_list_wrapper_max_height); $("#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; return h;
} }

View File

@ -14,6 +14,7 @@ import * as keydown_util from "./keydown_util";
import {ListCursor} from "./list_cursor"; import {ListCursor} from "./list_cursor";
import * as narrow from "./narrow"; import * as narrow from "./narrow";
import * as narrow_state from "./narrow_state"; import * as narrow_state from "./narrow_state";
import * as pm_list from "./pm_list";
import * as popovers from "./popovers"; import * as popovers from "./popovers";
import * as resize from "./resize"; import * as resize from "./resize";
import * as scroll_util from "./scroll_util"; import * as scroll_util from "./scroll_util";
@ -268,9 +269,16 @@ export function zoom_in_topics(options) {
$elt.hide(); $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() { export function zoom_out_topics() {
// Show PM section
$(".private_messages_container").show();
// Show stream list titles and pinned stream splitter // Show stream list titles and pinned stream splitter
$(".stream-filters-label").each(function () { $(".stream-filters-label").each(function () {
$(this).show(); $(this).show();
@ -599,16 +607,33 @@ export function set_event_handlers() {
toggle_filter_displayed(e); 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 // 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; has_scrolled = true;
// remove listener once user has scrolled toggle_pm_header_icon();
$(this).off("scroll");
}); });
stream_cursor = new ListCursor({ stream_cursor = new ListCursor({
list: { list: {
scroll_container_sel: "#stream-filters-container", scroll_container_sel: "#left_sidebar_scroll_container",
find_li(opts) { find_li(opts) {
const stream_id = opts.key; const stream_id = opts.key;
const li = get_stream_li(stream_id); 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) { export function scroll_stream_into_view($stream_li) {
const $container = $("#stream-filters-container"); const $container = $("#left_sidebar_scroll_container");
if ($stream_li.length !== 1) { if ($stream_li.length !== 1) {
blueslip.error("Invalid stream_li was passed in"); blueslip.error("Invalid stream_li was passed in");

View File

@ -222,7 +222,8 @@ export function initialize() {
delegate("body", { delegate("body", {
target: [ target: [
".recipient_bar_icon", ".recipient_bar_icon",
".sidebar-title", "#streams_header .sidebar-title",
"#userlist-title",
"#user_filter_icon", "#user_filter_icon",
"#scroll-to-bottom-button-clickable-area", "#scroll-to-bottom-button-clickable-area",
".code_external_link", ".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,
});
} }

View File

@ -1,6 +1,5 @@
import $ from "jquery"; import $ from "jquery";
import * as pm_list from "./pm_list";
import * as resize from "./resize"; import * as resize from "./resize";
import * as ui_util from "./ui_util"; import * as ui_util from "./ui_util";
import * as unread_ui from "./unread_ui"; import * as unread_ui from "./unread_ui";
@ -27,9 +26,8 @@ function remove($elem) {
$elem.removeClass("active-filter active-sub-filter"); $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_all_messages"));
remove($(".top_left_private_messages"));
remove($(".top_left_starred_messages")); remove($(".top_left_starred_messages"));
remove($(".top_left_mentions")); remove($(".top_left_mentions"));
remove($(".top_left_recent_topics")); remove($(".top_left_recent_topics"));
@ -73,11 +71,9 @@ export function handle_narrow_deactivated() {
export function narrow_to_recent_topics() { export function narrow_to_recent_topics() {
remove($(".top_left_all_messages")); remove($(".top_left_all_messages"));
remove($(".top_left_private_messages"));
remove($(".top_left_starred_messages")); remove($(".top_left_starred_messages"));
remove($(".top_left_mentions")); remove($(".top_left_mentions"));
$(".top_left_recent_topics").addClass("active-filter"); $(".top_left_recent_topics").addClass("active-filter");
pm_list.close();
setTimeout(() => { setTimeout(() => {
resize.resize_stream_filters_container(); resize.resize_stream_filters_container();
}, 0); }, 0);

View File

@ -331,7 +331,7 @@ export function zoom_in() {
active_widget.build(); 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; const spinner = true;
active_widget.build(spinner); active_widget.build(spinner);

View File

@ -59,6 +59,7 @@ import * as overlays from "./overlays";
import {page_params} from "./page_params"; import {page_params} from "./page_params";
import * as people from "./people"; import * as people from "./people";
import * as pm_conversations from "./pm_conversations"; import * as pm_conversations from "./pm_conversations";
import * as pm_list from "./pm_list";
import * as popover_menus from "./popover_menus"; import * as popover_menus from "./popover_menus";
import * as presence from "./presence"; import * as presence from "./presence";
import * as realm_logo from "./realm_logo"; import * as realm_logo from "./realm_logo";
@ -677,6 +678,7 @@ export function initialize_everything() {
unread_ui.initialize(); unread_ui.initialize();
activity.initialize(); activity.initialize();
emoji_picker.initialize(); emoji_picker.initialize();
pm_list.initialize();
topic_list.initialize(); topic_list.initialize();
topic_zoom.initialize(); topic_zoom.initialize();
drafts.initialize(); drafts.initialize();

View File

@ -144,7 +144,9 @@ body.dark-theme {
.column-right .right-sidebar, .column-right .right-sidebar,
#groups_overlay .right, #groups_overlay .right,
#subscription_overlay .right, #subscription_overlay .right,
#settings_page .right { #settings_page .right,
#streams_header,
.private_messages_container {
background-color: hsl(212, 28%, 18%); background-color: hsl(212, 28%, 18%);
} }
@ -225,6 +227,14 @@ body.dark-theme {
background-color: hsl(208, 17%, 29%); 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 /* 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. */ on a dark background, and don't change the dark labels dark either. */
.message_header:not(.dark_background) .message_header:not(.dark_background)

View File

@ -8,6 +8,8 @@ $left_col_size: 19px;
the above (and another 5px of padding not measured here) */ the above (and another 5px of padding not measured here) */
$topic_indent: calc($far_left_gutter_size + $left_col_size + 4px); $topic_indent: calc($far_left_gutter_size + $left_col_size + 4px);
$topic_resolve_width: 13px; $topic_resolve_width: 13px;
/* Space between section in the left sidebar. */
$sections_vertical_gutter: 8px;
#left-sidebar { #left-sidebar {
#user-list { #user-list {
@ -50,11 +52,6 @@ $topic_resolve_width: 13px;
} }
} }
.pm_left_col {
min-width: $left_col_size;
margin-left: 15px;
}
#stream_filters, #stream_filters,
#global_filters { #global_filters {
margin-right: 12px; margin-right: 12px;
@ -72,7 +69,7 @@ li.show-more-topics {
float: right; float: right;
opacity: 0.5; opacity: 0.5;
padding: 3px; padding: 3px;
margin-left: 7px; margin-left: 4px;
&:hover { &:hover {
opacity: 1; opacity: 1;
@ -89,7 +86,7 @@ li.show-more-topics {
} }
#streams_inline_icon { #streams_inline_icon {
margin-right: 10px; margin-right: 8px;
} }
.tooltip { .tooltip {
@ -106,6 +103,10 @@ li.show-more-topics {
li { li {
a { a {
padding: 1px 0; padding: 1px 0;
&:hover {
text-decoration: none;
}
} }
ul { ul {
@ -190,8 +191,8 @@ li.show-more-topics {
} }
} }
#private-container, #left_sidebar_scroll_container {
#stream-filters-container { outline: none;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
position: relative; position: relative;
@ -199,16 +200,111 @@ li.show-more-topics {
width: 100%; width: 100%;
} }
#stream-filters-container .simplebar-content-wrapper { .private_messages_container {
outline: none; 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 { .active_private_messages_section {
max-height: 210px; #private_messages_section,
#private_messages_list,
#hide_more_private_messages {
background-color: hsl(202, 56%, 91%);
}
/* Match the opacity for global-filters icons. */ #private_messages_section {
span.fa-group { border-radius: 4px 4px 0 0;
opacity: 0.7; }
#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 { #global_filters {
margin-bottom: 16px; margin-bottom: $sections_vertical_gutter;
.filter-icon { .filter-icon {
display: inline-block; display: inline-block;
@ -301,20 +397,12 @@ li.active-sub-filter {
margin-top: 1px !important; 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 { i {
opacity: 0.7; opacity: 0.7;
} }
} }
li.top_left_all_messages, li.top_left_all_messages,
.private_messages_header,
li.top_left_mentions, li.top_left_mentions,
li.top_left_starred_messages, li.top_left_starred_messages,
li.top_left_drafts, li.top_left_drafts,
@ -333,12 +421,6 @@ li.top_left_recent_topics {
padding-right: 10px; padding-right: 10px;
} }
.top_left_row,
.bottom_left_row,
.top_left_private_messages {
border-radius: 4px;
}
.conversation-partners { .conversation-partners {
line-height: 1.25; line-height: 1.25;
} }
@ -348,12 +430,6 @@ li.top_left_recent_topics {
font-size: 15px; 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_mentions i.fa-at,
.top_left_starred_messages i.fa-star { .top_left_starred_messages i.fa-star {
font-size: 13px; font-size: 13px;
@ -479,32 +555,11 @@ ul.topic-list {
font-weight: normal; 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 { li.topic-list-item {
position: relative; position: relative;
padding-right: 5px; padding-right: 5px;
} }
li.expanded_private_message {
position: relative;
padding-top: 1px;
padding-bottom: 1px;
a {
margin: 1px 0;
}
}
.show-all-streams { .show-all-streams {
a { a {
color: hsl(0, 0%, 20%); color: hsl(0, 0%, 20%);
@ -525,7 +580,7 @@ li.expanded_private_message {
} }
.pm-box { .pm-box {
margin-right: 20px; margin-right: 16px;
align-items: center; align-items: center;
.user_circle { .user_circle {
@ -548,6 +603,10 @@ li.expanded_private_message {
#topics_header { #topics_header {
display: none; display: none;
} }
.zoom-out-hide {
display: none;
}
} }
#topics_header { #topics_header {
@ -560,7 +619,7 @@ li.expanded_private_message {
text-transform: uppercase; text-transform: uppercase;
i { i {
margin: 0 5px 0 10px; margin: 0 6px 0 13px;
position: relative; position: relative;
top: 1px; top: 1px;
} }
@ -569,9 +628,12 @@ li.expanded_private_message {
#streams_header { #streams_header {
margin-right: 12px; margin-right: 12px;
padding-left: $far_left_gutter_size;
cursor: pointer; 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 { input {
padding-right: 20px; padding-right: 20px;
@ -643,7 +705,41 @@ li.expanded_private_message {
display: none; 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 { .zoom-in-hide {
display: none; 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;
}
} }

View File

@ -18,7 +18,7 @@ go beneath the header.
*/ */
$sidebar_top: calc($header_height + $header_padding_bottom); $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; $left_sidebar_width: 270px;
$right_sidebar_width: 250px; $right_sidebar_width: 250px;
@ -532,7 +532,7 @@ p.n-margin {
.column-left .left-sidebar { .column-left .left-sidebar {
width: $left_sidebar_width; width: $left_sidebar_width;
padding-left: 0; padding-left: $left_sidebar_collapse_widget_gutter;
} }
.column-right .right-sidebar { .column-right .right-sidebar {
@ -548,7 +548,9 @@ p.n-margin {
.column-middle, .column-middle,
#compose-content { #compose-content {
margin-right: $right_sidebar_width; margin-right: $right_sidebar_width;
margin-left: $left_sidebar_width; margin-left: calc(
$left_sidebar_width + $left_sidebar_collapse_widget_gutter
);
position: relative; position: relative;
} }

View File

@ -22,20 +22,6 @@
<span>{{t 'Recent conversations' }}</span> <span>{{t 'Recent conversations' }}</span>
</a> </a>
</li> </li>
<li class="top_left_private_messages hidden-for-spectators">
<div class="private_messages_header top_left_row" title="{{t 'Private messages' }} (P)">
<a href="#narrow/is/private">
<span class="filter-icon">
<i class="fa fa-envelope" aria-hidden="true"></i>
</span>
{{~!-- squash whitespace --~}}
<span>{{t 'Private messages' }}</span>
<span class="unread_count"></span>
</a>
</div>
<div id="private-container" class="scrolling_list" data-simplebar>
</div>
</li>
<li class="top_left_mentions top_left_row hidden-for-spectators" title="{{t 'Mentions' }}"> <li class="top_left_mentions top_left_row hidden-for-spectators" title="{{t 'Mentions' }}">
<a href="#narrow/is/mentioned"> <a href="#narrow/is/mentioned">
<span class="filter-icon"> <span class="filter-icon">
@ -69,6 +55,31 @@
<span class="arrow drafts-sidebar-menu-icon"><i class="zulip-icon zulip-icon-ellipsis-v-solid" aria-hidden="true"></i></span> <span class="arrow drafts-sidebar-menu-icon"><i class="zulip-icon zulip-icon-ellipsis-v-solid" aria-hidden="true"></i></span>
</li> </li>
</ul> </ul>
</div>
<div class="private_messages_container zoom-out hidden-for-spectators">
<div id="private_messages_section">
<div id="private_messages_section_header" class="zoom-out zoom-in-sticky">
<span id="pm_tooltip_container">
<i id="toggle_private_messages_section_icon" class="fa fa-sm fa-caret-down toggle_private_messages_section zoom-in-hide" aria-hidden="true"></i>
<h4 class="sidebar-title toggle_private_messages_section">{{t 'PRIVATE MESSAGES' }}</h4>
</span>
<span class="unread_count"></span>
<a id="show_all_private_messages" href="#narrow/is/private">
<i class="fa fa-align-right" aria-label="{{t 'All private messages' }}"></i>
</a>
</div>
</div>
<a class="zoom-out-hide" id="hide_more_private_messages">
<span> {{t 'back to streams' }}</span>
</a>
</div>
{{~!-- squash whitespace --~}}
<div id="left_sidebar_scroll_container" class="scrolling_list" data-simplebar>
<div class="private_messages_container zoom-out hidden-for-spectators">
<div id="private_messages_list"></div>
</div>
<div id="streams_list" class="zoom-out"> <div id="streams_list" class="zoom-out">
<div id="streams_header" class="zoom-in-hide"><h4 class="sidebar-title" data-tippy-content="{{t 'Filter streams' }} (q)">{{t 'STREAMS' }}</h4> <div id="streams_header" class="zoom-in-hide"><h4 class="sidebar-title" data-tippy-content="{{t 'Filter streams' }} (q)">{{t 'STREAMS' }}</h4>
<span class="tippy-zulip-tooltip streams_inline_icon_wrapper hidden-for-spectators" data-tippy-content="{{t 'Add streams' }}"> <span class="tippy-zulip-tooltip streams_inline_icon_wrapper hidden-for-spectators" data-tippy-content="{{t 'Add streams' }}">
@ -85,7 +96,7 @@
<div id="topics_header"> <div id="topics_header">
<a class="show-all-streams" tabindex="0"> <i class="fa fa-chevron-left" aria-hidden="true"></i>{{t 'Back to streams' }}</a> <a class="show-all-streams" tabindex="0"> <i class="fa fa-chevron-left" aria-hidden="true"></i>{{t 'Back to streams' }}</a>
</div> </div>
<div id="stream-filters-container" class="scrolling_list" data-simplebar> <div id="stream-filters-container">
<ul id="stream_filters" class="filters"></ul> <ul id="stream_filters" class="filters"></ul>
{{#unless is_guest }} {{#unless is_guest }}
<div id="subscribe-to-more-streams"></div> <div id="subscribe-to-more-streams"></div>

View File

@ -0,0 +1,8 @@
<li id="show_more_private_messages" class="pm-list-item bottom_left_row {{#unless more_conversations_unread_count}}zero-pm-unreads{{/unless}}">
<span>
<a class="pm-name" tabindex="0">{{t "more conversations" }}</a>
<span class="unread_count {{#unless more_conversations_unread_count}}zero_count{{/unless}}">
{{more_conversations_unread_count}}
</span>
</span>
</li>

View File

@ -1,4 +1,4 @@
<li class='{{#if is_active}}active-sub-filter{{/if}} {{#if is_zero}}zero-pm-unreads{{/if}} top_left_row expanded_private_message' data-user-ids-string='{{user_ids_string}}'> <li class='{{#if is_active}}active-sub-filter{{/if}} {{#if is_zero}}zero-pm-unreads{{/if}} pm-list-item bottom_left_row' data-user-ids-string='{{user_ids_string}}'>
<span class='pm-box' id='pm_user_status' data-user-ids-string='{{user_ids_string}}' data-is-group='{{is_group}}'> <span class='pm-box' id='pm_user_status' data-user-ids-string='{{user_ids_string}}' data-is-group='{{is_group}}'>
<div class="pm_left_col"> <div class="pm_left_col">
@ -18,4 +18,3 @@
</span> </span>
</span> </span>
</li> </li>

View File

@ -79,6 +79,9 @@ IGNORED_PHRASES = [
r"more topics", r"more topics",
# Used alone in a parenthetical where capitalized looks worse. # Used alone in a parenthetical where capitalized looks worse.
r"^deprecated$", r"^deprecated$",
# We want the similar text in the Private Messages section to have the same capitalization.
r"more conversations",
r"back to streams",
# Capital 'i' looks weird in reminders popover # Capital 'i' looks weird in reminders popover
r"in 1 hour", r"in 1 hour",
r"in 20 minutes", r"in 20 minutes",