mirror of https://github.com/zulip/zulip.git
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:
parent
3079cf803c
commit
ee3b4f3ee9
|
@ -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', () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue