- 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>
34 lines
1.9 KiB
HTML
34 lines
1.9 KiB
HTML
{{define "comment_node"}}
|
|
<div id="comment-{{.Comment.ID}}" class="comment-node">
|
|
<div class="flex items-start gap-3 py-3">
|
|
{{if .UseGravatar}}
|
|
<img src="{{.Comment.GravatarURL 48}}" alt="" class="w-10 h-10 rounded-full bg-gray-100 flex-shrink-0">
|
|
{{else}}
|
|
<div class="w-10 h-10 rounded-full flex items-center justify-center text-white font-bold flex-shrink-0" style="background:{{.AvatarColor}}">{{.Comment.AuthorInitial}}</div>
|
|
{{end}}
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex flex-wrap items-center gap-2 text-sm">
|
|
{{if .Comment.Website}}
|
|
<a href="{{.Comment.Website}}" target="_blank" rel="nofollow ugc noopener" class="font-semibold text-gray-800 hover:text-blue-600">{{.Comment.AuthorName}}</a>
|
|
{{else}}
|
|
<span class="font-semibold text-gray-800">{{.Comment.AuthorName}}</span>
|
|
{{end}}
|
|
<span class="text-gray-400 text-xs">{{.RelTime}}</span>
|
|
{{if .Comment.IsPrivate}}<span class="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded">{{index .Tr "comments_private_badge"}}</span>{{end}}
|
|
{{if eq .Comment.Status 0}}<span class="text-xs bg-yellow-100 text-yellow-700 px-2 py-0.5 rounded">{{index .Tr "comments_pending"}}</span>{{end}}
|
|
</div>
|
|
<div class="comment-body mt-1 text-sm text-gray-700 prose prose-sm max-w-none" data-md="{{.Comment.Content}}"></div>
|
|
<button type="button" class="reply-btn mt-2 text-xs text-blue-600 hover:text-blue-800 font-medium bg-transparent border-none cursor-pointer"
|
|
data-id="{{.Comment.ID}}" data-name="{{.Comment.AuthorName}}">{{index .Tr "comments_reply"}}</button>
|
|
</div>
|
|
</div>
|
|
{{if .Children}}
|
|
<div class="ml-6 sm:ml-10 pl-4 border-l-2 border-gray-100">
|
|
{{range .Children}}
|
|
{{template "comment_node" .}}
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|