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,46 +251,64 @@ 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,
placement: popover_placement,
template: render_no_arrow_popover({class: template_class}),
title: 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");
popover_menus.toggle_popover_menu(
$popover_element[0],
{
placement: popover_placement,
arrow: false,
onCreate(instance) {
user_card_popovers[template_class].instance = instance;
instance.setContent(ui_util.parse_html(render_user_card_popover_content(args)));
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");
}
if (
args.bot_owner &&
$bot_owner_element.prop("clientWidth") < $bot_owner_element.prop("scrollWidth")
) {
$bot_owner_element.addClass("tippy-zulip-tooltip");
}
const $popover = $(instance.popper);
const $popover_title = $popover.find(".popover-title");
// 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"));
$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,
}),
);
},
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) {
@ -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) {
focus_user_card_popover_item();
}
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,172 +1,175 @@
{{! Contents of the user card popover }}
<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 }}
{{#if is_bot}}
<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>
<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 }}
{{#if is_bot}}
<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 show_manage_menu }}
<span class="user-card-popover-action-buttons">
<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>
</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>
{{#if show_manage_menu }}
<span class="user-card-popover-action-buttons">
<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>
</a>
</span>
{{/if}}
</li>
{{/if}}
{{#if is_me}}
<li>
<a tabindex="0" class="update_status_text">
<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>
</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>
<a tabindex="0" class="invisible_mode_turn_on">
<i class="fa fa-circle-o" aria-hidden="true"></i> {{t "Go invisible" }}
</a>
</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>
{{/if}}
{{/if}}
{{#unless spectator_view}}
<hr />
<li>
<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}}
{{#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>
</li>
{{/if}}
{{#if is_me}}
<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 tabindex="0" class="update_status_text">
<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>
</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}}
<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>
<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}}
</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}}
<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" }}
{{#unless spectator_view}}
<hr />
<li>
<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}}
{{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}}
</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>
</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}}
<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(),
});
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;