diff --git a/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js b/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js index 0b2c07f955d..694e867551d 100644 --- a/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js +++ b/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js @@ -10,9 +10,7 @@ import DiscourseURL from "discourse/lib/url"; import Composer from "discourse/models/composer"; import { capabilities } from "discourse/services/capabilities"; import { INPUT_DELAY } from "discourse-common/config/environment"; -import discourseDebounce from "discourse-common/lib/debounce"; import discourseLater from "discourse-common/lib/later"; -import { bind } from "discourse-common/utils/decorators"; import domUtils from "discourse-common/utils/dom-utils"; let extraKeyboardShortcutsHelp = {}; @@ -750,8 +748,11 @@ export default { for (const a of articles) { a.classList.remove("selected"); + a.removeAttribute("tabindex"); } article.classList.add("selected"); + article.setAttribute("tabindex", "0"); + article.focus(); this.appEvents.trigger("keyboard:move-selection", { articles, @@ -768,8 +769,7 @@ export default { ); } else if (article.classList.contains("topic-post")) { return this._scrollTo( - article.querySelector("#post_1") ? 0 : articleTopPosition, - { focusTabLoc: true } + article.querySelector("#post_1") ? 0 : articleTopPosition ); } @@ -786,25 +786,11 @@ export default { this._scrollTo(articleTopPosition - window.innerHeight * scrollRatio); }, - _scrollTo(scrollTop, opts = {}) { + _scrollTo(scrollTop) { window.scrollTo({ top: scrollTop, behavior: "smooth", }); - - if (opts.focusTabLoc) { - window.addEventListener("scroll", this._onScrollEnds, { passive: true }); - } - }, - - @bind - _onScrollEnds() { - window.removeEventListener("scroll", this._onScrollEnds, { passive: true }); - discourseDebounce(this, this._onScrollEndsCallback, animationDuration); - }, - - _onScrollEndsCallback() { - document.querySelector(".topic-post.selected span.tabLoc")?.focus(); }, categoriesTopicsList() { diff --git a/app/assets/javascripts/discourse/app/lib/utilities.js b/app/assets/javascripts/discourse/app/lib/utilities.js index 9d6e9d7d5a9..630a3746e46 100644 --- a/app/assets/javascripts/discourse/app/lib/utilities.js +++ b/app/assets/javascripts/discourse/app/lib/utilities.js @@ -78,17 +78,18 @@ export function highlightPost(postNumber) { return; } - container.querySelector(".tabLoc")?.focus(); - - const element = container.querySelector(".topic-body"); + const element = container.querySelector(".topic-body, .small-action-desc"); if (!element || element.classList.contains("highlighted")) { return; } element.classList.add("highlighted"); + element.setAttribute("tabindex", "0"); + element.focus(); const removeHighlighted = function () { element.classList.remove("highlighted"); + element.removeAttribute("tabindex"); element.removeEventListener("animationend", removeHighlighted); }; element.addEventListener("animationend", removeHighlighted); 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 c9ec30955e2..875eb114a51 100644 --- a/app/assets/javascripts/discourse/app/widgets/post-small-action.js +++ b/app/assets/javascripts/discourse/app/widgets/post-small-action.js @@ -190,9 +190,6 @@ 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/javascripts/discourse/app/widgets/post.js b/app/assets/javascripts/discourse/app/widgets/post.js index abf831dc847..d5695f2e91e 100644 --- a/app/assets/javascripts/discourse/app/widgets/post.js +++ b/app/assets/javascripts/discourse/app/widgets/post.js @@ -758,11 +758,7 @@ createWidget("post-article", { }, html(attrs, state) { - const rows = [ - h("span.tabLoc", { - attributes: { "aria-hidden": true, tabindex: -1 }, - }), - ]; + const rows = []; if (state.repliesAbove.length) { const replies = state.repliesAbove.map((p) => { return this.attach("embedded-post", p, { diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js index 04ccbd09908..631d2a80551 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/topic-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js @@ -11,6 +11,7 @@ import CategoryFixtures from "discourse/tests/fixtures/category-fixtures"; import topicFixtures from "discourse/tests/fixtures/topic"; import { acceptance, + chromeTest, count, exists, publishToMessageBus, @@ -412,18 +413,22 @@ acceptance("Topic featured links", function (needs) { ); }); - test("Quoting a quote with replyAsNewTopic keeps the original poster name", async function (assert) { - await visit("/t/internationalization-localization/280"); - await selectText("#post_5 blockquote"); - await triggerKeyEvent(document, "keypress", "J"); - await triggerKeyEvent(document, "keypress", "T"); + // Using J/K on Firefox clean the text selection, so this won't work there + chromeTest( + "Quoting a quote with replyAsNewTopic keeps the original poster name", + async function (assert) { + await visit("/t/internationalization-localization/280"); + await selectText("#post_5 blockquote"); + await triggerKeyEvent(document, "keypress", "J"); + await triggerKeyEvent(document, "keypress", "T"); - assert.ok( - query(".d-editor-input").value.includes( - 'quote="codinghorror said, post:3, topic:280"' - ) - ); - }); + assert.ok( + query(".d-editor-input").value.includes( + 'quote="codinghorror said, post:3, topic:280"' + ) + ); + } + ); test("Quoting by selecting text can mark the quote as full", async function (assert) { await visit("/t/internationalization-localization/280"); diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss index 3ee5ca6638d..3bc1abf630b 100644 --- a/app/assets/stylesheets/common/base/topic-post.scss +++ b/app/assets/stylesheets/common/base/topic-post.scss @@ -67,6 +67,7 @@ vertical-align: middle; a { color: var(--primary-high-or-secondary-low); + outline-offset: -1px; } } .fa { @@ -941,11 +942,24 @@ aside.quote { border-top: 1px solid var(--primary-low); padding-top: 0.5em; } + @media (prefers-reduced-motion: no-preference) { &.highlighted { animation: background-fade-highlight 2.5s ease-out; } } +} + +.topic-body:not(.deleted), +.small-action-desc { + @media (prefers-reduced-motion: no-preference) { + &.highlighted { + animation: background-fade-highlight 2.5s ease-out; + } + } + &.highlighted:focus-visible { + outline: none; + } .deleted & { // Disable so the deleted background is visible immediately &.highlighted { @@ -1140,6 +1154,10 @@ blockquote > *:last-child { display: flex; align-items: center; + &:focus-visible { + outline: none; + } + &.deleted { background-color: var(--danger-low-mid); } diff --git a/app/assets/stylesheets/common/components/keyboard_shortcuts.scss b/app/assets/stylesheets/common/components/keyboard_shortcuts.scss index 73397b26d56..65922f7f527 100644 --- a/app/assets/stylesheets/common/components/keyboard_shortcuts.scss +++ b/app/assets/stylesheets/common/components/keyboard_shortcuts.scss @@ -3,20 +3,22 @@ border-left: 1px solid transparent; } -.topic-list tr.selected td:first-child, -.topic-list-item.selected td:first-child, +.topic-list tr.selected, +.topic-list-item.selected, .latest-topic-list-item.selected, .search-results .fps-result.selected { box-shadow: inset 3px 0 0 var(--danger); // needs to be inset for Edge + &:focus-visible { + outline: none; + } } .featured-topic.selected, .topic-post.selected { box-shadow: -3px 0 0 var(--danger); -} - -.tabLoc:focus { - outline: none; + &:focus-visible { + outline: none; + } } .latest .featured-topic {