/* ===================================================================== * 评论模块(comment.js) * 评论列表(按 parent_id 分组展示回复)+ 发表评论 / 回复表单。 * 仅好友或博主可评论。 * ===================================================================== */ import { state } from "./state.js"; import { $, escapeHtml, formatDate, showToast } from "./utils.js"; import { api } from "./api.js"; import { openLoginModal } from "./auth.js"; import { applyFriend } from "./friend.js"; export async function renderComments(el, articleId) { let comments = []; try { comments = await api(`/api/comment/list?article_id=${articleId}`); } catch (err) { /* 好友文章对游客不可见评论,忽略 */ } el.innerHTML = `
共 ${comments.length} 条评论
`; const list = document.createElement("div"); list.className = "comment-list"; if (!comments.length) list.innerHTML = '暂无评论,来抢沙发吧
'; // 按 parent_id 分组:顶层评论在前,回复跟随其父评论 const byId = new Map(comments.map((c) => [c.id, c])); const topLevel = comments.filter((c) => c.parent_id == null); const repliesByParent = {}; comments.filter((c) => c.parent_id != null).forEach((r) => { (repliesByParent[r.parent_id] = repliesByParent[r.parent_id] || []).push(r); }); topLevel.forEach((c) => { list.appendChild(buildCommentItem(el, articleId, c, byId, false)); (repliesByParent[c.id] || []).forEach((r) => { list.appendChild(buildCommentItem(el, articleId, r, byId, true)); }); }); el.appendChild(list); // 评论 / 回复表单(仅好友或博主) const canComment = state.user && (state.user.role === "friend" || state.user.role === "blogger"); if (canComment) { el.appendChild(buildCommentForm(el, articleId, null)); } else { const hint = document.createElement("p"); hint.className = "muted comment-hint"; if (state.user) { hint.innerHTML = `仅好友或博主可以评论 `; $("#hint-apply", hint).addEventListener("click", applyFriend); } else { hint.innerHTML = `登录并成为好友后可以评论 `; $("#hint-login", hint).addEventListener("click", openLoginModal); } el.appendChild(hint); } } function buildCommentItem(el, articleId, comment, byId, isReply) { const item = document.createElement("div"); item.className = isReply ? "comment-item comment-reply" : "comment-item"; const parent = comment.parent_id && byId.has(comment.parent_id) ? byId.get(comment.parent_id) : null; const parentLabel = parent ? `回复 @${escapeHtml(parent.username || "匿名")}` : ""; // 评论者头像:有头像显示图片,没有则显示空占位 const avatarHtml = comment.avatar ? `${escapeHtml(comment.content)}
`; // 顶层评论显示“回复”按钮(仅好友/博主) const canReply = state.user && (state.user.role === "friend" || state.user.role === "blogger"); if (canReply && !isReply) { const replyBtn = document.createElement("button"); replyBtn.className = "btn btn-text btn-sm comment-reply-btn"; replyBtn.textContent = "回复"; replyBtn.addEventListener("click", () => { let box = item.querySelector(".comment-reply-box"); if (box) { box.remove(); return; } box = document.createElement("div"); box.className = "comment-reply-box"; box.appendChild(buildCommentForm(el, articleId, comment)); item.appendChild(box); }); item.appendChild(replyBtn); } return item; } function buildCommentForm(el, articleId, parent) { const form = document.createElement("div"); form.className = "comment-form"; const placeholder = parent ? `回复 @${parent.username || "匿名"}…` : "写下你的评论…"; const btnText = parent ? "回复" : "发表评论"; form.innerHTML = ` `; const textarea = $(".comment-input", form); const btn = $("button", form); btn.addEventListener("click", async () => { const content = textarea.value.trim(); if (!content) { showToast("评论内容不能为空", "error"); return; } if (content.length > 2000) { showToast("评论内容不能超过 2000 字", "error"); return; } btn.disabled = true; btn.textContent = "发送中…"; try { const body = parent ? { article_id: articleId, content, parent_id: parent.id } : { article_id: articleId, content }; await api("/api/comment/add", { method: "POST", body }); showToast("评论成功", "success"); await renderComments(el, articleId); } catch (err) { showToast(err.message, "error"); } }); return form; }