فهرست منبع

Refactor the emoji-picker somewhat

Trigger an `emojiSelected` event instead of manually calling `insertIntoTextArea` on the `converse-message-form` a component.
This loosens the coupling between the emoji picker and `converse-message-form`.

Call `disableArrowNavigation` when the emoji-picker is disconnected from
the DOM or when escape is pressed. See #2754
JC Brand 3 سال پیش
والد
کامیت
e52056bb33

+ 13 - 0
src/plugins/chatview/message-form.js

@@ -15,9 +15,22 @@ export default class MessageForm extends ElementView {
         await this.model.initialized;
         this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting);
         this.listenTo(this.model, 'change:composing_spoiler', () => this.render());
+
+        this.handleEmojiSelection = ({ detail }) => this.insertIntoTextArea(
+            detail.value,
+            detail.autocompleting,
+            false,
+            detail.ac_position
+        );
+        document.addEventListener("emojiSelected", this.handleEmojiSelection);
         this.render();
     }
 
+    disconnectedCallback () {
+        super.disconnectedCallback();
+        document.removeEventListener("emojiSelected", this.handleEmojiSelection);
+    }
+
     toHTML () {
         return tpl_message_form(
             Object.assign(this.model.toJSON(), {

+ 3 - 3
src/plugins/muc-views/tests/emojis.js

@@ -44,7 +44,7 @@ describe("Emojis", function () {
             expect(input.value).toBe(':grimacing:');
 
             // Check that ENTER now inserts the match
-            const enter_event = Object.assign({}, tab_event, {'keyCode': 13, 'key': 'Enter', 'target': input});
+            const enter_event = Object.assign({}, tab_event, {'keyCode': 13, 'key': 'Enter', 'target': input, 'bubbles': true});
             input.dispatchEvent(new KeyboardEvent('keydown', enter_event));
 
             await u.waitUntil(() => input.value === '');
@@ -148,7 +148,7 @@ describe("Emojis", function () {
             const input = picker.querySelector('.emoji-search');
             input.dispatchEvent(new KeyboardEvent('keydown', tab_event));
             await u.waitUntil(() => input.value === ':100:');
-            const enter_event = Object.assign({}, tab_event, {'keyCode': 13, 'key': 'Enter', 'target': input});
+            const enter_event = Object.assign({}, tab_event, {'keyCode': 13, 'key': 'Enter', 'target': input, 'bubbles': true});
             input.dispatchEvent(new KeyboardEvent('keydown', enter_event));
             expect(textarea.value).toBe(':100: ');
 
@@ -193,7 +193,7 @@ describe("Emojis", function () {
             expect(visible_emojis[1].getAttribute('data-emoji')).toBe(':smiley_cat:');
 
             // Check that pressing enter without an unambiguous match does nothing
-            const enter_event = Object.assign({}, event, {'keyCode': 13});
+            const enter_event = Object.assign({}, event, {'keyCode': 13, 'bubbles': true});
             input.dispatchEvent(new KeyboardEvent('keydown', enter_event));
             expect(input.value).toBe('smiley');
 

+ 1 - 6
src/shared/chat/emoji-dropdown.js

@@ -58,6 +58,7 @@ export default class EmojiDropdown extends DropdownBase {
                         <converse-emoji-picker
                                 .chatview=${this.chatview}
                                 .model=${this.model}
+                                @emojiSelected=${() => this.hideMenu()}
                                 ?render_emojis=${this.render_emojis}
                                 current_category="${this.model.get('current_category') || ''}"
                                 current_skintone="${this.model.get('current_skintone') || ''}"
@@ -95,12 +96,6 @@ export default class EmojiDropdown extends DropdownBase {
         super.showMenu();
         setTimeout(() => this.querySelector('.emoji-search')?.focus());
     }
-
-    hideMenu () {
-        this.chatview.querySelector('converse-emoji-picker')?.disableArrowNavigation();
-        super.hideMenu();
-        setTimeout(() => this.chatview.querySelector('.chat-textarea')?.focus());
-    }
 }
 
 api.elements.define('converse-emoji-dropdown', EmojiDropdown);

+ 20 - 24
src/shared/chat/emoji-picker.js

@@ -3,6 +3,7 @@ import './emoji-dropdown.js';
 import DOMNavigator from "shared/dom-navigator";
 import debounce from 'lodash-es/debounce';
 import { CustomElement } from 'shared/components/element.js';
+import { KEYCODES } from '@converse/headless/shared/constants.js';
 import { _converse, api, converse } from "@converse/headless/core";
 import { tpl_emoji_picker } from "./templates/emoji-picker.js";
 
@@ -56,7 +57,7 @@ export default class EmojiPicker extends CustomElement {
             'onCategoryPicked': ev => this.chooseCategory(ev),
             'onSearchInputBlurred': ev => this.chatview.emitFocused(ev),
             'onSearchInputFocus': ev => this.onSearchInputFocus(ev),
-            'onSearchInputKeyDown': ev => this.onKeyDown(ev),
+            'onSearchInputKeyDown': ev => this.onSearchInputKeyDown(ev),
             'onSkintonePicked': ev => this.chooseSkinTone(ev),
             'query': this.query,
             'search_results': this.search_results,
@@ -120,6 +121,7 @@ export default class EmojiPicker extends CustomElement {
     disconnectedCallback() {
         const body = document.querySelector('body');
         body.removeEventListener('keydown', this.onGlobalKeyDown);
+        this.disableArrowNavigation();
         super.disconnectedCallback();
     }
 
@@ -127,14 +129,15 @@ export default class EmojiPicker extends CustomElement {
         if (!this.navigator) {
             return;
         }
-        if (ev.keyCode === converse.keycodes.ENTER &&
-                this.navigator.selected &&
-                u.isVisible(this)) {
+        if (ev.keyCode === KEYCODES.ENTER && u.isVisible(this)) {
             this.onEnterPressed(ev);
-        } else if (ev.keyCode === converse.keycodes.DOWN_ARROW &&
+        } else if (ev.keyCode === KEYCODES.DOWN_ARROW &&
                 !this.navigator.enabled &&
                 u.isVisible(this)) {
             this.enableArrowNavigation(ev);
+        } else if (ev.keyCode === KEYCODES.ESCAPE) {
+            this.disableArrowNavigation();
+            setTimeout(() => this.chatview.querySelector('.chat-textarea').focus(), 50);
         }
     }
 
@@ -149,8 +152,13 @@ export default class EmojiPicker extends CustomElement {
     insertIntoTextArea (value) {
         const autocompleting = this.model.get('autocompleting');
         const ac_position = this.model.get('ac_position');
-        this.chatview.getMessageForm().insertIntoTextArea(value, autocompleting, false, ac_position);
         this.model.set({'autocompleting': null, 'query': '', 'ac_position': null});
+        this.disableArrowNavigation();
+        const options = {
+            'bubbles': true,
+            'detail': { value, autocompleting, ac_position }
+        };
+        this.dispatchEvent(new CustomEvent("emojiSelected", options));
     }
 
     chooseSkinTone (ev) {
@@ -174,8 +182,8 @@ export default class EmojiPicker extends CustomElement {
         !this.navigator.enabled && this.navigator.enable();
     }
 
-    onKeyDown (ev) {
-        if (ev.keyCode === converse.keycodes.TAB) {
+    onSearchInputKeyDown (ev) {
+        if (ev.keyCode === KEYCODES.TAB) {
             if (ev.target.value) {
                 ev.preventDefault();
                 const match = converse.emojis.shortnames.find(sn => _converse.FILTER_CONTAINS(sn, ev.target.value));
@@ -183,31 +191,19 @@ export default class EmojiPicker extends CustomElement {
             } else if (!this.navigator.enabled) {
                 this.enableArrowNavigation(ev);
             }
-        } else if (ev.keyCode === converse.keycodes.DOWN_ARROW && !this.navigator.enabled) {
+        } else if (ev.keyCode === KEYCODES.DOWN_ARROW && !this.navigator.enabled) {
             this.enableArrowNavigation(ev);
-        } else if (ev.keyCode === converse.keycodes.ENTER) {
-            this.onEnterPressed(ev);
-        } else if (ev.keyCode === converse.keycodes.ESCAPE) {
-            u.ancestor(this, 'converse-emoji-dropdown').hideMenu();
-            ev.stopPropagation();
-            ev.preventDefault();
         } else if (
-            ev.keyCode !== converse.keycodes.ENTER &&
-            ev.keyCode !== converse.keycodes.DOWN_ARROW
+            ev.keyCode !== KEYCODES.ENTER &&
+            ev.keyCode !== KEYCODES.DOWN_ARROW
         ) {
             this.debouncedFilter(ev.target);
         }
     }
 
     onEnterPressed (ev) {
-        if (ev.emoji_keypress_handled) {
-            // Prevent the emoji from being inserted a 2nd time due to this
-            // method being called by two event handlers: onKeyDown and _onGlobalKeyDown
-            return;
-        }
         ev.preventDefault();
         ev.stopPropagation();
-        ev.emoji_keypress_handled = true;
         if (converse.emojis.shortnames.includes(ev.target.value)) {
             this.insertIntoTextArea(ev.target.value);
         } else if (this.search_results.length === 1) {
@@ -259,7 +255,7 @@ export default class EmojiPicker extends CustomElement {
     }
 
     disableArrowNavigation () {
-        this.navigator.disable();
+        this.navigator?.disable();
     }
 
     enableArrowNavigation (ev) {

+ 15 - 3
src/shared/components/dropdown.js

@@ -1,9 +1,10 @@
 import 'shared/components/icons.js';
 import DOMNavigator from "shared/dom-navigator.js";
-import { converse, api } from "@converse/headless/core";
+import DropdownBase from 'shared/components/dropdownbase.js';
+import { KEYCODES } from '@converse/headless/shared/constants.js';
+import { api } from "@converse/headless/core";
 import { html } from 'lit';
 import { until } from 'lit/directives/until.js';
-import DropdownBase from 'shared/components/dropdownbase.js';
 
 import './styles/dropdown.scss';
 
@@ -40,6 +41,17 @@ export default class Dropdown extends DropdownBase {
         this.initArrowNavigation();
     }
 
+    connectedCallback () {
+        super.connectedCallback();
+        this.hideOnEscape = ev => (ev.keyCode === KEYCODES.ESCAPE && this.hideMenu());
+        document.addEventListener('keydown', this.hideOnEscape);
+    }
+
+    disconnectedCallback() {
+        document.removeEventListener('keydown', this.hideOnEscape);
+        super.disconnectedCallback();
+    }
+
     hideMenu () {
         super.hideMenu();
         this.navigator?.disable();
@@ -66,7 +78,7 @@ export default class Dropdown extends DropdownBase {
 
     handleKeyUp (ev) {
         super.handleKeyUp(ev);
-        if (ev.keyCode === converse.keycodes.DOWN_ARROW && !this.navigator.enabled) {
+        if (ev.keyCode === KEYCODES.DOWN_ARROW && !this.navigator.enabled) {
             this.enableArrowNavigation(ev);
         }
     }