From 4bf908c2d6f54fefd718fbb1d85182734db411db Mon Sep 17 00:00:00 2001 From: Ilona Brand Date: Thu, 15 Oct 2015 16:34:30 -0400 Subject: [PATCH] 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. --- frontend_tests/node_tests/templates.js | 20 ++++ static/js/click_handlers.js | 10 +- static/js/emoji.js | 7 ++ static/js/hotkey.js | 9 +- static/js/popovers.js | 100 +++++++++++++++++- static/js/ui.js | 6 ++ static/styles/zulip.css | 38 +++++++ .../emoji_popover_content.handlebars | 5 + templates/zerver/compose.html | 4 + templates/zerver/index.html | 1 + 10 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 static/templates/emoji_popover_content.handlebars diff --git a/frontend_tests/node_tests/templates.js b/frontend_tests/node_tests/templates.js index 255434732f..a152eb1782 100644 --- a/frontend_tests/node_tests/templates.js +++ b/frontend_tests/node_tests/templates.js @@ -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 = '
'; + html += render('emoji_popover_content', args); + html += "
"; + // 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": [ diff --git a/static/js/click_handlers.js b/static/js/click_handlers.js index 18960ae960..3356051052 100644 --- a/static/js/click_handlers.js +++ b/static/js/click_handlers.js @@ -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(); } }); diff --git a/static/js/emoji.js b/static/js/emoji.js index e837dd8ea9..6bec5d0478 100644 --- a/static/js/emoji.js +++ b/static/js/emoji.js @@ -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; diff --git a/static/js/hotkey.js b/static/js/hotkey.js index e67173c82f..e992cbd32e 100644 --- a/static/js/hotkey.js +++ b/static/js/hotkey.js @@ -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(); diff --git a/static/js/popovers.js b/static/js/popovers.js index 64e5529775..b77434f00a 100644 --- a/static/js/popovers.js +++ b/static/js/popovers.js @@ -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) { diff --git a/static/js/ui.js b/static/js/ui.js index 3e6a63d5eb..c3335071cf 100644 --- a/static/js/ui.js +++ b/static/js/ui.js @@ -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(); }); diff --git a/static/styles/zulip.css b/static/styles/zulip.css index 331cece35f..70c69939ca 100644 --- a/static/styles/zulip.css +++ b/static/styles/zulip.css @@ -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; diff --git a/static/templates/emoji_popover_content.handlebars b/static/templates/emoji_popover_content.handlebars new file mode 100644 index 0000000000..78797318a1 --- /dev/null +++ b/static/templates/emoji_popover_content.handlebars @@ -0,0 +1,5 @@ +{{! Contents of the "emoji map" popup }} +{{#each emoji_list}} +
+{{/each}} + diff --git a/templates/zerver/compose.html b/templates/zerver/compose.html index 82359951f5..5c071d865e 100644 --- a/templates/zerver/compose.html +++ b/templates/zerver/compose.html @@ -74,8 +74,12 @@
+
+
+ +