Add emoji map to the compose box.

- Expand a box full of emojis into the
compose window for users to graphically select emojis.

- Append an emoji to the end of the message when a user
clicks the emoji in the emoji box.

- Trap the escape key to always close the emoji box
before closing anything else if the box is open.

- Fixes: #147.
This commit is contained in:
Ilona Brand 2015-10-15 16:34:30 -04:00 committed by Tim Abbott
parent da86429378
commit 4bf908c2d6
10 changed files with 197 additions and 3 deletions

View File

@ -1,6 +1,12 @@
set_global('page_params', {realm_emoji: {
burrito: {display_url: '/static/third/gemoji/images/emoji/burrito.png',
source_url: '/static/third/gemoji/images/emoji/burrito.png'}
}});
add_dependencies({
Handlebars: 'handlebars',
templates: 'js/templates',
emoji: 'js/emoji',
i18n: 'i18next'
});
@ -301,6 +307,20 @@ fs.readdirSync(path.join(__dirname, "../../static/templates/", "settings")).forE
assert.equal(li.text(), 'The email will be forwarded to this stream');
}());
(function emoji_popover_content() {
var args = {
emoji_list: global.emoji.emojis_name_to_css_class
};
var html = '<div style="height: 250px">';
html += render('emoji_popover_content', args);
html += "</div>";
// test to make sure the first emoji is present in the popover
var emoji_key = $(html).find(".emoji-100").attr('title');
assert.equal(emoji_key, ':100:');
global.write_handlebars_output("emoji_popover_content", html);
}());
(function group_pms() {
var args = {
"group_pms": [

View File

@ -286,6 +286,13 @@ $(function () {
});
function handle_compose_click(e) {
// Emoji clicks should be handled by their own click handler in popover.js
if ($(e.target).is("#emoji_map") ||
$(e.target).is(".emoji_popover") ||
$(e.target).is(".emoji_popover.inner") ||
$(e.target).is("img.emoji")) {
return;
}
// Don't let clicks in the compose area count as
// "unfocusing" our compose -- in other words, e.g.
// clicking "Press enter to send" should not
@ -418,7 +425,8 @@ $(function () {
// of modals or selecting text (for copy+paste) trigger cancelling.
if (compose.composing() && !$(e.target).is("a") &&
($(e.target).closest(".modal").length === 0) &&
window.getSelection().toString() === "") {
window.getSelection().toString() === "" &&
($(e.target).closest('#emoji_map').length === 0)) {
compose.cancel();
}
});

View File

@ -45,6 +45,13 @@ exports.update_emojis = function update_emojis(realm_emojis) {
});
};
exports.initialize = function initialize () {
// Load the sprite image in the background so that the browser
// can cache it for later use.
var sprite = new Image();
sprite.src = '/static/third/gemoji/sprite.png';
};
exports.update_emojis(page_params.realm_emoji);
return exports;

View File

@ -195,6 +195,11 @@ function process_hotkey(e) {
// Process hotkeys specially when in an input, select, textarea, or send button
if ($('input:focus,select:focus,textarea:focus,#compose-send-button:focus').length > 0) {
if (event_name === 'escape') {
// emoji window should trap escape before it is able to close the compose box
if ($('.emoji_popover').css('display') === 'inline-block') {
popovers.hide_emoji_map_popover();
return;
}
// If one of our typeaheads is open, do nothing so that the Esc
// will go to close it
if ($("#subject").data().typeahead.shown ||
@ -270,7 +275,9 @@ function process_hotkey(e) {
narrow.by('is', 'private', opts);
});
case 'escape': // Esc: close actions popup, cancel compose, clear a find, or un-narrow
if (popovers.any_active()) {
if ($('.emoji_popover').css('display') === 'inline-block') {
popovers.hide_emoji_map_popover();
} else if (popovers.any_active()) {
popovers.hide_all();
} else if (compose.composing()) {
compose.cancel();

View File

@ -4,6 +4,7 @@ var exports = {};
var current_actions_popover_elem;
var current_message_info_popover_elem;
var emoji_map_is_open = false;
var userlist_placement = "right";
@ -209,6 +210,14 @@ function topic_sidebar_popped() {
return current_topic_sidebar_elem !== undefined;
}
exports.hide_emoji_map_popover = function () {
if (emoji_map_is_open) {
$('.emoji_popover').css('display', 'none');
$('.drag').css('display', 'none');
emoji_map_is_open = false;
}
};
exports.hide_stream_sidebar_popover = function () {
if (stream_sidebar_popped()) {
$(current_stream_sidebar_elem).popover("destroy");
@ -237,6 +246,21 @@ exports.hide_user_sidebar_popover = function () {
}
};
function render_emoji_popover() {
var content = templates.render('emoji_popover_content', {
emoji_list: emoji.emojis_name_to_css_class
});
$('.emoji_popover').append(content);
$('.drag').show();
$('.emoji_popover').css('display', 'inline-block');
$("#new_message_content").focus();
emoji_map_is_open = true;
}
exports.register_click_handlers = function () {
$("#main_div").on("click", ".actions_hover", function (e) {
var row = $(this).closest(".message_row");
@ -250,6 +274,79 @@ exports.register_click_handlers = function () {
show_message_info_popover(this, rows.id(row));
});
var isDragging=false;
var top_border = $('#floating_recipient_bar').position().top + $('#floating_recipient_bar').height();
var total_height;
var emoji_popover_height;
var emoji_popover_elem;
var previous_mouse_position;
var compose_box_padding;
var emoji_height = 25;
$("body").on("mouseover", ".emoji_popover", function (e) {
total_height = $('body > .app').outerHeight() - top_border - 70;
if (total_height <= 300) {
// don't allow dragging if the viewport is small enough that it
// would obscure everything to drag the emojis
$('.drag').hide();
} else {
$('.drag').show();
}
});
$("body").on("mousedown", ".drag", function (e) {
// leave a little extra padding for the message box so that it doesn't get too big
total_height = $('body > .app').outerHeight() - top_border - 70;
isDragging = true;
previous_mouse_position = e.pageY;
emoji_popover_elem = $(".emoji_popover");
emoji_popover_height = emoji_popover_elem.height();
compose_box_padding = $('#compose').height() - emoji_popover_height;
});
$("body").on("mousemove", function (e) {
e.preventDefault();
if (isDragging) {
var new_height = emoji_popover_height + (previous_mouse_position - e.pageY);
if (new_height + compose_box_padding > total_height) {
emoji_popover_elem.height(total_height - compose_box_padding);
} else if (new_height < emoji_height) {
emoji_popover_elem.height(emoji_height);
} else {
emoji_popover_elem.height(new_height);
}
}
});
$("body").on("mouseup", function (e) {
isDragging = false;
emoji_popover_height = null;
});
$("body").on("click", ".emoji_popover", function (e) {
e.stopPropagation();
});
$(".emoji_popover").on("click", ".emoji", function (e) {
var emoji_choice = $(e.target).attr("title");
var textarea = $("#new_message_content");
textarea.val(textarea.val() + " " + emoji_choice);
textarea.focus();
e.stopPropagation();
});
$("#compose").on("click", "#emoji_map", function (e) {
e.preventDefault();
e.stopPropagation();
if (emoji_map_is_open) {
// If the popover is already shown, clicking again should toggle it.
popovers.hide_emoji_map_popover();
return;
}
popovers.hide_all();
render_emoji_popover();
});
$('body').on('click', '.user_popover .narrow_to_private_messages', function (e) {
var email = $(e.target).parents('ul').attr('data-email');
popovers.hide_user_sidebar_popover();
@ -597,7 +694,7 @@ exports.register_click_handlers = function () {
exports.any_active = function () {
// True if any popover (that this module manages) is currently shown.
return popovers.actions_popped() || user_sidebar_popped() || stream_sidebar_popped() || topic_sidebar_popped() || message_info_popped();
return popovers.actions_popped() || user_sidebar_popped() || stream_sidebar_popped() || topic_sidebar_popped() || message_info_popped() || emoji_map_is_open;
};
exports.hide_all = function () {
@ -608,6 +705,7 @@ exports.hide_all = function () {
popovers.hide_user_sidebar_popover();
popovers.hide_userlist_sidebar();
popovers.hide_streamlist_sidebar();
popovers.hide_emoji_map_popover();
};
exports.set_userlist_placement = function (placement) {

View File

@ -396,6 +396,11 @@ $(function () {
}
});
// Override the #compose mousewheel prevention below just for the emoji box
$('.emoji_popover').mousewheel(function (e) {
e.stopPropagation();
});
// Ignore wheel events in the compose area which weren't already handled above.
$('#compose').mousewheel(function (e) {
e.stopPropagation();
@ -527,6 +532,7 @@ $(function () {
hashchange.initialize();
invite.initialize();
activity.initialize();
emoji.initialize();
});

View File

@ -3315,6 +3315,44 @@ li.expanded_private_message {
color: #000;
}
.drag {
display: none;
height: 18px;
width: 100%;
top: 23px;
position: relative;
cursor: ns-resize;
}
.emoji_popover {
display: none;
position: relative;
margin-top: 10px;
bottom: 0px;
z-index: 1010;
width: 100%;
height: 60px;
padding: 1px;
text-align: center;
background-color: #ffffff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
overflow: hidden;
overflow-y: scroll;
}
.emoji_popover .emoji {
margin: 2px;
box-sizing: border-box;
cursor: pointer;
display: inline-block;
}
.emoji_popover .emoji:active {
border-radius: 5px;
border: 2px white solid;
}
#enter_sends {
margin-top: 0px;
margin-right: 5px;

View File

@ -0,0 +1,5 @@
{{! Contents of the "emoji map" popup }}
{{#each emoji_list}}
<div class="emoji emoji-{{this}}" title= ":{{@key}}:" />
{{/each}}

View File

@ -74,8 +74,12 @@
<div id="markdown_preview_spinner"></div>
<div id="preview_content"></div>
</div>
<div class="drag"></div>
<div class="emoji_popover"></div>
<div id="below-compose-content">
<input type="file" id="file_input" class="notvisible pull-left" multiple />
<a class="message-control-button icon-vector-smile"
id="emoji_map" href="#"></a>
<a class="message-control-button icon-vector-dropbox notdisplayed"
id="attach_dropbox_files" href="#" title="{{ _('Attach files from Dropbox') }}"></a>
<a class="message-control-button icon-vector-paper-clip notdisplayed"

View File

@ -14,6 +14,7 @@ var page_params = {{ page_params }};
{% block customhead %}
<meta name="apple-mobile-web-app-capable" content="yes">
<link href="/static/images/logo/apple-touch-icon-precomposed.png" rel="apple-touch-icon-precomposed">
<link rel="stylesheet" type="text/css" href="/static/third/gemoji/sprite.css" />
<style type="text/css">
#css-loading {