[FIX] mail: let TAB cycle through @ mention propositions

Before this commit, when the partner mention propositions dropup was visible
on discuss composer, pressing on TAB result in selecting the next focusable DOM
element after the composer input, which is usually the 'Send' or 'Log' button.

This behaviour is confusing, because users expect that TAB either autofill the
input or cycle through the propositions.

We decided to align on Slack's behaviour of TAB, which is to cycle through
the mention popositions from top to bottom.
This commit is contained in:
Alexandre Kühn
2018-02-22 14:23:03 +01:00
parent 5532825851
commit 497a44ab7f
2 changed files with 114 additions and 34 deletions
+34 -21
View File
@@ -100,24 +100,6 @@ var MentionManager = Widget.extend({
return selections;
},
proposition_navigation: function (keycode) {
var $active = this.$('.o_mention_proposition.active');
if (keycode === $.ui.keyCode.ENTER) { // selecting proposition
$active.click();
} else { // navigation in propositions
var $to;
if (keycode === $.ui.keyCode.DOWN) {
$to = $active.nextAll('.o_mention_proposition').first();
} else {
$to = $active.prevAll('.o_mention_proposition').first();
}
if ($to.length) {
$active.removeClass('active');
$to.addClass('active');
}
}
},
/**
* Detects if the user is currently typing a mention word
* @return the search string if it is, false otherwise
@@ -215,7 +197,10 @@ var MentionManager = Widget.extend({
});
},
// Private functions
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Returns the matches (as RexExp.exec does) for the mention in the input text
*
@@ -269,6 +254,32 @@ var MentionManager = Widget.extend({
}
return -1;
},
/**
* @private
* @param {integer} keycode
*/
_propositionNavigation: function (keycode) {
var $active = this.$('.o_mention_proposition.active');
if (keycode === $.ui.keyCode.ENTER) { // selecting proposition
$active.click();
} else { // navigation in propositions
var $to;
if (keycode === $.ui.keyCode.DOWN) {
$to = $active.nextAll('.o_mention_proposition').first();
} else if (keycode === $.ui.keyCode.UP) {
$to = $active.prevAll('.o_mention_proposition').first();
} else if (keycode === $.ui.keyCode.TAB) {
$to = $active.nextAll('.o_mention_proposition').first();
if (!$to.length) {
$to = $active.prevAll('.o_mention_proposition').last();
}
}
if ($to && $to.length) {
$active.removeClass('active');
$to.addClass('active');
}
}
},
_render_suggestions: function () {
var suggestions = [];
if (_.find(this.get('mention_suggestions'), _.isArray)) {
@@ -583,6 +594,7 @@ var BasicComposer = Widget.extend({
// UP, DOWN: prevent moving cursor if navigation in mention propositions
case $.ui.keyCode.UP:
case $.ui.keyCode.DOWN:
case $.ui.keyCode.TAB:
if (this.mention_manager.is_open()) {
event.preventDefault();
}
@@ -627,12 +639,13 @@ var BasicComposer = Widget.extend({
this.trigger_up("escape_pressed");
}
break;
// ENTER, UP, DOWN: check if navigation in mention propositions
// ENTER, UP, DOWN, TAB: check if navigation in mention propositions
case $.ui.keyCode.ENTER:
case $.ui.keyCode.UP:
case $.ui.keyCode.DOWN:
case $.ui.keyCode.TAB:
if (this.mention_manager.is_open()) {
this.mention_manager.proposition_navigation(event.which);
this.mention_manager._propositionNavigation(event.which);
}
break;
// Otherwise, check if a mention is typed
+80 -13
View File
@@ -63,7 +63,7 @@ QUnit.test('basic rendering', function (assert) {
});
QUnit.test('@ mention in channel', function (assert) {
assert.expect(12);
assert.expect(34);
var done = assert.async();
// Remove throttles to speed up the test
@@ -96,7 +96,11 @@ QUnit.test('@ mention in channel', function (assert) {
mockRPC: function (route, args) {
if (args.method === 'channel_fetch_listeners') {
fetchListenersDef.resolve();
return $.when([ {id: 2, name: 'TestUser'} ]);
return $.when([
{id: 1, name: 'Admin'},
{id: 2, name: 'TestUser'},
{id: 3, name: 'DemoUser'}
]);
}
if (args.method === 'message_post') {
var data = {
@@ -138,18 +142,81 @@ QUnit.test('@ mention in channel', function (assert) {
assert.strictEqual(discuss.$('.dropup.o_composer_mention_dropdown.open').length, 1,
"dropup menu for partner mentions should be open");
var $mentionProposition = discuss.$('.o_mention_proposition');
assert.strictEqual($mentionProposition.length, 1,
"should display one partner mention proposition");
assert.strictEqual($mentionProposition.data('id'), 2,
"partner mention should link to the correct partner id");
assert.strictEqual($mentionProposition.find('.o_mention_name').text(), "TestUser",
"partner mention should display the correct partner name");
var $mentionPropositions = discuss.$('.o_mention_proposition');
assert.strictEqual($mentionPropositions.length, 3,
"should display 3 partner mention propositions");
$mentionProposition.click();
var $mention1 = $mentionPropositions.first();
var $mention2 = $mentionPropositions.first().next();
var $mention3 = $mentionPropositions.first().next().next();
// correct 1st mention proposition
assert.ok($mention1.hasClass('active'),
"first partner mention should be active");
assert.strictEqual($mention1.data('id'), 1,
"first partner mention should link to the correct partner id");
assert.strictEqual($mention1.find('.o_mention_name').text(), "Admin",
"first partner mention should display the correct partner name");
// correct 2nd mention proposition
assert.notOk($mention2.hasClass('active'),
"second partner mention should not be active");
assert.strictEqual($mention2.data('id'), 2,
"second partner mention should link to the correct partner id");
assert.strictEqual($mention2.find('.o_mention_name').text(), "TestUser",
"second partner mention should display the correct partner name");
// correct 3rd mention proposition
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
assert.strictEqual($mention3.data('id'), 3,
"third partner mention should link to the correct partner id");
assert.strictEqual($mention3.find('.o_mention_name').text(), "DemoUser",
"third partner mention should display the correct partner name");
$input.trigger($.Event('keyup', {which: $.ui.keyCode.DOWN}));
assert.notOk($mention1.hasClass('active'),
"first partner mention should not be active");
assert.ok($mention2.hasClass('active'),
"second partner mention should be active");
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
$input.trigger($.Event('keyup', {which: $.ui.keyCode.UP}));
assert.ok($mention1.hasClass('active'),
"first partner mention should be active");
assert.notOk($mention2.hasClass('active'),
"second partner mention should not be active");
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
$input.trigger($.Event('keyup', {which: $.ui.keyCode.TAB}));
assert.notOk($mention1.hasClass('active'),
"first partner mention should not be active");
assert.ok($mention2.hasClass('active'),
"second partner mention should be active");
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
$input.trigger($.Event('keyup', {which: $.ui.keyCode.TAB}));
assert.notOk($mention1.hasClass('active'),
"first partner mention should not be active");
assert.notOk($mention2.hasClass('active'),
"second partner mention should not be active");
assert.ok($mention3.hasClass('active'),
"third partner mention should be active");
$input.trigger($.Event('keyup', {which: $.ui.keyCode.TAB}));
assert.ok($mention1.hasClass('active'),
"first partner mention should be active");
assert.notOk($mention2.hasClass('active'),
"second partner mention should not be active");
assert.notOk($mention3.hasClass('active'),
"third partner mention should not be active");
// $mentionPropositions.find('active').click();
$input.trigger($.Event('keyup', {which: $.ui.keyCode.ENTER}));
assert.strictEqual(discuss.$('.o_mention_proposition').length, 0,
"should not have any partner mention proposition after clicking on it");
assert.strictEqual($input.find('a').text() , "@TestUser",
"should not have any partner mention proposition after ENTER");
assert.strictEqual($input.find('a').text() , "@Admin",
"should have the correct mention link in the composer input");
// send message
@@ -163,7 +230,7 @@ QUnit.test('@ mention in channel', function (assert) {
assert.strictEqual(discuss.$('.o_thread_message_content a').length, 1,
"should contain a link in the message content");
assert.strictEqual(discuss.$('.o_thread_message_content a').text(),
"@TestUser", "should have correct mention link in the message content");
"@Admin", "should have correct mention link in the message content");
// Restore throttles and window.getSelection
BasicComposer.prototype.MENTION_THROTTLE = mentionThrottle;