feat(mindspace): 全部页面管理、级联删除与页面同步去重
支持全部页面分页/多选/删除/分享,删除时可选移除广场帖并清理工作区附件;修复并发同步重复创建页面,并补齐预览下载链接重写与 iframe 下载权限。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+54
-17
@@ -300,6 +300,7 @@ async function bootstrapUserAuth() {
|
||||
},
|
||||
});
|
||||
mindSpacePublications = createPublicationService(pool, {
|
||||
h5Root: __dirname,
|
||||
storageRoot:
|
||||
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace'),
|
||||
publicPageLimit: Number(process.env.MINDSPACE_FREE_PUBLIC_PAGE_LIMIT ?? 5),
|
||||
@@ -2664,21 +2665,35 @@ async function resolveOwnedAssistantMessage(userId, sessionId, messageId) {
|
||||
|
||||
const SAVE_TARGET_CATEGORIES = new Set(['draft', 'oa', 'public']);
|
||||
|
||||
const pageSyncInFlight = new Map();
|
||||
|
||||
async function syncUserGeneratedPages(userId) {
|
||||
if (!mindSpacePages || !authPool || !userId) return;
|
||||
await syncGeneratedPagesFromPublicAssets({
|
||||
pool: authPool,
|
||||
pageService: mindSpacePages,
|
||||
assetService: mindSpaceAssets,
|
||||
userId,
|
||||
publishDir: resolvePublishDir(__dirname, { id: userId }),
|
||||
syncWorkspaceAssets:
|
||||
mindSpaceAssets && WORKSPACE_MAINTENANCE_ENABLED
|
||||
? (targetUserId, options) => mindSpaceAssets.syncWorkspaceAssets(targetUserId, options)
|
||||
: null,
|
||||
}).catch((error) => {
|
||||
console.warn('[MindSpace] page sync failed:', error?.message ?? error);
|
||||
});
|
||||
|
||||
let inFlight = pageSyncInFlight.get(userId);
|
||||
if (!inFlight) {
|
||||
inFlight = syncGeneratedPagesFromPublicAssets({
|
||||
pool: authPool,
|
||||
pageService: mindSpacePages,
|
||||
assetService: mindSpaceAssets,
|
||||
userId,
|
||||
publishDir: resolvePublishDir(__dirname, { id: userId }),
|
||||
syncWorkspaceAssets:
|
||||
mindSpaceAssets && WORKSPACE_MAINTENANCE_ENABLED
|
||||
? (targetUserId, options) => mindSpaceAssets.syncWorkspaceAssets(targetUserId, options)
|
||||
: null,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('[MindSpace] page sync failed:', error?.message ?? error);
|
||||
})
|
||||
.finally(() => {
|
||||
if (pageSyncInFlight.get(userId) === inFlight) {
|
||||
pageSyncInFlight.delete(userId);
|
||||
}
|
||||
});
|
||||
pageSyncInFlight.set(userId, inFlight);
|
||||
}
|
||||
await inFlight;
|
||||
}
|
||||
|
||||
async function resolveChatSaveBundle(user, h5Root, input = {}) {
|
||||
@@ -3083,10 +3098,24 @@ api.get('/mindspace/v1/pages', async (req, res) => {
|
||||
if (!mindSpacePages) return res.status(503).json({ message: 'MindSpace 未启用' });
|
||||
try {
|
||||
await syncUserGeneratedPages(req.currentUser.id);
|
||||
const pages = await mindSpacePages.listPages(req.currentUser.id, {
|
||||
const limit = Number.parseInt(String(req.query.limit ?? ''), 10);
|
||||
const offset = Number.parseInt(String(req.query.offset ?? ''), 10);
|
||||
const result = await mindSpacePages.listPages(req.currentUser.id, {
|
||||
status: typeof req.query.status === 'string' ? req.query.status : undefined,
|
||||
categoryCode: typeof req.query.category_code === 'string' ? req.query.category_code : undefined,
|
||||
...(Number.isFinite(limit) ? { limit } : {}),
|
||||
...(Number.isFinite(offset) ? { offset } : {}),
|
||||
});
|
||||
return res.json({
|
||||
data: result.items,
|
||||
page: {
|
||||
total: result.total,
|
||||
limit: result.limit,
|
||||
offset: result.offset,
|
||||
has_more: result.hasMore,
|
||||
next_cursor: null,
|
||||
},
|
||||
});
|
||||
return res.json({ data: pages, page: { next_cursor: null, has_more: false } });
|
||||
} catch (error) {
|
||||
return mindSpaceError(res, req, error);
|
||||
}
|
||||
@@ -3109,7 +3138,14 @@ api.get('/mindspace/v1/pages/:pageId', async (req, res) => {
|
||||
api.delete('/mindspace/v1/pages/:pageId', async (req, res) => {
|
||||
if (!mindSpacePages || !ensureMindSpaceEnabled(res, req)) return;
|
||||
try {
|
||||
const result = await mindSpacePages.deletePage(req.currentUser.id, req.params.pageId);
|
||||
const removeFromPlaza =
|
||||
String(req.query?.remove_from_plaza ?? req.body?.remove_from_plaza ?? '').toLowerCase() ===
|
||||
'true' ||
|
||||
req.query?.remove_from_plaza === '1' ||
|
||||
req.body?.remove_from_plaza === true;
|
||||
const result = await mindSpacePages.deletePage(req.currentUser.id, req.params.pageId, {
|
||||
removeFromPlaza,
|
||||
});
|
||||
const quota = await mindSpace.getQuota(req.currentUser.id);
|
||||
await mindSpaceAudit?.write({
|
||||
userId: req.currentUser.id,
|
||||
@@ -3119,6 +3155,7 @@ api.delete('/mindspace/v1/pages/:pageId', async (req, res) => {
|
||||
ip: req.ip,
|
||||
detail: {
|
||||
offlinedPublicationCount: result.offlinedPublicationCount,
|
||||
hiddenPlazaPostCount: result.hiddenPlazaPostCount,
|
||||
deletedAssetCount: result.deletedAssetCount,
|
||||
freedBytes: result.freedBytes,
|
||||
},
|
||||
@@ -4434,7 +4471,7 @@ function publishedPageShellHtml({ iframeUrl, shareUrl, title }) {
|
||||
<body>
|
||||
<div class="publication-shell">
|
||||
<!-- Game-like publications need script execution, but not same-origin sandbox escape. -->
|
||||
<iframe class="publication-frame" title="${safeTitle}" src="${iframeSrc}" sandbox="allow-scripts"></iframe>
|
||||
<iframe class="publication-frame" title="${safeTitle}" src="${iframeSrc}" sandbox="allow-scripts allow-downloads"></iframe>
|
||||
</div>
|
||||
<button type="button" class="publication-share-fab" id="publication-share-fab">分享</button>
|
||||
<div class="publication-share-sheet" id="publication-share-sheet" hidden>
|
||||
|
||||
Reference in New Issue
Block a user