@fullstackdatasolutions/articles 0.9.0 → 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 (47) hide show
  1. package/CHANGELOG.md +237 -0
  2. package/README.md +199 -29
  3. package/dist/index.cjs +635 -274
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +160 -56
  6. package/dist/index.d.ts +160 -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 +67 -0
  12. package/dist/nextjs.d.ts +67 -0
  13. package/dist/nextjs.js +40 -5
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +278 -15
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +87 -3
  18. package/dist/server.d.ts +87 -3
  19. package/dist/server.js +267 -15
  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__/authorUtils.test.ts +89 -0
  36. package/src/__tests__/renderMdx.test.tsx +35 -0
  37. package/src/__tests__/seoUtils-authors.test.ts +160 -0
  38. package/src/__tests__/seoUtils.test.ts +4 -0
  39. package/src/__tests__/server-articles.test.ts +159 -2
  40. package/src/articleTypes.ts +33 -0
  41. package/src/articlesConfig.ts +62 -0
  42. package/src/authorUtils.ts +95 -0
  43. package/src/index.ts +31 -9
  44. package/src/renderMdx.tsx +3 -1
  45. package/src/seoUtils.ts +226 -7
  46. package/src/server-articles.ts +98 -10
  47. package/src/server.ts +19 -1
package/dist/server.d.ts CHANGED
@@ -21,6 +21,7 @@ interface Article {
21
21
  date?: string;
22
22
  lastmod?: string;
23
23
  author: string;
24
+ authors?: string[];
24
25
  category: string;
25
26
  categories: string[];
26
27
  readTime: string;
@@ -45,6 +46,35 @@ interface CategoryInfo {
45
46
  count: number;
46
47
  featuredImage: string;
47
48
  }
49
+ interface AuthorSocial {
50
+ website?: string;
51
+ facebook?: string;
52
+ twitter?: string;
53
+ x?: string;
54
+ linkedin?: string;
55
+ instagram?: string;
56
+ youtube?: string;
57
+ tiktok?: string;
58
+ github?: string;
59
+ bluesky?: string;
60
+ threads?: string;
61
+ mastodon?: string;
62
+ medium?: string;
63
+ newsletter?: string;
64
+ other?: Record<string, string>;
65
+ }
66
+ interface AuthorProfile {
67
+ name: string;
68
+ slug: string;
69
+ bio: string;
70
+ avatar?: string;
71
+ url?: string;
72
+ social?: AuthorSocial;
73
+ }
74
+ interface BreadcrumbItem {
75
+ name: string;
76
+ url?: string;
77
+ }
48
78
 
49
79
  /** Keys for each renderable section of the articles listing page. */
50
80
  type ArticlesSection = 'hero' | 'search' | 'featured' | 'latest' | 'categories' | 'newsletter';
@@ -106,6 +136,39 @@ interface HeroConfig {
106
136
  }
107
137
  /** Controls how article body links set target/rel attributes. */
108
138
  type LinkTargetStrategy = 'external-new-tab' | 'all-new-tab' | 'same-tab';
139
+ type ArticleBreadcrumbToken = 'home' | 'articles' | 'primaryCategory' | 'folderPath' | 'articleTitle';
140
+ type CategoryBreadcrumbToken = 'home' | 'articles' | 'category';
141
+ type AuthorBreadcrumbToken = 'home' | 'articles' | 'authors' | 'authorName';
142
+ interface CustomBreadcrumbItem {
143
+ /** Label displayed in the breadcrumb trail. */
144
+ name: string;
145
+ /** Custom URL. Relative paths are resolved against `siteUrl` by server builders. */
146
+ url: string;
147
+ }
148
+ type ArticleBreadcrumbEntry = ArticleBreadcrumbToken | CustomBreadcrumbItem;
149
+ type CategoryBreadcrumbEntry = CategoryBreadcrumbToken | CustomBreadcrumbItem;
150
+ type AuthorBreadcrumbEntry = AuthorBreadcrumbToken | CustomBreadcrumbItem;
151
+ interface BreadcrumbLabels {
152
+ home?: string;
153
+ articles?: string;
154
+ authors?: string;
155
+ }
156
+ interface BreadcrumbsConfig {
157
+ /** Set to false to hide visible breadcrumbs and breadcrumb JSON-LD generated by the helper builders. */
158
+ show?: boolean;
159
+ /** Separator used by the visible Breadcrumb component. Default: '>'. */
160
+ separator?: string;
161
+ /** Set to false to render visible breadcrumbs without JSON-LD. Default: true. */
162
+ showSchema?: boolean;
163
+ /** Article breadcrumb trail. Example: ['primaryCategory', { name: 'Guides', url: '/guides' }, 'articleTitle']. */
164
+ article?: ArticleBreadcrumbEntry[];
165
+ /** Category breadcrumb trail. Default: ['home', 'articles', 'category']. */
166
+ category?: CategoryBreadcrumbEntry[];
167
+ /** Author breadcrumb trail. Default: ['home', 'articles', 'authors', 'authorName']. */
168
+ author?: AuthorBreadcrumbEntry[];
169
+ /** Optional label overrides for built-in breadcrumb items. */
170
+ labels?: BreadcrumbLabels;
171
+ }
109
172
  /** Top-level configuration object. Pass one instance to every library component. */
110
173
  interface ArticlesConfig {
111
174
  /** Canonical base URL of the site, used in metadata and JSON-LD. Example: `'https://yoursite.com'` */
@@ -141,14 +204,26 @@ interface ArticlesConfig {
141
204
  showBackToArticles?: boolean;
142
205
  /** Set to false to hide author names from UI and metadata. Default: true. */
143
206
  showAuthor?: boolean;
207
+ /** Author profiles keyed by slug. Omit to keep plain string author display. */
208
+ authors?: Record<string, AuthorProfile>;
209
+ /** Author slug used when article frontmatter omits author fields. */
210
+ defaultAuthor?: string;
211
+ /** Set to false to disable copied author page routes in consuming apps. Default: true. */
212
+ showAuthorPage?: boolean;
213
+ /** Set to false to disable breadcrumbs, or pass a config object to customize breadcrumb trails. */
214
+ breadcrumbs?: false | BreadcrumbsConfig;
144
215
  /** Article body link target behavior. Default: `'external-new-tab'`. */
145
216
  linkTargetStrategy?: LinkTargetStrategy;
146
217
  }
218
+ declare function getBreadcrumbsConfig(config: ArticlesConfig): BreadcrumbsConfig;
147
219
 
148
220
  declare function sanitizeImagePath(rawPath: string, articleSlug: string): string | null;
149
221
  declare function getAvailableArticleSlugs(): string[];
222
+ declare function getAuthorBySlug(slug: string, config: ArticlesConfig): AuthorProfile | null;
223
+ declare function getArticleAuthors(article: Article, config: ArticlesConfig): AuthorProfile[];
224
+ declare function getAllAuthors(config: ArticlesConfig): AuthorProfile[];
150
225
  declare const getArticleMetadata: (slug: string, config?: ArticlesConfig) => Promise<Article | null>;
151
- declare const getAllArticles: () => Promise<Article[]>;
226
+ declare const getAllArticles: (config?: ArticlesConfig) => Promise<Article[]>;
152
227
  declare function getAdjacentArticles(currentSlug: string): Promise<{
153
228
  previous: Article | null;
154
229
  next: Article | null;
@@ -161,7 +236,8 @@ declare function getAiRobotsTxtRules(): Promise<string>;
161
236
  declare function searchArticles(query: string, config?: ArticlesConfig): Promise<Article[]>;
162
237
  declare function categoryToSlug(category: string): string;
163
238
  declare function getAllCategories(): Promise<CategoryInfo[]>;
164
- declare function getArticlesByCategory(categorySlug: string): Promise<Article[]>;
239
+ declare function getArticlesByCategory(categorySlug: string, config?: ArticlesConfig): Promise<Article[]>;
240
+ declare function getArticlesByAuthor(authorSlug: string, config: ArticlesConfig): Promise<Article[]>;
165
241
 
166
242
  declare function generateRssFeed(articles: Article[], config: ArticlesConfig): string;
167
243
  declare function generateArticleStaticParams(): {
@@ -170,9 +246,17 @@ declare function generateArticleStaticParams(): {
170
246
  declare function generateCategoryStaticParams(): Promise<{
171
247
  category: string;
172
248
  }[]>;
249
+ declare function generateAuthorStaticParams(config: ArticlesConfig): {
250
+ author: string;
251
+ }[];
173
252
  declare function generateArticleMetadata(slug: string, config: ArticlesConfig): Promise<Metadata>;
174
253
  declare function generateArticlesIndexMetadata(config: ArticlesConfig): Metadata;
175
254
  declare function generateCategoryMetadata(categorySlug: string, config: ArticlesConfig): Promise<Metadata>;
255
+ declare function generateAuthorMetadata(authorSlug: string, config: ArticlesConfig): Promise<Metadata>;
256
+ declare function buildArticleBreadcrumbs(article: Pick<Article, 'slug' | 'title' | 'category'>, config: ArticlesConfig): BreadcrumbItem[];
257
+ declare function buildCategoryBreadcrumbs(category: string, config: ArticlesConfig, categoryName?: string): BreadcrumbItem[];
258
+ declare function buildAuthorBreadcrumbs(author: AuthorProfile, config: ArticlesConfig): BreadcrumbItem[];
259
+ declare function resolveAuthorAvatar(author: AuthorProfile, config: ArticlesConfig): string;
176
260
  declare function getArticleSitemapEntries(baseUrlOrConfig: string | ArticlesConfig): Promise<MetadataRoute.Sitemap>;
177
261
 
178
262
  declare function markdownToHtml(markdown: string, articleSlug?: string, config?: ArticlesConfig): Promise<string>;
@@ -202,4 +286,4 @@ type ArticleTOCProps = Readonly<{
202
286
  }>;
203
287
  declare function ArticleTOC({ toc, className }: ArticleTOCProps): react_jsx_runtime.JSX.Element | null;
204
288
 
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 };
289
+ 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
  }
@@ -1042,27 +1283,38 @@ function ArticleTOC({ toc, className }) {
1042
1283
  export {
1043
1284
  ArticleContent,
1044
1285
  ArticleTOC,
1286
+ buildArticleBreadcrumbs,
1287
+ buildAuthorBreadcrumbs,
1288
+ buildCategoryBreadcrumbs,
1045
1289
  categoryToSlug,
1046
1290
  extractToc,
1047
1291
  generateArticleMetadata,
1048
1292
  generateArticleStaticParams,
1049
1293
  generateArticlesIndexMetadata,
1294
+ generateAuthorMetadata,
1295
+ generateAuthorStaticParams,
1050
1296
  generateCategoryMetadata,
1051
1297
  generateCategoryStaticParams,
1052
1298
  generateRssFeed,
1053
1299
  getAdjacentArticles,
1054
1300
  getAiRobotsTxtRules,
1055
1301
  getAllArticles,
1302
+ getAllAuthors,
1056
1303
  getAllCategories,
1057
1304
  getArticleAiHeaders,
1305
+ getArticleAuthors,
1058
1306
  getArticleMarkdown,
1059
1307
  getArticleMarkdownResponse,
1060
1308
  getArticleMarkdownUrl,
1061
1309
  getArticleMetadata,
1062
1310
  getArticleSitemapEntries,
1311
+ getArticlesByAuthor,
1063
1312
  getArticlesByCategory,
1313
+ getAuthorBySlug,
1064
1314
  getAvailableArticleSlugs,
1315
+ getBreadcrumbsConfig,
1065
1316
  markdownToHtml,
1317
+ resolveAuthorAvatar,
1066
1318
  sanitizeImagePath2 as sanitizeImagePath,
1067
1319
  searchArticles,
1068
1320
  setArticlesErrorHandler