feat: 新增贴纸系统,重构评论编辑器与内容处理
1. 新增多平台贴纸库:贴吧、微博、知乎、小红书、抖音、B站共500+贴纸,搭配颜文字贴纸 2. 实现贴纸选择器面板,支持分类浏览、键盘导航和懒加载 3. 重构评论内容渲染:替换旧的@提及高亮逻辑,新增HTML净化与双向兼容 4. 重构评论编辑器为Tiptap富文本版本,支持表情插入、格式编辑 5. 优化评论编辑体验,修复滚动位置保留问题,调整评论区UI样式 6. 移除旧版EmojiPicker工具类,新增贴纸数据管理模块
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { POST_CONTENT_PURIFY_CONFIG } from './postContent';
|
||||
|
||||
/** 转义 HTML 并保留换行 */
|
||||
function escapeWithBreaks(text: string): string {
|
||||
return text
|
||||
@@ -18,6 +21,54 @@ export function highlightMentions(text: string): string {
|
||||
);
|
||||
}
|
||||
|
||||
/** 判断内容是否为 HTML(包含常见 HTML 标签) */
|
||||
function isHtmlContent(text: string): boolean {
|
||||
return /<(?:p|div|span|br|h[1-6]|ul|ol|li|pre|code|blockquote|a|img|table|strong|em|u|s)\b/i.test(text);
|
||||
}
|
||||
|
||||
/** 在 HTML 文本节点中高亮 @ 提及(DOM 遍历,避免破坏标签) */
|
||||
function processMentionsInHtml(html: string): string {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
const walker = document.createTreeWalker(div, NodeFilter.SHOW_TEXT);
|
||||
const textNodes: Text[] = [];
|
||||
let node: Node | null;
|
||||
while ((node = walker.nextNode())) {
|
||||
textNodes.push(node as Text);
|
||||
}
|
||||
for (const textNode of textNodes) {
|
||||
const text = textNode.textContent ?? '';
|
||||
if (!/@[\w\u4e00-\u9fa5_-]/.test(text)) continue;
|
||||
const frag = document.createDocumentFragment();
|
||||
const parts = text.split(/(@[\w\u4e00-\u9fa5_-]+)/);
|
||||
for (const part of parts) {
|
||||
const m = part.match(/^@([\w\u4e00-\u9fa5_-]+)$/);
|
||||
if (m) {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'mention';
|
||||
span.setAttribute('data-name', m[1]);
|
||||
span.setAttribute('role', 'link');
|
||||
span.setAttribute('tabindex', '0');
|
||||
span.textContent = part;
|
||||
frag.appendChild(span);
|
||||
} else if (part) {
|
||||
frag.appendChild(document.createTextNode(part));
|
||||
}
|
||||
}
|
||||
textNode.parentNode?.replaceChild(frag, textNode);
|
||||
}
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/** 渲染评论内容:HTML 净化 + @提及高亮,兼容旧版纯文本 */
|
||||
export function renderCommentContent(content: string): string {
|
||||
if (isHtmlContent(content)) {
|
||||
const sanitized = DOMPurify.sanitize(content, POST_CONTENT_PURIFY_CONFIG) as string;
|
||||
return processMentionsInHtml(sanitized);
|
||||
}
|
||||
return highlightMentions(content);
|
||||
}
|
||||
|
||||
/** 相对时间:刚刚 / N分钟前 / N小时前 / N天前;更早用具体日期 */
|
||||
export function formatTime(iso: string) {
|
||||
const d = new Date(iso);
|
||||
|
||||
Reference in New Issue
Block a user