@bettercms-ai/astro 0.27.0 → 0.27.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,13 +26,13 @@
26
26
  * actually contains that block (mirrors render-page.ts's `ctx.flags` gating).
27
27
  */
28
28
  import type { ContentBlock } from "@bettercms-ai/types";
29
- import { RESPONSIVE_CSS, blockStyleToCss, hideOnAttr, usesHideOn, visibleBlocks, windowState, blockStyleToInnerCss, splitButtonStyle, buttonIconOf, buttonIconSvg, buttonVariantOf, componentInstanceAddresses, headingPaintCss, withButtonLookProps } from "@bettercms-ai/types";
29
+ import { RESPONSIVE_CSS, imageFrameCss, blockStyleToCss, hiddenEverywhere, hideOnAttr, usesHideOn, visibleBlocks, windowState, blockStyleToInnerCss, splitButtonStyle, buttonIconOf, buttonIconSvg, buttonVariantOf, componentInstanceAddresses, headingPaintCss, withButtonLookProps } from "@bettercms-ai/types";
30
30
  import { bind, bindProps } from "../dist/bindings.js";
31
31
  import type { DeliveryComponent, DeliveryForm } from "@bettercms-ai/sdk";
32
32
  import { applyOverrides } from "@bettercms-ai/ui/resolve";
33
33
  import { addressFor } from "../dist/bindings.js";
34
34
  import { portableTextToHtml } from "@bettercms-ai/richtext/html";
35
- import { embeddedComponentBlock, readStructuredContent } from "@bettercms-ai/richtext";
35
+ import { embeddedComponentRender, readStructuredContent } from "@bettercms-ai/richtext";
36
36
  import { headingParts, richtextHeadingLevel } from "@bettercms-ai/richtext/heading";
37
37
  import BcmsImage from "./BcmsImage.astro";
38
38
  import BcmsForm from "./BcmsForm.astro";
@@ -69,8 +69,9 @@ interface Props {
69
69
  /** The instant block windows (`block.window`) are read against. Defaults to the render time:
70
70
  * the build for a static page, the request for a server-rendered one. */
71
71
  now?: Date;
72
- /** Draw the blocks outside their window too, marked `data-bcms-window="pending|expired"`, for
73
- * a draft preview. Off, a block outside its window is not rendered, at any depth. */
72
+ /** Draw the blocks outside their window too, marked `data-bcms-window="pending|expired"`, and the
73
+ * blocks hidden on every breakpoint, marked `data-bcms-hidden="all"`, for a draft preview. Off,
74
+ * neither is rendered, at any depth. */
74
75
  showScheduled?: boolean;
75
76
  }
76
77
 
@@ -174,13 +175,18 @@ const EMBED_SLOT = "<!--bcms-embed-->";
174
175
  * `chunks` has one more entry than `embeds`.
175
176
  */
176
177
  function richTextParts(structured: NonNullable<ReturnType<typeof readStructuredContent>>, base: string | null) {
177
- const embeds: ContentBlock[] = [];
178
+ /** A linked embed is its `component` block; a separate copy, its own frozen blocks (it never looks the component up). */
179
+ const embeds: ({ kind: "linked"; block: ContentBlock } | { kind: "copy"; componentId: string; id: string; blocks: ContentBlock[] })[] = [];
178
180
  const html = portableTextToHtml(structured, {
179
181
  ...(base ? { bindingBase: base } : {}),
180
182
  renderBlock: (v) => {
181
- const block = embeddedComponentBlock(v);
182
- if (!block) return undefined;
183
- embeds.push(block as ContentBlock);
183
+ const embed = embeddedComponentRender(v);
184
+ if (!embed) return undefined;
185
+ embeds.push(
186
+ embed.kind === "linked"
187
+ ? { kind: "linked", block: embed.block as ContentBlock }
188
+ : { kind: "copy", componentId: embed.copy.componentId, id: embed.copy.id, blocks: embed.copy.blocks as ContentBlock[] },
189
+ );
184
190
  return EMBED_SLOT;
185
191
  },
186
192
  });
@@ -270,7 +276,9 @@ const pass = { forms, components, turnstileSiteKey, nested: true, fieldAddresses
270
276
  */
271
277
  /** `showScheduled` only: which side of its window a drawn block is on. Rides the block's root, like the id. */
272
278
  const scheduled = showScheduled && windowState(block, at) !== "live" ? windowState(block, at) : undefined;
273
- const blockAttrs = style || inner || hide ? {} : nested ? { "data-bcms-window": scheduled } : { "data-bcms-block": block.id, "data-bcms-window": scheduled };
279
+ /** `showScheduled` only: hidden on every breakpoint ("Hide on this page"), drawn so its author can find it. */
280
+ const hiddenAll = showScheduled && hiddenEverywhere(block) ? "all" : undefined;
281
+ const blockAttrs = style || inner || hide ? {} : nested ? { "data-bcms-window": scheduled, "data-bcms-hidden": hiddenAll } : { "data-bcms-block": block.id, "data-bcms-window": scheduled, "data-bcms-hidden": hiddenAll };
274
282
  const node = (() => {
275
283
  switch (block.type) {
276
284
  case "heading": {
@@ -321,24 +329,37 @@ const pass = { forms, components, turnstileSiteKey, nested: true, fieldAddresses
321
329
  const { chunks, embeds } = richTextParts(structured, base);
322
330
  return (
323
331
  <div class="bcms-text" {...blockAttrs}>
324
- {chunks.map((html, i) => (
325
- <Fragment>
326
- <Fragment set:html={html} />
327
- {embeds[i] && <Astro.self blocks={[embeds[i]]} depth={depth} seen={seen} {...pass} owner={base} />}
328
- </Fragment>
329
- ))}
332
+ {chunks.map((html, i) => {
333
+ const e = embeds[i];
334
+ return (
335
+ <Fragment>
336
+ <Fragment set:html={html} />
337
+ {e?.kind === "linked" && <Astro.self blocks={[e.block]} depth={depth} seen={seen} {...pass} owner={base} />}
338
+ {e?.kind === "copy" && (
339
+ <div class="bcms-block bcms-block--component" data-bcms-component-copy={e.componentId} data-bcms-instance={e.id}>
340
+ <Astro.self blocks={e.blocks} depth={depth + 1} seen={seen} {...pass} fieldAddresses={new Map()} />
341
+ </div>
342
+ )}
343
+ </Fragment>
344
+ );
345
+ })}
330
346
  </div>
331
347
  );
332
348
  }
333
349
  return <div class="bcms-text" {...bind(block.id, "html", "richtext", bctx)} {...blockAttrs} set:html={p.html ?? ""} />;
334
350
  }
335
351
  case "image": {
352
+ // An unset src renders nothing, wrapper included, as on the hosted lane (render-page D3).
353
+ if (!String(p.src ?? "").trim()) return null;
336
354
  // Matches the Next adapter: the image block is click-to-select on the Visual Editor
337
355
  // canvas like heading/text/button, rather than dock-only. `kind="image"` means select
338
356
  // + live src reflection, never contentEditable.
339
357
  // `alt` rides the props lane: this element already carries the src binding and one
340
358
  // element cannot hold two addresses.
359
+ // V2's media frame (`style.aspect`), drawn as the hosted lane draws it.
360
+ const frame = imageFrameCss(block.style);
341
361
  const imgBind = {
362
+ ...(frame ? { style: [`width:${frame.width}`, `aspect-ratio:${frame.aspectRatio}`, `object-fit:${frame.objectFit}`, frame.borderRadius ? `border-radius:${frame.borderRadius}` : ""].filter(Boolean).join(";") } : {}),
342
363
  ...bind(block.id, "src", "image", bctx),
343
364
  ...bindProps(block.id, [{ prop: "alt", kind: "text", attr: "alt", present: p.alt != null }], bctx),
344
365
  };
@@ -599,10 +620,10 @@ const pass = { forms, components, turnstileSiteKey, nested: true, fieldAddresses
599
620
  * box is the root and carries the marker instead.
600
621
  */
601
622
  const stamp = nested ? undefined : block.id;
602
- if (style && inner) return <div style={style} data-bcms-hide={hide} data-bcms-block={stamp} data-bcms-window={scheduled}><div class="bcms-block-inner" style={inner}>{node}</div></div>;
603
- if (hide) return <div style={style} data-bcms-hide={hide} data-bcms-block={stamp} data-bcms-window={scheduled}>{inner ? <div class="bcms-block-inner" style={inner}>{node}</div> : node}</div>;
604
- if (inner) return <div class="bcms-block-inner" style={inner} data-bcms-block={stamp} data-bcms-window={scheduled}>{node}</div>;
605
- if (style) return <div style={style} data-bcms-block={stamp} data-bcms-window={scheduled}>{node}</div>;
623
+ if (style && inner) return <div style={style} data-bcms-hide={hide} data-bcms-block={stamp} data-bcms-window={scheduled} data-bcms-hidden={hiddenAll}><div class="bcms-block-inner" style={inner}>{node}</div></div>;
624
+ if (hide) return <div style={style} data-bcms-hide={hide} data-bcms-block={stamp} data-bcms-window={scheduled} data-bcms-hidden={hiddenAll}>{inner ? <div class="bcms-block-inner" style={inner}>{node}</div> : node}</div>;
625
+ if (inner) return <div class="bcms-block-inner" style={inner} data-bcms-block={stamp} data-bcms-window={scheduled} data-bcms-hidden={hiddenAll}>{node}</div>;
626
+ if (style) return <div style={style} data-bcms-block={stamp} data-bcms-window={scheduled} data-bcms-hidden={hiddenAll}>{node}</div>;
606
627
  return node;
607
628
  })}
608
629
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/astro",
3
- "version": "0.27.0",
3
+ "version": "0.27.2",
4
4
  "type": "module",
5
5
  "description": "The BetterCMS adapter for Astro — a `bettercms()` integration, a `bettercms:client` virtual module, typed content loaders, draft preview, and native .astro rendering components.",
6
6
  "main": "./dist/index.js",
@@ -61,11 +61,11 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@bettercms-ai/image-url": "^0.4.0",
64
- "@bettercms-ai/next": "^0.26.0",
65
- "@bettercms-ai/richtext": "^0.8.2",
66
- "@bettercms-ai/sdk": "^1.29.0",
67
- "@bettercms-ai/types": "^1.43.0",
68
- "@bettercms-ai/ui": "^0.16.0",
64
+ "@bettercms-ai/next": "^0.26.2",
65
+ "@bettercms-ai/richtext": "^0.10.0",
66
+ "@bettercms-ai/sdk": "^1.31.0",
67
+ "@bettercms-ai/types": "^1.47.0",
68
+ "@bettercms-ai/ui": "^0.18.0",
69
69
  "libphonenumber-js": "^1.13.13"
70
70
  },
71
71
  "peerDependencies": {