user_card_popover: Migrate all user card popovers to Tippy.

This commit is contained in:
Daniil Fadeev
2023-09-21 00:09:44 +06:00
committed by Tim Abbott
parent 10400f15d6
commit 8de397b37f
9 changed files with 310 additions and 371 deletions

View File

@@ -18,7 +18,7 @@ async function open_set_user_status_modal(page: Page): Promise<void> {
{},
menu_icon_selector,
);
await page.waitForSelector(".user_popover", {visible: true});
await page.waitForSelector(".user_popover_email", {visible: true});
// We are using evaluate to click because it is very hard to detect if the user info popover has opened.
await page.evaluate(() =>
document.querySelector<HTMLAnchorElement>(".update_status_text")!.click(),

View File

@@ -376,18 +376,18 @@ function handle_popover_events(event_name) {
return true;
}
if (user_card_popover.is_message_user_card_open()) {
user_card_popover.user_card_popover_for_message_handle_keyboard(event_name);
if (user_card_popover.message_user_card.is_open()) {
user_card_popover.message_user_card.handle_keyboard(event_name);
return true;
}
if (user_card_popover.is_user_card_open()) {
user_card_popover.user_card_popover_handle_keyboard(event_name);
if (user_card_popover.user_card.is_open()) {
user_card_popover.user_card.handle_keyboard(event_name);
return true;
}
if (user_card_popover.user_sidebar_popped()) {
user_card_popover.user_sidebar_popover_handle_keyboard(event_name);
if (user_card_popover.user_sidebar.is_open()) {
user_card_popover.user_sidebar.handle_keyboard(event_name);
return true;
}
@@ -741,7 +741,7 @@ export function process_hotkey(e, hotkey) {
return false;
}
if (overlays.settings_open() && !user_card_popover.is_user_card_open()) {
if (overlays.settings_open() && !user_card_popover.user_card.is_open()) {
return false;
}

View File

@@ -162,9 +162,9 @@ export function any_active() {
popover_menus.any_active() ||
stream_popover.is_open() ||
user_group_popover.is_open() ||
user_card_popover.user_sidebar_popped() ||
user_card_popover.is_message_user_card_open() ||
user_card_popover.is_user_card_open() ||
user_card_popover.user_sidebar.is_open() ||
user_card_popover.message_user_card.is_open() ||
user_card_popover.user_card.is_open() ||
emoji_picker.is_open() ||
playground_links_popover.is_open() ||
$("[class^='column-'].expanded").length

View File

@@ -3,7 +3,6 @@ import {parseISO} from "date-fns";
import $ from "jquery";
import tippy from "tippy.js";
import render_no_arrow_popover from "../templates/no_arrow_popover.hbs";
import render_user_card_popover_content from "../templates/user_card_popover_content.hbs";
import render_user_card_popover_manage_menu from "../templates/user_card_popover_manage_menu.hbs";
import render_user_card_popover_title from "../templates/user_card_popover_title.hbs";
@@ -43,9 +42,6 @@ import {user_settings} from "./user_settings";
import * as user_status from "./user_status";
import * as user_status_ui from "./user_status_ui";
let $current_message_user_card_popover_elem;
let $current_user_card_popover_elem;
let current_user_sidebar_popover;
let current_user_sidebar_user_id;
class PopoverMenu {
@@ -82,9 +78,30 @@ class PopoverMenu {
}
export const manage_menu = new PopoverMenu();
export const user_sidebar = new PopoverMenu();
export const message_user_card = new PopoverMenu();
export const user_card = new PopoverMenu();
function get_popover_classname(popover) {
const popovers = {
user_sidebar: "user-sidebar-popover-root",
message_user_card: "message-user-card-popover-root",
user_card: "user-card-popover-root",
};
return popovers[popover];
}
user_sidebar.hide = function () {
PopoverMenu.prototype.hide.call(this);
current_user_sidebar_user_id = undefined;
};
const user_card_popovers = {
manage_menu,
user_sidebar,
message_user_card,
user_card,
};
export function any_active() {
@@ -101,14 +118,11 @@ export function hide_all_instances() {
export function hide_all_user_card_popovers() {
hide_all_instances();
hide_message_user_card_popover();
hide_user_sidebar_popover();
hide_user_card_popover();
}
export function clear_for_testing() {
$current_message_user_card_popover_elem = undefined;
$current_user_card_popover_elem = undefined;
message_user_card.instance = undefined;
user_card.instance = undefined;
manage_menu.instance = undefined;
}
@@ -125,53 +139,15 @@ function clipboard_enable(arg) {
// Functions related to user card popover.
export function toggle_user_card_popover(element, user) {
const $last_popover_elem = $current_user_card_popover_elem;
hide_all();
if ($last_popover_elem !== undefined && $last_popover_elem.get()[0] === element) {
return;
}
const $elt = $(element);
render_user_card_popover(
user,
$elt,
$(element),
false,
false,
"compose_private_message",
"user-card-popover",
"user_card",
"right",
);
$current_user_card_popover_elem = $elt;
}
export function hide_user_card_popover() {
if (is_user_card_open()) {
$current_user_card_popover_elem.popover("destroy");
$current_user_card_popover_elem = undefined;
}
}
export function is_user_card_open() {
return $current_user_card_popover_elem !== undefined;
}
export function user_card_popover_handle_keyboard(key) {
const $items = get_user_card_popover_items();
popover_items_handle_keyboard(key, $items);
}
function get_user_card_popover_items() {
const $popover_elt = $("div.user-card-popover");
if (!$current_user_card_popover_elem || !$popover_elt.length) {
blueslip.error("Trying to get menu items when action popover is closed.");
return undefined;
}
if ($popover_elt.length >= 2) {
blueslip.error("More than one user info popovers cannot be opened at same time.");
return undefined;
}
return $("li:not(.divider):visible a", $popover_elt);
}
function get_user_card_popover_data(
@@ -265,6 +241,8 @@ function render_user_card_popover(
private_msg_class,
template_class,
popover_placement,
show_as_overlay,
on_mount,
) {
const args = get_user_card_popover_data(
user,
@@ -273,38 +251,34 @@ function render_user_card_popover(
private_msg_class,
);
const $popover_content = $(render_user_card_popover_content(args));
$popover_element.popover({
content: $popover_content.get(0),
fixed: true,
popover_menus.toggle_popover_menu(
$popover_element[0],
{
placement: popover_placement,
template: render_no_arrow_popover({class: template_class}),
title: render_user_card_popover_title({
arrow: false,
onCreate(instance) {
user_card_popovers[template_class].instance = instance;
instance.setContent(ui_util.parse_html(render_user_card_popover_content(args)));
const $popover = $(instance.popper);
const $popover_title = $popover.find(".popover-title");
$popover.addClass(get_popover_classname(template_class));
$popover_title.append(
render_user_card_popover_title({
// See the load_medium_avatar comment for important background.
user_avatar: people.small_avatar_url_for_person(user),
user_is_guest: user.is_guest,
}),
html: true,
trigger: "manual",
top_offset: $("#userlist-title").get_offset_to_window().top + 15,
fix_positions: true,
});
$popover_element.popover("show");
init_email_clipboard();
init_email_tooltip(user);
const $user_name_element = $popover_content.find(".user_full_name");
const $bot_owner_element = $popover_content.find(".bot_owner");
if ($user_name_element.prop("clientWidth") < $user_name_element.prop("scrollWidth")) {
$user_name_element.addClass("tippy-zulip-tooltip");
);
},
onHidden() {
user_card_popovers[template_class].hide();
},
onMount(instance) {
if (on_mount) {
on_mount(instance);
}
if (
args.bot_owner &&
$bot_owner_element.prop("clientWidth") < $bot_owner_element.prop("scrollWidth")
) {
$bot_owner_element.addClass("tippy-zulip-tooltip");
}
// Note: We pass the normal-size avatar in initial rendering, and
// then query the server to replace it with the medium-size
// avatar. The purpose of this double-fetch approach is to take
@@ -313,6 +287,28 @@ function render_user_card_popover(
// avatar rather than a blank area during the network delay for
// fetching the medium-size one.
load_medium_avatar(user, $(".popover-avatar"));
init_email_clipboard();
init_email_tooltip(user);
const $popover = $(instance.popper);
const $user_name_element = $popover.find(".user_full_name");
const $bot_owner_element = $popover.find(".bot_owner");
if (
$user_name_element.prop("clientWidth") < $user_name_element.prop("scrollWidth")
) {
$user_name_element.addClass("tippy-zulip-tooltip");
}
if (
args.bot_owner &&
$bot_owner_element.prop("clientWidth") < $bot_owner_element.prop("scrollWidth")
) {
$bot_owner_element.addClass("tippy-zulip-tooltip");
}
},
},
{show_as_overlay},
);
}
function copy_email_handler(e) {
@@ -438,17 +434,10 @@ export function get_user_card_popover_manage_menu_items() {
// element is the target element to pop off of
// user is the user whose profile to show
// message is the message containing it, which should be selected
function toggle_user_card_popover_for_message(element, user, message) {
const $last_popover_elem = $current_message_user_card_popover_elem;
hide_all();
if ($last_popover_elem !== undefined && $last_popover_elem.get()[0] === element) {
// We want it to be the case that a user can dismiss a popover
// by clicking on the same element that caused the popover.
return;
}
function toggle_user_card_popover_for_message(element, user, message, on_mount) {
message_lists.current.select_id(message.id);
const $elt = $(element);
if ($elt.data("popover") === undefined) {
if (!message_user_card.is_open()) {
if (user === undefined) {
// This is never supposed to happen, not even for deactivated
// users, so we'll need to debug this error if it occurs.
@@ -466,17 +455,25 @@ function toggle_user_card_popover_for_message(element, user, message) {
is_sender_popover,
true,
"respond_personal_button",
"message-user-card-popover",
"message_user_card",
"right",
undefined,
on_mount,
);
$current_message_user_card_popover_elem = $elt;
}
}
// This function serves as the entry point for toggling
// the user card popover via keyboard shortcut.
export function toggle_sender_info() {
if (message_user_card.is_open()) {
// We need to call the hide method here because
// the event wasn't triggered by the mouse.
// The Tippy unTrigger event wasn't called,
// so we have to manually hide the popover.
message_user_card.hide();
return;
}
const $message = $(".selected_message");
let $sender = $message.find(".message-avatar");
if ($sender.length === 0) {
@@ -487,10 +484,11 @@ export function toggle_sender_info() {
const message = message_lists.current.get(rows.id($message));
const user = people.get_by_user_id(message.sender_id);
toggle_user_card_popover_for_message($sender[0], user, message);
if ($current_message_user_card_popover_elem && !page_params.is_spectator) {
toggle_user_card_popover_for_message($sender[0], user, message, () => {
if (!page_params.is_spectator) {
focus_user_card_popover_item();
}
});
}
function focus_user_card_popover_item() {
@@ -505,35 +503,19 @@ function focus_user_card_popover_item() {
}
}
export function is_message_user_card_open() {
return $current_message_user_card_popover_elem !== undefined;
}
export function hide_message_user_card_popover() {
if (is_message_user_card_open()) {
$current_message_user_card_popover_elem.popover("destroy");
$current_message_user_card_popover_elem = undefined;
}
}
export function user_card_popover_for_message_handle_keyboard(key) {
const $items = get_user_card_popover_for_message_items();
popover_items_handle_keyboard(key, $items);
}
function get_user_card_popover_for_message_items() {
if (!$current_message_user_card_popover_elem) {
if (!message_user_card.is_open()) {
blueslip.error("Trying to get menu items when message user card popover is closed.");
return undefined;
}
const popover_data = $current_message_user_card_popover_elem.data("popover");
if (!popover_data) {
const $popover = $(message_user_card.instance.popper);
if (!$popover) {
blueslip.error("Cannot find popover data for message user card menu.");
return undefined;
}
return $("li:not(.divider):visible a", popover_data.$tip);
return $("li:not(.divider):visible a", $popover);
}
// Functions related to the user card popover in the user sidebar.
@@ -560,48 +542,20 @@ function toggle_sidebar_user_card_popover($target) {
false,
false,
"compose_private_message",
"user_popover",
"user_sidebar",
"left",
undefined,
(instance) => {
/* See comment in get_props_for_popover_centering for explanation of this. */
$(instance.popper).find(".tippy-box").addClass("show-when-reference-hidden");
},
);
current_user_sidebar_user_id = user.user_id;
current_user_sidebar_popover = $target.data("popover");
}
export function user_sidebar_popped() {
return current_user_sidebar_popover !== undefined;
}
export function hide_user_sidebar_popover() {
if (user_sidebar_popped()) {
// this hide_* method looks different from all the others since
// the presence list may be redrawn. Due to funkiness with jQuery's .data()
// this would confuse $.popover("destroy"), which looks at the .data() attached
// to a certain element. We thus save off the .data("popover") in the
// toggle_user_sidebar_popover and inject it here before calling destroy.
$("#user_presences").data("popover", current_user_sidebar_popover);
$("#user_presences").popover("destroy");
current_user_sidebar_user_id = undefined;
current_user_sidebar_popover = undefined;
}
}
function get_user_sidebar_popover_items() {
if (!current_user_sidebar_popover) {
blueslip.error("Trying to get menu items when user sidebar popover is closed.");
return undefined;
}
return $("li:not(.divider):visible a", current_user_sidebar_popover.$tip);
}
export function user_sidebar_popover_handle_keyboard(key) {
const $items = get_user_sidebar_popover_items();
popover_items_handle_keyboard(key, $items);
}
function register_click_handlers() {
$("#main_div").on("click", ".sender_name, .message-avatar", function (e) {
$("#main_div").on("click", ".sender_name, .inline_profile_picture", function (e) {
const $row = $(this).closest(".message_row");
e.stopPropagation();
const message = message_lists.current.get(rows.id($row));
@@ -696,7 +650,7 @@ function register_click_handlers() {
e.stopPropagation();
e.preventDefault();
});
$("body").on("click", ".user_popover .mention_user", (e) => {
$("body").on("click", ".user-card-popover-root .mention_user", (e) => {
if (!compose_state.composing()) {
compose_actions.start("stream", {trigger: "sidebar user actions"});
}
@@ -704,13 +658,13 @@ function register_click_handlers() {
const name = people.get_by_user_id(user_id).full_name;
const mention = people.get_mention_syntax(name, user_id);
compose_ui.insert_syntax_and_focus(mention);
hide_user_sidebar_popover();
user_sidebar.hide();
popovers.hide_userlist_sidebar();
e.stopPropagation();
e.preventDefault();
});
$("body").on("click", ".message-user-card-popover .mention_user", (e) => {
$("body").on("click", ".message-user-card-popover-root .mention_user", (e) => {
if (!compose_state.composing()) {
compose_actions.respond_to_message({trigger: "user sidebar popover"});
}
@@ -718,7 +672,7 @@ function register_click_handlers() {
const name = people.get_by_user_id(user_id).full_name;
const mention = people.get_mention_syntax(name, user_id);
compose_ui.insert_syntax_and_focus(mention);
hide_message_user_card_popover();
message_user_card.hide();
e.stopPropagation();
e.preventDefault();
});

View File

@@ -283,11 +283,11 @@ ul {
}
}
/* Important note: The user info popover user_popover class is applied
to user info popovers ONLY when they are opened from the right
sidebar; otherwise, it will have the user-card-popover class
instead. */
.user_popover {
/* Important note: The user info popover user-sidebar-popover-root
class is applied to user info popovers ONLY when they are opened
from the right sidebar; otherwise, it will have the
user-card-popover-root class instead. */
.user-sidebar-popover-root {
width: 240px;
margin: -14px;
@@ -304,8 +304,8 @@ ul {
}
.group-info-popover,
.message-user-card-popover,
.user-card-popover {
.message-user-card-popover-root,
.user-card-popover-root {
width: 240px;
padding: 0;
@@ -670,27 +670,6 @@ ul {
}
@media (width < $md_min) {
.user-card-popover {
display: flex !important;
justify-content: center;
align-items: center;
/* these are to override JS embedded inline styles. */
top: 0 !important;
left: 0 !important;
margin: 0 !important;
width: 100%;
height: 100%;
background-color: hsl(0deg 0% 0% / 70%) !important;
border-radius: 0;
border: none;
.popover-inner {
background-color: hsl(0deg 0% 100%);
}
}
.colorpicker-popover {
display: flex !important;
justify-content: center;
@@ -788,7 +767,10 @@ ul {
.giphy-popover,
.emoji-popover-root,
.user-group-popover-root {
.user-group-popover-root,
.user-sidebar-popover-root,
.message-user-card-popover-root,
.user-card-popover-root {
.tippy-content {
/* We remove the default padding from this container
as it is not necessary for the Giphy popover. */
@@ -798,6 +780,11 @@ ul {
we can ignore the colors applied from `tippy-box`. */
color: var(--color-text-default);
}
& .popover-content ul.nav {
/* TODO: Clean this logic up after drop Bootstrap styling */
margin: 0;
}
}
.user-group-popover-root {

View File

@@ -1,8 +0,0 @@
<div class="popover {{class}}">
<div class="popover-inner">
<div class="popover-title"></div>
<div class="popover-content">
<div></div>
</div>
</div>
</div>

View File

@@ -1,5 +1,7 @@
{{! Contents of the user card popover }}
<ul class="nav nav-list user-card-popover-actions" id="user_card_popover" data-user-id="{{user_id}}">
<div class="popover-title"></div>
<div class="popover-content">
<ul class="nav nav-list user-card-popover-actions" id="user_card_popover" data-user-id="{{user_id}}">
<li class="popover_user_name_row">
<b class="user_full_name" data-tippy-content="{{user_full_name}}">{{user_full_name}}</b>
{{#if is_active }}
@@ -169,4 +171,5 @@
</a>
</li>
{{/unless}}
</ul>
</ul>
</div>

View File

@@ -66,12 +66,18 @@ const overlays = mock_esm("../src/overlays", {
is_overlay_or_modal_open: () => overlays.is_modal_open() || overlays.is_active(),
});
const popovers = mock_esm("../src/user_card_popover", {
is_message_user_card_open: () => false,
user_sidebar_popped: () => false,
is_user_card_open: () => false,
manage_menu: {
is_open: () => false,
},
user_sidebar: {
is_open: () => false,
},
message_user_card: {
is_open: () => false,
},
user_card: {
is_open: () => false,
},
});
const popover_menus = mock_esm("../src/popover_menus", {
get_visible_instance: () => undefined,

View File

@@ -163,9 +163,6 @@
center--but this patch makes the popover more
likely to be usable. (If the screen is super
small, obviously we can't fit it completely.)
If you use this fix_positions option, you want
to also use the "no_arrow_popover" template.
*/
if (top < 0) {
top = 0;