import { FixedBaseEmoji, NativeEmojiData } from '../types/Reaction'; // @ts-ignore import { init, PartialI18n } from 'emoji-mart'; import { loadEmojiPanelI18n } from './i18n'; export type SizeClassType = 'default' | 'small' | 'medium' | 'large' | 'jumbo'; function getRegexUnicodeEmojis() { return /\p{Emoji_Presentation}/gu; } function getCountOfAllMatches(str: string) { const regex = getRegexUnicodeEmojis(); const matches = str.match(regex); return matches?.length || 0; } function hasNormalCharacters(str: string) { const noEmoji = str.replace(getRegexUnicodeEmojis(), '').trim(); return noEmoji.length > 0; } export function getEmojiSizeClass(str: string): SizeClassType { if (!str || !str.length) { return 'small'; } if (hasNormalCharacters(str)) { return 'small'; } const emojiCount = getCountOfAllMatches(str); if (emojiCount > 6) { return 'small'; } else if (emojiCount > 4) { return 'medium'; } else if (emojiCount > 2) { return 'large'; } else { return 'jumbo'; } } export let nativeEmojiData: NativeEmojiData | null = null; export let i18nEmojiData: PartialI18n | null = null; export async function initialiseEmojiData(data: any): Promise { const ariaLabels: Record = {}; Object.entries(data.emojis).forEach(([key, value]: [string, any]) => { value.search = `,${[ [value.id, false], [value.name, true], [value.keywords, false], [value.emoticons, false], ] .map(([strings, split]) => { if (!strings) { return null; } return (Array.isArray(strings) ? strings : [strings]) .map(string => (split ? string.split(/[-|_|\s]+/) : [string]).map((s: string) => s.toLowerCase()) ) .flat(); }) .flat() .filter(a => a && a.trim()) .join(',')})}`; (value as FixedBaseEmoji).skins.forEach(skin => { ariaLabels[skin.native] = value.name; }); data.emojis[key] = value; }); data.ariaLabels = ariaLabels; nativeEmojiData = data; i18nEmojiData = await loadEmojiPanelI18n(); // Data needs to be initialised once per page load for the emoji components // See https://github.com/missive/emoji-mart#%EF%B8%8F%EF%B8%8F-headless-search init({ data, i18n: i18nEmojiData }); } // Synchronous version of Emoji Mart's SearchIndex.search() // If we upgrade the package things will probably break export function searchSync(query: string, args?: any): Array { if (!nativeEmojiData) { window.log.error('No native emoji data found'); return []; } if (!query || !query.trim().length) { return []; } const maxResults = args && args.maxResults ? args.maxResults : 90; const values = query .toLowerCase() .replace(/(\w)-/, '$1 ') .split(/[\s|,]+/) .filter((word: string, i: number, words: Array) => { return word.trim() && words.indexOf(word) === i; }); if (!values.length) { return []; } let pool: any = Object.values(nativeEmojiData.emojis); let results: Array = []; let scores: Record = {}; for (const value of values) { if (!pool.length) { break; } results = []; scores = {}; for (const emoji of pool) { if (!emoji.search) { continue; } const score: number = emoji.search.indexOf(`,${value}`); if (score === -1) { continue; } results.push(emoji); scores[emoji.id] = scores[emoji.id] ? scores[emoji.id] : 0; scores[emoji.id] += emoji.id === value ? 0 : score + 1; } pool = results; } if (results.length < 2) { return results; } results.sort((a: FixedBaseEmoji, b: FixedBaseEmoji) => { const aScore = scores[a.id]; const bScore = scores[b.id]; if (aScore === bScore) { return a.id.localeCompare(b.id); } return aScore - bScore; }); if (results.length > maxResults) { results = results.slice(0, maxResults); } return results; }