feat: add stats block widget management and update block_3_button component content
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
--color-success: #28a745;
|
||||
--color-warning: #ffc107;
|
||||
--color-danger: #dc3545;
|
||||
--color-old-brick: #881C1C;
|
||||
|
||||
/* Neutrals */
|
||||
--color-background: #ffffff;
|
||||
@@ -57,4 +58,12 @@
|
||||
/* Allows clicks to pass through to the video controls */
|
||||
z-index: 1;
|
||||
/* Ensures it sits above the video */
|
||||
}
|
||||
|
||||
/* Custom Color Utilities */
|
||||
.text-old-brick {
|
||||
color: var(--color-old-brick) !important;
|
||||
}
|
||||
.bg-old-brick {
|
||||
background-color: var(--color-old-brick) !important;
|
||||
}
|
||||
+119
-37
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Custom Editor.js Block Tool for embedding predefined HTML snippets.
|
||||
* Uses iframe for WYSIWYG preview - snippets look exactly like on the live page.
|
||||
*/
|
||||
class HtmlSnippetTool {
|
||||
static get toolbox() {
|
||||
@@ -21,12 +22,10 @@ class HtmlSnippetTool {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.wrapper.classList.add('ce-snippet-wrapper');
|
||||
this.wrapper.style.border = '1px solid #ddd';
|
||||
this.wrapper.style.padding = '15px';
|
||||
this.wrapper.style.borderRadius = '5px';
|
||||
this.wrapper.style.background = '#fafafa';
|
||||
this.wrapper.style.width = '100%';
|
||||
this.wrapper.style.boxSizing = 'border-box';
|
||||
this.wrapper.style.overflowX = 'auto'; // allow scrolling instead of stretching editor
|
||||
|
||||
if (this.data.id) {
|
||||
this._showPreview(this.data.id);
|
||||
@@ -43,11 +42,13 @@ class HtmlSnippetTool {
|
||||
const title = document.createElement('h4');
|
||||
title.style.marginTop = '0';
|
||||
title.style.marginBottom = '10px';
|
||||
title.style.padding = '15px 15px 0 15px';
|
||||
title.innerText = 'Insert Predefined HTML Snippet';
|
||||
|
||||
const inputContainer = document.createElement('div');
|
||||
inputContainer.style.display = 'flex';
|
||||
inputContainer.style.gap = '10px';
|
||||
inputContainer.style.padding = '0 15px 15px 15px';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.classList.add('ce-input');
|
||||
@@ -73,8 +74,48 @@ class HtmlSnippetTool {
|
||||
}
|
||||
|
||||
_showPreview(snippetId) {
|
||||
this.wrapper.innerHTML = '<div style="color: #666;">Loading preview...</div>';
|
||||
this.wrapper.innerHTML = '';
|
||||
|
||||
// Header bar with snippet name and Edit ID button
|
||||
const header = document.createElement('div');
|
||||
header.style.display = 'flex';
|
||||
header.style.justifyContent = 'space-between';
|
||||
header.style.alignItems = 'center';
|
||||
header.style.padding = '8px 15px';
|
||||
header.style.borderBottom = '1px solid #ddd';
|
||||
header.style.background = '#f0f0f0';
|
||||
header.style.borderRadius = '5px 5px 0 0';
|
||||
|
||||
const title = document.createElement('strong');
|
||||
title.innerText = 'Snippet: ' + snippetId;
|
||||
title.style.fontSize = '13px';
|
||||
title.style.color = '#555';
|
||||
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.innerText = 'Edit ID';
|
||||
editBtn.style.fontSize = '12px';
|
||||
editBtn.style.cursor = 'pointer';
|
||||
editBtn.style.padding = '2px 8px';
|
||||
editBtn.style.border = '1px solid #ccc';
|
||||
editBtn.style.borderRadius = '3px';
|
||||
editBtn.style.background = '#fff';
|
||||
editBtn.addEventListener('click', () => {
|
||||
this._showInput();
|
||||
});
|
||||
|
||||
header.appendChild(title);
|
||||
header.appendChild(editBtn);
|
||||
this.wrapper.appendChild(header);
|
||||
|
||||
// Loading indicator
|
||||
const loadingDiv = document.createElement('div');
|
||||
loadingDiv.style.padding = '20px';
|
||||
loadingDiv.style.color = '#666';
|
||||
loadingDiv.style.textAlign = 'center';
|
||||
loadingDiv.innerText = 'Loading preview...';
|
||||
this.wrapper.appendChild(loadingDiv);
|
||||
|
||||
// Fetch the snippet HTML
|
||||
fetch('/api/manage/snippets/' + encodeURIComponent(snippetId))
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Snippet not found');
|
||||
@@ -83,47 +124,88 @@ class HtmlSnippetTool {
|
||||
.then(html => {
|
||||
this.data.id = snippetId;
|
||||
|
||||
this.wrapper.innerHTML = '';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.style.display = 'flex';
|
||||
header.style.justifyContent = 'space-between';
|
||||
header.style.alignItems = 'center';
|
||||
header.style.marginBottom = '10px';
|
||||
header.style.borderBottom = '1px solid #ddd';
|
||||
header.style.paddingBottom = '5px';
|
||||
|
||||
const title = document.createElement('strong');
|
||||
title.innerText = 'Snippet: ' + snippetId;
|
||||
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.innerText = 'Edit ID';
|
||||
editBtn.style.fontSize = '12px';
|
||||
editBtn.style.cursor = 'pointer';
|
||||
editBtn.addEventListener('click', () => {
|
||||
this._showInput();
|
||||
});
|
||||
|
||||
header.appendChild(title);
|
||||
header.appendChild(editBtn);
|
||||
|
||||
const previewArea = document.createElement('div');
|
||||
previewArea.innerHTML = html;
|
||||
|
||||
// Prevent interactions inside preview from submitting forms or acting up
|
||||
previewArea.style.pointerEvents = 'none';
|
||||
|
||||
this.wrapper.appendChild(header);
|
||||
this.wrapper.appendChild(previewArea);
|
||||
// Remove loading indicator
|
||||
loadingDiv.remove();
|
||||
|
||||
// Create an iframe for isolated WYSIWYG rendering
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.border = 'none';
|
||||
iframe.style.display = 'block';
|
||||
iframe.style.borderRadius = '0 0 5px 5px';
|
||||
iframe.style.minHeight = '80px';
|
||||
// Scrolling is disabled; height auto-adjusts
|
||||
iframe.scrolling = 'no';
|
||||
|
||||
this.wrapper.appendChild(iframe);
|
||||
|
||||
// Write the full HTML document into the iframe, including theme CSS
|
||||
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
iframeDoc.open();
|
||||
iframeDoc.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400..700;1,400..700&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/umass.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
/* Prevent any internal links from being clickable */
|
||||
a { pointer-events: none; }
|
||||
/* Ensure images/videos scale properly */
|
||||
img, video, iframe { max-width: 100%; height: auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${html}
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
iframeDoc.close();
|
||||
|
||||
// Auto-resize iframe to fit content
|
||||
const resizeIframe = () => {
|
||||
try {
|
||||
const body = iframeDoc.body;
|
||||
const docEl = iframeDoc.documentElement;
|
||||
const height = Math.max(
|
||||
body.scrollHeight, body.offsetHeight,
|
||||
docEl.clientHeight, docEl.scrollHeight, docEl.offsetHeight
|
||||
);
|
||||
iframe.style.height = height + 'px';
|
||||
} catch (e) {
|
||||
iframe.style.height = '300px';
|
||||
}
|
||||
};
|
||||
|
||||
// Resize after load and after images load
|
||||
iframe.addEventListener('load', resizeIframe);
|
||||
setTimeout(resizeIframe, 500);
|
||||
setTimeout(resizeIframe, 1500);
|
||||
setTimeout(resizeIframe, 3000);
|
||||
})
|
||||
.catch(error => {
|
||||
this.wrapper.innerHTML = '<div style="color: red;">Error: ' + error.message + '</div>';
|
||||
loadingDiv.remove();
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.style.padding = '15px';
|
||||
errorDiv.innerHTML = '<div style="color: red;">Error: ' + error.message + '</div>';
|
||||
const backBtn = document.createElement('button');
|
||||
backBtn.innerText = 'Try Again';
|
||||
backBtn.style.marginTop = '10px';
|
||||
backBtn.style.cursor = 'pointer';
|
||||
backBtn.addEventListener('click', () => this._showInput());
|
||||
this.wrapper.appendChild(backBtn);
|
||||
errorDiv.appendChild(backBtn);
|
||||
this.wrapper.appendChild(errorDiv);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user