forked from UKSOURCE/cms.lams
feat(cms): refactor page content management and implement detailed editors
Refactor the CMS page content system to use a more modular configuration-driven approach. This replaces the monolithic `pageContentConfig.js` with individual configuration files for each page and introduces a shared field utility to standardize editor components. Key changes: - Implement a generic `_renderSingletonPageView` helper to reduce duplication across controllers. - Enhance `_createPageContentController` with `normalizeForEditor`, `normalizeForApi`, and `preparePayload` hooks for custom data transformation. - Create dedicated configuration and view structures for Partnerships, History, Accreditation, Admissions, and Policies pages. - Add a specialized section editor for Policies to manage complex nested content. - Improve the frontend `page-content-editor.js` with support for visibility logic, auto-sequencing for arrays, and URL synchronization for active tabs. - Update data JSON files to align with the new schema.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
const {
|
||||
text,
|
||||
textarea,
|
||||
icon,
|
||||
url,
|
||||
combobox,
|
||||
object,
|
||||
objectList,
|
||||
stringList,
|
||||
variantList,
|
||||
} = require("./sharedFields");
|
||||
|
||||
const baseConfig = {
|
||||
key: "policies",
|
||||
title: "Policies Management",
|
||||
subtitle: "Edit content displayed on the policies page",
|
||||
routeBase: "/admin/policies",
|
||||
apiPath: "/api/policies",
|
||||
previewPath: "/policies",
|
||||
dataFile: "policies",
|
||||
imageType: "policies",
|
||||
tabs: [
|
||||
{
|
||||
key: "hero",
|
||||
label: "Hero",
|
||||
icon: "fas fa-scale-balanced",
|
||||
schema: object("hero", "Hero", [
|
||||
text("badge", "Eyebrow label", { maxLength: 40 }),
|
||||
icon("icon", "Hero icon", {
|
||||
helpText: "Policies uses icon-only controls and does not require image upload.",
|
||||
}),
|
||||
text("titlePrefix", "Headline prefix", { maxLength: 50 }),
|
||||
text("titleHighlight", "Headline highlight", { maxLength: 40 }),
|
||||
textarea("description", "Supporting text", {
|
||||
maxLength: 220,
|
||||
rows: 4,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "sidebar",
|
||||
label: "Sidebar",
|
||||
icon: "fas fa-bars",
|
||||
schema: object("sidebar", "Sidebar", [
|
||||
text("heading", "Sidebar heading", { maxLength: 30 }),
|
||||
text("helperText", "Helper text", { maxLength: 60 }),
|
||||
text("contactLabel", "Contact link label", { maxLength: 40 }),
|
||||
url("contactHref", "Contact URL", { maxLength: 255 }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
key: "policies",
|
||||
label: "Policies",
|
||||
icon: "fas fa-file-lines",
|
||||
schema: objectList(
|
||||
"policies",
|
||||
"Policies",
|
||||
[
|
||||
text("id", "Policy key", { maxLength: 40 }),
|
||||
text("navLabel", "Sidebar label", { maxLength: 40 }),
|
||||
text("title", "Policy title", { maxLength: 70 }),
|
||||
text("effectiveDate", "Effective date", { maxLength: 50 }),
|
||||
textarea("intro", "Intro text", { maxLength: 260, rows: 4 }),
|
||||
],
|
||||
{
|
||||
itemLabel: "Policy",
|
||||
sortable: true,
|
||||
itemTitleKey: "title",
|
||||
itemSubtitleKey: "effectiveDate",
|
||||
emptyText: "No policies yet.",
|
||||
itemActions: [
|
||||
{
|
||||
label: "Edit Sections",
|
||||
icon: "fas fa-pen-to-square",
|
||||
hrefTemplate: "/admin/policies/{id}/section",
|
||||
className: "btn btn-outline-primary btn-sm",
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function createPoliciesSectionEditorConfig(policy, allPolicies = []) {
|
||||
const policyOptions = allPolicies
|
||||
.filter((item) => item.id && item.id !== policy.id)
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: item.title || item.navLabel || item.id,
|
||||
}));
|
||||
|
||||
return {
|
||||
key: "policySections",
|
||||
title: `Policy Sections: ${policy.title || policy.id}`,
|
||||
subtitle: "Manage section content and paragraph order for this policy",
|
||||
routeBase: `/admin/policies/${policy.id}/section`,
|
||||
previewPath: "/policies",
|
||||
imageType: "policies",
|
||||
tabs: [
|
||||
{
|
||||
key: "sections",
|
||||
label: "Sections",
|
||||
icon: "fas fa-file-lines",
|
||||
schema: variantList(
|
||||
"sections",
|
||||
"Sections",
|
||||
{
|
||||
text: {
|
||||
label: "Text section",
|
||||
schema: object("section", "Text section", [
|
||||
text("heading", "Section heading", { maxLength: 60 }),
|
||||
objectList(
|
||||
"paragraphs",
|
||||
"Paragraphs",
|
||||
[
|
||||
textarea("text", "Paragraph text", {
|
||||
maxLength: 500,
|
||||
rows: 4,
|
||||
}),
|
||||
objectList(
|
||||
"links",
|
||||
"Inline links",
|
||||
[
|
||||
text("label", "Link label", { maxLength: 50 }),
|
||||
url("href", "Link URL", { maxLength: 255 }),
|
||||
combobox("tabId", "Switch to policy", {
|
||||
maxLength: 40,
|
||||
options: policyOptions,
|
||||
helpText:
|
||||
"Optional. Choose another policy to switch tabs on the frontend.",
|
||||
}),
|
||||
],
|
||||
{
|
||||
itemLabel: "Link",
|
||||
sortable: true,
|
||||
emptyText: "No inline links yet.",
|
||||
},
|
||||
),
|
||||
],
|
||||
{
|
||||
itemLabel: "Paragraph",
|
||||
sortable: true,
|
||||
emptyText: "No paragraphs yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
list: {
|
||||
label: "List section",
|
||||
schema: object("section", "List section", [
|
||||
text("heading", "Section heading", { maxLength: 60 }),
|
||||
textarea("intro", "Intro text", { maxLength: 220, rows: 3 }),
|
||||
stringList("items", "List items", {
|
||||
itemLabel: "List item",
|
||||
maxLength: 180,
|
||||
fieldType: "textarea",
|
||||
sortable: true,
|
||||
emptyText: "No list items yet.",
|
||||
}),
|
||||
]),
|
||||
},
|
||||
cards: {
|
||||
label: "Card section",
|
||||
schema: object("section", "Card section", [
|
||||
objectList(
|
||||
"cards",
|
||||
"Cards",
|
||||
[
|
||||
icon("icon", "Card icon"),
|
||||
text("title", "Card title", { maxLength: 50 }),
|
||||
textarea("description", "Card description", {
|
||||
maxLength: 220,
|
||||
rows: 4,
|
||||
}),
|
||||
object("link", "Card link", [
|
||||
text("label", "Link label", { maxLength: 50 }),
|
||||
url("href", "Link URL", { maxLength: 255 }),
|
||||
combobox("tabId", "Switch to policy", {
|
||||
maxLength: 40,
|
||||
options: policyOptions,
|
||||
}),
|
||||
]),
|
||||
],
|
||||
{
|
||||
itemLabel: "Card",
|
||||
sortable: true,
|
||||
emptyText: "No cards yet.",
|
||||
},
|
||||
),
|
||||
]),
|
||||
},
|
||||
},
|
||||
{
|
||||
itemLabel: "Section",
|
||||
sortable: true,
|
||||
emptyText: "No sections yet.",
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
baseConfig,
|
||||
createPoliciesSectionEditorConfig,
|
||||
};
|
||||
Reference in New Issue
Block a user