@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.
- package/CHANGELOG.md +237 -0
- package/README.md +199 -29
- package/dist/index.cjs +635 -274
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +160 -56
- package/dist/index.d.ts +160 -56
- package/dist/index.js +614 -250
- package/dist/index.js.map +1 -1
- package/dist/nextjs.cjs +40 -5
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.d.cts +67 -0
- package/dist/nextjs.d.ts +67 -0
- package/dist/nextjs.js +40 -5
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +278 -15
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +87 -3
- package/dist/server.d.ts +87 -3
- package/dist/server.js +267 -15
- package/dist/server.js.map +1 -1
- package/package.json +8 -5
- package/src/ArticleDetailHero.tsx +27 -2
- package/src/ArticleSchemas.tsx +27 -27
- package/src/AuthorArticlesPage.tsx +60 -0
- package/src/AuthorCard.tsx +112 -0
- package/src/AuthorDetailHero.tsx +56 -0
- package/src/Breadcrumb.tsx +78 -0
- package/src/CategoryArticlesPage.tsx +62 -11
- package/src/__tests__/ArticleDetailHero.test.tsx +21 -1
- package/src/__tests__/ArticleSchemas.test.tsx +47 -2
- package/src/__tests__/AuthorArticlesPage.test.tsx +74 -0
- package/src/__tests__/AuthorCard.test.tsx +98 -0
- package/src/__tests__/AuthorDetailHero.test.tsx +51 -0
- package/src/__tests__/CategoryArticlesPage.test.tsx +31 -5
- package/src/__tests__/authorUtils.test.ts +89 -0
- package/src/__tests__/renderMdx.test.tsx +35 -0
- package/src/__tests__/seoUtils-authors.test.ts +160 -0
- package/src/__tests__/seoUtils.test.ts +4 -0
- package/src/__tests__/server-articles.test.ts +159 -2
- package/src/articleTypes.ts +33 -0
- package/src/articlesConfig.ts +62 -0
- package/src/authorUtils.ts +95 -0
- package/src/index.ts +31 -9
- package/src/renderMdx.tsx +3 -1
- package/src/seoUtils.ts +226 -7
- package/src/server-articles.ts +98 -10
- 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
|
|
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
|
|
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:
|
|
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":
|
|
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([
|
|
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
|
-
|
|
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
|