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
					
				
					 7 changed files with 89 additions and 24 deletions
				
			
		| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
<a href="/accounts/{verifyCredentials.id}"
 | 
			
		||||
   class="compose-box-avatar"
 | 
			
		||||
   aria-label="Profile for {verifyCredentials.display_name || verifyCredentials.acct}">
 | 
			
		||||
   aria-label="Profile for {accessibleName}">
 | 
			
		||||
  <Avatar account={verifyCredentials} size="small"/>
 | 
			
		||||
</a>
 | 
			
		||||
<a class="compose-box-display-name" href="/accounts/{verifyCredentials.id}">
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +52,7 @@
 | 
			
		|||
  import Avatar from '../Avatar.html'
 | 
			
		||||
  import { store } from '../../_store/store'
 | 
			
		||||
  import AccountDisplayName from '../profile/AccountDisplayName.html'
 | 
			
		||||
  import { removeEmoji } from '../../_utils/removeEmoji'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    components: {
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +61,15 @@
 | 
			
		|||
    },
 | 
			
		||||
    store: () => store,
 | 
			
		||||
    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>
 | 
			
		||||
| 
						 | 
				
			
			@ -5,12 +5,10 @@
 | 
			
		|||
  }
 | 
			
		||||
</style>
 | 
			
		||||
