Sfoglia il codice sorgente

emoji-views: incorporate tab-based navigation

JC Brand 5 anni fa
parent
commit
17654aaf40
5 ha cambiato i file con 97 aggiunte e 73 eliminazioni
  1. 7 4
      sass/_emoji.scss
  2. 1 1
      sass/_variables.scss
  3. 3 3
      spec/emojis.js
  4. 56 40
      src/converse-emoji-views.js
  5. 30 25
      src/dom-navigator.js

+ 7 - 4
sass/_emoji.scss

@@ -75,7 +75,6 @@
                     position: relative;
                     &.insert-emoji {
                         margin: 0;
-                        padding: 3px;
                         height: 30px;
                         width: 32px;
 
@@ -86,10 +85,11 @@
                             background-color: var(--highlight-color);
                         }
                         a {
+                            padding: 3px;
+                            font-size: var(--font-size-huge);
                             &:hover {
                                 background-color: var(--highlight-color);
                             }
-                            font-size: var(--font-size-huge);
                         }
                     }
                 }
@@ -111,6 +111,8 @@
                     flex-wrap: wrap;
 
                     .emoji-category {
+                        padding: 0.25em 0;
+                        font-size: var(--font-size-huge);
                         &.picked {
                             background-color: white;
                             border: 1px var(--chat-head-color) solid;
@@ -119,11 +121,12 @@
                         &.selected {
                             background-color: var(--highlight-color);
                         }
-                        padding: 0.25em;
-                        font-size: var(--font-size-huge);
                         &:hover {
                             background-color: var(--highlight-color);
                         }
+                        a {
+                            padding: 0.25em;
+                        }
                     }
                 }
             }

+ 1 - 1
sass/_variables.scss

@@ -62,7 +62,7 @@ $mobile_portrait_length: 480px !default;
     --chat-topic-display: block;
     --chat-info-display: block;
 
-    --highlight-color: #B0E8E2;
+    --highlight-color: #DCF9F6;
 
     --primary-color: var(--light-blue);
     --primary-color-dark:  #397491;

+ 3 - 3
spec/emojis.js

@@ -74,7 +74,7 @@
 
                 // Check that ENTER now inserts the match
                 const enter_event = Object.assign({}, tab_event, {'keyCode': 13, 'key': 'Enter', 'target': input});
-                view.emoji_picker_view._onGlobalKeyDown(enter_event);
+                view.emoji_picker_view.onKeyDown(enter_event);
                 expect(input.value).toBe('');
                 expect(textarea.value).toBe(':grimacing: ');
 
@@ -136,7 +136,7 @@
 
                 // Check that pressing enter without an unambiguous match does nothing
                 const enter_event = Object.assign({}, event, {'keyCode': 13});
-                view.emoji_picker_view._onGlobalKeyDown(enter_event);
+                view.emoji_picker_view.onKeyDown(enter_event);
                 expect(input.value).toBe('smiley');
 
                 // Test that TAB autocompletes the to first match
@@ -148,7 +148,7 @@
                 expect(visible_emojis[0].getAttribute('data-emoji')).toBe(':smiley:');
 
                 // Check that ENTER now inserts the match
-                view.emoji_picker_view._onGlobalKeyDown(enter_event);
+                view.emoji_picker_view.onKeyDown(enter_event);
                 expect(input.value).toBe('');
                 expect(view.el.querySelector('textarea.chat-textarea').value).toBe(':smiley: ');
                 done();

+ 56 - 40
src/converse-emoji-views.js

@@ -143,7 +143,9 @@ converse.plugins.add('converse-emoji-views', {
 
             async initialize () {
                 this.onGlobalKeyDown = ev => this._onGlobalKeyDown(ev);
-                document.addEventListener('keydown', this.onGlobalKeyDown);
+
+                const body = document.querySelector('body');
+                body.addEventListener('keydown', this.onGlobalKeyDown);
 
                 this.search_results = [];
                 this.debouncedFilter = debounce(input => this.filter(input.value), 150);
@@ -180,7 +182,8 @@ converse.plugins.add('converse-emoji-views', {
             },
 
             remove () {
-                document.removeEventListener('keydown', this.onGlobalKeyDown);
+                const body = document.querySelector('body');
+                body.removeEventListener('keydown', this.onGlobalKeyDown);
                 Backbone.VDOMView.prototype.remove.call(this);
             },
 
@@ -209,13 +212,27 @@ converse.plugins.add('converse-emoji-views', {
                                 return default_selector;
                             }
                         },
-                        'onSelected': el => el.matches('.insert-emoji') && this.setCategoryForElement(el.parentElement)
+                        'onSelected': el => {
+                            el.matches('.insert-emoji') && this.setCategoryForElement(el.parentElement);
+                            el.matches('.insert-emoji, .emoji-category') && el.firstElementChild.focus();
+                            el.matches('.emoji-search') && el.focus();
+                        }
                     };
                     this.navigator = new DOMNavigator(this.el, options);
                     this.listenTo(this.chatview.model, 'destroy', () => this.navigator.destroy());
                 }
             },
 
+            enableArrowNavigation (ev) {
+                if (ev) {
+                    ev.preventDefault();
+                    ev.stopPropagation();
+                }
+                this.disableArrowNavigation();
+                this.navigator.enable();
+                this.navigator.handleKeydown(ev);
+            },
+
             disableArrowNavigation () {
                 this.navigator.disable();
             },
@@ -299,38 +316,29 @@ converse.plugins.add('converse-emoji-views', {
                 this.disableArrowNavigation();
             },
 
+            onEnterPressed (ev) {
+                ev.preventDefault();
+                ev.stopPropagation();
+                if (_converse.emoji_shortnames.includes(ev.target.value)) {
+                    this.insertIntoTextArea(ev.target.value);
+                } else if (this.search_results.length === 1) {
+                    this.insertIntoTextArea(this.search_results[0].sn);
+                } else if (this.navigator.selected && this.navigator.selected.matches('.insert-emoji')) {
+                    this.insertIntoTextArea(this.navigator.selected.getAttribute('data-emoji'));
+                } else if (this.navigator.selected && this.navigator.selected.matches('.emoji-category')) {
+                    this.chooseCategory({'target': this.navigator.selected});
+                }
+            },
+
             _onGlobalKeyDown (ev) {
-                if (ev.keyCode === converse.keycodes.ENTER) {
-                    if (ev.target.matches('.emoji-search') || (
-                            ev.target.matches('body') &&
-                            u.isVisible(this.el) &&
-                            this.navigator.selected
-                    )) {
-                        ev.preventDefault();
-                        ev.stopPropagation();
-                        if (_converse.emoji_shortnames.includes(ev.target.value)) {
-                            this.insertIntoTextArea(ev.target.value);
-                        } else if (this.search_results.length === 1) {
-                            this.insertIntoTextArea(this.search_results[0].sn);
-                        } else if (this.navigator.selected && this.navigator.selected.matches('.insert-emoji')) {
-                            this.insertIntoTextArea(this.navigator.selected.getAttribute('data-emoji'));
-                        } else if (this.navigator.selected && this.navigator.selected.matches('.emoji-category')) {
-                            this.chooseCategory({'target': this.navigator.selected});
-                        }
-                    }
-                } else if (ev.keyCode === converse.keycodes.DOWN_ARROW) {
-                    if (ev.target.matches('.emoji-search') || (
-                            !this.navigator.enabled &&
-                            (ev.target.matches('.pick-category') || ev.target.matches('body')) &&
-                            u.isVisible(this.el)
-                    )) {
-                        ev.preventDefault();
-                        ev.stopPropagation();
-                        ev.target.blur();
-                        this.disableArrowNavigation();
-                        this.navigator.enable();
-                        this.navigator.handleKeydown(ev);
-                    }
+                if (ev.keyCode === converse.keycodes.ENTER &&
+                        this.navigator.selected &&
+                        u.isVisible(this.el)) {
+                    this.onEnterPressed(ev);
+                } else if (ev.keyCode === converse.keycodes.DOWN_ARROW &&
+                        !this.navigator.enabled &&
+                        u.isVisible(this.el)) {
+                    this.enableArrowNavigation(ev);
                 }
             },
 
@@ -342,9 +350,17 @@ converse.plugins.add('converse-emoji-views', {
                     const first_el = this.el.querySelector('.pick-category');
                     this.navigator.select(first_el, 'right');
                 } else if (ev.keyCode === converse.keycodes.TAB) {
-                    ev.preventDefault();
-                    const match = find(_converse.emoji_shortnames, sn => _converse.FILTER_CONTAINS(sn, ev.target.value));
-                    match && this.filter(match, true);
+                    if (ev.target.value) {
+                        ev.preventDefault();
+                        const match = find(_converse.emoji_shortnames, sn => _converse.FILTER_CONTAINS(sn, ev.target.value));
+                        match && this.filter(match, true);
+                    } else if (!this.navigator.enabled) {
+                        this.enableArrowNavigation(ev);
+                    }
+                } else if (ev.keyCode === converse.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.ENTER &&
                     ev.keyCode !== converse.keycodes.DOWN_ARROW
@@ -399,9 +415,9 @@ converse.plugins.add('converse-emoji-views', {
                 ev.stopPropagation && ev.stopPropagation();
                 const input = this.el.querySelector('.emoji-search');
                 input.value = '';
-                const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
-                this.setCategoryForElement(target);
-                this.navigator.select(target);
+                const el = ev.target.matches('li') ? ev.target : u.ancestor(ev.target, 'li');
+                this.setCategoryForElement(el);
+                this.navigator.select(el);
                 this.setScrollPosition();
             },
 

+ 30 - 25
src/dom-navigator.js

@@ -66,10 +66,12 @@ class DOMNavigator {
      */
     static get DIRECTION () {
         return {
+            down: 'down',
+            end: 'end',
+            home: 'home',
             left: 'left',
-            up: 'up',
             right: 'right',
-            down: 'down'
+            up: 'up'
         };
     }
 
@@ -90,17 +92,22 @@ class DOMNavigator {
      */
     static get DEFAULTS () {
         return {
-            down: 40,
+            home: [`${converse.keycodes.SHIFT}+${converse.keycodes.UP_ARROW}`],
+            end: [`${converse.keycodes.SHIFT}+${converse.keycodes.DOWN_ARROW}`],
+            up: [converse.keycodes.UP_ARROW],
+            down: [converse.keycodes.DOWN_ARROW],
+            left: [
+                converse.keycodes.LEFT_ARROW,
+                `${converse.keycodes.SHIFT}+${converse.keycodes.TAB}`
+            ],
+            right: [converse.keycodes.RIGHT_ARROW, converse.keycodes.TAB],
             getSelector: null,
             jump_to_picked: null,
             jump_to_picked_direction: null,
             jump_to_picked_selector: 'picked',
-            left: 37,
             onSelected: null,
-            right: 39,
             selected: 'selected',
             selector: 'li',
-            up: 38,
         };
     }
 
@@ -163,10 +170,12 @@ class DOMNavigator {
         this.elements = {};
         // Create hotkeys map.
         this.keys = {};
-        this.keys[this.options.left] = DOMNavigator.DIRECTION.left;
-        this.keys[this.options.up] = DOMNavigator.DIRECTION.up;
-        this.keys[this.options.right] = DOMNavigator.DIRECTION.right;
-        this.keys[this.options.down] = DOMNavigator.DIRECTION.down;
+        this.options.down.forEach(key => this.keys[key] = DOMNavigator.DIRECTION.down);
+        this.options.end.forEach(key => this.keys[key] = DOMNavigator.DIRECTION.end);
+        this.options.home.forEach(key => this.keys[key] = DOMNavigator.DIRECTION.home);
+        this.options.left.forEach(key => this.keys[key] = DOMNavigator.DIRECTION.left);
+        this.options.right.forEach(key => this.keys[key] = DOMNavigator.DIRECTION.right);
+        this.options.up.forEach(key => this.keys[key] = DOMNavigator.DIRECTION.up);
     }
 
     /**
@@ -211,7 +220,11 @@ class DOMNavigator {
      */
     getNextElement (direction) {
         let el;
-        if (this.selected) {
+        if (direction === DOMNavigator.DIRECTION.home) {
+            el = this.getElements(direction)[0];
+        } else if (direction  === DOMNavigator.DIRECTION.end) {
+            el = Array.from(this.getElements(direction)).pop();
+        } else if (this.selected) {
             if (direction === DOMNavigator.DIRECTION.right) {
                 const els = this.getElements(direction);
                 el = els.slice(els.indexOf(this.selected))[1];
@@ -396,21 +409,13 @@ class DOMNavigator {
      * @method DOMNavigator#handleKeydown
      * @param { Event } event The event object.
      */
-    handleKeydown (event) {
-        const direction = this.keys[event.which];
+    handleKeydown (ev) {
+        const keys = converse.keycodes;
+        const direction = ev.shiftKey ? this.keys[`${keys.SHIFT}+${ev.which}`] : this.keys[ev.which];
         if (direction) {
-            event.preventDefault();
-            event.stopPropagation();
-            let next;
-            if (event.shiftKey && direction === DOMNavigator.DIRECTION.up) {
-                // shift-up goes to the first element
-                next = this.getElements(direction)[0];
-            } else if (event.shiftKey && direction === DOMNavigator.DIRECTION.down) {
-                // shift-down goes to the last element
-                next = Array.from(this.getElements(direction)).pop();
-            } else {
-                next = this.getNextElement(direction, event);
-            }
+            ev.preventDefault();
+            ev.stopPropagation();
+            const next = this.getNextElement(direction, ev);
             this.select(next, direction);
         }
     }