forked from UKSOURCE/cms.lams
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:
@@ -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
@@ -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>"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user