2016-12-02 13:23:23 +01:00
|
|
|
var reactions = (function () {
|
|
|
|
var exports = {};
|
|
|
|
|
|
|
|
function send_reaction_ajax(message_id, emoji_name, operation) {
|
2017-05-01 01:13:28 +02:00
|
|
|
if (!emoji.emojis_by_name[emoji_name] && !emoji.realm_emojis[emoji_name]) {
|
2016-12-02 13:23:23 +01:00
|
|
|
// Emoji doesn't exist
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var args = {
|
|
|
|
url: '/json/messages/' + message_id + '/emoji_reactions/' + encodeURIComponent(emoji_name),
|
|
|
|
data: {},
|
|
|
|
success: function () {},
|
|
|
|
error: function (xhr) {
|
|
|
|
var response = channel.xhr_error_message("Error sending reaction", xhr);
|
2017-03-24 23:46:20 +01:00
|
|
|
// Errors are somewhat commmon here, due to race conditions
|
|
|
|
// where the user tries to add/remove the reaction when there is already
|
|
|
|
// an in-flight request. We eventually want to make this a blueslip
|
|
|
|
// error, rather than a warning, but we need to implement either
|
|
|
|
// #4291 or #4295 first.
|
|
|
|
blueslip.warn(response);
|
2017-01-12 00:17:43 +01:00
|
|
|
},
|
2016-12-02 13:23:23 +01:00
|
|
|
};
|
|
|
|
if (operation === 'add') {
|
|
|
|
channel.put(args);
|
|
|
|
} else if (operation === 'remove') {
|
|
|
|
channel.del(args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-11 17:00:24 +02:00
|
|
|
exports.current_user_has_reacted_to_emoji = function (message, emoji_name) {
|
|
|
|
var user_id = page_params.user_id;
|
|
|
|
return _.any(message.reactions, function (r) {
|
|
|
|
return (r.user.id === user_id) && (r.emoji_name === emoji_name);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-05-11 17:22:18 +02:00
|
|
|
function get_user_list_for_message_reaction(message, emoji_name) {
|
2016-12-02 13:23:23 +01:00
|
|
|
var matching_reactions = message.reactions.filter(function (reaction) {
|
|
|
|
return reaction.emoji_name === emoji_name;
|
|
|
|
});
|
|
|
|
return matching_reactions.map(function (reaction) {
|
|
|
|
return reaction.user.id;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-05-11 17:00:24 +02:00
|
|
|
function get_message(message_id) {
|
|
|
|
var message = message_store.get(message_id);
|
|
|
|
if (!message) {
|
|
|
|
blueslip.error('reactions: Bad message id: ' + message_id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
2016-12-02 13:23:23 +01:00
|
|
|
exports.message_reaction_on_click = function (message_id, emoji_name) {
|
2017-05-11 17:00:24 +02:00
|
|
|
// This toggles the current user's reaction to the clicked emoji.
|
|
|
|
|
|
|
|
var message = get_message(message_id);
|
|
|
|
if (!message) {
|
|
|
|
return;
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
2017-05-11 17:00:24 +02:00
|
|
|
|
|
|
|
var has_reacted = exports.current_user_has_reacted_to_emoji(message, emoji_name);
|
|
|
|
var operation = has_reacted ? 'remove' : 'add';
|
|
|
|
|
2016-12-02 13:23:23 +01:00
|
|
|
send_reaction_ajax(message_id, emoji_name, operation);
|
|
|
|
};
|
|
|
|
|
2017-04-19 07:49:54 +02:00
|
|
|
function get_selected_emoji() {
|
2017-04-28 22:26:22 +02:00
|
|
|
return $(".emoji-popover-emoji").filter(":focus")[0];
|
2017-04-19 07:49:54 +02:00
|
|
|
}
|
|
|
|
|
2017-03-19 02:38:01 +01:00
|
|
|
exports.toggle_reaction = function (message_id, emoji_name) {
|
2017-05-11 17:00:24 +02:00
|
|
|
var message = get_message(message_id);
|
|
|
|
if (!message) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-04-19 07:49:54 +02:00
|
|
|
var selected_emoji = get_selected_emoji();
|
|
|
|
if (emoji_name === undefined && selected_emoji === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (selected_emoji) {
|
|
|
|
emoji_name = selected_emoji.title;
|
|
|
|
}
|
2017-05-11 17:00:24 +02:00
|
|
|
|
|
|
|
var has_reacted = exports.current_user_has_reacted_to_emoji(message, emoji_name);
|
|
|
|
var operation = has_reacted ? 'remove' : 'add';
|
|
|
|
|
2016-12-02 13:23:23 +01:00
|
|
|
send_reaction_ajax(message_id, emoji_name, operation);
|
2017-04-28 22:26:22 +02:00
|
|
|
emoji_picker.hide_emoji_popover();
|
2017-03-19 02:38:01 +01:00
|
|
|
};
|
2016-12-02 13:23:23 +01:00
|
|
|
|
2017-04-19 07:54:21 +02:00
|
|
|
var reaction_show_list = []; // local reaction_show_list
|
|
|
|
|
|
|
|
exports.render_reaction_show_list = function () {
|
2017-04-28 22:26:22 +02:00
|
|
|
var reaction_list = $(".emoji-popover-emoji");
|
2017-04-19 07:54:21 +02:00
|
|
|
reaction_show_list = reaction_list.filter(function () {
|
|
|
|
return this.style.display === "block" || this.style.display === "";
|
|
|
|
}).toArray();
|
|
|
|
};
|
|
|
|
|
2016-12-02 13:23:23 +01:00
|
|
|
function filter_emojis() {
|
2017-04-28 22:26:22 +02:00
|
|
|
var elt = $(".emoji-popover-filter").expectOne();
|
2017-05-02 22:12:53 +02:00
|
|
|
var search_term = elt.val().trim().toLowerCase();
|
2017-04-28 22:26:22 +02:00
|
|
|
var reaction_list = $(".emoji-popover-emoji");
|
2016-12-02 13:23:23 +01:00
|
|
|
if (search_term !== '') {
|
|
|
|
reaction_list.filter(function () {
|
|
|
|
return this.title.indexOf(search_term) === -1;
|
|
|
|
}).css("display", "none");
|
|
|
|
reaction_list.filter(function () {
|
|
|
|
return this.title.indexOf(search_term) !== -1;
|
|
|
|
}).css("display", "block");
|
|
|
|
} else {
|
|
|
|
reaction_list.css("display", "block");
|
|
|
|
}
|
2017-04-19 07:54:21 +02:00
|
|
|
exports.render_reaction_show_list();
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
|
|
|
|
2017-04-19 07:56:29 +02:00
|
|
|
function get_emoji_at_index(index) {
|
|
|
|
if (index >= 0 && index < reaction_show_list.length) {
|
|
|
|
return reaction_show_list[index];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function find_index_for_emoji(emoji) {
|
|
|
|
return reaction_show_list.findIndex(function (reaction) {
|
|
|
|
return emoji === reaction;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-03-19 05:10:39 +01:00
|
|
|
function maybe_select_emoji(e) {
|
2017-04-19 07:56:29 +02:00
|
|
|
if (e.keyCode === 13) { // enter key
|
2017-03-19 05:10:39 +01:00
|
|
|
e.preventDefault();
|
2017-04-19 07:56:29 +02:00
|
|
|
var first_emoji = get_emoji_at_index(0);
|
|
|
|
if (first_emoji) {
|
2017-05-10 19:46:20 +02:00
|
|
|
if (emoji_picker.is_composition(first_emoji)) {
|
|
|
|
first_emoji.click();
|
|
|
|
} else {
|
|
|
|
exports.toggle_reaction(current_msg_list.selected_id(), first_emoji.title);
|
|
|
|
}
|
2017-03-19 05:10:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-28 22:26:22 +02:00
|
|
|
$(document).on('click', '.emoji-popover-emoji.reaction', function () {
|
2017-03-19 02:38:01 +01:00
|
|
|
// When an emoji is clicked in the popover,
|
|
|
|
// if the user has reacted to this message with this emoji
|
|
|
|
// the reaction is removed
|
|
|
|
// otherwise, the reaction is added
|
|
|
|
var emoji_name = this.title;
|
|
|
|
var message_id = $(this).parent().attr('data-message-id');
|
2017-05-11 17:00:24 +02:00
|
|
|
|
|
|
|
var message = get_message(message_id);
|
|
|
|
if (!message) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (exports.current_user_has_reacted_to_emoji(message, emoji_name)) {
|
2017-03-19 02:38:01 +01:00
|
|
|
$(this).removeClass('reacted');
|
|
|
|
}
|
|
|
|
exports.toggle_reaction(message_id, emoji_name);
|
|
|
|
});
|
|
|
|
|
2017-04-28 22:26:22 +02:00
|
|
|
$(document).on('input', '.emoji-popover-filter', filter_emojis);
|
|
|
|
$(document).on('keydown', '.emoji-popover-filter', maybe_select_emoji);
|
2016-12-02 13:23:23 +01:00
|
|
|
|
2017-04-19 07:59:01 +02:00
|
|
|
exports.reaction_navigate = function (e, event_name) {
|
|
|
|
var first_emoji = get_emoji_at_index(0);
|
|
|
|
var selected_emoji = get_selected_emoji();
|
|
|
|
var selected_index = find_index_for_emoji(selected_emoji);
|
|
|
|
|
|
|
|
// special cases
|
|
|
|
if (event_name === 'down_arrow') {
|
2017-04-28 22:26:22 +02:00
|
|
|
if ($('.emoji-popover-filter').is(':focus') && first_emoji) { // move down into emoji map
|
2017-04-19 07:59:01 +02:00
|
|
|
$(first_emoji).focus();
|
|
|
|
}
|
|
|
|
} else if (event_name === 'up_arrow') {
|
2017-05-05 18:38:14 +02:00
|
|
|
if (selected_emoji && selected_index < 6) {
|
|
|
|
// In this case, we're move up into the reaction filter
|
|
|
|
// rows. Here, we override the default browser behavior,
|
|
|
|
// which in Firefox is good (preserving the cursor
|
|
|
|
// position) and in Chrome is bad (cursor goes to
|
|
|
|
// beginning) with something reasonable and consistent
|
|
|
|
// (cursor goes to the end of the filter string).
|
|
|
|
$('.emoji-popover-filter').focus().caret(Infinity);
|
|
|
|
return true;
|
2017-04-19 07:59:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selected_emoji === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
var next_index;
|
|
|
|
switch (event_name) {
|
|
|
|
case 'down_arrow':
|
|
|
|
next_index = selected_index + 6;
|
|
|
|
break;
|
|
|
|
case 'up_arrow':
|
|
|
|
next_index = selected_index - 6;
|
|
|
|
break;
|
|
|
|
case 'left_arrow':
|
|
|
|
next_index = selected_index - 1;
|
|
|
|
break;
|
|
|
|
case 'right_arrow':
|
|
|
|
next_index = selected_index + 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
var next_emoji = get_emoji_at_index(next_index);
|
|
|
|
if (next_emoji) {
|
|
|
|
$(next_emoji).focus();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2016-12-02 13:23:23 +01:00
|
|
|
function full_name(user_id) {
|
|
|
|
if (user_id === page_params.user_id) {
|
|
|
|
return 'You (click to remove)';
|
|
|
|
}
|
|
|
|
return people.get_person_from_user_id(user_id).full_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
function generate_title(emoji_name, user_ids) {
|
|
|
|
var i = user_ids.indexOf(page_params.user_id);
|
|
|
|
if (i !== -1) {
|
|
|
|
// Move current user's id to start of list
|
|
|
|
user_ids.splice(i, 1);
|
|
|
|
user_ids.unshift(page_params.user_id);
|
|
|
|
}
|
|
|
|
var reacted_with_string = ' reacted with :' + emoji_name + ':';
|
|
|
|
var user_names = user_ids.map(full_name);
|
|
|
|
if (user_names.length === 1) {
|
|
|
|
return user_names[0] + reacted_with_string;
|
|
|
|
}
|
|
|
|
return _.initial(user_names).join(', ') + ' and ' + _.last(user_names) + reacted_with_string;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.add_reaction = function (event) {
|
|
|
|
event.emoji_name_css_class = emoji.emoji_name_to_css_class(event.emoji_name);
|
|
|
|
event.user.id = event.user.user_id;
|
2017-01-17 09:10:41 +01:00
|
|
|
var message = message_store.get(event.message_id);
|
|
|
|
if (message === undefined) {
|
|
|
|
// If we don't have the message in cache, do nothing; if we
|
|
|
|
// ever fetch it from the server, it'll come with the
|
|
|
|
// latest reactions attached
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
message.reactions.push(event);
|
2016-12-02 13:23:23 +01:00
|
|
|
var message_element = $('.message_table').find("[zid='" + event.message_id + "']");
|
|
|
|
var message_reactions_element = message_element.find('.message_reactions');
|
2017-05-11 17:22:18 +02:00
|
|
|
var user_list = get_user_list_for_message_reaction(message, event.emoji_name);
|
2016-12-02 13:23:23 +01:00
|
|
|
var new_title = generate_title(event.emoji_name, user_list);
|
|
|
|
if (user_list.length === 1) {
|
|
|
|
if (emoji.realm_emojis[event.emoji_name]) {
|
|
|
|
event.is_realm_emoji = true;
|
|
|
|
event.url = emoji.realm_emojis[event.emoji_name].emoji_url;
|
|
|
|
}
|
|
|
|
event.count = 1;
|
|
|
|
event.title = new_title;
|
2017-03-02 08:30:53 +01:00
|
|
|
event.emoji_alt_code = page_params.emoji_alt_code;
|
2017-02-13 07:51:40 +01:00
|
|
|
if (event.user.id === page_params.user_id) {
|
|
|
|
event.class = "message_reaction reacted";
|
|
|
|
} else {
|
|
|
|
event.class = "message_reaction";
|
|
|
|
}
|
2016-12-02 13:23:23 +01:00
|
|
|
var reaction_button_element = message_reactions_element.find('.reaction_button');
|
|
|
|
$(templates.render('message_reaction', event)).insertBefore(reaction_button_element);
|
|
|
|
} else {
|
|
|
|
var reaction = message_reactions_element.find("[data-emoji-name='" + event.emoji_name + "']");
|
|
|
|
var count_element = reaction.find('.message_reaction_count');
|
|
|
|
count_element.html(user_list.length);
|
|
|
|
reaction.prop('title', new_title);
|
2017-02-13 07:51:40 +01:00
|
|
|
if (event.user.id === page_params.user_id) {
|
|
|
|
reaction.addClass("reacted");
|
|
|
|
}
|
2016-12-02 13:23:23 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.remove_reaction = function (event) {
|
|
|
|
var emoji_name = event.emoji_name;
|
|
|
|
var message_id = event.message_id;
|
|
|
|
var user_id = event.user.user_id;
|
|
|
|
var i = -1;
|
2017-01-17 09:10:41 +01:00
|
|
|
var message = message_store.get(message_id);
|
|
|
|
if (message === undefined) {
|
|
|
|
// If we don't have the message in cache, do nothing; if we
|
|
|
|
// ever fetch it from the server, it'll come with the
|
|
|
|
// latest reactions attached
|
|
|
|
return;
|
|
|
|
}
|
2016-12-02 13:23:23 +01:00
|
|
|
_.each(message.reactions, function (reaction, index) {
|
|
|
|
if (reaction.emoji_name === emoji_name && reaction.user.id === user_id) {
|
|
|
|
i = index;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (i !== -1) {
|
|
|
|
message.reactions.splice(i, 1);
|
|
|
|
}
|
2017-05-11 17:22:18 +02:00
|
|
|
var user_list = get_user_list_for_message_reaction(message, emoji_name);
|
2016-12-02 13:23:23 +01:00
|
|
|
var new_title = generate_title(emoji_name, user_list);
|
|
|
|
var message_element = $('.message_table').find("[zid='" + message_id + "']");
|
|
|
|
var message_reactions_element = message_element.find('.message_reactions');
|
|
|
|
var matching_reactions = message_reactions_element.find('[data-emoji-name="' + emoji_name + '"]');
|
|
|
|
var count_element = matching_reactions.find('.message_reaction_count');
|
|
|
|
matching_reactions.prop('title', new_title);
|
2017-02-13 07:51:40 +01:00
|
|
|
if (user_id === page_params.user_id) {
|
|
|
|
matching_reactions.removeClass("reacted");
|
|
|
|
}
|
2016-12-02 13:23:23 +01:00
|
|
|
count_element.html(user_list.length);
|
|
|
|
if (user_list.length === 0) {
|
|
|
|
matching_reactions.remove();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.get_emojis_used_by_user_for_message_id = function (message_id) {
|
|
|
|
var user_id = page_params.user_id;
|
|
|
|
var message = message_store.get(message_id);
|
|
|
|
var reactions_by_user = message.reactions.filter(function (reaction) {
|
|
|
|
return reaction.user.id === user_id;
|
|
|
|
});
|
|
|
|
return reactions_by_user.map(function (reaction) {
|
|
|
|
return reaction.emoji_name;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.get_message_reactions = function (message) {
|
|
|
|
var message_reactions = new Dict();
|
|
|
|
_.each(message.reactions, function (reaction) {
|
2017-03-26 20:38:47 +02:00
|
|
|
var user_id = reaction.user.id;
|
|
|
|
if (!people.is_known_user_id(user_id)) {
|
|
|
|
blueslip.warn('Unknown user_id ' + user_id +
|
|
|
|
'in reaction for message ' + message.id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-12-02 13:23:23 +01:00
|
|
|
var user_list = message_reactions.setdefault(reaction.emoji_name, []);
|
2017-03-26 20:38:47 +02:00
|
|
|
user_list.push(user_id);
|
2016-12-02 13:23:23 +01:00
|
|
|
});
|
|
|
|
var reactions = message_reactions.items().map(function (item) {
|
2017-03-26 20:25:31 +02:00
|
|
|
var emoji_name = item[0];
|
|
|
|
var user_ids = item[1];
|
2016-12-02 13:23:23 +01:00
|
|
|
var reaction = {
|
2017-03-26 20:25:31 +02:00
|
|
|
emoji_name: emoji_name,
|
|
|
|
emoji_name_css_class: emoji.emoji_name_to_css_class(emoji_name),
|
|
|
|
count: user_ids.length,
|
|
|
|
title: generate_title(emoji_name, user_ids),
|
2017-03-02 08:30:53 +01:00
|
|
|
emoji_alt_code: page_params.emoji_alt_code,
|
2016-12-02 13:23:23 +01:00
|
|
|
};
|
|
|
|
if (emoji.realm_emojis[reaction.emoji_name]) {
|
|
|
|
reaction.is_realm_emoji = true;
|
|
|
|
reaction.url = emoji.realm_emojis[reaction.emoji_name].emoji_url;
|
|
|
|
}
|
2017-05-11 17:00:24 +02:00
|
|
|
if (user_ids.indexOf(page_params.user_id) !== -1) {
|
2017-02-13 07:51:40 +01:00
|
|
|
reaction.class = "message_reaction reacted";
|
|
|
|
} else {
|
|
|
|
reaction.class = "message_reaction";
|
|
|
|
}
|
2016-12-02 13:23:23 +01:00
|
|
|
return reaction;
|
|
|
|
});
|
|
|
|
return reactions;
|
|
|
|
};
|
|
|
|
|
2017-02-07 23:09:41 +01:00
|
|
|
$(function () {
|
|
|
|
$(document).on('message_id_changed', function (event) {
|
|
|
|
// When a message ID is changed via editing, update any
|
|
|
|
// data-message-id references to it.
|
|
|
|
var elts = $(".message_reactions[data-message-id='" + event.old_id + "']");
|
|
|
|
elts.attr("data-message-id", event.new_id);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2016-12-02 13:23:23 +01:00
|
|
|
return exports;
|
|
|
|
}());
|
|
|
|
|
|
|
|
if (typeof module !== 'undefined') {
|
|
|
|
module.exports = reactions;
|
|
|
|
}
|