feat(评论): 添加Markdown支持和XSS过滤功能
添加marked和xss依赖包用于评论内容的Markdown渲染和XSS过滤 修改邮件模板以支持HTML格式的评论内容显示 更新数据库存储逻辑,同时保存原始文本和渲染后的HTML内容
This commit is contained in:
@@ -19,7 +19,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hono": "^4.11.3",
|
"hono": "^4.11.3",
|
||||||
|
"marked": "^17.0.1",
|
||||||
"nodemailer": "^7.0.12",
|
"nodemailer": "^7.0.12",
|
||||||
"ua-parser-js": "^2.0.7"
|
"ua-parser-js": "^2.0.7",
|
||||||
|
"xss": "^1.0.15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1484
cwd-comments-api/pnpm-lock.yaml
generated
1484
cwd-comments-api/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -67,9 +67,22 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
`).run();
|
`).run();
|
||||||
|
|
||||||
// 3. 准备数据
|
// 3. 准备数据
|
||||||
const content = checkContent(rawContent);
|
const contentText = checkContent(rawContent);
|
||||||
const author = checkContent(rawAuthor);
|
const author = checkContent(rawAuthor);
|
||||||
|
|
||||||
|
// Markdown 渲染与 XSS 过滤
|
||||||
|
const html = await marked.parse(rawContent, { async: true });
|
||||||
|
const contentHtml = xss(html, {
|
||||||
|
whiteList: {
|
||||||
|
...xss.whiteList,
|
||||||
|
code: ['class'],
|
||||||
|
span: ['class', 'style'],
|
||||||
|
pre: ['class'],
|
||||||
|
div: ['class', 'style'],
|
||||||
|
img: ['src', 'alt', 'title', 'width', 'height', 'style']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
console.log('PostComment:request', {
|
console.log('PostComment:request', {
|
||||||
postSlug: post_slug,
|
postSlug: post_slug,
|
||||||
hasParent: parentId !== null && parentId !== undefined,
|
hasParent: parentId !== null && parentId !== undefined,
|
||||||
@@ -99,8 +112,8 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
`${uaResult.browser.name || ""} ${uaResult.browser.version || ""}`.trim(),
|
`${uaResult.browser.name || ""} ${uaResult.browser.version || ""}`.trim(),
|
||||||
uaResult.device.model || uaResult.device.type || "Desktop",
|
uaResult.device.model || uaResult.device.type || "Desktop",
|
||||||
userAgent,
|
userAgent,
|
||||||
content,
|
contentText,
|
||||||
content,
|
contentHtml,
|
||||||
parentId || null,
|
parentId || null,
|
||||||
"approved" // 或者从环境变量读取默认状态
|
"approved" // 或者从环境变量读取默认状态
|
||||||
).run();
|
).run();
|
||||||
@@ -140,8 +153,8 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
const isAdminReply = !!adminEmail && email === adminEmail;
|
const isAdminReply = !!adminEmail && email === adminEmail;
|
||||||
|
|
||||||
const parentComment = await c.env.CWD_DB.prepare(
|
const parentComment = await c.env.CWD_DB.prepare(
|
||||||
"SELECT author, email, content_text FROM Comment WHERE id = ?"
|
"SELECT author, email, content_html FROM Comment WHERE id = ?"
|
||||||
).bind(parentId).first<{ author: string, email: string, content_text: string }>();
|
).bind(parentId).first<{ author: string, email: string, content_html: string }>();
|
||||||
|
|
||||||
if (parentComment && parentComment.email && parentComment.email !== email) {
|
if (parentComment && parentComment.email && parentComment.email !== email) {
|
||||||
let canSendUserMail = true;
|
let canSendUserMail = true;
|
||||||
@@ -163,9 +176,9 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
toEmail: parentComment.email,
|
toEmail: parentComment.email,
|
||||||
toName: parentComment.author,
|
toName: parentComment.author,
|
||||||
postTitle: data.post_title,
|
postTitle: data.post_title,
|
||||||
parentComment: parentComment.content_text,
|
parentComment: parentComment.content_html,
|
||||||
replyAuthor: author,
|
replyAuthor: author,
|
||||||
replyContent: content,
|
replyContent: contentHtml,
|
||||||
postUrl: data.post_url,
|
postUrl: data.post_url,
|
||||||
}, notifySettings.smtp);
|
}, notifySettings.smtp);
|
||||||
await c.env.CWD_DB.prepare(
|
await c.env.CWD_DB.prepare(
|
||||||
@@ -187,7 +200,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
postTitle: data.post_title,
|
postTitle: data.post_title,
|
||||||
postUrl: data.post_url,
|
postUrl: data.post_url,
|
||||||
commentAuthor: author,
|
commentAuthor: author,
|
||||||
commentContent: content
|
commentContent: contentHtml
|
||||||
}, notifySettings.smtp);
|
}, notifySettings.smtp);
|
||||||
await c.env.CWD_DB.prepare(
|
await c.env.CWD_DB.prepare(
|
||||||
"INSERT INTO EmailLog (recipient, type, ip_address, created_at) VALUES (?, ?, ?, ?)"
|
"INSERT INTO EmailLog (recipient, type, ip_address, created_at) VALUES (?, ?, ?, ?)"
|
||||||
|
|||||||
@@ -255,11 +255,11 @@ export async function sendCommentReplyNotification(
|
|||||||
</p>
|
</p>
|
||||||
<div style="margin:0 0 18px 0;padding:14px 16px;border-radius:10px;background:#f3f4f6;border:1px solid #e5e7eb;">
|
<div style="margin:0 0 18px 0;padding:14px 16px;border-radius:10px;background:#f3f4f6;border:1px solid #e5e7eb;">
|
||||||
<div style="font-size:12px;color:#6b7280;margin-bottom:6px;">你之前的评论</div>
|
<div style="font-size:12px;color:#6b7280;margin-bottom:6px;">你之前的评论</div>
|
||||||
<div style="font-size:14px;color:#374151;white-space:pre-wrap;">${parentComment}</div>
|
<div style="font-size:14px;color:#374151;">${parentComment}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin:0 0 24px 0;padding:14px 16px;border-radius:10px;background:#eff6ff;border:1px solid #bfdbfe;">
|
<div style="margin:0 0 24px 0;padding:14px 16px;border-radius:10px;background:#eff6ff;border:1px solid #bfdbfe;">
|
||||||
<div style="font-size:12px;color:#1d4ed8;margin-bottom:6px;">最新回复</div>
|
<div style="font-size:12px;color:#1d4ed8;margin-bottom:6px;">最新回复</div>
|
||||||
<div style="font-size:14px;color:#1f2937;white-space:pre-wrap;">${replyContent}</div>
|
<div style="font-size:14px;color:#1f2937;">${replyContent}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align:center;margin-bottom:8px;">
|
<div style="text-align:center;margin-bottom:8px;">
|
||||||
<a href="${postUrl}" style="display:inline-block;padding:10px 22px;border-radius:999px;background:#2563eb;color:#ffffff;font-size:14px;font-weight:500;text-decoration:none;">
|
<a href="${postUrl}" style="display:inline-block;padding:10px 22px;border-radius:999px;background:#2563eb;color:#ffffff;font-size:14px;font-weight:500;text-decoration:none;">
|
||||||
@@ -327,7 +327,7 @@ export async function sendCommentNotification(
|
|||||||
</p>
|
</p>
|
||||||
<div style="margin:0 0 18px 0;padding:14px 16px;border-radius:10px;background:#f9fafb;border:1px solid #e5e7eb;">
|
<div style="margin:0 0 18px 0;padding:14px 16px;border-radius:10px;background:#f9fafb;border:1px solid #e5e7eb;">
|
||||||
<div style="font-size:12px;color:#6b7280;margin-bottom:6px;">评论内容</div>
|
<div style="font-size:12px;color:#6b7280;margin-bottom:6px;">评论内容</div>
|
||||||
<div style="font-size:14px;color:#374151;white-space:pre-wrap;">${commentContent}</div>
|
<div style="font-size:14px;color:#374151;">${commentContent}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin:0 0 8px 0;">
|
<div style="margin:0 0 8px 0;">
|
||||||
<a href="${postUrl}" style="display:inline-block;padding:10px 22px;border-radius:999px;background:#047857;color:#ffffff;font-size:14px;font-weight:500;text-decoration:none;">
|
<a href="${postUrl}" style="display:inline-block;padding:10px 22px;border-radius:999px;background:#047857;color:#ffffff;font-size:14px;font-weight:500;text-decoration:none;">
|
||||||
|
|||||||
Reference in New Issue
Block a user