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
This commit is contained in:
Tống Thành Đạt
2026-04-22 12:02:00 +07:00
parent cdaddddfeb
commit a44d005c13
7 changed files with 620 additions and 25 deletions
+12 -6
View File
@@ -163,16 +163,22 @@ module.exports = {
delete updatedPolicy.sections; delete updatedPolicy.sections;
delete updatedPolicy.contentByLanguage; delete updatedPolicy.contentByLanguage;
normalizedPolicies.splice(policyIndex, 1, updatedPolicy); normalizedPolicies.splice(policyIndex, 1, updatedPolicy);
doc.policies = normalizedPolicies; const nextHero = {
doc.hero = {
...(doc.hero || {}), ...(doc.hero || {}),
lastUpdated: formatLastUpdated(), lastUpdated: formatLastUpdated(),
}; };
doc.markModified("policies"); await PoliciesPage.updateOne(
doc.markModified("hero"); { _id: doc._id },
await doc.save(); {
$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); const changes = diffObject(beforeData, afterData);
if (changes.length > 0) { if (changes.length > 0) {
+67 -6
View File
@@ -5,7 +5,7 @@
"titlePrefix": "Our Commitment to", "titlePrefix": "Our Commitment to",
"titleHighlight": "Transparency", "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.", "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": { "sidebar": {
"heading": "Policies", "heading": "Policies",
@@ -260,13 +260,74 @@
} }
}, },
{ {
"navLabel": "hehehe", "navLabel": "hehehehehehehehehehehehehehehehehehehehe",
"title": "hehehe", "title": "hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe",
"effectiveDate": "hehehe", "effectiveDate": "Effective Date: April 22, 2026",
"intro": "hehehe", "intro": "hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe",
"id": "hehehe", "id": "hehehe",
"content": { "content": {
"blocks": [] "blocks": [
{
"id": "block-1776828969787-k3scn",
"type": "heading",
"level": 1,
"html": "<p>hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe</p>"
},
{
"id": "block-1776828970153-r5lpx",
"type": "paragraph",
"html": "<p>Paragraph persist QA 2.</p>"
},
{
"id": "block-1776828972201-p2bgf",
"type": "list",
"style": "ordered",
"items": [
{
"id": "block-1776828972201-p2bgf-item-1",
"html": "<p>Item A2</p>"
},
{
"id": "block-1776828972201-p2bgf-item-2",
"html": "<p>hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe</p>"
}
]
},
{
"id": "block-1776828974269-s4z7m",
"type": "quote",
"html": "<p>hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe</p>",
"caption": "Persist Source 2"
},
{
"id": "block-1776828976335-unxd1",
"type": "callout",
"tone": "success",
"title": "Callout Persist 2",
"icon": "fa-circle-check",
"html": "<p>hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe</p>"
},
{
"id": "block-1776828978384-r45ps",
"type": "divider"
},
{
"id": "block-1776829917567-zycm5",
"type": "callout",
"tone": "success",
"title": "Callout save repro",
"icon": "fa-c",
"html": "<p>hehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehehe</p>"
},
{
"id": "block-1776829967348-g03qc",
"type": "callout",
"tone": "warning",
"title": "Callout save fixed",
"icon": "fa-envelope",
"html": "<p>Callout body fixed persist</p>"
}
]
} }
} }
] ]
+192 -4
View File
@@ -7,6 +7,7 @@
const previewContent = document.getElementById("previewContent"); const previewContent = document.getElementById("previewContent");
const blockTypeSelect = document.getElementById("blockTypeSelect"); const blockTypeSelect = document.getElementById("blockTypeSelect");
const addBlockButton = document.getElementById("addBlockButton"); const addBlockButton = document.getElementById("addBlockButton");
const toggleFullscreenButton = document.getElementById("toggleFullscreenButton");
const validationBanner = document.getElementById("validationBanner"); const validationBanner = document.getElementById("validationBanner");
const validationSummary = document.getElementById("validationSummary"); const validationSummary = document.getElementById("validationSummary");
const previewDevice = document.getElementById("previewDevice"); const previewDevice = document.getElementById("previewDevice");
@@ -43,6 +44,7 @@
toolbarTarget: null, toolbarTarget: null,
sortable: null, sortable: null,
previewTargetTimer: null, previewTargetTimer: null,
isFullscreen: false,
}; };
const stylePalette = [ const stylePalette = [
@@ -50,6 +52,7 @@
{ value: "warning", label: "Warning" }, { value: "warning", label: "Warning" },
{ value: "success", label: "Success" }, { value: "success", label: "Success" },
]; ];
const iconOptions = Array.isArray(config.iconOptions) ? config.iconOptions : [];
bindStaticEvents(); bindStaticEvents();
render(); render();
@@ -80,6 +83,12 @@
insertBlock(blockTypeSelect.value || "paragraph"); insertBlock(blockTypeSelect.value || "paragraph");
}); });
if (toggleFullscreenButton) {
toggleFullscreenButton.addEventListener("click", function () {
setEditorFullscreen(!ui.isFullscreen);
});
}
form.querySelectorAll("[data-intent]").forEach((button) => { form.querySelectorAll("[data-intent]").forEach((button) => {
button.addEventListener("click", function () { button.addEventListener("click", function () {
intentInput.value = this.dataset.intent || "save"; intentInput.value = this.dataset.intent || "save";
@@ -101,9 +110,31 @@
}); });
document.addEventListener("selectionchange", handleSelectionChange); 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("click", handleGlobalClick);
document.addEventListener("keydown", function (event) { document.addEventListener("keydown", function (event) {
if (event.key === "Escape") { if (event.key === "Escape") {
if (ui.isFullscreen) {
setEditorFullscreen(false);
}
hideFloatingToolbar(); hideFloatingToolbar();
hideLinkPopover(); hideLinkPopover();
hideSlashMenu(); hideSlashMenu();
@@ -164,11 +195,39 @@
hideLinkPopover(); hideLinkPopover();
hideSlashMenu(); hideSlashMenu();
updatePreviewModeButtons(); updatePreviewModeButtons();
updateFullscreenToggle();
renderBlocks(); renderBlocks();
renderPreview(); renderPreview();
renderValidation(validateState()); 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() { function updatePreviewModeButtons() {
root.querySelectorAll("[data-preview-mode]").forEach((button) => { root.querySelectorAll("[data-preview-mode]").forEach((button) => {
button.classList.toggle("is-active", button.dataset.previewMode === ui.previewMode); button.classList.toggle("is-active", button.dataset.previewMode === ui.previewMode);
@@ -490,7 +549,25 @@
</div> </div>
<div> <div>
<label class="form-label fw-semibold">Icon</label> <label class="form-label fw-semibold">Icon</label>
<input type="text" class="form-control" data-field="icon" value="${escapeAttribute(block.icon || "")}" placeholder="fa-circle-info" /> <div class="icon-combobox" data-role="icon-combobox">
<input type="hidden" data-field="icon" value="${escapeAttribute(block.icon || "")}" />
<button type="button" class="icon-combobox-trigger">
<span class="icon-combobox-value">
<span class="icon-combobox-preview">
${block.icon ? `<i class="fa-solid ${escapeAttribute(block.icon)}"></i>` : '<i class="fas fa-icons"></i>'}
</span>
<span class="icon-combobox-text">
<strong>${escapeHtml(block.icon || "Select icon")}</strong>
</span>
</span>
<i class="fas fa-chevron-down icon-combobox-caret"></i>
</button>
<div class="icon-combobox-panel is-hidden">
<input type="text" class="form-control icon-combobox-search" placeholder="Search icon name" value="${escapeAttribute(block.icon || "")}" />
<div class="icon-combobox-options"></div>
<div class="icon-combobox-empty is-hidden">No matching icons. Press Enter to use custom value.</div>
</div>
</div>
</div> </div>
<div> <div>
<label class="form-label fw-semibold">Title</label> <label class="form-label fw-semibold">Title</label>
@@ -506,12 +583,16 @@
block.html = value; block.html = value;
}); });
body.querySelectorAll("[data-field]").forEach((input) => { initializeIconCombobox(body.querySelector('[data-role="icon-combobox"]'), block);
input.addEventListener("input", function () {
body.querySelectorAll("input[data-field], select[data-field], textarea[data-field]").forEach((input) => {
const syncField = function () {
block[this.dataset.field] = this.value; block[this.dataset.field] = this.value;
renderPreview(); renderPreview();
renderValidation(validateState()); renderValidation(validateState());
}); };
input.addEventListener("input", syncField);
input.addEventListener("change", syncField);
}); });
return; return;
} }
@@ -580,6 +661,113 @@
return editor; 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
? `<i class="fa-solid ${escapeAttribute(nextValue)}"></i>`
: '<i class="fas fa-icons"></i>';
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 = `
<span class="icon-combobox-option-main">
<i class="fa-solid ${escapeAttribute(option)}"></i>
<span>${escapeHtml(option)}</span>
</span>
${option === hiddenInput.value ? '<i class="fas fa-check small"></i>' : ""}
`;
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) { function bindEditable(element, onChange, options) {
element.addEventListener("input", function () { element.addEventListener("input", function () {
normalizeEditorHtml(element); normalizeEditorHtml(element);
+6 -1
View File
@@ -1,5 +1,7 @@
const { const {
ICON_OPTIONS,
text, text,
date,
textarea, textarea,
icon, icon,
url, url,
@@ -58,7 +60,9 @@ const baseConfig = {
[ [
text("navLabel", "Sidebar label", { maxLength: 40 }), text("navLabel", "Sidebar label", { maxLength: 40 }),
text("title", "Policy title", { maxLength: 70 }), 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 }), textarea("intro", "Intro text", { maxLength: 260, rows: 4 }),
], ],
{ {
@@ -98,6 +102,7 @@ function createPoliciesSectionEditorConfig(policy, allPolicies = []) {
imageType: "policies", imageType: "policies",
policyId: policy.id, policyId: policy.id,
policyOptions, policyOptions,
iconOptions: ICON_OPTIONS,
}; };
} }
+8
View File
@@ -32,6 +32,13 @@ const text = (key, label, options = {}) => ({
...options, ...options,
}); });
const date = (key, label, options = {}) => ({
key,
label,
type: "date",
...options,
});
const hidden = (key, options = {}) => ({ const hidden = (key, options = {}) => ({
key, key,
type: "hidden", type: "hidden",
@@ -169,6 +176,7 @@ const linkFields = (prefix = "Link") => [
module.exports = { module.exports = {
ICON_OPTIONS, ICON_OPTIONS,
text, text,
date,
hidden, hidden,
textarea, textarea,
image, image,
@@ -408,6 +408,23 @@
return; 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") { if (schema.type === "image") {
const group = document.createElement("div"); const group = document.createElement("div");
group.className = "input-group"; group.className = "input-group";
@@ -805,6 +822,79 @@
return "col-md-6"; 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) { function resolveImageUrl(path) {
if (!path) return ""; if (!path) return "";
if (/^https?:\/\//i.test(path)) return path; if (/^https?:\/\//i.test(path)) return path;
+245 -8
View File
@@ -4,6 +4,7 @@
} }
.policy-block-editor { .policy-block-editor {
--editor-fullscreen-top-offset: 88px;
--editor-border: rgba(148, 163, 184, 0.22); --editor-border: rgba(148, 163, 184, 0.22);
--editor-muted: #64748b; --editor-muted: #64748b;
--editor-bg: #f8fafc; --editor-bg: #f8fafc;
@@ -16,13 +17,34 @@
padding-bottom: 140px; 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 { .policy-block-editor .editor-shell {
display: grid; 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; gap: 1.5rem;
align-items: start; 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 .editor-panel,
.policy-block-editor .preview-panel { .policy-block-editor .preview-panel {
border: 1px solid var(--editor-border); border: 1px solid var(--editor-border);
@@ -65,10 +87,34 @@
min-height: calc(100vh - 210px); 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 { .policy-block-editor .preview-panel {
min-height: calc(100vh - 210px); 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 { .policy-block-editor .panel-toolbar {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -100,16 +146,32 @@
} }
.policy-block-editor .editor-toolbar-title { .policy-block-editor .editor-toolbar-title {
min-width: 220px; min-width: 0;
flex: 1 1 320px;
margin-right: 0.35rem; 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 { .policy-block-editor .editor-scroll {
max-height: calc(100vh - 340px); max-height: calc(100vh - 340px);
overflow: auto; overflow: auto;
padding-right: 0.35rem; 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 { .policy-block-editor .validation-banner {
display: none; display: none;
gap: 0.75rem; gap: 0.75rem;
@@ -141,6 +203,11 @@
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease; 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:hover,
.policy-block-editor .block-card.is-selected { .policy-block-editor .block-card.is-selected {
border-color: rgba(184, 183, 106, 0.62); border-color: rgba(184, 183, 106, 0.62);
@@ -259,6 +326,139 @@
margin-bottom: 0.9rem; 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 { .policy-block-editor .list-items {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -295,21 +495,26 @@
scroll-behavior: smooth; scroll-behavior: smooth;
} }
.policy-block-editor .preview-device { .policy-block-editor .preview-panel {
margin: 0 auto;
min-width: 0; 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"] { .policy-block-editor .preview-device[data-mode="desktop"] {
width: 1120px; width: min(100%, 920px);
} }
.policy-block-editor .preview-device[data-mode="tablet"] { .policy-block-editor .preview-device[data-mode="tablet"] {
width: 820px; width: min(100%, 820px);
} }
.policy-block-editor .preview-device[data-mode="mobile"] { .policy-block-editor .preview-device[data-mode="mobile"] {
width: 390px; width: min(100%, 390px);
} }
.policy-block-editor .preview-canvas { .policy-block-editor .preview-canvas {
@@ -318,6 +523,8 @@
border: 1px solid rgba(148, 163, 184, 0.14); border: 1px solid rgba(148, 163, 184, 0.14);
padding: 2rem; padding: 2rem;
min-height: 480px; min-height: 480px;
overflow-wrap: anywhere;
word-break: break-word;
} }
.policy-block-editor .preview-device[data-mode="desktop"] .preview-canvas { .policy-block-editor .preview-device[data-mode="desktop"] .preview-canvas {
@@ -326,7 +533,7 @@
} }
.policy-block-editor .preview-device[data-mode="desktop"] #previewContent { .policy-block-editor .preview-device[data-mode="desktop"] #previewContent {
max-width: none; max-width: 100%;
} }
.policy-block-editor .preview-block { .policy-block-editor .preview-block {
@@ -335,6 +542,18 @@
scroll-margin-top: 140px; 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 { .policy-block-editor .preview-block.is-highlighted {
background: rgba(184, 183, 106, 0.12); background: rgba(184, 183, 106, 0.12);
outline: 1px solid rgba(184, 183, 106, 0.35); outline: 1px solid rgba(184, 183, 106, 0.35);
@@ -369,6 +588,20 @@
padding: 1.1rem 1.2rem; padding: 1.1rem 1.2rem;
border: 1px solid rgba(148, 163, 184, 0.16); border: 1px solid rgba(148, 163, 184, 0.16);
background: #f8fafc; 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"] { .policy-block-editor .callout-block[data-tone="warning"] {
@@ -534,6 +767,10 @@
<button type="button" class="btn btn-primary" id="addBlockButton"> <button type="button" class="btn btn-primary" id="addBlockButton">
<i class="fas fa-plus me-2"></i>Add block <i class="fas fa-plus me-2"></i>Add block
</button> </button>
<button type="button" class="btn btn-outline-secondary" id="toggleFullscreenButton" aria-pressed="false">
<i class="fas fa-expand me-2" data-role="fullscreen-icon"></i>
<span data-role="fullscreen-label">Full screen edit</span>
</button>
</div> </div>
</div> </div>