feat(compose): 正文内嵌隐藏块并升级 Markdown 编辑器

去掉整帖可见性表单,改为 :::hide 按块脱敏;工具栏补齐代码块、链接/图片对话框与粘贴拖拽上传。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-17 04:58:20 +08:00
parent d1155cbd64
commit 01e2fd05ca
19 changed files with 1867 additions and 471 deletions

View File

@@ -9,8 +9,6 @@ type Props = {
initialContent: string;
initialTags: string;
initialPostType?: string;
initialContentAccess?: string;
initialAccessPoints?: number;
initialTypeMeta?: string;
initialAttachments?: PostAttachment[];
canEditPollOptions?: boolean;
@@ -26,8 +24,6 @@ export default function EditPostClient({
initialContent,
initialTags,
initialPostType,
initialContentAccess,
initialAccessPoints,
initialTypeMeta,
initialAttachments,
canEditPollOptions,
@@ -44,8 +40,6 @@ export default function EditPostClient({
initialContent={initialContent}
initialTags={initialTags}
initialPostType={initialPostType}
initialContentAccess={initialContentAccess}
initialAccessPoints={initialAccessPoints}
initialTypeMeta={initialTypeMeta}
initialAttachments={initialAttachments}
canEditPollOptions={canEditPollOptions}

View File

@@ -60,8 +60,6 @@ export default async function EditPostPage({ params }: PageProps) {
initialContent={post.content}
initialTags={post.tags || ""}
initialPostType={post.post_type}
initialContentAccess={post.content_access}
initialAccessPoints={post.access_points}
initialTypeMeta={post.type_meta}
initialAttachments={post.attachments || []}
canEditPollOptions={post.poll?.can_edit_options !== false}

View File

@@ -34,7 +34,7 @@ import MobilePostBar from "@/components/MobilePostBar";
import Avatar from "@/components/Avatar";
import RoleBadge from "@/components/RoleBadge";
import MarkdownBody from "@/components/MarkdownBody";
import ContentLockGate from "@/components/ContentLockGate";
import { publicMarkdownForSeo } from "@/lib/hideBlocks";
import PostAttachments from "@/components/PostAttachments";
import PostInteractPanels, { QuestionStatusCard } from "@/components/PostInteractPanels";
import AcceptedAnswerCard from "@/components/AcceptedAnswerCard";
@@ -60,9 +60,10 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
robots: { index: false, follow: false },
};
}
const desc = post.content_locked
? post.access_hint || "部分内容需满足条件后可见"
: (post.content || "").slice(0, 160);
const publicBody = publicMarkdownForSeo(post.content || "");
const desc = publicBody
? publicBody.slice(0, 160)
: post.access_hint || "部分内容需满足条件后可见";
return {
title: post.title,
description: desc,
@@ -481,10 +482,11 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
const related = (relatedData?.posts ?? []).filter((p) => p.id !== post.id).slice(0, 5);
const authorPosts = (profile?.posts ?? []).filter((p) => p.id !== post.id).slice(0, 5);
const toc = post.content_locked ? [] : extractToc(post.content);
const publicBody = publicMarkdownForSeo(post.content || "");
const toc = extractToc(publicBody);
const readingMinutes = Math.max(
1,
Math.ceil((post.content_locked ? post.title.length : post.content.length) / 400)
Math.ceil((publicBody.length > 0 ? publicBody.length : post.title.length) / 400)
);
const edited =
new Date(post.updated_at).getTime() - new Date(post.created_at).getTime() > 60_000;
@@ -527,8 +529,8 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
"@context": "https://schema.org",
"@type": "DiscussionForumPosting",
headline: post.title,
// 锁定正文不进 JSON-LD,避免 SEO 泄露付费/回复可见内容
articleBody: post.content_locked ? undefined : post.content,
// 仅公开片段进 JSON-LD,避免 SEO 泄露隐藏块原文
articleBody: publicBody || undefined,
datePublished: post.created_at,
dateModified: post.updated_at,
author: { "@type": "Person", name: post.user.nickname },
@@ -541,7 +543,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
...(allowComments &&
acceptedAnswer &&
!acceptedAnswer.deleted &&
!post.content_locked && {
!!publicBody && {
acceptedAnswer: {
"@type": "Comment",
text: acceptedAnswer.content,
@@ -718,24 +720,14 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
</div>
)}
{post.content_locked ? (
<ContentLockGate
postId={post.id}
hint={post.access_hint}
access={post.content_access}
points={post.access_points}
loggedIn={!!me.user}
/>
) : (
<MarkdownBody
content={post.content}
components={{ h2: headingRenderer("h2"), h3: headingRenderer("h3") }}
/>
)}
<MarkdownBody
content={post.content}
postId={post.id}
loggedIn={!!me.user}
components={{ h2: headingRenderer("h2"), h3: headingRenderer("h3") }}
/>
{!post.content_locked && (
<PostAttachments postId={post.id} items={post.attachments || []} />
)}
<PostAttachments postId={post.id} items={post.attachments || []} />
{/* 类型特殊块(投票/悬赏/抽奖/问答)统一放正文最下方 */}
<PostInteractPanels
@@ -745,7 +737,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
lottery={post.lottery}
loggedIn={!!me.user}
/>
{!post.content_locked && allowComments && acceptedAnswer ? (
{allowComments && acceptedAnswer ? (
<AcceptedAnswerCard
answer={acceptedAnswer}
variant={acceptedAnswerVariant}