MDL-65896 message: add emoji picker to message app
authorRyan Wyllie <ryan@moodle.com>
Fri, 27 Sep 2019 06:26:55 +0000 (14:26 +0800)
committerRyan Wyllie <ryan@moodle.com>
Wed, 23 Oct 2019 02:59:58 +0000 (10:59 +0800)
22 files changed:
lib/templates/emoji/emoji_row.mustache
lib/templates/emoji/header_row.mustache
lib/templates/emoji/picker.mustache
message/amd/build/message_drawer_view_conversation.min.js
message/amd/build/message_drawer_view_conversation.min.js.map
message/amd/build/message_drawer_view_conversation_constants.min.js
message/amd/build/message_drawer_view_conversation_constants.min.js.map
message/amd/build/message_drawer_view_conversation_patcher.min.js
message/amd/build/message_drawer_view_conversation_patcher.min.js.map
message/amd/build/message_drawer_view_conversation_renderer.min.js
message/amd/build/message_drawer_view_conversation_renderer.min.js.map
message/amd/build/message_drawer_view_conversation_state_manager.min.js
message/amd/build/message_drawer_view_conversation_state_manager.min.js.map
message/amd/src/message_drawer_view_conversation.js
message/amd/src/message_drawer_view_conversation_constants.js
message/amd/src/message_drawer_view_conversation_patcher.js
message/amd/src/message_drawer_view_conversation_renderer.js
message/amd/src/message_drawer_view_conversation_state_manager.js
message/templates/message_drawer_view_conversation_footer_content.mustache
theme/boost/scss/moodle/message.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css

index 9b2b65f..83f1d44 100644 (file)
@@ -15,7 +15,7 @@
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template core_message/emoji/picker
+    @template core/emoji/picker
 
     This template will render the emoji picker.
 
index 08fe9e6..3b7dafa 100644 (file)
@@ -15,7 +15,7 @@
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template core_message/emoji/picker
+    @template core/emoji/picker
 
     This template will render the emoji picker.
 
index 395a03e..e4177ae 100644 (file)
@@ -15,7 +15,7 @@
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    @template core_message/emoji/picker
+    @template core/emoji/picker
 
     This template will render the emoji picker.
 
