@fullstackdatasolutions/articles 0.4.3 → 0.5.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/dist/server.cjs CHANGED
@@ -67,7 +67,10 @@ var __async = (__this, __arguments, generator) => {
67
67
  // src/server.ts
68
68
  var server_exports = {};
69
69
  __export(server_exports, {
70
+ ArticleContent: () => ArticleContent,
71
+ ArticleTOC: () => ArticleTOC,
70
72
  categoryToSlug: () => categoryToSlug,
73
+ extractToc: () => extractToc,
71
74
  generateArticleMetadata: () => generateArticleMetadata,
72
75
  generateArticleStaticParams: () => generateArticleStaticParams,
73
76
  generateArticlesIndexMetadata: () => generateArticlesIndexMetadata,
@@ -94,11 +97,14 @@ var import_node_path = __toESM(require("path"), 1);
94
97
  var import_reading_time = __toESM(require("reading-time"), 1);
95
98
 
96
99
  // src/markdown.ts
100
+ var import_rehype_autolink_headings = __toESM(require("rehype-autolink-headings"), 1);
97
101
  var import_rehype_prism_plus = __toESM(require("rehype-prism-plus"), 1);
98
102
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
103
+ var import_rehype_slug = __toESM(require("rehype-slug"), 1);
99
104
  var import_rehype_stringify = __toESM(require("rehype-stringify"), 1);
100
105
  var import_remark = require("remark");
101
106
  var import_remark_gfm = __toESM(require("remark-gfm"), 1);
107
+ var import_remark_github_blockquote_alert = __toESM(require("remark-github-blockquote-alert"), 1);
102
108
  var import_remark_parse = __toESM(require("remark-parse"), 1);
103
109
  var import_remark_rehype = __toESM(require("remark-rehype"), 1);
104
110
  var import_unist_util_visit = require("unist-util-visit");
@@ -129,6 +135,88 @@ function sanitizeImagePath(rawPath, articleSlug) {
129
135
  return `/articles/${articleSlug}/${cleanPath}`;
130
136
  }
131
137
  }
138
+ function styleFootnoteLinks(nodes) {
139
+ nodes.forEach((n) => {
140
+ var _a, _b;
141
+ if (n.type !== "element") return;
142
+ const el = n;
143
+ if (el.tagName === "a") {
144
+ 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";
145
+ if (isBackRef) {
146
+ el.properties.className = "text-primary hover:underline ml-1";
147
+ if (typeof el.properties.href === "string") {
148
+ el.properties.href = el.properties.href.replaceAll("#user-content-fnref-", "#ref-");
149
+ }
150
+ } else {
151
+ el.properties.className = "text-primary hover:underline break-all";
152
+ }
153
+ }
154
+ if (el.children) styleFootnoteLinks(el.children);
155
+ });
156
+ }
157
+ function processFootnoteRef(node) {
158
+ var _a, _b, _c, _d, _e, _f;
159
+ if (node.tagName !== "sup" || ((_b = (_a = node.children) == null ? void 0 : _a[0]) == null ? void 0 : _b.type) !== "element" || node.children[0].tagName !== "a")
160
+ return;
161
+ const link = node.children[0];
162
+ const href = (_c = link.properties) == null ? void 0 : _c.href;
163
+ if (typeof href !== "string" || !href.startsWith("#user-content-fn-")) return;
164
+ delete link.properties.target;
165
+ delete link.properties.rel;
166
+ link.properties.className = "text-primary hover:underline";
167
+ link.properties.href = href.replaceAll("#user-content-fn-", "#footnote-");
168
+ if (typeof ((_d = node.properties) == null ? void 0 : _d.id) === "string") {
169
+ link.properties.id = node.properties.id.replaceAll("user-content-fnref-", "ref-");
170
+ delete node.properties.id;
171
+ }
172
+ if (((_f = (_e = link.children) == null ? void 0 : _e[0]) == null ? void 0 : _f.type) === "text") {
173
+ link.children[0].value = `[${link.children[0].value}]`;
174
+ }
175
+ }
176
+ function processFootnotesSection(node) {
177
+ var _a;
178
+ const cls = (_a = node.properties) == null ? void 0 : _a.className;
179
+ const isFootnotes = node.tagName === "section" && (Array.isArray(cls) ? cls.includes("footnotes") : cls === "footnotes");
180
+ if (!isFootnotes) return;
181
+ const olCandidate = node.children.find(
182
+ (child) => child.type === "element" && child.tagName === "ol"
183
+ );
184
+ if ((olCandidate == null ? void 0 : olCandidate.type) !== "element") return;
185
+ const ol = olCandidate;
186
+ ol.properties.className = "list-decimal ml-6 space-y-2 text-sm text-muted-foreground";
187
+ ol.children.forEach((li) => {
188
+ if (li.type !== "element" || li.tagName !== "li") return;
189
+ const liEl = li;
190
+ if (liEl.properties) {
191
+ liEl.properties.className = "pl-2";
192
+ if (typeof liEl.properties.id === "string") {
193
+ liEl.properties.id = liEl.properties.id.replaceAll("user-content-fn-", "footnote-");
194
+ }
195
+ }
196
+ const pIndex = liEl.children.findIndex(
197
+ (child) => child.type === "element" && child.tagName === "p"
198
+ );
199
+ if (pIndex !== -1) {
200
+ const p = liEl.children[pIndex];
201
+ liEl.children.splice(pIndex, 1, ...p.children);
202
+ }
203
+ styleFootnoteLinks(liEl.children);
204
+ });
205
+ const hr = {
206
+ type: "element",
207
+ tagName: "hr",
208
+ properties: { className: "my-8 border-border" },
209
+ children: []
210
+ };
211
+ const h3 = {
212
+ type: "element",
213
+ tagName: "h3",
214
+ properties: { className: "text-lg font-semibold mb-4" },
215
+ children: [{ type: "text", value: "References" }]
216
+ };
217
+ node.children = [hr, h3, ol];
218
+ if (node.properties) node.properties.className = void 0;
219
+ }
132
220
  var customRenderer = () => {
133
221
  return (tree) => {
134
222
  (0, import_unist_util_visit.visit)(tree, "element", (node) => {
@@ -195,95 +283,8 @@ var customRenderer = () => {
195
283
  }
196
284
  });
197
285
  (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
- }
286
+ processFootnoteRef(node);
287
+ processFootnotesSection(node);
287
288
  });
288
289
  };
289
290
  };
