feat: add article comment system with moderation
- Comment model with nested replies (ParentID), dual authorship (logged-in UserID / anonymous GuestToken cookie), email hash for Gravatar, private flag, and moderation status - CommentConfig singleton (enabled / allow guest / guest-require-approval / use Gravatar) cached like the other platform config - Markdown comments with built-in emoji picker, preview, and markdown help; rendered client-side via marked + DOMPurify, with server-side HTML/dangerous-scheme stripping as a first XSS defense - Private comments visible only to admin and the author; pending comments visible only to admin and the author - Admin moderation list (pending/approved/rejected/all tabs) with approve/reject/delete and a pending-count badge - Comment settings page with the four toggles - One-time session flash notice (auto-dismissed after 4s) so the "comment posted" banner no longer persists across refreshes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+142
@@ -198,6 +198,77 @@ var translations = map[Lang]map[string]string{
|
||||
"cat_archive": "Archive",
|
||||
"cat_video": "Video",
|
||||
"cat_other": "Other",
|
||||
|
||||
// Comments
|
||||
"comments_title": "Comments",
|
||||
"comments_count": "%d Comments",
|
||||
"comments_empty": "No comments yet. Be the first to comment.",
|
||||
"comments_reply": "Reply",
|
||||
"comments_cancel_reply": "Cancel reply",
|
||||
"comments_replying_to": "Replying to %s",
|
||||
"comments_name": "Name",
|
||||
"comments_email": "Email",
|
||||
"comments_email_hint": "Used for your Gravatar avatar; never shown publicly.",
|
||||
"comments_website": "Website (optional)",
|
||||
"comments_content": "Comment",
|
||||
"comments_content_ph": "Markdown is supported...",
|
||||
"comments_private": "Private comment",
|
||||
"comments_private_hint": "Only you and the administrator can see this.",
|
||||
"comments_preview": "Preview",
|
||||
"comments_edit": "Edit",
|
||||
"comments_emoji": "Emoji",
|
||||
"comments_markdown_help": "Markdown help",
|
||||
"comments_submit": "Post Comment",
|
||||
"comments_posted": "Comment posted.",
|
||||
"comments_pending_notice": "Your comment is awaiting moderation.",
|
||||
"comments_disabled": "Comments are disabled.",
|
||||
"comments_guests_disabled": "You must sign in to comment.",
|
||||
"comments_required_name": "Name is required.",
|
||||
"comments_required_email": "Email is required.",
|
||||
"comments_required_content":"Comment is required.",
|
||||
"comments_invalid_email": "Please enter a valid email address.",
|
||||
"comments_invalid_url": "Please enter a valid URL.",
|
||||
"comments_too_long": "Comment is too long (max %d characters).",
|
||||
"comments_pending": "Pending",
|
||||
"comments_approved": "Approved",
|
||||
"comments_rejected": "Rejected",
|
||||
"comments_private_badge": "Private",
|
||||
"comments_markdown_bold": "**bold**",
|
||||
"comments_markdown_italic": "*italic*",
|
||||
"comments_markdown_code": "`code`",
|
||||
"comments_markdown_link": "[text](url)",
|
||||
"comments_markdown_quote": "> quote",
|
||||
|
||||
// Admin: comments
|
||||
"admin_comments": "Comments",
|
||||
"admin_comments_title": "Comments",
|
||||
"comment_status_pending": "Pending",
|
||||
"comment_status_approved": "Approved",
|
||||
"comment_status_rejected": "Rejected",
|
||||
"comment_status_all": "All",
|
||||
"comment_approve": "Approve",
|
||||
"comment_reject": "Reject",
|
||||
"comment_delete": "Delete",
|
||||
"comment_delete_confirm": "Delete this comment?",
|
||||
"comment_col_author": "Author",
|
||||
"comment_col_article": "Article",
|
||||
"comment_col_content": "Content",
|
||||
"comment_col_status": "Status",
|
||||
"comment_col_time": "Time",
|
||||
"comment_col_actions": "Actions",
|
||||
"comment_empty": "No comments.",
|
||||
"comment_approved": "Comment approved.",
|
||||
"comment_rejected": "Comment rejected.",
|
||||
"comment_deleted": "Comment deleted.",
|
||||
|
||||
// Admin: comment settings
|
||||
"comment_settings_title": "Comment Settings",
|
||||
"comment_settings_desc": "Control whether comments are enabled and how they are moderated.",
|
||||
"comment_settings_enabled": "Enable comments",
|
||||
"comment_settings_allow_guest": "Allow anonymous comments",
|
||||
"comment_settings_guest_approval": "Require approval for guest comments",
|
||||
"comment_settings_use_gravatar": "Use Gravatar avatars",
|
||||
"comment_settings_use_gravatar_hint": "When off, avatars show the author's initial on a colored background.",
|
||||
},
|
||||
ZH: {
|
||||
// 导航
|
||||
@@ -381,6 +452,77 @@ var translations = map[Lang]map[string]string{
|
||||
"cat_archive": "压缩包",
|
||||
"cat_video": "视频",
|
||||
"cat_other": "其他",
|
||||
|
||||
// 评论
|
||||
"comments_title": "评论",
|
||||
"comments_count": "%d 条评论",
|
||||
"comments_empty": "暂无评论,快来抢沙发吧。",
|
||||
"comments_reply": "回复",
|
||||
"comments_cancel_reply": "取消回复",
|
||||
"comments_replying_to": "回复 %s",
|
||||
"comments_name": "名称",
|
||||
"comments_email": "邮箱",
|
||||
"comments_email_hint": "用于显示 Gravatar 头像,不会公开。",
|
||||
"comments_website": "网址(可选)",
|
||||
"comments_content": "评论内容",
|
||||
"comments_content_ph": "支持 Markdown 语法...",
|
||||
"comments_private": "私密评论",
|
||||
"comments_private_hint": "仅你和管理员可见。",
|
||||
"comments_preview": "预览",
|
||||
"comments_edit": "编辑",
|
||||
"comments_emoji": "表情",
|
||||
"comments_markdown_help": "Markdown 帮助",
|
||||
"comments_submit": "发表评论",
|
||||
"comments_posted": "评论已发表。",
|
||||
"comments_pending_notice": "你的评论正在等待审核。",
|
||||
"comments_disabled": "评论功能已关闭。",
|
||||
"comments_guests_disabled": "请先登录后再评论。",
|
||||
"comments_required_name": "请填写名称。",
|
||||
"comments_required_email": "请填写邮箱。",
|
||||
"comments_required_content":"请填写评论内容。",
|
||||
"comments_invalid_email": "请输入有效的邮箱地址。",
|
||||
"comments_invalid_url": "请输入有效的网址。",
|
||||
"comments_too_long": "评论内容过长(最多 %d 字)。",
|
||||
"comments_pending": "待审核",
|
||||
"comments_approved": "已通过",
|
||||
"comments_rejected": "已拒绝",
|
||||
"comments_private_badge": "私密",
|
||||
"comments_markdown_bold": "**粗体**",
|
||||
"comments_markdown_italic": "*斜体*",
|
||||
"comments_markdown_code": "`代码`",
|
||||
"comments_markdown_link": "[文本](网址)",
|
||||
"comments_markdown_quote": "> 引用",
|
||||
|
||||
// 后台:评论
|
||||
"admin_comments": "评论管理",
|
||||
"admin_comments_title": "评论管理",
|
||||
"comment_status_pending": "待审核",
|
||||
"comment_status_approved": "已通过",
|
||||
"comment_status_rejected": "已拒绝",
|
||||
"comment_status_all": "全部",
|
||||
"comment_approve": "通过",
|
||||
"comment_reject": "拒绝",
|
||||
"comment_delete": "删除",
|
||||
"comment_delete_confirm": "删除该评论?",
|
||||
"comment_col_author": "评论者",
|
||||
"comment_col_article": "文章",
|
||||
"comment_col_content": "内容",
|
||||
"comment_col_status": "状态",
|
||||
"comment_col_time": "时间",
|
||||
"comment_col_actions": "操作",
|
||||
"comment_empty": "暂无评论。",
|
||||
"comment_approved": "评论已通过。",
|
||||
"comment_rejected": "评论已拒绝。",
|
||||
"comment_deleted": "评论已删除。",
|
||||
|
||||
// 后台:评论设置
|
||||
"comment_settings_title": "评论设置",
|
||||
"comment_settings_desc": "控制是否启用评论以及评论的审核方式。",
|
||||
"comment_settings_enabled": "启用评论",
|
||||
"comment_settings_allow_guest": "允许匿名评论",
|
||||
"comment_settings_guest_approval": "非登录用户评论需审核",
|
||||
"comment_settings_use_gravatar": "使用 Gravatar 头像",
|
||||
"comment_settings_use_gravatar_hint": "关闭后,头像将显示作者名称首字母的彩色占位。",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user