@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.js CHANGED
@@ -46,11 +46,14 @@ import path from "node:path";
46
46
  import readingTime from "reading-time";
47
47
 
48
48
  // src/markdown.ts
49
+ import rehypeAutolinkHeadings from "rehype-autolink-headings";
49
50
  import rehypePrism from "rehype-prism-plus";
50
51
  import rehypeSanitize from "rehype-sanitize";
52
+ import rehypeSlug from "rehype-slug";
51
53
  import rehypeStringify from "rehype-stringify";
52
54
  import { remark } from "remark";
53
55
  import remarkGfm from "remark-gfm";
56
+ import remarkGithubBlockquoteAlert from "remark-github-blockquote-alert";
54
57
  import remarkParse from "remark-parse";
55
58
  import remarkRehype from "remark-rehype";
56
59
  import { visit } from "unist-util-visit";
@@ -81,6 +84,88 @@ function sanitizeImagePath(rawPath, articleSlug) {
81
84
  return `/articles/${articleSlug}/${cleanPath}`;
82
85
  }
83
86
  }
87
+ function styleFootnoteLinks(nodes) {
88
+ nodes.forEach((n) => {
89
+ var _a, _b;
90
+ if (n.type !== "element") return;
91
+ const el = n;
92
+ if (el.tagName === "a") {
93
+ 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";
94
+ if (isBackRef) {
95
+ el.properties.className = "text-primary hover:underline ml-1";
96
+ if (typeof el.properties.href === "string") {
97
+ el.properties.href = el.properties.href.replaceAll("#user-content-fnref-", "#ref-");
98
+ }
99
+ } else {
100
+ el.properties.className = "text-primary hover:underline break-all";
101
+ }
102
+ }
103
+ if (el.children) styleFootnoteLinks(el.children);
104
+ });
105
+ }
106
+ function processFootnoteRef(node) {
107
+ var _a, _b, _c, _d, _e, _f;
108
+ if (node.tagName !== "sup" || ((_b = (_a = node.children) == null ? void 0 : _a[0]) == null ? void 0 : _b.type) !== "element" || node.children[0].tagName !== "a")
109
+ return;
110
+ const link = node.children[0];
111
+ const href = (_c = link.properties) == null ? void 0 : _c.href;
112
+ if (typeof href !== "string" || !href.startsWith("#user-content-fn-")) return;
113
+ delete link.properties.target;
114
+ delete link.properties.rel;
115
+ link.properties.className = "text-primary hover:underline";
116
+ link.properties.href = href.replaceAll("#user-content-fn-", "#footnote-");
117
+ if (typeof ((_d = node.properties) == null ? void 0 : _d.id) === "string") {
118
+ link.properties.id = node.properties.id.replaceAll("user-content-fnref-", "ref-");
119
+ delete node.properties.id;
120
+ }
121
+ if (((_f = (_e = link.children) == null ? void 0 : _e[0]) == null ? void 0 : _f.type) === "text") {
122
+ link.children[0].value = `[${link.children[0].value}]`;
123
+ }
124
+ }
125
+ function processFootnotesSection(node) {
126
+ var _a;
127
+ const cls = (_a = node.properties) == null ? void 0 : _a.className;
128
+ const isFootnotes = node.tagName === "section" && (Array.isArray(cls) ? cls.includes("footnotes") : cls === "footnotes");
129
+ if (!isFootnotes) return;
130
+ const olCandidate = node.children.find(
131
+ (child) => child.type === "element" && child.tagName === "ol"
132
+ );
133
+ if ((olCandidate == null ? void 0 : olCandidate.type) !== "element") return;
134
+ const ol = olCandidate;
135
+ ol.properties.className = "list-decimal ml-6 space-y-2 text-sm text-muted-foreground";
136
+ ol.children.forEach((li) => {
137
+ if (li.type !== "element" || li.tagName !== "li") return;
138
+ const liEl = li;
139
+ if (liEl.properties) {
140
+ liEl.properties.className = "pl-2";
141
+ if (typeof liEl.properties.id === "string") {
142
+ liEl.properties.id = liEl.properties.id.replaceAll("user-content-fn-", "footnote-");
143
+ }
144
+ }
145
+ const pIndex = liEl.children.findIndex(
146
+ (child) => child.type === "element" && child.tagName === "p"
147
+ );
148
+ if (pIndex !== -1) {
149
+ const p = liEl.children[pIndex];
150
+ liEl.children.splice(pIndex, 1, ...p.children);
151
+ }
152
+ styleFootnoteLinks(liEl.children);
153
+ });
154
+ const hr = {
155
+ type: "element",
156
+ tagName: "hr",
157
+ properties: { className: "my-8 border-border" },
158
+ children: []
159
+ };
160
+ const h3 = {
161
+ type: "element",
162
+ tagName: "h3",
163
+ properties: { className: "text-lg font-semibold mb-4" },
164
+ children: [{ type: "text", value: "References" }]
165
+ };
166
+ node.children = [hr, h3, ol];
167
+ if (node.properties) node.properties.className = void 0;
168
+ }
84
169
  var customRenderer = () => {
85
170
  return (tree) => {
86
171
  visit(tree, "element", (node) => {
@@ -147,95 +232,8 @@ var customRenderer = () => {
147
232
  }
148
233
  });
149
234
  visit(tree, "element", (node) => {
150
- var _a, _b, _c, _d, _e, _f, _g, _h;
151
- if (node.tagName === "sup" && ((_b = (_a = node.children) == null ? void 0 : _a[0]) == null ? void 0 : _b.type) === "element" && node.children[0].tagName === "a") {
152
- const link = node.children[0];
153
- if (((_c = link.properties) == null ? void 0 : _c.href) && typeof link.properties.href === "string" && link.properties.href.startsWith("#user-content-fn-")) {
154
- if (link.properties) {
155
- delete link.properties.target;
156
- delete link.properties.rel;
157
- link.properties.className = "text-primary hover:underline";
158
- }
159
- link.properties.href = link.properties.href.replaceAll("#user-content-fn-", "#footnote-");
160
- if (((_d = node.properties) == null ? void 0 : _d.id) && typeof node.properties.id === "string") {
161
- const newId = node.properties.id.replaceAll("user-content-fnref-", "ref-");
162
- link.properties.id = newId;
163
- delete node.properties.id;
164
- }
165
- if (((_f = (_e = link.children) == null ? void 0 : _e[0]) == null ? void 0 : _f.type) === "text") {
166
- link.children[0].value = `[${link.children[0].value}]`;
167
- }
168
- }
169
- }
170
- 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")) {
171
- const olCandidate = node.children.find(
172
- (child) => child.type === "element" && child.tagName === "ol"
173
- );
174
- if ((olCandidate == null ? void 0 : olCandidate.type) === "element") {
175
- const ol = olCandidate;
176
- ol.properties.className = "list-decimal ml-6 space-y-2 text-sm text-muted-foreground";
177
- ol.children.forEach((li) => {
178
- if (li.type === "element" && li.tagName === "li") {
179
- const liEl = li;
180
- if (liEl.properties) {
181
- liEl.properties.className = "pl-2";
182
- if (typeof liEl.properties.id === "string") {
183
- liEl.properties.id = liEl.properties.id.replaceAll(
184
- "user-content-fn-",
185
- "footnote-"
186
- );
187
- }
188
- }
189
- const pIndex = liEl.children.findIndex(
190
- (child) => child.type === "element" && child.tagName === "p"
191
- );
192
- if (pIndex !== -1) {
193
- const p = liEl.children[pIndex];
194
- liEl.children.splice(pIndex, 1, ...p.children);
195
- }
196
- const styleLinks = (nodes) => {
197
- nodes.forEach((n) => {
198
- var _a2, _b2;
199
- if (n.type !== "element") return;
200
- const el = n;
201
- if (el.tagName === "a") {
202
- 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";
203
- if (isBackRef) {
204
- el.properties.className = "text-primary hover:underline ml-1";
205
- if (typeof el.properties.href === "string") {
206
- el.properties.href = el.properties.href.replaceAll(
207
- "#user-content-fnref-",
208
- "#ref-"
209
- );
210
- }
211
- } else {
212
- el.properties.className = "text-primary hover:underline break-all";
213
- }
214
- }
215
- if (el.children) styleLinks(el.children);
216
- });
217
- };
218
- styleLinks(liEl.children);
219
- }
220
- });
221
- const hr = {
222
- type: "element",
223
- tagName: "hr",
224
- properties: { className: "my-8 border-border" },
225
- children: []
226
- };
227
- const h3 = {
228
- type: "element",
229
- tagName: "h3",
230
- properties: { className: "text-lg font-semibold mb-4" },
231
- children: [{ type: "text", value: "References" }]
232
- };
233
- node.children = [hr, h3, ol];
234
- if (node.properties) {
235
- node.properties.className = void 0;
236
- }
237
- }
238
- }
235
+ processFootnoteRef(node);
236
+ processFootnotesSection(node);
239
237
  });
240
238
  };
