From a44d005c1387ef9656ad5f5efc82a43609672dec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?T=E1=BB=91ng=20Th=C3=A0nh=20=C4=90=E1=BA=A1t?=
<84076965+tongthanhdat009@users.noreply.github.com>
Date: Wed, 22 Apr 2026 12:02:00 +0700
Subject: [PATCH] feat(cms): enhance policy editor with fullscreen mode and
date picking
Improve the policy content management experience by adding a dedicated fullscreen editing mode and a structured date picker for effective dates.
- Implement fullscreen toggle in `policies-block-editor.js` with corresponding CSS for an expanded workspace
- Add `date` field type to `sharedFields.js` and integrate it into `policiesConfig.js`
- Implement date parsing and formatting utilities in `editor-script.ejs` to handle "Effective Date" strings
- Update `policiesController.js` to use `updateOne` for more reliable document persistence and state reloading
- Add support for `ICON_OPTIONS` in the policy editor configuration
- Refine editor UI layout and overflow handling for better stability with long content strings
---
controllers/policiesController.js | 18 +-
data/policies.json | 73 ++++-
public/js/policies-block-editor.js | 196 +++++++++++++-
utils/contentEditors/policiesConfig.js | 7 +-
utils/contentEditors/sharedFields.js | 8 +
.../admin/policies/partials/editor-script.ejs | 90 +++++++
views/admin/policies/sections.ejs | 253 +++++++++++++++++-
7 files changed, 620 insertions(+), 25 deletions(-)
diff --git a/controllers/policiesController.js b/controllers/policiesController.js
index 9a00ee4..65dc7c9 100644
--- a/controllers/policiesController.js
+++ b/controllers/policiesController.js
@@ -163,16 +163,22 @@ module.exports = {
delete updatedPolicy.sections;
delete updatedPolicy.contentByLanguage;
normalizedPolicies.splice(policyIndex, 1, updatedPolicy);
- doc.policies = normalizedPolicies;
- doc.hero = {
+ const nextHero = {
...(doc.hero || {}),
lastUpdated: formatLastUpdated(),
};
- doc.markModified("policies");
- doc.markModified("hero");
- await doc.save();
+ await PoliciesPage.updateOne(
+ { _id: doc._id },
+ {
+ $set: {
+ policies: normalizedPolicies,
+ hero: nextHero,
+ },
+ },
+ );
- const afterData = JSON.parse(JSON.stringify(doc.toObject()));
+ const reloadedDoc = await PoliciesPage.findById(doc._id);
+ const afterData = JSON.parse(JSON.stringify(reloadedDoc.toObject()));
const changes = diffObject(beforeData, afterData);
if (changes.length > 0) {
diff --git a/data/policies.json b/data/policies.json
index 1b71b0f..e848f85 100644
--- a/data/policies.json
+++ b/data/policies.json
@@ -5,7 +5,7 @@
"titlePrefix": "Our Commitment to",
"titleHighlight": "Transparency",
"description": "Review our policies, terms of service, and commitments to privacy and accessibility. We believe in clear, straightforward communication with our academic community.",
- "lastUpdated": "Last updated: April 21, 2026"
+ "lastUpdated": "Last updated: April 22, 2026"
},
"sidebar": {
"heading": "Policies",
@@ -260,13 +260,74 @@
}
},
{
- "navLabel": "hehehe",
- "title": "hehehe",
- "effectiveDate": "hehehe",
- "intro": "hehehe",
+ "navLabel": "hehehehehehehehehehehehehehehehehehehehe",
+ "title": "hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe",
+ "effectiveDate": "Effective Date: April 22, 2026",
+ "intro": "hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe",
"id": "hehehe",
"content": {
- "blocks": []
+ "blocks": [
+ {
+ "id": "block-1776828969787-k3scn",
+ "type": "heading",
+ "level": 1,
+ "html": "
hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe
"
+ },
+ {
+ "id": "block-1776828970153-r5lpx",
+ "type": "paragraph",
+ "html": "Paragraph persist QA 2.
"
+ },
+ {
+ "id": "block-1776828972201-p2bgf",
+ "type": "list",
+ "style": "ordered",
+ "items": [
+ {
+ "id": "block-1776828972201-p2bgf-item-1",
+ "html": "Item A2
"
+ },
+ {
+ "id": "block-1776828972201-p2bgf-item-2",
+ "html": "hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe
"
+ }
+ ]
+ },
+ {
+ "id": "block-1776828974269-s4z7m",
+ "type": "quote",
+ "html": "hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe
",
+ "caption": "Persist Source 2"
+ },
+ {
+ "id": "block-1776828976335-unxd1",
+ "type": "callout",
+ "tone": "success",
+ "title": "Callout Persist 2",
+ "icon": "fa-circle-check",
+ "html": "hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe
"
+ },
+ {
+ "id": "block-1776828978384-r45ps",
+ "type": "divider"
+ },
+ {
+ "id": "block-1776829917567-zycm5",
+ "type": "callout",
+ "tone": "success",
+ "title": "Callout save repro",
+ "icon": "fa-c",
+ "html": "hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe
"
+ },
+ {
+ "id": "block-1776829967348-g03qc",
+ "type": "callout",
+ "tone": "warning",
+ "title": "Callout save fixed",
+ "icon": "fa-envelope",
+ "html": "Callout body fixed persist
"
+ }
+ ]
}
}
]
diff --git a/public/js/policies-block-editor.js b/public/js/policies-block-editor.js
index 0150cf6..2a249d6 100644
--- a/public/js/policies-block-editor.js
+++ b/public/js/policies-block-editor.js
@@ -7,6 +7,7 @@
const previewContent = document.getElementById("previewContent");
const blockTypeSelect = document.getElementById("blockTypeSelect");
const addBlockButton = document.getElementById("addBlockButton");
+ const toggleFullscreenButton = document.getElementById("toggleFullscreenButton");
const validationBanner = document.getElementById("validationBanner");
const validationSummary = document.getElementById("validationSummary");
const previewDevice = document.getElementById("previewDevice");
@@ -43,6 +44,7 @@
toolbarTarget: null,
sortable: null,
previewTargetTimer: null,
+ isFullscreen: false,
};
const stylePalette = [
@@ -50,6 +52,7 @@
{ value: "warning", label: "Warning" },
{ value: "success", label: "Success" },
];
+ const iconOptions = Array.isArray(config.iconOptions) ? config.iconOptions : [];
bindStaticEvents();
render();
@@ -80,6 +83,12 @@
insertBlock(blockTypeSelect.value || "paragraph");
});
+ if (toggleFullscreenButton) {
+ toggleFullscreenButton.addEventListener("click", function () {
+ setEditorFullscreen(!ui.isFullscreen);
+ });
+ }
+
form.querySelectorAll("[data-intent]").forEach((button) => {
button.addEventListener("click", function () {
intentInput.value = this.dataset.intent || "save";
@@ -101,9 +110,31 @@
});
document.addEventListener("selectionchange", handleSelectionChange);
+ root.addEventListener(
+ "wheel",
+ function (event) {
+ if (!ui.isFullscreen) {
+ return;
+ }
+
+ const interactiveTarget = event.target.closest(
+ ".icon-combobox-panel, .icon-combobox-options",
+ );
+ if (interactiveTarget) {
+ return;
+ }
+
+ root.scrollTop += event.deltaY;
+ event.preventDefault();
+ },
+ { passive: false },
+ );
document.addEventListener("click", handleGlobalClick);
document.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
+ if (ui.isFullscreen) {
+ setEditorFullscreen(false);
+ }
hideFloatingToolbar();
hideLinkPopover();
hideSlashMenu();
@@ -164,11 +195,39 @@
hideLinkPopover();
hideSlashMenu();
updatePreviewModeButtons();
+ updateFullscreenToggle();
renderBlocks();
renderPreview();
renderValidation(validateState());
}
+ function setEditorFullscreen(nextValue) {
+ ui.isFullscreen = Boolean(nextValue);
+ root.classList.toggle("is-editor-fullscreen", ui.isFullscreen);
+ document.body.classList.toggle("policy-editor-fullscreen", ui.isFullscreen);
+ updateFullscreenToggle();
+ }
+
+ function updateFullscreenToggle() {
+ if (!toggleFullscreenButton) {
+ return;
+ }
+
+ const label = toggleFullscreenButton.querySelector('[data-role="fullscreen-label"]');
+ const icon = toggleFullscreenButton.querySelector('[data-role="fullscreen-icon"]');
+ toggleFullscreenButton.setAttribute("aria-pressed", ui.isFullscreen ? "true" : "false");
+ toggleFullscreenButton.classList.toggle("btn-outline-secondary", !ui.isFullscreen);
+ toggleFullscreenButton.classList.toggle("btn-outline-primary", ui.isFullscreen);
+
+ if (label) {
+ label.textContent = ui.isFullscreen ? "Exit full screen" : "Full screen edit";
+ }
+
+ if (icon) {
+ icon.className = `fas ${ui.isFullscreen ? "fa-compress" : "fa-expand"} me-2`;
+ }
+ }
+
function updatePreviewModeButtons() {
root.querySelectorAll("[data-preview-mode]").forEach((button) => {
button.classList.toggle("is-active", button.dataset.previewMode === ui.previewMode);
@@ -490,7 +549,25 @@
-
+
+
+
+
+
+
+
No matching icons. Press Enter to use custom value.
+
+
@@ -506,12 +583,16 @@
block.html = value;
});
- body.querySelectorAll("[data-field]").forEach((input) => {
- input.addEventListener("input", function () {
+ initializeIconCombobox(body.querySelector('[data-role="icon-combobox"]'), block);
+
+ body.querySelectorAll("input[data-field], select[data-field], textarea[data-field]").forEach((input) => {
+ const syncField = function () {
block[this.dataset.field] = this.value;
renderPreview();
renderValidation(validateState());
- });
+ };
+ input.addEventListener("input", syncField);
+ input.addEventListener("change", syncField);
});
return;
}
@@ -580,6 +661,113 @@
return editor;
}
+ function initializeIconCombobox(wrapper, block) {
+ if (!wrapper) {
+ return;
+ }
+
+ const hiddenInput = wrapper.querySelector('input[data-field="icon"]');
+ const trigger = wrapper.querySelector(".icon-combobox-trigger");
+ const panel = wrapper.querySelector(".icon-combobox-panel");
+ const searchInput = wrapper.querySelector(".icon-combobox-search");
+ const optionsContainer = wrapper.querySelector(".icon-combobox-options");
+ const emptyState = wrapper.querySelector(".icon-combobox-empty");
+ const preview = wrapper.querySelector(".icon-combobox-preview");
+ const title = wrapper.querySelector(".icon-combobox-text strong");
+ const card = wrapper.closest(".block-card");
+
+ if (!hiddenInput || !trigger || !panel || !searchInput || !optionsContainer || !emptyState || !preview || !title) {
+ return;
+ }
+
+ const setOpen = function (isOpen) {
+ wrapper.classList.toggle("is-open", isOpen);
+ panel.classList.toggle("is-hidden", !isOpen);
+ if (card) {
+ card.classList.toggle("has-floating-ui", isOpen);
+ }
+ if (isOpen) {
+ searchInput.focus();
+ searchInput.select();
+ }
+ };
+
+ const syncIconValue = function (value) {
+ const nextValue = String(value || "").trim();
+ hiddenInput.value = nextValue;
+ searchInput.value = nextValue;
+ block.icon = nextValue;
+ preview.innerHTML = nextValue
+ ? ``
+ : '';
+ title.textContent = nextValue || "Select icon";
+ renderPreview();
+ renderValidation(validateState());
+ };
+
+ const renderOptions = function (query) {
+ const normalizedQuery = String(query || "").trim().toLowerCase();
+ const filteredOptions = iconOptions.filter((option) =>
+ option.toLowerCase().includes(normalizedQuery),
+ );
+
+ optionsContainer.innerHTML = "";
+ emptyState.classList.toggle("is-hidden", filteredOptions.length > 0);
+
+ filteredOptions.forEach((option) => {
+ const button = document.createElement("button");
+ button.type = "button";
+ button.className = "icon-combobox-option";
+ button.classList.toggle("is-active", option === hiddenInput.value);
+ button.innerHTML = `
+
+
+ ${escapeHtml(option)}
+
+ ${option === hiddenInput.value ? '' : ""}
+ `;
+ button.addEventListener("click", function () {
+ syncIconValue(option);
+ renderOptions(option);
+ setOpen(false);
+ });
+ optionsContainer.appendChild(button);
+ });
+ };
+
+ trigger.addEventListener("click", function () {
+ const nextIsOpen = panel.classList.contains("is-hidden");
+ setOpen(nextIsOpen);
+ if (nextIsOpen) {
+ renderOptions(searchInput.value || hiddenInput.value);
+ }
+ });
+
+ searchInput.addEventListener("input", function () {
+ renderOptions(searchInput.value);
+ });
+
+ searchInput.addEventListener("keydown", function (event) {
+ if (event.key === "Enter") {
+ event.preventDefault();
+ syncIconValue(searchInput.value);
+ renderOptions(searchInput.value);
+ setOpen(false);
+ }
+ });
+
+ wrapper.addEventListener("focusout", function () {
+ window.setTimeout(function () {
+ if (!wrapper.contains(document.activeElement)) {
+ setOpen(false);
+ }
+ }, 0);
+ });
+
+ syncIconValue(block.icon || "");
+ renderOptions(block.icon || "");
+ }
+
function bindEditable(element, onChange, options) {
element.addEventListener("input", function () {
normalizeEditorHtml(element);
diff --git a/utils/contentEditors/policiesConfig.js b/utils/contentEditors/policiesConfig.js
index 8b5a6d2..95b100e 100644
--- a/utils/contentEditors/policiesConfig.js
+++ b/utils/contentEditors/policiesConfig.js
@@ -1,5 +1,7 @@
const {
+ ICON_OPTIONS,
text,
+ date,
textarea,
icon,
url,
@@ -58,7 +60,9 @@ const baseConfig = {
[
text("navLabel", "Sidebar label", { maxLength: 40 }),
text("title", "Policy title", { maxLength: 70 }),
- text("effectiveDate", "Effective date", { maxLength: 50 }),
+ date("effectiveDate", "Effective date", {
+ helpText: "Pick a calendar date. It will be saved in the standard policy display format.",
+ }),
textarea("intro", "Intro text", { maxLength: 260, rows: 4 }),
],
{
@@ -98,6 +102,7 @@ function createPoliciesSectionEditorConfig(policy, allPolicies = []) {
imageType: "policies",
policyId: policy.id,
policyOptions,
+ iconOptions: ICON_OPTIONS,
};
}
diff --git a/utils/contentEditors/sharedFields.js b/utils/contentEditors/sharedFields.js
index 0e34342..6da8d58 100644
--- a/utils/contentEditors/sharedFields.js
+++ b/utils/contentEditors/sharedFields.js
@@ -32,6 +32,13 @@ const text = (key, label, options = {}) => ({
...options,
});
+const date = (key, label, options = {}) => ({
+ key,
+ label,
+ type: "date",
+ ...options,
+});
+
const hidden = (key, options = {}) => ({
key,
type: "hidden",
@@ -169,6 +176,7 @@ const linkFields = (prefix = "Link") => [
module.exports = {
ICON_OPTIONS,
text,
+ date,
hidden,
textarea,
image,
diff --git a/views/admin/policies/partials/editor-script.ejs b/views/admin/policies/partials/editor-script.ejs
index c4c32b7..ff17f8a 100644
--- a/views/admin/policies/partials/editor-script.ejs
+++ b/views/admin/policies/partials/editor-script.ejs
@@ -408,6 +408,23 @@
return;
}
+ if (schema.type === "date") {
+ const input = document.createElement("input");
+ input.className = "form-control";
+ input.type = "date";
+ input.value = toDateInputValue(parent[key]);
+ input.addEventListener("input", function () {
+ parent[key] = fromDateInputValue(input.value);
+ });
+ input.addEventListener("change", function () {
+ parent[key] = fromDateInputValue(input.value);
+ });
+ col.appendChild(input);
+ appendHelp(col, schema, parent[key]);
+ container.appendChild(col);
+ return;
+ }
+
if (schema.type === "image") {
const group = document.createElement("div");
group.className = "input-group";
@@ -805,6 +822,79 @@
return "col-md-6";
}
+ function toDateInputValue(value) {
+ const parsed = parseDateValue(value);
+ if (!parsed) {
+ return "";
+ }
+
+ const year = parsed.getUTCFullYear();
+ const month = String(parsed.getUTCMonth() + 1).padStart(2, "0");
+ const day = String(parsed.getUTCDate()).padStart(2, "0");
+ return `${year}-${month}-${day}`;
+ }
+
+ function fromDateInputValue(value) {
+ const normalized = String(value || "").trim();
+ if (!normalized) {
+ return "";
+ }
+
+ const [year, month, day] = normalized.split("-").map(Number);
+ if (!year || !month || !day) {
+ return "";
+ }
+
+ const date = new Date(Date.UTC(year, month - 1, day));
+ return `Effective Date: ${new Intl.DateTimeFormat("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ timeZone: "UTC",
+ }).format(date)}`;
+ }
+
+ function parseDateValue(value) {
+ const raw = String(value || "").trim();
+ if (!raw) {
+ return null;
+ }
+
+ if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
+ const isoDate = new Date(`${raw}T00:00:00Z`);
+ return Number.isNaN(isoDate.getTime()) ? null : isoDate;
+ }
+
+ const withoutPrefix = raw.replace(/^Effective Date:\s*/i, "").trim();
+ const textMatch = withoutPrefix.match(
+ /^([A-Za-z]+)\s+(\d{1,2}),\s*(\d{4})$/,
+ );
+ if (textMatch) {
+ const monthIndex = [
+ "january",
+ "february",
+ "march",
+ "april",
+ "may",
+ "june",
+ "july",
+ "august",
+ "september",
+ "october",
+ "november",
+ "december",
+ ].indexOf(textMatch[1].toLowerCase());
+ if (monthIndex !== -1) {
+ return new Date(
+ Date.UTC(Number(textMatch[3]), monthIndex, Number(textMatch[2])),
+ );
+ }
+ }
+
+ const fallbackDate = new Date(`${withoutPrefix} UTC`);
+ return Number.isNaN(fallbackDate.getTime()) ? null : fallbackDate;
+ }
+
function resolveImageUrl(path) {
if (!path) return "";
if (/^https?:\/\//i.test(path)) return path;
diff --git a/views/admin/policies/sections.ejs b/views/admin/policies/sections.ejs
index dcd72f6..639ca65 100644
--- a/views/admin/policies/sections.ejs
+++ b/views/admin/policies/sections.ejs
@@ -4,6 +4,7 @@
}
.policy-block-editor {
+ --editor-fullscreen-top-offset: 88px;
--editor-border: rgba(148, 163, 184, 0.22);
--editor-muted: #64748b;
--editor-bg: #f8fafc;
@@ -16,13 +17,34 @@
padding-bottom: 140px;
}
+ .policy-block-editor.is-editor-fullscreen {
+ position: fixed;
+ inset: var(--editor-fullscreen-top-offset) 0 0 0;
+ z-index: 1080;
+ width: 100%;
+ max-width: none !important;
+ margin: 0;
+ padding: 1rem 1.25rem 140px;
+ background: #f8fafc;
+ overflow: auto;
+ }
+
+ body.policy-editor-fullscreen {
+ overflow: hidden;
+ }
+
.policy-block-editor .editor-shell {
display: grid;
- grid-template-columns: minmax(0, 1.18fr) minmax(320px, 0.82fr);
+ grid-template-columns: minmax(0, 0.98fr) minmax(420px, 1.02fr);
gap: 1.5rem;
align-items: start;
}
+ .policy-block-editor.is-editor-fullscreen .editor-shell {
+ grid-template-columns: minmax(0, 0.88fr) minmax(520px, 1.12fr);
+ min-height: calc(100vh - var(--editor-fullscreen-top-offset) - 77px);
+ }
+
.policy-block-editor .editor-panel,
.policy-block-editor .preview-panel {
border: 1px solid var(--editor-border);
@@ -65,10 +87,34 @@
min-height: calc(100vh - 210px);
}
+ .policy-block-editor.is-editor-fullscreen .editor-panel {
+ position: static;
+ min-height: auto;
+ height: auto;
+ min-width: 0;
+ overflow: visible;
+ }
+
.policy-block-editor .preview-panel {
min-height: calc(100vh - 210px);
}
+ .policy-block-editor.is-editor-fullscreen .preview-panel {
+ position: static;
+ min-height: auto;
+ height: auto;
+ min-width: 0;
+ overflow: visible;
+ }
+
+ .policy-block-editor .visual-editor {
+ min-height: 0;
+ }
+
+ .policy-block-editor.is-editor-fullscreen .visual-editor {
+ min-height: 0;
+ }
+
.policy-block-editor .panel-toolbar {
display: flex;
flex-wrap: wrap;
@@ -100,16 +146,32 @@
}
.policy-block-editor .editor-toolbar-title {
- min-width: 220px;
+ min-width: 0;
+ flex: 1 1 320px;
margin-right: 0.35rem;
}
+ .policy-block-editor .editor-toolbar-title h1,
+ .policy-block-editor .editor-toolbar-title p {
+ overflow-wrap: anywhere;
+ word-break: break-word;
+ }
+
.policy-block-editor .editor-scroll {
max-height: calc(100vh - 340px);
overflow: auto;
padding-right: 0.35rem;
}
+ .policy-block-editor.is-editor-fullscreen .editor-scroll {
+ max-height: none;
+ overflow: visible;
+ }
+
+ .policy-block-editor.is-editor-fullscreen .preview-frame {
+ overflow: visible;
+ }
+
.policy-block-editor .validation-banner {
display: none;
gap: 0.75rem;
@@ -141,6 +203,11 @@
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
}
+ .policy-block-editor .block-card.has-floating-ui {
+ overflow: visible;
+ z-index: 20;
+ }
+
.policy-block-editor .block-card:hover,
.policy-block-editor .block-card.is-selected {
border-color: rgba(184, 183, 106, 0.62);
@@ -259,6 +326,139 @@
margin-bottom: 0.9rem;
}
+ .policy-block-editor .icon-combobox {
+ position: relative;
+ }
+
+ .policy-block-editor .icon-combobox-trigger {
+ width: 100%;
+ border: 1px solid rgba(148, 163, 184, 0.18);
+ border-radius: 14px;
+ background: #fff;
+ color: #334155;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem;
+ padding: 0.72rem 0.85rem;
+ text-align: left;
+ }
+
+ .policy-block-editor .icon-combobox-trigger:focus,
+ .policy-block-editor .icon-combobox.is-open .icon-combobox-trigger {
+ border-color: rgba(184, 183, 106, 0.65);
+ box-shadow: 0 0 0 3px rgba(184, 183, 106, 0.12);
+ outline: none;
+ }
+
+ .policy-block-editor .icon-combobox-value {
+ display: flex;
+ align-items: center;
+ gap: 0.65rem;
+ min-width: 0;
+ flex: 1;
+ }
+
+ .policy-block-editor .icon-combobox-preview {
+ width: 34px;
+ height: 34px;
+ border-radius: 12px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(184, 183, 106, 0.12);
+ color: #0f172a;
+ flex: 0 0 auto;
+ }
+
+ .policy-block-editor .icon-combobox-text {
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.1rem;
+ }
+
+ .policy-block-editor .icon-combobox-text strong,
+ .policy-block-editor .icon-combobox-text span {
+ overflow-wrap: anywhere;
+ word-break: break-word;
+ }
+
+ .policy-block-editor .icon-combobox-caret {
+ color: #64748b;
+ flex: 0 0 auto;
+ }
+
+ .policy-block-editor .icon-combobox-panel {
+ position: absolute;
+ top: calc(100% + 0.4rem);
+ left: 0;
+ right: 0;
+ z-index: 40;
+ border: 1px solid rgba(148, 163, 184, 0.18);
+ border-radius: 16px;
+ background: #fff;
+ box-shadow: 0 18px 36px rgba(15, 23, 42, 0.12);
+ padding: 0.75rem;
+ }
+
+ .policy-block-editor .icon-combobox-panel.is-hidden {
+ display: none;
+ }
+
+ .policy-block-editor .icon-combobox-search {
+ margin-bottom: 0.6rem;
+ }
+
+ .policy-block-editor .icon-combobox-options {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ max-height: 220px;
+ overflow: auto;
+ }
+
+ .policy-block-editor .icon-combobox-option {
+ width: 100%;
+ border: 0;
+ background: #fff;
+ border-radius: 12px;
+ padding: 0.6rem 0.7rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem;
+ text-align: left;
+ }
+
+ .policy-block-editor .icon-combobox-option:hover,
+ .policy-block-editor .icon-combobox-option.is-active {
+ background: rgba(184, 183, 106, 0.12);
+ color: #0f172a;
+ }
+
+ .policy-block-editor .icon-combobox-option-main {
+ display: flex;
+ align-items: center;
+ gap: 0.65rem;
+ min-width: 0;
+ }
+
+ .policy-block-editor .icon-combobox-option-main span {
+ overflow-wrap: anywhere;
+ word-break: break-word;
+ }
+
+ .policy-block-editor .icon-combobox-empty {
+ color: #64748b;
+ font-size: 0.875rem;
+ padding: 0.35rem 0.2rem;
+ }
+
+ .policy-block-editor .icon-combobox-empty.is-hidden {
+ display: none;
+ }
+
.policy-block-editor .list-items {
display: flex;
flex-direction: column;
@@ -295,21 +495,26 @@
scroll-behavior: smooth;
}
- .policy-block-editor .preview-device {
- margin: 0 auto;
+ .policy-block-editor .preview-panel {
min-width: 0;
}
+ .policy-block-editor .preview-device {
+ margin: 0 auto;
+ min-width: 0;
+ max-width: 100%;
+ }
+
.policy-block-editor .preview-device[data-mode="desktop"] {
- width: 1120px;
+ width: min(100%, 920px);
}
.policy-block-editor .preview-device[data-mode="tablet"] {
- width: 820px;
+ width: min(100%, 820px);
}
.policy-block-editor .preview-device[data-mode="mobile"] {
- width: 390px;
+ width: min(100%, 390px);
}
.policy-block-editor .preview-canvas {
@@ -318,6 +523,8 @@
border: 1px solid rgba(148, 163, 184, 0.14);
padding: 2rem;
min-height: 480px;
+ overflow-wrap: anywhere;
+ word-break: break-word;
}
.policy-block-editor .preview-device[data-mode="desktop"] .preview-canvas {
@@ -326,7 +533,7 @@
}
.policy-block-editor .preview-device[data-mode="desktop"] #previewContent {
- max-width: none;
+ max-width: 100%;
}
.policy-block-editor .preview-block {
@@ -335,6 +542,18 @@
scroll-margin-top: 140px;
}
+ .policy-block-editor .preview-canvas h2,
+ .policy-block-editor .preview-canvas h3,
+ .policy-block-editor .preview-canvas h4,
+ .policy-block-editor .preview-canvas h5,
+ .policy-block-editor .preview-canvas p,
+ .policy-block-editor .preview-canvas li,
+ .policy-block-editor .preview-canvas blockquote,
+ .policy-block-editor .preview-canvas .text-uppercase {
+ overflow-wrap: anywhere;
+ word-break: break-word;
+ }
+
.policy-block-editor .preview-block.is-highlighted {
background: rgba(184, 183, 106, 0.12);
outline: 1px solid rgba(184, 183, 106, 0.35);
@@ -369,6 +588,20 @@
padding: 1.1rem 1.2rem;
border: 1px solid rgba(148, 163, 184, 0.16);
background: #f8fafc;
+ overflow-wrap: anywhere;
+ word-break: break-word;
+ }
+
+ .policy-block-editor .callout-block > div,
+ .policy-block-editor .callout-block p,
+ .policy-block-editor .callout-block li,
+ .policy-block-editor .callout-block h3,
+ .policy-block-editor .callout-block h4,
+ .policy-block-editor .callout-block h5,
+ .policy-block-editor .callout-block a,
+ .policy-block-editor .callout-block code {
+ overflow-wrap: anywhere;
+ word-break: break-word;
}
.policy-block-editor .callout-block[data-tone="warning"] {
@@ -534,6 +767,10 @@
+