feat: 全站评论开关,并统一后台表单控件
关闭评论后前台隐藏评论能力;外观/设置/公告改用 AdminField 与 Stepper。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -85,9 +85,11 @@ function BoardIntro({ board }: { board: BoardWithCount }) {
|
||||
function BoardPulse({
|
||||
stats,
|
||||
checkin,
|
||||
allowComments,
|
||||
}: {
|
||||
stats: BoardSidebarStats;
|
||||
checkin?: CheckinStatus | null;
|
||||
allowComments: boolean;
|
||||
}) {
|
||||
const cells = [
|
||||
{
|
||||
@@ -104,20 +106,24 @@ function BoardPulse({
|
||||
foot: "本日新增",
|
||||
up: stats.today_posts > 0,
|
||||
},
|
||||
{
|
||||
icon: <MessageCircle size={12.5} />,
|
||||
label: "回复总数",
|
||||
value: formatCompact(stats.comment_count),
|
||||
foot: `+${formatCompact(stats.today_comments)} 今日`,
|
||||
up: true,
|
||||
},
|
||||
{
|
||||
icon: <Heart size={12.5} />,
|
||||
label: "今日回复",
|
||||
value: formatCompact(stats.today_comments),
|
||||
foot: "本日新增",
|
||||
up: stats.today_comments > 0,
|
||||
},
|
||||
...(allowComments
|
||||
? [
|
||||
{
|
||||
icon: <MessageCircle size={12.5} />,
|
||||
label: "回复总数",
|
||||
value: formatCompact(stats.comment_count),
|
||||
foot: `+${formatCompact(stats.today_comments)} 今日`,
|
||||
up: true,
|
||||
},
|
||||
{
|
||||
icon: <Heart size={12.5} />,
|
||||
label: "今日回复",
|
||||
value: formatCompact(stats.today_comments),
|
||||
foot: "本日新增",
|
||||
up: stats.today_comments > 0,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
return (
|
||||
<section className="j13-module" aria-label="板块数据">
|
||||
@@ -150,7 +156,7 @@ function BoardPulse({
|
||||
|
||||
/* ---------- 4. 本板热门 ---------- */
|
||||
|
||||
function BoardHot({ posts }: { posts: PostListItem[] }) {
|
||||
function BoardHot({ posts, allowComments }: { posts: PostListItem[]; allowComments: boolean }) {
|
||||
return (
|
||||
<section className="j13-module" aria-label="本板热门">
|
||||
<p className="j13-module-title mb-1.5">
|
||||
@@ -169,9 +175,11 @@ function BoardHot({ posts }: { posts: PostListItem[] }) {
|
||||
{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>
|
||||
@@ -188,7 +196,7 @@ function BoardHot({ posts }: { posts: PostListItem[] }) {
|
||||
|
||||
/* ---------- 5. 本板活跃 ---------- */
|
||||
|
||||
function BoardActive({ users }: { users: ActiveUser[] }) {
|
||||
function BoardActive({ users, allowComments }: { users: ActiveUser[]; allowComments: boolean }) {
|
||||
return (
|
||||
<section className="j13-module" aria-label="本板活跃成员">
|
||||
<p className="j13-module-title mb-2">
|
||||
@@ -221,17 +229,23 @@ function BoardActive({ users }: { users: ActiveUser[] }) {
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<PenLine size={10.5} /> 发帖 {u.post_count}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<MessageCircle size={10.5} /> 回复 {u.comment_count}
|
||||
</span>
|
||||
{allowComments ? (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<MessageCircle size={10.5} /> 回复 {u.comment_count}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="shrink-0 text-[13px] tabular-nums"
|
||||
style={{ color: "var(--accent)" }}
|
||||
aria-label={`本周共 ${u.post_count + u.comment_count} 次互动`}
|
||||
aria-label={
|
||||
allowComments
|
||||
? `本周共 ${u.post_count + u.comment_count} 次互动`
|
||||
: `本周发帖 ${u.post_count}`
|
||||
}
|
||||
>
|
||||
{u.post_count + u.comment_count}
|
||||
{allowComments ? u.post_count + u.comment_count : u.post_count}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
@@ -252,6 +266,7 @@ type BoardPanelsProps = {
|
||||
hot: PostListItem[];
|
||||
activeUsers: ActiveUser[];
|
||||
checkin?: CheckinStatus | null;
|
||||
allowComments: boolean;
|
||||
};
|
||||
|
||||
function BoardPanelsBody(props: BoardPanelsProps) {
|
||||
@@ -259,9 +274,9 @@ function BoardPanelsBody(props: BoardPanelsProps) {
|
||||
<>
|
||||
<Moderators items={props.moderators} />
|
||||
<BoardIntro board={props.board} />
|
||||
<BoardPulse stats={props.stats} checkin={props.checkin} />
|
||||
<BoardHot posts={props.hot} />
|
||||
<BoardActive users={props.activeUsers} />
|
||||
<BoardPulse stats={props.stats} checkin={props.checkin} allowComments={props.allowComments} />
|
||||
<BoardHot posts={props.hot} allowComments={props.allowComments} />
|
||||
<BoardActive users={props.activeUsers} allowComments={props.allowComments} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,15 @@ import { formatCompact, formatShortDate } from "@/lib/format";
|
||||
|
||||
/* ---------- 站点数据(2×2 bento 统计瓦片) ---------- */
|
||||
|
||||
function PulseCard({ stats, checkin }: { stats: OverviewStats; checkin?: CheckinStatus | null }) {
|
||||
function PulseCard({
|
||||
stats,
|
||||
checkin,
|
||||
allowComments,
|
||||
}: {
|
||||
stats: OverviewStats;
|
||||
checkin?: CheckinStatus | null;
|
||||
allowComments: boolean;
|
||||
}) {
|
||||
const cells = [
|
||||
{
|
||||
icon: <Users size={12.5} />,
|
||||
@@ -43,13 +51,25 @@ function PulseCard({ stats, checkin }: { stats: OverviewStats; checkin?: Checkin
|
||||
foot: `+${formatCompact(stats.today_posts)} 今日`,
|
||||
up: true,
|
||||
},
|
||||
{
|
||||
icon: <MessageCircle size={12.5} />,
|
||||
label: "回复总数",
|
||||
value: formatCompact(stats.comments),
|
||||
foot: `+${formatCompact(stats.today_comments)} 今日`,
|
||||
up: true,
|
||||
},
|
||||
...(allowComments
|
||||
? [
|
||||
{
|
||||
icon: <MessageCircle size={12.5} />,
|
||||
label: "回复总数",
|
||||
value: formatCompact(stats.comments),
|
||||
foot: `+${formatCompact(stats.today_comments)} 今日`,
|
||||
up: true,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
icon: <TrendingUp size={12.5} />,
|
||||
label: "今日新帖",
|
||||
value: formatCompact(stats.today_posts),
|
||||
foot: "本日新增",
|
||||
up: stats.today_posts > 0,
|
||||
},
|
||||
]),
|
||||
{
|
||||
icon: <Activity size={12.5} />,
|
||||
label: "在线用户",
|
||||
@@ -138,7 +158,7 @@ function Announcements({ items }: { items: AnnouncementSummary[] }) {
|
||||
|
||||
/* ---------- 热门榜 ---------- */
|
||||
|
||||
function HotRanking({ posts }: { posts: PostListItem[] }) {
|
||||
function HotRanking({ posts, allowComments }: { posts: PostListItem[]; allowComments: boolean }) {
|
||||
return (
|
||||
<section className="j13-module" aria-label="热门讨论">
|
||||
<p className="j13-module-title mb-1.5">
|
||||
@@ -157,9 +177,11 @@ function HotRanking({ posts }: { posts: PostListItem[] }) {
|
||||
{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>
|
||||
@@ -211,7 +233,7 @@ function NewUsers({ users }: { users: NewUser[] }) {
|
||||
|
||||
/* ---------- 活跃成员(本周发帖 / 回复) ---------- */
|
||||
|
||||
function ActiveUsers({ users }: { users: ActiveUser[] }) {
|
||||
function ActiveUsers({ users, allowComments }: { users: ActiveUser[]; allowComments: boolean }) {
|
||||
return (
|
||||
<section className="j13-module" aria-label="本周活跃成员">
|
||||
<p className="j13-module-title mb-2">
|
||||
@@ -241,17 +263,23 @@ function ActiveUsers({ users }: { users: ActiveUser[] }) {
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<PenLine size={10.5} /> 发帖 {u.post_count}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<MessageCircle size={10.5} /> 回复 {u.comment_count}
|
||||
</span>
|
||||
{allowComments ? (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<MessageCircle size={10.5} /> 回复 {u.comment_count}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
className="shrink-0 text-[13px] tabular-nums"
|
||||
style={{ color: "var(--accent)" }}
|
||||
aria-label={`本周共 ${u.post_count + u.comment_count} 次互动`}
|
||||
aria-label={
|
||||
allowComments
|
||||
? `本周共 ${u.post_count + u.comment_count} 次互动`
|
||||
: `本周发帖 ${u.post_count}`
|
||||
}
|
||||
>
|
||||
{u.post_count + u.comment_count}
|
||||
{allowComments ? u.post_count + u.comment_count : u.post_count}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
@@ -309,46 +337,44 @@ type PanelsProps = {
|
||||
announcements: AnnouncementSummary[];
|
||||
newUsers: NewUser[];
|
||||
checkin?: CheckinStatus | null;
|
||||
allowComments: boolean;
|
||||
};
|
||||
|
||||
export function CommunityPanels({
|
||||
function CommunityPanelsBody({
|
||||
stats,
|
||||
hot,
|
||||
activeUsers,
|
||||
announcements,
|
||||
newUsers,
|
||||
checkin,
|
||||
allowComments,
|
||||
}: PanelsProps) {
|
||||
return (
|
||||
<>
|
||||
<PulseCard stats={stats} checkin={checkin} allowComments={allowComments} />
|
||||
<Announcements items={announcements} />
|
||||
<HotRanking posts={hot} allowComments={allowComments} />
|
||||
<NewUsers users={newUsers} />
|
||||
<ActiveUsers users={activeUsers} allowComments={allowComments} />
|
||||
<FriendLinks />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommunityPanels(props: PanelsProps) {
|
||||
return (
|
||||
<aside className="hidden w-[280px] shrink-0 lg:block xl:w-[300px]" aria-label="社区面板">
|
||||
<div className="sticky top-24 space-y-4">
|
||||
<PulseCard stats={stats} checkin={checkin} />
|
||||
<Announcements items={announcements} />
|
||||
<HotRanking posts={hot} />
|
||||
<NewUsers users={newUsers} />
|
||||
<ActiveUsers users={activeUsers} />
|
||||
<FriendLinks />
|
||||
<CommunityPanelsBody {...props} />
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommunityPanelsMobile({
|
||||
stats,
|
||||
hot,
|
||||
activeUsers,
|
||||
announcements,
|
||||
newUsers,
|
||||
checkin,
|
||||
}: PanelsProps) {
|
||||
export function CommunityPanelsMobile(props: PanelsProps) {
|
||||
return (
|
||||
<div className="space-y-4 lg:hidden" aria-label="社区面板">
|
||||
<PulseCard stats={stats} checkin={checkin} />
|
||||
<Announcements items={announcements} />
|
||||
<HotRanking posts={hot} />
|
||||
<NewUsers users={newUsers} />
|
||||
<ActiveUsers users={activeUsers} />
|
||||
<FriendLinks />
|
||||
<CommunityPanelsBody {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useRef } from "react";
|
||||
import { MessageSquareText, Clock, Sparkles } from "lucide-react";
|
||||
import { useAllowComments } from "@/components/CommentsPolicyProvider";
|
||||
|
||||
export type FeedSort = "latest" | "new" | "recommended";
|
||||
|
||||
@@ -22,18 +23,20 @@ function tabHref(key: FeedSort, boardId: number) {
|
||||
export default function FeedTabs({ current, boardId = 0 }: { current: FeedSort; boardId?: number }) {
|
||||
const router = useRouter();
|
||||
const tabRefs = useRef<(HTMLAnchorElement | null)[]>([]);
|
||||
const allowComments = useAllowComments();
|
||||
const keys = allowComments ? TAB_KEYS : TAB_KEYS.filter((k) => k !== "latest");
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent, i: number) => {
|
||||
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
|
||||
e.preventDefault();
|
||||
const next = (i + (e.key === "ArrowRight" ? 1 : TAB_KEYS.length - 1)) % TAB_KEYS.length;
|
||||
const next = (i + (e.key === "ArrowRight" ? 1 : keys.length - 1)) % keys.length;
|
||||
tabRefs.current[next]?.focus();
|
||||
router.push(tabHref(TAB_KEYS[next], boardId));
|
||||
router.push(tabHref(keys[next], boardId));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 sm:gap-2" role="tablist" aria-label="帖子排序">
|
||||
{TAB_KEYS.map((key, i) => {
|
||||
{keys.map((key, i) => {
|
||||
const Icon = key === "latest" ? MessageSquareText : key === "new" ? Clock : Sparkles;
|
||||
return (
|
||||
<Link
|
||||
|
||||
Reference in New Issue
Block a user