fix(rspace): serve canvas through renderShell like all other rapps
The rspace module was serving raw canvas.html as a standalone page (bypassing renderShell) because canvas-module.html didn't exist. This meant navigating to rspace caused a full page replacement with no shared shell, tabs, or TabCache support — appearing to open in a "new window." Now extracts body content, styles, and scripts from canvas.html at startup (stripping the duplicate shell chrome) and renders through renderShell like every other module. This makes rspace fully interoperable with the tab system and TabCache. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a39cf6e1c2
commit
6bd23a6778
|
|
@ -16,34 +16,90 @@ const DIST_DIR = resolve(import.meta.dir, "../../dist");
|
|||
|
||||
const routes = new Hono();
|
||||
|
||||
/**
|
||||
* Extract body content and scripts from the full canvas.html page.
|
||||
* Strips the shell chrome (header, tab-bar, welcome overlay) that renderShell provides,
|
||||
* and returns just the canvas-specific DOM + inline styles + module scripts.
|
||||
*/
|
||||
function extractCanvasContent(html: string): { body: string; styles: string; scripts: string } {
|
||||
// Extract inline <style> blocks from <head> (canvas CSS)
|
||||
const headMatch = html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
||||
const headContent = headMatch?.[1] || "";
|
||||
const styleBlocks: string[] = [];
|
||||
headContent.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (match) => {
|
||||
styleBlocks.push(match);
|
||||
return "";
|
||||
});
|
||||
|
||||
// Extract module script src from <head>
|
||||
const scriptSrcs: string[] = [];
|
||||
headContent.replace(/<script[^>]*\bsrc="([^"]+)"[^>]*>/gi, (_m, src: string) => {
|
||||
if (!src.includes("shell.js")) scriptSrcs.push(src);
|
||||
return "";
|
||||
});
|
||||
|
||||
// Extract <body> content
|
||||
const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
||||
let bodyContent = bodyMatch?.[1] || "";
|
||||
|
||||
// Strip shell chrome that renderShell already provides:
|
||||
// header, tab-bar (contains only a <rstack-tab-bar>), and welcome overlay.
|
||||
bodyContent = bodyContent
|
||||
.replace(/<header[\s\S]*?<\/header>/i, "")
|
||||
.replace(/<div class="rstack-tab-row"[^>]*>\s*<rstack-tab-bar[^>]*><\/rstack-tab-bar>\s*<\/div>/i, "")
|
||||
.replace(/<!--\s*Welcome overlay[\s\S]*?<\/div>\s*<\/div>\s*<\/div>/i, "");
|
||||
|
||||
const styles = styleBlocks.join("\n");
|
||||
const scripts = scriptSrcs.map(s => `<script type="module" crossorigin src="${s}"></script>`).join("\n");
|
||||
|
||||
return { body: bodyContent.trim(), styles, scripts };
|
||||
}
|
||||
|
||||
// Cache the extracted canvas content (parsed once at startup)
|
||||
let canvasContentCache: { body: string; styles: string; scripts: string } | null = null;
|
||||
|
||||
async function getCanvasContent(): Promise<{ body: string; styles: string; scripts: string }> {
|
||||
if (canvasContentCache) return canvasContentCache;
|
||||
|
||||
// Prefer canvas-module.html (pre-extracted body partial)
|
||||
const moduleFile = Bun.file(resolve(DIST_DIR, "canvas-module.html"));
|
||||
if (await moduleFile.exists()) {
|
||||
canvasContentCache = {
|
||||
body: await moduleFile.text(),
|
||||
styles: "",
|
||||
scripts: `<script type="module" src="/canvas-module.js"></script>`,
|
||||
};
|
||||
return canvasContentCache;
|
||||
}
|
||||
|
||||
// Fall back to extracting from full canvas.html
|
||||
const fullFile = Bun.file(resolve(DIST_DIR, "canvas.html"));
|
||||
if (await fullFile.exists()) {
|
||||
canvasContentCache = extractCanvasContent(await fullFile.text());
|
||||
return canvasContentCache;
|
||||
}
|
||||
|
||||
return {
|
||||
body: `<div style="padding:2rem;text-align:center;color:#64748b;">Canvas loading...</div>`,
|
||||
styles: "",
|
||||
scripts: "",
|
||||
};
|
||||
}
|
||||
|
||||
// GET / — serve the canvas page wrapped in shell
|
||||
routes.get("/", async (c) => {
|
||||
const spaceSlug = c.req.param("space") || c.req.query("space") || "demo";
|
||||
|
||||
// Read the canvas page template from dist
|
||||
const canvasFile = Bun.file(resolve(DIST_DIR, "canvas-module.html"));
|
||||
let canvasBody = "";
|
||||
if (await canvasFile.exists()) {
|
||||
canvasBody = await canvasFile.text();
|
||||
} else {
|
||||
// Fallback: serve full canvas.html directly if module template not built yet
|
||||
const fallbackFile = Bun.file(resolve(DIST_DIR, "canvas.html"));
|
||||
if (await fallbackFile.exists()) {
|
||||
return new Response(fallbackFile, {
|
||||
headers: { "Content-Type": "text/html" },
|
||||
});
|
||||
}
|
||||
canvasBody = `<div style="padding:2rem;text-align:center;color:#64748b;">Canvas loading...</div>`;
|
||||
}
|
||||
const canvas = await getCanvasContent();
|
||||
|
||||
const html = renderShell({
|
||||
title: `${spaceSlug} — Canvas | rSpace`,
|
||||
moduleId: "rspace",
|
||||
spaceSlug,
|
||||
body: canvasBody,
|
||||
body: canvas.body,
|
||||
modules: getModuleInfoList(),
|
||||
theme: "dark",
|
||||
scripts: `<script type="module" src="/canvas-module.js"></script>`,
|
||||
styles: canvas.styles,
|
||||
scripts: canvas.scripts,
|
||||
});
|
||||
|
||||
return c.html(html);
|
||||
|
|
|
|||
Loading…
Reference in New Issue