// lib/configWorkflow.js // The presence of configuration_workflow is the single switch that makes every // facility (headers/routes/layout) a (cfg)=>value factory (ARCHITECTURE.md 3.1/5.1). // This workflow mounts: // (1) an informational step linking to the builder SPA (URL_PREFIX + "/editor"), and // (2) the layoutMode toggle, HARD-BLOCKED unless layout_by_role covers every live // role so the last-installed fallback is never the live selector and per-request // overlay suppression stays deterministic (ARCHITECTURE.md 5.7 / 12.3). const Workflow = require("@saltcorn/data/models/workflow"); const Form = require("@saltcorn/data/models/form"); const Role = require("@saltcorn/data/models/role"); const { getState } = require("@saltcorn/data/db/state"); const { PLUGIN_NAME, URL_PREFIX, CFG } = require("./constants"); const EDITOR_URL = URL_PREFIX + "/editor"; // Every role row in _sc_roles is a "live" role. A role is covered when // layout_by_role names theme-builder for it (keys are JSON strings, role.id is // numeric -> normalize to String on both sides). Returns the list of uncovered // role names; empty means full coverage (ARCHITECTURE.md 5.7 / 12.3). async function uncoveredRoles() { const byRole = getState().getConfig("layout_by_role", {}) || {}; const roles = await Role.find({}, { orderBy: "id" }); const out = []; for (const role of roles) { const mapped = byRole[role.id] != null ? byRole[role.id] : byRole[String(role.id)]; if (mapped !== PLUGIN_NAME) { out.push(role.role); } } return out; } function configuration_workflow() { return new Workflow({ steps: [ { name: "Theme builder", form: async () => new Form({ blurb: "Design and manage themes in the visual builder, then activate one " + "from the theme list. The builder opens in a full-page editor.", fields: [ { name: "_builder_link", label: "Open the builder", input_type: "custom_html", attributes: { html: 'Open theme builder »', }, }, ], }), }, { name: "Layout mode", form: async () => { // Resolve coverage up front so the (synchronous) form validator can // hard-block enabling layoutMode when any live role is uncovered. const uncovered = await uncoveredRoles(); return new Form({ blurb: "By default theme-builder only RECOLORS your current theme (a CSS " + "overlay on top of your active layout). Turn this on to let " + "theme-builder provide the PAGE LAYOUT itself (navbar / sidebar / " + "content), making it a selectable site layout. It stays off until " + "you opt in so that installing the plugin never changes your site's " + "layout (Saltcorn uses the last-installed layout as the default). " + "When on, assign theme-builder to roles under 'layout by role'; it " + "must cover every role before this can be enabled." + (uncovered.length ? '
Roles not yet mapped to ' + "theme-builder: " + uncovered.join(", ") + ". Map them under Settings -> Users and security -> Roles before " + "enabling layout mode.
" : ""), fields: [ { name: CFG.LAYOUT_MODE, label: "Use theme-builder as the page layout", type: "Bool", default: false, sublabel: "Hard-blocked unless layout_by_role covers every live role.", }, ], validator(values) { if (values[CFG.LAYOUT_MODE] && uncovered.length) { return ( "Cannot use theme-builder as the page layout yet: assign it to " + "every role under 'layout by role' first. Uncovered roles: " + uncovered.join(", ") + ". (This prevents some roles from silently falling back to a " + "different layout.)" ); } }, }); }, }, ], }); } module.exports = configuration_workflow;