feat(mindspace): add achievements showcase with paginated management
Memind CI / Test, build, and release guards (push) Successful in 3m28s
Memind CI / Test, build, and release guards (push) Successful in 3m28s
Ship the M成果 page with list/delete UX, fix deletePage for incomplete page records, route /space/achievements, and add M成果 under the WeChat M空间 menu. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -54,6 +54,7 @@ export function normalizeProductRoute(pathname: string, search = '') {
|
||||
export function resolveProductRouteName(pathname: string) {
|
||||
if (pathname === '/') return 'chat';
|
||||
if (pathname === '/space') return 'mindspace_home';
|
||||
if (pathname === '/space/achievements') return 'mindspace_achievements';
|
||||
if (pathname.startsWith('/space/page/')) return 'mindspace_page';
|
||||
if (pathname.startsWith('/feedback/')) return 'feedback_detail';
|
||||
if (pathname === '/feedback') return 'feedback';
|
||||
|
||||
@@ -88,7 +88,9 @@ export {
|
||||
getMindSpacePage,
|
||||
getMindSpacePageDeletePreview,
|
||||
getMindSpacePageLiveRevision,
|
||||
isMindSpacePageNotFoundError,
|
||||
listMindSpacePages,
|
||||
listMindSpaceAchievementPages,
|
||||
openMindSpaceDraftPreviewWindow,
|
||||
regenerateMindSpacePageThumbnail,
|
||||
rewriteMindSpacePageDownloadLinks,
|
||||
|
||||
@@ -29,6 +29,14 @@ export async function getMindSpacePageDeletePreview(
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export function isMindSpacePageNotFoundError(err: unknown): boolean {
|
||||
return (
|
||||
err instanceof ApiError &&
|
||||
err.status === 404 &&
|
||||
(err.code === 'page_not_found' || err.message === '页面不存在')
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteMindSpacePage(
|
||||
pageId: string,
|
||||
options?: { removeFromPlaza?: boolean },
|
||||
@@ -61,6 +69,26 @@ export async function listMindSpacePages(options?: {
|
||||
return { items: result.data, page: result.page ?? {} };
|
||||
}
|
||||
|
||||
export async function listMindSpaceAchievementPages(options?: {
|
||||
status?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
categoryCode?: string;
|
||||
includeStats?: boolean;
|
||||
}): 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);
|
||||
if (options?.includeStats) params.set('include_stats', '1');
|
||||
const query = params.toString() ? `?${params.toString()}` : '';
|
||||
const result = await apiFetch<{ data: MindSpacePage[]; page?: MindSpaceListPage }>(
|
||||
`/mindspace/v1/pages/achievements${query}`,
|
||||
);
|
||||
return { items: result.data, page: result.page ?? {} };
|
||||
}
|
||||
|
||||
export async function getMindSpacePage(pageId: string): Promise<MindSpacePage> {
|
||||
const result = await apiFetch<{ data: MindSpacePage }>(
|
||||
`/mindspace/v1/pages/${encodeURIComponent(pageId)}`,
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import type { MindSpacePage } from '../types';
|
||||
|
||||
const PAGE_STATUS_LABELS: Record<string, string> = {
|
||||
draft: '草稿',
|
||||
reviewing: '待检查',
|
||||
risk_found: '发现风险',
|
||||
ready: '可发布',
|
||||
published: '已公开',
|
||||
protected: '受保护',
|
||||
expired: '已过期',
|
||||
offline: '已下线',
|
||||
};
|
||||
|
||||
function formatDateTime(timestamp: number) {
|
||||
return new Date(timestamp).toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
|
||||
function formatDateHeading(timestamp: number) {
|
||||
return new Date(timestamp).toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
weekday: 'short',
|
||||
});
|
||||
}
|
||||
|
||||
function formatMetric(value: number | null | undefined) {
|
||||
if (value == null) return '—';
|
||||
return value.toLocaleString('zh-CN');
|
||||
}
|
||||
|
||||
function formatClickMetric(page: MindSpacePage) {
|
||||
if (page.clickCount == null) return '—';
|
||||
return formatMetric(page.clickCount);
|
||||
}
|
||||
|
||||
function groupPagesByCreatedDate(pages: MindSpacePage[]) {
|
||||
const groups = new Map<string, MindSpacePage[]>();
|
||||
for (const page of pages) {
|
||||
const key = new Date(page.createdAt).toISOString().slice(0, 10);
|
||||
const bucket = groups.get(key);
|
||||
if (bucket) bucket.push(page);
|
||||
else groups.set(key, [page]);
|
||||
}
|
||||
return [...groups.entries()]
|
||||
.sort(([left], [right]) => right.localeCompare(left))
|
||||
.map(([dateKey, items]) => ({
|
||||
dateKey,
|
||||
heading: formatDateHeading(items[0]?.createdAt ?? Date.parse(`${dateKey}T00:00:00`)),
|
||||
items,
|
||||
}));
|
||||
}
|
||||
|
||||
type MindSpaceAchievementsPanelProps = {
|
||||
items: MindSpacePage[];
|
||||
total: number;
|
||||
loading: boolean;
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
selectedIds: string[];
|
||||
deleting: boolean;
|
||||
deletingPageId?: string | null;
|
||||
onBack: () => void;
|
||||
onOpenPage: (page: MindSpacePage) => void;
|
||||
onShowPage: (pageId: string) => void;
|
||||
onTogglePage: (pageId: string, checked: boolean) => void;
|
||||
onToggleDateGroup: (pageIds: string[], checked: boolean) => void;
|
||||
onToggleAll: () => void;
|
||||
onClearSelection: () => void;
|
||||
onDeleteSelected: () => void;
|
||||
onDeletePage: (page: MindSpacePage) => void;
|
||||
onPrevPage: () => void;
|
||||
onNextPage: () => void;
|
||||
onFirstPage: () => void;
|
||||
};
|
||||
|
||||
export function MindSpaceAchievementsPanel({
|
||||
items,
|
||||
total,
|
||||
loading,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
selectedIds,
|
||||
deleting,
|
||||
deletingPageId,
|
||||
onBack,
|
||||
onOpenPage,
|
||||
onShowPage,
|
||||
onTogglePage,
|
||||
onToggleDateGroup,
|
||||
onToggleAll,
|
||||
onClearSelection,
|
||||
onDeleteSelected,
|
||||
onDeletePage,
|
||||
onPrevPage,
|
||||
onNextPage,
|
||||
onFirstPage,
|
||||
}: MindSpaceAchievementsPanelProps) {
|
||||
const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
||||
const grouped = useMemo(() => groupPagesByCreatedDate(items), [items]);
|
||||
const allVisibleSelected =
|
||||
items.length > 0 && items.every((page) => selectedSet.has(page.id));
|
||||
const pageCount = Math.max(1, Math.ceil(total / pageSize));
|
||||
|
||||
return (
|
||||
<section className="mindspace-independent-page mindspace-achievements">
|
||||
<div className="mindspace-section-heading">
|
||||
<div>
|
||||
<button type="button" className="mindspace-inline-back" onClick={onBack}>
|
||||
返回空间首页
|
||||
</button>
|
||||
<p className="mindspace-eyebrow">MY ACHIEVEMENTS</p>
|
||||
<h2>我的成果展示</h2>
|
||||
</div>
|
||||
<span>{total} 个页面</span>
|
||||
</div>
|
||||
|
||||
<p className="mindspace-achievements-intro">
|
||||
按创建日期汇总你制作过的页面,每页 {pageSize} 条。浏览量来自公开页访问统计;点击量需开启 Umami 后才会显示。
|
||||
</p>
|
||||
|
||||
{items.length > 0 && (
|
||||
<div className="mindspace-asset-bulkbar mindspace-achievements-bulkbar">
|
||||
<button type="button" onClick={onToggleAll} disabled={deleting || items.length === 0}>
|
||||
{allVisibleSelected ? '取消全选' : '全选本页'}
|
||||
</button>
|
||||
<span>已选 {selectedIds.length} 项</span>
|
||||
{selectedIds.length > 0 && (
|
||||
<button type="button" onClick={onClearSelection} disabled={deleting}>
|
||||
清空
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-asset-bulk-delete"
|
||||
onClick={onDeleteSelected}
|
||||
disabled={selectedIds.length === 0 || deleting}
|
||||
>
|
||||
{deleting ? '删除中…' : '删除选中'}
|
||||
</button>
|
||||
{total > pageSize && pageIndex > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-achievements-back-page"
|
||||
disabled={loading}
|
||||
onClick={onFirstPage}
|
||||
>
|
||||
返回
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading && items.length === 0 ? (
|
||||
<div className="mindspace-state">正在加载成果列表…</div>
|
||||
) : total > 0 ? (
|
||||
<>
|
||||
<div className="mindspace-achievements-groups">
|
||||
{grouped.map((group) => {
|
||||
const groupIds = group.items.map((page) => page.id);
|
||||
const groupSelected =
|
||||
groupIds.length > 0 && groupIds.every((id) => selectedSet.has(id));
|
||||
return (
|
||||
<section className="mindspace-achievements-group" key={group.dateKey}>
|
||||
<div className="mindspace-achievements-group-heading">
|
||||
<h3>{group.heading}</h3>
|
||||
<label className="mindspace-achievements-group-select">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={groupSelected}
|
||||
disabled={deleting}
|
||||
onChange={(event) =>
|
||||
onToggleDateGroup(groupIds, event.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
<span>{groupSelected ? '取消全选本日' : '全选本日'}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="mindspace-achievements-table-wrap">
|
||||
<table className="mindspace-achievements-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" className="mindspace-achievements-col-select">
|
||||
选择
|
||||
</th>
|
||||
<th scope="col">标题</th>
|
||||
<th scope="col">状态</th>
|
||||
<th scope="col">创建时间</th>
|
||||
<th scope="col">最后修改</th>
|
||||
<th scope="col">浏览量</th>
|
||||
<th scope="col">点击量</th>
|
||||
<th scope="col">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{group.items.map((page) => (
|
||||
<tr key={page.id}>
|
||||
<td className="mindspace-achievements-col-select">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedSet.has(page.id)}
|
||||
disabled={deleting}
|
||||
aria-label={`选择 ${page.title}`}
|
||||
onChange={(event) =>
|
||||
onTogglePage(page.id, event.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<td className="mindspace-achievements-title">
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-achievements-title-link"
|
||||
onClick={() => void onOpenPage(page)}
|
||||
>
|
||||
{page.title}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-achievements-detail-link"
|
||||
onClick={() => onShowPage(page.id)}
|
||||
>
|
||||
详情
|
||||
</button>
|
||||
</td>
|
||||
<td>{PAGE_STATUS_LABELS[page.status] ?? page.status}</td>
|
||||
<td>{formatDateTime(page.createdAt)}</td>
|
||||
<td>{formatDateTime(page.updatedAt)}</td>
|
||||
<td>{formatMetric(page.viewCount ?? 0)}</td>
|
||||
<td>{formatClickMetric(page)}</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
className="mindspace-achievements-delete-link"
|
||||
disabled={deleting || deletingPageId === page.id}
|
||||
onClick={() => onDeletePage(page)}
|
||||
>
|
||||
{deletingPageId === page.id ? '删除中…' : '删除'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{total > pageSize && (
|
||||
<div className="mindspace-pagination mindspace-achievements-pagination">
|
||||
<button type="button" disabled={pageIndex <= 0 || loading} onClick={onPrevPage}>
|
||||
上一页
|
||||
</button>
|
||||
<span>
|
||||
第 {pageIndex + 1} / {pageCount} 页
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={loading || (pageIndex + 1) * pageSize >= total}
|
||||
onClick={onNextPage}
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="mindspace-recent-work-empty">
|
||||
<p>上传资料或从聊天保存页面后,你的成果会按日期集中展示在这里。</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -15,10 +15,12 @@ import {
|
||||
getMindSpacePage,
|
||||
getMindSpacePageDeletePreview,
|
||||
ignoreMindSpaceScheduleReminder,
|
||||
isMindSpacePageNotFoundError,
|
||||
listMindSpaceAgentJobs,
|
||||
listMindSpaceAssets,
|
||||
listMindSpaceCleanupItems,
|
||||
listMindSpacePages,
|
||||
listMindSpaceAchievementPages,
|
||||
retryMindSpaceAgentJob,
|
||||
runMindSpaceAgentJob,
|
||||
runMindSpaceCleanup,
|
||||
@@ -55,6 +57,7 @@ import {
|
||||
} from '../utils/mindspaceCards';
|
||||
import { PREVIEW_ASSETS, PREVIEW_PAGES, PREVIEW_SPACE } from '../dev/mindspacePreviewData';
|
||||
import { MindSpaceFeedCard } from './MindSpaceFeedCard';
|
||||
import { MindSpaceAchievementsPanel } from './MindSpaceAchievementsPanel';
|
||||
import { MindSpacePageDetail } from './MindSpacePageDetail';
|
||||
import { MindSpaceModal } from './MindSpaceModal';
|
||||
import { MindSpaceDeletePlazaOption } from './MindSpaceDeletePlazaOption';
|
||||
@@ -83,6 +86,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;
|
||||
const ACHIEVEMENTS_PAGE_SIZE = 20;
|
||||
|
||||
function sortPagesByCreatedAt(pages: MindSpacePage[]) {
|
||||
return [...pages].sort((left, right) => {
|
||||
@@ -499,6 +503,7 @@ function canGenerateWithAgent(asset: MindSpaceAsset) {
|
||||
|
||||
type MindSpaceRouteSync = {
|
||||
pushHome: () => void;
|
||||
pushAchievements: () => void;
|
||||
pushCategory: (code: MindSpaceSaveCategory | MindSpaceCategory['code']) => void;
|
||||
pushPage: (pageId: string) => void;
|
||||
};
|
||||
@@ -528,6 +533,7 @@ export function MindSpaceView({
|
||||
previewMode = false,
|
||||
initialPageId,
|
||||
initialCategoryCode,
|
||||
initialAchievementsOpen = false,
|
||||
onBack,
|
||||
onLogout,
|
||||
onOpenFeedback,
|
||||
@@ -537,6 +543,7 @@ export function MindSpaceView({
|
||||
previewMode?: boolean;
|
||||
initialPageId?: string | null;
|
||||
initialCategoryCode?: MindSpaceSaveCategory | null;
|
||||
initialAchievementsOpen?: boolean;
|
||||
onBack: () => void;
|
||||
onLogout: () => void;
|
||||
onOpenFeedback?: () => void;
|
||||
@@ -589,6 +596,13 @@ export function MindSpaceView({
|
||||
const [allPagesTotal, setAllPagesTotal] = useState(0);
|
||||
const [allPagesPageIndex, setAllPagesPageIndex] = useState(0);
|
||||
const [allPagesLoading, setAllPagesLoading] = useState(false);
|
||||
const [achievementsOpen, setAchievementsOpen] = useState(initialAchievementsOpen);
|
||||
const [achievementsItems, setAchievementsItems] = useState<MindSpacePage[]>([]);
|
||||
const [achievementsTotal, setAchievementsTotal] = useState(0);
|
||||
const [achievementsPageIndex, setAchievementsPageIndex] = useState(0);
|
||||
const [achievementsLoading, setAchievementsLoading] = useState(false);
|
||||
const [selectedAchievementPageIds, setSelectedAchievementPageIds] = useState<string[]>([]);
|
||||
const [bulkDeletingAchievements, setBulkDeletingAchievements] = useState(false);
|
||||
const [selectedAllPageIds, setSelectedAllPageIds] = useState<string[]>([]);
|
||||
const [bulkDeletingAllPages, setBulkDeletingAllPages] = useState(false);
|
||||
const [selectedScheduleReminderIds, setSelectedScheduleReminderIds] = useState<string[]>([]);
|
||||
@@ -605,7 +619,7 @@ export function MindSpaceView({
|
||||
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 [bulkPageDeleteSource, setBulkPageDeleteSource] = useState<'all' | 'draft' | 'achievements'>('all');
|
||||
const [bulkPageDeletePreviews, setBulkPageDeletePreviews] = useState<
|
||||
MindSpacePageDeletePreview[] | null
|
||||
>(null);
|
||||
@@ -649,11 +663,11 @@ export function MindSpaceView({
|
||||
}, [selectedPageId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!allPagesOpen && !selectedPageId) return;
|
||||
if (!allPagesOpen && !achievementsOpen && !selectedPageId) return;
|
||||
window.requestAnimationFrame(() => {
|
||||
window.scrollTo({ top: 0, left: 0 });
|
||||
});
|
||||
}, [allPagesOpen, selectedPageId]);
|
||||
}, [allPagesOpen, achievementsOpen, selectedPageId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedPageId || !session?.id || previewMode || pageFullscreenPreviewOpen) return;
|
||||
@@ -802,8 +816,30 @@ export function MindSpaceView({
|
||||
setPageDeleteTarget(null);
|
||||
setPageDeletePreview(null);
|
||||
setPageDeleteRemoveFromPlaza(false);
|
||||
setSelectedAchievementPageIds((ids) => ids.filter((id) => id !== pageDeleteTarget.id));
|
||||
setAchievementsItems((items) => items.filter((page) => page.id !== pageDeleteTarget.id));
|
||||
setAchievementsTotal((total) => Math.max(0, total - 1));
|
||||
await refreshMindSpaceSnapshot({ quota: result.quota, freedBytes: result.freedBytes });
|
||||
if (achievementsOpen) {
|
||||
await loadAchievementsPage(achievementsPageIndex);
|
||||
}
|
||||
} catch (err) {
|
||||
if (isMindSpacePageNotFoundError(err)) {
|
||||
if (selectedPageId === pageDeleteTarget.id) {
|
||||
closePage();
|
||||
}
|
||||
setPageDeleteTarget(null);
|
||||
setPageDeletePreview(null);
|
||||
setPageDeleteRemoveFromPlaza(false);
|
||||
setSelectedAchievementPageIds((ids) => ids.filter((id) => id !== pageDeleteTarget.id));
|
||||
setAchievementsItems((items) => items.filter((page) => page.id !== pageDeleteTarget.id));
|
||||
setAchievementsTotal((total) => Math.max(0, total - 1));
|
||||
await refreshMindSpaceSnapshot();
|
||||
if (achievementsOpen) {
|
||||
await loadAchievementsPage(achievementsPageIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
setError(err instanceof Error ? err.message : '删除页面失败');
|
||||
} finally {
|
||||
setDeletingPageId(null);
|
||||
@@ -959,12 +995,58 @@ export function MindSpaceView({
|
||||
setSelectedPageId(null);
|
||||
setNewPageOpen(false);
|
||||
setAgentJobsPanelOpen(false);
|
||||
setAchievementsOpen(false);
|
||||
setAllPagesOpen(true);
|
||||
setSelectedAllPageIds([]);
|
||||
routeSync?.pushHome();
|
||||
void loadAllPagesPage(0);
|
||||
};
|
||||
|
||||
const loadAchievementsPage = async (pageIndex: number) => {
|
||||
if (previewMode) {
|
||||
setAchievementsItems(PREVIEW_PAGES);
|
||||
setAchievementsTotal(PREVIEW_PAGES.length);
|
||||
setAchievementsPageIndex(0);
|
||||
setSelectedAchievementPageIds([]);
|
||||
return;
|
||||
}
|
||||
setAchievementsLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const { items, page } = await listMindSpaceAchievementPages({
|
||||
limit: ACHIEVEMENTS_PAGE_SIZE,
|
||||
offset: pageIndex * ACHIEVEMENTS_PAGE_SIZE,
|
||||
includeStats: false,
|
||||
});
|
||||
setAchievementsItems(items);
|
||||
setAchievementsPageIndex(pageIndex);
|
||||
setAchievementsTotal(page.total ?? items.length);
|
||||
setSelectedAchievementPageIds([]);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : '成果列表加载失败');
|
||||
} finally {
|
||||
setAchievementsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openAchievementsPanel = () => {
|
||||
setSelectedCategory(null);
|
||||
setSelectedPageId(null);
|
||||
setNewPageOpen(false);
|
||||
setAgentJobsPanelOpen(false);
|
||||
setAllPagesOpen(false);
|
||||
setAchievementsOpen(true);
|
||||
setSelectedAchievementPageIds([]);
|
||||
routeSync?.pushAchievements();
|
||||
void loadAchievementsPage(0);
|
||||
};
|
||||
|
||||
const closeAchievementsPanel = () => {
|
||||
setAchievementsOpen(false);
|
||||
setSelectedAchievementPageIds([]);
|
||||
routeSync?.pushHome();
|
||||
};
|
||||
|
||||
const refreshAgentJobsSummary = async () => {
|
||||
if (previewMode) return;
|
||||
const { page } = await listMindSpaceAgentJobs({ limit: 1, offset: 0 });
|
||||
@@ -999,6 +1081,7 @@ export function MindSpaceView({
|
||||
setSelectedPageId(null);
|
||||
setNewPageOpen(false);
|
||||
setAllPagesOpen(false);
|
||||
setAchievementsOpen(false);
|
||||
setAgentJobsPanelOpen(true);
|
||||
routeSync?.pushHome();
|
||||
void loadAgentJobsPage(0);
|
||||
@@ -1066,6 +1149,9 @@ export function MindSpaceView({
|
||||
if (allPagesOpen) {
|
||||
await loadAllPagesPage(allPagesPageIndex);
|
||||
}
|
||||
if (achievementsOpen) {
|
||||
await loadAchievementsPage(achievementsPageIndex);
|
||||
}
|
||||
} catch {
|
||||
// ignore background list refresh errors
|
||||
}
|
||||
@@ -1114,7 +1200,7 @@ export function MindSpaceView({
|
||||
useEffect(() => {
|
||||
if (previewMode) return;
|
||||
const refreshSchedule = () => {
|
||||
if (!selectedCategory && !selectedPageId && !newPageOpen && !agentJobsPanelOpen && !allPagesOpen) {
|
||||
if (!selectedCategory && !selectedPageId && !newPageOpen && !agentJobsPanelOpen && !allPagesOpen && !achievementsOpen) {
|
||||
void refreshSpaceQuietly();
|
||||
}
|
||||
};
|
||||
@@ -1130,6 +1216,7 @@ export function MindSpaceView({
|
||||
newPageOpen,
|
||||
agentJobsPanelOpen,
|
||||
allPagesOpen,
|
||||
achievementsOpen,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1137,10 +1224,21 @@ export function MindSpaceView({
|
||||
}, [initialPageId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!space || !initialCategoryCode || initialCategoryCode === 'draft') return;
|
||||
if (!space || !initialCategoryCode || initialCategoryCode === 'draft' || initialAchievementsOpen) return;
|
||||
const category = space.categories.find((item) => item.code === initialCategoryCode);
|
||||
if (category) void openCategory(category);
|
||||
}, [space, initialCategoryCode]);
|
||||
}, [space, initialCategoryCode, initialAchievementsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialAchievementsOpen || previewMode) return;
|
||||
setSelectedCategory(null);
|
||||
setSelectedPageId(null);
|
||||
setNewPageOpen(false);
|
||||
setAgentJobsPanelOpen(false);
|
||||
setAllPagesOpen(false);
|
||||
setAchievementsOpen(true);
|
||||
void loadAchievementsPage(0);
|
||||
}, [initialAchievementsOpen, previewMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (previewMode || !agentJob || !['queued', 'running'].includes(agentJob.status)) return;
|
||||
@@ -1167,6 +1265,7 @@ export function MindSpaceView({
|
||||
const openCategory = async (category: MindSpaceCategory) => {
|
||||
setAgentJobsPanelOpen(false);
|
||||
setAllPagesOpen(false);
|
||||
setAchievementsOpen(false);
|
||||
setSelectedCategory(category);
|
||||
setSelectedPageId(null);
|
||||
setNewPageOpen(false);
|
||||
@@ -1586,13 +1685,13 @@ export function MindSpaceView({
|
||||
}, [bulkPageDeletePreviews]);
|
||||
|
||||
const closeBulkPageDeleteDialog = () => {
|
||||
if (bulkDeletingAllPages || bulkDeletingDraftPages) return;
|
||||
if (bulkDeletingAllPages || bulkDeletingDraftPages || bulkDeletingAchievements) return;
|
||||
setBulkPageDeleteOpen(false);
|
||||
setBulkPageDeletePreviews(null);
|
||||
setBulkPageDeleteRemoveFromPlaza(false);
|
||||
};
|
||||
|
||||
const startBulkPageDelete = async (pageIds: string[], source: 'all' | 'draft') => {
|
||||
const startBulkPageDelete = async (pageIds: string[], source: 'all' | 'draft' | 'achievements') => {
|
||||
if (pageIds.length === 0) return;
|
||||
if (previewMode) {
|
||||
setError('预览模式下无法删除页面');
|
||||
@@ -1632,7 +1731,11 @@ export function MindSpaceView({
|
||||
if (!bulkPageDeletePreviews || bulkPageDeletePreviews.length === 0) return;
|
||||
const deletingIds = bulkPageDeletePreviews.map((preview) => preview.page.id);
|
||||
const setBulkDeleting =
|
||||
bulkPageDeleteSource === 'all' ? setBulkDeletingAllPages : setBulkDeletingDraftPages;
|
||||
bulkPageDeleteSource === 'all'
|
||||
? setBulkDeletingAllPages
|
||||
: bulkPageDeleteSource === 'draft'
|
||||
? setBulkDeletingDraftPages
|
||||
: setBulkDeletingAchievements;
|
||||
setBulkDeleting(true);
|
||||
setError(null);
|
||||
const failedIds: string[] = [];
|
||||
@@ -1645,6 +1748,10 @@ export function MindSpaceView({
|
||||
});
|
||||
if (selectedPageId === preview.page.id) closePage();
|
||||
} catch (err) {
|
||||
if (isMindSpacePageNotFoundError(err)) {
|
||||
if (selectedPageId === preview.page.id) closePage();
|
||||
continue;
|
||||
}
|
||||
failedIds.push(preview.page.id);
|
||||
failedNames.push(preview.page.title);
|
||||
console.error('[MindSpace] delete page failed:', preview.page.id, err);
|
||||
@@ -1653,8 +1760,10 @@ export function MindSpaceView({
|
||||
const deletedIds = deletingIds.filter((id) => !failedIds.includes(id));
|
||||
if (bulkPageDeleteSource === 'all') {
|
||||
setSelectedAllPageIds((ids) => ids.filter((id) => failedIds.includes(id)));
|
||||
} else {
|
||||
} else if (bulkPageDeleteSource === 'draft') {
|
||||
setSelectedDraftPageIds((ids) => ids.filter((id) => failedIds.includes(id)));
|
||||
} else {
|
||||
setSelectedAchievementPageIds((ids) => ids.filter((id) => failedIds.includes(id)));
|
||||
}
|
||||
setBulkPageDeleteOpen(false);
|
||||
setBulkPageDeletePreviews(null);
|
||||
@@ -1668,6 +1777,12 @@ export function MindSpaceView({
|
||||
: allPagesPageIndex;
|
||||
await loadAllPagesPage(nextPageIndex);
|
||||
}
|
||||
if (bulkPageDeleteSource === 'achievements' && deletedIds.length > 0) {
|
||||
setAchievementsItems((items) => items.filter((page) => !deletedIds.includes(page.id)));
|
||||
setAchievementsTotal((total) => Math.max(0, total - deletedIds.length));
|
||||
setSelectedAchievementPageIds((ids) => ids.filter((id) => !deletedIds.includes(id)));
|
||||
await loadAchievementsPage(achievementsPageIndex);
|
||||
}
|
||||
if (failedNames.length > 0) {
|
||||
setError(`有 ${failedNames.length} 个页面删除失败:${failedNames.slice(0, 3).join('、')}`);
|
||||
}
|
||||
@@ -1698,6 +1813,45 @@ export function MindSpaceView({
|
||||
void startBulkPageDelete([...selectedAllPageIds], 'all');
|
||||
};
|
||||
|
||||
const selectedAchievementPageSet = useMemo(
|
||||
() => new Set(selectedAchievementPageIds),
|
||||
[selectedAchievementPageIds],
|
||||
);
|
||||
|
||||
const toggleAchievementPageSelected = (pageId: string, checked: boolean) => {
|
||||
setSelectedAchievementPageIds((ids) =>
|
||||
checked ? (ids.includes(pageId) ? ids : [...ids, pageId]) : ids.filter((id) => id !== pageId),
|
||||
);
|
||||
};
|
||||
|
||||
const toggleAchievementDateGroupSelected = (pageIds: string[], checked: boolean) => {
|
||||
setSelectedAchievementPageIds((ids) => {
|
||||
if (!checked) {
|
||||
const remove = new Set(pageIds);
|
||||
return ids.filter((id) => !remove.has(id));
|
||||
}
|
||||
return [...new Set([...ids, ...pageIds])];
|
||||
});
|
||||
};
|
||||
|
||||
const toggleAllAchievementPagesSelected = () => {
|
||||
const allSelected =
|
||||
achievementsItems.length > 0 &&
|
||||
achievementsItems.every((page) => selectedAchievementPageSet.has(page.id));
|
||||
if (allSelected) {
|
||||
const visibleIds = new Set(achievementsItems.map((page) => page.id));
|
||||
setSelectedAchievementPageIds((ids) => ids.filter((id) => !visibleIds.has(id)));
|
||||
return;
|
||||
}
|
||||
setSelectedAchievementPageIds((ids) => [
|
||||
...new Set([...ids, ...achievementsItems.map((page) => page.id)]),
|
||||
]);
|
||||
};
|
||||
|
||||
const removeSelectedAchievementPages = () => {
|
||||
void startBulkPageDelete([...selectedAchievementPageIds], 'achievements');
|
||||
};
|
||||
|
||||
const allPagesPageCount = Math.max(1, Math.ceil(allPagesTotal / ALL_PAGES_PAGE_SIZE));
|
||||
|
||||
const previewAsset = useMemo(
|
||||
@@ -1926,7 +2080,13 @@ export function MindSpaceView({
|
||||
);
|
||||
|
||||
const showHomeModules =
|
||||
space && !selectedCategory && !selectedPageId && !newPageOpen && !agentJobsPanelOpen && !allPagesOpen;
|
||||
space &&
|
||||
!selectedCategory &&
|
||||
!selectedPageId &&
|
||||
!newPageOpen &&
|
||||
!agentJobsPanelOpen &&
|
||||
!allPagesOpen &&
|
||||
!achievementsOpen;
|
||||
const rawUpcomingReminders = previewMode ? [] : space?.schedule?.upcomingReminders ?? [];
|
||||
const scheduleReminders = previewMode
|
||||
? PREVIEW_SCHEDULE_REMINDERS.map((item) => ({ ...item }))
|
||||
@@ -2066,7 +2226,7 @@ export function MindSpaceView({
|
||||
</header>
|
||||
|
||||
<main className="mindspace-content">
|
||||
{!allPagesOpen && (
|
||||
{!allPagesOpen && !achievementsOpen && (
|
||||
<section className="mindspace-hero">
|
||||
<div className="mindspace-hero-left">
|
||||
<p className="mindspace-eyebrow">MINDSPACE</p>
|
||||
@@ -2088,6 +2248,7 @@ export function MindSpaceView({
|
||||
setSelectedCategory(null);
|
||||
setSelectedPageId(null);
|
||||
setAllPagesOpen(false);
|
||||
setAchievementsOpen(false);
|
||||
setNewPageOpen(true);
|
||||
}}
|
||||
>
|
||||
@@ -2362,7 +2523,7 @@ export function MindSpaceView({
|
||||
<section className="mindspace-page-reader">
|
||||
<header className="mindspace-page-reader-bar">
|
||||
<button type="button" className="mindspace-inline-back" onClick={closePage}>
|
||||
{allPagesOpen ? '返回全部页面' : '返回空间首页'}
|
||||
{allPagesOpen ? '返回全部页面' : achievementsOpen ? '返回成果展示' : '返回空间首页'}
|
||||
</button>
|
||||
<span>站内浏览</span>
|
||||
</header>
|
||||
@@ -2444,6 +2605,29 @@ export function MindSpaceView({
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
) : achievementsOpen ? (
|
||||
<MindSpaceAchievementsPanel
|
||||
items={achievementsItems}
|
||||
total={achievementsTotal}
|
||||
loading={achievementsLoading}
|
||||
pageIndex={achievementsPageIndex}
|
||||
pageSize={ACHIEVEMENTS_PAGE_SIZE}
|
||||
selectedIds={selectedAchievementPageIds}
|
||||
deleting={bulkDeletingAchievements}
|
||||
deletingPageId={deletingPageId}
|
||||
onBack={closeAchievementsPanel}
|
||||
onOpenPage={(page) => void openPageViewInNewTab(page)}
|
||||
onShowPage={(pageId) => showPage(pageId)}
|
||||
onTogglePage={toggleAchievementPageSelected}
|
||||
onToggleDateGroup={toggleAchievementDateGroupSelected}
|
||||
onToggleAll={toggleAllAchievementPagesSelected}
|
||||
onClearSelection={() => setSelectedAchievementPageIds([])}
|
||||
onDeleteSelected={() => void removeSelectedAchievementPages()}
|
||||
onDeletePage={(page) => void openPageDeleteDialog(page)}
|
||||
onPrevPage={() => void loadAchievementsPage(achievementsPageIndex - 1)}
|
||||
onNextPage={() => void loadAchievementsPage(achievementsPageIndex + 1)}
|
||||
onFirstPage={() => void loadAchievementsPage(0)}
|
||||
/>
|
||||
) : allPagesOpen ? (
|
||||
<section className="mindspace-independent-page mindspace-all-pages">
|
||||
<div className="mindspace-section-heading">
|
||||
@@ -3023,6 +3207,17 @@ export function MindSpaceView({
|
||||
))}
|
||||
</div>
|
||||
<div className="mindspace-grid-secondary">
|
||||
<article className="mindspace-card mindspace-card-compact mindspace-card-achievements">
|
||||
<div className="mindspace-card-top">
|
||||
<span className="mindspace-card-code">SHOWCASE</span>
|
||||
<span>{recentPagesTotal} 项</span>
|
||||
</div>
|
||||
<h3>我的成果展示</h3>
|
||||
<p>按日期查看页面标题、创建/修改时间与访问数据。</p>
|
||||
<button type="button" onClick={openAchievementsPanel}>
|
||||
查看成果
|
||||
</button>
|
||||
</article>
|
||||
{space.categories
|
||||
.filter((category) => category.code === 'draft')
|
||||
.map((category) => (
|
||||
@@ -3048,7 +3243,7 @@ export function MindSpaceView({
|
||||
</section>
|
||||
)}
|
||||
|
||||
{!selectedCategory && !selectedPageId && !newPageOpen && !agentJobsPanelOpen && !allPagesOpen && (
|
||||
{!selectedCategory && !selectedPageId && !newPageOpen && !agentJobsPanelOpen && !allPagesOpen && !achievementsOpen && (
|
||||
<section className="mindspace-section mindspace-recent-work">
|
||||
<div className="mindspace-section-heading">
|
||||
<div>
|
||||
@@ -3433,7 +3628,7 @@ export function MindSpaceView({
|
||||
eyebrow="DELETE PAGES"
|
||||
className="mindspace-delete-dialog"
|
||||
disableClose={
|
||||
bulkPageDeleteLoading || bulkDeletingAllPages || bulkDeletingDraftPages
|
||||
bulkPageDeleteLoading || bulkDeletingAllPages || bulkDeletingDraftPages || bulkDeletingAchievements
|
||||
}
|
||||
>
|
||||
{bulkPageDeleteLoading && <div className="mindspace-state">正在分析连带内容…</div>}
|
||||
@@ -3460,7 +3655,7 @@ export function MindSpaceView({
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeBulkPageDeleteDialog}
|
||||
disabled={bulkDeletingAllPages || bulkDeletingDraftPages}
|
||||
disabled={bulkDeletingAllPages || bulkDeletingDraftPages || bulkDeletingAchievements}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
@@ -3468,9 +3663,11 @@ export function MindSpaceView({
|
||||
type="button"
|
||||
className="mindspace-primary"
|
||||
onClick={() => void confirmBulkPageDelete()}
|
||||
disabled={bulkDeletingAllPages || bulkDeletingDraftPages}
|
||||
disabled={bulkDeletingAllPages || bulkDeletingDraftPages || bulkDeletingAchievements}
|
||||
>
|
||||
{bulkDeletingAllPages || bulkDeletingDraftPages ? '删除中…' : '确认删除'}
|
||||
{bulkDeletingAllPages || bulkDeletingDraftPages || bulkDeletingAchievements
|
||||
? '删除中…'
|
||||
: '确认删除'}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -111,6 +111,8 @@ export const PREVIEW_PAGES: MindSpacePage[] = [
|
||||
currentVersionId: 'page-1-v2',
|
||||
versionNo: 2,
|
||||
contentFormat: 'markdown',
|
||||
viewCount: 86,
|
||||
clickCount: 5,
|
||||
createdAt: Date.now() - 172_800_000,
|
||||
updatedAt: Date.now() - 43_200_000,
|
||||
},
|
||||
@@ -130,6 +132,8 @@ export const PREVIEW_PAGES: MindSpacePage[] = [
|
||||
currentVersionId: 'page-2-v1',
|
||||
versionNo: 1,
|
||||
contentFormat: 'html',
|
||||
viewCount: 12,
|
||||
clickCount: 2,
|
||||
createdAt: Date.now() - 259_200_000,
|
||||
updatedAt: Date.now() - 86_400_000,
|
||||
},
|
||||
|
||||
+206
@@ -7964,6 +7964,212 @@ body,
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-intro {
|
||||
margin: 0 0 18px;
|
||||
color: rgba(24, 33, 29, 0.72);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.mindspace-achievements-bulkbar {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-bulkbar .mindspace-achievements-back-page {
|
||||
margin-left: auto;
|
||||
padding: 5px 11px;
|
||||
border: 1px solid rgba(47, 111, 87, 0.18);
|
||||
border-radius: 999px;
|
||||
color: #2f6f57;
|
||||
background: rgba(235, 248, 240, 0.72);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.mindspace-achievements-bulkbar .mindspace-achievements-back-page:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.mindspace-achievements-pagination {
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-pagination button {
|
||||
padding: 5px 11px;
|
||||
border: 1px solid rgba(24, 33, 29, 0.1);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 252, 244, 0.92);
|
||||
color: #2f6f57;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.mindspace-achievements-pagination span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-groups {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 22px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-group h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #18211d;
|
||||
}
|
||||
|
||||
.mindspace-achievements-group-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-group-select {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: rgba(24, 33, 29, 0.72);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mindspace-achievements-group-select input {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
accent-color: #2f6f57;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mindspace-achievements-col-select {
|
||||
width: 52px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mindspace-achievements-col-select input {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
accent-color: #2f6f57;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mindspace-achievements-delete-link {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #9b3d2f;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-delete-link:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.mindspace-achievements-load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 18px 0 8px;
|
||||
color: rgba(24, 33, 29, 0.62);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-load-more button {
|
||||
padding: 8px 14px;
|
||||
border: 1px solid rgba(47, 111, 87, 0.24);
|
||||
border-radius: 999px;
|
||||
background: rgba(235, 248, 240, 0.8);
|
||||
color: #1f4f3d;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mindspace-achievements-table-wrap {
|
||||
overflow-x: auto;
|
||||
border: 1px solid rgba(24, 33, 29, 0.08);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 252, 244, 0.92);
|
||||
}
|
||||
|
||||
.mindspace-achievements-table {
|
||||
width: 100%;
|
||||
min-width: 760px;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-table th,
|
||||
.mindspace-achievements-table td {
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid rgba(24, 33, 29, 0.08);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.mindspace-achievements-table th {
|
||||
color: rgba(24, 33, 29, 0.62);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
background: rgba(245, 240, 229, 0.72);
|
||||
}
|
||||
|
||||
.mindspace-achievements-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.mindspace-achievements-title {
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-title-link {
|
||||
display: inline;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #2f6f57;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.mindspace-achievements-title-link:hover {
|
||||
color: #1f4f3d;
|
||||
}
|
||||
|
||||
.mindspace-achievements-detail-link {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: rgba(24, 33, 29, 0.56);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.mindspace-card-achievements {
|
||||
border-color: rgba(47, 111, 87, 0.18);
|
||||
background: linear-gradient(180deg, rgba(235, 248, 240, 0.92), rgba(255, 252, 244, 0.96));
|
||||
}
|
||||
|
||||
.mindspace-all-pages .mindspace-feed-card-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -20,6 +20,7 @@ export function MindSpaceRoute({
|
||||
const location = useLocation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const pageMatch = matchPath('/space/page/:pageId', location.pathname);
|
||||
const achievementsMatch = matchPath('/space/achievements', location.pathname);
|
||||
const pageId = pageMatch?.params.pageId ?? null;
|
||||
const categoryCode = parseCategory(searchParams.get('category'));
|
||||
|
||||
@@ -28,11 +29,13 @@ export function MindSpaceRoute({
|
||||
user={user}
|
||||
initialPageId={pageId}
|
||||
initialCategoryCode={categoryCode}
|
||||
initialAchievementsOpen={Boolean(achievementsMatch)}
|
||||
onBack={() => navigate('/')}
|
||||
onLogout={onLogout}
|
||||
onOpenFeedback={() => navigate('/feedback')}
|
||||
routeSync={{
|
||||
pushHome: () => navigate('/space'),
|
||||
pushAchievements: () => navigate('/space/achievements'),
|
||||
pushCategory: (code) => navigate(`/space?category=${code}`),
|
||||
pushPage: (id) => navigate(`/space/page/${id}`),
|
||||
}}
|
||||
|
||||
@@ -870,6 +870,12 @@ export type MindSpacePage = {
|
||||
status: 'draft' | 'reviewing' | 'risk_found' | 'ready' | 'published' | 'protected' | 'expired' | 'offline';
|
||||
visibility: 'private' | 'internal' | 'public';
|
||||
publicationAccessMode?: 'public' | 'password' | 'private_link' | 'time_limited' | 'login_required' | 'owner_only' | null;
|
||||
publicationId?: string | null;
|
||||
publicationStatus?: string | null;
|
||||
viewCount?: number;
|
||||
clickCount?: number | null;
|
||||
statsSource?: 'none' | 'publication' | 'umami';
|
||||
publishedAt?: number | null;
|
||||
publicationUrl?: string | null;
|
||||
workspaceRelativePath?: string | null;
|
||||
workspacePublicUrl?: string | null;
|
||||
|
||||
Reference in New Issue
Block a user