将 Markdown 编辑器替换为 TipTap WYSIWYG,新增帖子修订记录与 diff 展示;首页支持最新/回复排序与本地缓存;后台可配置编辑时限与锁定帖子;侧边栏整合板块导航并优化 Feed 布局。 Co-authored-by: Cursor <cursoragent@cursor.com>
78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
import { diffLines, diffWords } from 'diff';
|
||
import DOMPurify from 'dompurify';
|
||
import { POST_CONTENT_PURIFY_CONFIG } from './postContent';
|
||
|
||
export interface PostSnapshot {
|
||
title: string;
|
||
content: string;
|
||
tags: string;
|
||
}
|
||
|
||
export interface RevisionChangeSummary {
|
||
titleChanged: boolean;
|
||
tagsChanged: boolean;
|
||
contentChanged: boolean;
|
||
hasChanges: boolean;
|
||
}
|
||
|
||
export interface DiffPart {
|
||
value: string;
|
||
added?: boolean;
|
||
removed?: boolean;
|
||
}
|
||
|
||
/** 将 HTML 正文转为适合 diff 的纯文本(保留段落换行) */
|
||
export function htmlToDiffText(html: string): string {
|
||
if (!html.trim()) return '';
|
||
const doc = new DOMParser().parseFromString(
|
||
DOMPurify.sanitize(html, POST_CONTENT_PURIFY_CONFIG),
|
||
'text/html',
|
||
);
|
||
doc.querySelectorAll('br').forEach(br => br.replaceWith('\n'));
|
||
const blocks = doc.querySelectorAll('p, div, li, h1, h2, h3, h4, h5, h6, blockquote, pre, members-only');
|
||
blocks.forEach(el => {
|
||
el.prepend(doc.createTextNode('\n'));
|
||
el.append(doc.createTextNode('\n'));
|
||
});
|
||
return (doc.body.textContent ?? '')
|
||
.replace(/\r\n/g, '\n')
|
||
.replace(/\n{3,}/g, '\n\n')
|
||
.trim();
|
||
}
|
||
|
||
export function summarizeChange(before: PostSnapshot, after: PostSnapshot): RevisionChangeSummary {
|
||
const titleChanged = before.title.trim() !== after.title.trim();
|
||
const tagsChanged = normalizeTags(before.tags) !== normalizeTags(after.tags);
|
||
const contentChanged = htmlToDiffText(before.content) !== htmlToDiffText(after.content);
|
||
return {
|
||
titleChanged,
|
||
tagsChanged,
|
||
contentChanged,
|
||
hasChanges: titleChanged || tagsChanged || contentChanged,
|
||
};
|
||
}
|
||
|
||
function normalizeTags(tags: string) {
|
||
return tags.split(/[,,]/).map(t => t.trim()).filter(Boolean).join(',');
|
||
}
|
||
|
||
export function diffTextLines(before: string, after: string): DiffPart[] {
|
||
return diffLines(before || '', after || '');
|
||
}
|
||
|
||
export function diffTextWords(before: string, after: string): DiffPart[] {
|
||
return diffWords(before || '', after || '');
|
||
}
|
||
|
||
/** 统计 diff 片段中的增删行数 */
|
||
export function countLineChanges(parts: DiffPart[]) {
|
||
let added = 0;
|
||
let removed = 0;
|
||
for (const p of parts) {
|
||
const lines = p.value.split('\n').filter(l => l.length > 0);
|
||
if (p.added) added += lines.length;
|
||
else if (p.removed) removed += lines.length;
|
||
}
|
||
return { added, removed };
|
||
}
|