@fullstackdatasolutions/articles 0.8.2 → 0.10.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +237 -0
  2. package/README.md +209 -78
  3. package/dist/index.cjs +635 -274
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +164 -56
  6. package/dist/index.d.ts +164 -56
  7. package/dist/index.js +614 -250
  8. package/dist/index.js.map +1 -1
  9. package/dist/nextjs.cjs +113 -38
  10. package/dist/nextjs.cjs.map +1 -1
  11. package/dist/nextjs.d.cts +71 -0
  12. package/dist/nextjs.d.ts +71 -0
  13. package/dist/nextjs.js +113 -38
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +394 -52
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +96 -6
  18. package/dist/server.d.ts +96 -6
  19. package/dist/server.js +382 -52
  20. package/dist/server.js.map +1 -1
  21. package/package.json +8 -5
  22. package/src/ArticleContent.tsx +8 -3
  23. package/src/ArticleDetailHero.tsx +27 -2
  24. package/src/ArticleSchemas.tsx +27 -27
  25. package/src/AuthorArticlesPage.tsx +60 -0
  26. package/src/AuthorCard.tsx +112 -0
  27. package/src/AuthorDetailHero.tsx +56 -0
  28. package/src/Breadcrumb.tsx +78 -0
  29. package/src/CategoryArticlesPage.tsx +62 -11
  30. package/src/__tests__/ArticleContent.test.tsx +18 -2
  31. package/src/__tests__/ArticleDetailHero.test.tsx +21 -1
  32. package/src/__tests__/ArticleSchemas.test.tsx +47 -2
  33. package/src/__tests__/AuthorArticlesPage.test.tsx +74 -0
  34. package/src/__tests__/AuthorCard.test.tsx +98 -0
  35. package/src/__tests__/AuthorDetailHero.test.tsx +51 -0
  36. package/src/__tests__/CategoryArticlesPage.test.tsx +31 -5
  37. package/src/__tests__/authorUtils.test.ts +89 -0
  38. package/src/__tests__/markdown.test.ts +79 -3
  39. package/src/__tests__/renderMdx.test.tsx +57 -0
  40. package/src/__tests__/seoUtils-authors.test.ts +160 -0
  41. package/src/__tests__/seoUtils.test.ts +106 -0
  42. package/src/__tests__/server-articles.test.ts +174 -3
  43. package/src/articleTypes.ts +33 -0
  44. package/src/articlesConfig.ts +67 -0
  45. package/src/authorUtils.ts +95 -0
  46. package/src/index.ts +32 -9
  47. package/src/markdown.ts +67 -8
  48. package/src/renderMdx.tsx +6 -3
  49. package/src/seoUtils.ts +279 -6
  50. package/src/server-articles.ts +124 -34
  51. package/src/server.ts +21 -2
