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:
Prakhar Pratyush 2023-11-01 17:29:58 +05:30 committed by Tim Abbott
parent 6819ecee92
commit 8e2264b585
8 changed files with 65 additions and 10 deletions

View File

@ -14,7 +14,8 @@
!!! keyboard_tip ""
Use the <kbd>N</kbd> key to go to the next unread topic, or <kbd>P</kbd>
to go to the next unread direct message conversation.
Use the <kbd>N</kbd> key to go to the next unread topic, or <kbd>Shift</kbd> + <kbd>N</kbd>
for the next unread [followed](/help/follow-a-topic) topic, or <kbd>P</kbd> for the next
unread direct message conversation.
{end_tabs}

View File

@ -38,6 +38,8 @@ in the Zulip app to add more to your repertoire as needed.
* **Next unread topic**: <kbd>N</kbd>
* **Next unread followed topic**: <kbd>Shift</kbd> + <kbd>N</kbd>
* **Next unread direct message**: <kbd>P</kbd>
* **Search messages**: <kbd>/</kbd>

View File

@ -88,6 +88,7 @@ const keydown_shift_mappings = {
38: {name: "up_arrow", message_view_only: false}, // up arrow
40: {name: "down_arrow", message_view_only: false}, // down arrow
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 = {
@ -904,7 +905,10 @@ export function process_hotkey(e, hotkey) {
narrow.stream_cycle_forward();
return true;
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;
case "p_key":
narrow.narrow_to_next_pm_string({trigger: "hotkey"});

View File

@ -770,7 +770,11 @@ export function narrow_to_next_topic(opts = {}) {
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) {
return;

View File

@ -49,7 +49,7 @@ export function next_topic(streams, get_topics, has_unread_messages, curr_stream
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();
my_streams = my_streams.filter((stream_name) => {
@ -71,11 +71,28 @@ export function get_next_topic(curr_stream, curr_topic) {
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) {
const stream_id = stream_data.get_stream_id(stream_name);
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);
}

View File

@ -40,6 +40,10 @@
<td class="definition">{{t 'Next unread topic' }}</td>
<td><span class="hotkey"><kbd>N</kbd></span></td>
</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>
<td class="definition">{{t 'Next unread direct message' }}</td>
<td><span class="hotkey"><kbd>P</kbd></span></td>

View File

@ -182,6 +182,7 @@ run_test("mappings", () => {
assert.equal(map_down(13).name, "enter");
assert.equal(map_down(46).name, "delete");
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(106).name, "vim_down"); // j
@ -231,11 +232,15 @@ run_test("mappings", () => {
navigator.platform = "";
});
function process(s) {
function process(s, shiftKey, keydown = false) {
const e = {
which: s.codePointAt(0),
shiftKey,
};
try {
if (keydown) {
return hotkey.process_keydown(e);
}
return hotkey.process_keypress(e);
} catch (error) /* istanbul ignore next */ {
// 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) => {
assert.ok(process(c, shiftKey));
assert.ok(process(c, shiftKey, keydown));
assert.equal(stub.num_calls, 1);
});
}
@ -442,6 +447,10 @@ run_test("n/p keys", () => {
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", () => {
const codes = {
down_arrow: 40,

View File

@ -89,9 +89,9 @@ run_test("topics", ({override}) => {
override(stream_topic_history, "get_recent_topic_names", (stream_id) => {
switch (stream_id) {
case muted_stream_id:
return ["ms-topic1", "ms-topic2"];
return ["ms-topic1", "ms-topic2", "followed"];
case devel_stream_id:
return ["muted", "python"];
return ["muted", "python", "followed"];
}
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_followed", (_stream_name, topic) => topic === "followed");
let next_item = tg.get_next_topic("announce", "whatever");
assert.deepEqual(next_item, {
stream: "devel",
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);
assert.deepEqual(next_item, {
stream: "muted",