From 37e12e8d73e666b3ee07d409e28e03985a51baf4 Mon Sep 17 00:00:00 2001 From: Nolan Lawson <nolan@nolanlawson.com> Date: Sun, 19 Aug 2018 18:03:26 -0700 Subject: [PATCH] add option to remove emoji from user display names (#450) * add option to remove emoji from user display names fixes #449 * slight memory perf improvement --- package-lock.json | 5 ++ package.json | 1 + .../profile/AccountDisplayName.html | 16 +++++- routes/_pages/settings/general.html | 5 ++ routes/_store/store.js | 1 + routes/_utils/emojifyText.js | 12 ++++- routes/_utils/strings.js | 2 +- tests/spec/118-display-name-custom-emoji.js | 53 ++++++++++++++++++- tests/utils.js | 2 + 9 files changed, 92 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ca1470..0433bdf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4177,6 +4177,11 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "emoji-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.0.tgz", + "integrity": "sha512-lnvttkzAlYW8WpFPiStPWyd/YdS02cFsYwXwWqnbKY43fMgUeUx+vzW1Zaozu34n4Fm7sxygi8+SEL6dcks/hQ==" + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", diff --git a/package.json b/package.json index a22426f..28c1b97 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "chokidar": "^2.0.4", "cross-env": "^5.2.0", "css-loader": "^1.0.0", + "emoji-regex": "^7.0.0", "escape-html": "^1.0.3", "esm": "^3.0.77", "events": "^3.0.0", diff --git a/routes/_components/profile/AccountDisplayName.html b/routes/_components/profile/AccountDisplayName.html index 7a64502..40f25e6 100644 --- a/routes/_components/profile/AccountDisplayName.html +++ b/routes/_components/profile/AccountDisplayName.html @@ -5,17 +5,29 @@ } </style> <script> - import { emojifyText } from '../../_utils/emojifyText' + import { emojifyText, removeEmoji } from '../../_utils/emojifyText' import { store } from '../../_store/store' import escapeHtml from 'escape-html' + import emojiRegex from 'emoji-regex' + + let theEmojiRegex export default { store: () => store, computed: { emojis: ({ account }) => (account.emojis || []), accountName: ({ account }) => (account.display_name || account.username), - massagedAccountName: ({ accountName, emojis, $autoplayGifs }) => { + massagedAccountName: ({ accountName, emojis, $autoplayGifs, $omitEmojiInDisplayNames }) => { 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 + } + } + return emojifyText(accountName, emojis, $autoplayGifs) } } diff --git a/routes/_pages/settings/general.html b/routes/_pages/settings/general.html index 508565a..e98846e 100644 --- a/routes/_pages/settings/general.html +++ b/routes/_pages/settings/general.html @@ -18,6 +18,11 @@ bind:checked="$reduceMotion" on:change="$save()"> <label for="choice-reduce-motion">Reduce motion in UI animations</label> </div> + <div class="setting-group"> + <input type="checkbox" id="choice-omit-emoji-in-display-names" + bind:checked="$omitEmojiInDisplayNames" on:change="$save()"> + <label for="choice-omit-emoji-in-display-names">Remove emoji from user display names</label> + </div> </form> </SettingsLayout> diff --git a/routes/_store/store.js b/routes/_store/store.js index bccc71b..60c0578 100644 --- a/routes/_store/store.js +++ b/routes/_store/store.js @@ -15,6 +15,7 @@ const KEYS_TO_STORE_IN_LOCAL_STORAGE = new Set([ 'autoplayGifs', 'markMediaAsSensitive', 'reduceMotion', + 'omitEmojiInDisplayNames', 'pinnedPages', 'composeData' ]) diff --git a/routes/_utils/emojifyText.js b/routes/_utils/emojifyText.js index eb11baa..d203bba 100644 --- a/routes/_utils/emojifyText.js +++ b/routes/_utils/emojifyText.js @@ -1,7 +1,7 @@ import { replaceAll } from './strings' export function emojifyText (text, emojis, autoplayGifs) { - if (emojis && emojis.length) { + if (emojis) { for (let emoji of emojis) { let urlToUse = autoplayGifs ? emoji.url : emoji.static_url let shortcodeWithColons = `:${emoji.shortcode}:` @@ -15,3 +15,13 @@ 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 +} diff --git a/routes/_utils/strings.js b/routes/_utils/strings.js index dada260..c37790b 100644 --- a/routes/_utils/strings.js +++ b/routes/_utils/strings.js @@ -1,5 +1,5 @@ export function replaceAll (string, replacee, replacement) { - if (!string.length || !replacee.length || !replacement.length) { + if (!string.length || !replacee.length) { return string } let idx diff --git a/tests/spec/118-display-name-custom-emoji.js b/tests/spec/118-display-name-custom-emoji.js index 5640039..ea10bad 100644 --- a/tests/spec/118-display-name-custom-emoji.js +++ b/tests/spec/118-display-name-custom-emoji.js @@ -1,5 +1,10 @@ import { loginAsFoobar } from '../roles' -import { displayNameInComposeBox, getNthStatusSelector, getUrl, sleep } from '../utils' +import { + displayNameInComposeBox, generalSettingsButton, getNthStatusSelector, getUrl, homeNavButton, + removeEmojiFromDisplayNamesInput, + settingsNavButton, + sleep +} from '../utils' import { updateUserDisplayNameAs } from '../serverActions' import { Selector as $ } from 'testcafe' @@ -25,3 +30,49 @@ test('Cannot XSS using display name HTML', async t => { await t .expect(displayNameInComposeBox.innerText).eql('<script>alert("pwn")</script>') }) + +test('Can remove emoji from user display names', async t => { + await updateUserDisplayNameAs('foobar', '🌈 foo :blobpats: 🌈') + await sleep(1000) + await loginAsFoobar(t) + await t + .expect(displayNameInComposeBox.innerText).eql('🌈 foo 🌈') + .expect($('.compose-box-display-name img').exists).ok() + .click(settingsNavButton) + .click(generalSettingsButton) + .click(removeEmojiFromDisplayNamesInput) + .expect(removeEmojiFromDisplayNamesInput.checked).ok() + .click(homeNavButton) + .expect(displayNameInComposeBox.innerText).eql('foo') + .expect($('.compose-box-display-name img').exists).notOk() + .click(settingsNavButton) + .click(generalSettingsButton) + .click(removeEmojiFromDisplayNamesInput) + .expect(removeEmojiFromDisplayNamesInput.checked).notOk() + .click(homeNavButton) + .expect(displayNameInComposeBox.innerText).eql('🌈 foo 🌈') + .expect($('.compose-box-display-name img').exists).ok() +}) + +test('Cannot remove emoji from user display names if result would be empty', async t => { + await updateUserDisplayNameAs('foobar', '🌈 :blobpats: 🌈') + await sleep(1000) + await loginAsFoobar(t) + await t + .expect(displayNameInComposeBox.innerText).eql('🌈 🌈') + .expect($('.compose-box-display-name img').exists).ok() + .click(settingsNavButton) + .click(generalSettingsButton) + .click(removeEmojiFromDisplayNamesInput) + .expect(removeEmojiFromDisplayNamesInput.checked).ok() + .click(homeNavButton) + .expect(displayNameInComposeBox.innerText).eql('🌈 🌈') + .expect($('.compose-box-display-name img').exists).ok() + .click(settingsNavButton) + .click(generalSettingsButton) + .click(removeEmojiFromDisplayNamesInput) + .expect(removeEmojiFromDisplayNamesInput.checked).notOk() + .click(homeNavButton) + .expect(displayNameInComposeBox.innerText).eql('🌈 🌈') + .expect($('.compose-box-display-name img').exists).ok() +}) diff --git a/tests/utils.js b/tests/utils.js index 71e1031..836becd 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -41,6 +41,8 @@ export const followsButton = $('.account-profile-details > *:nth-child(2)') export const followersButton = $('.account-profile-details > *:nth-child(3)') export const avatarInComposeBox = $('.compose-box-avatar') export const displayNameInComposeBox = $('.compose-box-display-name') +export const generalSettingsButton = $('a[href="/settings/general"]') +export const removeEmojiFromDisplayNamesInput = $('#choice-omit-emoji-in-display-names') export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({ innerCount: el => parseInt(el.innerText, 10)