241
239
  };
@@ -260,7 +258,7 @@ var rehypeProcessImages = (options = {}) => {
260
258
  function markdownToHtml(markdown, articleSlug) {
261
259
  return __async(this, null, function* () {
262
260
  try {
263
- let processor = remark().use(remarkParse).use(remarkGfm).use(remarkRehype).use(customRenderer).use(rehypePrism).use(rehypeSanitize, {
261
+ let processor = remark().use(remarkParse).use(remarkGfm).use(remarkGithubBlockquoteAlert).use(remarkRehype).use(customRenderer).use(rehypeSlug).use(rehypeAutolinkHeadings, { behavior: "wrap" }).use(rehypePrism).use(rehypeSanitize, {
264
262
  attributes: {
265
263
  "*": ["className", "class", "id"],
266
264
  a: ["href", "target", "rel", "id"],
@@ -278,6 +276,31 @@ function markdownToHtml(markdown, articleSlug) {
278
276
  }
279
277
  });
280
278
  }
279
+ function nodeTextValue(c) {
280
+ return c.type === "text" ? c.value : "";
281
+ }
282
+ function extractHeadingItem(node) {
283
+ var _a;
284
+ const match = /^h([1-6])$/.exec(node.tagName);
285
+ if (!match) return null;
286
+ const id = typeof ((_a = node.properties) == null ? void 0 : _a.id) === "string" ? node.properties.id : "";
287
+ const text = node.children.map(nodeTextValue).join("");
288
+ if (!id || !text) return null;
289
+ return { id, depth: Number.parseInt(match[1], 10), text };
290
+ }
291
+ function extractToc(markdown) {
292
+ return __async(this, null, function* () {
293
+ const headings = [];
294
+ const collectHeadings = () => (tree) => {
295
+ visit(tree, "element", (node) => {
296
+ const item = extractHeadingItem(node);
297
+ if (item) headings.push(item);
298
+ });
299
+ };
300
+ yield remark().use(remarkParse).use(remarkGfm).use(remarkRehype).use(rehypeSlug).use(collectHeadings).use(rehypeStringify).process(markdown);
301
+ return headings;
302
+ });
303
+ }
281
304
 
282
305
  // src/server-articles.ts
283
306
  var articlesDirectory = path.join(process.cwd(), "public/articles");
@@ -324,23 +347,20 @@ function sanitizeImagePath2(rawPath, articleSlug) {
324
347
  }
325
348
  return `/articles/${articleSlug}/${cleanPath}`;
326
349
  }
350
+ function findArticleFile(slug) {
351
+ const mdPath = path.join(articlesDirectory, slug, "article.md");
352
+ const mdxPath = path.join(articlesDirectory, slug, "article.mdx");
353
+ if (fs.existsSync(mdPath)) return { filePath: mdPath, contentType: "md" };
354
+ if (fs.existsSync(mdxPath)) return { filePath: mdxPath, contentType: "mdx" };
355
+ return null;
356
+ }
327
357
  function getAvailableArticleSlugs() {
328
358
  try {
329
359
  if (!fs.existsSync(articlesDirectory)) return [];
330
360
  const items = fs.readdirSync(articlesDirectory, { withFileTypes: true });
331
361
  return items.filter((item) => item.isDirectory()).filter((dir) => {
332
- const articleDir = path.join(articlesDirectory, dir.name);
333
362
  try {
334
- const entries = fs.readdirSync(articleDir, {
335
- withFileTypes: true
336
- });
337
- return entries.some((entry) => {
338
- const name = typeof entry === "string" ? entry : entry == null ? void 0 : entry.name;
339
- if (name !== "article.md") return false;
340
- if (typeof entry === "string") return true;
341
- if (typeof (entry == null ? void 0 : entry.isFile) === "function") return entry.isFile();
342
- return true;
343
- });
363
+ return findArticleFile(dir.name) !== null;
344
364
  } catch (e) {
345
365
  return false;
346
366
  }
@@ -350,45 +370,64 @@ function getAvailableArticleSlugs() {
350
370
  return [];
351
371
  }
352
372
  }
373
+ function parseDateField(rawDate) {
374
+ if (!rawDate) return void 0;
375
+ try {
376
+ const parsed = new Date(rawDate);
377
+ if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split("T")[0];
378
+ } catch (e) {
379
+ }
380
+ return void 0;
381
+ }
382
+ function resolveFeaturedImage(rawImage, slug) {
383
+ const img = typeof rawImage === "string" ? rawImage : "";
384
+ if (img && !img.startsWith("http")) return sanitizeImagePath2(img, slug) || "/placeholder-logo.png";
385
+ if (!img) return findArticleImage(slug) || "/placeholder-logo.png";
386
+ return img;
387
+ }
388
+ function parseFaqItems(raw) {
389
+ if (!Array.isArray(raw)) return void 0;
390
+ const items = raw.filter(
391
+ (item) => typeof item === "object" && item !== null && typeof item.question === "string" && typeof item.answer === "string"
392
+ );
393
+ return items.length ? items : void 0;
394
+ }
395
+ function parseHowToSteps(raw) {
396
+ if (!Array.isArray(raw)) return void 0;
397
+ const steps = raw.filter(
398
+ (item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.text === "string"
399
+ );
400
+ return steps.length ? steps : void 0;
401
+ }
353
402
  function getArticleSummary(slug) {
354
403
  return __async(this, null, function* () {
355
404
  try {
356
- const articlePath = path.join(articlesDirectory, slug, "article.md");
357
- if (!fs.existsSync(articlePath)) return null;
358
- const fileContent = fs.readFileSync(articlePath, "utf8");
405
+ const found = findArticleFile(slug);
406
+ if (!found) return null;
407
+ const fileContent = fs.readFileSync(found.filePath, "utf8");
359
408
  const { data, content: markdownContent } = matter(fileContent);
360
409
  const readTime = getReadingTime(markdownContent);
361
- let featuredImage = data.featuredImage || "";
362
- if (featuredImage && !featuredImage.startsWith("http")) {
363
- const sanitizedPath = sanitizeImagePath2(featuredImage, slug);
364
- featuredImage = sanitizedPath || "/placeholder-logo.png";
365
- } else if (!featuredImage) {
366
- featuredImage = findArticleImage(slug) || "/placeholder-logo.png";
367
- }
368
- let articleDate;
369
- if (data.date) {
370
- try {
371
- const parsedDate = new Date(data.date);
372
- if (!Number.isNaN(parsedDate.getTime())) {
373
- articleDate = parsedDate.toISOString().split("T")[0];
374
- }
375
- } catch (e) {
376
- console.warn(`Invalid date for article ${slug}: ${data.date}`);
377
- }
378
- }
379
410
  const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
380
411
  const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
381
412
  return {
382
413
  slug,
383
414
  title: data.title || slug.replaceAll("-", " "),
384
415
  excerpt: data.excerpt || "",
385
- date: articleDate,
416
+ date: parseDateField(data.date),
417
+ lastmod: parseDateField(data.lastmod),
386
418
  author: data.author || "Andrew Blase",
387
419
  category: categories[0],
388
420
  categories,
389
421
  readTime,
390
- featuredImage,
391
- tags: data.tags || []
422
+ featuredImage: resolveFeaturedImage(data.featuredImage, slug),
423
+ tags: data.tags || [],
424
+ contentType: found.contentType,
425
+ draft: data.draft === true,
426
+ faq: parseFaqItems(data.faq),
427
+ howTo: parseHowToSteps(data.howTo),
428
+ canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
429
+ articleType: typeof data.articleType === "string" ? data.articleType : void 0,
430
+ series: typeof data.series === "string" ? data.series : void 0
392
431
  };
393
432
  } catch (error) {
394
433
  console.error(`Error loading article ${slug}:`, error);
@@ -398,46 +437,21 @@ function getArticleSummary(slug) {
398
437
  }
399
438
  var getArticleMetadata = cache((slug) => __async(void 0, null, function* () {
400
439
  try {
401
- const articlePath = path.join(articlesDirectory, slug, "article.md");
402
- if (!fs.existsSync(articlePath)) return null;
403
- const fileContent = fs.readFileSync(articlePath, "utf8");
404
- const { data, content: markdownContent } = matter(fileContent);
405
- const htmlContent = yield markdownToHtml(markdownContent, slug);
406
- const readTime = getReadingTime(markdownContent);
407
- let featuredImage = data.featuredImage || "";
408
- if (featuredImage && !featuredImage.startsWith("http")) {
409
- const sanitizedPath = sanitizeImagePath2(featuredImage, slug);
410
- featuredImage = sanitizedPath || "/placeholder-logo.png";
411
- } else if (!featuredImage) {
412
- featuredImage = findArticleImage(slug) || "/placeholder-logo.png";
440
+ const summary = yield getArticleSummary(slug);
441
+ if (!summary) return null;
442
+ const found = findArticleFile(slug);
443
+ if (!found) return null;
444
+ const fileContent = fs.readFileSync(found.filePath, "utf8");
445
+ const { content: markdownContent } = matter(fileContent);
446
+ const toc = yield extractToc(markdownContent);
447
+ let htmlContent;
448
+ let mdxSource;
449
+ if (found.contentType === "mdx") {
450
+ mdxSource = markdownContent;
451
+ } else {
452
+ htmlContent = yield markdownToHtml(markdownContent, slug);
413
453
  }
414
- let articleDate;
415
- if (data.date) {
416
- try {
417
- const parsedDate = new Date(data.date);
418
- if (!Number.isNaN(parsedDate.getTime())) {
419
- articleDate = parsedDate.toISOString().split("T")[0];
420
- }
421
- } catch (e) {
422
- console.warn(`Invalid date for article ${slug}: ${data.date}`);
423
- }
424
- }
425
- const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
426
- const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
427
- return {
428
- slug,
429
- title: data.title || slug.replaceAll("-", " "),
430
- excerpt: data.excerpt || "",
431
- date: articleDate,
432
- author: data.author || "Andrew Blase",
433
- category: categories[0],
434
- categories,
435
- readTime,
436
- featuredImage,
437
- tags: data.tags || [],
438
- content: markdownContent,
439
- htmlContent
440
- };
454
+ return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
441
455
  } catch (error) {
442
456
  console.error(`Error loading article ${slug}:`, error);
443
457
  return null;
@@ -447,7 +461,7 @@ var getAllArticles = cache(() => __async(void 0, null, function* () {
447
461
  const slugs = getAvailableArticleSlugs();
448
462
  const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
449
463
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
450
- return articles.filter((article) => article !== null).filter((article) => !article.date || article.date <= currentDate).sort((a, b) => {
464
+ 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) => {
451
465
  if (!a.date && !b.date) return 0;
452
466
  if (!a.date) return 1;
453
467
  if (!b.date) return -1;
@@ -531,7 +545,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
531
545
  }
532
546
  function generateArticleMetadata(slug, config) {
533
547
  return __async(this, null, function* () {
534
- var _a, _b, _c, _d, _e, _f;
548
+ var _a, _b, _c, _d, _e, _f, _g;
535
549
  const article = yield getArticleMetadata(slug);
536
550
  if (!article) {
537
551
  return {
@@ -541,19 +555,14 @@ function generateArticleMetadata(slug, config) {
541
555
  }
542
556
  const siteUrl = config.siteUrl.replace(/\/$/, "");
543
557
  const articleUrl = `${siteUrl}/articles/${slug}`;
558
+ const canonicalUrl = (_a = article.canonicalUrl) != null ? _a : articleUrl;
544
559
  const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : `${siteUrl}/placeholder-logo.png`;
545
- const description = (_a = article.excerpt) != null ? _a : `Read ${article.title} on ${config.siteName}.`;
560
+ const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
546
561
  return {
547
562
  title: `${article.title} | ${config.siteName}`,
548
563
  description,
549
- keywords: [
550
- "volunteer management software",
551
- "political campaign software",
552
- "campaign operations",
553
- "civic tech",
554
- ...((_b = article.tags) != null ? _b : []).map((tag) => tag.toLowerCase())
555
- ].join(", "),
556
- openGraph: __spreadProps(__spreadValues({
564
+ keywords: [...((_c = article.tags) != null ? _c : []).map((tag) => tag.toLowerCase())].join(", "),
565
+ openGraph: __spreadProps(__spreadValues(__spreadValues({
557
566
  title: article.title,
558
567
  description,
559
568
  url: articleUrl,
@@ -561,9 +570,9 @@ function generateArticleMetadata(slug, config) {
561
570
  images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
562
571
  locale: "en_US",
563
572
  type: "article"
564
- }, article.date && { publishedTime: article.date }), {
573
+ }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), {
565
574
  authors: [article.author],
566
- tags: (_c = article.tags) != null ? _c : []
575
+ tags: (_d = article.tags) != null ? _d : []
567
576
  }),
568
577
  twitter: {
569
578
  card: "summary_large_image",
@@ -572,7 +581,7 @@ function generateArticleMetadata(slug, config) {
572
581
  images: [imageUrl]
573
582
  },
574
583
  alternates: {
575
- canonical: articleUrl
584
+ canonical: canonicalUrl
576
585
  },
577
586
  robots: {
578
587
  index: true,
@@ -585,14 +594,16 @@ function generateArticleMetadata(slug, config) {
585
594
  "max-snippet": -1
586
595
  }
587
596
  },
588
- other: __spreadProps(__spreadValues({
597
+ other: __spreadProps(__spreadValues(__spreadValues({
589
598
  "article:author": article.author
590
599
  }, article.date && {
591
600
  "article:published_time": new Date(article.date).toISOString()
601
+ }), article.lastmod && {
602
+ "article:modified_time": new Date(article.lastmod).toISOString()
592
603
  }), {
593
604
  "article:section": article.category,
594
- "article:tag": (_e = (_d = article.tags) == null ? void 0 : _d.join(",")) != null ? _e : "",
595
- "linkedin:owner": (_f = process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID) != null ? _f : ""
605
+ "article:tag": (_f = (_e = article.tags) == null ? void 0 : _e.join(",")) != null ? _f : "",
606
+ "linkedin:owner": (_g = process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID) != null ? _g : ""
596
607
  })
597
608
  };
598
609
  });
@@ -620,7 +631,10 @@ function generateArticlesIndexMetadata(config) {
620
631
  description
621
632
  },
622
633
  alternates: {
623
- canonical: indexUrl
634
+ canonical: indexUrl,
635
+ types: {
636
+ "application/rss+xml": `${siteUrl}/articles/feed.xml`
637
+ }
624
638
  },
625
639
  robots: {
626
640
  index: true,
@@ -686,12 +700,17 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
686
700
  const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
687
701
  try {
688
702
  const [articles, categories] = yield Promise.all([getAllArticles(), getAllCategories()]);
689
- const articleEntries = articles.map((article) => ({
690
- url: `${baseUrl}/articles/${article.slug}`,
691
- lastModified: article.date ? new Date(article.date) : void 0,
692
- changeFrequency: "weekly",
693
- priority: 0.8
694
- }));
703
+ const articleEntries = articles.map((article) => {
704
+ var _a;
705
+ const dateStr = (_a = article.lastmod) != null ? _a : article.date;
706
+ const lastModified = dateStr ? new Date(dateStr) : void 0;
707
+ return {
708
+ url: `${baseUrl}/articles/${article.slug}`,
709
+ lastModified,
710
+ changeFrequency: "weekly",
711
+ priority: 0.8
712
+ };
713
+ });
695
714
  const categoryEntries = categories.map((cat) => ({
696
715
  url: `${baseUrl}/articles/category/${cat.slug}`,
697
716
  lastModified: /* @__PURE__ */ new Date(),
@@ -704,8 +723,45 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
704
723
  }
705
724
  });
706
725
  }
726
+
727
+ // src/ArticleContent.tsx
728
+ import { MDXRemote } from "next-mdx-remote/rsc";
729
+ import { jsx } from "react/jsx-runtime";
730
+ function ArticleContent({ article, className }) {
731
+ if (article.contentType === "mdx" && article.mdxSource) {
732
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(MDXRemote, { source: article.mdxSource }) });
733
+ }
734
+ return /* @__PURE__ */ jsx("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
735
+ }
736
+
737
+ // src/ArticleTOC.tsx
738
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
739
+ function ArticleTOC({ toc, className }) {
740
+ if (!toc.length) return null;
741
+ return /* @__PURE__ */ jsxs(
742
+ "nav",
743
+ {
744
+ "aria-label": "Table of contents",
745
+ className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
746
+ children: [
747
+ /* @__PURE__ */ jsx2("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
748
+ /* @__PURE__ */ jsx2("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ jsx2("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ jsx2(
749
+ "a",
750
+ {
751
+ href: `#${item.id}`,
752
+ className: "text-muted-foreground hover:text-foreground transition-colors",
753
+ children: item.text
754
+ }
755
+ ) }, item.id)) })
756
+ ]
757
+ }
758
+ );
759
+ }
707
760
  export {
761
+ ArticleContent,
762
+ ArticleTOC,
708
763
  categoryToSlug,
764
+ extractToc,
709
765
  generateArticleMetadata,
710
766
  generateArticleStaticParams,
711
767
  generateArticlesIndexMetadata,