2
0
JC Brand 8 жил өмнө
parent
commit
ca53a8d8ef

+ 1 - 0
CHANGES.md

@@ -2,6 +2,7 @@
 
 ## 3.1.0 (Unreleased)
 
+- Add support for Emojis (uses <a href="https://www.emojione.com/">Emojione</a>).
 - New non-core plugin `converse-singleton` which ensures that no more than
   one chat is visible at any given time. Used in the mobile build:
   `converse-mobile.js` and makes the unread messages counter possible there.

+ 20 - 2
css/converse.css

@@ -1260,6 +1260,9 @@
     -ms-transform: rotate(359deg);
     -o-transform: rotate(359deg);
     transform: rotate(359deg); } }
+  #converse-embedded-chat .emojione,
+  #conversejs .emojione {
+    height: 24px; }
   #converse-embedded-chat .spinner,
   #conversejs .spinner {
     -webkit-animation: spin 2s infinite, linear;
@@ -1739,7 +1742,6 @@
         background: #fff;
         bottom: 100%;
         box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
-        display: none;
         font-size: 12px;
         margin: 0;
         position: absolute;
@@ -1762,6 +1764,16 @@
         #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul,
         #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul {
           left: 0; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-category-picker,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-category-picker {
+            z-index: 100; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-category-picker .picked,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-category-picker .picked {
+              background-color: #DCF9F6; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-picker,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-picker {
+            height: 250px;
+            overflow: scroll; }
           #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li,
           #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li {
             font-size: 14px;
@@ -1956,7 +1968,7 @@
         #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom a:hover {
           color: #206485; }
         #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom.unread-msgs .open-room {
-          max-width: 50%;
+          max-width: 55%;
           width: auto;
           font-weight: bold; }
         #conversejs #controlbox #chatrooms .rooms-list-container dl.rooms-list dd.available-chatroom a.room-info:before {
@@ -2286,16 +2298,22 @@
           #conversejs #converse-roster .roster-contacts dd .open-chat.unread-msgs .contact-name {
             width: 70%; }
         #conversejs #converse-roster .roster-contacts dd .open-chat .msgs-indicator {
+          background-color: #E7A151;
           opacity: 1;
           border-radius: 10%;
           padding: 0 0.2em;
           font-size: 12px; }
         #conversejs #converse-roster .roster-contacts dd .open-chat .contact-name {
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
           padding: 0;
           margin: 0;
           max-width: 80%;
           float: none;
           height: 60px; }
+          #conversejs #converse-roster .roster-contacts dd .open-chat .contact-name.unread-msgs {
+            max-width: 60%; }
         #conversejs #converse-roster .roster-contacts dd .open-chat .avatar {
           float: left;
           display: inline-block;

+ 13 - 1
css/inverse.css

@@ -1260,6 +1260,9 @@
     -ms-transform: rotate(359deg);
     -o-transform: rotate(359deg);
     transform: rotate(359deg); } }
+  #converse-embedded-chat .emojione,
+  #conversejs .emojione {
+    height: 24px; }
   #converse-embedded-chat .spinner,
   #conversejs .spinner {
     -webkit-animation: spin 2s infinite, linear;
