From c8f61385ec291dd04e14a684c5658570df32e855 Mon Sep 17 00:00:00 2001 From: Anton Alexeyev Date: Fri, 2 May 2025 12:25:46 +0700 Subject: [PATCH] Extend emojis with Custom type Signed-off-by: Anton Alexeyev --- .../src/components/emoji/ActionsPopup.svelte | 26 +++++++------- .../src/components/emoji/EmojiButton.svelte | 26 +++++++------- .../components/emoji/EmojiGroupPalette.svelte | 7 ++-- .../ui/src/components/emoji/EmojiPopup.svelte | 20 ++++++----- .../src/components/emoji/SkinTonePopup.svelte | 8 ++--- .../components/emoji/SkinToneTooltip.svelte | 7 ++-- packages/ui/src/components/emoji/types.ts | 10 ++++-- packages/ui/src/components/emoji/utils.ts | 35 +++++++++++++++---- plugins/activity-resources/src/utils.ts | 5 +-- .../src/components/extension/emoji.ts | 5 +-- 10 files changed, 90 insertions(+), 59 deletions(-) diff --git a/packages/ui/src/components/emoji/ActionsPopup.svelte b/packages/ui/src/components/emoji/ActionsPopup.svelte index 88a3c04d96..3637df7bcb 100644 --- a/packages/ui/src/components/emoji/ActionsPopup.svelte +++ b/packages/ui/src/components/emoji/ActionsPopup.svelte @@ -5,34 +5,34 @@ // import { createEventDispatcher } from 'svelte' import type { Emoji } from 'emojibase' - import { EmojiWithGroup, getEmojiByHexcode } from '.' - import { EmojiButton, getSkinTone, emojiStore } from '.' + import { ExtendedEmoji, getEmojiByHexcode, EmojiButton, getSkinTone, emojiStore, getEmojiSkins } from '.' import { Label, IconDelete, closeTooltip, ButtonBase } from '../..' import plugin from '../../plugin' import SkinToneTooltip from './SkinToneTooltip.svelte' - export let emoji: EmojiWithGroup + export let emoji: ExtendedEmoji export let remove: boolean = false export let skinTone: number = getSkinTone() const dispatch = createEventDispatcher() closeTooltip() - const haveSkins = Array.isArray(emoji.skins) - const combinedEmoji = haveSkins && (emoji?.skins?.length ?? 0) > 5 + const skins = getEmojiSkins(emoji) + const haveSkins = Array.isArray(skins) + const combinedEmoji = haveSkins && (skins?.length ?? 0) > 5 const clickRemove = (): void => { dispatch('close', 'remove') } - const getEmojiParts = (e: EmojiWithGroup): EmojiWithGroup[] => { + const getEmojiParts = (): ExtendedEmoji[] => { const def = $emojiStore[168] - const temp = e.skins?.find((skin) => Array.isArray(skin.tone) && skin.tone.length > 1)?.hexcode.split('-200D-') + const temp = skins?.find((skin) => Array.isArray(skin.tone) && skin.tone.length > 1)?.hexcode.split('-200D-') if (temp === undefined || temp.length < 2) return [def, def] const firstEmoji = getEmojiByHexcode(temp[0].slice(0, -6)) ?? def const secondEmoji = getEmojiByHexcode(temp[temp.length - 1].slice(0, -6)) ?? def return [firstEmoji, secondEmoji] } - const emojiParts = getEmojiParts(emoji) + const emojiParts = getEmojiParts() const combinedTones: number[] = [skinTone, skinTone] const updateSkinTone = (result: CustomEvent<{ detail: number }>, index: number): void => { @@ -45,16 +45,16 @@ } } - const getEmojiByTone = (e: EmojiWithGroup, [a, b]: number[]): Emoji | undefined => { + const getEmojiByTone = (e: ExtendedEmoji, [a, b]: number[]): Emoji | undefined => { const equal = a === b const noTone = a === 0 return equal && noTone - ? e - : e.skins?.find((skin) => + ? e as Emoji + : getEmojiSkins(e)?.find((skin) => equal ? skin.tone === a : Array.isArray(skin.tone) && skin.tone[0] === a && skin.tone[1] === b ) } - const getEmojiStringByTone = (e: EmojiWithGroup, [a, b]: number[]): string | undefined => { + const getEmojiStringByTone = (e: ExtendedEmoji, [a, b]: number[]): string | undefined => { return getEmojiByTone(e, [a, b])?.emoji } @@ -92,7 +92,7 @@ {:else}
- {#each new Array((emoji.skins?.length ?? 5) + 1) as _, skin} + {#each new Array((skins?.length ?? 5) + 1) as _, skin} dispatch('close', result.detail)} /> {/each}
diff --git a/packages/ui/src/components/emoji/EmojiButton.svelte b/packages/ui/src/components/emoji/EmojiButton.svelte index 5caae69fce..daf35fb068 100644 --- a/packages/ui/src/components/emoji/EmojiButton.svelte +++ b/packages/ui/src/components/emoji/EmojiButton.svelte @@ -5,25 +5,23 @@ // import { createEventDispatcher } from 'svelte' import { getEmbeddedLabel } from '@hcengineering/platform' - import { tooltip, capitalizeFirstLetter, type EmojiWithGroup, type LabelAndProps } from '../../' - import { type Emoji } from 'emojibase' + import { tooltip, capitalizeFirstLetter, type LabelAndProps, type ExtendedEmoji, getEmojiSkins } from '../../' + import { isCustomEmoji } from './types' - export let emoji: EmojiWithGroup + export let emoji: ExtendedEmoji export let selected: boolean = false export let disabled: boolean = false export let preview: boolean = false export let skinTone: number = 0 export let showTooltip: LabelAndProps | undefined = undefined + const skins = getEmojiSkins(emoji) + const dispatch = createEventDispatcher() - const getSkinsCount = (e: EmojiWithGroup): number | undefined => { - return Array.isArray(e.skins) ? e.skins.length : undefined - } - - let displayedEmoji: Emoji | EmojiWithGroup - $: skinIndex = emoji?.skins?.findIndex((skin) => skin.tone === skinTone) ?? -1 - $: displayedEmoji = skinTone > 0 && Array.isArray(emoji.skins) && skinIndex > -1 ? emoji.skins[skinIndex] : emoji + let displayedEmoji: ExtendedEmoji + $: skinIndex = skins?.findIndex((skin) => skin.tone === skinTone) ?? -1 + $: displayedEmoji = skinTone > 0 && skins !== undefined && skinIndex > -1 ? skins[skinIndex] : emoji {#if emoji} @@ -32,9 +30,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={skins !== undefined && skins.length === 5} + class:constructor={skins !== undefined && skins.length > 5} + data-skins={skins?.length} {disabled} on:touchstart on:contextmenu @@ -43,7 +41,7 @@ dispatch('select', displayedEmoji) }} > - {displayedEmoji.emoji} + {isCustomEmoji(displayedEmoji) ? displayedEmoji.shortcode : displayedEmoji.emoji} {/if} diff --git a/packages/ui/src/components/emoji/EmojiGroupPalette.svelte b/packages/ui/src/components/emoji/EmojiGroupPalette.svelte index 2a1d2f6eee..0c83de9bd7 100644 --- a/packages/ui/src/components/emoji/EmojiGroupPalette.svelte +++ b/packages/ui/src/components/emoji/EmojiGroupPalette.svelte @@ -15,11 +15,12 @@
diff --git a/packages/ui/src/components/emoji/SkinToneTooltip.svelte b/packages/ui/src/components/emoji/SkinToneTooltip.svelte index b3fa283574..3f92c90f45 100644 --- a/packages/ui/src/components/emoji/SkinToneTooltip.svelte +++ b/packages/ui/src/components/emoji/SkinToneTooltip.svelte @@ -4,16 +4,17 @@ // Licensed under the Eclipse Public License v2.0 (SPDX: EPL-2.0). // import { createEventDispatcher } from 'svelte' - import { type EmojiWithGroup } from '.' + import { getEmojiSkins } from '.' import { ButtonBase, closeTooltip } from '../..' import { Emoji } from 'emojibase' - export let emoji: EmojiWithGroup + export let emoji: Emoji export let selected: number const dispatch = createEventDispatcher() - const skins: Emoji[] = emoji.skins !== undefined ? [emoji, ...emoji.skins] : [] + const emojiSkins = getEmojiSkins(emoji) + const skins: Emoji[] = emojiSkins !== undefined ? [emoji, ...emojiSkins] : []
diff --git a/packages/ui/src/components/emoji/types.ts b/packages/ui/src/components/emoji/types.ts index 696ce131d7..d390df12ae 100644 --- a/packages/ui/src/components/emoji/types.ts +++ b/packages/ui/src/components/emoji/types.ts @@ -16,7 +16,7 @@ import type { IntlString } from '@hcengineering/platform' import type { AnySvelteComponent } from '../..' import type { Emoji } from 'emojibase' -export type ExtendedEmoji = Emoji // | CustomEmoji +export type ExtendedEmoji = Emoji | CustomEmoji export type EmojiWithGroup = ExtendedEmoji & { key: string } export interface CustomEmoji { @@ -35,6 +35,10 @@ export interface EmojiCategory { emojis?: EmojiWithGroup[] } -/* export function isCustomEmoji (emoji: EmojiWithGroup): emoji is CustomEmoji & { key: string } { +export function isCustomEmoji (emoji: ExtendedEmoji): emoji is CustomEmoji { return 'url' in emoji -}*/ +} + +/* export function isCustomEmoji (emoji: ExtendedEmoji): boolean { + return false +} */ diff --git a/packages/ui/src/components/emoji/utils.ts b/packages/ui/src/components/emoji/utils.ts index 22e923e26a..0b393d470d 100644 --- a/packages/ui/src/components/emoji/utils.ts +++ b/packages/ui/src/components/emoji/utils.ts @@ -19,7 +19,7 @@ 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 } from '../..' +import { deviceOptionsStore as deviceInfo, type ExtendedEmoji, isCustomEmoji } from '../..' import type { EmojiWithGroup } from '.' import { emojiCategories, emojiStore } from '.' @@ -73,30 +73,41 @@ export async function updateEmojis (lang?: string): Promise { } export function getEmojiByHexcode (hexcode: string): EmojiWithGroup | undefined { - return get(emojiStore).find((e) => e.hexcode === hexcode) + return get(emojiStore).find((e) => !isCustomEmoji(e) && e.hexcode === hexcode) } export function getEmojiByEmoticon (emoticon: string | undefined): string | undefined { if (emoticon === undefined) return undefined - return findEmoji(e => Array.isArray(e.emoticon) ? e.emoticon.includes(emoticon) : e.emoticon === emoticon)?.emoji + 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 } export function getEmojiByShortCode (shortcode: string | undefined, skinTone?: number): ExtendedEmoji | undefined { if (shortcode === undefined) return undefined - return findEmoji((e) => e.shortcodes?.includes(shortcode.replaceAll(':', '')), skinTone) + return findEmoji((e) => { + if (isCustomEmoji(e)) return e.shortcode === shortcode + return e.shortcodes?.includes(shortcode.replaceAll(':', '')) + }, 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 = emoji.hexcode + const hexcode = isCustomEmoji(emoji) ? emoji.shortcode : emoji.hexcode if (hexcode === undefined) return const frequentlyEmojisKey = getEmojisLocalStorageKey() @@ -113,7 +124,7 @@ export const removeFrequentlyEmojis = (emoji: EmojiWithGroup): void => { } export const addFrequentlyEmojis = (emoji: EmojiWithGroup): void => { if (emoji === undefined) return - const hexcode = emoji.hexcode + const hexcode = isCustomEmoji(emoji) ? emoji.shortcode : emoji.hexcode const frequentlyEmojisKey = getEmojisLocalStorageKey() const frequentlyEmojis = window.localStorage.getItem(frequentlyEmojisKey) @@ -141,13 +152,23 @@ export const getFrequentlyEmojis = (): EmojiWithGroup[] | undefined => { const parsedEmojis = JSON.parse(frequentlyEmojis) if (!Array.isArray(parsedEmojis)) return undefined const emojis = get(emojiStore) - return emojis.filter(e => parsedEmojis.find(pe => pe.hexcode === e.hexcode || e.skins?.find(s => s.hexcode === pe.hexcode) !== undefined) !== undefined) + 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)) diff --git a/plugins/activity-resources/src/utils.ts b/plugins/activity-resources/src/utils.ts index 188f3a26d5..b685b164b5 100644 --- a/plugins/activity-resources/src/utils.ts +++ b/plugins/activity-resources/src/utils.ts @@ -8,7 +8,7 @@ import { getEventPositionElement, showPopup, type Location, - type EmojiWithGroup + type EmojiWithGroup, isCustomEmoji } from '@hcengineering/ui' import { type AttributeModel } from '@hcengineering/view' import { get } from 'svelte/store' @@ -62,7 +62,8 @@ export async function addReactionAction ( closePopup() showPopup(EmojiPopup, {}, element, (emoji: EmojiWithGroup) => { - if (emoji?.emoji !== undefined) void updateDocReactions(reactions, message, emoji.emoji) + // TODO: add custom emoji + if (!isCustomEmoji(emoji) && emoji?.emoji !== undefined) void updateDocReactions(reactions, message, emoji.emoji) params?.onClose?.() }) params?.onOpen?.() diff --git a/plugins/text-editor-resources/src/components/extension/emoji.ts b/plugins/text-editor-resources/src/components/extension/emoji.ts index c681926e3a..eeba837094 100644 --- a/plugins/text-editor-resources/src/components/extension/emoji.ts +++ b/plugins/text-editor-resources/src/components/extension/emoji.ts @@ -8,7 +8,7 @@ import { getEmojiByShortCode, emojiRegex, emojiGlobalRegex, - type ExtendedEmoji + type ExtendedEmoji, isCustomEmoji } from '@hcengineering/ui' import { type ResolvedPos } from '@tiptap/pm/model' import { type ExtendedRegExpMatchArray, type SingleCommands, type Range, InputRule, PasteRule } from '@tiptap/core' @@ -37,7 +37,8 @@ function handleEmoji ( } const emoji = getEmojiFunction(match.pop()) if (emoji === undefined) return - const unicodeEmoji = typeof emoji === 'string' ? emoji : emoji.emoji + // TODO: add custom emoji + const unicodeEmoji = typeof emoji === 'string' ? emoji : (isCustomEmoji(emoji) ? emoji.shortcode : emoji.emoji) commands.insertContentAt(range, [ { type: 'emoji',