atagen

malice

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