@@ -1785,7 +1788,6 @@ body {
         background: #fff;
         bottom: 100%;
         box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
-        display: none;
         font-size: 12px;
         margin: 0;
         position: absolute;
@@ -1808,6 +1810,16 @@ body {
         #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul,
         #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul {
           left: 0; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-category-picker,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-category-picker {
+            z-index: 100; }
+            #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-category-picker .picked,
+            #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-category-picker .picked {
+              background-color: #DCF9F6; }
+          #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-picker,
+          #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul.emoji-picker {
+            height: 250px;
+            overflow: scroll; }
           #converse-embedded-chat .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li,
           #conversejs .chatbox form.sendXMPPMessage .chat-toolbar .toggle-smiley ul li {
             font-size: 16px;

+ 8 - 0
docs/source/configuration.rst

@@ -993,6 +993,14 @@ Notification will be shown in the following cases:
 
 Requires the `src/converse-notification.js` plugin.
 
+show_emojione
+-------------
+* Default: ``false``
+
+Determines whether `Emojione <https://www.emojione.com/>`_ should be used to
+render emojis. The default is not to do this, but to simply let the operating
+system or browser render emoji (if it has support for them).
+
 show_only_online_users
 ----------------------
 

+ 1 - 0
package.json

@@ -40,6 +40,7 @@
     "bootstrap": "^3.3.7",
     "bourbon": "^4.3.2",
     "clean-css-cli": "^4.0.10",
+    "emojione": "^3.0.3",
     "es6-promise": "^4.1.0",
     "eslint": "^3.19.0",
     "eslint-plugin-lodash": "^2.3.3",

+ 10 - 1
sass/_chatbox.scss

@@ -309,7 +309,6 @@
                     background: #fff;
                     bottom: 100%;
                     box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.4);
-                    display: none;
                     font-size: 12px;
                     margin: 0;
                     position: absolute;
@@ -330,6 +329,16 @@
                     color: $link-color;
                     padding-left: 5px;
                     ul {
+                        &.emoji-category-picker {
+                            z-index: 100;
+                            .picked {
+                                background-color: $highlight-color;
+                            }
+                        }
+                        &.emoji-picker {
+                            height: 250px;
+                            overflow: scroll;
+                        }
                         left: 0;
                         li {
                             font-size: $font-size;

+ 4 - 0
sass/_core.scss

@@ -91,6 +91,10 @@
         }
     }
 
+    .emojione {
+        height: $emoji_height;
+    }
+
     .spinner {
         @include animation(spin 2s infinite, linear);
         display: block;

+ 3 - 0
sass/converse/_variables.scss

@@ -47,8 +47,11 @@ $save-button-color: $green !default;
 $chat-textarea-height: 70px !default;
 $send-button-height: 27px !default;
 $send-button-margin: 3px !default;
+
 $message-them-color: $green !default;
 
+$emoji_height : 24px !default;
+
 $roster-height: 194px !default;
 $roster-item-height: 60px !default;
 

+ 3 - 0
sass/inverse/_variables.scss

@@ -47,8 +47,11 @@ $save-button-color: $green !default;
 $chat-textarea-height: 70px !default;
 $send-button-height: 27px !default;
 $send-button-margin: 3px !default;
+
 $message-them-color: $green !default;
 
+$emoji_height : 24px !default;
+
 $roster-height: 194px !default;
 $roster-item-height: 30px !default;
 

+ 5 - 57
spec/chatbox.js

@@ -351,42 +351,22 @@
                         expect($toolbar.children('li.toggle-smiley').length).toBe(1);
                         // Register spies
                         spyOn(view, 'toggleEmoticonMenu').and.callThrough();
-                        spyOn(view, 'insertEmoticon').and.callThrough();
+                        spyOn(view, 'insertEmoticon');
                         view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                         $toolbar.children('li.toggle-smiley').click();
 
-                        expect(view.toggleEmoticonMenu).toHaveBeenCalled();
-                        var $menu = view.$el.find('.toggle-smiley ul');
-                        var $items = $menu.children('li');
-                        expect($menu.is(':visible')).toBeTruthy();
-                        expect($items.length).toBe(13);
-                        expect($($items[0]).children('a').data('emoticon')).toBe(':)');
-                        expect($($items[1]).children('a').data('emoticon')).toBe(';)');
-                        expect($($items[2]).children('a').data('emoticon')).toBe(':D');
-                        expect($($items[3]).children('a').data('emoticon')).toBe(':P');
-                        expect($($items[4]).children('a').data('emoticon')).toBe('8)');
-                        expect($($items[5]).children('a').data('emoticon')).toBe('>:)');
-                        expect($($items[6]).children('a').data('emoticon')).toBe(':S');
-                        expect($($items[7]).children('a').data('emoticon')).toBe(':\\');
-                        expect($($items[8]).children('a').data('emoticon')).toBe('>:(');
-                        expect($($items[9]).children('a').data('emoticon')).toBe(':(');
-                        expect($($items[10]).children('a').data('emoticon')).toBe(':O');
-                        expect($($items[11]).children('a').data('emoticon')).toBe('(^.^)b');
-                        expect($($items[12]).children('a').data('emoticon')).toBe('<3');
+                        var $picker = view.$el.find('.toggle-smiley .emoji-picker-container');
+                        // expect($picker.is(':visible')).toBeTruthy();
+                        // expect(view.toggleEmoticonMenu).toHaveBeenCalled();
+                        var $items = $picker.find('.emoji-picker li');
                         $items.first().click();
-
                         expect(view.insertEmoticon).toHaveBeenCalled();
-                        expect($textarea.val()).toBe(':) ');
                         expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeFalsy();
                         $toolbar.children('li.toggle-smiley').click();
-
                         expect(view.toggleEmoticonMenu).toHaveBeenCalled();
-                        expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeTruthy();
                         view.$el.find('.toggle-smiley ul').children('li').last().click();
-
                         expect(view.insertEmoticon).toHaveBeenCalled();
                         expect(view.$el.find('.toggle-smiley ul').is(':visible')).toBeFalsy();
-                        expect($textarea.val()).toBe(':) <3 ');
                         done();
                     });
                 }));
