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:
@@ -24,7 +24,14 @@
|
||||
|
||||
document.querySelectorAll("[data-tab-key]").forEach((tabTrigger) => {
|
||||
tabTrigger.addEventListener("shown.bs.tab", function () {
|
||||
activeTabInput.value = this.dataset.tabKey;
|
||||
const tabKey = this.dataset.tabKey;
|
||||
|
||||
if (!tabKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeTabInput.value = tabKey;
|
||||
updateTabUrl(tabKey);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,6 +51,16 @@
|
||||
config.tabs.forEach((tab) => renderSection(tab.key));
|
||||
}
|
||||
|
||||
function updateTabUrl(tabKey) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("tab", tabKey);
|
||||
window.history.replaceState(
|
||||
{},
|
||||
"",
|
||||
`${url.pathname}?${url.searchParams.toString()}${url.hash}`,
|
||||
);
|
||||
}
|
||||
|
||||
function renderSection(tabKey) {
|
||||
const tab = config.tabs.find((item) => item.key === tabKey);
|
||||
const container = document.querySelector(
|
||||
@@ -53,10 +70,18 @@
|
||||
if (!tab || !container) return;
|
||||
|
||||
container.innerHTML = "";
|
||||
renderField(tab.schema, container, state, tab.schema.key, tabKey);
|
||||
renderField(tab.schema, container, state, tab.schema.key, tabKey, {
|
||||
path: tab.schema.key,
|
||||
root: state,
|
||||
item: null,
|
||||
});
|
||||
}
|
||||
|
||||
function renderField(schema, container, parent, key, tabKey) {
|
||||
function renderField(schema, container, parent, key, tabKey, context) {
|
||||
if (schema.visibleWhen && !passesVisibility(schema.visibleWhen, parent, context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (schema.type === "object") {
|
||||
if (!isObject(parent[key])) {
|
||||
parent[key] = {};
|
||||
@@ -67,7 +92,11 @@
|
||||
container.appendChild(groupWrapper);
|
||||
|
||||
(schema.fields || []).forEach((field) => {
|
||||
renderField(field, groupWrapper, parent[key], field.key, tabKey);
|
||||
renderField(field, groupWrapper, parent[key], field.key, tabKey, {
|
||||
path: appendPath(context.path, field.key),
|
||||
root: context.root,
|
||||
item: parent[key],
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -77,24 +106,27 @@
|
||||
parent[key] = [];
|
||||
}
|
||||
|
||||
applyAutoSequenceToArray(schema, parent[key]);
|
||||
|
||||
const col = createCol(schema.colClass || "col-12");
|
||||
const card = document.createElement("div");
|
||||
card.className = "border rounded-3 bg-light-subtle p-3";
|
||||
|
||||
const header = document.createElement("div");
|
||||
header.className = "d-flex justify-content-between align-items-center mb-3";
|
||||
header.className = "d-flex justify-content-between align-items-center mb-3 gap-3";
|
||||
header.innerHTML = `
|
||||
<div>
|
||||
<label class="form-label fw-semibold mb-1">${escapeHtml(schema.label)}</label>
|
||||
${schema.helpText ? `<div class="form-text mt-0">${escapeHtml(schema.helpText)}</div>` : ""}
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-plus me-1"></i>Add ${escapeHtml(schema.itemLabel || "Item")}
|
||||
<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);
|
||||
});
|
||||
|
||||
@@ -103,47 +135,114 @@
|
||||
if (parent[key].length === 0) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "text-muted small";
|
||||
empty.textContent = `No ${schema.itemLabel || "items"} yet.`;
|
||||
empty.textContent = schema.emptyText || `No ${schema.itemLabel || "items"} yet.`;
|
||||
card.appendChild(empty);
|
||||
} else {
|
||||
const list = document.createElement("div");
|
||||
list.className = "page-editor-array-list";
|
||||
card.appendChild(list);
|
||||
|
||||
parent[key].forEach((item, index) => {
|
||||
const itemCard = document.createElement("div");
|
||||
itemCard.className = "card shadow-sm border-0 mb-3";
|
||||
itemCard.dataset.index = String(index);
|
||||
|
||||
const itemHeader = document.createElement("div");
|
||||
itemHeader.className = "card-header bg-white d-flex justify-content-between align-items-center";
|
||||
itemHeader.className = "card-header bg-white d-flex justify-content-between align-items-center gap-2 flex-wrap";
|
||||
|
||||
const title = getArrayItemTitle(schema, item, index);
|
||||
const subtitle = getArrayItemSubtitle(schema, item);
|
||||
itemHeader.innerHTML = `
|
||||
<span class="fw-semibold">${escapeHtml(schema.itemLabel || "Item")} ${index + 1}</span>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm">
|
||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
||||
</button>
|
||||
<div class="d-flex align-items-center gap-2 flex-grow-1">
|
||||
${
|
||||
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>'
|
||||
: ""
|
||||
}
|
||||
<div>
|
||||
<div class="fw-semibold">${escapeHtml(title)}</div>
|
||||
${subtitle ? `<div class="small text-muted">${escapeHtml(subtitle)}</div>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
${renderItemActions(schema.itemActions, item)}
|
||||
<button type="button" class="btn btn-outline-danger btn-sm" data-remove-item="true">
|
||||
<i class="fas fa-trash-alt me-1"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
itemHeader.querySelector("button").addEventListener("click", function () {
|
||||
parent[key].splice(index, 1);
|
||||
renderSection(tabKey);
|
||||
|
||||
itemHeader
|
||||
.querySelector('[data-remove-item="true"]')
|
||||
.addEventListener("click", function () {
|
||||
parent[key].splice(index, 1);
|
||||
applyAutoSequenceToArray(schema, parent[key]);
|
||||
renderSection(tabKey);
|
||||
});
|
||||
|
||||
itemHeader.querySelectorAll("[data-item-href]").forEach((actionButton) => {
|
||||
actionButton.addEventListener("click", function () {
|
||||
window.location.href = actionButton.dataset.itemHref;
|
||||
});
|
||||
});
|
||||
|
||||
const itemBody = document.createElement("div");
|
||||
itemBody.className = "card-body";
|
||||
|
||||
if (schema.itemSchema.type === "primitive") {
|
||||
renderPrimitiveArrayItem(schema, itemBody, parent[key], index);
|
||||
renderPrimitiveArrayItem(schema, itemBody, parent[key], index, tabKey, context);
|
||||
} else if (schema.itemSchema.type === "variant") {
|
||||
renderVariantArrayItem(schema.itemSchema, itemBody, parent[key], index, tabKey);
|
||||
renderVariantArrayItem(
|
||||
schema.itemSchema,
|
||||
itemBody,
|
||||
parent[key],
|
||||
index,
|
||||
tabKey,
|
||||
{
|
||||
path: appendPath(context.path, String(index)),
|
||||
root: context.root,
|
||||
item: parent[key][index],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
const bodyRow = document.createElement("div");
|
||||
bodyRow.className = "row g-3";
|
||||
itemBody.appendChild(bodyRow);
|
||||
|
||||
(schema.itemSchema.fields || []).forEach((field) => {
|
||||
renderField(field, bodyRow, parent[key][index], field.key, tabKey);
|
||||
renderField(field, bodyRow, parent[key][index], field.key, tabKey, {
|
||||
path: appendPath(context.path, `${index}.${field.key}`),
|
||||
root: context.root,
|
||||
item: parent[key][index],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
itemCard.appendChild(itemHeader);
|
||||
itemCard.appendChild(itemBody);
|
||||
card.appendChild(itemCard);
|
||||
list.appendChild(itemCard);
|
||||
});
|
||||
|
||||
if (schema.sortable && window.Sortable) {
|
||||
window.Sortable.create(list, {
|
||||
animation: 150,
|
||||
handle: ".drag-handle",
|
||||
onEnd: function (event) {
|
||||
if (
|
||||
typeof event.oldIndex !== "number" ||
|
||||
typeof event.newIndex !== "number" ||
|
||||
event.oldIndex === event.newIndex
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const movedItem = parent[key].splice(event.oldIndex, 1)[0];
|
||||
parent[key].splice(event.newIndex, 0, movedItem);
|
||||
applyAutoSequenceToArray(schema, parent[key]);
|
||||
renderSection(tabKey);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
col.appendChild(card);
|
||||
@@ -156,10 +255,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
renderLeafField(schema, container, parent, key);
|
||||
renderLeafField(schema, container, parent, key, context);
|
||||
}
|
||||
|
||||
function renderPrimitiveArrayItem(arraySchema, container, targetArray, index) {
|
||||
function renderPrimitiveArrayItem(arraySchema, container, targetArray, index, tabKey, context) {
|
||||
const fieldSchema = arraySchema.itemSchema;
|
||||
const row = document.createElement("div");
|
||||
row.className = "row g-3";
|
||||
@@ -179,20 +278,25 @@
|
||||
row,
|
||||
holder,
|
||||
"value",
|
||||
{
|
||||
path: appendPath(context.path, String(index)),
|
||||
root: context.root,
|
||||
item: holder,
|
||||
},
|
||||
);
|
||||
|
||||
const input = row.querySelector("input, textarea");
|
||||
const input = row.querySelector("input, textarea, select");
|
||||
if (input) {
|
||||
input.addEventListener("input", function () {
|
||||
targetArray[index] = holder.value;
|
||||
});
|
||||
input.addEventListener("change", function () {
|
||||
targetArray[index] = holder.value;
|
||||
});
|
||||
const sync = function () {
|
||||
targetArray[index] =
|
||||
fieldSchema.fieldType === "number" ? Number(holder.value || 0) : holder.value;
|
||||
};
|
||||
input.addEventListener("input", sync);
|
||||
input.addEventListener("change", sync);
|
||||
}
|
||||
}
|
||||
|
||||
function renderVariantArrayItem(variantSchema, container, targetArray, index, tabKey) {
|
||||
function renderVariantArrayItem(variantSchema, container, targetArray, index, tabKey, context) {
|
||||
const item = targetArray[index];
|
||||
if (!isObject(item)) {
|
||||
targetArray[index] = {};
|
||||
@@ -212,13 +316,14 @@
|
||||
renderLeafField(
|
||||
{
|
||||
key: variantSchema.discriminator,
|
||||
label: "Section Type",
|
||||
label: "Section type",
|
||||
type: "select",
|
||||
options: variantSchema.options,
|
||||
},
|
||||
typeRow,
|
||||
targetArray[index],
|
||||
variantSchema.discriminator,
|
||||
context,
|
||||
);
|
||||
|
||||
const selectInput = typeRow.querySelector("select");
|
||||
@@ -236,14 +341,20 @@
|
||||
container.appendChild(sectionRow);
|
||||
|
||||
(currentVariant.schema.fields || []).forEach((field) => {
|
||||
renderField(field, sectionRow, targetArray[index], field.key, tabKey);
|
||||
renderField(field, sectionRow, targetArray[index], field.key, tabKey, {
|
||||
path: appendPath(context.path, field.key),
|
||||
root: context.root,
|
||||
item: targetArray[index],
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderLeafField(schema, container, parent, key) {
|
||||
function renderLeafField(schema, container, parent, key, context) {
|
||||
if (schema.type === "hidden") {
|
||||
parent[key] = parent[key] || "";
|
||||
if (typeof parent[key] === "undefined" || parent[key] === null) {
|
||||
parent[key] = schema.defaultValue || "";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -317,13 +428,17 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const input =
|
||||
schema.type === "select" ? document.createElement("select") : document.createElement("input");
|
||||
|
||||
input.className = "form-control";
|
||||
if (schema.type === "icon") {
|
||||
renderIconField(schema, col, parent, key);
|
||||
container.appendChild(col);
|
||||
return;
|
||||
}
|
||||
|
||||
if (schema.type === "select") {
|
||||
(schema.options || []).forEach((option) => {
|
||||
const input = document.createElement("select");
|
||||
input.className = "form-select";
|
||||
const options = resolveOptions(schema, context.root);
|
||||
options.forEach((option) => {
|
||||
const optionEl = document.createElement("option");
|
||||
if (typeof option === "string") {
|
||||
optionEl.value = option;
|
||||
@@ -338,31 +453,115 @@
|
||||
parent[key] = input.value;
|
||||
input.addEventListener("change", function () {
|
||||
parent[key] = input.value;
|
||||
renderAllSections();
|
||||
});
|
||||
} else {
|
||||
input.type =
|
||||
schema.type === "url" || schema.type === "number" || schema.type === "color"
|
||||
? schema.type
|
||||
: "text";
|
||||
col.appendChild(input);
|
||||
appendHelp(col, schema, parent[key]);
|
||||
container.appendChild(col);
|
||||
return;
|
||||
}
|
||||
|
||||
if (schema.type === "combobox") {
|
||||
const input = document.createElement("input");
|
||||
const listId = `list-${sanitizeId(context.path)}-${sanitizeId(key)}`;
|
||||
input.className = "form-control";
|
||||
input.type = "text";
|
||||
input.value = parent[key] || "";
|
||||
if (schema.placeholder) input.placeholder = schema.placeholder;
|
||||
if (schema.maxLength) input.maxLength = schema.maxLength;
|
||||
if (schema.step) input.step = schema.step;
|
||||
if (schema.type === "icon") {
|
||||
input.setAttribute("list", "cms-icon-options");
|
||||
}
|
||||
input.setAttribute("list", listId);
|
||||
input.addEventListener("input", function () {
|
||||
parent[key] =
|
||||
schema.type === "number" ? Number(input.value || 0) : input.value;
|
||||
updateCounter(counter, String(input.value || "").length, schema.maxLength);
|
||||
parent[key] = input.value;
|
||||
updateCounter(counter, input.value.length, schema.maxLength);
|
||||
});
|
||||
|
||||
const dataList = document.createElement("datalist");
|
||||
dataList.id = listId;
|
||||
resolveOptions(schema, context.root).forEach((option) => {
|
||||
const item = document.createElement("option");
|
||||
item.value = typeof option === "string" ? option : option.value;
|
||||
item.label = typeof option === "string" ? option : option.label;
|
||||
dataList.appendChild(item);
|
||||
});
|
||||
|
||||
col.appendChild(input);
|
||||
col.appendChild(dataList);
|
||||
const counter = appendHelp(col, schema, parent[key]);
|
||||
container.appendChild(col);
|
||||
return;
|
||||
}
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.className = "form-control";
|
||||
input.type =
|
||||
schema.type === "number" || schema.type === "color" ? schema.type : "text";
|
||||
input.value = parent[key] || "";
|
||||
if (schema.placeholder) input.placeholder = schema.placeholder;
|
||||
if (schema.maxLength) input.maxLength = schema.maxLength;
|
||||
if (schema.step) input.step = schema.step;
|
||||
input.addEventListener("input", function () {
|
||||
parent[key] = schema.type === "number" ? Number(input.value || 0) : input.value;
|
||||
updateCounter(counter, String(input.value || "").length, schema.maxLength);
|
||||
});
|
||||
|
||||
col.appendChild(input);
|
||||
const counter = appendHelp(col, schema, parent[key]);
|
||||
container.appendChild(col);
|
||||
}
|
||||
|
||||
function renderIconField(schema, col, parent, key) {
|
||||
const selected = parent[key] || "";
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "border rounded-3 bg-white p-3";
|
||||
|
||||
const top = document.createElement("div");
|
||||
top.className = "d-flex gap-2 align-items-center mb-3";
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.className = "form-control";
|
||||
input.value = selected;
|
||||
input.placeholder = "fa-shield-check";
|
||||
input.setAttribute("list", "cms-icon-options");
|
||||
top.appendChild(input);
|
||||
|
||||
const preview = document.createElement("div");
|
||||
preview.className = "border rounded-2 px-3 d-flex align-items-center justify-content-center";
|
||||
preview.style.width = "52px";
|
||||
preview.innerHTML = selected
|
||||
? `<i class="fa-solid ${escapeHtml(selected)}"></i>`
|
||||
: '<span class="text-muted small">--</span>';
|
||||
top.appendChild(preview);
|
||||
wrapper.appendChild(top);
|
||||
|
||||
const grid = document.createElement("div");
|
||||
grid.className = "d-flex flex-wrap gap-2";
|
||||
(schema.options || []).forEach((option) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = `btn btn-sm ${selected === option ? "btn-primary" : "btn-outline-secondary"}`;
|
||||
button.innerHTML = `<i class="fa-solid ${escapeHtml(option)} me-1"></i>${escapeHtml(option)}`;
|
||||
button.addEventListener("click", function () {
|
||||
parent[key] = option;
|
||||
input.value = option;
|
||||
preview.innerHTML = `<i class="fa-solid ${escapeHtml(option)}"></i>`;
|
||||
renderAllSections();
|
||||
});
|
||||
grid.appendChild(button);
|
||||
});
|
||||
wrapper.appendChild(grid);
|
||||
|
||||
input.addEventListener("input", function () {
|
||||
parent[key] = input.value;
|
||||
preview.innerHTML = input.value
|
||||
? `<i class="fa-solid ${escapeHtml(input.value)}"></i>`
|
||||
: '<span class="text-muted small">--</span>';
|
||||
});
|
||||
|
||||
col.appendChild(wrapper);
|
||||
appendHelp(col, schema, parent[key]);
|
||||
}
|
||||
|
||||
function renderCheckbox(schema, container, parent, key) {
|
||||
if (typeof parent[key] !== "boolean") {
|
||||
parent[key] = Boolean(parent[key]);
|
||||
@@ -492,7 +691,7 @@
|
||||
|
||||
function createDefaultValue(schema) {
|
||||
if (!schema) return "";
|
||||
if (schema.type === "primitive") return "";
|
||||
if (schema.type === "primitive") return schema.fieldType === "number" ? 0 : "";
|
||||
if (schema.type === "variant") {
|
||||
return { [schema.discriminator]: schema.options[0].value };
|
||||
}
|
||||
@@ -503,7 +702,7 @@
|
||||
else if (field.type === "object") value[field.key] = createDefaultValue(field);
|
||||
else if (field.type === "checkbox") value[field.key] = false;
|
||||
else if (field.type === "number") value[field.key] = 0;
|
||||
else value[field.key] = "";
|
||||
else value[field.key] = field.defaultValue || "";
|
||||
});
|
||||
return value;
|
||||
}
|
||||
@@ -517,7 +716,7 @@
|
||||
}
|
||||
|
||||
function inferColClass(type) {
|
||||
if (type === "textarea" || type === "image") return "col-12";
|
||||
if (type === "textarea" || type === "image" || type === "icon") return "col-12";
|
||||
if (type === "checkbox") return "col-12";
|
||||
return "col-md-6";
|
||||
}
|
||||
@@ -529,6 +728,14 @@
|
||||
return `${backendUrl}/${path}`;
|
||||
}
|
||||
|
||||
function resolveOptions(schema, root) {
|
||||
if (schema.optionsPath) {
|
||||
const value = getValueByPath(root, schema.optionsPath);
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
return schema.options || [];
|
||||
}
|
||||
|
||||
function collectIcons(schema) {
|
||||
if (!schema) return [];
|
||||
if (schema.type === "icon") return schema.options || [];
|
||||
@@ -556,6 +763,99 @@
|
||||
document.body.appendChild(dataList);
|
||||
}
|
||||
|
||||
function applyAutoSequenceToArray(schema, targetArray) {
|
||||
if (!schema || !Array.isArray(targetArray) || schema.itemSchema.type !== "object") {
|
||||
return;
|
||||
}
|
||||
|
||||
(schema.itemSchema.fields || []).forEach((field) => {
|
||||
if (field.type !== "hidden" || !field.autoSequence) {
|
||||
return;
|
||||
}
|
||||
|
||||
targetArray.forEach((item, index) => {
|
||||
const value = String(index + 1);
|
||||
const padLength = field.autoSequence.padLength || 0;
|
||||
item[field.key] = padLength > 0 ? value.padStart(padLength, "0") : value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getArrayItemTitle(schema, item, index) {
|
||||
const value =
|
||||
item && schema.itemTitleKey && typeof item[schema.itemTitleKey] !== "undefined"
|
||||
? item[schema.itemTitleKey]
|
||||
: null;
|
||||
|
||||
return value || `${schema.itemLabel || "Item"} ${index + 1}`;
|
||||
}
|
||||
|
||||
function getArrayItemSubtitle(schema, item) {
|
||||
if (!item || !schema.itemSubtitleKey) return "";
|
||||
return item[schema.itemSubtitleKey] || "";
|
||||
}
|
||||
|
||||
function renderItemActions(actions, item) {
|
||||
if (!Array.isArray(actions) || !actions.length || !item) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return actions
|
||||
.map((action) => {
|
||||
const href = fillTemplate(action.hrefTemplate, item);
|
||||
if (!href) return "";
|
||||
return `<button type="button" class="${escapeHtml(
|
||||
action.className || "btn btn-outline-primary btn-sm",
|
||||
)}" data-item-href="${escapeHtml(href)}">${
|
||||
action.icon ? `<i class="${escapeHtml(action.icon)} me-1"></i>` : ""
|
||||
}${escapeHtml(action.label || "Open")}</button>`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
function passesVisibility(condition, parent, context) {
|
||||
if (!condition || !condition.path) return true;
|
||||
const target =
|
||||
condition.path === "$item"
|
||||
? context.item
|
||||
: getValueByPath(parent, condition.path) ??
|
||||
getValueByPath(context.item, condition.path) ??
|
||||
getValueByPath(context.root, condition.path);
|
||||
|
||||
if (Array.isArray(condition.equals)) {
|
||||
return condition.equals.includes(target);
|
||||
}
|
||||
|
||||
return target === condition.equals;
|
||||
}
|
||||
|
||||
function appendPath(basePath, segment) {
|
||||
return basePath ? `${basePath}.${segment}` : segment;
|
||||
}
|
||||
|
||||
function getValueByPath(target, path) {
|
||||
if (!target || !path) return undefined;
|
||||
return String(path)
|
||||
.split(".")
|
||||
.reduce((current, segment) => {
|
||||
if (current === null || typeof current === "undefined") return undefined;
|
||||
return current[segment];
|
||||
}, target);
|
||||
}
|
||||
|
||||
function fillTemplate(template, item) {
|
||||
if (!template) return "";
|
||||
return template.replace(/\{([^}]+)\}/g, function (_, key) {
|
||||
return item[key] || "";
|
||||
});
|
||||
}
|
||||
|
||||
function sanitizeId(value) {
|
||||
return String(value || "")
|
||||
.replace(/[^a-zA-Z0-9_-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
function isObject(value) {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user