diff --git a/app/assets/javascripts/discourse/app/components/topic-entrance.js b/app/assets/javascripts/discourse/app/components/topic-entrance.js index 6cfe3d4fb6a..ca6b3ed3ac1 100644 --- a/app/assets/javascripts/discourse/app/components/topic-entrance.js +++ b/app/assets/javascripts/discourse/app/components/topic-entrance.js @@ -4,6 +4,7 @@ import DiscourseURL from "discourse/lib/url"; import I18n from "I18n"; import discourseComputed, { bind } from "discourse-common/utils/decorators"; import { scheduleOnce } from "@ember/runloop"; +import { inject as service } from "@ember/service"; function entranceDate(dt, showTime) { const today = new Date(); @@ -29,6 +30,8 @@ function entranceDate(dt, showTime) { } export default Component.extend(CleansUp, { + router: service(), + session: service(), elementId: "topic-entrance", classNameBindings: ["visible::hidden"], topic: null, @@ -161,6 +164,11 @@ export default Component.extend(CleansUp, { }, _jumpTo(destination) { + this.session.set("lastTopicIdViewed", { + topicId: this.topic.id, + historyUuid: this.router.location.getState?.().uuid, + }); + this.cleanUp(); DiscourseURL.routeTo(destination); }, diff --git a/app/assets/javascripts/discourse/app/components/topic-list-item.js b/app/assets/javascripts/discourse/app/components/topic-list-item.js index 07eb0892b10..6e3eee45990 100644 --- a/app/assets/javascripts/discourse/app/components/topic-list-item.js +++ b/app/assets/javascripts/discourse/app/components/topic-list-item.js @@ -37,9 +37,9 @@ export function showEntrance(e) { export function navigateToTopic(topic, href) { const owner = getOwner(this); - const siteSettings = owner.lookup("service:site-settings"); const router = owner.lookup("service:router"); const session = owner.lookup("service:session"); + const siteSettings = owner.lookup("service:site-settings"); const appEvents = owner.lookup("service:appEvents"); if (siteSettings.page_loading_indicator !== "slider") { @@ -284,7 +284,10 @@ export default Component.extend({ } } - if (classList.contains("raw-topic-link")) { + if ( + classList.contains("raw-topic-link") || + classList.contains("post-activity") + ) { if (wantsNewWindow(e)) { return true; } @@ -319,6 +322,13 @@ export default Component.extend({ unhandledRowClick() {}, + keyDown(e) { + if (e.key === "Enter" && e.target.classList.contains("post-activity")) { + e.preventDefault(); + return this.navigateToTopic(this.topic, e.target.getAttribute("href")); + } + }, + navigateToTopic, highlight(opts = { isLastViewedTopic: false }) { diff --git a/app/assets/javascripts/discourse/app/lib/utilities.js b/app/assets/javascripts/discourse/app/lib/utilities.js index 02c37b535ab..a7d24da4ab9 100644 --- a/app/assets/javascripts/discourse/app/lib/utilities.js +++ b/app/assets/javascripts/discourse/app/lib/utilities.js @@ -76,6 +76,9 @@ export function highlightPost(postNumber) { if (!container) { return; } + + container.querySelector(".tabLoc")?.focus(); + const element = container.querySelector(".topic-body"); if (!element || element.classList.contains("highlighted")) { return; @@ -88,7 +91,6 @@ export function highlightPost(postNumber) { element.removeEventListener("animationend", removeHighlighted); }; element.addEventListener("animationend", removeHighlighted); - container.querySelector(".tabLoc").focus(); } export function emailValid(email) { diff --git a/app/assets/javascripts/discourse/app/widgets/post-small-action.js b/app/assets/javascripts/discourse/app/widgets/post-small-action.js index cf502373f6c..f90ec6db284 100644 --- a/app/assets/javascripts/discourse/app/widgets/post-small-action.js +++ b/app/assets/javascripts/discourse/app/widgets/post-small-action.js @@ -186,6 +186,9 @@ export default createWidget("post-small-action", { } return [ + h("span.tabLoc", { + attributes: { "aria-hidden": true, tabindex: -1 }, + }), h("div.topic-avatar", iconNode(icons[attrs.actionCode] || "exclamation")), h("div.small-action-desc", [ h("div.small-action-contents", contents), diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss index f4fa950feed..4e48a52afed 100644 --- a/app/assets/stylesheets/common/base/_topic-list.scss +++ b/app/assets/stylesheets/common/base/_topic-list.scss @@ -328,6 +328,9 @@ .num.activity { a { padding: 15px 5px; + span.relative-date { + pointer-events: none; + } } } } diff --git a/spec/system/page_objects/components/topic_list.rb b/spec/system/page_objects/components/topic_list.rb index 4b03dcb3c93..49b97ead3b0 100644 --- a/spec/system/page_objects/components/topic_list.rb +++ b/spec/system/page_objects/components/topic_list.rb @@ -38,6 +38,15 @@ module PageObjects find("#{topic_list_item_class(topic)} a.raw-topic-link").click end + def visit_topic_last_reply_via_keyboard(topic) + find("#{topic_list_item_class(topic)} a.post-activity").native.send_keys(:return) + end + + def visit_topic_first_reply_via_keyboard(topic) + find("#{topic_list_item_class(topic)} button.posts-map").native.send_keys(:return) + find("#topic-entrance button.jump-top").native.send_keys(:return) + end + private def topic_list_item_class(topic) diff --git a/spec/system/topic_list_focus_spec.rb b/spec/system/topic_list_focus_spec.rb index 228fcd4e303..4c6a2008f7a 100644 --- a/spec/system/topic_list_focus_spec.rb +++ b/spec/system/topic_list_focus_spec.rb @@ -1,9 +1,18 @@ # frozen_string_literal: true describe "Topic list focus", type: :system do - let!(:topics) { Fabricate.times(10, :post).map(&:topic) } + fab!(:topics) { Fabricate.times(10, :post).map(&:topic) } - before { Fabricate(:admin) } + before_all do + sidebar_url = Fabricate(:sidebar_url, name: "my topic link", value: "/t/#{topics[4].id}") + + Fabricate( + :sidebar_section_link, + sidebar_section: + SidebarSection.find_by(section_type: SidebarSection.section_types[:community]), + linkable: sidebar_url, + ) + end let(:discovery) { PageObjects::Pages::Discovery.new } let(:topic) { PageObjects::Pages::Topic.new } @@ -15,7 +24,7 @@ describe "Topic list focus", type: :system do end it "refocusses last clicked topic when going back to topic list" do - visit("/") + visit("/latest") expect(page).to have_css("body.navigation-topics") expect(discovery.topic_list).to have_topics @@ -37,4 +46,39 @@ describe "Topic list focus", type: :system do expect(page).to have_css("body.navigation-topics") expect(focussed_topic_id).to eq(nil) end + + it "refocusses properly when navigating via the 'last activity' link" do + visit("/latest") + + # Visit topic via activity column and keyboard + discovery.topic_list.visit_topic_last_reply_via_keyboard(topics[2]) + expect(topic).to have_topic_title(topics[2].title) + + # Going back to the topic-list should re-focus + page.go_back + expect(page).to have_css("body.navigation-topics") + expect(focussed_topic_id).to eq(topics[2].id) + + # Visit topic via keyboard using posts map (OP button) + discovery.topic_list.visit_topic_first_reply_via_keyboard(topics[4]) + expect(topic).to have_topic_title(topics[4].title) + + # Going back to the topic-list should re-focus + page.go_back + expect(page).to have_css("body.navigation-topics") + expect(focussed_topic_id).to eq(topics[4].id) + end + + it "does not refocus topic when visiting via something other than topic list" do + visit("/latest") + + # Clicking sidebar link should visit topic + find(".sidebar-section-link[data-link-name='my topic link']").click + expect(topic).to have_topic_title(topics[4].title) + + # Going back to the topic-list should not re-focus + page.go_back + expect(page).to have_css("body.navigation-topics") + expect(focussed_topic_id).to eq(nil) + end end