Initial commit: Memind H5 portal with MindSpace, Plaza, and agent jobs.
Track application source and tests; exclude local env, user workspaces, and runtime data via .gitignore. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import fs from 'node:fs';
|
||||
import fsPromises from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { ensureHtmlThumbnail } from './mindspace-thumbnails.mjs';
|
||||
|
||||
export function workspaceThumbnailRelativePath(htmlRelativePath) {
|
||||
const normalized = String(htmlRelativePath ?? '').replace(/^\/+/, '');
|
||||
const dir = path.posix.dirname(normalized);
|
||||
const base = path.basename(normalized, path.extname(normalized));
|
||||
const thumb = `${base}.thumbnail.svg`;
|
||||
return dir === '.' ? thumb : path.posix.join(dir, thumb);
|
||||
}
|
||||
|
||||
export function workspaceThumbnailAbsolutePath(publishDir, htmlRelativePath) {
|
||||
return path.join(publishDir, workspaceThumbnailRelativePath(htmlRelativePath));
|
||||
}
|
||||
|
||||
export async function ensureWorkspaceHtmlThumbnail(publishDir, htmlRelativePath, html, meta = {}) {
|
||||
const htmlPath = path.join(publishDir, htmlRelativePath);
|
||||
const content = html ?? (await fsPromises.readFile(htmlPath, 'utf8'));
|
||||
const thumbRel = workspaceThumbnailRelativePath(htmlRelativePath);
|
||||
return ensureHtmlThumbnail(publishDir, thumbRel, content, {
|
||||
...meta,
|
||||
contentBaseDir: path.dirname(htmlPath),
|
||||
});
|
||||
}
|
||||
|
||||
export async function readWorkspaceThumbnailIfExists(publishDir, htmlRelativePath) {
|
||||
try {
|
||||
return await fsPromises.readFile(workspaceThumbnailAbsolutePath(publishDir, htmlRelativePath), 'utf8');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function scanPublishTree(publishRoot) {
|
||||
if (!fs.existsSync(publishRoot)) return;
|
||||
const entries = await fsPromises.readdir(publishRoot, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory() || entry.name === 'wiki' || entry.name.startsWith('.')) continue;
|
||||
const userDir = path.join(publishRoot, entry.name);
|
||||
await scanUserHtmlFiles(userDir);
|
||||
}
|
||||
}
|
||||
|
||||
async function scanUserHtmlFiles(userDir) {
|
||||
const walk = async (dir) => {
|
||||
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === '.agents' || entry.name === 'node_modules') continue;
|
||||
await walk(full);
|
||||
continue;
|
||||
}
|
||||
if (!entry.name.endsWith('.html') || entry.name.endsWith('.thumbnail.svg')) continue;
|
||||
const rel = path.relative(userDir, full);
|
||||
await ensureWorkspaceHtmlThumbnail(userDir, rel).catch(() => {});
|
||||
}
|
||||
};
|
||||
await walk(userDir);
|
||||
}
|
||||
|
||||
export function startWorkspaceThumbnailWatcher(publishRoot) {
|
||||
if (!fs.existsSync(publishRoot)) {
|
||||
fs.mkdirSync(publishRoot, { recursive: true });
|
||||
}
|
||||
|
||||
void scanPublishTree(publishRoot);
|
||||
|
||||
const pending = new Map();
|
||||
const schedule = (userDir, relativePath) => {
|
||||
const key = path.join(userDir, relativePath);
|
||||
const existing = pending.get(key);
|
||||
if (existing) clearTimeout(existing);
|
||||
pending.set(
|
||||
key,
|
||||
setTimeout(() => {
|
||||
pending.delete(key);
|
||||
void ensureWorkspaceHtmlThumbnail(userDir, relativePath).catch(() => {});
|
||||
}, 400),
|
||||
);
|
||||
};
|
||||
|
||||
const attachUserWatcher = (userDir) => {
|
||||
if (!fs.existsSync(userDir)) return;
|
||||
void scanUserHtmlFiles(userDir);
|
||||
try {
|
||||
fs.watch(userDir, { recursive: true }, (_event, filename) => {
|
||||
if (!filename || !String(filename).endsWith('.html')) return;
|
||||
if (String(filename).endsWith('.thumbnail.svg')) return;
|
||||
schedule(userDir, filename);
|
||||
});
|
||||
} catch {
|
||||
// Some platforms disallow recursive watch; startup scan still runs.
|
||||
}
|
||||
};
|
||||
|
||||
for (const entry of fs.readdirSync(publishRoot, { withFileTypes: true })) {
|
||||
if (entry.isDirectory() && entry.name !== 'wiki' && !entry.name.startsWith('.')) {
|
||||
attachUserWatcher(path.join(publishRoot, entry.name));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fs.watch(publishRoot, (_event, filename) => {
|
||||
if (!filename) return;
|
||||
const userDir = path.join(publishRoot, filename);
|
||||
if (fs.existsSync(userDir) && fs.statSync(userDir).isDirectory()) {
|
||||
attachUserWatcher(userDir);
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user