@bettercms-ai/astro 0.8.2 → 0.8.3
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.
- package/components/BcmsBlocks.astro +49 -30
- package/components/BcmsForm.astro +7 -1
- package/package.json +4 -4
|
@@ -46,6 +46,10 @@ interface Props {
|
|
|
46
46
|
/** Set on every self-recursion so only the outermost instance emits the runtime
|
|
47
47
|
* scripts. Internal; do not pass. */
|
|
48
48
|
nested?: boolean;
|
|
49
|
+
/** Attributes for the container <div> this component always emits. Used only by the
|
|
50
|
+
* `component` case, so a component instance's marker rides the container its own
|
|
51
|
+
* recursion already produces instead of gaining a second <div>. Internal; do not pass. */
|
|
52
|
+
containerAttrs?: Record<string, string | undefined>;
|
|
49
53
|
class?: string;
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -60,6 +64,7 @@ const {
|
|
|
60
64
|
depth = 0,
|
|
61
65
|
seen = [],
|
|
62
66
|
nested = false,
|
|
67
|
+
containerAttrs = {},
|
|
63
68
|
class: className,
|
|
64
69
|
} = Astro.props;
|
|
65
70
|
const formMap = new Map(forms.map((f) => [f.id, f]));
|
|
@@ -150,46 +155,68 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
150
155
|
component recurses into itself for columns/tabs/sliders/components, so an HTML comment here
|
|
151
156
|
shipped once per nesting level to every visitor (FLO-1161).
|
|
152
157
|
*/}
|
|
153
|
-
<div class={className} data-bcms-page={nested ? undefined : ""}>
|
|
158
|
+
<div class={className} data-bcms-page={nested ? undefined : ""} {...containerAttrs}>
|
|
154
159
|
{blocks.map((block) => {
|
|
155
160
|
const p = anyProps(block);
|
|
156
161
|
const style = styleAttr(block);
|
|
162
|
+
/**
|
|
163
|
+
* 🔴 `data-bcms-block` — the Visual Editor hit-tests this to find sections. It rides the
|
|
164
|
+
* block's OWN root element; it does NOT get a wrapper.
|
|
165
|
+
*
|
|
166
|
+
* A wrapper <div> is what used to be emitted here, and it was a block box around every
|
|
167
|
+
* top-level block. Two consecutive `button` blocks — whose `<a class="bcms-btn">` roots are
|
|
168
|
+
* `inline-flex` and are meant to sit side by side — were each sealed in their own block box
|
|
169
|
+
* and stacked. Measured on the live amor site: both wrappers `display:block`, width 1048,
|
|
170
|
+
* y=462 and y=540. The Next adapter never had this because React lets it `cloneElement`;
|
|
171
|
+
* Astro has no cloneElement, so the attribute is applied AT CONSTRUCTION instead — the same
|
|
172
|
+
* spread idiom this file already uses for the image binding below.
|
|
173
|
+
*
|
|
174
|
+
* `undefined` values are omitted by Astro, which is why `nested` blocks (an empty object
|
|
175
|
+
* here) emit nothing — a column's child must never look like a draggable section.
|
|
176
|
+
*
|
|
177
|
+
* When the block carries style tokens the wrapper below is its root, so the marker rides
|
|
178
|
+
* that instead and `bind` stays empty. Style tokens keep their own box: merging them onto
|
|
179
|
+
* the block's root changes behaviour (a spacer's padding is absorbed under `border-box`, a
|
|
180
|
+
* section's `padding` collides last-wins instead of adding, and a button's `text-align`
|
|
181
|
+
* would centre its label instead of the button).
|
|
182
|
+
*/
|
|
183
|
+
const blockAttrs = nested || style ? {} : { "data-bcms-block": block.id };
|
|
157
184
|
const node = (() => {
|
|
158
185
|
switch (block.type) {
|
|
159
186
|
case "heading": {
|
|
160
187
|
const Tag = `h${clamp(Number(p.level) || 2, 1, 6)}` as keyof HTMLElementTagNameMap;
|
|
161
|
-
return <Tag data-bcms-field={`${block.id}__text`}>{p.text}</Tag>;
|
|
188
|
+
return <Tag data-bcms-field={`${block.id}__text`} {...blockAttrs}>{p.text}</Tag>;
|
|
162
189
|
}
|
|
163
190
|
// `richtext` and `text` carry the same payload and differ only in the authoring
|
|
164
191
|
// surface, so delivery renders them identically — the hosted renderer does the same.
|
|
165
192
|
case "text":
|
|
166
193
|
case "richtext":
|
|
167
|
-
return <div class="bcms-text" data-bcms-field={`${block.id}__html`} data-bcms-kind="richtext" set:html={p.html ?? ""} />;
|
|
194
|
+
return <div class="bcms-text" data-bcms-field={`${block.id}__html`} data-bcms-kind="richtext" {...blockAttrs} set:html={p.html ?? ""} />;
|
|
168
195
|
case "image": {
|
|
169
196
|
// Matches the Next adapter: the image block is click-to-select on the Visual Editor
|
|
170
197
|
// canvas like heading/text/button, rather than dock-only. `kind="image"` means select
|
|
171
198
|
// + live src reflection, never contentEditable.
|
|
172
199
|
const bind = { "data-bcms-field": `${block.id}__src`, "data-bcms-kind": "image" };
|
|
173
200
|
return p.caption ? (
|
|
174
|
-
<figure>
|
|
201
|
+
<figure {...blockAttrs}>
|
|
175
202
|
<BcmsImage src={p.src} alt={p.alt ?? ""} width={p.width} height={p.height} {...bind} />
|
|
176
203
|
<figcaption>{p.caption}</figcaption>
|
|
177
204
|
</figure>
|
|
178
205
|
) : (
|
|
179
|
-
<BcmsImage src={p.src} alt={p.alt ?? ""} width={p.width} height={p.height} {...bind} />
|
|
206
|
+
<BcmsImage src={p.src} alt={p.alt ?? ""} width={p.width} height={p.height} {...bind} {...blockAttrs} />
|
|
180
207
|
);
|
|
181
208
|
}
|
|
182
209
|
case "button":
|
|
183
210
|
return (
|
|
184
|
-
<a class={`bcms-btn bcms-btn-${p.variant === "secondary" ? "secondary" : "primary"}`} href={p.href ?? "#"} data-bcms-field={`${block.id}__text`}>
|
|
211
|
+
<a class={`bcms-btn bcms-btn-${p.variant === "secondary" ? "secondary" : "primary"}`} href={p.href ?? "#"} data-bcms-field={`${block.id}__text`} {...blockAttrs}>
|
|
185
212
|
{p.text}
|
|
186
213
|
</a>
|
|
187
214
|
);
|
|
188
215
|
case "spacer":
|
|
189
|
-
return <div class="bcms-spacer" style={`height:${Number(p.height) || 0}px`} />;
|
|
216
|
+
return <div class="bcms-spacer" style={`height:${Number(p.height) || 0}px`} {...blockAttrs} />;
|
|
190
217
|
case "video":
|
|
191
218
|
return (
|
|
192
|
-
<video src={p.url} poster={p.poster} controls autoplay={!!p.autoplay} muted={!!p.autoplay} loop={!!p.loop} playsinline />
|
|
219
|
+
<video src={p.url} poster={p.poster} controls autoplay={!!p.autoplay} muted={!!p.autoplay} loop={!!p.loop} playsinline {...blockAttrs} />
|
|
193
220
|
);
|
|
194
221
|
case "columns": {
|
|
195
222
|
const cols: ContentBlock[][] = Array.isArray(p.columns) ? p.columns : [];
|
|
@@ -197,6 +224,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
197
224
|
<div
|
|
198
225
|
class="bcms-cols"
|
|
199
226
|
style={`display:grid;grid-template-columns:repeat(${cols.length || 1},1fr);gap:${Number(p.gap) || 0}px`}
|
|
227
|
+
{...blockAttrs}
|
|
200
228
|
>
|
|
201
229
|
{cols.map((col) => (
|
|
202
230
|
<div class="bcms-col">
|
|
@@ -208,7 +236,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
208
236
|
}
|
|
209
237
|
case "form": {
|
|
210
238
|
const form = formMap.get(p.formId);
|
|
211
|
-
return form ? <BcmsForm form={form} turnstileSiteKey={turnstileSiteKey} /> : null;
|
|
239
|
+
return form ? <BcmsForm form={form} turnstileSiteKey={turnstileSiteKey} {...blockAttrs} /> : null;
|
|
212
240
|
}
|
|
213
241
|
case "section": {
|
|
214
242
|
const inner = <Astro.self blocks={arr(p.children)} depth={depth} seen={seen} {...pass} />;
|
|
@@ -220,6 +248,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
220
248
|
p.background ? `background:${p.background}` : "",
|
|
221
249
|
p.paddingY != null ? `padding:${Number(p.paddingY) || 0}px 0` : "",
|
|
222
250
|
].filter(Boolean).join(";") || undefined}
|
|
251
|
+
{...blockAttrs}
|
|
223
252
|
>
|
|
224
253
|
{mw ? <div class="bcms-section-inner" style={`max-width:${mw}px;margin:0 auto`}>{inner}</div> : inner}
|
|
225
254
|
</section>
|
|
@@ -228,7 +257,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
228
257
|
case "navbar": {
|
|
229
258
|
const logo = p.logo && typeof p.logo === "object" ? p.logo : null;
|
|
230
259
|
return (
|
|
231
|
-
<nav class="bcms-navbar">
|
|
260
|
+
<nav class="bcms-navbar" {...blockAttrs}>
|
|
232
261
|
{logo?.src ? (
|
|
233
262
|
<a class="bcms-navbar-logo" href={logo.href ?? "/"}>
|
|
234
263
|
<img src={logo.src} alt={logo.alt ?? ""} />
|
|
@@ -243,7 +272,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
243
272
|
}
|
|
244
273
|
case "footer":
|
|
245
274
|
return (
|
|
246
|
-
<footer class="bcms-footer">
|
|
275
|
+
<footer class="bcms-footer" {...blockAttrs}>
|
|
247
276
|
<div class="bcms-footer-cols">
|
|
248
277
|
{arr(p.columns).map((col: any) => (
|
|
249
278
|
<div class="bcms-footer-col">
|
|
@@ -268,6 +297,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
268
297
|
data-bcms-slider
|
|
269
298
|
data-autoplay={p.autoplay ? String(Number(p.interval) || 5000) : undefined}
|
|
270
299
|
data-loop={p.loop ? "" : undefined}
|
|
300
|
+
{...blockAttrs}
|
|
271
301
|
>
|
|
272
302
|
<div class="bcms-slides">
|
|
273
303
|
{arr(p.slides).map((s: any, i: number) => (
|
|
@@ -282,7 +312,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
282
312
|
);
|
|
283
313
|
case "tabs":
|
|
284
314
|
return (
|
|
285
|
-
<div class="bcms-tabs" data-bcms-tabs>
|
|
315
|
+
<div class="bcms-tabs" data-bcms-tabs {...blockAttrs}>
|
|
286
316
|
<div class="bcms-tablist" role="tablist">
|
|
287
317
|
{arr(p.tabs).map((t: any, i: number) => (
|
|
288
318
|
<button role="tab" class="bcms-tab" aria-selected={i === 0 ? "true" : "false"} data-tab={String(i)}>
|
|
@@ -300,7 +330,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
300
330
|
case "component": {
|
|
301
331
|
const resolved = resolveComponent(p);
|
|
302
332
|
if (!resolved) return null;
|
|
303
|
-
return <Astro.self blocks={resolved.blocks} depth={depth + 1} seen={[...seen, resolved.id]} {...pass} />;
|
|
333
|
+
return <Astro.self blocks={resolved.blocks} depth={depth + 1} seen={[...seen, resolved.id]} containerAttrs={blockAttrs} {...pass} />;
|
|
304
334
|
}
|
|
305
335
|
default:
|
|
306
336
|
return null;
|
|
@@ -310,25 +340,14 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
310
340
|
// the builder canvas, the Next adapter and the hosted site.
|
|
311
341
|
if (node == null) return null;
|
|
312
342
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* TOP LEVEL ONLY (`!nested`): a section IS a top-level block, and tagging nested ones would
|
|
319
|
-
* make a column's child look like a section you could drag out of its parent.
|
|
320
|
-
*
|
|
321
|
-
* A wrapper element rather than an attribute on `node`, because Astro renders to HTML and has
|
|
322
|
-
* no `cloneElement` — the same constraint that made the hosted renderer wrap
|
|
323
|
-
* (`render-page.ts`), and the same reasoning applies: a plain block-level div, so a full-bleed
|
|
324
|
-
* section background still reaches the edges. The Next adapter clones instead, precisely
|
|
325
|
-
* because React lets it and a wrapper there would land inside the customer's own layout.
|
|
343
|
+
* The style wrapper is the ONLY wrapper left, and it is unchanged: a style token needs a box
|
|
344
|
+
* to apply to. When it exists it IS this block's root, so it carries the marker and
|
|
345
|
+
* `blockAttrs` above is empty — the two are mutually exclusive by construction, so a block
|
|
346
|
+
* never gets the attribute twice and never gets a second <div>.
|
|
326
347
|
*
|
|
327
|
-
*
|
|
348
|
+
* Everything else now carries `data-bcms-block` on its own root (see `blockAttrs` above).
|
|
328
349
|
*/
|
|
329
|
-
|
|
330
|
-
if (style) return <div style={style} data-bcms-block={blockId}>{node}</div>;
|
|
331
|
-
if (blockId) return <div data-bcms-block={blockId}>{node}</div>;
|
|
350
|
+
if (style) return <div style={style} data-bcms-block={nested ? undefined : block.id}>{node}</div>;
|
|
332
351
|
return node;
|
|
333
352
|
})}
|
|
334
353
|
</div>
|
|
@@ -14,9 +14,14 @@ interface Props {
|
|
|
14
14
|
form: DeliveryForm;
|
|
15
15
|
turnstileSiteKey?: string | null;
|
|
16
16
|
class?: string;
|
|
17
|
+
/** Anything else lands on the <form> itself. <BcmsBlocks> uses this to put a form block's
|
|
18
|
+
* `data-bcms-block` marker on its own root — without it the attribute is passed as a prop
|
|
19
|
+
* nothing reads, the marker silently vanishes, and a top-level form becomes a section the
|
|
20
|
+
* Visual Editor cannot see. Caught by the marker canary in blocks.test.ts. */
|
|
21
|
+
[key: `data-${string}`]: unknown;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
const { form, turnstileSiteKey, class: className } = Astro.props;
|
|
24
|
+
const { form, turnstileSiteKey, class: className, ...rest } = Astro.props;
|
|
20
25
|
const action = `${config.apiUrl}/api/v1/forms/public/${encodeURIComponent(form.id)}/submissions`;
|
|
21
26
|
|
|
22
27
|
const INPUT_TYPE: Partial<Record<DeliveryFormField["type"], string>> = {
|
|
@@ -37,6 +42,7 @@ const showTurnstile = Boolean(form.turnstileEnabled && turnstileSiteKey);
|
|
|
37
42
|
data-bcms-form={form.id}
|
|
38
43
|
data-bcms-form-success={form.successMessage ?? "Thanks!"}
|
|
39
44
|
data-bcms-form-redirect={form.redirectUrl ?? ""}
|
|
45
|
+
{...rest}
|
|
40
46
|
>
|
|
41
47
|
{form.fields?.map((f) => {
|
|
42
48
|
const wrapAttrs = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettercms-ai/astro",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
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",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@bettercms-ai/image-url": "^0.2.0",
|
|
51
|
-
"@bettercms-ai/sdk": "^1.
|
|
52
|
-
"@bettercms-ai/types": "^1.
|
|
53
|
-
"@bettercms-ai/ui": "^0.
|
|
51
|
+
"@bettercms-ai/sdk": "^1.13.0",
|
|
52
|
+
"@bettercms-ai/types": "^1.10.0",
|
|
53
|
+
"@bettercms-ai/ui": "^0.6.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"astro": ">=4"
|