@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.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";
413
- }
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
- }
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);
424
453
  }
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,18 +594,61 @@ 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
  });
599
610
  }
611
+ function generateArticlesIndexMetadata(config) {
612
+ var _a, _b;
613
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
614
+ const indexUrl = `${siteUrl}/articles`;
615
+ const title = `Articles | ${config.siteName}`;
616
+ const description = (_b = (_a = config.hero) == null ? void 0 : _a.description) != null ? _b : `Expert analysis and insights from ${config.siteName}.`;
617
+ return {
618
+ title,
619
+ description,
620
+ openGraph: {
621
+ title,
622
+ description,
623
+ url: indexUrl,
624
+ siteName: config.siteName,
625
+ type: "website",
626
+ locale: "en_US"
627
+ },
628
+ twitter: {
629
+ card: "summary_large_image",
630
+ title,
631
+ description
632
+ },
633
+ alternates: {
634
+ canonical: indexUrl,
635
+ types: {
636
+ "application/rss+xml": `${siteUrl}/articles/feed.xml`
637
+ }
638
+ },
639
+ robots: {
640
+ index: true,
641
+ follow: true,
642
+ googleBot: {
643
+ index: true,
644
+ follow: true,
645
+ "max-video-preview": -1,
646
+ "max-image-preview": "large",
647
+ "max-snippet": -1
648
+ }
649
+ }
650
+ };
651
+ }
600
652
  function generateCategoryMetadata(categorySlug, config) {
601
653
  return __async(this, null, function* () {
602
654
  var _a, _b;
@@ -608,18 +660,37 @@ function generateCategoryMetadata(categorySlug, config) {
608
660
  const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[categorySlug];
609
661
  const fallback = `Browse ${articles.length} article${articles.length === 1 ? "" : "s"} in the ${categoryName} category.`;
610
662
  const description = typeof raw === "string" ? raw : (_b = raw == null ? void 0 : raw.short) != null ? _b : fallback;
663
+ const title = `${categoryName} Articles | ${config.siteName}`;
611
664
  return {
612
- title: `${categoryName} Articles | ${config.siteName}`,
665
+ title,
613
666
  description,
614
667
  openGraph: {
615
668
  title: `${categoryName} Articles`,
616
669
  description,
617
670
  url: categoryUrl,
618
671
  siteName: config.siteName,
619
- images: [{ url: articles[0].featuredImage }]
672
+ images: [{ url: articles[0].featuredImage }],
673
+ type: "website",
674
+ locale: "en_US"
675
+ },
676
+ twitter: {
677
+ card: "summary_large_image",
678
+ title: `${categoryName} Articles`,
679
+ description
620
680
  },
621
681
  alternates: {
622
682
  canonical: categoryUrl
683
+ },
684
+ robots: {
685
+ index: true,
686
+ follow: true,
687
+ googleBot: {
688
+ index: true,
689
+ follow: true,
690
+ "max-video-preview": -1,
691
+ "max-image-preview": "large",
692
+ "max-snippet": -1
693
+ }
623
694
  }
624
695
  };
625
696
  });
@@ -629,12 +700,17 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
629
700
  const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
630
701
  try {
631
702
  const [articles, categories] = yield Promise.all([getAllArticles(), getAllCategories()]);
632
- const articleEntries = articles.map((article) => ({
633
- url: `${baseUrl}/articles/${article.slug}`,
634
- lastModified: article.date ? new Date(article.date) : void 0,
635
- changeFrequency: "weekly",
636
- priority: 0.8
637
- }));
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
+ });
638
714
  const categoryEntries = categories.map((cat) => ({
639
715
  url: `${baseUrl}/articles/category/${cat.slug}`,
640
716
  lastModified: /* @__PURE__ */ new Date(),
@@ -647,10 +723,23 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
647
723
  }
648
724
  });
649
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
+ }
650
736
  export {
737
+ ArticleContent,
651
738
  categoryToSlug,
739
+ extractToc,
652
740
  generateArticleMetadata,
653
741
  generateArticleStaticParams,
742
+ generateArticlesIndexMetadata,
654
743
  generateCategoryMetadata,
655
744
  generateCategoryStaticParams,
656
745
  getAdjacentArticles,