A11Y: multiple fixes to user stream items (#18368)

- in group activity, allows avatars to be selectable by tabbing or screen readers
- in user activity > drafts, fixes a bug where for draft replies, the wrong avatar was being shown in the user card
- in both group and user activity, fixes the order of focusable items
This commit is contained in:
Penar Musaraj 2022-09-27 10:59:26 -04:00 committed by GitHub
parent b97cb222c2
commit 217274f2c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 71 additions and 39 deletions

View File

@ -3,6 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { prioritizeNameInUx } from "discourse/lib/settings"; import { prioritizeNameInUx } from "discourse/lib/settings";
import { propertyEqual } from "discourse/lib/computed"; import { propertyEqual } from "discourse/lib/computed";
import { userPath } from "discourse/lib/url";
export default Component.extend({ export default Component.extend({
classNameBindings: [ classNameBindings: [
@ -35,4 +36,9 @@ export default Component.extend({
return `group-${postUser.primary_group_name}`; return `group-${postUser.primary_group_name}`;
} }
}, },
@discourseComputed("post.user.username")
userUrl(username) {
return userPath(username.toLowerCase());
},
}); });

View File

@ -2,6 +2,8 @@ import Component from "@ember/component";
import { actionDescription } from "discourse/widgets/post-small-action"; import { actionDescription } from "discourse/widgets/post-small-action";
import { computed } from "@ember/object"; import { computed } from "@ember/object";
import { propertyEqual } from "discourse/lib/computed"; import { propertyEqual } from "discourse/lib/computed";
import { userPath } from "discourse/lib/url";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({ export default Component.extend({
tagName: "li", tagName: "li",
@ -28,4 +30,9 @@ export default Component.extend({
"item.created_at", "item.created_at",
"item.action_code_who" "item.action_code_who"
), ),
@discourseComputed("item.draft_username", "item.username")
userUrl(draftUsername, username) {
return userPath((draftUsername || username).toLowerCase());
},
}); });

View File

@ -1,4 +1,4 @@
import { and, equal, or } from "@ember/object/computed"; import { equal, or } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import categoryFromId from "discourse-common/utils/category-macro"; import categoryFromId from "discourse-common/utils/category-macro";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
@ -108,7 +108,6 @@ const UserAction = RestModel.extend({
mentionType: equal("action_type", UserActionTypes.mentions), mentionType: equal("action_type", UserActionTypes.mentions),
isPM: or("messageSentType", "messageReceivedType"), isPM: or("messageSentType", "messageReceivedType"),
postReplyType: or("postType", "replyType"), postReplyType: or("postType", "replyType"),
removableBookmark: and("bookmarkType", "sameUser"),
addChild(action) { addChild(action) {
let groups = this.childGroups; let groups = this.childGroups;

View File

@ -1,11 +1,9 @@
<div class="clearfix info"> <div class="user-stream-item__header info">
<a href={{this.post.user.userUrl}} data-user-card={{this.post.user.username}} class="avatar-link"> <a href={{this.userUrl}} data-user-card={{this.post.user.username}} class="avatar-link">
{{avatar this.post.user imageSize="large" extraClasses="actor" ignoreTitle="true"}} {{avatar this.post.user imageSize="large" extraClasses="actor" ignoreTitle="true"}}
</a> </a>
<span class="time">{{format-date this.post.created_at leaveAgo="true"}}</span>
<ExpandPost @item={{this.post}} />
<div class="stream-topic-details"> <div class="user-stream-item__details">
<div class="stream-topic-title"> <div class="stream-topic-title">
<span class="title"> <span class="title">
<a href={{this.postUrl}}>{{html-safe this.post.topic.fancyTitle}}</a> <a href={{this.postUrl}}>{{html-safe this.post.topic.fancyTitle}}</a>
@ -20,6 +18,9 @@
</div> </div>
{{/if}} {{/if}}
</div> </div>
<ExpandPost @item={{this.post}} />
<span class="time">{{format-date this.post.created_at leaveAgo="true"}}</span>
</div> </div>
<div class="excerpt"> <div class="excerpt">

View File

@ -1,12 +1,11 @@
<div class="clearfix info"> <div class="user-stream-item__header info">
<a href={{@item.userUrl}} data-user-card={{@item.username}} class="avatar-link"><div class="avatar-wrapper">{{avatar @item imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div></a> <a href={{this.userUrl}} data-user-card={{(or @item.draft_username @item.username)}} class="avatar-link">
<span class="time">{{format-date @item.created_at}}</span> <div class="avatar-wrapper">
{{#if @item.draftType}} {{avatar @item imageSize="large" extraClasses="actor" ignoreTitle="true"}}
<span class="draft-type">{{html-safe @item.draftType}}</span> </div>
{{else}} </a>
<ExpandPost @item={{@item}} />
{{/if}} <div class="user-stream-item__details">
<div class="stream-topic-details">
<div class="stream-topic-title"> <div class="stream-topic-title">
<TopicStatus @topic={{@item}} @disableActions={{true}} /> <TopicStatus @topic={{@item}} @disableActions={{true}} />
<span class="title"> <span class="title">
@ -20,6 +19,13 @@
<div class="category">{{category-link @item.category}}</div> <div class="category">{{category-link @item.category}}</div>
</div> </div>
{{#if @item.draftType}}
<span class="draft-type">{{html-safe @item.draftType}}</span>
{{else}}
<ExpandPost @item={{@item}} />
{{/if}}
<span class="time">{{format-date @item.created_at}}</span>
{{#if @item.deleted_by}} {{#if @item.deleted_by}}
<span class="delete-info"> <span class="delete-info">
{{d-icon "far-trash-alt"}} {{d-icon "far-trash-alt"}}
@ -50,12 +56,12 @@
<div class="user-stream-item-actions child-actions"> <div class="user-stream-item-actions child-actions">
{{d-icon child.icon class="icon"}} {{d-icon child.icon class="icon"}}
{{#each child.items as |grandChild|}} {{#each child.items as |grandChild|}}
{{#if grandChild.removableBookmark}} <a href={{grandChild.userUrl}} data-user-card={{grandChild.username}} class="avatar-link">
<DButton @class="btn-default remove-bookmark" @action={{action @removeBookmark grandChild}} @icon="times" @label="bookmarks.remove" /> <div class="avatar-wrapper">
{{else}} {{avatar grandChild imageSize="tiny" extraClasses="actor" ignoreTitle="true" avatarTemplatePath="acting_avatar_template"}}
<a href={{grandChild.userUrl}} data-user-card={{grandChild.username}} class="avatar-link"><div class="avatar-wrapper">{{avatar grandChild imageSize="tiny" extraClasses="actor" ignoreTitle="true" avatarTemplatePath="acting_avatar_template"}}</div></a> </div>
</a>
{{#if grandChild.edit_reason}} &mdash; <span class="edit-reason">{{grandChild.edit_reason}}</span>{{/if}} {{#if grandChild.edit_reason}} &mdash; <span class="edit-reason">{{grandChild.edit_reason}}</span>{{/if}}
{{/if}}
{{/each}} {{/each}}
</div> </div>
{{/each}} {{/each}}

View File

@ -283,6 +283,13 @@ acceptance("Group - Authenticated", function (needs) {
); );
await click(".dialog-footer .btn-default"); await click(".dialog-footer .btn-default");
await visit("/g/discourse/activity/posts");
assert.ok(
".user-stream-item a.avatar-link[href='/u/awesomerobot']",
"avatar link contains href (is tabbable)"
);
}); });
test("Moderator Viewing Group", async function (assert) { test("Moderator Viewing Group", async function (assert) {

View File

@ -62,5 +62,12 @@ acceptance("User Drafts", function (needs) {
), ),
"shows the excerpt" "shows the excerpt"
); );
assert.ok(
query(".user-stream-item:nth-child(2) a.avatar-link").href.endsWith(
"/u/eviltrout"
),
"has correct avatar link"
);
}); });
}); });

View File

@ -26,7 +26,7 @@ export default {
draft_key: "topic_280", draft_key: "topic_280",
sequence: 0, sequence: 0,
draft_username: "eviltrout", draft_username: "eviltrout",
avatar_template: "/letter_avatar_proxy/v2/letter/p/a87d85/{size}.png", avatar_template: "/user_avatar/localhost/eviltrout/{size}/2_1.png",
data: '{"reply":"The last reply to this topic was 6 months ago. Your reply will bump the topic to the top of its list.","action":"reply","categoryId":8,"archetypeId":"regular","metaData":null,"composerTime":139499,"typingTime":6100}', data: '{"reply":"The last reply to this topic was 6 months ago. Your reply will bump the topic to the top of its list.","action":"reply","categoryId":8,"archetypeId":"regular","metaData":null,"composerTime":139499,"typingTime":6100}',
topic_id: 280, topic_id: 280,
username: "zogstrip", username: "zogstrip",

View File

@ -26,6 +26,15 @@
} }
} }
.user-stream-item__header {
display: flex;
align-items: flex-start;
}
.user-stream-item__details {
flex-grow: 1;
}
.type, .type,
span.name { span.name {
color: var(--primary); color: var(--primary);
@ -34,9 +43,10 @@
.time, .time,
.delete-info, .delete-info,
.draft-type { .draft-type {
float: right; line-height: var(--line-height-small);
color: var(--primary-medium); color: var(--primary-medium);
font-size: $font-down-2; font-size: $font-down-2;
padding-top: 5px;
} }
.notification .time { .notification .time {
@ -44,30 +54,21 @@
float: none; float: none;
} }
.draft-type {
clear: right;
}
.delete-info .d-icon { .delete-info .d-icon {
font-size: $font-0; font-size: $font-0;
} }
.expand-item, .expand-item,
.collapse-item { .collapse-item {
float: right;
margin-right: 0.5em; margin-right: 0.5em;
line-height: $line-height-small; margin-left: 0.25em;
line-height: var(--line-height-small);
padding-top: 3px;
color: var(--primary-medium); color: var(--primary-medium);
} }
.avatar-link { .avatar-link {
float: left; margin-right: 0.5em;
margin-right: 4px;
}
.title {
@include ellipsis;
display: block;
} }
.name { .name {
@ -81,10 +82,8 @@
padding: 3px 5px 5px 5px; padding: 3px 5px 5px 5px;
} }
.remove-bookmark,
.remove-draft { .remove-draft {
float: right; float: right;
margin-top: -4px;
} }
.notification { .notification {