hotkeys: Map `CTRL + .` to narrow to compose box target.

Also adds relevant tests and documentation. We currently
do not narrow to a new topic, and instead just narrow to
the stream. Similarly, we do not narrow to a PM if any of
the recipients are invalid.
This commit is contained in:
Rohitt Vashishtha 2018-11-29 23:20:10 +00:00 committed by Tim Abbott
parent 3079cf803c
commit ee3b4f3ee9
7 changed files with 185 additions and 2 deletions

View File

@ -101,6 +101,7 @@ run_test('mappings', () => {
assert.equal(map_down(219, false, true).name, 'escape'); // ctrl + [ assert.equal(map_down(219, false, true).name, 'escape'); // ctrl + [
assert.equal(map_down(75, false, true).name, 'search_with_k'); // ctrl + k assert.equal(map_down(75, false, true).name, 'search_with_k'); // ctrl + k
assert.equal(map_down(83, false, true).name, 'star_message'); // ctrl + s assert.equal(map_down(83, false, true).name, 'star_message'); // ctrl + s
assert.equal(map_down(190, false, true).name, 'narrow_to_compose_target'); // ctrl + .
// More negative tests. // More negative tests.
assert.equal(map_down(47), undefined); assert.equal(map_down(47), undefined);
@ -133,6 +134,8 @@ run_test('mappings', () => {
assert.equal(map_down(75, false, true, false), undefined); // ctrl + k assert.equal(map_down(75, false, true, false), undefined); // ctrl + k
assert.equal(map_down(83, false, false, true).name, 'star_message'); // cmd + s assert.equal(map_down(83, false, false, true).name, 'star_message'); // cmd + s
assert.equal(map_down(83, false, true, false), undefined); // ctrl + s assert.equal(map_down(83, false, true, false), undefined); // ctrl + s
assert.equal(map_down(190, false, false, true).name, 'narrow_to_compose_target'); // cmd + .
assert.equal(map_down(190, false, true, false), undefined); // ctrl + .
// Reset userAgent // Reset userAgent
global.navigator.userAgent = ''; global.navigator.userAgent = '';
}); });
@ -309,9 +312,7 @@ run_test('basic_chars', () => {
global.current_msg_list.empty = return_true; global.current_msg_list.empty = return_true;
assert_mapping('n', 'narrow.narrow_to_next_topic'); assert_mapping('n', 'narrow.narrow_to_next_topic');
global.current_msg_list.empty = return_false; global.current_msg_list.empty = return_false;
}); });
run_test('motion_keys', () => { run_test('motion_keys', () => {

View File

@ -3,6 +3,7 @@ zrequire('hashchange');
zrequire('narrow_state'); zrequire('narrow_state');
zrequire('people'); zrequire('people');
zrequire('stream_data'); zrequire('stream_data');
zrequire('util');
zrequire('Filter', 'js/filter'); zrequire('Filter', 'js/filter');
set_global('i18n', global.stub_i18n); set_global('i18n', global.stub_i18n);
@ -177,3 +178,125 @@ run_test('show_empty_narrow_message', () => {
assert.equal(hide_id,'.empty_feed_notice'); assert.equal(hide_id,'.empty_feed_notice');
assert.equal(show_id, '#empty_search_narrow_message'); assert.equal(show_id, '#empty_search_narrow_message');
}); });
run_test('narrow_to_compose_target', () => {
set_global('compose_state', {});
set_global('topic_data', {});
const args = {called: false};
const activate_backup = narrow.activate;
narrow.activate = function (operators, opts) {
args.operators = operators;
args.opts = opts;
args.called = true;
};
// No-op when not composing.
global.compose_state.composing = () => false;
narrow.to_compose_target();
assert.equal(args.called, false);
global.compose_state.composing = () => true;
// No-op when empty stream.
global.compose_state.get_message_type = () => 'stream';
global.compose_state.stream_name = () => '';
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, false);
// --- Tests for stream messages ---
global.compose_state.get_message_type = () => 'stream';
stream_data.add_sub('ROME', {name: 'ROME', stream_id: 99});
global.compose_state.stream_name = () => 'ROME';
global.topic_data.get_recent_names = () => ['one', 'two', 'three'];
// Test with existing topic
global.compose_state.topic = () => 'one';
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, true);
assert.equal(args.opts.trigger, 'narrow_to_compose_target');
assert.deepEqual(args.operators, [
{operator: 'stream', operand: 'ROME'},
{operator: 'topic', operand: 'one'},
]);
// Test with new topic
global.compose_state.topic = () => 'four';
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, true);
assert.deepEqual(args.operators, [
{operator: 'stream', operand: 'ROME'},
]);
// Test with blank topic
global.compose_state.topic = () => '';
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, true);
assert.deepEqual(args.operators, [
{operator: 'stream', operand: 'ROME'},
]);
// Test with no topic
global.compose_state.topic = () => {};
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, true);
assert.deepEqual(args.operators, [
{operator: 'stream', operand: 'ROME'},
]);
// --- Tests for PMs ---
global.compose_state.get_message_type = () => 'private';
people.add_in_realm(ray);
people.add_in_realm(alice);
people.add_in_realm(me);
// Test with valid person
global.compose_state.recipient = () => 'alice@example.com';
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, true);
assert.deepEqual(args.operators, [
{operator: 'pm-with', operand: 'alice@example.com'},
]);
// Test with valid persons
global.compose_state.recipient = () => 'alice@example.com,ray@example.com';
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, true);
assert.deepEqual(args.operators, [
{operator: 'pm-with', operand: 'alice@example.com,ray@example.com'},
]);
// Test with some inavlid persons
global.compose_state.recipient = () => 'alice@example.com,random,ray@example.com';
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, true);
assert.deepEqual(args.operators, [
{operator: 'is', operand: 'private'},
]);
// Test with all inavlid persons
global.compose_state.recipient = () => 'alice,random,ray';
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, true);
assert.deepEqual(args.operators, [
{operator: 'is', operand: 'private'},
]);
// Test with no persons
global.compose_state.recipient = () => '';
args.called = false;
narrow.to_compose_target();
assert.equal(args.called, true);
assert.deepEqual(args.operators, [
{operator: 'is', operand: 'private'},
]);
narrow.activate = activate_backup;
});

