From ca58d80b0c972f40ab865926a8b1661b52d59837 Mon Sep 17 00:00:00 2001
From: OsamaSayegh <asooomaasoooma90@gmail.com>
Date: Wed, 30 Mar 2022 00:43:27 +0300
Subject: [PATCH] A11Y: Improve accessibility of embedded replies below post

The changes are:

* Add an aria-label for the button that embeds/expand the replies of a
post below it
* Add an aria-label for the button that collapses the embedded replies
* Add an aria-label to describe the embedded replies section when
expanded and an aria-label for each embedded reply
---
 .../discourse/app/widgets/embedded-post.js    | 34 ++++++++++++-------
 .../discourse/app/widgets/post-menu.js        |  8 +++++
 .../javascripts/discourse/app/widgets/post.js | 12 ++++++-
 config/locales/client.en.yml                  |  6 ++++
 4 files changed, 47 insertions(+), 13 deletions(-)

diff --git a/app/assets/javascripts/discourse/app/widgets/embedded-post.js b/app/assets/javascripts/discourse/app/widgets/embedded-post.js
index e5192f44865..a845fc70eb1 100644
--- a/app/assets/javascripts/discourse/app/widgets/embedded-post.js
+++ b/app/assets/javascripts/discourse/app/widgets/embedded-post.js
@@ -21,24 +21,34 @@ createWidget("post-link-arrow", {
 });
 
 export default createWidget("embedded-post", {
+  tagName: "div.reply",
   buildKey: (attrs) => `embedded-post-${attrs.id}`,
 
+  buildAttributes(attrs) {
+    const attributes = { "data-post-id": attrs.id };
+    if (this.state.role) {
+      attributes.role = this.state.role;
+    }
+    if (this.state["aria-label"]) {
+      attributes["aria-label"] = this.state["aria-label"];
+    }
+    return attributes;
+  },
+
   html(attrs, state) {
     attrs.embeddedPost = true;
     return [
-      h("div.reply", { attributes: { "data-post-id": attrs.id } }, [
-        h("div.row", [
-          this.attach("post-avatar", attrs),
-          h("div.topic-body", [
-            h("div.topic-meta-data.embedded-reply", [
-              this.attach("poster-name", attrs),
-              this.attach("post-link-arrow", {
-                above: state.above,
-                shareUrl: attrs.customShare,
-              }),
-            ]),
-            new PostCooked(attrs, new DecoratorHelper(this), this.currentUser),
+      h("div.row", [
+        this.attach("post-avatar", attrs),
+        h("div.topic-body", [
+          h("div.topic-meta-data.embedded-reply", [
+            this.attach("poster-name", attrs),
+            this.attach("post-link-arrow", {
+              above: state.above,
+              shareUrl: attrs.customShare,
+            }),
           ]),
+          new PostCooked(attrs, new DecoratorHelper(this), this.currentUser),
         ]),
       ]),
     ];
diff --git a/app/assets/javascripts/discourse/app/widgets/post-menu.js b/app/assets/javascripts/discourse/app/widgets/post-menu.js
index ee5a4173114..8b9272783e9 100644
--- a/app/assets/javascripts/discourse/app/widgets/post-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/post-menu.js
@@ -270,6 +270,10 @@ registerButton("replies", (attrs, state, siteSettings) => {
     return;
   }
 
+  let ariaPressed;
+  if (!siteSettings.enable_filtered_replies_view) {
+    ariaPressed = state.repliesShown ? "true" : "false";
+  }
   return {
     action,
     icon,
@@ -284,6 +288,10 @@ registerButton("replies", (attrs, state, siteSettings) => {
     label: attrs.mobileView ? "post.has_replies_count" : "post.has_replies",
     iconRight: !siteSettings.enable_filtered_replies_view || attrs.mobileView,
     disabled: !!attrs.deleted,
+    translatedAriaLabel: I18n.t("post.sr_expand_replies", {
+      count: replyCount,
+    }),
+    ariaPressed,
   };
 });
 
diff --git a/app/assets/javascripts/discourse/app/widgets/post.js b/app/assets/javascripts/discourse/app/widgets/post.js
index 0e014710250..5811513ca49 100644
--- a/app/assets/javascripts/discourse/app/widgets/post.js
+++ b/app/assets/javascripts/discourse/app/widgets/post.js
@@ -464,7 +464,16 @@ createWidget("post-contents", {
       result.push(
         h("section.embedded-posts.bottom", [
           repliesBelow.map((p) => {
-            return this.attach("embedded-post", p, { model: p.asPost });
+            return this.attach("embedded-post", p, {
+              model: p.asPost,
+              state: {
+                role: "region",
+                "aria-label": I18n.t("post.sr_embedded_reply_description", {
+                  post_number: attrs.post_number,
+                  username: p.username,
+                }),
+              },
+            });
           }),
           this.attach("button", {
             title: "post.collapse",
@@ -472,6 +481,7 @@ createWidget("post-contents", {
             action: "toggleRepliesBelow",
             actionParam: "true",
             className: "btn collapse-up",
+            translatedAriaLabel: I18n.t("post.sr_collapse_replies"),
           }),
         ])
       );
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index eef8d919fe6..d3fa3ea0dea 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -3065,7 +3065,13 @@ en:
       show_hidden: "View ignored content."
       deleted_by_author_simple: "(post deleted by author)"
       collapse: "collapse"
+      sr_collapse_replies: "Collapse embedded replies"
+      sr_expand_replies:
+        one: "This post has %{count} reply. Click to expand"
+        other: "This post has %{count} replies. Click to expand"
       expand_collapse: "expand/collapse"
+      sr_below_embedded_posts_description: "post #%{post_number} replies"
+      sr_embedded_reply_description: "reply by @%{username} to post #%{post_number}"
       locked: "a staff member has locked this post from being edited"
       gap:
         one: "view %{count} hidden reply"