@fullstackdatasolutions/articles 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.cjs CHANGED
@@ -67,9 +67,12 @@ var __async = (__this, __arguments, generator) => {
67
67
  // src/server.ts
68
68
  var server_exports = {};
69
69
  __export(server_exports, {
70
+ ArticleContent: () => ArticleContent,
70
71
  categoryToSlug: () => categoryToSlug,
72
+ extractToc: () => extractToc,
71
73
  generateArticleMetadata: () => generateArticleMetadata,
72
74
  generateArticleStaticParams: () => generateArticleStaticParams,
75
+ generateArticlesIndexMetadata: () => generateArticlesIndexMetadata,
73
76
  generateCategoryMetadata: () => generateCategoryMetadata,
74
77
  generateCategoryStaticParams: () => generateCategoryStaticParams,
75
78
  getAdjacentArticles: () => getAdjacentArticles,
@@ -93,11 +96,14 @@ var import_node_path = __toESM(require("path"), 1);
93
96
  var import_reading_time = __toESM(require("reading-time"), 1);
94
97
 
95
98
  // src/markdown.ts
99
+ var import_rehype_autolink_headings = __toESM(require("rehype-autolink-headings"), 1);
96
100
  var import_rehype_prism_plus = __toESM(require("rehype-prism-plus"), 1);
97
101
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
102
+ var import_rehype_slug = __toESM(require("rehype-slug"), 1);
98
103
  var import_rehype_stringify = __toESM(require("rehype-stringify"), 1);
99
104
  var import_remark = require("remark");
100
105
  var import_remark_gfm = __toESM(require("remark-gfm"), 1);
106
+ var import_remark_github_blockquote_alert = __toESM(require("remark-github-blockquote-alert"), 1);
101
107
  var import_remark_parse = __toESM(require("remark-parse"), 1);
102
108
  var import_remark_rehype = __toESM(require("remark-rehype"), 1);
103
109
  var import_unist_util_visit = require("unist-util-visit");
@@ -128,6 +134,88 @@ function sanitizeImagePath(rawPath, articleSlug) {
128
134
  return `/articles/${articleSlug}/${cleanPath}`;
129
135
  }
130
136
  }
137
+ function styleFootnoteLinks(nodes) {
138
+ nodes.forEach((n) => {
139
+ var _a, _b;
140
+ if (n.type !== "element") return;
141
+ const el = n;
142
+ if (el.tagName === "a") {
143
+ const isBackRef = ((_a = el.properties) == null ? void 0 : _a["dataFootnoteBackref"]) !== void 0 || ((_b = el.children[0]) == null ? void 0 : _b.type) === "text" && el.children[0].value === "\u21A9";
144
+ if (isBackRef) {
145
+ el.properties.className = "text-primary hover:underline ml-1";
146
+ if (typeof el.properties.href === "string") {
147
+ el.properties.href = el.properties.href.replaceAll("#user-content-fnref-", "#ref-");
148
+ }
149
+ } else {
150
+ el.properties.className = "text-primary hover:underline break-all";
151
+ }
152
+ }
153
+ if (el.children) styleFootnoteLinks(el.children);
154
+ });
155
+ }
156
+ function processFootnoteRef(node) {
157
+ var _a, _b, _c, _d, _e, _f;
158
+ if (node.tagName !== "sup" || ((_b = (_a = node.children) == null ? void 0 : _a[0]) == null ? void 0 : _b.type) !== "element" || node.children[0].tagName !== "a")
159
+ return;
160
+ const link = node.children[0];
161
+ const href = (_c = link.properties) == null ? void 0 : _c.href;
162
+ if (typeof href !== "string" || !href.startsWith("#user-content-fn-")) return;
163
+ delete link.properties.target;
164
+ delete link.properties.rel;
165
+ link.properties.className = "text-primary hover:underline";
166
+ link.properties.href = href.replaceAll("#user-content-fn-", "#footnote-");
167
+ if (typeof ((_d = node.properties) == null ? void 0 : _d.id) === "string") {
168
+ link.properties.id = node.properties.id.replaceAll("user-content-fnref-", "ref-");
169
+ delete node.properties.id;
170
+ }
171
+ if (((_f = (_e = link.children) == null ? void 0 : _e[0]) == null ? void 0 : _f.type) === "text") {
172
+ link.children[0].value = `[${link.children[0].value}]`;
173
+ }
174
+ }
175
+ function processFootnotesSection(node) {
176
+ var _a;
177
+ const cls = (_a = node.properties) == null ? void 0 : _a.className;
178
+ const isFootnotes = node.tagName === "section" && (Array.isArray(cls) ? cls.includes("footnotes") : cls === "footnotes");
179
+ if (!isFootnotes) return;
180
+ const olCandidate = node.children.find(
181
+ (child) => child.type === "element" && child.tagName === "ol"
182
+ );
183
+ if ((olCandidate == null ? void 0 : olCandidate.type) !== "element") return;
184
+ const ol = olCandidate;
185
+ ol.properties.className = "list-decimal ml-6 space-y-2 text-sm text-muted-foreground";
186
+ ol.children.forEach((li) => {
187
+ if (li.type !== "element" || li.tagName !== "li") return;
188
+ const liEl = li;
189
+ if (liEl.properties) {
190
+ liEl.properties.className = "pl-2";
191
+ if (typeof liEl.properties.id === "string") {
192
+ liEl.properties.id = liEl.properties.id.replaceAll("user-content-fn-", "footnote-");
193
+ }
194
+ }
195
+ const pIndex = liEl.children.findIndex(
196
+ (child) => child.type === "element" && child.tagName === "p"
197
+ );
198
+ if (pIndex !== -1) {
199
+ const p = liEl.children[pIndex];
200
+ liEl.children.splice(pIndex, 1, ...p.children);
201
+ }
202
+ styleFootnoteLinks(liEl.children);
203
+ });
204
+ const hr = {
205
+ type: "element",
206
+ tagName: "hr",
207
+ properties: { className: "my-8 border-border" },
208
+ children: []
209
+ };
210
+ const h3 = {
211
+ type: "element",
212
+ tagName: "h3",
213
+ properties: { className: "text-lg font-semibold mb-4" },
214
+ children: [{ type: "text", value: "References" }]
215
+ };
216
+ node.children = [hr, h3, ol];
217
+ if (node.properties) node.properties.className = void 0;
218
+ }
131
219
  var customRenderer = () => {
132
220
  return (tree) => {
133
221
  (0, import_unist_util_visit.visit)(tree, "element", (node) => {
@@ -194,95 +282,8 @@ var customRenderer = () => {
194
282
  }
195
283
  });
196
284
  (0, import_unist_util_visit.visit)(tree, "element", (node) => {
197
- var _a, _b, _c, _d, _e, _f, _g, _h;
198
- if (node.tagName === "sup" && ((_b = (_a = node.children) == null ? void 0 : _a[0]) == null ? void 0 : _b.type) === "element" && node.children[0].tagName === "a") {
199
- const link = node.children[0];
200
- if (((_c = link.properties) == null ? void 0 : _c.href) && typeof link.properties.href === "string" && link.properties.href.startsWith("#user-content-fn-")) {
201
- if (link.properties) {
202
- delete link.properties.target;
203
- delete link.properties.rel;
204
- link.properties.className = "text-primary hover:underline";
205
- }
206
- link.properties.href = link.properties.href.replaceAll("#user-content-fn-", "#footnote-");
207
- if (((_d = node.properties) == null ? void 0 : _d.id) && typeof node.properties.id === "string") {
208
- const newId = node.properties.id.replaceAll("user-content-fnref-", "ref-");
209
- link.properties.id = newId;
210
- delete node.properties.id;
211
- }
212
- if (((_f = (_e = link.children) == null ? void 0 : _e[0]) == null ? void 0 : _f.type) === "text") {
213
- link.children[0].value = `[${link.children[0].value}]`;
214
- }
215
- }
216
- }
217
- if (node.tagName === "section" && (Array.isArray((_g = node.properties) == null ? void 0 : _g.className) ? node.properties.className.includes("footnotes") : ((_h = node.properties) == null ? void 0 : _h.className) === "footnotes")) {
218
- const olCandidate = node.children.find(
219
- (child) => child.type === "element" && child.tagName === "ol"
220
- );
221
- if ((olCandidate == null ? void 0 : olCandidate.type) === "element") {
222
- const ol = olCandidate;
223
- ol.properties.className = "list-decimal ml-6 space-y-2 text-sm text-muted-foreground";
224
- ol.children.forEach((li) => {
225
- if (li.type === "element" && li.tagName === "li") {
226
- const liEl = li;
227
- if (liEl.properties) {
228
- liEl.properties.className = "pl-2";
229
- if (typeof liEl.properties.id === "string") {
230
- liEl.properties.id = liEl.properties.id.replaceAll(
231
- "user-content-fn-",
232
- "footnote-"
233
- );
234
- }
235
- }
236
- const pIndex = liEl.children.findIndex(
237
- (child) => child.type === "element" && child.tagName === "p"
238
- );
239
- if (pIndex !== -1) {
240
- const p = liEl.children[pIndex];
241
- liEl.children.splice(pIndex, 1, ...p.children);
242
- }
243
- const styleLinks = (nodes) => {
244
- nodes.forEach((n) => {
245
- var _a2, _b2;
246
- if (n.type !== "element") return;
247
- const el = n;
248
- if (el.tagName === "a") {
249
- const isBackRef = ((_a2 = el.properties) == null ? void 0 : _a2["dataFootnoteBackref"]) !== void 0 || ((_b2 = el.children[0]) == null ? void 0 : _b2.type) === "text" && el.children[0].value === "\u21A9";
250
- if (isBackRef) {
251
- el.properties.className = "text-primary hover:underline ml-1";
252
- if (typeof el.properties.href === "string") {
253
- el.properties.href = el.properties.href.replaceAll(
254
- "#user-content-fnref-",
255
- "#ref-"
256
- );
257
- }
258
- } else {
259
- el.properties.className = "text-primary hover:underline break-all";
260
- }
261
- }
262
- if (el.children) styleLinks(el.children);
263
- });
264
- };
265
- styleLinks(liEl.children);
266
- }
267
- });
268
- const hr = {
269
- type: "element",
270
- tagName: "hr",
271
- properties: { className: "my-8 border-border" },
272
- children: []
273
- };
274
- const h3 = {
275
- type: "element",
276
- tagName: "h3",
277
- properties: { className: "text-lg font-semibold mb-4" },
278
- children: [{ type: "text", value: "References" }]
279
- };
280
- node.children = [hr, h3, ol];
281
- if (node.properties) {
282
- node.properties.className = void 0;
283
- }
284
- }
285
- }
285
+ processFootnoteRef(node);
286
+ processFootnotesSection(node);
286
287
  });
287
288
  };
288
289
  };
@@ -307,7 +308,7 @@ var rehypeProcessImages = (options = {}) => {
307
308
  function markdownToHtml(markdown, articleSlug) {
308
309
  return __async(this, null, function* () {
309
310
  try {
310
- let processor = (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_rehype.default).use(customRenderer).use(import_rehype_prism_plus.default).use(import_rehype_sanitize.default, {
311
+ let processor = (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_github_blockquote_alert.default).use(import_remark_rehype.default).use(customRenderer).use(import_rehype_slug.default).use(import_rehype_autolink_headings.default, { behavior: "wrap" }).use(import_rehype_prism_plus.default).use(import_rehype_sanitize.default, {
311
312
  attributes: {
312
313
  "*": ["className", "class", "id"],
313
314
  a: ["href", "target", "rel", "id"],
@@ -325,6 +326,31 @@ function markdownToHtml(markdown, articleSlug) {
325
326
  }
326
327
  });
327
328
  }
329
+ function nodeTextValue(c) {
330
+ return c.type === "text" ? c.value : "";
331
+ }
332
+ function extractHeadingItem(node) {
333
+ var _a;
334
+ const match = /^h([1-6])$/.exec(node.tagName);
335
+ if (!match) return null;
336
+ const id = typeof ((_a = node.properties) == null ? void 0 : _a.id) === "string" ? node.properties.id : "";
337
+ const text = node.children.map(nodeTextValue).join("");
338
+ if (!id || !text) return null;
339
+ return { id, depth: Number.parseInt(match[1], 10), text };
340
+ }
341
+ function extractToc(markdown) {
342
+ return __async(this, null, function* () {
343
+ const headings = [];
344
+ const collectHeadings = () => (tree) => {
345
+ (0, import_unist_util_visit.visit)(tree, "element", (node) => {
346
+ const item = extractHeadingItem(node);
347
+ if (item) headings.push(item);
348
+ });
349
+ };
350
+ yield (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_rehype.default).use(import_rehype_slug.default).use(collectHeadings).use(import_rehype_stringify.default).process(markdown);
351
+ return headings;
352
+ });
353
+ }
328
354
 
329
355
  // src/server-articles.ts
330
356
  var articlesDirectory = import_node_path.default.join(process.cwd(), "public/articles");
@@ -371,23 +397,20 @@ function sanitizeImagePath2(rawPath, articleSlug) {
371
397
  }
372
398
  return `/articles/${articleSlug}/${cleanPath}`;
373
399
  }
400
+ function findArticleFile(slug) {
401
+ const mdPath = import_node_path.default.join(articlesDirectory, slug, "article.md");
402
+ const mdxPath = import_node_path.default.join(articlesDirectory, slug, "article.mdx");
403
+ if (import_node_fs.default.existsSync(mdPath)) return { filePath: mdPath, contentType: "md" };
404
+ if (import_node_fs.default.existsSync(mdxPath)) return { filePath: mdxPath, contentType: "mdx" };
405
+ return null;
406
+ }
374
407
  function getAvailableArticleSlugs() {
375
408
  try {
376
409
  if (!import_node_fs.default.existsSync(articlesDirectory)) return [];
377
410
  const items = import_node_fs.default.readdirSync(articlesDirectory, { withFileTypes: true });
378
411
  return items.filter((item) => item.isDirectory()).filter((dir) => {
379
- const articleDir = import_node_path.default.join(articlesDirectory, dir.name);
380
412
  try {
381
- const entries = import_node_fs.default.readdirSync(articleDir, {
382
- withFileTypes: true
383
- });
384
- return entries.some((entry) => {
385
- const name = typeof entry === "string" ? entry : entry == null ? void 0 : entry.name;
386
- if (name !== "article.md") return false;
387
- if (typeof entry === "string") return true;
388
- if (typeof (entry == null ? void 0 : entry.isFile) === "function") return entry.isFile();
389
- return true;
390
- });
413
+ return findArticleFile(dir.name) !== null;
391
414
  } catch (e) {
392
415
  return false;
393
416
  }
@@ -397,45 +420,64 @@ function getAvailableArticleSlugs() {
397
420
  return [];
398
421
  }
399
422
  }
423
+ function parseDateField(rawDate) {
424
+ if (!rawDate) return void 0;
425
+ try {
426
+ const parsed = new Date(rawDate);
427
+ if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split("T")[0];
428
+ } catch (e) {
429
+ }
430
+ return void 0;
431
+ }
432
+ function resolveFeaturedImage(rawImage, slug) {
433
+ const img = typeof rawImage === "string" ? rawImage : "";
434
+ if (img && !img.startsWith("http")) return sanitizeImagePath2(img, slug) || "/placeholder-logo.png";
435
+ if (!img) return findArticleImage(slug) || "/placeholder-logo.png";
436
+ return img;
437
+ }
438
+ function parseFaqItems(raw) {
439
+ if (!Array.isArray(raw)) return void 0;
440
+ const items = raw.filter(
441
+ (item) => typeof item === "object" && item !== null && typeof item.question === "string" && typeof item.answer === "string"
442
+ );
443
+ return items.length ? items : void 0;
444
+ }
445
+ function parseHowToSteps(raw) {
446
+ if (!Array.isArray(raw)) return void 0;
447
+ const steps = raw.filter(
448
+ (item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.text === "string"
449
+ );
450
+ return steps.length ? steps : void 0;
451
+ }
400
452
  function getArticleSummary(slug) {
401
453
  return __async(this, null, function* () {
402
454
  try {
403
- const articlePath = import_node_path.default.join(articlesDirectory, slug, "article.md");
404
- if (!import_node_fs.default.existsSync(articlePath)) return null;
405
- const fileContent = import_node_fs.default.readFileSync(articlePath, "utf8");
455
+ const found = findArticleFile(slug);
456
+ if (!found) return null;
457
+ const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
406
458
  const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
407
459
  const readTime = getReadingTime(markdownContent);
408
- let featuredImage = data.featuredImage || "";
409
- if (featuredImage && !featuredImage.startsWith("http")) {
410
- const sanitizedPath = sanitizeImagePath2(featuredImage, slug);
411
- featuredImage = sanitizedPath || "/placeholder-logo.png";
412
- } else if (!featuredImage) {
413
- featuredImage = findArticleImage(slug) || "/placeholder-logo.png";
414
- }
415
- let articleDate;
416
- if (data.date) {
417
- try {
418
- const parsedDate = new Date(data.date);
419
- if (!Number.isNaN(parsedDate.getTime())) {
420
- articleDate = parsedDate.toISOString().split("T")[0];
421
- }
422
- } catch (e) {
423
- console.warn(`Invalid date for article ${slug}: ${data.date}`);
424
- }
425
- }
426
460
  const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
427
461
  const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
428
462
  return {
429
463
  slug,
430
464
  title: data.title || slug.replaceAll("-", " "),
431
465
  excerpt: data.excerpt || "",
432
- date: articleDate,
466
+ date: parseDateField(data.date),
467
+ lastmod: parseDateField(data.lastmod),
433
468
  author: data.author || "Andrew Blase",
434
469
  category: categories[0],
435
470
  categories,
436
471
  readTime,
437
- featuredImage,
438
- tags: data.tags || []
472
+ featuredImage: resolveFeaturedImage(data.featuredImage, slug),
473
+ tags: data.tags || [],
474
+ contentType: found.contentType,
475
+ draft: data.draft === true,
476
+ faq: parseFaqItems(data.faq),
477
+ howTo: parseHowToSteps(data.howTo),
478
+ canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
479
+ articleType: typeof data.articleType === "string" ? data.articleType : void 0,
480
+ series: typeof data.series === "string" ? data.series : void 0
439
481
  };
440
482
  } catch (error) {
441
483
  console.error(`Error loading article ${slug}:`, error);
@@ -445,46 +487,21 @@ function getArticleSummary(slug) {
445
487
  }
446
488
  var getArticleMetadata = (0, import_react.cache)((slug) => __async(void 0, null, function* () {
447
489
  try {
448
- const articlePath = import_node_path.default.join(articlesDirectory, slug, "article.md");
449
- if (!import_node_fs.default.existsSync(articlePath)) return null;
450
- const fileContent = import_node_fs.default.readFileSync(articlePath, "utf8");
451
- const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
452
- const htmlContent = yield markdownToHtml(markdownContent, slug);
453
- const readTime = getReadingTime(markdownContent);
454
- let featuredImage = data.featuredImage || "";
455
- if (featuredImage && !featuredImage.startsWith("http")) {
456
- const sanitizedPath = sanitizeImagePath2(featuredImage, slug);
457
- featuredImage = sanitizedPath || "/placeholder-logo.png";
458
- } else if (!featuredImage) {
459
- featuredImage = findArticleImage(slug) || "/placeholder-logo.png";
460
- }
461
- let articleDate;
462
- if (data.date) {
463
- try {
464
- const parsedDate = new Date(data.date);
465
- if (!Number.isNaN(parsedDate.getTime())) {
466
- articleDate = parsedDate.toISOString().split("T")[0];
467
- }
468
- } catch (e) {
469
- console.warn(`Invalid date for article ${slug}: ${data.date}`);
470
- }
490
+ const summary = yield getArticleSummary(slug);
491
+ if (!summary) return null;
492
+ const found = findArticleFile(slug);
493
+ if (!found) return null;
494
+ const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
495
+ const { content: markdownContent } = (0, import_gray_matter.default)(fileContent);
496
+ const toc = yield extractToc(markdownContent);
497
+ let htmlContent;
498
+ let mdxSource;
499
+ if (found.contentType === "mdx") {
500
+ mdxSource = markdownContent;
501
+ } else {
502
+ htmlContent = yield markdownToHtml(markdownContent, slug);
471
503
  }
472
- const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
473
- const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
474
- return {
475
- slug,
476
- title: data.title || slug.replaceAll("-", " "),
477
- excerpt: data.excerpt || "",
478
- date: articleDate,
479
- author: data.author || "Andrew Blase",
480
- category: categories[0],
481
- categories,
482
- readTime,
483
- featuredImage,
484
- tags: data.tags || [],
485
- content: markdownContent,
486
- htmlContent
487
- };
504
+ return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
488
505
  } catch (error) {
489
506
  console.error(`Error loading article ${slug}:`, error);
490
507
  return null;
@@ -494,7 +511,7 @@ var getAllArticles = (0, import_react.cache)(() => __async(void 0, null, functio
494
511
  const slugs = getAvailableArticleSlugs();
495
512
  const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
496
513
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
497
- return articles.filter((article) => article !== null).filter((article) => !article.date || article.date <= currentDate).sort((a, b) => {
514
+ return articles.filter((article) => article !== null).filter((article) => !article.date || article.date <= currentDate).filter((article) => !(article.draft && process.env.NODE_ENV === "production")).sort((a, b) => {
498
515
  if (!a.date && !b.date) return 0;
499
516
  if (!a.date) return 1;
500
517
  if (!b.date) return -1;
@@ -578,7 +595,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
578
595
  }
579
596
  function generateArticleMetadata(slug, config) {
580
597
  return __async(this, null, function* () {
581
- var _a, _b, _c, _d, _e, _f;
598
+ var _a, _b, _c, _d, _e, _f, _g;
582
599
  const article = yield getArticleMetadata(slug);
583
600
  if (!article) {
584
601
  return {
@@ -588,19 +605,14 @@ function generateArticleMetadata(slug, config) {
588
605
  }
589
606
  const siteUrl = config.siteUrl.replace(/\/$/, "");
590
607
  const articleUrl = `${siteUrl}/articles/${slug}`;
608
+ const canonicalUrl = (_a = article.canonicalUrl) != null ? _a : articleUrl;
591
609
  const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : `${siteUrl}/placeholder-logo.png`;
592
- const description = (_a = article.excerpt) != null ? _a : `Read ${article.title} on ${config.siteName}.`;
610
+ const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
593
611
  return {
594
612
  title: `${article.title} | ${config.siteName}`,
595
613
  description,
596
- keywords: [
597
- "volunteer management software",
598
- "political campaign software",
599
- "campaign operations",
600
- "civic tech",
601
- ...((_b = article.tags) != null ? _b : []).map((tag) => tag.toLowerCase())
602
- ].join(", "),
603
- openGraph: __spreadProps(__spreadValues({
614
+ keywords: [...((_c = article.tags) != null ? _c : []).map((tag) => tag.toLowerCase())].join(", "),
615
+ openGraph: __spreadProps(__spreadValues(__spreadValues({
604
616
  title: article.title,
605
617
  description,
606
618
  url: articleUrl,
@@ -608,9 +620,9 @@ function generateArticleMetadata(slug, config) {
608
620
  images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
609
621
  locale: "en_US",
610
622
  type: "article"
611
- }, article.date && { publishedTime: article.date }), {
623
+ }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), {
612
624
  authors: [article.author],
613
- tags: (_c = article.tags) != null ? _c : []
625
+ tags: (_d = article.tags) != null ? _d : []
614
626
  }),
615
627
  twitter: {
616
628
  card: "summary_large_image",
@@ -619,7 +631,7 @@ function generateArticleMetadata(slug, config) {
619
631
  images: [imageUrl]
620
632
  },
621
633
  alternates: {
622
- canonical: articleUrl
634
+ canonical: canonicalUrl
623
635
  },
624
636
  robots: {
625
637
  index: true,
@@ -632,18 +644,61 @@ function generateArticleMetadata(slug, config) {
632
644
  "max-snippet": -1
633
645
  }
634
646
  },
635
- other: __spreadProps(__spreadValues({
647
+ other: __spreadProps(__spreadValues(__spreadValues({
636
648
  "article:author": article.author
637
649
  }, article.date && {
638
650
  "article:published_time": new Date(article.date).toISOString()
651
+ }), article.lastmod && {
652
+ "article:modified_time": new Date(article.lastmod).toISOString()
639
653
  }), {
640
654
  "article:section": article.category,
641
- "article:tag": (_e = (_d = article.tags) == null ? void 0 : _d.join(",")) != null ? _e : "",
642
- "linkedin:owner": (_f = process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID) != null ? _f : ""
655
+ "article:tag": (_f = (_e = article.tags) == null ? void 0 : _e.join(",")) != null ? _f : "",
656
+ "linkedin:owner": (_g = process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID) != null ? _g : ""
643
657
  })
644
658
  };
645
659
  });
646
660
  }
661
+ function generateArticlesIndexMetadata(config) {
662
+ var _a, _b;
663
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
664
+ const indexUrl = `${siteUrl}/articles`;
665
+ const title = `Articles | ${config.siteName}`;
666
+ const description = (_b = (_a = config.hero) == null ? void 0 : _a.description) != null ? _b : `Expert analysis and insights from ${config.siteName}.`;
667
+ return {
668
+ title,
669
+ description,
670
+ openGraph: {
671
+ title,
672
+ description,
673
+ url: indexUrl,
674
+ siteName: config.siteName,
675
+ type: "website",
676
+ locale: "en_US"
677
+ },
678
+ twitter: {
679
+ card: "summary_large_image",
680
+ title,
681
+ description
682
+ },
683
+ alternates: {
684
+ canonical: indexUrl,
685
+ types: {
686
+ "application/rss+xml": `${siteUrl}/articles/feed.xml`
687
+ }
688
+ },
689
+ robots: {
690
+ index: true,
691
+ follow: true,
692
+ googleBot: {
693
+ index: true,
694
+ follow: true,
695
+ "max-video-preview": -1,
696
+ "max-image-preview": "large",
697
+ "max-snippet": -1
698
+ }
699
+ }
700
+ };
701
+ }
647
702
  function generateCategoryMetadata(categorySlug, config) {
648
703
  return __async(this, null, function* () {
649
704
  var _a, _b;
@@ -655,18 +710,37 @@ function generateCategoryMetadata(categorySlug, config) {
655
710
  const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[categorySlug];
656
711
  const fallback = `Browse ${articles.length} article${articles.length === 1 ? "" : "s"} in the ${categoryName} category.`;
657
712
  const description = typeof raw === "string" ? raw : (_b = raw == null ? void 0 : raw.short) != null ? _b : fallback;
713
+ const title = `${categoryName} Articles | ${config.siteName}`;
658
714
  return {
659
- title: `${categoryName} Articles | ${config.siteName}`,
715
+ title,
660
716
  description,
661
717
  openGraph: {
662
718
  title: `${categoryName} Articles`,
663
719
  description,
664
720
  url: categoryUrl,
665
721
  siteName: config.siteName,
666
- images: [{ url: articles[0].featuredImage }]
722
+ images: [{ url: articles[0].featuredImage }],
723
+ type: "website",
724
+ locale: "en_US"
725
+ },
726
+ twitter: {
727
+ card: "summary_large_image",
728
+ title: `${categoryName} Articles`,
729
+ description
667
730
  },
668
731
  alternates: {
669
732
  canonical: categoryUrl
733
+ },
734
+ robots: {
735
+ index: true,
736
+ follow: true,
737
+ googleBot: {
738
+ index: true,
739
+ follow: true,
740
+ "max-video-preview": -1,
741
+ "max-image-preview": "large",
742
+ "max-snippet": -1
743
+ }
670
744
  }
671
745
  };
672
746
  });
@@ -676,12 +750,17 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
676
750
  const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
677
751
  try {
678
752
  const [articles, categories] = yield Promise.all([getAllArticles(), getAllCategories()]);
679
- const articleEntries = articles.map((article) => ({
680
- url: `${baseUrl}/articles/${article.slug}`,
681
- lastModified: article.date ? new Date(article.date) : void 0,
682
- changeFrequency: "weekly",
683
- priority: 0.8
684
- }));
753
+ const articleEntries = articles.map((article) => {
754
+ var _a;
755
+ const dateStr = (_a = article.lastmod) != null ? _a : article.date;
756
+ const lastModified = dateStr ? new Date(dateStr) : void 0;
757
+ return {
758
+ url: `${baseUrl}/articles/${article.slug}`,
759
+ lastModified,
760
+ changeFrequency: "weekly",
761
+ priority: 0.8
762
+ };
763
+ });
685
764
  const categoryEntries = categories.map((cat) => ({
686
765
  url: `${baseUrl}/articles/category/${cat.slug}`,
687
766
  lastModified: /* @__PURE__ */ new Date(),
@@ -694,11 +773,24 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
694
773
  }
695
774
  });
696
775
  }
776
+
777
+ // src/ArticleContent.tsx
778
+ var import_rsc = require("next-mdx-remote/rsc");
779
+ var import_jsx_runtime = require("react/jsx-runtime");
780
+ function ArticleContent({ article, className }) {
781
+ if (article.contentType === "mdx" && article.mdxSource) {
782
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_rsc.MDXRemote, { source: article.mdxSource }) });
783
+ }
784
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
785
+ }
697
786
  // Annotate the CommonJS export names for ESM import in node:
698
787
  0 && (module.exports = {
788
+ ArticleContent,
699
789
  categoryToSlug,
790
+ extractToc,
700
791
  generateArticleMetadata,
701
792
  generateArticleStaticParams,
793
+ generateArticlesIndexMetadata,
702
794
  generateCategoryMetadata,
703
795
  generateCategoryStaticParams,
704
796
  getAdjacentArticles,