forked from UKSOURCE/cms.lams
style(cms): improve editor ui and implement collapsible item cards
Enhance the CMS administration interface by introducing a more refined design system for content editors. This update replaces generic Bootstrap utility classes with dedicated CMS styling to improve visual hierarchy and usability. Key changes: - Introduce `cms-editor-group` and `cms-item-card` classes for consistent grouping and item styling. - Implement collapsible functionality for item cards to manage long lists of content more effectively. - Standardize editor buttons, including new styles for drag handles, remove buttons, and add buttons. - Update the fixed bottom action bar with improved backdrop filters, shadows, and spacing. - Refactor editor scripts across Accreditation, Admissions, History, Partnerships, and Policies to utilize the new UI components and collapse logic. - Add comprehensive CSS to `main.ejs` to support the new CMS design tokens and layout behaviors.
This commit is contained in:
@@ -111,26 +111,17 @@
|
|||||||
|
|
||||||
const col = createCol(schema.colClass || "col-12");
|
const col = createCol(schema.colClass || "col-12");
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.className = "border rounded-3 bg-light-subtle p-3";
|
card.className = "cms-editor-group";
|
||||||
|
|
||||||
const header = document.createElement("div");
|
const header = document.createElement("div");
|
||||||
header.className = "d-flex justify-content-between align-items-center mb-3 gap-3";
|
header.className = "mb-3";
|
||||||
header.innerHTML = `
|
header.innerHTML = `
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
||||||
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm">
|
|
||||||
<i class="fas fa-plus me-1"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}
|
|
||||||
</button>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
header.querySelector("button").addEventListener("click", function () {
|
|
||||||
parent[key].push(createDefaultValue(schema.itemSchema));
|
|
||||||
applyAutoSequenceToArray(schema, parent[key]);
|
|
||||||
renderSection(tabKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
card.appendChild(header);
|
card.appendChild(header);
|
||||||
|
|
||||||
if (parent[key].length === 0) {
|
if (parent[key].length === 0) {
|
||||||
@@ -145,11 +136,11 @@
|
|||||||
|
|
||||||
parent[key].forEach((item, index) => {
|
parent[key].forEach((item, index) => {
|
||||||
const itemCard = document.createElement("div");
|
const itemCard = document.createElement("div");
|
||||||
itemCard.className = "card shadow-sm border-0 mb-3";
|
itemCard.className = "card cms-item-card mb-3";
|
||||||
itemCard.dataset.index = String(index);
|
itemCard.dataset.index = String(index);
|
||||||
|
|
||||||
const itemHeader = document.createElement("div");
|
const itemHeader = document.createElement("div");
|
||||||
itemHeader.className = "bg-white border-bottom px-3 py-3 d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
itemHeader.className = "card-header d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
||||||
|
|
||||||
const title = getArrayItemTitle(schema, item, index);
|
const title = getArrayItemTitle(schema, item, index);
|
||||||
const subtitle = getArrayItemSubtitle(schema, item);
|
const subtitle = getArrayItemSubtitle(schema, item);
|
||||||
@@ -157,7 +148,7 @@
|
|||||||
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
||||||
${
|
${
|
||||||
schema.sortable
|
schema.sortable
|
||||||
? '<button type="button" class="btn btn-light btn-sm border drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
? '<button type="button" class="drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
@@ -166,13 +157,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||||
|
<button type="button" class="cms-collapse-toggle" data-toggle-item="true" title="Collapse section">
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
${renderItemActions(schema.itemActions, item)}
|
${renderItemActions(schema.itemActions, item)}
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" data-remove-item="true">
|
<button type="button" class="cms-remove-button" data-remove-item="true" title="Remove item">
|
||||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
itemHeader
|
||||||
|
.querySelector('[data-toggle-item="true"]')
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
itemCard.classList.toggle("is-collapsed");
|
||||||
|
});
|
||||||
|
|
||||||
itemHeader
|
itemHeader
|
||||||
.querySelector('[data-remove-item="true"]')
|
.querySelector('[data-remove-item="true"]')
|
||||||
.addEventListener("click", function () {
|
.addEventListener("click", function () {
|
||||||
@@ -224,6 +224,17 @@
|
|||||||
list.appendChild(itemCard);
|
list.appendChild(itemCard);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addButton = document.createElement("button");
|
||||||
|
addButton.type = "button";
|
||||||
|
addButton.className = "cms-add-button mt-2";
|
||||||
|
addButton.innerHTML = `<i class="fas fa-plus me-2"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}`;
|
||||||
|
addButton.addEventListener("click", function () {
|
||||||
|
parent[key].push(createDefaultValue(schema.itemSchema));
|
||||||
|
applyAutoSequenceToArray(schema, parent[key]);
|
||||||
|
renderSection(tabKey);
|
||||||
|
});
|
||||||
|
card.appendChild(addButton);
|
||||||
|
|
||||||
if (schema.sortable && window.Sortable) {
|
if (schema.sortable && window.Sortable) {
|
||||||
window.Sortable.create(list, {
|
window.Sortable.create(list, {
|
||||||
animation: 150,
|
animation: 150,
|
||||||
@@ -598,7 +609,7 @@
|
|||||||
|
|
||||||
function appendHelp(col, schema, value, extraHint) {
|
function appendHelp(col, schema, value, extraHint) {
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.className = "d-flex justify-content-between gap-3";
|
wrapper.className = "field-meta-row";
|
||||||
|
|
||||||
const help = document.createElement("div");
|
const help = document.createElement("div");
|
||||||
help.className = "form-text";
|
help.className = "form-text";
|
||||||
@@ -608,7 +619,7 @@
|
|||||||
let counter = null;
|
let counter = null;
|
||||||
if (schema.maxLength) {
|
if (schema.maxLength) {
|
||||||
counter = document.createElement("div");
|
counter = document.createElement("div");
|
||||||
counter.className = "form-text text-end ms-auto";
|
counter.className = "field-char-count";
|
||||||
updateCounter(counter, String(value || "").length, schema.maxLength);
|
updateCounter(counter, String(value || "").length, schema.maxLength);
|
||||||
wrapper.appendChild(counter);
|
wrapper.appendChild(counter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,26 +111,17 @@
|
|||||||
|
|
||||||
const col = createCol(schema.colClass || "col-12");
|
const col = createCol(schema.colClass || "col-12");
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.className = "border rounded-3 bg-light-subtle p-3";
|
card.className = "cms-editor-group";
|
||||||
|
|
||||||
const header = document.createElement("div");
|
const header = document.createElement("div");
|
||||||
header.className = "d-flex justify-content-between align-items-center mb-3 gap-3";
|
header.className = "mb-3";
|
||||||
header.innerHTML = `
|
header.innerHTML = `
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
||||||
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm">
|
|
||||||
<i class="fas fa-plus me-1"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}
|
|
||||||
</button>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
header.querySelector("button").addEventListener("click", function () {
|
|
||||||
parent[key].push(createDefaultValue(schema.itemSchema));
|
|
||||||
applyAutoSequenceToArray(schema, parent[key]);
|
|
||||||
renderSection(tabKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
card.appendChild(header);
|
card.appendChild(header);
|
||||||
|
|
||||||
if (parent[key].length === 0) {
|
if (parent[key].length === 0) {
|
||||||
@@ -145,11 +136,11 @@
|
|||||||
|
|
||||||
parent[key].forEach((item, index) => {
|
parent[key].forEach((item, index) => {
|
||||||
const itemCard = document.createElement("div");
|
const itemCard = document.createElement("div");
|
||||||
itemCard.className = "card shadow-sm border-0 mb-3";
|
itemCard.className = "card cms-item-card mb-3";
|
||||||
itemCard.dataset.index = String(index);
|
itemCard.dataset.index = String(index);
|
||||||
|
|
||||||
const itemHeader = document.createElement("div");
|
const itemHeader = document.createElement("div");
|
||||||
itemHeader.className = "bg-white border-bottom px-3 py-3 d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
itemHeader.className = "card-header d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
||||||
|
|
||||||
const title = getArrayItemTitle(schema, item, index);
|
const title = getArrayItemTitle(schema, item, index);
|
||||||
const subtitle = getArrayItemSubtitle(schema, item);
|
const subtitle = getArrayItemSubtitle(schema, item);
|
||||||
@@ -157,7 +148,7 @@
|
|||||||
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
||||||
${
|
${
|
||||||
schema.sortable
|
schema.sortable
|
||||||
? '<button type="button" class="btn btn-light btn-sm border drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
? '<button type="button" class="drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
@@ -166,13 +157,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||||
|
<button type="button" class="cms-collapse-toggle" data-toggle-item="true" title="Collapse section">
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
${renderItemActions(schema.itemActions, item)}
|
${renderItemActions(schema.itemActions, item)}
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" data-remove-item="true">
|
<button type="button" class="cms-remove-button" data-remove-item="true" title="Remove item">
|
||||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
itemHeader
|
||||||
|
.querySelector('[data-toggle-item="true"]')
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
itemCard.classList.toggle("is-collapsed");
|
||||||
|
});
|
||||||
|
|
||||||
itemHeader
|
itemHeader
|
||||||
.querySelector('[data-remove-item="true"]')
|
.querySelector('[data-remove-item="true"]')
|
||||||
.addEventListener("click", function () {
|
.addEventListener("click", function () {
|
||||||
@@ -224,6 +224,17 @@
|
|||||||
list.appendChild(itemCard);
|
list.appendChild(itemCard);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addButton = document.createElement("button");
|
||||||
|
addButton.type = "button";
|
||||||
|
addButton.className = "cms-add-button mt-2";
|
||||||
|
addButton.innerHTML = `<i class="fas fa-plus me-2"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}`;
|
||||||
|
addButton.addEventListener("click", function () {
|
||||||
|
parent[key].push(createDefaultValue(schema.itemSchema));
|
||||||
|
applyAutoSequenceToArray(schema, parent[key]);
|
||||||
|
renderSection(tabKey);
|
||||||
|
});
|
||||||
|
card.appendChild(addButton);
|
||||||
|
|
||||||
if (schema.sortable && window.Sortable) {
|
if (schema.sortable && window.Sortable) {
|
||||||
window.Sortable.create(list, {
|
window.Sortable.create(list, {
|
||||||
animation: 150,
|
animation: 150,
|
||||||
@@ -598,7 +609,7 @@
|
|||||||
|
|
||||||
function appendHelp(col, schema, value, extraHint) {
|
function appendHelp(col, schema, value, extraHint) {
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.className = "d-flex justify-content-between gap-3";
|
wrapper.className = "field-meta-row";
|
||||||
|
|
||||||
const help = document.createElement("div");
|
const help = document.createElement("div");
|
||||||
help.className = "form-text";
|
help.className = "form-text";
|
||||||
@@ -608,7 +619,7 @@
|
|||||||
let counter = null;
|
let counter = null;
|
||||||
if (schema.maxLength) {
|
if (schema.maxLength) {
|
||||||
counter = document.createElement("div");
|
counter = document.createElement("div");
|
||||||
counter.className = "form-text text-end ms-auto";
|
counter.className = "field-char-count";
|
||||||
updateCounter(counter, String(value || "").length, schema.maxLength);
|
updateCounter(counter, String(value || "").length, schema.maxLength);
|
||||||
wrapper.appendChild(counter);
|
wrapper.appendChild(counter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,26 +111,17 @@
|
|||||||
|
|
||||||
const col = createCol(schema.colClass || "col-12");
|
const col = createCol(schema.colClass || "col-12");
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.className = "border rounded-3 bg-light-subtle p-3";
|
card.className = "cms-editor-group";
|
||||||
|
|
||||||
const header = document.createElement("div");
|
const header = document.createElement("div");
|
||||||
header.className = "d-flex justify-content-between align-items-center mb-3 gap-3";
|
header.className = "mb-3";
|
||||||
header.innerHTML = `
|
header.innerHTML = `
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
||||||
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm">
|
|
||||||
<i class="fas fa-plus me-1"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}
|
|
||||||
</button>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
header.querySelector("button").addEventListener("click", function () {
|
|
||||||
parent[key].push(createDefaultValue(schema.itemSchema));
|
|
||||||
applyAutoSequenceToArray(schema, parent[key]);
|
|
||||||
renderSection(tabKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
card.appendChild(header);
|
card.appendChild(header);
|
||||||
|
|
||||||
if (parent[key].length === 0) {
|
if (parent[key].length === 0) {
|
||||||
@@ -145,11 +136,11 @@
|
|||||||
|
|
||||||
parent[key].forEach((item, index) => {
|
parent[key].forEach((item, index) => {
|
||||||
const itemCard = document.createElement("div");
|
const itemCard = document.createElement("div");
|
||||||
itemCard.className = "card shadow-sm border-0 mb-3";
|
itemCard.className = "card cms-item-card mb-3";
|
||||||
itemCard.dataset.index = String(index);
|
itemCard.dataset.index = String(index);
|
||||||
|
|
||||||
const itemHeader = document.createElement("div");
|
const itemHeader = document.createElement("div");
|
||||||
itemHeader.className = "bg-white border-bottom px-3 py-3 d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
itemHeader.className = "card-header d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
||||||
|
|
||||||
const title = getArrayItemTitle(schema, item, index);
|
const title = getArrayItemTitle(schema, item, index);
|
||||||
const subtitle = getArrayItemSubtitle(schema, item);
|
const subtitle = getArrayItemSubtitle(schema, item);
|
||||||
@@ -157,7 +148,7 @@
|
|||||||
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
||||||
${
|
${
|
||||||
schema.sortable
|
schema.sortable
|
||||||
? '<button type="button" class="btn btn-light btn-sm border drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
? '<button type="button" class="drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
@@ -166,13 +157,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||||
|
<button type="button" class="cms-collapse-toggle" data-toggle-item="true" title="Collapse section">
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
${renderItemActions(schema.itemActions, item)}
|
${renderItemActions(schema.itemActions, item)}
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" data-remove-item="true">
|
<button type="button" class="cms-remove-button" data-remove-item="true" title="Remove item">
|
||||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
itemHeader
|
||||||
|
.querySelector('[data-toggle-item="true"]')
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
itemCard.classList.toggle("is-collapsed");
|
||||||
|
});
|
||||||
|
|
||||||
itemHeader
|
itemHeader
|
||||||
.querySelector('[data-remove-item="true"]')
|
.querySelector('[data-remove-item="true"]')
|
||||||
.addEventListener("click", function () {
|
.addEventListener("click", function () {
|
||||||
@@ -224,6 +224,17 @@
|
|||||||
list.appendChild(itemCard);
|
list.appendChild(itemCard);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addButton = document.createElement("button");
|
||||||
|
addButton.type = "button";
|
||||||
|
addButton.className = "cms-add-button mt-2";
|
||||||
|
addButton.innerHTML = `<i class="fas fa-plus me-2"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}`;
|
||||||
|
addButton.addEventListener("click", function () {
|
||||||
|
parent[key].push(createDefaultValue(schema.itemSchema));
|
||||||
|
applyAutoSequenceToArray(schema, parent[key]);
|
||||||
|
renderSection(tabKey);
|
||||||
|
});
|
||||||
|
card.appendChild(addButton);
|
||||||
|
|
||||||
if (schema.sortable && window.Sortable) {
|
if (schema.sortable && window.Sortable) {
|
||||||
window.Sortable.create(list, {
|
window.Sortable.create(list, {
|
||||||
animation: 150,
|
animation: 150,
|
||||||
@@ -598,7 +609,7 @@
|
|||||||
|
|
||||||
function appendHelp(col, schema, value, extraHint) {
|
function appendHelp(col, schema, value, extraHint) {
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.className = "d-flex justify-content-between gap-3";
|
wrapper.className = "field-meta-row";
|
||||||
|
|
||||||
const help = document.createElement("div");
|
const help = document.createElement("div");
|
||||||
help.className = "form-text";
|
help.className = "form-text";
|
||||||
@@ -608,7 +619,7 @@
|
|||||||
let counter = null;
|
let counter = null;
|
||||||
if (schema.maxLength) {
|
if (schema.maxLength) {
|
||||||
counter = document.createElement("div");
|
counter = document.createElement("div");
|
||||||
counter.className = "form-text text-end ms-auto";
|
counter.className = "field-char-count";
|
||||||
updateCounter(counter, String(value || "").length, schema.maxLength);
|
updateCounter(counter, String(value || "").length, schema.maxLength);
|
||||||
wrapper.appendChild(counter);
|
wrapper.appendChild(counter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,30 +19,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border rounded-3 bg-light-subtle p-3 mb-4">
|
<div class="cms-editor-group mb-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="mb-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label fw-semibold mb-1">Category tabs</label>
|
<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>
|
<div class="form-text mt-0">The frontend shows up to 3 direct tabs. Additional tabs move into a More dropdown.</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm" id="addDirectoryTabBtn">
|
|
||||||
<i class="fas fa-plus me-1"></i>Add Tab
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="directoryTabsList"></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
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border rounded-3 bg-light-subtle p-3">
|
<div class="cms-editor-group">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="mb-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label fw-semibold mb-1">Partners</label>
|
<label class="form-label fw-semibold mb-1">Partners</label>
|
||||||
<div class="form-text mt-0">Use a short unique partner key so card state stays stable.</div>
|
<div class="form-text mt-0">Use a short unique partner key so card state stays stable.</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm" id="addPartnerBtn">
|
|
||||||
<i class="fas fa-plus me-1"></i>Add Partner
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="partnersList"></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
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -140,6 +140,9 @@
|
|||||||
renderDirectoryTabs();
|
renderDirectoryTabs();
|
||||||
renderPartners();
|
renderPartners();
|
||||||
});
|
});
|
||||||
|
node.querySelector("[data-toggle-item]")?.addEventListener("click", function () {
|
||||||
|
node.classList.toggle("is-collapsed");
|
||||||
|
});
|
||||||
|
|
||||||
directoryTabsList.appendChild(node);
|
directoryTabsList.appendChild(node);
|
||||||
});
|
});
|
||||||
@@ -204,6 +207,9 @@
|
|||||||
state.directory.partners.splice(index, 1);
|
state.directory.partners.splice(index, 1);
|
||||||
renderPartners();
|
renderPartners();
|
||||||
});
|
});
|
||||||
|
node.querySelector("[data-toggle-item]")?.addEventListener("click", function () {
|
||||||
|
node.classList.toggle("is-collapsed");
|
||||||
|
});
|
||||||
|
|
||||||
partnersList.appendChild(node);
|
partnersList.appendChild(node);
|
||||||
});
|
});
|
||||||
@@ -277,6 +283,9 @@
|
|||||||
field.options.push("");
|
field.options.push("");
|
||||||
renderInquiryFields();
|
renderInquiryFields();
|
||||||
});
|
});
|
||||||
|
node.querySelector("[data-toggle-item]")?.addEventListener("click", function () {
|
||||||
|
node.classList.toggle("is-collapsed");
|
||||||
|
});
|
||||||
|
|
||||||
function toggleOptions() {
|
function toggleOptions() {
|
||||||
optionsWrap.classList.toggle("d-none", typeSelect.value !== "select");
|
optionsWrap.classList.toggle("d-none", typeSelect.value !== "select");
|
||||||
|
|||||||
@@ -15,17 +15,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border rounded-3 bg-light-subtle p-3">
|
<div class="cms-editor-group">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="mb-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label fw-semibold mb-1">Form fields</label>
|
<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>
|
<div class="form-text mt-0">Manage labels, placeholders, type, width, and dropdown options.</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm" id="addInquiryFieldBtn">
|
|
||||||
<i class="fas fa-plus me-1"></i>Add Field
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="inquiryFieldsList"></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
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
<template id="directoryTabTemplate">
|
<template id="directoryTabTemplate">
|
||||||
<div class="card shadow-sm border-0 mb-3" data-item="directory-tab">
|
<div class="card cms-item-card mb-3" data-item="directory-tab">
|
||||||
<div class="bg-white border-bottom px-3 py-3 d-flex justify-content-between align-items-center gap-2">
|
<div class="card-header d-flex justify-content-between align-items-center gap-2">
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<button type="button" class="btn btn-light btn-sm border drag-handle" title="Drag to reorder">
|
<button type="button" class="drag-handle" title="Drag to reorder">
|
||||||
<i class="fas fa-grip-vertical"></i>
|
<i class="fas fa-grip-vertical"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="fw-semibold">Category Tab</div>
|
<div class="fw-semibold">Category Tab</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" data-remove-item>
|
<div class="d-flex align-items-center gap-2">
|
||||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
<button type="button" class="cms-collapse-toggle" data-toggle-item title="Collapse item">
|
||||||
</button>
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="cms-remove-button" data-remove-item title="Remove item">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<label class="form-label fw-semibold">Tab label</label>
|
<label class="form-label fw-semibold">Tab label</label>
|
||||||
@@ -19,10 +24,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="partnerTemplate">
|
<template id="partnerTemplate">
|
||||||
<div class="card shadow-sm border-0 mb-3" data-item="partner">
|
<div class="card cms-item-card mb-3" data-item="partner">
|
||||||
<div class="bg-white border-bottom px-3 py-3 d-flex justify-content-between align-items-center gap-2 flex-wrap">
|
<div class="card-header d-flex justify-content-between align-items-center gap-2 flex-wrap">
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<button type="button" class="btn btn-light btn-sm border drag-handle" title="Drag to reorder">
|
<button type="button" class="drag-handle" title="Drag to reorder">
|
||||||
<i class="fas fa-grip-vertical"></i>
|
<i class="fas fa-grip-vertical"></i>
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
@@ -30,9 +35,14 @@
|
|||||||
<div class="small text-muted" data-subtitle></div>
|
<div class="small text-muted" data-subtitle></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" data-remove-item>
|
<div class="d-flex align-items-center gap-2">
|
||||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
<button type="button" class="cms-collapse-toggle" data-toggle-item title="Collapse item">
|
||||||
</button>
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="cms-remove-button" data-remove-item title="Remove item">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
@@ -87,10 +97,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="inquiryFieldTemplate">
|
<template id="inquiryFieldTemplate">
|
||||||
<div class="card shadow-sm border-0 mb-3" data-item="inquiry-field">
|
<div class="card cms-item-card mb-3" data-item="inquiry-field">
|
||||||
<div class="bg-white border-bottom px-3 py-3 d-flex justify-content-between align-items-center gap-2 flex-wrap">
|
<div class="card-header d-flex justify-content-between align-items-center gap-2 flex-wrap">
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<button type="button" class="btn btn-light btn-sm border drag-handle" title="Drag to reorder">
|
<button type="button" class="drag-handle" title="Drag to reorder">
|
||||||
<i class="fas fa-grip-vertical"></i>
|
<i class="fas fa-grip-vertical"></i>
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
@@ -98,9 +108,14 @@
|
|||||||
<div class="small text-muted" data-subtitle></div>
|
<div class="small text-muted" data-subtitle></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" data-remove-item>
|
<div class="d-flex align-items-center gap-2">
|
||||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
<button type="button" class="cms-collapse-toggle" data-toggle-item title="Collapse item">
|
||||||
</button>
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="cms-remove-button" data-remove-item title="Remove item">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
@@ -138,15 +153,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12" data-options-wrap>
|
<div class="col-12" data-options-wrap>
|
||||||
<div class="border rounded-3 p-3 bg-light">
|
<div class="cms-editor-group">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label fw-semibold mb-0">Dropdown options</label>
|
<label class="form-label fw-semibold mb-0">Dropdown options</label>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm" data-add-option>
|
|
||||||
<i class="fas fa-plus me-1"></i>Add Option
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text mb-2">Only used when the field type is Dropdown.</div>
|
<div class="form-text mb-2">Only used when the field type is Dropdown.</div>
|
||||||
<div data-options-list></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
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -156,11 +171,11 @@
|
|||||||
|
|
||||||
<template id="inquiryOptionTemplate">
|
<template id="inquiryOptionTemplate">
|
||||||
<div class="input-group mb-2" data-item="inquiry-option">
|
<div class="input-group mb-2" data-item="inquiry-option">
|
||||||
<button type="button" class="btn btn-light border drag-handle" title="Drag to reorder">
|
<button type="button" class="drag-handle" title="Drag to reorder">
|
||||||
<i class="fas fa-grip-vertical"></i>
|
<i class="fas fa-grip-vertical"></i>
|
||||||
</button>
|
</button>
|
||||||
<input class="form-control" data-option-value maxlength="50" placeholder="Option label">
|
<input class="form-control" data-option-value maxlength="50" placeholder="Option label">
|
||||||
<button type="button" class="btn btn-outline-danger" data-remove-item>
|
<button type="button" class="cms-remove-button" data-remove-item title="Remove option">
|
||||||
<i class="fas fa-trash-alt"></i>
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -111,26 +111,17 @@
|
|||||||
|
|
||||||
const col = createCol(schema.colClass || "col-12");
|
const col = createCol(schema.colClass || "col-12");
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.className = "border rounded-3 bg-light-subtle p-3";
|
card.className = "cms-editor-group";
|
||||||
|
|
||||||
const header = document.createElement("div");
|
const header = document.createElement("div");
|
||||||
header.className = "d-flex justify-content-between align-items-center mb-3 gap-3";
|
header.className = "mb-3";
|
||||||
header.innerHTML = `
|
header.innerHTML = `
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
||||||
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm">
|
|
||||||
<i class="fas fa-plus me-1"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}
|
|
||||||
</button>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
header.querySelector("button").addEventListener("click", function () {
|
|
||||||
parent[key].push(createDefaultValue(schema.itemSchema));
|
|
||||||
applyAutoSequenceToArray(schema, parent[key]);
|
|
||||||
renderSection(tabKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
card.appendChild(header);
|
card.appendChild(header);
|
||||||
|
|
||||||
if (parent[key].length === 0) {
|
if (parent[key].length === 0) {
|
||||||
@@ -145,11 +136,11 @@
|
|||||||
|
|
||||||
parent[key].forEach((item, index) => {
|
parent[key].forEach((item, index) => {
|
||||||
const itemCard = document.createElement("div");
|
const itemCard = document.createElement("div");
|
||||||
itemCard.className = "card shadow-sm border-0 mb-3";
|
itemCard.className = "card cms-item-card mb-3";
|
||||||
itemCard.dataset.index = String(index);
|
itemCard.dataset.index = String(index);
|
||||||
|
|
||||||
const itemHeader = document.createElement("div");
|
const itemHeader = document.createElement("div");
|
||||||
itemHeader.className = "bg-white border-bottom px-3 py-3 d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
itemHeader.className = "card-header d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
||||||
|
|
||||||
const title = getArrayItemTitle(schema, item, index);
|
const title = getArrayItemTitle(schema, item, index);
|
||||||
const subtitle = getArrayItemSubtitle(schema, item);
|
const subtitle = getArrayItemSubtitle(schema, item);
|
||||||
@@ -157,7 +148,7 @@
|
|||||||
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
||||||
${
|
${
|
||||||
schema.sortable
|
schema.sortable
|
||||||
? '<button type="button" class="btn btn-light btn-sm border drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
? '<button type="button" class="drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
@@ -166,13 +157,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||||
|
<button type="button" class="cms-collapse-toggle" data-toggle-item="true" title="Collapse section">
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
${renderItemActions(schema.itemActions, item)}
|
${renderItemActions(schema.itemActions, item)}
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" data-remove-item="true">
|
<button type="button" class="cms-remove-button" data-remove-item="true" title="Remove item">
|
||||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
itemHeader
|
||||||
|
.querySelector('[data-toggle-item="true"]')
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
itemCard.classList.toggle("is-collapsed");
|
||||||
|
});
|
||||||
|
|
||||||
itemHeader
|
itemHeader
|
||||||
.querySelector('[data-remove-item="true"]')
|
.querySelector('[data-remove-item="true"]')
|
||||||
.addEventListener("click", function () {
|
.addEventListener("click", function () {
|
||||||
@@ -224,6 +224,17 @@
|
|||||||
list.appendChild(itemCard);
|
list.appendChild(itemCard);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addButton = document.createElement("button");
|
||||||
|
addButton.type = "button";
|
||||||
|
addButton.className = "cms-add-button mt-2";
|
||||||
|
addButton.innerHTML = `<i class="fas fa-plus me-2"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}`;
|
||||||
|
addButton.addEventListener("click", function () {
|
||||||
|
parent[key].push(createDefaultValue(schema.itemSchema));
|
||||||
|
applyAutoSequenceToArray(schema, parent[key]);
|
||||||
|
renderSection(tabKey);
|
||||||
|
});
|
||||||
|
card.appendChild(addButton);
|
||||||
|
|
||||||
if (schema.sortable && window.Sortable) {
|
if (schema.sortable && window.Sortable) {
|
||||||
window.Sortable.create(list, {
|
window.Sortable.create(list, {
|
||||||
animation: 150,
|
animation: 150,
|
||||||
@@ -598,7 +609,7 @@
|
|||||||
|
|
||||||
function appendHelp(col, schema, value, extraHint) {
|
function appendHelp(col, schema, value, extraHint) {
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.className = "d-flex justify-content-between gap-3";
|
wrapper.className = "field-meta-row";
|
||||||
|
|
||||||
const help = document.createElement("div");
|
const help = document.createElement("div");
|
||||||
help.className = "form-text";
|
help.className = "form-text";
|
||||||
@@ -608,7 +619,7 @@
|
|||||||
let counter = null;
|
let counter = null;
|
||||||
if (schema.maxLength) {
|
if (schema.maxLength) {
|
||||||
counter = document.createElement("div");
|
counter = document.createElement("div");
|
||||||
counter.className = "form-text text-end ms-auto";
|
counter.className = "field-char-count";
|
||||||
updateCounter(counter, String(value || "").length, schema.maxLength);
|
updateCounter(counter, String(value || "").length, schema.maxLength);
|
||||||
wrapper.appendChild(counter);
|
wrapper.appendChild(counter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,26 +111,17 @@
|
|||||||
|
|
||||||
const col = createCol(schema.colClass || "col-12");
|
const col = createCol(schema.colClass || "col-12");
|
||||||
const card = document.createElement("div");
|
const card = document.createElement("div");
|
||||||
card.className = "border rounded-3 bg-light-subtle p-3";
|
card.className = "cms-editor-group";
|
||||||
|
|
||||||
const header = document.createElement("div");
|
const header = document.createElement("div");
|
||||||
header.className = "d-flex justify-content-between align-items-center mb-3 gap-3";
|
header.className = "mb-3";
|
||||||
header.innerHTML = `
|
header.innerHTML = `
|
||||||
<div>
|
<div class="mb-2">
|
||||||
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
||||||
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm">
|
|
||||||
<i class="fas fa-plus me-1"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}
|
|
||||||
</button>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
header.querySelector("button").addEventListener("click", function () {
|
|
||||||
parent[key].push(createDefaultValue(schema.itemSchema));
|
|
||||||
applyAutoSequenceToArray(schema, parent[key]);
|
|
||||||
renderSection(tabKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
card.appendChild(header);
|
card.appendChild(header);
|
||||||
|
|
||||||
if (parent[key].length === 0) {
|
if (parent[key].length === 0) {
|
||||||
@@ -145,11 +136,11 @@
|
|||||||
|
|
||||||
parent[key].forEach((item, index) => {
|
parent[key].forEach((item, index) => {
|
||||||
const itemCard = document.createElement("div");
|
const itemCard = document.createElement("div");
|
||||||
itemCard.className = "card shadow-sm border-0 mb-3";
|
itemCard.className = "card cms-item-card mb-3";
|
||||||
itemCard.dataset.index = String(index);
|
itemCard.dataset.index = String(index);
|
||||||
|
|
||||||
const itemHeader = document.createElement("div");
|
const itemHeader = document.createElement("div");
|
||||||
itemHeader.className = "bg-white border-bottom px-3 py-3 d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
itemHeader.className = "card-header d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
||||||
|
|
||||||
const title = getArrayItemTitle(schema, item, index);
|
const title = getArrayItemTitle(schema, item, index);
|
||||||
const subtitle = getArrayItemSubtitle(schema, item);
|
const subtitle = getArrayItemSubtitle(schema, item);
|
||||||
@@ -157,7 +148,7 @@
|
|||||||
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
||||||
${
|
${
|
||||||
schema.sortable
|
schema.sortable
|
||||||
? '<button type="button" class="btn btn-light btn-sm border drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
? '<button type="button" class="drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></button>'
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
@@ -166,13 +157,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||||
|
<button type="button" class="cms-collapse-toggle" data-toggle-item="true" title="Collapse section">
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</button>
|
||||||
${renderItemActions(schema.itemActions, item)}
|
${renderItemActions(schema.itemActions, item)}
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" data-remove-item="true">
|
<button type="button" class="cms-remove-button" data-remove-item="true" title="Remove item">
|
||||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
itemHeader
|
||||||
|
.querySelector('[data-toggle-item="true"]')
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
itemCard.classList.toggle("is-collapsed");
|
||||||
|
});
|
||||||
|
|
||||||
itemHeader
|
itemHeader
|
||||||
.querySelector('[data-remove-item="true"]')
|
.querySelector('[data-remove-item="true"]')
|
||||||
.addEventListener("click", function () {
|
.addEventListener("click", function () {
|
||||||
@@ -224,6 +224,17 @@
|
|||||||
list.appendChild(itemCard);
|
list.appendChild(itemCard);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addButton = document.createElement("button");
|
||||||
|
addButton.type = "button";
|
||||||
|
addButton.className = "cms-add-button mt-2";
|
||||||
|
addButton.innerHTML = `<i class="fas fa-plus me-2"></i>${escapeHtml(schema.addLabel || `Add ${schema.itemLabel || "Item"}`)}`;
|
||||||
|
addButton.addEventListener("click", function () {
|
||||||
|
parent[key].push(createDefaultValue(schema.itemSchema));
|
||||||
|
applyAutoSequenceToArray(schema, parent[key]);
|
||||||
|
renderSection(tabKey);
|
||||||
|
});
|
||||||
|
card.appendChild(addButton);
|
||||||
|
|
||||||
if (schema.sortable && window.Sortable) {
|
if (schema.sortable && window.Sortable) {
|
||||||
window.Sortable.create(list, {
|
window.Sortable.create(list, {
|
||||||
animation: 150,
|
animation: 150,
|
||||||
@@ -598,7 +609,7 @@
|
|||||||
|
|
||||||
function appendHelp(col, schema, value, extraHint) {
|
function appendHelp(col, schema, value, extraHint) {
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.className = "d-flex justify-content-between gap-3";
|
wrapper.className = "field-meta-row";
|
||||||
|
|
||||||
const help = document.createElement("div");
|
const help = document.createElement("div");
|
||||||
help.className = "form-text";
|
help.className = "form-text";
|
||||||
@@ -608,7 +619,7 @@
|
|||||||
let counter = null;
|
let counter = null;
|
||||||
if (schema.maxLength) {
|
if (schema.maxLength) {
|
||||||
counter = document.createElement("div");
|
counter = document.createElement("div");
|
||||||
counter.className = "form-text text-end ms-auto";
|
counter.className = "field-char-count";
|
||||||
updateCounter(counter, String(value || "").length, schema.maxLength);
|
updateCounter(counter, String(value || "").length, schema.maxLength);
|
||||||
wrapper.appendChild(counter);
|
wrapper.appendChild(counter);
|
||||||
}
|
}
|
||||||
|
|||||||
+177
-6
@@ -283,18 +283,22 @@
|
|||||||
|
|
||||||
.fixed-bottom-buttons {
|
.fixed-bottom-buttons {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding: 1rem;
|
bottom: 0;
|
||||||
|
padding: 0.875rem 1.5rem;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
width: 25%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 0.5rem;
|
gap: 0.75rem;
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
border-top: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.08);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-bottom-buttons .btn {
|
.fixed-bottom-buttons .btn {
|
||||||
min-width: 120px;
|
min-width: 136px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -303,7 +307,174 @@
|
|||||||
|
|
||||||
/* Add padding to prevent content from being hidden behind fixed buttons */
|
/* Add padding to prevent content from being hidden behind fixed buttons */
|
||||||
.content-with-fixed-buttons {
|
.content-with-fixed-buttons {
|
||||||
padding-bottom: 80px;
|
padding-bottom: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .card {
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.16) !important;
|
||||||
|
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.04) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .card-header {
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid rgba(148, 163, 184, 0.14);
|
||||||
|
padding: 0.875rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .card-body {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .form-control,
|
||||||
|
.content-with-fixed-buttons .form-select {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .form-control:focus,
|
||||||
|
.content-with-fixed-buttons .form-select:focus {
|
||||||
|
border-color: rgba(184, 183, 106, 0.55);
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(184, 183, 106, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .form-text {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .card-header-tabs {
|
||||||
|
margin-bottom: -0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .nav-tabs {
|
||||||
|
border-bottom: 0;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .nav-tabs .nav-link {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
color: #64748b;
|
||||||
|
padding: 0.75rem 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-with-fixed-buttons .nav-tabs .nav-link.active {
|
||||||
|
color: #0f172a;
|
||||||
|
background: rgba(248, 250, 252, 0.96);
|
||||||
|
box-shadow: inset 0 -2px 0 var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-editor-group {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.12);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-item-card {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.12) !important;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-item-card .card-header {
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: 1px solid rgba(148, 163, 184, 0.1);
|
||||||
|
padding: 0.75rem 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-item-card .card-body {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-item-card.is-collapsed .card-body {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-item-card .drag-handle {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: #94a3b8;
|
||||||
|
padding: 0.25rem 0.375rem;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-item-card .drag-handle:hover {
|
||||||
|
color: #475569;
|
||||||
|
background: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-collapse-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #94a3b8;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-collapse-toggle:hover {
|
||||||
|
background: rgba(148, 163, 184, 0.12);
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-collapse-toggle i {
|
||||||
|
transition: transform 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-item-card.is-collapsed .cms-collapse-toggle i {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-remove-button {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #94a3b8;
|
||||||
|
padding: 0.25rem;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-remove-button:hover {
|
||||||
|
color: #ef4444;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-add-button {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px dashed rgba(148, 163, 184, 0.55);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
color: #475569;
|
||||||
|
padding: 0.8rem 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-add-button:hover {
|
||||||
|
background: #fff;
|
||||||
|
border-color: rgba(184, 183, 106, 0.65);
|
||||||
|
color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-meta-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-char-count {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
|||||||
Reference in New Issue
Block a user