diff --git a/cwd-comments-admin/.env.example b/cwd-comments-admin/.env.example new file mode 100644 index 0000000..b6ca4eb --- /dev/null +++ b/cwd-comments-admin/.env.example @@ -0,0 +1,3 @@ +VITE_API_BASE_URL=https://api.example.com +VITE_ADMIN_NAME=anghunk@example.com +VITE_ADMIN_PASSWORD=123456 diff --git a/cwd-comments-api/src/api/public/postComment.ts b/cwd-comments-api/src/api/public/postComment.ts index 6d03515..b33b284 100644 --- a/cwd-comments-api/src/api/public/postComment.ts +++ b/cwd-comments-api/src/api/public/postComment.ts @@ -13,7 +13,8 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => { if (!data || typeof data !== 'object') { return c.json({ message: '无效的请求体' }, 400); } - const { post_slug, content: rawContent, author: rawAuthor, email, url, parent_id, post_title, post_url } = data; + const { post_slug, content: rawContent, author: rawAuthor, email, url, post_title, post_url } = data; + const parentId = (data as any).parent_id ?? (data as any).parentId ?? null; if (!post_slug || typeof post_slug !== 'string') { return c.json({ message: 'post_slug 必填' }, 400); } @@ -64,7 +65,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => { console.log('PostComment:request', { postSlug: post_slug, - hasParent: !!parent_id, + hasParent: parentId !== null && parentId !== undefined, author, email, ip, @@ -95,7 +96,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => { userAgent, content, content, - parent_id || null, + parentId || null, "approved" // 或者从环境变量读取默认状态 ).run(); @@ -103,70 +104,60 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => { console.log('PostComment:inserted', { postSlug: post_slug, - hasParent: !!parent_id, + hasParent: parentId !== null && parentId !== undefined, ip }); // 5. 发送邮件通知 (后台异步执行,不阻塞用户响应) if (c.env.SEND_EMAIL && c.env.CF_FROM_EMAIL) { console.log('PostComment:mailDispatch:start', { - hasParent: !!data.parent_id + hasParent: parentId !== null && parentId !== undefined }); c.executionCtx.waitUntil((async () => { try { - if (data.parent_id) { + if (parentId !== null && parentId !== undefined) { + let adminEmail: string | null = null; + try { + adminEmail = await getAdminNotifyEmail(c.env); + } catch (e) { + console.error('PostComment:mailDispatch:userReply:getAdminEmailFailed', e); + } + const isAdminReply = !!adminEmail && email === adminEmail; + const parentComment = await c.env.CWD_DB.prepare( "SELECT author, email, content_text FROM Comment WHERE id = ?" - ).bind(data.parent_id).first<{ author: string, email: string, content_text: string }>(); + ).bind(parentId).first<{ author: string, email: string, content_text: string }>(); - if (parentComment && parentComment.email !== data.email) { - let adminEmail: string | null = null; - try { - adminEmail = await getAdminNotifyEmail(c.env); - } catch (e) { - console.error('PostComment:mailDispatch:userReply:getAdminEmailFailed', e); - } - - const isAdminReply = !!adminEmail && email === adminEmail; - const isParentThirdParty = !!adminEmail && parentComment.email !== adminEmail; - - if (isAdminReply && isParentThirdParty) { + if (parentComment && parentComment.email && parentComment.email !== email) { + let canSendUserMail = true; + if (!isAdminReply) { const recentUserMail = await c.env.CWD_DB.prepare( "SELECT created_at FROM EmailLog WHERE recipient = ? AND type = 'user-reply' ORDER BY created_at DESC LIMIT 1" ).bind(parentComment.email).first<{ created_at: string }>(); - const canSendUserMail = !recentUserMail || (Date.now() - new Date(recentUserMail.created_at).getTime() > 60 * 1000); - - if (canSendUserMail && isValidEmail(parentComment.email)) { - console.log('PostComment:mailDispatch:userReply:send', { - toEmail: parentComment.email, - toName: parentComment.author - }); - await sendCommentReplyNotification(c.env, { - toEmail: parentComment.email, - toName: parentComment.author, - postTitle: data.post_title, - parentComment: parentComment.content_text, - replyAuthor: author, - replyContent: content, - postUrl: data.post_url, - }); - await c.env.CWD_DB.prepare( - "INSERT INTO EmailLog (recipient, type, ip_address, created_at) VALUES (?, ?, ?, ?)" - ).bind(parentComment.email, 'user-reply', ip, new Date().toISOString()).run(); - console.log('PostComment:mailDispatch:userReply:logInserted', { - toEmail: parentComment.email - }); - } - if (!canSendUserMail) { - console.log('PostComment:mailDispatch:userReply:skippedByRateLimit', { - toEmail: parentComment.email - }); - } - } else { - console.log('PostComment:mailDispatch:userReply:skipNonAdminReply', { - adminEmail, - currentEmail: email, - parentEmail: parentComment.email + canSendUserMail = + !recentUserMail || + (Date.now() - new Date(recentUserMail.created_at).getTime() > 30 * 1000); + } + + if (canSendUserMail && isValidEmail(parentComment.email)) { + console.log('PostComment:mailDispatch:userReply:send', { + toEmail: parentComment.email, + toName: parentComment.author + }); + await sendCommentReplyNotification(c.env, { + toEmail: parentComment.email, + toName: parentComment.author, + postTitle: data.post_title, + parentComment: parentComment.content_text, + replyAuthor: author, + replyContent: content, + postUrl: data.post_url, + }); + await c.env.CWD_DB.prepare( + "INSERT INTO EmailLog (recipient, type, ip_address, created_at) VALUES (?, ?, ?, ?)" + ).bind(parentComment.email, 'user-reply', ip, new Date().toISOString()).run(); + console.log('PostComment:mailDispatch:userReply:logInserted', { + toEmail: parentComment.email }); } } diff --git a/cwd-comments-api/src/utils/email.ts b/cwd-comments-api/src/utils/email.ts index 02ad2ce..9f7bf42 100644 --- a/cwd-comments-api/src/utils/email.ts +++ b/cwd-comments-api/src/utils/email.ts @@ -121,7 +121,7 @@ export async function sendCommentNotification(
-

