@fullstackdatasolutions/articles 0.4.3 → 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,7 +67,9 @@ 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,
73
75
  generateArticlesIndexMetadata: () => generateArticlesIndexMetadata,
@@ -94,11 +96,14 @@ var import_node_path = __toESM(require("path"), 1);
94
96
  var import_reading_time = __toESM(require("reading-time"), 1);
95
97
 
96
98
  // src/markdown.ts
99
+ var import_rehype_autolink_headings = __toESM(require("rehype-autolink-headings"), 1);
97
100
  var import_rehype_prism_plus = __toESM(require("rehype-prism-plus"), 1);
98
101
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
102
+ var import_rehype_slug = __toESM(require("rehype-slug"), 1);
99
103
  var import_rehype_stringify = __toESM(require("rehype-stringify"), 1);
100
104
  var import_remark = require("remark");
101
105
  var import_remark_gfm = __toESM(require("remark-gfm"), 1);
106
+ var import_remark_github_blockquote_alert = __toESM(require("remark-github-blockquote-alert"), 1);
102
107
  var import_remark_parse = __toESM(require("remark-parse"), 1);
103
108
  var import_remark_rehype = __toESM(require("remark-rehype"), 1);
104
109
  var import_unist_util_visit = require("unist-util-visit");
@@ -129,6 +134,88 @@ function sanitizeImagePath(rawPath, articleSlug) {
129
134
  return `/articles/${articleSlug}/${cleanPath}`;
130
135
  }
131
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
+ }
132
219
  var customRenderer = () => {
133
220
  return (tree) => {
134
221
  (0, import_unist_util_visit.visit)(tree, "element", (node) => {
@@ -195,95 +282,8 @@ var customRenderer = () => {
195
282
  }
196
283
  });
197
284
  (0, import_unist_util_visit.visit)(tree, "element", (node) => {
198
- var _a, _b, _c, _d, _e, _f, _g, _h;
199
- if (node.tagName === "sup" && ((_b = (_a = node.children) == null ? void 0 : _a[0]) == null ? void 0 : _b.type) === "element" && node.children[0].tagName === "a") {
200
- const link = node.children[0];
201
- if (((_c = link.properties) == null ? void 0 : _c.href) && typeof link.properties.href === "string" && link.properties.href.startsWith("#user-content-fn-")) {
202
- if (link.properties) {
203
- delete link.properties.target;
204
- delete link.properties.rel;
205
- link.properties.className = "text-primary hover:underline";
206
- }
207
- link.properties.href = link.properties.href.replaceAll("#user-content-fn-", "#footnote-");
208
- if (((_d = node.properties) == null ? void 0 : _d.id) && typeof node.properties.id === "string") {
209
- const newId = node.properties.id.replaceAll("user-content-fnref-", "ref-");
210
- link.properties.id = newId;
211
- delete node.properties.id;
212
- }
213
- if (((_f = (_e = link.children) == null ? void 0 : _e[0]) == null ? void 0 : _f.type) === "text") {
214
- link.children[0].value = `[${link.children[0].value}]`;
215
- }
216
- }
217
- }
218
- 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")) {
219
- const olCandidate = node.children.find(
220
- (child) => child.type === "element" && child.tagName === "ol"
221
- );
222
- if ((olCandidate == null ? void 0 : olCandidate.type) === "element") {
223
- const ol = olCandidate;
224
- ol.properties.className = "list-decimal ml-6 space-y-2 text-sm text-muted-foreground";
225
- ol.children.forEach((li) => {
226
- if (li.type === "element" && li.tagName === "li") {
227
- const liEl = li;
228
- if (liEl.properties) {
229
- liEl.properties.className = "pl-2";
230
- if (typeof liEl.properties.id === "string") {
231
- liEl.properties.id = liEl.properties.id.replaceAll(
232
- "user-content-fn-",
233
- "footnote-"
234
- );
235
- }
236
- }
237
- const pIndex = liEl.children.findIndex(
238
- (child) => child.type === "element" && child.tagName === "p"
239
- );
240
- if (pIndex !== -1) {
241
- const p = liEl.children[pIndex];
242
- liEl.children.splice(pIndex, 1, ...p.children);
243
- }
244
- const styleLinks = (nodes) => {
245
- nodes.forEach((n) => {
246
- var _a2, _b2;
247
- if (n.type !== "element") return;
248
- const el = n;
249
- if (el.tagName === "a") {
250
- 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";
251
- if (isBackRef) {
252
- el.properties.className = "text-primary hover:underline ml-1";
253
- if (typeof el.properties.href === "string") {
254
- el.properties.href = el.properties.href.replaceAll(
255
- "#user-content-fnref-",
256
- "#ref-"
257
- );
258
- }
259
- } else {
260
- el.properties.className = "text-primary hover:underline break-all";
261
- }
262
- }
263
- if (el.children) styleLinks(el.children);
264
- });
265
- };
266
- styleLinks(liEl.children);
267
- }
268
- });
269
- const hr = {
270
- type: "element",
271
- tagName: "hr",
272
- properties: { className: "my-8 border-border" },
273
- children: []
274
- };
275
- const h3 = {
276
- type: "element",
277
- tagName: "h3",
278
- properties: { className: "text-lg font-semibold mb-4" },
279
- children: [{ type: "text", value: "References" }]
280
- };
281
- node.children = [hr, h3, ol];
282
- if (node.properties) {
283
- node.properties.className = void 0;
284
- }
285
- }
286
- }
285
+ processFootnoteRef(node);
286
+ processFootnotesSection(node);
287
287
  });
288
288
  };
