// lib/page.js
// The Builder SPA shell page (ARCHITECTURE.md 8.2).
//
// renderEditorShell(req, res) builds a COMPLETE, dependency-free HTML document
// (no React in the served shell) that:
// (a) loads the compiled Phase-1 editor from the plugin's static public dir,
// /plugins/public/theme-builder/builderApp.js (plugins.js:1246-1276),
// (b) bootstraps window.__TB__ = { apiBase, cssRoute, csrfToken } -- the CSRF
// token is read from req exactly the way core does it (req.csrfToken(),
// e.g. server/wrapper.js:259, server/app.js:74 where it defaults to ""),
// (c) exposes a single mount point
the editor
// attaches to.
//
// The function RETURNS the complete document string; when a res is supplied
// (the apiHandlers.getEditor path) it also res.send()s that document. We send a
// standalone document rather than res.sendWrap()'ing a body fragment so the SPA
// owns the whole page and stays free of the core React chrome -- per the task's
// "COMPLETE HTML document / dependency-free shell" contract.
const { API_BASE, CSS_ROUTE, URL_PREFIX, PLUGIN_NAME } = require("./constants");
// The plugin's committed editor bundle, served by core's static plugin route.
const BUNDLE_URL = `/plugins/public/${PLUGIN_NAME}/builderApp.js`;
// Mirror core's CSRF read: req.csrfToken is a function (csurf adds it; in some
// modes app.js stubs it to return ""). Guard so a missing token never throws.
function readCsrfToken(req) {
if (req && typeof req.csrfToken === "function") {
try {
return req.csrfToken() || "";
} catch (e) {
return "";
}
}
return "";
}
// Minimal HTML-escaper for the
and any attribute text. The JSON blob is
// emitted via JSON.stringify with "<" escaped so it cannot break out of the
// " / "