@styleflow.app/flowpress-astro-core 0.2.0-beta.0 → 0.2.0-beta.10
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/CHANGELOG.md +53 -0
- package/README.md +20 -1
- package/dist/astro/BaseLayout.astro +4 -1
- package/dist/astro/DefaultTemplate.astro +31 -21
- package/dist/astro/GutenbergRenderer.astro +93 -120
- package/dist/astro/blocks/ColumnBlock.astro +83 -0
- package/dist/astro/blocks/FormBlock.astro +233 -0
- package/dist/astro/blocks/GridBlock.astro +70 -0
- package/dist/astro/blocks/HeroBlock.astro +113 -0
- package/dist/astro/blocks/ListItemFallback.astro +28 -0
- package/dist/astro/blocks/TextBlock.astro +79 -0
- package/dist/astro/blocks/helpers.d.ts +24 -0
- package/dist/astro/blocks/helpers.d.ts.map +1 -0
- package/dist/astro/blocks/helpers.js +104 -0
- package/dist/astro/blocks/helpers.js.map +1 -0
- package/dist/astro/blocks/helpers.ts +143 -0
- package/dist/client.js.map +1 -1
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/style-classes.d.ts +9 -0
- package/dist/style-classes.d.ts.map +1 -0
- package/dist/style-classes.js +47 -0
- package/dist/style-classes.js.map +1 -0
- package/dist/styleflow.d.ts.map +1 -1
- package/dist/styleflow.js +12 -0
- package/dist/styleflow.js.map +1 -1
- package/dist/template-hierarchy.js.map +1 -1
- package/dist/templates.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
import S from "@styleflow.app/astro/S.astro";
|
|
3
|
+
import type { FlowpressBlock } from "../../types.js";
|
|
4
|
+
import type { StyleflowContract } from "../../styleflow.js";
|
|
5
|
+
import { blockHtmlAttributes, semanticProps } from "./helpers.js";
|
|
6
|
+
|
|
7
|
+
interface FormField {
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
values?: string[];
|
|
12
|
+
labels?: string[];
|
|
13
|
+
options?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface FormSchema {
|
|
17
|
+
id: number;
|
|
18
|
+
title: string;
|
|
19
|
+
fields: FormField[];
|
|
20
|
+
submitUrl: string;
|
|
21
|
+
turnstileSiteKey: string;
|
|
22
|
+
available: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
block: FlowpressBlock;
|
|
27
|
+
contract?: StyleflowContract;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { block, contract } = Astro.props;
|
|
31
|
+
const schema = block.attributes.formSchema as FormSchema | undefined;
|
|
32
|
+
const semantic = semanticProps(block.attributes, contract);
|
|
33
|
+
const htmlAttributes = blockHtmlAttributes(block.attributes);
|
|
34
|
+
const labelFor = (field: FormField): string => field.name
|
|
35
|
+
.replace(/[-_]+/g, " ")
|
|
36
|
+
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
37
|
+
const optionLabel = (field: FormField, index: number): string =>
|
|
38
|
+
field.labels?.[index] || field.values?.[index] || "";
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
{schema?.available ? (
|
|
42
|
+
<S as="div" {...semantic} {...htmlAttributes}>
|
|
43
|
+
<flowpress-cf7-form>
|
|
44
|
+
<form action={schema.submitUrl} method="post" enctype="multipart/form-data" novalidate>
|
|
45
|
+
{block.attributes.title || schema.title ? (
|
|
46
|
+
<S as="h2">{String(block.attributes.title || schema.title)}</S>
|
|
47
|
+
) : null}
|
|
48
|
+
{schema.fields.map((field) => {
|
|
49
|
+
if (field.type === "submit") {
|
|
50
|
+
return (
|
|
51
|
+
<button type="submit">
|
|
52
|
+
{field.values?.[0] || "Invia"}
|
|
53
|
+
</button>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (field.type === "hidden") {
|
|
57
|
+
return <input type="hidden" name={field.name} value={field.values?.[0] || ""} />;
|
|
58
|
+
}
|
|
59
|
+
if (["checkbox", "radio"].includes(field.type)) {
|
|
60
|
+
return (
|
|
61
|
+
<fieldset>
|
|
62
|
+
<legend>{labelFor(field)}</legend>
|
|
63
|
+
{(field.values || []).map((value, index) => (
|
|
64
|
+
<label>
|
|
65
|
+
<input
|
|
66
|
+
type={field.type}
|
|
67
|
+
name={field.type === "checkbox" ? `${field.name}[]` : field.name}
|
|
68
|
+
value={value}
|
|
69
|
+
required={field.required}
|
|
70
|
+
/>
|
|
71
|
+
{optionLabel(field, index)}
|
|
72
|
+
</label>
|
|
73
|
+
))}
|
|
74
|
+
<span data-field-error={field.name}></span>
|
|
75
|
+
</fieldset>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (field.type === "acceptance") {
|
|
79
|
+
return (
|
|
80
|
+
<label>
|
|
81
|
+
<input type="checkbox" name={field.name} value="1" required={field.required} />
|
|
82
|
+
{field.values?.[0] || labelFor(field)}
|
|
83
|
+
<span data-field-error={field.name}></span>
|
|
84
|
+
</label>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (field.type === "select") {
|
|
88
|
+
return (
|
|
89
|
+
<label>
|
|
90
|
+
{labelFor(field)}
|
|
91
|
+
<select
|
|
92
|
+
name={field.options?.includes("multiple") ? `${field.name}[]` : field.name}
|
|
93
|
+
required={field.required}
|
|
94
|
+
multiple={field.options?.includes("multiple")}
|
|
95
|
+
>
|
|
96
|
+
{(field.values || []).map((value, index) => (
|
|
97
|
+
<option value={value}>{optionLabel(field, index)}</option>
|
|
98
|
+
))}
|
|
99
|
+
</select>
|
|
100
|
+
<span data-field-error={field.name}></span>
|
|
101
|
+
</label>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (field.type === "textarea") {
|
|
105
|
+
return (
|
|
106
|
+
<label>
|
|
107
|
+
{labelFor(field)}
|
|
108
|
+
<textarea name={field.name} required={field.required}></textarea>
|
|
109
|
+
<span data-field-error={field.name}></span>
|
|
110
|
+
</label>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return (
|
|
114
|
+
<label>
|
|
115
|
+
{labelFor(field)}
|
|
116
|
+
<input
|
|
117
|
+
type={field.type === "file" ? "file" : field.type}
|
|
118
|
+
name={field.name}
|
|
119
|
+
required={field.required}
|
|
120
|
+
/>
|
|
121
|
+
<span data-field-error={field.name}></span>
|
|
122
|
+
</label>
|
|
123
|
+
);
|
|
124
|
+
})}
|
|
125
|
+
<div
|
|
126
|
+
data-turnstile
|
|
127
|
+
data-sitekey={schema.turnstileSiteKey}
|
|
128
|
+
aria-label="Verifica anti-spam"
|
|
129
|
+
></div>
|
|
130
|
+
<p data-form-status role="status" aria-live="polite"></p>
|
|
131
|
+
</form>
|
|
132
|
+
</flowpress-cf7-form>
|
|
133
|
+
</S>
|
|
134
|
+
) : (
|
|
135
|
+
<S as="p" {...semantic} {...htmlAttributes} role="status">
|
|
136
|
+
Il modulo non è temporaneamente disponibile.
|
|
137
|
+
</S>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
<script>
|
|
141
|
+
declare global {
|
|
142
|
+
interface Window {
|
|
143
|
+
turnstile?: {
|
|
144
|
+
render(target: Element, options: Record<string, unknown>): string;
|
|
145
|
+
reset(widgetId?: string): void;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let turnstilePromise: Promise<void> | undefined;
|
|
151
|
+
const loadTurnstile = (): Promise<void> => {
|
|
152
|
+
if (window.turnstile) return Promise.resolve();
|
|
153
|
+
if (turnstilePromise) return turnstilePromise;
|
|
154
|
+
turnstilePromise = new Promise((resolve, reject) => {
|
|
155
|
+
const existing = document.querySelector<HTMLScriptElement>(
|
|
156
|
+
'script[data-flowpress-turnstile]'
|
|
157
|
+
);
|
|
158
|
+
if (existing) {
|
|
159
|
+
existing.addEventListener("load", () => resolve(), { once: true });
|
|
160
|
+
existing.addEventListener("error", () => reject(new Error("Turnstile unavailable")), { once: true });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const script = document.createElement("script");
|
|
164
|
+
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
165
|
+
script.async = true;
|
|
166
|
+
script.defer = true;
|
|
167
|
+
script.dataset.flowpressTurnstile = "true";
|
|
168
|
+
script.addEventListener("load", () => resolve(), { once: true });
|
|
169
|
+
script.addEventListener("error", () => reject(new Error("Turnstile unavailable")), { once: true });
|
|
170
|
+
document.head.append(script);
|
|
171
|
+
});
|
|
172
|
+
return turnstilePromise;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (!customElements.get("flowpress-cf7-form")) {
|
|
176
|
+
customElements.define("flowpress-cf7-form", class extends HTMLElement {
|
|
177
|
+
private widgetId?: string;
|
|
178
|
+
|
|
179
|
+
connectedCallback() {
|
|
180
|
+
const form = this.querySelector<HTMLFormElement>("form");
|
|
181
|
+
const target = this.querySelector<HTMLElement>("[data-turnstile]");
|
|
182
|
+
const status = this.querySelector<HTMLElement>("[data-form-status]");
|
|
183
|
+
if (!form || !target || !status) return;
|
|
184
|
+
|
|
185
|
+
loadTurnstile()
|
|
186
|
+
.then(() => {
|
|
187
|
+
if (!window.turnstile || this.widgetId) return;
|
|
188
|
+
this.widgetId = window.turnstile.render(target, {
|
|
189
|
+
sitekey: target.dataset.sitekey
|
|
190
|
+
});
|
|
191
|
+
})
|
|
192
|
+
.catch(() => {
|
|
193
|
+
status.textContent = "La verifica anti-spam non è disponibile.";
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
form.addEventListener("submit", async (event) => {
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
status.textContent = "Invio in corso…";
|
|
199
|
+
form.querySelectorAll<HTMLElement>("[data-field-error]")
|
|
200
|
+
.forEach((node) => { node.textContent = ""; });
|
|
201
|
+
const submit = form.querySelector<HTMLButtonElement>('[type="submit"]');
|
|
202
|
+
if (submit) submit.disabled = true;
|
|
203
|
+
try {
|
|
204
|
+
const response = await fetch(form.action, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
body: new FormData(form),
|
|
207
|
+
headers: { Accept: "application/json" }
|
|
208
|
+
});
|
|
209
|
+
const result = await response.json() as {
|
|
210
|
+
message?: string;
|
|
211
|
+
invalidFields?: Array<{ field?: string; into?: string; message?: string }>;
|
|
212
|
+
};
|
|
213
|
+
status.textContent = result.message || (response.ok ? "Messaggio inviato." : "Invio non riuscito.");
|
|
214
|
+
for (const invalid of result.invalidFields || []) {
|
|
215
|
+
const name = invalid.field || invalid.into?.replace(/^span\./, "") || "";
|
|
216
|
+
const node = name
|
|
217
|
+
? form.querySelector<HTMLElement>(`[data-field-error="${CSS.escape(name)}"]`)
|
|
218
|
+
: null;
|
|
219
|
+
if (node) node.textContent = invalid.message || "";
|
|
220
|
+
}
|
|
221
|
+
if (response.ok) form.reset();
|
|
222
|
+
if (window.turnstile) window.turnstile.reset(this.widgetId);
|
|
223
|
+
} catch {
|
|
224
|
+
status.textContent = "Impossibile inviare il modulo. Riprova.";
|
|
225
|
+
if (window.turnstile) window.turnstile.reset(this.widgetId);
|
|
226
|
+
} finally {
|
|
227
|
+
if (submit) submit.disabled = false;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
</script>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
import S from "@styleflow.app/astro/S.astro";
|
|
3
|
+
import type { FlowpressBlock } from "../../types.js";
|
|
4
|
+
import type { StyleflowContract } from "../../styleflow.js";
|
|
5
|
+
import {
|
|
6
|
+
blockHtmlAttributes,
|
|
7
|
+
responsiveValues,
|
|
8
|
+
semanticProps
|
|
9
|
+
} from "./helpers.js";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
block: FlowpressBlock;
|
|
13
|
+
contract?: StyleflowContract;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { block, contract } = Astro.props;
|
|
17
|
+
const values = responsiveValues(
|
|
18
|
+
block.attributes,
|
|
19
|
+
"columns",
|
|
20
|
+
Number(block.attributes.columns) || 12
|
|
21
|
+
);
|
|
22
|
+
const align = ["start", "center", "end", "stretch"].includes(
|
|
23
|
+
String(block.attributes.align)
|
|
24
|
+
) ? String(block.attributes.align) : "stretch";
|
|
25
|
+
const semantic = semanticProps(block.attributes, contract, {
|
|
26
|
+
surface: "default",
|
|
27
|
+
layoutRole: "container",
|
|
28
|
+
density: "md"
|
|
29
|
+
});
|
|
30
|
+
const htmlAttributes = blockHtmlAttributes(block.attributes, "flowpress-grid");
|
|
31
|
+
const style = [
|
|
32
|
+
`--flowpress-grid-base:${values.base}`,
|
|
33
|
+
`--flowpress-grid-sm:${values.sm}`,
|
|
34
|
+
`--flowpress-grid-md:${values.md}`,
|
|
35
|
+
`--flowpress-grid-lg:${values.lg}`,
|
|
36
|
+
`--flowpress-grid-xl:${values.xl}`,
|
|
37
|
+
`--flowpress-grid-2xl:${values["2xl"]}`
|
|
38
|
+
].join(";");
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
<S as="div" {...semantic} {...htmlAttributes} data-align={align} style={style}>
|
|
42
|
+
<slot />
|
|
43
|
+
</S>
|
|
44
|
+
|
|
45
|
+
<style>
|
|
46
|
+
.flowpress-grid {
|
|
47
|
+
display: grid;
|
|
48
|
+
grid-template-columns: repeat(var(--flowpress-grid-base), minmax(0, 1fr));
|
|
49
|
+
align-items: var(--flowpress-grid-align, stretch);
|
|
50
|
+
}
|
|
51
|
+
.flowpress-grid[data-align="start"] { --flowpress-grid-align: start; }
|
|
52
|
+
.flowpress-grid[data-align="center"] { --flowpress-grid-align: center; }
|
|
53
|
+
.flowpress-grid[data-align="end"] { --flowpress-grid-align: end; }
|
|
54
|
+
.flowpress-grid[data-align="stretch"] { --flowpress-grid-align: stretch; }
|
|
55
|
+
@media (min-width: 40rem) {
|
|
56
|
+
.flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-sm), minmax(0, 1fr)); }
|
|
57
|
+
}
|
|
58
|
+
@media (min-width: 48rem) {
|
|
59
|
+
.flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-md), minmax(0, 1fr)); }
|
|
60
|
+
}
|
|
61
|
+
@media (min-width: 64rem) {
|
|
62
|
+
.flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-lg), minmax(0, 1fr)); }
|
|
63
|
+
}
|
|
64
|
+
@media (min-width: 80rem) {
|
|
65
|
+
.flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-xl), minmax(0, 1fr)); }
|
|
66
|
+
}
|
|
67
|
+
@media (min-width: 96rem) {
|
|
68
|
+
.flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-2xl), minmax(0, 1fr)); }
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
import S from "@styleflow.app/astro/S.astro";
|
|
3
|
+
import type { FlowpressBlock } from "../../types.js";
|
|
4
|
+
import type { StyleflowContract } from "../../styleflow.js";
|
|
5
|
+
import {
|
|
6
|
+
blockHtmlAttributes,
|
|
7
|
+
objectValue,
|
|
8
|
+
semanticProps
|
|
9
|
+
} from "./helpers.js";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
block: FlowpressBlock;
|
|
13
|
+
contract?: StyleflowContract;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { block, contract } = Astro.props;
|
|
17
|
+
const attributes = block.attributes;
|
|
18
|
+
const semantic = semanticProps(attributes, contract, {
|
|
19
|
+
surface: "default",
|
|
20
|
+
layoutRole: "region",
|
|
21
|
+
density: "md"
|
|
22
|
+
});
|
|
23
|
+
const htmlAttributes = blockHtmlAttributes(attributes, "flowpress-hero");
|
|
24
|
+
const backgroundType = ["color", "image", "video"].includes(
|
|
25
|
+
String(attributes.backgroundType)
|
|
26
|
+
) ? String(attributes.backgroundType) : "color";
|
|
27
|
+
const image = objectValue(block, "backgroundImage");
|
|
28
|
+
const video = objectValue(block, "backgroundVideo");
|
|
29
|
+
const imageSrc = String(image.url ?? image.sourceUrl ?? "");
|
|
30
|
+
const videoSrc = String(video.url ?? video.sourceUrl ?? video.mediaItemUrl ?? "");
|
|
31
|
+
const scrimEnabled = Boolean(attributes.scrimEnabled) && backgroundType !== "color";
|
|
32
|
+
const scrimIntensity = Math.min(10, Math.max(1, Number(attributes.scrimIntensity) || 6));
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<S
|
|
36
|
+
as="section"
|
|
37
|
+
{...semantic}
|
|
38
|
+
{...htmlAttributes}
|
|
39
|
+
style={`--flowpress-scrim-opacity:${scrimIntensity * 10}%`}
|
|
40
|
+
>
|
|
41
|
+
{backgroundType === "image" && imageSrc ? (
|
|
42
|
+
<img
|
|
43
|
+
class="flowpress-hero__media"
|
|
44
|
+
src={imageSrc}
|
|
45
|
+
alt=""
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
loading="eager"
|
|
48
|
+
decoding="async"
|
|
49
|
+
/>
|
|
50
|
+
) : null}
|
|
51
|
+
{backgroundType === "video" && videoSrc ? (
|
|
52
|
+
<video
|
|
53
|
+
class="flowpress-hero__media"
|
|
54
|
+
src={videoSrc}
|
|
55
|
+
poster={imageSrc || undefined}
|
|
56
|
+
autoplay
|
|
57
|
+
muted
|
|
58
|
+
loop
|
|
59
|
+
playsinline
|
|
60
|
+
preload="metadata"
|
|
61
|
+
aria-hidden="true"
|
|
62
|
+
tabindex="-1"
|
|
63
|
+
/>
|
|
64
|
+
) : null}
|
|
65
|
+
{scrimEnabled ? <span class="flowpress-hero__scrim" aria-hidden="true"></span> : null}
|
|
66
|
+
<div class="flowpress-hero__content">
|
|
67
|
+
<slot />
|
|
68
|
+
</div>
|
|
69
|
+
</S>
|
|
70
|
+
|
|
71
|
+
<style>
|
|
72
|
+
.flowpress-hero {
|
|
73
|
+
position: relative;
|
|
74
|
+
display: grid;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
isolation: isolate;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.flowpress-hero__media,
|
|
80
|
+
.flowpress-hero__scrim {
|
|
81
|
+
position: absolute;
|
|
82
|
+
inset: 0;
|
|
83
|
+
width: 100%;
|
|
84
|
+
height: 100%;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.flowpress-hero__media {
|
|
88
|
+
z-index: -2;
|
|
89
|
+
object-fit: cover;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.flowpress-hero__scrim {
|
|
93
|
+
z-index: -1;
|
|
94
|
+
background: color-mix(
|
|
95
|
+
in srgb,
|
|
96
|
+
var(--sf-background) var(--flowpress-scrim-opacity),
|
|
97
|
+
transparent
|
|
98
|
+
);
|
|
99
|
+
pointer-events: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.flowpress-hero__content {
|
|
103
|
+
position: relative;
|
|
104
|
+
display: grid;
|
|
105
|
+
gap: inherit;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@media (prefers-reduced-motion: reduce) {
|
|
109
|
+
.flowpress-hero__media:is(video) {
|
|
110
|
+
display: none;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
import S from "@styleflow.app/astro/S.astro";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
item: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { item } = Astro.props;
|
|
9
|
+
const image = item.image && typeof item.image === "object" && !Array.isArray(item.image)
|
|
10
|
+
? item.image as Record<string, unknown>
|
|
11
|
+
: {};
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{image.url ? (
|
|
15
|
+
<img
|
|
16
|
+
src={String(image.url)}
|
|
17
|
+
alt={String(image.alt ?? "")}
|
|
18
|
+
loading="lazy"
|
|
19
|
+
decoding="async"
|
|
20
|
+
/>
|
|
21
|
+
) : null}
|
|
22
|
+
{item.uri ? (
|
|
23
|
+
<S as="a" href={String(item.uri)}>{String(item.title ?? "")}</S>
|
|
24
|
+
) : (
|
|
25
|
+
<S as="span">{String(item.title ?? "")}</S>
|
|
26
|
+
)}
|
|
27
|
+
{item.excerpt ? <S as="p">{String(item.excerpt)}</S> : null}
|
|
28
|
+
{item.priceHtml ? <S as="div" set:html={String(item.priceHtml)} /> : null}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
import S from "@styleflow.app/astro/S.astro";
|
|
3
|
+
import type { FlowpressBlock } from "../../types.js";
|
|
4
|
+
import type { StyleflowContract } from "../../styleflow.js";
|
|
5
|
+
import {
|
|
6
|
+
blockHtmlAttributes,
|
|
7
|
+
preserveRichTextSpacing,
|
|
8
|
+
semanticProps,
|
|
9
|
+
textValue,
|
|
10
|
+
unwrapCoreHtml
|
|
11
|
+
} from "./helpers.js";
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
block: FlowpressBlock;
|
|
15
|
+
contract?: StyleflowContract;
|
|
16
|
+
hasChildren?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { block, contract, hasChildren = false } = Astro.props;
|
|
20
|
+
const name = block.name.replace(/^sf\//, "");
|
|
21
|
+
const attributes = block.attributes;
|
|
22
|
+
const heading = name === "heading" || name === "core/heading";
|
|
23
|
+
const level = Math.min(6, Math.max(1, Number(attributes.level ?? 2)));
|
|
24
|
+
const headingTag = `h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
|
25
|
+
const defaults = heading
|
|
26
|
+
? {
|
|
27
|
+
ty: "heading",
|
|
28
|
+
v: String(attributes.v ?? attributes.variant ?? level),
|
|
29
|
+
w: String(attributes.w ?? attributes.weight ?? "strong"),
|
|
30
|
+
foreground: "primary"
|
|
31
|
+
}
|
|
32
|
+
: {
|
|
33
|
+
ty: "body",
|
|
34
|
+
v: String(attributes.v ?? (name.includes("quote") ? "lg" : "md")),
|
|
35
|
+
w: String(attributes.w ?? "default"),
|
|
36
|
+
foreground: "primary"
|
|
37
|
+
};
|
|
38
|
+
const semantic = semanticProps(attributes, contract, defaults);
|
|
39
|
+
const htmlAttributes = blockHtmlAttributes(attributes);
|
|
40
|
+
const coreHtml = String(
|
|
41
|
+
attributes.content
|
|
42
|
+
?? attributes.value
|
|
43
|
+
?? block.innerHtml
|
|
44
|
+
?? ""
|
|
45
|
+
);
|
|
46
|
+
const html = name === "heading"
|
|
47
|
+
? textValue(block)
|
|
48
|
+
: name === "rich-text"
|
|
49
|
+
? preserveRichTextSpacing(String(attributes.content ?? ""))
|
|
50
|
+
: name === "quote"
|
|
51
|
+
? textValue(block, "quote")
|
|
52
|
+
: unwrapCoreHtml(preserveRichTextSpacing(coreHtml));
|
|
53
|
+
const listTag = Boolean(attributes.ordered) ? "ol" : "ul";
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
{heading ? (
|
|
57
|
+
<S as={headingTag} {...semantic} {...htmlAttributes} set:html={html} />
|
|
58
|
+
) : name === "core/list" ? (
|
|
59
|
+
<S as={listTag} {...semantic} {...htmlAttributes}>
|
|
60
|
+
{hasChildren ? <slot /> : <Fragment set:html={html} />}
|
|
61
|
+
</S>
|
|
62
|
+
) : name === "core/list-item" ? (
|
|
63
|
+
<S as="li" {...semantic} {...htmlAttributes}>
|
|
64
|
+
<span set:html={html}></span>
|
|
65
|
+
{hasChildren ? <slot /> : null}
|
|
66
|
+
</S>
|
|
67
|
+
) : name === "quote" || name === "core/quote" ? (
|
|
68
|
+
<S as="blockquote" {...semantic} {...htmlAttributes}>
|
|
69
|
+
{hasChildren ? <slot /> : <div set:html={html}></div>}
|
|
70
|
+
{attributes.citation ? <cite>{String(attributes.citation)}</cite> : null}
|
|
71
|
+
</S>
|
|
72
|
+
) : (
|
|
73
|
+
<S
|
|
74
|
+
as={name === "core/paragraph" ? "p" : "div"}
|
|
75
|
+
{...semantic}
|
|
76
|
+
{...htmlAttributes}
|
|
77
|
+
set:html={html}
|
|
78
|
+
/>
|
|
79
|
+
)}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type StyleflowContract } from "../../styleflow.js";
|
|
2
|
+
import type { FlowpressBlock } from "../../types.js";
|
|
3
|
+
export declare const DEFAULT_SEMANTIC_PROPS: {
|
|
4
|
+
surface: string;
|
|
5
|
+
layoutRole: string;
|
|
6
|
+
density: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function blockHtmlAttributes(attributes: Record<string, unknown>, baseClass?: string): {
|
|
9
|
+
id?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function semanticProps(attributes: Record<string, unknown>, contract?: StyleflowContract, defaults?: Record<string, unknown>): Record<string, unknown>;
|
|
13
|
+
export declare function objectValue(block: FlowpressBlock, key: string): Record<string, unknown>;
|
|
14
|
+
export declare function textValue(block: FlowpressBlock, key?: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Keep intentional blank lines visible across current and legacy RichText
|
|
17
|
+
* serializations. Gutenberg can represent them either as repeated `<br>` tags,
|
|
18
|
+
* empty paragraph elements, or literal newlines in an attribute value.
|
|
19
|
+
*/
|
|
20
|
+
export declare function preserveRichTextSpacing(html: string): string;
|
|
21
|
+
export declare function linkValue(block: FlowpressBlock, key?: string): string;
|
|
22
|
+
export declare function responsiveValues(attributes: Record<string, unknown>, prefix: "columns" | "span", fallback: number): Record<"base" | "sm" | "md" | "lg" | "xl" | "2xl", number>;
|
|
23
|
+
export declare function unwrapCoreHtml(html: string): string;
|
|
24
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,eAAO,MAAM,sBAAsB;;;;CAIlC,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,SAAS,SAAK,GACb;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgBjC;AAqBD,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAA0B,GACzD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAErE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB5D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAMrE;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE,MAAM,CAAC,CAe5D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { resolveDynamicStyleflowProps } from "../../styleflow.js";
|
|
2
|
+
export const DEFAULT_SEMANTIC_PROPS = {
|
|
3
|
+
surface: "default",
|
|
4
|
+
layoutRole: "stack",
|
|
5
|
+
density: "md"
|
|
6
|
+
};
|
|
7
|
+
export function blockHtmlAttributes(attributes, baseClass = "") {
|
|
8
|
+
const anchor = typeof attributes.anchor === "string"
|
|
9
|
+
? attributes.anchor.trim()
|
|
10
|
+
: "";
|
|
11
|
+
const customClass = typeof attributes.className === "string"
|
|
12
|
+
? attributes.className.trim()
|
|
13
|
+
: "";
|
|
14
|
+
const className = [baseClass.trim(), customClass]
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.join(" ")
|
|
17
|
+
.replace(/\s+/g, " ");
|
|
18
|
+
return {
|
|
19
|
+
...(anchor ? { id: anchor } : {}),
|
|
20
|
+
...(className ? { class: className } : {})
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const SEMANTIC_KEYS = [
|
|
24
|
+
"tone",
|
|
25
|
+
"intensity",
|
|
26
|
+
"surface",
|
|
27
|
+
"layoutRole",
|
|
28
|
+
"density",
|
|
29
|
+
"ty",
|
|
30
|
+
"v",
|
|
31
|
+
"w",
|
|
32
|
+
"foreground",
|
|
33
|
+
"interactivePriority",
|
|
34
|
+
"noBackground",
|
|
35
|
+
"noBorder",
|
|
36
|
+
"noPadding",
|
|
37
|
+
"noGap",
|
|
38
|
+
"noRadius",
|
|
39
|
+
"noForeground"
|
|
40
|
+
];
|
|
41
|
+
export function semanticProps(attributes, contract, defaults = DEFAULT_SEMANTIC_PROPS) {
|
|
42
|
+
const input = Object.fromEntries(SEMANTIC_KEYS
|
|
43
|
+
.filter((key) => attributes[key] !== undefined)
|
|
44
|
+
.map((key) => [key, attributes[key]]));
|
|
45
|
+
return contract
|
|
46
|
+
? resolveDynamicStyleflowProps(input, defaults, contract)
|
|
47
|
+
: { ...defaults, ...input };
|
|
48
|
+
}
|
|
49
|
+
export function objectValue(block, key) {
|
|
50
|
+
const value = block.attributes[key];
|
|
51
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
52
|
+
? value
|
|
53
|
+
: {};
|
|
54
|
+
}
|
|
55
|
+
export function textValue(block, key = "text") {
|
|
56
|
+
return String(block.attributes[key] ?? "");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Keep intentional blank lines visible across current and legacy RichText
|
|
60
|
+
* serializations. Gutenberg can represent them either as repeated `<br>` tags,
|
|
61
|
+
* empty paragraph elements, or literal newlines in an attribute value.
|
|
62
|
+
*/
|
|
63
|
+
export function preserveRichTextSpacing(html) {
|
|
64
|
+
const visibleEmptyBlocks = html
|
|
65
|
+
.replace(/\r\n?/g, "\n")
|
|
66
|
+
.replace(/<(p|div)(\s[^>]*)?>(?:\s| | )*<\/\1>/gi, (_match, tag, attributes = "") => `<${tag}${attributes}><br aria-hidden="true"></${tag}>`);
|
|
67
|
+
return visibleEmptyBlocks
|
|
68
|
+
.split(/(<[^>]+>)/g)
|
|
69
|
+
.map((part) => {
|
|
70
|
+
if (part.startsWith("<") || part.trim() === "") {
|
|
71
|
+
return part;
|
|
72
|
+
}
|
|
73
|
+
return part.replace(/\n(?:[ \t]*\n)+/g, (newlines) => "<br>".repeat((newlines.match(/\n/g) || []).length));
|
|
74
|
+
})
|
|
75
|
+
.join("");
|
|
76
|
+
}
|
|
77
|
+
export function linkValue(block, key = "link") {
|
|
78
|
+
const value = block.attributes[key];
|
|
79
|
+
const href = typeof value === "string"
|
|
80
|
+
? value
|
|
81
|
+
: String(objectValue(block, key).url ?? "");
|
|
82
|
+
return /^(?:\/(?!\/)|#|https?:\/\/|mailto:|tel:)/i.test(href) ? href : "";
|
|
83
|
+
}
|
|
84
|
+
export function responsiveValues(attributes, prefix, fallback) {
|
|
85
|
+
const keys = ["base", "sm", "md", "lg", "xl", "2xl"];
|
|
86
|
+
const result = {};
|
|
87
|
+
let inherited = fallback;
|
|
88
|
+
for (const key of keys) {
|
|
89
|
+
const suffix = key === "base"
|
|
90
|
+
? "Base"
|
|
91
|
+
: key === "2xl" ? "2xl" : key.charAt(0).toUpperCase() + key.slice(1);
|
|
92
|
+
const candidate = Number(attributes[`${prefix}${suffix}`]);
|
|
93
|
+
if (Number.isInteger(candidate) && candidate >= 1 && candidate <= 12) {
|
|
94
|
+
inherited = candidate;
|
|
95
|
+
}
|
|
96
|
+
result[key] = inherited;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
export function unwrapCoreHtml(html) {
|
|
101
|
+
const match = html.trim().match(/^<(p|h[1-6]|ul|ol|li|blockquote)(?:\s[^>]*)?>([\s\S]*)<\/\1>$/);
|
|
102
|
+
return match?.[2] ?? html;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveDynamicStyleflowProps,\n type StyleflowContract\n} from \"../../styleflow.js\";\nimport type { FlowpressBlock } from \"../../types.js\";\n\nexport const DEFAULT_SEMANTIC_PROPS = {\n surface: \"default\",\n layoutRole: \"stack\",\n density: \"md\"\n};\n\nexport function blockHtmlAttributes(\n attributes: Record<string, unknown>,\n baseClass = \"\"\n): { id?: string; class?: string } {\n const anchor = typeof attributes.anchor === \"string\"\n ? attributes.anchor.trim()\n : \"\";\n const customClass = typeof attributes.className === \"string\"\n ? attributes.className.trim()\n : \"\";\n const className = [baseClass.trim(), customClass]\n .filter(Boolean)\n .join(\" \")\n .replace(/\\s+/g, \" \");\n\n return {\n ...(anchor ? { id: anchor } : {}),\n ...(className ? { class: className } : {})\n };\n}\n\nconst SEMANTIC_KEYS = [\n \"tone\",\n \"intensity\",\n \"surface\",\n \"layoutRole\",\n \"density\",\n \"ty\",\n \"v\",\n \"w\",\n \"foreground\",\n \"interactivePriority\",\n \"noBackground\",\n \"noBorder\",\n \"noPadding\",\n \"noGap\",\n \"noRadius\",\n \"noForeground\"\n] as const;\n\nexport function semanticProps(\n attributes: Record<string, unknown>,\n contract?: StyleflowContract,\n defaults: Record<string, unknown> = DEFAULT_SEMANTIC_PROPS\n): Record<string, unknown> {\n const input = Object.fromEntries(\n SEMANTIC_KEYS\n .filter((key) => attributes[key] !== undefined)\n .map((key) => [key, attributes[key]])\n );\n return contract\n ? resolveDynamicStyleflowProps(input, defaults, contract)\n : { ...defaults, ...input };\n}\n\nexport function objectValue(\n block: FlowpressBlock,\n key: string\n): Record<string, unknown> {\n const value = block.attributes[key];\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nexport function textValue(block: FlowpressBlock, key = \"text\"): string {\n return String(block.attributes[key] ?? \"\");\n}\n\n/**\n * Keep intentional blank lines visible across current and legacy RichText\n * serializations. Gutenberg can represent them either as repeated `<br>` tags,\n * empty paragraph elements, or literal newlines in an attribute value.\n */\nexport function preserveRichTextSpacing(html: string): string {\n const visibleEmptyBlocks = html\n .replace(/\\r\\n?/g, \"\\n\")\n .replace(\n /<(p|div)(\\s[^>]*)?>(?:\\s| | )*<\\/\\1>/gi,\n (_match, tag: string, attributes = \"\") =>\n `<${tag}${attributes}><br aria-hidden=\"true\"></${tag}>`\n );\n\n return visibleEmptyBlocks\n .split(/(<[^>]+>)/g)\n .map((part) => {\n if (part.startsWith(\"<\") || part.trim() === \"\") {\n return part;\n }\n return part.replace(/\\n(?:[ \\t]*\\n)+/g, (newlines) =>\n \"<br>\".repeat((newlines.match(/\\n/g) || []).length)\n );\n })\n .join(\"\");\n}\n\nexport function linkValue(block: FlowpressBlock, key = \"link\"): string {\n const value = block.attributes[key];\n const href = typeof value === \"string\"\n ? value\n : String(objectValue(block, key).url ?? \"\");\n return /^(?:\\/(?!\\/)|#|https?:\\/\\/|mailto:|tel:)/i.test(href) ? href : \"\";\n}\n\nexport function responsiveValues(\n attributes: Record<string, unknown>,\n prefix: \"columns\" | \"span\",\n fallback: number\n): Record<\"base\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\", number> {\n const keys = [\"base\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n const result = {} as Record<(typeof keys)[number], number>;\n let inherited = fallback;\n for (const key of keys) {\n const suffix = key === \"base\"\n ? \"Base\"\n : key === \"2xl\" ? \"2xl\" : key.charAt(0).toUpperCase() + key.slice(1);\n const candidate = Number(attributes[`${prefix}${suffix}`]);\n if (Number.isInteger(candidate) && candidate >= 1 && candidate <= 12) {\n inherited = candidate;\n }\n result[key] = inherited;\n }\n return result;\n}\n\nexport function unwrapCoreHtml(html: string): string {\n const match = html.trim().match(\n /^<(p|h[1-6]|ul|ol|li|blockquote)(?:\\s[^>]*)?>([\\s\\S]*)<\\/\\1>$/\n );\n return match?.[2] ?? html;\n}\n"]}
|