chaingrow-blog 0.2.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,93 +21,777 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var react_exports = {};
22
22
  __export(react_exports, {
23
23
  BlockRenderer: () => BlockRenderer,
24
+ BlogDetail: () => BlogDetail,
25
+ BlogIndex: () => BlogIndex,
26
+ BlogJsonLd: () => BlogJsonLd,
24
27
  BlogRenderer: () => BlogRenderer,
25
- DefaultCode: () => DefaultCode,
28
+ DefaultBlockquote: () => DefaultBlockquote,
29
+ DefaultBulletList: () => DefaultBulletList,
30
+ DefaultCodeBlock: () => DefaultCodeBlock,
31
+ DefaultDivider: () => DefaultDivider,
26
32
  DefaultHeading: () => DefaultHeading,
27
33
  DefaultImage: () => DefaultImage,
28
- DefaultList: () => DefaultList,
34
+ DefaultMedia: () => DefaultMedia,
35
+ DefaultOrderedList: () => DefaultOrderedList,
29
36
  DefaultParagraph: () => DefaultParagraph,
30
- DefaultQuote: () => DefaultQuote
37
+ JsonLdGraph: () => JsonLdGraph,
38
+ distributeMediaPositions: () => distributeMediaPositions,
39
+ interleaveMedia: () => interleaveMedia,
40
+ mergeBlogs: () => mergeBlogs,
41
+ toBlogCard: () => toBlogCard
31
42
  });
32
43
  module.exports = __toCommonJS(react_exports);
33
44
 
45
+ // src/media-placement.ts
46
+ function distributeMediaPositions(blockCount, mediaCount) {
47
+ if (mediaCount <= 0) return [];
48
+ if (blockCount <= 0) return Array.from({ length: mediaCount }, () => 0);
49
+ return Array.from(
50
+ { length: mediaCount },
51
+ (_, i) => Math.floor(i * blockCount / mediaCount)
52
+ );
53
+ }
54
+ function interleaveMedia(blocks, media) {
55
+ const mediaList = media ?? [];
56
+ const positions = distributeMediaPositions(blocks.length, mediaList.length);
57
+ const slots = [];
58
+ let mediaCursor = 0;
59
+ for (let b = 0; b <= blocks.length; b++) {
60
+ while (mediaCursor < mediaList.length && positions[mediaCursor] === b) {
61
+ const media2 = mediaList[mediaCursor];
62
+ slots.push({ kind: "media", media: media2, index: mediaCursor });
63
+ mediaCursor++;
64
+ }
65
+ if (b < blocks.length) {
66
+ const block = blocks[b];
67
+ slots.push({ kind: "block", block, index: b });
68
+ }
69
+ }
70
+ return slots;
71
+ }
72
+
34
73
  // src/react/defaults.tsx
35
74
  var import_jsx_runtime = require("react/jsx-runtime");
36
75
  var DefaultHeading = ({
37
76
  level,
38
- children
77
+ children,
78
+ className
39
79
  }) => {
40
- const Tag = level === 2 ? "h2" : level === 4 ? "h4" : "h3";
41
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tag, { children });
80
+ if (level === 1) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className, children });
81
+ if (level === 3) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className, children });
82
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className, children });
42
83
  };
