@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.
- package/CHANGELOG.md +243 -0
- package/README.md +226 -29
- package/dist/index.cjs +635 -274
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -56
- package/dist/index.d.ts +165 -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 +72 -0
- package/dist/nextjs.d.ts +72 -0
- package/dist/nextjs.js +40 -5
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +280 -16
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +92 -3
- package/dist/server.d.ts +92 -3
- package/dist/server.js +269 -16
- 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__/articlesConfig.test.ts +20 -1
- package/src/__tests__/authorUtils.test.ts +89 -0
- package/src/__tests__/renderMdx.test.tsx +113 -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 +68 -0
- package/src/authorUtils.ts +95 -0
- package/src/index.ts +32 -9
- package/src/renderMdx.tsx +8 -2
- 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
|
@@ -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
|
|
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
|
}
|
|
@@ -999,7 +1240,8 @@ function renderMdxSource(source, basePath, config) {
|
|
|
999
1240
|
]
|
|
1000
1241
|
}));
|
|
1001
1242
|
const Content = mdxModule.default;
|
|
1002
|
-
const
|
|
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
|