From cc81a7bec6fbfc733cd5978e978e3b0f5f1c0c08 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sun, 25 Nov 2018 01:20:58 -0800 Subject: [PATCH] fix(a11y): improved aria-label for status and notifications (#690) * fix(a11y): improved aria-label for status and notifications fixes #689 * only calculate formatted date once * fixup tests * fixup tests more * fixup * fixup tests again --- package.json | 3 +- routes/_a11y/getAccessibleLabelForStatus.js | 49 +++++++++++++ routes/_a11y/getAccountAccessibleName.js | 10 +++ routes/_components/status/Notification.html | 10 ++- routes/_components/status/Status.html | 30 ++++---- .../status/StatusRelativeDate.html | 17 +---- routes/_intl/formatTimeagoDate.js | 11 +++ routes/_utils/htmlToPlainText.js | 13 ++++ tests/spec/005-status-types.js | 7 +- tests/spec/022-status-aria-label.js | 69 +++++++++++++++++++ tests/spec/118-display-name-custom-emoji.js | 25 +++++-- 11 files changed, 203 insertions(+), 41 deletions(-) create mode 100644 routes/_a11y/getAccessibleLabelForStatus.js create mode 100644 routes/_a11y/getAccountAccessibleName.js create mode 100644 routes/_intl/formatTimeagoDate.js create mode 100644 routes/_utils/htmlToPlainText.js create mode 100644 tests/spec/022-status-aria-label.js diff --git a/package.json b/package.json index 01a87c0..2e5a7a4 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,8 @@ "Element", "Image", "NotificationEvent", - "NodeList" + "NodeList", + "DOMParser" ], "ignore": [ "dist", diff --git a/routes/_a11y/getAccessibleLabelForStatus.js b/routes/_a11y/getAccessibleLabelForStatus.js new file mode 100644 index 0000000..9b5beb6 --- /dev/null +++ b/routes/_a11y/getAccessibleLabelForStatus.js @@ -0,0 +1,49 @@ +import { getAccountAccessibleName } from './getAccountAccessibleName' +import { htmlToPlainText } from '../_utils/htmlToPlainText' +import { POST_PRIVACY_OPTIONS } from '../_static/statuses' + +function notificationText (notification, omitEmojiInDisplayNames) { + if (!notification) { + return + } + let notificationAccountDisplayName = getAccountAccessibleName(notification.account, omitEmojiInDisplayNames) + if (notification.type === 'reblog') { + return `${notificationAccountDisplayName} boosted your status` + } else if (notification.type === 'favourite') { + return `${notificationAccountDisplayName} favorited your status` + } +} + +function privacyText (visibility) { + for (let option of POST_PRIVACY_OPTIONS) { + if (option.key === visibility) { + return option.label + } + } +} + +function reblogText (reblog, account, omitEmojiInDisplayNames) { + if (!reblog) { + return + } + let accountDisplayName = getAccountAccessibleName(account, omitEmojiInDisplayNames) + return `Boosted by ${accountDisplayName}` +} + +export function getAccessibleLabelForStatus (originalAccount, account, content, + timeagoFormattedDate, spoilerText, showContent, + reblog, notification, visibility, omitEmojiInDisplayNames) { + let originalAccountDisplayName = getAccountAccessibleName(originalAccount, omitEmojiInDisplayNames) + + let values = [ + notificationText(notification, omitEmojiInDisplayNames), + originalAccountDisplayName, + (showContent || !spoilerText) ? htmlToPlainText(content) : `Content warning: ${spoilerText}`, + timeagoFormattedDate, + `@${originalAccount.acct}`, + privacyText(visibility), + reblogText(reblog, account, omitEmojiInDisplayNames) + ].filter(Boolean) + + return values.join(', ') +} diff --git a/routes/_a11y/getAccountAccessibleName.js b/routes/_a11y/getAccountAccessibleName.js new file mode 100644 index 0000000..e5df001 --- /dev/null +++ b/routes/_a11y/getAccountAccessibleName.js @@ -0,0 +1,10 @@ +import { removeEmoji } from '../_utils/removeEmoji' + +export function getAccountAccessibleName (account, omitEmojiInDisplayNames) { + let emojis = account.emojis + let displayName = account.display_name || account.username + if (omitEmojiInDisplayNames) { + displayName = removeEmoji(displayName, emojis) || displayName + } + return displayName +} diff --git a/routes/_components/status/Notification.html b/routes/_components/status/Notification.html index 8510ca8..6a26137 100644 --- a/routes/_components/status/Notification.html +++ b/routes/_components/status/Notification.html @@ -6,7 +6,9 @@
+ aria-setsize={length} + aria-label={ariaLabel} + >
@@ -30,6 +32,7 @@ import Status from './Status.html' import StatusHeader from './StatusHeader.html' import { store } from '../../_store/store' + import { getAccountAccessibleName } from '../../_a11y/getAccountAccessibleName' export default { components: { @@ -45,7 +48,10 @@ statusId: ({ status }) => status && status.id, uuid: ({ $currentInstance, timelineType, timelineValue, notificationId, statusId }) => { return `${$currentInstance}/${timelineType}/${timelineValue}/${notificationId}/${statusId || ''}` - } + }, + ariaLabel: ({ status, account, $omitEmojiInDisplayNames }) => ( + !status && `${getAccountAccessibleName(account, $omitEmojiInDisplayNames)} followed you, @${account.acct}` + ) } } \ No newline at end of file diff --git a/routes/_components/status/Status.html b/routes/_components/status/Status.html index da6fa1e..4403ebd 100644 --- a/routes/_components/status/Status.html +++ b/routes/_components/status/Status.html @@ -110,7 +110,9 @@ import { classname } from '../../_utils/classname' import { checkDomAncestors } from '../../_utils/checkDomAncestors' import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' - import { removeEmoji } from '../../_utils/removeEmoji' + import { getAccountAccessibleName } from '../../_a11y/getAccountAccessibleName' + import { getAccessibleLabelForStatus } from '../../_a11y/getAccessibleLabelForStatus' + import { formatTimeagoDate } from '../../_intl/formatTimeagoDate' const INPUT_TAGS = new Set(['a', 'button', 'input', 'textarea']) const isUserInputElement = node => INPUT_TAGS.has(node.localName) @@ -211,16 +213,17 @@ ), originalAccountEmojis: ({ originalAccount }) => (originalAccount.emojis || []), originalAccountDisplayName: ({ originalAccount }) => (originalAccount.display_name || originalAccount.username), - originalAccountAccessibleName: ({ originalAccountDisplayName, originalAccountEmojis, $omitEmojiInDisplayNames }) => { - if ($omitEmojiInDisplayNames) { - return removeEmoji(originalAccountDisplayName, originalAccountEmojis) || originalAccountDisplayName - } - return originalAccountDisplayName + originalAccountAccessibleName: ({ originalAccount, $omitEmojiInDisplayNames }) => { + return getAccountAccessibleName(originalAccount, $omitEmojiInDisplayNames) }, - ariaLabel: ({ originalAccountAccessibleName, originalStatus, visibility, isStatusInOwnThread }) => ( - (visibility === 'direct' ? 'Direct message' : 'Status') + - ` by ${originalAccountAccessibleName}` + - (isStatusInOwnThread ? ' (focused)' : '') + createdAtDate: ({ originalStatus }) => originalStatus.created_at, + timeagoFormattedDate: ({ createdAtDate }) => formatTimeagoDate(createdAtDate), + reblog: ({ status }) => status.reblog, + ariaLabel: ({ originalAccount, account, content, timeagoFormattedDate, spoilerText, + showContent, reblog, notification, visibility, $omitEmojiInDisplayNames }) => ( + getAccessibleLabelForStatus(originalAccount, account, content, + timeagoFormattedDate, spoilerText, showContent, + reblog, notification, visibility, $omitEmojiInDisplayNames) ), showHeader: ({ notification, status, timelineType }) => ( (notification && (notification.type === 'reblog' || notification.type === 'favourite')) || @@ -238,7 +241,8 @@ params: ({ notification, notificationId, status, statusId, timelineType, account, accountId, uuid, isStatusInNotification, isStatusInOwnThread, originalAccount, originalAccountId, spoilerShown, visibility, replyShown, - replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId }) => ({ + replyVisibility, spoilerText, originalStatus, originalStatusId, inReplyToId, + createdAtDate, timeagoFormattedDate }) => ({ notification, notificationId, status, @@ -258,7 +262,9 @@ spoilerText, originalStatus, originalStatusId, - inReplyToId + inReplyToId, + createdAtDate, + timeagoFormattedDate }) } } diff --git a/routes/_components/status/StatusRelativeDate.html b/routes/_components/status/StatusRelativeDate.html index cea2160..1eb5af9 100644 --- a/routes/_components/status/StatusRelativeDate.html +++ b/routes/_components/status/StatusRelativeDate.html @@ -2,9 +2,9 @@ href="/statuses/{originalStatusId}" focus-key={focusKey} > -