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, 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. // We are using evaluate to click because it is very hard to detect if the user info popover has opened.
await page.evaluate(() => await page.evaluate(() =>
document.querySelector<HTMLAnchorElement>(".update_status_text")!.click(), document.querySelector<HTMLAnchorElement>(".update_status_text")!.click(),

View File

@@ -376,18 +376,18 @@ function handle_popover_events(event_name) {
return true; return true;
} }
if (user_card_popover.is_message_user_card_open()) { if (user_card_popover.message_user_card.is_open()) {
user_card_popover.user_card_popover_for_message_handle_keyboard(event_name); user_card_popover.message_user_card.handle_keyboard(event_name);
return true; return true;
} }
if (user_card_popover.is_user_card_open()) { if (user_card_popover.user_card.is_open()) {
user_card_popover.user_card_popover_handle_keyboard(event_name); user_card_popover.user_card.handle_keyboard(event_name);
return true; return true;
} }
if (user_card_popover.user_sidebar_popped()) { if (user_card_popover.user_sidebar.is_open()) {
user_card_popover.user_sidebar_popover_handle_keyboard(event_name); user_card_popover.user_sidebar.handle_keyboard(event_name);
return true; return true;
} }
@@ -741,7 +741,7 @@ export function process_hotkey(e, hotkey) {
return false; 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; return false;
} }

View File

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

View File

