forked from cybrespace/pinafore
better support for de-emojified user display names (#451)
improvements to #450 to fix #449, especially for aria labels
This commit is contained in:
parent
37e12e8d73
commit
af1d4b63d3
|
@ -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>
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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')) ||
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -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: 🌈')
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue