feat: 全站评论开关,并统一后台表单控件
关闭评论后前台隐藏评论能力;外观/设置/公告改用 AdminField 与 Stepper。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
type UserProfile,
|
||||
type PostListItem,
|
||||
type Board,
|
||||
type CommentsResponse,
|
||||
} from "@/lib/api";
|
||||
import { authCookieHeader } from "@/lib/cookies";
|
||||
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
|
||||
@@ -152,13 +153,19 @@ function BoardCard({ board }: { board: Board }) {
|
||||
);
|
||||
}
|
||||
|
||||
function AuthorRailCard({ profile }: { profile: UserProfile }) {
|
||||
function AuthorRailCard({
|
||||
profile,
|
||||
allowComments,
|
||||
}: {
|
||||
profile: UserProfile;
|
||||
allowComments: boolean;
|
||||
}) {
|
||||
const u = profile.user;
|
||||
const signature = (u.signature || "").trim();
|
||||
const { post_count, comment_count, points, streak } = profile.stats;
|
||||
const stats = [
|
||||
{ label: "发帖", value: post_count },
|
||||
{ label: "评论", value: comment_count },
|
||||
...(allowComments ? [{ label: "评论", value: comment_count }] : []),
|
||||
{ label: "积分", value: points },
|
||||
];
|
||||
return (
|
||||
@@ -197,7 +204,7 @@ function AuthorRailCard({ profile }: { profile: UserProfile }) {
|
||||
</p>
|
||||
) : null}
|
||||
<div
|
||||
className="mt-3 grid grid-cols-3 border-t"
|
||||
className={`mt-3 grid border-t ${stats.length === 3 ? "grid-cols-3" : "grid-cols-2"}`}
|
||||
style={{ borderColor: "var(--line)" }}
|
||||
>
|
||||
{stats.map((s, i) => (
|
||||
@@ -271,11 +278,13 @@ function RelatedPosts({
|
||||
posts,
|
||||
boardId,
|
||||
uid = "related",
|
||||
allowComments,
|
||||
}: {
|
||||
posts: PostListItem[];
|
||||
boardId: number;
|
||||
// 右栏与移动端内联实例同时存在,id 必须区分以免 aria-labelledby 指向隐藏副本
|
||||
uid?: string;
|
||||
allowComments: boolean;
|
||||
}) {
|
||||
if (posts.length === 0) return null;
|
||||
const titleId = `${uid}-title`;
|
||||
@@ -294,9 +303,11 @@ function RelatedPosts({
|
||||
{p.title}
|
||||
</span>
|
||||
<span className="mt-1 flex items-center gap-2.5 meta text-[11.5px]">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<MessageCircle size={11.5} /> {p.comment_count}
|
||||
</span>
|
||||
{allowComments ? (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<MessageCircle size={11.5} /> {p.comment_count}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Heart size={11.5} /> {p.like_count}
|
||||
</span>
|
||||
@@ -318,7 +329,13 @@ function RelatedPosts({
|
||||
}
|
||||
|
||||
/* 移动端 / 窄屏作者卡(xl 以下内联在正文与评论之间,替代左栏作者模块) */
|
||||
function AuthorCardStacked({ profile }: { profile: UserProfile }) {
|
||||
function AuthorCardStacked({
|
||||
profile,
|
||||
allowComments,
|
||||
}: {
|
||||
profile: UserProfile;
|
||||
allowComments: boolean;
|
||||
}) {
|
||||
const u = profile.user;
|
||||
const signature = (u.signature || "").trim();
|
||||
const { post_count, comment_count, points, streak } = profile.stats;
|
||||
@@ -349,8 +366,12 @@ function AuthorCardStacked({ profile }: { profile: UserProfile }) {
|
||||
<span>@{u.username}</span>
|
||||
<span aria-hidden>·</span>
|
||||
<span>发帖 {post_count}</span>
|
||||
<span aria-hidden>·</span>
|
||||
<span>评论 {comment_count}</span>
|
||||
{allowComments ? (
|
||||
<>
|
||||
<span aria-hidden>·</span>
|
||||
<span>评论 {comment_count}</span>
|
||||
</>
|
||||
) : null}
|
||||
<span aria-hidden>·</span>
|
||||
<span>积分 {points}</span>
|
||||
{streak > 0 && (
|
||||
@@ -441,11 +462,18 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
);
|
||||
}
|
||||
|
||||
// 评论 / 当前用户 / 站点设置 / 作者资料 / 同板块热议并行拉取;侧栏数据失败一律静默降级,不阻塞正文
|
||||
const [commentsData, me, siteSettings, profile, relatedData] = await Promise.all([
|
||||
fetchComments(id, commentPage, cookie),
|
||||
// 站点设置已在 layout 缓存;关评时不拉评论树,避免首屏 HTML 泄露历史评论
|
||||
const allowComments = (await getPublicSettingsCached()).allow_comments !== false;
|
||||
const emptyComments: CommentsResponse = {
|
||||
comments: [],
|
||||
total: 0,
|
||||
total_comments: 0,
|
||||
page: 1,
|
||||
size: 20,
|
||||
};
|
||||
const [commentsData, me, profile, relatedData] = await Promise.all([
|
||||
allowComments ? fetchComments(id, commentPage, cookie) : Promise.resolve(emptyComments),
|
||||
getMeCached(cookie || undefined),
|
||||
getPublicSettingsCached(),
|
||||
fetchUserProfile(String(post.user.id), 1, cookie).catch(() => null),
|
||||
fetchPosts(1, 8, post.board_id, "recommended", "", false, cookie).catch(() => null),
|
||||
]);
|
||||
@@ -504,13 +532,14 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
datePublished: post.created_at,
|
||||
dateModified: post.updated_at,
|
||||
author: { "@type": "Person", name: post.user.nickname },
|
||||
commentCount: post.comment_count,
|
||||
...(allowComments ? { commentCount: post.comment_count } : {}),
|
||||
interactionStatistic: {
|
||||
"@type": "InteractionCounter",
|
||||
interactionType: { "@type": "ReadAction" },
|
||||
userInteractionCount: post.view_count,
|
||||
},
|
||||
...(acceptedAnswer &&
|
||||
...(allowComments &&
|
||||
acceptedAnswer &&
|
||||
!acceptedAnswer.deleted &&
|
||||
!post.content_locked && {
|
||||
acceptedAnswer: {
|
||||
@@ -546,7 +575,12 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
<>
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||
{/* 移动端浮动操作条:长文阅读中途可点赞 / 跳评论 / 回顶部 */}
|
||||
<MobilePostBar postId={id} liked={post.liked} likeCount={post.like_count} />
|
||||
<MobilePostBar
|
||||
postId={id}
|
||||
liked={post.liked}
|
||||
likeCount={post.like_count}
|
||||
allowComments={allowComments}
|
||||
/>
|
||||
|
||||
{/* 三栏阅读布局:宽度与首页工作台一致——lg = 左224 + 正文 + 右280,xl = 左240 + 正文 + 右300;以下单栏 */}
|
||||
<div className="grid grid-cols-1 items-start lg:grid-cols-[224px_minmax(0,1fr)_280px] xl:grid-cols-[240px_minmax(0,1fr)_300px] gap-x-6 xl:gap-x-7">
|
||||
@@ -558,7 +592,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
aria-label="帖子导航"
|
||||
>
|
||||
<BoardCard board={post.board} />
|
||||
{profile && <AuthorRailCard profile={profile} />}
|
||||
{profile && <AuthorRailCard profile={profile} allowComments={allowComments} />}
|
||||
{profile && <AuthorPosts posts={authorPosts} />}
|
||||
</aside>
|
||||
|
||||
@@ -656,9 +690,11 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
<span className="inline-flex items-center gap-1.5" aria-label={`浏览 ${post.view_count} 次`}>
|
||||
<IconEye size={14} /> {post.view_count} 浏览
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1.5" aria-label={`评论 ${post.comment_count} 条`}>
|
||||
<IconComment size={14} /> {post.comment_count} 评论
|
||||
</span>
|
||||
{allowComments ? (
|
||||
<span className="inline-flex items-center gap-1.5" aria-label={`评论 ${post.comment_count} 条`}>
|
||||
<IconComment size={14} /> {post.comment_count} 评论
|
||||
</span>
|
||||
) : null}
|
||||
<span className="inline-flex items-center gap-1.5" aria-label={`阅读约需 ${readingMinutes} 分钟`}>
|
||||
<Clock size={14} /> 约 {readingMinutes} 分钟读完
|
||||
</span>
|
||||
@@ -707,7 +743,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
lottery={post.lottery}
|
||||
loggedIn={!!me.user}
|
||||
/>
|
||||
{!post.content_locked && acceptedAnswer ? (
|
||||
{!post.content_locked && allowComments && acceptedAnswer ? (
|
||||
<AcceptedAnswerCard
|
||||
answer={acceptedAnswer}
|
||||
variant={acceptedAnswerVariant}
|
||||
@@ -738,11 +774,11 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
{/* 窄屏作者卡(lg 起由左栏承载,避免重复展示) */}
|
||||
{profile && (
|
||||
<div className="lg:hidden mt-6">
|
||||
<AuthorCardStacked profile={profile} />
|
||||
<AuthorCardStacked profile={profile} allowComments={allowComments} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 非公开帖不开放评论(后端同样拒绝),给出状态提示 */}
|
||||
{/* 关评:前台仅提示不支持评论,不展示历史楼层;非公开帖不提及评论 */}
|
||||
{isPublished ? (
|
||||
<CommentSection
|
||||
postId={id}
|
||||
@@ -754,7 +790,6 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
page={commentsData.page}
|
||||
size={commentsData.size}
|
||||
user={me.user}
|
||||
allowComments={siteSettings.allow_comments}
|
||||
acceptMode={acceptMode}
|
||||
acceptedCommentId={
|
||||
post.question?.accepted_comment_id ||
|
||||
@@ -765,11 +800,11 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
post.lottery?.drawn
|
||||
? (post.lottery.winners || [])
|
||||
.map((w) => w.comment_id || 0)
|
||||
.filter((id) => id > 0)
|
||||
.filter((cid) => cid > 0)
|
||||
: []
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
) : allowComments ? (
|
||||
<section className="panel p-6 sm:p-8 mt-6 text-center" aria-label="评论区">
|
||||
<p className="text-[14px]" style={{ color: "var(--ink-2)" }}>
|
||||
{post.status === "pending" ? "帖子审核通过后开放评论" : "帖子未通过审核,评论已关闭"}
|
||||
@@ -782,11 +817,16 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
: "审核通过后评论区将自动开放"}
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{/* 窄屏相关讨论(lg 起由右栏承载) */}
|
||||
<div className="lg:hidden mt-6">
|
||||
<RelatedPosts posts={related} boardId={post.board_id} uid="related-inline" />
|
||||
<RelatedPosts
|
||||
posts={related}
|
||||
boardId={post.board_id}
|
||||
uid="related-inline"
|
||||
allowComments={allowComments}
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
@@ -797,7 +837,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
aria-label="阅读辅助"
|
||||
>
|
||||
{toc.length > 0 && <PostToc items={toc} />}
|
||||
<RelatedPosts posts={related} boardId={post.board_id} />
|
||||
<RelatedPosts posts={related} boardId={post.board_id} allowComments={allowComments} />
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user