feat: 可配置站点 URL 风格
- 新增 urlStyle 工具与测试:postPath/userPath/sitePagePath 统一生成链接 - 管理端新增 UrlStylePanel 设置面板,settings 服务与 handler 适配 - 全站路由链接与 JSON-LD/sitemap 切换为统一 URL 生成 - middleware 与相关组件适配
This commit is contained in:
@@ -41,6 +41,8 @@ import DeleteCommentModal, { type DeleteCommentPayload } from "@/components/Dele
|
||||
import EditHistoryModal from "@/components/EditHistoryModal";
|
||||
import { scrollFlashTo } from "@/lib/flash";
|
||||
import { fetchCommentLocation } from "@/lib/api";
|
||||
import { postPath, userPath } from "@/lib/urlStyle";
|
||||
import { useUrlStyle } from "@/components/SiteBrandProvider";
|
||||
|
||||
interface CommentSectionProps {
|
||||
postId: string;
|
||||
@@ -215,6 +217,7 @@ export default function CommentSection({
|
||||
}: CommentSectionProps) {
|
||||
const winnerCommentIdSet = new Set(winnerCommentIds.filter((id) => id > 0));
|
||||
const router = useRouter();
|
||||
const urlStyle = useUrlStyle();
|
||||
const commentsOpen = useAllowComments();
|
||||
const commentsRequireLogin = useCommentsRequireLogin();
|
||||
const staffViewer = !!user && isStaff(user.role);
|
||||
@@ -329,7 +332,7 @@ export default function CommentSection({
|
||||
if (cancelled || floor <= 0) return;
|
||||
const destPage = Math.max(1, Math.ceil(floor / size));
|
||||
if (destPage === page) return; // 同页却无节点(异常数据),放弃
|
||||
router.replace(`/post/${postId}?page=${destPage}#comment-${id}`, { scroll: false });
|
||||
router.replace(`${postPath(urlStyle, Number(postId))}?page=${destPage}#comment-${id}`, { scroll: false });
|
||||
} catch {
|
||||
/* 定位失败静默:停留在当前页 */
|
||||
}
|
||||
@@ -359,15 +362,15 @@ export default function CommentSection({
|
||||
if (destPage !== page) {
|
||||
setAwayHref(
|
||||
destPage > 1
|
||||
? `/post/${postId}?page=${destPage}#comment-${res.comment.id}`
|
||||
: `/post/${postId}#comment-${res.comment.id}`
|
||||
? `${postPath(urlStyle, Number(postId))}?page=${destPage}#comment-${res.comment.id}`
|
||||
: `${postPath(urlStyle, Number(postId))}#comment-${res.comment.id}`
|
||||
);
|
||||
} else {
|
||||
setAwayHref(null);
|
||||
}
|
||||
router.refresh();
|
||||
} else if (res.error === "未登录") {
|
||||
router.push(`/login?redirect=${encodeURIComponent(`/post/${postId}`)}`);
|
||||
router.push(`/login?redirect=${encodeURIComponent(postPath(urlStyle, Number(postId)))}`);
|
||||
} else {
|
||||
const msg = res.error || "评论失败";
|
||||
setFloorError(msg);
|
||||
@@ -415,7 +418,7 @@ export default function CommentSection({
|
||||
router.refresh();
|
||||
focusCommentControl(target.id, "reply");
|
||||
} else if (res.error === "未登录") {
|
||||
router.push(`/login?redirect=${encodeURIComponent(`/post/${postId}`)}`);
|
||||
router.push(`/login?redirect=${encodeURIComponent(postPath(urlStyle, Number(postId)))}`);
|
||||
} else {
|
||||
const msg = res.error || "回复失败";
|
||||
setReplyError(msg);
|
||||
@@ -929,7 +932,7 @@ export default function CommentSection({
|
||||
<MosaicAvatar size={isFloor ? 34 : 32} />
|
||||
) : author ? (
|
||||
<Link
|
||||
href={`/u/${author.id}`}
|
||||
href={userPath(urlStyle, author.id)}
|
||||
className={isFloor ? "shrink-0 mt-0.5" : "shrink-0"}
|
||||
aria-label={displayName}
|
||||
>
|
||||
@@ -942,7 +945,7 @@ export default function CommentSection({
|
||||
const nameRow = showMosaic ? (
|
||||
<MosaicName />
|
||||
) : author ? (
|
||||
<Link href={`/u/${author.id}`} className="group">
|
||||
<Link href={userPath(urlStyle, author.id)} className="group">
|
||||
<span
|
||||
className={`truncate group-hover:text-[var(--accent)] transition-colors ${
|
||||
isFloor ? "text-sm" : "cmt-reply-name"
|
||||
@@ -980,7 +983,7 @@ export default function CommentSection({
|
||||
{c.staff_deleted && deleter ? (
|
||||
<div className="cmt-tombstone-mod">
|
||||
<span>处理人</span>
|
||||
<Link href={`/u/${deleter.id}`} aria-label={`查看管理员 ${deleterName}`}>
|
||||
<Link href={userPath(urlStyle, deleter.id)} aria-label={`查看管理员 ${deleterName}`}>
|
||||
<Avatar name={deleterName} src={deleter.avatar || undefined} size={20} />
|
||||
{deleterName}
|
||||
</Link>
|
||||
@@ -1088,7 +1091,7 @@ export default function CommentSection({
|
||||
<div key={c.id} id={`comment-${c.id}`} className="cmt-reply-row scroll-mt-24">
|
||||
<div className="flex gap-3">
|
||||
{author ? (
|
||||
<Link href={`/u/${author.id}`} className="shrink-0" aria-label={authorName}>
|
||||
<Link href={userPath(urlStyle, author.id)} className="shrink-0" aria-label={authorName}>
|
||||
<Avatar name={authorName} src={author.avatar || undefined} size={32} />
|
||||
</Link>
|
||||
) : (
|
||||
@@ -1098,7 +1101,7 @@ export default function CommentSection({
|
||||
<div className="cmt-meta">
|
||||
<div className="cmt-meta-lead">
|
||||
{author ? (
|
||||
<Link href={`/u/${author.id}`} className="cmt-name-link group">
|
||||
<Link href={userPath(urlStyle, author.id)} className="cmt-name-link group">
|
||||
<span className="cmt-name group-hover:text-[var(--accent)] transition-colors">
|
||||
{authorName}
|
||||
</span>
|
||||
@@ -1189,7 +1192,7 @@ export default function CommentSection({
|
||||
<div key={c.id} id={`comment-${c.id}`} className="cmt-item scroll-mt-24">
|
||||
<div className="flex gap-3">
|
||||
{author ? (
|
||||
<Link href={`/u/${author.id}`} className="shrink-0" aria-label={authorName}>
|
||||
<Link href={userPath(urlStyle, author.id)} className="shrink-0" aria-label={authorName}>
|
||||
<Avatar name={authorName} src={author.avatar || undefined} size={34} />
|
||||
</Link>
|
||||
) : (
|
||||
@@ -1199,7 +1202,7 @@ export default function CommentSection({
|
||||
<div className="cmt-meta">
|
||||
<div className="cmt-meta-lead">
|
||||
{author ? (
|
||||
<Link href={`/u/${author.id}`} className="cmt-name-link group">
|
||||
<Link href={userPath(urlStyle, author.id)} className="cmt-name-link group">
|
||||
<span className="cmt-name group-hover:text-[var(--accent)] transition-colors">
|
||||
{authorName}
|
||||
</span>
|
||||
@@ -1302,7 +1305,7 @@ export default function CommentSection({
|
||||
return <CommentsUnsupported />;
|
||||
}
|
||||
if (commentsRequireLogin && !user) {
|
||||
return <CommentsLoginRequired redirect={`/post/${postId}#comments`} />;
|
||||
return <CommentsLoginRequired redirect={`${postPath(urlStyle, Number(postId))}#comments`} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -1386,7 +1389,7 @@ export default function CommentSection({
|
||||
<span className="text-sm" style={{ color: "var(--ink-2)" }}>
|
||||
登录后参与讨论
|
||||
</span>
|
||||
<Link href={`/login?redirect=${encodeURIComponent(`/post/${postId}`)}`} className="btn btn-line btn-sm">
|
||||
<Link href={`/login?redirect=${encodeURIComponent(postPath(urlStyle, Number(postId)))}`} className="btn btn-line btn-sm">
|
||||
去登录
|
||||
</Link>
|
||||
</div>
|
||||
@@ -1405,7 +1408,7 @@ export default function CommentSection({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Pagination total={total} page={page} size={size} basePath={`/post/${postId}`} />
|
||||
<Pagination total={total} page={page} size={size} basePath={postPath(urlStyle, Number(postId))} />
|
||||
|
||||
<ConfirmDialog
|
||||
open={rejectTargetId != null}
|
||||
|
||||
Reference in New Issue
Block a user