新评论提醒 - ${postTitle}

+

新评论提醒

你的文章收到了新的评论

diff --git a/docs/guide/backend-config.md b/docs/guide/backend-config.md index 9beff0e..5015cce 100644 --- a/docs/guide/backend-config.md +++ b/docs/guide/backend-config.md @@ -102,7 +102,6 @@ npm install | `ADMIN_NAME` | 管理员登录名称 | | `ADMIN_PASSWORD` | 管理员登录密码 | | `CF_FROM_EMAIL` | 作为发件人显示的邮箱地址(需在 Cloudflare Email 路由中预先配置)选填 | -| `EMAIL_ADDRESS` | 管理员接收通知邮件的默认邮箱(可在后台设置中覆盖)选填 | **注:** 需要在 Cloudflare 控制面板中为 Email 路由开启发送权限并配置发件人域和地址,并在 `wrangler.jsonc` 中为 Worker 添加 `send_email` 绑定,以便在代码中通过 `env.SEND_EMAIL.send()` 直接发信。 @@ -115,12 +114,11 @@ npm install ... "send_email": [ { - "name": "SEND_EMAIL", - "destination_address": "xxx" + "name": "SEND_EMAIL" } ], ... } ``` -`destination_address` 和参数 `CF_FROM_EMAIL` 这里填写的邮箱是你绑定域名后,创建的 email 路由,两者需保持一致 +参数 `CF_FROM_EMAIL` 这里填写的邮箱是你绑定域名后,创建的 email 路由,两者需保持一致 diff --git a/widget/src/core/CWDComments.js b/widget/src/core/CWDComments.js index 921f320..0881817 100644 --- a/widget/src/core/CWDComments.js +++ b/widget/src/core/CWDComments.js @@ -28,6 +28,12 @@ export class CWDComments { */ constructor(config) { this.config = { ...config }; + if (!this.config.postTitle && typeof document !== 'undefined') { + this.config.postTitle = document.title || this.config.postSlug; + } + if (!this.config.postUrl && typeof window !== 'undefined') { + this.config.postUrl = window.location.href; + } this.hostElement = this._resolveElement(config.el); this.shadowRoot = null; this.mountPoint = null; diff --git a/widget/src/core/store.js b/widget/src/core/store.js index d430db5..c443a2a 100644 --- a/widget/src/core/store.js +++ b/widget/src/core/store.js @@ -102,7 +102,7 @@ export function createCommentStore(config, fetchComments, submitComment) { const store = new Store({ // 评论数据 comments: [], - loading: false, + loading: true, error: null, // 分页