81 lines
4.3 KiB
JavaScript
81 lines
4.3 KiB
JavaScript
// lib/layout.js
|
|
// layout(cfg) -- Phase-1 returns undefined; Phase-2 returns a real PluginLayout
|
|
// (ARCHITECTURE.md 5.1, 7.10). When Phase 2 is enabled the plugin registers into
|
|
// state.layouts["theme-builder"] (state.ts:1113-1117) and is selectable via
|
|
// layout_by_role[role] === "theme-builder" (or user._attributes.layout).
|
|
//
|
|
// wrap() OWNS the document: it links the vendored stock Bootstrap CSS + the
|
|
// theme overlay (var(--bs-*)) CSS, renders the page chrome from the active
|
|
// layoutTree (renderTree.js), and threads Saltcorn's headersInHead/Body. Because
|
|
// wrap() emits the token <link> itself, headers(cfg)'s only_if suppresses the
|
|
// Phase-1 overlay header for theme-builder-served requests (7.6). Sass is the
|
|
// documented opt-in; the default engine is the overlay on stock Bootstrap.
|
|
|
|
const { PLUGIN_NAME, CSS_ROUTE } = require("./constants");
|
|
const { isLayoutMode, activeHashHint, activeLayoutTree } = require("./cfgReaders");
|
|
const { renderTreeToHtml, renderToasts, esc } = require("./renderTree");
|
|
const { headersInHead, headersInBody } = require("@saltcorn/markup/layout_utils");
|
|
const { renderForm } = require("@saltcorn/markup");
|
|
|
|
const ASSET_BASE = `/plugins/public/${PLUGIN_NAME}`;
|
|
const BOOTSTRAP_CSS = `${ASSET_BASE}/themeBootstrap.min.css`;
|
|
const BOOTSTRAP_JS = `${ASSET_BASE}/themeBootstrap.bundle.min.js`;
|
|
|
|
|
|
function headLinks(cfg, role, headers) {
|
|
const v = activeHashHint(cfg, role);
|
|
return `<meta charset="utf-8">`
|
|
+ `<meta name="viewport" content="width=device-width, initial-scale=1">`
|
|
+ `<link rel="stylesheet" href="${BOOTSTRAP_CSS}">`
|
|
+ `<link rel="stylesheet" href="${CSS_ROUTE}?v=${v}">`
|
|
+ headersInHead(headers || []);
|
|
}
|
|
|
|
|
|
function layout(cfg) {
|
|
if (!isLayoutMode(cfg)) {
|
|
return undefined; // falsy => not registered as a layout (layout mode off)
|
|
}
|
|
return {
|
|
pluginName: PLUGIN_NAME,
|
|
|
|
wrap: ({ title, body, brand, menu, alerts, headers, bodyClass, role, req, currentUrl } = {}) => {
|
|
const chrome = renderTreeToHtml(activeLayoutTree(cfg, role), {
|
|
title, body, brand, menu, alerts: alerts || [], role, req, currentUrl,
|
|
});
|
|
return `<!doctype html><html lang="en"><head>${headLinks(cfg, role, headers)}`
|
|
+ `<title>${esc(title || "")}</title></head>`
|
|
+ `<body class="${esc(bodyClass || "")}">${chrome}`
|
|
+ `<script src="${BOOTSTRAP_JS}"></script>${headersInBody(headers || [])}`
|
|
+ `</body></html>`;
|
|
},
|
|
|
|
// Auth pages (login/signup): a centered card. The login/signup FORM arrives
|
|
// as a Saltcorn Form OBJECT under `form` and MUST be rendered via
|
|
// renderForm(form, csrfToken) -- it carries the _csrf field. authLinks are
|
|
// the login/forgot/signup cross-links; afterForm is extra HTML.
|
|
authWrap: ({ title, form, afterForm, authLinks, alerts, headers, csrfToken, role, req } = {}) => {
|
|
const links = [];
|
|
if (authLinks) {
|
|
if (authLinks.login) links.push(`<a href="${esc(authLinks.login)}">Login</a>`);
|
|
if (authLinks.forgot) links.push(`<a href="${esc(authLinks.forgot)}">Forgot password?</a>`);
|
|
if (authLinks.signup) links.push(`<a href="${esc(authLinks.signup)}">Create an account</a>`);
|
|
}
|
|
const formHtml = form ? renderForm(form, csrfToken || "") : "";
|
|
return `<!doctype html><html lang="en"><head>${headLinks(cfg, role, headers)}`
|
|
+ `<title>${esc(title || "")}</title></head>`
|
|
+ `<body class="tb-auth"><div class="container" style="max-width:28rem"><div class="py-5">`
|
|
+ `<div class="card shadow-sm"><div class="card-body p-4">`
|
|
+ `<h1 class="h4 mb-3 text-center">${esc(title || "")}</h1>`
|
|
+ `${formHtml}<div class="mt-3 text-center small">${links.join(" | ")}</div>${afterForm || ""}`
|
|
+ `</div></div></div></div>${renderToasts(alerts || [])}`
|
|
+ `<script src="${BOOTSTRAP_JS}"></script>${headersInBody(headers || [])}`
|
|
+ `</body></html>`;
|
|
},
|
|
|
|
// renderBody: passthrough -- the body content is already rendered HTML.
|
|
renderBody: ({ body } = {}) => body || "",
|
|
};
|
|
}
|
|
|
|
module.exports = { layout };
|