@bettercms-ai/ui 0.20.1 → 0.23.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/index.js CHANGED
@@ -616,6 +616,24 @@ var PT_TYPES = {
616
616
  pullQuote: "pullQuote",
617
617
  /** Key takeaways — see `PortableTextKeyTakeaways`. The V2 kit's `<aside data-rt="takeaways">`. */
618
618
  keyTakeaways: "keyTakeaways",
619
+ /**
620
+ * A third-party embed (X, CodePen, Figma, Maps, Spotify, Podcasts, audio, a link card) — see
621
+ * `PortableTextEmbed` and `embed.ts`. The V2 kit's `<figure data-rt="embed">`. Gated by the
622
+ * `embed` feature; YouTube and Vimeo links still resolve here too, through the video resolver.
623
+ */
624
+ embed: "embed",
625
+ /** A gallery of images, a grid or a carousel — see `PortableTextGallery`. The V2 kit's `<figure data-rt="gallery">`. */
626
+ gallery: "gallery",
627
+ /** A table of contents built from the headings — see `PortableTextTableOfContents`. The V2 kit's `<nav data-rt="toc">`. */
628
+ tableOfContents: "tableOfContents",
629
+ /** A footnote, an INLINE object in body text — see `PortableTextFootnote`. The V2 kit's `<sup data-rt="fn">`. */
630
+ footnote: "footnote",
631
+ /** Columns of text — see `PortableTextColumns`. The V2 kit's `<div data-rt="columns">`; the legacy `builtin:columns` reads the same way. */
632
+ columns: "columns",
633
+ /** An author bio — see `PortableTextAuthorBio`. The V2 kit's `<aside data-rt="author">`. */
634
+ authorBio: "authorBio",
635
+ /** Related posts — see `PortableTextRelatedPosts`. The V2 kit's `<aside data-rt="related">`. */
636
+ relatedPosts: "relatedPosts",
619
637
  /**
620
638
  * A table. Spelled `table`, unprefixed, because that is what every other Portable Text
621
639
  * producer calls one — the Sanity-shaped `{_type:"table", rows}` this platform used to refuse
@@ -642,7 +660,13 @@ var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
642
660
  PT_TYPES.video,
643
661
  PT_TYPES.file,
644
662
  PT_TYPES.pullQuote,
645
- PT_TYPES.keyTakeaways
663
+ PT_TYPES.keyTakeaways,
664
+ PT_TYPES.embed,
665
+ PT_TYPES.gallery,
666
+ PT_TYPES.tableOfContents,
667
+ PT_TYPES.columns,
668
+ PT_TYPES.authorBio,
669
+ PT_TYPES.relatedPosts
646
670
  ]);
647
671
  var EMBEDDED_COMPONENT_KEY = "component";
648
672
 
@@ -1117,16 +1141,109 @@ function keyTakeawaysView(value) {
1117
1141
  return { title, points };
1118
1142
  }
1119
1143
 
1120
- // ../richtext/src/component-copy.ts
1144
+ // ../richtext/src/image-layout.ts
1145
+ import {
1146
+ focalInCrop,
1147
+ formatImageRect,
1148
+ imageValueUrl,
1149
+ normalizeImageCrop,
1150
+ normalizeImageFocal,
1151
+ objectPosition,
1152
+ parseImageRect
1153
+ } from "@bettercms-ai/image-url";
1154
+ function richTextImageFrame(src, crop, focal) {
1155
+ const c = normalizeImageCrop(crop);
1156
+ const f = normalizeImageFocal(focal);
1157
+ if (!c && !f) return { src, crop: null, focal: null, position: null, data: [] };
1158
+ const drawn = imageValueUrl({ url: src, crop: c, focal: f }) ?? src;
1159
+ const data = [];
1160
+ if (drawn !== src) data.push(["data-src", src]);
1161
+ if (c) data.push(["data-crop", formatImageRect(c)]);
1162
+ if (f) data.push(["data-focal", `${f.x},${f.y}`]);
1163
+ return { src: drawn, crop: c, focal: f, position: f ? objectPosition(focalInCrop(f, c)) : null, data };
1164
+ }
1165
+
1166
+ // ../richtext/src/gallery.ts
1121
1167
  var isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1122
- var fieldsOf = (value) => isRecord3(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord3(value.fields) ? value.fields : null;
1168
+ var galleryMediaSrc = (raw) => safeHref(raw) ?? (raw !== "" && !raw.startsWith("//") && !/[:\s]/.test(raw) ? raw : null);
1169
+ var GALLERY_STYLES = {
1170
+ grid: {
1171
+ ul: "display:grid;grid-template-columns:repeat(auto-fill,minmax(12rem,1fr));gap:1rem;list-style:none;margin:0;padding:0",
1172
+ li: "margin:0",
1173
+ figure: "margin:0",
1174
+ img: "display:block;width:100%;height:auto"
1175
+ },
1176
+ carousel: {
1177
+ ul: "display:flex;gap:1rem;overflow-x:auto;scroll-snap-type:x mandatory;list-style:none;margin:0;padding:0",
1178
+ li: "flex:0 0 100%;scroll-snap-align:start;margin:0",
1179
+ figure: "margin:0",
1180
+ img: "display:block;width:100%;height:auto"
1181
+ }
1182
+ };
1183
+ function galleryView(value) {
1184
+ const v = isRecord3(value) ? value : {};
1185
+ const layout = v.layout === "carousel" ? "carousel" : "grid";
1186
+ const images = [];
1187
+ (Array.isArray(v.images) ? v.images : []).forEach((item, i) => {
1188
+ if (!isRecord3(item) || !isRecord3(item.image)) return;
1189
+ const src = galleryMediaSrc(typeof item.image.src === "string" ? item.image.src.trim() : "");
1190
+ if (!src) return;
1191
+ const frame = richTextImageFrame(src, item.image.crop, item.image.focal);
1192
+ images.push({
1193
+ key: typeof item._key === "string" && item._key ? item._key : `i${i}`,
1194
+ src: frame.src,
1195
+ position: frame.position,
1196
+ data: frame.data,
1197
+ alt: typeof item.image.alt === "string" ? item.image.alt : "",
1198
+ caption: typeof item.caption === "string" ? item.caption.trim() : ""
1199
+ });
1200
+ });
1201
+ return { layout, images };
1202
+ }
1203
+
1204
+ // ../richtext/src/columns.ts
1205
+ var PT_COLUMNS_LIMITS = { min: 2, max: 4 };
1206
+ var BUILTIN_COLUMNS_KEY = "builtin:columns";
1207
+ var isRecord4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1208
+ var COLUMNS_STYLES = {
1209
+ s: { row: "display:flex;flex-wrap:wrap;gap:0.75rem", col: "flex:1 1 14rem;min-width:0" },
1210
+ m: { row: "display:flex;flex-wrap:wrap;gap:1.25rem", col: "flex:1 1 14rem;min-width:0" },
1211
+ l: { row: "display:flex;flex-wrap:wrap;gap:2rem", col: "flex:1 1 14rem;min-width:0" }
1212
+ };
1213
+ var legacyContent = (text, key) => text.split(/\n{2,}/).filter((p) => p.trim() !== "").map((p, i) => ({ _type: "block", _key: `${key}p${i}`, style: "normal", markDefs: [], children: [{ _type: "span", _key: `${key}s${i}`, text: p, marks: [] }] }));
1214
+ function columnsView(node) {
1215
+ if (!isRecord4(node)) return null;
1216
+ if (node._type === PT_TYPES.columns) {
1217
+ const gap = node.gap === "s" || node.gap === "l" ? node.gap : "m";
1218
+ const columns = (Array.isArray(node.columns) ? node.columns : []).map((c, i) => ({
1219
+ key: isRecord4(c) && typeof c._key === "string" && c._key ? c._key : `c${i}`,
1220
+ content: isRecord4(c) && Array.isArray(c.content) ? c.content : []
1221
+ }));
1222
+ return { gap, columns };
1223
+ }
1224
+ if (node._type === PT_TYPES.block && node.schemaKey === BUILTIN_COLUMNS_KEY) {
1225
+ const f = isRecord4(node.fields) ? node.fields : {};
1226
+ if (!Array.isArray(f.columns) || !f.columns.every((c) => typeof c === "string")) return null;
1227
+ const gap = f.gap === "small" ? "s" : f.gap === "large" ? "l" : "m";
1228
+ const columns = (Array.isArray(f.columns) ? f.columns : []).slice(0, PT_COLUMNS_LIMITS.max).map((c, i) => ({
1229
+ key: `c${i}`,
1230
+ content: legacyContent(typeof c === "string" ? c : "", `c${i}`)
1231
+ }));
1232
+ return { gap, columns };
1233
+ }
1234
+ return null;
1235
+ }
1236
+
1237
+ // ../richtext/src/component-copy.ts
1238
+ var isRecord5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1239
+ var fieldsOf = (value) => isRecord5(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord5(value.fields) ? value.fields : null;
1123
1240
  function detachedComponentCopy(value) {
1124
1241
  const f = fieldsOf(value);
1125
1242
  if (!f || f.detached !== true || !Array.isArray(f.blocks)) return null;
1126
1243
  return {
1127
- id: isRecord3(value) && typeof value._key === "string" ? value._key : "",
1244
+ id: isRecord5(value) && typeof value._key === "string" ? value._key : "",
1128
1245
  componentId: typeof f.componentId === "string" ? f.componentId : "",
1129
- props: isRecord3(f.props) ? f.props : {},
1246
+ props: isRecord5(f.props) ? f.props : {},
1130
1247
  blocks: f.blocks,
1131
1248
  ...typeof f.sourcePublishedAt === "string" ? { sourcePublishedAt: f.sourcePublishedAt } : {},
1132
1249
  ...typeof f.variant === "string" ? { variant: f.variant } : {}
@@ -1139,7 +1256,7 @@ function componentCopyLines(copy) {
1139
1256
  const t = v.replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
1140
1257
  return t || null;
1141
1258
  }
1142
- if (isRecord3(v)) return line(v.label ?? v.text ?? v.title);
1259
+ if (isRecord5(v)) return line(v.label ?? v.text ?? v.title);
1143
1260
  return null;
1144
1261
  };
1145
1262
  return Object.values(copy.props).map(line).filter((s) => !!s);
@@ -1166,6 +1283,18 @@ function portableTextToPlainText(value) {
1166
1283
  lines.push(...title ? [title] : [], ...points);
1167
1284
  continue;
1168
1285
  }
1286
+ if (node._type === PT_TYPES.gallery) {
1287
+ lines.push(...galleryView(node).images.map((x) => x.caption).filter(Boolean));
1288
+ continue;
1289
+ }
1290
+ const cols = columnsView(node);
1291
+ if (cols) {
1292
+ for (const c of cols.columns) {
1293
+ const text = portableTextToPlainText(c.content);
1294
+ if (text) lines.push(text);
1295
+ }
1296
+ continue;
1297
+ }
1169
1298
  const table = migrateTable(node);
1170
1299
  if (table) {
1171
1300
  lines.push(...tableLines(table));
@@ -1204,20 +1333,121 @@ var pullQuoteLines = (v) => {
1204
1333
  return [text(v.quote), ...by ? [`\u2014 ${by}`] : []].filter(Boolean);
1205
1334
  };
1206
1335
 
1336
+ // ../richtext/src/toc.ts
1337
+ import { slugify } from "@bettercms-ai/types";
1338
+ var isRecord6 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1339
+ var isHeadingAnchor = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_:.-]{0,127}$/.test(v);
1340
+ var headingText = (node) => (Array.isArray(node.children) ? node.children : []).map((c) => isRecord6(c) && typeof c.text === "string" ? c.text : "").join("").replace(/​/g, "").trim();
1341
+ function outlineOf(value) {
1342
+ const heads = [];
1343
+ const used = /* @__PURE__ */ new Set();
1344
+ for (const node of Array.isArray(value) ? value : []) {
1345
+ if (!isRecord6(node) || node._type !== "block" || node.listItem || node.style !== "h2" && node.style !== "h3") continue;
1346
+ const text = headingText(node);
1347
+ if (!text) continue;
1348
+ const anchor = isHeadingAnchor(node.anchor) && !used.has(node.anchor) ? node.anchor : null;
1349
+ if (anchor) used.add(anchor);
1350
+ heads.push({ node, text, anchor });
1351
+ }
1352
+ const items = [];
1353
+ const nodes = /* @__PURE__ */ new WeakMap();
1354
+ for (const { node, text, anchor } of heads) {
1355
+ let id = anchor;
1356
+ if (!id) {
1357
+ const base = slugify(text);
1358
+ id = base;
1359
+ for (let n = 2; used.has(id); n++) id = `${base}-${n}`;
1360
+ used.add(id);
1361
+ }
1362
+ nodes.set(node, id);
1363
+ items.push({ id, text, depth: node.style === "h2" ? 2 : 3 });
1364
+ }
1365
+ return { items, nodes };
1366
+ }
1367
+ function tocTree(items, depth) {
1368
+ const out = [];
1369
+ for (const it of items) {
1370
+ if (it.depth === 3 && depth === "2") continue;
1371
+ const last = out[out.length - 1];
1372
+ if (it.depth === 3 && last) last.children.push(it);
1373
+ else out.push({ ...it, children: [] });
1374
+ }
1375
+ return out;
1376
+ }
1377
+ function tocView(value) {
1378
+ const v = isRecord6(value) ? value : {};
1379
+ const title = typeof v.title === "string" ? v.title.trim() : "";
1380
+ return { title, label: title || "Table of contents", depth: v.depth === "2" ? "2" : "3" };
1381
+ }
1382
+
1383
+ // ../richtext/src/footnote.ts
1384
+ var isRecord7 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1385
+ function footnoteOrder(value) {
1386
+ const notes = [];
1387
+ const nodes = /* @__PURE__ */ new WeakMap();
1388
+ for (const node of Array.isArray(value) ? value : []) {
1389
+ if (!isRecord7(node) || node._type !== "block" || !Array.isArray(node.children)) continue;
1390
+ for (const child of node.children) {
1391
+ if (!isRecord7(child) || child._type !== PT_TYPES.footnote) continue;
1392
+ nodes.set(child, notes.push(typeof child.note === "string" ? child.note : ""));
1393
+ }
1394
+ }
1395
+ return { notes, nodes };
1396
+ }
1397
+
1398
+ // ../richtext/src/references.ts
1399
+ var PT_RELATED_LIMITS = { titleChars: 40, minCount: 1, maxCount: 6, posts: 6 };
1400
+ var isRecord8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1401
+ var refOf = (v) => isRecord8(v) && typeof v._ref === "string" ? v._ref : "";
1402
+ var imageSrc = (raw) => safeHref(raw) ?? (raw !== "" && !raw.startsWith("//") && !/[:\s]/.test(raw) ? raw : null);
1403
+ function relatedPostsView(value, resolved = {}) {
1404
+ const v = isRecord8(value) ? value : {};
1405
+ const title = typeof v.title === "string" ? v.title.trim() : "";
1406
+ const mode = v.mode === "manual" || v.mode === "newest" ? v.mode : "topic";
1407
+ const n = typeof v.count === "number" && Number.isInteger(v.count) ? v.count : 3;
1408
+ const count = Math.min(PT_RELATED_LIMITS.maxCount, Math.max(PT_RELATED_LIMITS.minCount, n));
1409
+ const picked = (Array.isArray(v.posts) ? v.posts : []).map(refOf).filter(Boolean);
1410
+ const key = typeof v._key === "string" ? v._key : "";
1411
+ const ids = mode === "manual" ? picked : (resolved.related?.[key] ?? []).slice(0, count);
1412
+ const items = [];
1413
+ for (const id of ids) {
1414
+ const post = resolved.posts?.[id];
1415
+ const url = post && typeof post.url === "string" ? safeHref(post.url) : null;
1416
+ if (post && typeof post.title === "string" && url) items.push({ ...post, id, url });
1417
+ }
1418
+ return { key, title, mode, count, picked, items };
1419
+ }
1420
+ function authorBioView(value, resolved = {}) {
1421
+ const key = isRecord8(value) && typeof value._key === "string" ? value._key : "";
1422
+ const id = refOf(isRecord8(value) ? value.author : void 0);
1423
+ const a = id ? resolved.authors?.[id] : void 0;
1424
+ if (!a || typeof a.name !== "string") return { key, id, author: null };
1425
+ const avatar = typeof a.avatar === "string" ? imageSrc(a.avatar.trim()) : null;
1426
+ return {
1427
+ key,
1428
+ id,
1429
+ author: {
1430
+ name: a.name,
1431
+ ...avatar ? { avatar } : {},
1432
+ ...typeof a.bio === "string" && a.bio.trim() ? { bio: a.bio.trim() } : {}
1433
+ }
1434
+ };
1435
+ }
1436
+
1207
1437
  // ../richtext/src/read.ts
