@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.js CHANGED
@@ -85,6 +85,48 @@ function reportArticlesError(report) {
85
85
  }
86
86
 
87
87
  // src/markdown.ts
88
+ var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
89
+ function isNonBrowserNavigationLink(href) {
90
+ return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href) && !href.startsWith("http://") && !href.startsWith("https://");
91
+ }
92
+ function getOrigin(url) {
93
+ if (!url) return null;
94
+ try {
95
+ return new URL(url).origin;
96
+ } catch (e) {
97
+ return null;
98
+ }
99
+ }
100
+ function isExternalHttpLink(href, siteUrl) {
101
+ if (!href.startsWith("http://") && !href.startsWith("https://")) return false;
102
+ const siteOrigin = getOrigin(siteUrl);
103
+ if (!siteOrigin) return true;
104
+ return getOrigin(href) !== siteOrigin;
105
+ }
106
+ function shouldOpenInNewTab(href, options = {}) {
107
+ var _a;
108
+ if (!href || href.startsWith("#") || isNonBrowserNavigationLink(href)) return false;
109
+ const strategy = (_a = options.strategy) != null ? _a : DEFAULT_LINK_TARGET_STRATEGY;
110
+ if (strategy === "same-tab") return false;
111
+ if (strategy === "all-new-tab") return true;
112
+ return isExternalHttpLink(href, options.siteUrl);
113
+ }
114
+ function applyLinkTarget(props, options) {
115
+ const href = typeof props.href === "string" ? props.href : "";
116
+ if (shouldOpenInNewTab(href, options)) {
117
+ props.target = "_blank";
118
+ props.rel = "noopener noreferrer";
119
+ return;
120
+ }
121
+ delete props.target;
122
+ delete props.rel;
123
+ }
124
+ function getLinkTargetOptions(config) {
125
+ return {
126
+ strategy: config == null ? void 0 : config.linkTargetStrategy,
127
+ siteUrl: config == null ? void 0 : config.siteUrl
128
+ };
129
+ }
88
130
  function sanitizeImagePath(rawPath, articleSlug) {
89
131
  if (!rawPath || typeof rawPath !== "string") {
90
132
  return null;
@@ -206,7 +248,7 @@ function processFootnotesSection(node) {
206
248
  node.children = [hr, h3, ol];
207
249
  if (node.properties) node.properties.className = void 0;
208
250
  }
209
- var customRenderer = () => {
251
+ var customRenderer = (linkTargetOptions = {}) => {
210
252
  return (tree) => {
211
253
  visit(tree, "element", (node) => {
212
254
  if (node.tagName) {
@@ -229,11 +271,7 @@ var customRenderer = () => {
229
271
  break;
230
272
  case "a": {
231
273
  props.className = "text-primary hover:underline transition-colors duration-200";
232
- const href = typeof props.href === "string" ? props.href : "";
233
- if (!href.startsWith("#")) {
234
- props.target = "_blank";
235
- props.rel = "noopener noreferrer";
236
- }
274
+ applyLinkTarget(props, linkTargetOptions);
237
275
  break;
238
276
  }
239
277
  case "ul":
@@ -303,10 +341,10 @@ var rehypeProcessImages = (options = {}) => {
303
341
  });
304
342
  };
305
343
  };
306
- function markdownToHtml(markdown, articleSlug) {
344
+ function markdownToHtml(markdown, articleSlug, config) {
307
345
  return __async(this, null, function* () {
308
346
  try {
309
- let processor = remark().use(remarkParse).use(remarkGfm).use(remarkGithubBlockquoteAlert).use(remarkRehype).use(customRenderer).use(rehypeSlug).use(rehypePrism).use(rehypeSanitize, {
347
+ let processor = remark().use(remarkParse).use(remarkGfm).use(remarkGithubBlockquoteAlert).use(remarkRehype).use(customRenderer, getLinkTargetOptions(config)).use(rehypeSlug).use(rehypePrism).use(rehypeSanitize, {
310
348
  attributes: {
311
349
  "*": ["className", "class", "id"],
312
350
  a: ["href", "target", "rel", "id"],
@@ -482,7 +520,67 @@ function parseHowToSteps(raw) {
482
520
  );
483
521
  return steps.length ? steps : void 0;
484
522
  }
485
- function getArticleSummary(slug) {
523
+ function parseAuthors(raw) {
524
+ if (!Array.isArray(raw)) return void 0;
525
+ const authors = raw.filter(
526
+ (author) => typeof author === "string" && author.trim().length > 0
527
+ );
528
+ return authors;
529
+ }
530
+ function getAuthorBySlug(slug, config) {
531
+ var _a, _b;
532
+ const profile = (_a = config.authors) == null ? void 0 : _a[slug];
533
+ if (!profile) return null;
534
+ return __spreadProps(__spreadValues({}, profile), {
535
+ url: (_b = profile.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${profile.slug}`
536
+ });
537
+ }
538
+ function getConfiguredAuthorByName(name, config) {
539
+ var _a;
540
+ const normalizedName = name.trim().toLowerCase();
541
+ const profile = Object.values((_a = config.authors) != null ? _a : {}).find(
542
+ (author) => author.name.toLowerCase() === normalizedName
543
+ );
544
+ return profile ? getAuthorBySlug(profile.slug, config) : null;
545
+ }
546
+ function resolveArticleAuthorName(rawAuthor, rawAuthors, config) {
547
+ var _a, _b, _c, _d;
548
+ const authorArray = parseAuthors(rawAuthors);
549
+ const firstAuthor = authorArray == null ? void 0 : authorArray[0];
550
+ const rawAuthorValue = typeof rawAuthor === "string" && rawAuthor.trim() ? rawAuthor : void 0;
551
+ const author = firstAuthor != null ? firstAuthor : rawAuthorValue;
552
+ const resolved = author != null ? author : config == null ? void 0 : config.defaultAuthor;
553
+ if (!resolved) return "";
554
+ if (!config) return resolved;
555
+ 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;
556
+ }
557
+ function getArticleAuthors(article, config) {
558
+ var _a;
559
+ const fallbackAuthors = Array.from(
560
+ new Set(
561
+ [article.author, config.defaultAuthor].filter(
562
+ (author) => typeof author === "string" && author.trim().length > 0
563
+ )
564
+ )
565
+ );
566
+ const authorValues = (_a = article.authors) != null ? _a : fallbackAuthors;
567
+ if (authorValues.length === 0) return [];
568
+ const resolvedAuthors = authorValues.map((author) => {
569
+ var _a2;
570
+ return (_a2 = getAuthorBySlug(author, config)) != null ? _a2 : getConfiguredAuthorByName(author, config);
571
+ }).filter((author) => author !== null).filter((author, index, all) => all.findIndex((a) => a.slug === author.slug) === index);
572
+ if (resolvedAuthors.length > 0) return resolvedAuthors;
573
+ return authorValues.map((fallbackName) => ({
574
+ name: fallbackName,
575
+ slug: categoryToSlug(fallbackName),
576
+ bio: ""
577
+ }));
578
+ }
579
+ function getAllAuthors(config) {
580
+ var _a;
581
+ return Object.keys((_a = config.authors) != null ? _a : {}).map((slug) => getAuthorBySlug(slug, config)).filter((author) => author !== null);
582
+ }
583
+ function getArticleSummary(slug, config) {
486
584
  return __async(this, null, function* () {
487
585
  try {
488
586
  const found = findArticleFile(slug);
@@ -498,7 +596,8 @@ function getArticleSummary(slug) {
498
596
  excerpt: data.excerpt || "",
499
597
  date: parseDateField(data.date),
500
598
  lastmod: parseDateField(data.lastmod),
501
- author: data.author || "Andrew Blase",
599
+ author: resolveArticleAuthorName(data.author, data.authors, config),
600
+ authors: parseAuthors(data.authors),
502
601
  category: categories[0],
503
602
  categories,
504
603
  readTime,
@@ -524,36 +623,38 @@ function getArticleSummary(slug) {
524
623
  }
525
624
  });
526
625
  }
527
- var getArticleMetadata = cache((slug) => __async(void 0, null, function* () {
528
- try {
529
- const summary = yield getArticleSummary(slug);
530
- if (!summary) return null;
531
- const found = findArticleFile(slug);
532
- if (!found) return null;
533
- const fileContent = fs.readFileSync(found.filePath, "utf8");
534
- const { content: markdownContent } = matter(fileContent);
535
- const toc = yield extractToc(markdownContent);
536
- let htmlContent;
537
- let mdxSource;
538
- if (found.contentType === "mdx") {
539
- mdxSource = markdownContent;
540
- } else {
541
- htmlContent = yield markdownToHtml(markdownContent, slug);
626
+ var getArticleMetadata = cache(
627
+ (slug, config) => __async(void 0, null, function* () {
628
+ try {
629
+ const summary = yield getArticleSummary(slug, config);
630
+ if (!summary) return null;
631
+ const found = findArticleFile(slug);
632
+ if (!found) return null;
633
+ const fileContent = fs.readFileSync(found.filePath, "utf8");
634
+ const { content: markdownContent } = matter(fileContent);
635
+ const toc = yield extractToc(markdownContent);
636
+ let htmlContent;
637
+ let mdxSource;
638
+ if (found.contentType === "mdx") {
639
+ mdxSource = markdownContent;
640
+ } else {
641
+ htmlContent = yield markdownToHtml(markdownContent, slug, config);
642
+ }
643
+ return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
644
+ } catch (error) {
645
+ reportArticlesError({
646
+ code: "article-load-failed",
647
+ message: "Unable to load article metadata.",
648
+ error,
649
+ context: { slug }
650
+ });
651
+ return null;
542
652
  }
543
- return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
544
- } catch (error) {
545
- reportArticlesError({
546
- code: "article-load-failed",
547
- message: "Unable to load article metadata.",
548
- error,
549
- context: { slug }
550
- });
551
- return null;
552
- }
553
- }));
554
- var getAllArticles = cache(() => __async(void 0, null, function* () {
653
+ })
654
+ );
655
+ var getAllArticles = cache((config) => __async(void 0, null, function* () {
555
656
  const slugs = getAvailableArticleSlugs();
556
- const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
657
+ const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug, config)));
557
658
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
558
659
  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) => {
559
660
  if (!a.date && !b.date) return 0;
@@ -643,8 +744,8 @@ function getAiRobotsTxtRules() {
643
744
  }
644
745
  function searchArticles(query, config) {
645
746
  return __async(this, null, function* () {
646
- if (!(query == null ? void 0 : query.trim())) return getAllArticles();
647
- const articles = yield getAllArticles();
747
+ if (!(query == null ? void 0 : query.trim())) return getAllArticles(config);
748
+ const articles = yield getAllArticles(config);
648
749
  const searchTerm = query.toLowerCase().trim();
649
750
  const includeAuthor = (config == null ? void 0 : config.showAuthor) !== false;
650
751
  return articles.filter((article) => {
@@ -681,16 +782,72 @@ function getAllCategories() {
681
782
  })).sort((a, b) => b.count - a.count);
682
783
  });
683
784
  }
684
- function getArticlesByCategory(categorySlug) {
785
+ function getArticlesByCategory(categorySlug, config) {
685
786
  return __async(this, null, function* () {
686
- const articles = yield getAllArticles();
787
+ const articles = yield getAllArticles(config);
687
788
  return articles.filter(
688
789
  (article) => article.categories.some((cat) => categoryToSlug(cat) === categorySlug)
689
790
  );
690
791
  });
691
792
  }
793
+ function getArticlesByAuthor(authorSlug, config) {
794
+ return __async(this, null, function* () {
795
+ const articles = yield getAllArticles(config);
796
+ return articles.filter(
797
+ (article) => getArticleAuthors(article, config).some((author) => author.slug === authorSlug)
798
+ );
799
+ });
800
+ }
801
+
802
+ // src/articlesConfig.ts
803
+ function breadcrumbsAreEnabled(config) {
804
+ var _a;
805
+ return config.breadcrumbs !== false && ((_a = config.breadcrumbs) == null ? void 0 : _a.show) !== false;
806
+ }
807
+ function getBreadcrumbsConfig(config) {
808
+ var _a;
809
+ if (config.breadcrumbs === false) return {};
810
+ return (_a = config.breadcrumbs) != null ? _a : {};
811
+ }
692
812
 
693
813
  // src/seoUtils.ts
814
+ function escapeXml(str) {
815
+ return str.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
816
+ }
817
+ function generateRssFeed(articles, config) {
818
+ var _a;
819
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
820
+ const showAuthor = config.showAuthor !== false;
821
+ const items = articles.map((article) => {
822
+ const url = `${siteUrl}/articles/${article.slug}`;
823
+ const pubDate = article.date ? new Date(article.date).toUTCString() : "";
824
+ const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : "";
825
+ return [
826
+ " <item>",
827
+ ` <title><![CDATA[${article.title}]]></title>`,
828
+ ` <link>${url}</link>`,
829
+ ` <guid isPermaLink="true">${url}</guid>`,
830
+ pubDate ? ` <pubDate>${pubDate}</pubDate>` : "",
831
+ article.excerpt ? ` <description><![CDATA[${article.excerpt}]]></description>` : "",
832
+ showAuthor && article.author ? ` <author>${escapeXml(article.author)}</author>` : "",
833
+ article.category ? ` <category><![CDATA[${article.category}]]></category>` : "",
834
+ imageUrl ? ` <media:content url="${imageUrl}" medium="image" width="1200" height="630"/>` : "",
835
+ " </item>"
836
+ ].filter(Boolean).join("\n");
837
+ }).join("\n");
838
+ const description = (_a = config.description) != null ? _a : `${config.siteName} articles`;
839
+ return `<?xml version="1.0" encoding="UTF-8" ?>
840
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
841
+ <channel>
842
+ <title><![CDATA[${config.siteName}]]></title>
843
+ <link>${siteUrl}/articles</link>
844
+ <description><![CDATA[${description}]]></description>
845
+ <language>en</language>
846
+ <atom:link href="${siteUrl}/articles/feed.xml" rel="self" type="application/rss+xml" />
847
+ ${items}
848
+ </channel>
849
+ </rss>`;
850
+ }
694
851
  function generateArticleStaticParams() {
695
852
  return getAvailableArticleSlugs().map((slug) => ({ slug }));
696
853
  }
@@ -700,6 +857,10 @@ function generateCategoryStaticParams() {
700
857
  return categories.map((cat) => ({ category: cat.slug }));
701
858
  });
702
859
  }
860
+ function generateAuthorStaticParams(config) {
861
+ if (config.showAuthorPage === false) return [];
862
+ return getAllAuthors(config).map((author) => ({ author: author.slug }));
863
+ }
703
864
  function resolveImageUrl(featuredImage, siteUrl) {
704
865
  const base = siteUrl.replace(/\/$/, "");
705
866
  if (featuredImage.startsWith("http://") || featuredImage.startsWith("https://")) {
@@ -710,7 +871,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
710
871
  function generateArticleMetadata(slug, config) {
711
872
  return __async(this, null, function* () {
712
873
  var _a, _b, _c, _d, _e, _f, _g;
713
- const article = yield getArticleMetadata(slug);
874
+ const article = yield getArticleMetadata(slug, config);
714
875
  if (!article) {
715
876
  return {
716
877
  title: "Article Not Found",
@@ -724,6 +885,7 @@ function generateArticleMetadata(slug, config) {
724
885
  const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
725
886
  const showAuthor = config.showAuthor !== false;
726
887
  const markdownUrl = getArticleMarkdownUrl(article, config);
888
+ const authorNames = getArticleAuthors(article, config).map((author) => author.name);
727
889
  return {
728
890
  title: `${article.title} | ${config.siteName}`,
729
891
  description,
@@ -736,7 +898,7 @@ function generateArticleMetadata(slug, config) {
736
898
  images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
737
899
  locale: "en_US",
738
900
  type: "article"
739
- }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), showAuthor && { authors: [article.author] }), {
901
+ }, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), showAuthor && authorNames.length > 0 && { authors: authorNames }), {
740
902
  tags: (_d = article.tags) != null ? _d : []
741
903
  }),
742
904
  twitter: {
@@ -763,7 +925,7 @@ function generateArticleMetadata(slug, config) {
763
925
  "max-snippet": -1
764
926
  }
765
927
  },
766
- other: __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, showAuthor && { "article:author": article.author }), article.date && {
928
+ other: __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, showAuthor && authorNames.length > 0 && { "article:author": authorNames.join(", ") }), article.date && {
767
929
  "article:published_time": new Date(article.date).toISOString()
768
930
  }), article.lastmod && {
769
931
  "article:modified_time": new Date(article.lastmod).toISOString()
@@ -862,11 +1024,162 @@ function generateCategoryMetadata(categorySlug, config) {
862
1024
  };
863
1025
  });
864
1026
  }
1027
+ function generateAuthorMetadata(authorSlug, config) {
1028
+ return __async(this, null, function* () {
1029
+ var _a;
1030
+ const author = getAuthorBySlug(authorSlug, config);
1031
+ if (!author || config.showAuthorPage === false) return { title: "Author Not Found" };
1032
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1033
+ const authorUrl = (_a = author.url) != null ? _a : `${siteUrl}/articles/authors/${author.slug}`;
1034
+ const title = `${author.name} Articles | ${config.siteName}`;
1035
+ return {
1036
+ title,
1037
+ description: author.bio,
1038
+ openGraph: __spreadValues({
1039
+ title,
1040
+ description: author.bio,
1041
+ url: authorUrl,
1042
+ siteName: config.siteName,
1043
+ type: "profile",
1044
+ locale: "en_US"
1045
+ }, author.avatar && { images: [{ url: resolveAuthorAvatar(author, config) }] }),
1046
+ twitter: __spreadValues({
1047
+ card: "summary_large_image",
1048
+ title,
1049
+ description: author.bio
1050
+ }, author.avatar && { images: [resolveAuthorAvatar(author, config)] }),
1051
+ alternates: {
1052
+ canonical: authorUrl
1053
+ },
1054
+ robots: {
1055
+ index: true,
1056
+ follow: true
1057
+ }
1058
+ };
1059
+ });
1060
+ }
1061
+ function formatCategoryName(category) {
1062
+ return category.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
1063
+ }
1064
+ function buildArticleBreadcrumbs(article, config) {
1065
+ var _a, _b;
1066
+ if (!breadcrumbsAreEnabled(config)) return [];
1067
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1068
+ const breadcrumbConfig = getBreadcrumbsConfig(config);
1069
+ const labels = (_a = breadcrumbConfig.labels) != null ? _a : {};
1070
+ const categorySlug = categoryToSlug(article.category);
1071
+ const trail = (_b = breadcrumbConfig.article) != null ? _b : ["home", "articles", "primaryCategory", "articleTitle"];
1072
+ const folderSegments = article.slug.split("/").filter(Boolean).slice(0, -1);
1073
+ return trail.flatMap(
1074
+ (token) => buildArticleBreadcrumbToken(token, {
1075
+ article,
1076
+ siteUrl,
1077
+ categorySlug,
1078
+ folderSegments,
1079
+ labels
1080
+ })
1081
+ );
1082
+ }
1083
+ function buildCategoryBreadcrumbs(category, config, categoryName = formatCategoryName(category)) {
1084
+ var _a, _b;
1085
+ if (!breadcrumbsAreEnabled(config)) return [];
1086
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1087
+ const breadcrumbConfig = getBreadcrumbsConfig(config);
1088
+ const labels = (_a = breadcrumbConfig.labels) != null ? _a : {};
1089
+ const trail = (_b = breadcrumbConfig.category) != null ? _b : ["home", "articles", "category"];
1090
+ return trail.flatMap(
1091
+ (entry) => buildCategoryBreadcrumbEntry(entry, { categoryName, siteUrl, labels })
1092
+ );
1093
+ }
1094
+ function buildAuthorBreadcrumbs(author, config) {
1095
+ var _a, _b;
1096
+ if (!breadcrumbsAreEnabled(config)) return [];
1097
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1098
+ const breadcrumbConfig = getBreadcrumbsConfig(config);
1099
+ const labels = (_a = breadcrumbConfig.labels) != null ? _a : {};
1100
+ const trail = (_b = breadcrumbConfig.author) != null ? _b : ["home", "articles", "authors", "authorName"];
1101
+ return trail.flatMap(
1102
+ (entry) => buildAuthorBreadcrumbEntry(entry, { author, siteUrl, labels })
1103
+ );
1104
+ }
1105
+ function isCustomBreadcrumbItem(entry) {
1106
+ return typeof entry === "object" && entry !== null && "name" in entry && "url" in entry;
1107
+ }
1108
+ function resolveCustomBreadcrumbItem(item, siteUrl) {
1109
+ if (item.url.startsWith("/")) return { name: item.name, url: `${siteUrl}${item.url}` };
1110
+ return { name: item.name, url: item.url };
1111
+ }
1112
+ function buildArticleBreadcrumbToken(entry, context) {
1113
+ var _a, _b;
1114
+ if (isCustomBreadcrumbItem(entry)) {
1115
+ return [resolveCustomBreadcrumbItem(entry, context.siteUrl)];
1116
+ }
1117
+ if (entry === "home") return [{ name: (_a = context.labels.home) != null ? _a : "Home", url: context.siteUrl }];
1118
+ if (entry === "articles") {
1119
+ return [{ name: (_b = context.labels.articles) != null ? _b : "Articles", url: `${context.siteUrl}/articles` }];
1120
+ }
1121
+ if (entry === "primaryCategory") {
1122
+ return [
1123
+ {
1124
+ name: context.article.category,
1125
+ url: `${context.siteUrl}/articles/category/${context.categorySlug}`
1126
+ }
1127
+ ];
1128
+ }
1129
+ if (entry === "folderPath") {
1130
+ return context.folderSegments.map((segment, index) => ({
1131
+ name: formatCategoryName(segment),
1132
+ url: `${context.siteUrl}/articles/${context.folderSegments.slice(0, index + 1).join("/")}`
1133
+ }));
1134
+ }
1135
+ return [{ name: context.article.title }];
1136
+ }
1137
+ function buildCategoryBreadcrumbEntry(entry, context) {
1138
+ var _a, _b;
1139
+ if (isCustomBreadcrumbItem(entry)) {
1140
+ return [resolveCustomBreadcrumbItem(entry, context.siteUrl)];
1141
+ }
1142
+ if (entry === "home") return [{ name: (_a = context.labels.home) != null ? _a : "Home", url: context.siteUrl }];
1143
+ if (entry === "articles") {
1144
+ return [{ name: (_b = context.labels.articles) != null ? _b : "Articles", url: `${context.siteUrl}/articles` }];
1145
+ }
1146
+ return [{ name: context.categoryName }];
1147
+ }
1148
+ function buildAuthorBreadcrumbEntry(entry, context) {
1149
+ var _a, _b, _c;
1150
+ if (isCustomBreadcrumbItem(entry)) {
1151
+ return [resolveCustomBreadcrumbItem(entry, context.siteUrl)];
1152
+ }
1153
+ if (entry === "home") return [{ name: (_a = context.labels.home) != null ? _a : "Home", url: context.siteUrl }];
1154
+ if (entry === "articles") {
1155
+ return [{ name: (_b = context.labels.articles) != null ? _b : "Articles", url: `${context.siteUrl}/articles` }];
1156
+ }
1157
+ if (entry === "authors") {
1158
+ return [
1159
+ {
1160
+ name: (_c = context.labels.authors) != null ? _c : "Authors",
1161
+ url: `${context.siteUrl}/articles/authors`
1162
+ }
1163
+ ];
1164
+ }
1165
+ return [{ name: context.author.name }];
1166
+ }
1167
+ function resolveAuthorAvatar(author, config) {
1168
+ if (!author.avatar) return "";
1169
+ if (author.avatar.startsWith("http://") || author.avatar.startsWith("https://")) {
1170
+ return author.avatar;
1171
+ }
1172
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1173
+ return `${siteUrl}/articles/authors/${author.slug}/${author.avatar.replace(/^\/+/, "")}`;
1174
+ }
865
1175
  function getArticleSitemapEntries(baseUrlOrConfig) {
866
1176
  return __async(this, null, function* () {
867
1177
  const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
868
1178
  try {
869
- const [articles, categories] = yield Promise.all([getAllArticles(), getAllCategories()]);
1179
+ const [articles, categories] = yield Promise.all([
1180
+ getAllArticles(typeof baseUrlOrConfig === "string" ? void 0 : baseUrlOrConfig),
1181
+ getAllCategories()
1182
+ ]);
870
1183
  const articleEntries = articles.map((article) => {
871
1184
  var _a;
872
1185
  const dateStr = (_a = article.lastmod) != null ? _a : article.date;
@@ -884,7 +1197,12 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
884
1197
  changeFrequency: "weekly",
885
1198
  priority: 0.7
886
1199
  }));
887
- return [...articleEntries, ...categoryEntries];
1200
+ const authorEntries = typeof baseUrlOrConfig === "string" || baseUrlOrConfig.showAuthorPage === false ? [] : getAllAuthors(baseUrlOrConfig).map((author) => ({
1201
+ url: `${baseUrl}/articles/authors/${author.slug}`,
1202
+ changeFrequency: "monthly",
1203
+ priority: 0.6
1204
+ }));
1205
+ return [...articleEntries, ...categoryEntries, ...authorEntries];
888
1206
  } catch (e) {
889
1207
  return [];
890
1208
  }
@@ -904,18 +1222,18 @@ import { jsx } from "react/jsx-runtime";
904
1222
  function makeImgComponent(basePath) {
905
1223
  return function MdxImage(_a) {
906
1224
  var _b = _a, { src, alt } = _b, props = __objRest(_b, ["src", "alt"]);
907
- const resolvedSrc = src && !src.startsWith("http") && !src.startsWith("/") ? `${basePath}/${src}` : src;
1225
+ const resolvedSrc = typeof src === "string" && !src.startsWith("http") && !src.startsWith("/") ? `${basePath}/${src}` : src;
908
1226
  return React.createElement("img", __spreadValues({ src: resolvedSrc, alt }, props));
909
1227
  };
910
1228
  }
911
- function renderMdxSource(source, basePath) {
1229
+ function renderMdxSource(source, basePath, config) {
912
1230
  return __async(this, null, function* () {
913
1231
  const isDevelopment = process.env.NODE_ENV === "development";
914
1232
  const mdxModule = yield evaluate(source, __spreadProps(__spreadValues({}, isDevelopment ? devRuntime : runtime), {
915
1233
  development: isDevelopment,
916
1234
  remarkPlugins: [remarkGfm2, remarkGithubBlockquoteAlert2],
917
1235
  rehypePlugins: [
918
- customRenderer,
1236
+ [customRenderer, { strategy: config == null ? void 0 : config.linkTargetStrategy, siteUrl: config == null ? void 0 : config.siteUrl }],
919
1237
  rehypeSlug2,
920
1238
  // @ts-ignore
921
1239
  rehypePrism2
@@ -930,9 +1248,9 @@ function renderMdxSource(source, basePath) {
930
1248
  // src/ArticleContent.tsx
931
1249
  import { jsx as jsx2 } from "react/jsx-runtime";
932
1250
  function ArticleContent(_0) {
933
- return __async(this, arguments, function* ({ article, className }) {
1251
+ return __async(this, arguments, function* ({ article, className, config }) {
934
1252
  if (article.contentType === "mdx" && article.mdxSource) {
935
- const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`);
1253
+ const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config);
936
1254
  return /* @__PURE__ */ jsx2("div", { className, children: content });
937
1255
  }
938
1256
  return /* @__PURE__ */ jsx2("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
@@ -965,26 +1283,38 @@ function ArticleTOC({ toc, className }) {
965
1283
  export {
966
1284
  ArticleContent,
967
1285
  ArticleTOC,
1286
+ buildArticleBreadcrumbs,
1287
+ buildAuthorBreadcrumbs,
1288
+ buildCategoryBreadcrumbs,
968
1289
  categoryToSlug,
969
1290
  extractToc,
970
1291
  generateArticleMetadata,
971
1292
  generateArticleStaticParams,
972
1293
  generateArticlesIndexMetadata,
1294
+ generateAuthorMetadata,
1295
+ generateAuthorStaticParams,
973
1296
  generateCategoryMetadata,
974
1297
  generateCategoryStaticParams,
1298
+ generateRssFeed,
975
1299
  getAdjacentArticles,
976
1300
  getAiRobotsTxtRules,
977
1301
  getAllArticles,
1302
+ getAllAuthors,
978
1303
  getAllCategories,
979
1304
  getArticleAiHeaders,
1305
+ getArticleAuthors,
980
1306
  getArticleMarkdown,
981
1307
  getArticleMarkdownResponse,
982
1308
  getArticleMarkdownUrl,
983
1309
  getArticleMetadata,
984
1310
  getArticleSitemapEntries,
1311
+ getArticlesByAuthor,
985
1312
  getArticlesByCategory,
1313
+ getAuthorBySlug,
986
1314
  getAvailableArticleSlugs,
1315
+ getBreadcrumbsConfig,
987
1316
  markdownToHtml,
1317
+ resolveAuthorAvatar,
988
1318
  sanitizeImagePath2 as sanitizeImagePath,
989
1319
  searchArticles,
990
1320
  setArticlesErrorHandler