Skip to content

Commit

Permalink
A11Y: improve setting focus to a post (#24786)
Browse files Browse the repository at this point in the history
See #23367 for implementation details.
  • Loading branch information
pmusaraj committed Dec 8, 2023
1 parent 27144f1 commit 6dc5fe0
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 44 deletions.
24 changes: 5 additions & 19 deletions app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down Expand Up @@ -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,
Expand All @@ -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
);
}

Expand All @@ -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() {
Expand Down
10 changes: 7 additions & 3 deletions app/assets/javascripts/discourse/app/lib/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,21 @@ 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");

if (postNumber > 1) {
element.setAttribute("tabindex", "0");
element.focus();
}

const removeHighlighted = function () {
element.classList.remove("highlighted");
element.removeAttribute("tabindex");
element.removeEventListener("animationend", removeHighlighted);
};
element.addEventListener("animationend", removeHighlighted);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
6 changes: 1 addition & 5 deletions app/assets/javascripts/discourse/app/widgets/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -795,11 +795,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, {
Expand Down
27 changes: 16 additions & 11 deletions app/assets/javascripts/discourse/tests/acceptance/topic-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -425,18 +426,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");
Expand Down
18 changes: 18 additions & 0 deletions app/assets/stylesheets/common/base/topic-post.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
vertical-align: middle;
a {
color: var(--primary-high-or-secondary-low);
outline-offset: -1px;
}
}
.fa {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1140,6 +1154,10 @@ blockquote > *:last-child {
display: flex;
align-items: center;

&:focus-visible {
outline: none;
}

&.deleted {
background-color: var(--danger-low-mid);
}
Expand Down
13 changes: 10 additions & 3 deletions app/assets/stylesheets/common/components/keyboard_shortcuts.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@
.topic-list-item.selected td:first-child,
.latest-topic-list-item.selected,
.search-results .fps-result.selected {
box-shadow: inset 3px 0 0 var(--danger); // needs to be inset for Edge
box-shadow: inset 3px 0 0 var(--danger);
}

.featured-topic.selected,
.topic-post.selected {
box-shadow: -3px 0 0 var(--danger);
}

.tabLoc:focus {
outline: none;
.topic-list tr.selected,
.topic-list-item.selected,
.featured-topic.selected,
.topic-post.selected,
.latest-topic-list-item.selected,
.search-results .fps-result.selected {
&:focus-visible {
outline: none;
}
}

.latest .featured-topic {
Expand Down
17 changes: 17 additions & 0 deletions spec/system/topic_list_focus_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def focussed_topic_id
)&.to_i
end

def focussed_post_id
page.evaluate_script("document.activeElement.closest('.onscreen-post')?.dataset.postId")&.to_i
end

it "refocusses last clicked topic when going back to topic list" do
visit("/latest")
expect(page).to have_css("body.navigation-topics")
Expand Down Expand Up @@ -103,4 +107,17 @@ def focussed_topic_id
expect(page).to have_css("body.navigation-topics")
expect(focussed_topic_id).to eq(oldest_topic.id)
end

it "sets focus to the last post when navigating to a topic" do
extra_posts = Fabricate.times(5, :post, topic: topics[2])

visit("/latest")

discovery.topic_list.visit_topic_last_reply_via_keyboard(topics[2])
# send Tab key twice, the first event serves to focus the window
find("body").native.send_keys :tab
find("body").native.send_keys :tab

expect(focussed_post_id).to eq(topics[2].posts.last.id)
end
end

1 comment on commit 6dc5fe0

@discoursebot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit has been mentioned on Discourse Meta. There might be relevant details there:

https://meta.discourse.org/t/discourse-with-a-screen-reader/178105/152

Please sign in to comment.