@qkix/better-blocks-react-renderer 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1269 @@
1
+ // src/BlocksRenderer.tsx
2
+ import {
3
+ Fragment,
4
+ cloneElement,
5
+ createElement,
6
+ isValidElement
7
+ } from "react";
8
+ import katex from "katex";
9
+ import {
10
+ buildTextMarks,
11
+ formatFileSize,
12
+ getBlockStyle as coreGetBlockStyle,
13
+ getDefaultMarkRender,
14
+ getFileIcon,
15
+ getListStyleType,
16
+ getModifierProps,
17
+ getPlainText
18
+ } from "@qkix/better-blocks-core";
19
+
20
+ // src/media.ts
21
+ import { getAspectRatio } from "@qkix/better-blocks-core";
22
+ var MEDIA_ALIGN_ITEMS = {
23
+ left: "flex-start",
24
+ center: "center",
25
+ right: "flex-end",
26
+ none: "stretch"
27
+ };
28
+ var MEDIA_MAX_WIDTH = "48rem";
29
+ function getMediaFigureStyle(alignment) {
30
+ return {
31
+ display: "flex",
32
+ flexDirection: "column",
33
+ gap: "0.5rem",
34
+ margin: "1rem 0",
35
+ alignItems: MEDIA_ALIGN_ITEMS[alignment]
36
+ };
37
+ }
38
+ function getMediaFrameStyle(alignment, aspectRatio) {
39
+ return {
40
+ width: "100%",
41
+ maxWidth: alignment === "none" ? "100%" : MEDIA_MAX_WIDTH,
42
+ aspectRatio
43
+ };
44
+ }
45
+ var MEDIA_CAPTION_STYLE = {
46
+ fontSize: "0.875rem",
47
+ color: "#6b7280"
48
+ };
49
+
50
+ // src/CodeBlock.tsx
51
+ import { useEffect, useRef, useState } from "react";
52
+
53
+ // src/code.ts
54
+ import { normalizeCodeLang } from "@qkix/better-blocks-core";
55
+
56
+ // src/CodeBlock.tsx
57
+ import { jsx, jsxs } from "react/jsx-runtime";
58
+ var highlighters = /* @__PURE__ */ new Map();
59
+ function loadHighlighter(theme, lang) {
60
+ let highlighter = highlighters.get(theme);
61
+ if (!highlighter) {
62
+ highlighter = import("shiki").then(
63
+ (shiki) => shiki.createHighlighter({ themes: [theme], langs: [lang] })
64
+ );
65
+ highlighters.set(theme, highlighter);
66
+ return highlighter;
67
+ }
68
+ return highlighter.then(async (instance) => {
69
+ if (!instance.getLoadedLanguages().includes(lang)) {
70
+ await instance.loadLanguage(lang);
71
+ }
72
+ return instance;
73
+ });
74
+ }
75
+ function CopyButton({ plainText }) {
76
+ const [copied, setCopied] = useState(false);
77
+ const timer = useRef(null);
78
+ useEffect(() => {
79
+ return () => {
80
+ if (timer.current) clearTimeout(timer.current);
81
+ };
82
+ }, []);
83
+ const handleCopy = () => {
84
+ if (!navigator.clipboard) return;
85
+ navigator.clipboard.writeText(plainText).then(() => {
86
+ setCopied(true);
87
+ if (timer.current) clearTimeout(timer.current);
88
+ timer.current = setTimeout(() => setCopied(false), 2e3);
89
+ });
90
+ };
91
+ return /* @__PURE__ */ jsx("button", { className: "bb-code-copy", type: "button", onClick: handleCopy, "aria-label": "Copy code", children: copied ? "Copied!" : "Copy" });
92
+ }
93
+ function CodeBlock({
94
+ plainText,
95
+ language,
96
+ theme,
97
+ copyButton
98
+ }) {
99
+ const [html, setHtml] = useState(null);
100
+ useEffect(() => {
101
+ let cancelled = false;
102
+ setHtml(null);
103
+ if (!plainText) return;
104
+ const lang = normalizeCodeLang(language);
105
+ loadHighlighter(theme, lang).then((highlighter) => highlighter.codeToHtml(plainText, { lang, theme })).then((highlighted) => {
106
+ if (!cancelled) setHtml(highlighted);
107
+ }).catch(() => {
108
+ if (!cancelled) setHtml(null);
109
+ });
110
+ return () => {
111
+ cancelled = true;
112
+ };
113
+ }, [plainText, language, theme]);
114
+ return /* @__PURE__ */ jsxs("div", { className: "bb-code", children: [
115
+ html ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } }) : /* @__PURE__ */ jsx("pre", { className: "bb-code-pre", children: /* @__PURE__ */ jsx("code", { children: plainText }) }),
116
+ copyButton && /* @__PURE__ */ jsx(CopyButton, { plainText })
117
+ ] });
118
+ }
119
+
120
+ // src/MermaidDiagram.tsx
121
+ import { useEffect as useEffect2, useState as useState2 } from "react";
122
+ import { jsx as jsx2 } from "react/jsx-runtime";
123
+ var mermaidPromise = null;
124
+ function loadMermaid() {
125
+ if (!mermaidPromise) {
126
+ mermaidPromise = import("mermaid").then((mod) => {
127
+ const mermaid = mod.default;
128
+ mermaid.initialize({ startOnLoad: false });
129
+ return mermaid;
130
+ });
131
+ }
132
+ return mermaidPromise;
133
+ }
134
+ var renderCount = 0;
135
+ function MermaidDiagram({ value }) {
136
+ const [svg, setSvg] = useState2(null);
137
+ useEffect2(() => {
138
+ let cancelled = false;
139
+ setSvg(null);
140
+ if (!value) return;
141
+ loadMermaid().then((mermaid) => mermaid.render(`bb-mermaid-${renderCount++}`, value)).then(({ svg: svg2 }) => {
142
+ if (!cancelled) setSvg(svg2);
143
+ }).catch(() => {
144
+ if (!cancelled) setSvg(null);
145
+ });
146
+ return () => {
147
+ cancelled = true;
148
+ };
149
+ }, [value]);
150
+ if (svg) {
151
+ return /* @__PURE__ */ jsx2("div", { className: "mermaid-diagram", dangerouslySetInnerHTML: { __html: svg } });
152
+ }
153
+ return /* @__PURE__ */ jsx2("pre", { className: "mermaid-source", children: value });
154
+ }
155
+
156
+ // src/SocialEmbed.tsx
157
+ import { useEffect as useEffect3, useMemo, useRef as useRef2 } from "react";
158
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
159
+ var socialWindow = () => window;
160
+ var WIDGET_SCRIPTS = {
161
+ twitter: {
162
+ src: "https://platform.twitter.com/widgets.js",
163
+ process: () => {
164
+ var _a, _b;
165
+ const load = (_b = (_a = socialWindow().twttr) == null ? void 0 : _a.widgets) == null ? void 0 : _b.load;
166
+ load == null ? void 0 : load();
167
+ return Boolean(load);
168
+ }
169
+ },
170
+ instagram: {
171
+ src: "https://www.instagram.com/embed.js",
172
+ process: () => {
173
+ var _a, _b;
174
+ const process = (_b = (_a = socialWindow().instgrm) == null ? void 0 : _a.Embeds) == null ? void 0 : _b.process;
175
+ process == null ? void 0 : process();
176
+ return Boolean(process);
177
+ }
178
+ },
179
+ tiktok: {
180
+ src: "https://www.tiktok.com/embed.js",
181
+ process: (container) => {
182
+ var _a, _b;
183
+ const render = (_b = (_a = socialWindow().tiktokEmbed) == null ? void 0 : _a.lib) == null ? void 0 : _b.render;
184
+ render == null ? void 0 : render(Array.from(container.querySelectorAll(".tiktok-embed")));
185
+ return Boolean(render);
186
+ }
187
+ },
188
+ pinterest: {
189
+ src: "https://assets.pinterest.com/js/pinit.js",
190
+ process: () => {
191
+ var _a;
192
+ const build = (_a = socialWindow().PinUtils) == null ? void 0 : _a.build;
193
+ build == null ? void 0 : build();
194
+ return Boolean(build);
195
+ }
196
+ },
197
+ facebook: {
198
+ src: "https://connect.facebook.net/en_US/sdk.js",
199
+ process: () => {
200
+ var _a, _b;
201
+ const parse = (_b = (_a = socialWindow().FB) == null ? void 0 : _a.XFBML) == null ? void 0 : _b.parse;
202
+ parse == null ? void 0 : parse();
203
+ return Boolean(parse);
204
+ }
205
+ },
206
+ linkedin: null
207
+ };
208
+ var PLATFORM_LABELS = {
209
+ twitter: "X",
210
+ instagram: "Instagram",
211
+ facebook: "Facebook",
212
+ tiktok: "TikTok",
213
+ linkedin: "LinkedIn",
214
+ pinterest: "Pinterest"
215
+ };
216
+ var SCRIPT_MARKER = "data-bb-social-script";
217
+ var scriptPromises = /* @__PURE__ */ new Map();
218
+ var reinjected = /* @__PURE__ */ new Set();
219
+ function injectScript(src, platform) {
220
+ const promise = new Promise((resolve) => {
221
+ const script = document.createElement("script");
222
+ script.src = src;
223
+ script.async = true;
224
+ script.setAttribute(SCRIPT_MARKER, platform);
225
+ script.addEventListener("load", () => resolve());
226
+ script.addEventListener("error", () => resolve());
227
+ document.body.appendChild(script);
228
+ });
229
+ scriptPromises.set(src, promise);
230
+ return promise;
231
+ }
232
+ function loadWidgetScript(src, platform) {
233
+ if (typeof document === "undefined") return Promise.resolve();
234
+ const cached = scriptPromises.get(src);
235
+ if (cached) return cached;
236
+ const existing = document.querySelector(`script[${SCRIPT_MARKER}="${platform}"]`);
237
+ if (existing) {
238
+ const resolved = Promise.resolve();
239
+ scriptPromises.set(src, resolved);
240
+ return resolved;
241
+ }
242
+ return injectScript(src, platform);
243
+ }
244
+ function reinjectWidgetScript(src, platform) {
245
+ if (typeof document === "undefined" || reinjected.has(platform)) return;
246
+ reinjected.add(platform);
247
+ document.querySelectorAll(`script[${SCRIPT_MARKER}="${platform}"]`).forEach((script) => {
248
+ script.remove();
249
+ });
250
+ scriptPromises.delete(src);
251
+ void injectScript(src, platform);
252
+ }
253
+ var SCRIPT_TAG = /<script\b[^>]*>[\s\S]*?<\/script\s*>/gi;
254
+ function stripScripts(html) {
255
+ return html == null ? void 0 : html.replace(SCRIPT_TAG, "");
256
+ }
257
+ function FallbackCard({
258
+ url,
259
+ oembed,
260
+ providerName
261
+ }) {
262
+ var _a;
263
+ const title = (_a = oembed == null ? void 0 : oembed.title) != null ? _a : (oembed == null ? void 0 : oembed.author) ? `Post by ${oembed.author}` : null;
264
+ const style = {
265
+ display: "inline-flex",
266
+ alignItems: "center",
267
+ gap: "0.75rem",
268
+ padding: "0.75rem 1rem",
269
+ border: "1px solid #d0d7de",
270
+ borderRadius: "0.5rem",
271
+ textDecoration: "none",
272
+ color: "inherit",
273
+ textAlign: "left"
274
+ };
275
+ const Tag = url ? "a" : "div";
276
+ const linkProps = url ? { href: url, target: "_blank", rel: "noopener noreferrer" } : {};
277
+ return /* @__PURE__ */ jsxs2(Tag, { className: "bb-social-embed-fallback", style, ...linkProps, children: [
278
+ (oembed == null ? void 0 : oembed.thumbnailUrl) && /* @__PURE__ */ jsx3(
279
+ "img",
280
+ {
281
+ className: "bb-social-embed-fallback-thumb",
282
+ src: oembed.thumbnailUrl,
283
+ alt: "",
284
+ loading: "lazy",
285
+ width: 48,
286
+ height: 48,
287
+ style: { borderRadius: "0.25rem", objectFit: "cover", flexShrink: 0 }
288
+ }
289
+ ),
290
+ /* @__PURE__ */ jsxs2("span", { className: "bb-social-embed-fallback-body", children: [
291
+ /* @__PURE__ */ jsx3(
292
+ "span",
293
+ {
294
+ className: "bb-social-embed-fallback-title",
295
+ style: { display: "block", fontWeight: 600 },
296
+ children: title != null ? title : `View on ${providerName}`
297
+ }
298
+ ),
299
+ title && /* @__PURE__ */ jsx3(
300
+ "span",
301
+ {
302
+ className: "bb-social-embed-fallback-provider",
303
+ style: { opacity: 0.7, fontSize: "0.875em" },
304
+ children: providerName
305
+ }
306
+ )
307
+ ] })
308
+ ] });
309
+ }
310
+ function SocialEmbed({
311
+ platform,
312
+ url,
313
+ embedCode,
314
+ oembed,
315
+ alignment = "center",
316
+ caption
317
+ }) {
318
+ var _a;
319
+ const containerRef = useRef2(null);
320
+ const html = useMemo(() => stripScripts(embedCode != null ? embedCode : oembed == null ? void 0 : oembed.html), [embedCode, oembed == null ? void 0 : oembed.html]);
321
+ useEffect3(() => {
322
+ var _a2;
323
+ if (!html) return;
324
+ (_a2 = containerRef.current) == null ? void 0 : _a2.querySelectorAll("iframe").forEach((iframe) => {
325
+ if (!iframe.hasAttribute("loading")) iframe.setAttribute("loading", "lazy");
326
+ });
327
+ const widget = WIDGET_SCRIPTS[platform];
328
+ if (!widget) return;
329
+ let cancelled = false;
330
+ void loadWidgetScript(widget.src, platform).then(() => {
331
+ const container = containerRef.current;
332
+ if (cancelled || !container) return;
333
+ if (!widget.process(container)) reinjectWidgetScript(widget.src, platform);
334
+ });
335
+ return () => {
336
+ cancelled = true;
337
+ };
338
+ }, [platform, html]);
339
+ const providerName = (_a = oembed == null ? void 0 : oembed.providerName) != null ? _a : PLATFORM_LABELS[platform];
340
+ const ariaLabel = (oembed == null ? void 0 : oembed.author) ? `${providerName} post by ${oembed.author}` : `${providerName} post`;
341
+ const figureStyle = {
342
+ margin: alignment === "center" ? "1rem auto" : "1rem 0",
343
+ textAlign: alignment
344
+ };
345
+ return /* @__PURE__ */ jsxs2(
346
+ "figure",
347
+ {
348
+ className: `bb-social-embed bb-social-embed-${platform} social-embed align-${alignment}`,
349
+ "aria-label": ariaLabel,
350
+ style: figureStyle,
351
+ children: [
352
+ html ? /* @__PURE__ */ jsx3(
353
+ "div",
354
+ {
355
+ ref: containerRef,
356
+ className: "bb-social-embed-html",
357
+ dangerouslySetInnerHTML: { __html: html }
358
+ }
359
+ ) : /* @__PURE__ */ jsx3(FallbackCard, { url, oembed, providerName }),
360
+ caption && /* @__PURE__ */ jsx3("figcaption", { className: "bb-social-embed-caption", children: caption })
361
+ ]
362
+ }
363
+ );
364
+ }
365
+
366
+ // src/Video.tsx
367
+ import {
368
+ useEffect as useEffect4,
369
+ useRef as useRef3,
370
+ useState as useState3
371
+ } from "react";
372
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
373
+ var HLS_MANIFEST = /\.m3u8(?:[?#]|$)/i;
374
+ var DASH_MANIFEST = /\.mpd(?:[?#]|$)/i;
375
+ var MuxPlayer = "mux-player";
376
+ function Video({
377
+ provider,
378
+ url,
379
+ playbackId,
380
+ poster,
381
+ title,
382
+ caption,
383
+ transcript,
384
+ player,
385
+ alignment = "center",
386
+ aspectRatio,
387
+ customAspectRatio,
388
+ instanceId
389
+ }) {
390
+ var _a, _b, _c, _d, _e;
391
+ const videoRef = useRef3(null);
392
+ const wantsMux = provider === "mux" && Boolean(playbackId);
393
+ const [muxDefined, setMuxDefined] = useState3(false);
394
+ useEffect4(() => {
395
+ if (!wantsMux || typeof window === "undefined" || !window.customElements) return;
396
+ if (window.customElements.get("mux-player")) {
397
+ setMuxDefined(true);
398
+ return;
399
+ }
400
+ let cancelled = false;
401
+ void window.customElements.whenDefined("mux-player").then(() => {
402
+ if (!cancelled) setMuxDefined(true);
403
+ });
404
+ return () => {
405
+ cancelled = true;
406
+ };
407
+ }, [wantsMux]);
408
+ const isHls = HLS_MANIFEST.test(url);
409
+ const isStreaming = isHls || DASH_MANIFEST.test(url);
410
+ const useMux = wantsMux && muxDefined;
411
+ useEffect4(() => {
412
+ var _a2;
413
+ const video = videoRef.current;
414
+ if (!video || !isHls) return;
415
+ const Hls = window.Hls;
416
+ if (!Hls || ((_a2 = Hls.isSupported) == null ? void 0 : _a2.call(Hls)) === false) return;
417
+ let instance;
418
+ try {
419
+ instance = new Hls();
420
+ instance.loadSource(url);
421
+ instance.attachMedia(video);
422
+ } catch (e) {
423
+ instance == null ? void 0 : instance.destroy();
424
+ instance = void 0;
425
+ }
426
+ return () => instance == null ? void 0 : instance.destroy();
427
+ }, [isHls, url, useMux]);
428
+ const capId = caption ? `bb-video-cap-${(_a = instanceId != null ? instanceId : playbackId) != null ? _a : 0}` : void 0;
429
+ const ratio = getAspectRatio(aspectRatio, customAspectRatio);
430
+ const frameStyle = getMediaFrameStyle(alignment, ratio);
431
+ const fillStyle = { width: "100%", height: "100%", display: "block" };
432
+ return /* @__PURE__ */ jsxs3(
433
+ "figure",
434
+ {
435
+ className: `bb-video align-${alignment}`,
436
+ style: getMediaFigureStyle(alignment),
437
+ "aria-label": title ? `Video: ${title}` : void 0,
438
+ children: [
439
+ title && /* @__PURE__ */ jsx4("figcaption", { className: "bb-video-title", style: { fontWeight: 600 }, children: title }),
440
+ /* @__PURE__ */ jsx4("div", { className: "bb-video-frame", style: frameStyle, children: useMux ? /* @__PURE__ */ jsx4(
441
+ MuxPlayer,
442
+ {
443
+ className: "bb-video-player",
444
+ "playback-id": playbackId,
445
+ poster,
446
+ "metadata-video-title": title,
447
+ autoplay: (player == null ? void 0 : player.autoplay) ? true : void 0,
448
+ loop: (player == null ? void 0 : player.loop) ? true : void 0,
449
+ muted: (player == null ? void 0 : player.muted) ? true : void 0,
450
+ "aria-describedby": capId,
451
+ style: fillStyle
452
+ }
453
+ ) : /* @__PURE__ */ jsxs3(
454
+ "video",
455
+ {
456
+ ref: videoRef,
457
+ className: "bb-video-player",
458
+ src: url,
459
+ poster,
460
+ controls: (_b = player == null ? void 0 : player.controls) != null ? _b : true,
461
+ autoPlay: (_c = player == null ? void 0 : player.autoplay) != null ? _c : false,
462
+ loop: (_d = player == null ? void 0 : player.loop) != null ? _d : false,
463
+ muted: (_e = player == null ? void 0 : player.muted) != null ? _e : false,
464
+ playsInline: true,
465
+ preload: isStreaming ? "none" : "metadata",
466
+ "aria-label": title || "Video player",
467
+ "aria-describedby": capId,
468
+ style: fillStyle,
469
+ children: [
470
+ transcript && /* @__PURE__ */ jsx4("track", { kind: "captions", src: transcript, label: "Captions" }),
471
+ "Your browser does not support the video element. ",
472
+ /* @__PURE__ */ jsx4("a", { href: url, children: "Download the video" }),
473
+ "."
474
+ ]
475
+ }
476
+ ) }),
477
+ caption && /* @__PURE__ */ jsx4("figcaption", { id: capId, className: "bb-video-caption", style: MEDIA_CAPTION_STYLE, children: caption })
478
+ ]
479
+ }
480
+ );
481
+ }
482
+
483
+ // src/BlocksRenderer.tsx
484
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
485
+ function renderTextNode(node, key, modifiers) {
486
+ const marks = buildTextMarks(node);
487
+ let content = node.text;
488
+ for (let i = marks.length - 1; i >= 0; i -= 1) {
489
+ const mark = marks[i];
490
+ const Comp = modifiers == null ? void 0 : modifiers[mark.name];
491
+ if (Comp) {
492
+ content = /* @__PURE__ */ jsx5(Comp, { ...getModifierProps(mark), children: content });
493
+ continue;
494
+ }
495
+ const { tag, style } = getDefaultMarkRender(mark);
496
+ content = createElement(tag, { style }, content);
497
+ }
498
+ return /* @__PURE__ */ jsx5(Fragment, { children: content }, key);
499
+ }
500
+ function renderMath(node, key, blocks) {
501
+ var _a;
502
+ const isBlock = node.format === "block";
503
+ const MathComp = blocks == null ? void 0 : blocks.math;
504
+ const formula = (_a = node.value) != null ? _a : "";
505
+ if (MathComp) {
506
+ return /* @__PURE__ */ jsx5(MathComp, { formula, inline: !isBlock }, key);
507
+ }
508
+ const Tag = isBlock ? "div" : "span";
509
+ const className = isBlock ? "katex-block" : "katex-inline";
510
+ try {
511
+ const html = katex.renderToString(formula, {
512
+ displayMode: isBlock,
513
+ throwOnError: false
514
+ });
515
+ return /* @__PURE__ */ jsx5(Tag, { className, dangerouslySetInnerHTML: { __html: html } }, key);
516
+ } catch (e) {
517
+ return /* @__PURE__ */ jsx5(Tag, { className, children: formula }, key);
518
+ }
519
+ }
520
+ function renderInlineContent(children, blocks, modifiers) {
521
+ return children.map((child, index) => {
522
+ if (child.type === "text") {
523
+ return renderTextNode(child, index, modifiers);
524
+ }
525
+ if (child.type === "math") {
526
+ return renderMath(child, index, blocks);
527
+ }
528
+ if (child.type === "link") {
529
+ const LinkComp = blocks == null ? void 0 : blocks.link;
530
+ const linkChildren = child.children.map(
531
+ (textNode, i) => renderTextNode(textNode, i, modifiers)
532
+ );
533
+ return LinkComp ? /* @__PURE__ */ jsx5(LinkComp, { url: child.url, target: child.target, rel: child.rel, children: linkChildren }, index) : /* @__PURE__ */ jsx5("a", { href: child.url, target: child.target, rel: child.rel, children: linkChildren }, index);
534
+ }
535
+ return null;
536
+ });
537
+ }
538
+ function renderListItem(node, key, isTodo, blocks, modifiers) {
539
+ var _a;
540
+ const ListItemComp = blocks == null ? void 0 : blocks["list-item"];
541
+ const children = renderInlineContent(node.children, blocks, modifiers);
542
+ if (ListItemComp) {
543
+ return /* @__PURE__ */ jsx5(ListItemComp, { checked: isTodo ? node.checked : void 0, children }, key);
544
+ }
545
+ if (isTodo) {
546
+ const checked = (_a = node.checked) != null ? _a : false;
547
+ return /* @__PURE__ */ jsxs4("li", { style: { listStyle: "none" }, children: [
548
+ /* @__PURE__ */ jsx5("input", { type: "checkbox", checked, readOnly: true, style: { marginRight: "0.5em" } }),
549
+ /* @__PURE__ */ jsx5("span", { style: checked ? { textDecoration: "line-through", opacity: 0.6 } : void 0, children })
550
+ ] }, key);
551
+ }
552
+ return /* @__PURE__ */ jsx5("li", { children }, key);
553
+ }
554
+ function renderList(node, key, blocks, modifiers) {
555
+ const ListComp = blocks == null ? void 0 : blocks.list;
556
+ const indentLevel = node.indentLevel || 0;
557
+ const isTodo = node.format === "todo";
558
+ const children = node.children.map((child, index) => {
559
+ if (child.type === "list-item") {
560
+ return renderListItem(child, index, isTodo, blocks, modifiers);
561
+ }
562
+ if (child.type === "list") {
563
+ return renderList(child, index, blocks, modifiers);
564
+ }
565
+ return null;
566
+ });
567
+ if (ListComp) {
568
+ return /* @__PURE__ */ jsx5(ListComp, { format: node.format, indentLevel, children }, key);
569
+ }
570
+ if (isTodo) {
571
+ return /* @__PURE__ */ jsx5("ul", { style: { listStyle: "none", paddingLeft: indentLevel > 0 ? "1.5em" : 0 }, children }, key);
572
+ }
573
+ const Tag = node.format === "ordered" ? "ol" : "ul";
574
+ const listStyleType = getListStyleType(node.format, indentLevel);
575
+ return /* @__PURE__ */ jsx5(Tag, { style: { listStyleType }, children }, key);
576
+ }
577
+ function renderTable(block, key, blocks, modifiers) {
578
+ const TableComp = blocks == null ? void 0 : blocks.table;
579
+ const RowComp = blocks == null ? void 0 : blocks["table-row"];
580
+ const CellComp = blocks == null ? void 0 : blocks["table-cell"];
581
+ const HeaderCellComp = blocks == null ? void 0 : blocks["table-header-cell"];
582
+ let headerRowCount = 0;
583
+ while (isHeaderRow(block.children[headerRowCount])) headerRowCount++;
584
+ const rows = block.children.map((row, rowIndex) => {
585
+ const inHeader = rowIndex < headerRowCount;
586
+ const cells = row.children.map((cell, cellIndex) => {
587
+ const cellChildren = renderInlineContent(cell.children, blocks, modifiers);
588
+ const isHeader = cell.type === "table-header-cell";
589
+ const style = cell.align ? { textAlign: cell.align } : void 0;
590
+ const cellProps = {
591
+ align: cell.align,
592
+ colSpan: cell.colSpan,
593
+ rowSpan: cell.rowSpan,
594
+ style
595
+ };
596
+ if (isHeader && HeaderCellComp) {
597
+ return /* @__PURE__ */ jsx5(HeaderCellComp, { ...cellProps, children: cellChildren }, cellIndex);
598
+ }
599
+ if (!isHeader && CellComp) {
600
+ return /* @__PURE__ */ jsx5(CellComp, { ...cellProps, children: cellChildren }, cellIndex);
601
+ }
602
+ return isHeader ? /* @__PURE__ */ jsx5(
603
+ "th",
604
+ {
605
+ scope: inHeader ? "col" : "row",
606
+ colSpan: cell.colSpan,
607
+ rowSpan: cell.rowSpan,
608
+ style,
609
+ children: cellChildren
610
+ },
611
+ cellIndex
612
+ ) : /* @__PURE__ */ jsx5("td", { colSpan: cell.colSpan, rowSpan: cell.rowSpan, style, children: cellChildren }, cellIndex);
613
+ });
614
+ return RowComp ? /* @__PURE__ */ jsx5(RowComp, { children: cells }, rowIndex) : /* @__PURE__ */ jsx5("tr", { children: cells }, rowIndex);
615
+ });
616
+ if (TableComp) {
617
+ return /* @__PURE__ */ jsx5(TableComp, { children: rows }, key);
618
+ }
619
+ return /* @__PURE__ */ jsxs4("table", { className: "bb-table", children: [
620
+ headerRowCount > 0 && /* @__PURE__ */ jsx5("thead", { children: rows.slice(0, headerRowCount) }),
621
+ rows.length > headerRowCount && /* @__PURE__ */ jsx5("tbody", { children: rows.slice(headerRowCount) })
622
+ ] }, key);
623
+ }
624
+ function isHeaderRow(row) {
625
+ return !!row && row.children.length > 0 && row.children.every((c) => c.type === "table-header-cell");
626
+ }
627
+ function renderBlock(block, key, blocks, modifiers, options) {
628
+ switch (block.type) {
629
+ case "paragraph":
630
+ return renderParagraph(block, key, blocks, modifiers);
631
+ case "heading":
632
+ return renderHeading(block, key, blocks, modifiers);
633
+ case "list":
634
+ return renderList(block, key, blocks, modifiers);
635
+ case "quote":
636
+ return renderQuote(block, key, blocks, modifiers);
637
+ case "code":
638
+ return renderCode(block, key, blocks, options);
639
+ case "image":
640
+ return renderImage(block, key, blocks);
641
+ case "horizontal-line":
642
+ return renderHorizontalLine(block, key, blocks);
643
+ case "table":
644
+ return renderTable(block, key, blocks, modifiers);
645
+ case "media-embed":
646
+ return renderMediaEmbed(block, key, blocks);
647
+ case "embed":
648
+ return renderEmbed(block, key, blocks);
649
+ case "video":
650
+ return renderVideo(block, key, blocks);
651
+ case "math":
652
+ return renderMath(block, key, blocks);
653
+ case "diagram":
654
+ return renderDiagram(block, key, blocks);
655
+ case "callout":
656
+ return renderCallout(block, key, blocks, modifiers, options);
657
+ case "details":
658
+ return renderDetails(block, key, blocks, modifiers, options);
659
+ case "button":
660
+ return renderButton(block, key, blocks);
661
+ case "social-embed":
662
+ return renderSocialEmbed(block, key, blocks);
663
+ case "audio":
664
+ return renderAudio(block, key, blocks);
665
+ default:
666
+ return null;
667
+ }
668
+ }
669
+ function getBlockStyle(block) {
670
+ return coreGetBlockStyle(block);
671
+ }
672
+ function renderParagraph(block, key, blocks, modifiers) {
673
+ const ParagraphComp = blocks == null ? void 0 : blocks.paragraph;
674
+ const children = renderInlineContent(block.children, blocks, modifiers);
675
+ const style = getBlockStyle(block);
676
+ return ParagraphComp ? /* @__PURE__ */ jsx5(ParagraphComp, { style, children }, key) : /* @__PURE__ */ jsx5("p", { style, children }, key);
677
+ }
678
+ function renderHeading(block, key, blocks, modifiers) {
679
+ const HeadingComp = blocks == null ? void 0 : blocks.heading;
680
+ const children = renderInlineContent(block.children, blocks, modifiers);
681
+ const style = getBlockStyle(block);
682
+ if (HeadingComp) {
683
+ return /* @__PURE__ */ jsx5(HeadingComp, { level: block.level, style, children }, key);
684
+ }
685
+ const Tag = `h${block.level}`;
686
+ return /* @__PURE__ */ jsx5(Tag, { style, children }, key);
687
+ }
688
+ function renderQuote(block, key, blocks, modifiers) {
689
+ const QuoteComp = blocks == null ? void 0 : blocks.quote;
690
+ const children = renderInlineContent(block.children, blocks, modifiers);
691
+ const style = getBlockStyle(block);
692
+ return QuoteComp ? /* @__PURE__ */ jsx5(QuoteComp, { style, children }, key) : /* @__PURE__ */ jsx5("blockquote", { className: "bb-quote", style, children }, key);
693
+ }
694
+ var DEFAULT_CODE_THEME = "github-dark";
695
+ function renderCode(block, key, blocks, options) {
696
+ var _a, _b;
697
+ const CodeComp = blocks == null ? void 0 : blocks.code;
698
+ const plainText = getPlainText(block.children);
699
+ if (CodeComp) {
700
+ return /* @__PURE__ */ jsx5(CodeComp, { plainText, language: block.language, children: plainText }, key);
701
+ }
702
+ return /* @__PURE__ */ jsx5(
703
+ CodeBlock,
704
+ {
705
+ plainText,
706
+ language: block.language,
707
+ theme: (_a = options == null ? void 0 : options.codeTheme) != null ? _a : DEFAULT_CODE_THEME,
708
+ copyButton: (_b = options == null ? void 0 : options.codeCopyButton) != null ? _b : false
709
+ },
710
+ key
711
+ );
712
+ }
713
+ var TABLE_CSS = ".bb-table{display:block;width:max-content;max-width:100%;margin:1rem 0;overflow:auto;border-collapse:collapse;border-spacing:0;border:1px solid var(--bb-table-border,#d0d7de)}.bb-table th,.bb-table td{padding:.375rem .8125rem;border:1px solid var(--bb-table-border,#d0d7de);text-align:left}.bb-table th{font-weight:600;background:var(--bb-table-header-bg,#f6f8fa)}.bb-table tbody tr{background:var(--bb-table-row-bg,#fff)}.bb-table tbody tr:nth-child(2n){background:var(--bb-table-stripe-bg,#f6f8fa)}";
714
+ var QUOTE_CSS = ".bb-quote{margin:1rem 0;padding:0 1rem;color:var(--bb-quote-fg,#57606a);border-left:.25rem solid var(--bb-quote-border,#d0d7de)}";
715
+ var CODE_CSS = ".bb-code{position:relative;margin:1rem 0}.bb-code pre{margin:0;padding:1rem;border-radius:6px;font-size:.875rem;line-height:1.45;tab-size:2;overflow-x:auto}.bb-code-pre{background:var(--bb-code-fallback-bg,#24292e);color:var(--bb-code-fallback-fg,#e1e4e8)}.bb-code-copy{position:absolute;top:.5rem;right:.5rem;padding:.25rem .5rem;font-size:.75rem;line-height:1;color:var(--bb-code-copy-fg,#e1e4e8);background:var(--bb-code-copy-bg,rgba(110,118,129,.4));border:1px solid var(--bb-code-copy-border,rgba(240,246,252,.1));border-radius:6px;cursor:pointer;opacity:0;transition:opacity .15s ease,background .15s ease}.bb-code:hover .bb-code-copy,.bb-code-copy:focus-visible{opacity:1}.bb-code-copy:hover{background:var(--bb-code-copy-hover-bg,rgba(110,118,129,.6))}";
716
+ function renderImage(block, key, blocks) {
717
+ const ImageComp = blocks == null ? void 0 : blocks.image;
718
+ if (ImageComp) {
719
+ return /* @__PURE__ */ jsx5(
720
+ ImageComp,
721
+ {
722
+ image: block.image,
723
+ caption: block.caption,
724
+ imageAlign: block.imageAlign
725
+ },
726
+ key
727
+ );
728
+ }
729
+ const align = block.imageAlign || "center";
730
+ const alignStyle = {
731
+ textAlign: align
732
+ };
733
+ return /* @__PURE__ */ jsxs4("figure", { style: alignStyle, children: [
734
+ /* @__PURE__ */ jsx5(
735
+ "img",
736
+ {
737
+ src: block.image.url,
738
+ alt: block.image.alternativeText || "",
739
+ width: block.image.width,
740
+ height: block.image.height
741
+ }
742
+ ),
743
+ block.caption && /* @__PURE__ */ jsx5("figcaption", { children: block.caption })
744
+ ] }, key);
745
+ }
746
+ function renderHorizontalLine(_block, key, blocks) {
747
+ const HrComp = blocks == null ? void 0 : blocks["horizontal-line"];
748
+ return HrComp ? /* @__PURE__ */ jsx5(HrComp, {}, key) : /* @__PURE__ */ jsx5("hr", {}, key);
749
+ }
750
+ function renderMediaEmbed(block, key, blocks) {
751
+ const EmbedComp = blocks == null ? void 0 : blocks["media-embed"];
752
+ if (EmbedComp) {
753
+ return /* @__PURE__ */ jsx5(EmbedComp, { url: block.url, originalUrl: block.originalUrl }, key);
754
+ }
755
+ return /* @__PURE__ */ jsx5("div", { style: { position: "relative", paddingBottom: "56.25%", height: 0 }, children: /* @__PURE__ */ jsx5(
756
+ "iframe",
757
+ {
758
+ src: block.url,
759
+ style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", border: 0 },
760
+ allowFullScreen: true,
761
+ title: "Embedded media"
762
+ }
763
+ ) }, key);
764
+ }
765
+ var EMBED_FRAME_CSS = ".bb-embed-frame{overflow:hidden}.bb-embed-frame iframe{width:100%;height:100%;border:0;display:block}";
766
+ function renderEmbed(block, key, blocks) {
767
+ var _a;
768
+ const EmbedComp = blocks == null ? void 0 : blocks.embed;
769
+ if (EmbedComp) {
770
+ return /* @__PURE__ */ jsx5(
771
+ EmbedComp,
772
+ {
773
+ source: block.source,
774
+ url: block.url,
775
+ iframe: block.iframe,
776
+ embedHtml: block.embedHtml,
777
+ embedSrc: block.embedSrc,
778
+ provider: block.provider,
779
+ thumbnail: block.thumbnail,
780
+ aspectRatio: block.aspectRatio,
781
+ customAspectRatio: block.customAspectRatio,
782
+ alignment: block.alignment,
783
+ caption: block.caption,
784
+ title: block.title
785
+ },
786
+ key
787
+ );
788
+ }
789
+ const alignment = (_a = block.alignment) != null ? _a : "center";
790
+ const ratio = getAspectRatio(block.aspectRatio, block.customAspectRatio);
791
+ const body = block.embedHtml ? /* @__PURE__ */ jsx5(
792
+ "div",
793
+ {
794
+ className: "bb-embed-frame",
795
+ style: getMediaFrameStyle(alignment, ratio),
796
+ dangerouslySetInnerHTML: { __html: block.embedHtml }
797
+ }
798
+ ) : block.url ? /* @__PURE__ */ jsx5("a", { className: "bb-embed-fallback", href: block.url, target: "_blank", rel: "noopener noreferrer", children: block.title || block.url }) : null;
799
+ if (!body) return null;
800
+ return /* @__PURE__ */ jsxs4(
801
+ "figure",
802
+ {
803
+ className: `bb-embed align-${alignment}`,
804
+ style: getMediaFigureStyle(alignment),
805
+ "aria-label": block.title,
806
+ children: [
807
+ body,
808
+ block.caption && /* @__PURE__ */ jsx5("figcaption", { className: "bb-embed-caption", style: MEDIA_CAPTION_STYLE, children: block.caption })
809
+ ]
810
+ },
811
+ key
812
+ );
813
+ }
814
+ function renderVideo(block, key, blocks) {
815
+ var _a, _b, _c, _d;
816
+ const VideoComp = blocks == null ? void 0 : blocks.video;
817
+ if (VideoComp) {
818
+ return /* @__PURE__ */ jsx5(
819
+ VideoComp,
820
+ {
821
+ provider: block.provider,
822
+ url: (_a = block.url) != null ? _a : "",
823
+ assetId: block.assetId,
824
+ playbackId: block.playbackId,
825
+ file: block.file,
826
+ poster: block.poster,
827
+ title: block.title,
828
+ caption: block.caption,
829
+ transcript: block.transcript,
830
+ player: block.player,
831
+ alignment: block.alignment,
832
+ aspectRatio: block.aspectRatio,
833
+ customAspectRatio: block.customAspectRatio
834
+ },
835
+ key
836
+ );
837
+ }
838
+ return /* @__PURE__ */ jsx5(
839
+ Video,
840
+ {
841
+ provider: block.provider,
842
+ url: (_b = block.url) != null ? _b : "",
843
+ assetId: block.assetId,
844
+ playbackId: block.playbackId,
845
+ file: block.file,
846
+ poster: block.poster,
847
+ title: block.title,
848
+ caption: block.caption,
849
+ transcript: block.transcript,
850
+ player: block.player,
851
+ alignment: block.alignment,
852
+ aspectRatio: block.aspectRatio,
853
+ customAspectRatio: block.customAspectRatio,
854
+ instanceId: (_d = (_c = block.file) == null ? void 0 : _c.id) != null ? _d : key
855
+ },
856
+ key
857
+ );
858
+ }
859
+ function renderSocialEmbed(block, key, blocks) {
860
+ const SocialComp = blocks == null ? void 0 : blocks["social-embed"];
861
+ if (SocialComp) {
862
+ return /* @__PURE__ */ jsx5(
863
+ SocialComp,
864
+ {
865
+ platform: block.platform,
866
+ url: block.url,
867
+ embedCode: block.embedCode,
868
+ oembed: block.oembed,
869
+ alignment: block.alignment,
870
+ caption: block.caption
871
+ },
872
+ key
873
+ );
874
+ }
875
+ return /* @__PURE__ */ jsx5(
876
+ SocialEmbed,
877
+ {
878
+ platform: block.platform,
879
+ url: block.url,
880
+ embedCode: block.embedCode,
881
+ oembed: block.oembed,
882
+ alignment: block.alignment,
883
+ caption: block.caption
884
+ },
885
+ key
886
+ );
887
+ }
888
+ var AUDIO_ALIGN_ITEMS = {
889
+ left: "flex-start",
890
+ center: "center",
891
+ right: "flex-end",
892
+ none: "stretch"
893
+ };
894
+ function renderAudio(block, key, blocks) {
895
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
896
+ const AudioComp = blocks == null ? void 0 : blocks.audio;
897
+ if (AudioComp) {
898
+ return /* @__PURE__ */ jsx5(
899
+ AudioComp,
900
+ {
901
+ file: block.file,
902
+ title: block.title,
903
+ caption: block.caption,
904
+ player: (_a = block.player) != null ? _a : {},
905
+ alignment: block.alignment
906
+ },
907
+ key
908
+ );
909
+ }
910
+ const { file, title, caption } = block;
911
+ const player = (_b = block.player) != null ? _b : {};
912
+ const alignment = (_c = block.alignment) != null ? _c : "center";
913
+ const capId = caption ? `bb-audio-cap-${(_e = (_d = file.id) != null ? _d : file.hash) != null ? _e : key}` : void 0;
914
+ return /* @__PURE__ */ jsxs4(
915
+ "figure",
916
+ {
917
+ className: `bb-audio align-${alignment}`,
918
+ style: {
919
+ display: "flex",
920
+ flexDirection: "column",
921
+ gap: "0.5rem",
922
+ margin: "1rem 0",
923
+ alignItems: AUDIO_ALIGN_ITEMS[alignment]
924
+ },
925
+ children: [
926
+ title && /* @__PURE__ */ jsx5("figcaption", { className: "bb-audio-title", style: { fontWeight: 600 }, children: title }),
927
+ /* @__PURE__ */ jsxs4(
928
+ "audio",
929
+ {
930
+ className: "bb-audio-player",
931
+ src: file.url,
932
+ controls: (_f = player.controls) != null ? _f : true,
933
+ autoPlay: (_g = player.autoplay) != null ? _g : false,
934
+ loop: (_h = player.loop) != null ? _h : false,
935
+ preload: (_i = player.preload) != null ? _i : "metadata",
936
+ "aria-label": title || "Audio player",
937
+ "aria-describedby": capId,
938
+ style: { width: "100%", maxWidth: alignment === "none" ? "100%" : "32rem" },
939
+ children: [
940
+ "Your browser does not support the audio element. ",
941
+ /* @__PURE__ */ jsx5("a", { href: file.url, children: "Download the audio" }),
942
+ "."
943
+ ]
944
+ }
945
+ ),
946
+ caption && /* @__PURE__ */ jsx5(
947
+ "figcaption",
948
+ {
949
+ id: capId,
950
+ className: "bb-audio-caption",
951
+ style: { fontSize: "0.875rem", color: "#6b7280" },
952
+ children: caption
953
+ }
954
+ )
955
+ ]
956
+ },
957
+ key
958
+ );
959
+ }
960
+ function renderDiagram(block, key, blocks) {
961
+ var _a;
962
+ const DiagramComp = blocks == null ? void 0 : blocks.diagram;
963
+ const code = (_a = block.value) != null ? _a : "";
964
+ if (DiagramComp) {
965
+ return /* @__PURE__ */ jsx5(DiagramComp, { code, format: block.format }, key);
966
+ }
967
+ return /* @__PURE__ */ jsx5(MermaidDiagram, { value: code }, key);
968
+ }
969
+ var CALLOUT_VARIANTS = {
970
+ note: {
971
+ color: "#0969da",
972
+ label: "Note",
973
+ icon: "M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"
974
+ },
975
+ tip: {
976
+ color: "#1a7f37",
977
+ label: "Tip",
978
+ icon: "M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"
979
+ },
980
+ important: {
981
+ color: "#8250df",
982
+ label: "Important",
983
+ icon: "M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
984
+ },
985
+ warning: {
986
+ color: "#9a6700",
987
+ label: "Warning",
988
+ icon: "M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
989
+ },
990
+ caution: {
991
+ color: "#d1242f",
992
+ label: "Caution",
993
+ icon: "M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .39.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.39.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"
994
+ }
995
+ };
996
+ function renderCallout(block, key, blocks, modifiers, options) {
997
+ var _a;
998
+ const variant = CALLOUT_VARIANTS[block.variant] ? block.variant : "note";
999
+ const meta = CALLOUT_VARIANTS[variant];
1000
+ const childNodes = block.children.map(
1001
+ (child, index) => renderBlock(child, index, blocks, modifiers, options)
1002
+ );
1003
+ const lastIndex = childNodes.length - 1;
1004
+ const children = childNodes.map((node, index) => {
1005
+ if (!isValidElement(node)) return node;
1006
+ const element = node;
1007
+ const collapsed = {};
1008
+ if (index === 0) collapsed.marginTop = 0;
1009
+ if (index === lastIndex) collapsed.marginBottom = 0;
1010
+ return cloneElement(element, { style: { ...element.props.style, ...collapsed } });
1011
+ });
1012
+ const CalloutComp = blocks == null ? void 0 : blocks.callout;
1013
+ if (CalloutComp) {
1014
+ return /* @__PURE__ */ jsx5(CalloutComp, { variant, title: block.title, children }, key);
1015
+ }
1016
+ const title = ((_a = block.title) == null ? void 0 : _a.trim()) ? block.title : meta.label;
1017
+ return /* @__PURE__ */ jsxs4(
1018
+ "aside",
1019
+ {
1020
+ className: `bb-callout bb-callout-${variant}`,
1021
+ role: "note",
1022
+ style: {
1023
+ borderLeft: `0.25rem solid ${meta.color}`,
1024
+ // Subtle accent-tinted background (~8% opacity) to match the editor preview.
1025
+ backgroundColor: `${meta.color}14`,
1026
+ padding: "0.5rem 1rem",
1027
+ margin: "1rem 0"
1028
+ },
1029
+ children: [
1030
+ /* @__PURE__ */ jsxs4(
1031
+ "p",
1032
+ {
1033
+ className: "bb-callout-title",
1034
+ style: {
1035
+ display: "flex",
1036
+ alignItems: "center",
1037
+ gap: "0.5rem",
1038
+ // GitHub spacing: tight title line with a 1rem gap before the body.
1039
+ lineHeight: 1,
1040
+ margin: "0 0 1rem",
1041
+ fontWeight: 600,
1042
+ color: meta.color
1043
+ },
1044
+ children: [
1045
+ /* @__PURE__ */ jsx5(
1046
+ "svg",
1047
+ {
1048
+ className: "bb-callout-icon",
1049
+ viewBox: "0 0 16 16",
1050
+ width: "16",
1051
+ height: "16",
1052
+ fill: "currentColor",
1053
+ "aria-hidden": "true",
1054
+ children: /* @__PURE__ */ jsx5("path", { d: meta.icon })
1055
+ }
1056
+ ),
1057
+ title
1058
+ ]
1059
+ }
1060
+ ),
1061
+ children
1062
+ ]
1063
+ },
1064
+ key
1065
+ );
1066
+ }
1067
+ function renderDetails(block, key, blocks, modifiers, options) {
1068
+ const children = block.children.map(
1069
+ (child, index) => renderBlock(child, index, blocks, modifiers, options)
1070
+ );
1071
+ const DetailsComp = blocks == null ? void 0 : blocks.details;
1072
+ if (DetailsComp) {
1073
+ return /* @__PURE__ */ jsx5(DetailsComp, { summary: block.summary, defaultOpen: block.defaultOpen, children }, key);
1074
+ }
1075
+ return /* @__PURE__ */ jsxs4("details", { className: "bb-details", open: block.defaultOpen, children: [
1076
+ /* @__PURE__ */ jsx5("summary", { className: "bb-details-summary", children: block.summary }),
1077
+ children
1078
+ ] }, key);
1079
+ }
1080
+ var BUTTON_HOVER_CSS = ".bb-button{transition:background-color .15s ease,color .15s ease}.bb-button:hover{background-color:var(--bb-button-hover-bg,var(--bb-button-bg))!important;color:var(--bb-button-hover-color,var(--bb-button-color))!important}.bb-button:focus-visible{outline:2px solid currentColor;outline-offset:2px}";
1081
+ function contentHasBlock(nodes, type) {
1082
+ for (const node of nodes) {
1083
+ if (node.type === type) return true;
1084
+ const children = node.children;
1085
+ if (Array.isArray(children) && contentHasBlock(children, type)) return true;
1086
+ }
1087
+ return false;
1088
+ }
1089
+ function getButtonStyle(style) {
1090
+ const out = {
1091
+ display: "inline-block",
1092
+ textDecoration: "none",
1093
+ cursor: "pointer"
1094
+ };
1095
+ if (!style) return out;
1096
+ if (style.backgroundColor) {
1097
+ out.backgroundColor = style.backgroundColor;
1098
+ out["--bb-button-bg"] = style.backgroundColor;
1099
+ }
1100
+ if (style.textColor) {
1101
+ out.color = style.textColor;
1102
+ out["--bb-button-color"] = style.textColor;
1103
+ }
1104
+ if (style.borderRadius) out.borderRadius = style.borderRadius;
1105
+ if (style.fontSize) out.fontSize = style.fontSize;
1106
+ if (style.fontWeight) out.fontWeight = style.fontWeight;
1107
+ if (style.padding) out.padding = style.padding;
1108
+ if (style.border) out.border = style.border;
1109
+ if (style.hoverBackgroundColor) out["--bb-button-hover-bg"] = style.hoverBackgroundColor;
1110
+ if (style.hoverTextColor) out["--bb-button-hover-color"] = style.hoverTextColor;
1111
+ return out;
1112
+ }
1113
+ function handleFileDownload(event, url, name) {
1114
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
1115
+ return;
1116
+ }
1117
+ event.preventDefault();
1118
+ void (async () => {
1119
+ try {
1120
+ const response = await fetch(url);
1121
+ if (!response.ok) throw new Error(`Request failed: ${response.status}`);
1122
+ const blob = await response.blob();
1123
+ const objectUrl = URL.createObjectURL(blob);
1124
+ const anchor = document.createElement("a");
1125
+ anchor.href = objectUrl;
1126
+ anchor.download = name;
1127
+ document.body.appendChild(anchor);
1128
+ anchor.click();
1129
+ anchor.remove();
1130
+ URL.revokeObjectURL(objectUrl);
1131
+ } catch (e) {
1132
+ window.location.href = url;
1133
+ }
1134
+ })();
1135
+ }
1136
+ function renderButton(block, key, blocks) {
1137
+ var _a;
1138
+ const ButtonComp = blocks == null ? void 0 : blocks.button;
1139
+ if (ButtonComp) {
1140
+ return /* @__PURE__ */ jsx5(
1141
+ ButtonComp,
1142
+ {
1143
+ label: block.label,
1144
+ buttonType: block.buttonType,
1145
+ alignment: block.alignment,
1146
+ link: block.link,
1147
+ file: block.file,
1148
+ showFileSize: block.showFileSize,
1149
+ showFileIcon: block.showFileIcon,
1150
+ filePreview: block.filePreview,
1151
+ style: block.style,
1152
+ cssClass: block.cssClass
1153
+ },
1154
+ key
1155
+ );
1156
+ }
1157
+ const style = getButtonStyle(block.style);
1158
+ const className = block.cssClass ? `bb-button ${block.cssClass}` : "bb-button";
1159
+ let control;
1160
+ if (block.buttonType === "file" && block.file) {
1161
+ const file = block.file;
1162
+ const icon = block.showFileIcon ? getFileIcon(file) : null;
1163
+ const size = block.showFileSize && typeof file.size === "number" ? formatFileSize(file.size) : null;
1164
+ const filePreview = block.filePreview === true;
1165
+ control = filePreview ? /* @__PURE__ */ jsxs4(
1166
+ "a",
1167
+ {
1168
+ href: file.url,
1169
+ target: "_blank",
1170
+ rel: "noopener noreferrer",
1171
+ "aria-label": `Preview ${file.name}`,
1172
+ className,
1173
+ style,
1174
+ children: [
1175
+ icon && /* @__PURE__ */ jsxs4("span", { className: "bb-button-icon", "aria-hidden": "true", children: [
1176
+ icon,
1177
+ " "
1178
+ ] }),
1179
+ block.label,
1180
+ size && /* @__PURE__ */ jsxs4("span", { className: "bb-button-size", children: [
1181
+ " (",
1182
+ size,
1183
+ ")"
1184
+ ] })
1185
+ ]
1186
+ }
1187
+ ) : /* @__PURE__ */ jsxs4(
1188
+ "a",
1189
+ {
1190
+ href: file.url,
1191
+ download: file.name,
1192
+ "aria-label": `Download ${file.name}`,
1193
+ className,
1194
+ style,
1195
+ onClick: (event) => handleFileDownload(event, file.url, file.name),
1196
+ children: [
1197
+ icon && /* @__PURE__ */ jsxs4("span", { className: "bb-button-icon", "aria-hidden": "true", children: [
1198
+ icon,
1199
+ " "
1200
+ ] }),
1201
+ block.label,
1202
+ size && /* @__PURE__ */ jsxs4("span", { className: "bb-button-size", children: [
1203
+ " (",
1204
+ size,
1205
+ ")"
1206
+ ] })
1207
+ ]
1208
+ }
1209
+ );
1210
+ } else if (block.link) {
1211
+ const link = block.link;
1212
+ control = /* @__PURE__ */ jsx5(
1213
+ "a",
1214
+ {
1215
+ href: link.url,
1216
+ target: link.target,
1217
+ rel: link.rel,
1218
+ "aria-label": link.ariaLabel,
1219
+ className,
1220
+ style,
1221
+ children: block.label
1222
+ }
1223
+ );
1224
+ } else {
1225
+ control = /* @__PURE__ */ jsx5("span", { className, style, children: block.label });
1226
+ }
1227
+ const alignment = (_a = block.alignment) != null ? _a : "left";
1228
+ if (alignment === "none") {
1229
+ return /* @__PURE__ */ jsx5(Fragment, { children: control }, key);
1230
+ }
1231
+ return /* @__PURE__ */ jsx5(
1232
+ "div",
1233
+ {
1234
+ className: "bb-button-wrapper",
1235
+ style: { textAlign: alignment, margin: "0.5rem 0" },
1236
+ children: control
1237
+ },
1238
+ key
1239
+ );
1240
+ }
1241
+ function BlocksRenderer({
1242
+ content,
1243
+ blocks,
1244
+ modifiers,
1245
+ codeTheme = DEFAULT_CODE_THEME,
1246
+ codeCopyButton = false
1247
+ }) {
1248
+ if (!content || !Array.isArray(content) || content.length === 0) {
1249
+ return null;
1250
+ }
1251
+ const needsButtonCss = !(blocks == null ? void 0 : blocks.button) && contentHasBlock(content, "button");
1252
+ const needsEmbedCss = !(blocks == null ? void 0 : blocks.embed) && contentHasBlock(content, "embed");
1253
+ const needsTableCss = !(blocks == null ? void 0 : blocks.table) && contentHasBlock(content, "table");
1254
+ const needsQuoteCss = !(blocks == null ? void 0 : blocks.quote) && contentHasBlock(content, "quote");
1255
+ const needsCodeCss = !(blocks == null ? void 0 : blocks.code) && contentHasBlock(content, "code");
1256
+ const options = { codeTheme, codeCopyButton };
1257
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1258
+ needsButtonCss && /* @__PURE__ */ jsx5("style", { children: BUTTON_HOVER_CSS }),
1259
+ needsEmbedCss && /* @__PURE__ */ jsx5("style", { children: EMBED_FRAME_CSS }),
1260
+ needsTableCss && /* @__PURE__ */ jsx5("style", { children: TABLE_CSS }),
1261
+ needsQuoteCss && /* @__PURE__ */ jsx5("style", { children: QUOTE_CSS }),
1262
+ needsCodeCss && /* @__PURE__ */ jsx5("style", { children: CODE_CSS }),
1263
+ content.map((block, index) => renderBlock(block, index, blocks, modifiers, options))
1264
+ ] });
1265
+ }
1266
+ export {
1267
+ BlocksRenderer
1268
+ };
1269
+ //# sourceMappingURL=index.mjs.map