tfrere HF Staff Cursor commited on
Commit
09a820f
·
1 Parent(s): ae6c9d8

fix(editor): preview the banner correctly in the Embed Studio

Browse files

The 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