feat: 完善表情选择与评论富文本展示

发帖编辑器支持 OwO 贴纸;评论区复用文章列表、段落与代码块阅读态,并优化表情面板间距与悬停反馈。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 03:57:41 +08:00
parent 4d93e455f9
commit 76be8926f2
11 changed files with 492 additions and 164 deletions

View File

@@ -1,5 +1,6 @@
import DOMPurify from 'dompurify';
import { POST_CONTENT_PURIFY_CONFIG } from './postContent';
import { enhanceCodeBlocks } from './enhanceCodeBlocks';
/** 转义 HTML 并保留换行 */
function escapeWithBreaks(text: string): string {
@@ -60,11 +61,15 @@ function processMentionsInHtml(html: string): string {
return div.innerHTML;
}
/** 渲染评论内容:HTML 净化 + @提及高亮,兼容旧版纯文本 */
/** 渲染评论内容:HTML 净化 + @提及高亮 + 代码块阅读态,兼容旧版纯文本 */
export function renderCommentContent(content: string): string {
if (isHtmlContent(content)) {
const sanitized = DOMPurify.sanitize(content, POST_CONTENT_PURIFY_CONFIG) as string;
return processMentionsInHtml(sanitized);
const withMentions = processMentionsInHtml(sanitized);
const doc = new DOMParser().parseFromString(`<div id="j13-comment-root">${withMentions}</div>`, 'text/html');
const root = doc.getElementById('j13-comment-root') ?? doc.body;
enhanceCodeBlocks(root);
return root.innerHTML;
}
return highlightMentions(content);
}

View File

@@ -177,3 +177,44 @@ export function enhanceCodeBlocks(root: ParentNode): void {
if (display.lineNumbers) pre.classList.add('md-codeblock__pre--lines');
});
}
const CODE_FOLD_SHORT_LINES = 5;
/**
* 阅读态代码块:折叠 / 复制。由帖子与评论共用,点击已处理时返回 true。
*/
export async function handleMdCodeBlockUiClick(target: EventTarget | null): Promise<boolean> {
if (!(target instanceof Element)) return false;
const foldBtn = target.closest<HTMLElement>('[data-code-fold]');
if (foldBtn) {
const block = foldBtn.closest('.md-codeblock');
if (!block) return true;
const collapsed = block.classList.toggle('md-codeblock--collapsed');
const lineCount = parseInt(block.getAttribute('data-line-count') || '0', 10)
|| block.querySelectorAll('.md-code-line').length
|| 1;
if (collapsed && lineCount <= CODE_FOLD_SHORT_LINES) block.classList.add('md-codeblock--short');
else block.classList.remove('md-codeblock--short');
foldBtn.textContent = collapsed ? '展开' : '收起';
return true;
}
const copyBtn = target.closest<HTMLElement>('[data-code-copy]');
if (!copyBtn) return false;
const block = copyBtn.closest('.md-codeblock');
const bodies = block?.querySelectorAll('.md-code-line__body');
const text = bodies && bodies.length
? [...bodies].map(el => el.textContent ?? '').join('\n')
: (block?.querySelector('pre')?.textContent ?? '');
await navigator.clipboard.writeText(text);
const prev = copyBtn.textContent;
copyBtn.textContent = '已复制';
copyBtn.classList.add('is-copied');
window.setTimeout(() => {
copyBtn.textContent = prev || '复制';
copyBtn.classList.remove('is-copied');
}, 1600);
return true;
}