mirror of https://github.com/FreeTubeApp/FreeTube
Fix handling of emojis with ZWJ sequences in profile initials (#5023)
This commit is contained in:
parent
7f3925d0c5
commit
88bed9eaf6
|
@ -1,6 +1,7 @@
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import { sanitizeForHtmlId } from '../../helpers/accessibility'
|
import { sanitizeForHtmlId } from '../../helpers/accessibility'
|
||||||
import { MAIN_PROFILE_ID } from '../../../constants'
|
import { MAIN_PROFILE_ID } from '../../../constants'
|
||||||
|
import { getFirstCharacter } from '../../helpers/strings'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'FtProfileBubble',
|
name: 'FtProfileBubble',
|
||||||
|
@ -24,6 +25,9 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
emits: ['click'],
|
emits: ['click'],
|
||||||
computed: {
|
computed: {
|
||||||
|
locale: function () {
|
||||||
|
return this.$i18n.locale.replace('_', '-')
|
||||||
|
},
|
||||||
isMainProfile: function () {
|
isMainProfile: function () {
|
||||||
return this.profileId === MAIN_PROFILE_ID
|
return this.profileId === MAIN_PROFILE_ID
|
||||||
},
|
},
|
||||||
|
@ -31,7 +35,9 @@ export default defineComponent({
|
||||||
return 'profileBubble' + sanitizeForHtmlId(this.profileId)
|
return 'profileBubble' + sanitizeForHtmlId(this.profileId)
|
||||||
},
|
},
|
||||||
profileInitial: function () {
|
profileInitial: function () {
|
||||||
return this?.profileName?.length > 0 ? Array.from(this.translatedProfileName)[0].toUpperCase() : ''
|
return this.profileName
|
||||||
|
? getFirstCharacter(this.translatedProfileName, this.locale).toUpperCase()
|
||||||
|
: ''
|
||||||
},
|
},
|
||||||
translatedProfileName: function () {
|
translatedProfileName: function () {
|
||||||
return this.isMainProfile ? this.$t('Profile.All Channels') : this.profileName
|
return this.isMainProfile ? this.$t('Profile.All Channels') : this.profileName
|
||||||
|
|
|
@ -8,6 +8,7 @@ import FtButton from '../../components/ft-button/ft-button.vue'
|
||||||
import { MAIN_PROFILE_ID } from '../../../constants'
|
import { MAIN_PROFILE_ID } from '../../../constants'
|
||||||
import { calculateColorLuminance, colors } from '../../helpers/colors'
|
import { calculateColorLuminance, colors } from '../../helpers/colors'
|
||||||
import { showToast } from '../../helpers/utils'
|
import { showToast } from '../../helpers/utils'
|
||||||
|
import { getFirstCharacter } from '../../helpers/strings'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'FtProfileEdit',
|
name: 'FtProfileEdit',
|
||||||
|
@ -47,11 +48,16 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
locale: function () {
|
||||||
|
return this.$i18n.locale.replace('_', '-')
|
||||||
|
},
|
||||||
colorValues: function () {
|
colorValues: function () {
|
||||||
return colors.map(color => color.value)
|
return colors.map(color => color.value)
|
||||||
},
|
},
|
||||||
profileInitial: function () {
|
profileInitial: function () {
|
||||||
return this?.profileName?.length > 0 ? Array.from(this.translatedProfileName)[0].toUpperCase() : ''
|
return this.profileName
|
||||||
|
? getFirstCharacter(this.translatedProfileName, this.locale).toUpperCase()
|
||||||
|
: ''
|
||||||
},
|
},
|
||||||
activeProfile: function () {
|
activeProfile: function () {
|
||||||
return this.$store.getters.getActiveProfile
|
return this.$store.getters.getActiveProfile
|
||||||
|
|
|
@ -5,6 +5,7 @@ import FtCard from '../../components/ft-card/ft-card.vue'
|
||||||
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
|
||||||
import { showToast } from '../../helpers/utils'
|
import { showToast } from '../../helpers/utils'
|
||||||
import { MAIN_PROFILE_ID } from '../../../constants'
|
import { MAIN_PROFILE_ID } from '../../../constants'
|
||||||
|
import { getFirstCharacter } from '../../helpers/strings'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'FtProfileSelector',
|
name: 'FtProfileSelector',
|
||||||
|
@ -19,6 +20,9 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
locale: function () {
|
||||||
|
return this.$i18n.locale.replace('_', '-')
|
||||||
|
},
|
||||||
profileList: function () {
|
profileList: function () {
|
||||||
return this.$store.getters.getProfileList
|
return this.$store.getters.getProfileList
|
||||||
},
|
},
|
||||||
|
@ -26,12 +30,15 @@ export default defineComponent({
|
||||||
return this.$store.getters.getActiveProfile
|
return this.$store.getters.getActiveProfile
|
||||||
},
|
},
|
||||||
activeProfileInitial: function () {
|
activeProfileInitial: function () {
|
||||||
// use Array.from, so that emojis don't get split up into individual character codes
|
return this.activeProfile?.name
|
||||||
return this.activeProfile?.name?.length > 0 ? Array.from(this.translatedProfileName(this.activeProfile))[0].toUpperCase() : ''
|
? getFirstCharacter(this.translatedProfileName(this.activeProfile), this.locale).toUpperCase()
|
||||||
|
: ''
|
||||||
},
|
},
|
||||||
profileInitials: function () {
|
profileInitials: function () {
|
||||||
return this.profileList.map((profile) => {
|
return this.profileList.map((profile) => {
|
||||||
return profile?.name?.length > 0 ? Array.from(this.translatedProfileName(profile))[0].toUpperCase() : ''
|
return profile?.name
|
||||||
|
? getFirstCharacter(this.translatedProfileName(profile), this.locale).toUpperCase()
|
||||||
|
: ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,6 +6,7 @@ import FtButton from '../../components/ft-button/ft-button.vue'
|
||||||
|
|
||||||
import { MAIN_PROFILE_ID } from '../../../constants'
|
import { MAIN_PROFILE_ID } from '../../../constants'
|
||||||
import { deepCopy, showToast } from '../../helpers/utils'
|
import { deepCopy, showToast } from '../../helpers/utils'
|
||||||
|
import { getFirstCharacter } from '../../helpers/strings'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'FtSubscribeButton',
|
name: 'FtSubscribeButton',
|
||||||
|
@ -45,9 +46,14 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
locale: function () {
|
||||||
|
return this.$i18n.locale.replace('_', '-')
|
||||||
|
},
|
||||||
profileInitials: function () {
|
profileInitials: function () {
|
||||||
return this.profileDisplayList.map((profile) => {
|
return this.profileDisplayList.map((profile) => {
|
||||||
return profile?.name?.length > 0 ? Array.from(profile.name)[0].toUpperCase() : ''
|
return profile.name
|
||||||
|
? getFirstCharacter(profile.name, this.locale).toUpperCase()
|
||||||
|
: ''
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -52,3 +52,31 @@ export function translateWindowTitle(title, i18n) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first user-perceived character,
|
||||||
|
* respecting language specific rules and
|
||||||
|
* emojis made up of multiple codepoints
|
||||||
|
* like flags, families and skin tone modifiers.
|
||||||
|
* @param {string} text
|
||||||
|
* @param {string} locale
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getFirstCharacter(text, locale) {
|
||||||
|
if (text.length === 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Firefox only received support for Intl.Segmenter support in version 125 (2024-04-16)
|
||||||
|
// so fallback to Array.from just in case.
|
||||||
|
// TODO: Remove fallback in the future
|
||||||
|
if (Intl.Segmenter) {
|
||||||
|
const segmenter = new Intl.Segmenter([locale, 'en'], { granularity: 'grapheme' })
|
||||||
|
|
||||||
|
// Use iterator directly as we only need the first segment
|
||||||
|
const firstSegment = segmenter.segment(text)[Symbol.iterator]().next().value
|
||||||
|
return firstSegment.segment
|
||||||
|
} else {
|
||||||
|
return Array.from(text)[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue