@fullstackdatasolutions/articles 0.9.0 → 0.11.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 (48) hide show
  1. package/CHANGELOG.md +243 -0
  2. package/README.md +226 -29
  3. package/dist/index.cjs +635 -274
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +165 -56
  6. package/dist/index.d.ts +165 -56
  7. package/dist/index.js +614 -250
  8. package/dist/index.js.map +1 -1
  9. package/dist/nextjs.cjs +40 -5
  10. package/dist/nextjs.cjs.map +1 -1
  11. package/dist/nextjs.d.cts +72 -0
  12. package/dist/nextjs.d.ts +72 -0
  13. package/dist/nextjs.js +40 -5
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +280 -16
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +92 -3
  18. package/dist/server.d.ts +92 -3
  19. package/dist/server.js +269 -16
  20. package/dist/server.js.map +1 -1
  21. package/package.json +8 -5
  22. package/src/ArticleDetailHero.tsx +27 -2
  23. package/src/ArticleSchemas.tsx +27 -27
  24. package/src/AuthorArticlesPage.tsx +60 -0
  25. package/src/AuthorCard.tsx +112 -0
  26. package/src/AuthorDetailHero.tsx +56 -0
  27. package/src/Breadcrumb.tsx +78 -0
  28. package/src/CategoryArticlesPage.tsx +62 -11
  29. package/src/__tests__/ArticleDetailHero.test.tsx +21 -1
  30. package/src/__tests__/ArticleSchemas.test.tsx +47 -2
  31. package/src/__tests__/AuthorArticlesPage.test.tsx +74 -0
  32. package/src/__tests__/AuthorCard.test.tsx +98 -0
  33. package/src/__tests__/AuthorDetailHero.test.tsx +51 -0
  34. package/src/__tests__/CategoryArticlesPage.test.tsx +31 -5
  35. package/src/__tests__/articlesConfig.test.ts +20 -1
  36. package/src/__tests__/authorUtils.test.ts +89 -0
  37. package/src/__tests__/renderMdx.test.tsx +113 -0
  38. package/src/__tests__/seoUtils-authors.test.ts +160 -0
  39. package/src/__tests__/seoUtils.test.ts +4 -0
  40. package/src/__tests__/server-articles.test.ts +159 -2
  41. package/src/articleTypes.ts +33 -0
  42. package/src/articlesConfig.ts +68 -0
  43. package/src/authorUtils.ts +95 -0
  44. package/src/index.ts +32 -9
  45. package/src/renderMdx.tsx +8 -2
  46. package/src/seoUtils.ts +226 -7
  47. package/src/server-articles.ts +98 -10
  48. package/src/server.ts +19 -1
package/dist/server.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ComponentType } from 'react';
1
2
  import { Metadata, MetadataRoute } from 'next';
2
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
4
 
