@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/blocks.js
CHANGED
|
@@ -29,841 +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"
|
|
192
109
|
};
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const candidate = `${prefix}-${parts.join("")}`.slice(0, 30);
|
|
204
|
-
let key = candidate;
|
|
205
|
-
for (let n = 1; reserved.has(key); n += 1) key = `${candidate}-${n}`;
|
|
206
|
-
reserved.add(key);
|
|
207
|
-
return key;
|
|
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"
|
|
208
120
|
};
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
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
|
+
] });
|
|
218
130
|
}
|
|
219
|
-
return
|
|
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
|
+
);
|
|
220
148
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return { _key: key, content: textContent(String(raw), inner) };
|
|
238
|
-
}
|
|
239
|
-
if (!isRecord(raw)) return { _key: key, content: [] };
|
|
240
|
-
const span = spanOf(raw.span ?? raw.colSpan ?? raw.colspan);
|
|
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
|
-
};
|
|
149
|
+
|
|
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
|
+
);
|
|
251
165
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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))
|
|
185
|
+
}
|
|
186
|
+
);
|
|
259
187
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const normalized = normalizeRow(row, i, mint, asIs);
|
|
270
|
-
if (!(hasHeader && i === 0)) return normalized;
|
|
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) {
|
|
271
197
|
return {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
198
|
+
embedUrl: `https://www.youtube.com/embed/${ytMatch[1]}`,
|
|
199
|
+
provider: "youtube"
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
|
|
203
|
+
if (vimeoMatch) {
|
|
204
|
+
return {
|
|
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
|
-
});
|
|
327
|
-
}
|
|
328
|
-
function readStructuredContent(props) {
|
|
329
|
-
if (!isRecord2(props)) return null;
|
|
330
|
-
const candidate = props.content ?? props.value;
|
|
331
|
-
const arr = Array.isArray(candidate) ? candidate : isRecord2(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
|
|
332
|
-
return arr && arr.length > 0 ? arr : null;
|
|
277
|
+
);
|
|
333
278
|
}
|
|
334
279
|
|
|
335
|
-
// src/blocks/
|
|
336
|
-
import {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
+
] });
|
|
349
295
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
+
] });
|
|
357
307
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
);
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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) })
|
|
308
|
+
|
|
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" })
|
|
380
321
|
] });
|
|
381
322
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const v = value;
|
|
403
|
-
const key = typeof v.schemaKey === "string" ? v.schemaKey : "block";
|
|
404
|
-
return /* @__PURE__ */ jsx2("div", { className: "bcms-block", "data-bcms-schema-key": key });
|
|
405
|
-
},
|
|
406
|
-
[PT_TYPES.mention]: ({ value }) => {
|
|
407
|
-
const v = value;
|
|
408
|
-
return /* @__PURE__ */ jsx2("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
|
|
409
|
-
}
|
|
410
|
-
},
|
|
411
|
-
marks: {
|
|
412
|
-
/**
|
|
413
|
-
* The only attribute this renderer writes from author data. An unsafe href renders the
|
|
414
|
-
* LABEL rather than deleting it — blocking a link must not lose the words.
|
|
415
|
-
*/
|
|
416
|
-
[PT_TYPES.link]: ({ value, children }) => {
|
|
417
|
-
const v = value ?? {};
|
|
418
|
-
const href = typeof v.href === "string" ? safeHref(v.href) : null;
|
|
419
|
-
if (!href) return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
420
|
-
const blank = v.target === "_blank";
|
|
421
|
-
return /* @__PURE__ */ jsx2("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
|
|
422
|
-
},
|
|
423
|
-
/**
|
|
424
|
-
* The site's own inline markup, rendered as itself — the React half of
|
|
425
|
-
* `portableTextToHtml`'s `styled` mark, and it has to stay the same shape: Astro and the
|
|
426
|
-
* editor canvas render through that serialiser while a Next site renders through this
|
|
427
|
-
* one, and two renderers of one value that disagree is the whole bug class.
|
|
428
|
-
*
|
|
429
|
-
* The tag is re-checked against `STYLED_TAGS` and the attribute bag re-filtered, because
|
|
430
|
-
* a markDef is DATA and arrives from imports, the Management API and AI writes. React
|
|
431
|
-
* escapes the values it writes; the allowlist is what keeps a `href` or an `onClick` out
|
|
432
|
-
* of the props entirely. An unrecognised tag renders its CHILDREN — the words are the
|
|
433
|
-
* author's, the wrapper is only the design.
|
|
434
|
-
*/
|
|
435
|
-
[PT_TYPES.styled]: ({ value, children }) => {
|
|
436
|
-
const v = value ?? {};
|
|
437
|
-
const tag = typeof v.tag === "string" ? v.tag.toLowerCase() : "";
|
|
438
|
-
if (!isStyledTag(tag)) return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
439
|
-
const Tag = tag;
|
|
440
|
-
const className = typeof v.className === "string" && v.className ? v.className : void 0;
|
|
441
|
-
const attrs = styledAttrEntries(v.attrs);
|
|
442
|
-
const style = attrs.find(([name]) => name === "style")?.[1];
|
|
443
|
-
return /* @__PURE__ */ jsx2(
|
|
444
|
-
Tag,
|
|
445
|
-
{
|
|
446
|
-
className,
|
|
447
|
-
...Object.fromEntries(attrs.filter(([name]) => name !== "style")),
|
|
448
|
-
...style ? { style: styleObject(style) } : {},
|
|
449
|
-
children
|
|
450
|
-
}
|
|
451
|
-
);
|
|
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
|
|
452
343
|
},
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
};
|
|
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 = ""
|
|
356
|
+
}) {
|
|
357
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn(box, className), children: [
|
|
358
|
+
"Component instance: ",
|
|
359
|
+
/* @__PURE__ */ jsx12("code", { children: componentId })
|
|
360
|
+
] });
|
|
361
|
+
}
|
|
362
|
+
function FormPlaceholder({ formId, className = "" }) {
|
|
363
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn(box, className), children: [
|
|
364
|
+
"Form: ",
|
|
365
|
+
/* @__PURE__ */ jsx12("code", { children: formId })
|
|
366
|
+
] });
|
|
470
367
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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"
|
|
478
467
|
}
|
|
479
|
-
|
|
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);
|
|
480
475
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
};
|
|
491
|
-
}, [bindingBase, blocks, components]);
|
|
492
|
-
if (blocks.length === 0) return null;
|
|
493
|
-
if (!bindingBase || !listComponents) return /* @__PURE__ */ jsx2(PT, { value: blocks, components });
|
|
494
|
-
return /* @__PURE__ */ jsx2(Fragment, { children: runsOf(blocks).map(
|
|
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
|
-
) });
|
|
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 } : {} };
|
|
497
485
|
}
|
|
498
|
-
function
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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 });
|
|
492
|
+
}
|
|
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 {};
|
|
504
506
|
}
|
|
505
|
-
return [];
|
|
506
507
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
+
);
|
|
521
|
+
}
|
|
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);
|
|
526
|
+
}
|
|
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);
|
|
536
561
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
import { clsx } from "clsx";
|
|
540
|
-
import { twMerge } from "tailwind-merge";
|
|
541
|
-
function cn(...inputs) {
|
|
542
|
-
return twMerge(clsx(inputs));
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// src/blocks/Image.tsx
|
|
546
|
-
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
547
|
-
function Image({
|
|
548
|
-
src,
|
|
549
|
-
alt,
|
|
550
|
-
width,
|
|
551
|
-
height,
|
|
552
|
-
caption,
|
|
562
|
+
function BlocksRenderer({
|
|
563
|
+
blocks: all,
|
|
553
564
|
className = "",
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
565
|
+
renderBlock,
|
|
566
|
+
annotate = false,
|
|
567
|
+
now
|
|
557
568
|
}) {
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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,
|
|
562
576
|
{
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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 })
|
|
577
|
+
block,
|
|
578
|
+
renderBlock,
|
|
579
|
+
annotate,
|
|
580
|
+
stampAt: annotate ? at : void 0
|
|
581
|
+
},
|
|
582
|
+
block.id
|
|
583
|
+
))
|
|
576
584
|
] });
|
|
577
585
|
}
|
|
578
586
|
|
|
579
|
-
// src/
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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}`);
|
|
630
714
|
}
|
|
631
|
-
return
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
text,
|
|
644
|
-
arrow === true && /* @__PURE__ */ jsx5("span", { className: "ml-2 inline-flex", dangerouslySetInnerHTML: { __html: buttonIconSvg("arrow-right", "bcms-btn-arrow") } })
|
|
645
|
-
]
|
|
646
|
-
}
|
|
647
|
-
);
|
|
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;
|
|
648
727
|
}
|
|
649
728
|
|
|
650
|
-
// src/
|
|
651
|
-
|
|
652
|
-
function
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
"px"
|
|
662
|
-
] })
|
|
663
|
-
}
|
|
664
|
-
);
|
|
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
|
+
}
|
|
665
740
|
}
|
|
666
741
|
|
|
667
|
-
// src/
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
return
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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))
|
|
685
|
-
}
|
|
686
|
-
);
|
|
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;
|
|
752
|
+
}
|
|
753
|
+
function deterministicKeys(prefix = "k") {
|
|
754
|
+
let n = 0;
|
|
755
|
+
return () => {
|
|
756
|
+
n += 1;
|
|
757
|
+
return `${prefix}${n}`;
|
|
758
|
+
};
|
|
687
759
|
}
|
|
688
760
|
|
|
689
|
-
// src/
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
);
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
+
};
|
|
775
|
+
}
|
|
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);
|
|
783
|
+
}
|
|
701
784
|
}
|
|
702
|
-
|
|
703
|
-
|
|
785
|
+
return out;
|
|
786
|
+
}
|
|
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
|
+
};
|
|
818
|
+
}
|
|
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;
|
|
704
839
|
return {
|
|
705
|
-
|
|
706
|
-
|
|
840
|
+
...normalized,
|
|
841
|
+
head: true,
|
|
842
|
+
cells: normalized.cells.map((c) => ({ ...c, header: true }))
|
|
707
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
|
+
});
|
|
853
|
+
}
|
|
708
854
|
}
|
|
709
|
-
return {
|
|
855
|
+
return { _type: PT_TYPES.table, _key: tableKey, rows, legacy: true };
|
|
710
856
|
}
|
|
711
|
-
function
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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
|
+
};
|
|
873
|
+
}
|
|
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
|
+
};
|
|
884
|
+
}
|
|
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) } });
|
|
733
900
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
allowFullScreen: true,
|
|
750
|
-
loading: "lazy",
|
|
751
|
-
onLoad: () => setIsLoaded(true),
|
|
752
|
-
className: "w-full h-full"
|
|
753
|
-
}
|
|
754
|
-
)
|
|
755
|
-
] });
|
|
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
|
+
};
|
|
756
916
|
}
|
|
757
917
|
|
|
758
|
-
// src/
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
maxWidth,
|
|
766
|
-
className = "",
|
|
767
|
-
renderBlock
|
|
768
|
-
}) {
|
|
769
|
-
const inner = (children ?? []).map((block) => /* @__PURE__ */ jsx9("span", { children: renderBlock ? renderBlock(block) : null }, block.id));
|
|
770
|
-
return /* @__PURE__ */ jsx9(
|
|
771
|
-
"section",
|
|
772
|
-
{
|
|
773
|
-
className: cn("w-full", className),
|
|
774
|
-
style: { background, paddingTop: paddingY, paddingBottom: paddingY },
|
|
775
|
-
children: maxWidth ? /* @__PURE__ */ jsx9("div", { style: { maxWidth, marginInline: "auto" }, children: inner }) : inner
|
|
776
|
-
}
|
|
777
|
-
);
|
|
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 };
|
|
778
925
|
}
|
|
779
926
|
|
|
780
|
-
// src/
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
] });
|
|
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
|
+
};
|
|
795
941
|
}
|
|
796
942
|
|
|
797
|
-
// src/
|
|
798
|
-
|
|
799
|
-
function
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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;
|
|
807
970
|
}
|
|
808
971
|
|
|
809
|
-
// src/blocks/
|
|
810
|
-
import {
|
|
811
|
-
|
|
812
|
-
function
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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;
|
|
986
|
+
}
|
|
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
|
+
}
|
|
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
|
|
821
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;
|
|
822
1039
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
|
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 : "" }) });
|
|
843
1049
|
},
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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 : "" });
|
|
1104
|
+
}
|
|
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]);
|
|
1173
|
+
}
|
|
1174
|
+
return runs;
|
|
1175
|
+
}
|
|
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 [];
|
|
848
1201
|
}
|
|
849
1202
|
|
|
850
|
-
// src/blocks/
|
|
851
|
-
import {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1203
|
+
// src/blocks/Text.tsx
|
|
1204
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
1205
|
+
function Text({
|
|
1206
|
+
html,
|
|
1207
|
+
content,
|
|
1208
|
+
align = "left",
|
|
1209
|
+
className = "",
|
|
1210
|
+
"data-bcms-field": field,
|
|
1211
|
+
"data-bcms-kind": kind
|
|
856
1212
|
}) {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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",
|
|
1222
|
+
{
|
|
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
|
+
)
|
|
1229
|
+
}
|
|
1230
|
+
);
|
|
867
1231
|
}
|
|
868
1232
|
export {
|
|
869
1233
|
Button,
|