@@ -411,11 +391,7 @@
                         view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
 
                         $toolbar.children('li.toggle-otr').click();
-
                         expect(view.toggleOTRMenu).toHaveBeenCalled();
-                        var $menu = view.$el.find('.toggle-otr ul');
-                        expect($menu.is(':visible')).toBeTruthy();
-                        expect($menu.children('li').length).toBe(2);
                         done();
                     });
                 }));
@@ -1139,34 +1115,6 @@
                     expect(msg.html()).toEqual('&lt;p&gt;This message contains &lt;em&gt;some&lt;/em&gt; &lt;b&gt;markup&lt;/b&gt;&lt;/p&gt;');
                 }));
 
-                it("should display emoticons correctly", mock.initConverse(function (_converse) {
-                    test_utils.createContacts(_converse, 'current');
-                    test_utils.openControlBox();
-                    test_utils.openContactsPanel(_converse);
-
-                    var contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
-                    test_utils.openChatBoxFor(_converse, contact_jid);
-                    var view = _converse.chatboxviews.get(contact_jid);
-                    var messages = [':)', ';)', ':D', ':P', '8)', '>:)', ':S', ':\\', '>:(', ':(', ':O', '(^.^)b', '<3'];
-                    var emoticons = [
-                        '<span class="emoticon icon-smiley"></span>', '<span class="emoticon icon-wink"></span>',
-                        '<span class="emoticon icon-grin"></span>', '<span class="emoticon icon-tongue"></span>',
-                        '<span class="emoticon icon-cool"></span>', '<span class="emoticon icon-evil"></span>',
-                        '<span class="emoticon icon-confused"></span>', '<span class="emoticon icon-wondering"></span>',
-                        '<span class="emoticon icon-angry"></span>', '<span class="emoticon icon-sad"></span>',
-                        '<span class="emoticon icon-shocked"></span>', '<span class="emoticon icon-thumbs-up"></span>',
-                        '<span class="emoticon icon-heart"></span>'
-                        ];
-                    spyOn(view, 'sendMessage').and.callThrough();
-                    for (var i = 0; i < messages.length; i++) {
-                        var message = messages[i];
-                        test_utils.sendMessage(view, message);
-                        expect(view.sendMessage).toHaveBeenCalled();
-                        var msg = view.$el.find('.chat-content').find('.chat-message').last().find('.chat-msg-content');
-                        expect(msg.html()).toEqual(emoticons[i]);
-                    }
-                }));
-
                 it("can contain hyperlinks, which will be clickable", mock.initConverse(function (_converse) {
                     test_utils.createContacts(_converse, 'current');
                     test_utils.openControlBox();

+ 3 - 1
src/config.js

@@ -21,6 +21,7 @@ require.config({
         "backbone.noconflict":      "src/backbone.noconflict",
         "backbone.browserStorage":  "node_modules/backbone.browserStorage/backbone.browserStorage",
         "backbone.overview":        "node_modules/backbone.overview/backbone.overview",
+        "emojione":                 "node_modules/emojione/lib/js/emojione",
         "eventemitter":             "node_modules/otr/build/dep/eventemitter",
         "es6-promise":              "node_modules/es6-promise/dist/es6-promise",
         "jquery":                   "node_modules/jquery/dist/jquery",
@@ -136,6 +137,7 @@ require.config({
 
     // define module dependencies for modules not using define
     shim: {
-        'awesomplete':          { exports: 'Awesomplete' }
+        'awesomplete':          { exports: 'Awesomplete'},
+        'emojione':             { exports: 'emojione'},
     }
 });

+ 98 - 14
src/converse-chatview.js

@@ -9,9 +9,11 @@
 (function (root, factory) {
     define([
             "converse-core",
+            "emojione",
             "tpl!chatbox",
             "tpl!new_day",
             "tpl!action",
+            "tpl!emojis",
             "tpl!message",
             "tpl!help_message",
             "tpl!toolbar",
@@ -20,9 +22,11 @@
     ], factory);
 }(this, function (
             converse,
+            emojione,
             tpl_chatbox,
             tpl_new_day,
             tpl_action,
+            tpl_emojis,
             tpl_message,
             tpl_help_message,
             tpl_toolbar,
@@ -43,7 +47,6 @@
         FORWARD_SLASH: 47
     };
 
-
     converse.plugins.add('converse-chatview', {
 
         overrides: {
@@ -52,6 +55,18 @@
             // relevant objects or classes.
             //
             // New functions which don't exist yet can also be added.
+            //
+            registerGlobalEventHandlers: function () {
+                this.__super__.registerGlobalEventHandlers();
+                document.addEventListener('click', function () {
+                    if ($('.toggle-smiley ul').is(':visible')) {
+                        _.each(
+                            document.querySelectorAll('.toggle-smiley .emoji-picker-container'),
+                            utils.hideElement
+                        );
+                    }
+                });
+            },
 
             ChatBoxViews: {
                 onChatBoxAdded: function (item) {
@@ -68,7 +83,6 @@
             }
         },
 
-
         initialize: function () {
             /* The initialize function gets called as soon as the plugin is
              * loaded by converse.js's plugin machinery.
@@ -78,7 +92,9 @@
 
             this.updateSettings({
                 chatview_avatar_height: 32,
-                chatview_avatar_width: 32,
+                chatview_avatartrue: 32,
+                show_emojione: false, // By default, use native emojis.
+                emojione_path: 'https://cdn.jsdelivr.net/emojione/assets/' + emojione.emojiVersion + '/png/' + emojione.emojiSize + '/',
                 show_toolbar: true,
                 time_format: 'HH:mm',
                 visible_toolbar_buttons: {
@@ -88,15 +104,65 @@
                 },
             });
 
+            if (_converse.show_emojione) {
+                // If using Emojione, we also convert ascii smileys into emoji.
+                emojione.ascii = true;
+                emojione.imagePathPNG = _converse.emojione_path
+            }
+
             var onWindowStateChanged = function (data) {
                 var state = data.state;
                 _converse.chatboxviews.each(function (chatboxview) {
                     chatboxview.onWindowStateChanged(state);
                 })
             };
-
             _converse.api.listen.on('windowStateChanged', onWindowStateChanged);
 
+
+            _converse.EmojiPicker = Backbone.Model.extend({ 
+                defaults: {
+                    'current_category': 'people'
+                }
+            });
+
+            _converse.EmojiPickerView = Backbone.View.extend({
+                className: 'emoji-picker-container hidden',
+                events: {
+                    'click .emoji-category-picker li a': 'chooseCategory',
+                },
+
+                initialize: function () {
+                    this.model.on('change', this.render, this);
+                },
+
+                render: function () {
+                    var emojis_by_category = utils.marshallEmojis(emojione);
+                    var converter;
+                    if (_converse.show_emojione) {
+                        converter = emojione.toImage
+                    } else {
+                        converter = emojione.shortnameToUnicode
+                    }
+                    var emojis_html = tpl_emojis(
+                        _.extend(
+                            this.model.toJSON(), {
+                                'emojis_by_category': emojis_by_category,
+                                'emojione': emojione,
+                                'converter': converter
+                            }
+                        ));
+                    this.el.innerHTML = emojis_html;
+                    return this;
+                },
+
+                chooseCategory: function (ev) {
+                    ev.preventDefault();
+                    ev.stopPropagation();
+                    var category = ev.target.parentElement.getAttribute("data-category").trim();
+                    this.model.set({'current_category': category});
+                }
+            });
+
             _converse.ChatBoxView = Backbone.View.extend({
                 length: 200,
                 tagName: 'div',
@@ -108,13 +174,15 @@
                     'keypress .chat-textarea': 'keyPressed',
                     'click .send-button': 'onSendButtonClicked',
                     'click .toggle-smiley': 'toggleEmoticonMenu',
-                    'click .toggle-smiley ul li': 'insertEmoticon',
+                    'click .toggle-smiley ul.emoji-picker li': 'insertEmoticon',
                     'click .toggle-clear': 'clearMessages',
                     'click .toggle-call': 'toggleCall',
                     'click .new-msgs-indicator': 'viewUnreadMessages'
                 },
 
                 initialize: function () {
+                    this.emoji_picker_view = new _converse.EmojiPickerView({model: new _converse.EmojiPicker() });
+
                     this.model.messages.on('add', this.onMessageAdded, this);
                     this.model.on('show', this.show, this);
                     this.model.on('destroy', this.hide, this);
@@ -374,7 +442,10 @@
                     $msg.find('.chat-msg-content').first()
                         .text(text)
                         .addHyperlinks()
-                        .addEmoticons(_converse.visible_toolbar_buttons.emoticons);
+                        .addEmoticons(
+                            _converse,
+                            emojione,
+                            _converse.visible_toolbar_buttons.emoticons);
                     return $msg;
                 },
 
@@ -642,15 +713,24 @@
 
                 insertEmoticon: function (ev) {
                     ev.stopPropagation();
-                    this.$el.find('.toggle-smiley ul').slideToggle(200);
-                    var $target = $(ev.target);
-                    $target = $target.is('a') ? $target : $target.children('a');
-                    this.insertIntoTextArea($target.data('emoticon'));
+                    this.toggleEmoticonMenu();
+                    var target;
+                    if (ev.target.nodeName === 'IMG') {
+                        target = ev.target.parentElement;
+                    } else {
+                        target = ev.target;
+                    }
+                    this.insertIntoTextArea(
+                        emojione.shortnameToUnicode(
+                            target.getAttribute('data-emoticon')
+                        ));
                 },
 
                 toggleEmoticonMenu: function (ev) {
-                    ev.stopPropagation();
-                    this.$el.find('.toggle-smiley ul').slideToggle(200);
+                    if (!_.isUndefined(ev)) {
+                        ev.stopPropagation();
+                    }
+                    utils.toggleElement(this.emoji_picker_view.el);
                 },
 
                 toggleCall: function (ev) {
@@ -726,11 +806,15 @@
                 renderToolbar: function (toolbar, options) {
                     if (!_converse.show_toolbar) { return; }
                     toolbar = toolbar || tpl_toolbar;
-                    options = _.extend(
+                    options = _.assign(
                         this.model.toJSON(),
                         this.getToolbarOptions(options || {})
                     );
-                    this.$el.find('.chat-toolbar').html(toolbar(options));
+                    this.el.querySelector('.chat-toolbar').innerHTML = toolbar(options);
+
+                    var toggle = this.el.querySelector('.toggle-smiley');
+                    toggle.innerHTML = '';
+                    toggle.appendChild(this.emoji_picker_view.render().el);
                     return this;
                 },
 

+ 2 - 0
src/converse-muc.js

@@ -449,6 +449,8 @@
                     this.model.on('change:description', this.renderHeading, this);
                     this.model.on('change:name', this.renderHeading, this);
 
+                    this.emoji_picker_view = new _converse.EmojiPickerView({model: new _converse.EmojiPicker() });
+
                     this.createOccupantsView();
                     this.render().insertIntoDOM();
                     this.registerHandlers();

+ 6 - 9
src/converse-otr.js

@@ -58,12 +58,9 @@
  
             registerGlobalEventHandlers: function () {
                 this.__super__.registerGlobalEventHandlers();
-                $(document).click(function () {
+                document.addEventListener('click', function () {
                     if ($('.toggle-otr ul').is(':visible')) {
-                        $('.toggle-otr ul', this).slideUp();
-                    }
-                    if ($('.toggle-smiley ul').is(':visible')) {
-                        $('.toggle-smiley ul', this).slideUp();
+                        _.each($('.toggle-otr ul', this), utils.hideElement);
                     }
                 });
             },
@@ -400,7 +397,7 @@
 
                 toggleOTRMenu: function (ev) {
                     ev.stopPropagation();
-                    this.$el.find('.toggle-otr ul').slideToggle(200);
+                    utils.toggleElement(this.el.querySelector('.toggle-otr ul'));
                 },
                 
                 getOTRTooltip: function () {
@@ -444,9 +441,9 @@
                     });
                     this.__super__.renderToolbar.apply(this, arguments);
                     this.$el.find('.chat-toolbar').append(
-                            tpl_toolbar_otr(
-                                _.extend(this.model.toJSON(), options || {})
-                            ));
+                        tpl_toolbar_otr(
+                            _.extend(this.model.toJSON(), options || {})
+                        ));
                     return this;
                 }
             }

+ 1 - 15
src/templates/chatroom_toolbar.html

@@ -1,20 +1,6 @@
 {[ if (show_emoticons)  { ]}
     <li class="toggle-smiley icon-happy" title="{{{label_insert_smiley}}}">
-        <ul>
-            <li><a class="icon-smiley" href="#" data-emoticon=":)"></a></li>
-            <li><a class="icon-wink" href="#" data-emoticon=";)"></a></li>
-            <li><a class="icon-grin" href="#" data-emoticon=":D"></a></li>
-            <li><a class="icon-tongue" href="#" data-emoticon=":P"></a></li>
-            <li><a class="icon-cool" href="#" data-emoticon="8)"></a></li>
-            <li><a class="icon-evil" href="#" data-emoticon=">:)"></a></li>
-            <li><a class="icon-confused" href="#" data-emoticon=":S"></a></li>
-            <li><a class="icon-wondering" href="#" data-emoticon=":\"></a></li>
-            <li><a class="icon-angry" href="#" data-emoticon=">:("></a></li>
-            <li><a class="icon-sad" href="#" data-emoticon=":("></a></li>
-            <li><a class="icon-shocked" href="#" data-emoticon=":O"></a></li>
-            <li><a class="icon-thumbs-up" href="#" data-emoticon="(^.^)b"></a></li>
-            <li><a class="icon-heart" href="#" data-emoticon="<3"></a></li>
-        </ul>
+        <ul class="emoji-picker"></ul>
     </li>
 {[ } ]}
 {[ if (show_call_button)  { ]}

+ 1 - 17
src/templates/toolbar.html

@@ -1,21 +1,5 @@
 {[ if (show_emoticons)  { ]}
-    <li class="toggle-smiley icon-happy" title="{{{label_insert_smiley}}}">
-        <ul>
-            <li><a class="icon-smiley" href="#" data-emoticon=":)"></a></li>
-            <li><a class="icon-wink" href="#" data-emoticon=";)"></a></li>
-            <li><a class="icon-grin" href="#" data-emoticon=":D"></a></li>
-            <li><a class="icon-tongue" href="#" data-emoticon=":P"></a></li>
-            <li><a class="icon-cool" href="#" data-emoticon="8)"></a></li>
-            <li><a class="icon-evil" href="#" data-emoticon=">:)"></a></li>
-            <li><a class="icon-confused" href="#" data-emoticon=":S"></a></li>
-            <li><a class="icon-wondering" href="#" data-emoticon=":\"></a></li>
-            <li><a class="icon-angry" href="#" data-emoticon=">:("></a></li>
-            <li><a class="icon-sad" href="#" data-emoticon=":("></a></li>
-            <li><a class="icon-shocked" href="#" data-emoticon=":O"></a></li>
-            <li><a class="icon-thumbs-up" href="#" data-emoticon="(^.^)b"></a></li>
-            <li><a class="icon-heart" href="#" data-emoticon="<3"></a></li>
-        </ul>
-    </li>
+    <li class="toggle-smiley icon-happy" title="{{{label_insert_smiley}}}"></li>
 {[ } ]}
 {[ if (show_call_button)  { ]}
 <li class="toggle-call"><a class="icon-phone" title="{{{label_start_call}}}"></a></li>

+ 1 - 1
src/templates/toolbar_otr.html

@@ -13,7 +13,7 @@
         {[ if (otr_status == FINISHED) { ]}
             <span class="icon-unlocked"></span>
         {[ } ]}
-        <ul>
+        <ul class="hidden">
             {[ if (otr_status == UNENCRYPTED) { ]}
                <li><a class="start-otr" href="#">{{{label_start_encrypted_conversation}}}</a></li>
             {[ } ]}

+ 55 - 30
src/utils.js

@@ -115,7 +115,10 @@
                 _.forEach(list, function (url) {
                     isImage(unescapeHTML(url)).then(function (img) {
                         img.className = 'chat-image';
-                        throttledHTML(obj.querySelector('a'), img.outerHTML);
+                        var a = obj.querySelector('a');
+                        if (!_.isNull(a)) {
+                            throttledHTML(a, img.outerHTML);
+                        }
                     });
                 });
             });
@@ -123,36 +126,12 @@
         return this;
     };
 
-    $.fn.addEmoticons = function (allowed) {
+    $.fn.addEmoticons = function (_converse, emojione, allowed) {
         if (allowed) {
-            if (this.length > 0) {
-                this.each(function (i, obj) {
-                    var text = $(obj).html();
-                    text = text.replace(/&gt;:\)/g, '<span class="emoticon icon-evil"></span>');
-                    text = text.replace(/:\)/g, '<span class="emoticon icon-smiley"></span>');
-                    text = text.replace(/:\-\)/g, '<span class="emoticon icon-smiley"></span>');
-                    text = text.replace(/;\)/g, '<span class="emoticon icon-wink"></span>');
-                    text = text.replace(/;\-\)/g, '<span class="emoticon icon-wink"></span>');
-                    text = text.replace(/:D/g, '<span class="emoticon icon-grin"></span>');
-                    text = text.replace(/:\-D/g, '<span class="emoticon icon-grin"></span>');
-                    text = text.replace(/:P/g, '<span class="emoticon icon-tongue"></span>');
-                    text = text.replace(/:\-P/g, '<span class="emoticon icon-tongue"></span>');
-                    text = text.replace(/:p/g, '<span class="emoticon icon-tongue"></span>');
-                    text = text.replace(/:\-p/g, '<span class="emoticon icon-tongue"></span>');
-                    text = text.replace(/8\)/g, '<span class="emoticon icon-cool"></span>');
-                    text = text.replace(/:S/g, '<span class="emoticon icon-confused"></span>');
-                    text = text.replace(/:\\/g, '<span class="emoticon icon-wondering"></span>');
-                    text = text.replace(/:\/ /g, '<span class="emoticon icon-wondering"></span>');
-                    text = text.replace(/&gt;:\(/g, '<span class="emoticon icon-angry"></span>');
-                    text = text.replace(/:\(/g, '<span class="emoticon icon-sad"></span>');
-                    text = text.replace(/:\-\(/g, '<span class="emoticon icon-sad"></span>');
-                    text = text.replace(/:O/g, '<span class="emoticon icon-shocked"></span>');
-                    text = text.replace(/:\-O/g, '<span class="emoticon icon-shocked"></span>');
-                    text = text.replace(/\=\-O/g, '<span class="emoticon icon-shocked"></span>');
-                    text = text.replace(/\(\^.\^\)b/g, '<span class="emoticon icon-thumbs-up"></span>');
-                    text = text.replace(/&lt;3/g, '<span class="emoticon icon-heart"></span>');
-                    $(obj).html(text);
-                });
+            if (_converse.show_emojione) {
+                this.html(emojione.toImage(this.text()));
+            } else {
+                this.html(emojione.shortnameToUnicode(this.text()));
             }
         }
         return this;
@@ -203,6 +182,19 @@
             }
         },
 
+        hideElement: function (el) {
+            el.classList.add('hidden');
+        },
+
+        toggleElement: function (el) {
+            if (_.includes(el.classList, 'hidden')) {
+                // XXX: use fadeIn?
+                el.classList.remove('hidden');
+            } else {
+                this.hideElement (el);
+            }
+        },
+
         fadeIn: function (el, callback) {
             if ($.fx.off) {
                 el.classList.remove('hidden');
@@ -522,5 +514,38 @@
         frag = tmp = null;
     }
 
+    utils.marshallEmojis = function (emojione) {
+        /* Return a dict of emojis with the categories as keys and
+         * lists of emojis in that category as values.
+         */
+        if (_.isUndefined(this.emojis_by_category)) {
+            var emojis = _.values(_.mapValues(emojione.emojioneList, function (value, key, o) {
+                value._shortname = key;
+                return value
+            }));
+            var tones = [':tone1:', ':tone2:', ':tone3:', ':tone4:', ':tone5:'];
+            var categories = _.uniq(_.map(emojis, _.partial(_.get, _, 'category')));
+            var emojis_by_category = {};
+            _.forEach(categories, function (cat) {
+                var list = _.sortBy(_.filter(emojis, ['category', cat]), ['uc_base']);
+                list = _.filter(list, function (item) {
+                    return !_.includes(tones, item._shortname);
+                });
+                if (cat === 'people') {
+                    var idx = _.findIndex(list, ['uc_base', '1f600']);
+                    list = _.union(_.slice(list, idx), _.slice(list, 0, idx+1));
+                } else if (cat === 'activity') {
+                    list = _.union(_.slice(list, 27-1), _.slice(list, 0, 27));
+                } else if (cat === 'objects') {
+                    list = _.union(_.slice(list, 24-1), _.slice(list, 0, 24));
+                } else if (cat === 'travel') {
+                    list = _.union(_.slice(list, 17-1), _.slice(list, 0, 17));
+                }
+                emojis_by_category[cat] = list;
+            });
+            this.emojis_by_category = emojis_by_category;
+        }
+        return this.emojis_by_category;
+    }
     return utils;
 }));