43
84
  var DefaultParagraph = ({
44
- children
45
- }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children });
46
- var DefaultList = ({ items }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: item }, i)) });
47
- var DefaultQuote = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("blockquote", { children });
48
- var DefaultCode = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children }) });
85
+ children,
86
+ className
87
+ }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className, children });
88
+ var DefaultBulletList = ({
89
+ items,
90
+ className,
91
+ renderBlock
92
+ }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: item.content.map((b, j) => renderBlock(b, j)) }, i)) });
93
+ var DefaultOrderedList = ({
94
+ items,
95
+ className,
96
+ renderBlock
97
+ }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { className, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: item.content.map((b, j) => renderBlock(b, j)) }, i)) });
98
+ var DefaultBlockquote = ({
99
+ children,
100
+ className
101
+ }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("blockquote", { className, children });
102
+ var DefaultCodeBlock = ({
103
+ language,
104
+ code,
105
+ className
106
+ }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: language ? `language-${language}` : void 0, children: code }) });
49
107
  var DefaultImage = ({
50
108
  src,
51
- alt
109
+ alt,
110
+ caption,
111
+ width,
112
+ height,
113
+ className
52
114
  }) => {
53
- if (!src) return null;
54
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src, alt });
115
+ const img = (
116
+ // eslint-disable-next-line @next/next/no-img-element
117
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src, alt, width, height })
118
+ );
119
+ if (caption) {
120
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("figure", { className, children: [
121
+ img,
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("figcaption", { children: caption })
123
+ ] });
124
+ }
125
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className, children: img });
126
+ };
127
+ var DefaultDivider = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { className });
128
+ var DefaultMedia = ({ media }) => {
129
+ if (media.media_type === "VIDEO") {
130
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
131
+ "video",
132
+ {
133
+ src: media.media_url,
134
+ controls: true,
135
+ playsInline: true,
136
+ preload: "metadata"
137
+ }
138
+ );
139
+ }
140
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: media.media_url, alt: media.prompt });
55
141
  };
56
142
 
57
143
  // src/react/BlogRenderer.tsx
58
144
  var import_jsx_runtime2 = require("react/jsx-runtime");
59
- var parseListItems = (content) => content.split("\n").map((line) => line.replace(/^\s*[-•*]\s*/, "").trim()).filter(Boolean);
145
+ var MARK_ORDER = [
146
+ "bold",
147
+ "italic",
148
+ "strike",
149
+ "code",
150
+ "link"
151
+ ];
152
+ function InlineRenderer({ inline }) {
153
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: inline.map((run, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineRun, { run }, i)) });
154
+ }
155
+ function InlineRun({ run }) {
156
+ const marks = run.marks ? [...run.marks].sort(
157
+ (a, b) => MARK_ORDER.indexOf(b.type) - MARK_ORDER.indexOf(a.type)
158
+ ) : [];
159
+ let node = run.text;
160
+ for (const mark of marks) {
161
+ switch (mark.type) {
162
+ case "bold":
163
+ node = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: node });
164
+ break;
165
+ case "italic":
166
+ node = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("em", { children: node });
167
+ break;
168
+ case "strike":
169
+ node = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("s", { children: node });
170
+ break;
171
+ case "code":
172
+ node = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { children: node });
173
+ break;
174
+ case "link":
175
+ node = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: mark.href, rel: "noopener noreferrer", children: node });
176
+ break;
177
+ }
178
+ }
179
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: node });
180
+ }
60
181
  function BlockRenderer({
61
182
  block,
62
- components = {}
183
+ components = {},
184
+ classNames = {},
185
+ renderBlock
63
186
  }) {
187
+ const recurse = renderBlock ?? ((b, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
188
+ BlockRenderer,
189
+ {
190
+ block: b,
191
+ components,
192
+ classNames,
193
+ renderBlock: recurse
194
+ },
195
+ i
196
+ ));
64
197
  switch (block.type) {
65
198
  case "heading": {
66
199
  const Heading = components.heading ?? DefaultHeading;
67
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Heading, { level: block.level ?? 2, children: block.content });
200
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Heading, { level: block.level, className: classNames.heading, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineRenderer, { inline: block.content }) });
68
201
  }
69
202
  case "paragraph": {
70
203
  const Paragraph = components.paragraph ?? DefaultParagraph;
71
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Paragraph, { children: block.content });
204
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Paragraph, { className: classNames.paragraph, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineRenderer, { inline: block.content }) });
205
+ }
206
+ case "bulletList": {
207
+ const BulletList = components.bulletList ?? DefaultBulletList;
208
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
209
+ BulletList,
210
+ {
211
+ items: block.items,
212
+ className: classNames.bulletList,
213
+ renderBlock: recurse
214
+ }
215
+ );
72
216
  }
