1/** A `:shortcode:` emoji the editor can complete, matched by its code or its keywords. */
2export interface Emoji {
3 code: string;
4 char: string;
5 keywords?: readonly string[];
6}
7
8// A curated set skewed toward dev-chat use rather than the full Unicode table, so the whole
9// autocomplete stack stays a static in-bundle lookup with no dependency.
10const EMOJI: readonly Emoji[] = [
11 { code: "smile", char: "๐Ÿ˜„", keywords: ["happy", "joy"] },
12 { code: "smiley", char: "๐Ÿ˜ƒ", keywords: ["happy"] },
13 { code: "grin", char: "๐Ÿ˜" },
14 { code: "laughing", char: "๐Ÿ˜†", keywords: ["lol", "haha"] },
15 { code: "rofl", char: "๐Ÿคฃ", keywords: ["lmao"] },
16 { code: "joy", char: "๐Ÿ˜‚", keywords: ["tears", "cry", "laugh"] },
17 { code: "sweat_smile", char: "๐Ÿ˜…", keywords: ["phew"] },
18 { code: "wink", char: "๐Ÿ˜‰" },
19 { code: "blush", char: "๐Ÿ˜Š", keywords: ["happy"] },
20 { code: "slight_smile", char: "๐Ÿ™‚" },
21 { code: "upside_down", char: "๐Ÿ™ƒ" },
22 { code: "yum", char: "๐Ÿ˜‹", keywords: ["tasty"] },
23 { code: "sunglasses", char: "๐Ÿ˜Ž", keywords: ["cool"] },
24 { code: "heart_eyes", char: "๐Ÿ˜", keywords: ["love"] },
25 { code: "star_struck", char: "๐Ÿคฉ", keywords: ["amazed"] },
26 { code: "thinking", char: "๐Ÿค”", keywords: ["hmm"] },
27 { code: "smirk", char: "๐Ÿ˜" },
28 { code: "neutral", char: "๐Ÿ˜", keywords: ["meh"] },
29 { code: "expressionless", char: "๐Ÿ˜‘" },
30 { code: "unamused", char: "๐Ÿ˜’" },
31 { code: "roll_eyes", char: "๐Ÿ™„" },
32 { code: "grimacing", char: "๐Ÿ˜ฌ", keywords: ["yikes"] },
33 { code: "relieved", char: "๐Ÿ˜Œ" },
34 { code: "pensive", char: "๐Ÿ˜”" },
35 { code: "sleepy", char: "๐Ÿ˜ช", keywords: ["tired"] },
36 { code: "sleeping", char: "๐Ÿ˜ด", keywords: ["zzz"] },
37 { code: "mask", char: "๐Ÿ˜ท", keywords: ["sick"] },
38 { code: "dizzy_face", char: "๐Ÿ˜ต" },
39 { code: "exploding_head", char: "๐Ÿคฏ", keywords: ["mind", "blown"] },
40 { code: "cowboy", char: "๐Ÿค " },
41 { code: "sob", char: "๐Ÿ˜ญ", keywords: ["cry", "crying"] },
42 { code: "cry", char: "๐Ÿ˜ข", keywords: ["sad", "tear"] },
43 { code: "disappointed", char: "๐Ÿ˜ž", keywords: ["sad"] },
44 { code: "worried", char: "๐Ÿ˜Ÿ" },
45 { code: "confused", char: "๐Ÿ˜•" },
46 { code: "frowning", char: "๐Ÿ™" },
47 { code: "persevere", char: "๐Ÿ˜ฃ" },
48 { code: "confounded", char: "๐Ÿ˜–" },
49 { code: "tired_face", char: "๐Ÿ˜ซ" },
50 { code: "weary", char: "๐Ÿ˜ฉ" },
51 { code: "pleading", char: "๐Ÿฅบ", keywords: ["puppy"] },
52 { code: "triumph", char: "๐Ÿ˜ค" },
53 { code: "angry", char: "๐Ÿ˜ ", keywords: ["mad"] },
54 { code: "rage", char: "๐Ÿ˜ก", keywords: ["mad", "furious"] },
55 { code: "cursing", char: "๐Ÿคฌ", keywords: ["swear"] },
56 { code: "scream", char: "๐Ÿ˜ฑ", keywords: ["shock", "fear"] },
57 { code: "fearful", char: "๐Ÿ˜จ" },
58 { code: "cold_sweat", char: "๐Ÿ˜ฐ" },
59 { code: "flushed", char: "๐Ÿ˜ณ", keywords: ["blush", "embarrassed"] },
60 { code: "hot", char: "๐Ÿฅต", keywords: ["heat"] },
61 { code: "cold", char: "๐Ÿฅถ", keywords: ["freezing"] },
62 { code: "woozy", char: "๐Ÿฅด" },
63 { code: "nauseated", char: "๐Ÿคข", keywords: ["sick"] },
64 { code: "vomiting", char: "๐Ÿคฎ", keywords: ["puke"] },
65 { code: "sneezing", char: "๐Ÿคง" },
66 { code: "shush", char: "๐Ÿคซ", keywords: ["quiet"] },
67 { code: "zipper_mouth", char: "๐Ÿค" },
68 { code: "raised_eyebrow", char: "๐Ÿคจ", keywords: ["skeptical", "suspicious"] },
69 { code: "monocle", char: "๐Ÿง", keywords: ["inspect"] },
70 { code: "nerd", char: "๐Ÿค“", keywords: ["geek"] },
71 { code: "shrug", char: "๐Ÿคท", keywords: ["dunno", "idk"] },
72 { code: "facepalm", char: "๐Ÿคฆ", keywords: ["ugh"] },
73 { code: "wave", char: "๐Ÿ‘‹", keywords: ["hi", "hello", "bye"] },
74 { code: "thumbsup", char: "๐Ÿ‘", keywords: ["+1", "yes", "approve", "lgtm"] },
75 { code: "thumbsdown", char: "๐Ÿ‘Ž", keywords: ["-1", "no"] },
76 { code: "ok_hand", char: "๐Ÿ‘Œ" },
77 { code: "pinch", char: "๐Ÿค", keywords: ["small", "tiny"] },
78 { code: "v", char: "โœŒ๏ธ", keywords: ["peace"] },
79 { code: "crossed_fingers", char: "๐Ÿคž", keywords: ["luck", "hope"] },
80 { code: "point_up", char: "โ˜๏ธ" },
81 { code: "point_down", char: "๐Ÿ‘‡" },
82 { code: "point_left", char: "๐Ÿ‘ˆ" },
83 { code: "point_right", char: "๐Ÿ‘‰" },
84 { code: "raised_hands", char: "๐Ÿ™Œ", keywords: ["praise", "yay"] },
85 { code: "clap", char: "๐Ÿ‘", keywords: ["applause"] },
86 { code: "pray", char: "๐Ÿ™", keywords: ["please", "thanks", "thank you"] },
87 { code: "handshake", char: "๐Ÿค", keywords: ["deal"] },
88 { code: "muscle", char: "๐Ÿ’ช", keywords: ["strong", "flex"] },
89 { code: "fist", char: "โœŠ" },
90 { code: "punch", char: "๐Ÿ‘Š", keywords: ["bump"] },
91 { code: "eyes", char: "๐Ÿ‘€", keywords: ["look", "watching"] },
92 { code: "brain", char: "๐Ÿง " },
93 { code: "heart", char: "โค๏ธ", keywords: ["love"] },
94 { code: "orange_heart", char: "๐Ÿงก" },
95 { code: "yellow_heart", char: "๐Ÿ’›" },
96 { code: "green_heart", char: "๐Ÿ’š" },
97 { code: "blue_heart", char: "๐Ÿ’™" },
98 { code: "purple_heart", char: "๐Ÿ’œ" },
99 { code: "black_heart", char: "๐Ÿ–ค" },
100 { code: "broken_heart", char: "๐Ÿ’”" },
101 { code: "sparkling_heart", char: "๐Ÿ’–" },
102 { code: "fire", char: "๐Ÿ”ฅ", keywords: ["lit", "hot", "flame"] },
103 { code: "sparkles", char: "โœจ", keywords: ["shiny", "clean"] },
104 { code: "star", char: "โญ", keywords: ["favorite"] },
105 { code: "star2", char: "๐ŸŒŸ", keywords: ["glowing"] },
106 { code: "zap", char: "โšก", keywords: ["lightning", "fast"] },
107 { code: "boom", char: "๐Ÿ’ฅ", keywords: ["explosion", "collision"] },
108 { code: "collision", char: "๐Ÿ’ข" },
109 { code: "dizzy", char: "๐Ÿ’ซ" },
110 { code: "sweat_drops", char: "๐Ÿ’ฆ" },
111 { code: "dash", char: "๐Ÿ’จ", keywords: ["fast", "wind"] },
112 { code: "hundred", char: "๐Ÿ’ฏ", keywords: ["100", "perfect"] },
113 { code: "tada", char: "๐ŸŽ‰", keywords: ["party", "celebrate", "yay"] },
114 { code: "confetti", char: "๐ŸŽŠ", keywords: ["party"] },
115 { code: "balloon", char: "๐ŸŽˆ" },
116 { code: "gift", char: "๐ŸŽ", keywords: ["present"] },
117 { code: "trophy", char: "๐Ÿ†", keywords: ["win", "award"] },
118 { code: "medal", char: "๐Ÿ…" },
119 { code: "rocket", char: "๐Ÿš€", keywords: ["ship", "launch", "deploy"] },
120 { code: "airplane", char: "โœˆ๏ธ" },
121 { code: "bulb", char: "๐Ÿ’ก", keywords: ["idea"] },
122 { code: "wrench", char: "๐Ÿ”ง", keywords: ["fix", "tool"] },
123 { code: "hammer", char: "๐Ÿ”จ", keywords: ["build"] },
124 { code: "hammer_and_wrench", char: "๐Ÿ› ๏ธ", keywords: ["tools"] },
125 { code: "gear", char: "โš™๏ธ", keywords: ["settings", "config"] },
126 { code: "nut_and_bolt", char: "๐Ÿ”ฉ" },
127 { code: "lock", char: "๐Ÿ”’", keywords: ["secure", "private"] },
128 { code: "unlock", char: "๐Ÿ”“" },
129 { code: "key", char: "๐Ÿ”‘" },
130 { code: "mag", char: "๐Ÿ”", keywords: ["search", "find", "zoom"] },
131 { code: "link", char: "๐Ÿ”—" },
132 { code: "paperclip", char: "๐Ÿ“Ž" },
133 { code: "pushpin", char: "๐Ÿ“Œ", keywords: ["pin"] },
134 { code: "memo", char: "๐Ÿ“", keywords: ["note", "write", "edit"] },
135 { code: "clipboard", char: "๐Ÿ“‹", keywords: ["copy", "paste"] },
136 { code: "books", char: "๐Ÿ“š", keywords: ["docs", "read"] },
137 { code: "book", char: "๐Ÿ“–" },
138 { code: "package", char: "๐Ÿ“ฆ", keywords: ["box", "npm", "module"] },
139 { code: "computer", char: "๐Ÿ’ป", keywords: ["laptop", "code"] },
140 { code: "keyboard", char: "โŒจ๏ธ" },
141 { code: "floppy_disk", char: "๐Ÿ’พ", keywords: ["save"] },
142 { code: "battery", char: "๐Ÿ”‹" },
143 { code: "electric_plug", char: "๐Ÿ”Œ" },
144 { code: "hourglass", char: "โณ", keywords: ["wait", "loading", "time"] },
145 { code: "watch", char: "โŒš", keywords: ["time"] },
146 { code: "alarm_clock", char: "โฐ", keywords: ["time", "reminder"] },
147 { code: "calendar", char: "๐Ÿ“…", keywords: ["date", "schedule"] },
148 { code: "chart", char: "๐Ÿ“ˆ", keywords: ["graph", "up", "growth"] },
149 { code: "chart_down", char: "๐Ÿ“‰", keywords: ["graph", "down"] },
150 { code: "bar_chart", char: "๐Ÿ“Š", keywords: ["graph", "stats"] },
151 { code: "bug", char: "๐Ÿ›", keywords: ["issue", "defect"] },
152 { code: "skull", char: "๐Ÿ’€", keywords: ["dead", "cooked"] },
153 { code: "ghost", char: "๐Ÿ‘ป", keywords: ["boo"] },
154 { code: "alien", char: "๐Ÿ‘ฝ" },
155 { code: "robot", char: "๐Ÿค–", keywords: ["bot", "ai"] },
156 { code: "poop", char: "๐Ÿ’ฉ", keywords: ["crap"] },
157 { code: "clown", char: "๐Ÿคก" },
158 { code: "check", char: "โœ…", keywords: ["done", "yes", "pass", "white_check_mark"] },
159 { code: "heavy_check", char: "โœ”๏ธ", keywords: ["done"] },
160 { code: "x", char: "โŒ", keywords: ["no", "fail", "cross"] },
161 { code: "warning", char: "โš ๏ธ", keywords: ["caution", "alert"] },
162 { code: "no_entry", char: "โ›”", keywords: ["stop", "blocked"] },
163 { code: "question", char: "โ“", keywords: ["help"] },
164 { code: "exclamation", char: "โ—", keywords: ["important"] },
165 { code: "recycle", char: "โ™ป๏ธ", keywords: ["refactor"] },
166 { code: "arrows_clockwise", char: "๐Ÿ”„", keywords: ["refresh", "sync", "retry"] },
167 { code: "back", char: "๐Ÿ”™" },
168 { code: "arrow_up", char: "โฌ†๏ธ" },
169 { code: "arrow_down", char: "โฌ‡๏ธ" },
170 { code: "arrow_right", char: "โžก๏ธ" },
171 { code: "arrow_left", char: "โฌ…๏ธ" },
172 { code: "hash", char: "#๏ธโƒฃ" },
173 { code: "red_circle", char: "๐Ÿ”ด", keywords: ["record", "live"] },
174 { code: "green_circle", char: "๐ŸŸข", keywords: ["online", "ok"] },
175 { code: "yellow_circle", char: "๐ŸŸก" },
176 { code: "coffee", char: "โ˜•", keywords: ["cafe"] },
177 { code: "beer", char: "๐Ÿบ", keywords: ["drink", "cheers"] },
178 { code: "pizza", char: "๐Ÿ•" },
179 { code: "cake", char: "๐ŸŽ‚", keywords: ["birthday"] },
180 { code: "hamburger", char: "๐Ÿ”" },
181 { code: "sun", char: "โ˜€๏ธ", keywords: ["sunny"] },
182 { code: "moon", char: "๐ŸŒ™", keywords: ["night"] },
183 { code: "cloud", char: "โ˜๏ธ" },
184 { code: "rainbow", char: "๐ŸŒˆ" },
185 { code: "snowflake", char: "โ„๏ธ", keywords: ["cold", "snow"] },
186 { code: "cat", char: "๐Ÿฑ" },
187 { code: "dog", char: "๐Ÿถ" },
188 { code: "unicorn", char: "๐Ÿฆ„" },
189 { code: "snake", char: "๐Ÿ", keywords: ["python"] },
190 { code: "penguin", char: "๐Ÿง", keywords: ["linux"] },
191 { code: "four_leaf_clover", char: "๐Ÿ€", keywords: ["luck", "clover"] },
192];
193
194/**
195 * Emoji matching `query` (the text after `:`), code-prefix matches first, then code substrings, then
196 * keyword hits. An empty query returns the leading slice so the menu can open on a bare `:`.
197 */
198export function searchEmoji(query: string, limit = 8): Emoji[] {
199 const q = query.toLowerCase();
200 if (q === "") return EMOJI.slice(0, limit);
201 const prefix: Emoji[] = [];
202 const infix: Emoji[] = [];
203 const keyword: Emoji[] = [];
204 for (const emoji of EMOJI) {
205 if (emoji.code.startsWith(q)) prefix.push(emoji);
206 else if (emoji.code.includes(q)) infix.push(emoji);
207 else if (emoji.keywords?.some((word) => word.includes(q))) keyword.push(emoji);
208 if (prefix.length >= limit) break;
209 }
210 return [...prefix, ...infix, ...keyword].slice(0, limit);
211}