mirror of https://github.com/zulip/zulip.git
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:
parent
15b9e9c7cc
commit
6f9e97921d
|
@ -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");
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 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 un_narrow(page);
|
||||
|
|
|
@ -64,6 +64,16 @@ async function navigate_to_subscriptions(page: Page): Promise<void> {
|
|||
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> {
|
||||
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<void> {
|
|||
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");
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,20 +22,6 @@
|
|||
<span>{{t 'Recent conversations' }}</span>
|
||||
</a>
|
||||
</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' }}">
|
||||
<a href="#narrow/is/mentioned">
|
||||
<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>
|
||||
</li>
|
||||
</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_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' }}">
|
||||
|
@ -85,7 +96,7 @@
|
|||
<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>
|
||||
</div>
|
||||
<div id="stream-filters-container" class="scrolling_list" data-simplebar>
|
||||
<div id="stream-filters-container">
|
||||
<ul id="stream_filters" class="filters"></ul>
|
||||
{{#unless is_guest }}
|
||||
<div id="subscribe-to-more-streams"></div>
|
||||
|
|
|
@ -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>
|
|
@ -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}}'>
|
||||
|
||||
<div class="pm_left_col">
|
||||
|
@ -18,4 +18,3 @@
|
|||
</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
|
|
|
@ -79,6 +79,9 @@ IGNORED_PHRASES = [
|
|||
r"more topics",
|
||||
# Used alone in a parenthetical where capitalized looks worse.
|
||||
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
|
||||
r"in 1 hour",
|
||||
r"in 20 minutes",
|
||||
|
|
Loading…
Reference in New Issue