Spaces:
Sleeping
Sleeping
fix(editor): preview the banner correctly in the Embed Studio
Browse filesThe studio built every preview srcdoc without fullBleed and let the iframe
fill the whole preview pane, so the banner (an edge-to-edge 5:2 embed) was
rendered like a height-reporting chart and looked nothing like the hero.
When editing banner.html, build the srcdoc with `fullBleed: true` and render
it inside a centered 5:2 stage (matching .hero-banner dimensions), so the
studio preview is a true WYSIWYG of the published header. Regular charts are
unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
frontend/src/components/EmbedStudio.tsx
CHANGED
|
@@ -156,6 +156,10 @@ export function EmbedStudio({
|
|
| 156 |
onSelectChart,
|
| 157 |
}: EmbedStudioProps) {
|
| 158 |
const { isDark, primaryColor } = useTheme();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
const [html, setHtml] = useState("");
|
| 160 |
const [viewMode, setViewMode] = useState<"preview" | "code">("preview");
|
| 161 |
const [selectedDataFile, setSelectedDataFile] = useState<string | null>(null);
|
|
@@ -181,11 +185,12 @@ export function EmbedStudio({
|
|
| 181 |
return embedStore.observeKey(src, setHtml);
|
| 182 |
}, [embedStore, src]);
|
| 183 |
|
| 184 |
-
// Build srcdoc
|
|
|
|
| 185 |
const srcdoc = useMemo(() => {
|
| 186 |
if (!html) return "";
|
| 187 |
-
return buildDoc(html, { isDark, primaryColor });
|
| 188 |
-
}, [html, isDark, primaryColor]);
|
| 189 |
|
| 190 |
// Load srcdoc into inactive frame
|
| 191 |
useEffect(() => {
|
|
@@ -305,7 +310,6 @@ export function EmbedStudio({
|
|
| 305 |
<div className="es-chat">
|
| 306 |
<div className="es-chat__messages">
|
| 307 |
{chat.messages.length === 0 && (() => {
|
| 308 |
-
const isBanner = src === "banner.html";
|
| 309 |
const hasChart = html.trim().length > 0;
|
| 310 |
let title: string;
|
| 311 |
let hint: string;
|
|
@@ -423,10 +427,12 @@ export function EmbedStudio({
|
|
| 423 |
|
| 424 |
{/* Both panels always mounted - toggle with hidden class to preserve iframe state */}
|
| 425 |
<div
|
| 426 |
-
className={`es-preview__frame-container ${viewMode !== "preview" || selectedFile ? "es-hidden" : ""}`}
|
| 427 |
>
|
| 428 |
{html ? (
|
| 429 |
-
<
|
|
|
|
|
|
|
| 430 |
<iframe
|
| 431 |
ref={frameA}
|
| 432 |
title="Chart preview A"
|
|
@@ -441,7 +447,7 @@ export function EmbedStudio({
|
|
| 441 |
onLoad={() => handleFrameLoad("b")}
|
| 442 |
style={frameStyle("b")}
|
| 443 |
/>
|
| 444 |
-
</>
|
| 445 |
) : (
|
| 446 |
<div className="es-preview__empty">
|
| 447 |
<span style={{ fontSize: 32, opacity: 0.4 }}>📊</span>
|
|
|
|
| 156 |
onSelectChart,
|
| 157 |
}: EmbedStudioProps) {
|
| 158 |
const { isDark, primaryColor } = useTheme();
|
| 159 |
+
// The banner is a special embed: it renders edge-to-edge in a 5:2 box (the
|
| 160 |
+
// hero) rather than as a height-reporting chart. The studio must preview it
|
| 161 |
+
// the same way (fullBleed srcdoc + constrained 5:2 stage) so it's WYSIWYG.
|
| 162 |
+
const isBanner = src === "banner.html";
|
| 163 |
const [html, setHtml] = useState("");
|
| 164 |
const [viewMode, setViewMode] = useState<"preview" | "code">("preview");
|
| 165 |
const [selectedDataFile, setSelectedDataFile] = useState<string | null>(null);
|
|
|
|
| 185 |
return embedStore.observeKey(src, setHtml);
|
| 186 |
}, [embedStore, src]);
|
| 187 |
|
| 188 |
+
// Build srcdoc. Banner previews use fullBleed so the content fills the
|
| 189 |
+
// iframe edge-to-edge, matching FrontmatterHero and the published output.
|
| 190 |
const srcdoc = useMemo(() => {
|
| 191 |
if (!html) return "";
|
| 192 |
+
return buildDoc(html, { isDark, primaryColor, fullBleed: isBanner });
|
| 193 |
+
}, [html, isDark, primaryColor, isBanner]);
|
| 194 |
|
| 195 |
// Load srcdoc into inactive frame
|
| 196 |
useEffect(() => {
|
|
|
|
| 310 |
<div className="es-chat">
|
| 311 |
<div className="es-chat__messages">
|
| 312 |
{chat.messages.length === 0 && (() => {
|
|
|
|
| 313 |
const hasChart = html.trim().length > 0;
|
| 314 |
let title: string;
|
| 315 |
let hint: string;
|
|
|
|
| 427 |
|
| 428 |
{/* Both panels always mounted - toggle with hidden class to preserve iframe state */}
|
| 429 |
<div
|
| 430 |
+
className={`es-preview__frame-container ${isBanner ? "es-preview__frame-container--banner" : ""} ${viewMode !== "preview" || selectedFile ? "es-hidden" : ""}`}
|
| 431 |
>
|
| 432 |
{html ? (
|
| 433 |
+
<div
|
| 434 |
+
className={`es-preview__stage ${isBanner ? "es-preview__stage--banner" : ""}`}
|
| 435 |
+
>
|
| 436 |
<iframe
|
| 437 |
ref={frameA}
|
| 438 |
title="Chart preview A"
|
|
|
|
| 447 |
onLoad={() => handleFrameLoad("b")}
|
| 448 |
style={frameStyle("b")}
|
| 449 |
/>
|
| 450 |
+
</div>
|
| 451 |
) : (
|
| 452 |
<div className="es-preview__empty">
|
| 453 |
<span style={{ fontSize: 32, opacity: 0.4 }}>📊</span>
|
frontend/src/styles/components/_embed-studio.css
CHANGED
|
@@ -331,6 +331,33 @@
|
|
| 331 |
background: var(--page-bg);
|
| 332 |
}
|
| 333 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
.es-preview__frame-container iframe {
|
| 335 |
/* Some browsers paint a default white canvas on `srcdoc` iframes
|
| 336 |
before the document's own <style> applies. Forcing the element
|
|
|
|
| 331 |
background: var(--page-bg);
|
| 332 |
}
|
| 333 |
|
| 334 |
+
/* Stage: the positioned context the double-buffered iframes fill (inset:0).
|
| 335 |
+
Default: fills the whole frame container (chart previews). */
|
| 336 |
+
.es-preview__stage {
|
| 337 |
+
position: relative;
|
| 338 |
+
width: 100%;
|
| 339 |
+
height: 100%;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
/* Banner mode: center a 5:2 box matching .hero-banner so the preview is a
|
| 343 |
+
true WYSIWYG of how the banner renders in the article header. */
|
| 344 |
+
.es-preview__frame-container--banner {
|
| 345 |
+
display: flex;
|
| 346 |
+
align-items: center;
|
| 347 |
+
justify-content: center;
|
| 348 |
+
padding: 24px;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.es-preview__frame-container--banner .es-preview__stage {
|
| 352 |
+
height: auto;
|
| 353 |
+
width: 100%;
|
| 354 |
+
max-width: 980px;
|
| 355 |
+
aspect-ratio: 5 / 2;
|
| 356 |
+
border: 1px solid var(--border-color);
|
| 357 |
+
border-radius: 8px;
|
| 358 |
+
overflow: hidden;
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
.es-preview__frame-container iframe {
|
| 362 |
/* Some browsers paint a default white canvas on `srcdoc` iframes
|
| 363 |
before the document's own <style> applies. Forcing the element
|