Browse Source

Move Views associated to emojis into a new plugin

JC Brand 6 years ago
parent
commit
1cf9a936a4

+ 4 - 0
sass/_core.scss

@@ -283,6 +283,10 @@ body.converse-fullscreen {
         margin: 0;
     }
 
+    a {
+      cursor: pointer;
+    }
+
     a, a:visited, a:not([href]):not([tabindex]) {
         text-decoration: none;
         color: var(--link-color);

+ 12 - 11
sass/_emoji.scss

@@ -16,6 +16,8 @@
                 padding-bottom: 0;
                 background-color: var(--chat-head-color);
                 .emoji-picker__container {
+                    display: flex;
+                    flex-direction: column;
                     overflow-y: hidden;
                     background: white;
                     .emoji-picker__lists {
@@ -29,13 +31,18 @@
                         flex-direction: column;
                     }
                     .emoji-skintone-picker {
-                        padding: 0.25em 0;
+                        display: flex;
+                        label {
+                            margin: 0;
+                            padding: 0 0.5em;
+                            white-space: nowrap;
+                            font-size: var(--font-size);
+                            color: var(--heading-color);
+                        }
+                        padding: 0.5em 0;
                         background-color: var(--chat-head-color);
                         width: auto;
-                        font-size: var(--font-size-huge);
-                        &:hover {
-                            background-color: var(--highlight-color);
-                        }
+                        font-size: var(--font-size-large);
                     }
                 }
                 .emoji-picker {
@@ -114,9 +121,6 @@
 #conversejs.converse-overlayed {
     .emoji-picker__container {
         height: var(--embedded-emoji-picker-height);
-        .emoji-picker__lists {
-            height: calc(var(--embedded-emoji-picker-height) - 4em;
-        }
     }
 }
 
@@ -152,9 +156,6 @@
 #conversejs.converse-fullscreen {
     .emoji-picker__container {
         height: var(--fullpage-emoji-picker-height);
-        .emoji-picker__lists {
-            height: calc(var(--fullpage-emoji-picker-height) - 4em;
-        }
     }
     .chatbox {
         .sendXMPPMessage {

+ 3 - 3
sass/_variables.scss

@@ -153,7 +153,7 @@ $mobile_portrait_length: 480px !default;
     --occupants-border-bottom: 1px solid lightgrey;
     --occupants-features-display: block;
 
-    --embedded-emoji-picker-height: 200px;
+    --embedded-emoji-picker-height: 300px;
 
     --avatar-border-radius: 10%;
     --avatar-border: 1px solid lightgrey;
@@ -162,14 +162,14 @@ $mobile_portrait_length: 480px !default;
     --fullpage-chat-head-height: 62px;
     --fullpage-chat-height: calc(var(--vh, 1vh) * 100);
     --fullpage-chat-width: 100%;
-    --fullpage-emoji-picker-height: 200px;
+    --fullpage-emoji-picker-height: 300px;
     --fullpage-max-chat-textarea-height: 15em;
 
     --overlayed-chat-head-height: 55px;
     --overlayed-chat-height: 450px;
     --overlayed-chat-width: 250px;
     --overlayed-chatbox-hover-height: 1em;
-    --overlayed-emoji-picker-height: 100px;
+    --overlayed-emoji-picker-height: 150px;
     --overlayed-max-chat-textarea-height: 200px;
     --overlayed-badge-color: #818479; // $gray-color
 

+ 7 - 11
spec/chatbox.js

@@ -451,18 +451,15 @@
                     const view = _converse.chatboxviews.get(contact_jid);
                     const toolbar = view.el.querySelector('ul.chat-toolbar');
                     expect(toolbar.querySelectorAll('li.toggle-smiley').length).toBe(1);
-                    // Register spies
                     spyOn(view, 'toggleEmojiMenu').and.callThrough();
-                    spyOn(view, 'insertEmoji').and.callThrough();
 
                     view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
                     toolbar.querySelector('li.toggle-smiley').click();
 
-                    await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container')));
-                    const picker = view.el.querySelector('.toggle-smiley .emoji-picker-container');
-                    const items = picker.querySelectorAll('.emoji-picker li');
-                    items[0].click()
-                    expect(view.insertEmoji).toHaveBeenCalled();
+                    await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker__container')));
+                    const picker = await u.waitUntil(() => view.el.querySelector('.toggle-smiley .emoji-picker__container'));
+                    const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji'));
+                    item.click()
                     expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':grinning: ');
                     toolbar.querySelector('li.toggle-smiley').click(); // Close the panel again
                     done();
@@ -485,10 +482,9 @@
                     expect(counter.textContent).toBe('188');
 
                     toolbar.querySelector('li.toggle-smiley').click();
-                    await u.waitUntil(() => u.isVisible(view.el.querySelector('.toggle-smiley .emoji-picker-container')));
-                    var picker = view.el.querySelector('.toggle-smiley .emoji-picker-container');
-                    var items = picker.querySelectorAll('.emoji-picker li');
-                    items[0].click()
+                    const picker = await u.waitUntil(() => view.el.querySelector('.toggle-smiley .emoji-picker__container'));
+                    const item = await u.waitUntil(() => picker.querySelector('.emoji-picker li.insert-emoji'));
+                    item.click()
                     expect(counter.textContent).toBe('177');
 
                     const textarea = view.el.querySelector('.chat-textarea');

+ 6 - 148
src/converse-chatview.js

@@ -7,20 +7,16 @@
 /**
  * @module converse-chatview
  */
-import "@converse/headless/converse-emoji";
 import "backbone.nativeview";
 import "converse-chatboxviews";
 import "converse-message-view";
 import "converse-modal";
-import * as twemoji from "twemoji";
 import BrowserStorage from "backbone.browserStorage";
 import { Overview } from "backbone.overview";
-import bootstrap from "bootstrap.native";
 import converse from "@converse/headless/converse-core";
 import tpl_chatbox from "templates/chatbox.html";
 import tpl_chatbox_head from "templates/chatbox_head.html";
 import tpl_chatbox_message_form from "templates/chatbox_message_form.html";
-import tpl_emojis from "templates/emojis.html";
 import tpl_error_message from "templates/error_message.html";
 import tpl_help_message from "templates/help_message.html";
 import tpl_info from "templates/info.html";
@@ -49,7 +45,6 @@ converse.plugins.add('converse-chatview', {
      * NB: These plugins need to have already been loaded via require.js.
      */
     dependencies: [
-        "converse-emoji",
         "converse-chatboxviews",
         "converse-disco",
         "converse-message-view",
@@ -65,20 +60,16 @@ converse.plugins.add('converse-chatview', {
 
         _converse.api.settings.update({
             'auto_focus': true,
-            'emoji_image_path': twemoji.default.base,
             'message_limit': 0,
             'show_send_button': false,
             'show_toolbar': true,
             'time_format': 'HH:mm',
-            'use_system_emojis': true,
             'visible_toolbar_buttons': {
                 'call': false,
                 'clear': true,
-                'emoji': true,
                 'spoiler': true
             },
         });
-        twemoji.default.base = _converse.emoji_image_path;
 
         function onWindowStateChanged (data) {
             if (_converse.chatboxviews) {
@@ -92,87 +83,6 @@ converse.plugins.add('converse-chatview', {
         _converse.api.listen.on('windowStateChanged', onWindowStateChanged);
 
 
-        _converse.EmojiPicker = Backbone.Model.extend({
-            defaults: {
-                'current_category': 'people',
-                'current_skintone': '',
-                'scroll_position': 0
-            }
-        });
-
-
-        _converse.EmojiPickerView = Backbone.VDOMView.extend({
-            className: 'emoji-picker-container',
-            events: {
-                'click .emoji-category-picker li.emoji-category': 'chooseCategory',
-                'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone'
-            },
-
-            initialize () {
-                this.model.on('change:current_skintone', this.render, this);
-                this.model.on('change:current_category', this.render, this);
-                _converse.api.trigger('emojiPickerViewInitialized');
-            },
-
-            toHTML () {
-                const html = tpl_emojis(
-                    Object.assign(
-                        this.model.toJSON(), {
-                            '_': _,
-                            '_converse': _converse,
-                            'emoji_categories': _converse.emoji_categories,
-                            'emojis_by_category': u.getEmojisByCategory(),
-                            'shouldBeHidden': this.shouldBeHidden,
-                            'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'],
-                            'toned_emojis': _converse.emojis.toned,
-                            'transform': u.getEmojiRenderer()
-                        }
-                    )
-                );
-                return html;
-            },
-
-            shouldBeHidden (shortname, current_skintone, toned_emojis) {
-                /* Helper method for the template which decides whether an
-                 * emoji should be hidden, based on which skin tone is
-                 * currently being applied.
-                 */
-                if (_.includes(shortname, '_tone')) {
-                    if (!current_skintone || !_.includes(shortname, current_skintone)) {
-                        return true;
-                    }
-                } else {
-                    if (current_skintone && _.includes(toned_emojis, shortname)) {
-                        return true;
-                    }
-                }
-                return false;
-            },
-
-            chooseSkinTone (ev) {
-                ev.preventDefault();
-                ev.stopPropagation();
-                const target = ev.target.nodeName === 'IMG' ?
-                    ev.target.parentElement : ev.target;
-                const skintone = target.getAttribute("data-skintone").trim();
-                if (this.model.get('current_skintone') === skintone) {
-                    this.model.save({'current_skintone': ''});
-                } else {
-                    this.model.save({'current_skintone': skintone});
-                }
-            },
-
-            chooseCategory (ev) {
-                const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
-                const category = target.getAttribute("data-category").trim();
-                this.model.save({
-                    'current_category': category,
-                    'scroll_position': 0
-                });
-            }
-        });
-
-
         _converse.ChatBoxHeading = _converse.ViewWithAvatar.extend({
             initialize () {
                 this.model.on('change:status', this.onStatusMessageChanged, this);
@@ -336,8 +246,6 @@ converse.plugins.add('converse-chatview', {
                 'click .toggle-call': 'toggleCall',
                 'click .toggle-clear': 'clearMessages',
                 'click .toggle-compose-spoiler': 'toggleComposeSpoilerMessage',
-                'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
-                'click .toggle-smiley': 'toggleEmojiMenu',
                 'click .upload-file': 'toggleFileUpload',
                 'input .chat-textarea': 'inputChanged',
                 'keydown .chat-textarea': 'onKeyDown',
@@ -361,9 +269,6 @@ converse.plugins.add('converse-chatview', {
 
                 this.model.presence.on('change:show', this.onPresenceChanged, this);
                 this.render();
-                this.createEmojiPicker();
-                this.insertEmojiPicker();
-                await this.renderEmojiPicker();
                 await this.updateAfterMessagesFetched();
 
                 /**
@@ -509,11 +414,7 @@ converse.plugins.add('converse-chatview', {
                 const all_resources_support_spolers = results.reduce((acc, val) => (acc && val), true);
                 if (all_resources_support_spolers) {
                     const html = tpl_spoiler_button(this.model.toJSON());
-                    if (_converse.visible_toolbar_buttons.emoji) {
-                        this.el.querySelector('.toggle-smiley').insertAdjacentHTML('afterEnd', html);
-                    } else {
-                        this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html);
-                    }
+                    this.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html);
                 }
             },
 
@@ -544,9 +445,7 @@ converse.plugins.add('converse-chatview', {
                     'message_limit': _converse.message_limit,
                     'show_call_button': _converse.visible_toolbar_buttons.call,
                     'show_spoiler_button': _converse.visible_toolbar_buttons.spoiler,
-                    'tooltip_insert_smiley': __('Insert emojis'),
                     'tooltip_start_call': __('Start a call'),
-                    'use_emoji': _converse.visible_toolbar_buttons.emoji,
                 }
             },
 
@@ -987,10 +886,7 @@ converse.plugins.add('converse-chatview', {
                     } else if (ev.keyCode === _converse.keycodes.ESCAPE) {
                         return this.onEscapePressed(ev);
                     } else if (ev.keyCode === _converse.keycodes.ENTER) {
-                        if (this.emoji_dropdown && u.isVisible(this.emoji_dropdown.el.querySelector('.emoji-picker'))) {
-                            this.emoji_dropdown.toggle();
-                        }
-                        return this.onFormSubmitted(ev);
+                        return this.onEnterPressed(ev);
                     } else if (ev.keyCode === _converse.keycodes.UP_ARROW && !ev.target.selectionEnd) {
                         const textarea = this.el.querySelector('.chat-textarea');
                         if (!textarea.value || u.hasClass('correcting', textarea)) {
@@ -1022,6 +918,10 @@ converse.plugins.add('converse-chatview', {
                 return this.model.messages.filter({'sender': 'me'});
             },
 
+            onEnterPressed (ev) {
+                return this.onFormSubmitted(ev);
+            },
+
             onEscapePressed (ev) {
                 ev.preventDefault();
                 const idx = this.model.messages.findLastIndex('correcting'),
@@ -1143,35 +1043,6 @@ converse.plugins.add('converse-chatview', {
                 u.placeCaretAtEnd(textarea);
             },
 
-            createEmojiPicker () {
-                if (_converse.emojipicker === undefined) {
-                    const storage = _converse.config.get('storage'),
-                          id = `converse.emoji-${_converse.bare_jid}`;
-                    _converse.emojipicker = new _converse.EmojiPicker({'id': id});
-                    _converse.emojipicker.browserStorage = new BrowserStorage[storage](id);
-                    _converse.emojipicker.fetch();
-                }
-                this.emoji_picker_view = new _converse.EmojiPickerView({'model': _converse.emojipicker});
-            },
-
-            insertEmoji (ev) {
-                ev.preventDefault();
-                ev.stopPropagation();
-                const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
-                this.insertIntoTextArea(target.getAttribute('data-emoji'));
-            },
-
-            toggleEmojiMenu (ev) {
-                if (this.emoji_dropdown === undefined) {
-                    ev.stopPropagation();
-
-                    const dropdown_el = this.el.querySelector('.toggle-smiley.dropup');
-                    this.emoji_dropdown = new bootstrap.Dropdown(dropdown_el, true);
-                    this.emoji_dropdown.el = dropdown_el;
-                    this.emoji_dropdown.toggle();
-                }
-            },
-
             toggleCall (ev) {
                 ev.stopPropagation();
                 /**
@@ -1269,19 +1140,6 @@ converse.plugins.add('converse-chatview', {
                 return this;
             },
 
-            async renderEmojiPicker () {
-                await _converse.api.waitUntil('emojisInitialized');
-                this.emoji_picker_view.render();
-            },
-
-            insertEmojiPicker () {
-                const picker_el = this.el.querySelector('.emoji-picker');
-                if (picker_el !== null) {
-                    picker_el.innerHTML = '';
-                    picker_el.appendChild(this.emoji_picker_view.el);
-                }
-            },
-
             emitFocused () {
                 /**
                  * Triggered when the focus has been moved to a particular chat.

+ 207 - 0
src/converse-emoji-views.js

@@ -0,0 +1,207 @@
+// Converse.js
+// https://conversejs.org
+//
+// Copyright (c) 2013-2019, the Converse.js developers
+// Licensed under the Mozilla Public License (MPLv2)
+
+/**
+ * @module converse-emoji-views
+ */
+
+import "@converse/headless/converse-emoji";
+import BrowserStorage from "backbone.browserStorage";
+import bootstrap from "bootstrap.native";
+import tpl_emoji_button from "templates/emoji_button.html";
+import tpl_emojis from "templates/emojis.html";
+const { Backbone } = converse.env;
+const u = converse.env.utils;
+
+
+converse.plugins.add('converse-emoji-views', {
+    /* Plugin dependencies are other plugins which might be
+     * overridden or relied upon, and therefore need to be loaded before
+     * this plugin.
+     *
+     * If the setting "strict_plugin_dependencies" is set to true,
+     * an error will be raised if the plugin is not found. By default it's
+     * false, which means these plugins are only loaded opportunistically.
+     *
+     * NB: These plugins need to have already been loaded via require.js.
+     */
+    dependencies: ["converse-emoji", "converse-chatview"],
+
+
+    overrides: {
+        ChatBoxView: {
+            events: {
+                'click .toggle-smiley': 'toggleEmojiMenu',
+            },
+
+            onEnterPressed () {
+                if (this.emoji_dropdown && u.isVisible(this.emoji_dropdown.el.querySelector('.emoji-picker'))) {
+                    this.emoji_dropdown.toggle();
+                }
+                this.__super__.onEnterPressed.apply(this, arguments);
+            }
+        },
+
+        ChatRoomView: {
+            events: {
+                'click .toggle-smiley': 'toggleEmojiMenu'
+            },
+        }
+    },
+
+
+    initialize () {
+        /* The initialize function gets called as soon as the plugin is
+         * loaded by converse.js's plugin machinery.
+         */
+        const { _converse } = this;
+        const { __ } = _converse;
+
+        _converse.api.settings.update({
+            'use_system_emojis': true,
+            'visible_toolbar_buttons': {
+                'emoji': true
+            },
+        });
+
+
+        const emoji_aware_chat_view = {
+
+            createEmojiPicker () {
+                if (_converse.emojipicker === undefined) {
+                    const storage = _converse.config.get('storage'),
+                          id = `converse.emoji-${_converse.bare_jid}`;
+                    _converse.emojipicker = new _converse.EmojiPicker({'id': id});
+                    _converse.emojipicker.browserStorage = new BrowserStorage[storage](id);
+                    _converse.emojipicker.fetch();
+                }
+                this.emoji_picker_view = new _converse.EmojiPickerView({'model': _converse.emojipicker});
+                this.emoji_picker_view.chatview = this;
+            },
+
+            async toggleEmojiMenu (ev) {
+                if (this.emoji_dropdown === undefined) {
+                    ev.stopPropagation();
+                    const dropdown_el = this.el.querySelector('.toggle-smiley.dropup');
+                    this.emoji_dropdown = new bootstrap.Dropdown(dropdown_el, true);
+                    this.emoji_dropdown.el = dropdown_el;
+                    this.emoji_dropdown.toggle();
+                    await _converse.api.waitUntil('emojisInitialized');
+                    this.emoji_picker_view.render();
+                    this.emoji_picker_view.setScrollPosition();
+                }
+            },
+
+            insertEmojiPicker () {
+                const picker_el = this.el.querySelector('.emoji-picker');
+                if (picker_el !== null) {
+                    picker_el.innerHTML = '';
+                    picker_el.appendChild(this.emoji_picker_view.el);
+                }
+            }
+        };
+        Object.assign(_converse.ChatBoxView.prototype, emoji_aware_chat_view);
+
+
+        function emojiShouldBeHidden (shortname, current_skintone, toned_emojis) {
+            // Helper method for the template which decides whether an
+            // emoji should be hidden, based on which skin tone is
+            // currently being applied.
+            if (shortname.includes('_tone')) {
+                if (!current_skintone || !shortname.includes(current_skintone)) {
+                    return true;
+                }
+            } else {
+                if (current_skintone && toned_emojis.includes(shortname)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+
+        _converse.EmojiPickerView = Backbone.VDOMView.extend({
+            className: 'emoji-picker__container',
+            events: {
+                'click .emoji-category-picker li.emoji-category': 'chooseCategory',
+                'click .emoji-skintone-picker li.emoji-skintone': 'chooseSkinTone',
+                'click .toggle-smiley ul.emoji-picker li': 'insertEmoji'
+            },
+
+            initialize () {
+                this.model.on('change:current_skintone', this.render, this);
+                this.model.on('change:current_category', () => {
+                    this.render();
+                    this.setScrollPosition();
+                });
+                _converse.api.trigger('emojiPickerViewInitialized');
+            },
+
+            toHTML () {
+                const html = tpl_emojis(
+                    Object.assign(
+                        this.model.toJSON(), {
+                            '_converse': _converse,
+                            'emoji_categories': _converse.emoji_categories,
+                            'emojis_by_category': u.getEmojisByCategory(),
+                            'shouldBeHidden': emojiShouldBeHidden,
+                            'skintones': ['tone1', 'tone2', 'tone3', 'tone4', 'tone5'],
+                            'toned_emojis': _converse.emojis.toned,
+                            'transform': u.getEmojiRenderer()
+                        }
+                    )
+                );
+                return html;
+            },
+
+            chooseSkinTone (ev) {
+                ev.preventDefault();
+                ev.stopPropagation();
+                const target = ev.target.nodeName === 'IMG' ?
+                    ev.target.parentElement : ev.target;
+                const skintone = target.getAttribute("data-skintone").trim();
+                if (this.model.get('current_skintone') === skintone) {
+                    this.model.save({'current_skintone': ''});
+                } else {
+                    this.model.save({'current_skintone': skintone});
+                }
+            },
+
+            chooseCategory (ev) {
+                ev.preventDefault();
+                ev.stopPropagation();
+                const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
+                const category = target.getAttribute("data-category").trim();
+                this.model.save({'current_category': category});
+            },
+
+            setScrollPosition () {
+                const category = this.model.get('current_category');
+                const el = this.el.querySelector('.emoji-picker__lists');
+                const heading = this.el.querySelector(`#emoji-picker-${category}`);
+                el.scrollTop = heading.offsetTop - heading.offsetHeight*2;
+            },
+
+            insertEmoji (ev) {
+                ev.preventDefault();
+                ev.stopPropagation();
+                const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
+                this.chatview.insertIntoTextArea(target.getAttribute('data-emoji'));
+            }
+        });
+
+
+        /************************ BEGIN Event Handlers ************************/
+        _converse.api.listen.on('renderToolbar', view => {
+            if (_converse.visible_toolbar_buttons.emoji) {
+                const html = tpl_emoji_button({'tooltip_insert_smiley': __('Insert emojis')});
+                view.el.querySelector('.chat-toolbar').insertAdjacentHTML('afterBegin', html);
+                view.createEmojiPicker();
+                view.insertEmojiPicker();
+            }
+        });
+    }
+});

+ 1 - 6
src/converse-muc-views.js

@@ -624,8 +624,6 @@ converse.plugins.add('converse-muc-views', {
                 'click .show-room-details-modal': 'showRoomDetailsModal',
                 'click .toggle-call': 'toggleCall',
                 'click .toggle-occupants': 'toggleOccupants',
-                'click .toggle-smiley ul.emoji-picker li': 'insertEmoji',
-                'click .toggle-smiley': 'toggleEmojiMenu',
                 'click .upload-file': 'toggleFileUpload',
                 'keydown .chat-textarea': 'onKeyDown',
                 'keyup .chat-textarea': 'onKeyUp',
@@ -635,7 +633,7 @@ converse.plugins.add('converse-muc-views', {
                 'drop .chat-textarea': 'onDrop',
             },
 
-            async initialize () {
+            initialize () {
                 this.initDebounced();
 
                 this.model.messages.on('add', this.onMessageAdded, this);
@@ -662,9 +660,6 @@ converse.plugins.add('converse-muc-views', {
                 this.model.occupants.on('change:affiliation', this.onOccupantAffiliationChanged, this);
 
                 this.render();
-                this.createEmojiPicker();
-                this.insertEmojiPicker();
-                await this.renderEmojiPicker();
                 this.updateAfterMessagesFetched();
                 this.createOccupantsView();
                 this.onConnectionStatusChanged();

+ 3 - 1
src/converse.js

@@ -8,6 +8,7 @@ import "converse-bookmark-views";  // Views for XEP-0048 Bookmarks
 import "converse-chatview";        // Renders standalone chat boxes for single user chat
 import "converse-controlbox";      // The control box
 import "converse-dragresize";      // Allows chat boxes to be resized by dragging them
+import "converse-emoji-views";
 import "converse-fullscreen";
 import "converse-headline";        // Support for headline messages
 import "converse-mam-views";
@@ -15,8 +16,8 @@ import "converse-minimize";        // Allows chat boxes to be minimized
 import "converse-muc-views";       // Views related to MUC
 import "converse-notification";    // HTML5 Notifications
 import "converse-omemo";
-import "converse-push";            // XEP-0357 Push Notifications
 import "converse-profile";
+import "converse-push";            // XEP-0357 Push Notifications
 import "converse-register";        // XEP-0077 In-band registration
 import "converse-roomslist";       // Show currently open chat rooms
 import "converse-rosterview";
@@ -33,6 +34,7 @@ const WHITELISTED_PLUGINS = [
     'converse-chatview',
     'converse-controlbox',
     'converse-dragresize',
+    'converse-emoji-views',
     'converse-fullscreen',
     'converse-headline',
     'converse-mam-views',

+ 9 - 1
src/headless/converse-emoji.js

@@ -10,7 +10,7 @@ import * as twemoji from "twemoji";
 import _ from "./lodash.noconflict";
 import converse from "./converse-core";
 
-const { Strophe } = converse.env;
+const { Backbone, Strophe } = converse.env;
 const u = converse.env.utils;
 
 const ASCII_LIST = {
@@ -198,6 +198,14 @@ converse.plugins.add('converse-emoji', {
             "flags": __("Flags")
         }
 
+        _converse.EmojiPicker = Backbone.Model.extend({
+            defaults: {
+                'current_category': 'people',
+                'current_skintone': '',
+                'scroll_position': 0
+            }
+        });
+
         _converse.emojis = {};
 
         u.getEmojiRenderer = function () {

+ 4 - 0
src/templates/emoji_button.html

@@ -0,0 +1,4 @@
+<li class="toggle-toolbar-menu toggle-smiley dropup">
+    <a class="toggle-smiley far fa-smile" title="{{{o.tooltip_insert_smiley}}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></a>
+    <div class="emoji-picker dropdown-menu toolbar-menu"></div>
+</li>

+ 12 - 9
src/templates/emojis.html

@@ -10,9 +10,9 @@
     </div>
     <div class="emoji-picker__lists">
         {[ Object.keys(o.emoji_categories).forEach(function (category) { ]}
-        <a id="emoji-picker-{{{category}}}" class="emoji-category__heading">{{{o._converse.emoji_category_labels[category]}}}</a>
+            <a id="emoji-picker-{{{category}}}" class="emoji-category__heading">{{{o._converse.emoji_category_labels[category]}}}</a>
             <ul class="emoji-picker emoji-picker-{{{category}}}">
-                {[ o._.forEach(o.emojis_by_category[category], function (emoji) { ]}
+                {[ o.emojis_by_category[category].forEach(function (emoji) { ]}
                 <li class="emoji insert-emoji {[ if (o.shouldBeHidden(emoji.sn, o.current_skintone, o.toned_emojis)) { ]} hidden {[ }; ]}"
                     data-emoji="{{{emoji.sn}}}" title="{{{emoji.sn}}}">
                         <a href="#" data-emoji="{{{emoji.sn}}}"> {{ o.transform(emoji.sn) }}  </a>
@@ -21,11 +21,14 @@
             </ul>
         {[ }); ]}
     </div>
-    <ul class="emoji-skintone-picker">
-        {[ o._.forEach(o.skintones, function (skintone) { ]}
-            <li data-skintone="{{{skintone}}}" class="emoji-skintone {[ if (o.current_skintone === skintone) { ]} picked {[ } ]}">
-                <a class="pick-skintone" href="#" data-skintone="{{{skintone}}}"> {{ o.transform(':'+skintone+':') }} </a>
-            </li>
-        {[ }); ]}
-    </ul>
+    <div class="emoji-skintone-picker">
+        <label>Skin tone:</label>
+        <ul>
+            {[ o.skintones.forEach(function (skintone) { ]}
+                <li data-skintone="{{{skintone}}}" class="emoji-skintone {[ if (o.current_skintone === skintone) { ]} picked {[ } ]}">
+                    <a class="pick-skintone" href="#" data-skintone="{{{skintone}}}"> {{ o.transform(':'+skintone+':') }} </a>
+                </li>
+            {[ }); ]}
+        </ul>
+    </div>
 </div>

+ 0 - 6
src/templates/toolbar.html

@@ -1,9 +1,3 @@
-{[ if (o.use_emoji)  { ]}
-<li class="toggle-toolbar-menu toggle-smiley dropup">
-    <a class="toggle-smiley far fa-smile" title="{{{o.tooltip_insert_smiley}}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></a> 
-    <div class="emoji-picker dropdown-menu toolbar-menu"></div>
-</li>
-{[ } ]}
 {[ if (o.show_call_button)  { ]}
 <li class="toggle-call fa fa-phone" title="{{{o.label_start_call}}}"></li>
 {[ } ]}