feat: 新增贴纸系统,重构评论编辑器与内容处理
1. 新增多平台贴纸库:贴吧、微博、知乎、小红书、抖音、B站共500+贴纸,搭配颜文字贴纸 2. 实现贴纸选择器面板,支持分类浏览、键盘导航和懒加载 3. 重构评论内容渲染:替换旧的@提及高亮逻辑,新增HTML净化与双向兼容 4. 重构评论编辑器为Tiptap富文本版本,支持表情插入、格式编辑 5. 优化评论编辑体验,修复滚动位置保留问题,调整评论区UI样式 6. 移除旧版EmojiPicker工具类,新增贴纸数据管理模块
This commit is contained in:
1594
frontend/src/data/stickers/emoji.json
Normal file
1594
frontend/src/data/stickers/emoji.json
Normal file
File diff suppressed because it is too large
Load Diff
57
frontend/src/data/stickers/emojiData.ts
Normal file
57
frontend/src/data/stickers/emojiData.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import type { Sticker, StickerCategory } from './index';
|
||||
import emojiJson from './emoji.json';
|
||||
|
||||
/** 平台 → 分类名映射 */
|
||||
const PLATFORM_TO_CATEGORY: Record<string, StickerCategory> = {
|
||||
tieba: '贴吧',
|
||||
zhihu: '知乎',
|
||||
xiaohongshu: '小红书',
|
||||
douyin: '抖音',
|
||||
bilibili: 'B站',
|
||||
weibo: '微博',
|
||||
};
|
||||
|
||||
interface EmojiItem {
|
||||
url: string;
|
||||
name: string;
|
||||
tags: string[];
|
||||
keywords: string[];
|
||||
}
|
||||
|
||||
/** 将 emoji.json 的 url (output/tieba/tb_01.avif) 转为本地路径 (/stickers/tieba/tb_01.avif) */
|
||||
function toLocalUrl(url: string): string {
|
||||
// output/tieba/tb_01.avif → /stickers/tieba/tb_01.avif
|
||||
return url.replace(/^output\//, '/stickers/');
|
||||
}
|
||||
|
||||
/** 缓存:平台 → Sticker[] */
|
||||
const cache = new Map<string, Sticker[]>();
|
||||
|
||||
/** 按平台获取贴纸列表 */
|
||||
export function getStickersByPlatform(platform: string): Sticker[] {
|
||||
if (cache.has(platform)) return cache.get(platform)!;
|
||||
|
||||
const category = PLATFORM_TO_CATEGORY[platform];
|
||||
if (!category) return [];
|
||||
|
||||
const entry = (emojiJson as Array<{ platform: string; emojis: EmojiItem[] }>)
|
||||
.find((p) => p.platform === platform);
|
||||
if (!entry) return [];
|
||||
|
||||
const stickers: Sticker[] = entry.emojis.map((e, i) => ({
|
||||
id: `${platform}-${i + 1}`,
|
||||
name: e.name,
|
||||
category,
|
||||
type: 'image' as const,
|
||||
url: toLocalUrl(e.url),
|
||||
aliases: e.keywords,
|
||||
}));
|
||||
|
||||
cache.set(platform, stickers);
|
||||
return stickers;
|
||||
}
|
||||
|
||||
/** 获取所有平台的贴纸(用于热门筛选) */
|
||||
export function getAllStickers(): Sticker[] {
|
||||
return Object.keys(PLATFORM_TO_CATEGORY).flatMap((p) => getStickersByPlatform(p));
|
||||
}
|
||||
24
frontend/src/data/stickers/hot.ts
Normal file
24
frontend/src/data/stickers/hot.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { Sticker } from './index';
|
||||
import { getAllStickers } from './emojiData';
|
||||
import { KAOMOJI_STICKERS } from './kaomoji';
|
||||
|
||||
/** 热门贴纸 — 从各平台精选 */
|
||||
const HOT_KEYWORDS = [
|
||||
'笑哭', '开心', '大笑', '哈哈', '捂脸', '捂嘴', '飙泪笑',
|
||||
'酷', '怒', '哭', '大哭', '生气', '惊讶', '尴尬',
|
||||
'真棒', '赞', '666', '耶', '爱', '害羞', '思考',
|
||||
'滑稽', '吃瓜', '调皮', '发呆', '机智',
|
||||
];
|
||||
|
||||
/** 热门贴纸懒加载:合并所有平台后按关键词筛选 */
|
||||
export async function loadHotStickers(): Promise<Sticker[]> {
|
||||
const allEmoji = getAllStickers();
|
||||
const all = [...allEmoji, ...KAOMOJI_STICKERS];
|
||||
return all
|
||||
.filter((s) => {
|
||||
if (s.category === '颜文字') return true;
|
||||
return s.aliases?.some((a) => HOT_KEYWORDS.includes(a)) || HOT_KEYWORDS.includes(s.name);
|
||||
})
|
||||
.slice(0, 30)
|
||||
.map((s) => ({ ...s, category: '热门' as const }));
|
||||
}
|
||||
35
frontend/src/data/stickers/index.ts
Normal file
35
frontend/src/data/stickers/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/** 贴纸类型定义(URL 方式,非 sprite 裁切) */
|
||||
|
||||
export type StickerCategory = '热门' | '贴吧' | '知乎' | '小红书' | '抖音' | 'B站' | '微博' | '颜文字';
|
||||
|
||||
export type StickerType = 'image' | 'text';
|
||||
|
||||
export interface Sticker {
|
||||
id: string;
|
||||
name: string;
|
||||
category: StickerCategory;
|
||||
aliases?: string[];
|
||||
type: StickerType;
|
||||
/** image 类型:图片路径(相对于 public 目录) */
|
||||
url?: string;
|
||||
/** text 类型:颜文字字符串 */
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export const STICKER_CATEGORIES: StickerCategory[] = ['热门', '贴吧', '知乎', '小红书', '抖音', 'B站', '微博', '颜文字'];
|
||||
|
||||
const CATEGORY_LOADERS: Record<StickerCategory, () => Promise<Sticker[]>> = {
|
||||
'热门': () => import('./hot').then(m => m.loadHotStickers()),
|
||||
'贴吧': () => import('./emojiData').then(m => m.getStickersByPlatform('tieba')),
|
||||
'知乎': () => import('./emojiData').then(m => m.getStickersByPlatform('zhihu')),
|
||||
'小红书': () => import('./emojiData').then(m => m.getStickersByPlatform('xiaohongshu')),
|
||||
'抖音': () => import('./emojiData').then(m => m.getStickersByPlatform('douyin')),
|
||||
'B站': () => import('./emojiData').then(m => m.getStickersByPlatform('bilibili')),
|
||||
'微博': () => import('./emojiData').then(m => m.getStickersByPlatform('weibo')),
|
||||
'颜文字': () => import('./kaomoji').then(m => m.KAOMOJI_STICKERS),
|
||||
};
|
||||
|
||||
/** 按分类动态加载贴纸数据 */
|
||||
export async function loadStickersByCategory(cat: StickerCategory): Promise<Sticker[]> {
|
||||
return CATEGORY_LOADERS[cat]();
|
||||
}
|
||||
32
frontend/src/data/stickers/kaomoji.ts
Normal file
32
frontend/src/data/stickers/kaomoji.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { Sticker } from './index';
|
||||
|
||||
/** 颜文字贴纸 — 纯文本类型,选择器和编辑器中均作为纯文本显示 */
|
||||
|
||||
export const KAOMOJI_STICKERS: Sticker[] = [
|
||||
{ id: 'km-happy', name: '开心', category: '颜文字', type: 'text', text: '(ノ´∀`)ノ', aliases: ['哈哈', '开心'] },
|
||||
{ id: 'km-laugh', name: '大笑', category: '颜文字', type: 'text', text: '(≧∇≦)ノ', aliases: ['哈哈哈', '笑死'] },
|
||||
{ id: 'km-cry', name: '哭', category: '颜文字', type: 'text', text: '(´;ω;`)', aliases: ['呜呜', '伤心'] },
|
||||
{ id: 'km-angry', name: '生气', category: '颜文字', type: 'text', text: 'ヽ(`⌒´)ノ', aliases: ['怒', '气死'] },
|
||||
{ id: 'km-shrug', name: '无奈', category: '颜文字', type: 'text', text: '╮(°-°)╭', aliases: ['呵呵', '无语'] },
|
||||
{ id: 'km-determined', name: '加油', category: '颜文字', type: 'text', text: '(๑•̀ㅁ•́ฅ)', aliases: ['冲', '奥利给'] },
|
||||
{ id: 'km-sad', name: '难过', category: '颜文字', type: 'text', text: '( ´・ω・`)', aliases: ['失落', '低落'] },
|
||||
{ id: 'km-sparkle', name: '兴奋', category: '颜文字', type: 'text', text: '(ノ´ヮ`)ノ*: ・゚', aliases: ['太棒了', '耶'] },
|
||||
{ id: 'km-tear', name: '泪奔', category: '颜文字', type: 'text', text: '(╥﹏╥)', aliases: ['泪流', '呜呜'] },
|
||||
{ id: 'km-love', name: '喜欢', category: '颜文字', type: 'text', text: '(◕ᴗ◕✿)', aliases: ['爱', '心动'] },
|
||||
{ id: 'km-cool', name: '酷', category: '颜文字', type: 'text', text: '(⌐■_■)', aliases: ['帅', '墨镜'] },
|
||||
{ id: 'km-stare', name: '盯', category: '颜文字', type: 'text', text: 'ಠ_ಠ', aliases: ['凝视', '盯着看'] },
|
||||
{ id: 'km-tableflip', name: '掀桌', category: '颜文字', type: 'text', text: '(╯°□°)╯︵ ┻━┻', aliases: ['掀桌', '愤怒'] },
|
||||
{ id: 'km-bow', name: '拜托', category: '颜文字', type: 'text', text: '(人・ω・)💦', aliases: ['求求', '拜托了'] },
|
||||
{ id: 'km-proud', name: '得意', category: '颜文字', type: 'text', text: '( ̄▽ ̄)"', aliases: ['嘿嘿', '自满'] },
|
||||
{ id: 'km-sleep', name: '困', category: '颜文字', type: 'text', text: '(-ω-)Zzz', aliases: ['睡觉', '晚安'] },
|
||||
{ id: 'km-wave', name: '招手', category: '颜文字', type: 'text', text: '(´・ω・)ノ', aliases: ['你好', '拜拜'] },
|
||||
{ id: 'km-wink', name: '眨眼', category: '颜文字', type: 'text', text: '(◠‿◠)', aliases: ['抛媚眼', '嘿嘿'] },
|
||||
{ id: 'km-sorry', name: '抱歉', category: '颜文字', type: 'text', text: 'm(._.)m', aliases: ['对不起', '跪了'] },
|
||||
{ id: 'km-doubt', name: '疑惑', category: '颜文字', type: 'text', text: '(╬ Ò _ Ó)', aliases: ['什么', '???'] },
|
||||
{ id: 'km-hungry', name: '饿了', category: '颜文字', type: 'text', text: '(๑´ㅂ`๑)', aliases: ['想吃', '吃货'] },
|
||||
{ id: 'km-gameover', name: 'GG', category: '颜文字', type: 'text', text: '(╯︿╰﹀ )', aliases: ['GG', '完了'] },
|
||||
{ id: 'km-gift', name: '送花', category: '颜文字', type: 'text', text: '(✿◠‿◠)', aliases: ['送花', '谢谢'] },
|
||||
{ id: 'km-clap', name: '鼓掌', category: '颜文字', type: 'text', text: 'ヾ(´▽`;)ゝ', aliases: ['呱唧', '鼓掌'] },
|
||||
{ id: 'km-cheer', name: '加油', category: '颜文字', type: 'text', text: '\(^ω^\)', aliases: ['冲鸭', 'go'] },
|
||||
{ id: 'km-please', name: '拜托了', category: '颜文字', type: 'text', text: '( ´・ω・`)', aliases: ['嘤嘤', '求求了'] },
|
||||
];
|
||||
Reference in New Issue
Block a user