// Emoji shortcode interpolation: ":tada:" โ†’ "๐ŸŽ‰". A curated set of the common // ones (not the full ~1800 โ€” smaller and less obscure for a family/kids' chat). // Pure + framework-free so it's unit-testable in Node. Replacement happens on // send, so the encrypted message stores the real emoji and renders natively // everywhere (bubbles, previews, notifications). Only known names convert, so // URLs and times like 10:30 are left alone. Easily extended โ€” just add entries. export const EMOJI = { // smileys & emotion grinning: "๐Ÿ˜€", smiley: "๐Ÿ˜ƒ", smile: "๐Ÿ˜„", grin: "๐Ÿ˜", laughing: "๐Ÿ˜†", satisfied: "๐Ÿ˜†", sweat_smile: "๐Ÿ˜…", rofl: "๐Ÿคฃ", joy: "๐Ÿ˜‚", slightly_smiling_face: "๐Ÿ™‚", upside_down_face: "๐Ÿ™ƒ", wink: "๐Ÿ˜‰", blush: "๐Ÿ˜Š", innocent: "๐Ÿ˜‡", heart_eyes: "๐Ÿ˜", star_struck: "๐Ÿคฉ", kissing_heart: "๐Ÿ˜˜", yum: "๐Ÿ˜‹", stuck_out_tongue: "๐Ÿ˜›", stuck_out_tongue_winking_eye: "๐Ÿ˜œ", zany_face: "๐Ÿคช", money_mouth_face: "๐Ÿค‘", hugs: "๐Ÿค—", thinking: "๐Ÿค”", shushing_face: "๐Ÿคซ", zipper_mouth_face: "๐Ÿค", neutral_face: "๐Ÿ˜", expressionless: "๐Ÿ˜‘", no_mouth: "๐Ÿ˜ถ", smirk: "๐Ÿ˜", unamused: "๐Ÿ˜’", roll_eyes: "๐Ÿ™„", grimacing: "๐Ÿ˜ฌ", lying_face: "๐Ÿคฅ", relieved: "๐Ÿ˜Œ", pensive: "๐Ÿ˜”", sleepy: "๐Ÿ˜ช", sleeping: "๐Ÿ˜ด", mask: "๐Ÿ˜ท", face_with_thermometer: "๐Ÿค’", nauseated_face: "๐Ÿคข", vomiting: "๐Ÿคฎ", sneezing_face: "๐Ÿคง", hot_face: "๐Ÿฅต", cold_face: "๐Ÿฅถ", woozy_face: "๐Ÿฅด", dizzy_face: "๐Ÿ˜ต", exploding_head: "๐Ÿคฏ", cowboy_hat_face: "๐Ÿค ", partying_face: "๐Ÿฅณ", sunglasses: "๐Ÿ˜Ž", nerd_face: "๐Ÿค“", confused: "๐Ÿ˜•", worried: "๐Ÿ˜Ÿ", frowning_face: "๐Ÿ™", open_mouth: "๐Ÿ˜ฎ", hushed: "๐Ÿ˜ฏ", astonished: "๐Ÿ˜ฒ", flushed: "๐Ÿ˜ณ", pleading_face: "๐Ÿฅบ", cry: "๐Ÿ˜ข", sob: "๐Ÿ˜ญ", scream: "๐Ÿ˜ฑ", confounded: "๐Ÿ˜–", persevere: "๐Ÿ˜ฃ", disappointed: "๐Ÿ˜ž", sweat: "๐Ÿ˜“", weary: "๐Ÿ˜ฉ", tired_face: "๐Ÿ˜ซ", yawning_face: "๐Ÿฅฑ", triumph: "๐Ÿ˜ค", rage: "๐Ÿ˜ก", angry: "๐Ÿ˜ ", cursing_face: "๐Ÿคฌ", smiling_imp: "๐Ÿ˜ˆ", imp: "๐Ÿ‘ฟ", skull: "๐Ÿ’€", poop: "๐Ÿ’ฉ", hankey: "๐Ÿ’ฉ", clown_face: "๐Ÿคก", ghost: "๐Ÿ‘ป", alien: "๐Ÿ‘ฝ", robot: "๐Ÿค–", // hearts & symbols heart: "โค๏ธ", orange_heart: "๐Ÿงก", yellow_heart: "๐Ÿ’›", green_heart: "๐Ÿ’š", blue_heart: "๐Ÿ’™", purple_heart: "๐Ÿ’œ", black_heart: "๐Ÿ–ค", broken_heart: "๐Ÿ’”", two_hearts: "๐Ÿ’•", sparkling_heart: "๐Ÿ’–", heartbeat: "๐Ÿ’“", heartpulse: "๐Ÿ’—", cupid: "๐Ÿ’˜", gift_heart: "๐Ÿ’", "100": "๐Ÿ’ฏ", anger: "๐Ÿ’ข", boom: "๐Ÿ’ฅ", collision: "๐Ÿ’ฅ", sweat_drops: "๐Ÿ’ฆ", dash: "๐Ÿ’จ", zzz: "๐Ÿ’ค", fire: "๐Ÿ”ฅ", star: "โญ", star2: "๐ŸŒŸ", sparkles: "โœจ", dizzy: "๐Ÿ’ซ", tada: "๐ŸŽ‰", confetti_ball: "๐ŸŽŠ", balloon: "๐ŸŽˆ", gift: "๐ŸŽ", white_check_mark: "โœ…", heavy_check_mark: "โœ”๏ธ", x: "โŒ", warning: "โš ๏ธ", question: "โ“", exclamation: "โ—", bell: "๐Ÿ””", crown: "๐Ÿ‘‘", gem: "๐Ÿ’Ž", lock: "๐Ÿ”’", key: "๐Ÿ”‘", bulb: "๐Ÿ’ก", // hands & people "+1": "๐Ÿ‘", thumbsup: "๐Ÿ‘", "-1": "๐Ÿ‘Ž", thumbsdown: "๐Ÿ‘Ž", ok_hand: "๐Ÿ‘Œ", punch: "๐Ÿ‘Š", fist: "โœŠ", wave: "๐Ÿ‘‹", raised_hands: "๐Ÿ™Œ", clap: "๐Ÿ‘", pray: "๐Ÿ™", muscle: "๐Ÿ’ช", point_up: "โ˜๏ธ", point_down: "๐Ÿ‘‡", point_left: "๐Ÿ‘ˆ", point_right: "๐Ÿ‘‰", v: "โœŒ๏ธ", crossed_fingers: "๐Ÿคž", handshake: "๐Ÿค", writing_hand: "โœ๏ธ", eyes: "๐Ÿ‘€", brain: "๐Ÿง ", baby: "๐Ÿ‘ถ", shrug: "๐Ÿคท", facepalm: "๐Ÿคฆ", strong: "๐Ÿ’ช", lol: "๐Ÿ˜‚", // animals & nature dog: "๐Ÿถ", cat: "๐Ÿฑ", mouse: "๐Ÿญ", fox: "๐ŸฆŠ", bear: "๐Ÿป", panda_face: "๐Ÿผ", koala: "๐Ÿจ", tiger: "๐Ÿฏ", lion: "๐Ÿฆ", cow: "๐Ÿฎ", pig: "๐Ÿท", frog: "๐Ÿธ", monkey_face: "๐Ÿต", chicken: "๐Ÿ”", penguin: "๐Ÿง", bird: "๐Ÿฆ", unicorn: "๐Ÿฆ„", bee: "๐Ÿ", bug: "๐Ÿ›", butterfly: "๐Ÿฆ‹", snail: "๐ŸŒ", turtle: "๐Ÿข", fish: "๐ŸŸ", dolphin: "๐Ÿฌ", whale: "๐Ÿณ", paw_prints: "๐Ÿพ", sun: "โ˜€๏ธ", cloud: "โ˜๏ธ", rainbow: "๐ŸŒˆ", snowflake: "โ„๏ธ", snowman: "โ›„", zap: "โšก", umbrella: "โ˜”", droplet: "๐Ÿ’ง", ocean: "๐ŸŒŠ", christmas_tree: "๐ŸŽ„", earth_africa: "๐ŸŒ", moon: "๐ŸŒ™", // food & drink pizza: "๐Ÿ•", hamburger: "๐Ÿ”", fries: "๐ŸŸ", hotdog: "๐ŸŒญ", taco: "๐ŸŒฎ", popcorn: "๐Ÿฟ", doughnut: "๐Ÿฉ", cookie: "๐Ÿช", cake: "๐Ÿฐ", birthday: "๐ŸŽ‚", icecream: "๐Ÿฆ", candy: "๐Ÿฌ", lollipop: "๐Ÿญ", chocolate_bar: "๐Ÿซ", apple: "๐ŸŽ", banana: "๐ŸŒ", strawberry: "๐Ÿ“", watermelon: "๐Ÿ‰", grapes: "๐Ÿ‡", avocado: "๐Ÿฅ‘", coffee: "โ˜•", tea: "๐Ÿต", beer: "๐Ÿบ", wine_glass: "๐Ÿท", cocktail: "๐Ÿธ", champagne: "๐Ÿพ", // activity, objects & travel soccer: "โšฝ", basketball: "๐Ÿ€", football: "๐Ÿˆ", baseball: "โšพ", tennis: "๐ŸŽพ", trophy: "๐Ÿ†", medal: "๐Ÿ…", dart: "๐ŸŽฏ", game_die: "๐ŸŽฒ", video_game: "๐ŸŽฎ", musical_note: "๐ŸŽต", notes: "๐ŸŽถ", guitar: "๐ŸŽธ", microphone: "๐ŸŽค", headphones: "๐ŸŽง", art: "๐ŸŽจ", camera: "๐Ÿ“ท", iphone: "๐Ÿ“ฑ", computer: "๐Ÿ’ป", money_with_wings: "๐Ÿ’ธ", moneybag: "๐Ÿ’ฐ", dollar: "๐Ÿ’ต", mag: "๐Ÿ”", link: "๐Ÿ”—", pushpin: "๐Ÿ“Œ", calendar: "๐Ÿ“…", memo: "๐Ÿ“", pencil: "๐Ÿ“", book: "๐Ÿ“–", books: "๐Ÿ“š", email: "โœ‰๏ธ", package: "๐Ÿ“ฆ", rocket: "๐Ÿš€", airplane: "โœˆ๏ธ", car: "๐Ÿš—", bike: "๐Ÿšฒ", house: "๐Ÿ ", school: "๐Ÿซ", // flags & misc checkered_flag: "๐Ÿ", tada_alias: "๐ŸŽ‰", jack_o_lantern: "๐ŸŽƒ", santa: "๐ŸŽ…", }; // emojiMatches ranks shortcodes for a typed ":query" (prefix hits first, then // substring), returning up to `limit` { code, emoji } for the composer typeahead. // De-dupes aliases that resolve to the same emoji (e.g. joy/laughing) โ€” keeping // the first (shortest-name) match โ€” so the menu isn't full of duplicate glyphs. export function emojiMatches(query, limit = 8) { const q = String(query || "").toLowerCase(); if (!q) return []; const names = Object.keys(EMOJI).filter((n) => n.includes(q)); names.sort((a, b) => { const ap = a.startsWith(q) ? 0 : 1; const bp = b.startsWith(q) ? 0 : 1; return ap - bp || a.length - b.length || a.localeCompare(b); }); const out = []; const seen = new Set(); for (const code of names) { if (seen.has(EMOJI[code])) continue; seen.add(EMOJI[code]); out.push({ code, emoji: EMOJI[code] }); if (out.length >= limit) break; } return out; } const SHORTCODE_RE = /:([a-z0-9_+-]+):/gi; // emojify replaces known :shortcodes: with their emoji, leaving unknown ones // (and anything that isn't a :name: token) untouched. export function emojify(text) { if (text == null) return ""; return String(text).replace(SHORTCODE_RE, (m, name) => EMOJI[name.toLowerCase()] || m); }