mirror of https://github.com/zulip/zulip.git
hotkey: Add 'narrow to next unread followed topic' hotkey.
This commit adds a 'Shift + N' keyboard shortcut, which is used to narrow to the next unread followed topic. Fixes part of #27323.
This commit is contained in:
parent
6819ecee92
commit
8e2264b585
|
@ -14,7 +14,8 @@
|
||||||
|
|
||||||
!!! keyboard_tip ""
|
!!! keyboard_tip ""
|
||||||
|
|
||||||
Use the <kbd>N</kbd> key to go to the next unread topic, or <kbd>P</kbd>
|
Use the <kbd>N</kbd> key to go to the next unread topic, or <kbd>Shift</kbd> + <kbd>N</kbd>
|
||||||
to go to the next unread direct message conversation.
|
for the next unread [followed](/help/follow-a-topic) topic, or <kbd>P</kbd> for the next
|
||||||
|
unread direct message conversation.
|
||||||
|
|
||||||
{end_tabs}
|
{end_tabs}
|
||||||
|
|
|
@ -38,6 +38,8 @@ in the Zulip app to add more to your repertoire as needed.
|
||||||
|
|
||||||
* **Next unread topic**: <kbd>N</kbd>
|
* **Next unread topic**: <kbd>N</kbd>
|
||||||
|
|
||||||
|
* **Next unread followed topic**: <kbd>Shift</kbd> + <kbd>N</kbd>
|
||||||
|
|
||||||
* **Next unread direct message**: <kbd>P</kbd>
|
* **Next unread direct message**: <kbd>P</kbd>
|
||||||
|
|
||||||
* **Search messages**: <kbd>/</kbd>
|
* **Search messages**: <kbd>/</kbd>
|
||||||
|
|
|
@ -88,6 +88,7 @@ const keydown_shift_mappings = {
|
||||||
38: {name: "up_arrow", message_view_only: false}, // up arrow
|
38: {name: "up_arrow", message_view_only: false}, // up arrow
|
||||||
40: {name: "down_arrow", message_view_only: false}, // down arrow
|
40: {name: "down_arrow", message_view_only: false}, // down arrow
|
||||||
72: {name: "view_edit_history", message_view_only: true}, // 'H'
|
72: {name: "view_edit_history", message_view_only: true}, // 'H'
|
||||||
|
78: {name: "narrow_to_next_unread_followed_topic", message_view_only: false}, // 'N'
|
||||||
};
|
};
|
||||||
|
|
||||||
const keydown_unshift_mappings = {
|
const keydown_unshift_mappings = {
|
||||||
|
@ -904,7 +905,10 @@ export function process_hotkey(e, hotkey) {
|
||||||
narrow.stream_cycle_forward();
|
narrow.stream_cycle_forward();
|
||||||
return true;
|
return true;
|
||||||
case "n_key":
|
case "n_key":
|
||||||
narrow.narrow_to_next_topic({trigger: "hotkey"});
|
narrow.narrow_to_next_topic({trigger: "hotkey", only_followed_topics: false});
|
||||||
|
return true;
|
||||||
|
case "narrow_to_next_unread_followed_topic":
|
||||||
|
narrow.narrow_to_next_topic({trigger: "hotkey", only_followed_topics: true});
|
||||||
return true;
|
return true;
|
||||||
case "p_key":
|
case "p_key":
|
||||||
narrow.narrow_to_next_pm_string({trigger: "hotkey"});
|
narrow.narrow_to_next_pm_string({trigger: "hotkey"});
|
||||||
|
|
|
@ -770,7 +770,11 @@ export function narrow_to_next_topic(opts = {}) {
|
||||||
topic: narrow_state.topic(),
|
topic: narrow_state.topic(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const next_narrow = topic_generator.get_next_topic(curr_info.stream, curr_info.topic);
|
const next_narrow = topic_generator.get_next_topic(
|
||||||
|
curr_info.stream,
|
||||||
|
curr_info.topic,
|
||||||
|
opts.only_followed_topics,
|
||||||
|
);
|
||||||
|
|
||||||
if (!next_narrow) {
|
if (!next_narrow) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -49,7 +49,7 @@ export function next_topic(streams, get_topics, has_unread_messages, curr_stream
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_next_topic(curr_stream, curr_topic) {
|
export function get_next_topic(curr_stream, curr_topic, only_followed_topics) {
|
||||||
let my_streams = stream_list_sort.get_streams();
|
let my_streams = stream_list_sort.get_streams();
|
||||||
|
|
||||||
my_streams = my_streams.filter((stream_name) => {
|
my_streams = my_streams.filter((stream_name) => {
|
||||||
|
@ -71,11 +71,28 @@ export function get_next_topic(curr_stream, curr_topic) {
|
||||||
return topics;
|
return topics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function get_followed_topics(stream_name) {
|
||||||
|
const stream_id = stream_data.get_stream_id(stream_name);
|
||||||
|
let topics = stream_topic_history.get_recent_topic_names(stream_id);
|
||||||
|
topics = topics.filter((topic) => user_topics.is_topic_followed(stream_id, topic));
|
||||||
|
return topics;
|
||||||
|
}
|
||||||
|
|
||||||
function has_unread_messages(stream_name, topic) {
|
function has_unread_messages(stream_name, topic) {
|
||||||
const stream_id = stream_data.get_stream_id(stream_name);
|
const stream_id = stream_data.get_stream_id(stream_name);
|
||||||
return unread.topic_has_any_unread(stream_id, topic);
|
return unread.topic_has_any_unread(stream_id, topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (only_followed_topics) {
|
||||||
|
return next_topic(
|
||||||
|
my_streams,
|
||||||
|
get_followed_topics,
|
||||||
|
has_unread_messages,
|
||||||
|
curr_stream,
|
||||||
|
curr_topic,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return next_topic(my_streams, get_unmuted_topics, has_unread_messages, curr_stream, curr_topic);
|
return next_topic(my_streams, get_unmuted_topics, has_unread_messages, curr_stream, curr_topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,10 @@
|
||||||
<td class="definition">{{t 'Next unread topic' }}</td>
|
<td class="definition">{{t 'Next unread topic' }}</td>
|
||||||
<td><span class="hotkey"><kbd>N</kbd></span></td>
|
<td><span class="hotkey"><kbd>N</kbd></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="definition">{{t 'Next unread followed topic' }}</td>
|
||||||
|
<td><span class="hotkey"><kbd>Shift</kbd> + <kbd>N</kbd></span></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="definition">{{t 'Next unread direct message' }}</td>
|
<td class="definition">{{t 'Next unread direct message' }}</td>
|
||||||
<td><span class="hotkey"><kbd>P</kbd></span></td>
|
<td><span class="hotkey"><kbd>P</kbd></span></td>
|
||||||
|
|
|
@ -182,6 +182,7 @@ run_test("mappings", () => {
|
||||||
assert.equal(map_down(13).name, "enter");
|
assert.equal(map_down(13).name, "enter");
|
||||||
assert.equal(map_down(46).name, "delete");
|
assert.equal(map_down(46).name, "delete");
|
||||||
assert.equal(map_down(13, true).name, "enter");
|
assert.equal(map_down(13, true).name, "enter");
|
||||||
|
assert.equal(map_down(78, true).name, "narrow_to_next_unread_followed_topic");
|
||||||
|
|
||||||
assert.equal(map_press(47).name, "search"); // slash
|
assert.equal(map_press(47).name, "search"); // slash
|
||||||
assert.equal(map_press(106).name, "vim_down"); // j
|
assert.equal(map_press(106).name, "vim_down"); // j
|
||||||
|
@ -231,11 +232,15 @@ run_test("mappings", () => {
|
||||||
navigator.platform = "";
|
navigator.platform = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
function process(s) {
|
function process(s, shiftKey, keydown = false) {
|
||||||
const e = {
|
const e = {
|
||||||
which: s.codePointAt(0),
|
which: s.codePointAt(0),
|
||||||
|
shiftKey,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
if (keydown) {
|
||||||
|
return hotkey.process_keydown(e);
|
||||||
|
}
|
||||||
return hotkey.process_keypress(e);
|
return hotkey.process_keypress(e);
|
||||||
} catch (error) /* istanbul ignore next */ {
|
} catch (error) /* istanbul ignore next */ {
|
||||||
// An exception will be thrown here if a different
|
// An exception will be thrown here if a different
|
||||||
|
@ -247,9 +252,9 @@ function process(s) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function assert_mapping(c, module, func_name, shiftKey) {
|
function assert_mapping(c, module, func_name, shiftKey, keydown) {
|
||||||
stubbing(module, func_name, (stub) => {
|
stubbing(module, func_name, (stub) => {
|
||||||
assert.ok(process(c, shiftKey));
|
assert.ok(process(c, shiftKey, keydown));
|
||||||
assert.equal(stub.num_calls, 1);
|
assert.equal(stub.num_calls, 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -442,6 +447,10 @@ run_test("n/p keys", () => {
|
||||||
assert_mapping("n", narrow, "narrow_to_next_topic");
|
assert_mapping("n", narrow, "narrow_to_next_topic");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
run_test("narrow next unread followed topic", () => {
|
||||||
|
assert_mapping("N", narrow, "narrow_to_next_topic", true, true);
|
||||||
|
});
|
||||||
|
|
||||||
run_test("motion_keys", () => {
|
run_test("motion_keys", () => {
|
||||||
const codes = {
|
const codes = {
|
||||||
down_arrow: 40,
|
down_arrow: 40,
|
||||||
|
|
|
@ -89,9 +89,9 @@ run_test("topics", ({override}) => {
|
||||||
override(stream_topic_history, "get_recent_topic_names", (stream_id) => {
|
override(stream_topic_history, "get_recent_topic_names", (stream_id) => {
|
||||||
switch (stream_id) {
|
switch (stream_id) {
|
||||||
case muted_stream_id:
|
case muted_stream_id:
|
||||||
return ["ms-topic1", "ms-topic2"];
|
return ["ms-topic1", "ms-topic2", "followed"];
|
||||||
case devel_stream_id:
|
case devel_stream_id:
|
||||||
return ["muted", "python"];
|
return ["muted", "python", "followed"];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
@ -107,12 +107,26 @@ run_test("topics", ({override}) => {
|
||||||
|
|
||||||
override(user_topics, "is_topic_muted", (_stream_name, topic) => topic === "muted");
|
override(user_topics, "is_topic_muted", (_stream_name, topic) => topic === "muted");
|
||||||
|
|
||||||
|
override(user_topics, "is_topic_followed", (_stream_name, topic) => topic === "followed");
|
||||||
|
|
||||||
let next_item = tg.get_next_topic("announce", "whatever");
|
let next_item = tg.get_next_topic("announce", "whatever");
|
||||||
assert.deepEqual(next_item, {
|
assert.deepEqual(next_item, {
|
||||||
stream: "devel",
|
stream: "devel",
|
||||||
topic: "python",
|
topic: "python",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
next_item = tg.get_next_topic("devel", "python");
|
||||||
|
assert.deepEqual(next_item, {
|
||||||
|
stream: "devel",
|
||||||
|
topic: "followed",
|
||||||
|
});
|
||||||
|
|
||||||
|
next_item = tg.get_next_topic("muted", "whatever", true);
|
||||||
|
assert.deepEqual(next_item, {
|
||||||
|
stream: "muted",
|
||||||
|
topic: "followed",
|
||||||
|
});
|
||||||
|
|
||||||
next_item = tg.get_next_topic("muted", undefined);
|
next_item = tg.get_next_topic("muted", undefined);
|
||||||
assert.deepEqual(next_item, {
|
assert.deepEqual(next_item, {
|
||||||
stream: "muted",
|
stream: "muted",
|
||||||
|
|
Loading…
Reference in New Issue