mirror of https://github.com/zulip/zulip.git
popovers: Extract `user_group_popover` into separate module.
This is a preparatory commit before we migrate `user_group_popover` from Bootstrap to Tippy library. The previous implementation was weirdly sharing the logic around `current_message_info_popover_elem` with the user info popovers based on a message; very likely an unfortunate latent bug caused by copy/paste. To address that, we need to add dedicated functions like get_user_group_popover_items to avoid breaking keyboard navigation with this extraction.
This commit is contained in:
parent
1765ce23b0
commit
7777c55b22
|
@ -222,6 +222,7 @@ EXEMPT_FILES = make_set(
|
||||||
"web/src/user_group_create_members_data.ts",
|
"web/src/user_group_create_members_data.ts",
|
||||||
"web/src/user_group_edit.js",
|
"web/src/user_group_edit.js",
|
||||||
"web/src/user_group_edit_members.js",
|
"web/src/user_group_edit_members.js",
|
||||||
|
"web/src/user_group_popover.js",
|
||||||
"web/src/user_group_ui_updates.js",
|
"web/src/user_group_ui_updates.js",
|
||||||
"web/src/user_groups.ts",
|
"web/src/user_groups.ts",
|
||||||
"web/src/user_groups_settings_ui.js",
|
"web/src/user_groups_settings_ui.js",
|
||||||
|
|
|
@ -48,6 +48,7 @@ import * as stream_popover from "./stream_popover";
|
||||||
import * as stream_settings_ui from "./stream_settings_ui";
|
import * as stream_settings_ui from "./stream_settings_ui";
|
||||||
import * as topic_zoom from "./topic_zoom";
|
import * as topic_zoom from "./topic_zoom";
|
||||||
import * as unread_ops from "./unread_ops";
|
import * as unread_ops from "./unread_ops";
|
||||||
|
import * as user_group_popover from "./user_group_popover";
|
||||||
import {user_settings} from "./user_settings";
|
import {user_settings} from "./user_settings";
|
||||||
import * as user_topics_ui from "./user_topics_ui";
|
import * as user_topics_ui from "./user_topics_ui";
|
||||||
|
|
||||||
|
@ -386,6 +387,11 @@ function handle_popover_events(event_name) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user_group_popover.is_open()) {
|
||||||
|
user_group_popover.handle_keyboard(event_name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,6 @@ import url_template_lib from "url-template";
|
||||||
|
|
||||||
import render_no_arrow_popover from "../templates/no_arrow_popover.hbs";
|
import render_no_arrow_popover from "../templates/no_arrow_popover.hbs";
|
||||||
import render_playground_links_popover_content from "../templates/playground_links_popover_content.hbs";
|
import render_playground_links_popover_content from "../templates/playground_links_popover_content.hbs";
|
||||||
import render_user_group_info_popover from "../templates/user_group_info_popover.hbs";
|
|
||||||
import render_user_group_info_popover_content from "../templates/user_group_info_popover_content.hbs";
|
|
||||||
import render_user_info_popover_content from "../templates/user_info_popover_content.hbs";
|
import render_user_info_popover_content from "../templates/user_info_popover_content.hbs";
|
||||||
import render_user_info_popover_manage_menu from "../templates/user_info_popover_manage_menu.hbs";
|
import render_user_info_popover_manage_menu from "../templates/user_info_popover_manage_menu.hbs";
|
||||||
import render_user_info_popover_title from "../templates/user_info_popover_title.hbs";
|
import render_user_info_popover_title from "../templates/user_info_popover_title.hbs";
|
||||||
|
@ -40,12 +38,11 @@ import * as settings_users from "./settings_users";
|
||||||
import * as stream_popover from "./stream_popover";
|
import * as stream_popover from "./stream_popover";
|
||||||
import * as timerender from "./timerender";
|
import * as timerender from "./timerender";
|
||||||
import * as ui_report from "./ui_report";
|
import * as ui_report from "./ui_report";
|
||||||
import * as user_groups from "./user_groups";
|
import * as user_group_popover from "./user_group_popover";
|
||||||
import * as user_profile from "./user_profile";
|
import * as user_profile from "./user_profile";
|
||||||
import {user_settings} from "./user_settings";
|
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";
|
||||||
import * as util from "./util";
|
|
||||||
|
|
||||||
let $current_message_info_popover_elem;
|
let $current_message_info_popover_elem;
|
||||||
let $current_user_info_popover_elem;
|
let $current_user_info_popover_elem;
|
||||||
|
@ -162,20 +159,6 @@ function load_medium_avatar(user, $elt) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculate_info_popover_placement(size, $elt) {
|
|
||||||
const ypos = $elt.get_offset_to_window().top;
|
|
||||||
|
|
||||||
if (!(ypos + size / 2 < message_viewport.height() && ypos > size / 2)) {
|
|
||||||
if (ypos + size < message_viewport.height()) {
|
|
||||||
return "bottom";
|
|
||||||
} else if (ypos > size) {
|
|
||||||
return "top";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hide_user_info_popover_manage_menu() {
|
export function hide_user_info_popover_manage_menu() {
|
||||||
if ($current_user_info_popover_manage_menu !== undefined) {
|
if ($current_user_info_popover_manage_menu !== undefined) {
|
||||||
$current_user_info_popover_manage_menu.popover("destroy");
|
$current_user_info_popover_manage_menu.popover("destroy");
|
||||||
|
@ -342,9 +325,6 @@ function render_user_info_popover(
|
||||||
load_medium_avatar(user, $(".popover-avatar"));
|
load_medium_avatar(user, $(".popover-avatar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// exporting for testability
|
|
||||||
export const _test_calculate_info_popover_placement = calculate_info_popover_placement;
|
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -448,62 +428,6 @@ function get_user_info_popover_manage_menu_items() {
|
||||||
return $(".user_info_popover_manage_menu li:not(.divider):visible a", popover_data.$tip);
|
return $(".user_info_popover_manage_menu li:not(.divider):visible a", popover_data.$tip);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetch_group_members(member_ids) {
|
|
||||||
return member_ids
|
|
||||||
.map((m) => people.maybe_get_user_by_id(m))
|
|
||||||
.filter((m) => m !== undefined)
|
|
||||||
.map((p) => ({
|
|
||||||
...p,
|
|
||||||
user_circle_class: buddy_data.get_user_circle_class(p.user_id),
|
|
||||||
is_active: people.is_active_user_for_popover(p.user_id),
|
|
||||||
user_last_seen_time_status: buddy_data.user_last_seen_time_status(p.user_id),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort_group_members(members) {
|
|
||||||
return members.sort((a, b) => util.strcmp(a.full_name, b.fullname));
|
|
||||||
}
|
|
||||||
|
|
||||||
// exporting these functions for testing purposes
|
|
||||||
export const _test_fetch_group_members = fetch_group_members;
|
|
||||||
|
|
||||||
export const _test_sort_group_members = sort_group_members;
|
|
||||||
|
|
||||||
// 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 show_user_group_info_popover(element, group, message) {
|
|
||||||
const $last_popover_elem = $current_message_info_popover_elem;
|
|
||||||
// hardcoded pixel height of the popover
|
|
||||||
// note that the actual size varies (in group size), but this is about as big as it gets
|
|
||||||
const popover_size = 390;
|
|
||||||
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);
|
|
||||||
const $elt = $(element);
|
|
||||||
if ($elt.data("popover") === undefined) {
|
|
||||||
const args = {
|
|
||||||
group_name: group.name,
|
|
||||||
group_description: group.description,
|
|
||||||
members: sort_group_members(fetch_group_members([...group.members])),
|
|
||||||
};
|
|
||||||
$elt.popover({
|
|
||||||
placement: calculate_info_popover_placement(popover_size, $elt),
|
|
||||||
template: render_user_group_info_popover({class: "message-info-popover"}),
|
|
||||||
content: render_user_group_info_popover_content(args),
|
|
||||||
html: true,
|
|
||||||
trigger: "manual",
|
|
||||||
fixed: true,
|
|
||||||
});
|
|
||||||
$elt.popover("show");
|
|
||||||
$current_message_info_popover_elem = $elt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_action_menu_menu_items() {
|
function get_action_menu_menu_items() {
|
||||||
const $current_actions_popover_elem = $("[data-tippy-root] .actions_popover");
|
const $current_actions_popover_elem = $("[data-tippy-root] .actions_popover");
|
||||||
if (!$current_actions_popover_elem) {
|
if (!$current_actions_popover_elem) {
|
||||||
|
@ -760,20 +684,6 @@ export function register_click_handlers() {
|
||||||
show_user_info_popover_for_message(this, user, message);
|
show_user_info_popover_for_message(this, user, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#main_div").on("click", ".user-group-mention", function (e) {
|
|
||||||
const user_group_id = Number.parseInt($(this).attr("data-user-group-id"), 10);
|
|
||||||
const $row = $(this).closest(".message_row");
|
|
||||||
e.stopPropagation();
|
|
||||||
const message = message_lists.current.get(rows.id($row));
|
|
||||||
try {
|
|
||||||
const group = user_groups.get_user_group_from_id(user_group_id);
|
|
||||||
show_user_group_info_popover(this, group, message);
|
|
||||||
} catch {
|
|
||||||
// This user group has likely been deleted.
|
|
||||||
blueslip.info("Unable to find user group in message" + message.sender_id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#main_div, #preview_content, #message-history").on(
|
$("#main_div, #preview_content, #message-history").on(
|
||||||
"click",
|
"click",
|
||||||
".code_external_link",
|
".code_external_link",
|
||||||
|
@ -1066,6 +976,7 @@ export function any_active() {
|
||||||
// Expanded sidebars on mobile view count as popovers as well.
|
// Expanded sidebars on mobile view count as popovers as well.
|
||||||
return (
|
return (
|
||||||
popover_menus.any_active() ||
|
popover_menus.any_active() ||
|
||||||
|
user_group_popover.is_open() ||
|
||||||
user_sidebar_popped() ||
|
user_sidebar_popped() ||
|
||||||
stream_popover.stream_popped() ||
|
stream_popover.stream_popped() ||
|
||||||
message_info_popped() ||
|
message_info_popped() ||
|
||||||
|
@ -1086,6 +997,7 @@ export function hide_all_except_sidebars(opts) {
|
||||||
}
|
}
|
||||||
emoji_picker.hide_emoji_popover();
|
emoji_picker.hide_emoji_popover();
|
||||||
stream_popover.hide_stream_popover();
|
stream_popover.hide_stream_popover();
|
||||||
|
user_group_popover.hide();
|
||||||
hide_all_user_info_popovers();
|
hide_all_user_info_popovers();
|
||||||
hide_playground_links_popover();
|
hide_playground_links_popover();
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,7 @@ import * as unread_ui from "./unread_ui";
|
||||||
import * as upload from "./upload";
|
import * as upload from "./upload";
|
||||||
import * as user_group_edit from "./user_group_edit";
|
import * as user_group_edit from "./user_group_edit";
|
||||||
import * as user_group_edit_members from "./user_group_edit_members";
|
import * as user_group_edit_members from "./user_group_edit_members";
|
||||||
|
import * as user_group_popover from "./user_group_popover";
|
||||||
import * as user_groups from "./user_groups";
|
import * as user_groups from "./user_groups";
|
||||||
import * as user_group_settings_ui from "./user_groups_settings_ui";
|
import * as user_group_settings_ui from "./user_groups_settings_ui";
|
||||||
import {initialize_user_settings, user_settings} from "./user_settings";
|
import {initialize_user_settings, user_settings} from "./user_settings";
|
||||||
|
@ -761,6 +762,7 @@ export function initialize_everything() {
|
||||||
initialize_unread_ui();
|
initialize_unread_ui();
|
||||||
activity.initialize();
|
activity.initialize();
|
||||||
emoji_picker.initialize();
|
emoji_picker.initialize();
|
||||||
|
user_group_popover.initialize();
|
||||||
pm_list.initialize();
|
pm_list.initialize();
|
||||||
topic_list.initialize({
|
topic_list.initialize({
|
||||||
on_topic_click(stream_id, topic) {
|
on_topic_click(stream_id, topic) {
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
import $ from "jquery";
|
||||||
|
|
||||||
|
import render_user_group_info_popover from "../templates/user_group_info_popover.hbs";
|
||||||
|
import render_user_group_info_popover_content from "../templates/user_group_info_popover_content.hbs";
|
||||||
|
|
||||||
|
import * as blueslip from "./blueslip";
|
||||||
|
import * as buddy_data from "./buddy_data";
|
||||||
|
import * as message_lists from "./message_lists";
|
||||||
|
import * as message_viewport from "./message_viewport";
|
||||||
|
import * as people from "./people";
|
||||||
|
import {hide_all, popover_items_handle_keyboard} from "./popovers";
|
||||||
|
import * as rows from "./rows";
|
||||||
|
import * as user_groups from "./user_groups";
|
||||||
|
import * as util from "./util";
|
||||||
|
|
||||||
|
let $current_user_group_popover_elem;
|
||||||
|
|
||||||
|
export function hide() {
|
||||||
|
if (is_open()) {
|
||||||
|
$current_user_group_popover_elem.popover("destroy");
|
||||||
|
$current_user_group_popover_elem = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function is_open() {
|
||||||
|
return $current_user_group_popover_elem !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_user_group_popover_items() {
|
||||||
|
if (!$current_user_group_popover_elem) {
|
||||||
|
blueslip.error("Trying to get menu items when action popover is closed.");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const popover_data = $current_user_group_popover_elem.data("popover");
|
||||||
|
if (!popover_data) {
|
||||||
|
blueslip.error("Cannot find popover data for actions menu.");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $("li:not(.divider):visible a", popover_data.$tip);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handle_keyboard(key) {
|
||||||
|
const $items = get_user_group_popover_items();
|
||||||
|
popover_items_handle_keyboard(key, $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
|
||||||
|
export function show_user_group_info_popover(element, group, message) {
|
||||||
|
const $last_popover_elem = $current_user_group_popover_elem;
|
||||||
|
// hardcoded pixel height of the popover
|
||||||
|
// note that the actual size varies (in group size), but this is about as big as it gets
|
||||||
|
const popover_size = 390;
|
||||||
|
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);
|
||||||
|
const $elt = $(element);
|
||||||
|
if ($elt.data("popover") === undefined) {
|
||||||
|
const args = {
|
||||||
|
group_name: group.name,
|
||||||
|
group_description: group.description,
|
||||||
|
members: sort_group_members(fetch_group_members([...group.members])),
|
||||||
|
};
|
||||||
|
$elt.popover({
|
||||||
|
placement: calculate_info_popover_placement(popover_size, $elt),
|
||||||
|
template: render_user_group_info_popover({class: "message-info-popover"}),
|
||||||
|
content: render_user_group_info_popover_content(args),
|
||||||
|
html: true,
|
||||||
|
trigger: "manual",
|
||||||
|
fixed: true,
|
||||||
|
});
|
||||||
|
$elt.popover("show");
|
||||||
|
$current_user_group_popover_elem = $elt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function register_click_handlers() {
|
||||||
|
$("#main_div").on("click", ".user-group-mention", function (e) {
|
||||||
|
const user_group_id = Number.parseInt($(this).attr("data-user-group-id"), 10);
|
||||||
|
const $row = $(this).closest(".message_row");
|
||||||
|
e.stopPropagation();
|
||||||
|
const message = message_lists.current.get(rows.id($row));
|
||||||
|
try {
|
||||||
|
const group = user_groups.get_user_group_from_id(user_group_id);
|
||||||
|
show_user_group_info_popover(this, group, message);
|
||||||
|
} catch {
|
||||||
|
// This user group has likely been deleted.
|
||||||
|
blueslip.info("Unable to find user group in message" + message.sender_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetch_group_members(member_ids) {
|
||||||
|
return member_ids
|
||||||
|
.map((m) => people.maybe_get_user_by_id(m))
|
||||||
|
.filter((m) => m !== undefined)
|
||||||
|
.map((p) => ({
|
||||||
|
...p,
|
||||||
|
user_circle_class: buddy_data.get_user_circle_class(p.user_id),
|
||||||
|
is_active: people.is_active_user_for_popover(p.user_id),
|
||||||
|
user_last_seen_time_status: buddy_data.user_last_seen_time_status(p.user_id),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sort_group_members(members) {
|
||||||
|
return members.sort((a, b) => util.strcmp(a.full_name, b.fullname));
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculate_info_popover_placement(size, $elt) {
|
||||||
|
const ypos = $elt.get_offset_to_window().top;
|
||||||
|
|
||||||
|
if (!(ypos + size / 2 < message_viewport.height() && ypos > size / 2)) {
|
||||||
|
if (ypos + size < message_viewport.height()) {
|
||||||
|
return "bottom";
|
||||||
|
} else if (ypos > size) {
|
||||||
|
return "top";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// exporting these functions for testing purposes
|
||||||
|
export const _test_fetch_group_members = fetch_group_members;
|
||||||
|
|
||||||
|
export const _test_sort_group_members = sort_group_members;
|
||||||
|
|
||||||
|
export const _test_calculate_info_popover_placement = calculate_info_popover_placement;
|
||||||
|
|
||||||
|
export function initialize() {
|
||||||
|
register_click_handlers();
|
||||||
|
}
|
Loading…
Reference in New Issue