@bettercms-ai/ui 0.22.0 → 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/registry.js CHANGED
@@ -193,6 +193,14 @@ var PT_TYPES = {
193
193
  gallery: "gallery",
194
194
  /** A table of contents built from the headings — see `PortableTextTableOfContents`. The V2 kit's `<nav data-rt="toc">`. */
195
195
  tableOfContents: "tableOfContents",
196
+ /** A footnote, an INLINE object in body text — see `PortableTextFootnote`. The V2 kit's `<sup data-rt="fn">`. */
197
+ footnote: "footnote",
198
+ /** Columns of text — see `PortableTextColumns`. The V2 kit's `<div data-rt="columns">`; the legacy `builtin:columns` reads the same way. */
199
+ columns: "columns",
200
+ /** An author bio — see `PortableTextAuthorBio`. The V2 kit's `<aside data-rt="author">`. */
201
+ authorBio: "authorBio",
202
+ /** Related posts — see `PortableTextRelatedPosts`. The V2 kit's `<aside data-rt="related">`. */
203
+ relatedPosts: "relatedPosts",
196
204
  /**
197
205
  * A table. Spelled `table`, unprefixed, because that is what every other Portable Text
198
206
  * producer calls one — the Sanity-shaped `{_type:"table", rows}` this platform used to refuse
@@ -222,7 +230,10 @@ var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
222
230
  PT_TYPES.keyTakeaways,
223
231
  PT_TYPES.embed,
224
232
  PT_TYPES.gallery,
225
- PT_TYPES.tableOfContents
233
+ PT_TYPES.tableOfContents,
234
+ PT_TYPES.columns,
235
+ PT_TYPES.authorBio,
236
+ PT_TYPES.relatedPosts
226
237
  ]);
227
238
  var EMBEDDED_COMPONENT_KEY = "component";
228
239
 
@@ -697,6 +708,28 @@ function keyTakeawaysView(value) {
697
708
  return { title, points };
698
709
  }
699
710
 
711
+ // ../richtext/src/image-layout.ts
712
+ import {
713
+ focalInCrop,
714
+ formatImageRect,
715
+ imageValueUrl,
716
+ normalizeImageCrop,
717
+ normalizeImageFocal,
718
+ objectPosition,
719
+ parseImageRect
720
+ } from "@bettercms-ai/image-url";
721
+ function richTextImageFrame(src, crop, focal) {
722
+ const c = normalizeImageCrop(crop);
723
+ const f = normalizeImageFocal(focal);
724
+ if (!c && !f) return { src, crop: null, focal: null, position: null, data: [] };
725
+ const drawn = imageValueUrl({ url: src, crop: c, focal: f }) ?? src;
726
+ const data = [];
727
+ if (drawn !== src) data.push(["data-src", src]);
728
+ if (c) data.push(["data-crop", formatImageRect(c)]);
729
+ if (f) data.push(["data-focal", `${f.x},${f.y}`]);
730
+ return { src: drawn, crop: c, focal: f, position: f ? objectPosition(focalInCrop(f, c)) : null, data };
731
+ }
732
+
700
733
  // ../richtext/src/gallery.ts
701
734
  var isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
702
735
  var galleryMediaSrc = (raw) => safeHref(raw) ?? (raw !== "" && !raw.startsWith("//") && !/[:\s]/.test(raw) ? raw : null);
@@ -722,9 +755,12 @@ function galleryView(value) {
722
755
  if (!isRecord3(item) || !isRecord3(item.image)) return;
723
756
  const src = galleryMediaSrc(typeof item.image.src === "string" ? item.image.src.trim() : "");
724
757
  if (!src) return;
758
+ const frame = richTextImageFrame(src, item.image.crop, item.image.focal);
725
759
  images.push({
726
760
  key: typeof item._key === "string" && item._key ? item._key : `i${i}`,
727
- src,
761
+ src: frame.src,
762
+ position: frame.position,
763
+ data: frame.data,
728
764
  alt: typeof item.image.alt === "string" ? item.image.alt : "",
729
765
  caption: typeof item.caption === "string" ? item.caption.trim() : ""
730
766
  });
@@ -732,16 +768,49 @@ function galleryView(value) {
732
768
  return { layout, images };
733
769
  }
734
770
 
735
- // ../richtext/src/component-copy.ts
771
+ // ../richtext/src/columns.ts
772
+ var PT_COLUMNS_LIMITS = { min: 2, max: 4 };
773
+ var BUILTIN_COLUMNS_KEY = "builtin:columns";
736
774
  var isRecord4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
737
- var fieldsOf = (value) => isRecord4(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord4(value.fields) ? value.fields : null;
775
+ var COLUMNS_STYLES = {
776
+ s: { row: "display:flex;flex-wrap:wrap;gap:0.75rem", col: "flex:1 1 14rem;min-width:0" },
777
+ m: { row: "display:flex;flex-wrap:wrap;gap:1.25rem", col: "flex:1 1 14rem;min-width:0" },
778
+ l: { row: "display:flex;flex-wrap:wrap;gap:2rem", col: "flex:1 1 14rem;min-width:0" }
779
+ };
780
+ 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: [] }] }));
781
+ function columnsView(node) {
782
+ if (!isRecord4(node)) return null;
783
+ if (node._type === PT_TYPES.columns) {
784
+ const gap = node.gap === "s" || node.gap === "l" ? node.gap : "m";
785
+ const columns = (Array.isArray(node.columns) ? node.columns : []).map((c, i) => ({
786
+ key: isRecord4(c) && typeof c._key === "string" && c._key ? c._key : `c${i}`,
787
+ content: isRecord4(c) && Array.isArray(c.content) ? c.content : []
788
+ }));
789
+ return { gap, columns };
790
+ }
791
+ if (node._type === PT_TYPES.block && node.schemaKey === BUILTIN_COLUMNS_KEY) {
792
+ const f = isRecord4(node.fields) ? node.fields : {};
793
+ if (!Array.isArray(f.columns) || !f.columns.every((c) => typeof c === "string")) return null;
794
+ const gap = f.gap === "small" ? "s" : f.gap === "large" ? "l" : "m";
795
+ const columns = (Array.isArray(f.columns) ? f.columns : []).slice(0, PT_COLUMNS_LIMITS.max).map((c, i) => ({
796
+ key: `c${i}`,
797
+ content: legacyContent(typeof c === "string" ? c : "", `c${i}`)
798
+ }));
799
+ return { gap, columns };
800
+ }
801
+ return null;
802
+ }
803
+
804
+ // ../richtext/src/component-copy.ts
805
+ var isRecord5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
806
+ var fieldsOf = (value) => isRecord5(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord5(value.fields) ? value.fields : null;
738
807
  function detachedComponentCopy(value) {
739
808
  const f = fieldsOf(value);
740
809
  if (!f || f.detached !== true || !Array.isArray(f.blocks)) return null;
741
810
  return {
742
- id: isRecord4(value) && typeof value._key === "string" ? value._key : "",
811
+ id: isRecord5(value) && typeof value._key === "string" ? value._key : "",
743
812
  componentId: typeof f.componentId === "string" ? f.componentId : "",
744
- props: isRecord4(f.props) ? f.props : {},
813
+ props: isRecord5(f.props) ? f.props : {},
745
814
  blocks: f.blocks,
746
815
  ...typeof f.sourcePublishedAt === "string" ? { sourcePublishedAt: f.sourcePublishedAt } : {},
747
816
  ...typeof f.variant === "string" ? { variant: f.variant } : {}
@@ -754,7 +823,7 @@ function componentCopyLines(copy) {
754
823
  const t = v.replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
755
824
  return t || null;
756
825
  }
757
- if (isRecord4(v)) return line(v.label ?? v.text ?? v.title);
826
+ if (isRecord5(v)) return line(v.label ?? v.text ?? v.title);
758
827
  return null;
759
828
  };
760
829
  return Object.values(copy.props).map(line).filter((s) => !!s);
@@ -785,6 +854,14 @@ function portableTextToPlainText(value) {
785
854
  lines.push(...galleryView(node).images.map((x) => x.caption).filter(Boolean));
786
855
  continue;
787
856
  }
857
+ const cols = columnsView(node);
858
+ if (cols) {
859
+ for (const c of cols.columns) {
860
+ const text = portableTextToPlainText(c.content);
861
+ if (text) lines.push(text);
862
+ }
863
+ continue;
864
+ }
788
865
  const table = migrateTable(node);
789
866
  if (table) {
790
867
  lines.push(...tableLines(table));
@@ -825,20 +902,30 @@ var pullQuoteLines = (v) => {
825
902
 
826
903
  // ../richtext/src/toc.ts
827
904
  import { slugify } from "@bettercms-ai/types";
828
- var isRecord5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
829
- var headingText = (node) => (Array.isArray(node.children) ? node.children : []).map((c) => isRecord5(c) && typeof c.text === "string" ? c.text : "").join("").replace(/​/g, "").trim();
905
+ var isRecord6 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
906
+ var isHeadingAnchor = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_:.-]{0,127}$/.test(v);
907
+ var headingText = (node) => (Array.isArray(node.children) ? node.children : []).map((c) => isRecord6(c) && typeof c.text === "string" ? c.text : "").join("").replace(/​/g, "").trim();
830
908
  function outlineOf(value) {
831
- const items = [];
832
- const nodes = /* @__PURE__ */ new WeakMap();
909
+ const heads = [];
833
910
  const used = /* @__PURE__ */ new Set();
834
911
  for (const node of Array.isArray(value) ? value : []) {
835
- if (!isRecord5(node) || node._type !== "block" || node.listItem || node.style !== "h2" && node.style !== "h3") continue;
912
+ if (!isRecord6(node) || node._type !== "block" || node.listItem || node.style !== "h2" && node.style !== "h3") continue;
836
913
  const text = headingText(node);
837
914
  if (!text) continue;
838
- const base = slugify(text);
839
- let id = base;
840
- for (let n = 2; used.has(id); n++) id = `${base}-${n}`;
841
- used.add(id);
915
+ const anchor = isHeadingAnchor(node.anchor) && !used.has(node.anchor) ? node.anchor : null;
916
+ if (anchor) used.add(anchor);
917
+ heads.push({ node, text, anchor });
918
+ }
919
+ const items = [];
920
+ const nodes = /* @__PURE__ */ new WeakMap();
921
+ for (const { node, text, anchor } of heads) {
922
+ let id = anchor;
923
+ if (!id) {
924
+ const base = slugify(text);
925
+ id = base;
926
+ for (let n = 2; used.has(id); n++) id = `${base}-${n}`;
927
+ used.add(id);
928
+ }
842
929
  nodes.set(node, id);
843
930
  items.push({ id, text, depth: node.style === "h2" ? 2 : 3 });
844
931
  }
@@ -855,25 +942,79 @@ function tocTree(items, depth) {
855
942
  return out;
856
943
  }
857
944
  function tocView(value) {
858
- const v = isRecord5(value) ? value : {};
945
+ const v = isRecord6(value) ? value : {};
859
946
  const title = typeof v.title === "string" ? v.title.trim() : "";
860
947
  return { title, label: title || "Table of contents", depth: v.depth === "2" ? "2" : "3" };
861
948
  }
862
949
 
950
+ // ../richtext/src/footnote.ts
951
+ var isRecord7 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
952
+ function footnoteOrder(value) {
953
+ const notes = [];
954
+ const nodes = /* @__PURE__ */ new WeakMap();
955
+ for (const node of Array.isArray(value) ? value : []) {
956
+ if (!isRecord7(node) || node._type !== "block" || !Array.isArray(node.children)) continue;
957
+ for (const child of node.children) {
958
+ if (!isRecord7(child) || child._type !== PT_TYPES.footnote) continue;
959
+ nodes.set(child, notes.push(typeof child.note === "string" ? child.note : ""));
960
+ }
961
+ }
962
+ return { notes, nodes };
963
+ }
964
+
965
+ // ../richtext/src/references.ts
966
+ var PT_RELATED_LIMITS = { titleChars: 40, minCount: 1, maxCount: 6, posts: 6 };
967
+ var isRecord8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
968
+ var refOf = (v) => isRecord8(v) && typeof v._ref === "string" ? v._ref : "";
969
+ var imageSrc = (raw) => safeHref(raw) ?? (raw !== "" && !raw.startsWith("//") && !/[:\s]/.test(raw) ? raw : null);
970
+ function relatedPostsView(value, resolved = {}) {
971
+ const v = isRecord8(value) ? value : {};
972
+ const title = typeof v.title === "string" ? v.title.trim() : "";
973
+ const mode = v.mode === "manual" || v.mode === "newest" ? v.mode : "topic";
974
+ const n = typeof v.count === "number" && Number.isInteger(v.count) ? v.count : 3;
975
+ const count = Math.min(PT_RELATED_LIMITS.maxCount, Math.max(PT_RELATED_LIMITS.minCount, n));
976
+ const picked = (Array.isArray(v.posts) ? v.posts : []).map(refOf).filter(Boolean);
977
+ const key = typeof v._key === "string" ? v._key : "";
978
+ const ids = mode === "manual" ? picked : (resolved.related?.[key] ?? []).slice(0, count);
979
+ const items = [];
980
+ for (const id of ids) {
981
+ const post = resolved.posts?.[id];
982
+ const url = post && typeof post.url === "string" ? safeHref(post.url) : null;
983
+ if (post && typeof post.title === "string" && url) items.push({ ...post, id, url });
984
+ }
985
+ return { key, title, mode, count, picked, items };
986
+ }
987
+ function authorBioView(value, resolved = {}) {
988
+ const key = isRecord8(value) && typeof value._key === "string" ? value._key : "";
989
+ const id = refOf(isRecord8(value) ? value.author : void 0);
990
+ const a = id ? resolved.authors?.[id] : void 0;
991
+ if (!a || typeof a.name !== "string") return { key, id, author: null };
992
+ const avatar = typeof a.avatar === "string" ? imageSrc(a.avatar.trim()) : null;
993
+ return {
994
+ key,
995
+ id,
996
+ author: {
997
+ name: a.name,
998
+ ...avatar ? { avatar } : {},
999
+ ...typeof a.bio === "string" && a.bio.trim() ? { bio: a.bio.trim() } : {}
1000
+ }
1001
+ };
1002
+ }
1003
+
863
1004
  // ../richtext/src/read.ts
864
- var isRecord6 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
1005
+ var isRecord9 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
865
1006
  function normalizePortableText(value, key = randomKey) {
866
1007
  if (!Array.isArray(value)) return [];
867
1008
  return value.flatMap((raw) => {
868
- if (!isRecord6(raw) || typeof raw._type !== "string") return [];
1009
+ if (!isRecord9(raw) || typeof raw._type !== "string") return [];
869
1010
  const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));
870
1011
  if (table) return [table];
871
- const node = { ...raw, _key: isRecord6(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
1012
+ const node = { ...raw, _key: isRecord9(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
872
1013
  if (node._type === "block") {
873
1014
  if (!Array.isArray(node.markDefs)) node.markDefs = [];
874
1015
  if (typeof node.style !== "string") node.style = "normal";
875
1016
  node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {
876
- if (!isRecord6(c)) return c;
1017
+ if (!isRecord9(c)) return c;
877
1018
  const child = { ...c, _key: typeof c._key === "string" && c._key ? c._key : key() };
878
1019
  if (child._type === "span" && !Array.isArray(child.marks)) child.marks = [];
879
1020
  return child;
@@ -883,9 +1024,9 @@ function normalizePortableText(value, key = randomKey) {
883
1024
  });
884
1025
  }
885
1026
  function readStructuredContent(props) {
886
- if (!isRecord6(props)) return null;
1027
+ if (!isRecord9(props)) return null;
887
1028
  const candidate = props.content ?? props.value;
888
- const arr = Array.isArray(candidate) ? candidate : isRecord6(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
1029
+ const arr = Array.isArray(candidate) ? candidate : isRecord9(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
889
1030
  return arr && arr.length > 0 ? arr : null;
890
1031
  }
891
1032
 
@@ -1242,6 +1383,47 @@ function XCard({ url, card }) {
1242
1383
  /* @__PURE__ */ jsx3("a", { href: url, children: card.date || "View on X" })
1243
1384
  ] });
1244
1385
  }
1386
+ function RelatedPosts({ value, resolved }) {
1387
+ const { key, title, mode, count, picked, items } = relatedPostsView(value, resolved);
1388
+ if (!resolved) {
1389
+ return /* @__PURE__ */ jsx3("aside", { "data-rt": "related", "data-key": key, "data-mode": mode, "data-count": count, "data-posts": picked.join(","), "data-title": title || void 0 });
1390
+ }
1391
+ if (!items.length) return null;
1392
+ return /* @__PURE__ */ jsxs2("aside", { "data-rt": "related", "data-mode": mode, "data-count": count, "data-posts": picked.join(","), children: [
1393
+ title ? /* @__PURE__ */ jsx3("p", { "data-title": "", children: title }) : null,
1394
+ items.length ? /* @__PURE__ */ jsx3("ul", { children: items.map((p) => /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3("a", { href: p.url, children: p.title }) }, p.id)) }) : null
1395
+ ] });
1396
+ }
1397
+ function AuthorBio({ value, resolved }) {
1398
+ const { key, id, author } = authorBioView(value, resolved);
1399
+ if (!resolved) return /* @__PURE__ */ jsx3("aside", { "data-rt": "author", "data-key": key, "data-author": id });
1400
+ if (!author) return null;
1401
+ return /* @__PURE__ */ jsxs2("aside", { "data-rt": "author", "data-author": id, children: [
1402
+ author?.avatar ? /* @__PURE__ */ jsx3("img", { src: author.avatar, alt: "" }) : null,
1403
+ author ? /* @__PURE__ */ jsx3("p", { "data-name": "", children: author.name }) : null,
1404
+ author?.bio ? /* @__PURE__ */ jsx3("p", { "data-bio": "", children: author.bio }) : null
1405
+ ] });
1406
+ }
1407
+ function Columns({ value, components }) {
1408
+ const view = columnsView(value);
1409
+ if (!view) return null;
1410
+ const s = COLUMNS_STYLES[view.gap];
1411
+ return /* @__PURE__ */ jsx3("div", { "data-rt": "columns", "data-gap": view.gap, "data-cols": view.columns.length, style: styleObject(s.row), children: view.columns.map((c) => /* @__PURE__ */ jsx3("div", { "data-col": "true", style: styleObject(s.col), children: c.content.length ? /* @__PURE__ */ jsx3(PT, { value: c.content, components }) : null }, c.key)) });
1412
+ }
1413
+ function FootnoteMark({ value, footnotes }) {
1414
+ const note = typeof value.note === "string" ? value.note : "";
1415
+ const n = footnotes.nodes.get(value);
1416
+ if (!n) return /* @__PURE__ */ jsx3("sup", { "data-rt": "fn", "data-note": note });
1417
+ return /* @__PURE__ */ jsx3("sup", { "data-rt": "fn", id: `fnref-${n}`, "data-note": note, children: /* @__PURE__ */ jsx3("a", { href: `#fn-${n}`, "aria-label": `Footnote ${n}`, children: n }) });
1418
+ }
1419
+ function FootnoteList({ notes }) {
1420
+ if (!notes.length) return null;
1421
+ return /* @__PURE__ */ jsx3("ol", { className: "cs-footnotes", "data-rt": "footnotes", "aria-label": "Footnotes", children: notes.map((note, i) => /* @__PURE__ */ jsxs2("li", { id: `fn-${i + 1}`, children: [
1422
+ note,
1423
+ " ",
1424
+ /* @__PURE__ */ jsx3("a", { href: `#fnref-${i + 1}`, "aria-label": `Back to reference ${i + 1}`, children: "\u21A9" })
1425
+ ] }, i)) });
1426
+ }
1245
1427
  function Toc({ value, outline }) {
1246
1428
  const { title, label, depth } = tocView(value);
1247
1429
  const link = (o) => /* @__PURE__ */ jsx3("a", { href: `#${o.id}`, children: o.text });
@@ -1257,7 +1439,16 @@ function Gallery({ value }) {
1257
1439
  const { layout, images } = galleryView(value);
1258
1440
  const s = GALLERY_STYLES[layout];
1259
1441
  return /* @__PURE__ */ jsx3("figure", { "data-rt": "gallery", "data-layout": layout, children: /* @__PURE__ */ jsx3("ul", { style: styleObject(s.ul), tabIndex: layout === "carousel" ? 0 : void 0, children: images.map((x) => /* @__PURE__ */ jsx3("li", { style: styleObject(s.li), children: /* @__PURE__ */ jsxs2("figure", { style: styleObject(s.figure), children: [
1260
- /* @__PURE__ */ jsx3("img", { src: x.src, alt: x.alt, style: styleObject(s.img), loading: "lazy" }),
1442
+ /* @__PURE__ */ jsx3(
1443
+ "img",
1444
+ {
1445
+ alt: x.alt,
1446
+ style: styleObject(x.position ? `${s.img};object-position:${x.position}` : s.img),
1447
+ loading: "lazy",
1448
+ ...Object.fromEntries(x.data),
1449
+ src: x.src
1450
+ }
1451
+ ),
1261
1452
  x.caption ? /* @__PURE__ */ jsx3("figcaption", { children: x.caption }) : null
1262
1453
  ] }) }, x.key)) }) });
1263
1454
  }
@@ -1281,8 +1472,8 @@ function Embed({ value, embeds }) {
1281
1472
  }
1282
1473
  return /* @__PURE__ */ jsx3("figure", { "data-rt": "embed", "data-url": url, "data-provider": r.provider, "data-aspect": aspect, "aria-label": title, children: body });
1283
1474
  }
1284
- function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes: /* @__PURE__ */ new WeakMap() }) {
1285
- return {
1475
+ function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes: /* @__PURE__ */ new WeakMap() }, footnotes = { notes: [], nodes: /* @__PURE__ */ new WeakMap() }, resolved) {
1476
+ const components = {
1286
1477
  // RT5: every top-level h2 and h3 carries its outline id, as html.ts writes it.
1287
1478
  block: {
1288
1479
  h2: ({ value, children }) => /* @__PURE__ */ jsx3("h2", { id: outline.nodes.get(value), children }),
@@ -1310,6 +1501,7 @@ function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes:
1310
1501
  if (rendered !== void 0) return /* @__PURE__ */ jsx3(Fragment, { children: rendered });
1311
1502
  const builtIn = calloutOrToggle(value);
1312
1503
  if (builtIn) return builtIn;
1504
+ if (columnsView(value)) return /* @__PURE__ */ jsx3(Columns, { value, components });
1313
1505
  const copy = detachedComponentCopy(value);
1314
1506
  if (copy) {
1315
1507
  return /* @__PURE__ */ jsx3("div", { className: "bcms-block bcms-block--component", "data-bcms-component-copy": copy.componentId, "data-bcms-instance": copy.id, children: /* @__PURE__ */ jsx3(BlocksRenderer, { blocks: copy.blocks }) });
@@ -1342,6 +1534,9 @@ function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes:
1342
1534
  [PT_TYPES.embed]: ({ value }) => /* @__PURE__ */ jsx3(Embed, { value, embeds }),
1343
1535
  [PT_TYPES.gallery]: ({ value }) => /* @__PURE__ */ jsx3(Gallery, { value }),
1344
1536
  [PT_TYPES.tableOfContents]: ({ value }) => /* @__PURE__ */ jsx3(Toc, { value, outline }),
1537
+ [PT_TYPES.columns]: ({ value }) => /* @__PURE__ */ jsx3(Columns, { value, components }),
1538
+ [PT_TYPES.relatedPosts]: ({ value }) => /* @__PURE__ */ jsx3(RelatedPosts, { value, resolved }),
1539
+ [PT_TYPES.authorBio]: ({ value }) => /* @__PURE__ */ jsx3(AuthorBio, { value, resolved }),
1345
1540
  /**
1346
1541
  * Key takeaways — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's
1347
1542
  * `<aside data-rt="takeaways">`, read through the one `keyTakeawaysView` both renderers share.
@@ -1354,6 +1549,7 @@ function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes:
1354
1549
  /* @__PURE__ */ jsx3("ul", { children: points.map((p, i) => /* @__PURE__ */ jsx3("li", { children: p }, i)) })
1355
1550
  ] });
1356
1551
  },
1552
+ [PT_TYPES.footnote]: ({ value }) => /* @__PURE__ */ jsx3(FootnoteMark, { value, footnotes }),
1357
1553
  [PT_TYPES.mention]: ({ value }) => {
1358
1554
  const v = value;
1359
1555
  return /* @__PURE__ */ jsx3("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
@@ -1418,6 +1614,7 @@ function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes:
1418
1614
  unknownType: ({ children }) => /* @__PURE__ */ jsx3(Fragment, { children: children ?? null }),
1419
1615
  unknownBlockStyle: ({ children }) => /* @__PURE__ */ jsx3("p", { children })
1420
1616
  };
1617
+ return components;
1421
1618
  }
1422
1619
  var isListItem = (node) => node._type === "block" && typeof node.listItem === "string" && !!node.listItem;
1423
1620
  function runsOf(blocks) {
@@ -1458,10 +1655,14 @@ function calloutOrToggle(value) {
1458
1655
  }
1459
1656
  return null;
1460
1657
  }
1461
- function PortableText({ value, bindingBase, renderBlock, embeds }) {
1658
+ function PortableText({ value, bindingBase, renderBlock, embeds, resolved }) {
1462
1659
  const blocks = React2.useMemo(() => normalizePortableText(readValue(value)), [value]);
1463
1660
  const outline = React2.useMemo(() => outlineOf(blocks), [blocks]);
1464
- const components = React2.useMemo(() => buildComponents(renderBlock, embeds, outline), [renderBlock, embeds, outline]);
1661
+ const footnotes = React2.useMemo(() => footnoteOrder(blocks), [blocks]);
1662
+ const components = React2.useMemo(
1663
+ () => buildComponents(renderBlock, embeds, outline, footnotes, resolved),
1664
+ [renderBlock, embeds, outline, footnotes, resolved]
1665
+ );
1465
1666
  const listComponents = React2.useMemo(() => {
1466
1667
  if (!bindingBase) return null;
1467
1668
  const keys = new Set(blocks.map((b) => b._key));
@@ -1477,10 +1678,19 @@ function PortableText({ value, bindingBase, renderBlock, embeds }) {
1477
1678
  };
1478
1679
  }, [bindingBase, blocks, components]);
1479
1680
  if (blocks.length === 0) return null;
1480
- if (!bindingBase || !listComponents) return /* @__PURE__ */ jsx3(PT, { value: blocks, components });
1481
- return /* @__PURE__ */ jsx3(Fragment, { children: runsOf(blocks).map(
1482
- (run) => isListItem(run[0]) ? /* @__PURE__ */ jsx3(PT, { value: run, components: listComponents }, run[0]._key) : /* @__PURE__ */ jsx3(Bound, { base: bindingBase, nodeKey: run[0]._key, children: /* @__PURE__ */ jsx3(PT, { value: run, components }) }, run[0]._key)
1483
- ) });
1681
+ const notes = /* @__PURE__ */ jsx3(FootnoteList, { notes: footnotes.notes });
1682
+ if (!bindingBase || !listComponents) {
1683
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
1684
+ /* @__PURE__ */ jsx3(PT, { value: blocks, components }),
1685
+ notes
1686
+ ] });
1687
+ }
1688
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
1689
+ runsOf(blocks).map(
1690
+ (run) => isListItem(run[0]) ? /* @__PURE__ */ jsx3(PT, { value: run, components: listComponents }, run[0]._key) : /* @__PURE__ */ jsx3(Bound, { base: bindingBase, nodeKey: run[0]._key, children: /* @__PURE__ */ jsx3(PT, { value: run, components }) }, run[0]._key)
1691
+ ),
1692
+ notes
1693
+ ] });
1484
1694
  }
1485
1695
  function readValue(value) {
1486
1696
  if (Array.isArray(value)) return value;
@@ -1654,7 +1864,7 @@ function Spacer({ height = 32, className = "" }) {
1654
1864
  // src/blocks/Columns.tsx
1655
1865
  import "react";
1656
1866
  import { jsx as jsx8 } from "react/jsx-runtime";
1657
- function Columns({
1867
+ function Columns2({
1658
1868
  columns,
1659
1869
  gap = 16,
1660
1870
  className = "",
@@ -1908,7 +2118,7 @@ var BLOCK_REGISTRY = {
1908
2118
  icon: "minus"
1909
2119
  },
1910
2120
  columns: {
1911
- component: Columns,
2121
+ component: Columns2,
1912
2122
  label: "Columns",
1913
2123
  icon: "columns"
1914
2124
  },