73
- case "list": {
74
- const List = components.list ?? DefaultList;
75
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(List, { items: parseListItems(block.content) });
217
+ case "orderedList": {
218
+ const OrderedList = components.orderedList ?? DefaultOrderedList;
219
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
220
+ OrderedList,
221
+ {
222
+ items: block.items,
223
+ className: classNames.orderedList,
224
+ renderBlock: recurse
225
+ }
226
+ );
76
227
  }
77
- case "quote": {
78
- const Quote = components.quote ?? DefaultQuote;
79
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Quote, { children: block.content });
228
+ case "blockquote": {
229
+ const Blockquote = components.blockquote ?? DefaultBlockquote;
230
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Blockquote, { className: classNames.blockquote, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineRenderer, { inline: block.content }) });
80
231
  }
81
- case "code": {
82
- const Code = components.code ?? DefaultCode;
83
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Code, { children: block.content });
232
+ case "codeBlock": {
233
+ const CodeBlock = components.codeBlock ?? DefaultCodeBlock;
234
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
235
+ CodeBlock,
236
+ {
237
+ language: block.language,
238
+ code: block.code,
239
+ className: classNames.codeBlock
240
+ }
241
+ );
84
242
  }
85
- case "image_placeholder": {
86
- const Image = components.image ?? DefaultImage;
87
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Image, { alt: block.content });
243
+ case "image": {
244
+ const ImageComp = components.image ?? DefaultImage;
245
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
246
+ ImageComp,
247
+ {
248
+ src: block.src,
249
+ alt: block.alt,
250
+ caption: block.caption,
251
+ width: block.width,
252
+ height: block.height,
253
+ mediaId: block.mediaId,
254
+ className: classNames.image
255
+ }
256
+ );
257
+ }
258
+ case "divider": {
259
+ const Divider = components.divider ?? DefaultDivider;
260
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Divider, { className: classNames.divider });
88
261
  }
89
- default:
90
- return null;
91
262
  }
92
263
  }
93
264
  function BlogRenderer({
94
265
  blog,
95
- components = {}
266
+ components = {},
267
+ classNames = {}
96
268
  }) {
269
+ const Media = components.media ?? DefaultMedia;
97
270
  const Wrapper = components.wrapper;
98
- const content = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: blog.blocks.map((block, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BlockRenderer, { block, components }, i)) });
271
+ const slots = interleaveMedia(blog.blocks, blog.generatedMedia);
272
+ const content = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: slots.map(
273
+ (slot) => slot.kind === "block" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
274
+ BlockRenderer,
275
+ {
276
+ block: slot.block,
277
+ components,
278
+ classNames
279
+ },
280
+ `b:${slot.index}`
281
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Media, { media: slot.media }, `m:${slot.media.id}`)
282
+ ) });
99
283
  if (Wrapper) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Wrapper, { blog, children: content });
100
284
  return content;
101
285
  }
