1// static/js/emojiShortcodes.js 2// 3// Emoji shortcode â Unicode conversion (issue #345). 4// 5// Chat models frequently emit GitHub/Slack-style `:shortcode:` text â e.g. 6// `:blush:`, `:fire:`, `:microphone:` â instead of the actual emoji character. 7// Nothing in the render pipeline used to translate these, so they showed up as 8// literal `:blush:` text in the chat bubble. 9// 10// This module turns the common shortcode set into the real Unicode emoji. The 11// chat renderer (markdown.js â svgifyEmoji) runs this BEFORE its existing 12// Unicode-emoji â monochrome-SVG pass, so a converted `:blush:` renders as the 13// same theme-tinted single-color line icon as any other emoji (project rule: 14// never colorful emoji), not as a colored system glyph. 15// 16// Pure and browser-free on purpose: no DOM, no imports, so it can be unit 17// tested with plain `node` (see tests/test_emoji_shortcodes_js.py). 18 19// Canonical map of common shortcode â Unicode emoji. Names follow the GitHub 20// convention (lowercase, underscore-separated). A handful of well-known aliases 21// (`+1`, `thumbsup`, `grinning_face`, â¦) point at the same glyph so the most 22// frequent model spellings all resolve. 23export const EMOJI_SHORTCODES = { 24 // ââ Smileys & emotion ââ 25 grinning: 'ð', grinning_face: 'ð', 26 smiley: 'ð', smiley_face: 'ð', 27 smile: 'ð', 28 grin: 'ð', 29 laughing: 'ð', satisfied: 'ð', 30 sweat_smile: 'ð ', 31 rofl: 'ð¤£', rolling_on_the_floor_laughing: 'ð¤£', 32 joy: 'ð', 33 slightly_smiling_face: 'ð', slight_smile: 'ð', 34 upside_down_face: 'ð', upside_down: 'ð', 35 wink: 'ð', winking_face: 'ð', 36 blush: 'ð', smiling_face_with_smiling_eyes: 'ð', 37 innocent: 'ð', 38 smiling_face_with_three_hearts: 'ð¥°', 39 heart_eyes: 'ð', heart_eyes_face: 'ð', 40 star_struck: 'ð¤©', 41 kissing_heart: 'ð', 42 kissing: 'ð', 43 kissing_closed_eyes: 'ð', 44 kissing_smiling_eyes: 'ð', 45 yum: 'ð', 46 stuck_out_tongue: 'ð', 47 stuck_out_tongue_winking_eye: 'ð', 48 zany_face: 'ð¤ª', 49 stuck_out_tongue_closed_eyes: 'ð', 50 money_mouth_face: 'ð¤', 51 hugs: 'ð¤', hugging_face: 'ð¤', 52 hand_over_mouth: 'ð¤', 53 shushing_face: 'ð¤«', 54 thinking: 'ð¤', thinking_face: 'ð¤', 55 zipper_mouth_face: 'ð¤', 56 raised_eyebrow: 'ð¤¨', 57 neutral_face: 'ð', 58 expressionless: 'ð', 59 no_mouth: 'ð¶', 60 smirk: 'ð', smirk_face: 'ð', 61 unamused: 'ð', 62 roll_eyes: 'ð', face_with_rolling_eyes: 'ð', 63 grimacing: 'ð¬', 64 lying_face: 'ð¤¥', 65 relieved: 'ð', 66 pensive: 'ð', 67 sleepy: 'ðª', 68 drooling_face: 'ð¤¤', 69 sleeping: 'ð´', 70 mask: 'ð·', 71 face_with_thermometer: 'ð¤', 72 face_with_head_bandage: 'ð¤', 73 nauseated_face: 'ð¤¢', 74 vomiting_face: 'ð¤®', 75 sneezing_face: 'ð¤§', 76 hot_face: 'ð¥µ', 77 cold_face: 'ð¥¶', 78 woozy_face: 'ð¥´', 79 dizzy_face: 'ðµ', 80 exploding_head: 'ð¤¯', 81 cowboy_hat_face: 'ð¤ ', 82 partying_face: 'ð¥³', 83 sunglasses: 'ð', 84 nerd_face: 'ð¤', 85 monocle_face: 'ð§', 86 confused: 'ð', 87 worried: 'ð', 88 slightly_frowning_face: 'ð', 89 frowning_face: 'â¹ï¸', 90 open_mouth: 'ð®', 91 hushed: 'ð¯', 92 astonished: 'ð²', 93 flushed: 'ð³', 94 pleading_face: 'ð¥º', 95 frowning: 'ð¦', 96 anguished: 'ð§', 97 fearful: 'ð¨', 98 cold_sweat: 'ð°', 99 disappointed_relieved: 'ð¥', 100 cry: 'ð¢', 101 sob: 'ð', 102 scream: 'ð±', 103 confounded: 'ð', 104 persevere: 'ð£', 105 disappointed: 'ð', 106 sweat: 'ð', 107 weary: 'ð©', 108 tired_face: 'ð«', 109 yawning_face: 'ð¥±', 110 triumph: 'ð¤', 111 rage: 'ð¡', pout: 'ð¡', pouting_face: 'ð¡', 112 angry: 'ð ', 113 cursing_face: 'ð¤¬', 114 smiling_imp: 'ð', 115 imp: 'ð¿', 116 skull: 'ð', 117 skull_and_crossbones: 'â ï¸', 118 hankey: 'ð©', poop: 'ð©', shit: 'ð©', 119 clown_face: 'ð¤¡', 120 japanese_ogre: 'ð¹', 121 japanese_goblin: 'ðº', 122 ghost: 'ð»', 123 alien: 'ð½', 124 space_invader: 'ð¾', 125 robot: 'ð¤', robot_face: 'ð¤', 126 // ââ Cats ââ 127 smiley_cat: 'ðº', 128 smile_cat: 'ð¸', 129 joy_cat: 'ð¹', 130 heart_eyes_cat: 'ð»', 131 smirk_cat: 'ð¼', 132 kissing_cat: 'ð½', 133 scream_cat: 'ð', 134 crying_cat_face: 'ð¿', 135 pouting_cat: 'ð¾', 136 see_no_evil: 'ð', 137 hear_no_evil: 'ð', 138 speak_no_evil: 'ð', 139 // ââ Hands & body ââ 140 wave: 'ð', wave_hand: 'ð', 141 raised_back_of_hand: 'ð¤', 142 raised_hand_with_fingers_splayed: 'ðï¸', 143 hand: 'â', raised_hand: 'â', 144 vulcan_salute: 'ð', 145 ok_hand: 'ð', 146 pinched_fingers: 'ð¤', 147 pinching_hand: 'ð¤', 148 v: 'âï¸', victory_hand: 'âï¸', 149 crossed_fingers: 'ð¤', 150 love_you_gesture: 'ð¤', 151 metal: 'ð¤', 152 call_me_hand: 'ð¤', 153 point_left: 'ð', 154 point_right: 'ð', 155 point_up_2: 'ð', 156 middle_finger: 'ð', fu: 'ð', 157 point_down: 'ð', 158 point_up: 'âï¸', 159 '+1': 'ð', thumbsup: 'ð', thumbup: 'ð', thumbs_up: 'ð', 160 '-1': 'ð', thumbsdown: 'ð', thumbdown: 'ð', thumbs_down: 'ð', 161 fist_raised: 'â', fist: 'â', 162 fist_oncoming: 'ð', facepunch: 'ð', punch: 'ð', 163 fist_left: 'ð¤', 164 fist_right: 'ð¤', 165 clap: 'ð', clapping_hands: 'ð', 166 raised_hands: 'ð', 167 open_hands: 'ð', 168 palms_up_together: 'ð¤²', 169 handshake: 'ð¤', 170 pray: 'ð', folded_hands: 'ð', 171 writing_hand: 'âï¸', 172 nail_care: 'ð ', 173 selfie: 'ð¤³', 174 muscle: 'ðª', flexed_biceps: 'ðª', 175 // ââ Hearts & symbols of feeling ââ 176 heart: 'â¤ï¸', red_heart: 'â¤ï¸', 177 orange_heart: 'ð§¡', 178 yellow_heart: 'ð', 179 green_heart: 'ð', 180 blue_heart: 'ð', 181 purple_heart: 'ð', 182 black_heart: 'ð¤', 183 white_heart: 'ð¤', 184 brown_heart: 'ð¤', 185 broken_heart: 'ð', 186 heart_on_fire: 'â¤ï¸âð¥', 187 two_hearts: 'ð', 188 revolving_hearts: 'ð', 189 heartbeat: 'ð', 190 heartpulse: 'ð', 191 sparkling_heart: 'ð', 192 cupid: 'ð', 193 gift_heart: 'ð', 194 heart_decoration: 'ð', 195 heavy_heart_exclamation: 'â£ï¸', 196 // ââ Celebration & misc objects ââ 197 fire: 'ð¥', flame: 'ð¥', 198 '100': 'ð¯', hundred: 'ð¯', 199 sparkles: 'â¨', 200 star: 'â', 201 star2: 'ð', glowing_star: 'ð', 202 dizzy: 'ð«', 203 boom: 'ð¥', collision: 'ð¥', 204 anger: 'ð¢', 205 sweat_drops: 'ð¦', 206 dash: 'ð¨', 207 zzz: 'ð¤', 208 tada: 'ð', party_popper: 'ð', 209 confetti_ball: 'ð', 210 balloon: 'ð', 211 gift: 'ð', 212 trophy: 'ð', 213 '1st_place_medal': 'ð¥', 214 '2nd_place_medal': 'ð¥', 215 '3rd_place_medal': 'ð¥', 216 medal_sports: 'ð ', 217 zap: 'â¡', lightning: 'â¡', 218 bulb: 'ð¡', light_bulb: 'ð¡', 219 key: 'ð', 220 lock: 'ð', 221 unlock: 'ð', 222 bell: 'ð', 223 no_bell: 'ð', 224 loudspeaker: 'ð¢', 225 mega: 'ð£', megaphone: 'ð£', 226 speech_balloon: 'ð¬', 227 thought_balloon: 'ð', 228 white_check_mark: 'â ', 229 heavy_check_mark: 'âï¸', check_mark: 'âï¸', 230 ballot_box_with_check: 'âï¸', 231 x: 'â', cross_mark: 'â', 232 negative_squared_cross_mark: 'â', 233 question: 'â', 234 grey_question: 'â', 235 exclamation: 'â', heavy_exclamation_mark: 'â', 236 grey_exclamation: 'â', 237 warning: 'â ï¸', 238 no_entry: 'â', 239 no_entry_sign: 'ð«', 240 red_circle: 'ð´', 241 green_circle: 'ð¢', 242 large_blue_circle: 'ðµ', 243 yellow_circle: 'ð¡', 244 white_circle: 'âª', 245 black_circle: 'â«', 246 orange_circle: 'ð ', 247 purple_circle: 'ð£', 248 brown_circle: 'ð¤', 249 // ââ Tech, work, study ââ 250 rocket: 'ð', 251 eyes: 'ð', 252 eye: 'ðï¸', 253 brain: 'ð§ ', 254 books: 'ð', 255 book: 'ð', open_book: 'ð', 256 memo: 'ð', pencil: 'ð', 257 pencil2: 'âï¸', 258 page_facing_up: 'ð', 259 paperclip: 'ð', 260 pushpin: 'ð', 261 round_pushpin: 'ð', 262 link: 'ð', 263 bar_chart: 'ð', 264 chart_with_upwards_trend: 'ð', 265 chart_with_downwards_trend: 'ð', 266 mag: 'ð', 267 mag_right: 'ð',
268 globe_with_meridians: 'ð', 269 earth_africa: 'ð', 270 earth_americas: 'ð', 271 earth_asia: 'ð', 272 alarm_clock: 'â°', 273 hourglass_flowing_sand: 'â³', 274 hourglass: 'â', 275 microphone: 'ð¤', mic: 'ð¤', 276 musical_note: 'ðµ', 277 notes: 'ð¶', musical_notes: 'ð¶', 278 headphones: 'ð§', 279 camera: 'ð·', 280 camera_flash: 'ð¸', 281 clapper: 'ð¬', 282 tv: 'ðº', 283 computer: 'ð»', laptop: 'ð»', 284 desktop_computer: 'ð¥ï¸', 285 iphone: 'ð±', mobile_phone: 'ð±', 286 telephone: 'âï¸', 287 wrench: 'ð§', 288 hammer: 'ð¨', 289 gear: 'âï¸', 290 nut_and_bolt: 'ð©', 291 magnet: 'ð§²', 292 test_tube: 'ð§ª', 293 microscope: 'ð¬', 294 dart: 'ð¯', bullseye: 'ð¯', 295 game_die: 'ð²', 296 jigsaw: 'ð§©', 297 // ââ Food & drink ââ 298 pizza: 'ð', 299 hamburger: 'ð', 300 fries: 'ð', 301 taco: 'ð®', 302 sushi: 'ð£', 303 doughnut: 'ð©', donut: 'ð©', 304 coffee: 'â', 305 beer: 'ðº', 306 wine_glass: 'ð·', 307 // ââ Animals & nature ââ 308 dog: 'ð¶', 309 cat: 'ð±', 310 mouse: 'ð', 311 hamster: 'ð¹', 312 rabbit: 'ð°', 313 fox_face: 'ð¦', 314 bear: 'ð»', 315 panda_face: 'ð¼', 316 koala: 'ð¨', 317 tiger: 'ð¯', 318 lion: 'ð¦', 319 cow: 'ð®', 320 pig: 'ð·', 321 frog: 'ð¸', 322 monkey_face: 'ðµ', 323 chicken: 'ð', 324 penguin: 'ð§', 325 bird: 'ð¦', 326 eagle: 'ð¦ ', 327 duck: 'ð¦', 328 owl: 'ð¦', 329 wolf: 'ðº', 330 horse: 'ð´', 331 unicorn: 'ð¦', 332 bee: 'ð', honeybee: 'ð', 333 bug: 'ð', 334 butterfly: 'ð¦', 335 snail: 'ð', 336 lady_beetle: 'ð', 337 snake: 'ð', 338 turtle: 'ð¢', 339 octopus: 'ð', 340 crab: 'ð¦', 341 tropical_fish: 'ð ', 342 whale: 'ð³', 343 shark: 'ð¦', 344 cherry_blossom: 'ð¸', 345 rose: 'ð¹', 346 sunflower: 'ð»', 347 hibiscus: 'ðº', 348 tulip: 'ð·', 349 seedling: 'ð±', 350 evergreen_tree: 'ð²', 351 deciduous_tree: 'ð³', 352 four_leaf_clover: 'ð', 353 apple: 'ð', 354 green_apple: 'ð', 355 pear: 'ð', 356 tangerine: 'ð', 357 lemon: 'ð', 358 banana: 'ð', 359 watermelon: 'ð', 360 grapes: 'ð', 361 strawberry: 'ð', 362 blueberries: 'ð«', 363 peach: 'ð', 364 rainbow: 'ð', 365 sunny: 'âï¸', sun: 'âï¸', 366 partly_sunny: 'â ', 367 cloud: 'âï¸', 368 snowflake: 'âï¸', 369 ocean: 'ð', 370 // ââ Arrows & signs ââ 371 arrow_right: 'â¡ï¸', 372 arrow_left: '⬠ï¸', 373 arrow_up: 'â¬ï¸', 374 arrow_down: 'â¬ï¸', 375 arrow_upper_right: 'âï¸', 376 arrow_lower_right: 'âï¸', 377 arrow_lower_left: 'âï¸', 378 arrow_upper_left: 'âï¸', 379 leftwards_arrow_with_hook: 'â©ï¸', 380 arrow_right_hook: 'âªï¸', 381 arrows_counterclockwise: 'ð', 382 arrows_clockwise: 'ð', 383 heavy_plus_sign: 'â', 384 heavy_minus_sign: 'â', 385 heavy_division_sign: 'â', 386 heavy_multiplication_x: 'âï¸', 387 infinity: 'â¾ï¸', 388 copyright: '©ï¸', 389 registered: '®ï¸', 390 tm: 'â¢ï¸', 391 recycle: 'â»ï¸', 392 checkered_flag: 'ð', 393 triangular_flag_on_post: 'ð©', 394 white_flag: 'ð³ï¸', 395 black_flag: 'ð´', 396 // ââ People & wearables ââ 397 baby: 'ð¶', 398 boy: 'ð¦', 399 girl: 'ð§', 400 man: 'ð¨', 401 woman: 'ð©', 402 older_man: 'ð´', 403 older_woman: 'ðµ', 404 crown: 'ð', 405 gem: 'ð', 406 graduation_cap: 'ð', mortar_board: 'ð', 407}; 408 409// `:name:` where name is letters/digits/`_`/`+`/`-`. Length â¥1 so `:+1:` and 410// `:-1:` match. Global + case-insensitive for replace; a separate non-global 411// literal is used for the cheap presence check so there's no shared lastIndex 412// state to reset. 413const SHORTCODE_RE = /:([a-z0-9_+-]{1,40}):/gi; 414 415/** 416 * Cheap test for whether `text` could contain any emoji shortcode at all. 417 * Lets callers skip the replace pass entirely on the common no-shortcode path. 418 */ 419export function hasEmojiShortcode(text) { 420 return !!text && text.indexOf(':') !== -1 && /:[a-z0-9_+-]{1,40}:/i.test(text); 421} 422 423// A shortcode must stand on its own â flanked by whitespace, punctuation, a 424// string edge, or markup, never glued to an ASCII word character. Without this 425// guard, real `:name:` shortcodes that happen to sit inside a longer run of 426// digits/letters get converted by mistake and mangle perfectly literal text: 427// "1:100:2" â the `:100:` would become ð¯ ("1ð¯2")
428// "host:fire:port", URL authorities, `key:value:` pairs, etc. 429// Chat models always emit shortcodes delimited by spaces/punctuation (":fire:", 430// "**:microphone:**", "nice :tada:!"), so requiring a boundary keeps every real 431// shortcode working while leaving embedded colon runs untouched. `_` counts as a 432// word char too (identifier-like), but `+`/`-` do not, so "C++ :fire:" still works. 433const _WORDISH = /[A-Za-z0-9_]/; 434function _boundedOnBothSides(str, start, end) { 435 const before = start > 0 ? str[start - 1] : ''; 436 const after = end < str.length ? str[end] : ''; 437 return !_WORDISH.test(before) && !_WORDISH.test(after); 438} 439 440/** 441 * Replace every known `:shortcode:` in `text` with its Unicode emoji. Unknown 442 * shortcodes (`:definitely_not_emoji:`), colon runs that don't form a shortcode 443 * (`10:30:45`, `16:9`), and known shortcodes embedded mid-token (`1:100:2`) are 444 * all left exactly as-is. 445 */ 446export function replaceEmojiShortcodes(text) { 447 if (!text || text.indexOf(':') === -1) return text; 448 return text.replace(SHORTCODE_RE, (whole, name, offset, str) => { 449 const key = name.toLowerCase(); 450 if (!Object.prototype.hasOwnProperty.call(EMOJI_SHORTCODES, key)) return whole; 451 // Only convert when the `:shortcode:` is a standalone token, not glued to a 452 // surrounding word/number (which would mean it's literal text, not an emoji). 453 if (!_boundedOnBothSides(str, offset, offset + whole.length)) return whole; 454 return EMOJI_SHORTCODES[key]; 455 }); 456} 457 458export default { EMOJI_SHORTCODES, replaceEmojiShortcodes, hasEmojiShortcode };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.