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