286
+
287
+ // src/react/BlogDetail.tsx
288
+ var import_jsx_runtime3 = require("react/jsx-runtime");
289
+ var defaultFormatDate = (iso) => new Date(iso).toLocaleDateString();
290
+ function DefaultHeader({
291
+ blog,
292
+ formatDate
293
+ }) {
294
+ const hasInlineMedia = (blog.generatedMedia?.length ?? 0) > 0;
295
+ const heroImage = !hasInlineMedia && blog.og_image ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: blog.og_image, alt: blog.title }) : null;
296
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
297
+ heroImage,
298
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { children: blog.title }),
299
+ blog.published_at && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("time", { dateTime: blog.published_at, children: formatDate(blog.published_at) }),
300
+ blog.description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: blog.description })
301
+ ] });
302
+ }
303
+ function BlogDetail({
304
+ blog,
305
+ components,
306
+ formatDate = defaultFormatDate
307
+ }) {
308
+ const Header = components?.Header ?? DefaultHeader;
309
+ const Footer = components?.Footer;
310
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("article", { children: [
311
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Header, { blog, formatDate }),
312
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BlogRenderer, { blog, components: components?.blocks }),
313
+ Footer ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Footer, { blog }) : null
314
+ ] });
315
+ }
316
+
317
+ // src/client.ts
318
+ function createClient(opts) {
319
+ const baseUrl = opts.apiUrl.replace(/\/$/, "");
320
+ const doFetch = opts.fetch ?? fetch;
321
+ const request = async (path, init) => {
322
+ const url = `${baseUrl}${path}`;
323
+ const method = init?.method ?? "GET";
324
+ const buildInit = () => ({
325
+ ...init,
326
+ headers: {
327
+ "X-API-Key": opts.apiKey,
328
+ Accept: "application/json",
329
+ ...init?.headers ?? {}
330
+ }
331
+ });
332
+ let res;
333
+ try {
334
+ res = await doFetch(url, buildInit());
335
+ if (res.status === 429) {
336
+ const waitSec = parseInt(res.headers.get("Retry-After") ?? "1", 10);
337
+ await new Promise(
338
+ (resolve) => setTimeout(resolve, Math.max(0, waitSec) * 1e3)
339
+ );
340
+ res = await doFetch(url, buildInit());
341
+ }
342
+ } catch (err) {
343
+ const cause = err instanceof Error ? err.cause : void 0;
344
+ const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : void 0;
345
+ const detail = code ? `${code}${err instanceof Error && err.message ? ` (${err.message})` : ""}` : err instanceof Error ? err.message : String(err);
346
+ const e = new Error(
347
+ `chaingrow-blog: could not reach ${method} ${url} \u2014 ${detail}. Set CHAINGROW_API_URL to override the API host (default https://api.chaingrow.de).`
348
+ );
349
+ e.cause = err;
350
+ throw e;
351
+ }
352
+ if (res.status === 404) return null;
353
+ if (!res.ok) {
354
+ throw new Error(
355
+ `chaingrow-blog: ${method} ${url} failed (${res.status})`
356
+ );
357
+ }
358
+ return await res.json();
359
+ };
360
+ return {
361
+ async getBlog(slug, language) {
362
+ const qs = language ? `?language=${encodeURIComponent(language)}` : "";
363
+ return request(
364
+ `/api/v1/blogs/by-slug/${encodeURIComponent(slug)}${qs}`
365
+ );
366
+ },
367
+ async getBlogById(id) {
368
+ return request(`/api/v1/blogs/${encodeURIComponent(id)}`);
369
+ },
370
+ async listBlogs(listOpts = {}) {
371
+ const collected = [];
372
+ const MAX_PAGES = 100;
373
+ for (let page = 0; page < MAX_PAGES; page++) {
374
+ const params = new URLSearchParams();
375
+ if (listOpts.status) params.set("status", listOpts.status);
376
+ if (listOpts.language) params.set("language", listOpts.language);
377
+ if (collected.length > 0)
378
+ params.set("offset", String(collected.length));
379
+ const qs = params.toString();
380
+ const response = await request(
381
+ `/api/v1/blogs${qs ? `?${qs}` : ""}`
382
+ );
383
+ if (!response) break;
384
+ const batch = response.data ?? [];
385
+ collected.push(...batch);
386
+ const total = typeof response.total === "number" ? response.total : collected.length;
387
+ if (batch.length === 0 || collected.length >= total) break;
388
+ }
389
+ if (listOpts.language) {
390
+ return collected.filter((b) => b.language === listOpts.language);
391
+ }
392
+ return collected;
393
+ }
394
+ };
395
+ }
396
+
397
+ // src/json-ld.ts
398
+ var Schema = Object.freeze({ context: "https://schema.org" });
399
+ function buildBlogPostingSchema(blog, opts = {}) {
400
+ const schema = {
401
+ "@type": "BlogPosting",
402
+ headline: blog.title,
403
+ datePublished: blog.published_at
404
+ };
405
+ if (blog.description) schema.description = blog.description;
406
+ if (blog.og_image) schema.image = blog.og_image;
407
+ if (opts.url) {
408
+ schema.url = opts.url;
409
+ schema.mainEntityOfPage = opts.url;
410
+ }
411
+ if (opts.author) {
412
+ schema.author = {
413
+ "@type": opts.author.type ?? "Organization",
414
+ name: opts.author.name,
415
+ ...opts.author.url ? { url: opts.author.url } : {}
416
+ };
417
+ }
418
+ if (opts.publisher) {
419
+ schema.publisher = {
420
+ "@type": "Organization",
421
+ name: opts.publisher.name,
422
+ ...opts.publisher.logo ? { logo: { "@type": "ImageObject", url: opts.publisher.logo } } : {}
423
+ };
424
+ }
425
+ return schema;
426
+ }
427
+ function buildFAQPageSchema(faq) {
428
+ if (!faq || faq.length === 0) return null;
429
+ return {
430
+ "@type": "FAQPage",
431
+ mainEntity: faq.map(({ question, answer }) => ({
432
+ "@type": "Question",
433
+ name: question,
434
+ acceptedAnswer: { "@type": "Answer", text: answer }
435
+ }))
436
+ };
437
+ }
438
+ function buildBlogJsonLd(blog, opts = {}) {
439
+ const items = [buildBlogPostingSchema(blog, opts)];
440
+ const faq = buildFAQPageSchema(blog.faq);
441
+ if (faq) items.push(faq);
442
+ items.push(...extractLlmJsonLd(blog.structured_data));
443
+ return items;
444
+ }
445
+ var SCHEMA_ORG_CONTEXT = Schema.context;
446
+ function extractLlmJsonLd(sd) {
447
+ if (!sd || typeof sd !== "object") return [];
448
+ const ld = sd.jsonLd;
449
+ if (!Array.isArray(ld)) return [];
450
+ return ld.filter(
451
+ (entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry)
452
+ );
453
+ }
454
+
455
+ // src/react/BlogJsonLd.tsx
456
+ var import_jsx_runtime4 = require("react/jsx-runtime");
457
+ function JsonLdGraph({
458
+ items
459
+ }) {
460
+ if (!items || items.length === 0) return null;
461
+ const payload = JSON.stringify({
462
+ "@context": SCHEMA_ORG_CONTEXT,
463
+ "@graph": items
464
+ });
465
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
466
+ "script",
467
+ {
468
+ type: "application/ld+json",
469
+ dangerouslySetInnerHTML: { __html: payload }
470
+ }
471
+ );
472
+ }
473
+ function BlogJsonLd({
474
+ blog,
475
+ extraSchemas,
476
+ ...opts
477
+ }) {
478
+ const items = [...extraSchemas ?? [], ...buildBlogJsonLd(blog, opts)];
479
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(JsonLdGraph, { items });
480
+ }
481
+
482
+ // src/next/index.ts
483
+ var DEFAULT_API_URL = "https://api.chaingrow.de";
484
+ var DEFAULT_REVALIDATE_SECONDS = 60 * 60 * 24 * 7;
485
+ var resolveEnv = (opts = {}) => {
486
+ const apiUrl = opts.apiUrl ?? process.env.CHAINGROW_API_URL ?? DEFAULT_API_URL;
487
+ const apiKey = opts.apiKey ?? process.env.CHAINGROW_API_KEY;
488
+ if (!apiKey) {
489
+ throw new Error(
490
+ "chaingrow-blog: CHAINGROW_API_KEY is not set (env var or apiKey option required)"
491
+ );
492
+ }
493
+ return { apiUrl, apiKey };
494
+ };
495
+ var buildFetch = (tag, revalidate) => {
496
+ const effectiveRevalidate = revalidate === void 0 ? DEFAULT_REVALIDATE_SECONDS : revalidate;
497
+ return ((input, init) => fetch(input, {
498
+ ...init,
499
+ next: {
500
+ tags: ["chaingrow:blog", tag],
501
+ revalidate: effectiveRevalidate
502
+ }
503
+ }));
504
+ };
505
+ async function listBlogs(listOpts = {}, opts = {}) {
506
+ const { apiUrl, apiKey } = resolveEnv(opts);
507
+ const client = createClient({
508
+ apiUrl,
509
+ apiKey,
510
+ fetch: buildFetch("chaingrow:blog:list", opts.revalidate)
511
+ });
512
+ return client.listBlogs(listOpts);
513
+ }
514
+
515
+ // src/blog-cards.ts
516
+ var normalizePathPrefix = (s) => {
517
+ let p = s.trim();
518
+ if (!p.startsWith("/")) p = "/" + p;
519
+ while (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
520
+ return p;
521
+ };
522
+ function toBlogCard(blog, opts = {}) {
523
+ const prefix = normalizePathPrefix(opts.pathPrefix ?? "/blog");
524
+ return {
525
+ id: blog.id,
526
+ href: opts.hrefFor ? opts.hrefFor(blog) : `${prefix}/${blog.slug}`,
527
+ title: blog.title,
528
+ description: blog.description,
529
+ og_image: blog.og_image,
530
+ published_at: blog.published_at,
531
+ language: blog.language
532
+ };
533
+ }
534
+ function mergeBlogs(chaingrow, extra = []) {
535
+ const combined = [...chaingrow, ...extra];
536
+ combined.sort((a, b) => {
537
+ const ta = a.published_at ? Date.parse(a.published_at) : 0;
538
+ const tb = b.published_at ? Date.parse(b.published_at) : 0;
539
+ return tb - ta;
540
+ });
541
+ return combined;
542
+ }
543
+
544
+ // src/react/BlogIndex.tsx
545
+ var import_jsx_runtime5 = require("react/jsx-runtime");
546
+ var defaultFormatDate2 = (iso) => new Date(iso).toLocaleDateString();
547
+ async function BlogIndex({
548
+ format = "minimalist",
549
+ language,
550
+ pathPrefix,
551
+ hrefFor,
552
+ formatDate = defaultFormatDate2,
553
+ apiKey,
554
+ apiUrl,
555
+ revalidate
556
+ } = {}) {
557
+ const blogs = await listBlogs(
558
+ { status: "PUBLISHED", language },
559
+ { apiKey, apiUrl, revalidate }
560
+ );
561
+ const cards = blogs.map((b) => toBlogCard(b, { pathPrefix, hrefFor }));
562
+ if (cards.length === 0) {
563
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EmptyState, {});
564
+ }
565
+ if (format === "bare") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BareLayout, { cards, formatDate });
566
+ if (format === "list") return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ListLayout, { cards, formatDate });
567
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MinimalistLayout, { cards, formatDate });
568
+ }
569
+ function EmptyState() {
570
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "No posts yet." });
571
+ }
572
+ function BareLayout({
573
+ cards,
574
+ formatDate
575
+ }) {
576
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { children: cards.map((card) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { children: [
577
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href: card.href, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { children: card.title }) }),
578
+ card.published_at && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("time", { dateTime: card.published_at, children: formatDate(card.published_at) }),
579
+ card.description && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: card.description })
580
+ ] }, card.id)) });
581
+ }
582
+ var MINIMALIST_CSS = `
583
+ .cg-blog-min-container {
584
+ max-width: 1400px;
585
+ margin: 4rem auto;
586
+ padding: 0 2rem;
587
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
588
+ -webkit-font-smoothing: antialiased;
589
+ -moz-osx-font-smoothing: grayscale;
590
+ }
591
+ .cg-blog-min-grid {
592
+ display: grid;
593
+ grid-template-columns: repeat(3, 1fr);
594
+ column-gap: 2rem;
595
+ row-gap: 4rem;
596
+ list-style: none;
597
+ padding: 0;
598
+ margin: 0;
599
+ }
600
+ .cg-blog-min-grid > li { list-style: none; }
601
+ .cg-blog-min-card {
602
+ display: block;
603
+ text-decoration: none;
604
+ color: inherit;
605
+ }
606
+ .cg-blog-min-card:focus-visible {
607
+ outline: 2px solid currentColor;
608
+ outline-offset: 4px;
609
+ border-radius: 4px;
610
+ }
611
+ .cg-blog-min-image {
612
+ width: 100%;
613
+ aspect-ratio: 1 / 1;
614
+ object-fit: cover;
615
+ display: block;
616
+ border-radius: 6px;
617
+ background: rgba(128, 128, 128, 0.08);
618
+ margin-bottom: 1.25rem;
619
+ transition: opacity 200ms ease;
620
+ }
621
+ .cg-blog-min-card:hover .cg-blog-min-image {
622
+ opacity: 0.88;
623
+ }
624
+ .cg-blog-min-no-image {
625
+ width: 100%;
626
+ aspect-ratio: 1 / 1;
627
+ background: rgba(128, 128, 128, 0.08);
628
+ border-radius: 6px;
629
+ margin-bottom: 1.25rem;
630
+ }
631
+ .cg-blog-min-title {
632
+ font-size: 1.25rem;
633
+ font-weight: 600;
634
+ margin: 0 0 0.5rem;
635
+ line-height: 1.35;
636
+ color: inherit;
637
+ letter-spacing: -0.01em;
638
+ }
639
+ .cg-blog-min-date {
640
+ font-size: 0.8125rem;
641
+ color: currentColor;
642
+ opacity: 0.55;
643
+ margin: 0;
644
+ font-weight: 400;
645
+ }
646
+ @media (max-width: 1024px) {
647
+ .cg-blog-min-grid {
648
+ grid-template-columns: repeat(2, 1fr);
649
+ column-gap: 1.5rem;
650
+ row-gap: 3rem;
651
+ }
652
+ }
653
+ @media (max-width: 640px) {
654
+ .cg-blog-min-container {
655
+ margin: 2.5rem auto;
656
+ padding: 0 1.25rem;
657
+ }
658
+ .cg-blog-min-grid {
659
+ grid-template-columns: 1fr;
660
+ row-gap: 2.5rem;
661
+ }
662
+ .cg-blog-min-title { font-size: 1.125rem; }
663
+ }
664
+ `;
665
+ function MinimalistLayout({
666
+ cards,
667
+ formatDate
668
+ }) {
669
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "cg-blog-min-container", children: [
670
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { dangerouslySetInnerHTML: { __html: MINIMALIST_CSS } }),
671
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "cg-blog-min-grid", children: cards.map((card) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("a", { href: card.href, className: "cg-blog-min-card", children: [
672
+ card.og_image ? (
673
+ // eslint-disable-next-line @next/next/no-img-element
674
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
675
+ "img",
676
+ {
677
+ src: card.og_image,
678
+ alt: card.title,
679
+ className: "cg-blog-min-image",
680
+ loading: "lazy"
681
+ }
682
+ )
683
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "cg-blog-min-no-image", "aria-hidden": "true" }),
684
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "cg-blog-min-title", children: card.title }),
685
+ card.published_at && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "cg-blog-min-date", children: formatDate(card.published_at) })
686
+ ] }) }, card.id)) })
687
+ ] });
688
+ }
689
+ var LIST_CSS = `
690
+ .cg-blog-list { list-style: none; padding: 0; margin: 0; }
691
+ .cg-blog-list-item {
692
+ margin-bottom: 2rem;
693
+ padding-bottom: 2rem;
694
+ border-bottom: 1px solid currentColor;
695
+ }
696
+ .cg-blog-list-item { border-bottom-color: color-mix(in srgb, currentColor 15%, transparent); }
697
+ .cg-blog-list-item:last-child {
698
+ margin-bottom: 0;
699
+ padding-bottom: 0;
700
+ border-bottom: none;
701
+ }
702
+ .cg-blog-list-link {
703
+ display: flex;
704
+ gap: 1.5rem;
705
+ text-decoration: none;
706
+ color: inherit;
707
+ align-items: flex-start;
708
+ }
709
+ .cg-blog-list-image {
710
+ flex-shrink: 0;
711
+ width: 12rem;
712
+ height: 8rem;
713
+ object-fit: cover;
714
+ border-radius: 0.5rem;
715
+ background: rgba(128, 128, 128, 0.08);
716
+ transition: opacity 200ms ease;
717
+ }
718
+ .cg-blog-list-link:hover .cg-blog-list-image { opacity: 0.88; }
719
+ .cg-blog-list-body { flex: 1; min-width: 0; }
720
+ .cg-blog-list-date {
721
+ font-size: 0.75rem;
722
+ color: currentColor;
723
+ opacity: 0.55;
724
+ margin: 0 0 0.5rem 0;
725
+ }
726
+ .cg-blog-list-title {
727
+ font-size: 1.5rem;
728
+ font-weight: 700;
729
+ margin: 0 0 0.5rem 0;
730
+ line-height: 1.3;
731
+ color: inherit;
732
+ transition: opacity 150ms ease;
733
+ }
734
+ .cg-blog-list-link:hover .cg-blog-list-title { opacity: 0.7; }
735
+ .cg-blog-list-description {
736
+ font-size: 1rem;
737
+ color: currentColor;
738
+ opacity: 0.65;
739
+ margin: 0;
740
+ line-height: 1.6;
741
+ display: -webkit-box;
742
+ -webkit-line-clamp: 2;
743
+ -webkit-box-orient: vertical;
744
+ overflow: hidden;
745
+ }
746
+ @media (max-width: 640px) {
747
+ .cg-blog-list-link { flex-direction: column; }
748
+ .cg-blog-list-image { width: 100%; height: 12rem; }
749
+ }
750
+ `;
751
+ function ListLayout({
752
+ cards,
753
+ formatDate
754
+ }) {
755
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
756
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { dangerouslySetInnerHTML: { __html: LIST_CSS } }),
757
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "cg-blog-list", children: cards.map((card) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { className: "cg-blog-list-item", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("a", { href: card.href, className: "cg-blog-list-link", children: [
758
+ card.og_image && // eslint-disable-next-line @next/next/no-img-element
759
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
760
+ "img",
761
+ {
762
+ src: card.og_image,
763
+ alt: card.title,
764
+ className: "cg-blog-list-image"
765
+ }
766
+ ),
767
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "cg-blog-list-body", children: [
768
+ card.published_at && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "cg-blog-list-date", children: formatDate(card.published_at) }),
769
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "cg-blog-list-title", children: card.title }),
770
+ card.description && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "cg-blog-list-description", children: card.description })
771
+ ] })
772
+ ] }) }, card.id)) })
773
+ ] });
774
+ }
102
775
  // Annotate the CommonJS export names for ESM import in node:
103
776
  0 && (module.exports = {
104
777
  BlockRenderer,
778
+ BlogDetail,
779
+ BlogIndex,
780
+ BlogJsonLd,
105
781
  BlogRenderer,
106
- DefaultCode,
782
+ DefaultBlockquote,
783
+ DefaultBulletList,
784
+ DefaultCodeBlock,
785
+ DefaultDivider,
107
786
  DefaultHeading,
108
787
  DefaultImage,
109
- DefaultList,
788
+ DefaultMedia,
789
+ DefaultOrderedList,
110
790
  DefaultParagraph,
111
- DefaultQuote
791
+ JsonLdGraph,
792
+ distributeMediaPositions,
793
+ interleaveMedia,
794
+ mergeBlogs,
795
+ toBlogCard
112
796
  });
113
797
  //# sourceMappingURL=index.cjs.map