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
This commit is contained in:
parent
350667e5df
commit
37e12e8d73
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -4177,6 +4177,11 @@
|
||||||
"minimalistic-crypto-utils": "^1.0.0"
|
"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": {
|
"emojis-list": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
"chokidar": "^2.0.4",
|
"chokidar": "^2.0.4",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
|
"emoji-regex": "^7.0.0",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"esm": "^3.0.77",
|
"esm": "^3.0.77",
|
||||||
"events": "^3.0.0",
|
"events": "^3.0.0",
|
||||||
|
|
|
@ -5,17 +5,29 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import { emojifyText } from '../../_utils/emojifyText'
|
import { emojifyText, removeEmoji } 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'
|
||||||
|
|
||||||
|
let theEmojiRegex
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
store: () => store,
|
store: () => store,
|
||||||
computed: {
|
computed: {
|
||||||
emojis: ({ account }) => (account.emojis || []),
|
emojis: ({ account }) => (account.emojis || []),
|
||||||
accountName: ({ account }) => (account.display_name || account.username),
|
accountName: ({ account }) => (account.display_name || account.username),
|
||||||
massagedAccountName: ({ accountName, emojis, $autoplayGifs }) => {
|
massagedAccountName: ({ accountName, emojis, $autoplayGifs, $omitEmojiInDisplayNames }) => {
|
||||||
accountName = escapeHtml(accountName)
|
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)
|
return emojifyText(accountName, emojis, $autoplayGifs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,11 @@
|
||||||
bind:checked="$reduceMotion" on:change="$save()">
|
bind:checked="$reduceMotion" on:change="$save()">
|
||||||
<label for="choice-reduce-motion">Reduce motion in UI animations</label>
|
<label for="choice-reduce-motion">Reduce motion in UI animations</label>
|
||||||
</div>
|
</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>
|
</form>
|
||||||
|
|
||||||
</SettingsLayout>
|
</SettingsLayout>
|
||||||
|
|
|
@ -15,6 +15,7 @@ const KEYS_TO_STORE_IN_LOCAL_STORAGE = new Set([
|
||||||
'autoplayGifs',
|
'autoplayGifs',
|
||||||
'markMediaAsSensitive',
|
'markMediaAsSensitive',
|
||||||
'reduceMotion',
|
'reduceMotion',
|
||||||
|
'omitEmojiInDisplayNames',
|
||||||
'pinnedPages',
|
'pinnedPages',
|
||||||
'composeData'
|
'composeData'
|
||||||
])
|
])
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { replaceAll } from './strings'
|
import { replaceAll } from './strings'
|
||||||
|
|
||||||
export function emojifyText (text, emojis, autoplayGifs) {
|
export function emojifyText (text, emojis, autoplayGifs) {
|
||||||
if (emojis && emojis.length) {
|
if (emojis) {
|
||||||
for (let emoji of emojis) {
|
for (let emoji of emojis) {
|
||||||
let urlToUse = autoplayGifs ? emoji.url : emoji.static_url
|
let urlToUse = autoplayGifs ? emoji.url : emoji.static_url
|
||||||
let shortcodeWithColons = `:${emoji.shortcode}:`
|
let shortcodeWithColons = `:${emoji.shortcode}:`
|
||||||
|
@ -15,3 +15,13 @@ 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
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export function replaceAll (string, replacee, replacement) {
|
export function replaceAll (string, replacee, replacement) {
|
||||||
if (!string.length || !replacee.length || !replacement.length) {
|
if (!string.length || !replacee.length) {
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
let idx
|
let idx
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { loginAsFoobar } from '../roles'
|
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 { updateUserDisplayNameAs } from '../serverActions'
|
||||||
import { Selector as $ } from 'testcafe'
|
import { Selector as $ } from 'testcafe'
|
||||||
|
|
||||||
|
@ -25,3 +30,49 @@ test('Cannot XSS using display name HTML', async t => {
|
||||||
await t
|
await t
|
||||||
.expect(displayNameInComposeBox.innerText).eql('<script>alert("pwn")</script>')
|
.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()
|
||||||
|
})
|
||||||
|
|
|
@ -41,6 +41,8 @@ export const followsButton = $('.account-profile-details > *:nth-child(2)')
|
||||||
export const followersButton = $('.account-profile-details > *:nth-child(3)')
|
export const followersButton = $('.account-profile-details > *:nth-child(3)')
|
||||||
export const avatarInComposeBox = $('.compose-box-avatar')
|
export const avatarInComposeBox = $('.compose-box-avatar')
|
||||||
export const displayNameInComposeBox = $('.compose-box-display-name')
|
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({
|
export const favoritesCountElement = $('.status-favs-reblogs:nth-child(3)').addCustomDOMProperties({
|
||||||
innerCount: el => parseInt(el.innerText, 10)
|
innerCount: el => parseInt(el.innerText, 10)
|
||||||
|
|
Loading…
Reference in a new issue