@@ -21,6 +22,7 @@ interface Article {
21
22
  date?: string;
22
23
  lastmod?: string;
23
24
  author: string;
25
+ authors?: string[];
24
26
  category: string;
25
27
  categories: string[];
26
28
  readTime: string;
@@ -45,6 +47,35 @@ interface CategoryInfo {
45
47
  count: number;
46
48
  featuredImage: string;
47
49
  }
50
+ interface AuthorSocial {
51
+ website?: string;
52
+ facebook?: string;
53
+ twitter?: string;
54
+ x?: string;
55
+ linkedin?: string;
56
+ instagram?: string;
57
+ youtube?: string;
58
+ tiktok?: string;
59
+ github?: string;
60
+ bluesky?: string;
61
+ threads?: string;
62
+ mastodon?: string;
63
+ medium?: string;
64
+ newsletter?: string;
65
+ other?: Record<string, string>;
66
+ }
67
+ interface AuthorProfile {
68
+ name: string;
69
+ slug: string;
70
+ bio: string;
71
+ avatar?: string;
72
+ url?: string;
73
+ social?: AuthorSocial;
74
+ }
75
+ interface BreadcrumbItem {
76
+ name: string;
77
+ url?: string;
78
+ }
48
79
 
49
80
  /** Keys for each renderable section of the articles listing page. */
50
81
  type ArticlesSection = 'hero' | 'search' | 'featured' | 'latest' | 'categories' | 'newsletter';
@@ -106,6 +137,41 @@ interface HeroConfig {
106
137
  }
107
138
  /** Controls how article body links set target/rel attributes. */
108
139
  type LinkTargetStrategy = 'external-new-tab' | 'all-new-tab' | 'same-tab';
140
+ /** React components that article MDX bodies can reference by JSX tag name. */
141
+ type MdxComponents = Record<string, ComponentType<never>>;
142
+ type ArticleBreadcrumbToken = 'home' | 'articles' | 'primaryCategory' | 'folderPath' | 'articleTitle';
143
+ type CategoryBreadcrumbToken = 'home' | 'articles' | 'category';
144
+ type AuthorBreadcrumbToken = 'home' | 'articles' | 'authors' | 'authorName';
145
+ interface CustomBreadcrumbItem {
146
+ /** Label displayed in the breadcrumb trail. */
147
+ name: string;
148
+ /** Custom URL. Relative paths are resolved against `siteUrl` by server builders. */
149
+ url: string;
150
+ }
151
+ type ArticleBreadcrumbEntry = ArticleBreadcrumbToken | CustomBreadcrumbItem;
152
+ type CategoryBreadcrumbEntry = CategoryBreadcrumbToken | CustomBreadcrumbItem;
153
+ type AuthorBreadcrumbEntry = AuthorBreadcrumbToken | CustomBreadcrumbItem;
154
+ interface BreadcrumbLabels {
155
+ home?: string;
156
+ articles?: string;
157
+ authors?: string;
158
+ }
159
+ interface BreadcrumbsConfig {
160
+ /** Set to false to hide visible breadcrumbs and breadcrumb JSON-LD generated by the helper builders. */
161
+ show?: boolean;
162
+ /** Separator used by the visible Breadcrumb component. Default: '>'. */
163
+ separator?: string;
164
+ /** Set to false to render visible breadcrumbs without JSON-LD. Default: true. */
165
+ showSchema?: boolean;
166
+ /** Article breadcrumb trail. Example: ['primaryCategory', { name: 'Guides', url: '/guides' }, 'articleTitle']. */
167
+ article?: ArticleBreadcrumbEntry[];
168
+ /** Category breadcrumb trail. Default: ['home', 'articles', 'category']. */
169
+ category?: CategoryBreadcrumbEntry[];
170
+ /** Author breadcrumb trail. Default: ['home', 'articles', 'authors', 'authorName']. */
171
+ author?: AuthorBreadcrumbEntry[];
172
+ /** Optional label overrides for built-in breadcrumb items. */
173
+ labels?: BreadcrumbLabels;
174
+ }
109
175
  /** Top-level configuration object. Pass one instance to every library component. */
110
176
  interface ArticlesConfig {
111
177
  /** Canonical base URL of the site, used in metadata and JSON-LD. Example: `'https://yoursite.com'` */
@@ -141,14 +207,28 @@ interface ArticlesConfig {
141
207
  showBackToArticles?: boolean;
142
208
  /** Set to false to hide author names from UI and metadata. Default: true. */
143
209
  showAuthor?: boolean;
210
+ /** Author profiles keyed by slug. Omit to keep plain string author display. */
211
+ authors?: Record<string, AuthorProfile>;
212
+ /** Author slug used when article frontmatter omits author fields. */
213
+ defaultAuthor?: string;
214
+ /** Set to false to disable copied author page routes in consuming apps. Default: true. */
215
+ showAuthorPage?: boolean;
216
+ /** Set to false to disable breadcrumbs, or pass a config object to customize breadcrumb trails. */
217
+ breadcrumbs?: false | BreadcrumbsConfig;
144
218
  /** Article body link target behavior. Default: `'external-new-tab'`. */
145
219
  linkTargetStrategy?: LinkTargetStrategy;
220
+ /** Extra components exposed to article MDX bodies by JSX tag name. */
221
+ mdxComponents?: MdxComponents;
146
222
  }
223
+ declare function getBreadcrumbsConfig(config: ArticlesConfig): BreadcrumbsConfig;
147
224
 
148
225
  declare function sanitizeImagePath(rawPath: string, articleSlug: string): string | null;
149
226
  declare function getAvailableArticleSlugs(): string[];
227
+ declare function getAuthorBySlug(slug: string, config: ArticlesConfig): AuthorProfile | null;
228
+ declare function getArticleAuthors(article: Article, config: ArticlesConfig): AuthorProfile[];
229
+ declare function getAllAuthors(config: ArticlesConfig): AuthorProfile[];
150
230
  declare const getArticleMetadata: (slug: string, config?: ArticlesConfig) => Promise<Article | null>;
151
- declare const getAllArticles: () => Promise<Article[]>;
231
+ declare const getAllArticles: (config?: ArticlesConfig) => Promise<Article[]>;
152
232
  declare function getAdjacentArticles(currentSlug: string): Promise<{
153
233
  previous: Article | null;
154
234
  next: Article | null;
@@ -161,7 +241,8 @@ declare function getAiRobotsTxtRules(): Promise<string>;
161
241
  declare function searchArticles(query: string, config?: ArticlesConfig): Promise<Article[]>;
162
242
  declare function categoryToSlug(category: string): string;
163
243
  declare function getAllCategories(): Promise<CategoryInfo[]>;
164
- declare function getArticlesByCategory(categorySlug: string): Promise<Article[]>;
244
+ declare function getArticlesByCategory(categorySlug: string, config?: ArticlesConfig): Promise<Article[]>;
245
+ declare function getArticlesByAuthor(authorSlug: string, config: ArticlesConfig): Promise<Article[]>;
165
246
 
166
247
  declare function generateRssFeed(articles: Article[], config: ArticlesConfig): string;
167
248
  declare function generateArticleStaticParams(): {
@@ -170,9 +251,17 @@ declare function generateArticleStaticParams(): {
170
251
  declare function generateCategoryStaticParams(): Promise<{
171
252
  category: string;
172
253
  }[]>;
254
+ declare function generateAuthorStaticParams(config: ArticlesConfig): {
255
+ author: string;
256
+ }[];
173
257
  declare function generateArticleMetadata(slug: string, config: ArticlesConfig): Promise<Metadata>;
174
258
  declare function generateArticlesIndexMetadata(config: ArticlesConfig): Metadata;
175
259
  declare function generateCategoryMetadata(categorySlug: string, config: ArticlesConfig): Promise<Metadata>;
260
+ declare function generateAuthorMetadata(authorSlug: string, config: ArticlesConfig): Promise<Metadata>;
261
+ declare function buildArticleBreadcrumbs(article: Pick<Article, 'slug' | 'title' | 'category'>, config: ArticlesConfig): BreadcrumbItem[];
262
+ declare function buildCategoryBreadcrumbs(category: string, config: ArticlesConfig, categoryName?: string): BreadcrumbItem[];
263
+ declare function buildAuthorBreadcrumbs(author: AuthorProfile, config: ArticlesConfig): BreadcrumbItem[];
264
+ declare function resolveAuthorAvatar(author: AuthorProfile, config: ArticlesConfig): string;
176
265
  declare function getArticleSitemapEntries(baseUrlOrConfig: string | ArticlesConfig): Promise<MetadataRoute.Sitemap>;
177
266
 
178
267
  declare function markdownToHtml(markdown: string, articleSlug?: string, config?: ArticlesConfig): Promise<string>;
@@ -202,4 +291,4 @@ type ArticleTOCProps = Readonly<{
202
291
  }>;
203
292
  declare function ArticleTOC({ toc, className }: ArticleTOCProps): react_jsx_runtime.JSX.Element | null;
204
293
 
205
- export { type Article, ArticleContent, ArticleTOC, type ArticlesConfig, type ArticlesErrorCode, type ArticlesErrorContext, type ArticlesErrorHandler, type ArticlesErrorReport, type CategoryInfo, type LinkTargetStrategy, type TocItem, categoryToSlug, extractToc, generateArticleMetadata, generateArticleStaticParams, generateArticlesIndexMetadata, generateCategoryMetadata, generateCategoryStaticParams, generateRssFeed, getAdjacentArticles, getAiRobotsTxtRules, getAllArticles, getAllCategories, getArticleAiHeaders, getArticleMarkdown, getArticleMarkdownResponse, getArticleMarkdownUrl, getArticleMetadata, getArticleSitemapEntries, getArticlesByCategory, getAvailableArticleSlugs, markdownToHtml, sanitizeImagePath, searchArticles, setArticlesErrorHandler };
294
+ export { type Article, ArticleContent, ArticleTOC, type ArticlesConfig, type ArticlesErrorCode, type ArticlesErrorContext, type ArticlesErrorHandler, type ArticlesErrorReport, type AuthorProfile, type AuthorSocial, type BreadcrumbItem, type CategoryInfo, type LinkTargetStrategy, type TocItem, buildArticleBreadcrumbs, buildAuthorBreadcrumbs, buildCategoryBreadcrumbs, categoryToSlug, extractToc, generateArticleMetadata, generateArticleStaticParams, generateArticlesIndexMetadata, generateAuthorMetadata, generateAuthorStaticParams, generateCategoryMetadata, generateCategoryStaticParams, generateRssFeed, getAdjacentArticles, getAiRobotsTxtRules, getAllArticles, getAllAuthors, getAllCategories, getArticleAiHeaders, getArticleAuthors, getArticleMarkdown, getArticleMarkdownResponse, getArticleMarkdownUrl, getArticleMetadata, getArticleSitemapEntries, getArticlesByAuthor, getArticlesByCategory, getAuthorBySlug, getAvailableArticleSlugs, getBreadcrumbsConfig, markdownToHtml, resolveAuthorAvatar, sanitizeImagePath, searchArticles, setArticlesErrorHandler };
package/dist/server.js CHANGED
@@ -520,7 +520,67 @@ function parseHowToSteps(raw) {
520
520
  );
521
521
  return steps.length ? steps : void 0;
522
522
  }
523
- 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) {
524
584
  return __async(this, null, function* () {
525
585
  try {
526
586
  const found = findArticleFile(slug);
@@ -536,7 +596,8 @@ function getArticleSummary(slug) {
536
596
  excerpt: data.excerpt || "",
537
597
  date: parseDateField(data.date),
538
598
  lastmod: parseDateField(data.lastmod),
539
- author: data.author || "Andrew Blase",
599
+ author: resolveArticleAuthorName(data.author, data.authors, config),
600
+ authors: parseAuthors(data.authors),
540
601
  category: categories[0],
541
602
  categories,
542
603
  readTime,
@@ -565,7 +626,7 @@ function getArticleSummary(slug) {
565
626
  var getArticleMetadata = cache(
566
627
  (slug, config) => __async(void 0, null, function* () {
567
628
  try {
568
- const summary = yield getArticleSummary(slug);
629
+ const summary = yield getArticleSummary(slug, config);
569
630
  if (!summary) return null;
570
631
  const found = findArticleFile(slug);
571
632
  if (!found) return null;
@@ -591,9 +652,9 @@ var getArticleMetadata = cache(
591
652
  }
592
653
  })
593
654
  );
594
- var getAllArticles = cache(() => __async(void 0, null, function* () {
655
+ var getAllArticles = cache((config) => __async(void 0, null, function* () {
595
656
  const slugs = getAvailableArticleSlugs();
596
- const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
657
+ const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug, config)));
597
658
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
598
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) => {
599
660
  if (!a.date && !b.date) return 0;
@@ -683,8 +744,8 @@ function getAiRobotsTxtRules() {
683
744
  }
684
745
  function searchArticles(query, config) {
685
746
  return __async(this, null, function* () {
686
- if (!(query == null ? void 0 : query.trim())) return getAllArticles();
687
- const articles = yield getAllArticles();
747
+ if (!(query == null ? void 0 : query.trim())) return getAllArticles(config);
748
+ const articles = yield getAllArticles(config);
688
749
  const searchTerm = query.toLowerCase().trim();
689
750
  const includeAuthor = (config == null ? void 0 : config.showAuthor) !== false;
690
751
  return articles.filter((article) => {
@@ -721,14 +782,33 @@ function getAllCategories() {
721
782
  })).sort((a, b) => b.count - a.count);
722
783
  });
723
784
  }
724
- function getArticlesByCategory(categorySlug) {
785
+ function getArticlesByCategory(categorySlug, config) {
725
786
  return __async(this, null, function* () {
726
- const articles = yield getAllArticles();
787
+ const articles = yield getAllArticles(config);
727
788
  return articles.filter(
728
789
  (article) => article.categories.some((cat) => categoryToSlug(cat) === categorySlug)
729
790
  );
730
791
  });
731
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
+ }
732
812
 
733
813
  // src/seoUtils.ts
734
814
  function escapeXml(str) {
@@ -777,6 +857,10 @@ function generateCategoryStaticParams() {
777
857
  return categories.map((cat) => ({ category: cat.slug }));
778
858
  });
779
859
  }
860
+ function generateAuthorStaticParams(config) {
861
+ if (config.showAuthorPage === false) return [];
862
+ return getAllAuthors(config).map((author) => ({ author: author.slug }));
863
+ }
780
864
  function resolveImageUrl(featuredImage, siteUrl) {
781
865
  const base = siteUrl.replace(/\/$/, "");
782
866
  if (featuredImage.startsWith("http://") || featuredImage.startsWith("https://")) {
@@ -787,7 +871,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
787
871
  function generateArticleMetadata(slug, config) {
788
872
  return __async(this, null, function* () {
789
873
  var _a, _b, _c, _d, _e, _f, _g;
790
- const article = yield getArticleMetadata(slug);
874
+ const article = yield getArticleMetadata(slug, config);
791
875
  if (!article) {
792
876
  return {
793
877
  title: "Article Not Found",
@@ -801,6 +885,7 @@ function generateArticleMetadata(slug, config) {
801
885
  const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
802
886
  const showAuthor = config.showAuthor !== false;
803
887
  const markdownUrl = getArticleMarkdownUrl(article, config);
888
+ const authorNames = getArticleAuthors(article, config).map((author) => author.name);
804
889
  return {
805
890
  title: `${article.title} | ${config.siteName}`,
806
891
  description,
@@ -813,7 +898,7 @@ function generateArticleMetadata(slug, config) {
813
898
  images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
814
899
  locale: "en_US",
815
900
  type: "article"
816
- }, 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 }), {
817
902
  tags: (_d = article.tags) != null ? _d : []
818
903
  }),
819
904
  twitter: {
@@ -840,7 +925,7 @@ function generateArticleMetadata(slug, config) {
840
925
  "max-snippet": -1
841
926
  }
842
927
  },
843
- 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 && {
844
929
  "article:published_time": new Date(article.date).toISOString()
845
930
  }), article.lastmod && {
846
931
  "article:modified_time": new Date(article.lastmod).toISOString()
@@ -939,11 +1024,162 @@ function generateCategoryMetadata(categorySlug, config) {
939
1024
  };
940
1025
  });
941
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
+ }
942
1175
  function getArticleSitemapEntries(baseUrlOrConfig) {
943
1176
  return __async(this, null, function* () {
944
1177
  const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
945
1178
  try {
946
- 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
+ ]);
947
1183
  const articleEntries = articles.map((article) => {
948
1184
  var _a;
949
1185
  const dateStr = (_a = article.lastmod) != null ? _a : article.date;
@@ -961,7 +1197,12 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
961
1197
  changeFrequency: "weekly",
962
1198
  priority: 0.7
963
1199
  }));
964
- 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];
965
1206
  } catch (e) {
966
1207
  return [];
967
1208
  }
@@ -981,7 +1222,7 @@ import { jsx } from "react/jsx-runtime";
981
1222
  function makeImgComponent(basePath) {
982
1223
  return function MdxImage(_a) {
983
1224
  var _b = _a, { src, alt } = _b, props = __objRest(_b, ["src", "alt"]);
984
- 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;
985
1226
  return React.createElement("img", __spreadValues({ src: resolvedSrc, alt }, props));
986
1227
  };
987
1228
  }
@@ -999,7 +1240,8 @@ function renderMdxSource(source, basePath, config) {
999
1240
  ]
1000
1241
  }));
1001
1242
  const Content = mdxModule.default;
1002
- const components = basePath ? { img: makeImgComponent(basePath) } : void 0;
1243
+ const internalComponents = basePath ? { img: makeImgComponent(basePath) } : void 0;
1244
+ const components = internalComponents || (config == null ? void 0 : config.mdxComponents) ? __spreadValues(__spreadValues({}, internalComponents), config == null ? void 0 : config.mdxComponents) : void 0;
1003
1245
  return /* @__PURE__ */ jsx(Content, { components });
1004
1246
  });
1005
1247
  }
@@ -1042,27 +1284,38 @@ function ArticleTOC({ toc, className }) {
1042
1284
  export {
1043
1285
  ArticleContent,
1044
1286
  ArticleTOC,
1287
+ buildArticleBreadcrumbs,
1288
+ buildAuthorBreadcrumbs,
1289
+ buildCategoryBreadcrumbs,
1045
1290
  categoryToSlug,
1046
1291
  extractToc,
1047
1292
  generateArticleMetadata,
1048
1293
  generateArticleStaticParams,
1049
1294
  generateArticlesIndexMetadata,
1295
+ generateAuthorMetadata,
1296
+ generateAuthorStaticParams,
1050
1297
  generateCategoryMetadata,
1051
1298
  generateCategoryStaticParams,
1052
1299
  generateRssFeed,
1053
1300
  getAdjacentArticles,
1054
1301
  getAiRobotsTxtRules,
1055
1302
  getAllArticles,
1303
+ getAllAuthors,
1056
1304
  getAllCategories,
1057
1305
  getArticleAiHeaders,
1306
+ getArticleAuthors,
1058
1307
  getArticleMarkdown,
1059
1308
  getArticleMarkdownResponse,
1060
1309
  getArticleMarkdownUrl,
1061
1310
  getArticleMetadata,
1062
1311
  getArticleSitemapEntries,
1312
+ getArticlesByAuthor,
1063
1313
  getArticlesByCategory,
1314
+ getAuthorBySlug,
1064
1315
  getAvailableArticleSlugs,
1316
+ getBreadcrumbsConfig,
1065
1317
  markdownToHtml,
1318
+ resolveAuthorAvatar,
1066
1319
  sanitizeImagePath2 as sanitizeImagePath,
1067
1320
  searchArticles,
1068
1321
  setArticlesErrorHandler