diff --git a/app/assets/javascripts/discourse/app/controllers/keyboard-shortcuts-help.js b/app/assets/javascripts/discourse/app/controllers/keyboard-shortcuts-help.js index 2199e6b7118..7e380bd1fc7 100644 --- a/app/assets/javascripts/discourse/app/controllers/keyboard-shortcuts-help.js +++ b/app/assets/javascripts/discourse/app/controllers/keyboard-shortcuts-help.js @@ -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"] }), diff --git a/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js b/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js index 50cc5ee7aa0..2095c02a210 100644 --- a/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js +++ b/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js @@ -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) { diff --git a/app/assets/javascripts/discourse/app/lib/topic-list-tracker.js b/app/assets/javascripts/discourse/app/lib/topic-list-tracker.js new file mode 100644 index 00000000000..c6a5ac30ded --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/topic-list-tracker.js @@ -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; +} diff --git a/app/assets/javascripts/discourse/app/routes/discovery.js b/app/assets/javascripts/discourse/app/routes/discovery.js index 38059e24470..43f030d9abd 100644 --- a/app/assets/javascripts/discourse/app/routes/discovery.js +++ b/app/assets/javascripts/discourse/app/routes/discovery.js @@ -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; }, diff --git a/app/assets/javascripts/discourse/app/routes/tags-show.js b/app/assets/javascripts/discourse/app/routes/tags-show.js index 74a7ad5efbe..1b264249920 100644 --- a/app/assets/javascripts/discourse/app/routes/tags-show.js +++ b/app/assets/javascripts/discourse/app/routes/tags-show.js @@ -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"), diff --git a/app/assets/javascripts/discourse/app/routes/topic.js b/app/assets/javascripts/discourse/app/routes/topic.js index 6f7fd410c62..55c8a740d52 100644 --- a/app/assets/javascripts/discourse/app/routes/topic.js +++ b/app/assets/javascripts/discourse/app/routes/topic.js @@ -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; }, diff --git a/app/assets/javascripts/discourse/app/templates/modal/keyboard-shortcuts-help.hbs b/app/assets/javascripts/discourse/app/templates/modal/keyboard-shortcuts-help.hbs index 813ab3a1f65..514080379da 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/keyboard-shortcuts-help.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/keyboard-shortcuts-help.hbs @@ -15,6 +15,8 @@