feat(mindspace): 全部页面管理、级联删除与页面同步去重
支持全部页面分页/多选/删除/分享,删除时可选移除广场帖并清理工作区附件;修复并发同步重复创建页面,并补齐预览下载链接重写与 iframe 下载权限。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+24
-6
@@ -697,18 +697,36 @@ export async function getMindSpacePageDeletePreview(
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function deleteMindSpacePage(pageId: string): Promise<MindSpacePageDeleteResult> {
|
||||
export async function deleteMindSpacePage(
|
||||
pageId: string,
|
||||
options?: { removeFromPlaza?: boolean },
|
||||
): Promise<MindSpacePageDeleteResult> {
|
||||
const params = new URLSearchParams();
|
||||
if (options?.removeFromPlaza) params.set('remove_from_plaza', 'true');
|
||||
const query = params.toString() ? `?${params.toString()}` : '';
|
||||
const result = await apiFetch<{ data: MindSpacePageDeleteResult }>(
|
||||
`/mindspace/v1/pages/${encodeURIComponent(pageId)}`,
|
||||
`/mindspace/v1/pages/${encodeURIComponent(pageId)}${query}`,
|
||||
{ method: 'DELETE' },
|
||||
);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function listMindSpacePages(status?: string): Promise<MindSpacePage[]> {
|
||||
const query = status ? `?status=${encodeURIComponent(status)}` : '';
|
||||
const result = await apiFetch<{ data: MindSpacePage[] }>(`/mindspace/v1/pages${query}`);
|
||||
return result.data;
|
||||
export async function listMindSpacePages(options?: {
|
||||
status?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
categoryCode?: string;
|
||||
}): Promise<{ items: MindSpacePage[]; page: MindSpaceListPage }> {
|
||||
const params = new URLSearchParams();
|
||||
if (options?.status) params.set('status', options.status);
|
||||
if (options?.limit != null) params.set('limit', String(options.limit));
|
||||
if (options?.offset != null) params.set('offset', String(options.offset));
|
||||
if (options?.categoryCode) params.set('category_code', options.categoryCode);
|
||||
const query = params.toString() ? `?${params.toString()}` : '';
|
||||
const result = await apiFetch<{ data: MindSpacePage[]; page?: MindSpaceListPage }>(
|
||||
`/mindspace/v1/pages${query}`,
|
||||
);
|
||||
return { items: result.data, page: result.page ?? {} };
|
||||
}
|
||||
|
||||
export async function getMindSpacePage(pageId: string): Promise<MindSpacePage> {
|
||||
|
||||
@@ -73,7 +73,7 @@ export function ChatSharePreviewModal({
|
||||
<button type="button" className="chat-share-preview-close" onClick={onClose} aria-label="关闭">✕</button>
|
||||
</div>
|
||||
<div className="chat-share-preview-body">
|
||||
<iframe src={previewUrl} title="页面预览" sandbox="allow-same-origin allow-scripts allow-popups" className="chat-share-preview-frame" />
|
||||
<iframe src={previewUrl} title="页面预览" sandbox="allow-same-origin allow-scripts allow-popups allow-downloads" className="chat-share-preview-frame" />
|
||||
|
||||
<div className="chat-share-side-actions">
|
||||
{phase.kind === 'done' ? (
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { resolvePlazaPostUrl } from '../utils/publicUrl';
|
||||
import type { MindSpacePagePlazaPostPreview } from '../types';
|
||||
|
||||
function plazaPostStatusLabel(status: string): string {
|
||||
if (status === 'published') return '已公开';
|
||||
if (status === 'pending_review') return '待审核';
|
||||
if (status === 'rejected') return '已拒绝';
|
||||
return status;
|
||||
}
|
||||
|
||||
export function MindSpaceDeletePlazaOption({
|
||||
plazaPosts,
|
||||
removeFromPlaza,
|
||||
onRemoveFromPlazaChange,
|
||||
}: {
|
||||
plazaPosts: MindSpacePagePlazaPostPreview[];
|
||||
removeFromPlaza: boolean;
|
||||
onRemoveFromPlazaChange: (value: boolean) => void;
|
||||
}) {
|
||||
if (plazaPosts.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="mindspace-delete-plaza">
|
||||
<p className="mindspace-upload-dialog-desc">
|
||||
{plazaPosts.length === 1
|
||||
? '该页面已发布到广场。'
|
||||
: `该页面在广场有 ${plazaPosts.length} 个帖子。`}
|
||||
</p>
|
||||
<ul className="mindspace-delete-plaza-posts">
|
||||
{plazaPosts.map((post) => (
|
||||
<li key={post.id}>
|
||||
<strong>{post.title || '未命名帖子'}</strong>
|
||||
<span>{plazaPostStatusLabel(post.status)}</span>
|
||||
<a href={resolvePlazaPostUrl(post.id)} target="_blank" rel="noreferrer">
|
||||
查看广场帖
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<label className="mindspace-checkbox-row mindspace-delete-plaza-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={removeFromPlaza}
|
||||
onChange={(event) => onRemoveFromPlazaChange(event.target.checked)}
|
||||
/>
|
||||
同时从广场移除
|
||||
</label>
|
||||
<small className="mindspace-delete-plaza-hint">
|
||||
若不勾选,广场帖子将继续保留;页面与公开链接仍会删除。
|
||||
</small>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { MindSpacePage } from '../types';
|
||||
import { WorkCover } from './WorkCover';
|
||||
import { WorkCover, type WorkCoverMode } from './WorkCover';
|
||||
|
||||
const ACCESS_MODE_ICONS: Record<string, { icon: string; title: string }> = {
|
||||
public: { icon: '🌐', title: '完全公开' },
|
||||
@@ -56,6 +56,11 @@ export function MindSpaceFeedCard({
|
||||
actions,
|
||||
checked,
|
||||
onCheckChange,
|
||||
checkboxPlacement = 'top-left',
|
||||
coverMode = 'thumbnail-first',
|
||||
onOpenPage,
|
||||
onEditPage,
|
||||
onShare,
|
||||
}: {
|
||||
page: MindSpacePage;
|
||||
previewMode?: boolean;
|
||||
@@ -63,10 +68,16 @@ export function MindSpaceFeedCard({
|
||||
actions?: { label: string; onClick: (e: React.MouseEvent) => void }[];
|
||||
checked?: boolean;
|
||||
onCheckChange?: (checked: boolean) => void;
|
||||
checkboxPlacement?: 'top-left' | 'top-right';
|
||||
coverMode?: WorkCoverMode;
|
||||
onOpenPage?: () => void;
|
||||
onEditPage?: () => void;
|
||||
onShare?: () => void;
|
||||
}) {
|
||||
const typeLabel = feedTypeLabel(page);
|
||||
const isHtml = page.contentFormat === 'html';
|
||||
const previewThumbnail = previewMode ? PREVIEW_THUMBNAILS[page.id] : null;
|
||||
const showLinkActions = Boolean(onOpenPage || onEditPage || onShare);
|
||||
|
||||
return (
|
||||
<div className="mindspace-feed-card-wrap">
|
||||
@@ -74,7 +85,12 @@ export function MindSpaceFeedCard({
|
||||
<div className="mindspace-feed-cover">
|
||||
{onCheckChange !== undefined && (
|
||||
<label
|
||||
className="mindspace-feed-select"
|
||||
className={[
|
||||
'mindspace-feed-select',
|
||||
checkboxPlacement === 'top-right' ? 'is-top-right is-compact' : '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<input
|
||||
@@ -87,7 +103,7 @@ export function MindSpaceFeedCard({
|
||||
)}
|
||||
{isHtml ? (
|
||||
<WorkCover
|
||||
coverMode="thumbnail-first"
|
||||
coverMode={coverMode}
|
||||
thumbnailUrl={
|
||||
previewThumbnail ??
|
||||
`/api/mindspace/v1/pages/${page.id}/thumbnail?v=${page.updatedAt}`
|
||||
@@ -121,6 +137,46 @@ export function MindSpaceFeedCard({
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
{showLinkActions && (
|
||||
<div className="mindspace-feed-card-links">
|
||||
{onOpenPage && (
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-feed-card-link"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onOpenPage();
|
||||
}}
|
||||
>
|
||||
打开页面
|
||||
</button>
|
||||
)}
|
||||
{onEditPage && (
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-feed-card-link"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onEditPage();
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
)}
|
||||
{onShare && (
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-feed-card-link"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onShare();
|
||||
}}
|
||||
>
|
||||
分享
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{actions && actions.length > 0 && (
|
||||
<div className="mindspace-feed-card-actions">
|
||||
{actions.map((action) => (
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
} from './MindSpacePageFullscreenPreview';
|
||||
import { MindSpacePagePreviewPanel } from './MindSpacePagePreviewPanel';
|
||||
import { MindSpaceModal } from './MindSpaceModal';
|
||||
import { MindSpaceDeletePlazaOption } from './MindSpaceDeletePlazaOption';
|
||||
import { MindSpacePublishSuccess } from './MindSpacePublishSuccess';
|
||||
import { ShareSheet } from './ShareSheet';
|
||||
import { buildPageSharePayload, type SharePayload } from '../utils/shareChannels';
|
||||
@@ -269,6 +270,7 @@ export function MindSpacePageDetail({
|
||||
const [deletePreviewOpen, setDeletePreviewOpen] = useState(false);
|
||||
const [deletePreview, setDeletePreview] = useState<MindSpacePageDeletePreview | null>(null);
|
||||
const [deletePreviewLoading, setDeletePreviewLoading] = useState(false);
|
||||
const [deleteRemoveFromPlaza, setDeleteRemoveFromPlaza] = useState(false);
|
||||
const [sharePayload, setSharePayload] = useState<SharePayload | null>(null);
|
||||
const [previewFrame, setPreviewFrame] = useState<HTMLIFrameElement | null>(null);
|
||||
const [isExportingImage, setIsExportingImage] = useState(false);
|
||||
@@ -532,6 +534,7 @@ export function MindSpacePageDetail({
|
||||
setDeletePreviewOpen(true);
|
||||
setDeletePreviewLoading(true);
|
||||
setDeletePreview(null);
|
||||
setDeleteRemoveFromPlaza(false);
|
||||
setError(null);
|
||||
try {
|
||||
setDeletePreview(await getMindSpacePageDeletePreview(page.id));
|
||||
@@ -547,6 +550,7 @@ export function MindSpacePageDetail({
|
||||
if (deletingPage) return;
|
||||
setDeletePreviewOpen(false);
|
||||
setDeletePreview(null);
|
||||
setDeleteRemoveFromPlaza(false);
|
||||
};
|
||||
|
||||
const confirmDeletePage = async () => {
|
||||
@@ -554,9 +558,12 @@ export function MindSpacePageDetail({
|
||||
setDeletingPage(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await deleteMindSpacePage(page.id);
|
||||
const result = await deleteMindSpacePage(page.id, {
|
||||
removeFromPlaza: deleteRemoveFromPlaza,
|
||||
});
|
||||
setDeletePreviewOpen(false);
|
||||
setDeletePreview(null);
|
||||
setDeleteRemoveFromPlaza(false);
|
||||
await onSaved({ quota: result.quota, freedBytes: result.freedBytes });
|
||||
onBack();
|
||||
} catch (err) {
|
||||
@@ -950,6 +957,11 @@ export function MindSpacePageDetail({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<MindSpaceDeletePlazaOption
|
||||
plazaPosts={deletePreview.plazaPosts ?? []}
|
||||
removeFromPlaza={deleteRemoveFromPlaza}
|
||||
onRemoveFromPlazaChange={setDeleteRemoveFromPlaza}
|
||||
/>
|
||||
<div className="mindspace-page-editor-actions">
|
||||
<button type="button" onClick={closeDeleteDialog} disabled={deletingPage}>
|
||||
取消
|
||||
|
||||
@@ -129,7 +129,7 @@ export function MindSpacePageDraftPreviewFrame({
|
||||
ref={iframeRef}
|
||||
title={`${title || '页面'} 预览`}
|
||||
srcDoc={srcdoc}
|
||||
sandbox="allow-same-origin"
|
||||
sandbox="allow-same-origin allow-downloads"
|
||||
className={className}
|
||||
style={{ height: `${height}px`, visibility: srcdoc ? 'visible' : 'hidden' }}
|
||||
/>
|
||||
|
||||
@@ -130,7 +130,7 @@ export function MindSpacePageEditablePreviewFrame({
|
||||
ref={iframeRef}
|
||||
title={`${title || '页面'} 预览`}
|
||||
srcDoc={srcdoc}
|
||||
sandbox="allow-scripts"
|
||||
sandbox="allow-scripts allow-downloads"
|
||||
scrolling={scrollInsideFrame ? 'yes' : 'no'}
|
||||
className={frameClassName}
|
||||
style={scrollInsideFrame ? { height: '100%' } : { height: `${height}px` }}
|
||||
|
||||
@@ -53,10 +53,11 @@ import { PREVIEW_ASSETS, PREVIEW_PAGES, PREVIEW_SPACE } from '../dev/mindspacePr
|
||||
import { MindSpaceFeedCard } from './MindSpaceFeedCard';
|
||||
import { MindSpacePageDetail } from './MindSpacePageDetail';
|
||||
import { MindSpaceModal } from './MindSpaceModal';
|
||||
import { MindSpaceDeletePlazaOption } from './MindSpaceDeletePlazaOption';
|
||||
import { ShareSheet } from './ShareSheet';
|
||||
import { WorkCover } from './WorkCover';
|
||||
import { buildAssetSharePayload, type SharePayload } from '../utils/shareChannels';
|
||||
import { resolvePublicPageUrl, resolvePlazaHomeUrl } from '../utils/publicUrl';
|
||||
import { buildAssetSharePayload, buildPageSharePayload, type SharePayload } from '../utils/shareChannels';
|
||||
import { resolveMindSpaceHomeUrl, resolveMindSpacePageOwnerPreviewUrl, resolveMindSpacePagePublicViewUrl, resolvePublicPageUrl, resolvePlazaHomeUrl } from '../utils/publicUrl';
|
||||
import { PagePreviewFrame } from './PagePreviewFrame';
|
||||
import { BalanceRing } from './BalanceRing';
|
||||
import { NotificationCenter } from './NotificationCenter';
|
||||
@@ -77,6 +78,7 @@ const CATEGORY_ACTIONS: Partial<Record<MindSpaceCategory['code'], string>> = {
|
||||
|
||||
const AGENT_JOBS_PAGE_SIZE = 10;
|
||||
const RECENT_PAGES_PAGE_SIZE = 6;
|
||||
const ALL_PAGES_PAGE_SIZE = 15;
|
||||
|
||||
function sortPagesByCreatedAt(pages: MindSpacePage[]) {
|
||||
return [...pages].sort((left, right) => {
|
||||
@@ -526,6 +528,13 @@ export function MindSpaceView({
|
||||
const [agentJobsPage, setAgentJobsPage] = useState(0);
|
||||
const [agentJobsLoading, setAgentJobsLoading] = useState(false);
|
||||
const [allPagesOpen, setAllPagesOpen] = useState(false);
|
||||
const [allPagesItems, setAllPagesItems] = useState<MindSpacePage[]>([]);
|
||||
const [allPagesTotal, setAllPagesTotal] = useState(0);
|
||||
const [allPagesPageIndex, setAllPagesPageIndex] = useState(0);
|
||||
const [allPagesLoading, setAllPagesLoading] = useState(false);
|
||||
const [selectedAllPageIds, setSelectedAllPageIds] = useState<string[]>([]);
|
||||
const [bulkDeletingAllPages, setBulkDeletingAllPages] = useState(false);
|
||||
const [recentPagesTotal, setRecentPagesTotal] = useState(0);
|
||||
const [exportingPageId, setExportingPageId] = useState<string | null>(null);
|
||||
const [sharePayload, setSharePayload] = useState<SharePayload | null>(null);
|
||||
const [previewPageTarget, setPreviewPageTarget] = useState<MindSpacePage | null>(null);
|
||||
@@ -534,7 +543,15 @@ export function MindSpaceView({
|
||||
null,
|
||||
);
|
||||
const [pageDeleteLoading, setPageDeleteLoading] = useState(false);
|
||||
const [pageDeleteRemoveFromPlaza, setPageDeleteRemoveFromPlaza] = useState(false);
|
||||
const [deletingPageId, setDeletingPageId] = useState<string | null>(null);
|
||||
const [bulkPageDeleteOpen, setBulkPageDeleteOpen] = useState(false);
|
||||
const [bulkPageDeleteSource, setBulkPageDeleteSource] = useState<'all' | 'draft'>('all');
|
||||
const [bulkPageDeletePreviews, setBulkPageDeletePreviews] = useState<
|
||||
MindSpacePageDeletePreview[] | null
|
||||
>(null);
|
||||
const [bulkPageDeleteLoading, setBulkPageDeleteLoading] = useState(false);
|
||||
const [bulkPageDeleteRemoveFromPlaza, setBulkPageDeleteRemoveFromPlaza] = useState(false);
|
||||
const [assetDeleteDialog, setAssetDeleteDialog] = useState<{
|
||||
message: string;
|
||||
references: Array<{ id: string; title: string; publishedOnline?: boolean }>;
|
||||
@@ -632,6 +649,41 @@ export function MindSpaceView({
|
||||
return `${value.slice(0, max - 1)}…`;
|
||||
};
|
||||
|
||||
const resolvePagePublicViewUrl = async (page: MindSpacePage): Promise<string | null> => {
|
||||
const fromList = resolveMindSpacePagePublicViewUrl(page);
|
||||
if (fromList) return fromList;
|
||||
try {
|
||||
const pageDetail = await getMindSpacePage(page.id);
|
||||
const publicUrl = pageDetail.publication?.publicUrl ?? pageDetail.publicationUrl;
|
||||
return publicUrl ? resolvePublicPageUrl(publicUrl) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const openPageViewInNewTab = async (page: MindSpacePage) => {
|
||||
if (previewMode) {
|
||||
setError('预览模式下暂不支持打开页面');
|
||||
return;
|
||||
}
|
||||
const publicUrl = await resolvePagePublicViewUrl(page);
|
||||
const viewUrl = publicUrl ?? resolveMindSpacePageOwnerPreviewUrl(page);
|
||||
window.open(viewUrl, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const openPageShare = async (page: MindSpacePage) => {
|
||||
if (previewMode) {
|
||||
setError('预览模式下暂不支持分享');
|
||||
return;
|
||||
}
|
||||
const publicUrl = await resolvePagePublicViewUrl(page);
|
||||
if (!publicUrl) {
|
||||
setError('该页面尚未对外发布,暂无法分享链接;你可以先打开预览,或在编辑页完成发布后再分享');
|
||||
return;
|
||||
}
|
||||
setSharePayload(buildPageSharePayload(page, publicUrl));
|
||||
};
|
||||
|
||||
const showPage = (pageId: string, autoPlaza = false) => {
|
||||
setSelectedPageId(pageId);
|
||||
setSelectedPageAutoPlaza(autoPlaza);
|
||||
@@ -649,6 +701,7 @@ export function MindSpaceView({
|
||||
}
|
||||
setPageDeleteTarget(page);
|
||||
setPageDeletePreview(null);
|
||||
setPageDeleteRemoveFromPlaza(false);
|
||||
setPageDeleteLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
@@ -665,6 +718,7 @@ export function MindSpaceView({
|
||||
if (deletingPageId) return;
|
||||
setPageDeleteTarget(null);
|
||||
setPageDeletePreview(null);
|
||||
setPageDeleteRemoveFromPlaza(false);
|
||||
};
|
||||
|
||||
const confirmDeletePage = async () => {
|
||||
@@ -672,12 +726,15 @@ export function MindSpaceView({
|
||||
setDeletingPageId(pageDeleteTarget.id);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await deleteMindSpacePage(pageDeleteTarget.id);
|
||||
const result = await deleteMindSpacePage(pageDeleteTarget.id, {
|
||||
removeFromPlaza: pageDeleteRemoveFromPlaza,
|
||||
});
|
||||
if (selectedPageId === pageDeleteTarget.id) {
|
||||
closePage();
|
||||
}
|
||||
setPageDeleteTarget(null);
|
||||
setPageDeletePreview(null);
|
||||
setPageDeleteRemoveFromPlaza(false);
|
||||
await refreshMindSpaceSnapshot({ quota: result.quota, freedBytes: result.freedBytes });
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '删除页面失败');
|
||||
@@ -734,24 +791,7 @@ export function MindSpaceView({
|
||||
};
|
||||
|
||||
const openPagePublicShare = async (page: MindSpacePage) => {
|
||||
if (previewMode) {
|
||||
setError('预览模式下暂不支持跳转分享页');
|
||||
return;
|
||||
}
|
||||
if (page.publicationUrl) {
|
||||
window.open(resolvePublicPageUrl(page.publicationUrl), '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const pageDetail = await getMindSpacePage(page.id);
|
||||
if (!pageDetail.publication?.publicUrl) {
|
||||
setError('该页面未发布,先发布后再分享');
|
||||
return;
|
||||
}
|
||||
window.open(resolvePublicPageUrl(pageDetail.publication.publicUrl), '_blank', 'noopener,noreferrer');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '获取分享链接失败');
|
||||
}
|
||||
await openPageViewInNewTab(page);
|
||||
};
|
||||
|
||||
const exportPageLongImage = async (page: MindSpacePage) => {
|
||||
@@ -823,6 +863,41 @@ export function MindSpaceView({
|
||||
}
|
||||
};
|
||||
|
||||
const loadAllPagesPage = async (pageIndex: number) => {
|
||||
if (previewMode) {
|
||||
setAllPagesItems(PREVIEW_PAGES);
|
||||
setAllPagesTotal(PREVIEW_PAGES.length);
|
||||
setAllPagesPageIndex(0);
|
||||
return;
|
||||
}
|
||||
setAllPagesLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const { items, page } = await listMindSpacePages({
|
||||
limit: ALL_PAGES_PAGE_SIZE,
|
||||
offset: pageIndex * ALL_PAGES_PAGE_SIZE,
|
||||
});
|
||||
setAllPagesItems(items);
|
||||
setAllPagesPageIndex(pageIndex);
|
||||
setAllPagesTotal(page.total ?? items.length);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '全部页面加载失败');
|
||||
} finally {
|
||||
setAllPagesLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openAllPagesPanel = () => {
|
||||
setSelectedCategory(null);
|
||||
setSelectedPageId(null);
|
||||
setNewPageOpen(false);
|
||||
setAgentJobsPanelOpen(false);
|
||||
setAllPagesOpen(true);
|
||||
setSelectedAllPageIds([]);
|
||||
routeSync?.pushHome();
|
||||
void loadAllPagesPage(0);
|
||||
};
|
||||
|
||||
const refreshAgentJobsSummary = async () => {
|
||||
if (previewMode) return;
|
||||
const { page } = await listMindSpaceAgentJobs({ limit: 1, offset: 0 });
|
||||
@@ -882,12 +957,13 @@ export function MindSpaceView({
|
||||
setPages(PREVIEW_PAGES);
|
||||
return;
|
||||
}
|
||||
const [nextSpace, nextPages] = await Promise.all([
|
||||
const [nextSpace, recentPagesResult] = await Promise.all([
|
||||
getMindSpace(),
|
||||
listMindSpacePages(),
|
||||
listMindSpacePages({ limit: RECENT_PAGES_PAGE_SIZE, offset: 0 }),
|
||||
]);
|
||||
setSpace(nextSpace);
|
||||
setPages(nextPages);
|
||||
setPages(recentPagesResult.items);
|
||||
setRecentPagesTotal(recentPagesResult.page.total ?? recentPagesResult.items.length);
|
||||
await refreshAgentJobsSummary();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '空间加载失败');
|
||||
@@ -910,8 +986,15 @@ export function MindSpaceView({
|
||||
const refreshPagesSilently = async () => {
|
||||
if (previewMode) return;
|
||||
try {
|
||||
const nextPages = await listMindSpacePages();
|
||||
setPages(nextPages);
|
||||
const recentPagesResult = await listMindSpacePages({
|
||||
limit: RECENT_PAGES_PAGE_SIZE,
|
||||
offset: 0,
|
||||
});
|
||||
setPages(recentPagesResult.items);
|
||||
setRecentPagesTotal(recentPagesResult.page.total ?? recentPagesResult.items.length);
|
||||
if (allPagesOpen) {
|
||||
await loadAllPagesPage(allPagesPageIndex);
|
||||
}
|
||||
} catch {
|
||||
// ignore background list refresh errors
|
||||
}
|
||||
@@ -1009,6 +1092,13 @@ export function MindSpaceView({
|
||||
return;
|
||||
}
|
||||
setAssets(await listMindSpaceAssets(category.code));
|
||||
if (['draft', 'oa', 'public'].includes(category.code)) {
|
||||
const { items } = await listMindSpacePages({ categoryCode: category.code, limit: 100 });
|
||||
setPages(items);
|
||||
if (category.code === 'draft') {
|
||||
setSelectedDraftPageIds([]);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '资产加载失败');
|
||||
} finally {
|
||||
@@ -1389,37 +1479,135 @@ export function MindSpaceView({
|
||||
}
|
||||
};
|
||||
|
||||
const removeDraftPages = async () => {
|
||||
if (selectedDraftPageIds.length === 0) return;
|
||||
const bulkDeletePlazaPosts = useMemo(() => {
|
||||
if (!bulkPageDeletePreviews) return [];
|
||||
const seen = new Set<string>();
|
||||
const posts = [];
|
||||
for (const preview of bulkPageDeletePreviews) {
|
||||
for (const post of preview.plazaPosts ?? []) {
|
||||
if (seen.has(post.id)) continue;
|
||||
seen.add(post.id);
|
||||
posts.push(post);
|
||||
}
|
||||
}
|
||||
return posts;
|
||||
}, [bulkPageDeletePreviews]);
|
||||
|
||||
const closeBulkPageDeleteDialog = () => {
|
||||
if (bulkDeletingAllPages || bulkDeletingDraftPages) return;
|
||||
setBulkPageDeleteOpen(false);
|
||||
setBulkPageDeletePreviews(null);
|
||||
setBulkPageDeleteRemoveFromPlaza(false);
|
||||
};
|
||||
|
||||
const startBulkPageDelete = async (pageIds: string[], source: 'all' | 'draft') => {
|
||||
if (pageIds.length === 0) return;
|
||||
if (previewMode) {
|
||||
setError('预览模式下无法删除页面');
|
||||
return;
|
||||
}
|
||||
setBulkDeletingDraftPages(true);
|
||||
setBulkPageDeleteSource(source);
|
||||
setBulkPageDeletePreviews(null);
|
||||
setBulkPageDeleteRemoveFromPlaza(false);
|
||||
setBulkPageDeleteLoading(true);
|
||||
setBulkPageDeleteOpen(true);
|
||||
setError(null);
|
||||
try {
|
||||
const previews = await Promise.all(
|
||||
pageIds.map((pageId) =>
|
||||
getMindSpacePageDeletePreview(pageId).catch((err) => {
|
||||
console.error('[MindSpace] delete preview failed:', pageId, err);
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
);
|
||||
const valid = previews.filter(Boolean) as MindSpacePageDeletePreview[];
|
||||
if (valid.length === 0) {
|
||||
setBulkPageDeleteOpen(false);
|
||||
setError('无法加载删除预览');
|
||||
return;
|
||||
}
|
||||
setBulkPageDeletePreviews(valid);
|
||||
} catch (err) {
|
||||
setBulkPageDeleteOpen(false);
|
||||
setError(err instanceof Error ? err.message : '无法加载删除预览');
|
||||
} finally {
|
||||
setBulkPageDeleteLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmBulkPageDelete = async () => {
|
||||
if (!bulkPageDeletePreviews || bulkPageDeletePreviews.length === 0) return;
|
||||
const deletingIds = bulkPageDeletePreviews.map((preview) => preview.page.id);
|
||||
const setBulkDeleting =
|
||||
bulkPageDeleteSource === 'all' ? setBulkDeletingAllPages : setBulkDeletingDraftPages;
|
||||
setBulkDeleting(true);
|
||||
setError(null);
|
||||
const deletingIds = [...selectedDraftPageIds];
|
||||
const failedIds: string[] = [];
|
||||
const failedNames: string[] = [];
|
||||
try {
|
||||
for (const pageId of deletingIds) {
|
||||
for (const preview of bulkPageDeletePreviews) {
|
||||
try {
|
||||
await deleteMindSpacePage(pageId);
|
||||
if (selectedPageId === pageId) closePage();
|
||||
} catch {
|
||||
failedIds.push(pageId);
|
||||
failedNames.push(pages.find((p) => p.id === pageId)?.title ?? pageId);
|
||||
await deleteMindSpacePage(preview.page.id, {
|
||||
removeFromPlaza: bulkPageDeleteRemoveFromPlaza,
|
||||
});
|
||||
if (selectedPageId === preview.page.id) closePage();
|
||||
} catch (err) {
|
||||
failedIds.push(preview.page.id);
|
||||
failedNames.push(preview.page.title);
|
||||
console.error('[MindSpace] delete page failed:', preview.page.id, err);
|
||||
}
|
||||
}
|
||||
setSelectedDraftPageIds((ids) => ids.filter((id) => failedIds.includes(id)));
|
||||
const deletedIds = deletingIds.filter((id) => !failedIds.includes(id));
|
||||
if (bulkPageDeleteSource === 'all') {
|
||||
setSelectedAllPageIds((ids) => ids.filter((id) => failedIds.includes(id)));
|
||||
} else {
|
||||
setSelectedDraftPageIds((ids) => ids.filter((id) => failedIds.includes(id)));
|
||||
}
|
||||
setBulkPageDeleteOpen(false);
|
||||
setBulkPageDeletePreviews(null);
|
||||
setBulkPageDeleteRemoveFromPlaza(false);
|
||||
await refreshMindSpaceSnapshot();
|
||||
await load();
|
||||
if (bulkPageDeleteSource === 'all' && deletedIds.length > 0) {
|
||||
const nextPageIndex =
|
||||
allPagesPageIndex > 0 && allPagesItems.length <= deletedIds.length
|
||||
? Math.max(0, allPagesPageIndex - 1)
|
||||
: allPagesPageIndex;
|
||||
await loadAllPagesPage(nextPageIndex);
|
||||
}
|
||||
if (failedNames.length > 0) {
|
||||
setError(`有 ${failedNames.length} 个页面删除失败:${failedNames.slice(0, 3).join('、')}`);
|
||||
}
|
||||
} finally {
|
||||
setBulkDeletingDraftPages(false);
|
||||
setBulkDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const removeDraftPages = () => {
|
||||
void startBulkPageDelete([...selectedDraftPageIds], 'draft');
|
||||
};
|
||||
|
||||
const selectedAllPageSet = useMemo(() => new Set(selectedAllPageIds), [selectedAllPageIds]);
|
||||
const allCurrentAllPagesSelected =
|
||||
allPagesItems.length > 0 && allPagesItems.every((page) => selectedAllPageSet.has(page.id));
|
||||
|
||||
const toggleAllPagesOnCurrentPageSelected = () => {
|
||||
if (allCurrentAllPagesSelected) {
|
||||
const currentIds = new Set(allPagesItems.map((page) => page.id));
|
||||
setSelectedAllPageIds((ids) => ids.filter((id) => !currentIds.has(id)));
|
||||
} else {
|
||||
const merged = new Set([...selectedAllPageIds, ...allPagesItems.map((page) => page.id)]);
|
||||
setSelectedAllPageIds([...merged]);
|
||||
}
|
||||
};
|
||||
|
||||
const removeSelectedAllPages = () => {
|
||||
void startBulkPageDelete([...selectedAllPageIds], 'all');
|
||||
};
|
||||
|
||||
const allPagesPageCount = Math.max(1, Math.ceil(allPagesTotal / ALL_PAGES_PAGE_SIZE));
|
||||
|
||||
const previewAsset = useMemo(
|
||||
() => assets.find((asset) => asset.id === previewAssetId) ?? null,
|
||||
[assets, previewAssetId],
|
||||
@@ -2054,19 +2242,89 @@ export function MindSpaceView({
|
||||
<p className="mindspace-eyebrow">ALL PAGES</p>
|
||||
<h2>全部页面</h2>
|
||||
</div>
|
||||
<span>{pages.length} 个页面</span>
|
||||
<span>{allPagesTotal} 个页面</span>
|
||||
</div>
|
||||
{pages.length > 0 ? (
|
||||
<div className="mindspace-feed-grid mindspace-recent-work-grid is-all-pages">
|
||||
{sortedPages.map((page) => (
|
||||
<MindSpaceFeedCard
|
||||
key={page.id}
|
||||
page={page}
|
||||
previewMode={previewMode}
|
||||
onClick={() => showPage(page.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{allPagesLoading && allPagesItems.length === 0 ? (
|
||||
<div className="mindspace-state">正在加载页面…</div>
|
||||
) : allPagesTotal > 0 ? (
|
||||
<>
|
||||
<div className="mindspace-asset-bulkbar mindspace-all-pages-bulkbar">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleAllPagesOnCurrentPageSelected}
|
||||
disabled={bulkDeletingAllPages || allPagesItems.length === 0}
|
||||
>
|
||||
{allCurrentAllPagesSelected ? '取消全选' : '全选'}
|
||||
</button>
|
||||
<span>已选 {selectedAllPageIds.length} 项</span>
|
||||
{selectedAllPageIds.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedAllPageIds([])}
|
||||
disabled={bulkDeletingAllPages}
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-asset-bulk-delete"
|
||||
onClick={() => void removeSelectedAllPages()}
|
||||
disabled={selectedAllPageIds.length === 0 || bulkDeletingAllPages}
|
||||
>
|
||||
{bulkDeletingAllPages ? '删除中…' : '删除选中'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mindspace-feed-grid mindspace-recent-work-grid is-all-pages">
|
||||
{allPagesItems.map((page) => (
|
||||
<MindSpaceFeedCard
|
||||
key={page.id}
|
||||
page={page}
|
||||
previewMode={previewMode}
|
||||
coverMode="thumbnail-only"
|
||||
checkboxPlacement="top-right"
|
||||
checked={selectedAllPageSet.has(page.id)}
|
||||
onCheckChange={(checked) => {
|
||||
setSelectedAllPageIds((ids) =>
|
||||
checked
|
||||
? ids.includes(page.id)
|
||||
? ids
|
||||
: [...ids, page.id]
|
||||
: ids.filter((id) => id !== page.id),
|
||||
);
|
||||
}}
|
||||
onClick={() => void openPageViewInNewTab(page)}
|
||||
onEditPage={() => showPage(page.id)}
|
||||
onOpenPage={() => void openPageViewInNewTab(page)}
|
||||
onShare={() => void openPageShare(page)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{allPagesTotal > ALL_PAGES_PAGE_SIZE && (
|
||||
<div className="mindspace-pagination">
|
||||
<button
|
||||
type="button"
|
||||
disabled={allPagesPageIndex <= 0 || allPagesLoading}
|
||||
onClick={() => void loadAllPagesPage(allPagesPageIndex - 1)}
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
<span>
|
||||
第 {allPagesPageIndex + 1} / {allPagesPageCount} 页
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={
|
||||
allPagesLoading ||
|
||||
(allPagesPageIndex + 1) * ALL_PAGES_PAGE_SIZE >= allPagesTotal
|
||||
}
|
||||
onClick={() => void loadAllPagesPage(allPagesPageIndex + 1)}
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="mindspace-recent-work-empty">
|
||||
<p>上传资料或从聊天保存页面后,所有页面会集中显示在这里。</p>
|
||||
@@ -2584,11 +2842,11 @@ export function MindSpaceView({
|
||||
<p className="mindspace-eyebrow">RECENT WORK</p>
|
||||
<h2>{pages.length > 0 ? '最近页面' : '这里会出现你的 AI 成果'}</h2>
|
||||
</div>
|
||||
{pages.length > 0 && (
|
||||
{recentPagesTotal > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-section-link"
|
||||
onClick={() => setAllPagesOpen(true)}
|
||||
onClick={openAllPagesPanel}
|
||||
>
|
||||
查看全部
|
||||
</button>
|
||||
@@ -2933,6 +3191,11 @@ export function MindSpaceView({
|
||||
{resolvePublicPageUrl(pageDeletePreview.onlinePublication.publicUrl)}
|
||||
</a>
|
||||
)}
|
||||
<MindSpaceDeletePlazaOption
|
||||
plazaPosts={pageDeletePreview.plazaPosts ?? []}
|
||||
removeFromPlaza={pageDeleteRemoveFromPlaza}
|
||||
onRemoveFromPlazaChange={setPageDeleteRemoveFromPlaza}
|
||||
/>
|
||||
<div className="mindspace-page-editor-actions">
|
||||
<button type="button" onClick={closePageDeleteDialog} disabled={Boolean(deletingPageId)}>
|
||||
取消
|
||||
@@ -2950,6 +3213,57 @@ export function MindSpaceView({
|
||||
)}
|
||||
</MindSpaceModal>
|
||||
|
||||
<MindSpaceModal
|
||||
open={bulkPageDeleteOpen}
|
||||
onClose={closeBulkPageDeleteDialog}
|
||||
title="确认批量删除页面"
|
||||
eyebrow="DELETE PAGES"
|
||||
className="mindspace-delete-dialog"
|
||||
disableClose={
|
||||
bulkPageDeleteLoading || bulkDeletingAllPages || bulkDeletingDraftPages
|
||||
}
|
||||
>
|
||||
{bulkPageDeleteLoading && <div className="mindspace-state">正在分析连带内容…</div>}
|
||||
{bulkPageDeletePreviews && (
|
||||
<>
|
||||
<p className="mindspace-upload-dialog-desc">
|
||||
确定删除选中的 {bulkPageDeletePreviews.length} 个页面吗?删除后无法恢复,关联附件与在线发布也会一并移除。
|
||||
</p>
|
||||
<ul className="mindspace-delete-summary">
|
||||
{bulkPageDeletePreviews.map((preview) => (
|
||||
<li key={preview.page.id}>
|
||||
{preview.page.title}
|
||||
{preview.onlinePublication ? '(含在线公开链接)' : ''}
|
||||
{(preview.plazaPosts?.length ?? 0) > 0 ? '(含广场帖子)' : ''}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<MindSpaceDeletePlazaOption
|
||||
plazaPosts={bulkDeletePlazaPosts}
|
||||
removeFromPlaza={bulkPageDeleteRemoveFromPlaza}
|
||||
onRemoveFromPlazaChange={setBulkPageDeleteRemoveFromPlaza}
|
||||
/>
|
||||
<div className="mindspace-page-editor-actions">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeBulkPageDeleteDialog}
|
||||
disabled={bulkDeletingAllPages || bulkDeletingDraftPages}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-primary"
|
||||
onClick={() => void confirmBulkPageDelete()}
|
||||
disabled={bulkDeletingAllPages || bulkDeletingDraftPages}
|
||||
>
|
||||
{bulkDeletingAllPages || bulkDeletingDraftPages ? '删除中…' : '确认删除'}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</MindSpaceModal>
|
||||
|
||||
<MindSpaceModal
|
||||
open={Boolean(assetDeleteDialog)}
|
||||
onClose={() => setAssetDeleteDialog(null)}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function PagePreviewFrame({
|
||||
key={`${src}:${reloadKey}`}
|
||||
title={title}
|
||||
src={src}
|
||||
sandbox="allow-same-origin"
|
||||
sandbox="allow-same-origin allow-downloads"
|
||||
className={className}
|
||||
style={{ height: `${height}px` }}
|
||||
/>
|
||||
|
||||
@@ -112,7 +112,7 @@ export function PageSavePreviewPanel({
|
||||
title="页面预览"
|
||||
src={pagePreviewUrl}
|
||||
className="page-save-mini-page-frame"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
sandbox="allow-scripts allow-same-origin allow-downloads"
|
||||
scrolling="yes"
|
||||
onError={() => setPreviewFailed(true)}
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import type { MindSpaceAsset } from '../types';
|
||||
import { buildAssetPreviewFrameConfig } from '../utils/mindspaceCards';
|
||||
|
||||
export type WorkCoverMode = 'thumbnail-first' | 'preview-first' | 'preview-only';
|
||||
export type WorkCoverMode = 'thumbnail-first' | 'thumbnail-only' | 'preview-first' | 'preview-only';
|
||||
|
||||
export function WorkCover({
|
||||
thumbnailUrl,
|
||||
@@ -34,6 +34,7 @@ export function WorkCover({
|
||||
: null;
|
||||
|
||||
const showPreview =
|
||||
coverMode !== 'thumbnail-only' &&
|
||||
Boolean(previewFrame) &&
|
||||
!previewFailed &&
|
||||
(coverMode === 'preview-first' || coverMode === 'preview-only' || thumbFailed);
|
||||
@@ -64,6 +65,10 @@ export function WorkCover({
|
||||
);
|
||||
}
|
||||
|
||||
if (coverMode === 'thumbnail-only') {
|
||||
return <span>{fallbackLabel}</span>;
|
||||
}
|
||||
|
||||
if (previewFrame && !previewFailed) {
|
||||
return (
|
||||
<div className={previewFrameClassName} aria-hidden="true">
|
||||
|
||||
@@ -5838,6 +5838,47 @@ body,
|
||||
color: #2f6f57;
|
||||
}
|
||||
|
||||
.mindspace-delete-plaza {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(24, 33, 29, 0.08);
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mindspace-delete-plaza-posts {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mindspace-delete-plaza-posts li {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.mindspace-delete-plaza-posts span {
|
||||
color: rgba(24, 33, 29, 0.62);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mindspace-delete-plaza-posts a {
|
||||
color: #2f6f57;
|
||||
font-size: 13px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.mindspace-delete-plaza-toggle {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.mindspace-delete-plaza-hint {
|
||||
color: rgba(24, 33, 29, 0.62);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.mindspace-delete-assets {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
@@ -6188,6 +6229,47 @@ body,
|
||||
container-type: size;
|
||||
}
|
||||
|
||||
.mindspace-feed-select.is-top-right {
|
||||
left: auto;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.mindspace-feed-select.is-compact span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mindspace-feed-card-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 12px;
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
.mindspace-feed-card-link {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #2f6f57;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.mindspace-feed-card-link:hover {
|
||||
color: #1f4f3d;
|
||||
}
|
||||
|
||||
.mindspace-all-pages-bulkbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mindspace-all-pages .mindspace-feed-card-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mindspace-feed-select {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
|
||||
@@ -619,6 +619,13 @@ export type MindSpacePage = {
|
||||
updatedAt: number;
|
||||
};
|
||||
|
||||
export type MindSpacePagePlazaPostPreview = {
|
||||
id: string;
|
||||
title: string;
|
||||
status: string;
|
||||
publicationId: string;
|
||||
};
|
||||
|
||||
export type MindSpacePageDeletePreview = {
|
||||
page: {
|
||||
id: string;
|
||||
@@ -633,6 +640,7 @@ export type MindSpacePageDeletePreview = {
|
||||
viewCount: number;
|
||||
} | null;
|
||||
offlinePublicationCount: number;
|
||||
plazaPosts: MindSpacePagePlazaPostPreview[];
|
||||
linkedAssets: Array<{
|
||||
id: string;
|
||||
displayName: string;
|
||||
@@ -650,6 +658,7 @@ export type MindSpacePageDeleteResult = {
|
||||
pageId: string;
|
||||
title: string;
|
||||
offlinedPublicationCount: number;
|
||||
hiddenPlazaPostCount: number;
|
||||
deletedAssetCount: number;
|
||||
freedBytes: number;
|
||||
clearedAgentJobCount: number;
|
||||
|
||||
@@ -36,3 +36,25 @@ export function resolveMindSpaceHomeUrl(): string {
|
||||
}
|
||||
return `${window.location.origin}/space`;
|
||||
}
|
||||
|
||||
/** 已发布页面的独立访问链接(非空间内编辑路由)。未发布时返回 null。 */
|
||||
export function resolveMindSpacePagePublicViewUrl(page: {
|
||||
publicationUrl?: string | null;
|
||||
}): string | null {
|
||||
const value = String(page.publicationUrl ?? '').trim();
|
||||
if (!value) return null;
|
||||
return resolvePublicPageUrl(value);
|
||||
}
|
||||
|
||||
/** @deprecated 使用 resolveMindSpacePagePublicViewUrl */
|
||||
export const resolveMindSpacePageViewUrl = resolveMindSpacePagePublicViewUrl;
|
||||
|
||||
/** 未发布时供本人查看的预览链接(需登录,不可对外分享)。 */
|
||||
export function resolveMindSpacePageOwnerPreviewUrl(page: {
|
||||
id: string;
|
||||
versionNo?: number;
|
||||
updatedAt?: number;
|
||||
}): string {
|
||||
const version = page.versionNo ?? page.updatedAt ?? Date.now();
|
||||
return `${window.location.origin}/api/mindspace/v1/pages/${encodeURIComponent(page.id)}/preview?v=${version}`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { resolveMindSpaceHomeUrl } from './publicUrl';
|
||||
import { resolveMindSpaceHomeUrl, resolveMindSpacePagePublicViewUrl, resolvePublicPageUrl } from './publicUrl';
|
||||
import type { MindSpacePage } from '../types';
|
||||
|
||||
export type SharePayload = {
|
||||
@@ -58,8 +58,8 @@ export const SHARE_CHANNELS: ShareChannel[] = [
|
||||
|
||||
export function buildPageSharePayload(page: MindSpacePage, pagePublicUrl?: string): SharePayload {
|
||||
const publicUrl = pagePublicUrl
|
||||
? new URL(pagePublicUrl, window.location.origin).toString()
|
||||
: window.location.href;
|
||||
? resolvePublicPageUrl(pagePublicUrl)
|
||||
: resolveMindSpacePagePublicViewUrl(page) ?? window.location.href;
|
||||
return {
|
||||
title: page.title || '我的 MindSpace 页面',
|
||||
url: publicUrl,
|
||||
|
||||
Reference in New Issue
Block a user