index c127aed..eb2ce6c 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation.min.js and b/message/amd/build/message_drawer_view_conversation.min.js differ
index 6407753..81efcb0 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation.min.js.map and b/message/amd/build/message_drawer_view_conversation.min.js.map differ
index 6620624..855afc1 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_constants.min.js and b/message/amd/build/message_drawer_view_conversation_constants.min.js differ
index 9db88ba..f8a9827 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_constants.min.js.map and b/message/amd/build/message_drawer_view_conversation_constants.min.js.map differ
index 7ee7ac7..4d79811 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_patcher.min.js and b/message/amd/build/message_drawer_view_conversation_patcher.min.js differ
index 61d2153..3c9c74b 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_patcher.min.js.map and b/message/amd/build/message_drawer_view_conversation_patcher.min.js.map differ
index 33f04aa..783198d 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_renderer.min.js and b/message/amd/build/message_drawer_view_conversation_renderer.min.js differ
index e32f25c..e06f275 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_renderer.min.js.map and b/message/amd/build/message_drawer_view_conversation_renderer.min.js.map differ
index c827c07..67b196b 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_state_manager.min.js and b/message/amd/build/message_drawer_view_conversation_state_manager.min.js differ
index 7da84f3..833ea30 100644 (file)
Binary files a/message/amd/build/message_drawer_view_conversation_state_manager.min.js.map and b/message/amd/build/message_drawer_view_conversation_state_manager.min.js.map differ
index b6b96df..2f3b2b3 100644 (file)
@@ -69,6 +69,7 @@ define(
     'core_message/message_drawer_view_conversation_state_manager',
     'core_message/message_drawer_router',
     'core_message/message_drawer_routes',
+    'core/emoji/picker'
 ],
 function(
     $,
@@ -85,7 +86,8 @@ function(
     Renderer,
     StateManager,
     MessageDrawerRouter,
-    MessageDrawerRoutes
+    MessageDrawerRoutes,
+    initialiseEmojiPicker
 ) {
 
     // Contains a cache of all view states that have been loaded so far
@@ -1512,6 +1514,36 @@ function(
         };
     };
 
+    /**
+     * Handle clicking on the emoji toggle button.
+     *
+     * @param {Object} e The event
+     * @param {Object} data The custom interaction event data
+     */
+    var handleToggleEmojiPicker = function(e, data) {
+        var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker);
+        render(newState);
+        data.originalEvent.preventDefault();
+    };
+
+    /**
+     * Handle clicking outside the emoji picker to close it.
+     *
+     * @param {Object} e The event
+     */
+    var handleCloseEmojiPicker = function(e) {
+        var target = $(e.target);
+
+        if (
+            viewState.showEmojiPicker &&
+            !target.closest(SELECTORS.EMOJI_PICKER_CONTAINER).length &&
+            !target.closest(SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON).length
+        ) {
+            var newState = StateManager.setShowEmojiPicker(viewState, false);
+            render(newState);
+        }
+    };
+
     /**
      * Listen to, and handle events for conversations.
      *
@@ -1523,6 +1555,8 @@ function(
     var registerEventListeners = function(namespace, header, body, footer) {
         var isLoadingMoreMessages = false;
         var messagesContainer = getMessagesContainer(body);
+        var emojiPickerElement = footer.find(SELECTORS.EMOJI_PICKER);
+        var messageTextArea = footer.find(SELECTORS.MESSAGE_TEXT_AREA);
         var headerActivateHandlers = [
             [SELECTORS.ACTION_REQUEST_BLOCK, generateConfirmActionHandler(requestBlockUser)],
             [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)],
@@ -1555,6 +1589,7 @@ function(
         ];
         var footerActivateHandlers = [
             [SELECTORS.SEND_MESSAGE_BUTTON, handleSendMessage],
+            [SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON, handleToggleEmojiPicker],
             [SELECTORS.ACTION_REQUEST_DELETE_SELECTED_MESSAGES, generateConfirmActionHandler(requestDeleteSelectedMessages)],
             [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],
             [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)],
@@ -1562,6 +1597,22 @@ function(
 
         AutoRows.init(footer);
 
+        initialiseEmojiPicker(emojiPickerElement[0], function(emoji) {
+            var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker);
+            render(newState);
+
+            messageTextArea.focus();
+            var cursorPos = messageTextArea.prop('selectionStart');
+            var currentText = messageTextArea.val();
+            var textBefore = currentText.substring(0, cursorPos);
+            var textAfter = currentText.substring(cursorPos, currentText.length);
+
+            messageTextArea.val(textBefore + emoji + textAfter);
+            // Set the cursor position to after the inserted emoji.
+            messageTextArea.prop('selectionStart', cursorPos + emoji.length);
+            messageTextArea.prop('selectionEnd', cursorPos + emoji.length);
+        });
+
         CustomEvents.define(header, [
             CustomEvents.events.activate
         ]);
@@ -1570,7 +1621,8 @@ function(
         ]);
         CustomEvents.define(footer, [
             CustomEvents.events.activate,
-            CustomEvents.events.enter
+            CustomEvents.events.enter,
+            CustomEvents.events.escape
         ]);
         CustomEvents.define(messagesContainer, [
             CustomEvents.events.scrollTop,
@@ -1625,6 +1677,9 @@ function(
             }
         });
 
+        footer.on(CustomEvents.events.escape, SELECTORS.EMOJI_PICKER_CONTAINER, handleToggleEmojiPicker);
+        $(document.body).on('click', handleCloseEmojiPicker);
+
         PubSub.subscribe(MessageDrawerEvents.ROUTE_CHANGED, function(newRouteData) {
             if (newMessagesPollTimer) {
                 if (newRouteData.route != MessageDrawerRoutes.VIEW_CONVERSATION) {
index 03aaa44..47c36ca 100644 (file)
@@ -65,6 +65,9 @@ define([], function() {
         DAY_MESSAGES_CONTAINER: '[data-region="day-messages-container"]',
         DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE: '[data-region="delete-messages-for-all-users-toggle"]',
         DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER: '[data-region="delete-messages-for-all-users-toggle-container"]',
+        EMOJI_PICKER_CONTAINER: '[data-region="emoji-picker-container"]',
+        EMOJI_PICKER: '[data-region="emoji-picker"]',
+        EMOJI_PICKER_SEARCH_INPUT: '[data-region="search-input"]',
         ERROR_MESSAGE_CONTAINER: '[data-region="error-message-container"]',
         ERROR_MESSAGE: '[data-region="error-message"]',
         FAVOURITE_ICON_CONTAINER: '[data-region="favourite-icon-container"]',
@@ -91,7 +94,8 @@ define([], function() {
         TEXT: '[data-region="text"]',
         TEXT_CONTAINER: '[data-region="text-container"]',
         TIME_CREATED: '[data-region="time-created"]',
-        TITLE: '[data-region="title"]'
+        TITLE: '[data-region="title"]',
+        TOGGLE_EMOJI_PICKER_BUTTON: '[data-action="toggle-emoji-picker"]'
     };
 
     var TEMPLATES = {
index c259512..e8029a7 100644 (file)
@@ -584,6 +584,23 @@ function(
         }
     };
 
+    /**
+     * Determine if we should show the emoji picker.
+     *
+     * @param  {Object} state The current state.
+     * @param  {Object} newState The new state.
+     * @return {Bool|Null}
+     */
+    var buildShowEmojiPicker = function(state, newState) {
+        if (!state.showEmojiPicker && newState.showEmojiPicker) {
+            return true;
+        } else if (state.showEmojiPicker && !newState.showEmojiPicker) {
+            return false;
+        } else {
+            return null;
+        }
+    };
+
     /**
      * Get the user Object of user to be blocked if pending.
      *
@@ -1335,7 +1352,8 @@ function(
                 inEditMode: buildInEditMode,
                 selectedMessages: buildSelectedMessages,
                 isFavourite: buildIsFavourite,
-                isMuted: buildIsMuted
+                isMuted: buildIsMuted,
+                showEmojiPicker: buildShowEmojiPicker
             }
         };
         // These build functions are only applicable to private conversations.
index 7f31923..226d63c 100644 (file)
@@ -420,6 +420,16 @@ function(
         getHeaderPlaceholderContainer(header).addClass('hidden');
     };
 
+    /**
+     * Get the emoji picker container element.
+     *
+     * @param  {Object} footer Conversation footer container element.
+     * @return {Object} The emoji picker container element.
+     */
+    var getEmojiPickerContainer = function(footer) {
+        return footer.find(SELECTORS.EMOJI_PICKER_CONTAINER);
+    };
+
     /**
      * Get a message element.
      *
@@ -970,6 +980,27 @@ function(
         }
     };
 
+    /**
+     * Hide or show the emoji picker.
+     *
+     * @param {Object} header The header container element.
+     * @param {Object} body The body container element.
+     * @param {Object} footer The footer container element.
+     * @param {Bool} show Should the emoji picker be visible.
+     */
+    var renderShowEmojiPicker = function(header, body, footer, show) {
+        var container = getEmojiPickerContainer(footer);
+
+        if (show) {
+            container.removeClass('hidden');
+            container.attr('aria-hidden', false);
+            container.find(SELECTORS.EMOJI_PICKER_SEARCH_INPUT).focus();
+        } else {
+            container.addClass('hidden');
+            container.attr('aria-hidden', true);
+        }
+    };
+
     /**
      * Show a confirmation dialogue
      *
@@ -1637,7 +1668,8 @@ function(
                 isFavourite: renderIsFavourite,
                 isMuted: renderIsMuted,
                 loadingConfirmAction: renderLoadingConfirmAction,
-                inEditMode: renderInEditMode
+                inEditMode: renderInEditMode,
+                showEmojiPicker: renderShowEmojiPicker
             },
             {
                 // Scrolling should be last to make sure everything
index f6b61e8..7adbf47 100644 (file)
@@ -144,7 +144,8 @@ define(['jquery'], function($) {
             pendingDeleteMessageIds: [],
             pendingSendMessageIds: [],
             pendingDeleteConversation: false,
-            selectedMessageIds: []
+            selectedMessageIds: [],
+            showEmojiPicker: false
         };
     };
 
@@ -527,6 +528,19 @@ define(['jquery'], function($) {
         return newState;
     };
 
+    /**
+     * Set the visibility of the emoji picker.
+     *
+     * @param  {Object} state Current state.
+     * @param  {Bool} show Should the emoji picker be shown.
+     * @return {Object} New state with array of pending delete message ids.
+     */
+    var setShowEmojiPicker = function(state, show) {
+        var newState = cloneState(state);
+        newState.showEmojiPicker = show;
+        return newState;
+    };
+
     /**
      * Set the state pending block userids.
      *
@@ -831,6 +845,7 @@ define(['jquery'], function($) {
         setMessagesSendPendingById: setMessagesSendPendingById,
         setMessagesSendSuccessById: setMessagesSendSuccessById,
         setMessagesSendFailById: setMessagesSendFailById,
+        setShowEmojiPicker: setShowEmojiPicker,
         addPendingBlockUsersById: addPendingBlockUsersById,
         addPendingRemoveContactsById: addPendingRemoveContactsById,
         addPendingUnblockUsersById: addPendingUnblockUsersById,
index 2d5f488..0e49ea2 100644 (file)
         placeholder="{{#str}} writeamessage, core_message {{/str}}"
         style="resize: none"
     ></textarea>
-    <button
-        class="btn btn-link btn-icon icon-size-3 ml-1 mt-auto"
-        aria-label="{{#str}} sendmessage, core_message {{/str}}"
-        data-action="send-message"
-    >
-        <span data-region="send-icon-container">{{#pix}} i/sendmessage, core {{/pix}}</span>
-        <span class="hidden" data-region="loading-icon-container">{{> core/loading }}</span>
-    </button>
+
+    <div class="position-relative d-flex flex-column">
+        <div
+            data-region="emoji-picker-container"
+            class="emoji-picker-container hidden"
+            aria-hidden="true"
+        >
+            {{> core/emoji/picker }}
+        </div>
+        <button
+            class="btn btn-link btn-icon icon-size-3 ml-1"
+            data-action="toggle-emoji-picker"
+        >
+            {{#pix}} e/emoticons, core{{/pix}}
+        </button>
+        <button
+            class="btn btn-link btn-icon icon-size-3 ml-1 mt-auto"
+            aria-label="{{#str}} sendmessage, core_message {{/str}}"
+            data-action="send-message"
+        >
+            <span data-region="send-icon-container">{{#pix}} i/sendmessage, core {{/pix}}</span>
+            <span class="hidden" data-region="loading-icon-container">{{> core/loading }}</span>
+        </button>
+    </div>
 </div>
index 314e97e..94ca183 100644 (file)
@@ -471,7 +471,6 @@ $message-day-color: color-yiq($message-app-bg) !default;
 
     .footer-container {
         flex-shrink: 0;
-        overflow-x: hidden;
 
         textarea {
             direction: ltr;
@@ -658,3 +657,26 @@ $message-day-color: color-yiq($message-app-bg) !default;
         box-shadow: 2px 2px 4px rgba(0, 0, 0, .08);
     }
 }
+
+.message-app {
+    .emoji-picker-container {
+        position: absolute;
+        top: -5px;
+        right: 5px;
+        transform: translateY(-100%);
+
+        .emoji-picker {
+            .picker-row {
+                // To override the button styling for the message app.
+                .emoji-button {
+                    height: $picker-emoji-button-size;
+                    width: $picker-emoji-button-size;
+                }
+            }
+        }
+
+        @include media-breakpoint-down(xs) {
+            right: -1 * map-get($spacers, 2);
+        }
+    }
+}
index 83502cd..d99bfc8 100644 (file)
@@ -14736,8 +14736,7 @@ a.ygtvspacer:hover {
       top: 0;
       bottom: 0; }
   .message-app .footer-container {
-    flex-shrink: 0;
-    overflow-x: hidden; }
+    flex-shrink: 0; }
     .message-app .footer-container textarea {
       direction: ltr; }
   .message-app .matchtext {
@@ -14880,6 +14879,18 @@ a.ygtvspacer:hover {
 .dir-rtl .message-drawer {
   box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.08); }
 
+.message-app .emoji-picker-container {
+  position: absolute;
+  top: -5px;
+  right: 5px;
+  transform: translateY(-100%); }
+  .message-app .emoji-picker-container .emoji-picker .picker-row .emoji-button {
+    height: 40px;
+    width: 40px; }
+  @media (max-width: 575.98px) {
+    .message-app .emoji-picker-container {
+      right: -0.5rem; } }
+
 /* Question */
 .questionbank h2 {
   margin-top: 0; }
index 6de5835..7195c67 100644 (file)
@@ -14998,8 +14998,7 @@ a.ygtvspacer:hover {
       top: 0;
       bottom: 0; }
   .message-app .footer-container {
-    flex-shrink: 0;
-    overflow-x: hidden; }
+    flex-shrink: 0; }
     .message-app .footer-container textarea {
       direction: ltr; }
   .message-app .matchtext {
@@ -15142,6 +15141,18 @@ a.ygtvspacer:hover {
 .dir-rtl .message-drawer {
   box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.08); }
 
+.message-app .emoji-picker-container {
+  position: absolute;
+  top: -5px;
+  right: 5px;
+  transform: translateY(-100%); }
+  .message-app .emoji-picker-container .emoji-picker .picker-row .emoji-button {
+    height: 40px;
+    width: 40px; }
+  @media (max-width: 575.98px) {
+    .message-app .emoji-picker-container {
+      right: -0.5rem; } }
+
 /* Question */
 .questionbank h2 {
   margin-top: 0; }