View File

@ -421,6 +421,11 @@ exports.on_narrow = function (opts) {
return; return;
} }
if (opts.trigger === "narrow_to_compose_target") {
compose_fade.update_message_list();
return;
}
if (narrow_state.narrowed_by_topic_reply()) { if (narrow_state.narrowed_by_topic_reply()) {
exports.on_topic_narrow(); exports.on_topic_narrow();
return; return;

View File

@ -55,6 +55,7 @@ var keydown_ctrl_mappings = {
var keydown_cmd_or_ctrl_mappings = { var keydown_cmd_or_ctrl_mappings = {
75: {name: 'search_with_k', message_view_only: false}, // 'K' 75: {name: 'search_with_k', message_view_only: false}, // 'K'
83: {name: 'star_message', message_view_only: true}, // 's' 83: {name: 'star_message', message_view_only: true}, // 's'
190: {name: 'narrow_to_compose_target', message_view_only: true}, // '.'
}; };
var keydown_either_mappings = { var keydown_either_mappings = {
@ -552,6 +553,11 @@ exports.process_hotkey = function (e, hotkey) {
} }
} }
if (event_name === 'narrow_to_compose_target') {
narrow.to_compose_target();
return true;
}
// Process hotkeys specially when in an input, select, textarea, or send button // Process hotkeys specially when in an input, select, textarea, or send button
if (exports.processing_text()) { if (exports.processing_text()) {
// Note that there is special handling for enter/escape too, but // Note that there is special handling for enter/escape too, but

View File

@ -599,6 +599,48 @@ exports.by_recipient = function (target_id, opts) {
} }
}; };
// Called by the narrow_to_compose_target hotkey.
exports.to_compose_target = function () {
if (!compose_state.composing()) {
return;
}
var opts = {
trigger: 'narrow_to_compose_target',
};
if (compose_state.get_message_type() === 'stream') {
var stream_name = compose_state.stream_name();
var stream_id = stream_data.get_stream_id(stream_name);
if (!stream_id) {
return;
}
// If we are composing to a new topic, we narrow to the stream but
// grey-out the message view instead of narrowing to an empty view.
var topics = topic_data.get_recent_names(stream_id);
var operators = [{operator: 'stream', operand: stream_name}];
var topic = compose_state.topic();
if (topics.indexOf(topic) !== -1) {
operators.push({operator: 'topic', operand: topic});
}
exports.activate(operators, opts);
return;
}
if (compose_state.get_message_type() === 'private') {
var recipient_string = compose_state.recipient();
var emails = util.extract_pm_recipients(recipient_string);
var invalid = _.reject(emails, people.is_valid_email_for_compose);
// If there are no recipients or any recipient is
// invalid, narrow to all PMs.
if (emails.length === 0 || invalid.length > 0) {
exports.by('is', 'private', opts);
return;
}
exports.by('pm-with', util.normalize_recipients(recipient_string), opts);
}
};
function handle_post_narrow_deactivate_processes() { function handle_post_narrow_deactivate_processes() {
compose_fade.update_message_list(); compose_fade.update_message_list();

View File

@ -186,6 +186,10 @@
<td class="hotkey">Esc, Ctrl + [</td> <td class="hotkey">Esc, Ctrl + [</td>
<td class="definition">{% trans %}Narrow to all unmuted messages{% endtrans %}</td> <td class="definition">{% trans %}Narrow to all unmuted messages{% endtrans %}</td>
</tr> </tr>
<tr>
<td class="hotkey">Ctrl + .</td>
<td class="definition">{% trans %}Narrow to current compose box recipient{% endtrans %}</td>
</tr>
</table> </table>
<table class="hotkeys_table table table-striped table-bordered table-condensed"> <table class="hotkeys_table table table-striped table-bordered table-condensed">
<thead> <thead>

View File

@ -80,6 +80,8 @@ below, and add more to your repertoire as needed.
* **Narrow to all messages**: `Esc` or `Ctrl` + `[` — Shows all unmuted messages. * **Narrow to all messages**: `Esc` or `Ctrl` + `[` — Shows all unmuted messages.
* **Narrow to current compose box recipient**: `Ctrl` + `.`
## Composing messages ## Composing messages
* **Reply to message**: `r` or `Enter` — Reply to the selected * **Reply to message**: `r` or `Enter` — Reply to the selected