popovers: Add a popover for group mentions.

This adds a click handler to `.user-group-mention` which works in a
fashion that is quite similar to `.user-mention`. It generates and
displays a popover.

The popover has a list of members, their online status (if they are
not bots) or their bot status if they are bots (it's not clear whether
ultimately bots should be able to be members of usergroups, but I'm
able to add one, so I thought it would be worth supporting).

The popover's `UL` element has max-height and overflow-y atttributes
so large groups will grow a scrollbar.

Fixes #8300.
This commit is contained in:
Eric Eslinger 2018-02-13 20:21:46 -08:00 committed by Tim Abbott
parent bc21344dc6
commit 786bda674c
5 changed files with 205 additions and 13 deletions

View File

@ -1350,6 +1350,53 @@ function render(template_name, args) {
}()); }());
(function user_group_info_popover() {
var html = render('user_group_info_popover');
global.write_handlebars_output("user_group_info_popover", html);
$(html).hasClass('popover message-info-popover group-info-popover');
}());
(function user_group_info_popover_content() {
var args = {
group_name: 'groupName',
group_description: 'groupDescription',
members: [
{
presence_status: 'active',
full_name: 'Active Alice',
user_last_seen_time_status: 'time',
is_bot: false,
},
{
presence_status: 'offline',
full_name: 'Bot Bob',
user_last_seen_time_status: 'time',
is_bot: true,
},
{
presence_status: 'offline',
full_name: 'Inactive Imogen',
user_last_seen_time_status: 'time',
is_bot: false,
},
],
};
var html = render('user_group_info_popover_content', args);
global.write_handlebars_output("user_group_info_popover_content", html);
var allUsers = $(html).find("li");
assert.equal(allUsers[0].classList.contains("user_active"), true);
assert.equal(allUsers[2].classList.contains("user_offline"), true);
assert.equal($(allUsers[0]).text().trim(), 'Active Alice');
assert.equal($(allUsers[1]).text().trim(), 'Bot Bob');
assert.equal($(allUsers[2]).text().trim(), 'Inactive Imogen');
assert.equal($(html).find('.group-name').text().trim(), 'groupName');
assert.equal($(html).find('.group-description').text().trim(), 'groupDescription');
}());
(function user_info_popover() { (function user_info_popover() {
var html = render('user_info_popover', {class: 'message-info-popover'}); var html = render('user_info_popover', {class: 'message-info-popover'});
global.write_handlebars_output("user_info_popover", html); global.write_handlebars_output("user_info_popover", html);

View File

@ -67,11 +67,28 @@ function user_last_seen_time_status(user_id) {
return timerender.last_seen_status_from_date(last_active_date.clone()); return timerender.last_seen_status_from_date(last_active_date.clone());
} }
function calculate_info_popover_placement(size, elt) {
var ypos = elt.offset().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';
}
}
}
// exporting for testability
exports._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
function show_user_info_popover(element, user, message) { function show_user_info_popover(element, user, message) {
var last_popover_elem = current_message_info_popover_elem; var last_popover_elem = current_message_info_popover_elem;
var popover_size = 428; // hardcoded pixel height of the popover
popovers.hide_all(); popovers.hide_all();
if (last_popover_elem !== undefined if (last_popover_elem !== undefined
&& last_popover_elem.get()[0] === element) { && last_popover_elem.get()[0] === element) {
@ -104,21 +121,9 @@ function show_user_info_popover(element, user, message) {
is_bot: people.get_person_from_user_id(user.user_id).is_bot, is_bot: people.get_person_from_user_id(user.user_id).is_bot,
}; };
var ypos = elt.offset().top;
var popover_size = 428;
var placement = "right";
if (!((ypos + (popover_size / 2) < message_viewport.height()) &&
(ypos > (popover_size / 2)))) {
if (((ypos + popover_size) < message_viewport.height())) {
placement = "bottom";
} else if (ypos > popover_size) {
placement = "top";
}
}
elt.popover({ elt.popover({
placement: placement, placement: calculate_info_popover_placement(popover_size, elt),
template: templates.render('user_info_popover', {class: "message-info-popover"}), template: templates.render('user_info_popover', {class: "message-info-popover"}),
title: templates.render('user_info_popover_title', title: templates.render('user_info_popover_title',
{user_avatar: "avatar/" + user.email}), {user_avatar: "avatar/" + user.email}),
@ -133,6 +138,73 @@ function show_user_info_popover(element, user, message) {
} }
} }
function fetch_group_members(member_ids) {
return member_ids
.map(function (m) {
return people.get_person_from_user_id(m);
})
.filter(function (m) {
return m !== undefined;
})
.map(function (p) {
return Object.assign({}, p, {
presence_status: presence.get_status(p.user_id),
is_active: people.is_active_user_for_popover(p.user_id),
user_last_seen_time_status: user_last_seen_time_status(p.user_id),
});
});
}
function sort_group_members(members) {
return members
.sort(function (a, b) {
return a.full_name.localeCompare(b.full_name);
});
}
// exporting these functions for testing purposes
exports._test_fetch_group_members = fetch_group_members;
exports._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) {
var 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
var popover_size = 390;
popovers.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;
}
current_msg_list.select_id(message.id);
var elt = $(element);
if (elt.data('popover') === undefined) {
var 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: templates.render('user_group_info_popover', {class: "message-info-popover"}),
content: templates.render('user_group_info_popover_content', args),
trigger: "manual",
});
elt.popover("show");
$('.nav.nav-list.member-list').perfectScrollbar({
suppressScrollX: true,
useKeyboard: false,
wheelSpeed: 0.5,
});
current_message_info_popover_elem = elt;
}
}
exports.toggle_actions_popover = function (element, id) { exports.toggle_actions_popover = function (element, id) {
var last_popover_elem = current_actions_popover_elem; var last_popover_elem = current_actions_popover_elem;
popovers.hide_all(); popovers.hide_all();
@ -449,6 +521,19 @@ exports.register_click_handlers = function () {
show_user_info_popover(this, user, message); show_user_info_popover(this, user, message);
}); });
$("#main_div").on("click", ".user-group-mention", function (e) {
var id = $(this).attr('data-user-group-id');
var row = $(this).closest(".message_row");
e.stopPropagation();
var message = current_msg_list.get(rows.id(row));
var group = user_groups.get_user_group_from_id(id);
if (group === undefined) {
blueslip.error('Unable to find user group in message' + message.sender_id);
} else {
show_user_group_info_popover(this, group, message);
}
});
$('body').on('click', '.user_popover .narrow_to_private_messages', function (e) { $('body').on('click', '.user_popover .narrow_to_private_messages', function (e) {
var user_id = $(e.target).parents('ul').attr('data-user-id'); var user_id = $(e.target).parents('ul').attr('data-user-id');
var email = people.get_person_from_user_id(user_id).email; var email = people.get_person_from_user_id(user_id).email;

View File

@ -96,6 +96,29 @@ ul.remind_me_popover .remind_icon {
width: 240px; width: 240px;
} }
.group-info-popover .group-info {
text-align: center;
}
.group-info-popover .group-info .group-name {
font-weight: bold;
}
.group-info-popover .member-list {
max-height: 300px;
overflow-y: auto;
list-style: none;
margin-left: 0;
}
.group-info-popover .member-list .bot {
color: hsl(180, 5%, 74%);
vertical-align: top;
width: 20px;
padding-top: 3.5px;
text-align: center;
}
.user_popover .popover-title { .user_popover .popover-title {
padding: 0; padding: 0;
} }

View File

@ -0,0 +1,7 @@
<div class="popover message-info-popover group-info-popover">
<div class="popover-inner">
<div class="popover-content">
<div></div>
</div>
</div>
</div>

View File

@ -0,0 +1,30 @@
{{! Contents of the "user group info" popup }}
<div class="group-info">
<div class="group-name"> {{group_name}} </div>
<div class="group-description">
{{group_description}}
</div>
</div>
<hr>
<ul class="nav nav-list member-list">
{{#each members}}
<li class="user_{{presence_status}}">
{{#if is_active }}
{{#if is_bot}}
<i class="zulip-icon bot" aria-hidden="true"></i>
{{else}}
<span class="user-status-indicator popover_user_presence" title="{{user_last_seen_time_status}}"></span>
{{/if}}
{{/if}}
<span>{{full_name}}</span>
</li>
{{/each}}
</ul>
<ul class="nav nav-list">
<li>
<a href="#organization/user-groups-admin">
<i class="icon-vector-cog"></i>
{{t 'Manage user groups' }}
</a>
</li>
</ul>