@@ -308,7 +309,7 @@ var rehypeProcessImages = (options = {}) => {
308
309
  function markdownToHtml(markdown, articleSlug) {
309
310
  return __async(this, null, function* () {
310
311
  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, {
312
+ 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
313
  attributes: {
313
314
  "*": ["className", "class", "id"],
314
315
  a: ["href", "target", "rel", "id"],
@@ -326,6 +327,31 @@ function markdownToHtml(markdown, articleSlug) {
326
327
  }
327
328
  });
328
329
  }
330
+ function nodeTextValue(c) {
331
+ return c.type === "text" ? c.value : "";
332
+ }
333
+ function extractHeadingItem(node) {
334
+ var _a;
335
+ const match = /^h([1-6])$/.exec(node.tagName);
336
+ if (!match) return null;
337
+ const id = typeof ((_a = node.properties) == null ? void 0 : _a.id) === "string" ? node.properties.id : "";
338
+ const text = node.children.map(nodeTextValue).join("");
339
+ if (!id || !text) return null;
340
+ return { id, depth: Number.parseInt(match[1], 10), text };
341
+ }
342
+ function extractToc(markdown) {
343
+ return __async(this, null, function* () {
344
+ const headings = [];
345
+ const collectHeadings = () => (tree) => {
346
+ (0, import_unist_util_visit.visit)(tree, "element", (node) => {
347
+ const item = extractHeadingItem(node);
348
+ if (item) headings.push(item);
349
+ });
350
+ };
351
+ 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);
352
+ return headings;
353
+ });
354
+ }
329
355
 
330
356
  // src/server-articles.ts
331
357
  var articlesDirectory = import_node_path.default.join(process.cwd(), "public/articles");
@@ -372,23 +398,20 @@ function sanitizeImagePath2(rawPath, articleSlug) {
372
398
  }
373
399
  return `/articles/${articleSlug}/${cleanPath}`;
374
400
  }
401
+ function findArticleFile(slug) {
402
+ const mdPath = import_node_path.default.join(articlesDirectory, slug, "article.md");
403
+ const mdxPath = import_node_path.default.join(articlesDirectory, slug, "article.mdx");
404
+ if (import_node_fs.default.existsSync(mdPath)) return { filePath: mdPath, contentType: "md" };
405
+ if (import_node_fs.default.existsSync(mdxPath)) return { filePath: mdxPath, contentType: "mdx" };
406
+ return null;
407
+ }
375
408
  function getAvailableArticleSlugs() {
376
409
  try {
377
410
  if (!import_node_fs.default.existsSync(articlesDirectory)) return [];
378
411
  const items = import_node_fs.default.readdirSync(articlesDirectory, { withFileTypes: true });
379
412
  return items.filter((item) => item.isDirectory()).filter((dir) => {
380
- const articleDir = import_node_path.default.join(articlesDirectory, dir.name);
381
413
  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
- });
414
+ return findArticleFile(dir.name) !== null;
392
415
  } catch (e) {
393
416
  return false;
394
417
  }
@@ -398,45 +421,64 @@ function getAvailableArticleSlugs() {
398
421
  return [];
399
422
  }
400
423
  }
424
+ function parseDateField(rawDate) {
425
+ if (!rawDate) return void 0;
426
+ try {
427
+ const parsed = new Date(rawDate);
428
+ if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split("T")[0];
429
+ } catch (e) {
430
+ }
431
+ return void 0;
432
+ }
433
+ function resolveFeaturedImage(rawImage, slug) {
434
+ const img = typeof rawImage === "string" ? rawImage : "";
435
+ if (img && !img.startsWith("http")) return sanitizeImagePath2(img, slug) || "/placeholder-logo.png";
436
+ if (!img) return findArticleImage(slug) || "/placeholder-logo.png";
437
+ return img;
438
+ }
439
+ function parseFaqItems(raw) {
440
+ if (!Array.isArray(raw)) return void 0;
441
+ const items = raw.filter(
442
+ (item) => typeof item === "object" && item !== null && typeof item.question === "string" && typeof item.answer === "string"
443
+ );
444
+ return items.length ? items : void 0;
445
+ }
446
+ function parseHowToSteps(raw) {
447
+ if (!Array.isArray(raw)) return void 0;
448
+ const steps = raw.filter(
449
+ (item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.text === "string"
450
+ );
451
+ return steps.length ? steps : void 0;
452
+ }
401
453
  function getArticleSummary(slug) {
402
454
  return __async(this, null, function* () {
403
455
  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");
456
+ const found = findArticleFile(slug);
457
+ if (!found) return null;
458
+ const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
407
459
  const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
408
460
  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
461
  const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
428
462
  const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
429
463
  return {
430
464
  slug,
431
465
  title: data.title || slug.replaceAll("-", " "),
432
466
  excerpt: data.excerpt || "",
433
- date: articleDate,
467
+ date: parseDateField(data.date),
468
+ lastmod: parseDateField(data.lastmod),
434
469
  author: data.author || "Andrew Blase",
435
470
  category: categories[0],
436
471
  categories,
437
472
  readTime,
438
- featuredImage,
439
- tags: data.tags || []
473
+ featuredImage: resolveFeaturedImage(data.featuredImage, slug),
474
+ tags: data.tags || [],
475
+ contentType: found.contentType,
476
+ draft: data.draft === true,
477
+ faq: parseFaqItems(data.faq),
478
+ howTo: parseHowToSteps(data.howTo),
479
+ canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
480
+ articleType: typeof data.articleType === "string" ? data.articleType : void 0,
481
+ series: typeof data.series === "string" ? data.series : void 0
440
482
  };
441
483
  } catch (error) {
442
484
  console.error(`Error loading article ${slug}:`, error);
@@ -446,46 +488,21 @@ function getArticleSummary(slug) {
446
488
  }
447
489
  var getArticleMetadata = (0, import_react.cache)((slug) => __async(void 0, null, function* () {
448
490
  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";
491
+ const summary = yield getArticleSummary(slug);
492
+ if (!summary) return null;
493
+ const found = findArticleFile(slug);
494
+ if (!found) return null;
495
+ const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
496
+ const { content: markdownContent } = (0, import_gray_matter.default)(fileContent);
497
+ const toc = yield extractToc(markdownContent);
498
+ let htmlContent;
499
+ let mdxSource;
500
+ if (found.contentType === "mdx") {
501
+ mdxSource = markdownContent;
502
+ } else {
503
+ htmlContent = yield markdownToHtml(markdownContent, slug);
461
504
  }
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
- };
505
+ return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
489
506
  } catch (error) {
490
507
  console.error(`Error loading article ${slug}:`, error);
491
508
  return null;
@@ -495,7 +512,7 @@ var getAllArticles = (0, import_react.cache)(() => __async(void 0, null, functio
495
512
  const slugs = getAvailableArticleSlugs();
496
513
  const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
497
514
  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) => {
515
+ 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
516
  if (!a.date && !b.date) return 0;
500
517
  if (!a.date) return 1;
501
518
  if (!b.date) return -1;
@@ -579,7 +596,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
579
596
  }
580
597
  function generateArticleMetadata(slug, config) {
581
598
  return __async(this, null, function* () {
582
- var _a, _b, _c, _d, _e, _f;
599
+ var _a, _b, _c, _d, _e, _f, _g;
583
600
  const article = yield getArticleMetadata(slug);
584
601
  if (!article) {
585
602
  return {
@@ -589,19 +606,14 @@ function generateArticleMetadata(slug, config) {
589
606
  }
590
607
  const siteUrl = config.siteUrl.replace(/\/$/, "");
591
608
  const articleUrl = `${siteUrl}/articles/${slug}`;
609
+ const canonicalUrl = (_a = article.canonicalUrl) != null ? _a : articleUrl;
592
610
  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}.`;
611
+ const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
594
612
  return {
595
613
  title: `${article.title} | ${config.siteName}`,
596
614
  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({
615
+ keywords: [...((_c = article.tags) != null ? _c : []).map((tag) => tag.toLowerCase())].join(", "),
616
+ openGraph: __spreadProps(__spreadValues(__spreadValues({
605
617
  title: article.title,
606
618
  description,
607
619
  url: articleUrl,
@@ -609,9 +621,9 @@ function generateArticleMetadata(slug, config) {
609
621
  images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
610
622
  locale: "en_US",
611
623
  type: "article"
612
- }, article.date && { publishedTime: article.date }), {
624
+ }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), {
613
625
  authors: [article.author],
614
- tags: (_c = article.tags) != null ? _c : []
626
+ tags: (_d = article.tags) != null ? _d : []
615
627
  }),
616
628
  twitter: {
617
629
  card: "summary_large_image",
@@ -620,7 +632,7 @@ function generateArticleMetadata(slug, config) {
620
632
  images: [imageUrl]
621
633
  },
622
634
  alternates: {
623
- canonical: articleUrl
635
+ canonical: canonicalUrl
624
636
  },
625
637
  robots: {
626
638
  index: true,
@@ -633,14 +645,16 @@ function generateArticleMetadata(slug, config) {
633
645
  "max-snippet": -1
634
646
  }
635
647
  },
636
- other: __spreadProps(__spreadValues({
648
+ other: __spreadProps(__spreadValues(__spreadValues({
637
649
  "article:author": article.author
638
650
  }, article.date && {
639
651
  "article:published_time": new Date(article.date).toISOString()
652
+ }), article.lastmod && {
653
+ "article:modified_time": new Date(article.lastmod).toISOString()
640
654
  }), {
641
655
  "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 : ""
656
+ "article:tag": (_f = (_e = article.tags) == null ? void 0 : _e.join(",")) != null ? _f : "",
657
+ "linkedin:owner": (_g = process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID) != null ? _g : ""
644
658
  })
645
659
  };
646
660
  });
@@ -668,7 +682,10 @@ function generateArticlesIndexMetadata(config) {
668
682
  description
669
683
  },
670
684
  alternates: {
671
- canonical: indexUrl
685
+ canonical: indexUrl,
686
+ types: {
687
+ "application/rss+xml": `${siteUrl}/articles/feed.xml`
688
+ }
672
689
  },
673
690
  robots: {
674
691
  index: true,
@@ -734,12 +751,17 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
734
751
  const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
735
752
  try {
736
753
  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
- }));
754
+ const articleEntries = articles.map((article) => {
755
+ var _a;
756
+ const dateStr = (_a = article.lastmod) != null ? _a : article.date;
757
+ const lastModified = dateStr ? new Date(dateStr) : void 0;
758
+ return {
759
+ url: `${baseUrl}/articles/${article.slug}`,
760
+ lastModified,
761
+ changeFrequency: "weekly",
762
+ priority: 0.8
763
+ };
764
+ });
743
765
  const categoryEntries = categories.map((cat) => ({
744
766
  url: `${baseUrl}/articles/category/${cat.slug}`,
745
767
  lastModified: /* @__PURE__ */ new Date(),
@@ -752,9 +774,46 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
752
774
  }
753
775
  });
754
776
  }
777
+
778
+ // src/ArticleContent.tsx
779
+ var import_rsc = require("next-mdx-remote/rsc");
780
+ var import_jsx_runtime = require("react/jsx-runtime");
781
+ function ArticleContent({ article, className }) {
782
+ if (article.contentType === "mdx" && article.mdxSource) {
783
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_rsc.MDXRemote, { source: article.mdxSource }) });
784
+ }
785
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
786
+ }
787
+
788
+ // src/ArticleTOC.tsx
789
+ var import_jsx_runtime2 = require("react/jsx-runtime");
790
+ function ArticleTOC({ toc, className }) {
791
+ if (!toc.length) return null;
792
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
793
+ "nav",
794
+ {
795
+ "aria-label": "Table of contents",
796
+ className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
797
+ children: [
798
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
799
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
800
+ "a",
801
+ {
802
+ href: `#${item.id}`,
803
+ className: "text-muted-foreground hover:text-foreground transition-colors",
804
+ children: item.text
805
+ }
806
+ ) }, item.id)) })
807
+ ]
808
+ }
809
+ );
810
+ }
755
811
  // Annotate the CommonJS export names for ESM import in node:
756
812
  0 && (module.exports = {
813
+ ArticleContent,
814
+ ArticleTOC,
757
815
  categoryToSlug,
816
+ extractToc,
758
817
  generateArticleMetadata,
759
818
  generateArticleStaticParams,
760
819
  generateArticlesIndexMetadata,