移除旧版 HTML 模板与兼容层,并完善私信、举报、媒体存储与 SEO。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-03 16:37:11 +08:00
parent 060b7707cb
commit 48db333272
121 changed files with 11147 additions and 3225 deletions

View File

@@ -15,6 +15,8 @@ import { Spinner } from '@/components/ui/spinner';
import { getCachedBoards } from '../utils/layoutCache';
import type { LayoutCtx } from '../layouts/MainLayout';
import { loginPath } from '../utils/authRedirect';
import { useNoIndexSEO } from '../hooks/usePageSEO';
import { parsePermalinkID, postPath } from '../utils/permalink';
import {
loadComposeDraft,
saveComposeDraft,
@@ -53,12 +55,13 @@ function formatEditRemaining(createdAt: string, windowHours: number): string {
export default function ComposePage() {
const nav = useNavigate();
const { id: editIdParam } = useParams();
const editId = editIdParam ? Number(editIdParam) : null;
const editId = editIdParam ? parsePermalinkID(editIdParam) : null;
const isEdit = editId !== null && !Number.isNaN(editId);
const [params] = useSearchParams();
const defaultBoard = params.get('board') || '';
const { user, loading: authLoading } = useAuth();
const { limits } = useForumLimits();
useNoIndexSEO(isEdit ? '编辑帖子' : '发帖');
const layoutCtx = useOutletContext<LayoutCtx | undefined>();
const [boards, setBoards] = useState<Board[]>(() => resolveBoards(layoutCtx?.boards));
@@ -100,12 +103,12 @@ export default function ComposePage() {
const isOwnerOrAdmin = user.role === 'admin' || post.user_id === user.id;
if (!isOwnerOrAdmin) {
notify.error('无权编辑此帖子');
nav(`/post/${editId}`);
nav(postPath(editId!, limits));
return;
}
if (!postData.can_edit) {
notify.error(postData.edit_block_reason || '当前无法编辑此帖子');
nav(`/post/${editId}`);
nav(postPath(editId!, limits));
return;
}
const loadedBoardId = String(post.board_id);
@@ -232,9 +235,9 @@ export default function ComposePage() {
title !== baseline.title
|| serializeTags(parseTags(tags)) !== serializeTags(parseTags(baseline.tags))
|| content !== baseline.content
|| (!isEdit && boardId !== baseline.boardId)
|| boardId !== baseline.boardId
);
}, [baseline, title, tags, content, boardId, isEdit]);
}, [baseline, title, tags, content, boardId]);
const {
dialogOpen,
@@ -287,7 +290,7 @@ export default function ComposePage() {
const handleSubmit = async () => {
const trimmedTitle = title.trim();
if (!isEdit && !boardId) { notify.warning('请选择板块'); return; }
if (!boardId) { notify.warning('请选择板块'); return; }
if (!trimmedTitle) { notify.warning('请输入标题'); return; }
if (isHtmlEmpty(content)) { notify.warning('请输入正文内容'); return; }
@@ -297,19 +300,20 @@ export default function ComposePage() {
title: trimmedTitle,
content: content.trim(),
tags: serializeTags(parseTags(tags)),
board_id: boardId,
};
if (isEdit) {
await api.updatePost(editId!, payload);
notify.success('帖子已更新');
notify.success(user?.role === 'admin' ? '帖子已更新' : '已更新并重新提交审核');
clearComposeDraft(editId);
markSaved();
nav(`/post/${editId}`);
nav(postPath(editId!, limits));
} else {
const res = await api.createPost({ board_id: boardId, ...payload });
notify.success('发帖成功');
const res = await api.createPost(payload);
notify.success(res.message || (res.status === 'pending' ? '已提交审核' : '发帖成功'));
clearComposeDraft(null);
markSaved();
nav(`/post/${res.post_id}`);
nav(postPath(res.post_id, limits));
}
} catch (e: unknown) {
notify.error(e instanceof Error ? e.message : isEdit ? '保存失败' : '发帖失败');
@@ -318,8 +322,6 @@ export default function ComposePage() {
}
};
const currentBoard = boards.find(b => String(b.id) === boardId);
return (
<div className="compose-page">
<div className="compose-canvas">
@@ -330,7 +332,7 @@ export default function ComposePage() {
type="button"
className="compose-back"
onClick={() => requestLeave(() => {
if (isEdit) nav(`/post/${editId}`);
if (isEdit) nav(postPath(editId!, limits));
else nav(-1);
})}
>
@@ -360,26 +362,20 @@ export default function ComposePage() {
<section className="compose-context" aria-label="发布设置">
<div className="compose-context-row">
<span className="compose-context-label"></span>
{!isEdit ? (
<div className="compose-board-pills" role="listbox" aria-label="选择板块">
{boards.map(b => (
<button
key={b.id}
type="button"
role="option"
aria-selected={String(b.id) === boardId}
className={`compose-board-pill${String(b.id) === boardId ? ' active' : ''}`}
onClick={() => setBoardId(String(b.id))}
>
{b.name}
</button>
))}
</div>
) : currentBoard ? (
<div className="compose-board-pills">
<span className="compose-board-pill active">{currentBoard.name}</span>
</div>
) : null}
<div className="compose-board-pills" role="listbox" aria-label={isEdit ? '修改板块' : '选择板块'}>
{boards.map(b => (
<button
key={b.id}
type="button"
role="option"
aria-selected={String(b.id) === boardId}
className={`compose-board-pill${String(b.id) === boardId ? ' active' : ''}`}
onClick={() => setBoardId(String(b.id))}
>
{b.name}
</button>
))}
</div>
</div>
<div className="compose-context-row compose-context-row--tags">
<span className="compose-context-label"></span>