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