Remove redundant skin tone code

Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
This commit is contained in:
Anton Alexeyev 2025-05-01 17:11:15 +07:00
parent 752638f448
commit e19aec5abd
9 changed files with 64 additions and 118 deletions

View File

@ -6,7 +6,6 @@
import { createEventDispatcher } from 'svelte'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { tooltip, capitalizeFirstLetter, type EmojiWithGroup, type LabelAndProps } from '../../'
import { getEmoji } from '.'
import { type Emoji } from 'emojibase'
export let emoji: EmojiWithGroup
@ -18,14 +17,13 @@
const dispatch = createEventDispatcher()
const getSkinsCount = (e: Emoji | EmojiWithGroup): number | undefined => {
const getSkinsCount = (e: EmojiWithGroup): number | undefined => {
return Array.isArray(e.skins) ? e.skins.length : undefined
}
let displayedEmoji: Emoji | EmojiWithGroup
$: _emoji = (getSkinsCount(emoji) ?? 0) > 0 ? emoji : getEmoji(emoji.hexcode)?.parent ?? emoji
$: skinIndex = _emoji?.skins?.findIndex((skin) => skin.tone === skinTone) ?? -1
$: displayedEmoji = skinTone > 0 && Array.isArray(_emoji.skins) && skinIndex > -1 ? _emoji.skins[skinIndex] : _emoji
$: skinIndex = emoji?.skins?.findIndex((skin) => skin.tone === skinTone) ?? -1
$: displayedEmoji = skinTone > 0 && Array.isArray(emoji.skins) && skinIndex > -1 ? emoji.skins[skinIndex] : emoji
</script>
{#if emoji}
@ -34,9 +32,9 @@
class="hulyPopupEmoji-button"
class:preview
class:selected
class:skins={_emoji?.skins !== undefined && _emoji.skins.length === 5}
class:constructor={_emoji?.skins !== undefined && _emoji.skins.length > 5}
data-skins={getSkinsCount(_emoji)}
class:skins={emoji?.skins !== undefined && emoji.skins.length === 5}
class:constructor={emoji?.skins !== undefined && emoji.skins.length > 5}
data-skins={getSkinsCount(emoji)}
{disabled}
on:touchstart
on:contextmenu

View File

@ -4,7 +4,6 @@
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
//
import { createEventDispatcher, onMount, onDestroy } from 'svelte'
import type { Emoji } from 'emojibase'
import {
Scroller,
SearchInput,
@ -13,18 +12,16 @@
showPopup,
eventToHTMLElement,
ButtonBase,
closeTooltip
closeTooltip, getSkinnedEmoji
} from '../../'
import plugin from '../../plugin'
import {
searchEmoji,
emojiStore,
generateSkinToneEmojis,
emojiCategories,
getFrequentlyEmojis,
addFrequentlyEmojis,
removeFrequentlyEmojis,
getEmoji,
getSkinTone,
setSkinTone
} from '.'
@ -100,25 +97,23 @@
if (selectedCategory !== undefined) currentCategory = selectedCategory
}
const sendEmoji = (emoji: Emoji | EmojiWithGroup): void => {
const sendEmoji = (emoji: EmojiWithGroup): void => {
selected = emoji.emoji
addFrequentlyEmojis(emoji.hexcode)
addFrequentlyEmojis(emoji)
dispatch('close', {
emoji: emoji.emoji,
codes: emoji.hexcode.split('-').map((hc) => parseInt(hc, 16))
})
}
const selectedEmoji = (event: CustomEvent<{ detail: EmojiWithGroup }>): void => {
const selectedEmoji = (event: CustomEvent<EmojiWithGroup>): void => {
if (event.detail === undefined || typeof event.detail !== 'object') return
const detail = event.detail as unknown as EmojiWithGroup
sendEmoji(detail)
sendEmoji(event.detail)
}
function openContextMenu (event: TouchEvent | MouseEvent, _emoji: EmojiWithGroup, remove: boolean): void {
event.preventDefault()
const temp = getEmoji(_emoji.hexcode)
const emoji = temp?.parent ?? temp?.emoji
const emoji = _emoji
if (emoji === undefined) return
clearTimer()
@ -127,9 +122,9 @@
ActionsPopup,
{ emoji, remove },
eventToHTMLElement(event),
(result: 'remove' | Emoji | EmojiWithGroup) => {
(result: 'remove' | EmojiWithGroup) => {
if (result === 'remove') {
removeFrequentlyEmojis(emoji.hexcode)
removeFrequentlyEmojis(emoji)
const index = emojisCat.findIndex((ec) => ec.id === 'frequently-used')
if (index > -1) emojisCat[index].emojis = getFrequentlyEmojis()
emojisCat = emojisCat.filter(
@ -171,7 +166,7 @@
const showSkinMenu = (event: MouseEvent): void => {
shownSTM = true
showPopup(SkinTonePopup, { emoji: 0x1f590, selected: skinTone }, eventToHTMLElement(event), (result) => {
showPopup(SkinTonePopup, { emoji: getSkinnedEmoji(':hand:'), selected: skinTone }, eventToHTMLElement(event), (result) => {
if (typeof result === 'number') {
skinTone = result
setSkinTone(skinTone)
@ -270,7 +265,7 @@
tooltip={{ label: plugin.string.DefaultSkinTone }}
on:click={showSkinMenu}
>
<span style:font-size={'1.5rem'}>{generateSkinToneEmojis(0x1f590)[skinTone]}</span>
<span style:font-size={'1.5rem'}>{getSkinnedEmoji(':hand:', skinTone)?.emoji}</span>
</ButtonBase>
</div>
<Scroller

View File

@ -6,16 +6,16 @@
import { createEventDispatcher } from 'svelte'
import type { Emoji } from 'emojibase'
import { Label, closeTooltip, ModernCheckbox } from '../../'
import { generateSkinToneEmojis, skinTones, getEmojiCode } from '.'
import { skinTones } from '.'
import type { EmojiWithGroup } from '.'
export let emoji: number | number[] | string | Emoji | EmojiWithGroup
export let emoji: EmojiWithGroup
export let selected: number
const dispatch = createEventDispatcher()
closeTooltip()
const skins: string[] = generateSkinToneEmojis(getEmojiCode(emoji))
const skins: Emoji[] = emoji.skins !== undefined ? [emoji, ...emoji.skins] : []
</script>
<div class="hulyPopup-container noPadding">
@ -31,7 +31,7 @@
dispatch('close', index)
}}
>
<span style:font-size={'1.5rem'}>{skin}</span>
<span style:font-size={'1.5rem'}>{skin.emoji}</span>
{#if label}<span class="hulyPopup-row__label"><Label {label} /></span>{/if}
{#if disabled}<span class="hulyPopup-row__keys"><ModernCheckbox checked disabled /></span>{/if}
</button>

View File

@ -4,16 +4,16 @@
// Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0).
//
import { createEventDispatcher } from 'svelte'
import type { Emoji } from 'emojibase'
import { generateSkinToneEmojis, getEmojiCode, type EmojiWithGroup } from '.'
import { type EmojiWithGroup } from '.'
import { ButtonBase, closeTooltip } from '../..'
import { Emoji } from 'emojibase'
export let emoji: number | number[] | string | Emoji | EmojiWithGroup
export let emoji: EmojiWithGroup
export let selected: number
const dispatch = createEventDispatcher()
const skins: string[] = generateSkinToneEmojis(getEmojiCode(emoji))
const skins: Emoji[] = emoji.skins ?? []
</script>
<div class="flex-row-center flex-gap-1">
@ -30,7 +30,7 @@
closeTooltip()
}}
>
<span style:font-size={'1.5rem'}>{skin}</span>
<span style:font-size={'1.5rem'}>{skin.emoji}</span>
</ButtonBase>
{/each}
</div>

View File

@ -13,11 +13,9 @@
// limitations under the License.
//
import { writable, derived } from 'svelte/store'
import type { Emoji } from 'emojibase'
import type { EmojiWithGroup } from '.'
export const emojiStore = writable<EmojiWithGroup[]>([])
export const emojiComponents = writable<Emoji[]>([])
export const searchEmoji = writable<string>('')
export const resultEmojis = derived([emojiStore, searchEmoji], ([emojis, search]) => {
@ -29,16 +27,3 @@ export const resultEmojis = derived([emojiStore, searchEmoji], ([emojis, search]
)
: emojis
})
export const groupsResultEmojis = derived(resultEmojis, (result) => {
const keys = new Set(result.map((res) => res?.key ?? ''))
const groups: string[] = keys?.size > 0 ? [...keys] : ['']
return {
emojis: new Map<string, EmojiWithGroup[]>(
groups.map((group) => {
return [group, result.filter((res) => res.key === group)]
})
),
groups
}
})

View File

@ -17,7 +17,13 @@ import type { AnySvelteComponent } from '../..'
import type { Emoji } from 'emojibase'
export type EmojiWithGroup = Emoji & { key: string }
export type Emojis = Emoji | EmojiWithGroup
export interface CustomEmoji {
shortcode: string
label: string
url: string
tags?: string[]
}
export interface EmojiCategory {
id: string

View File

@ -13,15 +13,15 @@
// limitations under the License.
//
import { get } from 'svelte/store'
import type { Emoji, Locale } from 'emojibase'
import { fetchEmojis, fetchMessages } from 'emojibase'
import EMOJI_REGEX from 'emojibase-regex'
import EMOTICON_REGEX from 'emojibase-regex/emoticon'
import SHORTCODE_REGEX from 'emojibase-regex/shortcode'
import type { Emoji, Locale } from 'emojibase'
import { getCurrentAccount } from '@hcengineering/core'
import { deviceOptionsStore as deviceInfo } from '../..'
import { emojiStore, emojiComponents, emojiCategories, skinTonesCodes } from '.'
import type { EmojiWithGroup, EmojiHierarchy } from '.'
import type { EmojiWithGroup } from '.'
import { emojiCategories, emojiStore } from '.'
export const emojiRegex = EMOJI_REGEX
export const emojiGlobalRegex = new RegExp(EMOJI_REGEX.source, EMOJI_REGEX.flags + 'g')
@ -32,12 +32,9 @@ export const emoticonGlobalRegex = new RegExp(`(?<!\\S)${EMOTICON_REGEX.source}(
export const shortcodeRegex = new RegExp(`(?:^|\\s)(${SHORTCODE_REGEX.source})$`)
export const shortcodeGlobalRegex = new RegExp(`(?<!\\S)${SHORTCODE_REGEX.source}(?!\\S)`, SHORTCODE_REGEX.flags + 'g')
let availableEmojis: Emoji[]
let availableEmojis: EmojiWithGroup[]
export async function loadEmojis (lang?: string): Promise<{
emojis: EmojiWithGroup[]
components: Emoji[]
}> {
export async function loadEmojis (lang?: string): Promise<EmojiWithGroup[]> {
const local = lang ?? get(deviceInfo).language ?? 'en'
const englishEmojis =
local === 'en'
@ -64,27 +61,32 @@ export async function loadEmojis (lang?: string): Promise<{
})
: (englishEmojis as Emoji[])
return {
emojis: emojis
.filter((e) => e.group !== 2 && e.group !== undefined)
.map((e) => {
return { ...e, key: categories.get(groupKeys.get(e?.group ?? 0) ?? '') ?? '' }
}),
components: emojis.filter((e) => e.group === 2)
}
return emojis
.filter((e) => e.group !== 2 && e.group !== undefined)
.map((e) => {
return { ...e, key: categories.get(groupKeys.get(e?.group ?? 0) ?? '') ?? '' }
})
}
export async function updateEmojis (lang?: string): Promise<void> {
const { emojis, components } = await loadEmojis(lang)
const emojis = await loadEmojis(lang)
availableEmojis = emojis
emojiStore.set(emojis)
emojiComponents.set(components)
}
export function getSkinnedEmoji (shortcode: string | undefined, skinTone?: number): Emoji | undefined {
if (shortcode === undefined) return undefined
const shortcodeSlice = shortcode.slice(1, -1)
const matchEmoji = availableEmojis.find((e) => e.shortcodes?.includes(shortcodeSlice))
if (skinTone === undefined || matchEmoji === undefined) return matchEmoji
if (skinTone === 0) return matchEmoji
return matchEmoji.skins === undefined ? undefined : matchEmoji.skins[skinTone - 1]
}
export function getEmojiForShortCode (shortcode: string | undefined): string | undefined {
shortcode = shortcode?.slice(1, -1)
if (shortcode === undefined) return undefined
const result = availableEmojis.find(e => e.shortcodes?.includes(shortcode))
const shortcodeSlice = shortcode.slice(1, -1)
const result = availableEmojis.find(e => e.shortcodes?.includes(shortcodeSlice))
return result === undefined ? undefined : result.emoji
}
@ -99,9 +101,8 @@ function getEmojisLocalStorageKey (suffix: string = 'frequently'): string {
return `emojis.${suffix}.${me.uuid}`
}
export const removeFrequentlyEmojis = (_hexcode: string): void => {
const emoji = getEmoji(_hexcode)
const hexcode = emoji?.parent?.hexcode ?? emoji?.emoji.hexcode
export const removeFrequentlyEmojis = (emoji: EmojiWithGroup): void => {
const hexcode = emoji.hexcode
if (hexcode === undefined) return
const frequentlyEmojisKey = getEmojisLocalStorageKey()
@ -116,10 +117,9 @@ export const removeFrequentlyEmojis = (_hexcode: string): void => {
}
}
}
export const addFrequentlyEmojis = (_hexcode: string): void => {
const emoji = getEmoji(_hexcode)
const hexcode = emoji?.parent?.hexcode ?? emoji?.emoji.hexcode
if (hexcode === undefined) return
export const addFrequentlyEmojis = (emoji: EmojiWithGroup): void => {
if (emoji === undefined) return
const hexcode = emoji.hexcode
const frequentlyEmojisKey = getEmojisLocalStorageKey()
const frequentlyEmojis = window.localStorage.getItem(frequentlyEmojisKey)
@ -146,18 +146,8 @@ export const getFrequentlyEmojis = (): EmojiWithGroup[] | undefined => {
try {
const parsedEmojis = JSON.parse(frequentlyEmojis)
if (!Array.isArray(parsedEmojis)) return undefined
const res: EmojiWithGroup[] = []
for (const val of parsedEmojis) {
const map = getEmoji(val.hexcode)
const emoji = map?.parent ?? map?.emoji
if (emoji !== undefined) {
res.push(emoji)
}
}
return res
const emojis = get(emojiStore)
return emojis.filter(e => parsedEmojis.find(pe => pe.hexcode === e.hexcode) !== undefined)
} catch (e) {
console.error(e)
return undefined
@ -180,31 +170,3 @@ export const getSkinTone = (): number => {
return 0
}
}
export const generateSkinToneEmojis = (baseEmoji: number | number[]): string[] => {
const isArray = Array.isArray(baseEmoji)
return [
String.fromCodePoint(...(isArray ? baseEmoji : [baseEmoji])),
...skinTonesCodes.map((skinTone) => {
return String.fromCodePoint(...(isArray ? baseEmoji : [baseEmoji]), skinTone)
})
]
}
export const getEmojiMap = (): Map<string, EmojiHierarchy> | undefined => {
const result = new Map<string, EmojiHierarchy>()
const emojis = get(emojiStore)
emojis.forEach((emoji) => {
result.set(emoji.hexcode, { emoji })
emoji.skins?.forEach((skin) => result.set(skin.hexcode, { emoji: { ...skin, key: emoji.key }, parent: emoji }))
})
return result
}
export const getEmoji = (hexcode: string): EmojiHierarchy | undefined => {
return getEmojiMap()?.get(hexcode)
}
export const getEmojiCode = (e: number | number[] | string | Emoji | EmojiWithGroup): number | number[] => {
return typeof e === 'number' || Array.isArray(e)
? e
: (typeof e === 'object' ? e.hexcode : e).split('-').map((h) => parseInt(h, 16))
}

View File

@ -8,7 +8,7 @@ import {
getEventPositionElement,
showPopup,
type Location,
type Emojis
type EmojiWithGroup
} from '@hcengineering/ui'
import { type AttributeModel } from '@hcengineering/view'
import { get } from 'svelte/store'
@ -61,7 +61,7 @@ export async function addReactionAction (
closePopup()
showPopup(EmojiPopup, {}, element, (emoji: Emojis) => {
showPopup(EmojiPopup, {}, element, (emoji: EmojiWithGroup) => {
if (emoji?.emoji !== undefined) void updateDocReactions(reactions, message, emoji.emoji)
params?.onClose?.()
})

View File

@ -34,7 +34,7 @@ function handleEmoji (
if (!isValidEmojiPosition($from)) {
return
}
const emoji = getEmojiFunction(match[0])
const emoji = getEmojiFunction(match.pop())
if (emoji === undefined) return
commands.insertContentAt(range, [
{