Commit 17654aaf authored by JC Brand's avatar JC Brand

emoji-views: incorporate tab-based navigation

parent e87db91e
......@@ -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;
}
}
}
}
......
......@@ -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;
......
......@@ -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();
......
......@@ -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();
},
......
......@@ -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);
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment