import { join, resolve } from 'node:path'; import { writeTextFileAtomic } from '../atomic-write.js'; import { renderProjectDetail } from '../monitor/project-detail.js'; /** * Persist a universal, file://-openable gallery for ONE project. * * The monitor's `renderProjectDetail` is a universal gallery (Pages / Videos / * Images / lightbox) that works for projects of ANY era — including legacy/v2 * projects with no brief/storyboard that the rich preview-portal surfaces can't * describe. But its media/page URLs are monitor-server routes (`/m/`, `/p/`) * that only resolve through that running server. We render the SAME gallery * with absolute `file://` URL builders injected, so the resulting * `gallery.html` is fully clickable when opened directly from disk. * * Returns the absolute path to the written `gallery.html`. */ export async function writeProjectGallery(root: string, slug: string): Promise { // Per-segment encode (mirrors project-detail's segEncode). encodeURI would // leave `#`/`?`/`&` unescaped, breaking a file:// link for a deliverable named // e.g. "final cut #2.mp4" (the browser would read `#2.mp4` as a URL fragment). const fileUrl = (rel: string): string => `file://${join(resolve(root), rel).split('/').map(encodeURIComponent).join('/')}`; const html = await renderProjectDetail(root, slug, { mediaUrl: fileUrl, pageUrl: fileUrl }); const galleryPath = join(root, 'projects', slug, 'gallery.html'); await writeTextFileAtomic(galleryPath, html); return galleryPath; }