refactor(policies): migrate to block-based content editor

Replace the legacy section-based policy editor with a flexible block-based system. This transition enables real-time previews, a wider variety of content types (headings, quotes, callouts), and improved data normalization.
- Implement `policies-block-editor.js` for dynamic content management
- Add `normalizePoliciesDocument` and `validateContent` utilities to handle data migration and integrity
- Update `policiesController.js` to support the new block structure and validation logic
- Migrate `policies.json` from `sections` array to a `content.blocks` structure
- Remove obsolete section editor views and partials
- Update `policiesConfig.js` to reflect the new editor capabilities
This commit is contained in:
Tống Thành Đạt
2026-04-22 10:14:54 +07:00
parent f26b072954
commit cdaddddfeb
10 changed files with 2438 additions and 1265 deletions
+52 -10
View File
@@ -10,6 +10,11 @@ const writeAuditLog = require("../audit/writeAuditLog");
const diffObject = require("../audit/diffObject");
const jsonHelper = require("../utils/jsonHelper");
const { ensureUniqueIds } = require("../utils/contentEditorIds");
const {
normalizePolicy,
normalizePoliciesDocument,
validateContent,
} = require("../utils/policiesBlockContent");
function formatLastUpdated(date = new Date()) {
return `Last updated: ${new Intl.DateTimeFormat("en-US", {
@@ -29,10 +34,11 @@ function withLastUpdated(payload, { beforeData } = {}) {
"policy",
).map((policy) => {
const existingPolicy = existingPolicies.find((item) => item.id === policy.id);
const normalizedExistingPolicy = normalizePolicy(existingPolicy || policy);
return {
...policy,
sections: Array.isArray(existingPolicy?.sections) ? existingPolicy.sections : [],
content: normalizedExistingPolicy.content,
};
});
@@ -51,6 +57,8 @@ const baseController = createPageContentController({
modelName: "PoliciesPage",
auditAction: AUDIT_ACTIONS.UPDATE_POLICIES,
editorConfig: policiesConfig,
normalizeForEditor: normalizePoliciesDocument,
normalizeForApi: normalizePoliciesDocument,
preparePayload: withLastUpdated,
});
@@ -73,7 +81,7 @@ module.exports = {
async editSections(req, res) {
try {
const doc = await PoliciesPage.getSingle();
const data = doc.toObject();
const data = normalizePoliciesDocument(doc.toObject());
const policy = (data.policies || []).find(
(item) => item.id === req.params.policyId,
);
@@ -95,7 +103,10 @@ module.exports = {
layout: "layouts/main",
title: editorConfig.title,
subtitle: editorConfig.subtitle,
data: { sections: policy.sections || [] },
data: {
policy,
content: policy.content,
},
editorConfig,
activeTab: "sections",
frontendUrl,
@@ -128,9 +139,31 @@ module.exports = {
return req.session.save(() => res.redirect("/admin/policies"));
}
doc.policies[policyIndex].sections = Array.isArray(payload.sections)
? payload.sections
: [];
const policyIds = (doc.policies || [])
.map((item) => item.id)
.filter(Boolean);
const validation = validateContent(payload.content, policyIds);
if (validation.errors.length > 0) {
req.flash("error_msg", validation.errors.join(" "));
return req.session.save(() =>
res.redirect(`/admin/policies/${req.params.policyId}/section`),
);
}
const normalizedPolicies = (doc.policies || []).map((item) =>
normalizePolicy(JSON.parse(JSON.stringify(item))),
);
const currentPolicy = normalizedPolicies[policyIndex];
const updatedPolicy = {
...currentPolicy,
content: validation.content,
};
delete updatedPolicy.sections;
delete updatedPolicy.contentByLanguage;
normalizedPolicies.splice(policyIndex, 1, updatedPolicy);
doc.policies = normalizedPolicies;
doc.hero = {
...(doc.hero || {}),
lastUpdated: formatLastUpdated(),
@@ -154,14 +187,23 @@ module.exports = {
});
}
const finalData = await PoliciesPage.findOne()
const finalData = normalizePoliciesDocument(
(await PoliciesPage.findOne()
.select("-_id -__v -createdAt -updatedAt")
.lean();
.lean()) || {},
);
jsonHelper.writeJsonFile(policiesConfig.dataFile, finalData);
req.flash("success_msg", "Policy sections updated successfully");
const successMessage = validation.warnings.length
? `Policy content updated with warnings: ${validation.warnings.join(" ")}`
: "Policy content updated successfully";
req.flash("success_msg", successMessage);
const redirectUrl =
req.body.intent === "save-back"
? "/admin/policies?tab=policies"
: `/admin/policies/${req.params.policyId}/section`;
return req.session.save(() =>
res.redirect(`/admin/policies/${req.params.policyId}/section`),
res.redirect(redirectUrl),
);
} catch (error) {
console.error("policies section update error:", error);