@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/README.md +2 -2
- package/dist/blocks.d.ts +14 -2
- package/dist/blocks.js +599 -22
- package/dist/blocks.js.map +1 -1
- package/dist/index.js +599 -22
- package/dist/index.js.map +1 -1
- package/dist/registry.js +601 -24
- package/dist/registry.js.map +1 -1
- package/dist/renderer.js +601 -24
- package/dist/renderer.js.map +1 -1
- package/package.json +4 -3
package/dist/renderer.js
CHANGED
|
@@ -67,6 +67,24 @@ var PT_TYPES = {
|
|
|
67
67
|
pullQuote: "pullQuote",
|
|
68
68
|
/** Key takeaways — see `PortableTextKeyTakeaways`. The V2 kit's `<aside data-rt="takeaways">`. */
|
|
69
69
|
keyTakeaways: "keyTakeaways",
|
|
70
|
+
/**
|
|
71
|
+
* A third-party embed (X, CodePen, Figma, Maps, Spotify, Podcasts, audio, a link card) — see
|
|
72
|
+
* `PortableTextEmbed` and `embed.ts`. The V2 kit's `<figure data-rt="embed">`. Gated by the
|
|
73
|
+
* `embed` feature; YouTube and Vimeo links still resolve here too, through the video resolver.
|
|
74
|
+
*/
|
|
75
|
+
embed: "embed",
|
|
76
|
+
/** A gallery of images, a grid or a carousel — see `PortableTextGallery`. The V2 kit's `<figure data-rt="gallery">`. */
|
|
77
|
+
gallery: "gallery",
|
|
78
|
+
/** A table of contents built from the headings — see `PortableTextTableOfContents`. The V2 kit's `<nav data-rt="toc">`. */
|
|
79
|
+
tableOfContents: "tableOfContents",
|
|
80
|
+
/** A footnote, an INLINE object in body text — see `PortableTextFootnote`. The V2 kit's `<sup data-rt="fn">`. */
|
|
81
|
+
footnote: "footnote",
|
|
82
|
+
/** Columns of text — see `PortableTextColumns`. The V2 kit's `<div data-rt="columns">`; the legacy `builtin:columns` reads the same way. */
|
|
83
|
+
columns: "columns",
|
|
84
|
+
/** An author bio — see `PortableTextAuthorBio`. The V2 kit's `<aside data-rt="author">`. */
|
|
85
|
+
authorBio: "authorBio",
|
|
86
|
+
/** Related posts — see `PortableTextRelatedPosts`. The V2 kit's `<aside data-rt="related">`. */
|
|
87
|
+
relatedPosts: "relatedPosts",
|
|
70
88
|
/**
|
|
71
89
|
* A table. Spelled `table`, unprefixed, because that is what every other Portable Text
|
|
72
90
|
* producer calls one — the Sanity-shaped `{_type:"table", rows}` this platform used to refuse
|
|
@@ -93,7 +111,13 @@ var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
|
93
111
|
PT_TYPES.video,
|
|
94
112
|
PT_TYPES.file,
|
|
95
113
|
PT_TYPES.pullQuote,
|
|
96
|
-
PT_TYPES.keyTakeaways
|
|
114
|
+
PT_TYPES.keyTakeaways,
|
|
115
|
+
PT_TYPES.embed,
|
|
116
|
+
PT_TYPES.gallery,
|
|
117
|
+
PT_TYPES.tableOfContents,
|
|
118
|
+
PT_TYPES.columns,
|
|
119
|
+
PT_TYPES.authorBio,
|
|
120
|
+
PT_TYPES.relatedPosts
|
|
97
121
|
]);
|
|
98
122
|
var EMBEDDED_COMPONENT_KEY = "component";
|
|
99
123
|
|
|
@@ -568,16 +592,109 @@ function keyTakeawaysView(value) {
|
|
|
568
592
|
return { title, points };
|
|
569
593
|
}
|
|
570
594
|
|
|
571
|
-
// ../richtext/src/
|
|
595
|
+
// ../richtext/src/image-layout.ts
|
|
596
|
+
import {
|
|
597
|
+
focalInCrop,
|
|
598
|
+
formatImageRect,
|
|
599
|
+
imageValueUrl,
|
|
600
|
+
normalizeImageCrop,
|
|
601
|
+
normalizeImageFocal,
|
|
602
|
+
objectPosition,
|
|
603
|
+
parseImageRect
|
|
604
|
+
} from "@bettercms-ai/image-url";
|
|
605
|
+
function richTextImageFrame(src, crop, focal) {
|
|
606
|
+
const c = normalizeImageCrop(crop);
|
|
607
|
+
const f = normalizeImageFocal(focal);
|
|
608
|
+
if (!c && !f) return { src, crop: null, focal: null, position: null, data: [] };
|
|
609
|
+
const drawn = imageValueUrl({ url: src, crop: c, focal: f }) ?? src;
|
|
610
|
+
const data = [];
|
|
611
|
+
if (drawn !== src) data.push(["data-src", src]);
|
|
612
|
+
if (c) data.push(["data-crop", formatImageRect(c)]);
|
|
613
|
+
if (f) data.push(["data-focal", `${f.x},${f.y}`]);
|
|
614
|
+
return { src: drawn, crop: c, focal: f, position: f ? objectPosition(focalInCrop(f, c)) : null, data };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ../richtext/src/gallery.ts
|
|
572
618
|
var isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
573
|
-
var
|
|
619
|
+
var galleryMediaSrc = (raw) => safeHref(raw) ?? (raw !== "" && !raw.startsWith("//") && !/[:\s]/.test(raw) ? raw : null);
|
|
620
|
+
var GALLERY_STYLES = {
|
|
621
|
+
grid: {
|
|
622
|
+
ul: "display:grid;grid-template-columns:repeat(auto-fill,minmax(12rem,1fr));gap:1rem;list-style:none;margin:0;padding:0",
|
|
623
|
+
li: "margin:0",
|
|
624
|
+
figure: "margin:0",
|
|
625
|
+
img: "display:block;width:100%;height:auto"
|
|
626
|
+
},
|
|
627
|
+
carousel: {
|
|
628
|
+
ul: "display:flex;gap:1rem;overflow-x:auto;scroll-snap-type:x mandatory;list-style:none;margin:0;padding:0",
|
|
629
|
+
li: "flex:0 0 100%;scroll-snap-align:start;margin:0",
|
|
630
|
+
figure: "margin:0",
|
|
631
|
+
img: "display:block;width:100%;height:auto"
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
function galleryView(value) {
|
|
635
|
+
const v = isRecord3(value) ? value : {};
|
|
636
|
+
const layout = v.layout === "carousel" ? "carousel" : "grid";
|
|
637
|
+
const images = [];
|
|
638
|
+
(Array.isArray(v.images) ? v.images : []).forEach((item, i) => {
|
|
639
|
+
if (!isRecord3(item) || !isRecord3(item.image)) return;
|
|
640
|
+
const src = galleryMediaSrc(typeof item.image.src === "string" ? item.image.src.trim() : "");
|
|
641
|
+
if (!src) return;
|
|
642
|
+
const frame = richTextImageFrame(src, item.image.crop, item.image.focal);
|
|
643
|
+
images.push({
|
|
644
|
+
key: typeof item._key === "string" && item._key ? item._key : `i${i}`,
|
|
645
|
+
src: frame.src,
|
|
646
|
+
position: frame.position,
|
|
647
|
+
data: frame.data,
|
|
648
|
+
alt: typeof item.image.alt === "string" ? item.image.alt : "",
|
|
649
|
+
caption: typeof item.caption === "string" ? item.caption.trim() : ""
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
return { layout, images };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// ../richtext/src/columns.ts
|
|
656
|
+
var PT_COLUMNS_LIMITS = { min: 2, max: 4 };
|
|
657
|
+
var BUILTIN_COLUMNS_KEY = "builtin:columns";
|
|
658
|
+
var isRecord4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
659
|
+
var COLUMNS_STYLES = {
|
|
660
|
+
s: { row: "display:flex;flex-wrap:wrap;gap:0.75rem", col: "flex:1 1 14rem;min-width:0" },
|
|
661
|
+
m: { row: "display:flex;flex-wrap:wrap;gap:1.25rem", col: "flex:1 1 14rem;min-width:0" },
|
|
662
|
+
l: { row: "display:flex;flex-wrap:wrap;gap:2rem", col: "flex:1 1 14rem;min-width:0" }
|
|
663
|
+
};
|
|
664
|
+
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: [] }] }));
|
|
665
|
+
function columnsView(node) {
|
|
666
|
+
if (!isRecord4(node)) return null;
|
|
667
|
+
if (node._type === PT_TYPES.columns) {
|
|
668
|
+
const gap = node.gap === "s" || node.gap === "l" ? node.gap : "m";
|
|
669
|
+
const columns = (Array.isArray(node.columns) ? node.columns : []).map((c, i) => ({
|
|
670
|
+
key: isRecord4(c) && typeof c._key === "string" && c._key ? c._key : `c${i}`,
|
|
671
|
+
content: isRecord4(c) && Array.isArray(c.content) ? c.content : []
|
|
672
|
+
}));
|
|
673
|
+
return { gap, columns };
|
|
674
|
+
}
|
|
675
|
+
if (node._type === PT_TYPES.block && node.schemaKey === BUILTIN_COLUMNS_KEY) {
|
|
676
|
+
const f = isRecord4(node.fields) ? node.fields : {};
|
|
677
|
+
if (!Array.isArray(f.columns) || !f.columns.every((c) => typeof c === "string")) return null;
|
|
678
|
+
const gap = f.gap === "small" ? "s" : f.gap === "large" ? "l" : "m";
|
|
679
|
+
const columns = (Array.isArray(f.columns) ? f.columns : []).slice(0, PT_COLUMNS_LIMITS.max).map((c, i) => ({
|
|
680
|
+
key: `c${i}`,
|
|
681
|
+
content: legacyContent(typeof c === "string" ? c : "", `c${i}`)
|
|
682
|
+
}));
|
|
683
|
+
return { gap, columns };
|
|
684
|
+
}
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// ../richtext/src/component-copy.ts
|
|
689
|
+
var isRecord5 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
690
|
+
var fieldsOf = (value) => isRecord5(value) && value.schemaKey === EMBEDDED_COMPONENT_KEY && isRecord5(value.fields) ? value.fields : null;
|
|
574
691
|
function detachedComponentCopy(value) {
|
|
575
692
|
const f = fieldsOf(value);
|
|
576
693
|
if (!f || f.detached !== true || !Array.isArray(f.blocks)) return null;
|
|
577
694
|
return {
|
|
578
|
-
id:
|
|
695
|
+
id: isRecord5(value) && typeof value._key === "string" ? value._key : "",
|
|
579
696
|
componentId: typeof f.componentId === "string" ? f.componentId : "",
|
|
580
|
-
props:
|
|
697
|
+
props: isRecord5(f.props) ? f.props : {},
|
|
581
698
|
blocks: f.blocks,
|
|
582
699
|
...typeof f.sourcePublishedAt === "string" ? { sourcePublishedAt: f.sourcePublishedAt } : {},
|
|
583
700
|
...typeof f.variant === "string" ? { variant: f.variant } : {}
|
|
@@ -590,7 +707,7 @@ function componentCopyLines(copy) {
|
|
|
590
707
|
const t = v.replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/\s+/g, " ").trim();
|
|
591
708
|
return t || null;
|
|
592
709
|
}
|
|
593
|
-
if (
|
|
710
|
+
if (isRecord5(v)) return line(v.label ?? v.text ?? v.title);
|
|
594
711
|
return null;
|
|
595
712
|
};
|
|
596
713
|
return Object.values(copy.props).map(line).filter((s) => !!s);
|
|
@@ -617,6 +734,18 @@ function portableTextToPlainText(value) {
|
|
|
617
734
|
lines.push(...title ? [title] : [], ...points);
|
|
618
735
|
continue;
|
|
619
736
|
}
|
|
737
|
+
if (node._type === PT_TYPES.gallery) {
|
|
738
|
+
lines.push(...galleryView(node).images.map((x) => x.caption).filter(Boolean));
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
const cols = columnsView(node);
|
|
742
|
+
if (cols) {
|
|
743
|
+
for (const c of cols.columns) {
|
|
744
|
+
const text = portableTextToPlainText(c.content);
|
|
745
|
+
if (text) lines.push(text);
|
|
746
|
+
}
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
620
749
|
const table = migrateTable(node);
|
|
621
750
|
if (table) {
|
|
622
751
|
lines.push(...tableLines(table));
|
|
@@ -655,20 +784,121 @@ var pullQuoteLines = (v) => {
|
|
|
655
784
|
return [text(v.quote), ...by ? [`\u2014 ${by}`] : []].filter(Boolean);
|
|
656
785
|
};
|
|
657
786
|
|
|
787
|
+
// ../richtext/src/toc.ts
|
|
788
|
+
import { slugify } from "@bettercms-ai/types";
|
|
789
|
+
var isRecord6 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
790
|
+
var isHeadingAnchor = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_:.-]{0,127}$/.test(v);
|
|
791
|
+
var headingText = (node) => (Array.isArray(node.children) ? node.children : []).map((c) => isRecord6(c) && typeof c.text === "string" ? c.text : "").join("").replace(//g, "").trim();
|
|
792
|
+
function outlineOf(value) {
|
|
793
|
+
const heads = [];
|
|
794
|
+
const used = /* @__PURE__ */ new Set();
|
|
795
|
+
for (const node of Array.isArray(value) ? value : []) {
|
|
796
|
+
if (!isRecord6(node) || node._type !== "block" || node.listItem || node.style !== "h2" && node.style !== "h3") continue;
|
|
797
|
+
const text = headingText(node);
|
|
798
|
+
if (!text) continue;
|
|
799
|
+
const anchor = isHeadingAnchor(node.anchor) && !used.has(node.anchor) ? node.anchor : null;
|
|
800
|
+
if (anchor) used.add(anchor);
|
|
801
|
+
heads.push({ node, text, anchor });
|
|
802
|
+
}
|
|
803
|
+
const items = [];
|
|
804
|
+
const nodes = /* @__PURE__ */ new WeakMap();
|
|
805
|
+
for (const { node, text, anchor } of heads) {
|
|
806
|
+
let id = anchor;
|
|
807
|
+
if (!id) {
|
|
808
|
+
const base = slugify(text);
|
|
809
|
+
id = base;
|
|
810
|
+
for (let n = 2; used.has(id); n++) id = `${base}-${n}`;
|
|
811
|
+
used.add(id);
|
|
812
|
+
}
|
|
813
|
+
nodes.set(node, id);
|
|
814
|
+
items.push({ id, text, depth: node.style === "h2" ? 2 : 3 });
|
|
815
|
+
}
|
|
816
|
+
return { items, nodes };
|
|
817
|
+
}
|
|
818
|
+
function tocTree(items, depth) {
|
|
819
|
+
const out = [];
|
|
820
|
+
for (const it of items) {
|
|
821
|
+
if (it.depth === 3 && depth === "2") continue;
|
|
822
|
+
const last = out[out.length - 1];
|
|
823
|
+
if (it.depth === 3 && last) last.children.push(it);
|
|
824
|
+
else out.push({ ...it, children: [] });
|
|
825
|
+
}
|
|
826
|
+
return out;
|
|
827
|
+
}
|
|
828
|
+
function tocView(value) {
|
|
829
|
+
const v = isRecord6(value) ? value : {};
|
|
830
|
+
const title = typeof v.title === "string" ? v.title.trim() : "";
|
|
831
|
+
return { title, label: title || "Table of contents", depth: v.depth === "2" ? "2" : "3" };
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// ../richtext/src/footnote.ts
|
|
835
|
+
var isRecord7 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
836
|
+
function footnoteOrder(value) {
|
|
837
|
+
const notes = [];
|
|
838
|
+
const nodes = /* @__PURE__ */ new WeakMap();
|
|
839
|
+
for (const node of Array.isArray(value) ? value : []) {
|
|
840
|
+
if (!isRecord7(node) || node._type !== "block" || !Array.isArray(node.children)) continue;
|
|
841
|
+
for (const child of node.children) {
|
|
842
|
+
if (!isRecord7(child) || child._type !== PT_TYPES.footnote) continue;
|
|
843
|
+
nodes.set(child, notes.push(typeof child.note === "string" ? child.note : ""));
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return { notes, nodes };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// ../richtext/src/references.ts
|
|
850
|
+
var PT_RELATED_LIMITS = { titleChars: 40, minCount: 1, maxCount: 6, posts: 6 };
|
|
851
|
+
var isRecord8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
852
|
+
var refOf = (v) => isRecord8(v) && typeof v._ref === "string" ? v._ref : "";
|
|
853
|
+
var imageSrc = (raw) => safeHref(raw) ?? (raw !== "" && !raw.startsWith("//") && !/[:\s]/.test(raw) ? raw : null);
|
|
854
|
+
function relatedPostsView(value, resolved = {}) {
|
|
855
|
+
const v = isRecord8(value) ? value : {};
|
|
856
|
+
const title = typeof v.title === "string" ? v.title.trim() : "";
|
|
857
|
+
const mode = v.mode === "manual" || v.mode === "newest" ? v.mode : "topic";
|
|
858
|
+
const n = typeof v.count === "number" && Number.isInteger(v.count) ? v.count : 3;
|
|
859
|
+
const count = Math.min(PT_RELATED_LIMITS.maxCount, Math.max(PT_RELATED_LIMITS.minCount, n));
|
|
860
|
+
const picked = (Array.isArray(v.posts) ? v.posts : []).map(refOf).filter(Boolean);
|
|
861
|
+
const key = typeof v._key === "string" ? v._key : "";
|
|
862
|
+
const ids = mode === "manual" ? picked : (resolved.related?.[key] ?? []).slice(0, count);
|
|
863
|
+
const items = [];
|
|
864
|
+
for (const id of ids) {
|
|
865
|
+
const post = resolved.posts?.[id];
|
|
866
|
+
const url = post && typeof post.url === "string" ? safeHref(post.url) : null;
|
|
867
|
+
if (post && typeof post.title === "string" && url) items.push({ ...post, id, url });
|
|
868
|
+
}
|
|
869
|
+
return { key, title, mode, count, picked, items };
|
|
870
|
+
}
|
|
871
|
+
function authorBioView(value, resolved = {}) {
|
|
872
|
+
const key = isRecord8(value) && typeof value._key === "string" ? value._key : "";
|
|
873
|
+
const id = refOf(isRecord8(value) ? value.author : void 0);
|
|
874
|
+
const a = id ? resolved.authors?.[id] : void 0;
|
|
875
|
+
if (!a || typeof a.name !== "string") return { key, id, author: null };
|
|
876
|
+
const avatar = typeof a.avatar === "string" ? imageSrc(a.avatar.trim()) : null;
|
|
877
|
+
return {
|
|
878
|
+
key,
|
|
879
|
+
id,
|
|
880
|
+
author: {
|
|
881
|
+
name: a.name,
|
|
882
|
+
...avatar ? { avatar } : {},
|
|
883
|
+
...typeof a.bio === "string" && a.bio.trim() ? { bio: a.bio.trim() } : {}
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
658
888
|
// ../richtext/src/read.ts
|
|
659
|
-
var
|
|
889
|
+
var isRecord9 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
660
890
|
function normalizePortableText(value, key = randomKey) {
|
|
661
891
|
if (!Array.isArray(value)) return [];
|
|
662
892
|
return value.flatMap((raw) => {
|
|
663
|
-
if (!
|
|
893
|
+
if (!isRecord9(raw) || typeof raw._type !== "string") return [];
|
|
664
894
|
const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));
|
|
665
895
|
if (table) return [table];
|
|
666
|
-
const node = { ...raw, _key:
|
|
896
|
+
const node = { ...raw, _key: isRecord9(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
|
|
667
897
|
if (node._type === "block") {
|
|
668
898
|
if (!Array.isArray(node.markDefs)) node.markDefs = [];
|
|
669
899
|
if (typeof node.style !== "string") node.style = "normal";
|
|
670
900
|
node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {
|
|
671
|
-
if (!
|
|
901
|
+
if (!isRecord9(c)) return c;
|
|
672
902
|
const child = { ...c, _key: typeof c._key === "string" && c._key ? c._key : key() };
|
|
673
903
|
if (child._type === "span" && !Array.isArray(child.marks)) child.marks = [];
|
|
674
904
|
return child;
|
|
@@ -678,12 +908,219 @@ function normalizePortableText(value, key = randomKey) {
|
|
|
678
908
|
});
|
|
679
909
|
}
|
|
680
910
|
function readStructuredContent(props) {
|
|
681
|
-
if (!
|
|
911
|
+
if (!isRecord9(props)) return null;
|
|
682
912
|
const candidate = props.content ?? props.value;
|
|
683
|
-
const arr = Array.isArray(candidate) ? candidate :
|
|
913
|
+
const arr = Array.isArray(candidate) ? candidate : isRecord9(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
|
|
684
914
|
return arr && arr.length > 0 ? arr : null;
|
|
685
915
|
}
|
|
686
916
|
|
|
917
|
+
// ../richtext/src/video.ts
|
|
918
|
+
var videoId = (value) => value && /^[a-zA-Z0-9_-]{6,32}$/.test(value) ? value : null;
|
|
919
|
+
function resolveVideoSource(value) {
|
|
920
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
921
|
+
let parsed;
|
|
922
|
+
try {
|
|
923
|
+
parsed = new URL(value.trim());
|
|
924
|
+
} catch {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
928
|
+
const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
|
|
929
|
+
let id = null;
|
|
930
|
+
if (host === "youtu.be") id = videoId(parsed.pathname.split("/").filter(Boolean)[0]);
|
|
931
|
+
if (host === "youtube.com" || host === "m.youtube.com" || host === "youtube-nocookie.com") {
|
|
932
|
+
id = videoId(
|
|
933
|
+
parsed.searchParams.get("v") ?? parsed.pathname.match(/^\/(?:embed|shorts)\/([^/?#]+)/)?.[1]
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
if (id) {
|
|
937
|
+
return {
|
|
938
|
+
kind: "iframe",
|
|
939
|
+
src: `https://www.youtube-nocookie.com/embed/${id}`,
|
|
940
|
+
provider: "YouTube"
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
if (host === "vimeo.com" || host === "player.vimeo.com") {
|
|
944
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
945
|
+
let vimeoId = null;
|
|
946
|
+
for (let i = segments.length - 1; i >= 0; i -= 1) {
|
|
947
|
+
if (/^\d+$/.test(segments[i])) {
|
|
948
|
+
vimeoId = segments[i];
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (vimeoId) {
|
|
953
|
+
return {
|
|
954
|
+
kind: "iframe",
|
|
955
|
+
src: `https://player.vimeo.com/video/${vimeoId}`,
|
|
956
|
+
provider: "Vimeo"
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
if (/\.(?:m4v|mp4|og[gv]|webm)$/i.test(parsed.pathname)) {
|
|
961
|
+
return { kind: "video", src: parsed.toString(), provider: "Video file" };
|
|
962
|
+
}
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// ../richtext/src/embed.ts
|
|
967
|
+
var EMBED_ASPECTS = ["16:9", "4:3", "1:1", "9:16"];
|
|
968
|
+
var isEmbedAspect = (v) => EMBED_ASPECTS.includes(v);
|
|
969
|
+
function detectEmbed(url) {
|
|
970
|
+
const u = url.trim();
|
|
971
|
+
let host = "";
|
|
972
|
+
try {
|
|
973
|
+
host = new URL(u).hostname.replace(/^www\./, "");
|
|
974
|
+
} catch {
|
|
975
|
+
host = "";
|
|
976
|
+
}
|
|
977
|
+
const is = (re) => re.test(u);
|
|
978
|
+
if (is(/(?:youtube\.com\/(?:watch\?|embed\/|shorts\/|live\/)|youtu\.be\/)/i)) return { provider: "youtube", label: "YouTube", host };
|
|
979
|
+
if (is(/vimeo\.com\/(?:video\/)?\d+/i)) return { provider: "vimeo", label: "Vimeo", host };
|
|
980
|
+
if (is(/(?:twitter|x)\.com\/[^/]+\/status\/\d+/i)) return { provider: "x", label: "X post", host };
|
|
981
|
+
if (is(/codepen\.io\/[^/]+\/(?:pen|embed)\//i)) return { provider: "codepen", label: "CodePen", host };
|
|
982
|
+
if (is(/figma\.com\/(?:file|design|proto|board)\//i)) return { provider: "figma", label: "Figma", host };
|
|
983
|
+
if (is(/(?:google\.[a-z.]+\/maps|maps\.app\.goo\.gl|goo\.gl\/maps)/i)) return { provider: "maps", label: "Google Maps", host };
|
|
984
|
+
if (is(/open\.spotify\.com\/(?:episode|track|show|playlist|album)\//i)) return { provider: "spotify", label: "Spotify", host };
|
|
985
|
+
if (is(/podcasts\.apple\.com\//i)) return { provider: "podcast", label: "Apple Podcasts", host };
|
|
986
|
+
if (is(/\.(?:mp3|m4a|wav|ogg|aac)(?:\?|$)/i)) return { provider: "audio", label: "Audio", host };
|
|
987
|
+
return { provider: "link", label: host || "Link", host };
|
|
988
|
+
}
|
|
989
|
+
function detectMore(url) {
|
|
990
|
+
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
991
|
+
const path = url.pathname;
|
|
992
|
+
if (host === "youtube-nocookie.com" && path.startsWith("/embed/")) return { provider: "youtube", label: "YouTube" };
|
|
993
|
+
if (host === "open.spotify.com" && /^\/embed\/(?:episode|track|show|playlist|album)\//.test(path)) return { provider: "spotify", label: "Spotify" };
|
|
994
|
+
if (host === "calendly.com") return { provider: "calendly", label: "Calendly" };
|
|
995
|
+
if (host.endsWith(".typeform.com") && /^\/to\//.test(path)) return { provider: "typeform", label: "Typeform" };
|
|
996
|
+
if (host === "loom.com" && /^\/(?:share|embed)\//.test(path)) return { provider: "loom", label: "Loom" };
|
|
997
|
+
if (host === "airtable.com") return { provider: "airtable", label: "Airtable" };
|
|
998
|
+
if (host === "docs.google.com") {
|
|
999
|
+
const kind = path.split("/")[1];
|
|
1000
|
+
const label = { forms: "Google Forms", document: "Google Docs", spreadsheets: "Google Sheets", presentation: "Google Slides" }[kind ?? ""];
|
|
1001
|
+
if (label) return { provider: "google", label };
|
|
1002
|
+
}
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
var FRAME_SIZE = {
|
|
1006
|
+
"16:9": ["640", "360"],
|
|
1007
|
+
"4:3": ["640", "480"],
|
|
1008
|
+
"1:1": ["640", "640"],
|
|
1009
|
+
"9:16": ["360", "640"]
|
|
1010
|
+
};
|
|
1011
|
+
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
1012
|
+
var FULL_WIDTH_HEIGHT = { calendly: "700", typeform: "500", airtable: "533" };
|
|
1013
|
+
function frameOf(provider, url) {
|
|
1014
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
1015
|
+
switch (provider) {
|
|
1016
|
+
case "youtube":
|
|
1017
|
+
case "vimeo": {
|
|
1018
|
+
const video = resolveVideoSource(url.href);
|
|
1019
|
+
return video?.kind === "iframe" ? { src: video.src } : null;
|
|
1020
|
+
}
|
|
1021
|
+
case "codepen": {
|
|
1022
|
+
const [user, mode, id] = parts;
|
|
1023
|
+
return user && ID.test(user) && (mode === "pen" || mode === "embed") && id && ID.test(id) ? { src: `https://codepen.io/${user}/embed/${id}?default-tab=result` } : null;
|
|
1024
|
+
}
|
|
1025
|
+
case "figma": {
|
|
1026
|
+
const [mode, key] = parts;
|
|
1027
|
+
if (!mode || !["file", "design", "proto", "board"].includes(mode) || !key || !ID.test(key)) return null;
|
|
1028
|
+
const frame = new URL(`https://embed.figma.com/${mode}/${key}`);
|
|
1029
|
+
const node = url.searchParams.get("node-id");
|
|
1030
|
+
if (node && /^[0-9A-Za-z:_-]{1,64}$/.test(node)) frame.searchParams.set("node-id", node);
|
|
1031
|
+
frame.searchParams.set("embed-host", "bettercms");
|
|
1032
|
+
return { src: frame.href };
|
|
1033
|
+
}
|
|
1034
|
+
case "maps": {
|
|
1035
|
+
if (!/^(?:www\.)?google\.[a-z.]+$/i.test(url.hostname)) return null;
|
|
1036
|
+
const pb = url.searchParams.get("pb");
|
|
1037
|
+
if (parts[0] === "maps" && parts[1] === "embed" && pb && /^[!A-Za-z0-9._:%+-]{1,2000}$/.test(pb)) {
|
|
1038
|
+
return { src: `https://www.google.com/maps/embed?pb=${encodeURIComponent(pb)}` };
|
|
1039
|
+
}
|
|
1040
|
+
const at = /@(-?\d{1,3}(?:\.\d+)?),(-?\d{1,3}(?:\.\d+)?)/.exec(url.pathname);
|
|
1041
|
+
const place = parts[0] === "maps" && parts[1] === "place" && parts[2] ? decodeURIComponent(parts[2]).replace(/\+/g, " ") : null;
|
|
1042
|
+
const q = at ? `${at[1]},${at[2]}` : url.searchParams.get("q") ?? place;
|
|
1043
|
+
if (!q || q.length > 200) return null;
|
|
1044
|
+
return { src: `https://maps.google.com/maps?q=${encodeURIComponent(q)}&output=embed` };
|
|
1045
|
+
}
|
|
1046
|
+
case "spotify": {
|
|
1047
|
+
const [type, id] = parts[0] === "embed" ? parts.slice(1) : parts;
|
|
1048
|
+
if (!type || !["episode", "track", "show", "playlist", "album"].includes(type) || !id || !ID.test(id)) return null;
|
|
1049
|
+
return { src: `https://open.spotify.com/embed/${type}/${id}`, height: type === "track" || type === "episode" ? "152" : "352" };
|
|
1050
|
+
}
|
|
1051
|
+
case "podcast":
|
|
1052
|
+
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;
|
|
1053
|
+
case "calendly":
|
|
1054
|
+
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;
|
|
1055
|
+
case "typeform": {
|
|
1056
|
+
const id = parts[0] === "to" ? parts[1] : void 0;
|
|
1057
|
+
return id && /^[A-Za-z0-9]{4,32}$/.test(id) ? { src: `https://form.typeform.com/to/${id}` } : null;
|
|
1058
|
+
}
|
|
1059
|
+
case "loom": {
|
|
1060
|
+
const id = parts[1];
|
|
1061
|
+
return (parts[0] === "share" || parts[0] === "embed") && id && /^[0-9a-f]{32}$/.test(id) ? { src: `https://www.loom.com/embed/${id}` } : null;
|
|
1062
|
+
}
|
|
1063
|
+
case "airtable": {
|
|
1064
|
+
const ids = parts[0] === "embed" ? parts.slice(1) : parts;
|
|
1065
|
+
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;
|
|
1066
|
+
}
|
|
1067
|
+
case "google": {
|
|
1068
|
+
const [kind, d, maybeE, maybeId] = parts;
|
|
1069
|
+
if (d !== "d") return null;
|
|
1070
|
+
const published = maybeE === "e";
|
|
1071
|
+
const id = published ? maybeId : maybeE;
|
|
1072
|
+
if (!id || !/^[A-Za-z0-9_-]{10,128}$/.test(id)) return null;
|
|
1073
|
+
const base = `https://docs.google.com/${kind}/d/${published ? `e/${id}` : id}`;
|
|
1074
|
+
if (kind === "forms") return { src: `${base}/viewform?embedded=true`, height: "800" };
|
|
1075
|
+
if (kind === "document") return { src: published ? `${base}/pub?embedded=true` : `${base}/preview`, height: "600" };
|
|
1076
|
+
if (kind === "spreadsheets") return { src: published ? `${base}/pubhtml?widget=true&headers=false` : `${base}/preview`, height: "600" };
|
|
1077
|
+
if (kind === "presentation") return { src: published ? `${base}/embed` : `${base}/preview` };
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
default:
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
var X_STATUS = /^\/([A-Za-z0-9_]{1,15})\/status\/(\d{1,25})(?:\/|$)/;
|
|
1085
|
+
var X_HOSTS = /^(?:www\.|mobile\.)?(?:twitter|x)\.com$/i;
|
|
1086
|
+
function canonicalEmbedUrl(raw) {
|
|
1087
|
+
const url = typeof raw === "string" ? raw.trim() : "";
|
|
1088
|
+
const safe = url ? safeHref(url) : null;
|
|
1089
|
+
if (!safe || !/^https?:\/\//i.test(safe)) return null;
|
|
1090
|
+
let parsed;
|
|
1091
|
+
try {
|
|
1092
|
+
parsed = new URL(safe);
|
|
1093
|
+
} catch {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
const status = X_HOSTS.test(parsed.hostname) ? X_STATUS.exec(parsed.pathname) : null;
|
|
1097
|
+
if (status) return `https://x.com/${status[1].toLowerCase()}/status/${status[2]}`;
|
|
1098
|
+
parsed.hash = "";
|
|
1099
|
+
return parsed.href;
|
|
1100
|
+
}
|
|
1101
|
+
function resolveEmbed(rawUrl, aspect = "16:9") {
|
|
1102
|
+
const url = typeof rawUrl === "string" ? rawUrl.trim() : "";
|
|
1103
|
+
const v2 = detectEmbed(url);
|
|
1104
|
+
const safe = url ? safeHref(url) : null;
|
|
1105
|
+
let parsed = null;
|
|
1106
|
+
try {
|
|
1107
|
+
parsed = safe && /^https?:\/\//i.test(safe) ? new URL(safe) : null;
|
|
1108
|
+
} catch {
|
|
1109
|
+
parsed = null;
|
|
1110
|
+
}
|
|
1111
|
+
const { provider, label } = v2.provider === "link" && parsed ? detectMore(parsed) ?? v2 : v2;
|
|
1112
|
+
const link = { kind: "link", provider, label, url: safe };
|
|
1113
|
+
if (!parsed) return link;
|
|
1114
|
+
if (provider === "x") return { kind: "card", provider: "x", label, url: canonicalEmbedUrl(parsed.href) ?? parsed.href };
|
|
1115
|
+
if (provider === "audio") return parsed.protocol === "https:" ? { kind: "audio", provider: "audio", label, src: parsed.href } : link;
|
|
1116
|
+
const frame = frameOf(provider, parsed);
|
|
1117
|
+
if (!frame) return link;
|
|
1118
|
+
const height = frame.height ?? FULL_WIDTH_HEIGHT[provider];
|
|
1119
|
+
if (height) return { kind: "frame", provider, label, src: frame.src, width: "100%", height };
|
|
1120
|
+
const [width, h] = FRAME_SIZE[isEmbedAspect(aspect) ? aspect : "16:9"];
|
|
1121
|
+
return { kind: "frame", provider, label, src: frame.src, width, height: h };
|
|
1122
|
+
}
|
|
1123
|
+
|
|
687
1124
|
// src/blocks/portableText.tsx
|
|
688
1125
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
689
1126
|
var TOKEN = /^[a-z0-9-]+$/i;
|
|
@@ -815,8 +1252,119 @@ function Table({ value }) {
|
|
|
815
1252
|
] }) : notes ? /* @__PURE__ */ jsx2("p", { "data-notes": "", id: notesId, children: notes }) : null
|
|
816
1253
|
] }) : table;
|
|
817
1254
|
}
|
|
818
|
-
function
|
|
819
|
-
return {
|
|
1255
|
+
function CheckItem({ checked, address, children }) {
|
|
1256
|
+
return /* @__PURE__ */ jsxs("li", { ...address, "data-checked": String(checked), children: [
|
|
1257
|
+
/* @__PURE__ */ jsx2("input", { type: "checkbox", disabled: true, defaultChecked: checked }),
|
|
1258
|
+
children
|
|
1259
|
+
] });
|
|
1260
|
+
}
|
|
1261
|
+
function XCard({ url, card }) {
|
|
1262
|
+
return /* @__PURE__ */ jsxs("blockquote", { className: "twitter-tweet bcms-embed-card", cite: url, children: [
|
|
1263
|
+
/* @__PURE__ */ jsx2("p", { children: card.text }),
|
|
1264
|
+
"\u2014 ",
|
|
1265
|
+
card.authorName,
|
|
1266
|
+
" ",
|
|
1267
|
+
/* @__PURE__ */ jsx2("a", { href: url, children: card.date || "View on X" })
|
|
1268
|
+
] });
|
|
1269
|
+
}
|
|
1270
|
+
function RelatedPosts({ value, resolved }) {
|
|
1271
|
+
const { key, title, mode, count, picked, items } = relatedPostsView(value, resolved);
|
|
1272
|
+
if (!resolved) {
|
|
1273
|
+
return /* @__PURE__ */ jsx2("aside", { "data-rt": "related", "data-key": key, "data-mode": mode, "data-count": count, "data-posts": picked.join(","), "data-title": title || void 0 });
|
|
1274
|
+
}
|
|
1275
|
+
if (!items.length) return null;
|
|
1276
|
+
return /* @__PURE__ */ jsxs("aside", { "data-rt": "related", "data-mode": mode, "data-count": count, "data-posts": picked.join(","), children: [
|
|
1277
|
+
title ? /* @__PURE__ */ jsx2("p", { "data-title": "", children: title }) : null,
|
|
1278
|
+
items.length ? /* @__PURE__ */ jsx2("ul", { children: items.map((p) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2("a", { href: p.url, children: p.title }) }, p.id)) }) : null
|
|
1279
|
+
] });
|
|
1280
|
+
}
|
|
1281
|
+
function AuthorBio({ value, resolved }) {
|
|
1282
|
+
const { key, id, author } = authorBioView(value, resolved);
|
|
1283
|
+
if (!resolved) return /* @__PURE__ */ jsx2("aside", { "data-rt": "author", "data-key": key, "data-author": id });
|
|
1284
|
+
if (!author) return null;
|
|
1285
|
+
return /* @__PURE__ */ jsxs("aside", { "data-rt": "author", "data-author": id, children: [
|
|
1286
|
+
author?.avatar ? /* @__PURE__ */ jsx2("img", { src: author.avatar, alt: "" }) : null,
|
|
1287
|
+
author ? /* @__PURE__ */ jsx2("p", { "data-name": "", children: author.name }) : null,
|
|
1288
|
+
author?.bio ? /* @__PURE__ */ jsx2("p", { "data-bio": "", children: author.bio }) : null
|
|
1289
|
+
] });
|
|
1290
|
+
}
|
|
1291
|
+
function Columns({ value, components }) {
|
|
1292
|
+
const view = columnsView(value);
|
|
1293
|
+
if (!view) return null;
|
|
1294
|
+
const s = COLUMNS_STYLES[view.gap];
|
|
1295
|
+
return /* @__PURE__ */ jsx2("div", { "data-rt": "columns", "data-gap": view.gap, "data-cols": view.columns.length, style: styleObject(s.row), children: view.columns.map((c) => /* @__PURE__ */ jsx2("div", { "data-col": "true", style: styleObject(s.col), children: c.content.length ? /* @__PURE__ */ jsx2(PT, { value: c.content, components }) : null }, c.key)) });
|
|
1296
|
+
}
|
|
1297
|
+
function FootnoteMark({ value, footnotes }) {
|
|
1298
|
+
const note = typeof value.note === "string" ? value.note : "";
|
|
1299
|
+
const n = footnotes.nodes.get(value);
|
|
1300
|
+
if (!n) return /* @__PURE__ */ jsx2("sup", { "data-rt": "fn", "data-note": note });
|
|
1301
|
+
return /* @__PURE__ */ jsx2("sup", { "data-rt": "fn", id: `fnref-${n}`, "data-note": note, children: /* @__PURE__ */ jsx2("a", { href: `#fn-${n}`, "aria-label": `Footnote ${n}`, children: n }) });
|
|
1302
|
+
}
|
|
1303
|
+
function FootnoteList({ notes }) {
|
|
1304
|
+
if (!notes.length) return null;
|
|
1305
|
+
return /* @__PURE__ */ jsx2("ol", { className: "cs-footnotes", "data-rt": "footnotes", "aria-label": "Footnotes", children: notes.map((note, i) => /* @__PURE__ */ jsxs("li", { id: `fn-${i + 1}`, children: [
|
|
1306
|
+
note,
|
|
1307
|
+
" ",
|
|
1308
|
+
/* @__PURE__ */ jsx2("a", { href: `#fnref-${i + 1}`, "aria-label": `Back to reference ${i + 1}`, children: "\u21A9" })
|
|
1309
|
+
] }, i)) });
|
|
1310
|
+
}
|
|
1311
|
+
function Toc({ value, outline }) {
|
|
1312
|
+
const { title, label, depth } = tocView(value);
|
|
1313
|
+
const link = (o) => /* @__PURE__ */ jsx2("a", { href: `#${o.id}`, children: o.text });
|
|
1314
|
+
return /* @__PURE__ */ jsxs("nav", { "data-rt": "toc", "data-depth": depth, "aria-label": label, children: [
|
|
1315
|
+
title ? /* @__PURE__ */ jsx2("p", { "data-title": "", children: title }) : null,
|
|
1316
|
+
/* @__PURE__ */ jsx2("ol", { children: tocTree(outline.items, depth).map((e) => /* @__PURE__ */ jsxs("li", { children: [
|
|
1317
|
+
link(e),
|
|
1318
|
+
e.children.length ? /* @__PURE__ */ jsx2("ol", { children: e.children.map((c) => /* @__PURE__ */ jsx2("li", { children: link(c) }, c.id)) }) : null
|
|
1319
|
+
] }, e.id)) })
|
|
1320
|
+
] });
|
|
1321
|
+
}
|
|
1322
|
+
function Gallery({ value }) {
|
|
1323
|
+
const { layout, images } = galleryView(value);
|
|
1324
|
+
const s = GALLERY_STYLES[layout];
|
|
1325
|
+
return /* @__PURE__ */ jsx2("figure", { "data-rt": "gallery", "data-layout": layout, children: /* @__PURE__ */ jsx2("ul", { style: styleObject(s.ul), tabIndex: layout === "carousel" ? 0 : void 0, children: images.map((x) => /* @__PURE__ */ jsx2("li", { style: styleObject(s.li), children: /* @__PURE__ */ jsxs("figure", { style: styleObject(s.figure), children: [
|
|
1326
|
+
/* @__PURE__ */ jsx2(
|
|
1327
|
+
"img",
|
|
1328
|
+
{
|
|
1329
|
+
alt: x.alt,
|
|
1330
|
+
style: styleObject(x.position ? `${s.img};object-position:${x.position}` : s.img),
|
|
1331
|
+
loading: "lazy",
|
|
1332
|
+
...Object.fromEntries(x.data),
|
|
1333
|
+
src: x.src
|
|
1334
|
+
}
|
|
1335
|
+
),
|
|
1336
|
+
x.caption ? /* @__PURE__ */ jsx2("figcaption", { children: x.caption }) : null
|
|
1337
|
+
] }) }, x.key)) }) });
|
|
1338
|
+
}
|
|
1339
|
+
function Embed({ value, embeds }) {
|
|
1340
|
+
const v = value ?? {};
|
|
1341
|
+
const url = typeof v.url === "string" ? v.url.trim() : "";
|
|
1342
|
+
const aspect = isEmbedAspect(v.aspect) ? v.aspect : "16:9";
|
|
1343
|
+
const title = typeof v.title === "string" ? v.title.trim() : "";
|
|
1344
|
+
const r = resolveEmbed(url, aspect);
|
|
1345
|
+
const label = title || r.label;
|
|
1346
|
+
const card = r.kind === "card" ? embeds.cards?.[r.url] : void 0;
|
|
1347
|
+
let body = null;
|
|
1348
|
+
if (r.kind === "frame") {
|
|
1349
|
+
body = /* @__PURE__ */ jsx2("iframe", { src: r.src, title: label, width: r.width, height: r.height, loading: "lazy", referrerPolicy: "strict-origin-when-cross-origin", allowFullScreen: true });
|
|
1350
|
+
} else if (r.kind === "audio") {
|
|
1351
|
+
body = /* @__PURE__ */ jsx2("audio", { controls: true, preload: "none", src: r.src, title: label });
|
|
1352
|
+
} else if (r.kind === "card" && card) {
|
|
1353
|
+
body = /* @__PURE__ */ jsx2(XCard, { url: r.url, card });
|
|
1354
|
+
} else if (r.url) {
|
|
1355
|
+
body = /* @__PURE__ */ jsx2("a", { href: r.url, children: label });
|
|
1356
|
+
}
|
|
1357
|
+
return /* @__PURE__ */ jsx2("figure", { "data-rt": "embed", "data-url": url, "data-provider": r.provider, "data-aspect": aspect, "aria-label": title, children: body });
|
|
1358
|
+
}
|
|
1359
|
+
function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes: /* @__PURE__ */ new WeakMap() }, footnotes = { notes: [], nodes: /* @__PURE__ */ new WeakMap() }, resolved) {
|
|
1360
|
+
const components = {
|
|
1361
|
+
// RT5: every top-level h2 and h3 carries its outline id, as html.ts writes it.
|
|
1362
|
+
block: {
|
|
1363
|
+
h2: ({ value, children }) => /* @__PURE__ */ jsx2("h2", { id: outline.nodes.get(value), children }),
|
|
1364
|
+
h3: ({ value, children }) => /* @__PURE__ */ jsx2("h3", { id: outline.nodes.get(value), children })
|
|
1365
|
+
},
|
|
1366
|
+
list: { check: ({ children }) => /* @__PURE__ */ jsx2("ul", { "data-task": "true", children }) },
|
|
1367
|
+
listItem: { check: ({ value, children }) => /* @__PURE__ */ jsx2(CheckItem, { checked: value.checked === true, children }) },
|
|
820
1368
|
types: {
|
|
821
1369
|
[PT_TYPES.divider]: () => /* @__PURE__ */ jsx2("hr", {}),
|
|
822
1370
|
[PT_TYPES.table]: ({ value }) => /* @__PURE__ */ jsx2(Table, { value }),
|
|
@@ -837,6 +1385,7 @@ function buildComponents(renderBlock) {
|
|
|
837
1385
|
if (rendered !== void 0) return /* @__PURE__ */ jsx2(Fragment, { children: rendered });
|
|
838
1386
|
const builtIn = calloutOrToggle(value);
|
|
839
1387
|
if (builtIn) return builtIn;
|
|
1388
|
+
if (columnsView(value)) return /* @__PURE__ */ jsx2(Columns, { value, components });
|
|
840
1389
|
const copy = detachedComponentCopy(value);
|
|
841
1390
|
if (copy) {
|
|
842
1391
|
return /* @__PURE__ */ jsx2("div", { className: "bcms-block bcms-block--component", "data-bcms-component-copy": copy.componentId, "data-bcms-instance": copy.id, children: /* @__PURE__ */ jsx2(BlocksRenderer, { blocks: copy.blocks }) });
|
|
@@ -866,6 +1415,12 @@ function buildComponents(renderBlock) {
|
|
|
866
1415
|
] }) : null
|
|
867
1416
|
] });
|
|
868
1417
|
},
|
|
1418
|
+
[PT_TYPES.embed]: ({ value }) => /* @__PURE__ */ jsx2(Embed, { value, embeds }),
|
|
1419
|
+
[PT_TYPES.gallery]: ({ value }) => /* @__PURE__ */ jsx2(Gallery, { value }),
|
|
1420
|
+
[PT_TYPES.tableOfContents]: ({ value }) => /* @__PURE__ */ jsx2(Toc, { value, outline }),
|
|
1421
|
+
[PT_TYPES.columns]: ({ value }) => /* @__PURE__ */ jsx2(Columns, { value, components }),
|
|
1422
|
+
[PT_TYPES.relatedPosts]: ({ value }) => /* @__PURE__ */ jsx2(RelatedPosts, { value, resolved }),
|
|
1423
|
+
[PT_TYPES.authorBio]: ({ value }) => /* @__PURE__ */ jsx2(AuthorBio, { value, resolved }),
|
|
869
1424
|
/**
|
|
870
1425
|
* Key takeaways — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's
|
|
871
1426
|
* `<aside data-rt="takeaways">`, read through the one `keyTakeawaysView` both renderers share.
|
|
@@ -878,6 +1433,7 @@ function buildComponents(renderBlock) {
|
|
|
878
1433
|
/* @__PURE__ */ jsx2("ul", { children: points.map((p, i) => /* @__PURE__ */ jsx2("li", { children: p }, i)) })
|
|
879
1434
|
] });
|
|
880
1435
|
},
|
|
1436
|
+
[PT_TYPES.footnote]: ({ value }) => /* @__PURE__ */ jsx2(FootnoteMark, { value, footnotes }),
|
|
881
1437
|
[PT_TYPES.mention]: ({ value }) => {
|
|
882
1438
|
const v = value;
|
|
883
1439
|
return /* @__PURE__ */ jsx2("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
|
|
@@ -893,7 +1449,7 @@ function buildComponents(renderBlock) {
|
|
|
893
1449
|
const href = typeof v.href === "string" ? safeHref(v.href) : null;
|
|
894
1450
|
if (!href) return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
895
1451
|
const blank = v.target === "_blank";
|
|
896
|
-
return /* @__PURE__ */ jsx2("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
|
|
1452
|
+
return /* @__PURE__ */ jsx2("a", { ...v.button === true ? { "data-button": "true" } : {}, href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
|
|
897
1453
|
},
|
|
898
1454
|
/**
|
|
899
1455
|
* The site's own inline markup, rendered as itself — the React half of
|
|
@@ -942,6 +1498,7 @@ function buildComponents(renderBlock) {
|
|
|
942
1498
|
unknownType: ({ children }) => /* @__PURE__ */ jsx2(Fragment, { children: children ?? null }),
|
|
943
1499
|
unknownBlockStyle: ({ children }) => /* @__PURE__ */ jsx2("p", { children })
|
|
944
1500
|
};
|
|
1501
|
+
return components;
|
|
945
1502
|
}
|
|
946
1503
|
var isListItem = (node) => node._type === "block" && typeof node.listItem === "string" && !!node.listItem;
|
|
947
1504
|
function runsOf(blocks) {
|
|
@@ -982,22 +1539,42 @@ function calloutOrToggle(value) {
|
|
|
982
1539
|
}
|
|
983
1540
|
return null;
|
|
984
1541
|
}
|
|
985
|
-
function PortableText({ value, bindingBase, renderBlock }) {
|
|
1542
|
+
function PortableText({ value, bindingBase, renderBlock, embeds, resolved }) {
|
|
986
1543
|
const blocks = React.useMemo(() => normalizePortableText(readValue(value)), [value]);
|
|
987
|
-
const
|
|
1544
|
+
const outline = React.useMemo(() => outlineOf(blocks), [blocks]);
|
|
1545
|
+
const footnotes = React.useMemo(() => footnoteOrder(blocks), [blocks]);
|
|
1546
|
+
const components = React.useMemo(
|
|
1547
|
+
() => buildComponents(renderBlock, embeds, outline, footnotes, resolved),
|
|
1548
|
+
[renderBlock, embeds, outline, footnotes, resolved]
|
|
1549
|
+
);
|
|
988
1550
|
const listComponents = React.useMemo(() => {
|
|
989
1551
|
if (!bindingBase) return null;
|
|
990
1552
|
const keys = new Set(blocks.map((b) => b._key));
|
|
991
1553
|
return {
|
|
992
1554
|
...components,
|
|
993
|
-
listItem: ({ value: item, children }) =>
|
|
1555
|
+
listItem: ({ value: item, children }) => {
|
|
1556
|
+
const address = keys.has(item._key) ? { "data-bcms-field": `${bindingBase}#${item._key}`, "data-bcms-kind": "richtext" } : void 0;
|
|
1557
|
+
if (item.listItem === "check") {
|
|
1558
|
+
return /* @__PURE__ */ jsx2(CheckItem, { checked: item.checked === true, address, children });
|
|
1559
|
+
}
|
|
1560
|
+
return /* @__PURE__ */ jsx2("li", { ...address, children });
|
|
1561
|
+
}
|
|
994
1562
|
};
|
|
995
1563
|
}, [bindingBase, blocks, components]);
|
|
996
1564
|
if (blocks.length === 0) return null;
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1565
|
+
const notes = /* @__PURE__ */ jsx2(FootnoteList, { notes: footnotes.notes });
|
|
1566
|
+
if (!bindingBase || !listComponents) {
|
|
1567
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1568
|
+
/* @__PURE__ */ jsx2(PT, { value: blocks, components }),
|
|
1569
|
+
notes
|
|
1570
|
+
] });
|
|
1571
|
+
}
|
|
1572
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1573
|
+
runsOf(blocks).map(
|
|
1574
|
+
(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)
|
|
1575
|
+
),
|
|
1576
|
+
notes
|
|
1577
|
+
] });
|
|
1001
1578
|
}
|
|
1002
1579
|
function readValue(value) {
|
|
1003
1580
|
if (Array.isArray(value)) return value;
|
|
@@ -1171,7 +1748,7 @@ function Spacer({ height = 32, className = "" }) {
|
|
|
1171
1748
|
// src/blocks/Columns.tsx
|
|
1172
1749
|
import "react";
|
|
1173
1750
|
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1174
|
-
function
|
|
1751
|
+
function Columns2({
|
|
1175
1752
|
columns,
|
|
1176
1753
|
gap = 16,
|
|
1177
1754
|
className = "",
|
|
@@ -1425,7 +2002,7 @@ var BLOCK_REGISTRY = {
|
|
|
1425
2002
|
icon: "minus"
|
|
1426
2003
|
},
|
|
1427
2004
|
columns: {
|
|
1428
|
-
component:
|
|
2005
|
+
component: Columns2,
|
|
1429
2006
|
label: "Columns",
|
|
1430
2007
|
icon: "columns"
|
|
1431
2008
|
},
|