544a170ead2c | atagen | 2026-10-20 11:47 UTC | 1 | export interface EmojiEntry { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 2 | emoji: string; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 3 | label: string; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 4 | tags: string[]; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 5 | shortcodes: string[]; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 6 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 7 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 8 | export interface EmojiGroup { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 9 | name: string; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 10 | emojis: EmojiEntry[]; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 11 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 12 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 13 | interface RawEmoji { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 14 | hexcode: string; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 15 | unicode: string; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 16 | label: string; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 17 | group?: number; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 18 | order?: number; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 19 | tags?: string[]; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 20 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 21 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 22 | const GROUP_NAMES: Record<number, string> = { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 23 | 0: 'Smileys & emotion', |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 24 | 1: 'People & body', |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 25 | 3: 'Animals & nature', |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 26 | 4: 'Food & drink', |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 27 | 5: 'Travel & places', |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 28 | 6: 'Activities', |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 29 | 7: 'Objects', |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 30 | 8: 'Symbols', |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 31 | 9: 'Flags' |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 32 | }; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 33 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 34 | let loading: Promise<EmojiGroup[]> | null = null; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 35 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 36 | export function loadEmoji(): Promise<EmojiGroup[]> { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 37 | loading ??= (async () => { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 38 | const [compact, codes] = await Promise.all([ |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 39 | import('emojibase-data/en/compact.json'), |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 40 | import('emojibase-data/en/shortcodes/emojibase.json') |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 41 | ]); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 42 | const shortcodes = codes.default as Record<string, string | string[]>; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 43 | const byGroup = new Map<number, EmojiEntry[]>(); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 44 | for (const raw of compact.default as RawEmoji[]) { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 45 | if (raw.group === undefined || !(raw.group in GROUP_NAMES)) continue; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 46 | const code = shortcodes[raw.hexcode]; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 47 | const entry: EmojiEntry = { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 48 | emoji: raw.unicode, |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 49 | label: raw.label, |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 50 | tags: raw.tags ?? [], |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 51 | shortcodes: code === undefined ? [] : Array.isArray(code) ? code : [code] |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 52 | }; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 53 | const list = byGroup.get(raw.group) ?? []; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 54 | list.push(entry); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 55 | byGroup.set(raw.group, list); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 56 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 57 | return Object.entries(GROUP_NAMES).flatMap(([id, name]) => { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 58 | const emojis = byGroup.get(Number(id)); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 59 | return emojis ? [{ name, emojis }] : []; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 60 | }); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 61 | })().catch((e) => { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 62 | loading = null; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 63 | throw e; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 64 | }); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 65 | return loading; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 66 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 67 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 68 | export function matchesEmoji(entry: EmojiEntry, query: string): boolean { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 69 | const q = query.trim().toLowerCase().replace(/^:|:$/g, ''); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 70 | if (!q) return true; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 71 | return ( |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 72 | entry.label.toLowerCase().includes(q) || |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 73 | entry.tags.some((t) => t.toLowerCase().includes(q)) || |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 74 | entry.shortcodes.some((s) => s.toLowerCase().includes(q)) |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 75 | ); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 76 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 77 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 78 | export function searchEmoji(groups: EmojiGroup[], query: string): EmojiGroup[] { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 79 | return groups |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 80 | .map((g) => ({ name: g.name, emojis: g.emojis.filter((e) => matchesEmoji(e, query)) })) |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 81 | .filter((g) => g.emojis.length > 0); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 82 | } |