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