@bettercms-ai/ui 0.16.0 → 0.17.1
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/dist/blocks.js +1127 -763
- package/dist/blocks.js.map +1 -1
- package/dist/index.js +1124 -980
- package/dist/index.js.map +1 -1
- package/dist/registry.js +368 -106
- package/dist/registry.js.map +1 -1
- package/dist/renderer.d.ts +2 -2
- package/dist/renderer.js +163 -19
- package/dist/renderer.js.map +1 -1
- package/dist/resolve.js +22 -4
- package/dist/resolve.js.map +1 -1
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +3 -0
- package/dist/tokens.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -29,1061 +29,1205 @@ function Heading({
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// src/blocks/portableText.tsx
|
|
32
|
-
import
|
|
32
|
+
import React6 from "react";
|
|
33
33
|
import { PortableText as PT } from "@portabletext/react";
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
/** A generic embedded block: `{schemaKey, schemaLabel, fields}`, the successor to Lexical's BlockNode. */
|
|
38
|
-
block: "bcmsBlock",
|
|
39
|
-
/** An inline mention. A PT inline object is natively void, which is what Lexical's `token` mode was faking. */
|
|
40
|
-
mention: "bcmsMention",
|
|
41
|
-
divider: "bcmsDivider",
|
|
42
|
-
code: "bcmsCode",
|
|
43
|
-
/**
|
|
44
|
-
* A video — an uploaded file or a provider URL. DEDICATED rather than another `schemaKey` on
|
|
45
|
-
* the generic block, because a video is the one media node whose rendition depends on its own
|
|
46
|
-
* payload: the same field is a `<video>` for an mp4 and a provider `<iframe>` for a YouTube
|
|
47
|
-
* link (@see resolveVideoSource). The generic block carries a reference this package cannot
|
|
48
|
-
* resolve; a video carries a URL it can.
|
|
49
|
-
*/
|
|
50
|
-
video: "video",
|
|
51
|
-
/** A downloadable document — a PDF, a spec sheet. `<a href download>`, nothing more. */
|
|
52
|
-
file: "file",
|
|
53
|
-
/**
|
|
54
|
-
* A table. Spelled `table`, unprefixed, because that is what every other Portable Text
|
|
55
|
-
* producer calls one — the Sanity-shaped `{_type:"table", rows}` this platform used to refuse
|
|
56
|
-
* as foreign content now lands on OUR type and renders, rather than being data nothing draws.
|
|
57
|
-
*/
|
|
58
|
-
table: "table",
|
|
59
|
-
/** The annotation carrying a hyperlink's `href`. Spelled `link` because that is the spec's convention. */
|
|
60
|
-
link: "link",
|
|
61
|
-
/**
|
|
62
|
-
* The annotation carrying a site's OWN inline markup — `<span class>`, `<small>`, `<time
|
|
63
|
-
* datetime>`. Unprefixed, like `table`, because it describes HTML rather than a BetterCMS
|
|
64
|
-
* concept. See `styled.ts` for what it may hold and why the list is that short.
|
|
65
|
-
*/
|
|
66
|
-
styled: "styled"
|
|
67
|
-
};
|
|
68
|
-
var BUILTIN_TABLE_KEY = "builtin:table";
|
|
69
|
-
var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
70
|
-
"block",
|
|
71
|
-
PT_TYPES.block,
|
|
72
|
-
PT_TYPES.table,
|
|
73
|
-
PT_TYPES.code,
|
|
74
|
-
PT_TYPES.divider,
|
|
75
|
-
PT_TYPES.mention,
|
|
76
|
-
PT_TYPES.video,
|
|
77
|
-
PT_TYPES.file
|
|
78
|
-
]);
|
|
35
|
+
// src/renderer.tsx
|
|
36
|
+
import { RESPONSIVE_CSS, hiddenEverywhere, hideOnAttr, splitButtonStyle, usesHideOn, visibleBlocks, windowState } from "@bettercms-ai/types";
|
|
79
37
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"sub",
|
|
86
|
-
"kbd",
|
|
87
|
-
"abbr",
|
|
88
|
-
"time",
|
|
89
|
-
"strong",
|
|
90
|
-
"b",
|
|
91
|
-
"em",
|
|
92
|
-
"i",
|
|
93
|
-
"u",
|
|
94
|
-
"s",
|
|
95
|
-
"mark",
|
|
96
|
-
"code",
|
|
97
|
-
"q",
|
|
98
|
-
"cite",
|
|
99
|
-
"dfn",
|
|
100
|
-
"var",
|
|
101
|
-
"samp",
|
|
102
|
-
"ins",
|
|
103
|
-
"del"
|
|
104
|
-
];
|
|
105
|
-
var TAG_SET = new Set(STYLED_TAGS);
|
|
106
|
-
var isStyledTag = (tag) => typeof tag === "string" && TAG_SET.has(tag);
|
|
107
|
-
var ATTR_NAMES = /* @__PURE__ */ new Set(["title", "datetime", "style", "aria-label"]);
|
|
108
|
-
var DATA_ATTR = /^data-[a-z][a-z0-9-]*$/;
|
|
109
|
-
var isStyledAttr = (name) => ATTR_NAMES.has(name) || DATA_ATTR.test(name) && !name.startsWith("data-bcms-");
|
|
110
|
-
var STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
111
|
-
"color",
|
|
112
|
-
"background-color",
|
|
113
|
-
"opacity",
|
|
114
|
-
"font",
|
|
115
|
-
"font-family",
|
|
116
|
-
"font-size",
|
|
117
|
-
"font-style",
|
|
118
|
-
"font-weight",
|
|
119
|
-
"font-variant",
|
|
120
|
-
"font-feature-settings",
|
|
121
|
-
"font-variation-settings",
|
|
122
|
-
"letter-spacing",
|
|
123
|
-
"line-height",
|
|
124
|
-
"word-spacing",
|
|
125
|
-
"white-space",
|
|
126
|
-
"word-break",
|
|
127
|
-
"text-align",
|
|
128
|
-
"text-decoration",
|
|
129
|
-
"text-decoration-color",
|
|
130
|
-
"text-decoration-line",
|
|
131
|
-
"text-decoration-style",
|
|
132
|
-
"text-transform",
|
|
133
|
-
"text-underline-offset",
|
|
134
|
-
"vertical-align"
|
|
135
|
-
]);
|
|
136
|
-
var STYLE_VALUE_BAN = /url\(|expression\(|@import|\\|\/\*|[<>]/i;
|
|
137
|
-
function safeStyle(css) {
|
|
138
|
-
const kept = [];
|
|
139
|
-
for (const declaration of css.split(";")) {
|
|
140
|
-
const at = declaration.indexOf(":");
|
|
141
|
-
if (at < 0) continue;
|
|
142
|
-
const name = declaration.slice(0, at).trim().toLowerCase();
|
|
143
|
-
const value = declaration.slice(at + 1).trim();
|
|
144
|
-
if (!name || !value) continue;
|
|
145
|
-
if (!name.startsWith("--") && !STYLE_PROPS.has(name)) continue;
|
|
146
|
-
if (STYLE_VALUE_BAN.test(value)) continue;
|
|
147
|
-
kept.push(`${name}:${value}`);
|
|
148
|
-
}
|
|
149
|
-
return kept.length ? kept.join(";") : void 0;
|
|
150
|
-
}
|
|
151
|
-
function styledAttrEntries(attrs) {
|
|
152
|
-
if (!attrs || typeof attrs !== "object" || Array.isArray(attrs)) return [];
|
|
153
|
-
const out = [];
|
|
154
|
-
for (const [name, raw] of Object.entries(attrs)) {
|
|
155
|
-
if (!isStyledAttr(name) || typeof raw !== "string") continue;
|
|
156
|
-
const value = name === "style" ? safeStyle(raw) : raw;
|
|
157
|
-
if (value === void 0) continue;
|
|
158
|
-
out.push([name, value]);
|
|
159
|
-
}
|
|
160
|
-
return out;
|
|
38
|
+
// src/lib/utils.ts
|
|
39
|
+
import { clsx } from "clsx";
|
|
40
|
+
import { twMerge } from "tailwind-merge";
|
|
41
|
+
function cn(...inputs) {
|
|
42
|
+
return twMerge(clsx(inputs));
|
|
161
43
|
}
|
|
162
44
|
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
45
|
+
// src/blocks/Image.tsx
|
|
46
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
47
|
+
function Image({
|
|
48
|
+
src,
|
|
49
|
+
alt,
|
|
50
|
+
width,
|
|
51
|
+
height,
|
|
52
|
+
caption,
|
|
53
|
+
className = "",
|
|
54
|
+
sizes,
|
|
55
|
+
"data-bcms-field": field,
|
|
56
|
+
"data-bcms-kind": kind
|
|
57
|
+
}) {
|
|
58
|
+
const sized = typeof width === "number" && width > 0;
|
|
59
|
+
return /* @__PURE__ */ jsxs("figure", { className: cn(sized ? "max-w-full" : "w-full", className), children: [
|
|
60
|
+
/* @__PURE__ */ jsx2(
|
|
61
|
+
"img",
|
|
62
|
+
{
|
|
63
|
+
src,
|
|
64
|
+
alt,
|
|
65
|
+
width,
|
|
66
|
+
height,
|
|
67
|
+
sizes,
|
|
68
|
+
"data-bcms-field": field,
|
|
69
|
+
"data-bcms-kind": kind,
|
|
70
|
+
style: sized ? { width: `${width}px`, maxWidth: "100%", height: "auto" } : void 0,
|
|
71
|
+
className: cn(!sized && "w-full", "h-auto rounded-md object-cover"),
|
|
72
|
+
loading: "lazy"
|
|
73
|
+
}
|
|
74
|
+
),
|
|
75
|
+
caption && /* @__PURE__ */ jsx2("figcaption", { className: "mt-2 text-sm text-muted-foreground text-center italic", children: caption })
|
|
76
|
+
] });
|
|
174
77
|
}
|
|
175
78
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
79
|
+
// src/blocks/Button.tsx
|
|
80
|
+
import { LINK_RELS, buttonIconOf, buttonIconSvg, buttonVariantOf, isScriptHref } from "@bettercms-ai/types";
|
|
81
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
82
|
+
function Button({
|
|
83
|
+
text,
|
|
84
|
+
href,
|
|
85
|
+
variant = "primary",
|
|
86
|
+
size = "md",
|
|
87
|
+
fullWidth = false,
|
|
88
|
+
arrow = false,
|
|
89
|
+
icon: iconProp,
|
|
90
|
+
newTab = false,
|
|
91
|
+
rel,
|
|
92
|
+
action,
|
|
93
|
+
align = "left",
|
|
94
|
+
className = "",
|
|
95
|
+
style,
|
|
96
|
+
"data-bcms-field": field,
|
|
97
|
+
"data-bcms-kind": kind
|
|
98
|
+
}) {
|
|
99
|
+
const baseStyles = "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50";
|
|
100
|
+
const variantStyles = {
|
|
101
|
+
primary: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
102
|
+
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
103
|
+
tertiary: "bg-transparent text-primary hover:bg-primary/10",
|
|
104
|
+
brand: "bg-accent text-accent-foreground shadow hover:bg-accent/90",
|
|
105
|
+
dark: "bg-foreground text-background shadow hover:bg-foreground/90",
|
|
106
|
+
light: "border bg-background text-foreground shadow-sm hover:bg-muted",
|
|
107
|
+
outline: "border border-current bg-transparent text-primary hover:bg-primary/10",
|
|
108
|
+
link: "h-auto bg-transparent p-0 text-primary underline underline-offset-4"
|
|
109
|
+
};
|
|
110
|
+
const icon = buttonIconOf(iconProp);
|
|
111
|
+
const alignClass = {
|
|
112
|
+
left: "justify-start",
|
|
113
|
+
center: "justify-center",
|
|
114
|
+
right: "justify-end"
|
|
115
|
+
}[align];
|
|
116
|
+
const sizeStyles = {
|
|
117
|
+
sm: "px-3 py-1.5 text-xs",
|
|
118
|
+
md: "px-4 py-2",
|
|
119
|
+
lg: "px-6 py-3 text-base"
|
|
192
120
|
};
|
|
121
|
+
const sizeClass = size === "sm" || size === "lg" ? sizeStyles[size] : sizeStyles.md;
|
|
122
|
+
const fullClass = fullWidth === true ? "flex w-full justify-center" : "";
|
|
123
|
+
const relTokens = [...newTab === true ? ["noopener", "noreferrer"] : [], ...LINK_RELS.includes(rel) ? [rel] : []];
|
|
124
|
+
if (action === "code") {
|
|
125
|
+
return /* @__PURE__ */ jsxs2("button", { type: "button", "data-bcms-field": field, "data-bcms-kind": kind, style, className: cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className), children: [
|
|
126
|
+
icon && /* @__PURE__ */ jsx3("span", { className: "mr-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg(icon, "bcms-btn-icon") } }),
|
|
127
|
+
text,
|
|
128
|
+
arrow === true && /* @__PURE__ */ jsx3("span", { className: "ml-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg("arrow-right", "bcms-btn-arrow") } })
|
|
129
|
+
] });
|
|
130
|
+
}
|
|
131
|
+
return /* @__PURE__ */ jsxs2(
|
|
132
|
+
"a",
|
|
133
|
+
{
|
|
134
|
+
href: typeof href === "string" && isScriptHref(href) ? "#" : href,
|
|
135
|
+
...newTab === true ? { target: "_blank" } : {},
|
|
136
|
+
...relTokens.length ? { rel: relTokens.join(" ") } : {},
|
|
137
|
+
"data-bcms-field": field,
|
|
138
|
+
"data-bcms-kind": kind,
|
|
139
|
+
style,
|
|
140
|
+
className: cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className),
|
|
141
|
+
children: [
|
|
142
|
+
icon && /* @__PURE__ */ jsx3("span", { className: "mr-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg(icon, "bcms-btn-icon") } }),
|
|
143
|
+
text,
|
|
144
|
+
arrow === true && /* @__PURE__ */ jsx3("span", { className: "ml-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg("arrow-right", "bcms-btn-arrow") } })
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
);
|
|
193
148
|
}
|
|
194
149
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
150
|
+
// src/blocks/Spacer.tsx
|
|
151
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
152
|
+
function Spacer({ height = 32, className = "" }) {
|
|
153
|
+
return /* @__PURE__ */ jsx4(
|
|
154
|
+
"div",
|
|
155
|
+
{
|
|
156
|
+
className: `w-full border border-dashed border-muted-foreground/20 rounded flex items-center justify-center ${className}`,
|
|
157
|
+
style: { height: `${height}px` },
|
|
158
|
+
"aria-hidden": "true",
|
|
159
|
+
children: /* @__PURE__ */ jsxs3("span", { className: "text-xs text-muted-foreground/40 select-none", children: [
|
|
160
|
+
height,
|
|
161
|
+
"px"
|
|
162
|
+
] })
|
|
163
|
+
}
|
|
164
|
+
);
|
|
209
165
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
166
|
+
|
|
167
|
+
// src/blocks/Columns.tsx
|
|
168
|
+
import "react";
|
|
169
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
170
|
+
function Columns({
|
|
171
|
+
columns,
|
|
172
|
+
gap = 16,
|
|
173
|
+
className = "",
|
|
174
|
+
renderBlock
|
|
175
|
+
}) {
|
|
176
|
+
return /* @__PURE__ */ jsx5(
|
|
177
|
+
"div",
|
|
178
|
+
{
|
|
179
|
+
className: cn("grid w-full", className),
|
|
180
|
+
style: {
|
|
181
|
+
gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,
|
|
182
|
+
gap: `${gap}px`
|
|
183
|
+
},
|
|
184
|
+
children: columns.map((columnBlocks, colIndex) => /* @__PURE__ */ jsx5("div", { className: "flex flex-col gap-4 min-w-0", children: columnBlocks.map((block) => /* @__PURE__ */ jsx5("span", { children: renderBlock ? renderBlock(block) : null }, block.id)) }, colIndex))
|
|
217
185
|
}
|
|
218
|
-
|
|
219
|
-
return out;
|
|
186
|
+
);
|
|
220
187
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const key = isRecord(raw) && isValidKey(raw._key) ? raw._key : mint("r", row, "c", col);
|
|
235
|
-
const inner = deterministicKeys(`${key}-`);
|
|
236
|
-
if (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean") {
|
|
237
|
-
return { _key: key, content: textContent(String(raw), inner) };
|
|
188
|
+
|
|
189
|
+
// src/blocks/Video.tsx
|
|
190
|
+
import { useState } from "react";
|
|
191
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
192
|
+
function getEmbedUrl(url) {
|
|
193
|
+
const ytMatch = url.match(
|
|
194
|
+
/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
|
|
195
|
+
);
|
|
196
|
+
if (ytMatch) {
|
|
197
|
+
return {
|
|
198
|
+
embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,
|
|
199
|
+
provider: "youtube"
|
|
200
|
+
};
|
|
238
201
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const rowSpan = spanOf(raw.rowSpan ?? raw.rowspan);
|
|
242
|
-
return {
|
|
243
|
-
_key: key,
|
|
244
|
-
// `text` is the spelling a string matrix uses once its cells have been given objects; it is
|
|
245
|
-
// read here so a half-migrated value cannot lose its words.
|
|
246
|
-
content: Array.isArray(raw.content) ? read(raw.content, inner) : textContent(typeof raw.text === "string" ? raw.text : "", inner),
|
|
247
|
-
...raw.header === true ? { header: true } : {},
|
|
248
|
-
...span === void 0 ? {} : { span },
|
|
249
|
-
...rowSpan === void 0 ? {} : { rowSpan }
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
function normalizeRow(raw, at, mint, read) {
|
|
253
|
-
const cells = Array.isArray(raw) ? raw : isRecord(raw) && Array.isArray(raw.cells) ? raw.cells : [];
|
|
254
|
-
return {
|
|
255
|
-
_key: isRecord(raw) && isValidKey(raw._key) ? raw._key : mint("r", at),
|
|
256
|
-
cells: cells.map((c, col) => normalizeCell(c, [at, col], mint, read)),
|
|
257
|
-
...isRecord(raw) && raw.head === true ? { head: true } : {}
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
function builtInTableToTable(node, key = randomKey) {
|
|
261
|
-
if (!isBuiltInTableBlock(node)) return null;
|
|
262
|
-
const fields = node.fields;
|
|
263
|
-
const raw = isRecord(fields) && Array.isArray(fields.rows) ? fields.rows : [];
|
|
264
|
-
const hasHeader = isRecord(fields) ? fields.hasHeader !== false : true;
|
|
265
|
-
const own = node._key;
|
|
266
|
-
const tableKey = isValidKey(own) ? own : key();
|
|
267
|
-
const mint = keyMinter(tableKey, reservedKeys(raw));
|
|
268
|
-
const rows = raw.map((row, i) => {
|
|
269
|
-
const normalized = normalizeRow(row, i, mint, asIs);
|
|
270
|
-
if (!(hasHeader && i === 0)) return normalized;
|
|
202
|
+
const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
|
|
203
|
+
if (vimeoMatch) {
|
|
271
204
|
return {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
cells: normalized.cells.map((c) => ({ ...c, header: true }))
|
|
205
|
+
embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}`,
|
|
206
|
+
provider: "vimeo"
|
|
275
207
|
};
|
|
276
|
-
});
|
|
277
|
-
const width = rows.reduce((max, r) => Math.max(max, r.cells.length), 0);
|
|
278
|
-
for (const [i, row] of rows.entries()) {
|
|
279
|
-
while (row.cells.length < width) {
|
|
280
|
-
row.cells.push({
|
|
281
|
-
_key: mint("r", i, "c", row.cells.length),
|
|
282
|
-
content: [],
|
|
283
|
-
...row.head === true ? { header: true } : {}
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
208
|
}
|
|
287
|
-
return {
|
|
209
|
+
return { embedUrl: url, provider: "unknown" };
|
|
288
210
|
}
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
211
|
+
function Video({
|
|
212
|
+
url,
|
|
213
|
+
poster,
|
|
214
|
+
autoplay = false,
|
|
215
|
+
loop = false,
|
|
216
|
+
className = ""
|
|
217
|
+
}) {
|
|
218
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
219
|
+
const { embedUrl, provider } = getEmbedUrl(url);
|
|
220
|
+
if (provider === "unknown") {
|
|
221
|
+
return /* @__PURE__ */ jsx6(
|
|
222
|
+
"video",
|
|
223
|
+
{
|
|
224
|
+
src: url,
|
|
225
|
+
poster,
|
|
226
|
+
autoPlay: autoplay,
|
|
227
|
+
loop,
|
|
228
|
+
controls: true,
|
|
229
|
+
className,
|
|
230
|
+
playsInline: true
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return /* @__PURE__ */ jsxs4("div", { className: cn("w-full overflow-hidden rounded-md bg-black aspect-video", className), children: [
|
|
235
|
+
!isLoaded && poster && // eslint-disable-next-line @next/next/no-img-element
|
|
236
|
+
/* @__PURE__ */ jsx6(
|
|
237
|
+
"img",
|
|
238
|
+
{
|
|
239
|
+
src: poster,
|
|
240
|
+
alt: "Video thumbnail",
|
|
241
|
+
className: "w-full h-full object-cover absolute inset-0"
|
|
242
|
+
}
|
|
243
|
+
),
|
|
244
|
+
/* @__PURE__ */ jsx6(
|
|
245
|
+
"iframe",
|
|
246
|
+
{
|
|
247
|
+
src: `${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`,
|
|
248
|
+
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
|
249
|
+
allowFullScreen: true,
|
|
250
|
+
loading: "lazy",
|
|
251
|
+
onLoad: () => setIsLoaded(true),
|
|
252
|
+
className: "w-full h-full"
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
] });
|
|
304
256
|
}
|
|
305
257
|
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
})
|
|
258
|
+
// src/blocks/Section.tsx
|
|
259
|
+
import "react";
|
|
260
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
261
|
+
function Section({
|
|
262
|
+
children,
|
|
263
|
+
background,
|
|
264
|
+
paddingY,
|
|
265
|
+
maxWidth,
|
|
266
|
+
className = "",
|
|
267
|
+
renderBlock
|
|
268
|
+
}) {
|
|
269
|
+
const inner = (children ?? []).map((block) => /* @__PURE__ */ jsx7("span", { children: renderBlock ? renderBlock(block) : null }, block.id));
|
|
270
|
+
return /* @__PURE__ */ jsx7(
|
|
271
|
+
"section",
|
|
272
|
+
{
|
|
273
|
+
className: cn("w-full", className),
|
|
274
|
+
style: { background, paddingTop: paddingY, paddingBottom: paddingY },
|
|
275
|
+
children: maxWidth ? /* @__PURE__ */ jsx7("div", { style: { maxWidth, marginInline: "auto" }, children: inner }) : inner
|
|
324
276
|
}
|
|
325
|
-
|
|
326
|
-
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/blocks/Navbar.tsx
|
|
281
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
282
|
+
function Navbar({ logo, links, cta, className = "" }) {
|
|
283
|
+
return /* @__PURE__ */ jsxs5("nav", { className: cn("flex items-center gap-6 py-4", className), children: [
|
|
284
|
+
logo?.src ? /* @__PURE__ */ jsx8("a", { href: logo.href ?? "/", className: "shrink-0", children: /* @__PURE__ */ jsx8("img", { src: logo.src, alt: logo.alt, className: "h-8 w-auto" }) }) : null,
|
|
285
|
+
/* @__PURE__ */ jsx8("div", { className: "flex flex-1 gap-5", children: (links ?? []).map((l, i) => /* @__PURE__ */ jsx8("a", { href: l.href, className: "text-inherit no-underline", children: l.label }, i)) }),
|
|
286
|
+
cta ? /* @__PURE__ */ jsx8(
|
|
287
|
+
"a",
|
|
288
|
+
{
|
|
289
|
+
href: cta.href,
|
|
290
|
+
className: "rounded-lg bg-black px-4 py-2 font-semibold text-white no-underline",
|
|
291
|
+
children: cta.text
|
|
292
|
+
}
|
|
293
|
+
) : null
|
|
294
|
+
] });
|
|
327
295
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return
|
|
296
|
+
|
|
297
|
+
// src/blocks/Footer.tsx
|
|
298
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
299
|
+
function Footer({ columns, copyright, className = "" }) {
|
|
300
|
+
return /* @__PURE__ */ jsxs6("footer", { className: cn("mt-12 border-t pt-8", className), children: [
|
|
301
|
+
/* @__PURE__ */ jsx9("div", { className: "flex flex-wrap gap-12", children: (columns ?? []).map((col, i) => /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1.5", children: [
|
|
302
|
+
col.heading ? /* @__PURE__ */ jsx9("h4", { className: "mb-2 font-semibold", children: col.heading }) : null,
|
|
303
|
+
(col.links ?? []).map((l, j) => /* @__PURE__ */ jsx9("a", { href: l.href, className: "text-neutral-500 no-underline", children: l.label }, j))
|
|
304
|
+
] }, i)) }),
|
|
305
|
+
copyright ? /* @__PURE__ */ jsx9("p", { className: "mt-6 text-sm text-neutral-500", children: copyright }) : null
|
|
306
|
+
] });
|
|
333
307
|
}
|
|
334
308
|
|
|
335
|
-
// src/blocks/
|
|
336
|
-
import {
|
|
337
|
-
|
|
338
|
-
function
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
return out;
|
|
309
|
+
// src/blocks/Slider.tsx
|
|
310
|
+
import { useState as useState2 } from "react";
|
|
311
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
312
|
+
function Slider({ slides, className = "", renderBlock }) {
|
|
313
|
+
const [i, setI] = useState2(0);
|
|
314
|
+
const list = slides ?? [];
|
|
315
|
+
const go = (n) => setI((n + list.length) % (list.length || 1));
|
|
316
|
+
const btn = "absolute top-1/2 -translate-y-1/2 flex h-9 w-9 items-center justify-center rounded-full border-0 bg-black/50 text-white cursor-pointer";
|
|
317
|
+
return /* @__PURE__ */ jsxs7("div", { className: cn("relative w-full", className), children: [
|
|
318
|
+
list.map((s, idx) => /* @__PURE__ */ jsx10("div", { hidden: idx !== i, className: "flex flex-col gap-4", children: (s.children ?? []).map((b) => /* @__PURE__ */ jsx10("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, s.id)),
|
|
319
|
+
/* @__PURE__ */ jsx10("button", { type: "button", "aria-label": "Previous", onClick: () => go(i - 1), className: cn(btn, "left-2"), children: "\u2039" }),
|
|
320
|
+
/* @__PURE__ */ jsx10("button", { type: "button", "aria-label": "Next", onClick: () => go(i + 1), className: cn(btn, "right-2"), children: "\u203A" })
|
|
321
|
+
] });
|
|
349
322
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
323
|
+
|
|
324
|
+
// src/blocks/Tabs.tsx
|
|
325
|
+
import { useState as useState3 } from "react";
|
|
326
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
327
|
+
function Tabs({ tabs, className = "", renderBlock }) {
|
|
328
|
+
const [active, setActive] = useState3(0);
|
|
329
|
+
const list = tabs ?? [];
|
|
330
|
+
return /* @__PURE__ */ jsxs8("div", { className: cn("w-full", className), children: [
|
|
331
|
+
/* @__PURE__ */ jsx11("div", { role: "tablist", className: "mb-4 flex gap-1 border-b", children: list.map((t, idx) => /* @__PURE__ */ jsx11(
|
|
332
|
+
"button",
|
|
333
|
+
{
|
|
334
|
+
role: "tab",
|
|
335
|
+
type: "button",
|
|
336
|
+
"aria-selected": idx === active,
|
|
337
|
+
onClick: () => setActive(idx),
|
|
338
|
+
className: cn(
|
|
339
|
+
"cursor-pointer border-b-2 border-transparent bg-transparent px-4 py-2",
|
|
340
|
+
idx === active && "border-black font-semibold"
|
|
341
|
+
),
|
|
342
|
+
children: t.label
|
|
343
|
+
},
|
|
344
|
+
t.id
|
|
345
|
+
)) }),
|
|
346
|
+
list.map((t, idx) => /* @__PURE__ */ jsx11("div", { role: "tabpanel", hidden: idx !== active, className: "flex flex-col gap-4", children: (t.children ?? []).map((b) => /* @__PURE__ */ jsx11("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, t.id))
|
|
347
|
+
] });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/blocks/Placeholders.tsx
|
|
351
|
+
import { Fragment, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
352
|
+
var box = "rounded border border-dashed border-neutral-300 bg-neutral-50 p-4 text-sm text-neutral-500";
|
|
353
|
+
function ComponentInstance({
|
|
354
|
+
componentId,
|
|
355
|
+
className = ""
|
|
354
356
|
}) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn(box, className), children: [
|
|
358
|
+
"Component instance: ",
|
|
359
|
+
/* @__PURE__ */ jsx12("code", { children: componentId })
|
|
360
|
+
] });
|
|
357
361
|
}
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
[components]
|
|
363
|
-
);
|
|
364
|
-
const rows = Array.isArray(value?.rows) ? value.rows : [];
|
|
365
|
-
if (rows.length === 0) return null;
|
|
366
|
-
const cells = (row) => (Array.isArray(row?.cells) ? row.cells : []).map((cell) => {
|
|
367
|
-
const Tag = cell?.header === true ? "th" : "td";
|
|
368
|
-
const span = typeof cell?.span === "number" && cell.span > 1 ? cell.span : void 0;
|
|
369
|
-
const rowSpan = typeof cell?.rowSpan === "number" && cell.rowSpan > 1 ? cell.rowSpan : void 0;
|
|
370
|
-
const content = Array.isArray(cell?.content) ? cell.content : [];
|
|
371
|
-
const lone = content.length === 1 && content[0]._type === "block" && content[0].style === "normal" && !content[0].listItem;
|
|
372
|
-
return /* @__PURE__ */ jsx2(Tag, { ...span ? { colSpan: span } : {}, ...rowSpan ? { rowSpan } : {}, children: /* @__PURE__ */ jsx2(PT, { value: content, components: lone ? inline : components }) }, cell?._key);
|
|
373
|
-
});
|
|
374
|
-
const rendered = rows.map((row) => /* @__PURE__ */ jsx2("tr", { children: cells(row) }, row?._key));
|
|
375
|
-
const headCount = rows.findIndex((r) => r?.head !== true);
|
|
376
|
-
const head = headCount === -1 ? rows.length : headCount;
|
|
377
|
-
return /* @__PURE__ */ jsxs("table", { ...value.legacy === true ? { className: "bcms-table" } : {}, children: [
|
|
378
|
-
head > 0 && /* @__PURE__ */ jsx2("thead", { children: rendered.slice(0, head) }),
|
|
379
|
-
head < rows.length && /* @__PURE__ */ jsx2("tbody", { children: rendered.slice(head) })
|
|
362
|
+
function FormPlaceholder({ formId, className = "" }) {
|
|
363
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn(box, className), children: [
|
|
364
|
+
"Form: ",
|
|
365
|
+
/* @__PURE__ */ jsx12("code", { children: formId })
|
|
380
366
|
] });
|
|
381
367
|
}
|
|
382
|
-
function
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
368
|
+
function CollectionPlaceholder({
|
|
369
|
+
cardComponentId,
|
|
370
|
+
className = ""
|
|
371
|
+
}) {
|
|
372
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn(box, className), children: [
|
|
373
|
+
"Collection: entries of this page",
|
|
374
|
+
cardComponentId ? /* @__PURE__ */ jsxs9(Fragment, { children: [
|
|
375
|
+
" as ",
|
|
376
|
+
/* @__PURE__ */ jsx12("code", { children: cardComponentId })
|
|
377
|
+
] }) : /* @__PURE__ */ jsx12(Fragment, { children: " (default card)" })
|
|
378
|
+
] });
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/registry.ts
|
|
382
|
+
var BLOCK_REGISTRY = {
|
|
383
|
+
// The entries of the page this block sits on. No preview here by design: the list is
|
|
384
|
+
// fetched per request by the site renderer, so the builder has nothing to show.
|
|
385
|
+
collection: {
|
|
386
|
+
component: CollectionPlaceholder,
|
|
387
|
+
label: "Collection",
|
|
388
|
+
icon: "list"
|
|
389
|
+
},
|
|
390
|
+
heading: {
|
|
391
|
+
component: Heading,
|
|
392
|
+
label: "Heading",
|
|
393
|
+
icon: "type"
|
|
394
|
+
},
|
|
395
|
+
text: {
|
|
396
|
+
component: Text,
|
|
397
|
+
label: "Text",
|
|
398
|
+
icon: "align-left"
|
|
399
|
+
},
|
|
400
|
+
// Same payload, same renderer — deliberately. `richtext` differs from `text` only in how
|
|
401
|
+
// an author EDITS it (a formatting surface vs a plain field); at delivery both are an HTML
|
|
402
|
+
// string, so duplicating the component would be two things to keep in sync for no gain.
|
|
403
|
+
richtext: {
|
|
404
|
+
component: Text,
|
|
405
|
+
label: "Rich text",
|
|
406
|
+
icon: "pilcrow"
|
|
407
|
+
},
|
|
408
|
+
image: {
|
|
409
|
+
component: Image,
|
|
410
|
+
label: "Image",
|
|
411
|
+
icon: "image"
|
|
412
|
+
},
|
|
413
|
+
button: {
|
|
414
|
+
component: Button,
|
|
415
|
+
label: "Button",
|
|
416
|
+
icon: "square"
|
|
417
|
+
},
|
|
418
|
+
spacer: {
|
|
419
|
+
component: Spacer,
|
|
420
|
+
label: "Spacer",
|
|
421
|
+
icon: "minus"
|
|
422
|
+
},
|
|
423
|
+
columns: {
|
|
424
|
+
component: Columns,
|
|
425
|
+
label: "Columns",
|
|
426
|
+
icon: "columns"
|
|
427
|
+
},
|
|
428
|
+
video: {
|
|
429
|
+
component: Video,
|
|
430
|
+
label: "Video",
|
|
431
|
+
icon: "play"
|
|
432
|
+
},
|
|
433
|
+
section: {
|
|
434
|
+
component: Section,
|
|
435
|
+
label: "Section",
|
|
436
|
+
icon: "layout"
|
|
437
|
+
},
|
|
438
|
+
navbar: {
|
|
439
|
+
component: Navbar,
|
|
440
|
+
label: "Navbar",
|
|
441
|
+
icon: "menu"
|
|
442
|
+
},
|
|
443
|
+
footer: {
|
|
444
|
+
component: Footer,
|
|
445
|
+
label: "Footer",
|
|
446
|
+
icon: "panel-bottom"
|
|
447
|
+
},
|
|
448
|
+
slider: {
|
|
449
|
+
component: Slider,
|
|
450
|
+
label: "Slider",
|
|
451
|
+
icon: "gallery-horizontal"
|
|
452
|
+
},
|
|
453
|
+
tabs: {
|
|
454
|
+
component: Tabs,
|
|
455
|
+
label: "Tabs",
|
|
456
|
+
icon: "folder"
|
|
457
|
+
},
|
|
458
|
+
component: {
|
|
459
|
+
component: ComponentInstance,
|
|
460
|
+
label: "Component",
|
|
461
|
+
icon: "component"
|
|
462
|
+
},
|
|
463
|
+
form: {
|
|
464
|
+
component: FormPlaceholder,
|
|
465
|
+
label: "Form",
|
|
466
|
+
icon: "clipboard-list"
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
var BLOCK_TYPES = Object.keys(BLOCK_REGISTRY);
|
|
470
|
+
|
|
471
|
+
// src/blockStyle.ts
|
|
472
|
+
import { blockStyleToCss } from "@bettercms-ai/types";
|
|
473
|
+
function blockStyleToReactStyle(style) {
|
|
474
|
+
return blockStyleToCss(style);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/renderer.tsx
|
|
478
|
+
import "react";
|
|
479
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
480
|
+
var CONTAINER_TYPES = /* @__PURE__ */ new Set(["columns", "section", "slider", "tabs"]);
|
|
481
|
+
function styleOf(block) {
|
|
482
|
+
if (block.type !== "button") return { wrapper: blockStyleToReactStyle(block.style), paint: {} };
|
|
483
|
+
const { placement, paint } = splitButtonStyle(block.style);
|
|
484
|
+
return { wrapper: blockStyleToReactStyle(placement), paint: paint ? { style: paint } : {} };
|
|
470
485
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
else runs.push([node]);
|
|
478
|
-
}
|
|
479
|
-
return runs;
|
|
486
|
+
function withHide(block, wrapperStyle, el, stampAt) {
|
|
487
|
+
const hide = hideOnAttr(block.style);
|
|
488
|
+
const state = stampAt ? windowState(block, stampAt) : "live";
|
|
489
|
+
const hidden = !!stampAt && hiddenEverywhere(block);
|
|
490
|
+
if (!wrapperStyle && !hide && state === "live" && !hidden) return el;
|
|
491
|
+
return /* @__PURE__ */ jsx13("div", { style: wrapperStyle, ...hide ? { "data-bcms-hide": hide } : {}, ...state === "live" ? {} : { "data-bcms-window": state }, ...hidden ? { "data-bcms-hidden": "all" } : {}, children: el });
|
|
480
492
|
}
|
|
481
|
-
function
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
(run) => isListItem(run[0]) ? /* @__PURE__ */ jsx2(PT, { value: run, components: listComponents }, run[0]._key) : /* @__PURE__ */ jsx2(Bound, { base: bindingBase, nodeKey: run[0]._key, children: /* @__PURE__ */ jsx2(PT, { value: run, components }) }, run[0]._key)
|
|
496
|
-
) });
|
|
493
|
+
function bindingProps(block, annotate) {
|
|
494
|
+
if (!annotate || !block.id) return {};
|
|
495
|
+
switch (block.type) {
|
|
496
|
+
case "heading":
|
|
497
|
+
case "button":
|
|
498
|
+
return { "data-bcms-field": `${block.id}__text`, "data-bcms-kind": "text" };
|
|
499
|
+
case "text":
|
|
500
|
+
case "richtext":
|
|
501
|
+
return { "data-bcms-field": `${block.id}__html`, "data-bcms-kind": "richtext" };
|
|
502
|
+
case "image":
|
|
503
|
+
return { "data-bcms-field": `${block.id}__src`, "data-bcms-kind": "image" };
|
|
504
|
+
default:
|
|
505
|
+
return {};
|
|
506
|
+
}
|
|
497
507
|
}
|
|
498
|
-
function
|
|
499
|
-
|
|
500
|
-
if (
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
508
|
+
function resolveBlock(block, className, annotate = false, stampAt) {
|
|
509
|
+
const entry = BLOCK_REGISTRY[block.type];
|
|
510
|
+
if (!entry) {
|
|
511
|
+
return /* @__PURE__ */ jsxs10(
|
|
512
|
+
"div",
|
|
513
|
+
{
|
|
514
|
+
className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className ?? ""}`,
|
|
515
|
+
children: [
|
|
516
|
+
"Unknown block type: ",
|
|
517
|
+
/* @__PURE__ */ jsx13("code", { children: block.type })
|
|
518
|
+
]
|
|
519
|
+
}
|
|
520
|
+
);
|
|
504
521
|
}
|
|
505
|
-
|
|
522
|
+
const Component = entry.component;
|
|
523
|
+
const { wrapper: blockStyle, paint } = styleOf(block);
|
|
524
|
+
const el = /* @__PURE__ */ jsx13(Component, { ...block.props, ...paint, className, ...bindingProps(block, annotate) });
|
|
525
|
+
return withHide(block, blockStyle, el, stampAt);
|
|
506
526
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
510
|
-
function Text({
|
|
511
|
-
html,
|
|
512
|
-
content,
|
|
513
|
-
align = "left",
|
|
527
|
+
function BlockRenderer({
|
|
528
|
+
block,
|
|
514
529
|
className = "",
|
|
515
|
-
|
|
516
|
-
|
|
530
|
+
renderBlock,
|
|
531
|
+
annotate = false,
|
|
532
|
+
stampAt
|
|
517
533
|
}) {
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
534
|
+
const entry = BLOCK_REGISTRY[block.type];
|
|
535
|
+
if (!entry) {
|
|
536
|
+
return /* @__PURE__ */ jsxs10(
|
|
537
|
+
"div",
|
|
538
|
+
{
|
|
539
|
+
className: `w-full p-4 border border-dashed border-red-400 bg-red-50 text-red-600 text-sm rounded ${className}`,
|
|
540
|
+
children: [
|
|
541
|
+
"Unknown block type: ",
|
|
542
|
+
/* @__PURE__ */ jsx13("code", { children: block.type })
|
|
543
|
+
]
|
|
544
|
+
}
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
const Component = entry.component;
|
|
548
|
+
const containerProps = CONTAINER_TYPES.has(block.type) ? { renderBlock: renderBlock ?? ((child) => resolveBlock(child, void 0, annotate, stampAt)) } : {};
|
|
549
|
+
const { wrapper: blockStyle, paint } = styleOf(block);
|
|
550
|
+
const el = /* @__PURE__ */ jsx13(
|
|
551
|
+
Component,
|
|
527
552
|
{
|
|
528
|
-
|
|
529
|
-
...
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
)
|
|
553
|
+
...block.props,
|
|
554
|
+
...paint,
|
|
555
|
+
className,
|
|
556
|
+
...containerProps,
|
|
557
|
+
...bindingProps(block, annotate)
|
|
534
558
|
}
|
|
535
559
|
);
|
|
560
|
+
return withHide(block, blockStyle, el, stampAt);
|
|
561
|
+
}
|
|
562
|
+
function BlocksRenderer({
|
|
563
|
+
blocks: all,
|
|
564
|
+
className = "",
|
|
565
|
+
renderBlock,
|
|
566
|
+
annotate = false,
|
|
567
|
+
now
|
|
568
|
+
}) {
|
|
569
|
+
const at = now ?? /* @__PURE__ */ new Date();
|
|
570
|
+
const blocks = annotate ? all : visibleBlocks(all, at);
|
|
571
|
+
if (blocks.length === 0) return null;
|
|
572
|
+
return /* @__PURE__ */ jsxs10("div", { className, children: [
|
|
573
|
+
usesHideOn(blocks) && /* @__PURE__ */ jsx13("style", { dangerouslySetInnerHTML: { __html: RESPONSIVE_CSS } }),
|
|
574
|
+
blocks.map((block) => /* @__PURE__ */ jsx13(
|
|
575
|
+
BlockRenderer,
|
|
576
|
+
{
|
|
577
|
+
block,
|
|
578
|
+
renderBlock,
|
|
579
|
+
annotate,
|
|
580
|
+
stampAt: annotate ? at : void 0
|
|
581
|
+
},
|
|
582
|
+
block.id
|
|
583
|
+
))
|
|
584
|
+
] });
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// ../richtext/src/types.ts
|
|
588
|
+
function pullQuoteHref(link) {
|
|
589
|
+
if (typeof link === "string") return link.trim();
|
|
590
|
+
if (link && typeof link === "object" && typeof link.href === "string") {
|
|
591
|
+
return link.href.trim();
|
|
592
|
+
}
|
|
593
|
+
return "";
|
|
594
|
+
}
|
|
595
|
+
var PT_TYPES = {
|
|
596
|
+
/** A generic embedded block: `{schemaKey, schemaLabel, fields}`, the successor to Lexical's BlockNode. */
|
|
597
|
+
block: "bcmsBlock",
|
|
598
|
+
/** An inline mention. A PT inline object is natively void, which is what Lexical's `token` mode was faking. */
|
|
599
|
+
mention: "bcmsMention",
|
|
600
|
+
divider: "bcmsDivider",
|
|
601
|
+
code: "bcmsCode",
|
|
602
|
+
/**
|
|
603
|
+
* A video — an uploaded file or a provider URL. DEDICATED rather than another `schemaKey` on
|
|
604
|
+
* the generic block, because a video is the one media node whose rendition depends on its own
|
|
605
|
+
* payload: the same field is a `<video>` for an mp4 and a provider `<iframe>` for a YouTube
|
|
606
|
+
* link (@see resolveVideoSource). The generic block carries a reference this package cannot
|
|
607
|
+
* resolve; a video carries a URL it can.
|
|
608
|
+
*/
|
|
609
|
+
video: "video",
|
|
610
|
+
/** A downloadable document — a PDF, a spec sheet. `<a href download>`, nothing more. */
|
|
611
|
+
file: "file",
|
|
612
|
+
/** A pull quote — see `PortableTextPullQuote`. The V2 kit's `<figure data-rt="pullquote">`. */
|
|
613
|
+
pullQuote: "pullQuote",
|
|
614
|
+
/** Key takeaways — see `PortableTextKeyTakeaways`. The V2 kit's `<aside data-rt="takeaways">`. */
|
|
615
|
+
keyTakeaways: "keyTakeaways",
|
|
616
|
+
/**
|
|
617
|
+
* A table. Spelled `table`, unprefixed, because that is what every other Portable Text
|
|
618
|
+
* producer calls one — the Sanity-shaped `{_type:"table", rows}` this platform used to refuse
|
|
619
|
+
* as foreign content now lands on OUR type and renders, rather than being data nothing draws.
|
|
620
|
+
*/
|
|
621
|
+
table: "table",
|
|
622
|
+
/** The annotation carrying a hyperlink's `href`. Spelled `link` because that is the spec's convention. */
|
|
623
|
+
link: "link",
|
|
624
|
+
/**
|
|
625
|
+
* The annotation carrying a site's OWN inline markup — `<span class>`, `<small>`, `<time
|
|
626
|
+
* datetime>`. Unprefixed, like `table`, because it describes HTML rather than a BetterCMS
|
|
627
|
+
* concept. See `styled.ts` for what it may hold and why the list is that short.
|
|
628
|
+
*/
|
|
629
|
+
styled: "styled"
|
|
630
|
+
};
|
|
631
|
+
var BUILTIN_TABLE_KEY = "builtin:table";
|
|
632
|
+
var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
633
|
+
"block",
|
|
634
|
+
PT_TYPES.block,
|
|
635
|
+
PT_TYPES.table,
|
|
636
|
+
PT_TYPES.code,
|
|
637
|
+
PT_TYPES.divider,
|
|
638
|
+
PT_TYPES.mention,
|
|
639
|
+
PT_TYPES.video,
|
|
640
|
+
PT_TYPES.file,
|
|
641
|
+
PT_TYPES.pullQuote,
|
|
642
|
+
PT_TYPES.keyTakeaways
|
|
643
|
+
]);
|
|
644
|
+
var EMBEDDED_COMPONENT_KEY = "component";
|
|
645
|
+
|
|
646
|
+
// ../richtext/src/styled.ts
|
|
647
|
+
var STYLED_TAGS = [
|
|
648
|
+
"span",
|
|
649
|
+
"small",
|
|
650
|
+
"sup",
|
|
651
|
+
"sub",
|
|
652
|
+
"kbd",
|
|
653
|
+
"abbr",
|
|
654
|
+
"time",
|
|
655
|
+
"strong",
|
|
656
|
+
"b",
|
|
657
|
+
"em",
|
|
658
|
+
"i",
|
|
659
|
+
"u",
|
|
660
|
+
"s",
|
|
661
|
+
"mark",
|
|
662
|
+
"code",
|
|
663
|
+
"q",
|
|
664
|
+
"cite",
|
|
665
|
+
"dfn",
|
|
666
|
+
"var",
|
|
667
|
+
"samp",
|
|
668
|
+
"ins",
|
|
669
|
+
"del"
|
|
670
|
+
];
|
|
671
|
+
var TAG_SET = new Set(STYLED_TAGS);
|
|
672
|
+
var isStyledTag = (tag) => typeof tag === "string" && TAG_SET.has(tag);
|
|
673
|
+
var ATTR_NAMES = /* @__PURE__ */ new Set(["title", "datetime", "style", "aria-label"]);
|
|
674
|
+
var DATA_ATTR = /^data-[a-z][a-z0-9-]*$/;
|
|
675
|
+
var isStyledAttr = (name) => ATTR_NAMES.has(name) || DATA_ATTR.test(name) && !name.startsWith("data-bcms-");
|
|
676
|
+
var STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
677
|
+
"color",
|
|
678
|
+
"background-color",
|
|
679
|
+
"opacity",
|
|
680
|
+
"font",
|
|
681
|
+
"font-family",
|
|
682
|
+
"font-size",
|
|
683
|
+
"font-style",
|
|
684
|
+
"font-weight",
|
|
685
|
+
"font-variant",
|
|
686
|
+
"font-feature-settings",
|
|
687
|
+
"font-variation-settings",
|
|
688
|
+
"letter-spacing",
|
|
689
|
+
"line-height",
|
|
690
|
+
"word-spacing",
|
|
691
|
+
"white-space",
|
|
692
|
+
"word-break",
|
|
693
|
+
"text-align",
|
|
694
|
+
"text-decoration",
|
|
695
|
+
"text-decoration-color",
|
|
696
|
+
"text-decoration-line",
|
|
697
|
+
"text-decoration-style",
|
|
698
|
+
"text-transform",
|
|
699
|
+
"text-underline-offset",
|
|
700
|
+
"vertical-align"
|
|
701
|
+
]);
|
|
702
|
+
var STYLE_VALUE_BAN = /url\(|expression\(|@import|\\|\/\*|[<>]/i;
|
|
703
|
+
function safeStyle(css) {
|
|
704
|
+
const kept = [];
|
|
705
|
+
for (const declaration of css.split(";")) {
|
|
706
|
+
const at = declaration.indexOf(":");
|
|
707
|
+
if (at < 0) continue;
|
|
708
|
+
const name = declaration.slice(0, at).trim().toLowerCase();
|
|
709
|
+
const value = declaration.slice(at + 1).trim();
|
|
710
|
+
if (!name || !value) continue;
|
|
711
|
+
if (!name.startsWith("--") && !STYLE_PROPS.has(name)) continue;
|
|
712
|
+
if (STYLE_VALUE_BAN.test(value)) continue;
|
|
713
|
+
kept.push(`${name}:${value}`);
|
|
714
|
+
}
|
|
715
|
+
return kept.length ? kept.join(";") : void 0;
|
|
716
|
+
}
|
|
717
|
+
function styledAttrEntries(attrs) {
|
|
718
|
+
if (!attrs || typeof attrs !== "object" || Array.isArray(attrs)) return [];
|
|
719
|
+
const out = [];
|
|
720
|
+
for (const [name, raw] of Object.entries(attrs)) {
|
|
721
|
+
if (!isStyledAttr(name) || typeof raw !== "string") continue;
|
|
722
|
+
const value = name === "style" ? safeStyle(raw) : raw;
|
|
723
|
+
if (value === void 0) continue;
|
|
724
|
+
out.push([name, value]);
|
|
725
|
+
}
|
|
726
|
+
return out;
|
|
536
727
|
}
|
|
537
728
|
|
|
538
|
-
// src/
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
729
|
+
// ../richtext/src/href.ts
|
|
730
|
+
var SAFE_HREF_SCHEMES = ["http:", "https:", "mailto:", "tel:"];
|
|
731
|
+
function safeHref(raw) {
|
|
732
|
+
const trimmed = raw.trim();
|
|
733
|
+
if (trimmed.startsWith("#")) return /^#[^\s<>"'`]*$/.test(trimmed) ? trimmed : null;
|
|
734
|
+
if (trimmed.startsWith("/") && !trimmed.startsWith("//")) return trimmed;
|
|
735
|
+
try {
|
|
736
|
+
return SAFE_HREF_SCHEMES.includes(new URL(trimmed).protocol) ? trimmed : null;
|
|
737
|
+
} catch {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
543
740
|
}
|
|
544
741
|
|
|
545
|
-
// src/
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
"data-bcms-field": field,
|
|
556
|
-
"data-bcms-kind": kind
|
|
557
|
-
}) {
|
|
558
|
-
const sized = typeof width === "number" && width > 0;
|
|
559
|
-
return /* @__PURE__ */ jsxs2("figure", { className: cn(sized ? "max-w-full" : "w-full", className), children: [
|
|
560
|
-
/* @__PURE__ */ jsx4(
|
|
561
|
-
"img",
|
|
562
|
-
{
|
|
563
|
-
src,
|
|
564
|
-
alt,
|
|
565
|
-
width,
|
|
566
|
-
height,
|
|
567
|
-
sizes,
|
|
568
|
-
"data-bcms-field": field,
|
|
569
|
-
"data-bcms-kind": kind,
|
|
570
|
-
style: sized ? { width: `${width}px`, maxWidth: "100%", height: "auto" } : void 0,
|
|
571
|
-
className: cn(!sized && "w-full", "h-auto rounded-md object-cover"),
|
|
572
|
-
loading: "lazy"
|
|
573
|
-
}
|
|
574
|
-
),
|
|
575
|
-
caption && /* @__PURE__ */ jsx4("figcaption", { className: "mt-2 text-sm text-muted-foreground text-center italic", children: caption })
|
|
576
|
-
] });
|
|
742
|
+
// ../richtext/src/key.ts
|
|
743
|
+
var ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
744
|
+
var KEY_PATTERN = /^[A-Za-z0-9-]{1,32}$/;
|
|
745
|
+
var isValidKey = (k) => typeof k === "string" && KEY_PATTERN.test(k);
|
|
746
|
+
function randomKey(length = 12) {
|
|
747
|
+
let out = "";
|
|
748
|
+
for (let i = 0; i < length; i += 1) {
|
|
749
|
+
out += ALPHABET[Math.floor(Math.random() * ALPHABET.length)];
|
|
750
|
+
}
|
|
751
|
+
return out;
|
|
577
752
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
text,
|
|
584
|
-
href,
|
|
585
|
-
variant = "primary",
|
|
586
|
-
size = "md",
|
|
587
|
-
fullWidth = false,
|
|
588
|
-
arrow = false,
|
|
589
|
-
icon: iconProp,
|
|
590
|
-
newTab = false,
|
|
591
|
-
rel,
|
|
592
|
-
action,
|
|
593
|
-
align = "left",
|
|
594
|
-
className = "",
|
|
595
|
-
style,
|
|
596
|
-
"data-bcms-field": field,
|
|
597
|
-
"data-bcms-kind": kind
|
|
598
|
-
}) {
|
|
599
|
-
const baseStyles = "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50";
|
|
600
|
-
const variantStyles = {
|
|
601
|
-
primary: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
602
|
-
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
603
|
-
tertiary: "bg-transparent text-primary hover:bg-primary/10",
|
|
604
|
-
brand: "bg-accent text-accent-foreground shadow hover:bg-accent/90",
|
|
605
|
-
dark: "bg-foreground text-background shadow hover:bg-foreground/90",
|
|
606
|
-
light: "border bg-background text-foreground shadow-sm hover:bg-muted",
|
|
607
|
-
outline: "border border-current bg-transparent text-primary hover:bg-primary/10",
|
|
608
|
-
link: "h-auto bg-transparent p-0 text-primary underline underline-offset-4"
|
|
609
|
-
};
|
|
610
|
-
const icon = buttonIconOf(iconProp);
|
|
611
|
-
const alignClass = {
|
|
612
|
-
left: "justify-start",
|
|
613
|
-
center: "justify-center",
|
|
614
|
-
right: "justify-end"
|
|
615
|
-
}[align];
|
|
616
|
-
const sizeStyles = {
|
|
617
|
-
sm: "px-3 py-1.5 text-xs",
|
|
618
|
-
md: "px-4 py-2",
|
|
619
|
-
lg: "px-6 py-3 text-base"
|
|
753
|
+
function deterministicKeys(prefix = "k") {
|
|
754
|
+
let n = 0;
|
|
755
|
+
return () => {
|
|
756
|
+
n += 1;
|
|
757
|
+
return `${prefix}${n}`;
|
|
620
758
|
};
|
|
621
|
-
const sizeClass = size === "sm" || size === "lg" ? sizeStyles[size] : sizeStyles.md;
|
|
622
|
-
const fullClass = fullWidth === true ? "flex w-full justify-center" : "";
|
|
623
|
-
const relTokens = [...newTab === true ? ["noopener", "noreferrer"] : [], ...LINK_RELS.includes(rel) ? [rel] : []];
|
|
624
|
-
if (action === "code") {
|
|
625
|
-
return /* @__PURE__ */ jsxs3("button", { type: "button", "data-bcms-field": field, "data-bcms-kind": kind, style, className: cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className), children: [
|
|
626
|
-
icon && /* @__PURE__ */ jsx5("span", { className: "mr-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg(icon, "bcms-btn-icon") } }),
|
|
627
|
-
text,
|
|
628
|
-
arrow === true && /* @__PURE__ */ jsx5("span", { className: "ml-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg("arrow-right", "bcms-btn-arrow") } })
|
|
629
|
-
] });
|
|
630
|
-
}
|
|
631
|
-
return /* @__PURE__ */ jsxs3(
|
|
632
|
-
"a",
|
|
633
|
-
{
|
|
634
|
-
href: typeof href === "string" && isScriptHref(href) ? "#" : href,
|
|
635
|
-
...newTab === true ? { target: "_blank" } : {},
|
|
636
|
-
...relTokens.length ? { rel: relTokens.join(" ") } : {},
|
|
637
|
-
"data-bcms-field": field,
|
|
638
|
-
"data-bcms-kind": kind,
|
|
639
|
-
style,
|
|
640
|
-
className: cn(baseStyles, variantStyles[buttonVariantOf(variant)], alignClass, sizeClass, fullClass, className),
|
|
641
|
-
children: [
|
|
642
|
-
icon && /* @__PURE__ */ jsx5("span", { className: "mr-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg(icon, "bcms-btn-icon") } }),
|
|
643
|
-
text,
|
|
644
|
-
arrow === true && /* @__PURE__ */ jsx5("span", { className: "ml-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg("arrow-right", "bcms-btn-arrow") } })
|
|
645
|
-
]
|
|
646
|
-
}
|
|
647
|
-
);
|
|
648
759
|
}
|
|
649
760
|
|
|
650
|
-
// src/
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
);
|
|
761
|
+
// ../richtext/src/table.ts
|
|
762
|
+
var isRecord = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
763
|
+
var isPortableTextTable = (n) => isRecord(n) && n._type === PT_TYPES.table && Array.isArray(n.rows);
|
|
764
|
+
var isBuiltInTableBlock = (n) => isRecord(n) && n._type === PT_TYPES.block && n.schemaKey === BUILTIN_TABLE_KEY;
|
|
765
|
+
var asIs = (v) => Array.isArray(v) ? v : [];
|
|
766
|
+
function keyMinter(base, reserved) {
|
|
767
|
+
const prefix = (base.match(/[A-Za-z0-9-]/g) ?? []).join("").slice(0, 16) || "t";
|
|
768
|
+
return (...parts) => {
|
|
769
|
+
const candidate = `${prefix}-${parts.join("")}`.slice(0, 30);
|
|
770
|
+
let key = candidate;
|
|
771
|
+
for (let n = 1; reserved.has(key); n += 1) key = `${candidate}-${n}`;
|
|
772
|
+
reserved.add(key);
|
|
773
|
+
return key;
|
|
774
|
+
};
|
|
665
775
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
className = "",
|
|
674
|
-
renderBlock
|
|
675
|
-
}) {
|
|
676
|
-
return /* @__PURE__ */ jsx7(
|
|
677
|
-
"div",
|
|
678
|
-
{
|
|
679
|
-
className: cn("grid w-full", className),
|
|
680
|
-
style: {
|
|
681
|
-
gridTemplateColumns: `repeat(${columns.length}, minmax(0, 1fr))`,
|
|
682
|
-
gap: `${gap}px`
|
|
683
|
-
},
|
|
684
|
-
children: columns.map((columnBlocks, colIndex) => /* @__PURE__ */ jsx7("div", { className: "flex flex-col gap-4 min-w-0", children: columnBlocks.map((block) => /* @__PURE__ */ jsx7("span", { children: renderBlock ? renderBlock(block) : null }, block.id)) }, colIndex))
|
|
776
|
+
function reservedKeys(rows) {
|
|
777
|
+
const out = /* @__PURE__ */ new Set();
|
|
778
|
+
for (const row of rows) {
|
|
779
|
+
if (!isRecord(row)) continue;
|
|
780
|
+
if (isValidKey(row._key)) out.add(row._key);
|
|
781
|
+
for (const cell of Array.isArray(row.cells) ? row.cells : []) {
|
|
782
|
+
if (isRecord(cell) && isValidKey(cell._key)) out.add(cell._key);
|
|
685
783
|
}
|
|
686
|
-
);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// src/blocks/Video.tsx
|
|
690
|
-
import { useState } from "react";
|
|
691
|
-
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
692
|
-
function getEmbedUrl(url) {
|
|
693
|
-
const ytMatch = url.match(
|
|
694
|
-
/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
|
|
695
|
-
);
|
|
696
|
-
if (ytMatch) {
|
|
697
|
-
return {
|
|
698
|
-
embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,
|
|
699
|
-
provider: "youtube"
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
|
|
703
|
-
if (vimeoMatch) {
|
|
704
|
-
return {
|
|
705
|
-
embedUrl: `https://player.vimeo.com/video/${vimeoMatch[1]}`,
|
|
706
|
-
provider: "vimeo"
|
|
707
|
-
};
|
|
708
784
|
}
|
|
709
|
-
return
|
|
785
|
+
return out;
|
|
710
786
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
}
|
|
743
|
-
),
|
|
744
|
-
/* @__PURE__ */ jsx8(
|
|
745
|
-
"iframe",
|
|
746
|
-
{
|
|
747
|
-
src: `${embedUrl}?autoplay=${autoplay ? 1 : 0}&loop=${loop ? 1 : 0}&mute=${autoplay ? 1 : 0}`,
|
|
748
|
-
allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
|
|
749
|
-
allowFullScreen: true,
|
|
750
|
-
loading: "lazy",
|
|
751
|
-
onLoad: () => setIsLoaded(true),
|
|
752
|
-
className: "w-full h-full"
|
|
753
|
-
}
|
|
754
|
-
)
|
|
755
|
-
] });
|
|
787
|
+
var textContent = (text, key) => text === "" ? [] : [{
|
|
788
|
+
_type: "block",
|
|
789
|
+
_key: key(),
|
|
790
|
+
style: "normal",
|
|
791
|
+
markDefs: [],
|
|
792
|
+
children: [{ _type: "span", _key: key(), text, marks: [] }]
|
|
793
|
+
}];
|
|
794
|
+
var spanOf = (value) => {
|
|
795
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
796
|
+
return Number.isInteger(n) && n > 1 ? n : void 0;
|
|
797
|
+
};
|
|
798
|
+
function normalizeCell(raw, at, mint, read) {
|
|
799
|
+
const [row, col] = at;
|
|
800
|
+
const key = isRecord(raw) && isValidKey(raw._key) ? raw._key : mint("r", row, "c", col);
|
|
801
|
+
const inner = deterministicKeys(`${key}-`);
|
|
802
|
+
if (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean") {
|
|
803
|
+
return { _key: key, content: textContent(String(raw), inner) };
|
|
804
|
+
}
|
|
805
|
+
if (!isRecord(raw)) return { _key: key, content: [] };
|
|
806
|
+
const span = spanOf(raw.span ?? raw.colSpan ?? raw.colspan);
|
|
807
|
+
const rowSpan = spanOf(raw.rowSpan ?? raw.rowspan);
|
|
808
|
+
return {
|
|
809
|
+
_key: key,
|
|
810
|
+
// `text` is the spelling a string matrix uses once its cells have been given objects; it is
|
|
811
|
+
// read here so a half-migrated value cannot lose its words.
|
|
812
|
+
content: Array.isArray(raw.content) ? read(raw.content, inner) : textContent(typeof raw.text === "string" ? raw.text : "", inner),
|
|
813
|
+
...raw.header === true ? { header: true } : {},
|
|
814
|
+
...span === void 0 ? {} : { span },
|
|
815
|
+
...rowSpan === void 0 ? {} : { rowSpan },
|
|
816
|
+
...typeof raw.group === "string" ? { group: raw.group } : {}
|
|
817
|
+
};
|
|
756
818
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
819
|
+
function normalizeRow(raw, at, mint, read) {
|
|
820
|
+
const cells = Array.isArray(raw) ? raw : isRecord(raw) && Array.isArray(raw.cells) ? raw.cells : [];
|
|
821
|
+
return {
|
|
822
|
+
_key: isRecord(raw) && isValidKey(raw._key) ? raw._key : mint("r", at),
|
|
823
|
+
cells: cells.map((c, col) => normalizeCell(c, [at, col], mint, read)),
|
|
824
|
+
...isRecord(raw) && raw.head === true ? { head: true } : {},
|
|
825
|
+
...isRecord(raw) && typeof raw.group === "string" ? { group: raw.group } : {}
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
function builtInTableToTable(node, key = randomKey) {
|
|
829
|
+
if (!isBuiltInTableBlock(node)) return null;
|
|
830
|
+
const fields = node.fields;
|
|
831
|
+
const raw = isRecord(fields) && Array.isArray(fields.rows) ? fields.rows : [];
|
|
832
|
+
const hasHeader = isRecord(fields) ? fields.hasHeader !== false : true;
|
|
833
|
+
const own = node._key;
|
|
834
|
+
const tableKey = isValidKey(own) ? own : key();
|
|
835
|
+
const mint = keyMinter(tableKey, reservedKeys(raw));
|
|
836
|
+
const rows = raw.map((row, i) => {
|
|
837
|
+
const normalized = normalizeRow(row, i, mint, asIs);
|
|
838
|
+
if (!(hasHeader && i === 0)) return normalized;
|
|
839
|
+
return {
|
|
840
|
+
...normalized,
|
|
841
|
+
head: true,
|
|
842
|
+
cells: normalized.cells.map((c) => ({ ...c, header: true }))
|
|
843
|
+
};
|
|
844
|
+
});
|
|
845
|
+
const width = rows.reduce((max, r) => Math.max(max, r.cells.length), 0);
|
|
846
|
+
for (const [i, row] of rows.entries()) {
|
|
847
|
+
while (row.cells.length < width) {
|
|
848
|
+
row.cells.push({
|
|
849
|
+
_key: mint("r", i, "c", row.cells.length),
|
|
850
|
+
content: [],
|
|
851
|
+
...row.head === true ? { header: true } : {}
|
|
852
|
+
});
|
|
776
853
|
}
|
|
777
|
-
|
|
854
|
+
}
|
|
855
|
+
return { _type: PT_TYPES.table, _key: tableKey, rows, legacy: true };
|
|
778
856
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
)
|
|
794
|
-
|
|
857
|
+
function migrateTable(node, key = randomKey, read = asIs) {
|
|
858
|
+
const builtIn = builtInTableToTable(node, key);
|
|
859
|
+
if (builtIn) return builtIn;
|
|
860
|
+
if (!isPortableTextTable(node)) return null;
|
|
861
|
+
const rows = node.rows;
|
|
862
|
+
const tableKey = isValidKey(node._key) ? node._key : key();
|
|
863
|
+
const mint = keyMinter(tableKey, reservedKeys(rows));
|
|
864
|
+
return {
|
|
865
|
+
_type: PT_TYPES.table,
|
|
866
|
+
_key: tableKey,
|
|
867
|
+
rows: rows.map((r, i) => normalizeRow(r, i, mint, read)),
|
|
868
|
+
// Carried through: a migrated table that an author saves back is still the same table, and
|
|
869
|
+
// dropping the flag on that save would change the page the next time it renders.
|
|
870
|
+
...node.legacy === true ? { legacy: true } : {},
|
|
871
|
+
...tableExtras(node)
|
|
872
|
+
};
|
|
795
873
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
] });
|
|
874
|
+
function tableExtras(node) {
|
|
875
|
+
const groups = (list) => Array.isArray(list) ? list.filter((g) => isRecord(g) && typeof g._key === "string" && g._key !== "").map((g) => ({ _key: g._key, label: typeof g.label === "string" ? g.label : "" })) : void 0;
|
|
876
|
+
const rowGroups = groups(node.rowGroups);
|
|
877
|
+
const columnGroups = groups(node.columnGroups);
|
|
878
|
+
return {
|
|
879
|
+
...typeof node.caption === "string" ? { caption: node.caption } : {},
|
|
880
|
+
...typeof node.notes === "string" ? { notes: node.notes } : {},
|
|
881
|
+
...rowGroups ? { rowGroups } : {},
|
|
882
|
+
...columnGroups ? { columnGroups } : {}
|
|
883
|
+
};
|
|
807
884
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
const
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
885
|
+
function tableGroupLayout(table) {
|
|
886
|
+
const rows = Array.isArray(table.rows) ? table.rows : [];
|
|
887
|
+
const labels = (list) => new Map(
|
|
888
|
+
(Array.isArray(list) ? list : []).filter((g) => isRecord(g) && typeof g._key === "string").map((g) => [g._key, typeof g.label === "string" ? g.label : ""])
|
|
889
|
+
);
|
|
890
|
+
const cellsOf = (row) => isRecord(row) && Array.isArray(row.cells) ? row.cells : [];
|
|
891
|
+
const spanOf2 = (cell) => isRecord(cell) && typeof cell.span === "number" && Number.isInteger(cell.span) && cell.span > 1 ? cell.span : 1;
|
|
892
|
+
const width = rows.reduce((max, row) => Math.max(max, cellsOf(row).reduce((n, c) => n + spanOf2(c), 0)), 0);
|
|
893
|
+
const colLabels = labels(table.columnGroups);
|
|
894
|
+
const columns = [];
|
|
895
|
+
for (const cell of cellsOf(rows[0])) {
|
|
896
|
+
const key = isRecord(cell) && typeof cell.group === "string" && colLabels.has(cell.group) ? cell.group : void 0;
|
|
897
|
+
const last = columns[columns.length - 1];
|
|
898
|
+
if (last && last.key === key) last.width += spanOf2(cell);
|
|
899
|
+
else columns.push({ width: spanOf2(cell), ...key === void 0 ? {} : { key, label: colLabels.get(key) } });
|
|
900
|
+
}
|
|
901
|
+
const rowLabels = labels(table.rowGroups);
|
|
902
|
+
const headCount = rows.findIndex((r) => !isRecord(r) || r.head !== true);
|
|
903
|
+
const body = headCount === -1 ? [] : rows.slice(headCount);
|
|
904
|
+
const runs = [];
|
|
905
|
+
for (const row of body) {
|
|
906
|
+
const key = isRecord(row) && typeof row.group === "string" && rowLabels.has(row.group) ? row.group : void 0;
|
|
907
|
+
const last = runs[runs.length - 1];
|
|
908
|
+
if (last && last.key === key) last.rows.push(row);
|
|
909
|
+
else runs.push({ rows: [row], ...key === void 0 ? {} : { key, label: rowLabels.get(key) } });
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
columns: columns.some((r) => r.key !== void 0) ? columns : [],
|
|
913
|
+
rows: runs.some((r) => r.key !== void 0) ? runs : [],
|
|
914
|
+
width
|
|
915
|
+
};
|
|
822
916
|
}
|
|
823
917
|
|
|
824
|
-
// src/
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
const
|
|
830
|
-
return
|
|
831
|
-
/* @__PURE__ */ jsx13("div", { role: "tablist", className: "mb-4 flex gap-1 border-b", children: list.map((t, idx) => /* @__PURE__ */ jsx13(
|
|
832
|
-
"button",
|
|
833
|
-
{
|
|
834
|
-
role: "tab",
|
|
835
|
-
type: "button",
|
|
836
|
-
"aria-selected": idx === active,
|
|
837
|
-
onClick: () => setActive(idx),
|
|
838
|
-
className: cn(
|
|
839
|
-
"cursor-pointer border-b-2 border-transparent bg-transparent px-4 py-2",
|
|
840
|
-
idx === active && "border-black font-semibold"
|
|
841
|
-
),
|
|
842
|
-
children: t.label
|
|
843
|
-
},
|
|
844
|
-
t.id
|
|
845
|
-
)) }),
|
|
846
|
-
list.map((t, idx) => /* @__PURE__ */ jsx13("div", { role: "tabpanel", hidden: idx !== active, className: "flex flex-col gap-4", children: (t.children ?? []).map((b) => /* @__PURE__ */ jsx13("span", { children: renderBlock ? renderBlock(b) : null }, b.id)) }, t.id))
|
|
847
|
-
] });
|
|
918
|
+
// ../richtext/src/key-takeaways.ts
|
|
919
|
+
var isRecord2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
920
|
+
function keyTakeawaysView(value) {
|
|
921
|
+
const v = isRecord2(value) ? value : {};
|
|
922
|
+
const title = typeof v.title === "string" ? v.title.trim() : "";
|
|
923
|
+
const points = (Array.isArray(v.points) ? v.points : []).map((p) => typeof p === "string" ? p.trim() : "").filter(Boolean);
|
|
924
|
+
return { title, points };
|
|
848
925
|
}
|
|
849
926
|
|
|
850
|
-
// src/
|
|
851
|
-
|
|
852
|
-
var
|
|
853
|
-
function
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
"
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
"Form: ",
|
|
865
|
-
/* @__PURE__ */ jsx14("code", { children: formId })
|
|
866
|
-
] });
|
|
867
|
-
}
|
|
868
|
-
function CollectionPlaceholder({
|
|
869
|
-
cardComponentId,
|
|
870
|
-
className = ""
|
|
871
|
-
}) {
|
|
872
|
-
return /* @__PURE__ */ jsxs10("div", { className: cn(box, className), children: [
|
|
873
|
-
"Collection: entries of this page",
|
|
874
|
-
cardComponentId ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
875
|
-
" as ",
|
|
876
|
-
/* @__PURE__ */ jsx14("code", { children: cardComponentId })
|
|
877
|
-
] }) : /* @__PURE__ */ jsx14(Fragment2, { children: " (default card)" })
|
|
878
|
-
] });
|
|
927
|
+
// ../richtext/src/component-copy.ts
|
|
928
|
+
var isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
929
|
+
var fieldsOf = (value) => isRecord3(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord3(value.fields) ? value.fields : null;
|
|
930
|
+
function detachedComponentCopy(value) {
|
|
931
|
+
const f = fieldsOf(value);
|
|
932
|
+
if (!f || f.detached !== true || !Array.isArray(f.blocks)) return null;
|
|
933
|
+
return {
|
|
934
|
+
id: isRecord3(value) && typeof value._key === "string" ? value._key : "",
|
|
935
|
+
componentId: typeof f.componentId === "string" ? f.componentId : "",
|
|
936
|
+
props: isRecord3(f.props) ? f.props : {},
|
|
937
|
+
blocks: f.blocks,
|
|
938
|
+
...typeof f.sourcePublishedAt === "string" ? { sourcePublishedAt: f.sourcePublishedAt } : {},
|
|
939
|
+
...typeof f.variant === "string" ? { variant: f.variant } : {}
|
|
940
|
+
};
|
|
879
941
|
}
|
|
880
942
|
|
|
881
|
-
// src/
|
|
882
|
-
var
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
image: {
|
|
909
|
-
component: Image,
|
|
910
|
-
label: "Image",
|
|
911
|
-
icon: "image"
|
|
912
|
-
},
|
|
913
|
-
button: {
|
|
914
|
-
component: Button,
|
|
915
|
-
label: "Button",
|
|
916
|
-
icon: "square"
|
|
917
|
-
},
|
|
918
|
-
spacer: {
|
|
919
|
-
component: Spacer,
|
|
920
|
-
label: "Spacer",
|
|
921
|
-
icon: "minus"
|
|
922
|
-
},
|
|
923
|
-
columns: {
|
|
924
|
-
component: Columns,
|
|
925
|
-
label: "Columns",
|
|
926
|
-
icon: "columns"
|
|
927
|
-
},
|
|
928
|
-
video: {
|
|
929
|
-
component: Video,
|
|
930
|
-
label: "Video",
|
|
931
|
-
icon: "play"
|
|
932
|
-
},
|
|
933
|
-
section: {
|
|
934
|
-
component: Section,
|
|
935
|
-
label: "Section",
|
|
936
|
-
icon: "layout"
|
|
937
|
-
},
|
|
938
|
-
navbar: {
|
|
939
|
-
component: Navbar,
|
|
940
|
-
label: "Navbar",
|
|
941
|
-
icon: "menu"
|
|
942
|
-
},
|
|
943
|
-
footer: {
|
|
944
|
-
component: Footer,
|
|
945
|
-
label: "Footer",
|
|
946
|
-
icon: "panel-bottom"
|
|
947
|
-
},
|
|
948
|
-
slider: {
|
|
949
|
-
component: Slider,
|
|
950
|
-
label: "Slider",
|
|
951
|
-
icon: "gallery-horizontal"
|
|
952
|
-
},
|
|
953
|
-
tabs: {
|
|
954
|
-
component: Tabs,
|
|
955
|
-
label: "Tabs",
|
|
956
|
-
icon: "folder"
|
|
957
|
-
},
|
|
958
|
-
component: {
|
|
959
|
-
component: ComponentInstance,
|
|
960
|
-
label: "Component",
|
|
961
|
-
icon: "component"
|
|
962
|
-
},
|
|
963
|
-
form: {
|
|
964
|
-
component: FormPlaceholder,
|
|
965
|
-
label: "Form",
|
|
966
|
-
icon: "clipboard-list"
|
|
967
|
-
}
|
|
968
|
-
};
|
|
969
|
-
var BLOCK_TYPES = Object.keys(BLOCK_REGISTRY);
|
|
970
|
-
|
|
971
|
-
// src/renderer.tsx
|
|
972
|
-
import { RESPONSIVE_CSS, hideOnAttr, splitButtonStyle, usesHideOn, visibleBlocks, windowState } from "@bettercms-ai/types";
|
|
973
|
-
|
|
974
|
-
// src/blockStyle.ts
|
|
975
|
-
import { blockStyleToCss } from "@bettercms-ai/types";
|
|
976
|
-
function blockStyleToReactStyle(style) {
|
|
977
|
-
return blockStyleToCss(style);
|
|
943
|
+
// ../richtext/src/read.ts
|
|
944
|
+
var isRecord4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
945
|
+
function normalizePortableText(value, key = randomKey) {
|
|
946
|
+
if (!Array.isArray(value)) return [];
|
|
947
|
+
return value.flatMap((raw) => {
|
|
948
|
+
if (!isRecord4(raw) || typeof raw._type !== "string") return [];
|
|
949
|
+
const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));
|
|
950
|
+
if (table) return [table];
|
|
951
|
+
const node = { ...raw, _key: isRecord4(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
|
|
952
|
+
if (node._type === "block") {
|
|
953
|
+
if (!Array.isArray(node.markDefs)) node.markDefs = [];
|
|
954
|
+
if (typeof node.style !== "string") node.style = "normal";
|
|
955
|
+
node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {
|
|
956
|
+
if (!isRecord4(c)) return c;
|
|
957
|
+
const child = { ...c, _key: typeof c._key === "string" && c._key ? c._key : key() };
|
|
958
|
+
if (child._type === "span" && !Array.isArray(child.marks)) child.marks = [];
|
|
959
|
+
return child;
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
return [node];
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
function readStructuredContent(props) {
|
|
966
|
+
if (!isRecord4(props)) return null;
|
|
967
|
+
const candidate = props.content ?? props.value;
|
|
968
|
+
const arr = Array.isArray(candidate) ? candidate : isRecord4(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
|
|
969
|
+
return arr && arr.length > 0 ? arr : null;
|
|
978
970
|
}
|
|
979
971
|
|
|
980
|
-
// src/
|
|
981
|
-
import "react";
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
972
|
+
// src/blocks/portableText.tsx
|
|
973
|
+
import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
974
|
+
var TOKEN = /^[a-z0-9-]+$/i;
|
|
975
|
+
function styleObject(css) {
|
|
976
|
+
const out = {};
|
|
977
|
+
for (const declaration of css.split(";")) {
|
|
978
|
+
const at = declaration.indexOf(":");
|
|
979
|
+
if (at < 0) continue;
|
|
980
|
+
const name = declaration.slice(0, at).trim();
|
|
981
|
+
const value = declaration.slice(at + 1).trim();
|
|
982
|
+
if (!name || !value) continue;
|
|
983
|
+
out[name.startsWith("--") ? name : name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = value;
|
|
984
|
+
}
|
|
985
|
+
return out;
|
|
988
986
|
}
|
|
989
|
-
function
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
987
|
+
function Bound({
|
|
988
|
+
base,
|
|
989
|
+
nodeKey,
|
|
990
|
+
children
|
|
991
|
+
}) {
|
|
992
|
+
if (!base || !nodeKey) return /* @__PURE__ */ jsx14(Fragment2, { children });
|
|
993
|
+
return /* @__PURE__ */ jsx14("div", { "data-bcms-field": `${base}#${nodeKey}`, "data-bcms-kind": "richtext", children });
|
|
994
994
|
}
|
|
995
|
-
function
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
995
|
+
function Table({ value }) {
|
|
996
|
+
const components = React6.useMemo(buildComponents, []);
|
|
997
|
+
const inline = React6.useMemo(
|
|
998
|
+
() => ({ ...components, block: { normal: ({ children }) => /* @__PURE__ */ jsx14(Fragment2, { children }) } }),
|
|
999
|
+
[components]
|
|
1000
|
+
);
|
|
1001
|
+
const rows = Array.isArray(value?.rows) ? value.rows : [];
|
|
1002
|
+
if (rows.length === 0) return null;
|
|
1003
|
+
const cells = (row) => (Array.isArray(row?.cells) ? row.cells : []).map((cell) => {
|
|
1004
|
+
const Tag = cell?.header === true ? "th" : "td";
|
|
1005
|
+
const span2 = typeof cell?.span === "number" && cell.span > 1 ? cell.span : void 0;
|
|
1006
|
+
const rowSpan = typeof cell?.rowSpan === "number" && cell.rowSpan > 1 ? cell.rowSpan : void 0;
|
|
1007
|
+
const content = Array.isArray(cell?.content) ? cell.content : [];
|
|
1008
|
+
const lone = content.length === 1 && content[0]._type === "block" && content[0].style === "normal" && !content[0].listItem;
|
|
1009
|
+
return /* @__PURE__ */ jsx14(Tag, { ...span2 ? { colSpan: span2 } : {}, ...rowSpan ? { rowSpan } : {}, children: /* @__PURE__ */ jsx14(PT, { value: content, components: lone ? inline : components }) }, cell?._key);
|
|
1010
|
+
});
|
|
1011
|
+
const rowEl = (row) => /* @__PURE__ */ jsx14("tr", { children: cells(row) }, row?._key);
|
|
1012
|
+
const headCount = rows.findIndex((r) => r?.head !== true);
|
|
1013
|
+
const head = headCount === -1 ? rows.length : headCount;
|
|
1014
|
+
const layout = tableGroupLayout(value);
|
|
1015
|
+
const caption = typeof value.caption === "string" ? value.caption.trim() : "";
|
|
1016
|
+
const notes = typeof value.notes === "string" ? value.notes.trim() : "";
|
|
1017
|
+
const notesId = notes && typeof value._key === "string" && value._key ? `${value._key}-notes` : void 0;
|
|
1018
|
+
const span = (n) => n > 1 ? n : void 0;
|
|
1019
|
+
const groupRow = layout.columns.length ? /* @__PURE__ */ jsx14("tr", { children: layout.columns.flatMap(
|
|
1020
|
+
(run, i) => run.label === void 0 ? Array.from({ length: run.width }, (_, k) => /* @__PURE__ */ jsx14("td", {}, `${i}-${k}`)) : [/* @__PURE__ */ jsx14("th", { scope: "colgroup", colSpan: span(run.width), "data-group": run.key, children: run.label }, i)]
|
|
1021
|
+
) }) : null;
|
|
1022
|
+
const bodies = layout.rows.length ? layout.rows.map((run, i) => /* @__PURE__ */ jsxs11("tbody", { "data-key": run.key, children: [
|
|
1023
|
+
run.label !== void 0 && /* @__PURE__ */ jsx14("tr", { children: /* @__PURE__ */ jsx14("th", { scope: "rowgroup", colSpan: span(layout.width), children: run.label }) }),
|
|
1024
|
+
run.rows.map(rowEl)
|
|
1025
|
+
] }, i)) : head < rows.length && /* @__PURE__ */ jsx14("tbody", { children: rows.slice(head).map(rowEl) });
|
|
1026
|
+
const table = /* @__PURE__ */ jsxs11("table", { ...value.legacy === true ? { className: "bcms-table" } : {}, ...notesId ? { "aria-describedby": notesId } : {}, children: [
|
|
1027
|
+
caption && /* @__PURE__ */ jsx14("caption", { children: caption }),
|
|
1028
|
+
layout.columns.map((run, i) => /* @__PURE__ */ jsx14("colgroup", { span: span(run.width) }, i)),
|
|
1029
|
+
(groupRow || head > 0) && /* @__PURE__ */ jsxs11("thead", { children: [
|
|
1030
|
+
groupRow,
|
|
1031
|
+
rows.slice(0, head).map(rowEl)
|
|
1032
|
+
] }),
|
|
1033
|
+
bodies
|
|
1034
|
+
] });
|
|
1035
|
+
return notes ? /* @__PURE__ */ jsxs11("figure", { "data-rt": "table", children: [
|
|
1036
|
+
table,
|
|
1037
|
+
/* @__PURE__ */ jsx14("p", { "data-notes": "", id: notesId, children: notes })
|
|
1038
|
+
] }) : table;
|
|
1009
1039
|
}
|
|
1010
|
-
function
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
{
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1040
|
+
function buildComponents(renderBlock) {
|
|
1041
|
+
return {
|
|
1042
|
+
types: {
|
|
1043
|
+
[PT_TYPES.divider]: () => /* @__PURE__ */ jsx14("hr", {}),
|
|
1044
|
+
[PT_TYPES.table]: ({ value }) => /* @__PURE__ */ jsx14(Table, { value }),
|
|
1045
|
+
[PT_TYPES.code]: ({ value }) => {
|
|
1046
|
+
const v = value;
|
|
1047
|
+
const lang = typeof v.language === "string" && TOKEN.test(v.language) ? v.language : void 0;
|
|
1048
|
+
return /* @__PURE__ */ jsx14("pre", { children: /* @__PURE__ */ jsx14("code", { className: lang ? `language-${lang}` : void 0, children: typeof v.code === "string" ? v.code : "" }) });
|
|
1049
|
+
},
|
|
1050
|
+
/**
|
|
1051
|
+
* A generic embedded block renders as a marker, not as content: its payload references
|
|
1052
|
+
* something (a component, a media id) that has to be RESOLVED against the project, and
|
|
1053
|
+
* this renderer has no project. A marker keeps the node visible and addressable rather
|
|
1054
|
+
* than rendering nothing at all, which is how embedded content used to vanish. A caller
|
|
1055
|
+
* that CAN resolve it passes `renderBlock`.
|
|
1056
|
+
*/
|
|
1057
|
+
[PT_TYPES.block]: ({ value }) => {
|
|
1058
|
+
const rendered = renderBlock?.(value);
|
|
1059
|
+
if (rendered !== void 0) return /* @__PURE__ */ jsx14(Fragment2, { children: rendered });
|
|
1060
|
+
const copy = detachedComponentCopy(value);
|
|
1061
|
+
if (copy) {
|
|
1062
|
+
return /* @__PURE__ */ jsx14("div", { className: "bcms-block bcms-block--component", "data-bcms-component-copy": copy.componentId, "data-bcms-instance": copy.id, children: /* @__PURE__ */ jsx14(BlocksRenderer, { blocks: copy.blocks }) });
|
|
1063
|
+
}
|
|
1064
|
+
const v = value;
|
|
1065
|
+
const key = typeof v.schemaKey === "string" ? v.schemaKey : "block";
|
|
1066
|
+
return /* @__PURE__ */ jsx14("div", { className: "bcms-block", "data-bcms-schema-key": key });
|
|
1067
|
+
},
|
|
1068
|
+
/**
|
|
1069
|
+
* A pull quote — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's
|
|
1070
|
+
* `<figure data-rt="pullquote">`, the name linked (and the quote cited) only through
|
|
1071
|
+
* `safeHref`. pull-quote.test.tsx compares the two.
|
|
1072
|
+
*/
|
|
1073
|
+
[PT_TYPES.pullQuote]: ({ value }) => {
|
|
1074
|
+
const v = value;
|
|
1075
|
+
const text = (x) => typeof x === "string" ? x.trim() : "";
|
|
1076
|
+
const quote = typeof v.quote === "string" ? v.quote : "";
|
|
1077
|
+
const name = text(v.name);
|
|
1078
|
+
const role = text(v.role);
|
|
1079
|
+
const href = safeHref(pullQuoteHref(v.link)) ?? void 0;
|
|
1080
|
+
return /* @__PURE__ */ jsxs11("figure", { "data-rt": "pullquote", children: [
|
|
1081
|
+
/* @__PURE__ */ jsx14("blockquote", { cite: href, children: /* @__PURE__ */ jsx14("p", { children: quote }) }),
|
|
1082
|
+
name || role ? /* @__PURE__ */ jsxs11("figcaption", { children: [
|
|
1083
|
+
name ? href ? /* @__PURE__ */ jsx14("a", { href, "data-name": "", children: name }) : /* @__PURE__ */ jsx14("span", { "data-name": "", children: name }) : null,
|
|
1084
|
+
name && role ? ", " : null,
|
|
1085
|
+
role ? /* @__PURE__ */ jsx14("span", { "data-role": "", children: role }) : null
|
|
1086
|
+
] }) : null
|
|
1087
|
+
] });
|
|
1088
|
+
},
|
|
1089
|
+
/**
|
|
1090
|
+
* Key takeaways — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's
|
|
1091
|
+
* `<aside data-rt="takeaways">`, read through the one `keyTakeawaysView` both renderers share.
|
|
1092
|
+
* portable-text-key-takeaways.test.tsx compares the two.
|
|
1093
|
+
*/
|
|
1094
|
+
[PT_TYPES.keyTakeaways]: ({ value }) => {
|
|
1095
|
+
const { title, points } = keyTakeawaysView(value);
|
|
1096
|
+
return /* @__PURE__ */ jsxs11("aside", { "data-rt": "takeaways", children: [
|
|
1097
|
+
title ? /* @__PURE__ */ jsx14("p", { "data-title": "", children: title }) : null,
|
|
1098
|
+
/* @__PURE__ */ jsx14("ul", { children: points.map((p, i) => /* @__PURE__ */ jsx14("li", { children: p }, i)) })
|
|
1099
|
+
] });
|
|
1100
|
+
},
|
|
1101
|
+
[PT_TYPES.mention]: ({ value }) => {
|
|
1102
|
+
const v = value;
|
|
1103
|
+
return /* @__PURE__ */ jsx14("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
|
|
1021
1104
|
}
|
|
1022
|
-
|
|
1105
|
+
},
|
|
1106
|
+
marks: {
|
|
1107
|
+
/**
|
|
1108
|
+
* The only attribute this renderer writes from author data. An unsafe href renders the
|
|
1109
|
+
* LABEL rather than deleting it — blocking a link must not lose the words.
|
|
1110
|
+
*/
|
|
1111
|
+
[PT_TYPES.link]: ({ value, children }) => {
|
|
1112
|
+
const v = value ?? {};
|
|
1113
|
+
const href = typeof v.href === "string" ? safeHref(v.href) : null;
|
|
1114
|
+
if (!href) return /* @__PURE__ */ jsx14(Fragment2, { children });
|
|
1115
|
+
const blank = v.target === "_blank";
|
|
1116
|
+
return /* @__PURE__ */ jsx14("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
|
|
1117
|
+
},
|
|
1118
|
+
/**
|
|
1119
|
+
* The site's own inline markup, rendered as itself — the React half of
|
|
1120
|
+
* `portableTextToHtml`'s `styled` mark, and it has to stay the same shape: Astro and the
|
|
1121
|
+
* editor canvas render through that serialiser while a Next site renders through this
|
|
1122
|
+
* one, and two renderers of one value that disagree is the whole bug class.
|
|
1123
|
+
*
|
|
1124
|
+
* The tag is re-checked against `STYLED_TAGS` and the attribute bag re-filtered, because
|
|
1125
|
+
* a markDef is DATA and arrives from imports, the Management API and AI writes. React
|
|
1126
|
+
* escapes the values it writes; the allowlist is what keeps a `href` or an `onClick` out
|
|
1127
|
+
* of the props entirely. An unrecognised tag renders its CHILDREN — the words are the
|
|
1128
|
+
* author's, the wrapper is only the design.
|
|
1129
|
+
*/
|
|
1130
|
+
[PT_TYPES.styled]: ({ value, children }) => {
|
|
1131
|
+
const v = value ?? {};
|
|
1132
|
+
const tag = typeof v.tag === "string" ? v.tag.toLowerCase() : "";
|
|
1133
|
+
if (!isStyledTag(tag)) return /* @__PURE__ */ jsx14(Fragment2, { children });
|
|
1134
|
+
const Tag = tag;
|
|
1135
|
+
const className = typeof v.className === "string" && v.className ? v.className : void 0;
|
|
1136
|
+
const attrs = styledAttrEntries(v.attrs);
|
|
1137
|
+
const style = attrs.find(([name]) => name === "style")?.[1];
|
|
1138
|
+
return /* @__PURE__ */ jsx14(
|
|
1139
|
+
Tag,
|
|
1140
|
+
{
|
|
1141
|
+
className,
|
|
1142
|
+
...Object.fromEntries(attrs.filter(([name]) => name !== "style")),
|
|
1143
|
+
...style ? { style: styleObject(style) } : {},
|
|
1144
|
+
children
|
|
1145
|
+
}
|
|
1146
|
+
);
|
|
1147
|
+
},
|
|
1148
|
+
highlight: ({ children }) => /* @__PURE__ */ jsx14("mark", { children }),
|
|
1149
|
+
"strike-through": ({ children }) => /* @__PURE__ */ jsx14("s", { children }),
|
|
1150
|
+
/**
|
|
1151
|
+
* A colour mark carries a TOKEN name (`color:brand-accent`), never a baked hex, so the
|
|
1152
|
+
* consuming site styles it and a rebrand does not orphan the content.
|
|
1153
|
+
*/
|
|
1154
|
+
color: ({ children, markType }) => {
|
|
1155
|
+
const token = markType.startsWith("color:") ? markType.slice(6) : "";
|
|
1156
|
+
if (!TOKEN.test(token)) return /* @__PURE__ */ jsx14(Fragment2, { children });
|
|
1157
|
+
return /* @__PURE__ */ jsx14("span", { className: `bcms-color-${token}`, children });
|
|
1158
|
+
}
|
|
1159
|
+
},
|
|
1160
|
+
/** Unknown marks and types render their children. Never swallow content. */
|
|
1161
|
+
unknownMark: ({ children }) => /* @__PURE__ */ jsx14(Fragment2, { children }),
|
|
1162
|
+
unknownType: ({ children }) => /* @__PURE__ */ jsx14(Fragment2, { children: children ?? null }),
|
|
1163
|
+
unknownBlockStyle: ({ children }) => /* @__PURE__ */ jsx14("p", { children })
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
var isListItem = (node) => node._type === "block" && typeof node.listItem === "string" && !!node.listItem;
|
|
1167
|
+
function runsOf(blocks) {
|
|
1168
|
+
const runs = [];
|
|
1169
|
+
for (const node of blocks) {
|
|
1170
|
+
const last = runs.at(-1);
|
|
1171
|
+
if (last && isListItem(node) && isListItem(last[0])) last.push(node);
|
|
1172
|
+
else runs.push([node]);
|
|
1023
1173
|
}
|
|
1024
|
-
|
|
1025
|
-
const { wrapper: blockStyle, paint } = styleOf(block);
|
|
1026
|
-
const el = /* @__PURE__ */ jsx15(Component, { ...block.props, ...paint, className, ...bindingProps(block, annotate) });
|
|
1027
|
-
return withHide(block, blockStyle, el, stampAt);
|
|
1174
|
+
return runs;
|
|
1028
1175
|
}
|
|
1029
|
-
function
|
|
1030
|
-
|
|
1176
|
+
function PortableText({ value, bindingBase, renderBlock }) {
|
|
1177
|
+
const blocks = React6.useMemo(() => normalizePortableText(readValue(value)), [value]);
|
|
1178
|
+
const components = React6.useMemo(() => buildComponents(renderBlock), [renderBlock]);
|
|
1179
|
+
const listComponents = React6.useMemo(() => {
|
|
1180
|
+
if (!bindingBase) return null;
|
|
1181
|
+
const keys = new Set(blocks.map((b) => b._key));
|
|
1182
|
+
return {
|
|
1183
|
+
...components,
|
|
1184
|
+
listItem: ({ value: item, children }) => keys.has(item._key) ? /* @__PURE__ */ jsx14("li", { "data-bcms-field": `${bindingBase}#${item._key}`, "data-bcms-kind": "richtext", children }) : /* @__PURE__ */ jsx14("li", { children })
|
|
1185
|
+
};
|
|
1186
|
+
}, [bindingBase, blocks, components]);
|
|
1187
|
+
if (blocks.length === 0) return null;
|
|
1188
|
+
if (!bindingBase || !listComponents) return /* @__PURE__ */ jsx14(PT, { value: blocks, components });
|
|
1189
|
+
return /* @__PURE__ */ jsx14(Fragment2, { children: runsOf(blocks).map(
|
|
1190
|
+
(run) => isListItem(run[0]) ? /* @__PURE__ */ jsx14(PT, { value: run, components: listComponents }, run[0]._key) : /* @__PURE__ */ jsx14(Bound, { base: bindingBase, nodeKey: run[0]._key, children: /* @__PURE__ */ jsx14(PT, { value: run, components }) }, run[0]._key)
|
|
1191
|
+
) });
|
|
1192
|
+
}
|
|
1193
|
+
function readValue(value) {
|
|
1194
|
+
if (Array.isArray(value)) return value;
|
|
1195
|
+
if (value && typeof value === "object") {
|
|
1196
|
+
const o = value;
|
|
1197
|
+
if (Array.isArray(o.value)) return o.value;
|
|
1198
|
+
if (Array.isArray(o.content)) return o.content;
|
|
1199
|
+
}
|
|
1200
|
+
return [];
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/blocks/Text.tsx
|
|
1204
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1205
|
+
function Text({
|
|
1206
|
+
html,
|
|
1207
|
+
content,
|
|
1208
|
+
align = "left",
|
|
1031
1209
|
className = "",
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
stampAt
|
|
1210
|
+
"data-bcms-field": field,
|
|
1211
|
+
"data-bcms-kind": kind
|
|
1035
1212
|
}) {
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
]
|
|
1046
|
-
}
|
|
1047
|
-
);
|
|
1048
|
-
}
|
|
1049
|
-
const Component = entry.component;
|
|
1050
|
-
const containerProps = CONTAINER_TYPES.has(block.type) ? { renderBlock: renderBlock ?? ((child) => resolveBlock(child, void 0, annotate, stampAt)) } : {};
|
|
1051
|
-
const { wrapper: blockStyle, paint } = styleOf(block);
|
|
1052
|
-
const el = /* @__PURE__ */ jsx15(
|
|
1053
|
-
Component,
|
|
1213
|
+
const alignClass = {
|
|
1214
|
+
left: "text-left",
|
|
1215
|
+
center: "text-center",
|
|
1216
|
+
right: "text-right"
|
|
1217
|
+
}[align];
|
|
1218
|
+
const cls = `text-muted-foreground leading-relaxed ${alignClass} ${className}`;
|
|
1219
|
+
const structured = readStructuredContent({ content });
|
|
1220
|
+
return /* @__PURE__ */ jsx15(
|
|
1221
|
+
"div",
|
|
1054
1222
|
{
|
|
1055
|
-
|
|
1056
|
-
...
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1223
|
+
className: cls,
|
|
1224
|
+
...structured ? {} : { "data-bcms-field": field, "data-bcms-kind": kind },
|
|
1225
|
+
children: structured ? /* @__PURE__ */ jsx15(PortableText, { value: structured, bindingBase: field }) : (
|
|
1226
|
+
// eslint-disable-next-line react/no-danger
|
|
1227
|
+
/* @__PURE__ */ jsx15("span", { dangerouslySetInnerHTML: { __html: html } })
|
|
1228
|
+
)
|
|
1060
1229
|
}
|
|
1061
1230
|
);
|
|
1062
|
-
return withHide(block, blockStyle, el, stampAt);
|
|
1063
|
-
}
|
|
1064
|
-
function BlocksRenderer({
|
|
1065
|
-
blocks: all,
|
|
1066
|
-
className = "",
|
|
1067
|
-
renderBlock,
|
|
1068
|
-
annotate = false,
|
|
1069
|
-
now
|
|
1070
|
-
}) {
|
|
1071
|
-
const at = now ?? /* @__PURE__ */ new Date();
|
|
1072
|
-
const blocks = annotate ? all : visibleBlocks(all, at);
|
|
1073
|
-
if (blocks.length === 0) return null;
|
|
1074
|
-
return /* @__PURE__ */ jsxs11("div", { className, children: [
|
|
1075
|
-
usesHideOn(blocks) && /* @__PURE__ */ jsx15("style", { dangerouslySetInnerHTML: { __html: RESPONSIVE_CSS } }),
|
|
1076
|
-
blocks.map((block) => /* @__PURE__ */ jsx15(
|
|
1077
|
-
BlockRenderer,
|
|
1078
|
-
{
|
|
1079
|
-
block,
|
|
1080
|
-
renderBlock,
|
|
1081
|
-
annotate,
|
|
1082
|
-
stampAt: annotate ? at : void 0
|
|
1083
|
-
},
|
|
1084
|
-
block.id
|
|
1085
|
-
))
|
|
1086
|
-
] });
|
|
1087
1231
|
}
|
|
1088
1232
|
export {
|
|
1089
1233
|
BLOCK_REGISTRY,
|