Files
jiang13-bbs/frontend/lib/dyEmoji.ts
freefire 34aee91a13 feat: 论坛 emoji 表情支持
- 新增 dyEmoji 表情库与解析测试、EmojiPicker 选择器与 EmojiImg 组件
- Markdown 编辑器/渲染支持表情图片,新增 public/emoji 资源与生成脚本
- 聊天室与首页适配
2026-09-28 01:28:41 +08:00

342 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 抖音表情共享逻辑:短代码正则 / URL / 展开为 markdown 图片 / span 收集。
* 纯逻辑模块(无 React 依赖),服务端与客户端组件均可安全引入。
*/
/**
* 抖音表情短代码:[:key:]。key 有两种形态:
* - 默认表情:中文语义名(如 猫猫),经 DY_EMOJI_ALIASES 映射到实际文件
* - 合成表情:组合文件名(fus),原样透传
* 字符集排除 [ ] : 空白 . @(. 防止误吞文件扩展名写法;@ 保证与 @提及 span 不重叠)
*/
export const DY_EMOJI_RE = /\[:([^\[\]:\n\s.@]+):\]/g;
export const DY_EMOJI_BASE = "/emoji/dy";
/**
* 默认表情语义别名(人工看图命名),顺序与 manifest.json 的 tos 一致。
* 面板「默认」tab 与消息内容均以中文短代码为准;更新表情资产时需同步维护本表。
* 用有序数组做单一数据源:对象键会把整数型 key(如 "666")重排到最前,不能依赖键顺序。
*/
const DY_EMOJI_ALIAS_PAIRS: readonly (readonly [string, string])[] = [
["猫猫", "003d8daaa58e4a2fb324bcd3baf4c785"],
["大哭", "0043de2fff65408ab7522538f7b3292a"],
["晕", "01ccc692f9094a0f9839706665c7e8bb"],
["炸弹", "02a91594d67e462faf7c77e827a16eec"],
["禁止", "02fcc587c8b4499080e0c03994967511"],
["5V5", "032007fd6c254a72a162d8c5bf78d512"],
["坏笑", "0431a35382c641679fe74f690241b533"],
["纸飞机", "0636e634f58f4410a8b22675d36f52a9"],
["玫瑰", "06abb0913446422cbac6ab18ea8fa44a"],
["翻白眼", "06df57cc81954ef8bf65dad06a01a69a"],
["无语", "072caed2f2bd4ed5ba35142965845d85"],
["摸头", "0795718a5fe247b19ff80b064d02c66d"],
["礼物", "08d888562e1f4ea086fc4f3eff2ef517"],
["火苗", "094c1f852c2343658b2677023755249b"],
["嘟嘴", "0e13c8681abe4239b3d468635b723d40"],
["心碎", "0eb6e5cde36346289150a83a118bfa62"],
["精选", "1482ac09926b45a1bab8f1dc98a68d22"],
["笑哭", "167a2c32c7874da889fc902f8cf4500a"],
["不屑", "1691cd385ecd4fb6b888d000900d74bc"],
["鞭炮", "16c850aade5b475fa83b67e0266f0222"],
["庆祝", "18d02bf442484371bb918e1f07c43a05"],
["粽子", "1e8aef1795db468682f8b63521834f39"],
["酷哭", "1eabd1db85dd4bddbbd842abe9cdce49"],
["大喊", "1ec163608a2a49ffbbc766e505885bb1"],
["怒", "1edc3de7713647c2b269f7f48b494b2d"],
["发奋", "1f71ffdc4df041ac836cdc81940f68ff"],
["OK", "21b1e1cdd15742e9aacc6cfd764260cc"],
["气球", "24a048472a4c429c8d01d0401e846285"],
["抱抱", "24b237f43dee47c5a068bf4457b97145"],
["生气", "2609e5a35a3f436084bd618ef819227f"],
["卖萌", "2618a9c1f9424c78aaf7e6174f0242b4"],
["尴尬", "28d74529207c43178cc72cba847daf12"],
["得意", "29288834d417481f97eb63fee5c30b53"],
["凋谢", "2b7cfe4c3c524b709bd3f15537c06911"],
["大笑", "2d1fca45aceb46b8b3a8d55084977851"],
["太阳", "2fb7d94d6e7642349793def00e5b3e0c"],
["彩带", "30d222de7c4a42cdb6d7a9ea7bea162b"],
["睡觉", "30d9ccb7e4ae4a43997e76fbabd6b5cd"],
["奸笑", "3139a081129f4832a70da8d7808114fa"],
["OK手", "317a9b7fe5f94023a8b2987173708362"],
["露齿笑", "33745171e4a544ad913b52ebb6b46b60"],
["捂脸笑", "3545637c30284f6bb732a444f91196ab"],
["吐舌", "365405db173b45978ea6a45bd47e9d87"],
["手枪", "3b366738018343ae9831a79fa17b3dae"],
["惊恐", "3c8520db7cbd418f95aea788ee6a7cf0"],
["舔屏", "3d13332de5da45498cd1ef193cc4afff"],
["捂脸", "3df5f3ca07e546b697b301ab9e2e3c23"],
["难受", "3e8a89ca5dc74cb88b4000551a1312a7"],
["眯眼", "4201288b0e1b47b59f9f031a53c3d687"],
["嘿嘿", "42fd189ab7ad4f478f220d34046916fb"],
["亲亲", "434e5830fe874fdca427b0d8b23a751d"],
["花痴", "4364693f63a34e2999c54cbbc75df4e9"],
["眨眼", "44ddc9a6bcd9471a858a33c382f6fafa"],
["鸡腿", "45a1632fd059454b91c71d8d54e46a1b"],
["小蓝", "45f8ce9a22b0466fbcd1bb2a841d2225"],
["愤怒", "476e92daceb14bee99e36b8f50a40e81"],
["祈祷", "478afb0911604f5289d9cbfd4183b404"],
["贱笑", "47eb8964fef7479c80a1088f405beb43"],
["汗", "47fe86652a1b4cf288de5f929cba5d80"],
["擦汗", "49b5a54103ae43b0b02a564a3feec5a8"],
["思考", "49bb5e7832d644dd99f50d3b6733a4c6"],
["咖啡", "4b5f538005d148248338d97c47e20f9e"],
["晕圈", "4c0f3ba463734e50981103650f440be0"],
["点赞", "4c4a175aaf2b4e0c98b8d46684d64564"],
["可怜", "4cd68b81179d421ebaf2c4e36b3630c6"],
["开心", "4ec5e9c2001249b79bf4ff0c6a431cae"],
["圣诞帽", "4f1aeeedf61a4051aaf0bc96a6ead49f"],
["爱心眼", "4f6a86e4bfd14f8c8434f6d643782a41"],
["碰拳", "4f815c8c1795412cbb67233685d785a1"],
["冒烟", "517f7385630e4641af73c3ea93e44c7d"],
["委屈", "52aeca47432745729c08609d46211ed1"],
["指左", "54994d1e88d94722a71960508a8e9ce6"],
["二哈", "54afb78ebf6a45a09c6dc7d77fc3d3fb"],
["加油", "54e9a10981a242dba426a4f63d4103ab"],
["疑问", "55656c6385754a35acce9f807c4afa32"],
["惊呆", "55c305ac8ebe4f2a8e5fed639f188614"],
["笑泪", "590d12b8677d4738ad9e989ea0280f81"],
["绝", "5aaaff01fd1f493a82821b1a3943bf68"],
["香蕉", "5b0b93a490394665bed2a52ae67a2975"],
["福", "5cdc7b1819c3484db61fa60eeb989e2e"],
["捧腹", "5d7d5f3c3f9548679fb73090a69f538a"],
["红包", "5defde4959044aaeae628e060e82043f"],
["猫震惊", "5faa8e1516204480b74b6f6a3122d578"],
["呆", "600e9cfaf3a14174b451e2206baf1e0f"],
["柴犬", "603e30989b05474788e3709d7eebd4ef"],
["666", "61267a18cbf847b3af73190bc52ae36a"],
["握手", "613459694fa24ae689aa306fd07944f8"],
["吐", "616147770ba044a3abed4224c46fe605"],
["惊讶", "622ddf449a5d4ac6a0e15445356cfb94"],
["套头", "626ec790acdf4c10b5309a99ae75ecb1"],
["嫌弃", "62d34256ab7845709616c5a7bf42aee1"],
["给力", "63b22c1e04f34e8a8816ba2b8ac46ae3"],
["斜眼", "6572851608ba4ab3acc0f0c879594d1b"],
["星星脸", "66fb1287c3f04b4bbb427a1368d12a22"],
["蓝怪", "6b280673ae0740e78d82178ee018a811"],
["猪猪", "6c3589f8daec4143bd9b9d9b41e802ec"],
["点踩", "6c80753d816442718d2f5ca44e1eb13a"],
["关注我", "6ed70f4a306a48bcbc12a138165fb09d"],
["巧克力", "71e20043b5f34b03b7dd85afb69f0503"],
["调皮", "72045b3ac8654458a94767f463804c82"],
["红心", "740e29f4ca6547ccb4f7cf3fe0463629"],
["泪喷", "770948b40fe14c51a73e18dff5a8d29c"],
["红唇", "77a76f873a1f4e6ba5eeff343391b213"],
["怪笑", "77e480a1a9234d94bfcab3429f5f96bb"],
["钻戒", "7d2da9bea4f0477aa530340355fda832"],
["崩溃", "7e606e0d73d843a2bc1c15219a1318cd"],
["绿帽", "7e79587db89a4404a0ebcf4aabba3d2d"],
["圣诞树", "8260b9fef99e430aa08ff9259355e907"],
["敲打", "831ef27451104b0e9eb89660c3d7b716"],
["龇牙", "86d772fc25a0404090613bad4a123947"],
["扎心", "8a054c74604844498fa9c4ae809f1c79"],
["爱心枪", "8a7a9fb41d444aa68dd5568ba72040aa"],
["心动", "8bb95fd220bf4efeb44c9b3e6c63afdf"],
["墨镜笑", "8cd39208016c428ca80d3468e24edb0a"],
["贴贴", "8dcb4606e9f14613a3bbf98a1646e56f"],
["微笑", "907e61eb04e54074a101b43035d50145"],
["挨揍", "91656d99d6d645a5995187454e3293f4"],
["耳机", "91aa9af22f664ba08ca3c66184f2a838"],
["打响指", "94266b5d083d45afb4309ed6ed905dd6"],
["转眼", "94c011b83a3e40cbbcfeb09c6adb2b43"],
["狂笑", "9501220f825547d699c8409184846166"],
["麻将", "956e469de272416ebeb0aafd780bd78e"],
["雪花", "98c4f361b8cc49e2aeff0b546664af42"],
["飞吻", "9a0887a55ff94cbbbadbbbdb14c7396a"],
["指右", "9a35f66a62804e90b31455ab6f0d99ee"],
["沉思", "9e2b0289cd994a91a77676265d5ab7a7"],
["裂开", "9eb467c1c4d849f39b45ad4e7beff0ce"],
["烟花", "9ebf943dbb974a5e8dd13e5a339cff18"],
["仓鼠", "9ff343dc38e54a9f98e7fa896dd95715"],
["揉脸", "a3d1ce9301c5410d88cc2470a3e11c30"],
["鼓掌", "a4d28f23c10e4efaa979b4d119b7ade4"],
["黑脸", "a4d9f336b64f4554a0790eed0f0e1a71"],
["数钱", "a7a5a4db025642a392ee10d5c74e65c9"],
["哇", "a90f7139a14140dbba575425193ca6c5"],
["尬笑", "a993e64d05cd428e876600a75129332f"],
["舒心", "ab54a06f377346f88f094fe6281103ff"],
["作揖", "abf7cc107d4740e4bc1e238b770ac889"],
["窃喜", "aca47d7bc40a442b933812e3f482549d"],
["跌倒", "aceb3d3827574b68a40f7c7ebf1e3049"],
["楚楚", "ad1d6ca9bc694b78b8b3ad816aaccf0d"],
["拍脸", "ada62e0815f141cdbdc5a76e6781786d"],
["击掌", "ae9cd379b8c448d5a3c3aa107c3272d8"],
["香吻", "aec920e3a21444c79a79defd76a45a75"],
["抓狂", "afa666d51d474fc6b2f0a10aca57c372"],
["摇扇", "b1c3d09acfe84b9280a28c4190fa0056"],
["doge", "b217d170f0c349fc8ea86615738a5c81"],
["咋舌", "b21a518ebd8b48caab9c814e40b63115"],
["咧嘴", "b341f7cdcaea452a83a78eb81b5a30b4"],
["掩面", "b379ea709e484581830373adf1bce572"],
["狗拳", "b485611a693d4eadadf29f5f055511f6"],
["糖葫芦", "b67a198b447a45d0a36041196e26e447"],
["黄瓜", "ba6c382121e94f52bb44f41b9156f447"],
["脸红", "bbcb745bbb1c463089a0d40177623c9e"],
["叫好", "bd1068daba564862be71631451611ba7"],
["白菜狗", "bd5541fc02514e7e8aefde9b0aabc652"],
["捂嘴", "bf6b3de54ab34732af82809635c704f8"],
["互粉", "bfa5963893bd41519ff31c7415f6956f"],
["蛋糕", "c08640142c304d76959ee198cc8a2efd"],
["撇嘴", "c10ec8abb6da4adbb3388d435a980568"],
["求机位", "c5c18a6bfb3d43f9a96f833f9a758d71"],
["心心眼", "c6605942715f4717805101c311b41f24"],
["蓝毛", "c6646c7cd6fc4f8d89f332c2d46e4597"],
["元宝", "c67bf9ed7bb34e00af6ef863d6ecfdad"],
["吐血", "c98569a7f04943d7a8de0c15cef36151"],
["便便", "cabe6bcd9889449daa87c2df765f379e"],
["鞠躬", "cb9f16ad45d84f7ca4256f20048e422a"],
["苹果", "cc5884dd4b814d81a9982bbcee136e45"],
["痴笑", "cca02095fe5f448ea2289ece0e2ac49f"],
["哈士奇", "cce25443e61846699d1f08782fb365bb"],
["猜拳", "cd619c9151a14094ab32f44f7487b980"],
["棒棒糖", "cd8647d2106b48fd9142c9c41562c8f7"],
["掩嘴", "cf1a3c5b317f4c0399ed39888d73abe7"],
["彩虹", "cfeddc926d3846eabda23e750364e82d"],
["酷脸", "d15c89c9f3364485b514d51b43b16771"],
["吃橘", "d2a361001193458ab3250ab557c7ec4b"],
["西瓜", "d2c0b2c0484b4bd59d6b266c5a80c199"],
["躺平", "d3084c0ac6e74eae84dd39cbc26d990d"],
["掩脸", "d34c2cb72f9f45a1bd21669f832b9a94"],
["鬼脸", "d3cbf267d0d8426e90edfb9c75059314"],
["加油牌", "d525a68955854abda1c8bdf7f62ebadb"],
["出拳", "d8a316cb0427498b87e396350b83b504"],
["加一", "d9840b29c0d84082b758580ff6da0281"],
["吃瓜", "db6bb519e6bc4b9b819e04b05e194586"],
["瞪眼", "dcaa1bb24fed447b9ef155462db2a890"],
["黑球", "ddd30bbe0a194430af6bcd6fef98570f"],
["狼狗", "de102f5602824afb88009091971a862d"],
["猫哭", "df7b9c648eef4720962f604f1c81a77c"],
["捂眼", "dfaa3f289fd54142ad6c8088ee516852"],
["蹲下", "e44b432ef18e4d84844e9eb1e7d135ac"],
["干笑", "e4b71ab984154f0ab1709bd88e9defda"],
["不悦", "e5275fd92a234e34984044e3b2213ebc"],
["捧脸", "e58ab050664b44eaa7a7c8b8d6fe9cf7"],
["羞涩", "e7f9e93ae5da435884e17c57442729f1"],
["盯", "e80c05fa6c4943759dba8486512901e0"],
["开怀", "e9078540aac945b4992ae19dbf4d0783"],
["红酒", "e959a423dd0a43fd8fcd5dd0499928cf"],
["灯笼", "ea0d63f135594b82ac3e386cf684046a"],
["干杯", "ea97458fd890435c9fa6df3520f66b1f"],
["军帽", "eb8d8e8a04e34db082437c083539dd2f"],
["禁烟花", "ebff2644a4ab4149a41f1bc7282879b1"],
["减一", "efa80a6e10f04dcc8de89092744b2bf9"],
["叼玫瑰", "f1d000c0d62a4d77b14641076a05f5b9"],
["嚣张", "f266b481b9e14e588228016138913c75"],
["拍我", "f2efb004f2f842b1bc20284bd2e010e3"],
["干饭", "f68c5e1160c449508d55cbe2acc0bd47"],
["德牧", "f730fe9f0bd44b59b8f0ed5069a747dc"],
["糖果", "f75de5be772c458e9a5469b32c75413e"],
["甜点", "f840c0bdd606425a8dd1190b80126674"],
["比心", "f87a6fb3ac774f2e8f9f1760e3d2de45"],
["迷恋", "fba0f99d4b8049cd905bfd076d4efbf2"],
["惊呼", "fba1c2ac23394a31bc1c3405148bd363"],
["郁闷", "fbbbf98a169642d386020f79d06af11c"],
["含泪", "fbd4350861fe49cd991e76e82e382715"],
["三文鱼", "fd24c9f2258f4640af610472b442f9ec"],
];
/**
* 默认表情分组:同类型相邻排列(参考抖音/飞书面板)。
* keys 必须恰好覆盖 DY_EMOJI_ALIAS_PAIRS 的全部中文 key 且不重复(有单测锁定)。
*/
export const DY_EMOJI_GROUPS: readonly { label: string; keys: string[] }[] = [
{
label: "笑脸",
keys: [
"微笑", "露齿笑", "笑哭", "笑泪", "捂脸笑", "嘿嘿", "贱笑", "奸笑", "怪笑", "痴笑",
"狂笑", "大笑", "捧腹", "开怀", "窃喜", "尬笑", "干笑", "咧嘴", "龇牙", "吐舌",
"舔屏", "星星脸", "花痴", "得意", "坏笑", "墨镜笑", "嚣张", "酷哭", "酷脸", "调皮",
"卖萌", "呆", "鬼脸", "脸红", "羞涩", "楚楚", "捧脸", "舒心", "开心", "精选",
],
},
{
label: "情绪",
keys: [
"大哭", "含泪", "泪喷", "委屈", "难受", "郁闷", "不悦", "无语", "汗", "擦汗",
"晕", "晕圈", "崩溃", "抓狂", "怒", "生气", "愤怒", "黑脸", "尴尬", "可怜",
"裂开", "吐", "吐血", "嘟嘴", "撇嘴", "嫌弃", "不屑", "翻白眼", "斜眼", "哇",
"大喊", "惊呆", "惊讶", "惊呼", "惊恐", "瞪眼", "盯", "眨眼", "眯眼", "转眼",
"疑问", "思考", "沉思", "咋舌", "绝", "睡觉", "躺平",
],
},
{
label: "手势",
keys: [
"OK", "OK手", "点赞", "点踩", "碰拳", "击掌", "出拳", "猜拳", "狗拳", "握手",
"作揖", "鞠躬", "祈祷", "打响指", "鼓掌", "加油", "加油牌", "发奋", "指左", "指右",
"摸头", "揉脸", "捂脸", "掩面", "掩脸", "掩嘴", "捂嘴", "捂眼", "拍脸", "拍我",
"套头", "挨揍", "跌倒", "蹲下", "抱抱",
],
},
{
label: "爱心",
keys: [
"红心", "心碎", "爱心眼", "心心眼", "爱心枪", "心动", "比心", "迷恋", "贴贴", "亲亲",
"香吻", "飞吻", "红唇", "绿帽",
],
},
{
label: "动物",
keys: [
"猫猫", "猫震惊", "猫哭", "二哈", "哈士奇", "柴犬", "白菜狗", "德牧", "狼狗", "猪猪",
"仓鼠", "小蓝", "蓝怪", "蓝毛", "三文鱼", "doge",
],
},
{
label: "食物",
keys: [
"鸡腿", "咖啡", "香蕉", "苹果", "西瓜", "黄瓜", "吃瓜", "吃橘", "糖葫芦", "棒棒糖",
"巧克力", "蛋糕", "糖果", "甜点", "粽子", "红酒", "干饭", "干杯",
],
},
{
label: "物品",
keys: [
"炸弹", "禁止", "手枪", "火苗", "冒烟", "太阳", "雪花", "彩虹", "气球", "纸飞机",
"玫瑰", "凋谢", "叼玫瑰", "礼物", "钻戒", "元宝", "红包", "数钱", "便便", "黑球",
"耳机", "摇扇", "麻将", "军帽", "圣诞帽", "圣诞树", "灯笼", "福", "鞭炮", "烟花",
"禁烟花", "彩带", "庆祝", "叫好", "求机位", "关注我", "互粉", "5V5", "666", "加一",
"减一", "给力", "敲打", "扎心",
],
},
];
/** 中文语义名 → 实际文件名查找表 */
export const DY_EMOJI_ALIASES: Record<string, string> =
Object.fromEntries(DY_EMOJI_ALIAS_PAIRS);
/** key → 实际文件名:中文语义名查别名表,合成表情组合名原样透传 */
export function dyEmojiFileKey(key: string): string {
return DY_EMOJI_ALIASES[key] ?? key;
}
export function dyEmojiUrl(key: string): string {
return `${DY_EMOJI_BASE}/${encodeURIComponent(dyEmojiFileKey(key))}.png`;
}
/**
* 把 [:key:] 短代码展开为 markdown 行内图片(供 Markdown 渲染管线)。
* 注意:不感知代码围栏,围栏内的短代码也会被展开(v1 已知限制)。
*/
export function expandDyEmojiShortcodes(text: string): string {
if (!text || !text.includes("[:")) return text;
return text.replace(DY_EMOJI_RE, (_m, key: string) => {
return `![dy:${key}](${dyEmojiUrl(key)})`;
});
}
export type DyEmojiManifest = { tos: string[]; fus: string[] };
/** 收集文本中的表情短代码位置(聊天等纯文本渲染用;每次用独立正则避免 g 的 lastIndex 状态) */
export function collectDyEmojiSpans(
text: string
): { start: number; end: number; key: string }[] {
if (!text || !text.includes("[:")) return [];
const spans: { start: number; end: number; key: string }[] = [];
const re = new RegExp(DY_EMOJI_RE.source, "g");
for (let m = re.exec(text); m; m = re.exec(text)) {
spans.push({ start: m.index, end: m.index + m[0].length, key: m[1] });
}
return spans;
}