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