289
289
  };
@@ -308,7 +308,7 @@ var rehypeProcessImages = (options = {}) => {
308
308
  function markdownToHtml(markdown, articleSlug) {
309
309
  return __async(this, null, function* () {
310
310
  try {
311
- 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, {
312
312
  attributes: {
313
313
  "*": ["className", "class", "id"],
314
314
  a: ["href", "target", "rel", "id"],
@@ -326,6 +326,31 @@ function markdownToHtml(markdown, articleSlug) {
326
326
  }
327
327
  });
328
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
+ }
329
354
 
330
355
  // src/server-articles.ts
331
356
  var articlesDirectory = import_node_path.default.join(process.cwd(), "public/articles");
@@ -372,23 +397,20 @@ function sanitizeImagePath2(rawPath, articleSlug) {
372
397
  }
373
398
  return `/articles/${articleSlug}/${cleanPath}`;
374
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
+ }
375
407
  function getAvailableArticleSlugs() {
376
408
  try {
377
409
  if (!import_node_fs.default.existsSync(articlesDirectory)) return [];
378
410
  const items = import_node_fs.default.readdirSync(articlesDirectory, { withFileTypes: true });
379
411
  return items.filter((item) => item.isDirectory()).filter((dir) => {
380
- const articleDir = import_node_path.default.join(articlesDirectory, dir.name);
381
412
  try {
382
- const entries = import_node_fs.default.readdirSync(articleDir, {
383
- withFileTypes: true
384
- });
385
- return entries.some((entry) => {
386
- const name = typeof entry === "string" ? entry : entry == null ? void 0 : entry.name;
387
- if (name !== "article.md") return false;
388
- if (typeof entry === "string") return true;
389
- if (typeof (entry == null ? void 0 : entry.isFile) === "function") return entry.isFile();
390
- return true;
391
- });
413
+ return findArticleFile(dir.name) !== null;
392
414
  } catch (e) {
393
415
  return false;
394
416
  }
@@ -398,45 +420,64 @@ function getAvailableArticleSlugs() {
398
420
  return [];
399
421
  }
400
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
+ }
401
452
  function getArticleSummary(slug) {
402
453
  return __async(this, null, function* () {
403
454
  try {
404
- const articlePath = import_node_path.default.join(articlesDirectory, slug, "article.md");
405
- if (!import_node_fs.default.existsSync(articlePath)) return null;
406
- 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");
407
458
  const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
408
459
  const readTime = getReadingTime(markdownContent);
409
- let featuredImage = data.featuredImage || "";
410
- if (featuredImage && !featuredImage.startsWith("http")) {
411
- const sanitizedPath = sanitizeImagePath2(featuredImage, slug);
412
- featuredImage = sanitizedPath || "/placeholder-logo.png";
413
- } else if (!featuredImage) {
414
- featuredImage = findArticleImage(slug) || "/placeholder-logo.png";
415
- }
416
- let articleDate;
417
- if (data.date) {
418
- try {
419
- const parsedDate = new Date(data.date);
420
- if (!Number.isNaN(parsedDate.getTime())) {
421
- articleDate = parsedDate.toISOString().split("T")[0];
422
- }
423
- } catch (e) {
424
- console.warn(`Invalid date for article ${slug}: ${data.date}`);
425
- }
426
- }
427
460
  const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
428
461
  const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
429
462
  return {
430
463
  slug,
431
464
  title: data.title || slug.replaceAll("-", " "),
432
465
  excerpt: data.excerpt || "",
433
- date: articleDate,
466
+ date: parseDateField(data.date),
467
+ lastmod: parseDateField(data.lastmod),
434
468
  author: data.author || "Andrew Blase",
435
469
  category: categories[0],
436
470
  categories,
437
471
  readTime,
438
- featuredImage,
439
- 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
440
481
  };
441
482
  } catch (error) {
442
483
  console.error(`Error loading article ${slug}:`, error);
@@ -446,46 +487,21 @@ function getArticleSummary(slug) {
446
487
  }
447
488
  var getArticleMetadata = (0, import_react.cache)((slug) => __async(void 0, null, function* () {
448
489
  try {
449
- const articlePath = import_node_path.default.join(articlesDirectory, slug, "article.md");
450
- if (!import_node_fs.default.existsSync(articlePath)) return null;
451
- const fileContent = import_node_fs.default.readFileSync(articlePath, "utf8");
452
- const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
453
- const htmlContent = yield markdownToHtml(markdownContent, slug);
454
- const readTime = getReadingTime(markdownContent);
455
- let featuredImage = data.featuredImage || "";
456
- if (featuredImage && !featuredImage.startsWith("http")) {
457
- const sanitizedPath = sanitizeImagePath2(featuredImage, slug);
458
- featuredImage = sanitizedPath || "/placeholder-logo.png";
459
- } else if (!featuredImage) {
460
- featuredImage = findArticleImage(slug) || "/placeholder-logo.png";
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);
461
503
  }
462
- let articleDate;
463
- if (data.date) {
464
- try {
465
- const parsedDate = new Date(data.date);
466
- if (!Number.isNaN(parsedDate.getTime())) {
467
- articleDate = parsedDate.toISOString().split("T")[0];
468
- }
469
- } catch (e) {
470
- console.warn(`Invalid date for article ${slug}: ${data.date}`);
471
- }
472
- }
473
- const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
474
- const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
475
- return {
476
- slug,
477
- title: data.title || slug.replaceAll("-", " "),
478
- excerpt: data.excerpt || "",
479
- date: articleDate,
480
- author: data.author || "Andrew Blase",
481
- category: categories[0],
482
- categories,
483
- readTime,
484
- featuredImage,
485
- tags: data.tags || [],
486
- content: markdownContent,
487
- htmlContent
488
- };
504
+ return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
489
505
  } catch (error) {
490
506
  console.error(`Error loading article ${slug}:`, error);
491
507
  return null;
@@ -495,7 +511,7 @@ var getAllArticles = (0, import_react.cache)(() => __async(void 0, null, functio
495
511
  const slugs = getAvailableArticleSlugs();
496
512
  const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
497
513
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
498
- 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) => {
499
515
  if (!a.date && !b.date) return 0;
500
516
  if (!a.date) return 1;
501
517
  if (!b.date) return -1;
@@ -579,7 +595,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
579
595
  }
580
596
  function generateArticleMetadata(slug, config) {
581
597
  return __async(this, null, function* () {
582
- var _a, _b, _c, _d, _e, _f;
598
+ var _a, _b, _c, _d, _e, _f, _g;
583
599
  const article = yield getArticleMetadata(slug);
584
600
  if (!article) {
585
601
  return {
@@ -589,19 +605,14 @@ function generateArticleMetadata(slug, config) {
589
605
  }
590
606
  const siteUrl = config.siteUrl.replace(/\/$/, "");
591
607
  const articleUrl = `${siteUrl}/articles/${slug}`;
608
+ const canonicalUrl = (_a = article.canonicalUrl) != null ? _a : articleUrl;
592
609
  const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : `${siteUrl}/placeholder-logo.png`;
593
- 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}.`;
594
611
  return {
595
612
  title: `${article.title} | ${config.siteName}`,
596
613
  description,
597
- keywords: [
598
- "volunteer management software",
599
- "political campaign software",
600
- "campaign operations",
601
- "civic tech",
602
- ...((_b = article.tags) != null ? _b : []).map((tag) => tag.toLowerCase())
603
- ].join(", "),
604
- openGraph: __spreadProps(__spreadValues({
614
+ keywords: [...((_c = article.tags) != null ? _c : []).map((tag) => tag.toLowerCase())].join(", "),
615
+ openGraph: __spreadProps(__spreadValues(__spreadValues({
605
616
  title: article.title,
606
617
  description,
607
618
  url: articleUrl,
@@ -609,9 +620,9 @@ function generateArticleMetadata(slug, config) {
609
620
  images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
610
621
  locale: "en_US",
611
622
  type: "article"
612
- }, article.date && { publishedTime: article.date }), {
623
+ }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), {
613
624
  authors: [article.author],
614
- tags: (_c = article.tags) != null ? _c : []
625
+ tags: (_d = article.tags) != null ? _d : []
615
626
  }),
616
627
  twitter: {
617
628
  card: "summary_large_image",
@@ -620,7 +631,7 @@ function generateArticleMetadata(slug, config) {
620
631
  images: [imageUrl]
621
632
  },
622
633
  alternates: {
623
- canonical: articleUrl
634
+ canonical: canonicalUrl
624
635
  },
625
636
  robots: {
626
637
  index: true,
@@ -633,14 +644,16 @@ function generateArticleMetadata(slug, config) {
633
644
  "max-snippet": -1
634
645
  }
635
646
  },
636
- other: __spreadProps(__spreadValues({
647
+ other: __spreadProps(__spreadValues(__spreadValues({
637
648
  "article:author": article.author
638
649
  }, article.date && {
639
650
  "article:published_time": new Date(article.date).toISOString()
651
+ }), article.lastmod && {
652
+ "article:modified_time": new Date(article.lastmod).toISOString()
640
653
  }), {
641
654
  "article:section": article.category,
642
- "article:tag": (_e = (_d = article.tags) == null ? void 0 : _d.join(",")) != null ? _e : "",
643
- "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 : ""
644
657
  })
645
658
  };
646
659
  });
@@ -668,7 +681,10 @@ function generateArticlesIndexMetadata(config) {
668
681
  description
669
682
  },
670
683
  alternates: {
671
- canonical: indexUrl
684
+ canonical: indexUrl,
685
+ types: {
686
+ "application/rss+xml": `${siteUrl}/articles/feed.xml`
687
+ }
672
688
  },
673
689
  robots: {
674
690
  index: true,
@@ -734,12 +750,17 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
734
750
  const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
735
751
  try {
736
752
  const [articles, categories] = yield Promise.all([getAllArticles(), getAllCategories()]);
737
- const articleEntries = articles.map((article) => ({
738
- url: `${baseUrl}/articles/${article.slug}`,
739
- lastModified: article.date ? new Date(article.date) : void 0,
740
- changeFrequency: "weekly",
741
- priority: 0.8
742
- }));
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
+ });
743
764
  const categoryEntries = categories.map((cat) => ({
744
765
  url: `${baseUrl}/articles/category/${cat.slug}`,
745
766
  lastModified: /* @__PURE__ */ new Date(),
@@ -752,9 +773,21 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
752
773
  }
753
774
  });
754
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
+ }
755
786
  // Annotate the CommonJS export names for ESM import in node:
756
787
  0 && (module.exports = {
788
+ ArticleContent,
757
789
  categoryToSlug,
790
+ extractToc,
758
791
  generateArticleMetadata,
759
792
  generateArticleStaticParams,
760
793
  generateArticlesIndexMetadata,