移除旧版 HTML 模板与兼容层,并完善私信、举报、媒体存储与 SEO。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
import { memo } from 'react';
|
||||
import { MessageCircle, ThumbsUp } from 'lucide-react';
|
||||
import { Eye, Image as ImageIcon, MessageCircle, ThumbsUp } from 'lucide-react';
|
||||
import BoardBadge from '@/components/BoardBadge';
|
||||
import FeaturedIcon from '@/components/FeaturedIcon';
|
||||
import PinnedIcon from '@/components/PinnedIcon';
|
||||
import UserLink from '@/components/UserLink';
|
||||
import type { PostItem } from '../api/types';
|
||||
import type { FeedSort } from './FeedSortBar';
|
||||
import { formatTime } from '../utils/content';
|
||||
import { postPath } from '../utils/permalink';
|
||||
import { excerptFromHTML, firstImageFromHTML } from '../utils/seoText';
|
||||
|
||||
interface Props {
|
||||
post: PostItem;
|
||||
@@ -22,6 +25,10 @@ function PostListItem({ post, sort = 'latest', onSelect }: Props) {
|
||||
: formatTime(post.created_at);
|
||||
const commentCount = post.comment_count ?? 0;
|
||||
const likeCount = post.like_count ?? 0;
|
||||
const viewCount = post.view_count ?? 0;
|
||||
const href = postPath(post.id);
|
||||
const excerpt = excerptFromHTML(post.content || '', 72);
|
||||
const hasImage = !!firstImageFromHTML(post.content || '');
|
||||
|
||||
const openPost = () => onSelect(post.id);
|
||||
const onKeyDown = (e: React.KeyboardEvent) => {
|
||||
@@ -30,11 +37,21 @@ function PostListItem({ post, sort = 'latest', onSelect }: Props) {
|
||||
openPost();
|
||||
}
|
||||
};
|
||||
const onTitleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
// 修饰键 / 非左键:交给浏览器(新标签等)
|
||||
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
openPost();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="post-row"
|
||||
role="button"
|
||||
role="link"
|
||||
tabIndex={0}
|
||||
onClick={openPost}
|
||||
onKeyDown={onKeyDown}
|
||||
@@ -50,26 +67,68 @@ function PostListItem({ post, sort = 'latest', onSelect }: Props) {
|
||||
? <img src={post.user.avatar} alt="" loading="lazy" decoding="async" />
|
||||
: initial}
|
||||
</UserLink>
|
||||
|
||||
<div className="post-body">
|
||||
<div className="post-title">
|
||||
{post.pinned && <PinnedIcon className="mr-1.5" />}
|
||||
<div className="post-head">
|
||||
<div className="post-head-meta">
|
||||
<UserLink user={post.user} stopPropagation className="post-author" />
|
||||
<span className="post-head-dot" aria-hidden>·</span>
|
||||
<span className="post-time">{timeLabel}</span>
|
||||
</div>
|
||||
{(post.featured || post.pinned || post.status === 'pending' || post.status === 'rejected') && (
|
||||
<div className="post-head-badges">
|
||||
{post.status === 'pending' && (
|
||||
<span className="post-status-badge post-status-badge--pending" title="审核中">审核中</span>
|
||||
)}
|
||||
{post.status === 'rejected' && (
|
||||
<span className="post-status-badge post-status-badge--rejected" title="未通过">未通过</span>
|
||||
)}
|
||||
{post.featured && (
|
||||
<span className="post-feature-badge" title="精华">
|
||||
<FeaturedIcon size={12} />
|
||||
精华
|
||||
</span>
|
||||
)}
|
||||
{post.pinned && (
|
||||
<span className="post-pin-badge" title="置顶">
|
||||
<PinnedIcon size={12} />
|
||||
置顶
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<a href={href} className="post-title" onClick={onTitleClick}>
|
||||
{post.title}
|
||||
</a>
|
||||
|
||||
{excerpt && <p className="post-excerpt">{excerpt}</p>}
|
||||
|
||||
<div className="post-foot">
|
||||
<div className="post-foot-left">
|
||||
{post.board && <BoardBadge board={post.board} />}
|
||||
</div>
|
||||
<div className="post-stats">
|
||||
{hasImage && (
|
||||
<span className="post-stat post-stat--media" title="含图片">
|
||||
<ImageIcon aria-hidden />
|
||||
</span>
|
||||
)}
|
||||
<span className={`post-stat${commentCount === 0 ? ' post-stat--zero' : ''}`} title="评论">
|
||||
<MessageCircle aria-hidden />
|
||||
{commentCount}
|
||||
</span>
|
||||
<span className={`post-stat${likeCount === 0 ? ' post-stat--zero' : ''}`} title="点赞">
|
||||
<ThumbsUp aria-hidden />
|
||||
{likeCount}
|
||||
</span>
|
||||
<span className={`post-stat${viewCount === 0 ? ' post-stat--zero' : ''}`} title="浏览">
|
||||
<Eye aria-hidden />
|
||||
{viewCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="post-meta">
|
||||
{post.board && <BoardBadge board={post.board} />}
|
||||
<UserLink user={post.user} stopPropagation className="post-meta-user" />
|
||||
<span>{timeLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="post-stats">
|
||||
<span className={`post-stat${commentCount === 0 ? ' post-stat--zero' : ''}`}>
|
||||
<MessageCircle aria-hidden />
|
||||
{commentCount}
|
||||
</span>
|
||||
<span className={`post-stat${likeCount === 0 ? ' post-stat--zero' : ''}`}>
|
||||
<ThumbsUp aria-hidden />
|
||||
{likeCount}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user