ea6c49f046
支持全部页面分页/多选/删除/分享,删除时可选移除广场帖并清理工作区附件;修复并发同步重复创建页面,并补齐预览下载链接重写与 iframe 下载权限。 Co-authored-by: Cursor <cursoragent@cursor.com>
88 lines
2.7 KiB
JavaScript
88 lines
2.7 KiB
JavaScript
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import {
|
|
isRelativeDownloadReference,
|
|
resolveWorkspaceRelativeFilePath,
|
|
splitReferenceParts,
|
|
} from './mindspace-html-download-links.mjs';
|
|
import { workspaceThumbnailRelativePath } from './mindspace-workspace-thumbnails.mjs';
|
|
|
|
const PRIVATE_ASSET_URL_PATTERN =
|
|
/(?:https?:\/\/[^/]+)?\/api\/mindspace\/v1\/assets\/([a-z0-9-]+)\/download(?:\?[^"'<>\\\s)]*)?/gi;
|
|
|
|
const URL_ATTR_PATTERN = /(\b(?:href|src)\s*=\s*["'])([^"']+)(["'])/gi;
|
|
|
|
export function extractAssetIdsFromHtml(html) {
|
|
return [
|
|
...new Set(
|
|
[...String(html ?? '').matchAll(PRIVATE_ASSET_URL_PATTERN)]
|
|
.map((match) => match[1])
|
|
.filter(Boolean),
|
|
),
|
|
];
|
|
}
|
|
|
|
export function extractRelativeDownloadRefs(html) {
|
|
const refs = new Set();
|
|
for (const match of String(html ?? '').matchAll(URL_ATTR_PATTERN)) {
|
|
const url = match[2];
|
|
if (isRelativeDownloadReference(url)) {
|
|
refs.add(splitReferenceParts(url).pathPart);
|
|
}
|
|
}
|
|
return [...refs];
|
|
}
|
|
|
|
export function collectWorkspacePurgeTargets(htmlRelativePath, html) {
|
|
const targets = new Set();
|
|
const normalizedHtmlPath = String(htmlRelativePath ?? '').replace(/^\/+/, '');
|
|
if (normalizedHtmlPath) {
|
|
targets.add(normalizedHtmlPath);
|
|
if (normalizedHtmlPath.toLowerCase().endsWith('.html')) {
|
|
targets.add(workspaceThumbnailRelativePath(normalizedHtmlPath));
|
|
const pngThumb = normalizedHtmlPath.replace(/\.html$/i, '.thumbnail.png');
|
|
if (pngThumb !== normalizedHtmlPath) targets.add(pngThumb);
|
|
}
|
|
}
|
|
for (const ref of extractRelativeDownloadRefs(html)) {
|
|
targets.add(resolveWorkspaceRelativeFilePath(normalizedHtmlPath || 'public/index.html', ref));
|
|
}
|
|
return [...targets].filter(Boolean);
|
|
}
|
|
|
|
export async function purgeWorkspacePageArtifacts({
|
|
publishDir,
|
|
htmlRelativePath = null,
|
|
html = '',
|
|
} = {}) {
|
|
if (!publishDir) return { removed: [], skipped: [] };
|
|
const resolvedRoot = path.resolve(publishDir);
|
|
const removed = [];
|
|
const skipped = [];
|
|
|
|
for (const relativeTarget of collectWorkspacePurgeTargets(htmlRelativePath, html)) {
|
|
const absolutePath = path.resolve(publishDir, ...relativeTarget.split('/'));
|
|
if (
|
|
absolutePath !== resolvedRoot &&
|
|
!absolutePath.startsWith(`${resolvedRoot}${path.sep}`)
|
|
) {
|
|
skipped.push(relativeTarget);
|
|
continue;
|
|
}
|
|
try {
|
|
await fs.unlink(absolutePath);
|
|
removed.push(relativeTarget);
|
|
} catch (error) {
|
|
if (error?.code !== 'ENOENT') skipped.push(relativeTarget);
|
|
}
|
|
}
|
|
|
|
return { removed, skipped };
|
|
}
|
|
|
|
export const pagePurgeInternals = {
|
|
extractAssetIdsFromHtml,
|
|
extractRelativeDownloadRefs,
|
|
collectWorkspacePurgeTargets,
|
|
};
|