@@ -3,7 +3,6 @@ import {parseISO} from "date-fns";
import $ from "jquery"; import $ from "jquery";
import tippy from "tippy.js"; 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_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_manage_menu from "../templates/user_card_popover_manage_menu.hbs";
import render_user_card_popover_title from "../templates/user_card_popover_title.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 from "./user_status";
import * as user_status_ui from "./user_status_ui"; 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; let current_user_sidebar_user_id;
class PopoverMenu { class PopoverMenu {
@@ -82,9 +78,30 @@ class PopoverMenu {
} }
export const manage_menu = new 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 = { const user_card_popovers = {
manage_menu, manage_menu,
user_sidebar,
message_user_card,
user_card,
}; };
export function any_active() { export function any_active() {
@@ -101,14 +118,11 @@ export function hide_all_instances() {
export function hide_all_user_card_popovers() { export function hide_all_user_card_popovers() {
hide_all_instances(); hide_all_instances();
hide_message_user_card_popover();
hide_user_sidebar_popover();
hide_user_card_popover();
} }
export function clear_for_testing() { export function clear_for_testing() {
$current_message_user_card_popover_elem = undefined; message_user_card.instance = undefined;
$current_user_card_popover_elem = undefined; user_card.instance = undefined;
manage_menu.instance = undefined; manage_menu.instance = undefined;
} }
@@ -125,53 +139,15 @@ function clipboard_enable(arg) {
// Functions related to user card popover. // Functions related to user card popover.
export function toggle_user_card_popover(element, user) { 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( render_user_card_popover(
user, user,
$elt, $(element),
false, false,
false, false,
"compose_private_message", "compose_private_message",
"user-card-popover", "user_card",
"right", "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( function get_user_card_popover_data(
@@ -265,6 +241,8 @@ function render_user_card_popover(
private_msg_class, private_msg_class,
template_class, template_class,
popover_placement, popover_placement,
show_as_overlay,
on_mount,
) { ) {
const args = get_user_card_popover_data( const args = get_user_card_popover_data(
user, user,
@@ -273,46 +251,64 @@ function render_user_card_popover(
private_msg_class, private_msg_class,
); );
const $popover_content = $(render_user_card_popover_content(args)); popover_menus.toggle_popover_menu(
$popover_element.popover({ $popover_element[0],
content: $popover_content.get(0), {
fixed: true, placement: popover_placement,
placement: popover_placement, arrow: false,
template: render_no_arrow_popover({class: template_class}), onCreate(instance) {
title: render_user_card_popover_title({ user_card_popovers[template_class].instance = instance;
// See the load_medium_avatar comment for important background. instance.setContent(ui_util.parse_html(render_user_card_popover_content(args)));
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(); const $popover = $(instance.popper);
init_email_tooltip(user); const $popover_title = $popover.find(".popover-title");
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");
}
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 $popover.addClass(get_popover_classname(template_class));
// then query the server to replace it with the medium-size $popover_title.append(
// avatar. The purpose of this double-fetch approach is to take render_user_card_popover_title({
// advantage of the fact that the browser should already have the // See the load_medium_avatar comment for important background.
// low-resolution image cached and thus display a low-resolution user_avatar: people.small_avatar_url_for_person(user),
// avatar rather than a blank area during the network delay for user_is_guest: user.is_guest,
// fetching the medium-size one. }),
load_medium_avatar(user, $(".popover-avatar")); );
},
onHidden() {
user_card_popovers[template_class].hide();
},
onMount(instance) {
if (on_mount) {
on_mount(instance);
}
// 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
// advantage of the fact that the browser should already have the
// low-resolution image cached and thus display a low-resolution
// 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) { 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 // element is the target element to pop off of
// user is the user whose profile to show // user is the user whose profile to show
// message is the message containing it, which should be selected // message is the message containing it, which should be selected
function toggle_user_card_popover_for_message(element, user, message) { function toggle_user_card_popover_for_message(element, user, message, on_mount) {
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;
}
message_lists.current.select_id(message.id); message_lists.current.select_id(message.id);
const $elt = $(element); const $elt = $(element);
if ($elt.data("popover") === undefined) { if (!message_user_card.is_open()) {
if (user === undefined) { if (user === undefined) {
// This is never supposed to happen, not even for deactivated // This is never supposed to happen, not even for deactivated
// users, so we'll need to debug this error if it occurs. // 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, is_sender_popover,
true, true,
"respond_personal_button", "respond_personal_button",
"message-user-card-popover", "message_user_card",
"right", "right",
undefined,
on_mount,
); );
$current_message_user_card_popover_elem = $elt;
} }
} }
// This function serves as the entry point for toggling // This function serves as the entry point for toggling
// the user card popover via keyboard shortcut. // the user card popover via keyboard shortcut.
export function toggle_sender_info() { 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"); const $message = $(".selected_message");
let $sender = $message.find(".message-avatar"); let $sender = $message.find(".message-avatar");
if ($sender.length === 0) { if ($sender.length === 0) {
@@ -487,10 +484,11 @@ export function toggle_sender_info() {
const message = message_lists.current.get(rows.id($message)); const message = message_lists.current.get(rows.id($message));
const user = people.get_by_user_id(message.sender_id); const user = people.get_by_user_id(message.sender_id);
toggle_user_card_popover_for_message($sender[0], user, message); toggle_user_card_popover_for_message($sender[0], user, message, () => {
if ($current_message_user_card_popover_elem && !page_params.is_spectator) { if (!page_params.is_spectator) {
focus_user_card_popover_item(); focus_user_card_popover_item();
} }
});
} }
function 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() { 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."); blueslip.error("Trying to get menu items when message user card popover is closed.");
return undefined; return undefined;
} }
const popover_data = $current_message_user_card_popover_elem.data("popover"); const $popover = $(message_user_card.instance.popper);
if (!popover_data) { if (!$popover) {
blueslip.error("Cannot find popover data for message user card menu."); blueslip.error("Cannot find popover data for message user card menu.");
return undefined; 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. // Functions related to the user card popover in the user sidebar.
@@ -560,48 +542,20 @@ function toggle_sidebar_user_card_popover($target) {
false, false,
false, false,
"compose_private_message", "compose_private_message",
"user_popover", "user_sidebar",
"left", "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_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() { 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"); const $row = $(this).closest(".message_row");
e.stopPropagation(); e.stopPropagation();
const message = message_lists.current.get(rows.id($row)); const message = message_lists.current.get(rows.id($row));
@@ -696,7 +650,7 @@ function register_click_handlers() {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
}); });
$("body").on("click", ".user_popover .mention_user", (e) => { $("body").on("click", ".user-card-popover-root .mention_user", (e) => {
if (!compose_state.composing()) { if (!compose_state.composing()) {
compose_actions.start("stream", {trigger: "sidebar user actions"}); 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 name = people.get_by_user_id(user_id).full_name;
const mention = people.get_mention_syntax(name, user_id); const mention = people.get_mention_syntax(name, user_id);
compose_ui.insert_syntax_and_focus(mention); compose_ui.insert_syntax_and_focus(mention);
hide_user_sidebar_popover(); user_sidebar.hide();
popovers.hide_userlist_sidebar(); popovers.hide_userlist_sidebar();
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); 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()) { if (!compose_state.composing()) {
compose_actions.respond_to_message({trigger: "user sidebar popover"}); 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 name = people.get_by_user_id(user_id).full_name;
const mention = people.get_mention_syntax(name, user_id); const mention = people.get_mention_syntax(name, user_id);
compose_ui.insert_syntax_and_focus(mention); compose_ui.insert_syntax_and_focus(mention);
hide_message_user_card_popover(); message_user_card.hide();
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
}); });

View File

@@ -283,11 +283,11 @@ ul {
} }
} }
/* Important note: The user info popover user_popover class is applied /* Important note: The user info popover user-sidebar-popover-root
to user info popovers ONLY when they are opened from the right class is applied to user info popovers ONLY when they are opened
sidebar; otherwise, it will have the user-card-popover class from the right sidebar; otherwise, it will have the
instead. */ user-card-popover-root class instead. */
.user_popover { .user-sidebar-popover-root {
width: 240px; width: 240px;
margin: -14px; margin: -14px;
@@ -304,8 +304,8 @@ ul {
} }
.group-info-popover, .group-info-popover,
.message-user-card-popover, .message-user-card-popover-root,
.user-card-popover { .user-card-popover-root {
width: 240px; width: 240px;
padding: 0; padding: 0;
@@ -670,27 +670,6 @@ ul {
} }
@media (width < $md_min) { @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 { .colorpicker-popover {
display: flex !important; display: flex !important;
justify-content: center; justify-content: center;
@@ -788,7 +767,10 @@ ul {
.giphy-popover, .giphy-popover,
.emoji-popover-root, .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 { .tippy-content {
/* We remove the default padding from this container /* We remove the default padding from this container
as it is not necessary for the Giphy popover. */ as it is not necessary for the Giphy popover. */
@@ -798,6 +780,11 @@ ul {
we can ignore the colors applied from `tippy-box`. */ we can ignore the colors applied from `tippy-box`. */
color: var(--color-text-default); color: var(--color-text-default);
} }
& .popover-content ul.nav {
/* TODO: Clean this logic up after drop Bootstrap styling */
margin: 0;
}
} }
.user-group-popover-root { .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,172 +1,175 @@
{{! Contents of the user card popover }} {{! 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>
<li class="popover_user_name_row"> <div class="popover-content">
<b class="user_full_name" data-tippy-content="{{user_full_name}}">{{user_full_name}}</b> <ul class="nav nav-list user-card-popover-actions" id="user_card_popover" data-user-id="{{user_id}}">
{{#if is_active }} <li class="popover_user_name_row">
{{#if is_bot}} <b class="user_full_name" data-tippy-content="{{user_full_name}}">{{user_full_name}}</b>
<i class="zulip-icon zulip-icon-bot" aria-hidden="true"></i> {{#if is_active }}
{{else}} {{#if is_bot}}
<span class="{{user_circle_class}} user_circle popover_user_presence tippy-zulip-tooltip hidden-for-spectators" data-tippy-content="{{user_last_seen_time_status}}"></span> <i class="zulip-icon zulip-icon-bot" aria-hidden="true"></i>
{{else}}
<span class="{{user_circle_class}} user_circle popover_user_presence tippy-zulip-tooltip hidden-for-spectators" data-tippy-content="{{user_last_seen_time_status}}"></span>
{{/if}}
{{/if}} {{/if}}
{{/if}} {{#if show_manage_menu }}
{{#if show_manage_menu }} <span class="user-card-popover-action-buttons">
<span class="user-card-popover-action-buttons"> <a class="user-card-popover-manage-menu-btn" role="button" tabindex="0" aria-haspopup="true">
<a class="user-card-popover-manage-menu-btn" role="button" tabindex="0" aria-haspopup="true"> <i class="popover_action_icon zulip-icon zulip-icon-more-vertical" aria-hidden="true"></i>
<i class="popover_action_icon zulip-icon zulip-icon-more-vertical" aria-hidden="true"></i> </a>
</a>
</span>
{{/if}}
</li>
{{#if is_active }}
{{#if user_email}}
{{!-- This div is added to enable interactivity for tooltip - https://atomiks.github.io/tippyjs/v5/accessibility/#interactivity --}}
<div>
<li class="user_popover_email">
<i class="fa fa-clone hide_copy_icon" data-clipboard-text="{{ user_email }}"></i>
{{ user_email }}
</li>
</div>
{{/if}}
{{else}}
<li class="user_popover_email half-opacity italic">{{#if is_bot}}{{t "This bot has been deactivated." }}{{else}}{{t "This user has been deactivated." }}{{/if}}</li>
{{/if}}
{{#if is_bot}}
{{#if bot_owner}}
<li class="bot_owner" data-tippy-content="{{ bot_owner.full_name }}">{{t "Bot owner" }}:
<span class="bot-owner-name view_user_profile" data-user-id='{{ bot_owner.user_id }}'>
{{bot_owner.full_name}}
</span>
</li>
{{else if is_system_bot}}
<li>{{t "System bot" }}</li>
{{else}}
<li>{{t "Bot" }}</li>
{{/if}}
{{else}}
<li>{{ user_type }}</li>
{{/if}}
{{!-- Display selected custom profile fields in this popover. --}}
{{> user_custom_profile_fields profile_fields=display_profile_fields for_user_card_popover=true}}
<li class="only-visible-for-spectators">{{t "Joined {date_joined}" }}</li>
{{#if (or (and user_time is_active) is_me status_content_available) }}
<hr />
{{/if}}
{{#if (and user_time is_active)}}
<li class="hidden-for-spectators local_time">{{t "{user_time} local time" }}</li>
{{/if}}
{{#if status_content_available}}
<li class="user-card-status-text">
<span id="status_message">
{{#if status_emoji_info}}
{{#if status_emoji_info.emoji_alt_code}}
<div class="emoji_alt_code">&nbsp;:{{status_emoji_info.emoji_name}}:</div>
{{else if status_emoji_info.url}}
<img src="{{status_emoji_info.url}}" class="emoji status-emoji" data-tippy-content=":{{status_emoji_info.emoji_name}}:" />
{{else}}
<div class="emoji status-emoji emoji-{{status_emoji_info.emoji_code}}" data-tippy-content=":{{status_emoji_info.emoji_name}}:" ></div>
{{/if}}
{{/if}}
<span class="status_text">
{{status_text}}
{{#if is_me}}
<span class="clear_status_button">(<a tabindex="0" class="clear_status">{{t "clear" }}</a>)</span>
{{/if}}
</span>
</span> </span>
{{/if}}
</li> </li>
{{/if}}
{{#if is_me}}
<li> {{#if is_active }}
<a tabindex="0" class="update_status_text"> {{#if user_email}}
<i class="fa fa-comments" aria-hidden="true"></i> {{!-- This div is added to enable interactivity for tooltip - https://atomiks.github.io/tippyjs/v5/accessibility/#interactivity --}}
{{#if status_text}} <div>
{{t "Edit status" }} <li class="user_popover_email">
{{else}} <i class="fa fa-clone hide_copy_icon" data-clipboard-text="{{ user_email }}"></i>
{{t "Set a status" }} {{ user_email }}
{{/if}} </li>
</a> </div>
</li> {{/if}}
{{#if invisible_mode}}
<li>
<a tabindex="0" class="invisible_mode_turn_off">
<i class="fa fa-circle-o" aria-hidden="true"></i> {{t "Turn off invisible mode" }}
</a>
</li>
{{else}} {{else}}
<li> <li class="user_popover_email half-opacity italic">{{#if is_bot}}{{t "This bot has been deactivated." }}{{else}}{{t "This user has been deactivated." }}{{/if}}</li>
<a tabindex="0" class="invisible_mode_turn_on">
<i class="fa fa-circle-o" aria-hidden="true"></i> {{t "Go invisible" }}
</a>
</li>
{{/if}} {{/if}}
{{/if}}
{{#unless spectator_view}} {{#if is_bot}}
<hr /> {{#if bot_owner}}
<li> <li class="bot_owner" data-tippy-content="{{ bot_owner.full_name }}">{{t "Bot owner" }}:
<a tabindex="0" class="view_full_user_profile"> <span class="bot-owner-name view_user_profile" data-user-id='{{ bot_owner.user_id }}'>
{{#if is_me}} {{bot_owner.full_name}}
<i class="fa fa-user" aria-hidden="true"></i> {{t "View your profile" }} </span>
{{else}} </li>
<i class="fa fa-user" aria-hidden="true"></i> {{t "View profile" }} {{else if is_system_bot}}
{{/if}} <li>{{t "System bot" }}</li>
</a> {{else}}
</li> <li>{{t "Bot" }}</li>
{{#if can_send_private_message}} {{/if}}
{{else}}
<li>{{ user_type }}</li>
{{/if}}
{{!-- Display selected custom profile fields in this popover. --}}
{{> user_custom_profile_fields profile_fields=display_profile_fields for_user_card_popover=true}}
<li class="only-visible-for-spectators">{{t "Joined {date_joined}" }}</li>
{{#if (or (and user_time is_active) is_me status_content_available) }}
<hr />
{{/if}}
{{#if (and user_time is_active)}}
<li class="hidden-for-spectators local_time">{{t "{user_time} local time" }}</li>
{{/if}}
{{#if status_content_available}}
<li class="user-card-status-text">
<span id="status_message">
{{#if status_emoji_info}}
{{#if status_emoji_info.emoji_alt_code}}
<div class="emoji_alt_code">&nbsp;:{{status_emoji_info.emoji_name}}:</div>
{{else if status_emoji_info.url}}
<img src="{{status_emoji_info.url}}" class="emoji status-emoji" data-tippy-content=":{{status_emoji_info.emoji_name}}:" />
{{else}}
<div class="emoji status-emoji emoji-{{status_emoji_info.emoji_code}}" data-tippy-content=":{{status_emoji_info.emoji_name}}:" ></div>
{{/if}}
{{/if}}
<span class="status_text">
{{status_text}}
{{#if is_me}}
<span class="clear_status_button">(<a tabindex="0" class="clear_status">{{t "clear" }}</a>)</span>
{{/if}}
</span>
</span>
</li>
{{/if}}
{{#if is_me}}
<li> <li>
<a tabindex="0" class="{{ private_message_class }}"> <a tabindex="0" class="update_status_text">
<i class="fa fa-comment" aria-hidden="true"></i> {{t "Send direct message" }} {{#if is_sender_popover}}<span class="hotkey-hint">(R)</span>{{/if}} <i class="fa fa-comments" aria-hidden="true"></i>
{{#if status_text}}
{{t "Edit status" }}
{{else}}
{{t "Set a status" }}
{{/if}}
</a>
</li>
{{#if invisible_mode}}
<li>
<a tabindex="0" class="invisible_mode_turn_off">
<i class="fa fa-circle-o" aria-hidden="true"></i> {{t "Turn off invisible mode" }}
</a> </a>
</li> </li>
{{/if}}
{{#unless is_me}}
<li>
{{#if has_message_context}}
<a tabindex="0" class="mention_user">
<i class="fa fa-at" aria-hidden="true"></i>
{{#if is_bot}}{{t "Reply mentioning bot" }}{{else}}{{t "Reply mentioning user" }}{{/if}}
{{#if is_sender_popover}}<span class="hotkey-hint">(@)</span>{{/if}}
</a>
{{else}} {{else}}
<a tabindex="0" class="copy_mention_syntax" data-clipboard-text="{{ user_mention_syntax }}"> <li>
<i class="fa fa-at" aria-hidden="true"></i> <a tabindex="0" class="invisible_mode_turn_on">
{{t "Copy mention syntax" }} <i class="fa fa-circle-o" aria-hidden="true"></i> {{t "Go invisible" }}
{{#if is_sender_popover}}<span class="hotkey-hint">(@)</span>{{/if}} </a>
</a> </li>
{{/if}} {{/if}}
</li>
{{/unless}}
{{#if is_me}}
<li>
<a href="/#settings/profile">
<i class="fa fa-edit" aria-hidden="true"></i> {{t "Edit your profile" }}
</a>
</li>
{{/if}} {{/if}}
<hr />
<li>
<a href="{{ pm_with_url }}" class="narrow_to_private_messages"> {{#unless spectator_view}}
<i class="fa fa-envelope" aria-hidden="true"></i> <hr />
{{#if is_me}} <li>
{{t "View messages with yourself" }} <a tabindex="0" class="view_full_user_profile">
{{#if is_me}}
<i class="fa fa-user" aria-hidden="true"></i> {{t "View your profile" }}
{{else}}
<i class="fa fa-user" aria-hidden="true"></i> {{t "View profile" }}
{{/if}}
</a>
</li>
{{#if can_send_private_message}}
<li>
<a tabindex="0" class="{{ private_message_class }}">
<i class="fa fa-comment" aria-hidden="true"></i> {{t "Send direct message" }} {{#if is_sender_popover}}<span class="hotkey-hint">(R)</span>{{/if}}
</a>
</li>
{{/if}}
{{#unless is_me}}
<li>
{{#if has_message_context}}
<a tabindex="0" class="mention_user">
<i class="fa fa-at" aria-hidden="true"></i>
{{#if is_bot}}{{t "Reply mentioning bot" }}{{else}}{{t "Reply mentioning user" }}{{/if}}
{{#if is_sender_popover}}<span class="hotkey-hint">(@)</span>{{/if}}
</a>
{{else}} {{else}}
{{t "View direct messages" }} <a tabindex="0" class="copy_mention_syntax" data-clipboard-text="{{ user_mention_syntax }}">
<i class="fa fa-at" aria-hidden="true"></i>
{{t "Copy mention syntax" }}
{{#if is_sender_popover}}<span class="hotkey-hint">(@)</span>{{/if}}
</a>
{{/if}} {{/if}}
</a> </li>
</li> {{/unless}}
<li> {{#if is_me}}
<a href="{{ sent_by_url }}" class="narrow_to_messages_sent"> <li>
<i class="fa fa-paper-plane" aria-hidden="true"></i> {{t "View messages sent" }} <a href="/#settings/profile">
</a> <i class="fa fa-edit" aria-hidden="true"></i> {{t "Edit your profile" }}
</li> </a>
{{/unless}} </li>
</ul> {{/if}}
<hr />
<li>
<a href="{{ pm_with_url }}" class="narrow_to_private_messages">
<i class="fa fa-envelope" aria-hidden="true"></i>
{{#if is_me}}
{{t "View messages with yourself" }}
{{else}}
{{t "View direct messages" }}
{{/if}}
</a>
</li>
<li>
<a href="{{ sent_by_url }}" class="narrow_to_messages_sent">
<i class="fa fa-paper-plane" aria-hidden="true"></i> {{t "View messages sent" }}
</a>
</li>
{{/unless}}
</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(), is_overlay_or_modal_open: () => overlays.is_modal_open() || overlays.is_active(),
}); });
const popovers = mock_esm("../src/user_card_popover", { 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: { manage_menu: {
is_open: () => false, 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", { const popover_menus = mock_esm("../src/popover_menus", {
get_visible_instance: () => undefined, get_visible_instance: () => undefined,

View File

@@ -163,9 +163,6 @@
center--but this patch makes the popover more center--but this patch makes the popover more
likely to be usable. (If the screen is super likely to be usable. (If the screen is super
small, obviously we can't fit it completely.) 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) { if (top < 0) {
top = 0; top = 0;