import { memo } from 'react'; import { useNavigate } from 'react-router-dom'; import { MessageCircle } from 'lucide-react'; import BoardBadge from '@/components/BoardBadge'; import FeaturedIcon from '@/components/FeaturedIcon'; import UserLink from '@/components/UserLink'; import type { PostItem } from '../api/types'; import type { FeedSort } from './FeedSortBar'; import { useForumLimits } from '../hooks/useForumLimits'; import { formatTime } from '../utils/content'; import { boardPath, postPath } from '../utils/permalink'; import { toPostImageThumbSrc } from '../utils/postContent'; import { excerptFromHTML, firstImageFromHTML } from '../utils/seoText'; interface Props { post: PostItem; sort?: FeedSort; /** 当前板块 id,>0 时隐藏行内板块色标(避免板块页重复) */ boardId?: number; onSelect: (id: number) => void; } function PostListItem({ post, sort = 'latest', boardId = 0, onSelect }: Props) { const nav = useNavigate(); const { limits } = useForumLimits(); const feedStyle = limits.feed_list_style ?? 'title'; const showExcerpt = feedStyle === 'excerpt' || feedStyle === 'thumbnail'; const showThumb = feedStyle === 'thumbnail'; const titleOnly = feedStyle === 'title'; const initial = post.user?.nickname?.[0] || '?'; const timeLabel = sort === 'reply' && !post.last_reply_at ? '暂无回复' : formatTime(post.created_at); const lastReplyName = post.last_reply_user?.nickname?.trim() || post.last_reply_user?.username?.trim() || post.last_reply_guest_nick?.trim() || ''; const showLastReply = !!post.last_reply_at && (!!post.last_reply_user || !!lastReplyName); const commentCount = post.comment_count ?? 0; const href = postPath(post.id); const firstImage = firstImageFromHTML(post.content || ''); const thumbSrc = showThumb && firstImage ? toPostImageThumbSrc(firstImage) : null; const excerpt = showExcerpt ? excerptFromHTML(post.content || '', 60) : ''; const showBoardBadge = !!post.board && boardId !== post.board.id; const openPost = () => onSelect(post.id); const onKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openPost(); } }; const onTitleClick = (e: React.MouseEvent) => { if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) { e.stopPropagation(); return; } e.preventDefault(); e.stopPropagation(); openPost(); }; const titleRow = (
{post.pinned && ( 全局置顶 )} {post.board_pinned && ( 板块置顶 )} {post.featured && ( 精华 )} {post.status === 'pending' && ( 审核中 )} {post.status === 'rejected' && ( 未通过 )} {post.post_type === 'question' && ( {post.question_resolved ? '已解决' : '未解决'} )} {post.post_type === 'poll' && ( 投票 )} {post.post_type === 'bounty' && post.bounty_status === 'open' && (post.bounty_points ?? 0) > 0 && ( 悬赏 {post.bounty_points} )} {post.post_type === 'bounty' && post.bounty_status === 'awarded' && ( 已采纳 )} {post.post_type === 'lottery' && ( {post.lottery_status === 'drawn' ? '已开奖' : '抽奖'} )} {post.title}
); const metaLeft = (
{showBoardBadge && post.board && ( )} · {timeLabel} {showLastReply && ( {post.last_reply_user ? ( ) : ( {lastReplyName} )} {formatTime(post.last_reply_at!)} )}
); const stats = (
{commentCount}
); return (
{post.user?.avatar ? : initial} {thumbSrc ? (
{titleRow} {excerpt &&

{excerpt}

}
{metaLeft}
{stats}
) : (
{titleRow} {excerpt &&

{excerpt}

}
{metaLeft} {stats}
)}
); } export default memo(PostListItem);