forked from UKSOURCE/cms.lams
feat(cms): enhance content editors with dynamic UI configs and validation
Implement a more flexible, configuration-driven approach for CMS editors across accreditation, admissions, and partnerships modules. - Move UI labels, help texts, and field limits from hardcoded views to `editorUi` configurations in config files. - Add server-side and client-side validation to prevent duplicate category tabs in accreditation and partnerships editors. - Refactor partnership and admission views to dynamically render tabs and fields based on the provided configuration. - Update field length constraints and default values across multiple content editors to better align with frontend requirements. - Improve the admissions calculator editor with dynamic field configurations and default value fallbacks.
This commit is contained in:
@@ -20,34 +20,21 @@
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<ul class="nav nav-tabs card-header-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <%= activeTab === 'hero' ? 'active' : '' %>" data-bs-toggle="tab" href="#hero" role="tab" data-tab-key="hero">
|
||||
<i class="fas fa-image me-2"></i>Hero
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <%= activeTab === 'directory' ? 'active' : '' %>" data-bs-toggle="tab" href="#directory" role="tab" data-tab-key="directory">
|
||||
<i class="fas fa-handshake me-2"></i>Partner Directory
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <%= activeTab === 'cta' ? 'active' : '' %>" data-bs-toggle="tab" href="#cta" role="tab" data-tab-key="cta">
|
||||
<i class="fas fa-bullhorn me-2"></i>Call To Action
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <%= activeTab === 'inquiryForm' ? 'active' : '' %>" data-bs-toggle="tab" href="#inquiryForm" role="tab" data-tab-key="inquiryForm">
|
||||
<i class="fas fa-envelope me-2"></i>Inquiry Form
|
||||
</a>
|
||||
</li>
|
||||
<% editorConfig.tabs.forEach((tab) => { %>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <%= activeTab === tab.key ? 'active' : '' %>" data-bs-toggle="tab" href="#<%= tab.key %>" role="tab" data-tab-key="<%= tab.key %>">
|
||||
<i class="<%= tab.icon %> me-2"></i><%= tab.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
<%- include("partials/hero-tab", { activeTab, data, backendUrl }) %>
|
||||
<%- include("partials/directory-tab", { activeTab, data }) %>
|
||||
<%- include("partials/cta-tab", { activeTab, data }) %>
|
||||
<%- include("partials/inquiry-form-tab", { activeTab, data }) %>
|
||||
<%- include("partials/hero-tab", { activeTab, data, backendUrl, editorUi }) %>
|
||||
<%- include("partials/directory-tab", { activeTab, data, editorUi }) %>
|
||||
<%- include("partials/cta-tab", { activeTab, data, editorUi }) %>
|
||||
<%- include("partials/inquiry-form-tab", { activeTab, data, editorUi }) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,11 +53,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- include("partials/templates") %>
|
||||
<%- include("partials/templates", { editorUi }) %>
|
||||
|
||||
<script>
|
||||
window.partnershipsPageData = <%- JSON.stringify(data) %>;
|
||||
window.partnershipsBackendUrl = <%- JSON.stringify(backendUrl) %>;
|
||||
window.partnershipsEditorConfig = <%- JSON.stringify(editorConfig) %>;
|
||||
window.partnershipsEditorUi = <%- JSON.stringify(editorUi) %>;
|
||||
</script>
|
||||
<%- include("partials/editor-script") %>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div class="tab-pane fade <%= activeTab === 'cta' ? 'show active' : '' %>" id="cta" role="tabpanel">
|
||||
<% const ctaUi = editorUi.cta || {}; %>
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0"><i class="fas fa-bullhorn me-2"></i>Call To Action</h6>
|
||||
@@ -6,16 +7,16 @@
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Headline</label>
|
||||
<input class="form-control" id="ctaHeading" maxlength="80" value="<%= data.cta?.heading || '' %>">
|
||||
<label class="form-label fw-semibold"><%= ctaUi.heading?.label || "Headline" %></label>
|
||||
<input class="form-control" id="ctaHeading" maxlength="<%= ctaUi.heading?.maxLength || 80 %>" value="<%= data.cta?.heading || '' %>">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Supporting text</label>
|
||||
<textarea class="form-control" id="ctaDescription" rows="4" maxlength="220"><%= data.cta?.description || '' %></textarea>
|
||||
<label class="form-label fw-semibold"><%= ctaUi.description?.label || "Supporting text" %></label>
|
||||
<textarea class="form-control" id="ctaDescription" rows="<%= ctaUi.description?.rows || 4 %>" maxlength="<%= ctaUi.description?.maxLength || 220 %>"><%= data.cta?.description || '' %></textarea>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold">Button label</label>
|
||||
<input class="form-control" id="ctaButtonLabel" maxlength="40" value="<%= data.cta?.buttonLabel || '' %>">
|
||||
<label class="form-label fw-semibold"><%= ctaUi.buttonLabel?.label || "Button label" %></label>
|
||||
<input class="form-control" id="ctaButtonLabel" maxlength="<%= ctaUi.buttonLabel?.maxLength || 40 %>" value="<%= data.cta?.buttonLabel || '' %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div class="tab-pane fade <%= activeTab === 'directory' ? 'show active' : '' %>" id="directory" role="tabpanel">
|
||||
<% const directoryUi = editorUi.directory || {}; %>
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0"><i class="fas fa-handshake me-2"></i>Partner Directory</h6>
|
||||
@@ -6,38 +7,38 @@
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Section heading</label>
|
||||
<input class="form-control" id="directoryHeading" maxlength="70" value="<%= data.directory?.heading || '' %>">
|
||||
<label class="form-label fw-semibold"><%= directoryUi.heading?.label || "Section heading" %></label>
|
||||
<input class="form-control" id="directoryHeading" maxlength="<%= directoryUi.heading?.maxLength || 70 %>" value="<%= data.directory?.heading || '' %>">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Section description</label>
|
||||
<textarea class="form-control" id="directoryDescription" rows="3" maxlength="180"><%= data.directory?.description || '' %></textarea>
|
||||
<label class="form-label fw-semibold"><%= directoryUi.description?.label || "Section description" %></label>
|
||||
<textarea class="form-control" id="directoryDescription" rows="<%= directoryUi.description?.rows || 3 %>" maxlength="<%= directoryUi.description?.maxLength || 180 %>"><%= data.directory?.description || '' %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cms-editor-group mb-4">
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
<label class="form-label fw-semibold mb-1">Category tabs</label>
|
||||
<div class="form-text mt-0">The frontend shows up to 3 direct tabs. Additional tabs move into a More dropdown.</div>
|
||||
<label class="form-label fw-semibold mb-1"><%= directoryUi.tabs?.label || "Category tabs" %></label>
|
||||
<div class="form-text mt-0"><%= directoryUi.tabsFrontendHint || directoryUi.tabs?.helpText || "" %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="directoryTabsList"></div>
|
||||
<button type="button" class="cms-add-button mt-3" id="addDirectoryTabBtn">
|
||||
<i class="fas fa-plus me-2"></i>Add Tab
|
||||
<i class="fas fa-plus me-2"></i><%= directoryUi.tabs?.addLabel || "Add Tab" %>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="cms-editor-group">
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
<label class="form-label fw-semibold mb-1">Partners</label>
|
||||
<div class="form-text mt-0">Each partner card keeps its own open or closed state automatically.</div>
|
||||
<label class="form-label fw-semibold mb-1"><%= directoryUi.partners?.label || "Partners" %></label>
|
||||
<div class="form-text mt-0"><%= directoryUi.partnersHelpText || directoryUi.partners?.helpText || "" %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="partnersList"></div>
|
||||
<button type="button" class="cms-add-button mt-3" id="addPartnerBtn">
|
||||
<i class="fas fa-plus me-2"></i>Add Partner
|
||||
<i class="fas fa-plus me-2"></i><%= directoryUi.partners?.addLabel || "Add Partner" %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
(function () {
|
||||
const initialData = window.partnershipsPageData;
|
||||
const backendUrl = (window.partnershipsBackendUrl || "").replace(/\/$/, "");
|
||||
const editorConfig = window.partnershipsEditorConfig || {};
|
||||
const editorUi = window.partnershipsEditorUi || {};
|
||||
const form = document.getElementById("cmsEditorForm");
|
||||
const pageJsonInput = document.getElementById("pageJson");
|
||||
const activeTabInput = document.getElementById("activeTabInput");
|
||||
@@ -14,6 +16,10 @@
|
||||
}
|
||||
|
||||
const state = JSON.parse(JSON.stringify(initialData));
|
||||
const directoryUi = editorUi.directory || {};
|
||||
const inquiryUi = editorUi.inquiryForm || {};
|
||||
const partnerFields = directoryUi.partnerFields || {};
|
||||
const inquiryFieldFields = inquiryUi.fieldFields || {};
|
||||
const slugifyValue = (value, fallback) =>
|
||||
String(value || "")
|
||||
.trim()
|
||||
@@ -32,6 +38,42 @@
|
||||
renderAll();
|
||||
initStaticCounters();
|
||||
|
||||
function getTabConfig(tabKey) {
|
||||
return (editorConfig.tabs || []).find((tab) => tab.key === tabKey) || {};
|
||||
}
|
||||
|
||||
function getObjectFieldConfig(tabKey, fieldKey) {
|
||||
const fields = getTabConfig(tabKey)?.schema?.fields || [];
|
||||
return fields.find((field) => field.key === fieldKey) || {};
|
||||
}
|
||||
|
||||
function getDefaultPartner() {
|
||||
return {
|
||||
name: "",
|
||||
category: "",
|
||||
summary: "",
|
||||
logo: "",
|
||||
logoAlt: "",
|
||||
about: "",
|
||||
collabType: "",
|
||||
benefits: "",
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultInquiryField() {
|
||||
const typeOptions = inquiryFieldFields.type?.options || [];
|
||||
const widthOptions = inquiryFieldFields.width?.options || [];
|
||||
|
||||
return {
|
||||
label: "",
|
||||
placeholder: "",
|
||||
type: typeOptions[0]?.value || "text",
|
||||
width: widthOptions[0]?.value || "full",
|
||||
required: true,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
function bindStaticEvents() {
|
||||
document.querySelectorAll("[data-tab-key]").forEach((tabTrigger) => {
|
||||
tabTrigger.addEventListener("shown.bs.tab", function () {
|
||||
@@ -50,29 +92,13 @@
|
||||
});
|
||||
|
||||
document.getElementById("addPartnerBtn")?.addEventListener("click", function () {
|
||||
state.directory.partners.push({
|
||||
name: "",
|
||||
category: "",
|
||||
summary: "",
|
||||
logo: "",
|
||||
logoAlt: "",
|
||||
about: "",
|
||||
collabType: "",
|
||||
benefits: "",
|
||||
});
|
||||
state.directory.partners.push(getDefaultPartner());
|
||||
ensurePartnershipIds();
|
||||
renderPartners();
|
||||
});
|
||||
|
||||
document.getElementById("addInquiryFieldBtn")?.addEventListener("click", function () {
|
||||
state.inquiryForm.fields.push({
|
||||
label: "",
|
||||
placeholder: "",
|
||||
type: "text",
|
||||
width: "full",
|
||||
required: true,
|
||||
options: [],
|
||||
});
|
||||
state.inquiryForm.fields.push(getDefaultInquiryField());
|
||||
ensurePartnershipIds();
|
||||
renderInquiryFields();
|
||||
});
|
||||
@@ -81,8 +107,24 @@
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
form.addEventListener("submit", function () {
|
||||
form.addEventListener("submit", function (event) {
|
||||
syncStaticFields();
|
||||
const duplicateTabs = getDuplicateTabs(state?.directory?.tabs);
|
||||
|
||||
clearDirectoryTabsValidation();
|
||||
|
||||
if (duplicateTabs.length > 0) {
|
||||
const tabsLabel = directoryUi.tabs?.label || "Category tab";
|
||||
event.preventDefault();
|
||||
highlightDuplicateDirectoryTabs(duplicateTabs);
|
||||
showToast(
|
||||
`Duplicate ${tabsLabel.toLowerCase()}`,
|
||||
`${tabsLabel} "${duplicateTabs[0]}" already exists. Please use unique tab names before saving.`,
|
||||
"danger",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
ensurePartnershipIds();
|
||||
pageJsonInput.value = JSON.stringify(state);
|
||||
});
|
||||
@@ -152,6 +194,55 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getDuplicateTabs(tabs) {
|
||||
if (!Array.isArray(tabs)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const seen = new Set();
|
||||
const duplicates = [];
|
||||
|
||||
tabs.forEach((tab) => {
|
||||
const trimmedTab = String(tab || "").trim();
|
||||
const normalizedTab = trimmedTab.toLowerCase();
|
||||
|
||||
if (!normalizedTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (seen.has(normalizedTab)) {
|
||||
duplicates.push(trimmedTab);
|
||||
return;
|
||||
}
|
||||
|
||||
seen.add(normalizedTab);
|
||||
});
|
||||
|
||||
return duplicates;
|
||||
}
|
||||
|
||||
function clearDirectoryTabsValidation() {
|
||||
directoryTabsList
|
||||
?.querySelectorAll(".is-invalid")
|
||||
.forEach((element) => element.classList.remove("is-invalid"));
|
||||
}
|
||||
|
||||
function highlightDuplicateDirectoryTabs(duplicateTabs) {
|
||||
const normalizedDuplicates = new Set(
|
||||
duplicateTabs.map((tab) => String(tab || "").trim().toLowerCase()),
|
||||
);
|
||||
|
||||
const duplicateInputs = Array.from(
|
||||
directoryTabsList?.querySelectorAll('[data-field="label"]') || [],
|
||||
).filter((input) => {
|
||||
const inputValue = String(input.value || "").trim().toLowerCase();
|
||||
return inputValue && normalizedDuplicates.has(inputValue);
|
||||
});
|
||||
|
||||
duplicateInputs.forEach((input) => input.classList.add("is-invalid"));
|
||||
duplicateInputs[0]?.focus();
|
||||
}
|
||||
|
||||
function renderAll() {
|
||||
renderDirectoryTabs();
|
||||
renderPartners();
|
||||
@@ -205,7 +296,7 @@
|
||||
input.addEventListener("input", function () {
|
||||
partner[field] = input.value;
|
||||
if (field === "name") {
|
||||
title.textContent = input.value || `Partner ${index + 1}`;
|
||||
title.textContent = input.value || `${partnerFields.name?.label || "Partner"} ${index + 1}`;
|
||||
}
|
||||
if (field === "category") {
|
||||
subtitle.textContent = input.value || "";
|
||||
@@ -287,8 +378,8 @@
|
||||
input.value = field[key] || "";
|
||||
input.addEventListener("input", function () {
|
||||
field[key] = input.value;
|
||||
if (key === "label") {
|
||||
title.textContent = input.value || `Field ${index + 1}`;
|
||||
if (key === "label") {
|
||||
title.textContent = input.value || `${inquiryFieldFields.label?.label || "Field"} ${index + 1}`;
|
||||
}
|
||||
if (key === "type") {
|
||||
subtitle.textContent = input.value || "";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div class="tab-pane fade <%= activeTab === 'hero' ? 'show active' : '' %>" id="hero" role="tabpanel">
|
||||
<% const heroUi = editorUi.hero || {}; %>
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0"><i class="fas fa-image me-2"></i>Hero</h6>
|
||||
@@ -6,35 +7,35 @@
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold">Eyebrow label</label>
|
||||
<input class="form-control" id="heroBadge" maxlength="40" value="<%= data.hero?.badge || '' %>">
|
||||
<label class="form-label fw-semibold"><%= heroUi.badge?.label || "Eyebrow label" %></label>
|
||||
<input class="form-control" id="heroBadge" maxlength="<%= heroUi.badge?.maxLength || 40 %>" value="<%= data.hero?.badge || '' %>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold">Scroll link label</label>
|
||||
<input class="form-control" id="heroLinkLabel" maxlength="40" value="<%= data.hero?.linkLabel || '' %>">
|
||||
<label class="form-label fw-semibold"><%= heroUi.linkLabel?.label || "Scroll link label" %></label>
|
||||
<input class="form-control" id="heroLinkLabel" maxlength="<%= heroUi.linkLabel?.maxLength || 40 %>" value="<%= data.hero?.linkLabel || '' %>">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Headline</label>
|
||||
<input class="form-control" id="heroTitle" maxlength="90" value="<%= data.hero?.title || '' %>">
|
||||
<label class="form-label fw-semibold"><%= heroUi.title?.label || "Headline" %></label>
|
||||
<input class="form-control" id="heroTitle" maxlength="<%= heroUi.title?.maxLength || 90 %>" value="<%= data.hero?.title || '' %>">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Supporting text</label>
|
||||
<textarea class="form-control" id="heroDescription" rows="4" maxlength="220"><%= data.hero?.description || '' %></textarea>
|
||||
<label class="form-label fw-semibold"><%= heroUi.description?.label || "Supporting text" %></label>
|
||||
<textarea class="form-control" id="heroDescription" rows="<%= heroUi.description?.rows || 4 %>" maxlength="<%= heroUi.description?.maxLength || 220 %>"><%= data.hero?.description || '' %></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Hero image</label>
|
||||
<label class="form-label fw-semibold"><%= heroUi.image?.label || "Hero image" %></label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="heroImage" value="<%= data.hero?.image || '' %>">
|
||||
<button class="btn btn-outline-primary" type="button" data-upload-target="heroImage" data-preview-target="heroImagePreview">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">Recommended 720x630 px</div>
|
||||
<div class="form-text"><%= [heroUi.image?.helpText, heroUi.image?.imageHint].filter(Boolean).join(" ") %></div>
|
||||
<img id="heroImagePreview" src="<%= data.hero?.image ? `${backendUrl}${data.hero.image}` : '' %>" class="img-thumbnail mt-2 <%= data.hero?.image ? '' : 'd-none' %>" style="max-height: 200px;">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Hero image alt text</label>
|
||||
<input class="form-control" id="heroImageAlt" maxlength="120" value="<%= data.hero?.imageAlt || '' %>">
|
||||
<label class="form-label fw-semibold"><%= heroUi.imageAlt?.label || "Hero image alt text" %></label>
|
||||
<input class="form-control" id="heroImageAlt" maxlength="<%= heroUi.imageAlt?.maxLength || 120 %>" value="<%= data.hero?.imageAlt || '' %>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div class="tab-pane fade <%= activeTab === 'inquiryForm' ? 'show active' : '' %>" id="inquiryForm" role="tabpanel">
|
||||
<% const inquiryUi = editorUi.inquiryForm || {}; %>
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0"><i class="fas fa-envelope me-2"></i>Inquiry Form</h6>
|
||||
@@ -6,21 +7,21 @@
|
||||
<div class="card-body p-4">
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Modal title</label>
|
||||
<input class="form-control" id="inquiryTitle" maxlength="60" value="<%= data.inquiryForm?.title || '' %>">
|
||||
<label class="form-label fw-semibold"><%= inquiryUi.title?.label || "Modal title" %></label>
|
||||
<input class="form-control" id="inquiryTitle" maxlength="<%= inquiryUi.title?.maxLength || 60 %>" value="<%= data.inquiryForm?.title || '' %>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cms-editor-group">
|
||||
<div class="mb-3">
|
||||
<div>
|
||||
<label class="form-label fw-semibold mb-1">Form fields</label>
|
||||
<div class="form-text mt-0">Manage labels, placeholders, type, width, and dropdown options.</div>
|
||||
<label class="form-label fw-semibold mb-1"><%= inquiryUi.fields?.label || "Form fields" %></label>
|
||||
<div class="form-text mt-0"><%= inquiryUi.fieldsHelpText || inquiryUi.fields?.helpText || "" %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="inquiryFieldsList"></div>
|
||||
<button type="button" class="cms-add-button mt-3" id="addInquiryFieldBtn">
|
||||
<i class="fas fa-plus me-2"></i>Add Field
|
||||
<i class="fas fa-plus me-2"></i><%= inquiryUi.fields?.addLabel || "Add Field" %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
<% const directoryUi = editorUi.directory || {}; %>
|
||||
<% const partnerFields = directoryUi.partnerFields || {}; %>
|
||||
<% const inquiryUi = editorUi.inquiryForm || {}; %>
|
||||
<% const inquiryFieldFields = inquiryUi.fieldFields || {}; %>
|
||||
|
||||
<template id="directoryTabTemplate">
|
||||
<div class="card cms-item-card mb-3" data-item="directory-tab">
|
||||
<div class="card-header d-flex justify-content-between align-items-center gap-2">
|
||||
@@ -17,8 +22,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<label class="form-label fw-semibold">Tab label</label>
|
||||
<input class="form-control" data-field="label" maxlength="30" placeholder="Industry">
|
||||
<label class="form-label fw-semibold"><%= directoryUi.tabs?.itemLabel || "Tab label" %></label>
|
||||
<input class="form-control" data-field="label" maxlength="<%= directoryUi.tabs?.itemSchema?.maxLength || 30 %>" placeholder="<%= directoryUi.tabs?.itemSchema?.placeholder || 'Industry' %>">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,44 +52,44 @@
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Partner name</label>
|
||||
<input class="form-control" data-field="name" maxlength="90">
|
||||
<label class="form-label fw-semibold"><%= partnerFields.name?.label || "Partner name" %></label>
|
||||
<input class="form-control" data-field="name" maxlength="<%= partnerFields.name?.maxLength || 90 %>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Category</label>
|
||||
<input class="form-control" data-field="category" list="">
|
||||
<label class="form-label fw-semibold"><%= partnerFields.category?.label || "Category" %></label>
|
||||
<input class="form-control" data-field="category" list="" maxlength="<%= partnerFields.category?.maxLength || 30 %>">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Card summary</label>
|
||||
<textarea class="form-control" data-field="summary" rows="3" maxlength="130"></textarea>
|
||||
<div class="form-text">The card preview is capped at 130 characters.</div>
|
||||
<label class="form-label fw-semibold"><%= partnerFields.summary?.label || "Card summary" %></label>
|
||||
<textarea class="form-control" data-field="summary" rows="<%= partnerFields.summary?.rows || 3 %>" maxlength="<%= partnerFields.summary?.maxLength || 130 %>"></textarea>
|
||||
<div class="form-text"><%= partnerFields.summary?.helpText || "" %></div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Partner logo</label>
|
||||
<label class="form-label fw-semibold"><%= partnerFields.logo?.label || "Partner logo" %></label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" data-field="logo">
|
||||
<button class="btn btn-outline-primary" type="button" data-upload-button>
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">Recommended 105x80 px minimum visible ratio</div>
|
||||
<div class="form-text"><%= [partnerFields.logo?.helpText, partnerFields.logo?.imageHint].filter(Boolean).join(" ") %></div>
|
||||
<img class="img-thumbnail mt-2 d-none" data-preview style="max-height: 180px;">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold">Logo alt text</label>
|
||||
<input class="form-control" data-field="logoAlt" maxlength="120">
|
||||
<label class="form-label fw-semibold"><%= partnerFields.logoAlt?.label || "Logo alt text" %></label>
|
||||
<input class="form-control" data-field="logoAlt" maxlength="<%= partnerFields.logoAlt?.maxLength || 120 %>">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold">Collaboration type</label>
|
||||
<input class="form-control" data-field="collabType" maxlength="40">
|
||||
<label class="form-label fw-semibold"><%= partnerFields.collabType?.label || "Collaboration type" %></label>
|
||||
<input class="form-control" data-field="collabType" maxlength="<%= partnerFields.collabType?.maxLength || 40 %>">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">About text</label>
|
||||
<textarea class="form-control" data-field="about" rows="5" maxlength="600"></textarea>
|
||||
<label class="form-label fw-semibold"><%= partnerFields.about?.label || "About text" %></label>
|
||||
<textarea class="form-control" data-field="about" rows="<%= partnerFields.about?.rows || 5 %>" maxlength="<%= partnerFields.about?.maxLength || 600 %>"></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-semibold">Benefits</label>
|
||||
<textarea class="form-control" data-field="benefits" rows="4" maxlength="240"></textarea>
|
||||
<label class="form-label fw-semibold"><%= partnerFields.benefits?.label || "Benefits" %></label>
|
||||
<textarea class="form-control" data-field="benefits" rows="<%= partnerFields.benefits?.rows || 4 %>" maxlength="<%= partnerFields.benefits?.maxLength || 240 %>"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,43 +120,44 @@
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Field label</label>
|
||||
<input class="form-control" data-field="label" maxlength="40">
|
||||
<label class="form-label fw-semibold"><%= inquiryFieldFields.label?.label || "Field label" %></label>
|
||||
<input class="form-control" data-field="label" maxlength="<%= inquiryFieldFields.label?.maxLength || 40 %>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Placeholder text</label>
|
||||
<input class="form-control" data-field="placeholder" maxlength="80">
|
||||
<label class="form-label fw-semibold"><%= inquiryFieldFields.placeholder?.label || "Placeholder text" %></label>
|
||||
<input class="form-control" data-field="placeholder" maxlength="<%= inquiryFieldFields.placeholder?.maxLength || 80 %>">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Field type</label>
|
||||
<label class="form-label fw-semibold"><%= inquiryFieldFields.type?.label || "Field type" %></label>
|
||||
<select class="form-select" data-field="type">
|
||||
<option value="text">Single line text</option>
|
||||
<option value="textarea">Paragraph</option>
|
||||
<option value="select">Dropdown</option>
|
||||
<% (inquiryFieldFields.type?.options || []).forEach((option) => { %>
|
||||
<option value="<%= option.value %>"><%= option.label %></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">Field width</label>
|
||||
<label class="form-label fw-semibold"><%= inquiryFieldFields.width?.label || "Field width" %></label>
|
||||
<select class="form-select" data-field="width">
|
||||
<option value="half">Half width</option>
|
||||
<option value="full">Full width</option>
|
||||
<% (inquiryFieldFields.width?.options || []).forEach((option) => { %>
|
||||
<option value="<%= option.value %>"><%= option.label %></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" data-field="required">
|
||||
<label class="form-check-label fw-semibold">Required field</label>
|
||||
<label class="form-check-label fw-semibold"><%= inquiryFieldFields.required?.label || "Required field" %></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12" data-options-wrap>
|
||||
<div class="cms-editor-group">
|
||||
<div class="mb-2">
|
||||
<label class="form-label fw-semibold mb-0">Dropdown options</label>
|
||||
<label class="form-label fw-semibold mb-0"><%= inquiryFieldFields.options?.label || "Dropdown options" %></label>
|
||||
</div>
|
||||
<div class="form-text mb-2">Only used when the field type is Dropdown.</div>
|
||||
<div class="form-text mb-2"><%= inquiryFieldFields.options?.helpText || "" %></div>
|
||||
<div data-options-list></div>
|
||||
<button type="button" class="cms-add-button mt-3" data-add-option>
|
||||
<i class="fas fa-plus me-2"></i>Add Option
|
||||
<i class="fas fa-plus me-2"></i><%= inquiryFieldFields.options?.addLabel || "Add Option" %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,7 +171,7 @@
|
||||
<button type="button" class="drag-handle" title="Drag to reorder">
|
||||
<i class="fas fa-grip-vertical"></i>
|
||||
</button>
|
||||
<input class="form-control" data-option-value maxlength="50" placeholder="Option label">
|
||||
<input class="form-control" data-option-value maxlength="<%= inquiryFieldFields.options?.itemSchema?.maxLength || 50 %>" placeholder="<%= inquiryFieldFields.options?.itemSchema?.placeholder || 'Option label' %>">
|
||||
<button type="button" class="cms-remove-button" data-remove-item title="Remove option">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user