FEATURE: g,j and g,k to navigate to next and prev topic

After visiting a topic list (by tag / category / top level) we track the list

Once a list is tracked the combo `g` `j` can be used to go to the next topic
in the list and `g` `k` to go to previous topic.

This allows you to quickly work through subsets of topics without having
to navigate back to the top level lists

The shortcut does not work in PM lists yet, or search results, both are
under consideration.
This commit is contained in:
Sam Saffron 2020-07-30 17:54:47 +10:00
parent 6da90af6c6
commit dc14d156b6
No known key found for this signature in database
GPG Key ID: B9606168D2FFD9F5
8 changed files with 98 additions and 2 deletions

View File

@ -69,7 +69,9 @@ export default Controller.extend(ModalFunctionality, {
bookmarks: buildShortcut("jump_to.bookmarks", { keys1: ["g", "b"] }),
profile: buildShortcut("jump_to.profile", { keys1: ["g", "p"] }),
messages: buildShortcut("jump_to.messages", { keys1: ["g", "m"] }),
drafts: buildShortcut("jump_to.drafts", { keys1: ["g", "d"] })
drafts: buildShortcut("jump_to.drafts", { keys1: ["g", "d"] }),
next: buildShortcut("jump_to.next", { keys1: ["g", "j"] }),
previous: buildShortcut("jump_to.previous", { keys1: ["g", "k"] })
},
navigation: {
back: buildShortcut("navigation.back", { keys1: ["u"] }),

View File

@ -5,6 +5,10 @@ import { minimumOffset } from "discourse/lib/offset-calculator";
import { ajax } from "discourse/lib/ajax";
import { throttle, schedule } from "@ember/runloop";
import { INPUT_DELAY } from "discourse-common/config/environment";
import {
nextTopicUrl,
previousTopicUrl
} from "discourse/lib/topic-list-tracker";
const DEFAULT_BINDINGS = {
"!": { postAction: "showFlags" },
@ -36,6 +40,8 @@ const DEFAULT_BINDINGS = {
"g m": { path: "/my/messages" },
"g d": { path: "/my/activity/drafts" },
"g s": { handler: "goToFirstSuggestedTopic", anonymous: true },
"g j": { handler: "goToNextTopic", anonymous: true },
"g k": { handler: "goToPreviousTopic", anonymous: true },
home: { handler: "goToFirstPost", anonymous: true },
"command+up": { handler: "goToFirstPost", anonymous: true },
j: { handler: "selectDown", anonymous: true },
@ -228,6 +234,22 @@ export default {
return false;
},
goToNextTopic() {
nextTopicUrl().then(url => {
if (url) {
DiscourseURL.routeTo(url);
}
});
},
goToPreviousTopic() {
previousTopicUrl().then(url => {
if (url) {
DiscourseURL.routeTo(url);
}
});
},
goToFirstSuggestedTopic() {
const $el = $(".suggested-topics a.raw-topic-link:first");
if ($el.length) {

View File

@ -0,0 +1,56 @@
import { Promise } from "rsvp";
let model, currentTopicId;
export function setTopicList(incomingModel) {
model = incomingModel;
currentTopicId = null;
}
export function nextTopicUrl() {
return urlAt(1);
}
export function previousTopicUrl() {
return urlAt(-1);
}
function urlAt(delta) {
if (!model || !model.topics) {
return Promise.resolve(null);
}
let index = currentIndex();
if (index === -1) {
index = 0;
} else {
index += delta;
}
const topic = model.topics[index];
if (!topic && index > 0 && model.more_topics_url && model.loadMore) {
return model.loadMore().then(() => urlAt(delta));
}
if (topic) {
currentTopicId = topic.id;
return Promise.resolve(topic.lastUnreadUrl);
}
return Promise.resolve(null);
}
export function setTopicId(topicId) {
currentTopicId = topicId;
}
function currentIndex() {
if (currentTopicId && model && model.topics) {
const idx = _.findIndex(model.topics, t => t.id === currentTopicId);
if (idx > -1) {
return idx;
}
}
return -1;
}

View File

@ -6,6 +6,7 @@ import DiscourseRoute from "discourse/routes/discourse";
import OpenComposer from "discourse/mixins/open-composer";
import { scrollTop } from "discourse/mixins/scroll-top";
import User from "discourse/models/user";
import { setTopicList } from "discourse/lib/topic-list-tracker";
export default DiscourseRoute.extend(OpenComposer, {
queryParams: {
@ -46,6 +47,9 @@ export default DiscourseRoute.extend(OpenComposer, {
didTransition() {
this.controllerFor("discovery")._showFooter();
this.send("loadingComplete");
const model = this.controllerFor("discovery/topics").get("model");
setTopicList(model);
return false;
},

View File

@ -11,6 +11,7 @@ import PermissionType from "discourse/models/permission-type";
import Category from "discourse/models/category";
import FilterModeMixin from "discourse/mixins/filter-mode";
import { escapeExpression } from "discourse/lib/utilities";
import { setTopicList } from "discourse/lib/topic-list-tracker";
export default DiscourseRoute.extend(FilterModeMixin, {
navMode: "latest",
@ -99,6 +100,9 @@ export default DiscourseRoute.extend(FilterModeMixin, {
staff: list.topic_list.tags[0].staff
});
}
setTopicList(list);
controller.setProperties({
list,
canCreateTopic: list.get("can_create_topic"),

View File

@ -4,6 +4,7 @@ import { cancel, later, schedule } from "@ember/runloop";
import DiscourseRoute from "discourse/routes/discourse";
import DiscourseURL from "discourse/lib/url";
import { ID_CONSTRAINT } from "discourse/models/topic";
import { setTopicId } from "discourse/lib/topic-list-tracker";
const SCROLL_DELAY = 500;
@ -200,7 +201,10 @@ const TopicRoute = DiscourseRoute.extend({
},
didTransition() {
this.controllerFor("topic")._showFooter();
const controller = this.controllerFor("topic");
controller._showFooter();
const topicId = controller.get("model.id");
setTopicId(topicId);
return true;
},

View File

@ -15,6 +15,8 @@
<li>{{html-safe shortcuts.jump_to.messages}}</li>
{{/if}}
<li>{{html-safe shortcuts.jump_to.drafts}}</li>
<li>{{html-safe shortcuts.jump_to.next}}</li>
<li>{{html-safe shortcuts.jump_to.previous}}</li>
</ul>
</section>
<section>

View File

@ -3185,6 +3185,8 @@ en:
profile: "%{shortcut} Profile"
messages: "%{shortcut} Messages"
drafts: "%{shortcut} Drafts"
next: "%{shortcut} Next Topic"
previous: "%{shortcut} Previous Topic"
navigation:
title: "Navigation"
jump: "%{shortcut} Go to post #"