package/dist/server.cjs CHANGED
@@ -81,26 +81,38 @@ var server_exports = {};
81
81
  __export(server_exports, {
82
82
  ArticleContent: () => ArticleContent,
83
83
  ArticleTOC: () => ArticleTOC,
84
+ buildArticleBreadcrumbs: () => buildArticleBreadcrumbs,
85
+ buildAuthorBreadcrumbs: () => buildAuthorBreadcrumbs,
86
+ buildCategoryBreadcrumbs: () => buildCategoryBreadcrumbs,
84
87
  categoryToSlug: () => categoryToSlug,
85
88
  extractToc: () => extractToc,
86
89
  generateArticleMetadata: () => generateArticleMetadata,
87
90
  generateArticleStaticParams: () => generateArticleStaticParams,
88
91
  generateArticlesIndexMetadata: () => generateArticlesIndexMetadata,
92
+ generateAuthorMetadata: () => generateAuthorMetadata,
93
+ generateAuthorStaticParams: () => generateAuthorStaticParams,
89
94
  generateCategoryMetadata: () => generateCategoryMetadata,
90
95
  generateCategoryStaticParams: () => generateCategoryStaticParams,
96
+ generateRssFeed: () => generateRssFeed,
91
97
  getAdjacentArticles: () => getAdjacentArticles,
92
98
  getAiRobotsTxtRules: () => getAiRobotsTxtRules,
93
99
  getAllArticles: () => getAllArticles,
100
+ getAllAuthors: () => getAllAuthors,
94
101
  getAllCategories: () => getAllCategories,
95
102
  getArticleAiHeaders: () => getArticleAiHeaders,
103
+ getArticleAuthors: () => getArticleAuthors,
96
104
  getArticleMarkdown: () => getArticleMarkdown,
97
105
  getArticleMarkdownResponse: () => getArticleMarkdownResponse,
98
106
  getArticleMarkdownUrl: () => getArticleMarkdownUrl,
99
107
  getArticleMetadata: () => getArticleMetadata,
100
108
  getArticleSitemapEntries: () => getArticleSitemapEntries,
109
+ getArticlesByAuthor: () => getArticlesByAuthor,
101
110
  getArticlesByCategory: () => getArticlesByCategory,
111
+ getAuthorBySlug: () => getAuthorBySlug,
102
112
  getAvailableArticleSlugs: () => getAvailableArticleSlugs,
113
+ getBreadcrumbsConfig: () => getBreadcrumbsConfig,
103
114
  markdownToHtml: () => markdownToHtml,
115
+ resolveAuthorAvatar: () => resolveAuthorAvatar,
104
116
  sanitizeImagePath: () => sanitizeImagePath2,
105
117
  searchArticles: () => searchArticles,
106
118
  setArticlesErrorHandler: () => setArticlesErrorHandler
@@ -142,6 +154,48 @@ function reportArticlesError(report) {
142
154
  }
143
155
 
144
156
  // src/markdown.ts
157
+ var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
158
+ function isNonBrowserNavigationLink(href) {
159
+ return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href) && !href.startsWith("http://") && !href.startsWith("https://");
160
+ }
161
+ function getOrigin(url) {
162
+ if (!url) return null;
163
+ try {
164
+ return new URL(url).origin;
165
+ } catch (e) {
166
+ return null;
167
+ }
168
+ }
169
+ function isExternalHttpLink(href, siteUrl) {
170
+ if (!href.startsWith("http://") && !href.startsWith("https://")) return false;
171
+ const siteOrigin = getOrigin(siteUrl);
172
+ if (!siteOrigin) return true;
173
+ return getOrigin(href) !== siteOrigin;
174
+ }
175
+ function shouldOpenInNewTab(href, options = {}) {
176
+ var _a;
177
+ if (!href || href.startsWith("#") || isNonBrowserNavigationLink(href)) return false;
178
+ const strategy = (_a = options.strategy) != null ? _a : DEFAULT_LINK_TARGET_STRATEGY;
179
+ if (strategy === "same-tab") return false;
180
+ if (strategy === "all-new-tab") return true;
181
+ return isExternalHttpLink(href, options.siteUrl);
182
+ }
183
+ function applyLinkTarget(props, options) {
184
+ const href = typeof props.href === "string" ? props.href : "";
185
+ if (shouldOpenInNewTab(href, options)) {
186
+ props.target = "_blank";
187
+ props.rel = "noopener noreferrer";
188
+ return;
189
+ }
190
+ delete props.target;
191
+ delete props.rel;
192
+ }
193
+ function getLinkTargetOptions(config) {
194
+ return {
195
+ strategy: config == null ? void 0 : config.linkTargetStrategy,
196
+ siteUrl: config == null ? void 0 : config.siteUrl
197
+ };
198
+ }
145
199
  function sanitizeImagePath(rawPath, articleSlug) {
146
200
  if (!rawPath || typeof rawPath !== "string") {
147
201
  return null;
@@ -263,7 +317,7 @@ function processFootnotesSection(node) {
263
317
  node.children = [hr, h3, ol];
264
318
  if (node.properties) node.properties.className = void 0;
265
319
  }
266
- var customRenderer = () => {
320
+ var customRenderer = (linkTargetOptions = {}) => {
267
321
  return (tree) => {
268
322
  (0, import_unist_util_visit.visit)(tree, "element", (node) => {
269
323
  if (node.tagName) {
@@ -286,11 +340,7 @@ var customRenderer = () => {
286
340
  break;
287
341
  case "a": {
288
342
  props.className = "text-primary hover:underline transition-colors duration-200";
289
- const href = typeof props.href === "string" ? props.href : "";
290
- if (!href.startsWith("#")) {
291
- props.target = "_blank";
292
- props.rel = "noopener noreferrer";
293
- }
343
+ applyLinkTarget(props, linkTargetOptions);
294
344
  break;
295
345
  }
296
346
  case "ul":
@@ -360,10 +410,10 @@ var rehypeProcessImages = (options = {}) => {
360
410
  });
361
411
  };
362
412
  };
363
- function markdownToHtml(markdown, articleSlug) {
413
+ function markdownToHtml(markdown, articleSlug, config) {
364
414
  return __async(this, null, function* () {
365
415
  try {
366
- 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_prism_plus.default).use(import_rehype_sanitize.default, {
416
+ 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, getLinkTargetOptions(config)).use(import_rehype_slug.default).use(import_rehype_prism_plus.default).use(import_rehype_sanitize.default, {
367
417
  attributes: {
368
418
  "*": ["className", "class", "id"],
369
419
  a: ["href", "target", "rel", "id"],
@@ -539,7 +589,67 @@ function parseHowToSteps(raw) {
539
589
  );
540
590
  return steps.length ? steps : void 0;
541
591
  }
542
- function getArticleSummary(slug) {
592
+ function parseAuthors(raw) {
593
+ if (!Array.isArray(raw)) return void 0;
594
+ const authors = raw.filter(
595
+ (author) => typeof author === "string" && author.trim().length > 0
596
+ );
597
+ return authors;
598
+ }
599
+ function getAuthorBySlug(slug, config) {
600
+ var _a, _b;
601
+ const profile = (_a = config.authors) == null ? void 0 : _a[slug];
602
+ if (!profile) return null;
603
+ return __spreadProps(__spreadValues({}, profile), {
604
+ url: (_b = profile.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${profile.slug}`
605
+ });
606
+ }
607
+ function getConfiguredAuthorByName(name, config) {
608
+ var _a;
609
+ const normalizedName = name.trim().toLowerCase();
610
+ const profile = Object.values((_a = config.authors) != null ? _a : {}).find(
611
+ (author) => author.name.toLowerCase() === normalizedName
612
+ );
613
+ return profile ? getAuthorBySlug(profile.slug, config) : null;
614
+ }
615
+ function resolveArticleAuthorName(rawAuthor, rawAuthors, config) {
616
+ var _a, _b, _c, _d;
617
+ const authorArray = parseAuthors(rawAuthors);
618
+ const firstAuthor = authorArray == null ? void 0 : authorArray[0];
619
+ const rawAuthorValue = typeof rawAuthor === "string" && rawAuthor.trim() ? rawAuthor : void 0;
620
+ const author = firstAuthor != null ? firstAuthor : rawAuthorValue;
621
+ const resolved = author != null ? author : config == null ? void 0 : config.defaultAuthor;
622
+ if (!resolved) return "";
623
+ if (!config) return resolved;
624
+ return (_d = (_c = (_a = getAuthorBySlug(resolved, config)) == null ? void 0 : _a.name) != null ? _c : (_b = getConfiguredAuthorByName(resolved, config)) == null ? void 0 : _b.name) != null ? _d : resolved;
625
+ }
626
+ function getArticleAuthors(article, config) {
627
+ var _a;
628
+ const fallbackAuthors = Array.from(
629
+ new Set(
630
+ [article.author, config.defaultAuthor].filter(
631
+ (author) => typeof author === "string" && author.trim().length > 0
632
+ )
633
+ )
634
+ );
635
+ const authorValues = (_a = article.authors) != null ? _a : fallbackAuthors;
636
+ if (authorValues.length === 0) return [];
637
+ const resolvedAuthors = authorValues.map((author) => {
638
+ var _a2;
639
+ return (_a2 = getAuthorBySlug(author, config)) != null ? _a2 : getConfiguredAuthorByName(author, config);
640
+ }).filter((author) => author !== null).filter((author, index, all) => all.findIndex((a) => a.slug === author.slug) === index);
641
+ if (resolvedAuthors.length > 0) return resolvedAuthors;
642
+ return authorValues.map((fallbackName) => ({
643
+ name: fallbackName,
644
+ slug: categoryToSlug(fallbackName),
645
+ bio: ""
646
+ }));
647
+ }
648
+ function getAllAuthors(config) {
649
+ var _a;
650
+ return Object.keys((_a = config.authors) != null ? _a : {}).map((slug) => getAuthorBySlug(slug, config)).filter((author) => author !== null);
651
+ }
652
+ function getArticleSummary(slug, config) {
543
653
  return __async(this, null, function* () {
544
654
  try {
545
655
  const found = findArticleFile(slug);
@@ -555,7 +665,8 @@ function getArticleSummary(slug) {
555
665
  excerpt: data.excerpt || "",
556
666
  date: parseDateField(data.date),
557
667
  lastmod: parseDateField(data.lastmod),
558
- author: data.author || "Andrew Blase",
668
+ author: resolveArticleAuthorName(data.author, data.authors, config),
669
+ authors: parseAuthors(data.authors),
559
670
  category: categories[0],
560
671
  categories,
561
672
  readTime,
@@ -581,36 +692,38 @@ function getArticleSummary(slug) {
581
692
  }
582
693
  });
583
694
  }
584
- var getArticleMetadata = (0, import_react.cache)((slug) => __async(void 0, null, function* () {
585
- try {
586
- const summary = yield getArticleSummary(slug);
587
- if (!summary) return null;
588
- const found = findArticleFile(slug);
589
- if (!found) return null;
590
- const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
591
- const { content: markdownContent } = (0, import_gray_matter.default)(fileContent);
592
- const toc = yield extractToc(markdownContent);
593
- let htmlContent;
594
- let mdxSource;
595
- if (found.contentType === "mdx") {
596
- mdxSource = markdownContent;
597
- } else {
598
- htmlContent = yield markdownToHtml(markdownContent, slug);
695
+ var getArticleMetadata = (0, import_react.cache)(
696
+ (slug, config) => __async(void 0, null, function* () {
697
+ try {
698
+ const summary = yield getArticleSummary(slug, config);
699
+ if (!summary) return null;
700
+ const found = findArticleFile(slug);
701
+ if (!found) return null;
702
+ const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
703
+ const { content: markdownContent } = (0, import_gray_matter.default)(fileContent);
704
+ const toc = yield extractToc(markdownContent);
705
+ let htmlContent;
706
+ let mdxSource;
707
+ if (found.contentType === "mdx") {
708
+ mdxSource = markdownContent;
709
+ } else {
710
+ htmlContent = yield markdownToHtml(markdownContent, slug, config);
711
+ }
712
+ return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
713
+ } catch (error) {
714
+ reportArticlesError({
715
+ code: "article-load-failed",
716
+ message: "Unable to load article metadata.",
717
+ error,
718
+ context: { slug }
719
+ });
720
+ return null;
599
721
  }
600
- return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
601
- } catch (error) {
602
- reportArticlesError({
603
- code: "article-load-failed",
604
- message: "Unable to load article metadata.",
605
- error,
606
- context: { slug }
607
- });
608
- return null;
609
- }
610
- }));
611
- var getAllArticles = (0, import_react.cache)(() => __async(void 0, null, function* () {
722
+ })
723
+ );
724
+ var getAllArticles = (0, import_react.cache)((config) => __async(void 0, null, function* () {
612
725
  const slugs = getAvailableArticleSlugs();
613
- const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
726
+ const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug, config)));
614
727
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
615
728
  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) => {
616
729
  if (!a.date && !b.date) return 0;
@@ -700,8 +813,8 @@ function getAiRobotsTxtRules() {
700
813
  }
701
814
  function searchArticles(query, config) {
702
815
  return __async(this, null, function* () {
703
- if (!(query == null ? void 0 : query.trim())) return getAllArticles();
704
- const articles = yield getAllArticles();
816
+ if (!(query == null ? void 0 : query.trim())) return getAllArticles(config);
817
+ const articles = yield getAllArticles(config);
705
818
  const searchTerm = query.toLowerCase().trim();
706
819
  const includeAuthor = (config == null ? void 0 : config.showAuthor) !== false;
707
820
  return articles.filter((article) => {
@@ -738,16 +851,72 @@ function getAllCategories() {
738
851
  })).sort((a, b) => b.count - a.count);
739
852
  });
740
853
  }
741
- function getArticlesByCategory(categorySlug) {
854
+ function getArticlesByCategory(categorySlug, config) {
742
855
  return __async(this, null, function* () {
743
- const articles = yield getAllArticles();
856
+ const articles = yield getAllArticles(config);
744
857
  return articles.filter(
745
858
  (article) => article.categories.some((cat) => categoryToSlug(cat) === categorySlug)
746
859
  );
747
860
  });
748
861
  }
862
+ function getArticlesByAuthor(authorSlug, config) {
863
+ return __async(this, null, function* () {
864
+ const articles = yield getAllArticles(config);
865
+ return articles.filter(
866
+ (article) => getArticleAuthors(article, config).some((author) => author.slug === authorSlug)
867
+ );
868
+ });
869
+ }
870
+
871
+ // src/articlesConfig.ts
872
+ function breadcrumbsAreEnabled(config) {
873
+ var _a;
874
+ return config.breadcrumbs !== false && ((_a = config.breadcrumbs) == null ? void 0 : _a.show) !== false;
875
+ }
876
+ function getBreadcrumbsConfig(config) {
877
+ var _a;
878
+ if (config.breadcrumbs === false) return {};
879
+ return (_a = config.breadcrumbs) != null ? _a : {};
880
+ }
749
881
 
750
882
  // src/seoUtils.ts
883
+ function escapeXml(str) {
884
+ return str.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
885
+ }
886
+ function generateRssFeed(articles, config) {
887
+ var _a;
888
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
889
+ const showAuthor = config.showAuthor !== false;
890
+ const items = articles.map((article) => {
891
+ const url = `${siteUrl}/articles/${article.slug}`;
892
+ const pubDate = article.date ? new Date(article.date).toUTCString() : "";
893
+ const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : "";
894
+ return [
895
+ " <item>",
896
+ ` <title><![CDATA[${article.title}]]></title>`,
897
+ ` <link>${url}</link>`,
898
+ ` <guid isPermaLink="true">${url}</guid>`,
899
+ pubDate ? ` <pubDate>${pubDate}</pubDate>` : "",
900
+ article.excerpt ? ` <description><![CDATA[${article.excerpt}]]></description>` : "",
901
+ showAuthor && article.author ? ` <author>${escapeXml(article.author)}</author>` : "",
902
+ article.category ? ` <category><![CDATA[${article.category}]]></category>` : "",
903
+ imageUrl ? ` <media:content url="${imageUrl}" medium="image" width="1200" height="630"/>` : "",
904
+ " </item>"
905
+ ].filter(Boolean).join("\n");
906
+ }).join("\n");
907
+ const description = (_a = config.description) != null ? _a : `${config.siteName} articles`;
908
+ return `<?xml version="1.0" encoding="UTF-8" ?>
909
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
910
+ <channel>
911
+ <title><![CDATA[${config.siteName}]]></title>
912
+ <link>${siteUrl}/articles</link>
913
+ <description><![CDATA[${description}]]></description>
914
+ <language>en</language>
915
+ <atom:link href="${siteUrl}/articles/feed.xml" rel="self" type="application/rss+xml" />
916
+ ${items}
917
+ </channel>
918
+ </rss>`;
919
+ }
751
920
  function generateArticleStaticParams() {
752
921
  return getAvailableArticleSlugs().map((slug) => ({ slug }));
753
922
  }
@@ -757,6 +926,10 @@ function generateCategoryStaticParams() {
757
926
  return categories.map((cat) => ({ category: cat.slug }));
758
927
  });
759
928
  }
929
+ function generateAuthorStaticParams(config) {
930
+ if (config.showAuthorPage === false) return [];
931
+ return getAllAuthors(config).map((author) => ({ author: author.slug }));
932
+ }
760
933
  function resolveImageUrl(featuredImage, siteUrl) {
761
934
  const base = siteUrl.replace(/\/$/, "");
762
935
  if (featuredImage.startsWith("http://") || featuredImage.startsWith("https://")) {
@@ -767,7 +940,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
767
940
  function generateArticleMetadata(slug, config) {
768
941
  return __async(this, null, function* () {
769
942
  var _a, _b, _c, _d, _e, _f, _g;
770
- const article = yield getArticleMetadata(slug);
943
+ const article = yield getArticleMetadata(slug, config);
771
944
  if (!article) {
772
945
  return {
773
946
  title: "Article Not Found",
@@ -781,6 +954,7 @@ function generateArticleMetadata(slug, config) {
781
954
  const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
782
955
  const showAuthor = config.showAuthor !== false;
783
956
  const markdownUrl = getArticleMarkdownUrl(article, config);
957
+ const authorNames = getArticleAuthors(article, config).map((author) => author.name);
784
958
  return {
785
959
  title: `${article.title} | ${config.siteName}`,
786
960
  description,
@@ -793,7 +967,7 @@ function generateArticleMetadata(slug, config) {
793
967
  images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
794
968
  locale: "en_US",
795
969
  type: "article"
796
- }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), showAuthor && { authors: [article.author] }), {
970
+ }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), showAuthor && authorNames.length > 0 && { authors: authorNames }), {
797
971
  tags: (_d = article.tags) != null ? _d : []
798
972
  }),
799
973
  twitter: {
@@ -820,7 +994,7 @@ function generateArticleMetadata(slug, config) {
820
994
  "max-snippet": -1
821
995
  }
822
996
  },
823
- other: __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, showAuthor && { "article:author": article.author }), article.date && {
997
+ other: __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, showAuthor && authorNames.length > 0 && { "article:author": authorNames.join(", ") }), article.date && {
824
998
  "article:published_time": new Date(article.date).toISOString()
825
999
  }), article.lastmod && {
826
1000
  "article:modified_time": new Date(article.lastmod).toISOString()
@@ -919,11 +1093,162 @@ function generateCategoryMetadata(categorySlug, config) {
919
1093
  };
920
1094
  });
921
1095
  }
1096
+ function generateAuthorMetadata(authorSlug, config) {
1097
+ return __async(this, null, function* () {
1098
+ var _a;
1099
+ const author = getAuthorBySlug(authorSlug, config);
1100
+ if (!author || config.showAuthorPage === false) return { title: "Author Not Found" };
1101
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1102
+ const authorUrl = (_a = author.url) != null ? _a : `${siteUrl}/articles/authors/${author.slug}`;
1103
+ const title = `${author.name} Articles | ${config.siteName}`;
1104
+ return {
1105
+ title,
1106
+ description: author.bio,
1107
+ openGraph: __spreadValues({
1108
+ title,
1109
+ description: author.bio,
1110
+ url: authorUrl,
1111
+ siteName: config.siteName,
1112
+ type: "profile",
1113
+ locale: "en_US"
1114
+ }, author.avatar && { images: [{ url: resolveAuthorAvatar(author, config) }] }),
1115
+ twitter: __spreadValues({
1116
+ card: "summary_large_image",
1117
+ title,
1118
+ description: author.bio
1119
+ }, author.avatar && { images: [resolveAuthorAvatar(author, config)] }),
1120
+ alternates: {
1121
+ canonical: authorUrl
1122
+ },
1123
+ robots: {
1124
+ index: true,
1125
+ follow: true
1126
+ }
1127
+ };
1128
+ });
1129
+ }
1130
+ function formatCategoryName(category) {
1131
+ return category.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
1132
+ }
1133
+ function buildArticleBreadcrumbs(article, config) {
1134
+ var _a, _b;
1135
+ if (!breadcrumbsAreEnabled(config)) return [];
1136
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1137
+ const breadcrumbConfig = getBreadcrumbsConfig(config);
1138
+ const labels = (_a = breadcrumbConfig.labels) != null ? _a : {};
1139
+ const categorySlug = categoryToSlug(article.category);
1140
+ const trail = (_b = breadcrumbConfig.article) != null ? _b : ["home", "articles", "primaryCategory", "articleTitle"];
1141
+ const folderSegments = article.slug.split("/").filter(Boolean).slice(0, -1);
1142
+ return trail.flatMap(
1143
+ (token) => buildArticleBreadcrumbToken(token, {
1144
+ article,
1145
+ siteUrl,
1146
+ categorySlug,
1147
+ folderSegments,
1148
+ labels
1149
+ })
1150
+ );
1151
+ }
1152
+ function buildCategoryBreadcrumbs(category, config, categoryName = formatCategoryName(category)) {
1153
+ var _a, _b;
1154
+ if (!breadcrumbsAreEnabled(config)) return [];
1155
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1156
+ const breadcrumbConfig = getBreadcrumbsConfig(config);
1157
+ const labels = (_a = breadcrumbConfig.labels) != null ? _a : {};
1158
+ const trail = (_b = breadcrumbConfig.category) != null ? _b : ["home", "articles", "category"];
1159
+ return trail.flatMap(
1160
+ (entry) => buildCategoryBreadcrumbEntry(entry, { categoryName, siteUrl, labels })
1161
+ );
1162
+ }
1163
+ function buildAuthorBreadcrumbs(author, config) {
1164
+ var _a, _b;
1165
+ if (!breadcrumbsAreEnabled(config)) return [];
1166
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1167
+ const breadcrumbConfig = getBreadcrumbsConfig(config);
1168
+ const labels = (_a = breadcrumbConfig.labels) != null ? _a : {};
1169
+ const trail = (_b = breadcrumbConfig.author) != null ? _b : ["home", "articles", "authors", "authorName"];
1170
+ return trail.flatMap(
1171
+ (entry) => buildAuthorBreadcrumbEntry(entry, { author, siteUrl, labels })
1172
+ );
1173
+ }
1174
+ function isCustomBreadcrumbItem(entry) {
1175
+ return typeof entry === "object" && entry !== null && "name" in entry && "url" in entry;
1176
+ }
1177
+ function resolveCustomBreadcrumbItem(item, siteUrl) {
1178
+ if (item.url.startsWith("/")) return { name: item.name, url: `${siteUrl}${item.url}` };
1179
+ return { name: item.name, url: item.url };
1180
+ }
1181
+ function buildArticleBreadcrumbToken(entry, context) {
1182
+ var _a, _b;
1183
+ if (isCustomBreadcrumbItem(entry)) {
1184
+ return [resolveCustomBreadcrumbItem(entry, context.siteUrl)];
1185
+ }
1186
+ if (entry === "home") return [{ name: (_a = context.labels.home) != null ? _a : "Home", url: context.siteUrl }];
1187
+ if (entry === "articles") {
1188
+ return [{ name: (_b = context.labels.articles) != null ? _b : "Articles", url: `${context.siteUrl}/articles` }];
1189
+ }
1190
+ if (entry === "primaryCategory") {
1191
+ return [
1192
+ {
1193
+ name: context.article.category,
1194
+ url: `${context.siteUrl}/articles/category/${context.categorySlug}`
1195
+ }
1196
+ ];
1197
+ }
1198
+ if (entry === "folderPath") {
1199
+ return context.folderSegments.map((segment, index) => ({
1200
+ name: formatCategoryName(segment),
1201
+ url: `${context.siteUrl}/articles/${context.folderSegments.slice(0, index + 1).join("/")}`
1202
+ }));
1203
+ }
1204
+ return [{ name: context.article.title }];
1205
+ }
1206
+ function buildCategoryBreadcrumbEntry(entry, context) {
1207
+ var _a, _b;
1208
+ if (isCustomBreadcrumbItem(entry)) {
1209
+ return [resolveCustomBreadcrumbItem(entry, context.siteUrl)];
1210
+ }
1211
+ if (entry === "home") return [{ name: (_a = context.labels.home) != null ? _a : "Home", url: context.siteUrl }];
1212
+ if (entry === "articles") {
1213
+ return [{ name: (_b = context.labels.articles) != null ? _b : "Articles", url: `${context.siteUrl}/articles` }];
1214
+ }
1215
+ return [{ name: context.categoryName }];
1216
+ }
1217
+ function buildAuthorBreadcrumbEntry(entry, context) {
1218
+ var _a, _b, _c;
1219
+ if (isCustomBreadcrumbItem(entry)) {
1220
+ return [resolveCustomBreadcrumbItem(entry, context.siteUrl)];
1221
+ }
1222
+ if (entry === "home") return [{ name: (_a = context.labels.home) != null ? _a : "Home", url: context.siteUrl }];
1223
+ if (entry === "articles") {
1224
+ return [{ name: (_b = context.labels.articles) != null ? _b : "Articles", url: `${context.siteUrl}/articles` }];
1225
+ }
1226
+ if (entry === "authors") {
1227
+ return [
1228
+ {
1229
+ name: (_c = context.labels.authors) != null ? _c : "Authors",
1230
+ url: `${context.siteUrl}/articles/authors`
1231
+ }
1232
+ ];
1233
+ }
1234
+ return [{ name: context.author.name }];
1235
+ }
1236
+ function resolveAuthorAvatar(author, config) {
1237
+ if (!author.avatar) return "";
1238
+ if (author.avatar.startsWith("http://") || author.avatar.startsWith("https://")) {
1239
+ return author.avatar;
1240
+ }
1241
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1242
+ return `${siteUrl}/articles/authors/${author.slug}/${author.avatar.replace(/^\/+/, "")}`;
1243
+ }
922
1244
  function getArticleSitemapEntries(baseUrlOrConfig) {
923
1245
  return __async(this, null, function* () {
924
1246
  const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
925
1247
  try {
926
- const [articles, categories] = yield Promise.all([getAllArticles(), getAllCategories()]);
1248
+ const [articles, categories] = yield Promise.all([
1249
+ getAllArticles(typeof baseUrlOrConfig === "string" ? void 0 : baseUrlOrConfig),
1250
+ getAllCategories()
1251
+ ]);
927
1252
  const articleEntries = articles.map((article) => {
928
1253
  var _a;
929
1254
  const dateStr = (_a = article.lastmod) != null ? _a : article.date;
@@ -941,7 +1266,12 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
941
1266
  changeFrequency: "weekly",
942
1267
  priority: 0.7
943
1268
  }));
944
- return [...articleEntries, ...categoryEntries];
1269
+ const authorEntries = typeof baseUrlOrConfig === "string" || baseUrlOrConfig.showAuthorPage === false ? [] : getAllAuthors(baseUrlOrConfig).map((author) => ({
1270
+ url: `${baseUrl}/articles/authors/${author.slug}`,
1271
+ changeFrequency: "monthly",
1272
+ priority: 0.6
1273
+ }));
1274
+ return [...articleEntries, ...categoryEntries, ...authorEntries];
945
1275
  } catch (e) {
946
1276
  return [];
947
1277
  }
@@ -961,18 +1291,18 @@ var import_jsx_runtime = require("react/jsx-runtime");
961
1291
  function makeImgComponent(basePath) {
962
1292
  return function MdxImage(_a) {
963
1293
  var _b = _a, { src, alt } = _b, props = __objRest(_b, ["src", "alt"]);
964
- const resolvedSrc = src && !src.startsWith("http") && !src.startsWith("/") ? `${basePath}/${src}` : src;
1294
+ const resolvedSrc = typeof src === "string" && !src.startsWith("http") && !src.startsWith("/") ? `${basePath}/${src}` : src;
965
1295
  return import_react2.default.createElement("img", __spreadValues({ src: resolvedSrc, alt }, props));
966
1296
  };
967
1297
  }
968
- function renderMdxSource(source, basePath) {
1298
+ function renderMdxSource(source, basePath, config) {
969
1299
  return __async(this, null, function* () {
970
1300
  const isDevelopment = process.env.NODE_ENV === "development";
971
1301
  const mdxModule = yield (0, import_mdx.evaluate)(source, __spreadProps(__spreadValues({}, isDevelopment ? devRuntime : runtime), {
972
1302
  development: isDevelopment,
973
1303
  remarkPlugins: [import_remark_gfm2.default, import_remark_github_blockquote_alert2.default],
974
1304
  rehypePlugins: [
975
- customRenderer,
1305
+ [customRenderer, { strategy: config == null ? void 0 : config.linkTargetStrategy, siteUrl: config == null ? void 0 : config.siteUrl }],
976
1306
  import_rehype_slug2.default,
977
1307
  // @ts-ignore
978
1308
  import_rehype_prism_plus2.default
@@ -987,9 +1317,9 @@ function renderMdxSource(source, basePath) {
987
1317
  // src/ArticleContent.tsx
988
1318
  var import_jsx_runtime2 = require("react/jsx-runtime");
989
1319
  function ArticleContent(_0) {
990
- return __async(this, arguments, function* ({ article, className }) {
1320
+ return __async(this, arguments, function* ({ article, className, config }) {
991
1321
  if (article.contentType === "mdx" && article.mdxSource) {
992
- const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`);
1322
+ const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config);
993
1323
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, children: content });
994
1324
  }
995
1325
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
@@ -1023,26 +1353,38 @@ function ArticleTOC({ toc, className }) {
1023
1353
  0 && (module.exports = {
1024
1354
  ArticleContent,
1025
1355
  ArticleTOC,
1356
+ buildArticleBreadcrumbs,
1357
+ buildAuthorBreadcrumbs,
1358
+ buildCategoryBreadcrumbs,
1026
1359
  categoryToSlug,
1027
1360
  extractToc,
1028
1361
  generateArticleMetadata,
1029
1362
  generateArticleStaticParams,
1030
1363
  generateArticlesIndexMetadata,
1364
+ generateAuthorMetadata,
1365
+ generateAuthorStaticParams,
1031
1366
  generateCategoryMetadata,
1032
1367
  generateCategoryStaticParams,
1368
+ generateRssFeed,
1033
1369
  getAdjacentArticles,
1034
1370
  getAiRobotsTxtRules,
1035
1371
  getAllArticles,
1372
+ getAllAuthors,
1036
1373
  getAllCategories,
1037
1374
  getArticleAiHeaders,
1375
+ getArticleAuthors,
1038
1376
  getArticleMarkdown,
1039
1377
  getArticleMarkdownResponse,
1040
1378
  getArticleMarkdownUrl,
1041
1379
  getArticleMetadata,
1042
1380
  getArticleSitemapEntries,
1381
+ getArticlesByAuthor,
1043
1382
  getArticlesByCategory,
1383
+ getAuthorBySlug,
1044
1384
  getAvailableArticleSlugs,
1385
+ getBreadcrumbsConfig,
1045
1386
  markdownToHtml,
1387
+ resolveAuthorAvatar,
1046
1388
  sanitizeImagePath,
1047
1389
  searchArticles,
1048
1390
  setArticlesErrorHandler