Files
Tống Thành Đạt a44d005c13 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
2026-04-22 12:02:00 +07:00

897 lines
24 KiB
Plaintext

<style>
#policyBlockEditor.container {
max-width: 85%;
}
.policy-block-editor {
--editor-fullscreen-top-offset: 88px;
--editor-border: rgba(148, 163, 184, 0.22);
--editor-muted: #64748b;
--editor-bg: #f8fafc;
--editor-surface: #ffffff;
--editor-soft: #eef2ff;
--editor-danger: #dc2626;
--editor-warning: #d97706;
--editor-success: #15803d;
--editor-shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
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, 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);
border-radius: 24px;
background: rgba(255, 255, 255, 0.96);
box-shadow: var(--editor-shadow);
}
.policy-block-editor .preview-mode-toggle {
display: inline-flex;
border: 1px solid var(--editor-border);
border-radius: 999px;
padding: 0.25rem;
background: var(--editor-bg);
gap: 0.25rem;
}
.policy-block-editor .segment-button {
border: 0;
background: transparent;
color: var(--editor-muted);
padding: 0.55rem 0.95rem;
border-radius: 999px;
font-weight: 600;
}
.policy-block-editor .segment-button.is-active {
background: #0f172a;
color: #fff;
}
.policy-block-editor .editor-panel,
.policy-block-editor .preview-panel {
padding: 1.25rem;
position: sticky;
top: 92px;
}
.policy-block-editor .editor-panel {
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;
justify-content: space-between;
gap: 0.75rem;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(148, 163, 184, 0.14);
margin-bottom: 1rem;
}
.policy-block-editor .editor-toolbar-main,
.policy-block-editor .preview-toolbar-actions {
display: flex;
flex-wrap: wrap;
gap: 0.65rem;
align-items: center;
}
.policy-block-editor .editor-toolbar-main {
flex: 1 1 360px;
}
.policy-block-editor .editor-toolbar-secondary {
display: none;
}
.policy-block-editor .preview-toolbar-actions {
justify-content: flex-end;
}
.policy-block-editor .editor-toolbar-title {
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;
align-items: flex-start;
padding: 0.9rem 1rem;
border-radius: 18px;
border: 1px solid rgba(217, 119, 6, 0.25);
background: rgba(251, 191, 36, 0.12);
color: #92400e;
margin-bottom: 1rem;
}
.policy-block-editor .validation-banner.is-visible {
display: flex;
}
.policy-block-editor .block-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.policy-block-editor .block-card {
position: relative;
border: 1px solid var(--editor-border);
border-radius: 22px;
background: var(--editor-surface);
overflow: hidden;
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);
box-shadow: 0 18px 36px rgba(15, 23, 42, 0.08);
}
.policy-block-editor .block-card.is-invalid {
border-color: rgba(220, 38, 38, 0.55);
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.08);
}
.policy-block-editor .block-card.is-collapsed .block-body {
display: none;
}
.policy-block-editor .block-header {
display: flex;
justify-content: space-between;
gap: 0.75rem;
align-items: center;
padding: 0.9rem 1rem;
background: linear-gradient(180deg, rgba(248, 250, 252, 0.95), rgba(255, 255, 255, 0.96));
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
}
.policy-block-editor .block-title {
display: flex;
gap: 0.75rem;
align-items: center;
min-width: 0;
}
.policy-block-editor .block-index {
width: 34px;
height: 34px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 12px;
background: rgba(184, 183, 106, 0.12);
color: #0f172a;
font-weight: 700;
}
.policy-block-editor .block-actions {
display: flex;
align-items: center;
gap: 0.45rem;
opacity: 0.18;
transition: opacity 0.16s ease;
}
.policy-block-editor .block-card:hover .block-actions,
.policy-block-editor .block-card.is-selected .block-actions {
opacity: 1;
}
.policy-block-editor .icon-button {
width: 34px;
height: 34px;
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 12px;
background: #fff;
color: #334155;
display: inline-flex;
align-items: center;
justify-content: center;
}
.policy-block-editor .icon-button:hover {
border-color: rgba(184, 183, 106, 0.65);
color: #0f172a;
}
.policy-block-editor .icon-button.is-danger:hover {
border-color: rgba(220, 38, 38, 0.55);
color: var(--editor-danger);
}
.policy-block-editor .drag-handle {
cursor: grab;
}
.policy-block-editor .drag-handle:active {
cursor: grabbing;
}
.policy-block-editor .block-body {
padding: 1rem;
}
.policy-block-editor .block-content {
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 18px;
background: #fff;
min-height: 108px;
padding: 0.9rem 1rem;
outline: none;
}
.policy-block-editor .block-content:focus {
border-color: rgba(184, 183, 106, 0.65);
box-shadow: 0 0 0 3px rgba(184, 183, 106, 0.12);
}
.policy-block-editor .block-content[data-placeholder]:empty::before,
.policy-block-editor .list-item-content[data-placeholder]:empty::before {
content: attr(data-placeholder);
color: #94a3b8;
}
.policy-block-editor .block-meta-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.75rem;
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;
gap: 0.75rem;
}
.policy-block-editor .list-item {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 0.75rem;
align-items: start;
padding: 0.75rem;
border-radius: 18px;
background: var(--editor-bg);
border: 1px solid rgba(148, 163, 184, 0.12);
}
.policy-block-editor .list-item-content {
min-height: 70px;
padding: 0.65rem 0.8rem;
border-radius: 16px;
border: 1px solid rgba(148, 163, 184, 0.18);
background: #fff;
outline: none;
}
.policy-block-editor .preview-frame {
border-radius: 28px;
background: linear-gradient(180deg, #f8fafc, #ffffff);
border: 1px solid rgba(148, 163, 184, 0.16);
padding: 1rem;
overflow-x: auto;
overflow-y: hidden;
scroll-behavior: smooth;
}
.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: min(100%, 920px);
}
.policy-block-editor .preview-device[data-mode="tablet"] {
width: min(100%, 820px);
}
.policy-block-editor .preview-device[data-mode="mobile"] {
width: min(100%, 390px);
}
.policy-block-editor .preview-canvas {
background: #fff;
border-radius: 24px;
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 {
min-height: 720px;
padding: 2.5rem;
}
.policy-block-editor .preview-device[data-mode="desktop"] #previewContent {
max-width: 100%;
}
.policy-block-editor .preview-block {
padding: 0.65rem 0;
border-radius: 14px;
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);
}
.policy-block-editor .preview-block.is-targeted {
background: rgba(37, 99, 235, 0.1);
outline: 1px solid rgba(37, 99, 235, 0.45);
box-shadow: 0 0 0 6px rgba(37, 99, 235, 0.08);
animation: preview-target-pulse 0.9s ease;
}
@keyframes preview-target-pulse {
0% {
transform: scale(0.985);
box-shadow: 0 0 0 0 rgba(37, 99, 235, 0.18);
}
55% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(37, 99, 235, 0.08);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 6px rgba(37, 99, 235, 0.08);
}
}
.policy-block-editor .callout-block {
border-radius: 22px;
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"] {
background: rgba(251, 191, 36, 0.1);
border-color: rgba(217, 119, 6, 0.28);
}
.policy-block-editor .callout-block[data-tone="success"] {
background: rgba(34, 197, 94, 0.1);
border-color: rgba(21, 128, 61, 0.22);
}
#floatingToolbar,
#linkPopover,
#slashMenu {
position: fixed;
z-index: 1045;
border-radius: 18px;
border: 1px solid rgba(148, 163, 184, 0.18);
background: rgba(15, 23, 42, 0.96);
color: #fff;
box-shadow: 0 18px 38px rgba(15, 23, 42, 0.24);
padding: 0.55rem;
display: none;
}
#floatingToolbar.is-visible,
#linkPopover.is-visible,
#slashMenu.is-visible {
display: block;
}
#floatingToolbar .floating-toolbar-group {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
}
#floatingToolbar button,
#slashMenu button {
border: 0;
border-radius: 12px;
padding: 0.5rem 0.65rem;
background: transparent;
color: #fff;
}
#floatingToolbar button:hover,
#slashMenu button:hover {
background: rgba(255, 255, 255, 0.12);
}
#slashMenu .slash-menu-list {
display: flex;
flex-direction: column;
min-width: 210px;
gap: 0.25rem;
}
.policy-block-editor .editor-footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 1035;
display: flex;
justify-content: space-between;
gap: 1rem;
padding: 0.9rem 1.25rem;
background: rgba(255, 255, 255, 0.96);
border-top: 1px solid rgba(148, 163, 184, 0.14);
box-shadow: 0 -18px 36px rgba(15, 23, 42, 0.08);
backdrop-filter: blur(10px);
}
.policy-block-editor .editor-footer .btn {
min-width: 136px;
}
.policy-block-editor .drop-indicator {
height: 4px;
border-radius: 999px;
background: linear-gradient(90deg, #0f172a, rgba(184, 183, 106, 0.92));
margin: -0.35rem 0 0.65rem;
display: none;
}
.policy-block-editor .drop-indicator.is-visible {
display: block;
}
.policy-block-editor .sortable-ghost {
opacity: 0.35;
}
.policy-block-editor .sortable-chosen {
box-shadow: 0 18px 36px rgba(15, 23, 42, 0.16);
}
@media (max-width: 1200px) {
.policy-block-editor .editor-shell {
grid-template-columns: 1fr;
}
.policy-block-editor .editor-panel,
.policy-block-editor .preview-panel {
position: static;
min-height: auto;
}
.policy-block-editor .editor-scroll {
max-height: none;
}
}
@media (max-width: 768px) {
.policy-block-editor .block-meta-grid {
grid-template-columns: 1fr;
}
.policy-block-editor .editor-toolbar-secondary,
.policy-block-editor .preview-toolbar-actions {
justify-content: flex-start;
}
.policy-block-editor .editor-footer {
flex-direction: column;
}
.policy-block-editor .editor-footer .btn,
.policy-block-editor .editor-footer a {
width: 100%;
}
}
</style>
<div
class="container policy-block-editor"
id="policyBlockEditor"
data-policy-id="<%= editorConfig.policyId %>"
data-route-base="<%= editorConfig.routeBase %>"
>
<form action="<%= editorConfig.routeBase %>/update" method="POST" id="policyBlockEditorForm" novalidate>
<input type="hidden" name="pageJson" id="pageJson" />
<input type="hidden" name="intent" id="editorIntent" value="save" />
<div class="editor-shell">
<section class="editor-panel">
<div class="panel-toolbar">
<div class="editor-toolbar-main">
<div class="editor-toolbar-title">
<p class="text-uppercase text-muted small fw-semibold mb-1">Modern CMS Editor</p>
<h1 class="h4 mb-0" style="color: var(--primary-dark)"><%= data.policy.title %></h1>
</div>
<select class="form-select" id="blockTypeSelect" style="min-width: 180px">
<option value="paragraph">Paragraph</option>
<option value="heading">Heading</option>
<option value="list">List</option>
<option value="quote">Quote</option>
<option value="callout">Callout</option>
<option value="divider">Divider</option>
</select>
<button type="button" class="btn btn-primary" id="addBlockButton">
<i class="fas fa-plus me-2"></i>Add block
</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 class="validation-banner" id="validationBanner">
<i class="fas fa-triangle-exclamation mt-1"></i>
<div>
<div class="fw-semibold">Validation needed before saving</div>
<div id="validationSummary" class="small"></div>
</div>
</div>
<div class="visual-editor">
<div class="editor-scroll">
<div class="drop-indicator" id="dropIndicator"></div>
<div class="block-list" id="blockList"></div>
</div>
</div>
</section>
<aside class="preview-panel">
<div class="panel-toolbar">
<div>
<h2 class="h6 mb-1">Realtime Preview</h2>
<p class="text-muted small mb-0">Semantic rendering of the current content.</p>
</div>
<div class="preview-toolbar-actions">
<div class="preview-mode-toggle" data-role="preview-mode-toggle">
<button type="button" class="segment-button is-active" data-preview-mode="desktop">Desktop</button>
<button type="button" class="segment-button" data-preview-mode="tablet">Tablet</button>
<button type="button" class="segment-button" data-preview-mode="mobile">Mobile</button>
</div>
</div>
</div>
<div class="preview-frame">
<div class="preview-device" id="previewDevice" data-mode="desktop">
<div class="preview-canvas">
<div class="mb-4">
<div class="text-uppercase small fw-semibold text-muted mb-2"><%= data.policy.navLabel %></div>
<h2 class="h3 mb-2"><%= data.policy.title %></h2>
<% if (data.policy.effectiveDate) { %>
<p class="text-muted mb-2"><%= data.policy.effectiveDate %></p>
<% } %>
<% if (data.policy.intro) { %>
<p class="text-muted mb-0"><%= data.policy.intro %></p>
<% } %>
</div>
<div id="previewContent"></div>
</div>
</div>
</div>
</aside>
</div>
<div class="editor-footer">
<div class="d-flex gap-2 flex-wrap">
<a href="/admin/policies?tab=policies" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-2"></i>Back
</a>
</div>
<div class="d-flex gap-2 flex-wrap">
<button type="submit" class="btn btn-outline-primary" data-intent="save-back">
<i class="fas fa-arrow-turn-left me-2"></i>Save &amp; Back
</button>
<button type="submit" class="btn btn-primary" data-intent="save">
<i class="fas fa-save me-2"></i>Save Content
</button>
</div>
</div>
</form>
</div>
<div class="floating-toolbar" id="floatingToolbar">
<div class="floating-toolbar-group">
<button type="button" data-command="bold"><i class="fas fa-bold"></i></button>
<button type="button" data-command="italic"><i class="fas fa-italic"></i></button>
<button type="button" data-command="underline"><i class="fas fa-underline"></i></button>
<button type="button" data-command="strikeThrough"><i class="fas fa-strikethrough"></i></button>
<button type="button" data-command="toggleCode"><i class="fas fa-code"></i></button>
<button type="button" data-command="toggleHighlight"><i class="fas fa-highlighter"></i></button>
<button type="button" data-command="subscript"><i class="fas fa-subscript"></i></button>
<button type="button" data-command="superscript"><i class="fas fa-superscript"></i></button>
<input type="color" title="Text color" data-command="foreColor" value="#0f172a" />
<input type="color" title="Background color" data-command="hiliteColor" value="#fef08a" />
<button type="button" data-command="openLink"><i class="fas fa-link"></i></button>
</div>
</div>
<div class="link-popover" id="linkPopover">
<div class="mb-2 fw-semibold">Edit link</div>
<div class="mb-2">
<label class="form-label text-white-50 small">URL</label>
<input type="text" class="form-control" id="linkUrlInput" placeholder="https://example.com" />
</div>
<div class="mb-3">
<label class="form-label text-white-50 small">Internal page</label>
<select class="form-select" id="linkPolicySelect">
<option value="">None</option>
<% (editorConfig.policyOptions || []).forEach((option) => { %>
<option value="<%= option.value %>"><%= option.label %></option>
<% }) %>
</select>
</div>
<div class="d-flex gap-2 justify-content-end">
<button type="button" class="btn btn-outline-light btn-sm" id="removeLinkButton">Remove</button>
<button type="button" class="btn btn-primary btn-sm" id="saveLinkButton">Apply</button>
</div>
</div>
<div class="slash-menu" id="slashMenu">
<div class="slash-menu-list">
<button type="button" data-slash-type="heading">Heading</button>
<button type="button" data-slash-type="list">List</button>
<button type="button" data-slash-type="divider">Divider</button>
<button type="button" data-slash-type="quote">Quote</button>
<button type="button" data-slash-type="link">Link</button>
<button type="button" disabled>Image (Coming soon)</button>
</div>
</div>
<script type="application/json" id="policyBlockEditorDataPayload"><%- JSON.stringify(data) %></script>
<script type="application/json" id="policyBlockEditorConfigData"><%- JSON.stringify(editorConfig) %></script>
<script src="/js/policies-block-editor.js"></script>