chaingrow-blog 0.7.2 → 0.7.3
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 -0
- package/dist/{blog-cards-PjckhJAG.d.cts → blog-cards-0gdGpdZr.d.cts} +1 -1
- package/dist/{blog-cards-CcHb2bcv.d.ts → blog-cards-CuGlJiWH.d.ts} +1 -1
- package/dist/{chunk-2FIOFNM6.js → chunk-AJWQNJBF.js} +21 -2
- package/dist/chunk-AJWQNJBF.js.map +1 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/next/index.cjs +20 -1
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +1 -1
- package/dist/next/webhook.d.cts +1 -1
- package/dist/next/webhook.d.ts +1 -1
- package/dist/react/index.cjs +20 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +4 -4
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +1 -1
- package/dist/{types-DSBF7V30.d.cts → types-BZcsJXJY.d.cts} +2 -1
- package/dist/{types-DSBF7V30.d.ts → types-BZcsJXJY.d.ts} +2 -1
- package/package.json +1 -1
- package/dist/chunk-2FIOFNM6.js.map +0 -1
package/README.md
CHANGED
|
@@ -365,6 +365,8 @@ type Mark =
|
|
|
365
365
|
type ListItem = { content: Block[] }; // items can contain nested blocks
|
|
366
366
|
```
|
|
367
367
|
|
|
368
|
+
A line break inside a block (Shift+Enter in the ChainGrow editor) arrives as `"\n"` inside `Inline.text`. `<BlogRenderer>` turns it into `<br>`, drops breaks at the start or end of a block and treats several in a row as one. If you walk the blocks yourself, do the same, otherwise HTML shows the lines run together.
|
|
369
|
+
|
|
368
370
|
If you don't want to walk the blocks yourself, render the body with `<BlogRenderer blog={blog} />` and override per-block renderers via `components.blocks` (shown above).
|
|
369
371
|
|
|
370
372
|
### Pre-existing blog at `/blog`?
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as Blog, F as FaqItem, a as Block, E as EmbeddedMedia } from './types-
|
|
1
|
+
import { B as Blog, F as FaqItem, a as Block, E as EmbeddedMedia } from './types-BZcsJXJY.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Consumer-supplied extras for BlogPosting. Everything is optional — the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as Blog, F as FaqItem, a as Block, E as EmbeddedMedia } from './types-
|
|
1
|
+
import { B as Blog, F as FaqItem, a as Block, E as EmbeddedMedia } from './types-BZcsJXJY.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Consumer-supplied extras for BlogPosting. Everything is optional — the
|
|
@@ -178,8 +178,27 @@ var MARK_ORDER = [
|
|
|
178
178
|
"code",
|
|
179
179
|
"link"
|
|
180
180
|
];
|
|
181
|
+
var BREAK = "br";
|
|
182
|
+
function withBreaks(inline) {
|
|
183
|
+
const out = [];
|
|
184
|
+
for (const run of inline) {
|
|
185
|
+
const parts = (typeof run.text === "string" ? run.text : "").split("\n");
|
|
186
|
+
parts.forEach((text, i) => {
|
|
187
|
+
if (i > 0 && out.length > 0 && out[out.length - 1] !== BREAK) {
|
|
188
|
+
out.push(BREAK);
|
|
189
|
+
}
|
|
190
|
+
if (text && !(out[out.length - 1] === BREAK && !text.trim())) {
|
|
191
|
+
out.push({ ...run, text });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (out[out.length - 1] === BREAK) out.pop();
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
181
198
|
function InlineRenderer({ inline }) {
|
|
182
|
-
return /* @__PURE__ */ jsx2(Fragment, { children: inline.map(
|
|
199
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: withBreaks(inline).map(
|
|
200
|
+
(run, i) => run === BREAK ? /* @__PURE__ */ jsx2("br", {}, i) : /* @__PURE__ */ jsx2(InlineRun, { run }, i)
|
|
201
|
+
) });
|
|
183
202
|
}
|
|
184
203
|
function InlineRun({ run }) {
|
|
185
204
|
const marks = run.marks ? [...run.marks].sort(
|
|
@@ -634,4 +653,4 @@ export {
|
|
|
634
653
|
listBlogs,
|
|
635
654
|
getBlogSitemapEntries
|
|
636
655
|
};
|
|
637
|
-
//# sourceMappingURL=chunk-
|
|
656
|
+
//# sourceMappingURL=chunk-AJWQNJBF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next/metadata.ts","../src/next/BlogPage.tsx","../src/react/defaults.tsx","../src/react/BlogRenderer.tsx","../src/react/BlogDetail.tsx","../src/react/BlogJsonLd.tsx","../src/next/index.ts"],"sourcesContent":["import type { Metadata } from 'next';\nimport { getBlog, listBlogs } from './index';\nimport type { NextClientOptions } from './index';\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Opt-in hreflang configuration for `generateBlogMetadata`. ChainGrow serves\n * language variants of a post under one shared slug, so the SDK can emit\n * `alternates.languages` (and a canonical) for every variant it finds —\n * pass this when your site serves more than one language.\n */\nexport type BlogAlternatesOptions = {\n /** Absolute site origin, e.g. `https://example.com` (no trailing slash needed). */\n siteUrl: string;\n /**\n * Blog path prefix. A static string applies to every language; pass a\n * function for locale-prefixed routing, e.g.\n * `(lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`)`.\n * Defaults to `/blog`.\n */\n pathPrefix?: string | ((language: string) => string);\n /** Language whose URL is additionally emitted as `x-default`. */\n xDefaultLanguage?: string;\n};\n\nexport type BlogMetadataOptions = NextClientOptions & {\n alternates?: BlogAlternatesOptions;\n};\n\nconst blogUrlFor = (\n alternates: BlogAlternatesOptions,\n language: string,\n slug: string,\n): string => {\n const base = trimTrailingSlash(alternates.siteUrl);\n const rawPrefix =\n typeof alternates.pathPrefix === 'function'\n ? alternates.pathPrefix(language)\n : (alternates.pathPrefix ?? '/blog');\n return `${base}${normalizePathPrefix(rawPrefix)}/${slug}`;\n};\n\n/**\n * Next.js `Metadata` factory for a chaingrow blog. Wire it up like:\n *\n * ```ts\n * export async function generateMetadata({ params }) {\n * const { slug } = await params;\n * return generateBlogMetadata(slug);\n * }\n * ```\n *\n * Reuses `getBlog()` which is ISR-tagged, so `generateMetadata` + the page\n * render share one backend hit.\n *\n * Pass `opts.alternates` to also emit `alternates.canonical` and hreflang\n * `alternates.languages` built from the post's language variants (ChainGrow\n * publishes translations under the same slug):\n *\n * ```ts\n * return generateBlogMetadata(slug, locale, {\n * alternates: {\n * siteUrl: 'https://example.com',\n * pathPrefix: (lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`),\n * xDefaultLanguage: 'de',\n * },\n * });\n * ```\n *\n * The variant lookup reuses the ISR-cached `listBlogs()` response (same\n * cache entry as the blog index and sitemaps), so it does not add a backend\n * hit per page view.\n *\n * Returns `{}` on 404 — the page's own `notFound()` owns the HTTP status;\n * this helper just declines to emit half-populated metadata for a missing\n * post.\n *\n * Fields that aren't present on the Blog are omitted (not set to\n * `undefined`) so spread-overrides like\n * `{ ...await generateBlogMetadata(slug), alternates: { canonical } }` do\n * what you expect.\n */\nexport async function generateBlogMetadata(\n slug: string,\n language?: string,\n opts?: BlogMetadataOptions,\n): Promise<Metadata> {\n const blog = await getBlog(slug, language, opts);\n if (!blog) return {};\n\n const images = blog.og_image ? [blog.og_image] : undefined;\n const description = blog.description ?? undefined;\n\n const meta: Metadata = {\n title: blog.title,\n ...(description ? { description } : {}),\n openGraph: {\n type: 'article',\n title: blog.title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n ...(blog.published_at ? { publishedTime: blog.published_at } : {}),\n },\n twitter: {\n card: blog.og_image ? 'summary_large_image' : 'summary',\n title: blog.title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n },\n };\n\n if (opts?.alternates) {\n const alternates = opts.alternates;\n const canonical = blogUrlFor(alternates, blog.language, blog.slug);\n let languages: Record<string, string> | undefined;\n try {\n const all = await listBlogs({ status: 'PUBLISHED' }, opts);\n const variants = all.filter((b) => b.slug === blog.slug && b.language);\n if (variants.length > 1) {\n languages = {};\n for (const variant of variants) {\n languages[variant.language] = blogUrlFor(\n alternates,\n variant.language,\n variant.slug,\n );\n }\n const xDefaultUrl = alternates.xDefaultLanguage\n ? languages[alternates.xDefaultLanguage]\n : undefined;\n if (xDefaultUrl) languages['x-default'] = xDefaultUrl;\n }\n } catch (err) {\n // hreflang is an enhancement — never fail metadata generation over it.\n console.error(\n 'chaingrow-blog: hreflang variant lookup failed, emitting canonical only:',\n err,\n );\n }\n meta.alternates = {\n canonical,\n ...(languages ? { languages } : {}),\n };\n }\n\n return meta;\n}\n\nexport type BlogIndexMetadataOptions = {\n /** Page title, e.g. `'Blog | Acme'`. */\n title: string;\n description?: string;\n /** Absolute canonical URL of the index page, e.g. `https://example.com/blog`. */\n canonical?: string;\n /** OG/Twitter preview image URL. */\n ogImage?: string;\n};\n\n/**\n * Next.js `Metadata` for a blog *index* page (`app/blog/page.tsx`). The blog\n * detail page gets its metadata from the post itself, but the index has no\n * single post to draw from — without this, greenfield apps fall back to the\n * site-default title on `/blog`. Synchronous (no API call):\n *\n * ```ts\n * export const metadata = generateBlogIndexMetadata({\n * title: 'Blog | Acme',\n * description: 'Product updates and guides from Acme.',\n * canonical: 'https://example.com/blog',\n * });\n * ```\n */\nexport function generateBlogIndexMetadata(\n opts: BlogIndexMetadataOptions,\n): Metadata {\n const { title, description, canonical, ogImage } = opts;\n const images = ogImage ? [ogImage] : undefined;\n return {\n title,\n ...(description ? { description } : {}),\n ...(canonical ? { alternates: { canonical } } : {}),\n openGraph: {\n type: 'website',\n title,\n ...(description ? { description } : {}),\n ...(canonical ? { url: canonical } : {}),\n ...(images ? { images } : {}),\n },\n twitter: {\n card: ogImage ? 'summary_large_image' : 'summary',\n title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n },\n };\n}\n","import * as React from 'react';\nimport type { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport type { Blog } from '../types';\nimport { BlogDetail } from '../react/BlogDetail';\nimport { BlogJsonLd } from '../react/BlogJsonLd';\nimport { getBlog } from './index';\nimport { generateBlogMetadata, type BlogMetadataOptions } from './metadata';\n\nexport type BlogPageRouteParams = { slug: string; locale?: string };\n\ntype RouteArgs = {\n params: Promise<BlogPageRouteParams>;\n};\n\n/**\n * Default-batteries `app/blog/[slug]/page.tsx` component. Fetches the blog,\n * renders JSON-LD + the detail layout, 404s if missing. Zero configuration\n * and zero CSS framework dependency — typography styles are emitted inline\n * under the scoped `cg-blog-article` class so a fresh app looks good without\n * Tailwind, prose, or anything else installed.\n *\n * If you want a custom layout, breadcrumbs, or to coexist with an existing\n * blog, skip this component and compose `getBlog()` + `<BlogJsonLd>` +\n * `<BlogDetail>` directly in your page file.\n */\nexport async function BlogPage({\n params,\n}: RouteArgs): Promise<React.JSX.Element> {\n const { slug, locale } = await params;\n let blog: Blog | null = null;\n try {\n blog = await getBlog(slug, locale);\n } catch (err) {\n console.error('chaingrow-blog: <BlogPage> getBlog failed:', err);\n }\n if (!blog) notFound();\n\n return (\n <div className=\"cg-blog-article\">\n {/* eslint-disable-next-line react/no-danger */}\n <style dangerouslySetInnerHTML={{ __html: ARTICLE_CSS }} />\n <BlogJsonLd blog={blog} />\n <BlogDetail blog={blog} />\n </div>\n );\n}\n\n// Color strategy: every text element uses \\`color: inherit\\` so the host\n// app's text color cascades down. Muted variants (date, hr) use\n// \\`currentColor\\` with opacity. Backgrounds for placeholders use a\n// translucent neutral that works against any host background. No\n// \\`prefers-color-scheme\\` rules — the host's theme system (Tailwind\n// \\`dark:\\`, \\`data-theme\\`, OS preference, whatever) drives the SDK\n// without it having to know how the host implements the toggle.\nconst ARTICLE_CSS = `\n.cg-blog-article {\n max-width: 720px;\n margin: 0 auto;\n padding: 4rem 1.5rem 6rem;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n font-size: 1.0625rem;\n line-height: 1.7;\n color: inherit;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.cg-blog-article h1 {\n font-size: clamp(2rem, 4vw, 2.625rem);\n font-weight: 700;\n line-height: 1.15;\n letter-spacing: -0.022em;\n color: inherit;\n margin: 0 0 1rem;\n}\n.cg-blog-article time {\n display: block;\n font-size: 0.8125rem;\n color: currentColor;\n opacity: 0.55;\n margin: 0 0 2rem;\n font-weight: 400;\n}\n.cg-blog-article h2 {\n font-size: 1.625rem;\n font-weight: 700;\n line-height: 1.25;\n letter-spacing: -0.015em;\n color: inherit;\n margin: 3rem 0 1rem;\n}\n.cg-blog-article h3 {\n font-size: 1.25rem;\n font-weight: 700;\n line-height: 1.3;\n color: inherit;\n margin: 2.25rem 0 0.75rem;\n}\n.cg-blog-article h4 {\n font-size: 1.0625rem;\n font-weight: 700;\n color: inherit;\n margin: 1.75rem 0 0.5rem;\n}\n.cg-blog-article p {\n margin: 0 0 1.25rem;\n color: inherit;\n}\n.cg-blog-article a {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 3px;\n text-decoration-thickness: 1px;\n transition: opacity 150ms ease;\n}\n.cg-blog-article a:hover { opacity: 0.7; }\n.cg-blog-article strong { color: inherit; font-weight: 700; }\n.cg-blog-article em { font-style: italic; }\n.cg-blog-article ul,\n.cg-blog-article ol {\n margin: 0 0 1.5rem;\n padding-left: 1.5rem;\n color: inherit;\n}\n.cg-blog-article li {\n margin-bottom: 0.5rem;\n line-height: 1.65;\n}\n.cg-blog-article li > p { margin: 0 0 0.5rem; }\n.cg-blog-article li:last-child > p:last-child { margin-bottom: 0; }\n.cg-blog-article blockquote {\n margin: 2rem 0;\n padding: 0.5rem 0 0.5rem 1.5rem;\n border-left: 3px solid currentColor;\n font-style: italic;\n color: inherit;\n font-size: 1.125rem;\n line-height: 1.6;\n}\n.cg-blog-article blockquote p:last-child { margin-bottom: 0; }\n.cg-blog-article img {\n display: block;\n width: 100%;\n height: auto;\n margin: 2.25rem 0;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.08);\n}\n.cg-blog-article video {\n display: block;\n width: 100%;\n height: auto;\n margin: 2.25rem 0;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.15);\n}\n.cg-blog-article hr {\n border: 0;\n height: 1px;\n background: currentColor;\n opacity: 0.15;\n margin: 3rem 0;\n}\n.cg-blog-article code {\n background: rgba(128, 128, 128, 0.15);\n color: inherit;\n padding: 0.15em 0.4em;\n border-radius: 3px;\n font-size: 0.9em;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n}\n.cg-blog-article pre {\n background: rgba(128, 128, 128, 0.12);\n color: inherit;\n padding: 1.25rem 1.5rem;\n border-radius: 6px;\n overflow-x: auto;\n font-size: 0.9375rem;\n line-height: 1.6;\n margin: 1.75rem 0;\n}\n.cg-blog-article pre code {\n background: transparent;\n color: inherit;\n padding: 0;\n border-radius: 0;\n font-size: inherit;\n}\n@media (max-width: 640px) {\n .cg-blog-article {\n padding: 2.5rem 1.25rem 4rem;\n font-size: 1rem;\n }\n .cg-blog-article h2 { margin-top: 2.25rem; }\n .cg-blog-article h3 { margin-top: 1.75rem; }\n}\n`;\n\n/**\n * Default-batteries `generateMetadata` for the detail page. Zero configuration.\n *\n * Greenfield use:\n *\n * ```ts\n * export const generateMetadata = generateBlogPageMetadata();\n * ```\n *\n * Multilingual sites can pass `alternates` to get canonical + hreflang\n * `alternates.languages` for every language variant of the post:\n *\n * ```ts\n * export const generateMetadata = generateBlogPageMetadata({\n * alternates: {\n * siteUrl: 'https://example.com',\n * pathPrefix: (lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`),\n * },\n * });\n * ```\n *\n * For custom metadata or coexistence with a legacy blog, skip this and call\n * `generateBlogMetadata(slug, locale)` directly in your own\n * `generateMetadata`.\n */\nexport function generateBlogPageMetadata(\n opts?: BlogMetadataOptions,\n): (args: RouteArgs) => Promise<Metadata> {\n return async ({ params }) => {\n const { slug, locale } = await params;\n try {\n return await generateBlogMetadata(slug, locale, opts);\n } catch (err) {\n console.error(\n 'chaingrow-blog: generateBlogPageMetadata failed:',\n err,\n );\n return {};\n }\n };\n}\n","import * as React from 'react';\nimport type { Block, EmbeddedMedia, ListItem } from '../types';\n\n/**\n * Default components for each Block type. All are headless — no\n * classNames baked in. Consumers customize via:\n * - `classNames={{ heading: \"...\" }}` on `<BlogRenderer>` — threads\n * through to `className` on the Default component below.\n * - `components={{ heading: MyHeading }}` — full replacement.\n */\n\nexport const DefaultHeading = ({\n level,\n children,\n className,\n}: {\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n}) => {\n if (level === 1) return <h1 className={className}>{children}</h1>;\n if (level === 3) return <h3 className={className}>{children}</h3>;\n return <h2 className={className}>{children}</h2>;\n};\n\nexport const DefaultParagraph = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <p className={className}>{children}</p>;\n\nexport const DefaultBulletList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ul className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ul>\n);\n\nexport const DefaultOrderedList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ol className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ol>\n);\n\nexport const DefaultBlockquote = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <blockquote className={className}>{children}</blockquote>;\n\nexport const DefaultCodeBlock = ({\n language,\n code,\n className,\n}: {\n language?: string;\n code: string;\n className?: string;\n}) => (\n <pre className={className}>\n <code className={language ? `language-${language}` : undefined}>{code}</code>\n </pre>\n);\n\nexport const DefaultImage = ({\n src,\n alt,\n caption,\n width,\n height,\n className,\n}: {\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n}) => {\n const img = (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={src} alt={alt} width={width} height={height} />\n );\n if (caption) {\n return (\n <figure className={className}>\n {img}\n <figcaption>{caption}</figcaption>\n </figure>\n );\n }\n return <span className={className}>{img}</span>;\n};\n\nexport const DefaultDivider = ({ className }: { className?: string }) => (\n <hr className={className} />\n);\n\n/**\n * Default renderer for a single embedded media item. Bare HTML — no\n * classNames, no styles. Override via `components.media` on `<BlogRenderer>`.\n * `alt` falls back to the generation prompt, which is a sensible description\n * string since it was the LLM input that produced the image.\n */\nexport const DefaultMedia = ({ media }: { media: EmbeddedMedia }) => {\n if (media.media_type === 'VIDEO') {\n return (\n <video\n src={media.media_url}\n controls\n playsInline\n preload=\"metadata\"\n />\n );\n }\n // eslint-disable-next-line @next/next/no-img-element\n return <img src={media.media_url} alt={media.prompt} />;\n};\n","import * as React from 'react';\nimport type {\n Blog,\n Block,\n BlockType,\n EmbeddedMedia,\n Inline,\n ListItem,\n Mark,\n} from '../types';\nimport { interleaveMedia } from '../media-placement';\nimport {\n DefaultHeading,\n DefaultParagraph,\n DefaultBulletList,\n DefaultOrderedList,\n DefaultBlockquote,\n DefaultCodeBlock,\n DefaultImage,\n DefaultDivider,\n DefaultMedia,\n} from './defaults';\n\n/**\n * Override map for per-block components. Keys are the discriminants\n * of `Block`. Consumers pass any subset; unspecified blocks render\n * via the matching `Default*` component.\n *\n * v0.3.0 breaking change: the keys changed to match the new Block\n * union. If you upgraded from 0.2.x, rename `list` → `bulletList` /\n * `orderedList`, `quote` → `blockquote`, `code` → `codeBlock`, drop\n * `image_placeholder` (images are now first-class via `image`).\n */\nexport type BlockComponents = Partial<{\n heading: React.ComponentType<{\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n }>;\n paragraph: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n bulletList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n orderedList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n blockquote: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n codeBlock: React.ComponentType<{\n language?: string;\n code: string;\n className?: string;\n }>;\n image: React.ComponentType<{\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n }>;\n divider: React.ComponentType<{ className?: string }>;\n /** Renders a single attached image/video when `<BlogRenderer>` distributes\n * `blog.generatedMedia` across the body. Default renders a bare\n * `<img>` or `<video controls>`. */\n media: React.ComponentType<{ media: EmbeddedMedia }>;\n wrapper: React.ComponentType<{ children: React.ReactNode; blog: Blog }>;\n}>;\n\n/** Simple Tailwind-style className override map. Applied to the\n * default component for each block type. `components` override takes\n * precedence — if you replace the component, className won't reach it\n * unless your override handles it. */\nexport type BlockClassNames = Partial<Record<BlockType, string>>;\n\nconst MARK_ORDER: Mark['type'][] = [\n 'bold',\n 'italic',\n 'strike',\n 'code',\n 'link',\n];\n\nconst BREAK = 'br' as const;\n\n/**\n * A line break inside a block arrives as \"\\n\" in a text run and becomes\n * a `<br>`. Breaks at the start or end of the block are dropped and\n * several in a row count as one, so a break never adds an empty line.\n */\nfunction withBreaks(inline: Inline[]): (Inline | typeof BREAK)[] {\n const out: (Inline | typeof BREAK)[] = [];\n for (const run of inline) {\n const parts = (typeof run.text === 'string' ? run.text : '').split('\\n');\n parts.forEach((text, i) => {\n if (i > 0 && out.length > 0 && out[out.length - 1] !== BREAK) {\n out.push(BREAK);\n }\n if (text && !(out[out.length - 1] === BREAK && !text.trim())) {\n out.push({ ...run, text });\n }\n });\n }\n if (out[out.length - 1] === BREAK) out.pop();\n return out;\n}\n\n/** Render an array of Inline runs into text+mark JSX. */\nfunction InlineRenderer({ inline }: { inline: Inline[] }) {\n return (\n <>\n {withBreaks(inline).map((run, i) =>\n run === BREAK ? <br key={i} /> : <InlineRun key={i} run={run} />,\n )}\n </>\n );\n}\n\nfunction InlineRun({ run }: { run: Inline }) {\n const marks = run.marks\n ? [...run.marks].sort(\n (a, b) => MARK_ORDER.indexOf(b.type) - MARK_ORDER.indexOf(a.type),\n )\n : [];\n let node: React.ReactNode = run.text;\n for (const mark of marks) {\n switch (mark.type) {\n case 'bold':\n node = <strong>{node}</strong>;\n break;\n case 'italic':\n node = <em>{node}</em>;\n break;\n case 'strike':\n node = <s>{node}</s>;\n break;\n case 'code':\n node = <code>{node}</code>;\n break;\n case 'link':\n node = (\n <a href={mark.href} rel=\"noopener noreferrer\">\n {node}\n </a>\n );\n break;\n }\n }\n return <>{node}</>;\n}\n\nexport function BlockRenderer({\n block,\n components = {},\n classNames = {},\n renderBlock,\n}: {\n block: Block;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n renderBlock?: (b: Block, i: number) => React.ReactNode;\n}) {\n // Parent supplies a renderBlock callback so list items can recurse\n // into the same switch. When not provided (top-level call), wire it\n // to a local closure.\n const recurse: (b: Block, i: number) => React.ReactNode =\n renderBlock ??\n ((b, i) => (\n <BlockRenderer\n key={i}\n block={b}\n components={components}\n classNames={classNames}\n renderBlock={recurse}\n />\n ));\n\n switch (block.type) {\n case 'heading': {\n const Heading = components.heading ?? DefaultHeading;\n return (\n <Heading level={block.level} className={classNames.heading}>\n <InlineRenderer inline={block.content} />\n </Heading>\n );\n }\n case 'paragraph': {\n const Paragraph = components.paragraph ?? DefaultParagraph;\n return (\n <Paragraph className={classNames.paragraph}>\n <InlineRenderer inline={block.content} />\n </Paragraph>\n );\n }\n case 'bulletList': {\n const BulletList = components.bulletList ?? DefaultBulletList;\n return (\n <BulletList\n items={block.items}\n className={classNames.bulletList}\n renderBlock={recurse}\n />\n );\n }\n case 'orderedList': {\n const OrderedList = components.orderedList ?? DefaultOrderedList;\n return (\n <OrderedList\n items={block.items}\n className={classNames.orderedList}\n renderBlock={recurse}\n />\n );\n }\n case 'blockquote': {\n const Blockquote = components.blockquote ?? DefaultBlockquote;\n return (\n <Blockquote className={classNames.blockquote}>\n <InlineRenderer inline={block.content} />\n </Blockquote>\n );\n }\n case 'codeBlock': {\n const CodeBlock = components.codeBlock ?? DefaultCodeBlock;\n return (\n <CodeBlock\n language={block.language}\n code={block.code}\n className={classNames.codeBlock}\n />\n );\n }\n case 'image': {\n const ImageComp = components.image ?? DefaultImage;\n return (\n <ImageComp\n src={block.src}\n alt={block.alt}\n caption={block.caption}\n width={block.width}\n height={block.height}\n mediaId={block.mediaId}\n className={classNames.image}\n />\n );\n }\n case 'divider': {\n const Divider = components.divider ?? DefaultDivider;\n return <Divider className={classNames.divider} />;\n }\n }\n}\n\nexport function BlogRenderer({\n blog,\n components = {},\n classNames = {},\n}: {\n blog: Blog;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n}) {\n const Media = components.media ?? DefaultMedia;\n const Wrapper = components.wrapper;\n\n // Distribute `blog.generatedMedia` across `blog.blocks` at positions\n // floor(i * N / K) — first media at top of body, the rest spread evenly.\n // Images placed in the text render as their own `image` blocks instead.\n const slots = interleaveMedia(blog.blocks, blog.generatedMedia);\n\n const content = (\n <>\n {slots.map((slot) =>\n slot.kind === 'block' ? (\n <BlockRenderer\n key={`b:${slot.index}`}\n block={slot.block}\n components={components}\n classNames={classNames}\n />\n ) : (\n <Media key={`m:${slot.media.id}`} media={slot.media} />\n ),\n )}\n </>\n );\n\n if (Wrapper) return <Wrapper blog={blog}>{content}</Wrapper>;\n return content;\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport { hasTextImage } from '../media-placement';\nimport { BlogRenderer, type BlockComponents } from './BlogRenderer';\n\nexport type BlogDetailComponents = {\n /**\n * Renders above the body. Default: og_image → h1 → time → description.\n * Override to add tags, author chip, share buttons, read time, etc.\n */\n Header?: (props: {\n blog: Blog;\n formatDate: (iso: string) => string;\n }) => React.ReactNode;\n /** Renders below the body. Default: nothing. */\n Footer?: (props: { blog: Blog }) => React.ReactNode;\n /** Forwarded to <BlogRenderer components={...} />. */\n blocks?: BlockComponents;\n};\n\nconst defaultFormatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString();\n\nfunction DefaultHeader({\n blog,\n formatDate,\n}: {\n blog: Blog;\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n // Bare semantic HTML — no classNames, no styles. Consumers wrap the whole\n // BlogDetail in their own `.prose` / typography container to handle styling.\n //\n // When the body shows any image — attachments spread by <BlogRenderer>\n // (the first lands at the top of the body, right under this header) or\n // images placed in the text — `og_image` is one of them, so rendering it\n // here too would show it twice. It then stays a meta-only field (still\n // used by OpenGraph / Twitter card previews via `generateBlogMetadata`).\n const hasInlineMedia =\n (blog.generatedMedia?.length ?? 0) > 0 || hasTextImage(blog.blocks);\n // eslint-disable-next-line @next/next/no-img-element\n const heroImage =\n !hasInlineMedia && blog.og_image ? (\n <img src={blog.og_image} alt={blog.title} />\n ) : null;\n return (\n <>\n {heroImage}\n <h1>{blog.title}</h1>\n {blog.published_at && (\n <time dateTime={blog.published_at}>\n {formatDate(blog.published_at)}\n </time>\n )}\n {blog.description && <p>{blog.description}</p>}\n </>\n );\n}\n\nexport function BlogDetail({\n blog,\n components,\n formatDate = defaultFormatDate,\n}: {\n blog: Blog;\n components?: BlogDetailComponents;\n formatDate?: (iso: string) => string;\n}): React.JSX.Element {\n const Header = components?.Header ?? DefaultHeader;\n const Footer = components?.Footer;\n return (\n <article>\n <Header blog={blog} formatDate={formatDate} />\n <BlogRenderer blog={blog} components={components?.blocks} />\n {Footer ? <Footer blog={blog} /> : null}\n </article>\n );\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport {\n buildBlogJsonLd,\n SCHEMA_ORG_CONTEXT,\n type BlogJsonLdOptions,\n} from '../json-ld';\n\n/**\n * Render an array of Schema.org graph items as a single\n * `<script type=\"application/ld+json\">` tag with a merged `@graph`.\n * Google treats one `@graph` script as equivalent to N separate scripts, and\n * a single tag keeps the DOM uncluttered for consumers who already ship their\n * own schemas (Organization, breadcrumbs, WebSite, …).\n */\nexport function JsonLdGraph({\n items,\n}: {\n items: Record<string, unknown>[];\n}): React.JSX.Element | null {\n if (!items || items.length === 0) return null;\n const payload = JSON.stringify({\n '@context': SCHEMA_ORG_CONTEXT,\n '@graph': items,\n });\n return (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: payload }}\n />\n );\n}\n\n/**\n * Default-batteries JSON-LD for a chaingrow Blog: emits BlogPosting +\n * FAQPage (if the post has FAQs) + any LLM-generated extras from\n * `structured_data.jsonLd`. Consumers who already ship their own graph items\n * (breadcrumbs, Organization, …) pass them via `extraSchemas` and they end\n * up in the same `@graph`.\n */\nexport function BlogJsonLd({\n blog,\n extraSchemas,\n ...opts\n}: {\n blog: Blog;\n extraSchemas?: Record<string, unknown>[];\n} & BlogJsonLdOptions): React.JSX.Element | null {\n const items = [...(extraSchemas ?? []), ...buildBlogJsonLd(blog, opts)];\n return <JsonLdGraph items={items} />;\n}\n","import type { MetadataRoute } from 'next';\nimport type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst DEFAULT_API_URL = 'https://api.chaingrow.de';\n\n/**\n * Default ISR window for `getBlog` / `listBlogs` when the consumer doesn't\n * set `revalidate` explicitly. 7 days — blog posts are effectively immutable\n * once published, so caching for a week is sensible, and the webhook\n * (`chaingrow-blog/next/webhook`) invalidates the cache immediately on\n * publish anyway so this window is really just a safety net for when the\n * webhook isn't mounted or misses a delivery.\n *\n * Override per-call with `{ revalidate: N }` (seconds), `{ revalidate: 0 }`\n * (never cache — every request hits ChainGrow), or `{ revalidate: false }`\n * (cache forever, only the webhook can invalidate).\n */\nconst DEFAULT_REVALIDATE_SECONDS = 60 * 60 * 24 * 7;\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl =\n opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)',\n );\n }\n return { apiUrl, apiKey };\n};\n\nconst buildFetch = (tag: string, revalidate: number | false | undefined) => {\n const effectiveRevalidate: number | false =\n revalidate === undefined ? DEFAULT_REVALIDATE_SECONDS : revalidate;\n return ((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, {\n ...init,\n next: {\n tags: ['chaingrow:blog', tag],\n revalidate: effectiveRevalidate,\n },\n } as RequestInit)) as typeof fetch;\n};\n\nexport async function getBlog(\n slug: string,\n language?: string,\n opts: NextClientOptions = {},\n): Promise<Blog | null> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch(`chaingrow:blog:${slug}`, opts.revalidate),\n });\n return client.getBlog(slug, language);\n}\n\nexport async function listBlogs(\n listOpts: { status?: 'DRAFT' | 'PUBLISHED'; language?: string } = {},\n opts: NextClientOptions = {},\n): Promise<Blog[]> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch('chaingrow:blog:list', opts.revalidate),\n });\n return client.listBlogs(listOpts);\n}\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Returns ChainGrow blog entries already shaped for Next.js's\n * `app/sitemap.ts` (`MetadataRoute.Sitemap`). Spread the result into your\n * sitemap array — no mapping required at the call site.\n *\n * ```ts\n * // app/sitemap.ts\n * import type { MetadataRoute } from 'next';\n * import { getBlogSitemapEntries } from 'chaingrow-blog/next';\n *\n * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n * const blogs = await getBlogSitemapEntries({\n * siteUrl: 'https://example.com',\n * pathPrefix: '/blog',\n * });\n * return [\n * { url: 'https://example.com', lastModified: new Date() },\n * ...blogs,\n * ];\n * }\n * ```\n *\n * For news sitemaps, image sitemaps, or any case that needs the full blog\n * shape (title, language, attached media), call `listBlogs()` directly.\n */\nexport async function getBlogSitemapEntries(opts: {\n siteUrl: string;\n pathPrefix?: string;\n language?: string;\n} & NextClientOptions): Promise<MetadataRoute.Sitemap> {\n const baseUrl = trimTrailingSlash(opts.siteUrl);\n const pathPrefix = normalizePathPrefix(opts.pathPrefix ?? '/blog');\n const blogs = await listBlogs(\n { status: 'PUBLISHED', language: opts.language },\n {\n apiKey: opts.apiKey,\n apiUrl: opts.apiUrl,\n revalidate: opts.revalidate,\n },\n );\n return blogs.map((blog) => ({\n url: `${baseUrl}${pathPrefix}/${blog.slug}`,\n lastModified: new Date(blog.published_at),\n }));\n}\n\nexport type { Blog } from '../types';\nexport {\n generateBlogMetadata,\n generateBlogIndexMetadata,\n type BlogAlternatesOptions,\n type BlogMetadataOptions,\n type BlogIndexMetadataOptions,\n} from './metadata';\nexport { BlogPage, generateBlogPageMetadata } from './BlogPage';\nexport type { BlogPageRouteParams } from './BlogPage';\n"],"mappings":"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","names":["jsx","Fragment","jsx","jsxs","jsx","jsx","jsxs","trimTrailingSlash","normalizePathPrefix"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as Blog } from './types-
|
|
2
|
-
export { a as Block, b as BlockType, E as EmbeddedMedia, F as FaqItem, I as Inline, L as ListItem, M as Mark, S as SocialPost, W as WebhookEvent, c as WebhookPayload } from './types-
|
|
3
|
-
export { B as BlogCard, a as BlogJsonLdOptions, I as InterleavedSlot, S as SCHEMA_ORG_CONTEXT, T as ToBlogCardOptions, b as buildBlogJsonLd, c as buildBlogPostingSchema, d as buildFAQPageSchema, e as distributeMediaPositions, h as hasTextImage, i as interleaveMedia, m as mergeBlogs, p as placedMediaIds, t as toBlogCard } from './blog-cards-
|
|
1
|
+
import { B as Blog } from './types-BZcsJXJY.cjs';
|
|
2
|
+
export { a as Block, b as BlockType, E as EmbeddedMedia, F as FaqItem, I as Inline, L as ListItem, M as Mark, S as SocialPost, W as WebhookEvent, c as WebhookPayload } from './types-BZcsJXJY.cjs';
|
|
3
|
+
export { B as BlogCard, a as BlogJsonLdOptions, I as InterleavedSlot, S as SCHEMA_ORG_CONTEXT, T as ToBlogCardOptions, b as buildBlogJsonLd, c as buildBlogPostingSchema, d as buildFAQPageSchema, e as distributeMediaPositions, h as hasTextImage, i as interleaveMedia, m as mergeBlogs, p as placedMediaIds, t as toBlogCard } from './blog-cards-0gdGpdZr.cjs';
|
|
4
4
|
|
|
5
5
|
type ClientOptions = {
|
|
6
6
|
apiUrl: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { B as Blog } from './types-
|
|
2
|
-
export { a as Block, b as BlockType, E as EmbeddedMedia, F as FaqItem, I as Inline, L as ListItem, M as Mark, S as SocialPost, W as WebhookEvent, c as WebhookPayload } from './types-
|
|
3
|
-
export { B as BlogCard, a as BlogJsonLdOptions, I as InterleavedSlot, S as SCHEMA_ORG_CONTEXT, T as ToBlogCardOptions, b as buildBlogJsonLd, c as buildBlogPostingSchema, d as buildFAQPageSchema, e as distributeMediaPositions, h as hasTextImage, i as interleaveMedia, m as mergeBlogs, p as placedMediaIds, t as toBlogCard } from './blog-cards-
|
|
1
|
+
import { B as Blog } from './types-BZcsJXJY.js';
|
|
2
|
+
export { a as Block, b as BlockType, E as EmbeddedMedia, F as FaqItem, I as Inline, L as ListItem, M as Mark, S as SocialPost, W as WebhookEvent, c as WebhookPayload } from './types-BZcsJXJY.js';
|
|
3
|
+
export { B as BlogCard, a as BlogJsonLdOptions, I as InterleavedSlot, S as SCHEMA_ORG_CONTEXT, T as ToBlogCardOptions, b as buildBlogJsonLd, c as buildBlogPostingSchema, d as buildFAQPageSchema, e as distributeMediaPositions, h as hasTextImage, i as interleaveMedia, m as mergeBlogs, p as placedMediaIds, t as toBlogCard } from './blog-cards-CuGlJiWH.js';
|
|
4
4
|
|
|
5
5
|
type ClientOptions = {
|
|
6
6
|
apiUrl: string;
|
package/dist/next/index.cjs
CHANGED
|
@@ -351,8 +351,27 @@ var MARK_ORDER = [
|
|
|
351
351
|
"code",
|
|
352
352
|
"link"
|
|
353
353
|
];
|
|
354
|
+
var BREAK = "br";
|
|
355
|
+
function withBreaks(inline) {
|
|
356
|
+
const out = [];
|
|
357
|
+
for (const run of inline) {
|
|
358
|
+
const parts = (typeof run.text === "string" ? run.text : "").split("\n");
|
|
359
|
+
parts.forEach((text, i) => {
|
|
360
|
+
if (i > 0 && out.length > 0 && out[out.length - 1] !== BREAK) {
|
|
361
|
+
out.push(BREAK);
|
|
362
|
+
}
|
|
363
|
+
if (text && !(out[out.length - 1] === BREAK && !text.trim())) {
|
|
364
|
+
out.push({ ...run, text });
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (out[out.length - 1] === BREAK) out.pop();
|
|
369
|
+
return out;
|
|
370
|
+
}
|
|
354
371
|
function InlineRenderer({ inline }) {
|
|
355
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: inline.map(
|
|
372
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: withBreaks(inline).map(
|
|
373
|
+
(run, i) => run === BREAK ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("br", {}, i) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineRun, { run }, i)
|
|
374
|
+
) });
|
|
356
375
|
}
|
|
357
376
|
function InlineRun({ run }) {
|
|
358
377
|
const marks = run.marks ? [...run.marks].sort(
|
package/dist/next/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/next/index.ts","../../src/client.ts","../../src/next/metadata.ts","../../src/next/BlogPage.tsx","../../src/media-placement.ts","../../src/react/defaults.tsx","../../src/react/BlogRenderer.tsx","../../src/react/BlogDetail.tsx","../../src/json-ld.ts","../../src/react/BlogJsonLd.tsx"],"sourcesContent":["import type { MetadataRoute } from 'next';\nimport type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst DEFAULT_API_URL = 'https://api.chaingrow.de';\n\n/**\n * Default ISR window for `getBlog` / `listBlogs` when the consumer doesn't\n * set `revalidate` explicitly. 7 days — blog posts are effectively immutable\n * once published, so caching for a week is sensible, and the webhook\n * (`chaingrow-blog/next/webhook`) invalidates the cache immediately on\n * publish anyway so this window is really just a safety net for when the\n * webhook isn't mounted or misses a delivery.\n *\n * Override per-call with `{ revalidate: N }` (seconds), `{ revalidate: 0 }`\n * (never cache — every request hits ChainGrow), or `{ revalidate: false }`\n * (cache forever, only the webhook can invalidate).\n */\nconst DEFAULT_REVALIDATE_SECONDS = 60 * 60 * 24 * 7;\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl =\n opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)',\n );\n }\n return { apiUrl, apiKey };\n};\n\nconst buildFetch = (tag: string, revalidate: number | false | undefined) => {\n const effectiveRevalidate: number | false =\n revalidate === undefined ? DEFAULT_REVALIDATE_SECONDS : revalidate;\n return ((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, {\n ...init,\n next: {\n tags: ['chaingrow:blog', tag],\n revalidate: effectiveRevalidate,\n },\n } as RequestInit)) as typeof fetch;\n};\n\nexport async function getBlog(\n slug: string,\n language?: string,\n opts: NextClientOptions = {},\n): Promise<Blog | null> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch(`chaingrow:blog:${slug}`, opts.revalidate),\n });\n return client.getBlog(slug, language);\n}\n\nexport async function listBlogs(\n listOpts: { status?: 'DRAFT' | 'PUBLISHED'; language?: string } = {},\n opts: NextClientOptions = {},\n): Promise<Blog[]> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch('chaingrow:blog:list', opts.revalidate),\n });\n return client.listBlogs(listOpts);\n}\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Returns ChainGrow blog entries already shaped for Next.js's\n * `app/sitemap.ts` (`MetadataRoute.Sitemap`). Spread the result into your\n * sitemap array — no mapping required at the call site.\n *\n * ```ts\n * // app/sitemap.ts\n * import type { MetadataRoute } from 'next';\n * import { getBlogSitemapEntries } from 'chaingrow-blog/next';\n *\n * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n * const blogs = await getBlogSitemapEntries({\n * siteUrl: 'https://example.com',\n * pathPrefix: '/blog',\n * });\n * return [\n * { url: 'https://example.com', lastModified: new Date() },\n * ...blogs,\n * ];\n * }\n * ```\n *\n * For news sitemaps, image sitemaps, or any case that needs the full blog\n * shape (title, language, attached media), call `listBlogs()` directly.\n */\nexport async function getBlogSitemapEntries(opts: {\n siteUrl: string;\n pathPrefix?: string;\n language?: string;\n} & NextClientOptions): Promise<MetadataRoute.Sitemap> {\n const baseUrl = trimTrailingSlash(opts.siteUrl);\n const pathPrefix = normalizePathPrefix(opts.pathPrefix ?? '/blog');\n const blogs = await listBlogs(\n { status: 'PUBLISHED', language: opts.language },\n {\n apiKey: opts.apiKey,\n apiUrl: opts.apiUrl,\n revalidate: opts.revalidate,\n },\n );\n return blogs.map((blog) => ({\n url: `${baseUrl}${pathPrefix}/${blog.slug}`,\n lastModified: new Date(blog.published_at),\n }));\n}\n\nexport type { Blog } from '../types';\nexport {\n generateBlogMetadata,\n generateBlogIndexMetadata,\n type BlogAlternatesOptions,\n type BlogMetadataOptions,\n type BlogIndexMetadataOptions,\n} from './metadata';\nexport { BlogPage, generateBlogPageMetadata } from './BlogPage';\nexport type { BlogPageRouteParams } from './BlogPage';\n","import type { Blog } from './types';\n\nexport type ClientOptions = {\n apiUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n};\n\nexport type ListBlogsOptions = {\n status?: 'DRAFT' | 'PUBLISHED';\n language?: string;\n};\n\nexport type ChaingrowClient = {\n getBlog(slug: string, language?: string): Promise<Blog | null>;\n getBlogById(id: string): Promise<Blog | null>;\n listBlogs(opts?: ListBlogsOptions): Promise<Blog[]>;\n};\n\n// Error codes that indicate a transient network hiccup rather than a broken\n// setup: flaky DNS resolution (musl containers throw EAI_AGAIN under load),\n// dropped or timed-out connections. Anything else (bad host, TLS failure)\n// fails fast so misconfiguration stays visible.\nconst TRANSIENT_NETWORK_CODES = new Set([\n 'EAI_AGAIN',\n 'ETIMEDOUT',\n 'ECONNRESET',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n]);\n\nconst NETWORK_ATTEMPTS = 3;\n\nfunction causeCode(err: unknown): string | undefined {\n const cause = err instanceof Error ? err.cause : undefined;\n return cause && typeof cause === 'object' && 'code' in cause\n ? String((cause as { code?: unknown }).code)\n : undefined;\n}\n\nexport function createClient(opts: ClientOptions): ChaingrowClient {\n const baseUrl = opts.apiUrl.replace(/\\/$/, '');\n const doFetch = opts.fetch ?? fetch;\n\n const request = async <T>(\n path: string,\n init?: RequestInit,\n ): Promise<T | null> => {\n const url = `${baseUrl}${path}`;\n const method = init?.method ?? 'GET';\n const buildInit = (): RequestInit => ({\n ...init,\n headers: {\n 'X-API-Key': opts.apiKey,\n Accept: 'application/json',\n ...(init?.headers ?? {}),\n },\n });\n\n let res: Response | undefined;\n for (let attempt = 1; attempt <= NETWORK_ATTEMPTS; attempt++) {\n try {\n res = await doFetch(url, buildInit());\n\n // Transparent one-shot retry on 429, honoring Retry-After (seconds).\n if (res.status === 429) {\n const waitSec = parseInt(res.headers.get('Retry-After') ?? '1', 10);\n await new Promise((resolve) =>\n setTimeout(resolve, Math.max(0, waitSec) * 1000),\n );\n res = await doFetch(url, buildInit());\n }\n break;\n } catch (err) {\n const code = causeCode(err);\n if (\n attempt < NETWORK_ATTEMPTS &&\n code !== undefined &&\n TRANSIENT_NETWORK_CODES.has(code)\n ) {\n await new Promise((resolve) => setTimeout(resolve, 250 * attempt));\n continue;\n }\n\n // `fetch failed` / DNS / connection errors land here. Node's default\n // message buries the host in `err.cause`, which makes integration\n // debugging miserable. Surface the URL and the override env var on\n // the top-level message so it appears in the consumer's server log.\n const detail = code\n ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ''}`\n : err instanceof Error\n ? err.message\n : String(err);\n const e = new Error(\n `chaingrow-blog: could not reach ${method} ${url} — ${detail}. ` +\n `Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`,\n );\n (e as Error & { cause?: unknown }).cause = err;\n throw e;\n }\n }\n if (!res) {\n throw new Error(`chaingrow-blog: ${method} ${url} — no response`);\n }\n\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `chaingrow-blog: ${method} ${url} failed (${res.status})`,\n );\n }\n return (await res.json()) as T;\n };\n\n return {\n async getBlog(slug, language) {\n const qs = language ? `?language=${encodeURIComponent(language)}` : '';\n return request<Blog>(\n `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`,\n );\n },\n\n async getBlogById(id) {\n return request<Blog>(`/api/v1/blogs/${encodeURIComponent(id)}`);\n },\n\n async listBlogs(listOpts = {}) {\n const collected: Blog[] = [];\n // Page through until the server-reported `total` is reached. Backends\n // without pagination return everything on the first request\n // (total === data.length), so this loop runs exactly once there.\n const MAX_PAGES = 100;\n for (let page = 0; page < MAX_PAGES; page++) {\n const params = new URLSearchParams();\n if (listOpts.status) params.set('status', listOpts.status);\n if (listOpts.language) params.set('language', listOpts.language);\n if (collected.length > 0)\n params.set('offset', String(collected.length));\n const qs = params.toString();\n const response = await request<{ data: Blog[]; total: number }>(\n `/api/v1/blogs${qs ? `?${qs}` : ''}`,\n );\n if (!response) break;\n const batch = response.data ?? [];\n collected.push(...batch);\n const total =\n typeof response.total === 'number' ? response.total : collected.length;\n if (batch.length === 0 || collected.length >= total) break;\n }\n // The language filter also runs server-side (API builds that predate\n // it ignore the query param) — keep this client-side pass as a\n // fallback; against a filtering server it's a no-op.\n if (listOpts.language) {\n return collected.filter((b) => b.language === listOpts.language);\n }\n return collected;\n },\n };\n}\n","import type { Metadata } from 'next';\nimport { getBlog, listBlogs } from './index';\nimport type { NextClientOptions } from './index';\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Opt-in hreflang configuration for `generateBlogMetadata`. ChainGrow serves\n * language variants of a post under one shared slug, so the SDK can emit\n * `alternates.languages` (and a canonical) for every variant it finds —\n * pass this when your site serves more than one language.\n */\nexport type BlogAlternatesOptions = {\n /** Absolute site origin, e.g. `https://example.com` (no trailing slash needed). */\n siteUrl: string;\n /**\n * Blog path prefix. A static string applies to every language; pass a\n * function for locale-prefixed routing, e.g.\n * `(lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`)`.\n * Defaults to `/blog`.\n */\n pathPrefix?: string | ((language: string) => string);\n /** Language whose URL is additionally emitted as `x-default`. */\n xDefaultLanguage?: string;\n};\n\nexport type BlogMetadataOptions = NextClientOptions & {\n alternates?: BlogAlternatesOptions;\n};\n\nconst blogUrlFor = (\n alternates: BlogAlternatesOptions,\n language: string,\n slug: string,\n): string => {\n const base = trimTrailingSlash(alternates.siteUrl);\n const rawPrefix =\n typeof alternates.pathPrefix === 'function'\n ? alternates.pathPrefix(language)\n : (alternates.pathPrefix ?? '/blog');\n return `${base}${normalizePathPrefix(rawPrefix)}/${slug}`;\n};\n\n/**\n * Next.js `Metadata` factory for a chaingrow blog. Wire it up like:\n *\n * ```ts\n * export async function generateMetadata({ params }) {\n * const { slug } = await params;\n * return generateBlogMetadata(slug);\n * }\n * ```\n *\n * Reuses `getBlog()` which is ISR-tagged, so `generateMetadata` + the page\n * render share one backend hit.\n *\n * Pass `opts.alternates` to also emit `alternates.canonical` and hreflang\n * `alternates.languages` built from the post's language variants (ChainGrow\n * publishes translations under the same slug):\n *\n * ```ts\n * return generateBlogMetadata(slug, locale, {\n * alternates: {\n * siteUrl: 'https://example.com',\n * pathPrefix: (lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`),\n * xDefaultLanguage: 'de',\n * },\n * });\n * ```\n *\n * The variant lookup reuses the ISR-cached `listBlogs()` response (same\n * cache entry as the blog index and sitemaps), so it does not add a backend\n * hit per page view.\n *\n * Returns `{}` on 404 — the page's own `notFound()` owns the HTTP status;\n * this helper just declines to emit half-populated metadata for a missing\n * post.\n *\n * Fields that aren't present on the Blog are omitted (not set to\n * `undefined`) so spread-overrides like\n * `{ ...await generateBlogMetadata(slug), alternates: { canonical } }` do\n * what you expect.\n */\nexport async function generateBlogMetadata(\n slug: string,\n language?: string,\n opts?: BlogMetadataOptions,\n): Promise<Metadata> {\n const blog = await getBlog(slug, language, opts);\n if (!blog) return {};\n\n const images = blog.og_image ? [blog.og_image] : undefined;\n const description = blog.description ?? undefined;\n\n const meta: Metadata = {\n title: blog.title,\n ...(description ? { description } : {}),\n openGraph: {\n type: 'article',\n title: blog.title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n ...(blog.published_at ? { publishedTime: blog.published_at } : {}),\n },\n twitter: {\n card: blog.og_image ? 'summary_large_image' : 'summary',\n title: blog.title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n },\n };\n\n if (opts?.alternates) {\n const alternates = opts.alternates;\n const canonical = blogUrlFor(alternates, blog.language, blog.slug);\n let languages: Record<string, string> | undefined;\n try {\n const all = await listBlogs({ status: 'PUBLISHED' }, opts);\n const variants = all.filter((b) => b.slug === blog.slug && b.language);\n if (variants.length > 1) {\n languages = {};\n for (const variant of variants) {\n languages[variant.language] = blogUrlFor(\n alternates,\n variant.language,\n variant.slug,\n );\n }\n const xDefaultUrl = alternates.xDefaultLanguage\n ? languages[alternates.xDefaultLanguage]\n : undefined;\n if (xDefaultUrl) languages['x-default'] = xDefaultUrl;\n }\n } catch (err) {\n // hreflang is an enhancement — never fail metadata generation over it.\n console.error(\n 'chaingrow-blog: hreflang variant lookup failed, emitting canonical only:',\n err,\n );\n }\n meta.alternates = {\n canonical,\n ...(languages ? { languages } : {}),\n };\n }\n\n return meta;\n}\n\nexport type BlogIndexMetadataOptions = {\n /** Page title, e.g. `'Blog | Acme'`. */\n title: string;\n description?: string;\n /** Absolute canonical URL of the index page, e.g. `https://example.com/blog`. */\n canonical?: string;\n /** OG/Twitter preview image URL. */\n ogImage?: string;\n};\n\n/**\n * Next.js `Metadata` for a blog *index* page (`app/blog/page.tsx`). The blog\n * detail page gets its metadata from the post itself, but the index has no\n * single post to draw from — without this, greenfield apps fall back to the\n * site-default title on `/blog`. Synchronous (no API call):\n *\n * ```ts\n * export const metadata = generateBlogIndexMetadata({\n * title: 'Blog | Acme',\n * description: 'Product updates and guides from Acme.',\n * canonical: 'https://example.com/blog',\n * });\n * ```\n */\nexport function generateBlogIndexMetadata(\n opts: BlogIndexMetadataOptions,\n): Metadata {\n const { title, description, canonical, ogImage } = opts;\n const images = ogImage ? [ogImage] : undefined;\n return {\n title,\n ...(description ? { description } : {}),\n ...(canonical ? { alternates: { canonical } } : {}),\n openGraph: {\n type: 'website',\n title,\n ...(description ? { description } : {}),\n ...(canonical ? { url: canonical } : {}),\n ...(images ? { images } : {}),\n },\n twitter: {\n card: ogImage ? 'summary_large_image' : 'summary',\n title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n },\n };\n}\n","import * as React from 'react';\nimport type { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport type { Blog } from '../types';\nimport { BlogDetail } from '../react/BlogDetail';\nimport { BlogJsonLd } from '../react/BlogJsonLd';\nimport { getBlog } from './index';\nimport { generateBlogMetadata, type BlogMetadataOptions } from './metadata';\n\nexport type BlogPageRouteParams = { slug: string; locale?: string };\n\ntype RouteArgs = {\n params: Promise<BlogPageRouteParams>;\n};\n\n/**\n * Default-batteries `app/blog/[slug]/page.tsx` component. Fetches the blog,\n * renders JSON-LD + the detail layout, 404s if missing. Zero configuration\n * and zero CSS framework dependency — typography styles are emitted inline\n * under the scoped `cg-blog-article` class so a fresh app looks good without\n * Tailwind, prose, or anything else installed.\n *\n * If you want a custom layout, breadcrumbs, or to coexist with an existing\n * blog, skip this component and compose `getBlog()` + `<BlogJsonLd>` +\n * `<BlogDetail>` directly in your page file.\n */\nexport async function BlogPage({\n params,\n}: RouteArgs): Promise<React.JSX.Element> {\n const { slug, locale } = await params;\n let blog: Blog | null = null;\n try {\n blog = await getBlog(slug, locale);\n } catch (err) {\n console.error('chaingrow-blog: <BlogPage> getBlog failed:', err);\n }\n if (!blog) notFound();\n\n return (\n <div className=\"cg-blog-article\">\n {/* eslint-disable-next-line react/no-danger */}\n <style dangerouslySetInnerHTML={{ __html: ARTICLE_CSS }} />\n <BlogJsonLd blog={blog} />\n <BlogDetail blog={blog} />\n </div>\n );\n}\n\n// Color strategy: every text element uses \\`color: inherit\\` so the host\n// app's text color cascades down. Muted variants (date, hr) use\n// \\`currentColor\\` with opacity. Backgrounds for placeholders use a\n// translucent neutral that works against any host background. No\n// \\`prefers-color-scheme\\` rules — the host's theme system (Tailwind\n// \\`dark:\\`, \\`data-theme\\`, OS preference, whatever) drives the SDK\n// without it having to know how the host implements the toggle.\nconst ARTICLE_CSS = `\n.cg-blog-article {\n max-width: 720px;\n margin: 0 auto;\n padding: 4rem 1.5rem 6rem;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n font-size: 1.0625rem;\n line-height: 1.7;\n color: inherit;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.cg-blog-article h1 {\n font-size: clamp(2rem, 4vw, 2.625rem);\n font-weight: 700;\n line-height: 1.15;\n letter-spacing: -0.022em;\n color: inherit;\n margin: 0 0 1rem;\n}\n.cg-blog-article time {\n display: block;\n font-size: 0.8125rem;\n color: currentColor;\n opacity: 0.55;\n margin: 0 0 2rem;\n font-weight: 400;\n}\n.cg-blog-article h2 {\n font-size: 1.625rem;\n font-weight: 700;\n line-height: 1.25;\n letter-spacing: -0.015em;\n color: inherit;\n margin: 3rem 0 1rem;\n}\n.cg-blog-article h3 {\n font-size: 1.25rem;\n font-weight: 700;\n line-height: 1.3;\n color: inherit;\n margin: 2.25rem 0 0.75rem;\n}\n.cg-blog-article h4 {\n font-size: 1.0625rem;\n font-weight: 700;\n color: inherit;\n margin: 1.75rem 0 0.5rem;\n}\n.cg-blog-article p {\n margin: 0 0 1.25rem;\n color: inherit;\n}\n.cg-blog-article a {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 3px;\n text-decoration-thickness: 1px;\n transition: opacity 150ms ease;\n}\n.cg-blog-article a:hover { opacity: 0.7; }\n.cg-blog-article strong { color: inherit; font-weight: 700; }\n.cg-blog-article em { font-style: italic; }\n.cg-blog-article ul,\n.cg-blog-article ol {\n margin: 0 0 1.5rem;\n padding-left: 1.5rem;\n color: inherit;\n}\n.cg-blog-article li {\n margin-bottom: 0.5rem;\n line-height: 1.65;\n}\n.cg-blog-article li > p { margin: 0 0 0.5rem; }\n.cg-blog-article li:last-child > p:last-child { margin-bottom: 0; }\n.cg-blog-article blockquote {\n margin: 2rem 0;\n padding: 0.5rem 0 0.5rem 1.5rem;\n border-left: 3px solid currentColor;\n font-style: italic;\n color: inherit;\n font-size: 1.125rem;\n line-height: 1.6;\n}\n.cg-blog-article blockquote p:last-child { margin-bottom: 0; }\n.cg-blog-article img {\n display: block;\n width: 100%;\n height: auto;\n margin: 2.25rem 0;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.08);\n}\n.cg-blog-article video {\n display: block;\n width: 100%;\n height: auto;\n margin: 2.25rem 0;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.15);\n}\n.cg-blog-article hr {\n border: 0;\n height: 1px;\n background: currentColor;\n opacity: 0.15;\n margin: 3rem 0;\n}\n.cg-blog-article code {\n background: rgba(128, 128, 128, 0.15);\n color: inherit;\n padding: 0.15em 0.4em;\n border-radius: 3px;\n font-size: 0.9em;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n}\n.cg-blog-article pre {\n background: rgba(128, 128, 128, 0.12);\n color: inherit;\n padding: 1.25rem 1.5rem;\n border-radius: 6px;\n overflow-x: auto;\n font-size: 0.9375rem;\n line-height: 1.6;\n margin: 1.75rem 0;\n}\n.cg-blog-article pre code {\n background: transparent;\n color: inherit;\n padding: 0;\n border-radius: 0;\n font-size: inherit;\n}\n@media (max-width: 640px) {\n .cg-blog-article {\n padding: 2.5rem 1.25rem 4rem;\n font-size: 1rem;\n }\n .cg-blog-article h2 { margin-top: 2.25rem; }\n .cg-blog-article h3 { margin-top: 1.75rem; }\n}\n`;\n\n/**\n * Default-batteries `generateMetadata` for the detail page. Zero configuration.\n *\n * Greenfield use:\n *\n * ```ts\n * export const generateMetadata = generateBlogPageMetadata();\n * ```\n *\n * Multilingual sites can pass `alternates` to get canonical + hreflang\n * `alternates.languages` for every language variant of the post:\n *\n * ```ts\n * export const generateMetadata = generateBlogPageMetadata({\n * alternates: {\n * siteUrl: 'https://example.com',\n * pathPrefix: (lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`),\n * },\n * });\n * ```\n *\n * For custom metadata or coexistence with a legacy blog, skip this and call\n * `generateBlogMetadata(slug, locale)` directly in your own\n * `generateMetadata`.\n */\nexport function generateBlogPageMetadata(\n opts?: BlogMetadataOptions,\n): (args: RouteArgs) => Promise<Metadata> {\n return async ({ params }) => {\n const { slug, locale } = await params;\n try {\n return await generateBlogMetadata(slug, locale, opts);\n } catch (err) {\n console.error(\n 'chaingrow-blog: generateBlogPageMetadata failed:',\n err,\n );\n return {};\n }\n };\n}\n","import type { Block, EmbeddedMedia } from './types';\n\n/**\n * Compute the block-index insertion point for each media item when\n * distributing `mediaCount` attachments across `blockCount` blocks.\n *\n * Formula: media `i` goes at insertion index `floor(i * blockCount / mediaCount)`.\n * An insertion index of `0` means \"before block 0\" (top of body); `blockCount`\n * means \"after the last block\".\n *\n * Examples (blockCount = 10):\n * mediaCount = 1 → [0] (single media at top)\n * mediaCount = 2 → [0, 5] (top + middle)\n * mediaCount = 3 → [0, 3, 6] (top, 1/3, 2/3)\n * mediaCount = 4 → [0, 2, 5, 7] (every quarter)\n *\n * When `blockCount` is 0 every media falls at position 0 and stacks at the\n * top. When two media round to the same position they both land there and\n * render consecutively; this is deterministic but not always visually ideal\n * for very small `blockCount / mediaCount` ratios — assume content has\n * enough blocks to spread across.\n */\nexport function distributeMediaPositions(\n blockCount: number,\n mediaCount: number,\n): number[] {\n if (mediaCount <= 0) return [];\n if (blockCount <= 0) return Array.from({ length: mediaCount }, () => 0);\n return Array.from({ length: mediaCount }, (_, i) =>\n Math.floor((i * blockCount) / mediaCount),\n );\n}\n\n/**\n * Ids of the attachments already placed in the text as `image` blocks\n * (`mediaId`), list items included. `interleaveMedia` skips those, so an\n * image the author placed never shows a second time.\n */\nexport function placedMediaIds(blocks: Block[]): Set<string> {\n const ids = new Set<string>();\n const walk = (list: Block[]) => {\n for (const b of list) {\n if (b.type === 'image' && b.mediaId) ids.add(b.mediaId);\n else if (b.type === 'bulletList' || b.type === 'orderedList') {\n for (const item of b.items) walk(item.content);\n }\n }\n };\n walk(blocks);\n return ids;\n}\n\n/** Whether the text itself contains an image (list items included). */\nexport function hasTextImage(blocks: Block[]): boolean {\n const walk = (list: Block[]): boolean =>\n list.some(\n (b) =>\n b.type === 'image' ||\n ((b.type === 'bulletList' || b.type === 'orderedList') &&\n b.items.some((item) => walk(item.content))),\n );\n return walk(blocks);\n}\n\n/** One interleaved slot in a blog's body — either a block or a media item. */\nexport type InterleavedSlot =\n | { kind: 'block'; block: Block; index: number }\n | { kind: 'media'; media: EmbeddedMedia; index: number };\n\n/**\n * Walk `blocks` once, splicing in `media` at the positions returned by\n * `distributeMediaPositions`. Returns a flat array of typed slots that\n * `<BlogRenderer>` can map over directly. Exposed so consumers who need a\n * custom layout (e.g. wrapping each section in a `<section>`) can use the\n * same placement logic without re-implementing it. Media already placed in\n * the text as an `image` block is skipped (see `placedMediaIds`).\n */\nexport function interleaveMedia(\n blocks: Block[],\n media: EmbeddedMedia[] | undefined,\n): InterleavedSlot[] {\n const placed = placedMediaIds(blocks);\n const mediaList = (media ?? []).filter((m) => !placed.has(m.id));\n const positions = distributeMediaPositions(blocks.length, mediaList.length);\n\n const slots: InterleavedSlot[] = [];\n let mediaCursor = 0;\n for (let b = 0; b <= blocks.length; b++) {\n while (mediaCursor < mediaList.length && positions[mediaCursor] === b) {\n const media = mediaList[mediaCursor]!;\n slots.push({ kind: 'media', media, index: mediaCursor });\n mediaCursor++;\n }\n if (b < blocks.length) {\n const block = blocks[b]!;\n slots.push({ kind: 'block', block, index: b });\n }\n }\n return slots;\n}\n","import * as React from 'react';\nimport type { Block, EmbeddedMedia, ListItem } from '../types';\n\n/**\n * Default components for each Block type. All are headless — no\n * classNames baked in. Consumers customize via:\n * - `classNames={{ heading: \"...\" }}` on `<BlogRenderer>` — threads\n * through to `className` on the Default component below.\n * - `components={{ heading: MyHeading }}` — full replacement.\n */\n\nexport const DefaultHeading = ({\n level,\n children,\n className,\n}: {\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n}) => {\n if (level === 1) return <h1 className={className}>{children}</h1>;\n if (level === 3) return <h3 className={className}>{children}</h3>;\n return <h2 className={className}>{children}</h2>;\n};\n\nexport const DefaultParagraph = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <p className={className}>{children}</p>;\n\nexport const DefaultBulletList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ul className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ul>\n);\n\nexport const DefaultOrderedList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ol className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ol>\n);\n\nexport const DefaultBlockquote = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <blockquote className={className}>{children}</blockquote>;\n\nexport const DefaultCodeBlock = ({\n language,\n code,\n className,\n}: {\n language?: string;\n code: string;\n className?: string;\n}) => (\n <pre className={className}>\n <code className={language ? `language-${language}` : undefined}>{code}</code>\n </pre>\n);\n\nexport const DefaultImage = ({\n src,\n alt,\n caption,\n width,\n height,\n className,\n}: {\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n}) => {\n const img = (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={src} alt={alt} width={width} height={height} />\n );\n if (caption) {\n return (\n <figure className={className}>\n {img}\n <figcaption>{caption}</figcaption>\n </figure>\n );\n }\n return <span className={className}>{img}</span>;\n};\n\nexport const DefaultDivider = ({ className }: { className?: string }) => (\n <hr className={className} />\n);\n\n/**\n * Default renderer for a single embedded media item. Bare HTML — no\n * classNames, no styles. Override via `components.media` on `<BlogRenderer>`.\n * `alt` falls back to the generation prompt, which is a sensible description\n * string since it was the LLM input that produced the image.\n */\nexport const DefaultMedia = ({ media }: { media: EmbeddedMedia }) => {\n if (media.media_type === 'VIDEO') {\n return (\n <video\n src={media.media_url}\n controls\n playsInline\n preload=\"metadata\"\n />\n );\n }\n // eslint-disable-next-line @next/next/no-img-element\n return <img src={media.media_url} alt={media.prompt} />;\n};\n","import * as React from 'react';\nimport type {\n Blog,\n Block,\n BlockType,\n EmbeddedMedia,\n Inline,\n ListItem,\n Mark,\n} from '../types';\nimport { interleaveMedia } from '../media-placement';\nimport {\n DefaultHeading,\n DefaultParagraph,\n DefaultBulletList,\n DefaultOrderedList,\n DefaultBlockquote,\n DefaultCodeBlock,\n DefaultImage,\n DefaultDivider,\n DefaultMedia,\n} from './defaults';\n\n/**\n * Override map for per-block components. Keys are the discriminants\n * of `Block`. Consumers pass any subset; unspecified blocks render\n * via the matching `Default*` component.\n *\n * v0.3.0 breaking change: the keys changed to match the new Block\n * union. If you upgraded from 0.2.x, rename `list` → `bulletList` /\n * `orderedList`, `quote` → `blockquote`, `code` → `codeBlock`, drop\n * `image_placeholder` (images are now first-class via `image`).\n */\nexport type BlockComponents = Partial<{\n heading: React.ComponentType<{\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n }>;\n paragraph: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n bulletList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n orderedList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n blockquote: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n codeBlock: React.ComponentType<{\n language?: string;\n code: string;\n className?: string;\n }>;\n image: React.ComponentType<{\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n }>;\n divider: React.ComponentType<{ className?: string }>;\n /** Renders a single attached image/video when `<BlogRenderer>` distributes\n * `blog.generatedMedia` across the body. Default renders a bare\n * `<img>` or `<video controls>`. */\n media: React.ComponentType<{ media: EmbeddedMedia }>;\n wrapper: React.ComponentType<{ children: React.ReactNode; blog: Blog }>;\n}>;\n\n/** Simple Tailwind-style className override map. Applied to the\n * default component for each block type. `components` override takes\n * precedence — if you replace the component, className won't reach it\n * unless your override handles it. */\nexport type BlockClassNames = Partial<Record<BlockType, string>>;\n\nconst MARK_ORDER: Mark['type'][] = [\n 'bold',\n 'italic',\n 'strike',\n 'code',\n 'link',\n];\n\n/** Render an array of Inline runs into text+mark JSX. */\nfunction InlineRenderer({ inline }: { inline: Inline[] }) {\n return (\n <>\n {inline.map((run, i) => (\n <InlineRun key={i} run={run} />\n ))}\n </>\n );\n}\n\nfunction InlineRun({ run }: { run: Inline }) {\n const marks = run.marks\n ? [...run.marks].sort(\n (a, b) => MARK_ORDER.indexOf(b.type) - MARK_ORDER.indexOf(a.type),\n )\n : [];\n let node: React.ReactNode = run.text;\n for (const mark of marks) {\n switch (mark.type) {\n case 'bold':\n node = <strong>{node}</strong>;\n break;\n case 'italic':\n node = <em>{node}</em>;\n break;\n case 'strike':\n node = <s>{node}</s>;\n break;\n case 'code':\n node = <code>{node}</code>;\n break;\n case 'link':\n node = (\n <a href={mark.href} rel=\"noopener noreferrer\">\n {node}\n </a>\n );\n break;\n }\n }\n return <>{node}</>;\n}\n\nexport function BlockRenderer({\n block,\n components = {},\n classNames = {},\n renderBlock,\n}: {\n block: Block;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n renderBlock?: (b: Block, i: number) => React.ReactNode;\n}) {\n // Parent supplies a renderBlock callback so list items can recurse\n // into the same switch. When not provided (top-level call), wire it\n // to a local closure.\n const recurse: (b: Block, i: number) => React.ReactNode =\n renderBlock ??\n ((b, i) => (\n <BlockRenderer\n key={i}\n block={b}\n components={components}\n classNames={classNames}\n renderBlock={recurse}\n />\n ));\n\n switch (block.type) {\n case 'heading': {\n const Heading = components.heading ?? DefaultHeading;\n return (\n <Heading level={block.level} className={classNames.heading}>\n <InlineRenderer inline={block.content} />\n </Heading>\n );\n }\n case 'paragraph': {\n const Paragraph = components.paragraph ?? DefaultParagraph;\n return (\n <Paragraph className={classNames.paragraph}>\n <InlineRenderer inline={block.content} />\n </Paragraph>\n );\n }\n case 'bulletList': {\n const BulletList = components.bulletList ?? DefaultBulletList;\n return (\n <BulletList\n items={block.items}\n className={classNames.bulletList}\n renderBlock={recurse}\n />\n );\n }\n case 'orderedList': {\n const OrderedList = components.orderedList ?? DefaultOrderedList;\n return (\n <OrderedList\n items={block.items}\n className={classNames.orderedList}\n renderBlock={recurse}\n />\n );\n }\n case 'blockquote': {\n const Blockquote = components.blockquote ?? DefaultBlockquote;\n return (\n <Blockquote className={classNames.blockquote}>\n <InlineRenderer inline={block.content} />\n </Blockquote>\n );\n }\n case 'codeBlock': {\n const CodeBlock = components.codeBlock ?? DefaultCodeBlock;\n return (\n <CodeBlock\n language={block.language}\n code={block.code}\n className={classNames.codeBlock}\n />\n );\n }\n case 'image': {\n const ImageComp = components.image ?? DefaultImage;\n return (\n <ImageComp\n src={block.src}\n alt={block.alt}\n caption={block.caption}\n width={block.width}\n height={block.height}\n mediaId={block.mediaId}\n className={classNames.image}\n />\n );\n }\n case 'divider': {\n const Divider = components.divider ?? DefaultDivider;\n return <Divider className={classNames.divider} />;\n }\n }\n}\n\nexport function BlogRenderer({\n blog,\n components = {},\n classNames = {},\n}: {\n blog: Blog;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n}) {\n const Media = components.media ?? DefaultMedia;\n const Wrapper = components.wrapper;\n\n // Distribute `blog.generatedMedia` across `blog.blocks` at positions\n // floor(i * N / K) — first media at top of body, the rest spread evenly.\n // Images placed in the text render as their own `image` blocks instead.\n const slots = interleaveMedia(blog.blocks, blog.generatedMedia);\n\n const content = (\n <>\n {slots.map((slot) =>\n slot.kind === 'block' ? (\n <BlockRenderer\n key={`b:${slot.index}`}\n block={slot.block}\n components={components}\n classNames={classNames}\n />\n ) : (\n <Media key={`m:${slot.media.id}`} media={slot.media} />\n ),\n )}\n </>\n );\n\n if (Wrapper) return <Wrapper blog={blog}>{content}</Wrapper>;\n return content;\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport { hasTextImage } from '../media-placement';\nimport { BlogRenderer, type BlockComponents } from './BlogRenderer';\n\nexport type BlogDetailComponents = {\n /**\n * Renders above the body. Default: og_image → h1 → time → description.\n * Override to add tags, author chip, share buttons, read time, etc.\n */\n Header?: (props: {\n blog: Blog;\n formatDate: (iso: string) => string;\n }) => React.ReactNode;\n /** Renders below the body. Default: nothing. */\n Footer?: (props: { blog: Blog }) => React.ReactNode;\n /** Forwarded to <BlogRenderer components={...} />. */\n blocks?: BlockComponents;\n};\n\nconst defaultFormatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString();\n\nfunction DefaultHeader({\n blog,\n formatDate,\n}: {\n blog: Blog;\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n // Bare semantic HTML — no classNames, no styles. Consumers wrap the whole\n // BlogDetail in their own `.prose` / typography container to handle styling.\n //\n // When the body shows any image — attachments spread by <BlogRenderer>\n // (the first lands at the top of the body, right under this header) or\n // images placed in the text — `og_image` is one of them, so rendering it\n // here too would show it twice. It then stays a meta-only field (still\n // used by OpenGraph / Twitter card previews via `generateBlogMetadata`).\n const hasInlineMedia =\n (blog.generatedMedia?.length ?? 0) > 0 || hasTextImage(blog.blocks);\n // eslint-disable-next-line @next/next/no-img-element\n const heroImage =\n !hasInlineMedia && blog.og_image ? (\n <img src={blog.og_image} alt={blog.title} />\n ) : null;\n return (\n <>\n {heroImage}\n <h1>{blog.title}</h1>\n {blog.published_at && (\n <time dateTime={blog.published_at}>\n {formatDate(blog.published_at)}\n </time>\n )}\n {blog.description && <p>{blog.description}</p>}\n </>\n );\n}\n\nexport function BlogDetail({\n blog,\n components,\n formatDate = defaultFormatDate,\n}: {\n blog: Blog;\n components?: BlogDetailComponents;\n formatDate?: (iso: string) => string;\n}): React.JSX.Element {\n const Header = components?.Header ?? DefaultHeader;\n const Footer = components?.Footer;\n return (\n <article>\n <Header blog={blog} formatDate={formatDate} />\n <BlogRenderer blog={blog} components={components?.blocks} />\n {Footer ? <Footer blog={blog} /> : null}\n </article>\n );\n}\n","import type { Blog, FaqItem } from './types';\n\n/**\n * Consumer-supplied extras for BlogPosting. Everything is optional — the\n * SDK doesn't know the customer's base URL, organisation name, or author\n * identity, so it just omits what it isn't told. Schema.org treats\n * `author`, `publisher`, and `url` as recommended but not required.\n */\nexport type BlogJsonLdOptions = {\n /** Absolute URL of the blog page (e.g. `https://example.com/blog/my-post`). */\n url?: string;\n author?: { name: string; type?: 'Person' | 'Organization'; url?: string };\n publisher?: { name: string; logo?: string };\n};\n\nconst Schema = Object.freeze({ context: 'https://schema.org' });\n\n/**\n * Build a Schema.org `BlogPosting` JSON-LD object from a chaingrow Blog.\n * Fields that aren't present on the Blog (description, og_image, …) are\n * omitted entirely so the resulting JSON has no `undefined` junk.\n */\nexport function buildBlogPostingSchema(\n blog: Blog,\n opts: BlogJsonLdOptions = {},\n): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@type': 'BlogPosting',\n headline: blog.title,\n datePublished: blog.published_at,\n };\n if (blog.description) schema.description = blog.description;\n if (blog.og_image) schema.image = blog.og_image;\n if (opts.url) {\n schema.url = opts.url;\n schema.mainEntityOfPage = opts.url;\n }\n if (opts.author) {\n schema.author = {\n '@type': opts.author.type ?? 'Organization',\n name: opts.author.name,\n ...(opts.author.url ? { url: opts.author.url } : {}),\n };\n }\n if (opts.publisher) {\n schema.publisher = {\n '@type': 'Organization',\n name: opts.publisher.name,\n ...(opts.publisher.logo\n ? { logo: { '@type': 'ImageObject', url: opts.publisher.logo } }\n : {}),\n };\n }\n return schema;\n}\n\n/**\n * Build a Schema.org `FAQPage` JSON-LD object from chaingrow's `{question, answer}[]`\n * pairs. Returns `null` for empty input so callers can skip it with a single\n * `if (faqSchema) items.push(faqSchema)` / `filter(Boolean)`.\n */\nexport function buildFAQPageSchema(\n faq: FaqItem[] | null | undefined,\n): Record<string, unknown> | null {\n if (!faq || faq.length === 0) return null;\n return {\n '@type': 'FAQPage',\n mainEntity: faq.map(({ question, answer }) => ({\n '@type': 'Question',\n name: question,\n acceptedAnswer: { '@type': 'Answer', text: answer },\n })),\n };\n}\n\n/**\n * Combines BlogPosting + FAQPage + any LLM-generated `structured_data.jsonLd`\n * extras into a flat array ready for `@graph`. Tolerant of missing/malformed\n * `structured_data` — if the backend ever sends something unexpected we just\n * skip it rather than crashing the consumer's page.\n */\nexport function buildBlogJsonLd(\n blog: Blog,\n opts: BlogJsonLdOptions = {},\n): Record<string, unknown>[] {\n const items: Record<string, unknown>[] = [buildBlogPostingSchema(blog, opts)];\n const faq = buildFAQPageSchema(blog.faq);\n if (faq) items.push(faq);\n items.push(...extractLlmJsonLd(blog.structured_data));\n return items;\n}\n\n/** Schema-org context string — exposed so `<JsonLdGraph>` and any\n * bring-your-own renderer use the same value. */\nexport const SCHEMA_ORG_CONTEXT = Schema.context;\n\n/**\n * Pull the JSON-LD array out of `blog.structured_data`. Per\n * `structuredDataOutputSchema` in @chaingrow/contracts the expected shape is\n * `{ jsonLd: Array<Record<string, unknown>> }`, but historical rows may be\n * `null`, a plain object, or even malformed — normalise to an empty array in\n * every degenerate case so the caller gets a safe spread target.\n */\nfunction extractLlmJsonLd(\n sd: Blog['structured_data'],\n): Record<string, unknown>[] {\n if (!sd || typeof sd !== 'object') return [];\n const ld = (sd as { jsonLd?: unknown }).jsonLd;\n if (!Array.isArray(ld)) return [];\n return ld.filter(\n (entry): entry is Record<string, unknown> =>\n typeof entry === 'object' && entry !== null && !Array.isArray(entry),\n );\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport {\n buildBlogJsonLd,\n SCHEMA_ORG_CONTEXT,\n type BlogJsonLdOptions,\n} from '../json-ld';\n\n/**\n * Render an array of Schema.org graph items as a single\n * `<script type=\"application/ld+json\">` tag with a merged `@graph`.\n * Google treats one `@graph` script as equivalent to N separate scripts, and\n * a single tag keeps the DOM uncluttered for consumers who already ship their\n * own schemas (Organization, breadcrumbs, WebSite, …).\n */\nexport function JsonLdGraph({\n items,\n}: {\n items: Record<string, unknown>[];\n}): React.JSX.Element | null {\n if (!items || items.length === 0) return null;\n const payload = JSON.stringify({\n '@context': SCHEMA_ORG_CONTEXT,\n '@graph': items,\n });\n return (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: payload }}\n />\n );\n}\n\n/**\n * Default-batteries JSON-LD for a chaingrow Blog: emits BlogPosting +\n * FAQPage (if the post has FAQs) + any LLM-generated extras from\n * `structured_data.jsonLd`. Consumers who already ship their own graph items\n * (breadcrumbs, Organization, …) pass them via `extraSchemas` and they end\n * up in the same `@graph`.\n */\nexport function BlogJsonLd({\n blog,\n extraSchemas,\n ...opts\n}: {\n blog: Blog;\n extraSchemas?: Record<string, unknown>[];\n} & BlogJsonLdOptions): React.JSX.Element | null {\n const items = [...(extraSchemas ?? []), ...buildBlogJsonLd(blog, opts)];\n return <JsonLdGraph items={items} />;\n}\n"],"mappings":"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","names":["media","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","trimTrailingSlash","normalizePathPrefix"]}
|
|
1
|
+
{"version":3,"sources":["../../src/next/index.ts","../../src/client.ts","../../src/next/metadata.ts","../../src/next/BlogPage.tsx","../../src/media-placement.ts","../../src/react/defaults.tsx","../../src/react/BlogRenderer.tsx","../../src/react/BlogDetail.tsx","../../src/json-ld.ts","../../src/react/BlogJsonLd.tsx"],"sourcesContent":["import type { MetadataRoute } from 'next';\nimport type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst DEFAULT_API_URL = 'https://api.chaingrow.de';\n\n/**\n * Default ISR window for `getBlog` / `listBlogs` when the consumer doesn't\n * set `revalidate` explicitly. 7 days — blog posts are effectively immutable\n * once published, so caching for a week is sensible, and the webhook\n * (`chaingrow-blog/next/webhook`) invalidates the cache immediately on\n * publish anyway so this window is really just a safety net for when the\n * webhook isn't mounted or misses a delivery.\n *\n * Override per-call with `{ revalidate: N }` (seconds), `{ revalidate: 0 }`\n * (never cache — every request hits ChainGrow), or `{ revalidate: false }`\n * (cache forever, only the webhook can invalidate).\n */\nconst DEFAULT_REVALIDATE_SECONDS = 60 * 60 * 24 * 7;\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl =\n opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)',\n );\n }\n return { apiUrl, apiKey };\n};\n\nconst buildFetch = (tag: string, revalidate: number | false | undefined) => {\n const effectiveRevalidate: number | false =\n revalidate === undefined ? DEFAULT_REVALIDATE_SECONDS : revalidate;\n return ((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, {\n ...init,\n next: {\n tags: ['chaingrow:blog', tag],\n revalidate: effectiveRevalidate,\n },\n } as RequestInit)) as typeof fetch;\n};\n\nexport async function getBlog(\n slug: string,\n language?: string,\n opts: NextClientOptions = {},\n): Promise<Blog | null> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch(`chaingrow:blog:${slug}`, opts.revalidate),\n });\n return client.getBlog(slug, language);\n}\n\nexport async function listBlogs(\n listOpts: { status?: 'DRAFT' | 'PUBLISHED'; language?: string } = {},\n opts: NextClientOptions = {},\n): Promise<Blog[]> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch('chaingrow:blog:list', opts.revalidate),\n });\n return client.listBlogs(listOpts);\n}\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Returns ChainGrow blog entries already shaped for Next.js's\n * `app/sitemap.ts` (`MetadataRoute.Sitemap`). Spread the result into your\n * sitemap array — no mapping required at the call site.\n *\n * ```ts\n * // app/sitemap.ts\n * import type { MetadataRoute } from 'next';\n * import { getBlogSitemapEntries } from 'chaingrow-blog/next';\n *\n * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n * const blogs = await getBlogSitemapEntries({\n * siteUrl: 'https://example.com',\n * pathPrefix: '/blog',\n * });\n * return [\n * { url: 'https://example.com', lastModified: new Date() },\n * ...blogs,\n * ];\n * }\n * ```\n *\n * For news sitemaps, image sitemaps, or any case that needs the full blog\n * shape (title, language, attached media), call `listBlogs()` directly.\n */\nexport async function getBlogSitemapEntries(opts: {\n siteUrl: string;\n pathPrefix?: string;\n language?: string;\n} & NextClientOptions): Promise<MetadataRoute.Sitemap> {\n const baseUrl = trimTrailingSlash(opts.siteUrl);\n const pathPrefix = normalizePathPrefix(opts.pathPrefix ?? '/blog');\n const blogs = await listBlogs(\n { status: 'PUBLISHED', language: opts.language },\n {\n apiKey: opts.apiKey,\n apiUrl: opts.apiUrl,\n revalidate: opts.revalidate,\n },\n );\n return blogs.map((blog) => ({\n url: `${baseUrl}${pathPrefix}/${blog.slug}`,\n lastModified: new Date(blog.published_at),\n }));\n}\n\nexport type { Blog } from '../types';\nexport {\n generateBlogMetadata,\n generateBlogIndexMetadata,\n type BlogAlternatesOptions,\n type BlogMetadataOptions,\n type BlogIndexMetadataOptions,\n} from './metadata';\nexport { BlogPage, generateBlogPageMetadata } from './BlogPage';\nexport type { BlogPageRouteParams } from './BlogPage';\n","import type { Blog } from './types';\n\nexport type ClientOptions = {\n apiUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n};\n\nexport type ListBlogsOptions = {\n status?: 'DRAFT' | 'PUBLISHED';\n language?: string;\n};\n\nexport type ChaingrowClient = {\n getBlog(slug: string, language?: string): Promise<Blog | null>;\n getBlogById(id: string): Promise<Blog | null>;\n listBlogs(opts?: ListBlogsOptions): Promise<Blog[]>;\n};\n\n// Error codes that indicate a transient network hiccup rather than a broken\n// setup: flaky DNS resolution (musl containers throw EAI_AGAIN under load),\n// dropped or timed-out connections. Anything else (bad host, TLS failure)\n// fails fast so misconfiguration stays visible.\nconst TRANSIENT_NETWORK_CODES = new Set([\n 'EAI_AGAIN',\n 'ETIMEDOUT',\n 'ECONNRESET',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n]);\n\nconst NETWORK_ATTEMPTS = 3;\n\nfunction causeCode(err: unknown): string | undefined {\n const cause = err instanceof Error ? err.cause : undefined;\n return cause && typeof cause === 'object' && 'code' in cause\n ? String((cause as { code?: unknown }).code)\n : undefined;\n}\n\nexport function createClient(opts: ClientOptions): ChaingrowClient {\n const baseUrl = opts.apiUrl.replace(/\\/$/, '');\n const doFetch = opts.fetch ?? fetch;\n\n const request = async <T>(\n path: string,\n init?: RequestInit,\n ): Promise<T | null> => {\n const url = `${baseUrl}${path}`;\n const method = init?.method ?? 'GET';\n const buildInit = (): RequestInit => ({\n ...init,\n headers: {\n 'X-API-Key': opts.apiKey,\n Accept: 'application/json',\n ...(init?.headers ?? {}),\n },\n });\n\n let res: Response | undefined;\n for (let attempt = 1; attempt <= NETWORK_ATTEMPTS; attempt++) {\n try {\n res = await doFetch(url, buildInit());\n\n // Transparent one-shot retry on 429, honoring Retry-After (seconds).\n if (res.status === 429) {\n const waitSec = parseInt(res.headers.get('Retry-After') ?? '1', 10);\n await new Promise((resolve) =>\n setTimeout(resolve, Math.max(0, waitSec) * 1000),\n );\n res = await doFetch(url, buildInit());\n }\n break;\n } catch (err) {\n const code = causeCode(err);\n if (\n attempt < NETWORK_ATTEMPTS &&\n code !== undefined &&\n TRANSIENT_NETWORK_CODES.has(code)\n ) {\n await new Promise((resolve) => setTimeout(resolve, 250 * attempt));\n continue;\n }\n\n // `fetch failed` / DNS / connection errors land here. Node's default\n // message buries the host in `err.cause`, which makes integration\n // debugging miserable. Surface the URL and the override env var on\n // the top-level message so it appears in the consumer's server log.\n const detail = code\n ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ''}`\n : err instanceof Error\n ? err.message\n : String(err);\n const e = new Error(\n `chaingrow-blog: could not reach ${method} ${url} — ${detail}. ` +\n `Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`,\n );\n (e as Error & { cause?: unknown }).cause = err;\n throw e;\n }\n }\n if (!res) {\n throw new Error(`chaingrow-blog: ${method} ${url} — no response`);\n }\n\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `chaingrow-blog: ${method} ${url} failed (${res.status})`,\n );\n }\n return (await res.json()) as T;\n };\n\n return {\n async getBlog(slug, language) {\n const qs = language ? `?language=${encodeURIComponent(language)}` : '';\n return request<Blog>(\n `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`,\n );\n },\n\n async getBlogById(id) {\n return request<Blog>(`/api/v1/blogs/${encodeURIComponent(id)}`);\n },\n\n async listBlogs(listOpts = {}) {\n const collected: Blog[] = [];\n // Page through until the server-reported `total` is reached. Backends\n // without pagination return everything on the first request\n // (total === data.length), so this loop runs exactly once there.\n const MAX_PAGES = 100;\n for (let page = 0; page < MAX_PAGES; page++) {\n const params = new URLSearchParams();\n if (listOpts.status) params.set('status', listOpts.status);\n if (listOpts.language) params.set('language', listOpts.language);\n if (collected.length > 0)\n params.set('offset', String(collected.length));\n const qs = params.toString();\n const response = await request<{ data: Blog[]; total: number }>(\n `/api/v1/blogs${qs ? `?${qs}` : ''}`,\n );\n if (!response) break;\n const batch = response.data ?? [];\n collected.push(...batch);\n const total =\n typeof response.total === 'number' ? response.total : collected.length;\n if (batch.length === 0 || collected.length >= total) break;\n }\n // The language filter also runs server-side (API builds that predate\n // it ignore the query param) — keep this client-side pass as a\n // fallback; against a filtering server it's a no-op.\n if (listOpts.language) {\n return collected.filter((b) => b.language === listOpts.language);\n }\n return collected;\n },\n };\n}\n","import type { Metadata } from 'next';\nimport { getBlog, listBlogs } from './index';\nimport type { NextClientOptions } from './index';\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Opt-in hreflang configuration for `generateBlogMetadata`. ChainGrow serves\n * language variants of a post under one shared slug, so the SDK can emit\n * `alternates.languages` (and a canonical) for every variant it finds —\n * pass this when your site serves more than one language.\n */\nexport type BlogAlternatesOptions = {\n /** Absolute site origin, e.g. `https://example.com` (no trailing slash needed). */\n siteUrl: string;\n /**\n * Blog path prefix. A static string applies to every language; pass a\n * function for locale-prefixed routing, e.g.\n * `(lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`)`.\n * Defaults to `/blog`.\n */\n pathPrefix?: string | ((language: string) => string);\n /** Language whose URL is additionally emitted as `x-default`. */\n xDefaultLanguage?: string;\n};\n\nexport type BlogMetadataOptions = NextClientOptions & {\n alternates?: BlogAlternatesOptions;\n};\n\nconst blogUrlFor = (\n alternates: BlogAlternatesOptions,\n language: string,\n slug: string,\n): string => {\n const base = trimTrailingSlash(alternates.siteUrl);\n const rawPrefix =\n typeof alternates.pathPrefix === 'function'\n ? alternates.pathPrefix(language)\n : (alternates.pathPrefix ?? '/blog');\n return `${base}${normalizePathPrefix(rawPrefix)}/${slug}`;\n};\n\n/**\n * Next.js `Metadata` factory for a chaingrow blog. Wire it up like:\n *\n * ```ts\n * export async function generateMetadata({ params }) {\n * const { slug } = await params;\n * return generateBlogMetadata(slug);\n * }\n * ```\n *\n * Reuses `getBlog()` which is ISR-tagged, so `generateMetadata` + the page\n * render share one backend hit.\n *\n * Pass `opts.alternates` to also emit `alternates.canonical` and hreflang\n * `alternates.languages` built from the post's language variants (ChainGrow\n * publishes translations under the same slug):\n *\n * ```ts\n * return generateBlogMetadata(slug, locale, {\n * alternates: {\n * siteUrl: 'https://example.com',\n * pathPrefix: (lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`),\n * xDefaultLanguage: 'de',\n * },\n * });\n * ```\n *\n * The variant lookup reuses the ISR-cached `listBlogs()` response (same\n * cache entry as the blog index and sitemaps), so it does not add a backend\n * hit per page view.\n *\n * Returns `{}` on 404 — the page's own `notFound()` owns the HTTP status;\n * this helper just declines to emit half-populated metadata for a missing\n * post.\n *\n * Fields that aren't present on the Blog are omitted (not set to\n * `undefined`) so spread-overrides like\n * `{ ...await generateBlogMetadata(slug), alternates: { canonical } }` do\n * what you expect.\n */\nexport async function generateBlogMetadata(\n slug: string,\n language?: string,\n opts?: BlogMetadataOptions,\n): Promise<Metadata> {\n const blog = await getBlog(slug, language, opts);\n if (!blog) return {};\n\n const images = blog.og_image ? [blog.og_image] : undefined;\n const description = blog.description ?? undefined;\n\n const meta: Metadata = {\n title: blog.title,\n ...(description ? { description } : {}),\n openGraph: {\n type: 'article',\n title: blog.title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n ...(blog.published_at ? { publishedTime: blog.published_at } : {}),\n },\n twitter: {\n card: blog.og_image ? 'summary_large_image' : 'summary',\n title: blog.title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n },\n };\n\n if (opts?.alternates) {\n const alternates = opts.alternates;\n const canonical = blogUrlFor(alternates, blog.language, blog.slug);\n let languages: Record<string, string> | undefined;\n try {\n const all = await listBlogs({ status: 'PUBLISHED' }, opts);\n const variants = all.filter((b) => b.slug === blog.slug && b.language);\n if (variants.length > 1) {\n languages = {};\n for (const variant of variants) {\n languages[variant.language] = blogUrlFor(\n alternates,\n variant.language,\n variant.slug,\n );\n }\n const xDefaultUrl = alternates.xDefaultLanguage\n ? languages[alternates.xDefaultLanguage]\n : undefined;\n if (xDefaultUrl) languages['x-default'] = xDefaultUrl;\n }\n } catch (err) {\n // hreflang is an enhancement — never fail metadata generation over it.\n console.error(\n 'chaingrow-blog: hreflang variant lookup failed, emitting canonical only:',\n err,\n );\n }\n meta.alternates = {\n canonical,\n ...(languages ? { languages } : {}),\n };\n }\n\n return meta;\n}\n\nexport type BlogIndexMetadataOptions = {\n /** Page title, e.g. `'Blog | Acme'`. */\n title: string;\n description?: string;\n /** Absolute canonical URL of the index page, e.g. `https://example.com/blog`. */\n canonical?: string;\n /** OG/Twitter preview image URL. */\n ogImage?: string;\n};\n\n/**\n * Next.js `Metadata` for a blog *index* page (`app/blog/page.tsx`). The blog\n * detail page gets its metadata from the post itself, but the index has no\n * single post to draw from — without this, greenfield apps fall back to the\n * site-default title on `/blog`. Synchronous (no API call):\n *\n * ```ts\n * export const metadata = generateBlogIndexMetadata({\n * title: 'Blog | Acme',\n * description: 'Product updates and guides from Acme.',\n * canonical: 'https://example.com/blog',\n * });\n * ```\n */\nexport function generateBlogIndexMetadata(\n opts: BlogIndexMetadataOptions,\n): Metadata {\n const { title, description, canonical, ogImage } = opts;\n const images = ogImage ? [ogImage] : undefined;\n return {\n title,\n ...(description ? { description } : {}),\n ...(canonical ? { alternates: { canonical } } : {}),\n openGraph: {\n type: 'website',\n title,\n ...(description ? { description } : {}),\n ...(canonical ? { url: canonical } : {}),\n ...(images ? { images } : {}),\n },\n twitter: {\n card: ogImage ? 'summary_large_image' : 'summary',\n title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n },\n };\n}\n","import * as React from 'react';\nimport type { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport type { Blog } from '../types';\nimport { BlogDetail } from '../react/BlogDetail';\nimport { BlogJsonLd } from '../react/BlogJsonLd';\nimport { getBlog } from './index';\nimport { generateBlogMetadata, type BlogMetadataOptions } from './metadata';\n\nexport type BlogPageRouteParams = { slug: string; locale?: string };\n\ntype RouteArgs = {\n params: Promise<BlogPageRouteParams>;\n};\n\n/**\n * Default-batteries `app/blog/[slug]/page.tsx` component. Fetches the blog,\n * renders JSON-LD + the detail layout, 404s if missing. Zero configuration\n * and zero CSS framework dependency — typography styles are emitted inline\n * under the scoped `cg-blog-article` class so a fresh app looks good without\n * Tailwind, prose, or anything else installed.\n *\n * If you want a custom layout, breadcrumbs, or to coexist with an existing\n * blog, skip this component and compose `getBlog()` + `<BlogJsonLd>` +\n * `<BlogDetail>` directly in your page file.\n */\nexport async function BlogPage({\n params,\n}: RouteArgs): Promise<React.JSX.Element> {\n const { slug, locale } = await params;\n let blog: Blog | null = null;\n try {\n blog = await getBlog(slug, locale);\n } catch (err) {\n console.error('chaingrow-blog: <BlogPage> getBlog failed:', err);\n }\n if (!blog) notFound();\n\n return (\n <div className=\"cg-blog-article\">\n {/* eslint-disable-next-line react/no-danger */}\n <style dangerouslySetInnerHTML={{ __html: ARTICLE_CSS }} />\n <BlogJsonLd blog={blog} />\n <BlogDetail blog={blog} />\n </div>\n );\n}\n\n// Color strategy: every text element uses \\`color: inherit\\` so the host\n// app's text color cascades down. Muted variants (date, hr) use\n// \\`currentColor\\` with opacity. Backgrounds for placeholders use a\n// translucent neutral that works against any host background. No\n// \\`prefers-color-scheme\\` rules — the host's theme system (Tailwind\n// \\`dark:\\`, \\`data-theme\\`, OS preference, whatever) drives the SDK\n// without it having to know how the host implements the toggle.\nconst ARTICLE_CSS = `\n.cg-blog-article {\n max-width: 720px;\n margin: 0 auto;\n padding: 4rem 1.5rem 6rem;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n font-size: 1.0625rem;\n line-height: 1.7;\n color: inherit;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.cg-blog-article h1 {\n font-size: clamp(2rem, 4vw, 2.625rem);\n font-weight: 700;\n line-height: 1.15;\n letter-spacing: -0.022em;\n color: inherit;\n margin: 0 0 1rem;\n}\n.cg-blog-article time {\n display: block;\n font-size: 0.8125rem;\n color: currentColor;\n opacity: 0.55;\n margin: 0 0 2rem;\n font-weight: 400;\n}\n.cg-blog-article h2 {\n font-size: 1.625rem;\n font-weight: 700;\n line-height: 1.25;\n letter-spacing: -0.015em;\n color: inherit;\n margin: 3rem 0 1rem;\n}\n.cg-blog-article h3 {\n font-size: 1.25rem;\n font-weight: 700;\n line-height: 1.3;\n color: inherit;\n margin: 2.25rem 0 0.75rem;\n}\n.cg-blog-article h4 {\n font-size: 1.0625rem;\n font-weight: 700;\n color: inherit;\n margin: 1.75rem 0 0.5rem;\n}\n.cg-blog-article p {\n margin: 0 0 1.25rem;\n color: inherit;\n}\n.cg-blog-article a {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 3px;\n text-decoration-thickness: 1px;\n transition: opacity 150ms ease;\n}\n.cg-blog-article a:hover { opacity: 0.7; }\n.cg-blog-article strong { color: inherit; font-weight: 700; }\n.cg-blog-article em { font-style: italic; }\n.cg-blog-article ul,\n.cg-blog-article ol {\n margin: 0 0 1.5rem;\n padding-left: 1.5rem;\n color: inherit;\n}\n.cg-blog-article li {\n margin-bottom: 0.5rem;\n line-height: 1.65;\n}\n.cg-blog-article li > p { margin: 0 0 0.5rem; }\n.cg-blog-article li:last-child > p:last-child { margin-bottom: 0; }\n.cg-blog-article blockquote {\n margin: 2rem 0;\n padding: 0.5rem 0 0.5rem 1.5rem;\n border-left: 3px solid currentColor;\n font-style: italic;\n color: inherit;\n font-size: 1.125rem;\n line-height: 1.6;\n}\n.cg-blog-article blockquote p:last-child { margin-bottom: 0; }\n.cg-blog-article img {\n display: block;\n width: 100%;\n height: auto;\n margin: 2.25rem 0;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.08);\n}\n.cg-blog-article video {\n display: block;\n width: 100%;\n height: auto;\n margin: 2.25rem 0;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.15);\n}\n.cg-blog-article hr {\n border: 0;\n height: 1px;\n background: currentColor;\n opacity: 0.15;\n margin: 3rem 0;\n}\n.cg-blog-article code {\n background: rgba(128, 128, 128, 0.15);\n color: inherit;\n padding: 0.15em 0.4em;\n border-radius: 3px;\n font-size: 0.9em;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n}\n.cg-blog-article pre {\n background: rgba(128, 128, 128, 0.12);\n color: inherit;\n padding: 1.25rem 1.5rem;\n border-radius: 6px;\n overflow-x: auto;\n font-size: 0.9375rem;\n line-height: 1.6;\n margin: 1.75rem 0;\n}\n.cg-blog-article pre code {\n background: transparent;\n color: inherit;\n padding: 0;\n border-radius: 0;\n font-size: inherit;\n}\n@media (max-width: 640px) {\n .cg-blog-article {\n padding: 2.5rem 1.25rem 4rem;\n font-size: 1rem;\n }\n .cg-blog-article h2 { margin-top: 2.25rem; }\n .cg-blog-article h3 { margin-top: 1.75rem; }\n}\n`;\n\n/**\n * Default-batteries `generateMetadata` for the detail page. Zero configuration.\n *\n * Greenfield use:\n *\n * ```ts\n * export const generateMetadata = generateBlogPageMetadata();\n * ```\n *\n * Multilingual sites can pass `alternates` to get canonical + hreflang\n * `alternates.languages` for every language variant of the post:\n *\n * ```ts\n * export const generateMetadata = generateBlogPageMetadata({\n * alternates: {\n * siteUrl: 'https://example.com',\n * pathPrefix: (lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`),\n * },\n * });\n * ```\n *\n * For custom metadata or coexistence with a legacy blog, skip this and call\n * `generateBlogMetadata(slug, locale)` directly in your own\n * `generateMetadata`.\n */\nexport function generateBlogPageMetadata(\n opts?: BlogMetadataOptions,\n): (args: RouteArgs) => Promise<Metadata> {\n return async ({ params }) => {\n const { slug, locale } = await params;\n try {\n return await generateBlogMetadata(slug, locale, opts);\n } catch (err) {\n console.error(\n 'chaingrow-blog: generateBlogPageMetadata failed:',\n err,\n );\n return {};\n }\n };\n}\n","import type { Block, EmbeddedMedia } from './types';\n\n/**\n * Compute the block-index insertion point for each media item when\n * distributing `mediaCount` attachments across `blockCount` blocks.\n *\n * Formula: media `i` goes at insertion index `floor(i * blockCount / mediaCount)`.\n * An insertion index of `0` means \"before block 0\" (top of body); `blockCount`\n * means \"after the last block\".\n *\n * Examples (blockCount = 10):\n * mediaCount = 1 → [0] (single media at top)\n * mediaCount = 2 → [0, 5] (top + middle)\n * mediaCount = 3 → [0, 3, 6] (top, 1/3, 2/3)\n * mediaCount = 4 → [0, 2, 5, 7] (every quarter)\n *\n * When `blockCount` is 0 every media falls at position 0 and stacks at the\n * top. When two media round to the same position they both land there and\n * render consecutively; this is deterministic but not always visually ideal\n * for very small `blockCount / mediaCount` ratios — assume content has\n * enough blocks to spread across.\n */\nexport function distributeMediaPositions(\n blockCount: number,\n mediaCount: number,\n): number[] {\n if (mediaCount <= 0) return [];\n if (blockCount <= 0) return Array.from({ length: mediaCount }, () => 0);\n return Array.from({ length: mediaCount }, (_, i) =>\n Math.floor((i * blockCount) / mediaCount),\n );\n}\n\n/**\n * Ids of the attachments already placed in the text as `image` blocks\n * (`mediaId`), list items included. `interleaveMedia` skips those, so an\n * image the author placed never shows a second time.\n */\nexport function placedMediaIds(blocks: Block[]): Set<string> {\n const ids = new Set<string>();\n const walk = (list: Block[]) => {\n for (const b of list) {\n if (b.type === 'image' && b.mediaId) ids.add(b.mediaId);\n else if (b.type === 'bulletList' || b.type === 'orderedList') {\n for (const item of b.items) walk(item.content);\n }\n }\n };\n walk(blocks);\n return ids;\n}\n\n/** Whether the text itself contains an image (list items included). */\nexport function hasTextImage(blocks: Block[]): boolean {\n const walk = (list: Block[]): boolean =>\n list.some(\n (b) =>\n b.type === 'image' ||\n ((b.type === 'bulletList' || b.type === 'orderedList') &&\n b.items.some((item) => walk(item.content))),\n );\n return walk(blocks);\n}\n\n/** One interleaved slot in a blog's body — either a block or a media item. */\nexport type InterleavedSlot =\n | { kind: 'block'; block: Block; index: number }\n | { kind: 'media'; media: EmbeddedMedia; index: number };\n\n/**\n * Walk `blocks` once, splicing in `media` at the positions returned by\n * `distributeMediaPositions`. Returns a flat array of typed slots that\n * `<BlogRenderer>` can map over directly. Exposed so consumers who need a\n * custom layout (e.g. wrapping each section in a `<section>`) can use the\n * same placement logic without re-implementing it. Media already placed in\n * the text as an `image` block is skipped (see `placedMediaIds`).\n */\nexport function interleaveMedia(\n blocks: Block[],\n media: EmbeddedMedia[] | undefined,\n): InterleavedSlot[] {\n const placed = placedMediaIds(blocks);\n const mediaList = (media ?? []).filter((m) => !placed.has(m.id));\n const positions = distributeMediaPositions(blocks.length, mediaList.length);\n\n const slots: InterleavedSlot[] = [];\n let mediaCursor = 0;\n for (let b = 0; b <= blocks.length; b++) {\n while (mediaCursor < mediaList.length && positions[mediaCursor] === b) {\n const media = mediaList[mediaCursor]!;\n slots.push({ kind: 'media', media, index: mediaCursor });\n mediaCursor++;\n }\n if (b < blocks.length) {\n const block = blocks[b]!;\n slots.push({ kind: 'block', block, index: b });\n }\n }\n return slots;\n}\n","import * as React from 'react';\nimport type { Block, EmbeddedMedia, ListItem } from '../types';\n\n/**\n * Default components for each Block type. All are headless — no\n * classNames baked in. Consumers customize via:\n * - `classNames={{ heading: \"...\" }}` on `<BlogRenderer>` — threads\n * through to `className` on the Default component below.\n * - `components={{ heading: MyHeading }}` — full replacement.\n */\n\nexport const DefaultHeading = ({\n level,\n children,\n className,\n}: {\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n}) => {\n if (level === 1) return <h1 className={className}>{children}</h1>;\n if (level === 3) return <h3 className={className}>{children}</h3>;\n return <h2 className={className}>{children}</h2>;\n};\n\nexport const DefaultParagraph = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <p className={className}>{children}</p>;\n\nexport const DefaultBulletList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ul className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ul>\n);\n\nexport const DefaultOrderedList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ol className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ol>\n);\n\nexport const DefaultBlockquote = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <blockquote className={className}>{children}</blockquote>;\n\nexport const DefaultCodeBlock = ({\n language,\n code,\n className,\n}: {\n language?: string;\n code: string;\n className?: string;\n}) => (\n <pre className={className}>\n <code className={language ? `language-${language}` : undefined}>{code}</code>\n </pre>\n);\n\nexport const DefaultImage = ({\n src,\n alt,\n caption,\n width,\n height,\n className,\n}: {\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n}) => {\n const img = (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={src} alt={alt} width={width} height={height} />\n );\n if (caption) {\n return (\n <figure className={className}>\n {img}\n <figcaption>{caption}</figcaption>\n </figure>\n );\n }\n return <span className={className}>{img}</span>;\n};\n\nexport const DefaultDivider = ({ className }: { className?: string }) => (\n <hr className={className} />\n);\n\n/**\n * Default renderer for a single embedded media item. Bare HTML — no\n * classNames, no styles. Override via `components.media` on `<BlogRenderer>`.\n * `alt` falls back to the generation prompt, which is a sensible description\n * string since it was the LLM input that produced the image.\n */\nexport const DefaultMedia = ({ media }: { media: EmbeddedMedia }) => {\n if (media.media_type === 'VIDEO') {\n return (\n <video\n src={media.media_url}\n controls\n playsInline\n preload=\"metadata\"\n />\n );\n }\n // eslint-disable-next-line @next/next/no-img-element\n return <img src={media.media_url} alt={media.prompt} />;\n};\n","import * as React from 'react';\nimport type {\n Blog,\n Block,\n BlockType,\n EmbeddedMedia,\n Inline,\n ListItem,\n Mark,\n} from '../types';\nimport { interleaveMedia } from '../media-placement';\nimport {\n DefaultHeading,\n DefaultParagraph,\n DefaultBulletList,\n DefaultOrderedList,\n DefaultBlockquote,\n DefaultCodeBlock,\n DefaultImage,\n DefaultDivider,\n DefaultMedia,\n} from './defaults';\n\n/**\n * Override map for per-block components. Keys are the discriminants\n * of `Block`. Consumers pass any subset; unspecified blocks render\n * via the matching `Default*` component.\n *\n * v0.3.0 breaking change: the keys changed to match the new Block\n * union. If you upgraded from 0.2.x, rename `list` → `bulletList` /\n * `orderedList`, `quote` → `blockquote`, `code` → `codeBlock`, drop\n * `image_placeholder` (images are now first-class via `image`).\n */\nexport type BlockComponents = Partial<{\n heading: React.ComponentType<{\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n }>;\n paragraph: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n bulletList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n orderedList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n blockquote: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n codeBlock: React.ComponentType<{\n language?: string;\n code: string;\n className?: string;\n }>;\n image: React.ComponentType<{\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n }>;\n divider: React.ComponentType<{ className?: string }>;\n /** Renders a single attached image/video when `<BlogRenderer>` distributes\n * `blog.generatedMedia` across the body. Default renders a bare\n * `<img>` or `<video controls>`. */\n media: React.ComponentType<{ media: EmbeddedMedia }>;\n wrapper: React.ComponentType<{ children: React.ReactNode; blog: Blog }>;\n}>;\n\n/** Simple Tailwind-style className override map. Applied to the\n * default component for each block type. `components` override takes\n * precedence — if you replace the component, className won't reach it\n * unless your override handles it. */\nexport type BlockClassNames = Partial<Record<BlockType, string>>;\n\nconst MARK_ORDER: Mark['type'][] = [\n 'bold',\n 'italic',\n 'strike',\n 'code',\n 'link',\n];\n\nconst BREAK = 'br' as const;\n\n/**\n * A line break inside a block arrives as \"\\n\" in a text run and becomes\n * a `<br>`. Breaks at the start or end of the block are dropped and\n * several in a row count as one, so a break never adds an empty line.\n */\nfunction withBreaks(inline: Inline[]): (Inline | typeof BREAK)[] {\n const out: (Inline | typeof BREAK)[] = [];\n for (const run of inline) {\n const parts = (typeof run.text === 'string' ? run.text : '').split('\\n');\n parts.forEach((text, i) => {\n if (i > 0 && out.length > 0 && out[out.length - 1] !== BREAK) {\n out.push(BREAK);\n }\n if (text && !(out[out.length - 1] === BREAK && !text.trim())) {\n out.push({ ...run, text });\n }\n });\n }\n if (out[out.length - 1] === BREAK) out.pop();\n return out;\n}\n\n/** Render an array of Inline runs into text+mark JSX. */\nfunction InlineRenderer({ inline }: { inline: Inline[] }) {\n return (\n <>\n {withBreaks(inline).map((run, i) =>\n run === BREAK ? <br key={i} /> : <InlineRun key={i} run={run} />,\n )}\n </>\n );\n}\n\nfunction InlineRun({ run }: { run: Inline }) {\n const marks = run.marks\n ? [...run.marks].sort(\n (a, b) => MARK_ORDER.indexOf(b.type) - MARK_ORDER.indexOf(a.type),\n )\n : [];\n let node: React.ReactNode = run.text;\n for (const mark of marks) {\n switch (mark.type) {\n case 'bold':\n node = <strong>{node}</strong>;\n break;\n case 'italic':\n node = <em>{node}</em>;\n break;\n case 'strike':\n node = <s>{node}</s>;\n break;\n case 'code':\n node = <code>{node}</code>;\n break;\n case 'link':\n node = (\n <a href={mark.href} rel=\"noopener noreferrer\">\n {node}\n </a>\n );\n break;\n }\n }\n return <>{node}</>;\n}\n\nexport function BlockRenderer({\n block,\n components = {},\n classNames = {},\n renderBlock,\n}: {\n block: Block;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n renderBlock?: (b: Block, i: number) => React.ReactNode;\n}) {\n // Parent supplies a renderBlock callback so list items can recurse\n // into the same switch. When not provided (top-level call), wire it\n // to a local closure.\n const recurse: (b: Block, i: number) => React.ReactNode =\n renderBlock ??\n ((b, i) => (\n <BlockRenderer\n key={i}\n block={b}\n components={components}\n classNames={classNames}\n renderBlock={recurse}\n />\n ));\n\n switch (block.type) {\n case 'heading': {\n const Heading = components.heading ?? DefaultHeading;\n return (\n <Heading level={block.level} className={classNames.heading}>\n <InlineRenderer inline={block.content} />\n </Heading>\n );\n }\n case 'paragraph': {\n const Paragraph = components.paragraph ?? DefaultParagraph;\n return (\n <Paragraph className={classNames.paragraph}>\n <InlineRenderer inline={block.content} />\n </Paragraph>\n );\n }\n case 'bulletList': {\n const BulletList = components.bulletList ?? DefaultBulletList;\n return (\n <BulletList\n items={block.items}\n className={classNames.bulletList}\n renderBlock={recurse}\n />\n );\n }\n case 'orderedList': {\n const OrderedList = components.orderedList ?? DefaultOrderedList;\n return (\n <OrderedList\n items={block.items}\n className={classNames.orderedList}\n renderBlock={recurse}\n />\n );\n }\n case 'blockquote': {\n const Blockquote = components.blockquote ?? DefaultBlockquote;\n return (\n <Blockquote className={classNames.blockquote}>\n <InlineRenderer inline={block.content} />\n </Blockquote>\n );\n }\n case 'codeBlock': {\n const CodeBlock = components.codeBlock ?? DefaultCodeBlock;\n return (\n <CodeBlock\n language={block.language}\n code={block.code}\n className={classNames.codeBlock}\n />\n );\n }\n case 'image': {\n const ImageComp = components.image ?? DefaultImage;\n return (\n <ImageComp\n src={block.src}\n alt={block.alt}\n caption={block.caption}\n width={block.width}\n height={block.height}\n mediaId={block.mediaId}\n className={classNames.image}\n />\n );\n }\n case 'divider': {\n const Divider = components.divider ?? DefaultDivider;\n return <Divider className={classNames.divider} />;\n }\n }\n}\n\nexport function BlogRenderer({\n blog,\n components = {},\n classNames = {},\n}: {\n blog: Blog;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n}) {\n const Media = components.media ?? DefaultMedia;\n const Wrapper = components.wrapper;\n\n // Distribute `blog.generatedMedia` across `blog.blocks` at positions\n // floor(i * N / K) — first media at top of body, the rest spread evenly.\n // Images placed in the text render as their own `image` blocks instead.\n const slots = interleaveMedia(blog.blocks, blog.generatedMedia);\n\n const content = (\n <>\n {slots.map((slot) =>\n slot.kind === 'block' ? (\n <BlockRenderer\n key={`b:${slot.index}`}\n block={slot.block}\n components={components}\n classNames={classNames}\n />\n ) : (\n <Media key={`m:${slot.media.id}`} media={slot.media} />\n ),\n )}\n </>\n );\n\n if (Wrapper) return <Wrapper blog={blog}>{content}</Wrapper>;\n return content;\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport { hasTextImage } from '../media-placement';\nimport { BlogRenderer, type BlockComponents } from './BlogRenderer';\n\nexport type BlogDetailComponents = {\n /**\n * Renders above the body. Default: og_image → h1 → time → description.\n * Override to add tags, author chip, share buttons, read time, etc.\n */\n Header?: (props: {\n blog: Blog;\n formatDate: (iso: string) => string;\n }) => React.ReactNode;\n /** Renders below the body. Default: nothing. */\n Footer?: (props: { blog: Blog }) => React.ReactNode;\n /** Forwarded to <BlogRenderer components={...} />. */\n blocks?: BlockComponents;\n};\n\nconst defaultFormatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString();\n\nfunction DefaultHeader({\n blog,\n formatDate,\n}: {\n blog: Blog;\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n // Bare semantic HTML — no classNames, no styles. Consumers wrap the whole\n // BlogDetail in their own `.prose` / typography container to handle styling.\n //\n // When the body shows any image — attachments spread by <BlogRenderer>\n // (the first lands at the top of the body, right under this header) or\n // images placed in the text — `og_image` is one of them, so rendering it\n // here too would show it twice. It then stays a meta-only field (still\n // used by OpenGraph / Twitter card previews via `generateBlogMetadata`).\n const hasInlineMedia =\n (blog.generatedMedia?.length ?? 0) > 0 || hasTextImage(blog.blocks);\n // eslint-disable-next-line @next/next/no-img-element\n const heroImage =\n !hasInlineMedia && blog.og_image ? (\n <img src={blog.og_image} alt={blog.title} />\n ) : null;\n return (\n <>\n {heroImage}\n <h1>{blog.title}</h1>\n {blog.published_at && (\n <time dateTime={blog.published_at}>\n {formatDate(blog.published_at)}\n </time>\n )}\n {blog.description && <p>{blog.description}</p>}\n </>\n );\n}\n\nexport function BlogDetail({\n blog,\n components,\n formatDate = defaultFormatDate,\n}: {\n blog: Blog;\n components?: BlogDetailComponents;\n formatDate?: (iso: string) => string;\n}): React.JSX.Element {\n const Header = components?.Header ?? DefaultHeader;\n const Footer = components?.Footer;\n return (\n <article>\n <Header blog={blog} formatDate={formatDate} />\n <BlogRenderer blog={blog} components={components?.blocks} />\n {Footer ? <Footer blog={blog} /> : null}\n </article>\n );\n}\n","import type { Blog, FaqItem } from './types';\n\n/**\n * Consumer-supplied extras for BlogPosting. Everything is optional — the\n * SDK doesn't know the customer's base URL, organisation name, or author\n * identity, so it just omits what it isn't told. Schema.org treats\n * `author`, `publisher`, and `url` as recommended but not required.\n */\nexport type BlogJsonLdOptions = {\n /** Absolute URL of the blog page (e.g. `https://example.com/blog/my-post`). */\n url?: string;\n author?: { name: string; type?: 'Person' | 'Organization'; url?: string };\n publisher?: { name: string; logo?: string };\n};\n\nconst Schema = Object.freeze({ context: 'https://schema.org' });\n\n/**\n * Build a Schema.org `BlogPosting` JSON-LD object from a chaingrow Blog.\n * Fields that aren't present on the Blog (description, og_image, …) are\n * omitted entirely so the resulting JSON has no `undefined` junk.\n */\nexport function buildBlogPostingSchema(\n blog: Blog,\n opts: BlogJsonLdOptions = {},\n): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@type': 'BlogPosting',\n headline: blog.title,\n datePublished: blog.published_at,\n };\n if (blog.description) schema.description = blog.description;\n if (blog.og_image) schema.image = blog.og_image;\n if (opts.url) {\n schema.url = opts.url;\n schema.mainEntityOfPage = opts.url;\n }\n if (opts.author) {\n schema.author = {\n '@type': opts.author.type ?? 'Organization',\n name: opts.author.name,\n ...(opts.author.url ? { url: opts.author.url } : {}),\n };\n }\n if (opts.publisher) {\n schema.publisher = {\n '@type': 'Organization',\n name: opts.publisher.name,\n ...(opts.publisher.logo\n ? { logo: { '@type': 'ImageObject', url: opts.publisher.logo } }\n : {}),\n };\n }\n return schema;\n}\n\n/**\n * Build a Schema.org `FAQPage` JSON-LD object from chaingrow's `{question, answer}[]`\n * pairs. Returns `null` for empty input so callers can skip it with a single\n * `if (faqSchema) items.push(faqSchema)` / `filter(Boolean)`.\n */\nexport function buildFAQPageSchema(\n faq: FaqItem[] | null | undefined,\n): Record<string, unknown> | null {\n if (!faq || faq.length === 0) return null;\n return {\n '@type': 'FAQPage',\n mainEntity: faq.map(({ question, answer }) => ({\n '@type': 'Question',\n name: question,\n acceptedAnswer: { '@type': 'Answer', text: answer },\n })),\n };\n}\n\n/**\n * Combines BlogPosting + FAQPage + any LLM-generated `structured_data.jsonLd`\n * extras into a flat array ready for `@graph`. Tolerant of missing/malformed\n * `structured_data` — if the backend ever sends something unexpected we just\n * skip it rather than crashing the consumer's page.\n */\nexport function buildBlogJsonLd(\n blog: Blog,\n opts: BlogJsonLdOptions = {},\n): Record<string, unknown>[] {\n const items: Record<string, unknown>[] = [buildBlogPostingSchema(blog, opts)];\n const faq = buildFAQPageSchema(blog.faq);\n if (faq) items.push(faq);\n items.push(...extractLlmJsonLd(blog.structured_data));\n return items;\n}\n\n/** Schema-org context string — exposed so `<JsonLdGraph>` and any\n * bring-your-own renderer use the same value. */\nexport const SCHEMA_ORG_CONTEXT = Schema.context;\n\n/**\n * Pull the JSON-LD array out of `blog.structured_data`. Per\n * `structuredDataOutputSchema` in @chaingrow/contracts the expected shape is\n * `{ jsonLd: Array<Record<string, unknown>> }`, but historical rows may be\n * `null`, a plain object, or even malformed — normalise to an empty array in\n * every degenerate case so the caller gets a safe spread target.\n */\nfunction extractLlmJsonLd(\n sd: Blog['structured_data'],\n): Record<string, unknown>[] {\n if (!sd || typeof sd !== 'object') return [];\n const ld = (sd as { jsonLd?: unknown }).jsonLd;\n if (!Array.isArray(ld)) return [];\n return ld.filter(\n (entry): entry is Record<string, unknown> =>\n typeof entry === 'object' && entry !== null && !Array.isArray(entry),\n );\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport {\n buildBlogJsonLd,\n SCHEMA_ORG_CONTEXT,\n type BlogJsonLdOptions,\n} from '../json-ld';\n\n/**\n * Render an array of Schema.org graph items as a single\n * `<script type=\"application/ld+json\">` tag with a merged `@graph`.\n * Google treats one `@graph` script as equivalent to N separate scripts, and\n * a single tag keeps the DOM uncluttered for consumers who already ship their\n * own schemas (Organization, breadcrumbs, WebSite, …).\n */\nexport function JsonLdGraph({\n items,\n}: {\n items: Record<string, unknown>[];\n}): React.JSX.Element | null {\n if (!items || items.length === 0) return null;\n const payload = JSON.stringify({\n '@context': SCHEMA_ORG_CONTEXT,\n '@graph': items,\n });\n return (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: payload }}\n />\n );\n}\n\n/**\n * Default-batteries JSON-LD for a chaingrow Blog: emits BlogPosting +\n * FAQPage (if the post has FAQs) + any LLM-generated extras from\n * `structured_data.jsonLd`. Consumers who already ship their own graph items\n * (breadcrumbs, Organization, …) pass them via `extraSchemas` and they end\n * up in the same `@graph`.\n */\nexport function BlogJsonLd({\n blog,\n extraSchemas,\n ...opts\n}: {\n blog: Blog;\n extraSchemas?: Record<string, unknown>[];\n} & BlogJsonLdOptions): React.JSX.Element | null {\n const items = [...(extraSchemas ?? []), ...buildBlogJsonLd(blog, opts)];\n return <JsonLdGraph items={items} />;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACuBA,IAAM,0BAA0B,oBAAI,IAAI;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,mBAAmB;AAEzB,SAAS,UAAU,KAAkC;AACnD,QAAM,QAAQ,eAAe,QAAQ,IAAI,QAAQ;AACjD,SAAO,SAAS,OAAO,UAAU,YAAY,UAAU,QACnD,OAAQ,MAA6B,IAAI,IACzC;AACN;AAEO,SAAS,aAAa,MAAsC;AACjE,QAAM,UAAU,KAAK,OAAO,QAAQ,OAAO,EAAE;AAC7C,QAAM,UAAU,KAAK,SAAS;AAE9B,QAAM,UAAU,OACd,MACA,SACsB;AACtB,UAAM,MAAM,GAAG,OAAO,GAAG,IAAI;AAC7B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,YAAY,OAAoB;AAAA,MACpC,GAAG;AAAA,MACH,SAAS;AAAA,QACP,aAAa,KAAK;AAAA,QAClB,QAAQ;AAAA,QACR,GAAI,MAAM,WAAW,CAAC;AAAA,MACxB;AAAA,IACF;AAEA,QAAI;AACJ,aAAS,UAAU,GAAG,WAAW,kBAAkB,WAAW;AAC5D,UAAI;AACF,cAAM,MAAM,QAAQ,KAAK,UAAU,CAAC;AAGpC,YAAI,IAAI,WAAW,KAAK;AACtB,gBAAM,UAAU,SAAS,IAAI,QAAQ,IAAI,aAAa,KAAK,KAAK,EAAE;AAClE,gBAAM,IAAI;AAAA,YAAQ,CAAC,YACjB,WAAW,SAAS,KAAK,IAAI,GAAG,OAAO,IAAI,GAAI;AAAA,UACjD;AACA,gBAAM,MAAM,QAAQ,KAAK,UAAU,CAAC;AAAA,QACtC;AACA;AAAA,MACF,SAAS,KAAK;AACZ,cAAM,OAAO,UAAU,GAAG;AAC1B,YACE,UAAU,oBACV,SAAS,UACT,wBAAwB,IAAI,IAAI,GAChC;AACA,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,MAAM,OAAO,CAAC;AACjE;AAAA,QACF;AAMA,cAAM,SAAS,OACX,GAAG,IAAI,GAAG,eAAe,SAAS,IAAI,UAAU,KAAK,IAAI,OAAO,MAAM,EAAE,KACxE,eAAe,QACb,IAAI,UACJ,OAAO,GAAG;AAChB,cAAM,IAAI,IAAI;AAAA,UACZ,mCAAmC,MAAM,IAAI,GAAG,WAAM,MAAM;AAAA,QAE9D;AACA,QAAC,EAAkC,QAAQ;AAC3C,cAAM;AAAA,MACR;AAAA,IACF;AACA,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,mBAAmB,MAAM,IAAI,GAAG,qBAAgB;AAAA,IAClE;AAEA,QAAI,IAAI,WAAW,IAAK,QAAO;AAC/B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,mBAAmB,MAAM,IAAI,GAAG,YAAY,IAAI,MAAM;AAAA,MACxD;AAAA,IACF;AACA,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,MAAM,QAAQ,MAAM,UAAU;AAC5B,YAAM,KAAK,WAAW,aAAa,mBAAmB,QAAQ,CAAC,KAAK;AACpE,aAAO;AAAA,QACL,yBAAyB,mBAAmB,IAAI,CAAC,GAAG,EAAE;AAAA,MACxD;AAAA,IACF;AAAA,IAEA,MAAM,YAAY,IAAI;AACpB,aAAO,QAAc,iBAAiB,mBAAmB,EAAE,CAAC,EAAE;AAAA,IAChE;AAAA,IAEA,MAAM,UAAU,WAAW,CAAC,GAAG;AAC7B,YAAM,YAAoB,CAAC;AAI3B,YAAM,YAAY;AAClB,eAAS,OAAO,GAAG,OAAO,WAAW,QAAQ;AAC3C,cAAM,SAAS,IAAI,gBAAgB;AACnC,YAAI,SAAS,OAAQ,QAAO,IAAI,UAAU,SAAS,MAAM;AACzD,YAAI,SAAS,SAAU,QAAO,IAAI,YAAY,SAAS,QAAQ;AAC/D,YAAI,UAAU,SAAS;AACrB,iBAAO,IAAI,UAAU,OAAO,UAAU,MAAM,CAAC;AAC/C,cAAM,KAAK,OAAO,SAAS;AAC3B,cAAM,WAAW,MAAM;AAAA,UACrB,gBAAgB,KAAK,IAAI,EAAE,KAAK,EAAE;AAAA,QACpC;AACA,YAAI,CAAC,SAAU;AACf,cAAM,QAAQ,SAAS,QAAQ,CAAC;AAChC,kBAAU,KAAK,GAAG,KAAK;AACvB,cAAM,QACJ,OAAO,SAAS,UAAU,WAAW,SAAS,QAAQ,UAAU;AAClE,YAAI,MAAM,WAAW,KAAK,UAAU,UAAU,MAAO;AAAA,MACvD;AAIA,UAAI,SAAS,UAAU;AACrB,eAAO,UAAU,OAAO,CAAC,MAAM,EAAE,aAAa,SAAS,QAAQ;AAAA,MACjE;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AC1JA,IAAM,oBAAoB,CAAC,MAAsB,EAAE,QAAQ,QAAQ,EAAE;AACrE,IAAM,sBAAsB,CAAC,MAAsB;AACjD,MAAI,IAAI,EAAE,KAAK;AACf,MAAI,CAAC,EAAE,WAAW,GAAG,EAAG,KAAI,MAAM;AAClC,SAAO,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACzD,SAAO;AACT;AA0BA,IAAM,aAAa,CACjB,YACA,UACA,SACW;AACX,QAAM,OAAO,kBAAkB,WAAW,OAAO;AACjD,QAAM,YACJ,OAAO,WAAW,eAAe,aAC7B,WAAW,WAAW,QAAQ,IAC7B,WAAW,cAAc;AAChC,SAAO,GAAG,IAAI,GAAG,oBAAoB,SAAS,CAAC,IAAI,IAAI;AACzD;AA0CA,eAAsB,qBACpB,MACA,UACA,MACmB;AACnB,QAAM,OAAO,MAAM,QAAQ,MAAM,UAAU,IAAI;AAC/C,MAAI,CAAC,KAAM,QAAO,CAAC;AAEnB,QAAM,SAAS,KAAK,WAAW,CAAC,KAAK,QAAQ,IAAI;AACjD,QAAM,cAAc,KAAK,eAAe;AAExC,QAAM,OAAiB;AAAA,IACrB,OAAO,KAAK;AAAA,IACZ,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,IACrC,WAAW;AAAA,MACT,MAAM;AAAA,MACN,OAAO,KAAK;AAAA,MACZ,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,MACrC,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,MAC3B,GAAI,KAAK,eAAe,EAAE,eAAe,KAAK,aAAa,IAAI,CAAC;AAAA,IAClE;AAAA,IACA,SAAS;AAAA,MACP,MAAM,KAAK,WAAW,wBAAwB;AAAA,MAC9C,OAAO,KAAK;AAAA,MACZ,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,MACrC,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,IAC7B;AAAA,EACF;AAEA,MAAI,MAAM,YAAY;AACpB,UAAM,aAAa,KAAK;AACxB,UAAM,YAAY,WAAW,YAAY,KAAK,UAAU,KAAK,IAAI;AACjE,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,MAAM,UAAU,EAAE,QAAQ,YAAY,GAAG,IAAI;AACzD,YAAM,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,QAAQ,EAAE,QAAQ;AACrE,UAAI,SAAS,SAAS,GAAG;AACvB,oBAAY,CAAC;AACb,mBAAW,WAAW,UAAU;AAC9B,oBAAU,QAAQ,QAAQ,IAAI;AAAA,YAC5B;AAAA,YACA,QAAQ;AAAA,YACR,QAAQ;AAAA,UACV;AAAA,QACF;AACA,cAAM,cAAc,WAAW,mBAC3B,UAAU,WAAW,gBAAgB,IACrC;AACJ,YAAI,YAAa,WAAU,WAAW,IAAI;AAAA,MAC5C;AAAA,IACF,SAAS,KAAK;AAEZ,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,SAAK,aAAa;AAAA,MAChB;AAAA,MACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IACnC;AAAA,EACF;AAEA,SAAO;AACT;AA0BO,SAAS,0BACd,MACU;AACV,QAAM,EAAE,OAAO,aAAa,WAAW,QAAQ,IAAI;AACnD,QAAM,SAAS,UAAU,CAAC,OAAO,IAAI;AACrC,SAAO;AAAA,IACL;AAAA,IACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,IACrC,GAAI,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,CAAC;AAAA,IACjD,WAAW;AAAA,MACT,MAAM;AAAA,MACN;AAAA,MACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,MACrC,GAAI,YAAY,EAAE,KAAK,UAAU,IAAI,CAAC;AAAA,MACtC,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,IAC7B;AAAA,IACA,SAAS;AAAA,MACP,MAAM,UAAU,wBAAwB;AAAA,MACxC;AAAA,MACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,MACrC,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,IAC7B;AAAA,EACF;AACF;;;ACxMA,wBAAyB;;;ACoBlB,SAAS,yBACd,YACA,YACU;AACV,MAAI,cAAc,EAAG,QAAO,CAAC;AAC7B,MAAI,cAAc,EAAG,QAAO,MAAM,KAAK,EAAE,QAAQ,WAAW,GAAG,MAAM,CAAC;AACtE,SAAO,MAAM;AAAA,IAAK,EAAE,QAAQ,WAAW;AAAA,IAAG,CAAC,GAAG,MAC5C,KAAK,MAAO,IAAI,aAAc,UAAU;AAAA,EAC1C;AACF;AAOO,SAAS,eAAe,QAA8B;AAC3D,QAAM,MAAM,oBAAI,IAAY;AAC5B,QAAM,OAAO,CAAC,SAAkB;AAC9B,eAAW,KAAK,MAAM;AACpB,UAAI,EAAE,SAAS,WAAW,EAAE,QAAS,KAAI,IAAI,EAAE,OAAO;AAAA,eAC7C,EAAE,SAAS,gBAAgB,EAAE,SAAS,eAAe;AAC5D,mBAAW,QAAQ,EAAE,MAAO,MAAK,KAAK,OAAO;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AACA,OAAK,MAAM;AACX,SAAO;AACT;AAGO,SAAS,aAAa,QAA0B;AACrD,QAAM,OAAO,CAAC,SACZ,KAAK;AAAA,IACH,CAAC,MACC,EAAE,SAAS,YACT,EAAE,SAAS,gBAAgB,EAAE,SAAS,kBACtC,EAAE,MAAM,KAAK,CAAC,SAAS,KAAK,KAAK,OAAO,CAAC;AAAA,EAC/C;AACF,SAAO,KAAK,MAAM;AACpB;AAeO,SAAS,gBACd,QACA,OACmB;AACnB,QAAM,SAAS,eAAe,MAAM;AACpC,QAAM,aAAa,SAAS,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;AAC/D,QAAM,YAAY,yBAAyB,OAAO,QAAQ,UAAU,MAAM;AAE1E,QAAM,QAA2B,CAAC;AAClC,MAAI,cAAc;AAClB,WAAS,IAAI,GAAG,KAAK,OAAO,QAAQ,KAAK;AACvC,WAAO,cAAc,UAAU,UAAU,UAAU,WAAW,MAAM,GAAG;AACrE,YAAMA,SAAQ,UAAU,WAAW;AACnC,YAAM,KAAK,EAAE,MAAM,SAAS,OAAAA,QAAO,OAAO,YAAY,CAAC;AACvD;AAAA,IACF;AACA,QAAI,IAAI,OAAO,QAAQ;AACrB,YAAM,QAAQ,OAAO,CAAC;AACtB,YAAM,KAAK,EAAE,MAAM,SAAS,OAAO,OAAO,EAAE,CAAC;AAAA,IAC/C;AAAA,EACF;AACA,SAAO;AACT;;;AC/E0B;AATnB,IAAM,iBAAiB,CAAC;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,MAIM;AACJ,MAAI,UAAU,EAAG,QAAO,4CAAC,QAAG,WAAuB,UAAS;AAC5D,MAAI,UAAU,EAAG,QAAO,4CAAC,QAAG,WAAuB,UAAS;AAC5D,SAAO,4CAAC,QAAG,WAAuB,UAAS;AAC7C;AAEO,IAAM,mBAAmB,CAAC;AAAA,EAC/B;AAAA,EACA;AACF,MAGM,4CAAC,OAAE,WAAuB,UAAS;AAElC,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,MAKE,4CAAC,QAAG,WACD,gBAAM,IAAI,CAAC,MAAM,MAChB,4CAAC,QAAY,eAAK,QAAQ,IAAI,CAAC,GAAG,MAAM,YAAY,GAAG,CAAC,CAAC,KAAhD,CAAkD,CAC5D,GACH;AAGK,IAAM,qBAAqB,CAAC;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,MAKE,4CAAC,QAAG,WACD,gBAAM,IAAI,CAAC,MAAM,MAChB,4CAAC,QAAY,eAAK,QAAQ,IAAI,CAAC,GAAG,MAAM,YAAY,GAAG,CAAC,CAAC,KAAhD,CAAkD,CAC5D,GACH;AAGK,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA;AACF,MAGM,4CAAC,gBAAW,WAAuB,UAAS;AAE3C,IAAM,mBAAmB,CAAC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,MAKE,4CAAC,SAAI,WACH,sDAAC,UAAK,WAAW,WAAW,YAAY,QAAQ,KAAK,QAAY,gBAAK,GACxE;AAGK,IAAM,eAAe,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAQM;AACJ,QAAM;AAAA;AAAA,IAEJ,4CAAC,SAAI,KAAU,KAAU,OAAc,QAAgB;AAAA;AAEzD,MAAI,SAAS;AACX,WACE,6CAAC,YAAO,WACL;AAAA;AAAA,MACD,4CAAC,gBAAY,mBAAQ;AAAA,OACvB;AAAA,EAEJ;AACA,SAAO,4CAAC,UAAK,WAAuB,eAAI;AAC1C;AAEO,IAAM,iBAAiB,CAAC,EAAE,UAAU,MACzC,4CAAC,QAAG,WAAsB;AASrB,IAAM,eAAe,CAAC,EAAE,MAAM,MAAgC;AACnE,MAAI,MAAM,eAAe,SAAS;AAChC,WACE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,MAAM;AAAA,QACX,UAAQ;AAAA,QACR,aAAW;AAAA,QACX,SAAQ;AAAA;AAAA,IACV;AAAA,EAEJ;AAEA,SAAO,4CAAC,SAAI,KAAK,MAAM,WAAW,KAAK,MAAM,QAAQ;AACvD;;;ACrBI,IAAAC,sBAAA;AAnCJ,IAAM,aAA6B;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,QAAQ;AAOd,SAAS,WAAW,QAA6C;AAC/D,QAAM,MAAiC,CAAC;AACxC,aAAW,OAAO,QAAQ;AACxB,UAAM,SAAS,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,IAAI,MAAM,IAAI;AACvE,UAAM,QAAQ,CAAC,MAAM,MAAM;AACzB,UAAI,IAAI,KAAK,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,MAAM,OAAO;AAC5D,YAAI,KAAK,KAAK;AAAA,MAChB;AACA,UAAI,QAAQ,EAAE,IAAI,IAAI,SAAS,CAAC,MAAM,SAAS,CAAC,KAAK,KAAK,IAAI;AAC5D,YAAI,KAAK,EAAE,GAAG,KAAK,KAAK,CAAC;AAAA,MAC3B;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,IAAI,IAAI,SAAS,CAAC,MAAM,MAAO,KAAI,IAAI;AAC3C,SAAO;AACT;AAGA,SAAS,eAAe,EAAE,OAAO,GAAyB;AACxD,SACE,6EACG,qBAAW,MAAM,EAAE;AAAA,IAAI,CAAC,KAAK,MAC5B,QAAQ,QAAQ,6CAAC,UAAQ,CAAG,IAAK,6CAAC,aAAkB,OAAH,CAAa;AAAA,EAChE,GACF;AAEJ;AAEA,SAAS,UAAU,EAAE,IAAI,GAAoB;AAC3C,QAAM,QAAQ,IAAI,QACd,CAAC,GAAG,IAAI,KAAK,EAAE;AAAA,IACb,CAAC,GAAG,MAAM,WAAW,QAAQ,EAAE,IAAI,IAAI,WAAW,QAAQ,EAAE,IAAI;AAAA,EAClE,IACA,CAAC;AACL,MAAI,OAAwB,IAAI;AAChC,aAAW,QAAQ,OAAO;AACxB,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AACH,eAAO,6CAAC,YAAQ,gBAAK;AACrB;AAAA,MACF,KAAK;AACH,eAAO,6CAAC,QAAI,gBAAK;AACjB;AAAA,MACF,KAAK;AACH,eAAO,6CAAC,OAAG,gBAAK;AAChB;AAAA,MACF,KAAK;AACH,eAAO,6CAAC,UAAM,gBAAK;AACnB;AAAA,MACF,KAAK;AACH,eACE,6CAAC,OAAE,MAAM,KAAK,MAAM,KAAI,uBACrB,gBACH;AAEF;AAAA,IACJ;AAAA,EACF;AACA,SAAO,6EAAG,gBAAK;AACjB;AAEO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA,aAAa,CAAC;AAAA,EACd,aAAa,CAAC;AAAA,EACd;AACF,GAKG;AAID,QAAM,UACJ,gBACC,CAAC,GAAG,MACH;AAAA,IAAC;AAAA;AAAA,MAEC,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,aAAa;AAAA;AAAA,IAJR;AAAA,EAKP;AAGJ,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK,WAAW;AACd,YAAM,UAAU,WAAW,WAAW;AACtC,aACE,6CAAC,WAAQ,OAAO,MAAM,OAAO,WAAW,WAAW,SACjD,uDAAC,kBAAe,QAAQ,MAAM,SAAS,GACzC;AAAA,IAEJ;AAAA,IACA,KAAK,aAAa;AAChB,YAAM,YAAY,WAAW,aAAa;AAC1C,aACE,6CAAC,aAAU,WAAW,WAAW,WAC/B,uDAAC,kBAAe,QAAQ,MAAM,SAAS,GACzC;AAAA,IAEJ;AAAA,IACA,KAAK,cAAc;AACjB,YAAM,aAAa,WAAW,cAAc;AAC5C,aACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,WAAW,WAAW;AAAA,UACtB,aAAa;AAAA;AAAA,MACf;AAAA,IAEJ;AAAA,IACA,KAAK,eAAe;AAClB,YAAM,cAAc,WAAW,eAAe;AAC9C,aACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,MAAM;AAAA,UACb,WAAW,WAAW;AAAA,UACtB,aAAa;AAAA;AAAA,MACf;AAAA,IAEJ;AAAA,IACA,KAAK,cAAc;AACjB,YAAM,aAAa,WAAW,cAAc;AAC5C,aACE,6CAAC,cAAW,WAAW,WAAW,YAChC,uDAAC,kBAAe,QAAQ,MAAM,SAAS,GACzC;AAAA,IAEJ;AAAA,IACA,KAAK,aAAa;AAChB,YAAM,YAAY,WAAW,aAAa;AAC1C,aACE;AAAA,QAAC;AAAA;AAAA,UACC,UAAU,MAAM;AAAA,UAChB,MAAM,MAAM;AAAA,UACZ,WAAW,WAAW;AAAA;AAAA,MACxB;AAAA,IAEJ;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,YAAY,WAAW,SAAS;AACtC,aACE;AAAA,QAAC;AAAA;AAAA,UACC,KAAK,MAAM;AAAA,UACX,KAAK,MAAM;AAAA,UACX,SAAS,MAAM;AAAA,UACf,OAAO,MAAM;AAAA,UACb,QAAQ,MAAM;AAAA,UACd,SAAS,MAAM;AAAA,UACf,WAAW,WAAW;AAAA;AAAA,MACxB;AAAA,IAEJ;AAAA,IACA,KAAK,WAAW;AACd,YAAM,UAAU,WAAW,WAAW;AACtC,aAAO,6CAAC,WAAQ,WAAW,WAAW,SAAS;AAAA,IACjD;AAAA,EACF;AACF;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,aAAa,CAAC;AAAA,EACd,aAAa,CAAC;AAChB,GAIG;AACD,QAAM,QAAQ,WAAW,SAAS;AAClC,QAAM,UAAU,WAAW;AAK3B,QAAM,QAAQ,gBAAgB,KAAK,QAAQ,KAAK,cAAc;AAE9D,QAAM,UACJ,6EACG,gBAAM;AAAA,IAAI,CAAC,SACV,KAAK,SAAS,UACZ;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,KAAK;AAAA,QACZ;AAAA,QACA;AAAA;AAAA,MAHK,KAAK,KAAK,KAAK;AAAA,IAItB,IAEA,6CAAC,SAAiC,OAAO,KAAK,SAAlC,KAAK,KAAK,MAAM,EAAE,EAAuB;AAAA,EAEzD,GACF;AAGF,MAAI,QAAS,QAAO,6CAAC,WAAQ,MAAa,mBAAQ;AAClD,SAAO;AACT;;;AChQM,IAAAC,sBAAA;AAvBN,IAAM,oBAAoB,CAAC,QACzB,IAAI,KAAK,GAAG,EAAE,mBAAmB;AAEnC,SAAS,cAAc;AAAA,EACrB;AAAA,EACA;AACF,GAGsB;AASpB,QAAM,kBACH,KAAK,gBAAgB,UAAU,KAAK,KAAK,aAAa,KAAK,MAAM;AAEpE,QAAM,YACJ,CAAC,kBAAkB,KAAK,WACtB,6CAAC,SAAI,KAAK,KAAK,UAAU,KAAK,KAAK,OAAO,IACxC;AACN,SACE,8EACG;AAAA;AAAA,IACD,6CAAC,QAAI,eAAK,OAAM;AAAA,IACf,KAAK,gBACJ,6CAAC,UAAK,UAAU,KAAK,cAClB,qBAAW,KAAK,YAAY,GAC/B;AAAA,IAED,KAAK,eAAe,6CAAC,OAAG,eAAK,aAAY;AAAA,KAC5C;AAEJ;AAEO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,aAAa;AACf,GAIsB;AACpB,QAAM,SAAS,YAAY,UAAU;AACrC,QAAM,SAAS,YAAY;AAC3B,SACE,8CAAC,aACC;AAAA,iDAAC,UAAO,MAAY,YAAwB;AAAA,IAC5C,6CAAC,gBAAa,MAAY,YAAY,YAAY,QAAQ;AAAA,IACzD,SAAS,6CAAC,UAAO,MAAY,IAAK;AAAA,KACrC;AAEJ;;;AC9DA,IAAM,SAAS,OAAO,OAAO,EAAE,SAAS,qBAAqB,CAAC;AAOvD,SAAS,uBACd,MACA,OAA0B,CAAC,GACF;AACzB,QAAM,SAAkC;AAAA,IACtC,SAAS;AAAA,IACT,UAAU,KAAK;AAAA,IACf,eAAe,KAAK;AAAA,EACtB;AACA,MAAI,KAAK,YAAa,QAAO,cAAc,KAAK;AAChD,MAAI,KAAK,SAAU,QAAO,QAAQ,KAAK;AACvC,MAAI,KAAK,KAAK;AACZ,WAAO,MAAM,KAAK;AAClB,WAAO,mBAAmB,KAAK;AAAA,EACjC;AACA,MAAI,KAAK,QAAQ;AACf,WAAO,SAAS;AAAA,MACd,SAAS,KAAK,OAAO,QAAQ;AAAA,MAC7B,MAAM,KAAK,OAAO;AAAA,MAClB,GAAI,KAAK,OAAO,MAAM,EAAE,KAAK,KAAK,OAAO,IAAI,IAAI,CAAC;AAAA,IACpD;AAAA,EACF;AACA,MAAI,KAAK,WAAW;AAClB,WAAO,YAAY;AAAA,MACjB,SAAS;AAAA,MACT,MAAM,KAAK,UAAU;AAAA,MACrB,GAAI,KAAK,UAAU,OACf,EAAE,MAAM,EAAE,SAAS,eAAe,KAAK,KAAK,UAAU,KAAK,EAAE,IAC7D,CAAC;AAAA,IACP;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,mBACd,KACgC;AAChC,MAAI,CAAC,OAAO,IAAI,WAAW,EAAG,QAAO;AACrC,SAAO;AAAA,IACL,SAAS;AAAA,IACT,YAAY,IAAI,IAAI,CAAC,EAAE,UAAU,OAAO,OAAO;AAAA,MAC7C,SAAS;AAAA,MACT,MAAM;AAAA,MACN,gBAAgB,EAAE,SAAS,UAAU,MAAM,OAAO;AAAA,IACpD,EAAE;AAAA,EACJ;AACF;AAQO,SAAS,gBACd,MACA,OAA0B,CAAC,GACA;AAC3B,QAAM,QAAmC,CAAC,uBAAuB,MAAM,IAAI,CAAC;AAC5E,QAAM,MAAM,mBAAmB,KAAK,GAAG;AACvC,MAAI,IAAK,OAAM,KAAK,GAAG;AACvB,QAAM,KAAK,GAAG,iBAAiB,KAAK,eAAe,CAAC;AACpD,SAAO;AACT;AAIO,IAAM,qBAAqB,OAAO;AASzC,SAAS,iBACP,IAC2B;AAC3B,MAAI,CAAC,MAAM,OAAO,OAAO,SAAU,QAAO,CAAC;AAC3C,QAAM,KAAM,GAA4B;AACxC,MAAI,CAAC,MAAM,QAAQ,EAAE,EAAG,QAAO,CAAC;AAChC,SAAO,GAAG;AAAA,IACR,CAAC,UACC,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAAA,EACvE;AACF;;;ACvFI,IAAAC,sBAAA;AAXG,SAAS,YAAY;AAAA,EAC1B;AACF,GAE6B;AAC3B,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;AACzC,QAAM,UAAU,KAAK,UAAU;AAAA,IAC7B,YAAY;AAAA,IACZ,UAAU;AAAA,EACZ,CAAC;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MAEL,yBAAyB,EAAE,QAAQ,QAAQ;AAAA;AAAA,EAC7C;AAEJ;AASO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAGiD;AAC/C,QAAM,QAAQ,CAAC,GAAI,gBAAgB,CAAC,GAAI,GAAG,gBAAgB,MAAM,IAAI,CAAC;AACtE,SAAO,6CAAC,eAAY,OAAc;AACpC;;;ANZI,IAAAC,sBAAA;AAbJ,eAAsB,SAAS;AAAA,EAC7B;AACF,GAA0C;AACxC,QAAM,EAAE,MAAM,OAAO,IAAI,MAAM;AAC/B,MAAI,OAAoB;AACxB,MAAI;AACF,WAAO,MAAM,QAAQ,MAAM,MAAM;AAAA,EACnC,SAAS,KAAK;AACZ,YAAQ,MAAM,8CAA8C,GAAG;AAAA,EACjE;AACA,MAAI,CAAC,KAAM,iCAAS;AAEpB,SACE,8CAAC,SAAI,WAAU,mBAEb;AAAA,iDAAC,WAAM,yBAAyB,EAAE,QAAQ,YAAY,GAAG;AAAA,IACzD,6CAAC,cAAW,MAAY;AAAA,IACxB,6CAAC,cAAW,MAAY;AAAA,KAC1B;AAEJ;AASA,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwKb,SAAS,yBACd,MACwC;AACxC,SAAO,OAAO,EAAE,OAAO,MAAM;AAC3B,UAAM,EAAE,MAAM,OAAO,IAAI,MAAM;AAC/B,QAAI;AACF,aAAO,MAAM,qBAAqB,MAAM,QAAQ,IAAI;AAAA,IACtD,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,MACF;AACA,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;;;AHpOA,IAAM,kBAAkB;AAcxB,IAAM,6BAA6B,KAAK,KAAK,KAAK;AAElD,IAAM,aAAa,CAAC,OAA0B,CAAC,MAAM;AACnD,QAAM,SACJ,KAAK,UAAU,QAAQ,IAAI,qBAAqB;AAClD,QAAM,SAAS,KAAK,UAAU,QAAQ,IAAI;AAC1C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAEA,IAAM,aAAa,CAAC,KAAa,eAA2C;AAC1E,QAAM,sBACJ,eAAe,SAAY,6BAA6B;AAC1D,UAAQ,CAAC,OAA0B,SACjC,MAAM,OAAO;AAAA,IACX,GAAG;AAAA,IACH,MAAM;AAAA,MACJ,MAAM,CAAC,kBAAkB,GAAG;AAAA,MAC5B,YAAY;AAAA,IACd;AAAA,EACF,CAAgB;AACpB;AAEA,eAAsB,QACpB,MACA,UACA,OAA0B,CAAC,GACL;AACtB,QAAM,EAAE,QAAQ,OAAO,IAAI,WAAW,IAAI;AAC1C,QAAM,SAAS,aAAa;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,OAAO,WAAW,kBAAkB,IAAI,IAAI,KAAK,UAAU;AAAA,EAC7D,CAAC;AACD,SAAO,OAAO,QAAQ,MAAM,QAAQ;AACtC;AAEA,eAAsB,UACpB,WAAkE,CAAC,GACnE,OAA0B,CAAC,GACV;AACjB,QAAM,EAAE,QAAQ,OAAO,IAAI,WAAW,IAAI;AAC1C,QAAM,SAAS,aAAa;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,OAAO,WAAW,uBAAuB,KAAK,UAAU;AAAA,EAC1D,CAAC;AACD,SAAO,OAAO,UAAU,QAAQ;AAClC;AAEA,IAAMC,qBAAoB,CAAC,MAAsB,EAAE,QAAQ,QAAQ,EAAE;AACrE,IAAMC,uBAAsB,CAAC,MAAsB;AACjD,MAAI,IAAI,EAAE,KAAK;AACf,MAAI,CAAC,EAAE,WAAW,GAAG,EAAG,KAAI,MAAM;AAClC,SAAO,EAAE,SAAS,KAAK,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACzD,SAAO;AACT;AA2BA,eAAsB,sBAAsB,MAIW;AACrD,QAAM,UAAUD,mBAAkB,KAAK,OAAO;AAC9C,QAAM,aAAaC,qBAAoB,KAAK,cAAc,OAAO;AACjE,QAAM,QAAQ,MAAM;AAAA,IAClB,EAAE,QAAQ,aAAa,UAAU,KAAK,SAAS;AAAA,IAC/C;AAAA,MACE,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK;AAAA,MACb,YAAY,KAAK;AAAA,IACnB;AAAA,EACF;AACA,SAAO,MAAM,IAAI,CAAC,UAAU;AAAA,IAC1B,KAAK,GAAG,OAAO,GAAG,UAAU,IAAI,KAAK,IAAI;AAAA,IACzC,cAAc,IAAI,KAAK,KAAK,YAAY;AAAA,EAC1C,EAAE;AACJ;","names":["media","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","trimTrailingSlash","normalizePathPrefix"]}
|
package/dist/next/index.d.cts
CHANGED
package/dist/next/index.d.ts
CHANGED
package/dist/next/index.js
CHANGED
package/dist/next/webhook.d.cts
CHANGED
package/dist/next/webhook.d.ts
CHANGED
package/dist/react/index.cjs
CHANGED
|
@@ -169,8 +169,27 @@ var MARK_ORDER = [
|
|
|
169
169
|
"code",
|
|
170
170
|
"link"
|
|
171
171
|
];
|
|
172
|
+
var BREAK = "br";
|
|
173
|
+
function withBreaks(inline) {
|
|
174
|
+
const out = [];
|
|
175
|
+
for (const run of inline) {
|
|
176
|
+
const parts = (typeof run.text === "string" ? run.text : "").split("\n");
|
|
177
|
+
parts.forEach((text, i) => {
|
|
178
|
+
if (i > 0 && out.length > 0 && out[out.length - 1] !== BREAK) {
|
|
179
|
+
out.push(BREAK);
|
|
180
|
+
}
|
|
181
|
+
if (text && !(out[out.length - 1] === BREAK && !text.trim())) {
|
|
182
|
+
out.push({ ...run, text });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (out[out.length - 1] === BREAK) out.pop();
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
172
189
|
function InlineRenderer({ inline }) {
|
|
173
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: inline.map(
|
|
190
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: withBreaks(inline).map(
|
|
191
|
+
(run, i) => run === BREAK ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("br", {}, i) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineRun, { run }, i)
|
|
192
|
+
) });
|
|
174
193
|
}
|
|
175
194
|
function InlineRun({ run }) {
|
|
176
195
|
const marks = run.marks ? [...run.marks].sort(
|
package/dist/react/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.ts","../../src/media-placement.ts","../../src/react/defaults.tsx","../../src/react/BlogRenderer.tsx","../../src/react/BlogDetail.tsx","../../src/client.ts","../../src/json-ld.ts","../../src/react/BlogJsonLd.tsx","../../src/next/index.ts","../../src/blog-cards.ts","../../src/react/BlogIndex.tsx"],"sourcesContent":["export {\n BlogRenderer,\n BlockRenderer,\n type BlockComponents,\n type BlockClassNames,\n} from './BlogRenderer';\nexport { BlogDetail, type BlogDetailComponents } from './BlogDetail';\nexport {\n BlogIndex,\n type BlogIndexFormat,\n type BlogIndexProps,\n} from './BlogIndex';\nexport { BlogJsonLd, JsonLdGraph } from './BlogJsonLd';\nexport type { BlogJsonLdOptions } from '../json-ld';\nexport {\n toBlogCard,\n mergeBlogs,\n type BlogCard,\n type ToBlogCardOptions,\n} from '../blog-cards';\nexport {\n distributeMediaPositions,\n interleaveMedia,\n type InterleavedSlot,\n} from '../media-placement';\nexport * from './defaults';\nexport type {\n Blog,\n Block,\n BlockType,\n Inline,\n ListItem,\n Mark,\n FaqItem,\n SocialPost,\n EmbeddedMedia,\n} from '../types';\n","import type { Block, EmbeddedMedia } from './types';\n\n/**\n * Compute the block-index insertion point for each media item when\n * distributing `mediaCount` attachments across `blockCount` blocks.\n *\n * Formula: media `i` goes at insertion index `floor(i * blockCount / mediaCount)`.\n * An insertion index of `0` means \"before block 0\" (top of body); `blockCount`\n * means \"after the last block\".\n *\n * Examples (blockCount = 10):\n * mediaCount = 1 → [0] (single media at top)\n * mediaCount = 2 → [0, 5] (top + middle)\n * mediaCount = 3 → [0, 3, 6] (top, 1/3, 2/3)\n * mediaCount = 4 → [0, 2, 5, 7] (every quarter)\n *\n * When `blockCount` is 0 every media falls at position 0 and stacks at the\n * top. When two media round to the same position they both land there and\n * render consecutively; this is deterministic but not always visually ideal\n * for very small `blockCount / mediaCount` ratios — assume content has\n * enough blocks to spread across.\n */\nexport function distributeMediaPositions(\n blockCount: number,\n mediaCount: number,\n): number[] {\n if (mediaCount <= 0) return [];\n if (blockCount <= 0) return Array.from({ length: mediaCount }, () => 0);\n return Array.from({ length: mediaCount }, (_, i) =>\n Math.floor((i * blockCount) / mediaCount),\n );\n}\n\n/**\n * Ids of the attachments already placed in the text as `image` blocks\n * (`mediaId`), list items included. `interleaveMedia` skips those, so an\n * image the author placed never shows a second time.\n */\nexport function placedMediaIds(blocks: Block[]): Set<string> {\n const ids = new Set<string>();\n const walk = (list: Block[]) => {\n for (const b of list) {\n if (b.type === 'image' && b.mediaId) ids.add(b.mediaId);\n else if (b.type === 'bulletList' || b.type === 'orderedList') {\n for (const item of b.items) walk(item.content);\n }\n }\n };\n walk(blocks);\n return ids;\n}\n\n/** Whether the text itself contains an image (list items included). */\nexport function hasTextImage(blocks: Block[]): boolean {\n const walk = (list: Block[]): boolean =>\n list.some(\n (b) =>\n b.type === 'image' ||\n ((b.type === 'bulletList' || b.type === 'orderedList') &&\n b.items.some((item) => walk(item.content))),\n );\n return walk(blocks);\n}\n\n/** One interleaved slot in a blog's body — either a block or a media item. */\nexport type InterleavedSlot =\n | { kind: 'block'; block: Block; index: number }\n | { kind: 'media'; media: EmbeddedMedia; index: number };\n\n/**\n * Walk `blocks` once, splicing in `media` at the positions returned by\n * `distributeMediaPositions`. Returns a flat array of typed slots that\n * `<BlogRenderer>` can map over directly. Exposed so consumers who need a\n * custom layout (e.g. wrapping each section in a `<section>`) can use the\n * same placement logic without re-implementing it. Media already placed in\n * the text as an `image` block is skipped (see `placedMediaIds`).\n */\nexport function interleaveMedia(\n blocks: Block[],\n media: EmbeddedMedia[] | undefined,\n): InterleavedSlot[] {\n const placed = placedMediaIds(blocks);\n const mediaList = (media ?? []).filter((m) => !placed.has(m.id));\n const positions = distributeMediaPositions(blocks.length, mediaList.length);\n\n const slots: InterleavedSlot[] = [];\n let mediaCursor = 0;\n for (let b = 0; b <= blocks.length; b++) {\n while (mediaCursor < mediaList.length && positions[mediaCursor] === b) {\n const media = mediaList[mediaCursor]!;\n slots.push({ kind: 'media', media, index: mediaCursor });\n mediaCursor++;\n }\n if (b < blocks.length) {\n const block = blocks[b]!;\n slots.push({ kind: 'block', block, index: b });\n }\n }\n return slots;\n}\n","import * as React from 'react';\nimport type { Block, EmbeddedMedia, ListItem } from '../types';\n\n/**\n * Default components for each Block type. All are headless — no\n * classNames baked in. Consumers customize via:\n * - `classNames={{ heading: \"...\" }}` on `<BlogRenderer>` — threads\n * through to `className` on the Default component below.\n * - `components={{ heading: MyHeading }}` — full replacement.\n */\n\nexport const DefaultHeading = ({\n level,\n children,\n className,\n}: {\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n}) => {\n if (level === 1) return <h1 className={className}>{children}</h1>;\n if (level === 3) return <h3 className={className}>{children}</h3>;\n return <h2 className={className}>{children}</h2>;\n};\n\nexport const DefaultParagraph = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <p className={className}>{children}</p>;\n\nexport const DefaultBulletList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ul className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ul>\n);\n\nexport const DefaultOrderedList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ol className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ol>\n);\n\nexport const DefaultBlockquote = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <blockquote className={className}>{children}</blockquote>;\n\nexport const DefaultCodeBlock = ({\n language,\n code,\n className,\n}: {\n language?: string;\n code: string;\n className?: string;\n}) => (\n <pre className={className}>\n <code className={language ? `language-${language}` : undefined}>{code}</code>\n </pre>\n);\n\nexport const DefaultImage = ({\n src,\n alt,\n caption,\n width,\n height,\n className,\n}: {\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n}) => {\n const img = (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={src} alt={alt} width={width} height={height} />\n );\n if (caption) {\n return (\n <figure className={className}>\n {img}\n <figcaption>{caption}</figcaption>\n </figure>\n );\n }\n return <span className={className}>{img}</span>;\n};\n\nexport const DefaultDivider = ({ className }: { className?: string }) => (\n <hr className={className} />\n);\n\n/**\n * Default renderer for a single embedded media item. Bare HTML — no\n * classNames, no styles. Override via `components.media` on `<BlogRenderer>`.\n * `alt` falls back to the generation prompt, which is a sensible description\n * string since it was the LLM input that produced the image.\n */\nexport const DefaultMedia = ({ media }: { media: EmbeddedMedia }) => {\n if (media.media_type === 'VIDEO') {\n return (\n <video\n src={media.media_url}\n controls\n playsInline\n preload=\"metadata\"\n />\n );\n }\n // eslint-disable-next-line @next/next/no-img-element\n return <img src={media.media_url} alt={media.prompt} />;\n};\n","import * as React from 'react';\nimport type {\n Blog,\n Block,\n BlockType,\n EmbeddedMedia,\n Inline,\n ListItem,\n Mark,\n} from '../types';\nimport { interleaveMedia } from '../media-placement';\nimport {\n DefaultHeading,\n DefaultParagraph,\n DefaultBulletList,\n DefaultOrderedList,\n DefaultBlockquote,\n DefaultCodeBlock,\n DefaultImage,\n DefaultDivider,\n DefaultMedia,\n} from './defaults';\n\n/**\n * Override map for per-block components. Keys are the discriminants\n * of `Block`. Consumers pass any subset; unspecified blocks render\n * via the matching `Default*` component.\n *\n * v0.3.0 breaking change: the keys changed to match the new Block\n * union. If you upgraded from 0.2.x, rename `list` → `bulletList` /\n * `orderedList`, `quote` → `blockquote`, `code` → `codeBlock`, drop\n * `image_placeholder` (images are now first-class via `image`).\n */\nexport type BlockComponents = Partial<{\n heading: React.ComponentType<{\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n }>;\n paragraph: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n bulletList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n orderedList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n blockquote: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n codeBlock: React.ComponentType<{\n language?: string;\n code: string;\n className?: string;\n }>;\n image: React.ComponentType<{\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n }>;\n divider: React.ComponentType<{ className?: string }>;\n /** Renders a single attached image/video when `<BlogRenderer>` distributes\n * `blog.generatedMedia` across the body. Default renders a bare\n * `<img>` or `<video controls>`. */\n media: React.ComponentType<{ media: EmbeddedMedia }>;\n wrapper: React.ComponentType<{ children: React.ReactNode; blog: Blog }>;\n}>;\n\n/** Simple Tailwind-style className override map. Applied to the\n * default component for each block type. `components` override takes\n * precedence — if you replace the component, className won't reach it\n * unless your override handles it. */\nexport type BlockClassNames = Partial<Record<BlockType, string>>;\n\nconst MARK_ORDER: Mark['type'][] = [\n 'bold',\n 'italic',\n 'strike',\n 'code',\n 'link',\n];\n\n/** Render an array of Inline runs into text+mark JSX. */\nfunction InlineRenderer({ inline }: { inline: Inline[] }) {\n return (\n <>\n {inline.map((run, i) => (\n <InlineRun key={i} run={run} />\n ))}\n </>\n );\n}\n\nfunction InlineRun({ run }: { run: Inline }) {\n const marks = run.marks\n ? [...run.marks].sort(\n (a, b) => MARK_ORDER.indexOf(b.type) - MARK_ORDER.indexOf(a.type),\n )\n : [];\n let node: React.ReactNode = run.text;\n for (const mark of marks) {\n switch (mark.type) {\n case 'bold':\n node = <strong>{node}</strong>;\n break;\n case 'italic':\n node = <em>{node}</em>;\n break;\n case 'strike':\n node = <s>{node}</s>;\n break;\n case 'code':\n node = <code>{node}</code>;\n break;\n case 'link':\n node = (\n <a href={mark.href} rel=\"noopener noreferrer\">\n {node}\n </a>\n );\n break;\n }\n }\n return <>{node}</>;\n}\n\nexport function BlockRenderer({\n block,\n components = {},\n classNames = {},\n renderBlock,\n}: {\n block: Block;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n renderBlock?: (b: Block, i: number) => React.ReactNode;\n}) {\n // Parent supplies a renderBlock callback so list items can recurse\n // into the same switch. When not provided (top-level call), wire it\n // to a local closure.\n const recurse: (b: Block, i: number) => React.ReactNode =\n renderBlock ??\n ((b, i) => (\n <BlockRenderer\n key={i}\n block={b}\n components={components}\n classNames={classNames}\n renderBlock={recurse}\n />\n ));\n\n switch (block.type) {\n case 'heading': {\n const Heading = components.heading ?? DefaultHeading;\n return (\n <Heading level={block.level} className={classNames.heading}>\n <InlineRenderer inline={block.content} />\n </Heading>\n );\n }\n case 'paragraph': {\n const Paragraph = components.paragraph ?? DefaultParagraph;\n return (\n <Paragraph className={classNames.paragraph}>\n <InlineRenderer inline={block.content} />\n </Paragraph>\n );\n }\n case 'bulletList': {\n const BulletList = components.bulletList ?? DefaultBulletList;\n return (\n <BulletList\n items={block.items}\n className={classNames.bulletList}\n renderBlock={recurse}\n />\n );\n }\n case 'orderedList': {\n const OrderedList = components.orderedList ?? DefaultOrderedList;\n return (\n <OrderedList\n items={block.items}\n className={classNames.orderedList}\n renderBlock={recurse}\n />\n );\n }\n case 'blockquote': {\n const Blockquote = components.blockquote ?? DefaultBlockquote;\n return (\n <Blockquote className={classNames.blockquote}>\n <InlineRenderer inline={block.content} />\n </Blockquote>\n );\n }\n case 'codeBlock': {\n const CodeBlock = components.codeBlock ?? DefaultCodeBlock;\n return (\n <CodeBlock\n language={block.language}\n code={block.code}\n className={classNames.codeBlock}\n />\n );\n }\n case 'image': {\n const ImageComp = components.image ?? DefaultImage;\n return (\n <ImageComp\n src={block.src}\n alt={block.alt}\n caption={block.caption}\n width={block.width}\n height={block.height}\n mediaId={block.mediaId}\n className={classNames.image}\n />\n );\n }\n case 'divider': {\n const Divider = components.divider ?? DefaultDivider;\n return <Divider className={classNames.divider} />;\n }\n }\n}\n\nexport function BlogRenderer({\n blog,\n components = {},\n classNames = {},\n}: {\n blog: Blog;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n}) {\n const Media = components.media ?? DefaultMedia;\n const Wrapper = components.wrapper;\n\n // Distribute `blog.generatedMedia` across `blog.blocks` at positions\n // floor(i * N / K) — first media at top of body, the rest spread evenly.\n // Images placed in the text render as their own `image` blocks instead.\n const slots = interleaveMedia(blog.blocks, blog.generatedMedia);\n\n const content = (\n <>\n {slots.map((slot) =>\n slot.kind === 'block' ? (\n <BlockRenderer\n key={`b:${slot.index}`}\n block={slot.block}\n components={components}\n classNames={classNames}\n />\n ) : (\n <Media key={`m:${slot.media.id}`} media={slot.media} />\n ),\n )}\n </>\n );\n\n if (Wrapper) return <Wrapper blog={blog}>{content}</Wrapper>;\n return content;\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport { hasTextImage } from '../media-placement';\nimport { BlogRenderer, type BlockComponents } from './BlogRenderer';\n\nexport type BlogDetailComponents = {\n /**\n * Renders above the body. Default: og_image → h1 → time → description.\n * Override to add tags, author chip, share buttons, read time, etc.\n */\n Header?: (props: {\n blog: Blog;\n formatDate: (iso: string) => string;\n }) => React.ReactNode;\n /** Renders below the body. Default: nothing. */\n Footer?: (props: { blog: Blog }) => React.ReactNode;\n /** Forwarded to <BlogRenderer components={...} />. */\n blocks?: BlockComponents;\n};\n\nconst defaultFormatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString();\n\nfunction DefaultHeader({\n blog,\n formatDate,\n}: {\n blog: Blog;\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n // Bare semantic HTML — no classNames, no styles. Consumers wrap the whole\n // BlogDetail in their own `.prose` / typography container to handle styling.\n //\n // When the body shows any image — attachments spread by <BlogRenderer>\n // (the first lands at the top of the body, right under this header) or\n // images placed in the text — `og_image` is one of them, so rendering it\n // here too would show it twice. It then stays a meta-only field (still\n // used by OpenGraph / Twitter card previews via `generateBlogMetadata`).\n const hasInlineMedia =\n (blog.generatedMedia?.length ?? 0) > 0 || hasTextImage(blog.blocks);\n // eslint-disable-next-line @next/next/no-img-element\n const heroImage =\n !hasInlineMedia && blog.og_image ? (\n <img src={blog.og_image} alt={blog.title} />\n ) : null;\n return (\n <>\n {heroImage}\n <h1>{blog.title}</h1>\n {blog.published_at && (\n <time dateTime={blog.published_at}>\n {formatDate(blog.published_at)}\n </time>\n )}\n {blog.description && <p>{blog.description}</p>}\n </>\n );\n}\n\nexport function BlogDetail({\n blog,\n components,\n formatDate = defaultFormatDate,\n}: {\n blog: Blog;\n components?: BlogDetailComponents;\n formatDate?: (iso: string) => string;\n}): React.JSX.Element {\n const Header = components?.Header ?? DefaultHeader;\n const Footer = components?.Footer;\n return (\n <article>\n <Header blog={blog} formatDate={formatDate} />\n <BlogRenderer blog={blog} components={components?.blocks} />\n {Footer ? <Footer blog={blog} /> : null}\n </article>\n );\n}\n","import type { Blog } from './types';\n\nexport type ClientOptions = {\n apiUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n};\n\nexport type ListBlogsOptions = {\n status?: 'DRAFT' | 'PUBLISHED';\n language?: string;\n};\n\nexport type ChaingrowClient = {\n getBlog(slug: string, language?: string): Promise<Blog | null>;\n getBlogById(id: string): Promise<Blog | null>;\n listBlogs(opts?: ListBlogsOptions): Promise<Blog[]>;\n};\n\n// Error codes that indicate a transient network hiccup rather than a broken\n// setup: flaky DNS resolution (musl containers throw EAI_AGAIN under load),\n// dropped or timed-out connections. Anything else (bad host, TLS failure)\n// fails fast so misconfiguration stays visible.\nconst TRANSIENT_NETWORK_CODES = new Set([\n 'EAI_AGAIN',\n 'ETIMEDOUT',\n 'ECONNRESET',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n]);\n\nconst NETWORK_ATTEMPTS = 3;\n\nfunction causeCode(err: unknown): string | undefined {\n const cause = err instanceof Error ? err.cause : undefined;\n return cause && typeof cause === 'object' && 'code' in cause\n ? String((cause as { code?: unknown }).code)\n : undefined;\n}\n\nexport function createClient(opts: ClientOptions): ChaingrowClient {\n const baseUrl = opts.apiUrl.replace(/\\/$/, '');\n const doFetch = opts.fetch ?? fetch;\n\n const request = async <T>(\n path: string,\n init?: RequestInit,\n ): Promise<T | null> => {\n const url = `${baseUrl}${path}`;\n const method = init?.method ?? 'GET';\n const buildInit = (): RequestInit => ({\n ...init,\n headers: {\n 'X-API-Key': opts.apiKey,\n Accept: 'application/json',\n ...(init?.headers ?? {}),\n },\n });\n\n let res: Response | undefined;\n for (let attempt = 1; attempt <= NETWORK_ATTEMPTS; attempt++) {\n try {\n res = await doFetch(url, buildInit());\n\n // Transparent one-shot retry on 429, honoring Retry-After (seconds).\n if (res.status === 429) {\n const waitSec = parseInt(res.headers.get('Retry-After') ?? '1', 10);\n await new Promise((resolve) =>\n setTimeout(resolve, Math.max(0, waitSec) * 1000),\n );\n res = await doFetch(url, buildInit());\n }\n break;\n } catch (err) {\n const code = causeCode(err);\n if (\n attempt < NETWORK_ATTEMPTS &&\n code !== undefined &&\n TRANSIENT_NETWORK_CODES.has(code)\n ) {\n await new Promise((resolve) => setTimeout(resolve, 250 * attempt));\n continue;\n }\n\n // `fetch failed` / DNS / connection errors land here. Node's default\n // message buries the host in `err.cause`, which makes integration\n // debugging miserable. Surface the URL and the override env var on\n // the top-level message so it appears in the consumer's server log.\n const detail = code\n ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ''}`\n : err instanceof Error\n ? err.message\n : String(err);\n const e = new Error(\n `chaingrow-blog: could not reach ${method} ${url} — ${detail}. ` +\n `Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`,\n );\n (e as Error & { cause?: unknown }).cause = err;\n throw e;\n }\n }\n if (!res) {\n throw new Error(`chaingrow-blog: ${method} ${url} — no response`);\n }\n\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `chaingrow-blog: ${method} ${url} failed (${res.status})`,\n );\n }\n return (await res.json()) as T;\n };\n\n return {\n async getBlog(slug, language) {\n const qs = language ? `?language=${encodeURIComponent(language)}` : '';\n return request<Blog>(\n `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`,\n );\n },\n\n async getBlogById(id) {\n return request<Blog>(`/api/v1/blogs/${encodeURIComponent(id)}`);\n },\n\n async listBlogs(listOpts = {}) {\n const collected: Blog[] = [];\n // Page through until the server-reported `total` is reached. Backends\n // without pagination return everything on the first request\n // (total === data.length), so this loop runs exactly once there.\n const MAX_PAGES = 100;\n for (let page = 0; page < MAX_PAGES; page++) {\n const params = new URLSearchParams();\n if (listOpts.status) params.set('status', listOpts.status);\n if (listOpts.language) params.set('language', listOpts.language);\n if (collected.length > 0)\n params.set('offset', String(collected.length));\n const qs = params.toString();\n const response = await request<{ data: Blog[]; total: number }>(\n `/api/v1/blogs${qs ? `?${qs}` : ''}`,\n );\n if (!response) break;\n const batch = response.data ?? [];\n collected.push(...batch);\n const total =\n typeof response.total === 'number' ? response.total : collected.length;\n if (batch.length === 0 || collected.length >= total) break;\n }\n // The language filter also runs server-side (API builds that predate\n // it ignore the query param) — keep this client-side pass as a\n // fallback; against a filtering server it's a no-op.\n if (listOpts.language) {\n return collected.filter((b) => b.language === listOpts.language);\n }\n return collected;\n },\n };\n}\n","import type { Blog, FaqItem } from './types';\n\n/**\n * Consumer-supplied extras for BlogPosting. Everything is optional — the\n * SDK doesn't know the customer's base URL, organisation name, or author\n * identity, so it just omits what it isn't told. Schema.org treats\n * `author`, `publisher`, and `url` as recommended but not required.\n */\nexport type BlogJsonLdOptions = {\n /** Absolute URL of the blog page (e.g. `https://example.com/blog/my-post`). */\n url?: string;\n author?: { name: string; type?: 'Person' | 'Organization'; url?: string };\n publisher?: { name: string; logo?: string };\n};\n\nconst Schema = Object.freeze({ context: 'https://schema.org' });\n\n/**\n * Build a Schema.org `BlogPosting` JSON-LD object from a chaingrow Blog.\n * Fields that aren't present on the Blog (description, og_image, …) are\n * omitted entirely so the resulting JSON has no `undefined` junk.\n */\nexport function buildBlogPostingSchema(\n blog: Blog,\n opts: BlogJsonLdOptions = {},\n): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@type': 'BlogPosting',\n headline: blog.title,\n datePublished: blog.published_at,\n };\n if (blog.description) schema.description = blog.description;\n if (blog.og_image) schema.image = blog.og_image;\n if (opts.url) {\n schema.url = opts.url;\n schema.mainEntityOfPage = opts.url;\n }\n if (opts.author) {\n schema.author = {\n '@type': opts.author.type ?? 'Organization',\n name: opts.author.name,\n ...(opts.author.url ? { url: opts.author.url } : {}),\n };\n }\n if (opts.publisher) {\n schema.publisher = {\n '@type': 'Organization',\n name: opts.publisher.name,\n ...(opts.publisher.logo\n ? { logo: { '@type': 'ImageObject', url: opts.publisher.logo } }\n : {}),\n };\n }\n return schema;\n}\n\n/**\n * Build a Schema.org `FAQPage` JSON-LD object from chaingrow's `{question, answer}[]`\n * pairs. Returns `null` for empty input so callers can skip it with a single\n * `if (faqSchema) items.push(faqSchema)` / `filter(Boolean)`.\n */\nexport function buildFAQPageSchema(\n faq: FaqItem[] | null | undefined,\n): Record<string, unknown> | null {\n if (!faq || faq.length === 0) return null;\n return {\n '@type': 'FAQPage',\n mainEntity: faq.map(({ question, answer }) => ({\n '@type': 'Question',\n name: question,\n acceptedAnswer: { '@type': 'Answer', text: answer },\n })),\n };\n}\n\n/**\n * Combines BlogPosting + FAQPage + any LLM-generated `structured_data.jsonLd`\n * extras into a flat array ready for `@graph`. Tolerant of missing/malformed\n * `structured_data` — if the backend ever sends something unexpected we just\n * skip it rather than crashing the consumer's page.\n */\nexport function buildBlogJsonLd(\n blog: Blog,\n opts: BlogJsonLdOptions = {},\n): Record<string, unknown>[] {\n const items: Record<string, unknown>[] = [buildBlogPostingSchema(blog, opts)];\n const faq = buildFAQPageSchema(blog.faq);\n if (faq) items.push(faq);\n items.push(...extractLlmJsonLd(blog.structured_data));\n return items;\n}\n\n/** Schema-org context string — exposed so `<JsonLdGraph>` and any\n * bring-your-own renderer use the same value. */\nexport const SCHEMA_ORG_CONTEXT = Schema.context;\n\n/**\n * Pull the JSON-LD array out of `blog.structured_data`. Per\n * `structuredDataOutputSchema` in @chaingrow/contracts the expected shape is\n * `{ jsonLd: Array<Record<string, unknown>> }`, but historical rows may be\n * `null`, a plain object, or even malformed — normalise to an empty array in\n * every degenerate case so the caller gets a safe spread target.\n */\nfunction extractLlmJsonLd(\n sd: Blog['structured_data'],\n): Record<string, unknown>[] {\n if (!sd || typeof sd !== 'object') return [];\n const ld = (sd as { jsonLd?: unknown }).jsonLd;\n if (!Array.isArray(ld)) return [];\n return ld.filter(\n (entry): entry is Record<string, unknown> =>\n typeof entry === 'object' && entry !== null && !Array.isArray(entry),\n );\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport {\n buildBlogJsonLd,\n SCHEMA_ORG_CONTEXT,\n type BlogJsonLdOptions,\n} from '../json-ld';\n\n/**\n * Render an array of Schema.org graph items as a single\n * `<script type=\"application/ld+json\">` tag with a merged `@graph`.\n * Google treats one `@graph` script as equivalent to N separate scripts, and\n * a single tag keeps the DOM uncluttered for consumers who already ship their\n * own schemas (Organization, breadcrumbs, WebSite, …).\n */\nexport function JsonLdGraph({\n items,\n}: {\n items: Record<string, unknown>[];\n}): React.JSX.Element | null {\n if (!items || items.length === 0) return null;\n const payload = JSON.stringify({\n '@context': SCHEMA_ORG_CONTEXT,\n '@graph': items,\n });\n return (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: payload }}\n />\n );\n}\n\n/**\n * Default-batteries JSON-LD for a chaingrow Blog: emits BlogPosting +\n * FAQPage (if the post has FAQs) + any LLM-generated extras from\n * `structured_data.jsonLd`. Consumers who already ship their own graph items\n * (breadcrumbs, Organization, …) pass them via `extraSchemas` and they end\n * up in the same `@graph`.\n */\nexport function BlogJsonLd({\n blog,\n extraSchemas,\n ...opts\n}: {\n blog: Blog;\n extraSchemas?: Record<string, unknown>[];\n} & BlogJsonLdOptions): React.JSX.Element | null {\n const items = [...(extraSchemas ?? []), ...buildBlogJsonLd(blog, opts)];\n return <JsonLdGraph items={items} />;\n}\n","import type { MetadataRoute } from 'next';\nimport type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst DEFAULT_API_URL = 'https://api.chaingrow.de';\n\n/**\n * Default ISR window for `getBlog` / `listBlogs` when the consumer doesn't\n * set `revalidate` explicitly. 7 days — blog posts are effectively immutable\n * once published, so caching for a week is sensible, and the webhook\n * (`chaingrow-blog/next/webhook`) invalidates the cache immediately on\n * publish anyway so this window is really just a safety net for when the\n * webhook isn't mounted or misses a delivery.\n *\n * Override per-call with `{ revalidate: N }` (seconds), `{ revalidate: 0 }`\n * (never cache — every request hits ChainGrow), or `{ revalidate: false }`\n * (cache forever, only the webhook can invalidate).\n */\nconst DEFAULT_REVALIDATE_SECONDS = 60 * 60 * 24 * 7;\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl =\n opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)',\n );\n }\n return { apiUrl, apiKey };\n};\n\nconst buildFetch = (tag: string, revalidate: number | false | undefined) => {\n const effectiveRevalidate: number | false =\n revalidate === undefined ? DEFAULT_REVALIDATE_SECONDS : revalidate;\n return ((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, {\n ...init,\n next: {\n tags: ['chaingrow:blog', tag],\n revalidate: effectiveRevalidate,\n },\n } as RequestInit)) as typeof fetch;\n};\n\nexport async function getBlog(\n slug: string,\n language?: string,\n opts: NextClientOptions = {},\n): Promise<Blog | null> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch(`chaingrow:blog:${slug}`, opts.revalidate),\n });\n return client.getBlog(slug, language);\n}\n\nexport async function listBlogs(\n listOpts: { status?: 'DRAFT' | 'PUBLISHED'; language?: string } = {},\n opts: NextClientOptions = {},\n): Promise<Blog[]> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch('chaingrow:blog:list', opts.revalidate),\n });\n return client.listBlogs(listOpts);\n}\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Returns ChainGrow blog entries already shaped for Next.js's\n * `app/sitemap.ts` (`MetadataRoute.Sitemap`). Spread the result into your\n * sitemap array — no mapping required at the call site.\n *\n * ```ts\n * // app/sitemap.ts\n * import type { MetadataRoute } from 'next';\n * import { getBlogSitemapEntries } from 'chaingrow-blog/next';\n *\n * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n * const blogs = await getBlogSitemapEntries({\n * siteUrl: 'https://example.com',\n * pathPrefix: '/blog',\n * });\n * return [\n * { url: 'https://example.com', lastModified: new Date() },\n * ...blogs,\n * ];\n * }\n * ```\n *\n * For news sitemaps, image sitemaps, or any case that needs the full blog\n * shape (title, language, attached media), call `listBlogs()` directly.\n */\nexport async function getBlogSitemapEntries(opts: {\n siteUrl: string;\n pathPrefix?: string;\n language?: string;\n} & NextClientOptions): Promise<MetadataRoute.Sitemap> {\n const baseUrl = trimTrailingSlash(opts.siteUrl);\n const pathPrefix = normalizePathPrefix(opts.pathPrefix ?? '/blog');\n const blogs = await listBlogs(\n { status: 'PUBLISHED', language: opts.language },\n {\n apiKey: opts.apiKey,\n apiUrl: opts.apiUrl,\n revalidate: opts.revalidate,\n },\n );\n return blogs.map((blog) => ({\n url: `${baseUrl}${pathPrefix}/${blog.slug}`,\n lastModified: new Date(blog.published_at),\n }));\n}\n\nexport type { Blog } from '../types';\nexport {\n generateBlogMetadata,\n generateBlogIndexMetadata,\n type BlogAlternatesOptions,\n type BlogMetadataOptions,\n type BlogIndexMetadataOptions,\n} from './metadata';\nexport { BlogPage, generateBlogPageMetadata } from './BlogPage';\nexport type { BlogPageRouteParams } from './BlogPage';\n","import type { Blog } from './types';\n\nexport type BlogCard = {\n id: string;\n href: string;\n title: string;\n description: string | null;\n og_image: string | null;\n published_at: string | null;\n language?: string;\n};\n\nexport type ToBlogCardOptions = {\n /** URL prefix for the generated `href`. Defaults to `/blog`. */\n pathPrefix?: string;\n /**\n * Full control over the generated `href` — takes precedence over\n * `pathPrefix`. Use this when the route depends on more than a static\n * prefix, e.g. locale-prefixed routes:\n *\n * ```ts\n * hrefFor: (blog) =>\n * blog.language === 'de' ? `/blog/${blog.slug}` : `/${blog.language}/blog/${blog.slug}`\n * ```\n */\n hrefFor?: (blog: Blog) => string;\n};\n\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Project a chaingrow `Blog` into the small `BlogCard` shape used by\n * `<BlogIndex>` and `mergeBlogs`. Hybrid integrators can map their own\n * legacy posts into the same shape (`extra` prop on `<BlogIndex>`) and\n * the two sources render through the same Card slot.\n */\nexport function toBlogCard(blog: Blog, opts: ToBlogCardOptions = {}): BlogCard {\n const prefix = normalizePathPrefix(opts.pathPrefix ?? '/blog');\n return {\n id: blog.id,\n href: opts.hrefFor ? opts.hrefFor(blog) : `${prefix}/${blog.slug}`,\n title: blog.title,\n description: blog.description,\n og_image: blog.og_image,\n published_at: blog.published_at,\n language: blog.language,\n };\n}\n\n/**\n * Merge a list of chaingrow cards with extra (legacy) cards and return a\n * single list sorted newest-first by `published_at`. Cards without a\n * `published_at` sort to the end.\n */\nexport function mergeBlogs(\n chaingrow: BlogCard[],\n extra: BlogCard[] = [],\n): BlogCard[] {\n const combined = [...chaingrow, ...extra];\n combined.sort((a, b) => {\n const ta = a.published_at ? Date.parse(a.published_at) : 0;\n const tb = b.published_at ? Date.parse(b.published_at) : 0;\n return tb - ta;\n });\n return combined;\n}\n","import * as React from 'react';\nimport { listBlogs, type NextClientOptions } from '../next/index';\nimport { toBlogCard, type BlogCard } from '../blog-cards';\nimport type { Blog } from '../types';\n\nexport type { BlogCard };\n\nexport type BlogIndexFormat = 'bare' | 'minimalist' | 'list';\n\nexport type BlogIndexProps = {\n /** Visual layout. `minimalist` (default) is a stark monochrome grid:\n * square image, bold title, gray date — black-on-white in light mode,\n * white-on-black in dark mode, no borders or shadows. `list` is a\n * single-column feed with a thumbnail beside each entry. `bare` is\n * unstyled semantic HTML for users wiring their own design. All three\n * are emitted with self-contained styles — no CSS framework needed.\n * For full design control, skip this component entirely and compose\n * `listBlogs()` + your own JSX. */\n format?: BlogIndexFormat;\n /** Filter ChainGrow posts to a single language code (e.g. `'de'`, `'en'`). */\n language?: string;\n /** URL prefix for the generated card hrefs. Defaults to `/blog`.\n * On locale-prefixed sites, derive it from the active locale so EN cards\n * link to the EN route: `pathPrefix={locale === 'de' ? '/blog' : `/${locale}/blog`}`. */\n pathPrefix?: string;\n /** Full control over each card's href — takes precedence over `pathPrefix`.\n * Use when the route depends on more than a static prefix, e.g.\n * `hrefFor={(b) => (b.language === 'de' ? `/blog/${b.slug}` : `/${b.language}/blog/${b.slug}`)}`. */\n hrefFor?: (blog: Blog) => string;\n /** Override the date formatter shown on each card. */\n formatDate?: (iso: string) => string;\n} & NextClientOptions;\n\nconst defaultFormatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString();\n\n/**\n * Default-batteries blog list. Calls `listBlogs()` for the configured language\n * and renders one of three preset layouts. For full design control use\n * `listBlogs()` directly and write your own page.\n */\nexport async function BlogIndex({\n format = 'minimalist',\n language,\n pathPrefix,\n hrefFor,\n formatDate = defaultFormatDate,\n apiKey,\n apiUrl,\n revalidate,\n}: BlogIndexProps = {}): Promise<React.JSX.Element> {\n const blogs = await listBlogs(\n { status: 'PUBLISHED', language },\n { apiKey, apiUrl, revalidate },\n );\n const cards = blogs.map((b) => toBlogCard(b, { pathPrefix, hrefFor }));\n\n if (cards.length === 0) {\n return <EmptyState />;\n }\n\n if (format === 'bare') return <BareLayout cards={cards} formatDate={formatDate} />;\n if (format === 'list') return <ListLayout cards={cards} formatDate={formatDate} />;\n return <MinimalistLayout cards={cards} formatDate={formatDate} />;\n}\n\nfunction EmptyState(): React.JSX.Element {\n return <p>No posts yet.</p>;\n}\n\nfunction BareLayout({\n cards,\n formatDate,\n}: {\n cards: BlogCard[];\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n return (\n <ul>\n {cards.map((card) => (\n <li key={card.id}>\n <a href={card.href}>\n <h2>{card.title}</h2>\n </a>\n {card.published_at && (\n <time dateTime={card.published_at}>\n {formatDate(card.published_at)}\n </time>\n )}\n {card.description && <p>{card.description}</p>}\n </li>\n ))}\n </ul>\n );\n}\n\n// Color strategy: SDK never hardcodes light/dark. Title and date use\n// \\`color: inherit\\` / \\`currentColor\\` so they pick up whatever color\n// the host app's body/page is using. The host's theme toggle (Tailwind\n// \\`dark:\\` class, \\`data-theme\\`, \\`prefers-color-scheme\\`, anything) drives\n// the SDK automatically — no SDK-side theme knowledge needed.\n//\n// Date fades to 55% of currentColor — that gives the \"lighter gray\"\n// against any background (black-on-white → gray; white-on-black → gray).\n//\n// Image backgrounds use a translucent neutral so the placeholder is\n// visible on both light and dark host bodies without us picking a side.\nconst MINIMALIST_CSS = `\n.cg-blog-min-container {\n max-width: 1400px;\n margin: 4rem auto;\n padding: 0 2rem;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.cg-blog-min-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n column-gap: 2rem;\n row-gap: 4rem;\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.cg-blog-min-grid > li { list-style: none; }\n.cg-blog-min-card {\n display: block;\n text-decoration: none;\n color: inherit;\n}\n.cg-blog-min-card:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 4px;\n border-radius: 4px;\n}\n.cg-blog-min-image {\n width: 100%;\n aspect-ratio: 1 / 1;\n object-fit: cover;\n display: block;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.08);\n margin-bottom: 1.25rem;\n transition: opacity 200ms ease;\n}\n.cg-blog-min-card:hover .cg-blog-min-image {\n opacity: 0.88;\n}\n.cg-blog-min-no-image {\n width: 100%;\n aspect-ratio: 1 / 1;\n background: rgba(128, 128, 128, 0.08);\n border-radius: 6px;\n margin-bottom: 1.25rem;\n}\n.cg-blog-min-title {\n font-size: 1.25rem;\n font-weight: 600;\n margin: 0 0 0.5rem;\n line-height: 1.35;\n color: inherit;\n letter-spacing: -0.01em;\n}\n.cg-blog-min-date {\n font-size: 0.8125rem;\n color: currentColor;\n opacity: 0.55;\n margin: 0;\n font-weight: 400;\n}\n@media (max-width: 1024px) {\n .cg-blog-min-grid {\n grid-template-columns: repeat(2, 1fr);\n column-gap: 1.5rem;\n row-gap: 3rem;\n }\n}\n@media (max-width: 640px) {\n .cg-blog-min-container {\n margin: 2.5rem auto;\n padding: 0 1.25rem;\n }\n .cg-blog-min-grid {\n grid-template-columns: 1fr;\n row-gap: 2.5rem;\n }\n .cg-blog-min-title { font-size: 1.125rem; }\n}\n`;\n\nfunction MinimalistLayout({\n cards,\n formatDate,\n}: {\n cards: BlogCard[];\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n return (\n <div className=\"cg-blog-min-container\">\n {/* eslint-disable-next-line react/no-danger */}\n <style dangerouslySetInnerHTML={{ __html: MINIMALIST_CSS }} />\n <ul className=\"cg-blog-min-grid\">\n {cards.map((card) => (\n <li key={card.id}>\n <a href={card.href} className=\"cg-blog-min-card\">\n {card.og_image ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={card.og_image}\n alt={card.title}\n className=\"cg-blog-min-image\"\n loading=\"lazy\"\n />\n ) : (\n <div className=\"cg-blog-min-no-image\" aria-hidden=\"true\" />\n )}\n <h2 className=\"cg-blog-min-title\">{card.title}</h2>\n {card.published_at && (\n <p className=\"cg-blog-min-date\">\n {formatDate(card.published_at)}\n </p>\n )}\n </a>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n\n// Same color strategy as the minimalist layout and the article CSS:\n// \\`color: inherit\\` / \\`currentColor\\` with opacity for muted variants, and\n// translucent neutrals for image placeholders — the host app's theme\n// system drives everything, no \\`prefers-color-scheme\\` rules needed.\nconst LIST_CSS = `\n.cg-blog-list { list-style: none; padding: 0; margin: 0; }\n.cg-blog-list-item {\n margin-bottom: 2rem;\n padding-bottom: 2rem;\n border-bottom: 1px solid currentColor;\n}\n.cg-blog-list-item { border-bottom-color: color-mix(in srgb, currentColor 15%, transparent); }\n.cg-blog-list-item:last-child {\n margin-bottom: 0;\n padding-bottom: 0;\n border-bottom: none;\n}\n.cg-blog-list-link {\n display: flex;\n gap: 1.5rem;\n text-decoration: none;\n color: inherit;\n align-items: flex-start;\n}\n.cg-blog-list-image {\n flex-shrink: 0;\n width: 12rem;\n height: 8rem;\n object-fit: cover;\n border-radius: 0.5rem;\n background: rgba(128, 128, 128, 0.08);\n transition: opacity 200ms ease;\n}\n.cg-blog-list-link:hover .cg-blog-list-image { opacity: 0.88; }\n.cg-blog-list-body { flex: 1; min-width: 0; }\n.cg-blog-list-date {\n font-size: 0.75rem;\n color: currentColor;\n opacity: 0.55;\n margin: 0 0 0.5rem 0;\n}\n.cg-blog-list-title {\n font-size: 1.5rem;\n font-weight: 700;\n margin: 0 0 0.5rem 0;\n line-height: 1.3;\n color: inherit;\n transition: opacity 150ms ease;\n}\n.cg-blog-list-link:hover .cg-blog-list-title { opacity: 0.7; }\n.cg-blog-list-description {\n font-size: 1rem;\n color: currentColor;\n opacity: 0.65;\n margin: 0;\n line-height: 1.6;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n@media (max-width: 640px) {\n .cg-blog-list-link { flex-direction: column; }\n .cg-blog-list-image { width: 100%; height: 12rem; }\n}\n`;\n\nfunction ListLayout({\n cards,\n formatDate,\n}: {\n cards: BlogCard[];\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n return (\n <>\n {/* eslint-disable-next-line react/no-danger */}\n <style dangerouslySetInnerHTML={{ __html: LIST_CSS }} />\n <ul className=\"cg-blog-list\">\n {cards.map((card) => (\n <li key={card.id} className=\"cg-blog-list-item\">\n <a href={card.href} className=\"cg-blog-list-link\">\n {card.og_image && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={card.og_image}\n alt={card.title}\n className=\"cg-blog-list-image\"\n />\n )}\n <div className=\"cg-blog-list-body\">\n {card.published_at && (\n <p className=\"cg-blog-list-date\">\n {formatDate(card.published_at)}\n </p>\n )}\n <h2 className=\"cg-blog-list-title\">{card.title}</h2>\n {card.description && (\n <p className=\"cg-blog-list-description\">{card.description}</p>\n )}\n </div>\n </a>\n </li>\n ))}\n </ul>\n </>\n );\n}\n"],"mappings":"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","names":["media","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","defaultFormatDate"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.ts","../../src/media-placement.ts","../../src/react/defaults.tsx","../../src/react/BlogRenderer.tsx","../../src/react/BlogDetail.tsx","../../src/client.ts","../../src/json-ld.ts","../../src/react/BlogJsonLd.tsx","../../src/next/index.ts","../../src/blog-cards.ts","../../src/react/BlogIndex.tsx"],"sourcesContent":["export {\n BlogRenderer,\n BlockRenderer,\n type BlockComponents,\n type BlockClassNames,\n} from './BlogRenderer';\nexport { BlogDetail, type BlogDetailComponents } from './BlogDetail';\nexport {\n BlogIndex,\n type BlogIndexFormat,\n type BlogIndexProps,\n} from './BlogIndex';\nexport { BlogJsonLd, JsonLdGraph } from './BlogJsonLd';\nexport type { BlogJsonLdOptions } from '../json-ld';\nexport {\n toBlogCard,\n mergeBlogs,\n type BlogCard,\n type ToBlogCardOptions,\n} from '../blog-cards';\nexport {\n distributeMediaPositions,\n interleaveMedia,\n type InterleavedSlot,\n} from '../media-placement';\nexport * from './defaults';\nexport type {\n Blog,\n Block,\n BlockType,\n Inline,\n ListItem,\n Mark,\n FaqItem,\n SocialPost,\n EmbeddedMedia,\n} from '../types';\n","import type { Block, EmbeddedMedia } from './types';\n\n/**\n * Compute the block-index insertion point for each media item when\n * distributing `mediaCount` attachments across `blockCount` blocks.\n *\n * Formula: media `i` goes at insertion index `floor(i * blockCount / mediaCount)`.\n * An insertion index of `0` means \"before block 0\" (top of body); `blockCount`\n * means \"after the last block\".\n *\n * Examples (blockCount = 10):\n * mediaCount = 1 → [0] (single media at top)\n * mediaCount = 2 → [0, 5] (top + middle)\n * mediaCount = 3 → [0, 3, 6] (top, 1/3, 2/3)\n * mediaCount = 4 → [0, 2, 5, 7] (every quarter)\n *\n * When `blockCount` is 0 every media falls at position 0 and stacks at the\n * top. When two media round to the same position they both land there and\n * render consecutively; this is deterministic but not always visually ideal\n * for very small `blockCount / mediaCount` ratios — assume content has\n * enough blocks to spread across.\n */\nexport function distributeMediaPositions(\n blockCount: number,\n mediaCount: number,\n): number[] {\n if (mediaCount <= 0) return [];\n if (blockCount <= 0) return Array.from({ length: mediaCount }, () => 0);\n return Array.from({ length: mediaCount }, (_, i) =>\n Math.floor((i * blockCount) / mediaCount),\n );\n}\n\n/**\n * Ids of the attachments already placed in the text as `image` blocks\n * (`mediaId`), list items included. `interleaveMedia` skips those, so an\n * image the author placed never shows a second time.\n */\nexport function placedMediaIds(blocks: Block[]): Set<string> {\n const ids = new Set<string>();\n const walk = (list: Block[]) => {\n for (const b of list) {\n if (b.type === 'image' && b.mediaId) ids.add(b.mediaId);\n else if (b.type === 'bulletList' || b.type === 'orderedList') {\n for (const item of b.items) walk(item.content);\n }\n }\n };\n walk(blocks);\n return ids;\n}\n\n/** Whether the text itself contains an image (list items included). */\nexport function hasTextImage(blocks: Block[]): boolean {\n const walk = (list: Block[]): boolean =>\n list.some(\n (b) =>\n b.type === 'image' ||\n ((b.type === 'bulletList' || b.type === 'orderedList') &&\n b.items.some((item) => walk(item.content))),\n );\n return walk(blocks);\n}\n\n/** One interleaved slot in a blog's body — either a block or a media item. */\nexport type InterleavedSlot =\n | { kind: 'block'; block: Block; index: number }\n | { kind: 'media'; media: EmbeddedMedia; index: number };\n\n/**\n * Walk `blocks` once, splicing in `media` at the positions returned by\n * `distributeMediaPositions`. Returns a flat array of typed slots that\n * `<BlogRenderer>` can map over directly. Exposed so consumers who need a\n * custom layout (e.g. wrapping each section in a `<section>`) can use the\n * same placement logic without re-implementing it. Media already placed in\n * the text as an `image` block is skipped (see `placedMediaIds`).\n */\nexport function interleaveMedia(\n blocks: Block[],\n media: EmbeddedMedia[] | undefined,\n): InterleavedSlot[] {\n const placed = placedMediaIds(blocks);\n const mediaList = (media ?? []).filter((m) => !placed.has(m.id));\n const positions = distributeMediaPositions(blocks.length, mediaList.length);\n\n const slots: InterleavedSlot[] = [];\n let mediaCursor = 0;\n for (let b = 0; b <= blocks.length; b++) {\n while (mediaCursor < mediaList.length && positions[mediaCursor] === b) {\n const media = mediaList[mediaCursor]!;\n slots.push({ kind: 'media', media, index: mediaCursor });\n mediaCursor++;\n }\n if (b < blocks.length) {\n const block = blocks[b]!;\n slots.push({ kind: 'block', block, index: b });\n }\n }\n return slots;\n}\n","import * as React from 'react';\nimport type { Block, EmbeddedMedia, ListItem } from '../types';\n\n/**\n * Default components for each Block type. All are headless — no\n * classNames baked in. Consumers customize via:\n * - `classNames={{ heading: \"...\" }}` on `<BlogRenderer>` — threads\n * through to `className` on the Default component below.\n * - `components={{ heading: MyHeading }}` — full replacement.\n */\n\nexport const DefaultHeading = ({\n level,\n children,\n className,\n}: {\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n}) => {\n if (level === 1) return <h1 className={className}>{children}</h1>;\n if (level === 3) return <h3 className={className}>{children}</h3>;\n return <h2 className={className}>{children}</h2>;\n};\n\nexport const DefaultParagraph = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <p className={className}>{children}</p>;\n\nexport const DefaultBulletList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ul className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ul>\n);\n\nexport const DefaultOrderedList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ol className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ol>\n);\n\nexport const DefaultBlockquote = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <blockquote className={className}>{children}</blockquote>;\n\nexport const DefaultCodeBlock = ({\n language,\n code,\n className,\n}: {\n language?: string;\n code: string;\n className?: string;\n}) => (\n <pre className={className}>\n <code className={language ? `language-${language}` : undefined}>{code}</code>\n </pre>\n);\n\nexport const DefaultImage = ({\n src,\n alt,\n caption,\n width,\n height,\n className,\n}: {\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n}) => {\n const img = (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={src} alt={alt} width={width} height={height} />\n );\n if (caption) {\n return (\n <figure className={className}>\n {img}\n <figcaption>{caption}</figcaption>\n </figure>\n );\n }\n return <span className={className}>{img}</span>;\n};\n\nexport const DefaultDivider = ({ className }: { className?: string }) => (\n <hr className={className} />\n);\n\n/**\n * Default renderer for a single embedded media item. Bare HTML — no\n * classNames, no styles. Override via `components.media` on `<BlogRenderer>`.\n * `alt` falls back to the generation prompt, which is a sensible description\n * string since it was the LLM input that produced the image.\n */\nexport const DefaultMedia = ({ media }: { media: EmbeddedMedia }) => {\n if (media.media_type === 'VIDEO') {\n return (\n <video\n src={media.media_url}\n controls\n playsInline\n preload=\"metadata\"\n />\n );\n }\n // eslint-disable-next-line @next/next/no-img-element\n return <img src={media.media_url} alt={media.prompt} />;\n};\n","import * as React from 'react';\nimport type {\n Blog,\n Block,\n BlockType,\n EmbeddedMedia,\n Inline,\n ListItem,\n Mark,\n} from '../types';\nimport { interleaveMedia } from '../media-placement';\nimport {\n DefaultHeading,\n DefaultParagraph,\n DefaultBulletList,\n DefaultOrderedList,\n DefaultBlockquote,\n DefaultCodeBlock,\n DefaultImage,\n DefaultDivider,\n DefaultMedia,\n} from './defaults';\n\n/**\n * Override map for per-block components. Keys are the discriminants\n * of `Block`. Consumers pass any subset; unspecified blocks render\n * via the matching `Default*` component.\n *\n * v0.3.0 breaking change: the keys changed to match the new Block\n * union. If you upgraded from 0.2.x, rename `list` → `bulletList` /\n * `orderedList`, `quote` → `blockquote`, `code` → `codeBlock`, drop\n * `image_placeholder` (images are now first-class via `image`).\n */\nexport type BlockComponents = Partial<{\n heading: React.ComponentType<{\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n }>;\n paragraph: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n bulletList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n orderedList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n blockquote: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n codeBlock: React.ComponentType<{\n language?: string;\n code: string;\n className?: string;\n }>;\n image: React.ComponentType<{\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n }>;\n divider: React.ComponentType<{ className?: string }>;\n /** Renders a single attached image/video when `<BlogRenderer>` distributes\n * `blog.generatedMedia` across the body. Default renders a bare\n * `<img>` or `<video controls>`. */\n media: React.ComponentType<{ media: EmbeddedMedia }>;\n wrapper: React.ComponentType<{ children: React.ReactNode; blog: Blog }>;\n}>;\n\n/** Simple Tailwind-style className override map. Applied to the\n * default component for each block type. `components` override takes\n * precedence — if you replace the component, className won't reach it\n * unless your override handles it. */\nexport type BlockClassNames = Partial<Record<BlockType, string>>;\n\nconst MARK_ORDER: Mark['type'][] = [\n 'bold',\n 'italic',\n 'strike',\n 'code',\n 'link',\n];\n\nconst BREAK = 'br' as const;\n\n/**\n * A line break inside a block arrives as \"\\n\" in a text run and becomes\n * a `<br>`. Breaks at the start or end of the block are dropped and\n * several in a row count as one, so a break never adds an empty line.\n */\nfunction withBreaks(inline: Inline[]): (Inline | typeof BREAK)[] {\n const out: (Inline | typeof BREAK)[] = [];\n for (const run of inline) {\n const parts = (typeof run.text === 'string' ? run.text : '').split('\\n');\n parts.forEach((text, i) => {\n if (i > 0 && out.length > 0 && out[out.length - 1] !== BREAK) {\n out.push(BREAK);\n }\n if (text && !(out[out.length - 1] === BREAK && !text.trim())) {\n out.push({ ...run, text });\n }\n });\n }\n if (out[out.length - 1] === BREAK) out.pop();\n return out;\n}\n\n/** Render an array of Inline runs into text+mark JSX. */\nfunction InlineRenderer({ inline }: { inline: Inline[] }) {\n return (\n <>\n {withBreaks(inline).map((run, i) =>\n run === BREAK ? <br key={i} /> : <InlineRun key={i} run={run} />,\n )}\n </>\n );\n}\n\nfunction InlineRun({ run }: { run: Inline }) {\n const marks = run.marks\n ? [...run.marks].sort(\n (a, b) => MARK_ORDER.indexOf(b.type) - MARK_ORDER.indexOf(a.type),\n )\n : [];\n let node: React.ReactNode = run.text;\n for (const mark of marks) {\n switch (mark.type) {\n case 'bold':\n node = <strong>{node}</strong>;\n break;\n case 'italic':\n node = <em>{node}</em>;\n break;\n case 'strike':\n node = <s>{node}</s>;\n break;\n case 'code':\n node = <code>{node}</code>;\n break;\n case 'link':\n node = (\n <a href={mark.href} rel=\"noopener noreferrer\">\n {node}\n </a>\n );\n break;\n }\n }\n return <>{node}</>;\n}\n\nexport function BlockRenderer({\n block,\n components = {},\n classNames = {},\n renderBlock,\n}: {\n block: Block;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n renderBlock?: (b: Block, i: number) => React.ReactNode;\n}) {\n // Parent supplies a renderBlock callback so list items can recurse\n // into the same switch. When not provided (top-level call), wire it\n // to a local closure.\n const recurse: (b: Block, i: number) => React.ReactNode =\n renderBlock ??\n ((b, i) => (\n <BlockRenderer\n key={i}\n block={b}\n components={components}\n classNames={classNames}\n renderBlock={recurse}\n />\n ));\n\n switch (block.type) {\n case 'heading': {\n const Heading = components.heading ?? DefaultHeading;\n return (\n <Heading level={block.level} className={classNames.heading}>\n <InlineRenderer inline={block.content} />\n </Heading>\n );\n }\n case 'paragraph': {\n const Paragraph = components.paragraph ?? DefaultParagraph;\n return (\n <Paragraph className={classNames.paragraph}>\n <InlineRenderer inline={block.content} />\n </Paragraph>\n );\n }\n case 'bulletList': {\n const BulletList = components.bulletList ?? DefaultBulletList;\n return (\n <BulletList\n items={block.items}\n className={classNames.bulletList}\n renderBlock={recurse}\n />\n );\n }\n case 'orderedList': {\n const OrderedList = components.orderedList ?? DefaultOrderedList;\n return (\n <OrderedList\n items={block.items}\n className={classNames.orderedList}\n renderBlock={recurse}\n />\n );\n }\n case 'blockquote': {\n const Blockquote = components.blockquote ?? DefaultBlockquote;\n return (\n <Blockquote className={classNames.blockquote}>\n <InlineRenderer inline={block.content} />\n </Blockquote>\n );\n }\n case 'codeBlock': {\n const CodeBlock = components.codeBlock ?? DefaultCodeBlock;\n return (\n <CodeBlock\n language={block.language}\n code={block.code}\n className={classNames.codeBlock}\n />\n );\n }\n case 'image': {\n const ImageComp = components.image ?? DefaultImage;\n return (\n <ImageComp\n src={block.src}\n alt={block.alt}\n caption={block.caption}\n width={block.width}\n height={block.height}\n mediaId={block.mediaId}\n className={classNames.image}\n />\n );\n }\n case 'divider': {\n const Divider = components.divider ?? DefaultDivider;\n return <Divider className={classNames.divider} />;\n }\n }\n}\n\nexport function BlogRenderer({\n blog,\n components = {},\n classNames = {},\n}: {\n blog: Blog;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n}) {\n const Media = components.media ?? DefaultMedia;\n const Wrapper = components.wrapper;\n\n // Distribute `blog.generatedMedia` across `blog.blocks` at positions\n // floor(i * N / K) — first media at top of body, the rest spread evenly.\n // Images placed in the text render as their own `image` blocks instead.\n const slots = interleaveMedia(blog.blocks, blog.generatedMedia);\n\n const content = (\n <>\n {slots.map((slot) =>\n slot.kind === 'block' ? (\n <BlockRenderer\n key={`b:${slot.index}`}\n block={slot.block}\n components={components}\n classNames={classNames}\n />\n ) : (\n <Media key={`m:${slot.media.id}`} media={slot.media} />\n ),\n )}\n </>\n );\n\n if (Wrapper) return <Wrapper blog={blog}>{content}</Wrapper>;\n return content;\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport { hasTextImage } from '../media-placement';\nimport { BlogRenderer, type BlockComponents } from './BlogRenderer';\n\nexport type BlogDetailComponents = {\n /**\n * Renders above the body. Default: og_image → h1 → time → description.\n * Override to add tags, author chip, share buttons, read time, etc.\n */\n Header?: (props: {\n blog: Blog;\n formatDate: (iso: string) => string;\n }) => React.ReactNode;\n /** Renders below the body. Default: nothing. */\n Footer?: (props: { blog: Blog }) => React.ReactNode;\n /** Forwarded to <BlogRenderer components={...} />. */\n blocks?: BlockComponents;\n};\n\nconst defaultFormatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString();\n\nfunction DefaultHeader({\n blog,\n formatDate,\n}: {\n blog: Blog;\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n // Bare semantic HTML — no classNames, no styles. Consumers wrap the whole\n // BlogDetail in their own `.prose` / typography container to handle styling.\n //\n // When the body shows any image — attachments spread by <BlogRenderer>\n // (the first lands at the top of the body, right under this header) or\n // images placed in the text — `og_image` is one of them, so rendering it\n // here too would show it twice. It then stays a meta-only field (still\n // used by OpenGraph / Twitter card previews via `generateBlogMetadata`).\n const hasInlineMedia =\n (blog.generatedMedia?.length ?? 0) > 0 || hasTextImage(blog.blocks);\n // eslint-disable-next-line @next/next/no-img-element\n const heroImage =\n !hasInlineMedia && blog.og_image ? (\n <img src={blog.og_image} alt={blog.title} />\n ) : null;\n return (\n <>\n {heroImage}\n <h1>{blog.title}</h1>\n {blog.published_at && (\n <time dateTime={blog.published_at}>\n {formatDate(blog.published_at)}\n </time>\n )}\n {blog.description && <p>{blog.description}</p>}\n </>\n );\n}\n\nexport function BlogDetail({\n blog,\n components,\n formatDate = defaultFormatDate,\n}: {\n blog: Blog;\n components?: BlogDetailComponents;\n formatDate?: (iso: string) => string;\n}): React.JSX.Element {\n const Header = components?.Header ?? DefaultHeader;\n const Footer = components?.Footer;\n return (\n <article>\n <Header blog={blog} formatDate={formatDate} />\n <BlogRenderer blog={blog} components={components?.blocks} />\n {Footer ? <Footer blog={blog} /> : null}\n </article>\n );\n}\n","import type { Blog } from './types';\n\nexport type ClientOptions = {\n apiUrl: string;\n apiKey: string;\n fetch?: typeof fetch;\n};\n\nexport type ListBlogsOptions = {\n status?: 'DRAFT' | 'PUBLISHED';\n language?: string;\n};\n\nexport type ChaingrowClient = {\n getBlog(slug: string, language?: string): Promise<Blog | null>;\n getBlogById(id: string): Promise<Blog | null>;\n listBlogs(opts?: ListBlogsOptions): Promise<Blog[]>;\n};\n\n// Error codes that indicate a transient network hiccup rather than a broken\n// setup: flaky DNS resolution (musl containers throw EAI_AGAIN under load),\n// dropped or timed-out connections. Anything else (bad host, TLS failure)\n// fails fast so misconfiguration stays visible.\nconst TRANSIENT_NETWORK_CODES = new Set([\n 'EAI_AGAIN',\n 'ETIMEDOUT',\n 'ECONNRESET',\n 'UND_ERR_CONNECT_TIMEOUT',\n 'UND_ERR_SOCKET',\n]);\n\nconst NETWORK_ATTEMPTS = 3;\n\nfunction causeCode(err: unknown): string | undefined {\n const cause = err instanceof Error ? err.cause : undefined;\n return cause && typeof cause === 'object' && 'code' in cause\n ? String((cause as { code?: unknown }).code)\n : undefined;\n}\n\nexport function createClient(opts: ClientOptions): ChaingrowClient {\n const baseUrl = opts.apiUrl.replace(/\\/$/, '');\n const doFetch = opts.fetch ?? fetch;\n\n const request = async <T>(\n path: string,\n init?: RequestInit,\n ): Promise<T | null> => {\n const url = `${baseUrl}${path}`;\n const method = init?.method ?? 'GET';\n const buildInit = (): RequestInit => ({\n ...init,\n headers: {\n 'X-API-Key': opts.apiKey,\n Accept: 'application/json',\n ...(init?.headers ?? {}),\n },\n });\n\n let res: Response | undefined;\n for (let attempt = 1; attempt <= NETWORK_ATTEMPTS; attempt++) {\n try {\n res = await doFetch(url, buildInit());\n\n // Transparent one-shot retry on 429, honoring Retry-After (seconds).\n if (res.status === 429) {\n const waitSec = parseInt(res.headers.get('Retry-After') ?? '1', 10);\n await new Promise((resolve) =>\n setTimeout(resolve, Math.max(0, waitSec) * 1000),\n );\n res = await doFetch(url, buildInit());\n }\n break;\n } catch (err) {\n const code = causeCode(err);\n if (\n attempt < NETWORK_ATTEMPTS &&\n code !== undefined &&\n TRANSIENT_NETWORK_CODES.has(code)\n ) {\n await new Promise((resolve) => setTimeout(resolve, 250 * attempt));\n continue;\n }\n\n // `fetch failed` / DNS / connection errors land here. Node's default\n // message buries the host in `err.cause`, which makes integration\n // debugging miserable. Surface the URL and the override env var on\n // the top-level message so it appears in the consumer's server log.\n const detail = code\n ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ''}`\n : err instanceof Error\n ? err.message\n : String(err);\n const e = new Error(\n `chaingrow-blog: could not reach ${method} ${url} — ${detail}. ` +\n `Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`,\n );\n (e as Error & { cause?: unknown }).cause = err;\n throw e;\n }\n }\n if (!res) {\n throw new Error(`chaingrow-blog: ${method} ${url} — no response`);\n }\n\n if (res.status === 404) return null;\n if (!res.ok) {\n throw new Error(\n `chaingrow-blog: ${method} ${url} failed (${res.status})`,\n );\n }\n return (await res.json()) as T;\n };\n\n return {\n async getBlog(slug, language) {\n const qs = language ? `?language=${encodeURIComponent(language)}` : '';\n return request<Blog>(\n `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`,\n );\n },\n\n async getBlogById(id) {\n return request<Blog>(`/api/v1/blogs/${encodeURIComponent(id)}`);\n },\n\n async listBlogs(listOpts = {}) {\n const collected: Blog[] = [];\n // Page through until the server-reported `total` is reached. Backends\n // without pagination return everything on the first request\n // (total === data.length), so this loop runs exactly once there.\n const MAX_PAGES = 100;\n for (let page = 0; page < MAX_PAGES; page++) {\n const params = new URLSearchParams();\n if (listOpts.status) params.set('status', listOpts.status);\n if (listOpts.language) params.set('language', listOpts.language);\n if (collected.length > 0)\n params.set('offset', String(collected.length));\n const qs = params.toString();\n const response = await request<{ data: Blog[]; total: number }>(\n `/api/v1/blogs${qs ? `?${qs}` : ''}`,\n );\n if (!response) break;\n const batch = response.data ?? [];\n collected.push(...batch);\n const total =\n typeof response.total === 'number' ? response.total : collected.length;\n if (batch.length === 0 || collected.length >= total) break;\n }\n // The language filter also runs server-side (API builds that predate\n // it ignore the query param) — keep this client-side pass as a\n // fallback; against a filtering server it's a no-op.\n if (listOpts.language) {\n return collected.filter((b) => b.language === listOpts.language);\n }\n return collected;\n },\n };\n}\n","import type { Blog, FaqItem } from './types';\n\n/**\n * Consumer-supplied extras for BlogPosting. Everything is optional — the\n * SDK doesn't know the customer's base URL, organisation name, or author\n * identity, so it just omits what it isn't told. Schema.org treats\n * `author`, `publisher`, and `url` as recommended but not required.\n */\nexport type BlogJsonLdOptions = {\n /** Absolute URL of the blog page (e.g. `https://example.com/blog/my-post`). */\n url?: string;\n author?: { name: string; type?: 'Person' | 'Organization'; url?: string };\n publisher?: { name: string; logo?: string };\n};\n\nconst Schema = Object.freeze({ context: 'https://schema.org' });\n\n/**\n * Build a Schema.org `BlogPosting` JSON-LD object from a chaingrow Blog.\n * Fields that aren't present on the Blog (description, og_image, …) are\n * omitted entirely so the resulting JSON has no `undefined` junk.\n */\nexport function buildBlogPostingSchema(\n blog: Blog,\n opts: BlogJsonLdOptions = {},\n): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@type': 'BlogPosting',\n headline: blog.title,\n datePublished: blog.published_at,\n };\n if (blog.description) schema.description = blog.description;\n if (blog.og_image) schema.image = blog.og_image;\n if (opts.url) {\n schema.url = opts.url;\n schema.mainEntityOfPage = opts.url;\n }\n if (opts.author) {\n schema.author = {\n '@type': opts.author.type ?? 'Organization',\n name: opts.author.name,\n ...(opts.author.url ? { url: opts.author.url } : {}),\n };\n }\n if (opts.publisher) {\n schema.publisher = {\n '@type': 'Organization',\n name: opts.publisher.name,\n ...(opts.publisher.logo\n ? { logo: { '@type': 'ImageObject', url: opts.publisher.logo } }\n : {}),\n };\n }\n return schema;\n}\n\n/**\n * Build a Schema.org `FAQPage` JSON-LD object from chaingrow's `{question, answer}[]`\n * pairs. Returns `null` for empty input so callers can skip it with a single\n * `if (faqSchema) items.push(faqSchema)` / `filter(Boolean)`.\n */\nexport function buildFAQPageSchema(\n faq: FaqItem[] | null | undefined,\n): Record<string, unknown> | null {\n if (!faq || faq.length === 0) return null;\n return {\n '@type': 'FAQPage',\n mainEntity: faq.map(({ question, answer }) => ({\n '@type': 'Question',\n name: question,\n acceptedAnswer: { '@type': 'Answer', text: answer },\n })),\n };\n}\n\n/**\n * Combines BlogPosting + FAQPage + any LLM-generated `structured_data.jsonLd`\n * extras into a flat array ready for `@graph`. Tolerant of missing/malformed\n * `structured_data` — if the backend ever sends something unexpected we just\n * skip it rather than crashing the consumer's page.\n */\nexport function buildBlogJsonLd(\n blog: Blog,\n opts: BlogJsonLdOptions = {},\n): Record<string, unknown>[] {\n const items: Record<string, unknown>[] = [buildBlogPostingSchema(blog, opts)];\n const faq = buildFAQPageSchema(blog.faq);\n if (faq) items.push(faq);\n items.push(...extractLlmJsonLd(blog.structured_data));\n return items;\n}\n\n/** Schema-org context string — exposed so `<JsonLdGraph>` and any\n * bring-your-own renderer use the same value. */\nexport const SCHEMA_ORG_CONTEXT = Schema.context;\n\n/**\n * Pull the JSON-LD array out of `blog.structured_data`. Per\n * `structuredDataOutputSchema` in @chaingrow/contracts the expected shape is\n * `{ jsonLd: Array<Record<string, unknown>> }`, but historical rows may be\n * `null`, a plain object, or even malformed — normalise to an empty array in\n * every degenerate case so the caller gets a safe spread target.\n */\nfunction extractLlmJsonLd(\n sd: Blog['structured_data'],\n): Record<string, unknown>[] {\n if (!sd || typeof sd !== 'object') return [];\n const ld = (sd as { jsonLd?: unknown }).jsonLd;\n if (!Array.isArray(ld)) return [];\n return ld.filter(\n (entry): entry is Record<string, unknown> =>\n typeof entry === 'object' && entry !== null && !Array.isArray(entry),\n );\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport {\n buildBlogJsonLd,\n SCHEMA_ORG_CONTEXT,\n type BlogJsonLdOptions,\n} from '../json-ld';\n\n/**\n * Render an array of Schema.org graph items as a single\n * `<script type=\"application/ld+json\">` tag with a merged `@graph`.\n * Google treats one `@graph` script as equivalent to N separate scripts, and\n * a single tag keeps the DOM uncluttered for consumers who already ship their\n * own schemas (Organization, breadcrumbs, WebSite, …).\n */\nexport function JsonLdGraph({\n items,\n}: {\n items: Record<string, unknown>[];\n}): React.JSX.Element | null {\n if (!items || items.length === 0) return null;\n const payload = JSON.stringify({\n '@context': SCHEMA_ORG_CONTEXT,\n '@graph': items,\n });\n return (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: payload }}\n />\n );\n}\n\n/**\n * Default-batteries JSON-LD for a chaingrow Blog: emits BlogPosting +\n * FAQPage (if the post has FAQs) + any LLM-generated extras from\n * `structured_data.jsonLd`. Consumers who already ship their own graph items\n * (breadcrumbs, Organization, …) pass them via `extraSchemas` and they end\n * up in the same `@graph`.\n */\nexport function BlogJsonLd({\n blog,\n extraSchemas,\n ...opts\n}: {\n blog: Blog;\n extraSchemas?: Record<string, unknown>[];\n} & BlogJsonLdOptions): React.JSX.Element | null {\n const items = [...(extraSchemas ?? []), ...buildBlogJsonLd(blog, opts)];\n return <JsonLdGraph items={items} />;\n}\n","import type { MetadataRoute } from 'next';\nimport type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst DEFAULT_API_URL = 'https://api.chaingrow.de';\n\n/**\n * Default ISR window for `getBlog` / `listBlogs` when the consumer doesn't\n * set `revalidate` explicitly. 7 days — blog posts are effectively immutable\n * once published, so caching for a week is sensible, and the webhook\n * (`chaingrow-blog/next/webhook`) invalidates the cache immediately on\n * publish anyway so this window is really just a safety net for when the\n * webhook isn't mounted or misses a delivery.\n *\n * Override per-call with `{ revalidate: N }` (seconds), `{ revalidate: 0 }`\n * (never cache — every request hits ChainGrow), or `{ revalidate: false }`\n * (cache forever, only the webhook can invalidate).\n */\nconst DEFAULT_REVALIDATE_SECONDS = 60 * 60 * 24 * 7;\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl =\n opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)',\n );\n }\n return { apiUrl, apiKey };\n};\n\nconst buildFetch = (tag: string, revalidate: number | false | undefined) => {\n const effectiveRevalidate: number | false =\n revalidate === undefined ? DEFAULT_REVALIDATE_SECONDS : revalidate;\n return ((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, {\n ...init,\n next: {\n tags: ['chaingrow:blog', tag],\n revalidate: effectiveRevalidate,\n },\n } as RequestInit)) as typeof fetch;\n};\n\nexport async function getBlog(\n slug: string,\n language?: string,\n opts: NextClientOptions = {},\n): Promise<Blog | null> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch(`chaingrow:blog:${slug}`, opts.revalidate),\n });\n return client.getBlog(slug, language);\n}\n\nexport async function listBlogs(\n listOpts: { status?: 'DRAFT' | 'PUBLISHED'; language?: string } = {},\n opts: NextClientOptions = {},\n): Promise<Blog[]> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch('chaingrow:blog:list', opts.revalidate),\n });\n return client.listBlogs(listOpts);\n}\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Returns ChainGrow blog entries already shaped for Next.js's\n * `app/sitemap.ts` (`MetadataRoute.Sitemap`). Spread the result into your\n * sitemap array — no mapping required at the call site.\n *\n * ```ts\n * // app/sitemap.ts\n * import type { MetadataRoute } from 'next';\n * import { getBlogSitemapEntries } from 'chaingrow-blog/next';\n *\n * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n * const blogs = await getBlogSitemapEntries({\n * siteUrl: 'https://example.com',\n * pathPrefix: '/blog',\n * });\n * return [\n * { url: 'https://example.com', lastModified: new Date() },\n * ...blogs,\n * ];\n * }\n * ```\n *\n * For news sitemaps, image sitemaps, or any case that needs the full blog\n * shape (title, language, attached media), call `listBlogs()` directly.\n */\nexport async function getBlogSitemapEntries(opts: {\n siteUrl: string;\n pathPrefix?: string;\n language?: string;\n} & NextClientOptions): Promise<MetadataRoute.Sitemap> {\n const baseUrl = trimTrailingSlash(opts.siteUrl);\n const pathPrefix = normalizePathPrefix(opts.pathPrefix ?? '/blog');\n const blogs = await listBlogs(\n { status: 'PUBLISHED', language: opts.language },\n {\n apiKey: opts.apiKey,\n apiUrl: opts.apiUrl,\n revalidate: opts.revalidate,\n },\n );\n return blogs.map((blog) => ({\n url: `${baseUrl}${pathPrefix}/${blog.slug}`,\n lastModified: new Date(blog.published_at),\n }));\n}\n\nexport type { Blog } from '../types';\nexport {\n generateBlogMetadata,\n generateBlogIndexMetadata,\n type BlogAlternatesOptions,\n type BlogMetadataOptions,\n type BlogIndexMetadataOptions,\n} from './metadata';\nexport { BlogPage, generateBlogPageMetadata } from './BlogPage';\nexport type { BlogPageRouteParams } from './BlogPage';\n","import type { Blog } from './types';\n\nexport type BlogCard = {\n id: string;\n href: string;\n title: string;\n description: string | null;\n og_image: string | null;\n published_at: string | null;\n language?: string;\n};\n\nexport type ToBlogCardOptions = {\n /** URL prefix for the generated `href`. Defaults to `/blog`. */\n pathPrefix?: string;\n /**\n * Full control over the generated `href` — takes precedence over\n * `pathPrefix`. Use this when the route depends on more than a static\n * prefix, e.g. locale-prefixed routes:\n *\n * ```ts\n * hrefFor: (blog) =>\n * blog.language === 'de' ? `/blog/${blog.slug}` : `/${blog.language}/blog/${blog.slug}`\n * ```\n */\n hrefFor?: (blog: Blog) => string;\n};\n\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Project a chaingrow `Blog` into the small `BlogCard` shape used by\n * `<BlogIndex>` and `mergeBlogs`. Hybrid integrators can map their own\n * legacy posts into the same shape (`extra` prop on `<BlogIndex>`) and\n * the two sources render through the same Card slot.\n */\nexport function toBlogCard(blog: Blog, opts: ToBlogCardOptions = {}): BlogCard {\n const prefix = normalizePathPrefix(opts.pathPrefix ?? '/blog');\n return {\n id: blog.id,\n href: opts.hrefFor ? opts.hrefFor(blog) : `${prefix}/${blog.slug}`,\n title: blog.title,\n description: blog.description,\n og_image: blog.og_image,\n published_at: blog.published_at,\n language: blog.language,\n };\n}\n\n/**\n * Merge a list of chaingrow cards with extra (legacy) cards and return a\n * single list sorted newest-first by `published_at`. Cards without a\n * `published_at` sort to the end.\n */\nexport function mergeBlogs(\n chaingrow: BlogCard[],\n extra: BlogCard[] = [],\n): BlogCard[] {\n const combined = [...chaingrow, ...extra];\n combined.sort((a, b) => {\n const ta = a.published_at ? Date.parse(a.published_at) : 0;\n const tb = b.published_at ? Date.parse(b.published_at) : 0;\n return tb - ta;\n });\n return combined;\n}\n","import * as React from 'react';\nimport { listBlogs, type NextClientOptions } from '../next/index';\nimport { toBlogCard, type BlogCard } from '../blog-cards';\nimport type { Blog } from '../types';\n\nexport type { BlogCard };\n\nexport type BlogIndexFormat = 'bare' | 'minimalist' | 'list';\n\nexport type BlogIndexProps = {\n /** Visual layout. `minimalist` (default) is a stark monochrome grid:\n * square image, bold title, gray date — black-on-white in light mode,\n * white-on-black in dark mode, no borders or shadows. `list` is a\n * single-column feed with a thumbnail beside each entry. `bare` is\n * unstyled semantic HTML for users wiring their own design. All three\n * are emitted with self-contained styles — no CSS framework needed.\n * For full design control, skip this component entirely and compose\n * `listBlogs()` + your own JSX. */\n format?: BlogIndexFormat;\n /** Filter ChainGrow posts to a single language code (e.g. `'de'`, `'en'`). */\n language?: string;\n /** URL prefix for the generated card hrefs. Defaults to `/blog`.\n * On locale-prefixed sites, derive it from the active locale so EN cards\n * link to the EN route: `pathPrefix={locale === 'de' ? '/blog' : `/${locale}/blog`}`. */\n pathPrefix?: string;\n /** Full control over each card's href — takes precedence over `pathPrefix`.\n * Use when the route depends on more than a static prefix, e.g.\n * `hrefFor={(b) => (b.language === 'de' ? `/blog/${b.slug}` : `/${b.language}/blog/${b.slug}`)}`. */\n hrefFor?: (blog: Blog) => string;\n /** Override the date formatter shown on each card. */\n formatDate?: (iso: string) => string;\n} & NextClientOptions;\n\nconst defaultFormatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString();\n\n/**\n * Default-batteries blog list. Calls `listBlogs()` for the configured language\n * and renders one of three preset layouts. For full design control use\n * `listBlogs()` directly and write your own page.\n */\nexport async function BlogIndex({\n format = 'minimalist',\n language,\n pathPrefix,\n hrefFor,\n formatDate = defaultFormatDate,\n apiKey,\n apiUrl,\n revalidate,\n}: BlogIndexProps = {}): Promise<React.JSX.Element> {\n const blogs = await listBlogs(\n { status: 'PUBLISHED', language },\n { apiKey, apiUrl, revalidate },\n );\n const cards = blogs.map((b) => toBlogCard(b, { pathPrefix, hrefFor }));\n\n if (cards.length === 0) {\n return <EmptyState />;\n }\n\n if (format === 'bare') return <BareLayout cards={cards} formatDate={formatDate} />;\n if (format === 'list') return <ListLayout cards={cards} formatDate={formatDate} />;\n return <MinimalistLayout cards={cards} formatDate={formatDate} />;\n}\n\nfunction EmptyState(): React.JSX.Element {\n return <p>No posts yet.</p>;\n}\n\nfunction BareLayout({\n cards,\n formatDate,\n}: {\n cards: BlogCard[];\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n return (\n <ul>\n {cards.map((card) => (\n <li key={card.id}>\n <a href={card.href}>\n <h2>{card.title}</h2>\n </a>\n {card.published_at && (\n <time dateTime={card.published_at}>\n {formatDate(card.published_at)}\n </time>\n )}\n {card.description && <p>{card.description}</p>}\n </li>\n ))}\n </ul>\n );\n}\n\n// Color strategy: SDK never hardcodes light/dark. Title and date use\n// \\`color: inherit\\` / \\`currentColor\\` so they pick up whatever color\n// the host app's body/page is using. The host's theme toggle (Tailwind\n// \\`dark:\\` class, \\`data-theme\\`, \\`prefers-color-scheme\\`, anything) drives\n// the SDK automatically — no SDK-side theme knowledge needed.\n//\n// Date fades to 55% of currentColor — that gives the \"lighter gray\"\n// against any background (black-on-white → gray; white-on-black → gray).\n//\n// Image backgrounds use a translucent neutral so the placeholder is\n// visible on both light and dark host bodies without us picking a side.\nconst MINIMALIST_CSS = `\n.cg-blog-min-container {\n max-width: 1400px;\n margin: 4rem auto;\n padding: 0 2rem;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.cg-blog-min-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n column-gap: 2rem;\n row-gap: 4rem;\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.cg-blog-min-grid > li { list-style: none; }\n.cg-blog-min-card {\n display: block;\n text-decoration: none;\n color: inherit;\n}\n.cg-blog-min-card:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 4px;\n border-radius: 4px;\n}\n.cg-blog-min-image {\n width: 100%;\n aspect-ratio: 1 / 1;\n object-fit: cover;\n display: block;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.08);\n margin-bottom: 1.25rem;\n transition: opacity 200ms ease;\n}\n.cg-blog-min-card:hover .cg-blog-min-image {\n opacity: 0.88;\n}\n.cg-blog-min-no-image {\n width: 100%;\n aspect-ratio: 1 / 1;\n background: rgba(128, 128, 128, 0.08);\n border-radius: 6px;\n margin-bottom: 1.25rem;\n}\n.cg-blog-min-title {\n font-size: 1.25rem;\n font-weight: 600;\n margin: 0 0 0.5rem;\n line-height: 1.35;\n color: inherit;\n letter-spacing: -0.01em;\n}\n.cg-blog-min-date {\n font-size: 0.8125rem;\n color: currentColor;\n opacity: 0.55;\n margin: 0;\n font-weight: 400;\n}\n@media (max-width: 1024px) {\n .cg-blog-min-grid {\n grid-template-columns: repeat(2, 1fr);\n column-gap: 1.5rem;\n row-gap: 3rem;\n }\n}\n@media (max-width: 640px) {\n .cg-blog-min-container {\n margin: 2.5rem auto;\n padding: 0 1.25rem;\n }\n .cg-blog-min-grid {\n grid-template-columns: 1fr;\n row-gap: 2.5rem;\n }\n .cg-blog-min-title { font-size: 1.125rem; }\n}\n`;\n\nfunction MinimalistLayout({\n cards,\n formatDate,\n}: {\n cards: BlogCard[];\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n return (\n <div className=\"cg-blog-min-container\">\n {/* eslint-disable-next-line react/no-danger */}\n <style dangerouslySetInnerHTML={{ __html: MINIMALIST_CSS }} />\n <ul className=\"cg-blog-min-grid\">\n {cards.map((card) => (\n <li key={card.id}>\n <a href={card.href} className=\"cg-blog-min-card\">\n {card.og_image ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={card.og_image}\n alt={card.title}\n className=\"cg-blog-min-image\"\n loading=\"lazy\"\n />\n ) : (\n <div className=\"cg-blog-min-no-image\" aria-hidden=\"true\" />\n )}\n <h2 className=\"cg-blog-min-title\">{card.title}</h2>\n {card.published_at && (\n <p className=\"cg-blog-min-date\">\n {formatDate(card.published_at)}\n </p>\n )}\n </a>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n\n// Same color strategy as the minimalist layout and the article CSS:\n// \\`color: inherit\\` / \\`currentColor\\` with opacity for muted variants, and\n// translucent neutrals for image placeholders — the host app's theme\n// system drives everything, no \\`prefers-color-scheme\\` rules needed.\nconst LIST_CSS = `\n.cg-blog-list { list-style: none; padding: 0; margin: 0; }\n.cg-blog-list-item {\n margin-bottom: 2rem;\n padding-bottom: 2rem;\n border-bottom: 1px solid currentColor;\n}\n.cg-blog-list-item { border-bottom-color: color-mix(in srgb, currentColor 15%, transparent); }\n.cg-blog-list-item:last-child {\n margin-bottom: 0;\n padding-bottom: 0;\n border-bottom: none;\n}\n.cg-blog-list-link {\n display: flex;\n gap: 1.5rem;\n text-decoration: none;\n color: inherit;\n align-items: flex-start;\n}\n.cg-blog-list-image {\n flex-shrink: 0;\n width: 12rem;\n height: 8rem;\n object-fit: cover;\n border-radius: 0.5rem;\n background: rgba(128, 128, 128, 0.08);\n transition: opacity 200ms ease;\n}\n.cg-blog-list-link:hover .cg-blog-list-image { opacity: 0.88; }\n.cg-blog-list-body { flex: 1; min-width: 0; }\n.cg-blog-list-date {\n font-size: 0.75rem;\n color: currentColor;\n opacity: 0.55;\n margin: 0 0 0.5rem 0;\n}\n.cg-blog-list-title {\n font-size: 1.5rem;\n font-weight: 700;\n margin: 0 0 0.5rem 0;\n line-height: 1.3;\n color: inherit;\n transition: opacity 150ms ease;\n}\n.cg-blog-list-link:hover .cg-blog-list-title { opacity: 0.7; }\n.cg-blog-list-description {\n font-size: 1rem;\n color: currentColor;\n opacity: 0.65;\n margin: 0;\n line-height: 1.6;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n@media (max-width: 640px) {\n .cg-blog-list-link { flex-direction: column; }\n .cg-blog-list-image { width: 100%; height: 12rem; }\n}\n`;\n\nfunction ListLayout({\n cards,\n formatDate,\n}: {\n cards: BlogCard[];\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n return (\n <>\n {/* eslint-disable-next-line react/no-danger */}\n <style dangerouslySetInnerHTML={{ __html: LIST_CSS }} />\n <ul className=\"cg-blog-list\">\n {cards.map((card) => (\n <li key={card.id} className=\"cg-blog-list-item\">\n <a href={card.href} className=\"cg-blog-list-link\">\n {card.og_image && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={card.og_image}\n alt={card.title}\n className=\"cg-blog-list-image\"\n />\n )}\n <div className=\"cg-blog-list-body\">\n {card.published_at && (\n <p className=\"cg-blog-list-date\">\n {formatDate(card.published_at)}\n </p>\n )}\n <h2 className=\"cg-blog-list-title\">{card.title}</h2>\n {card.description && (\n <p className=\"cg-blog-list-description\">{card.description}</p>\n )}\n </div>\n </a>\n </li>\n ))}\n </ul>\n </>\n );\n}\n"],"mappings":"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","names":["media","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","defaultFormatDate"]}
|
package/dist/react/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { b as BlockType, L as ListItem, a as Block, E as EmbeddedMedia, B as Blog } from '../types-
|
|
3
|
-
export { F as FaqItem, I as Inline, M as Mark, S as SocialPost } from '../types-
|
|
2
|
+
import { b as BlockType, L as ListItem, a as Block, E as EmbeddedMedia, B as Blog } from '../types-BZcsJXJY.cjs';
|
|
3
|
+
export { F as FaqItem, I as Inline, M as Mark, S as SocialPost } from '../types-BZcsJXJY.cjs';
|
|
4
4
|
import { NextClientOptions } from '../next/index.cjs';
|
|
5
|
-
import { a as BlogJsonLdOptions } from '../blog-cards-
|
|
6
|
-
export { B as BlogCard, I as InterleavedSlot, T as ToBlogCardOptions, e as distributeMediaPositions, i as interleaveMedia, m as mergeBlogs, t as toBlogCard } from '../blog-cards-
|
|
5
|
+
import { a as BlogJsonLdOptions } from '../blog-cards-0gdGpdZr.cjs';
|
|
6
|
+
export { B as BlogCard, I as InterleavedSlot, T as ToBlogCardOptions, e as distributeMediaPositions, i as interleaveMedia, m as mergeBlogs, t as toBlogCard } from '../blog-cards-0gdGpdZr.cjs';
|
|
7
7
|
import 'next';
|
|
8
8
|
|
|
9
9
|
/**
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { b as BlockType, L as ListItem, a as Block, E as EmbeddedMedia, B as Blog } from '../types-
|
|
3
|
-
export { F as FaqItem, I as Inline, M as Mark, S as SocialPost } from '../types-
|
|
2
|
+
import { b as BlockType, L as ListItem, a as Block, E as EmbeddedMedia, B as Blog } from '../types-BZcsJXJY.js';
|
|
3
|
+
export { F as FaqItem, I as Inline, M as Mark, S as SocialPost } from '../types-BZcsJXJY.js';
|
|
4
4
|
import { NextClientOptions } from '../next/index.js';
|
|
5
|
-
import { a as BlogJsonLdOptions } from '../blog-cards-
|
|
6
|
-
export { B as BlogCard, I as InterleavedSlot, T as ToBlogCardOptions, e as distributeMediaPositions, i as interleaveMedia, m as mergeBlogs, t as toBlogCard } from '../blog-cards-
|
|
5
|
+
import { a as BlogJsonLdOptions } from '../blog-cards-CuGlJiWH.js';
|
|
6
|
+
export { B as BlogCard, I as InterleavedSlot, T as ToBlogCardOptions, e as distributeMediaPositions, i as interleaveMedia, m as mergeBlogs, t as toBlogCard } from '../blog-cards-CuGlJiWH.js';
|
|
7
7
|
import 'next';
|
|
8
8
|
|
|
9
9
|
/**
|
package/dist/react/index.js
CHANGED
|
@@ -11,7 +11,8 @@ type Mark = {
|
|
|
11
11
|
type: 'link';
|
|
12
12
|
href: string;
|
|
13
13
|
};
|
|
14
|
-
/** A text run within a block's content.
|
|
14
|
+
/** A text run within a block's content. A "\n" in `text` is a line
|
|
15
|
+
* break inside the block (render it as `<br>`). */
|
|
15
16
|
type Inline = {
|
|
16
17
|
text: string;
|
|
17
18
|
marks?: Mark[];
|
|
@@ -11,7 +11,8 @@ type Mark = {
|
|
|
11
11
|
type: 'link';
|
|
12
12
|
href: string;
|
|
13
13
|
};
|
|
14
|
-
/** A text run within a block's content.
|
|
14
|
+
/** A text run within a block's content. A "\n" in `text` is a line
|
|
15
|
+
* break inside the block (render it as `<br>`). */
|
|
15
16
|
type Inline = {
|
|
16
17
|
text: string;
|
|
17
18
|
marks?: Mark[];
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/next/metadata.ts","../src/next/BlogPage.tsx","../src/react/defaults.tsx","../src/react/BlogRenderer.tsx","../src/react/BlogDetail.tsx","../src/react/BlogJsonLd.tsx","../src/next/index.ts"],"sourcesContent":["import type { Metadata } from 'next';\nimport { getBlog, listBlogs } from './index';\nimport type { NextClientOptions } from './index';\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Opt-in hreflang configuration for `generateBlogMetadata`. ChainGrow serves\n * language variants of a post under one shared slug, so the SDK can emit\n * `alternates.languages` (and a canonical) for every variant it finds —\n * pass this when your site serves more than one language.\n */\nexport type BlogAlternatesOptions = {\n /** Absolute site origin, e.g. `https://example.com` (no trailing slash needed). */\n siteUrl: string;\n /**\n * Blog path prefix. A static string applies to every language; pass a\n * function for locale-prefixed routing, e.g.\n * `(lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`)`.\n * Defaults to `/blog`.\n */\n pathPrefix?: string | ((language: string) => string);\n /** Language whose URL is additionally emitted as `x-default`. */\n xDefaultLanguage?: string;\n};\n\nexport type BlogMetadataOptions = NextClientOptions & {\n alternates?: BlogAlternatesOptions;\n};\n\nconst blogUrlFor = (\n alternates: BlogAlternatesOptions,\n language: string,\n slug: string,\n): string => {\n const base = trimTrailingSlash(alternates.siteUrl);\n const rawPrefix =\n typeof alternates.pathPrefix === 'function'\n ? alternates.pathPrefix(language)\n : (alternates.pathPrefix ?? '/blog');\n return `${base}${normalizePathPrefix(rawPrefix)}/${slug}`;\n};\n\n/**\n * Next.js `Metadata` factory for a chaingrow blog. Wire it up like:\n *\n * ```ts\n * export async function generateMetadata({ params }) {\n * const { slug } = await params;\n * return generateBlogMetadata(slug);\n * }\n * ```\n *\n * Reuses `getBlog()` which is ISR-tagged, so `generateMetadata` + the page\n * render share one backend hit.\n *\n * Pass `opts.alternates` to also emit `alternates.canonical` and hreflang\n * `alternates.languages` built from the post's language variants (ChainGrow\n * publishes translations under the same slug):\n *\n * ```ts\n * return generateBlogMetadata(slug, locale, {\n * alternates: {\n * siteUrl: 'https://example.com',\n * pathPrefix: (lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`),\n * xDefaultLanguage: 'de',\n * },\n * });\n * ```\n *\n * The variant lookup reuses the ISR-cached `listBlogs()` response (same\n * cache entry as the blog index and sitemaps), so it does not add a backend\n * hit per page view.\n *\n * Returns `{}` on 404 — the page's own `notFound()` owns the HTTP status;\n * this helper just declines to emit half-populated metadata for a missing\n * post.\n *\n * Fields that aren't present on the Blog are omitted (not set to\n * `undefined`) so spread-overrides like\n * `{ ...await generateBlogMetadata(slug), alternates: { canonical } }` do\n * what you expect.\n */\nexport async function generateBlogMetadata(\n slug: string,\n language?: string,\n opts?: BlogMetadataOptions,\n): Promise<Metadata> {\n const blog = await getBlog(slug, language, opts);\n if (!blog) return {};\n\n const images = blog.og_image ? [blog.og_image] : undefined;\n const description = blog.description ?? undefined;\n\n const meta: Metadata = {\n title: blog.title,\n ...(description ? { description } : {}),\n openGraph: {\n type: 'article',\n title: blog.title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n ...(blog.published_at ? { publishedTime: blog.published_at } : {}),\n },\n twitter: {\n card: blog.og_image ? 'summary_large_image' : 'summary',\n title: blog.title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n },\n };\n\n if (opts?.alternates) {\n const alternates = opts.alternates;\n const canonical = blogUrlFor(alternates, blog.language, blog.slug);\n let languages: Record<string, string> | undefined;\n try {\n const all = await listBlogs({ status: 'PUBLISHED' }, opts);\n const variants = all.filter((b) => b.slug === blog.slug && b.language);\n if (variants.length > 1) {\n languages = {};\n for (const variant of variants) {\n languages[variant.language] = blogUrlFor(\n alternates,\n variant.language,\n variant.slug,\n );\n }\n const xDefaultUrl = alternates.xDefaultLanguage\n ? languages[alternates.xDefaultLanguage]\n : undefined;\n if (xDefaultUrl) languages['x-default'] = xDefaultUrl;\n }\n } catch (err) {\n // hreflang is an enhancement — never fail metadata generation over it.\n console.error(\n 'chaingrow-blog: hreflang variant lookup failed, emitting canonical only:',\n err,\n );\n }\n meta.alternates = {\n canonical,\n ...(languages ? { languages } : {}),\n };\n }\n\n return meta;\n}\n\nexport type BlogIndexMetadataOptions = {\n /** Page title, e.g. `'Blog | Acme'`. */\n title: string;\n description?: string;\n /** Absolute canonical URL of the index page, e.g. `https://example.com/blog`. */\n canonical?: string;\n /** OG/Twitter preview image URL. */\n ogImage?: string;\n};\n\n/**\n * Next.js `Metadata` for a blog *index* page (`app/blog/page.tsx`). The blog\n * detail page gets its metadata from the post itself, but the index has no\n * single post to draw from — without this, greenfield apps fall back to the\n * site-default title on `/blog`. Synchronous (no API call):\n *\n * ```ts\n * export const metadata = generateBlogIndexMetadata({\n * title: 'Blog | Acme',\n * description: 'Product updates and guides from Acme.',\n * canonical: 'https://example.com/blog',\n * });\n * ```\n */\nexport function generateBlogIndexMetadata(\n opts: BlogIndexMetadataOptions,\n): Metadata {\n const { title, description, canonical, ogImage } = opts;\n const images = ogImage ? [ogImage] : undefined;\n return {\n title,\n ...(description ? { description } : {}),\n ...(canonical ? { alternates: { canonical } } : {}),\n openGraph: {\n type: 'website',\n title,\n ...(description ? { description } : {}),\n ...(canonical ? { url: canonical } : {}),\n ...(images ? { images } : {}),\n },\n twitter: {\n card: ogImage ? 'summary_large_image' : 'summary',\n title,\n ...(description ? { description } : {}),\n ...(images ? { images } : {}),\n },\n };\n}\n","import * as React from 'react';\nimport type { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport type { Blog } from '../types';\nimport { BlogDetail } from '../react/BlogDetail';\nimport { BlogJsonLd } from '../react/BlogJsonLd';\nimport { getBlog } from './index';\nimport { generateBlogMetadata, type BlogMetadataOptions } from './metadata';\n\nexport type BlogPageRouteParams = { slug: string; locale?: string };\n\ntype RouteArgs = {\n params: Promise<BlogPageRouteParams>;\n};\n\n/**\n * Default-batteries `app/blog/[slug]/page.tsx` component. Fetches the blog,\n * renders JSON-LD + the detail layout, 404s if missing. Zero configuration\n * and zero CSS framework dependency — typography styles are emitted inline\n * under the scoped `cg-blog-article` class so a fresh app looks good without\n * Tailwind, prose, or anything else installed.\n *\n * If you want a custom layout, breadcrumbs, or to coexist with an existing\n * blog, skip this component and compose `getBlog()` + `<BlogJsonLd>` +\n * `<BlogDetail>` directly in your page file.\n */\nexport async function BlogPage({\n params,\n}: RouteArgs): Promise<React.JSX.Element> {\n const { slug, locale } = await params;\n let blog: Blog | null = null;\n try {\n blog = await getBlog(slug, locale);\n } catch (err) {\n console.error('chaingrow-blog: <BlogPage> getBlog failed:', err);\n }\n if (!blog) notFound();\n\n return (\n <div className=\"cg-blog-article\">\n {/* eslint-disable-next-line react/no-danger */}\n <style dangerouslySetInnerHTML={{ __html: ARTICLE_CSS }} />\n <BlogJsonLd blog={blog} />\n <BlogDetail blog={blog} />\n </div>\n );\n}\n\n// Color strategy: every text element uses \\`color: inherit\\` so the host\n// app's text color cascades down. Muted variants (date, hr) use\n// \\`currentColor\\` with opacity. Backgrounds for placeholders use a\n// translucent neutral that works against any host background. No\n// \\`prefers-color-scheme\\` rules — the host's theme system (Tailwind\n// \\`dark:\\`, \\`data-theme\\`, OS preference, whatever) drives the SDK\n// without it having to know how the host implements the toggle.\nconst ARTICLE_CSS = `\n.cg-blog-article {\n max-width: 720px;\n margin: 0 auto;\n padding: 4rem 1.5rem 6rem;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n font-size: 1.0625rem;\n line-height: 1.7;\n color: inherit;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.cg-blog-article h1 {\n font-size: clamp(2rem, 4vw, 2.625rem);\n font-weight: 700;\n line-height: 1.15;\n letter-spacing: -0.022em;\n color: inherit;\n margin: 0 0 1rem;\n}\n.cg-blog-article time {\n display: block;\n font-size: 0.8125rem;\n color: currentColor;\n opacity: 0.55;\n margin: 0 0 2rem;\n font-weight: 400;\n}\n.cg-blog-article h2 {\n font-size: 1.625rem;\n font-weight: 700;\n line-height: 1.25;\n letter-spacing: -0.015em;\n color: inherit;\n margin: 3rem 0 1rem;\n}\n.cg-blog-article h3 {\n font-size: 1.25rem;\n font-weight: 700;\n line-height: 1.3;\n color: inherit;\n margin: 2.25rem 0 0.75rem;\n}\n.cg-blog-article h4 {\n font-size: 1.0625rem;\n font-weight: 700;\n color: inherit;\n margin: 1.75rem 0 0.5rem;\n}\n.cg-blog-article p {\n margin: 0 0 1.25rem;\n color: inherit;\n}\n.cg-blog-article a {\n color: inherit;\n text-decoration: underline;\n text-underline-offset: 3px;\n text-decoration-thickness: 1px;\n transition: opacity 150ms ease;\n}\n.cg-blog-article a:hover { opacity: 0.7; }\n.cg-blog-article strong { color: inherit; font-weight: 700; }\n.cg-blog-article em { font-style: italic; }\n.cg-blog-article ul,\n.cg-blog-article ol {\n margin: 0 0 1.5rem;\n padding-left: 1.5rem;\n color: inherit;\n}\n.cg-blog-article li {\n margin-bottom: 0.5rem;\n line-height: 1.65;\n}\n.cg-blog-article li > p { margin: 0 0 0.5rem; }\n.cg-blog-article li:last-child > p:last-child { margin-bottom: 0; }\n.cg-blog-article blockquote {\n margin: 2rem 0;\n padding: 0.5rem 0 0.5rem 1.5rem;\n border-left: 3px solid currentColor;\n font-style: italic;\n color: inherit;\n font-size: 1.125rem;\n line-height: 1.6;\n}\n.cg-blog-article blockquote p:last-child { margin-bottom: 0; }\n.cg-blog-article img {\n display: block;\n width: 100%;\n height: auto;\n margin: 2.25rem 0;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.08);\n}\n.cg-blog-article video {\n display: block;\n width: 100%;\n height: auto;\n margin: 2.25rem 0;\n border-radius: 6px;\n background: rgba(128, 128, 128, 0.15);\n}\n.cg-blog-article hr {\n border: 0;\n height: 1px;\n background: currentColor;\n opacity: 0.15;\n margin: 3rem 0;\n}\n.cg-blog-article code {\n background: rgba(128, 128, 128, 0.15);\n color: inherit;\n padding: 0.15em 0.4em;\n border-radius: 3px;\n font-size: 0.9em;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n}\n.cg-blog-article pre {\n background: rgba(128, 128, 128, 0.12);\n color: inherit;\n padding: 1.25rem 1.5rem;\n border-radius: 6px;\n overflow-x: auto;\n font-size: 0.9375rem;\n line-height: 1.6;\n margin: 1.75rem 0;\n}\n.cg-blog-article pre code {\n background: transparent;\n color: inherit;\n padding: 0;\n border-radius: 0;\n font-size: inherit;\n}\n@media (max-width: 640px) {\n .cg-blog-article {\n padding: 2.5rem 1.25rem 4rem;\n font-size: 1rem;\n }\n .cg-blog-article h2 { margin-top: 2.25rem; }\n .cg-blog-article h3 { margin-top: 1.75rem; }\n}\n`;\n\n/**\n * Default-batteries `generateMetadata` for the detail page. Zero configuration.\n *\n * Greenfield use:\n *\n * ```ts\n * export const generateMetadata = generateBlogPageMetadata();\n * ```\n *\n * Multilingual sites can pass `alternates` to get canonical + hreflang\n * `alternates.languages` for every language variant of the post:\n *\n * ```ts\n * export const generateMetadata = generateBlogPageMetadata({\n * alternates: {\n * siteUrl: 'https://example.com',\n * pathPrefix: (lang) => (lang === 'de' ? '/blog' : `/${lang}/blog`),\n * },\n * });\n * ```\n *\n * For custom metadata or coexistence with a legacy blog, skip this and call\n * `generateBlogMetadata(slug, locale)` directly in your own\n * `generateMetadata`.\n */\nexport function generateBlogPageMetadata(\n opts?: BlogMetadataOptions,\n): (args: RouteArgs) => Promise<Metadata> {\n return async ({ params }) => {\n const { slug, locale } = await params;\n try {\n return await generateBlogMetadata(slug, locale, opts);\n } catch (err) {\n console.error(\n 'chaingrow-blog: generateBlogPageMetadata failed:',\n err,\n );\n return {};\n }\n };\n}\n","import * as React from 'react';\nimport type { Block, EmbeddedMedia, ListItem } from '../types';\n\n/**\n * Default components for each Block type. All are headless — no\n * classNames baked in. Consumers customize via:\n * - `classNames={{ heading: \"...\" }}` on `<BlogRenderer>` — threads\n * through to `className` on the Default component below.\n * - `components={{ heading: MyHeading }}` — full replacement.\n */\n\nexport const DefaultHeading = ({\n level,\n children,\n className,\n}: {\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n}) => {\n if (level === 1) return <h1 className={className}>{children}</h1>;\n if (level === 3) return <h3 className={className}>{children}</h3>;\n return <h2 className={className}>{children}</h2>;\n};\n\nexport const DefaultParagraph = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <p className={className}>{children}</p>;\n\nexport const DefaultBulletList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ul className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ul>\n);\n\nexport const DefaultOrderedList = ({\n items,\n className,\n renderBlock,\n}: {\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n}) => (\n <ol className={className}>\n {items.map((item, i) => (\n <li key={i}>{item.content.map((b, j) => renderBlock(b, j))}</li>\n ))}\n </ol>\n);\n\nexport const DefaultBlockquote = ({\n children,\n className,\n}: {\n children: React.ReactNode;\n className?: string;\n}) => <blockquote className={className}>{children}</blockquote>;\n\nexport const DefaultCodeBlock = ({\n language,\n code,\n className,\n}: {\n language?: string;\n code: string;\n className?: string;\n}) => (\n <pre className={className}>\n <code className={language ? `language-${language}` : undefined}>{code}</code>\n </pre>\n);\n\nexport const DefaultImage = ({\n src,\n alt,\n caption,\n width,\n height,\n className,\n}: {\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n}) => {\n const img = (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={src} alt={alt} width={width} height={height} />\n );\n if (caption) {\n return (\n <figure className={className}>\n {img}\n <figcaption>{caption}</figcaption>\n </figure>\n );\n }\n return <span className={className}>{img}</span>;\n};\n\nexport const DefaultDivider = ({ className }: { className?: string }) => (\n <hr className={className} />\n);\n\n/**\n * Default renderer for a single embedded media item. Bare HTML — no\n * classNames, no styles. Override via `components.media` on `<BlogRenderer>`.\n * `alt` falls back to the generation prompt, which is a sensible description\n * string since it was the LLM input that produced the image.\n */\nexport const DefaultMedia = ({ media }: { media: EmbeddedMedia }) => {\n if (media.media_type === 'VIDEO') {\n return (\n <video\n src={media.media_url}\n controls\n playsInline\n preload=\"metadata\"\n />\n );\n }\n // eslint-disable-next-line @next/next/no-img-element\n return <img src={media.media_url} alt={media.prompt} />;\n};\n","import * as React from 'react';\nimport type {\n Blog,\n Block,\n BlockType,\n EmbeddedMedia,\n Inline,\n ListItem,\n Mark,\n} from '../types';\nimport { interleaveMedia } from '../media-placement';\nimport {\n DefaultHeading,\n DefaultParagraph,\n DefaultBulletList,\n DefaultOrderedList,\n DefaultBlockquote,\n DefaultCodeBlock,\n DefaultImage,\n DefaultDivider,\n DefaultMedia,\n} from './defaults';\n\n/**\n * Override map for per-block components. Keys are the discriminants\n * of `Block`. Consumers pass any subset; unspecified blocks render\n * via the matching `Default*` component.\n *\n * v0.3.0 breaking change: the keys changed to match the new Block\n * union. If you upgraded from 0.2.x, rename `list` → `bulletList` /\n * `orderedList`, `quote` → `blockquote`, `code` → `codeBlock`, drop\n * `image_placeholder` (images are now first-class via `image`).\n */\nexport type BlockComponents = Partial<{\n heading: React.ComponentType<{\n level: 1 | 2 | 3;\n children: React.ReactNode;\n className?: string;\n }>;\n paragraph: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n bulletList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n orderedList: React.ComponentType<{\n items: ListItem[];\n className?: string;\n renderBlock: (b: Block, i: number) => React.ReactNode;\n }>;\n blockquote: React.ComponentType<{\n children: React.ReactNode;\n className?: string;\n }>;\n codeBlock: React.ComponentType<{\n language?: string;\n code: string;\n className?: string;\n }>;\n image: React.ComponentType<{\n src: string;\n alt: string;\n caption?: string;\n width?: number;\n height?: number;\n mediaId?: string;\n className?: string;\n }>;\n divider: React.ComponentType<{ className?: string }>;\n /** Renders a single attached image/video when `<BlogRenderer>` distributes\n * `blog.generatedMedia` across the body. Default renders a bare\n * `<img>` or `<video controls>`. */\n media: React.ComponentType<{ media: EmbeddedMedia }>;\n wrapper: React.ComponentType<{ children: React.ReactNode; blog: Blog }>;\n}>;\n\n/** Simple Tailwind-style className override map. Applied to the\n * default component for each block type. `components` override takes\n * precedence — if you replace the component, className won't reach it\n * unless your override handles it. */\nexport type BlockClassNames = Partial<Record<BlockType, string>>;\n\nconst MARK_ORDER: Mark['type'][] = [\n 'bold',\n 'italic',\n 'strike',\n 'code',\n 'link',\n];\n\n/** Render an array of Inline runs into text+mark JSX. */\nfunction InlineRenderer({ inline }: { inline: Inline[] }) {\n return (\n <>\n {inline.map((run, i) => (\n <InlineRun key={i} run={run} />\n ))}\n </>\n );\n}\n\nfunction InlineRun({ run }: { run: Inline }) {\n const marks = run.marks\n ? [...run.marks].sort(\n (a, b) => MARK_ORDER.indexOf(b.type) - MARK_ORDER.indexOf(a.type),\n )\n : [];\n let node: React.ReactNode = run.text;\n for (const mark of marks) {\n switch (mark.type) {\n case 'bold':\n node = <strong>{node}</strong>;\n break;\n case 'italic':\n node = <em>{node}</em>;\n break;\n case 'strike':\n node = <s>{node}</s>;\n break;\n case 'code':\n node = <code>{node}</code>;\n break;\n case 'link':\n node = (\n <a href={mark.href} rel=\"noopener noreferrer\">\n {node}\n </a>\n );\n break;\n }\n }\n return <>{node}</>;\n}\n\nexport function BlockRenderer({\n block,\n components = {},\n classNames = {},\n renderBlock,\n}: {\n block: Block;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n renderBlock?: (b: Block, i: number) => React.ReactNode;\n}) {\n // Parent supplies a renderBlock callback so list items can recurse\n // into the same switch. When not provided (top-level call), wire it\n // to a local closure.\n const recurse: (b: Block, i: number) => React.ReactNode =\n renderBlock ??\n ((b, i) => (\n <BlockRenderer\n key={i}\n block={b}\n components={components}\n classNames={classNames}\n renderBlock={recurse}\n />\n ));\n\n switch (block.type) {\n case 'heading': {\n const Heading = components.heading ?? DefaultHeading;\n return (\n <Heading level={block.level} className={classNames.heading}>\n <InlineRenderer inline={block.content} />\n </Heading>\n );\n }\n case 'paragraph': {\n const Paragraph = components.paragraph ?? DefaultParagraph;\n return (\n <Paragraph className={classNames.paragraph}>\n <InlineRenderer inline={block.content} />\n </Paragraph>\n );\n }\n case 'bulletList': {\n const BulletList = components.bulletList ?? DefaultBulletList;\n return (\n <BulletList\n items={block.items}\n className={classNames.bulletList}\n renderBlock={recurse}\n />\n );\n }\n case 'orderedList': {\n const OrderedList = components.orderedList ?? DefaultOrderedList;\n return (\n <OrderedList\n items={block.items}\n className={classNames.orderedList}\n renderBlock={recurse}\n />\n );\n }\n case 'blockquote': {\n const Blockquote = components.blockquote ?? DefaultBlockquote;\n return (\n <Blockquote className={classNames.blockquote}>\n <InlineRenderer inline={block.content} />\n </Blockquote>\n );\n }\n case 'codeBlock': {\n const CodeBlock = components.codeBlock ?? DefaultCodeBlock;\n return (\n <CodeBlock\n language={block.language}\n code={block.code}\n className={classNames.codeBlock}\n />\n );\n }\n case 'image': {\n const ImageComp = components.image ?? DefaultImage;\n return (\n <ImageComp\n src={block.src}\n alt={block.alt}\n caption={block.caption}\n width={block.width}\n height={block.height}\n mediaId={block.mediaId}\n className={classNames.image}\n />\n );\n }\n case 'divider': {\n const Divider = components.divider ?? DefaultDivider;\n return <Divider className={classNames.divider} />;\n }\n }\n}\n\nexport function BlogRenderer({\n blog,\n components = {},\n classNames = {},\n}: {\n blog: Blog;\n components?: BlockComponents;\n classNames?: BlockClassNames;\n}) {\n const Media = components.media ?? DefaultMedia;\n const Wrapper = components.wrapper;\n\n // Distribute `blog.generatedMedia` across `blog.blocks` at positions\n // floor(i * N / K) — first media at top of body, the rest spread evenly.\n // Images placed in the text render as their own `image` blocks instead.\n const slots = interleaveMedia(blog.blocks, blog.generatedMedia);\n\n const content = (\n <>\n {slots.map((slot) =>\n slot.kind === 'block' ? (\n <BlockRenderer\n key={`b:${slot.index}`}\n block={slot.block}\n components={components}\n classNames={classNames}\n />\n ) : (\n <Media key={`m:${slot.media.id}`} media={slot.media} />\n ),\n )}\n </>\n );\n\n if (Wrapper) return <Wrapper blog={blog}>{content}</Wrapper>;\n return content;\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport { hasTextImage } from '../media-placement';\nimport { BlogRenderer, type BlockComponents } from './BlogRenderer';\n\nexport type BlogDetailComponents = {\n /**\n * Renders above the body. Default: og_image → h1 → time → description.\n * Override to add tags, author chip, share buttons, read time, etc.\n */\n Header?: (props: {\n blog: Blog;\n formatDate: (iso: string) => string;\n }) => React.ReactNode;\n /** Renders below the body. Default: nothing. */\n Footer?: (props: { blog: Blog }) => React.ReactNode;\n /** Forwarded to <BlogRenderer components={...} />. */\n blocks?: BlockComponents;\n};\n\nconst defaultFormatDate = (iso: string): string =>\n new Date(iso).toLocaleDateString();\n\nfunction DefaultHeader({\n blog,\n formatDate,\n}: {\n blog: Blog;\n formatDate: (iso: string) => string;\n}): React.JSX.Element {\n // Bare semantic HTML — no classNames, no styles. Consumers wrap the whole\n // BlogDetail in their own `.prose` / typography container to handle styling.\n //\n // When the body shows any image — attachments spread by <BlogRenderer>\n // (the first lands at the top of the body, right under this header) or\n // images placed in the text — `og_image` is one of them, so rendering it\n // here too would show it twice. It then stays a meta-only field (still\n // used by OpenGraph / Twitter card previews via `generateBlogMetadata`).\n const hasInlineMedia =\n (blog.generatedMedia?.length ?? 0) > 0 || hasTextImage(blog.blocks);\n // eslint-disable-next-line @next/next/no-img-element\n const heroImage =\n !hasInlineMedia && blog.og_image ? (\n <img src={blog.og_image} alt={blog.title} />\n ) : null;\n return (\n <>\n {heroImage}\n <h1>{blog.title}</h1>\n {blog.published_at && (\n <time dateTime={blog.published_at}>\n {formatDate(blog.published_at)}\n </time>\n )}\n {blog.description && <p>{blog.description}</p>}\n </>\n );\n}\n\nexport function BlogDetail({\n blog,\n components,\n formatDate = defaultFormatDate,\n}: {\n blog: Blog;\n components?: BlogDetailComponents;\n formatDate?: (iso: string) => string;\n}): React.JSX.Element {\n const Header = components?.Header ?? DefaultHeader;\n const Footer = components?.Footer;\n return (\n <article>\n <Header blog={blog} formatDate={formatDate} />\n <BlogRenderer blog={blog} components={components?.blocks} />\n {Footer ? <Footer blog={blog} /> : null}\n </article>\n );\n}\n","import * as React from 'react';\nimport type { Blog } from '../types';\nimport {\n buildBlogJsonLd,\n SCHEMA_ORG_CONTEXT,\n type BlogJsonLdOptions,\n} from '../json-ld';\n\n/**\n * Render an array of Schema.org graph items as a single\n * `<script type=\"application/ld+json\">` tag with a merged `@graph`.\n * Google treats one `@graph` script as equivalent to N separate scripts, and\n * a single tag keeps the DOM uncluttered for consumers who already ship their\n * own schemas (Organization, breadcrumbs, WebSite, …).\n */\nexport function JsonLdGraph({\n items,\n}: {\n items: Record<string, unknown>[];\n}): React.JSX.Element | null {\n if (!items || items.length === 0) return null;\n const payload = JSON.stringify({\n '@context': SCHEMA_ORG_CONTEXT,\n '@graph': items,\n });\n return (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: payload }}\n />\n );\n}\n\n/**\n * Default-batteries JSON-LD for a chaingrow Blog: emits BlogPosting +\n * FAQPage (if the post has FAQs) + any LLM-generated extras from\n * `structured_data.jsonLd`. Consumers who already ship their own graph items\n * (breadcrumbs, Organization, …) pass them via `extraSchemas` and they end\n * up in the same `@graph`.\n */\nexport function BlogJsonLd({\n blog,\n extraSchemas,\n ...opts\n}: {\n blog: Blog;\n extraSchemas?: Record<string, unknown>[];\n} & BlogJsonLdOptions): React.JSX.Element | null {\n const items = [...(extraSchemas ?? []), ...buildBlogJsonLd(blog, opts)];\n return <JsonLdGraph items={items} />;\n}\n","import type { MetadataRoute } from 'next';\nimport type { Blog } from '../types';\nimport { createClient } from '../client';\n\nexport type NextClientOptions = {\n apiUrl?: string;\n apiKey?: string;\n revalidate?: number | false;\n};\n\nconst DEFAULT_API_URL = 'https://api.chaingrow.de';\n\n/**\n * Default ISR window for `getBlog` / `listBlogs` when the consumer doesn't\n * set `revalidate` explicitly. 7 days — blog posts are effectively immutable\n * once published, so caching for a week is sensible, and the webhook\n * (`chaingrow-blog/next/webhook`) invalidates the cache immediately on\n * publish anyway so this window is really just a safety net for when the\n * webhook isn't mounted or misses a delivery.\n *\n * Override per-call with `{ revalidate: N }` (seconds), `{ revalidate: 0 }`\n * (never cache — every request hits ChainGrow), or `{ revalidate: false }`\n * (cache forever, only the webhook can invalidate).\n */\nconst DEFAULT_REVALIDATE_SECONDS = 60 * 60 * 24 * 7;\n\nconst resolveEnv = (opts: NextClientOptions = {}) => {\n const apiUrl =\n opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;\n const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)',\n );\n }\n return { apiUrl, apiKey };\n};\n\nconst buildFetch = (tag: string, revalidate: number | false | undefined) => {\n const effectiveRevalidate: number | false =\n revalidate === undefined ? DEFAULT_REVALIDATE_SECONDS : revalidate;\n return ((input: RequestInfo | URL, init?: RequestInit) =>\n fetch(input, {\n ...init,\n next: {\n tags: ['chaingrow:blog', tag],\n revalidate: effectiveRevalidate,\n },\n } as RequestInit)) as typeof fetch;\n};\n\nexport async function getBlog(\n slug: string,\n language?: string,\n opts: NextClientOptions = {},\n): Promise<Blog | null> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch(`chaingrow:blog:${slug}`, opts.revalidate),\n });\n return client.getBlog(slug, language);\n}\n\nexport async function listBlogs(\n listOpts: { status?: 'DRAFT' | 'PUBLISHED'; language?: string } = {},\n opts: NextClientOptions = {},\n): Promise<Blog[]> {\n const { apiUrl, apiKey } = resolveEnv(opts);\n const client = createClient({\n apiUrl,\n apiKey,\n fetch: buildFetch('chaingrow:blog:list', opts.revalidate),\n });\n return client.listBlogs(listOpts);\n}\n\nconst trimTrailingSlash = (s: string): string => s.replace(/\\/+$/, '');\nconst normalizePathPrefix = (s: string): string => {\n let p = s.trim();\n if (!p.startsWith('/')) p = '/' + p;\n while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);\n return p;\n};\n\n/**\n * Returns ChainGrow blog entries already shaped for Next.js's\n * `app/sitemap.ts` (`MetadataRoute.Sitemap`). Spread the result into your\n * sitemap array — no mapping required at the call site.\n *\n * ```ts\n * // app/sitemap.ts\n * import type { MetadataRoute } from 'next';\n * import { getBlogSitemapEntries } from 'chaingrow-blog/next';\n *\n * export default async function sitemap(): Promise<MetadataRoute.Sitemap> {\n * const blogs = await getBlogSitemapEntries({\n * siteUrl: 'https://example.com',\n * pathPrefix: '/blog',\n * });\n * return [\n * { url: 'https://example.com', lastModified: new Date() },\n * ...blogs,\n * ];\n * }\n * ```\n *\n * For news sitemaps, image sitemaps, or any case that needs the full blog\n * shape (title, language, attached media), call `listBlogs()` directly.\n */\nexport async function getBlogSitemapEntries(opts: {\n siteUrl: string;\n pathPrefix?: string;\n language?: string;\n} & NextClientOptions): Promise<MetadataRoute.Sitemap> {\n const baseUrl = trimTrailingSlash(opts.siteUrl);\n const pathPrefix = normalizePathPrefix(opts.pathPrefix ?? '/blog');\n const blogs = await listBlogs(\n { status: 'PUBLISHED', language: opts.language },\n {\n apiKey: opts.apiKey,\n apiUrl: opts.apiUrl,\n revalidate: opts.revalidate,\n },\n );\n return blogs.map((blog) => ({\n url: `${baseUrl}${pathPrefix}/${blog.slug}`,\n lastModified: new Date(blog.published_at),\n }));\n}\n\nexport type { Blog } from '../types';\nexport {\n generateBlogMetadata,\n generateBlogIndexMetadata,\n type BlogAlternatesOptions,\n type BlogMetadataOptions,\n type BlogIndexMetadataOptions,\n} from './metadata';\nexport { BlogPage, generateBlogPageMetadata } from './BlogPage';\nexport type { BlogPageRouteParams } from './BlogPage';\n"],"mappings":"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","names":["jsx","Fragment","jsx","jsxs","jsx","jsx","jsxs","trimTrailingSlash","normalizePathPrefix"]}
|