@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/registry.js
CHANGED
|
@@ -183,6 +183,24 @@ var PT_TYPES = {
|
|
|
183
183
|
pullQuote: "pullQuote",
|
|
184
184
|
/** Key takeaways — see `PortableTextKeyTakeaways`. The V2 kit's `<aside data-rt="takeaways">`. */
|
|
185
185
|
keyTakeaways: "keyTakeaways",
|
|
186
|
+
/**
|
|
187
|
+
* A third-party embed (X, CodePen, Figma, Maps, Spotify, Podcasts, audio, a link card) — see
|
|
188
|
+
* `PortableTextEmbed` and `embed.ts`. The V2 kit's `<figure data-rt="embed">`. Gated by the
|
|
189
|
+
* `embed` feature; YouTube and Vimeo links still resolve here too, through the video resolver.
|
|
190
|
+
*/
|
|
191
|
+
embed: "embed",
|
|
192
|
+
/** A gallery of images, a grid or a carousel — see `PortableTextGallery`. The V2 kit's `<figure data-rt="gallery">`. */
|
|
193
|
+
gallery: "gallery",
|
|
194
|
+
/** A table of contents built from the headings — see `PortableTextTableOfContents`. The V2 kit's `<nav data-rt="toc">`. */
|
|
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",
|
|
186
204
|
/**
|
|
187
205
|
* A table. Spelled `table`, unprefixed, because that is what every other Portable Text
|
|
188
206
|
* producer calls one — the Sanity-shaped `{_type:"table", rows}` this platform used to refuse
|
|
@@ -209,7 +227,13 @@ var STORABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
|
209
227
|
PT_TYPES.video,
|
|
210
228
|
PT_TYPES.file,
|
|
211
229
|
PT_TYPES.pullQuote,
|
|
212
|
-
PT_TYPES.keyTakeaways
|
|
230
|
+
PT_TYPES.keyTakeaways,
|
|
231
|
+
PT_TYPES.embed,
|
|
232
|
+
PT_TYPES.gallery,
|
|
233
|
+
PT_TYPES.tableOfContents,
|
|
234
|
+
PT_TYPES.columns,
|
|
235
|
+
PT_TYPES.authorBio,
|
|
236
|
+
PT_TYPES.relatedPosts
|
|
213
237
|
]);
|
|
214
238
|
var EMBEDDED_COMPONENT_KEY = "component";
|
|
215
239
|
|
|
@@ -684,16 +708,109 @@ function keyTakeawaysView(value) {
|
|
|
684
708
|
return { title, points };
|
|
685
709
|
}
|
|
686
710
|
|
|
687
|
-
// ../richtext/src/
|
|
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
|
+
|
|
733
|
+
// ../richtext/src/gallery.ts
|
|
688
734
|
var isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
689
|
-
var
|
|
735
|
+
var galleryMediaSrc = (raw) => safeHref(raw) ?? (raw !== "" && !raw.startsWith("//") && !/[:\s]/.test(raw) ? raw : null);
|
|
736
|
+
var GALLERY_STYLES = {
|
|
737
|
+
grid: {
|
|
738
|
+
ul: "display:grid;grid-template-columns:repeat(auto-fill,minmax(12rem,1fr));gap:1rem;list-style:none;margin:0;padding:0",
|
|
739
|
+
li: "margin:0",
|
|
740
|
+
figure: "margin:0",
|
|
741
|
+
img: "display:block;width:100%;height:auto"
|
|
742
|
+
},
|
|
743
|
+
carousel: {
|
|
744
|
+
ul: "display:flex;gap:1rem;overflow-x:auto;scroll-snap-type:x mandatory;list-style:none;margin:0;padding:0",
|
|
745
|
+
li: "flex:0 0 100%;scroll-snap-align:start;margin:0",
|
|
746
|
+
figure: "margin:0",
|
|
747
|
+
img: "display:block;width:100%;height:auto"
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
function galleryView(value) {
|
|
751
|
+
const v = isRecord3(value) ? value : {};
|
|
752
|
+
const layout = v.layout === "carousel" ? "carousel" : "grid";
|
|
753
|
+
const images = [];
|
|
754
|
+
(Array.isArray(v.images) ? v.images : []).forEach((item, i) => {
|
|
755
|
+
if (!isRecord3(item) || !isRecord3(item.image)) return;
|
|
756
|
+
const src = galleryMediaSrc(typeof item.image.src === "string" ? item.image.src.trim() : "");
|
|
757
|
+
if (!src) return;
|
|
758
|
+
const frame = richTextImageFrame(src, item.image.crop, item.image.focal);
|
|
759
|
+
images.push({
|
|
760
|
+
key: typeof item._key === "string" && item._key ? item._key : `i${i}`,
|
|
761
|
+
src: frame.src,
|
|
762
|
+
position: frame.position,
|
|
763
|
+
data: frame.data,
|
|
764
|
+
alt: typeof item.image.alt === "string" ? item.image.alt : "",
|
|
765
|
+
caption: typeof item.caption === "string" ? item.caption.trim() : ""
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
return { layout, images };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// ../richtext/src/columns.ts
|
|
772
|
+
var PT_COLUMNS_LIMITS = { min: 2, max: 4 };
|
|
773
|
+
var BUILTIN_COLUMNS_KEY = "builtin:columns";
|
|
774
|
+
var isRecord4 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
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;
|
|
690
807
|
function detachedComponentCopy(value) {
|
|
691
808
|
const f = fieldsOf(value);
|
|
692
809
|
if (!f || f.detached !== true || !Array.isArray(f.blocks)) return null;
|
|
693
810
|
return {
|
|
694
|
-
id:
|
|
811
|
+
id: isRecord5(value) && typeof value._key === "string" ? value._key : "",
|
|
695
812
|
componentId: typeof f.componentId === "string" ? f.componentId : "",
|
|
696
|
-
props:
|
|
813
|
+
props: isRecord5(f.props) ? f.props : {},
|
|
697
814
|
blocks: f.blocks,
|
|
698
815
|
...typeof f.sourcePublishedAt === "string" ? { sourcePublishedAt: f.sourcePublishedAt } : {},
|
|
699
816
|
...typeof f.variant === "string" ? { variant: f.variant } : {}
|
|
@@ -706,7 +823,7 @@ function componentCopyLines(copy) {
|
|
|
706
823
|
const t = v.replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/\s+/g, " ").trim();
|
|
707
824
|
return t || null;
|
|
708
825
|
}
|
|
709
|
-
if (
|
|
826
|
+
if (isRecord5(v)) return line(v.label ?? v.text ?? v.title);
|
|
710
827
|
return null;
|
|
711
828
|
};
|
|
712
829
|
return Object.values(copy.props).map(line).filter((s) => !!s);
|
|
@@ -733,6 +850,18 @@ function portableTextToPlainText(value) {
|
|
|
733
850
|
lines.push(...title ? [title] : [], ...points);
|
|
734
851
|
continue;
|
|
735
852
|
}
|
|
853
|
+
if (node._type === PT_TYPES.gallery) {
|
|
854
|
+
lines.push(...galleryView(node).images.map((x) => x.caption).filter(Boolean));
|
|
855
|
+
continue;
|
|
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
|
+
}
|
|
736
865
|
const table = migrateTable(node);
|
|
737
866
|
if (table) {
|
|
738
867
|
lines.push(...tableLines(table));
|
|
@@ -771,20 +900,121 @@ var pullQuoteLines = (v) => {
|
|
|
771
900
|
return [text(v.quote), ...by ? [`\u2014 ${by}`] : []].filter(Boolean);
|
|
772
901
|
};
|
|
773
902
|
|
|
903
|
+
// ../richtext/src/toc.ts
|
|
904
|
+
import { slugify } from "@bettercms-ai/types";
|
|
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();
|
|
908
|
+
function outlineOf(value) {
|
|
909
|
+
const heads = [];
|
|
910
|
+
const used = /* @__PURE__ */ new Set();
|
|
911
|
+
for (const node of Array.isArray(value) ? value : []) {
|
|
912
|
+
if (!isRecord6(node) || node._type !== "block" || node.listItem || node.style !== "h2" && node.style !== "h3") continue;
|
|
913
|
+
const text = headingText(node);
|
|
914
|
+
if (!text) continue;
|
|
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
|
+
}
|
|
929
|
+
nodes.set(node, id);
|
|
930
|
+
items.push({ id, text, depth: node.style === "h2" ? 2 : 3 });
|
|
931
|
+
}
|
|
932
|
+
return { items, nodes };
|
|
933
|
+
}
|
|
934
|
+
function tocTree(items, depth) {
|
|
935
|
+
const out = [];
|
|
936
|
+
for (const it of items) {
|
|
937
|
+
if (it.depth === 3 && depth === "2") continue;
|
|
938
|
+
const last = out[out.length - 1];
|
|
939
|
+
if (it.depth === 3 && last) last.children.push(it);
|
|
940
|
+
else out.push({ ...it, children: [] });
|
|
941
|
+
}
|
|
942
|
+
return out;
|
|
943
|
+
}
|
|
944
|
+
function tocView(value) {
|
|
945
|
+
const v = isRecord6(value) ? value : {};
|
|
946
|
+
const title = typeof v.title === "string" ? v.title.trim() : "";
|
|
947
|
+
return { title, label: title || "Table of contents", depth: v.depth === "2" ? "2" : "3" };
|
|
948
|
+
}
|
|
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
|
+
|
|
774
1004
|
// ../richtext/src/read.ts
|
|
775
|
-
var
|
|
1005
|
+
var isRecord9 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
776
1006
|
function normalizePortableText(value, key = randomKey) {
|
|
777
1007
|
if (!Array.isArray(value)) return [];
|
|
778
1008
|
return value.flatMap((raw) => {
|
|
779
|
-
if (!
|
|
1009
|
+
if (!isRecord9(raw) || typeof raw._type !== "string") return [];
|
|
780
1010
|
const table = migrateTable(raw, key, (v, cellKey) => normalizePortableText(v, cellKey));
|
|
781
1011
|
if (table) return [table];
|
|
782
|
-
const node = { ...raw, _key:
|
|
1012
|
+
const node = { ...raw, _key: isRecord9(raw) && typeof raw._key === "string" && raw._key ? raw._key : key() };
|
|
783
1013
|
if (node._type === "block") {
|
|
784
1014
|
if (!Array.isArray(node.markDefs)) node.markDefs = [];
|
|
785
1015
|
if (typeof node.style !== "string") node.style = "normal";
|
|
786
1016
|
node.children = (Array.isArray(node.children) ? node.children : []).map((c) => {
|
|
787
|
-
if (!
|
|
1017
|
+
if (!isRecord9(c)) return c;
|
|
788
1018
|
const child = { ...c, _key: typeof c._key === "string" && c._key ? c._key : key() };
|
|
789
1019
|
if (child._type === "span" && !Array.isArray(child.marks)) child.marks = [];
|
|
790
1020
|
return child;
|
|
@@ -794,12 +1024,219 @@ function normalizePortableText(value, key = randomKey) {
|
|
|
794
1024
|
});
|
|
795
1025
|
}
|
|
796
1026
|
function readStructuredContent(props) {
|
|
797
|
-
if (!
|
|
1027
|
+
if (!isRecord9(props)) return null;
|
|
798
1028
|
const candidate = props.content ?? props.value;
|
|
799
|
-
const arr = Array.isArray(candidate) ? candidate :
|
|
1029
|
+
const arr = Array.isArray(candidate) ? candidate : isRecord9(candidate) ? Array.isArray(candidate.content) ? candidate.content : Array.isArray(candidate.value) ? candidate.value : null : null;
|
|
800
1030
|
return arr && arr.length > 0 ? arr : null;
|
|
801
1031
|
}
|
|
802
1032
|
|
|
1033
|
+
// ../richtext/src/video.ts
|
|
1034
|
+
var videoId = (value) => value && /^[a-zA-Z0-9_-]{6,32}$/.test(value) ? value : null;
|
|
1035
|
+
function resolveVideoSource(value) {
|
|
1036
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
1037
|
+
let parsed;
|
|
1038
|
+
try {
|
|
1039
|
+
parsed = new URL(value.trim());
|
|
1040
|
+
} catch {
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
1044
|
+
const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
|
|
1045
|
+
let id = null;
|
|
1046
|
+
if (host === "youtu.be") id = videoId(parsed.pathname.split("/").filter(Boolean)[0]);
|
|
1047
|
+
if (host === "youtube.com" || host === "m.youtube.com" || host === "youtube-nocookie.com") {
|
|
1048
|
+
id = videoId(
|
|
1049
|
+
parsed.searchParams.get("v") ?? parsed.pathname.match(/^\/(?:embed|shorts)\/([^/?#]+)/)?.[1]
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
if (id) {
|
|
1053
|
+
return {
|
|
1054
|
+
kind: "iframe",
|
|
1055
|
+
src: `https://www.youtube-nocookie.com/embed/${id}`,
|
|
1056
|
+
provider: "YouTube"
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
if (host === "vimeo.com" || host === "player.vimeo.com") {
|
|
1060
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
1061
|
+
let vimeoId = null;
|
|
1062
|
+
for (let i = segments.length - 1; i >= 0; i -= 1) {
|
|
1063
|
+
if (/^\d+$/.test(segments[i])) {
|
|
1064
|
+
vimeoId = segments[i];
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (vimeoId) {
|
|
1069
|
+
return {
|
|
1070
|
+
kind: "iframe",
|
|
1071
|
+
src: `https://player.vimeo.com/video/${vimeoId}`,
|
|
1072
|
+
provider: "Vimeo"
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (/\.(?:m4v|mp4|og[gv]|webm)$/i.test(parsed.pathname)) {
|
|
1077
|
+
return { kind: "video", src: parsed.toString(), provider: "Video file" };
|
|
1078
|
+
}
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// ../richtext/src/embed.ts
|
|
1083
|
+
var EMBED_ASPECTS = ["16:9", "4:3", "1:1", "9:16"];
|
|
1084
|
+
var isEmbedAspect = (v) => EMBED_ASPECTS.includes(v);
|
|
1085
|
+
function detectEmbed(url) {
|
|
1086
|
+
const u = url.trim();
|
|
1087
|
+
let host = "";
|
|
1088
|
+
try {
|
|
1089
|
+
host = new URL(u).hostname.replace(/^www\./, "");
|
|
1090
|
+
} catch {
|
|
1091
|
+
host = "";
|
|
1092
|
+
}
|
|
1093
|
+
const is = (re) => re.test(u);
|
|
1094
|
+
if (is(/(?:youtube\.com\/(?:watch\?|embed\/|shorts\/|live\/)|youtu\.be\/)/i)) return { provider: "youtube", label: "YouTube", host };
|
|
1095
|
+
if (is(/vimeo\.com\/(?:video\/)?\d+/i)) return { provider: "vimeo", label: "Vimeo", host };
|
|
1096
|
+
if (is(/(?:twitter|x)\.com\/[^/]+\/status\/\d+/i)) return { provider: "x", label: "X post", host };
|
|
1097
|
+
if (is(/codepen\.io\/[^/]+\/(?:pen|embed)\//i)) return { provider: "codepen", label: "CodePen", host };
|
|
1098
|
+
if (is(/figma\.com\/(?:file|design|proto|board)\//i)) return { provider: "figma", label: "Figma", host };
|
|
1099
|
+
if (is(/(?:google\.[a-z.]+\/maps|maps\.app\.goo\.gl|goo\.gl\/maps)/i)) return { provider: "maps", label: "Google Maps", host };
|
|
1100
|
+
if (is(/open\.spotify\.com\/(?:episode|track|show|playlist|album)\//i)) return { provider: "spotify", label: "Spotify", host };
|
|
1101
|
+
if (is(/podcasts\.apple\.com\//i)) return { provider: "podcast", label: "Apple Podcasts", host };
|
|
1102
|
+
if (is(/\.(?:mp3|m4a|wav|ogg|aac)(?:\?|$)/i)) return { provider: "audio", label: "Audio", host };
|
|
1103
|
+
return { provider: "link", label: host || "Link", host };
|
|
1104
|
+
}
|
|
1105
|
+
function detectMore(url) {
|
|
1106
|
+
const host = url.hostname.toLowerCase().replace(/^www\./, "");
|
|
1107
|
+
const path = url.pathname;
|
|
1108
|
+
if (host === "youtube-nocookie.com" && path.startsWith("/embed/")) return { provider: "youtube", label: "YouTube" };
|
|
1109
|
+
if (host === "open.spotify.com" && /^\/embed\/(?:episode|track|show|playlist|album)\//.test(path)) return { provider: "spotify", label: "Spotify" };
|
|
1110
|
+
if (host === "calendly.com") return { provider: "calendly", label: "Calendly" };
|
|
1111
|
+
if (host.endsWith(".typeform.com") && /^\/to\//.test(path)) return { provider: "typeform", label: "Typeform" };
|
|
1112
|
+
if (host === "loom.com" && /^\/(?:share|embed)\//.test(path)) return { provider: "loom", label: "Loom" };
|
|
1113
|
+
if (host === "airtable.com") return { provider: "airtable", label: "Airtable" };
|
|
1114
|
+
if (host === "docs.google.com") {
|
|
1115
|
+
const kind = path.split("/")[1];
|
|
1116
|
+
const label = { forms: "Google Forms", document: "Google Docs", spreadsheets: "Google Sheets", presentation: "Google Slides" }[kind ?? ""];
|
|
1117
|
+
if (label) return { provider: "google", label };
|
|
1118
|
+
}
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
var FRAME_SIZE = {
|
|
1122
|
+
"16:9": ["640", "360"],
|
|
1123
|
+
"4:3": ["640", "480"],
|
|
1124
|
+
"1:1": ["640", "640"],
|
|
1125
|
+
"9:16": ["360", "640"]
|
|
1126
|
+
};
|
|
1127
|
+
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
1128
|
+
var FULL_WIDTH_HEIGHT = { calendly: "700", typeform: "500", airtable: "533" };
|
|
1129
|
+
function frameOf(provider, url) {
|
|
1130
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
1131
|
+
switch (provider) {
|
|
1132
|
+
case "youtube":
|
|
1133
|
+
case "vimeo": {
|
|
1134
|
+
const video = resolveVideoSource(url.href);
|
|
1135
|
+
return video?.kind === "iframe" ? { src: video.src } : null;
|
|
1136
|
+
}
|
|
1137
|
+
case "codepen": {
|
|
1138
|
+
const [user, mode, id] = parts;
|
|
1139
|
+
return user && ID.test(user) && (mode === "pen" || mode === "embed") && id && ID.test(id) ? { src: `https://codepen.io/${user}/embed/${id}?default-tab=result` } : null;
|
|
1140
|
+
}
|
|
1141
|
+
case "figma": {
|
|
1142
|
+
const [mode, key] = parts;
|
|
1143
|
+
if (!mode || !["file", "design", "proto", "board"].includes(mode) || !key || !ID.test(key)) return null;
|
|
1144
|
+
const frame = new URL(`https://embed.figma.com/${mode}/${key}`);
|
|
1145
|
+
const node = url.searchParams.get("node-id");
|
|
1146
|
+
if (node && /^[0-9A-Za-z:_-]{1,64}$/.test(node)) frame.searchParams.set("node-id", node);
|
|
1147
|
+
frame.searchParams.set("embed-host", "bettercms");
|
|
1148
|
+
return { src: frame.href };
|
|
1149
|
+
}
|
|
1150
|
+
case "maps": {
|
|
1151
|
+
if (!/^(?:www\.)?google\.[a-z.]+$/i.test(url.hostname)) return null;
|
|
1152
|
+
const pb = url.searchParams.get("pb");
|
|
1153
|
+
if (parts[0] === "maps" && parts[1] === "embed" && pb && /^[!A-Za-z0-9._:%+-]{1,2000}$/.test(pb)) {
|
|
1154
|
+
return { src: `https://www.google.com/maps/embed?pb=${encodeURIComponent(pb)}` };
|
|
1155
|
+
}
|
|
1156
|
+
const at = /@(-?\d{1,3}(?:\.\d+)?),(-?\d{1,3}(?:\.\d+)?)/.exec(url.pathname);
|
|
1157
|
+
const place = parts[0] === "maps" && parts[1] === "place" && parts[2] ? decodeURIComponent(parts[2]).replace(/\+/g, " ") : null;
|
|
1158
|
+
const q = at ? `${at[1]},${at[2]}` : url.searchParams.get("q") ?? place;
|
|
1159
|
+
if (!q || q.length > 200) return null;
|
|
1160
|
+
return { src: `https://maps.google.com/maps?q=${encodeURIComponent(q)}&output=embed` };
|
|
1161
|
+
}
|
|
1162
|
+
case "spotify": {
|
|
1163
|
+
const [type, id] = parts[0] === "embed" ? parts.slice(1) : parts;
|
|
1164
|
+
if (!type || !["episode", "track", "show", "playlist", "album"].includes(type) || !id || !ID.test(id)) return null;
|
|
1165
|
+
return { src: `https://open.spotify.com/embed/${type}/${id}`, height: type === "track" || type === "episode" ? "152" : "352" };
|
|
1166
|
+
}
|
|
1167
|
+
case "podcast":
|
|
1168
|
+
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;
|
|
1169
|
+
case "calendly":
|
|
1170
|
+
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;
|
|
1171
|
+
case "typeform": {
|
|
1172
|
+
const id = parts[0] === "to" ? parts[1] : void 0;
|
|
1173
|
+
return id && /^[A-Za-z0-9]{4,32}$/.test(id) ? { src: `https://form.typeform.com/to/${id}` } : null;
|
|
1174
|
+
}
|
|
1175
|
+
case "loom": {
|
|
1176
|
+
const id = parts[1];
|
|
1177
|
+
return (parts[0] === "share" || parts[0] === "embed") && id && /^[0-9a-f]{32}$/.test(id) ? { src: `https://www.loom.com/embed/${id}` } : null;
|
|
1178
|
+
}
|
|
1179
|
+
case "airtable": {
|
|
1180
|
+
const ids = parts[0] === "embed" ? parts.slice(1) : parts;
|
|
1181
|
+
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;
|
|
1182
|
+
}
|
|
1183
|
+
case "google": {
|
|
1184
|
+
const [kind, d, maybeE, maybeId] = parts;
|
|
1185
|
+
if (d !== "d") return null;
|
|
1186
|
+
const published = maybeE === "e";
|
|
1187
|
+
const id = published ? maybeId : maybeE;
|
|
1188
|
+
if (!id || !/^[A-Za-z0-9_-]{10,128}$/.test(id)) return null;
|
|
1189
|
+
const base = `https://docs.google.com/${kind}/d/${published ? `e/${id}` : id}`;
|
|
1190
|
+
if (kind === "forms") return { src: `${base}/viewform?embedded=true`, height: "800" };
|
|
1191
|
+
if (kind === "document") return { src: published ? `${base}/pub?embedded=true` : `${base}/preview`, height: "600" };
|
|
1192
|
+
if (kind === "spreadsheets") return { src: published ? `${base}/pubhtml?widget=true&headers=false` : `${base}/preview`, height: "600" };
|
|
1193
|
+
if (kind === "presentation") return { src: published ? `${base}/embed` : `${base}/preview` };
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
default:
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
var X_STATUS = /^\/([A-Za-z0-9_]{1,15})\/status\/(\d{1,25})(?:\/|$)/;
|
|
1201
|
+
var X_HOSTS = /^(?:www\.|mobile\.)?(?:twitter|x)\.com$/i;
|
|
1202
|
+
function canonicalEmbedUrl(raw) {
|
|
1203
|
+
const url = typeof raw === "string" ? raw.trim() : "";
|
|
1204
|
+
const safe = url ? safeHref(url) : null;
|
|
1205
|
+
if (!safe || !/^https?:\/\//i.test(safe)) return null;
|
|
1206
|
+
let parsed;
|
|
1207
|
+
try {
|
|
1208
|
+
parsed = new URL(safe);
|
|
1209
|
+
} catch {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
const status = X_HOSTS.test(parsed.hostname) ? X_STATUS.exec(parsed.pathname) : null;
|
|
1213
|
+
if (status) return `https://x.com/${status[1].toLowerCase()}/status/${status[2]}`;
|
|
1214
|
+
parsed.hash = "";
|
|
1215
|
+
return parsed.href;
|
|
1216
|
+
}
|
|
1217
|
+
function resolveEmbed(rawUrl, aspect = "16:9") {
|
|
1218
|
+
const url = typeof rawUrl === "string" ? rawUrl.trim() : "";
|
|
1219
|
+
const v2 = detectEmbed(url);
|
|
1220
|
+
const safe = url ? safeHref(url) : null;
|
|
1221
|
+
let parsed = null;
|
|
1222
|
+
try {
|
|
1223
|
+
parsed = safe && /^https?:\/\//i.test(safe) ? new URL(safe) : null;
|
|
1224
|
+
} catch {
|
|
1225
|
+
parsed = null;
|
|
1226
|
+
}
|
|
1227
|
+
const { provider, label } = v2.provider === "link" && parsed ? detectMore(parsed) ?? v2 : v2;
|
|
1228
|
+
const link = { kind: "link", provider, label, url: safe };
|
|
1229
|
+
if (!parsed) return link;
|
|
1230
|
+
if (provider === "x") return { kind: "card", provider: "x", label, url: canonicalEmbedUrl(parsed.href) ?? parsed.href };
|
|
1231
|
+
if (provider === "audio") return parsed.protocol === "https:" ? { kind: "audio", provider: "audio", label, src: parsed.href } : link;
|
|
1232
|
+
const frame = frameOf(provider, parsed);
|
|
1233
|
+
if (!frame) return link;
|
|
1234
|
+
const height = frame.height ?? FULL_WIDTH_HEIGHT[provider];
|
|
1235
|
+
if (height) return { kind: "frame", provider, label, src: frame.src, width: "100%", height };
|
|
1236
|
+
const [width, h] = FRAME_SIZE[isEmbedAspect(aspect) ? aspect : "16:9"];
|
|
1237
|
+
return { kind: "frame", provider, label, src: frame.src, width, height: h };
|
|
1238
|
+
}
|
|
1239
|
+
|
|
803
1240
|
// src/blocks/portableText.tsx
|
|
804
1241
|
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
805
1242
|
var TOKEN = /^[a-z0-9-]+$/i;
|
|
@@ -931,8 +1368,119 @@ function Table({ value }) {
|
|
|
931
1368
|
] }) : notes ? /* @__PURE__ */ jsx3("p", { "data-notes": "", id: notesId, children: notes }) : null
|
|
932
1369
|
] }) : table;
|
|
933
1370
|
}
|
|
934
|
-
function
|
|
935
|
-
return {
|
|
1371
|
+
function CheckItem({ checked, address, children }) {
|
|
1372
|
+
return /* @__PURE__ */ jsxs2("li", { ...address, "data-checked": String(checked), children: [
|
|
1373
|
+
/* @__PURE__ */ jsx3("input", { type: "checkbox", disabled: true, defaultChecked: checked }),
|
|
1374
|
+
children
|
|
1375
|
+
] });
|
|
1376
|
+
}
|
|
1377
|
+
function XCard({ url, card }) {
|
|
1378
|
+
return /* @__PURE__ */ jsxs2("blockquote", { className: "twitter-tweet bcms-embed-card", cite: url, children: [
|
|
1379
|
+
/* @__PURE__ */ jsx3("p", { children: card.text }),
|
|
1380
|
+
"\u2014 ",
|
|
1381
|
+
card.authorName,
|
|
1382
|
+
" ",
|
|
1383
|
+
/* @__PURE__ */ jsx3("a", { href: url, children: card.date || "View on X" })
|
|
1384
|
+
] });
|
|
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
|
+
}
|
|
1427
|
+
function Toc({ value, outline }) {
|
|
1428
|
+
const { title, label, depth } = tocView(value);
|
|
1429
|
+
const link = (o) => /* @__PURE__ */ jsx3("a", { href: `#${o.id}`, children: o.text });
|
|
1430
|
+
return /* @__PURE__ */ jsxs2("nav", { "data-rt": "toc", "data-depth": depth, "aria-label": label, children: [
|
|
1431
|
+
title ? /* @__PURE__ */ jsx3("p", { "data-title": "", children: title }) : null,
|
|
1432
|
+
/* @__PURE__ */ jsx3("ol", { children: tocTree(outline.items, depth).map((e) => /* @__PURE__ */ jsxs2("li", { children: [
|
|
1433
|
+
link(e),
|
|
1434
|
+
e.children.length ? /* @__PURE__ */ jsx3("ol", { children: e.children.map((c) => /* @__PURE__ */ jsx3("li", { children: link(c) }, c.id)) }) : null
|
|
1435
|
+
] }, e.id)) })
|
|
1436
|
+
] });
|
|
1437
|
+
}
|
|
1438
|
+
function Gallery({ value }) {
|
|
1439
|
+
const { layout, images } = galleryView(value);
|
|
1440
|
+
const s = GALLERY_STYLES[layout];
|
|
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: [
|
|
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
|
+
),
|
|
1452
|
+
x.caption ? /* @__PURE__ */ jsx3("figcaption", { children: x.caption }) : null
|
|
1453
|
+
] }) }, x.key)) }) });
|
|
1454
|
+
}
|
|
1455
|
+
function Embed({ value, embeds }) {
|
|
1456
|
+
const v = value ?? {};
|
|
1457
|
+
const url = typeof v.url === "string" ? v.url.trim() : "";
|
|
1458
|
+
const aspect = isEmbedAspect(v.aspect) ? v.aspect : "16:9";
|
|
1459
|
+
const title = typeof v.title === "string" ? v.title.trim() : "";
|
|
1460
|
+
const r = resolveEmbed(url, aspect);
|
|
1461
|
+
const label = title || r.label;
|
|
1462
|
+
const card = r.kind === "card" ? embeds.cards?.[r.url] : void 0;
|
|
1463
|
+
let body = null;
|
|
1464
|
+
if (r.kind === "frame") {
|
|
1465
|
+
body = /* @__PURE__ */ jsx3("iframe", { src: r.src, title: label, width: r.width, height: r.height, loading: "lazy", referrerPolicy: "strict-origin-when-cross-origin", allowFullScreen: true });
|
|
1466
|
+
} else if (r.kind === "audio") {
|
|
1467
|
+
body = /* @__PURE__ */ jsx3("audio", { controls: true, preload: "none", src: r.src, title: label });
|
|
1468
|
+
} else if (r.kind === "card" && card) {
|
|
1469
|
+
body = /* @__PURE__ */ jsx3(XCard, { url: r.url, card });
|
|
1470
|
+
} else if (r.url) {
|
|
1471
|
+
body = /* @__PURE__ */ jsx3("a", { href: r.url, children: label });
|
|
1472
|
+
}
|
|
1473
|
+
return /* @__PURE__ */ jsx3("figure", { "data-rt": "embed", "data-url": url, "data-provider": r.provider, "data-aspect": aspect, "aria-label": title, children: body });
|
|
1474
|
+
}
|
|
1475
|
+
function buildComponents(renderBlock, embeds = {}, outline = { items: [], nodes: /* @__PURE__ */ new WeakMap() }, footnotes = { notes: [], nodes: /* @__PURE__ */ new WeakMap() }, resolved) {
|
|
1476
|
+
const components = {
|
|
1477
|
+
// RT5: every top-level h2 and h3 carries its outline id, as html.ts writes it.
|
|
1478
|
+
block: {
|
|
1479
|
+
h2: ({ value, children }) => /* @__PURE__ */ jsx3("h2", { id: outline.nodes.get(value), children }),
|
|
1480
|
+
h3: ({ value, children }) => /* @__PURE__ */ jsx3("h3", { id: outline.nodes.get(value), children })
|
|
1481
|
+
},
|
|
1482
|
+
list: { check: ({ children }) => /* @__PURE__ */ jsx3("ul", { "data-task": "true", children }) },
|
|
1483
|
+
listItem: { check: ({ value, children }) => /* @__PURE__ */ jsx3(CheckItem, { checked: value.checked === true, children }) },
|
|
936
1484
|
types: {
|
|
937
1485
|
[PT_TYPES.divider]: () => /* @__PURE__ */ jsx3("hr", {}),
|
|
938
1486
|
[PT_TYPES.table]: ({ value }) => /* @__PURE__ */ jsx3(Table, { value }),
|
|
@@ -953,6 +1501,7 @@ function buildComponents(renderBlock) {
|
|
|
953
1501
|
if (rendered !== void 0) return /* @__PURE__ */ jsx3(Fragment, { children: rendered });
|
|
954
1502
|
const builtIn = calloutOrToggle(value);
|
|
955
1503
|
if (builtIn) return builtIn;
|
|
1504
|
+
if (columnsView(value)) return /* @__PURE__ */ jsx3(Columns, { value, components });
|
|
956
1505
|
const copy = detachedComponentCopy(value);
|
|
957
1506
|
if (copy) {
|
|
958
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 }) });
|
|
@@ -982,6 +1531,12 @@ function buildComponents(renderBlock) {
|
|
|
982
1531
|
] }) : null
|
|
983
1532
|
] });
|
|
984
1533
|
},
|
|
1534
|
+
[PT_TYPES.embed]: ({ value }) => /* @__PURE__ */ jsx3(Embed, { value, embeds }),
|
|
1535
|
+
[PT_TYPES.gallery]: ({ value }) => /* @__PURE__ */ jsx3(Gallery, { value }),
|
|
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 }),
|
|
985
1540
|
/**
|
|
986
1541
|
* Key takeaways — the React half of `portableTextToHtml`'s, and the same DOM: the V2 kit's
|
|
987
1542
|
* `<aside data-rt="takeaways">`, read through the one `keyTakeawaysView` both renderers share.
|
|
@@ -994,6 +1549,7 @@ function buildComponents(renderBlock) {
|
|
|
994
1549
|
/* @__PURE__ */ jsx3("ul", { children: points.map((p, i) => /* @__PURE__ */ jsx3("li", { children: p }, i)) })
|
|
995
1550
|
] });
|
|
996
1551
|
},
|
|
1552
|
+
[PT_TYPES.footnote]: ({ value }) => /* @__PURE__ */ jsx3(FootnoteMark, { value, footnotes }),
|
|
997
1553
|
[PT_TYPES.mention]: ({ value }) => {
|
|
998
1554
|
const v = value;
|
|
999
1555
|
return /* @__PURE__ */ jsx3("span", { className: "bcms-mention", children: typeof v.label === "string" ? v.label : "" });
|
|
@@ -1009,7 +1565,7 @@ function buildComponents(renderBlock) {
|
|
|
1009
1565
|
const href = typeof v.href === "string" ? safeHref(v.href) : null;
|
|
1010
1566
|
if (!href) return /* @__PURE__ */ jsx3(Fragment, { children });
|
|
1011
1567
|
const blank = v.target === "_blank";
|
|
1012
|
-
return /* @__PURE__ */ jsx3("a", { href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
|
|
1568
|
+
return /* @__PURE__ */ jsx3("a", { ...v.button === true ? { "data-button": "true" } : {}, href, ...blank ? { target: "_blank", rel: "noopener noreferrer" } : {}, children });
|
|
1013
1569
|
},
|
|
1014
1570
|
/**
|
|
1015
1571
|
* The site's own inline markup, rendered as itself — the React half of
|
|
@@ -1058,6 +1614,7 @@ function buildComponents(renderBlock) {
|
|
|
1058
1614
|
unknownType: ({ children }) => /* @__PURE__ */ jsx3(Fragment, { children: children ?? null }),
|
|
1059
1615
|
unknownBlockStyle: ({ children }) => /* @__PURE__ */ jsx3("p", { children })
|
|
1060
1616
|
};
|
|
1617
|
+
return components;
|
|
1061
1618
|
}
|
|
1062
1619
|
var isListItem = (node) => node._type === "block" && typeof node.listItem === "string" && !!node.listItem;
|
|
1063
1620
|
function runsOf(blocks) {
|
|
@@ -1098,22 +1655,42 @@ function calloutOrToggle(value) {
|
|
|
1098
1655
|
}
|
|
1099
1656
|
return null;
|
|
1100
1657
|
}
|
|
1101
|
-
function PortableText({ value, bindingBase, renderBlock }) {
|
|
1658
|
+
function PortableText({ value, bindingBase, renderBlock, embeds, resolved }) {
|
|
1102
1659
|
const blocks = React2.useMemo(() => normalizePortableText(readValue(value)), [value]);
|
|
1103
|
-
const
|
|
1660
|
+
const outline = React2.useMemo(() => outlineOf(blocks), [blocks]);
|
|
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
|
+
);
|
|
1104
1666
|
const listComponents = React2.useMemo(() => {
|
|
1105
1667
|
if (!bindingBase) return null;
|
|
1106
1668
|
const keys = new Set(blocks.map((b) => b._key));
|
|
1107
1669
|
return {
|
|
1108
1670
|
...components,
|
|
1109
|
-
listItem: ({ value: item, children }) =>
|
|
1671
|
+
listItem: ({ value: item, children }) => {
|
|
1672
|
+
const address = keys.has(item._key) ? { "data-bcms-field": `${bindingBase}#${item._key}`, "data-bcms-kind": "richtext" } : void 0;
|
|
1673
|
+
if (item.listItem === "check") {
|
|
1674
|
+
return /* @__PURE__ */ jsx3(CheckItem, { checked: item.checked === true, address, children });
|
|
1675
|
+
}
|
|
1676
|
+
return /* @__PURE__ */ jsx3("li", { ...address, children });
|
|
1677
|
+
}
|
|
1110
1678
|
};
|
|
1111
1679
|
}, [bindingBase, blocks, components]);
|
|
1112
1680
|
if (blocks.length === 0) return null;
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
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
|
+
] });
|
|
1117
1694
|
}
|
|
1118
1695
|
function readValue(value) {
|
|
1119
1696
|
if (Array.isArray(value)) return value;
|
|
@@ -1287,7 +1864,7 @@ function Spacer({ height = 32, className = "" }) {
|
|
|
1287
1864
|
// src/blocks/Columns.tsx
|
|
1288
1865
|
import "react";
|
|
1289
1866
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1290
|
-
function
|
|
1867
|
+
function Columns2({
|
|
1291
1868
|
columns,
|
|
1292
1869
|
gap = 16,
|
|
1293
1870
|
className = "",
|
|
@@ -1541,7 +2118,7 @@ var BLOCK_REGISTRY = {
|
|
|
1541
2118
|
icon: "minus"
|
|
1542
2119
|
},
|
|
1543
2120
|
columns: {
|
|
1544
|
-
component:
|
|
2121
|
+
component: Columns2,
|
|
1545
2122
|
label: "Columns",
|
|
1546
2123
|
icon: "columns"
|
|
1547
2124
|
},
|