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 @@
- +
+ + + +
@@ -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 @@ +