1208
- var isRecord4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1438
+ var isRecord9 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1209
1439
  function normalizePortableText(value, key = randomKey) {
1210
1440
  if (!Array.isArray(value)) return [];
1211
1441
  return value.flatMap((raw) => {
1212
- if (!isRecord4(raw) || typeof raw._type !== "string") return [];
1442
+ if (!isRecord9(raw) || typeof raw._type !== "string") return [];
1213
1443
  const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));
1214
1444
  if (table) return [table];
1215
- const node = { ...raw, _key: isRecord4(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
1445
+ const node = { ...raw, _key: isRecord9(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
1216
1446
  if (node._type === "block") {
1217
1447
  if (!Array.isArray(node.markDefs)) node.markDefs = [];
1218
1448
  if (typeof node.style !== "string") node.style = "normal";
1219
1449
  node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {
1220
- if (!isRecord4(c)) return c;
1450
+ if (!isRecord9(c)) return c;
1221
1451
  const child = { ...c, _key: typeof c._key === "string" && c._key ? c._key : key() };
1222
1452
  if (child._type === "span" && !Array.isArray(child.marks)) child.marks = [];
1223
1453
  return child;
@@ -1227,12 +1457,219 @@ function normalizePortableText(value, key = randomKey) {
1227
1457
  });
1228
1458
  }
1229
1459
  function readStructuredContent(props) {
1230
- if (!isRecord4(props)) return null;
1460
+ if (!isRecord9(props)) return null;
1231
1461
  const candidate = props.content ?? props.value;
1232
- const arr = Array.isArray(candidate) ? candidate : isRecord4(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
1462
+ const arr = Array.isArray(candidate) ? candidate : isRecord9(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
1233
1463
  return arr && arr.length > 0 ? arr : null;
1234
1464
  }
1235
1465
 
1466
+ // ../richtext/src/video.ts
1467
+ var videoId = (value) => value && /^[a-zA-Z0-9_-]{6,32}$/.test(value) ? value : null;
1468
+ function resolveVideoSource(value) {
1469
+ if (typeof value !== "string" || !value.trim()) return null;
1470
+ let parsed;
1471
+ try {
1472
+ parsed = new URL(value.trim());
1473
+ } catch {
1474
+ return null;
1475
+ }
1476
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
1477
+ const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
1478
+ let id = null;
1479
+ if (host === "youtu.be") id = videoId(parsed.pathname.split("/").filter(Boolean)[0]);
1480
+ if (host === "youtube.com" || host === "m.youtube.com" || host === "youtube-nocookie.com") {
1481
+ id = videoId(
1482
+ parsed.searchParams.get("v") ?? parsed.pathname.match(/^\/(?:embed|shorts)\/([^/?#]+)/)?.[1]
1483
+ );
1484
+ }
1485
+ if (id) {
1486
+ return {
1487
+ kind: "iframe",
1488
+ src: `https://www.youtube-nocookie.com/embed/${id}`,
1489
+ provider: "YouTube"
1490
+ };
1491
+ }
1492
+ if (host === "vimeo.com" || host === "player.vimeo.com") {
1493
+ const segments = parsed.pathname.split("/").filter(Boolean);
1494
+ let vimeoId = null;
1495
+ for (let i = segments.length - 1; i >= 0; i -= 1) {
1496
+ if (/^\d+$/.test(segments[i])) {
1497
+ vimeoId = segments[i];
1498
+ break;
1499
+ }
1500
+ }
1501
+ if (vimeoId) {
1502
+ return {
1503
+ kind: "iframe",
1504
+ src: `https://player.vimeo.com/video/${vimeoId}`,
1505
+ provider: "Vimeo"
1506
+ };
1507
+ }
1508
+ }
1509
+ if (/\.(?:m4v|mp4|og[gv]|webm)$/i.test(parsed.pathname)) {
1510
+ return { kind: "video", src: parsed.toString(), provider: "Video file" };
1511
+ }
1512
+ return null;
1513
+ }
1514
+
1515
+ // ../richtext/src/embed.ts
1516
+ var EMBED_ASPECTS = ["16:9", "4:3", "1:1", "9:16"];
1517
+ var isEmbedAspect = (v) => EMBED_ASPECTS.includes(v);
1518
+ function detectEmbed(url) {
1519
+ const u = url.trim();
1520
+ let host = "";
1521
+ try {
1522
+ host = new URL(u).hostname.replace(/^www\./, "");
1523
+ } catch {
1524
+ host = "";
1525
+ }
1526
+ const is = (re) => re.test(u);
1527
+ if (is(/(?:youtube\.com\/(?:watch\?|embed\/|shorts\/|live\/)|youtu\.be\/)/i)) return { provider: "youtube", label: "YouTube", host };
1528
+ if (is(/vimeo\.com\/(?:video\/)?\d+/i)) return { provider: "vimeo", label: "Vimeo", host };
1529
+ if (is(/(?:twitter|x)\.com\/[^/]+\/status\/\d+/i)) return { provider: "x", label: "X post", host };
1530
+ if (is(/codepen\.io\/[^/]+\/(?:pen|embed)\//i)) return { provider: "codepen", label: "CodePen", host };
1531
+ if (is(/figma\.com\/(?:file|design|proto|board)\//i)) return { provider: "figma", label: "Figma", host };
1532
+ if (is(/(?:google\.[a-z.]+\/maps|maps\.app\.goo\.gl|goo\.gl\/maps)/i)) return { provider: "maps", label: "Google Maps", host };
1533
+ if (is(/open\.spotify\.com\/(?:episode|track|show|playlist|album)\//i)) return { provider: "spotify", label: "Spotify", host };
1534
+ if (is(/podcasts\.apple\.com\//i)) return { provider: "podcast", label: "Apple Podcasts", host };
1535
+ if (is(/\.(?:mp3|m4a|wav|ogg|aac)(?:\?|$)/i)) return { provider: "audio", label: "Audio", host };
1536
+ return { provider: "link", label: host || "Link", host };
1537
+ }
1538
+ function detectMore(url) {
1539
+ const host = url.hostname.toLowerCase().replace(/^www\./, "");
1540
+ const path = url.pathname;
1541
+ if (host === "youtube-nocookie.com" && path.startsWith("/embed/")) return { provider: "youtube", label: "YouTube" };
1542
+ if (host === "open.spotify.com" && /^\/embed\/(?:episode|track|show|playlist|album)\//.test(path)) return { provider: "spotify", label: "Spotify" };
1543
+ if (host === "calendly.com") return { provider: "calendly", label: "Calendly" };
1544
+ if (host.endsWith(".typeform.com") && /^\/to\//.test(path)) return { provider: "typeform", label: "Typeform" };
1545
+ if (host === "loom.com" && /^\/(?:share|embed)\//.test(path)) return { provider: "loom", label: "Loom" };
1546
+ if (host === "airtable.com") return { provider: "airtable", label: "Airtable" };
1547
+ if (host === "docs.google.com") {
1548
+ const kind = path.split("/")[1];
1549
+ const label = { forms: "Google Forms", document: "Google Docs", spreadsheets: "Google Sheets", presentation: "Google Slides" }[kind ?? ""];
1550
+ if (label) return { provider: "google", label };
1551
+ }
1552
+ return null;
1553
+ }
1554
+ var FRAME_SIZE = {
1555
+ "16:9": ["640", "360"],
1556
+ "4:3": ["640", "480"],
1557
+ "1:1": ["640", "640"],
1558
+ "9:16": ["360", "640"]
1559
+ };
1560
+ var ID = /^[A-Za-z0-9_-]{1,64}$/;
1561
+ var FULL_WIDTH_HEIGHT = { calendly: "700", typeform: "500", airtable: "533" };
1562
+ function frameOf(provider, url) {
1563
+ const parts = url.pathname.split("/").filter(Boolean);
1564
+ switch (provider) {
1565
+ case "youtube":
1566
+ case "vimeo": {
1567
+ const video = resolveVideoSource(url.href);
1568
+ return video?.kind === "iframe" ? { src: video.src } : null;
1569
+ }
1570
+ case "codepen": {
1571
+ const [user, mode, id] = parts;
1572
+ return user && ID.test(user) && (mode === "pen" || mode === "embed") && id && ID.test(id) ? { src: `https://codepen.io/${user}/embed/${id}?default-tab=result` } : null;
1573
+ }
1574
+ case "figma": {
1575
+ const [mode, key] = parts;
1576
+ if (!mode || !["file", "design", "proto", "board"].includes(mode) || !key || !ID.test(key)) return null;
1577
+ const frame = new URL(`https://embed.figma.com/${mode}/${key}`);
1578
+ const node = url.searchParams.get("node-id");
1579
+ if (node && /^[0-9A-Za-z:_-]{1,64}$/.test(node)) frame.searchParams.set("node-id", node);
1580
+ frame.searchParams.set("embed-host", "bettercms");
1581
+ return { src: frame.href };
1582
+ }
1583
+ case "maps": {
1584
+ if (!/^(?:www\.)?google\.[a-z.]+$/i.test(url.hostname)) return null;
1585
+ const pb = url.searchParams.get("pb");
1586
+ if (parts[0] === "maps" && parts[1] === "embed" && pb && /^[!A-Za-z0-9._:%+-]{1,2000}$/.test(pb)) {
1587
+ return { src: `https://www.google.com/maps/embed?pb=${encodeURIComponent(pb)}` };
1588
+ }
1589
+ const at = /@(-?\d{1,3}(?:\.\d+)?),(-?\d{1,3}(?:\.\d+)?)/.exec(url.pathname);
1590
+ const place = parts[0] === "maps" && parts[1] === "place" && parts[2] ? decodeURIComponent(parts[2]).replace(/\+/g, " ") : null;
1591
+ const q = at ? `${at[1]},${at[2]}` : url.searchParams.get("q") ?? place;
1592
+ if (!q || q.length > 200) return null;
1593
+ return { src: `https://maps.google.com/maps?q=${encodeURIComponent(q)}&output=embed` };
1594
+ }
1595
+ case "spotify": {
1596
+ const [type, id] = parts[0] === "embed" ? parts.slice(1) : parts;
1597
+ if (!type || !["episode", "track", "show", "playlist", "album"].includes(type) || !id || !ID.test(id)) return null;
1598
+ return { src: `https://open.spotify.com/embed/${type}/${id}`, height: type === "track" || type === "episode" ? "152" : "352" };
1599
+ }
1600
+ case "podcast":
1601
+ return (url.hostname === "podcasts.apple.com" || url.hostname === "embed.podcasts.apple.com") && parts.length >= 2 ? { src: `https://embed.podcasts.apple.com${url.pathname}${url.search}`, height: url.searchParams.has("i") ? "175" : "450" } : null;
1602
+ case "calendly":
1603
+ return parts.length >= 1 && parts.length <= 2 && parts.every((p) => ID.test(p)) ? { src: `https://calendly.com/${parts.join("/")}?embed_type=Inline&hide_gdpr_banner=1` } : null;
1604
+ case "typeform": {
1605
+ const id = parts[0] === "to" ? parts[1] : void 0;
1606
+ return id && /^[A-Za-z0-9]{4,32}$/.test(id) ? { src: `https://form.typeform.com/to/${id}` } : null;
1607
+ }
1608
+ case "loom": {
1609
+ const id = parts[1];
1610
+ return (parts[0] === "share" || parts[0] === "embed") && id && /^[0-9a-f]{32}$/.test(id) ? { src: `https://www.loom.com/embed/${id}` } : null;
1611
+ }
1612
+ case "airtable": {
1613
+ const ids = parts[0] === "embed" ? parts.slice(1) : parts;
1614
+ return ids.length >= 1 && ids.length <= 3 && ids.every((p) => /^(?:app|shr|tbl|viw)[A-Za-z0-9]{14}$/.test(p)) && ids.some((p) => p.startsWith("shr")) ? { src: `https://airtable.com/embed/${ids.join("/")}` } : null;
1615
+ }
1616
+ case "google": {
1617
+ const [kind, d, maybeE, maybeId] = parts;
1618
+ if (d !== "d") return null;
1619
+ const published = maybeE === "e";
1620
+ const id = published ? maybeId : maybeE;
1621
+ if (!id || !/^[A-Za-z0-9_-]{10,128}$/.test(id)) return null;
1622
+ const base = `https://docs.google.com/${kind}/d/${published ? `e/${id}` : id}`;
1623
+ if (kind === "forms") return { src: `${base}/viewform?embedded=true`, height: "800" };
1624
+ if (kind === "document") return { src: published ? `${base}/pub?embedded=true` : `${base}/preview`, height: "600" };
1625
+ if (kind === "spreadsheets") return { src: published ? `${base}/pubhtml?widget=true&headers=false` : `${base}/preview`, height: "600" };
1626
+ if (kind === "presentation") return { src: published ? `${base}/embed` : `${base}/preview` };
1627
+ return null;
1628
+ }
1629
+ default:
1630
+ return null;
1631
+ }
1632
+ }
1633
+ var X_STATUS = /^\/([A-Za-z0-9_]{1,15})\/status\/(\d{1,25})(?:\/|$)/;
1634
+ var X_HOSTS = /^(?:www\.|mobile\.)?(?:twitter|x)\.com$/i;
1635
+ function canonicalEmbedUrl(raw) {
1636
+ const url = typeof raw === "string" ? raw.trim() : "";
1637
+ const safe = url ? safeHref(url) : null;
1638
+ if (!safe || !/^https?:\/\//i.test(safe)) return null;
1639
+ let parsed;
1640
+ try {
1641
+ parsed = new URL(safe);
1642
+ } catch {
1643
+ return null;
1644
+ }
1645
+ const status = X_HOSTS.test(parsed.hostname) ? X_STATUS.exec(parsed.pathname) : null;
1646
+ if (status) return `https://x.com/${status[1].toLowerCase()}/status/${status[2]}`;
1647
+ parsed.hash = "";
1648
+ return parsed.href;
1649
+ }
1650
+ function resolveEmbed(rawUrl, aspect = "16:9") {
1651
+ const url = typeof rawUrl === "string" ? rawUrl.trim() : "";
1652
+ const v2 = detectEmbed(url);
1653
+ const safe = url ? safeHref(url) : null;
1654
+ let parsed = null;
1655
+ try {
1656
+ parsed = safe && /^https?:\/\//i.test(safe) ? new URL(safe) : null;
1657
+ } catch {
1658
+ parsed = null;
1659
+ }
1660
+ const { provider, label } = v2.provider === "link" && parsed ? detectMore(parsed) ?? v2 : v2;
1661
+ const link = { kind: "link", provider, label, url: safe };
1662
+ if (!parsed) return link;
1663
+ if (provider === "x") return { kind: "card", provider: "x", label, url: canonicalEmbedUrl(parsed.href) ?? parsed.href };
1664
+ if (provider === "audio") return parsed.protocol === "https:" ? { kind: "audio", provider: "audio", label, src: parsed.href } : link;
1665
+ const frame = frameOf(provider, parsed);
1666
+ if (!frame) return link;
1667
+ const height = frame.height ?? FULL_WIDTH_HEIGHT[provider];
1668
+ if (height) return { kind: "frame", provider, label, src: frame.src, width: "100%", height };
1669
+ const [width, h] = FRAME_SIZE[isEmbedAspect(aspect) ? aspect : "16:9"];
1670
+ return { kind: "frame", provider, label, src: frame.src, width, height: h };
1671
+ }
1672
+
1236
1673
  // src/blocks/portableText.tsx
1237
1674
  import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1238
1675
  var TOKEN = /^[a-z0-9-]+$/i;
@@ -1364,8 +1801,119 @@ function Table({ value }) {
1364
1801
  ] }) : notes ? /* @__PURE__ */ jsx14("p", { "data-notes": "", id: notesId, children: notes }) : null
1365
1802
  ] }) : table;
1366
1803
  }
1367
- function buildComponents(renderBlock) {
1368
- return {
1804
+ function CheckItem({ checked, address, children }) {
1805
+ return /* @__PURE__ */ jsxs11("li", { ...address, "data-checked": String(checked), children: [
1806
+ /* @__PURE__ */ jsx14("input", { type: "checkbox", disabled: true, defaultChecked: checked }),
1807
+ children
1808
+ ] });
1809
+ }
1810
+ function XCard({ url, card }) {
1811
+ return /* @__PURE__ */ jsxs11("blockquote", { className: "twitter-tweet bcms-embed-card", cite: url, children: [
1812
+ /* @__PURE__ */ jsx14("p", { children: card.text }),
1813
+ "\u2014 ",
1814
+ card.authorName,
1815
+ " ",
1816
+ /* @__PURE__ */ jsx14("a", { href: url, children: card.date || "View on X" })
1817
+ ] });
1818
+ }
1819
+ function RelatedPosts({ value, resolved }) {
1820
+ const { key, title, mode, count, picked, items } = relatedPostsView(value, resolved);
1821
+ if (!resolved) {
1822
+ return /* @__PURE__ */ jsx14("aside", { "data-rt": "related", "data-key": key, "data-mode": mode, "data-count": count, "data-posts": picked.join(","), "data-title": title || void 0 });
1823
+ }
1824
+ if (!items.length) return null;
1825
+ return /* @__PURE__ */ jsxs11("aside", { "data-rt": "related", "data-mode": mode, "data-count": count, "data-posts": picked.join(","), children: [
1826
+ title ? /* @__PURE__ */ jsx14("p", { "data-title": "", children: title }) : null,
1827
+ items.length ? /* @__PURE__ */ jsx14("ul", { children: items.map((p) => /* @__PURE__ */ jsx14("li", { children: /* @__PURE__ */ jsx14("a", { href: p.url, children: p.title }) }, p.id)) }) : null
1828
+ ] });
1829
+ }
1830
+ function AuthorBio({ value, resolved }) {
1831
+ const { key, id, author } = authorBioView(value, resolved);
1832
+ if (!resolved) return /* @__PURE__ */ jsx14("aside", { "data-rt": "author", "data-key": key, "data-author": id });
1833
+ if (!author) return null;
1834
+ return /* @__PURE__ */ jsxs11("aside", { "data-rt": "author", "data-author": id, children: [
1835
+ author?.avatar ? /* @__PURE__ */ jsx14("img", { src: author.avatar, alt: "" }) : null,
1836
+ author ? /* @__PURE__ */ jsx14("p", { "data-name": "", children: author.name }) : null,
1837
+ author?.bio ? /* @__PURE__ */ jsx14("p", { "data-bio": "", children: author.bio }) : null
1838
+ ] });
1839
+ }
1840
+ function Columns2({ value, components }) {
1841
+ const view = columnsView(value);
1842
+ if (!view) return null;
1843
+ const s = COLUMNS_STYLES[view.gap];
1844
+ return /* @__PURE__ */ jsx14("div", { "data-rt": "columns", "data-gap": view.gap, "data-cols": view.columns.length, style: styleObject(s.row), children: view.columns.map((c) => /* @__PURE__ */ jsx14("div", { "data-col": "true", style: styleObject(s.col), children: c.content.length ? /* @__PURE__ */ jsx14(PT, { value: c.content, components }) : null }, c.key)) });
1845
+ }
1846
+ function FootnoteMark({ value, footnotes }) {
1847
+ const note = typeof value.note === "string" ? value.note : "";
1848
+ const n = footnotes.nodes.get(value);
1849
+ if (!n) return /* @__PURE__ */ jsx14("sup", { "data-rt": "fn", "data-note": note });
1850
+ return /* @__PURE__ */ jsx14("sup", { "data-rt": "fn", id: `fnref-${n}`, "data-note": note, children: /* @__PURE__ */ jsx14("a", { href: `#fn-${n}`, "aria-label": `Footnote ${n}`, children: n }) });
1851
+ }
1852
+ function FootnoteList({ notes }) {
1853
+ if (!notes.length) return null;
1854
+ return /* @__PURE__ */ jsx14("ol", { className: "cs-footnotes", "data-rt": "footnotes", "aria-label": "Footnotes", children: notes.map((note, i) => /* @__PURE__ */ jsxs11("li", { id: `fn-${i + 1}`, children: [
1855
+ note,
1856
+ " ",
1857
+ /* @__PURE__ */ jsx14("a", { href: `#fnref-${i + 1}`, "aria-label": `Back to reference ${i + 1}`, children: "\u21A9" })
1858
+ ] }, i)) });
1859
+ }
1860
+ function Toc({ value, outline }) {
1861
+ const { title, label, depth } = tocView(value);
1862
+ const link = (o) => /* @__PURE__ */ jsx14("a", { href: `#${o.id}`, children: o.text });
1863
+ return /* @__PURE__ */ jsxs11("nav", { "data-rt": "toc", "data-depth": depth, "aria-label": label, children: [
1864
+ title ? /* @__PURE__ */ jsx14("p", { "data-title": "", children: title }) : null,
1865
+ /* @__PURE__ */ jsx14("ol", { children: tocTree(outline.items, depth).map((e) => /* @__PURE__ */ jsxs11("li", { children: [
1866
+ link(e),
1867
+ e.children.length ? /* @__PURE__ */ jsx14("ol", { children: e.children.map((c) => /* @__PURE__ */ jsx14("li", { children: link(c) }, c.id)) }) : null
1868
+ ] }, e.id)) })
1869
+ ] });
1870
+ }
1871
+ function Gallery({ value }) {
1872
+ const { layout, images } = galleryView(value);
1873
+ const s = GALLERY_STYLES[layout];
1874
+ return /* @__PURE__ */ jsx14("figure", { "data-rt": "gallery", "data-layout": layout, children: /* @__PURE__ */ jsx14("ul", { style: styleObject(s.ul), tabIndex: layout === "carousel" ? 0 : void 0, children: images.map((x) => /* @__PURE__ */ jsx14("li", { style: styleObject(s.li), children: /* @__PURE__ */ jsxs11("figure", { style: styleObject(s.figure), children: [
1875
+ /* @__PURE__ */ jsx14(
1876
+ "img",
1877
+ {
1878
+ alt: x.alt,
1879
+ style: styleObject(x.position ? `${s.img};object-position:${x.position}` : s.img),
1880
+ loading: "lazy",
1881
+ ...Object.fromEntries(x.data),
1882
+ src: x.src
1883
+ }
1884
+ ),
1885
+ x.caption ? /* @__PURE__ */ jsx14("figcaption", { children: x.caption }) : null
1886
+ ] }) }, x.key)) }) });
1887
+ }
1888
+ function Embed({ value, embeds }) {
1889
+ const v = value ?? {};
1890
+ const url = typeof v.url === "string" ? v.url.trim() : "";
1891
+ const aspect = isEmbedAspect(v.aspect) ? v.aspect : "16:9";
1892
+ const title = typeof v.title === "string" ? v.title.trim() : "";
1893
+ const r = resolveEmbed(url, aspect);
1894
+ const label = title || r.label;
1895
+ const card = r.kind === "card" ? embeds.cards?.[r.url] : void 0;
1896
+ let body = null;
1897
+ if (r.kind === "frame") {
1898
+ body = /* @__PURE__ */ jsx14("iframe", { src: r.src, title: label, width: r.width, height: r.height, loading: "lazy", referrerPolicy: "strict-origin-when-cross-origin", allowFullScreen: true });
1899
+ } else if (r.kind === "audio") {
1900
+ body = /* @__PURE__ */ jsx14("audio", { controls: true, preload: "none", src: r.src, title: label });
1901
+ } else if (r.kind === "card" && card) {
1902
+ body = /* @__PURE__ */ jsx14(XCard, { url: r.url, card });
1903
+ } else if (r.url) {
1904
+ body = /* @__PURE__ */ jsx14("a", { href: r.url, children: label });
1905
+ }
1906
+ return /* @__PURE__ */ jsx14("figure", { "data-rt": "embed", "data-url": url, "data-provider": r.provider, "data-aspect": aspect, "aria-label": title, children: body });
1907
+ }
1908
+ function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes: /* @__PURE__ */ new WeakMap() }, footnotes = { notes: [], nodes: /* @__PURE__ */ new WeakMap() }, resolved) {
1909
+ const components = {
1910
+ // RT5: every top-level h2 and h3 carries its outline id, as html.ts writes it.
1911
+ block: {
1912
+ h2: ({ value, children }) => /* @__PURE__ */ jsx14("h2", { id: outline.nodes.get(value), children }),
1913
+ h3: ({ value, children }) => /* @__PURE__ */ jsx14("h3", { id: outline.nodes.get(value), children })
1914
+ },
1915
+ list: { check: ({ children }) => /* @__PURE__ */ jsx14("ul", { "data-task": "true", children }) },
1916
+ listItem: { check: ({ value, children }) => /* @__PURE__ */ jsx14(CheckItem, { checked: value.checked === true, children }) },
1369
1917
  types: {
1370
1918
  [PT_TYPES.divider]: () => /* @__PURE__ */ jsx14("hr", {}),
1371
1919
  [PT_TYPES.table]: ({ value }) => /* @__PURE__ */ jsx14(Table, { value }),
@@ -1386,6 +1934,7 @@ function buildComponents(renderBlock) {
1386
1934
  if (rendered !== void 0) return /* @__PURE__ */ jsx14(Fragment2, { children: rendered });
1387
1935
  const builtIn = calloutOrToggle(value);
1388
1936
  if (builtIn) return builtIn;
1937
+ if (columnsView(value)) return /* @__PURE__ */ jsx14(Columns2, { value, components });
1389
1938
  const copy = detachedComponentCopy(value);
1390
1939
  if (copy) {
1391
1940
  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 }) });
@@ -1415,6 +1964,12 @@ function buildComponents(renderBlock) {
1415
1964
  ] }) : null
1416
1965
  ] });
1417
1966
  },
1967
+ [PT_TYPES.embed]: ({ value }) => /* @__PURE__ */ jsx14(Embed, { value, embeds }),
1968
+ [PT_TYPES.gallery]: ({ value }) => /* @__PURE__ */ jsx14(Gallery, { value }),
1969
+ [PT_TYPES.tableOfContents]: ({ value }) => /* @__PURE__ */ jsx14(Toc, { value, outline }),
1970
+ [PT_TYPES.columns]: ({ value }) => /* @__PURE__ */ jsx14(Columns2, { value, components }),
1971
+ [PT_TYPES.relatedPosts]: ({ value }) => /* @__PURE__ */ jsx14(RelatedPosts, { value, resolved }),
1972
+ [PT_TYPES.authorBio]: ({ value }) => /* @__PURE__ */ jsx14(AuthorBio, { value, resolved }),
1418
1973
  /**
1419
1974
  * Key takeaways — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's
1420
1975
  * `<aside data-rt="takeaways">`, read through the one `keyTakeawaysView` both renderers share.
@@ -1427,6 +1982,7 @@ function buildComponents(renderBlock) {
1427
1982
  /* @__PURE__ */ jsx14("ul", { children: points.map((p, i) => /* @__PURE__ */ jsx14("li", { children: p }, i)) })
1428
1983
  ] });
1429
1984
  },
1985
+ [PT_TYPES.footnote]: ({ value }) => /* @__PURE__ */ jsx14(FootnoteMark, { value, footnotes }),
1430
1986
  [PT_TYPES.mention]: ({ value }) => {
1431
1987
  const v = value;
1432
1988
  return /* @__PURE__ */ jsx14("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
@@ -1442,7 +1998,7 @@ function buildComponents(renderBlock) {
1442
1998
  const href = typeof v.href === "string" ? safeHref(v.href) : null;
1443
1999
  if (!href) return /* @__PURE__ */ jsx14(Fragment2, { children });
1444
2000
  const blank = v.target === "_blank";
1445
- return /* @__PURE__ */ jsx14("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
2001
+ return /* @__PURE__ */ jsx14("a", { ...v.button === true ? { "data-button": "true" } : {}, href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
1446
2002
  },
1447
2003
  /**
1448
2004
  * The site's own inline markup, rendered as itself — the React half of
@@ -1491,6 +2047,7 @@ function buildComponents(renderBlock) {
1491
2047
  unknownType: ({ children }) => /* @__PURE__ */ jsx14(Fragment2, { children: children ?? null }),
1492
2048
  unknownBlockStyle: ({ children }) => /* @__PURE__ */ jsx14("p", { children })
1493
2049
  };
2050
+ return components;
1494
2051
  }
1495
2052
  var isListItem = (node) => node._type === "block" && typeof node.listItem === "string" && !!node.listItem;
1496
2053
  function runsOf(blocks) {
@@ -1531,22 +2088,42 @@ function calloutOrToggle(value) {
1531
2088
  }
1532
2089
  return null;
1533
2090
  }
1534
- function PortableText({ value, bindingBase, renderBlock }) {
2091
+ function PortableText({ value, bindingBase, renderBlock, embeds, resolved }) {
1535
2092
  const blocks = React6.useMemo(() => normalizePortableText(readValue(value)), [value]);
1536
- const components = React6.useMemo(() => buildComponents(renderBlock), [renderBlock]);
2093
+ const outline = React6.useMemo(() => outlineOf(blocks), [blocks]);
2094
+ const footnotes = React6.useMemo(() => footnoteOrder(blocks), [blocks]);
2095
+ const components = React6.useMemo(
2096
+ () => buildComponents(renderBlock, embeds, outline, footnotes, resolved),
2097
+ [renderBlock, embeds, outline, footnotes, resolved]
2098
+ );
1537
2099
  const listComponents = React6.useMemo(() => {
1538
2100
  if (!bindingBase) return null;
1539
2101
  const keys = new Set(blocks.map((b) => b._key));
1540
2102
  return {
1541
2103
  ...components,
1542
- 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 })
2104
+ listItem: ({ value: item, children }) => {
2105
+ const address = keys.has(item._key) ? { "data-bcms-field": `${bindingBase}#${item._key}`, "data-bcms-kind": "richtext" } : void 0;
2106
+ if (item.listItem === "check") {
2107
+ return /* @__PURE__ */ jsx14(CheckItem, { checked: item.checked === true, address, children });
2108
+ }
2109
+ return /* @__PURE__ */ jsx14("li", { ...address, children });
2110
+ }
1543
2111
  };
1544
2112
  }, [bindingBase, blocks, components]);
1545
2113
  if (blocks.length === 0) return null;
1546
- if (!bindingBase || !listComponents) return /* @__PURE__ */ jsx14(PT, { value: blocks, components });
1547
- return /* @__PURE__ */ jsx14(Fragment2, { children: runsOf(blocks).map(
1548
- (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)
1549
- ) });
2114
+ const notes = /* @__PURE__ */ jsx14(FootnoteList, { notes: footnotes.notes });
2115
+ if (!bindingBase || !listComponents) {
2116
+ return /* @__PURE__ */ jsxs11(Fragment2, { children: [
2117
+ /* @__PURE__ */ jsx14(PT, { value: blocks, components }),
2118
+ notes
2119
+ ] });
2120
+ }
2121
+ return /* @__PURE__ */ jsxs11(Fragment2, { children: [
2122
+ runsOf(blocks).map(
2123
+ (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)
2124
+ ),
2125
+ notes
2126
+ ] });
1550
2127
  }
1551
2128
  function readValue(value) {
1552
2129
  if (Array.isArray(value)) return value;