<script>
 | 
			
		||||
  import { emojifyText, removeEmoji } from '../../_utils/emojifyText'
 | 
			
		||||
  import { emojifyText } from '../../_utils/emojifyText'
 | 
			
		||||
  import { store } from '../../_store/store'
 | 
			
		||||
  import escapeHtml from 'escape-html'
 | 
			
		||||
  import emojiRegex from 'emoji-regex'
 | 
			
		||||
 | 
			
		||||
  let theEmojiRegex
 | 
			
		||||
  import { removeEmoji } from '../../_utils/removeEmoji'
 | 
			
		||||
 | 
			
		||||
  export default {
 | 
			
		||||
    store: () => store,
 | 
			
		||||
| 
						 | 
				
			
			@ -21,10 +19,9 @@
 | 
			
		|||
        accountName = escapeHtml(accountName)
 | 
			
		||||
 | 
			
		||||
        if ($omitEmojiInDisplayNames) { // display name emoji are annoying to some screenreader users
 | 
			
		||||
          theEmojiRegex = theEmojiRegex || emojiRegex() // only init when needed
 | 
			
		||||
          let emojiFreeAccountName = removeEmoji(accountName.replace(theEmojiRegex, ''), emojis).trim()
 | 
			
		||||
          if (emojiFreeAccountName) {
 | 
			
		||||
            return emojiFreeAccountName // only remove emoji if the resulting username is non-empty
 | 
			
		||||
          let emojiFreeDisplayName = removeEmoji(accountName, emojis)
 | 
			
		||||
          if (emojiFreeDisplayName) {
 | 
			
		||||
            return emojiFreeDisplayName
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@
 | 
			
		|||
  <ExternalLink href={account.url}
 | 
			
		||||
                showIcon="true"
 | 
			
		||||
                normalIconColor="true"
 | 
			
		||||
                ariaLabel="{account.display_name || account.acct} (opens in new window)"
 | 
			
		||||
                ariaLabel="{accessibleName} (opens in new window)"
 | 
			
		||||
  >
 | 
			
		||||
    <AccountDisplayName {account} />
 | 
			
		||||
  </ExternalLink>
 | 
			
		||||
| 
						 | 
				
			
			@ -81,8 +81,21 @@
 | 
			
		|||
  import Avatar from '../Avatar.html'
 | 
			
		||||
  import ExternalLink from '../ExternalLink.html'
 | 
			
		||||
  import AccountDisplayName from '../profile/AccountDisplayName.html'
 | 
			
		||||
  import { removeEmoji } from '../../_utils/removeEmoji'
 | 
			
		||||
  import { store } from '../../_store/store'
 | 
			
		||||
 | 
			
		||||
  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: {
 | 
			
		||||
      Avatar,
 | 
			
		||||
      ExternalLink,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,6 +110,7 @@
 | 
			
		|||
  import { classname } from '../../_utils/classname'
 | 
			
		||||
  import { checkDomAncestors } from '../../_utils/checkDomAncestors'
 | 
			
		||||
  import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
 | 
			
		||||
  import { removeEmoji } from '../../_utils/removeEmoji'
 | 
			
		||||
 | 
			
		||||
  const INPUT_TAGS = new Set(['a', 'button', 'input', 'textarea'])
 | 
			
		||||
  const isUserInputElement = node => INPUT_TAGS.has(node.localName)
 | 
			
		||||
| 
						 | 
				
			
			@ -208,9 +209,17 @@
 | 
			
		|||
        originalStatus.media_attachments &&
 | 
			
		||||
        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') +
 | 
			
		||||
          ` by ${originalAccount.display_name || originalAccount.username}`
 | 
			
		||||
          ` by ${originalAccountAccessibleName}`
 | 
			
		||||
      ),
 | 
			
		||||
      showHeader: ({ notification, status, timelineType }) => (
 | 
			
		||||
        (notification && (notification.type === 'reblog' || notification.type === 'favourite')) ||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,13 +15,3 @@ export function emojifyText (text, emojis, autoplayGifs) {
 | 
			
		|||
  }
 | 
			
		||||
  return text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function removeEmoji (text, emojis) {
 | 
			
		||||
  if (emojis) {
 | 
			
		||||
    for (let emoji of emojis) {
 | 
			
		||||
      let shortcodeWithColons = `:${emoji.shortcode}:`
 | 
			
		||||
      text = replaceAll(text, shortcodeWithColons, '')
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return text
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								routes/_utils/removeEmoji.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								routes/_utils/removeEmoji.js
									
										
									
									
									
										Normal 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()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
import { loginAsFoobar } from '../roles'
 | 
			
		||||
import {
 | 
			
		||||
  displayNameInComposeBox, generalSettingsButton, getNthStatusSelector, getUrl, homeNavButton,
 | 
			
		||||
  avatarInComposeBox,
 | 
			
		||||
  displayNameInComposeBox, generalSettingsButton, getNthStatus, getNthStatusSelector, getUrl, homeNavButton,
 | 
			
		||||
  removeEmojiFromDisplayNamesInput,
 | 
			
		||||
  settingsNavButton,
 | 
			
		||||
  sleep
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +39,7 @@ test('Can remove emoji from user display names', async t => {
 | 
			
		|||
  await t
 | 
			
		||||
    .expect(displayNameInComposeBox.innerText).eql('🌈 foo  🌈')
 | 
			
		||||
    .expect($('.compose-box-display-name img').exists).ok()
 | 
			
		||||
    .expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 foo :blobpats: 🌈')
 | 
			
		||||
    .click(settingsNavButton)
 | 
			
		||||
    .click(generalSettingsButton)
 | 
			
		||||
    .click(removeEmojiFromDisplayNamesInput)
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +47,7 @@ test('Can remove emoji from user display names', async t => {
 | 
			
		|||
    .click(homeNavButton)
 | 
			
		||||
    .expect(displayNameInComposeBox.innerText).eql('foo')
 | 
			
		||||
    .expect($('.compose-box-display-name img').exists).notOk()
 | 
			
		||||
    .expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for foo')
 | 
			
		||||
    .click(settingsNavButton)
 | 
			
		||||
    .click(generalSettingsButton)
 | 
			
		||||
    .click(removeEmojiFromDisplayNamesInput)
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +55,7 @@ test('Can remove emoji from user display names', async t => {
 | 
			
		|||
    .click(homeNavButton)
 | 
			
		||||
    .expect(displayNameInComposeBox.innerText).eql('🌈 foo  🌈')
 | 
			
		||||
    .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 => {
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +65,7 @@ test('Cannot remove emoji from user display names if result would be empty', asy
 | 
			
		|||
  await t
 | 
			
		||||
    .expect(displayNameInComposeBox.innerText).eql('🌈  🌈')
 | 
			
		||||
    .expect($('.compose-box-display-name img').exists).ok()
 | 
			
		||||
    .expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 :blobpats: 🌈')
 | 
			
		||||
    .click(settingsNavButton)
 | 
			
		||||
    .click(generalSettingsButton)
 | 
			
		||||
    .click(removeEmojiFromDisplayNamesInput)
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +73,7 @@ test('Cannot remove emoji from user display names if result would be empty', asy
 | 
			
		|||
    .click(homeNavButton)
 | 
			
		||||
    .expect(displayNameInComposeBox.innerText).eql('🌈  🌈')
 | 
			
		||||
    .expect($('.compose-box-display-name img').exists).ok()
 | 
			
		||||
    .expect(avatarInComposeBox.getAttribute('aria-label')).eql('Profile for 🌈 :blobpats: 🌈')
 | 
			
		||||
    .click(settingsNavButton)
 | 
			
		||||
    .click(generalSettingsButton)
 | 
			
		||||
    .click(removeEmojiFromDisplayNamesInput)
 | 
			
		||||
| 
						 | 
				
			
			@ -75,4 +81,28 @@ test('Cannot remove emoji from user display names if result would be empty', asy
 | 
			
		|||
    .click(homeNavButton)
 | 
			
		||||
    .expect(displayNameInComposeBox.innerText).eql('🌈  🌈')
 | 
			
		||||
    .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…
	
	Add table
		
		Reference in a new issue