platform/packages/ui/src/components/emoji/utils.ts
Anton Alexeyev 14f5252d7f Add custom emojis presentation
Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
2025-05-02 22:02:54 +07:00

202 lines
8.0 KiB
TypeScript

//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// 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 { getCurrentAccount } from '@hcengineering/core'
import { deviceOptionsStore as deviceInfo, type ExtendedEmoji, isCustomEmoji } 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')
export const emoticonRegex = new RegExp(`(?:^|\\s)(${EMOTICON_REGEX.source})$`)
export const emoticonGlobalRegex = new RegExp(`(?<!\\S)${EMOTICON_REGEX.source}(?!\\S)`, EMOTICON_REGEX.flags + 'g')
export const shortcodeRegex = new RegExp(`(?:^|\\s)(${SHORTCODE_REGEX.source})$`)
export const shortcodeGlobalRegex = new RegExp(`(?<!\\S)${SHORTCODE_REGEX.source}(?!\\S)`, SHORTCODE_REGEX.flags + 'g')
export async function loadEmojis (lang?: string): Promise<EmojiWithGroup[]> {
const local = lang ?? get(deviceInfo).language ?? 'en'
const englishEmojis =
local === 'en'
? await fetchEmojis('en', { version: '15.0', shortcodes: ['iamcal'] })
: await fetchEmojis('en', { compact: true, version: '15.0', shortcodes: ['iamcal'] })
const languageEmojis = local === 'en' ? null : await fetchEmojis(local as Locale, { version: '15.0' })
const messages = await fetchMessages(local as Locale)
const groups = messages.groups
const groupKeys = new Map<number, string>(groups.map((group, index) => [index, group.key]))
const categories = new Map<string, string>()
emojiCategories.forEach((cat) => {
if (Array.isArray(cat.categories)) cat.categories.forEach((c) => categories.set(c, cat.id))
else if (typeof cat.categories === 'string') categories.set(cat.categories, cat.id)
})
const emojis =
languageEmojis !== null
? languageEmojis.map((langEmoji, index) => {
return {
...langEmoji,
tags: [...(englishEmojis[index]?.tags ?? []), ...(langEmoji?.tags ?? [])],
shortcodes: [...(englishEmojis[index]?.shortcodes ?? []), ...(langEmoji?.shortcodes ?? [])]
}
})
: (englishEmojis as Emoji[])
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 = await loadEmojis(lang)
emojis.push({
shortcode: 'huly',
label: 'huly',
url: 'https://fonts.gstatic.com/s/e/notoemoji/latest/1f979/512.gif',
tags: ['huly'],
key: 'flags'
})
emojiStore.set(emojis)
}
export function getEmojiByHexcode (hexcode: string): EmojiWithGroup | undefined {
return get(emojiStore).find((e) => !isCustomEmoji(e) && e.hexcode === hexcode)
}
export function getEmojiByEmoticon (emoticon: string | undefined): string | undefined {
if (emoticon === undefined) return undefined
const matchEmoji = findEmoji(e => !isCustomEmoji(e) && (Array.isArray(e.emoticon) ? e.emoticon.includes(emoticon) : e.emoticon === emoticon))
if (matchEmoji === undefined) return undefined
return !isCustomEmoji(matchEmoji) ? matchEmoji.emoji : undefined
}
export function getUnicodeEmojiByShortCode (shortcode: string | undefined, skinTone?: number): Emoji | undefined {
const emoji = getEmojiByShortCode(shortcode, skinTone)
if (emoji === undefined || isCustomEmoji(emoji)) return undefined
return emoji
}
export function getEmojiByShortCode (shortcode: string | undefined, skinTone?: number): ExtendedEmoji | undefined {
if (shortcode === undefined) return undefined
const pureShortcode = shortcode.replaceAll(':', '')
return findEmoji((e) => {
if (isCustomEmoji(e)) return e.shortcode === pureShortcode
return e.shortcodes?.includes(pureShortcode)
}, skinTone)
}
function findEmoji (predicate: (e: EmojiWithGroup) => boolean | undefined, skinTone?: number): ExtendedEmoji | undefined {
const emojis = get(emojiStore)
const matchEmoji = emojis.find(predicate)
if (matchEmoji === undefined) return undefined
if (isCustomEmoji(matchEmoji)) return matchEmoji
if (skinTone === undefined) skinTone = getSkinTone()
if (skinTone === 0 || matchEmoji.skins === undefined) return matchEmoji
return matchEmoji.skins[skinTone - 1]
}
export const removeFrequentlyEmojis = (emoji: EmojiWithGroup): void => {
const hexcode = isCustomEmoji(emoji) ? emoji.shortcode : emoji.hexcode
if (hexcode === undefined) return
const frequentlyEmojisKey = getEmojisLocalStorageKey()
const frequentlyEmojis = window.localStorage.getItem(frequentlyEmojisKey)
if (frequentlyEmojis != null) {
const parsedEmojis = JSON.parse(frequentlyEmojis)
if (Array.isArray(parsedEmojis)) {
window.localStorage.setItem(
frequentlyEmojisKey,
JSON.stringify(parsedEmojis.filter((pe) => pe.hexcode !== hexcode))
)
}
}
}
export const addFrequentlyEmojis = (emoji: EmojiWithGroup): void => {
if (emoji === undefined) return
const hexcode = isCustomEmoji(emoji) ? emoji.shortcode : emoji.hexcode
const frequentlyEmojisKey = getEmojisLocalStorageKey()
const frequentlyEmojis = window.localStorage.getItem(frequentlyEmojisKey)
const empty = frequentlyEmojis == null
if (!empty) {
const parsedEmojis = JSON.parse(frequentlyEmojis)
if (Array.isArray(parsedEmojis)) {
const index = parsedEmojis.findIndex((pe) => pe.hexcode === hexcode)
if (index === -1) parsedEmojis.push({ hexcode, count: 1 })
else parsedEmojis[index].count++
parsedEmojis.sort((a, b) => b.count - a.count)
window.localStorage.setItem(frequentlyEmojisKey, JSON.stringify(parsedEmojis))
return undefined
}
}
window.localStorage.setItem(frequentlyEmojisKey, JSON.stringify([{ hexcode, count: 1 }]))
}
export const getFrequentlyEmojis = (): EmojiWithGroup[] | undefined => {
const frequentlyEmojisKey = getEmojisLocalStorageKey()
const frequentlyEmojis = window.localStorage.getItem(frequentlyEmojisKey)
if (frequentlyEmojis == null) return undefined
try {
const parsedEmojis = JSON.parse(frequentlyEmojis)
if (!Array.isArray(parsedEmojis)) return undefined
const emojis = get(emojiStore)
return emojis.filter((e) => {
if (isCustomEmoji(e)) {
return parsedEmojis.find(pe => pe.hexcode === e.shortcode) !== undefined
}
return parsedEmojis.find(pe => pe.hexcode === e.hexcode || e.skins?.find(s => s.hexcode === pe.hexcode) !== undefined) !== undefined
})
} catch (e) {
console.error(e)
return undefined
}
}
export function getEmojiSkins (emoji: ExtendedEmoji): Emoji[] | undefined {
if (isCustomEmoji(emoji)) return undefined
return emoji.skins
}
export const setSkinTone = (skinTone: number): void => {
const skinToneKey = getEmojisLocalStorageKey('skinTone')
window.localStorage.setItem(skinToneKey, JSON.stringify(skinTone))
}
export const getSkinTone = (): number => {
const skinToneKey = getEmojisLocalStorageKey('skinTone')
const skinTone = window.localStorage.getItem(skinToneKey)
if (skinTone == null) return 0
try {
return JSON.parse(skinTone)
} catch (e) {
console.error(e)
return 0
}
}
function getEmojisLocalStorageKey (suffix: string = 'frequently'): string {
const me = getCurrentAccount()
return `emojis.${suffix}.${me.uuid}`
}