better support for de-emojified user display names (#451)

improvements to #450 to fix #449, especially for aria labels
This commit is contained in:
Nolan Lawson 2018-08-19 19:31:54 -07:00 committed by GitHub
parent 37e12e8d73
commit af1d4b63d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 89 additions and 24 deletions

View file

@ -1,6 +1,6 @@
<a href="/accounts/{verifyCredentials.id}" <a href="/accounts/{verifyCredentials.id}"
class="compose-box-avatar" class="compose-box-avatar"
aria-label="Profile for {verifyCredentials.display_name || verifyCredentials.acct}"> aria-label="Profile for {accessibleName}">
<Avatar account={verifyCredentials} size="small"/> <Avatar account={verifyCredentials} size="small"/>
</a> </a>
<a class="compose-box-display-name" href="/accounts/{verifyCredentials.id}"> <a class="compose-box-display-name" href="/accounts/{verifyCredentials.id}">
@ -52,6 +52,7 @@
import Avatar from '../Avatar.html' import Avatar from '../Avatar.html'
import { store } from '../../_store/store' import { store } from '../../_store/store'
import AccountDisplayName from '../profile/AccountDisplayName.html' import AccountDisplayName from '../profile/AccountDisplayName.html'
import { removeEmoji } from '../../_utils/removeEmoji'
export default { export default {
components: { components: {
@ -60,7 +61,15 @@
}, },
store: () => store, store: () => store,
computed: { computed: {
verifyCredentials: ({ $currentVerifyCredentials }) => $currentVerifyCredentials verifyCredentials: ({ $currentVerifyCredentials }) => $currentVerifyCredentials,
emojis: ({ verifyCredentials }) => (verifyCredentials.emojis || []),
displayName: ({ verifyCredentials }) => verifyCredentials.display_name || verifyCredentials.username,
accessibleName: ({ displayName, emojis, $omitEmojiInDisplayNames }) => {
if ($omitEmojiInDisplayNames) {
return removeEmoji(displayName, emojis) || displayName
}
return displayName
}
} }
} }
</script> </script>

View file

@ -5,12 +5,10 @@
} }
</style> </style>
<script> <script>
import { emojifyText, removeEmoji } from '../../_utils/emojifyText' import { emojifyText } from '../../_utils/emojifyText'
import { store } from '../../_store/store' import { store } from '../../_store/store'
import escapeHtml from 'escape-html' import escapeHtml from 'escape-html'
import emojiRegex from 'emoji-regex' import { removeEmoji } from '../../_utils/removeEmoji'
let theEmojiRegex
export default { export default {
store: () => store, store: () => store,
@ -21,10 +19,9 @@
accountName = escapeHtml(accountName) accountName = escapeHtml(accountName)
if ($omitEmojiInDisplayNames) { // display name emoji are annoying to some screenreader users if ($omitEmojiInDisplayNames) { // display name emoji are annoying to some screenreader users
theEmojiRegex = theEmojiRegex || emojiRegex() // only init when needed let emojiFreeDisplayName = removeEmoji(accountName, emojis)
let emojiFreeAccountName = removeEmoji(accountName.replace(theEmojiRegex, ''), emojis).trim() if (emojiFreeDisplayName) {
if (emojiFreeAccountName) { return emojiFreeDisplayName
return emojiFreeAccountName // only remove emoji if the resulting username is non-empty
} }
} }

View file

@ -5,7 +5,7 @@
<ExternalLink href={account.url} <ExternalLink href={account.url}
showIcon="true" showIcon="true"
normalIconColor="true" normalIconColor="true"
ariaLabel="{account.display_name || account.acct} (opens in new window)" ariaLabel="{accessibleName} (opens in new window)"
> >
<AccountDisplayName {account} /> <AccountDisplayName {account} />
</ExternalLink> </ExternalLink>
@ -81,8 +81,21 @@
import Avatar from '../Avatar.html' import Avatar from '../Avatar.html'
import ExternalLink from '../ExternalLink.html' import ExternalLink from '../ExternalLink.html'
import AccountDisplayName from '../profile/AccountDisplayName.html' import AccountDisplayName from '../profile/AccountDisplayName.html'
import { removeEmoji } from '../../_utils/removeEmoji'
import { store } from '../../_store/store'
export default { export default {
store: () => store,
computed: {
emojis: ({ account }) => (account.emojis || []),
displayName: ({ account }) => account.display_name || account.username,
accessibleName: ({ displayName, emojis, $omitEmojiInDisplayNames }) => {
if ($omitEmojiInDisplayNames) {
return removeEmoji(displayName, emojis) || displayName
}
return displayName
}
},
components: { components: {
Avatar, Avatar,
ExternalLink, ExternalLink,

View file

@ -110,6 +110,7 @@
import { classname } from '../../_utils/classname' import { classname } from '../../_utils/classname'
import { checkDomAncestors } from '../../_utils/checkDomAncestors' import { checkDomAncestors } from '../../_utils/checkDomAncestors'
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
import { removeEmoji } from '../../_utils/removeEmoji'
const INPUT_TAGS = new Set(['a', 'button', 'input', 'textarea']) const INPUT_TAGS = new Set(['a', 'button', 'input', 'textarea'])
const isUserInputElement = node => INPUT_TAGS.has(node.localName) const isUserInputElement = node => INPUT_TAGS.has(node.localName)
@ -208,9 +209,17 @@
originalStatus.media_attachments && originalStatus.media_attachments &&
originalStatus.media_attachments.length originalStatus.media_attachments.length
), ),
ariaLabel: ({ originalAccount, originalStatus, visibility }) => ( originalAccountEmojis: ({ originalAccount }) => (originalAccount.emojis || []),
originalAccountDisplayName: ({ originalAccount }) => (originalAccount.display_name || originalAccount.username),
originalAccountAccessibleName: ({ originalAccountDisplayName, originalAccountEmojis, $omitEmojiInDisplayNames }) => {
if ($omitEmojiInDisplayNames) {
return removeEmoji(originalAccountDisplayName, originalAccountEmojis) || originalAccountDisplayName
}
return originalAccountDisplayName
},
ariaLabel: ({ originalAccountAccessibleName, originalStatus, visibility }) => (
(visibility === 'direct' ? 'Direct message' : 'Status') + (visibility === 'direct' ? 'Direct message' : 'Status') +
` by ${originalAccount.display_name || originalAccount.username}` ` by ${originalAccountAccessibleName}`
), ),
showHeader: ({ notification, status, timelineType }) => ( showHeader: ({ notification, status, timelineType }) => (
(notification && (notification.type === 'reblog' || notification.type === 'favourite')) || (notification && (notification.type === 'reblog' || notification.type === 'favourite')) ||

View file

@ -15,13 +15,3 @@ export function emojifyText (text, emojis, autoplayGifs) {
} }
return text return text
} }
export function removeEmoji (text, emojis) {
if (emojis) {
for (let emoji of emojis) {
let shortcodeWithColons = `:${emoji.shortcode}:`
text = replaceAll(text, shortcodeWithColons, '')
}
}
return text
}

View file

@ -0,0 +1,17 @@
import { replaceAll } from './strings'
import emojiRegex from 'emoji-regex'
let theEmojiRegex
export function removeEmoji (text, emojis) {
// remove custom emoji
if (emojis) {
for (let emoji of emojis) {
let shortcodeWithColons = `:${emoji.shortcode}:`
text = replaceAll(text, shortcodeWithColons, '')
}
}
// remove regular emoji
theEmojiRegex = theEmojiRegex || emojiRegex() // only init when needed, then cache
return text.replace(theEmojiRegex, '').trim()
}

View file

@ -1,6 +1,7 @@
import { loginAsFoobar } from '../roles' import { loginAsFoobar } from '../roles'
import { import {
displayNameInComposeBox, generalSettingsButton, getNthStatusSelector, getUrl, homeNavButton, avatarInComposeBox,
displayNameInComposeBox, generalSettingsButton, getNthStatus, getNthStatusSelector, getUrl, homeNavButton,
removeEmojiFromDisplayNamesInput, removeEmojiFromDisplayNamesInput,
settingsNavButton, settingsNavButton,
sleep sleep
@ -38,6 +39,7 @@ test('Can remove emoji from user display names', async t => {
await t await t
.expect(displayNameInComposeBox.innerText).eql('🌈 foo 🌈') .expect(displayNameInComposeBox.innerText).eql('🌈 foo 🌈')
.expect($('.compose-box-display-name img').exists).ok() .expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 foo :blobpats: 🌈')
.click(settingsNavButton) .click(settingsNavButton)
.click(generalSettingsButton) .click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput) .click(removeEmojiFromDisplayNamesInput)
@ -45,6 +47,7 @@ test('Can remove emoji from user display names', async t => {
.click(homeNavButton) .click(homeNavButton)
.expect(displayNameInComposeBox.innerText).eql('foo') .expect(displayNameInComposeBox.innerText).eql('foo')
.expect($('.compose-box-display-name img').exists).notOk() .expect($('.compose-box-display-name img').exists).notOk()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for foo')
.click(settingsNavButton) .click(settingsNavButton)
.click(generalSettingsButton) .click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput) .click(removeEmojiFromDisplayNamesInput)
@ -52,6 +55,7 @@ test('Can remove emoji from user display names', async t => {
.click(homeNavButton) .click(homeNavButton)
.expect(displayNameInComposeBox.innerText).eql('🌈 foo 🌈') .expect(displayNameInComposeBox.innerText).eql('🌈 foo 🌈')
.expect($('.compose-box-display-name img').exists).ok() .expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 foo :blobpats: 🌈')
}) })
test('Cannot remove emoji from user display names if result would be empty', async t => { test('Cannot remove emoji from user display names if result would be empty', async t => {
@ -61,6 +65,7 @@ test('Cannot remove emoji from user display names if result would be empty', asy
await t await t
.expect(displayNameInComposeBox.innerText).eql('🌈 🌈') .expect(displayNameInComposeBox.innerText).eql('🌈 🌈')
.expect($('.compose-box-display-name img').exists).ok() .expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 :blobpats: 🌈')
.click(settingsNavButton) .click(settingsNavButton)
.click(generalSettingsButton) .click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput) .click(removeEmojiFromDisplayNamesInput)
@ -68,6 +73,7 @@ test('Cannot remove emoji from user display names if result would be empty', asy
.click(homeNavButton) .click(homeNavButton)
.expect(displayNameInComposeBox.innerText).eql('🌈 🌈') .expect(displayNameInComposeBox.innerText).eql('🌈 🌈')
.expect($('.compose-box-display-name img').exists).ok() .expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 :blobpats: 🌈')
.click(settingsNavButton) .click(settingsNavButton)
.click(generalSettingsButton) .click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput) .click(removeEmojiFromDisplayNamesInput)
@ -75,4 +81,28 @@ test('Cannot remove emoji from user display names if result would be empty', asy
.click(homeNavButton) .click(homeNavButton)
.expect(displayNameInComposeBox.innerText).eql('🌈 🌈') .expect(displayNameInComposeBox.innerText).eql('🌈 🌈')
.expect($('.compose-box-display-name img').exists).ok() .expect($('.compose-box-display-name img').exists).ok()
.expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 :blobpats: 🌈')
})
test('Check status aria labels for de-emojified text', async t => {
await updateUserDisplayNameAs('foobar', '🌈 foo :blobpats: 🌈')
await sleep(1000)
await loginAsFoobar(t)
await t
.click(displayNameInComposeBox)
.expect(getNthStatus(0).getAttribute('aria-label')).eql('Status by 🌈 foo :blobpats: 🌈')
.click(settingsNavButton)
.click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput)
.expect(removeEmojiFromDisplayNamesInput.checked).ok()
.click(homeNavButton)
.click(displayNameInComposeBox)
.expect(getNthStatus(0).getAttribute('aria-label')).eql('Status by foo')
.click(settingsNavButton)
.click(generalSettingsButton)
.click(removeEmojiFromDisplayNamesInput)
.expect(removeEmojiFromDisplayNamesInput.checked).notOk()
.click(homeNavButton)
.click(displayNameInComposeBox)
.expect(getNthStatus(0).getAttribute('aria-label')).eql('Status by 🌈 foo :blobpats: 🌈')
}) })