@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.cjs
CHANGED
|
@@ -81,26 +81,38 @@ var server_exports = {};
|
|
|
81
81
|
__export(server_exports, {
|
|
82
82
|
ArticleContent: () => ArticleContent,
|
|
83
83
|
ArticleTOC: () => ArticleTOC,
|
|
84
|
+
buildArticleBreadcrumbs: () => buildArticleBreadcrumbs,
|
|
85
|
+
buildAuthorBreadcrumbs: () => buildAuthorBreadcrumbs,
|
|
86
|
+
buildCategoryBreadcrumbs: () => buildCategoryBreadcrumbs,
|
|
84
87
|
categoryToSlug: () => categoryToSlug,
|
|
85
88
|
extractToc: () => extractToc,
|
|
86
89
|
generateArticleMetadata: () => generateArticleMetadata,
|
|
87
90
|
generateArticleStaticParams: () => generateArticleStaticParams,
|
|
88
91
|
generateArticlesIndexMetadata: () => generateArticlesIndexMetadata,
|
|
92
|
+
generateAuthorMetadata: () => generateAuthorMetadata,
|
|
93
|
+
generateAuthorStaticParams: () => generateAuthorStaticParams,
|
|
89
94
|
generateCategoryMetadata: () => generateCategoryMetadata,
|
|
90
95
|
generateCategoryStaticParams: () => generateCategoryStaticParams,
|
|
96
|
+
generateRssFeed: () => generateRssFeed,
|
|
91
97
|
getAdjacentArticles: () => getAdjacentArticles,
|
|
92
98
|
getAiRobotsTxtRules: () => getAiRobotsTxtRules,
|
|
93
99
|
getAllArticles: () => getAllArticles,
|
|
100
|
+
getAllAuthors: () => getAllAuthors,
|
|
94
101
|
getAllCategories: () => getAllCategories,
|
|
95
102
|
getArticleAiHeaders: () => getArticleAiHeaders,
|
|
103
|
+
getArticleAuthors: () => getArticleAuthors,
|
|
96
104
|
getArticleMarkdown: () => getArticleMarkdown,
|
|
97
105
|
getArticleMarkdownResponse: () => getArticleMarkdownResponse,
|
|
98
106
|
getArticleMarkdownUrl: () => getArticleMarkdownUrl,
|
|
99
107
|
getArticleMetadata: () => getArticleMetadata,
|
|
100
108
|
getArticleSitemapEntries: () => getArticleSitemapEntries,
|
|
109
|
+
getArticlesByAuthor: () => getArticlesByAuthor,
|
|
101
110
|
getArticlesByCategory: () => getArticlesByCategory,
|
|
111
|
+
getAuthorBySlug: () => getAuthorBySlug,
|
|
102
112
|
getAvailableArticleSlugs: () => getAvailableArticleSlugs,
|
|
113
|
+
getBreadcrumbsConfig: () => getBreadcrumbsConfig,
|
|
103
114
|
markdownToHtml: () => markdownToHtml,
|
|
115
|
+
resolveAuthorAvatar: () => resolveAuthorAvatar,
|
|
104
116
|
sanitizeImagePath: () => sanitizeImagePath2,
|
|
105
117
|
searchArticles: () => searchArticles,
|
|
106
118
|
setArticlesErrorHandler: () => setArticlesErrorHandler
|
|
@@ -142,6 +154,48 @@ function reportArticlesError(report) {
|
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
// src/markdown.ts
|
|
157
|
+
var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
|
|
158
|
+
function isNonBrowserNavigationLink(href) {
|
|
159
|
+
return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href) && !href.startsWith("http://") && !href.startsWith("https://");
|
|
160
|
+
}
|
|
161
|
+
function getOrigin(url) {
|
|
162
|
+
if (!url) return null;
|
|
163
|
+
try {
|
|
164
|
+
return new URL(url).origin;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function isExternalHttpLink(href, siteUrl) {
|
|
170
|
+
if (!href.startsWith("http://") && !href.startsWith("https://")) return false;
|
|
171
|
+
const siteOrigin = getOrigin(siteUrl);
|
|
172
|
+
if (!siteOrigin) return true;
|
|
173
|
+
return getOrigin(href) !== siteOrigin;
|
|
174
|
+
}
|
|
175
|
+
function shouldOpenInNewTab(href, options = {}) {
|
|
176
|
+
var _a;
|
|
177
|
+
if (!href || href.startsWith("#") || isNonBrowserNavigationLink(href)) return false;
|
|
178
|
+
const strategy = (_a = options.strategy) != null ? _a : DEFAULT_LINK_TARGET_STRATEGY;
|
|
179
|
+
if (strategy === "same-tab") return false;
|
|
180
|
+
if (strategy === "all-new-tab") return true;
|
|
181
|
+
return isExternalHttpLink(href, options.siteUrl);
|
|
182
|
+
}
|
|
183
|
+
function applyLinkTarget(props, options) {
|
|
184
|
+
const href = typeof props.href === "string" ? props.href : "";
|
|
185
|
+
if (shouldOpenInNewTab(href, options)) {
|
|
186
|
+
props.target = "_blank";
|
|
187
|
+
props.rel = "noopener noreferrer";
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
delete props.target;
|
|
191
|
+
delete props.rel;
|
|
192
|
+
}
|
|
193
|
+
function getLinkTargetOptions(config) {
|
|
194
|
+
return {
|
|
195
|
+
strategy: config == null ? void 0 : config.linkTargetStrategy,
|
|
196
|
+
siteUrl: config == null ? void 0 : config.siteUrl
|
|
197
|
+
};
|
|
198
|
+
}
|
|
145
199
|
function sanitizeImagePath(rawPath, articleSlug) {
|
|
146
200
|
if (!rawPath || typeof rawPath !== "string") {
|
|
147
201
|
return null;
|
|
@@ -263,7 +317,7 @@ function processFootnotesSection(node) {
|
|
|
263
317
|
node.children = [hr, h3, ol];
|
|
264
318
|
if (node.properties) node.properties.className = void 0;
|
|
265
319
|
}
|
|
266
|
-
var customRenderer = () => {
|
|
320
|
+
var customRenderer = (linkTargetOptions = {}) => {
|
|
267
321
|
return (tree) => {
|
|
268
322
|
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
269
323
|
if (node.tagName) {
|
|
@@ -286,11 +340,7 @@ var customRenderer = () => {
|
|
|
286
340
|
break;
|
|
287
341
|
case "a": {
|
|
288
342
|
props.className = "text-primary hover:underline transition-colors duration-200";
|
|
289
|
-
|
|
290
|
-
if (!href.startsWith("#")) {
|
|
291
|
-
props.target = "_blank";
|
|
292
|
-
props.rel = "noopener noreferrer";
|
|
293
|
-
}
|
|
343
|
+
applyLinkTarget(props, linkTargetOptions);
|
|
294
344
|
break;
|
|
295
345
|
}
|
|
296
346
|
case "ul":
|
|
@@ -360,10 +410,10 @@ var rehypeProcessImages = (options = {}) => {
|
|
|
360
410
|
});
|
|
361
411
|
};
|
|
362
412
|
};
|
|
363
|
-
function markdownToHtml(markdown, articleSlug) {
|
|
413
|
+
function markdownToHtml(markdown, articleSlug, config) {
|
|
364
414
|
return __async(this, null, function* () {
|
|
365
415
|
try {
|
|
366
|
-
let processor = (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_github_blockquote_alert.default).use(import_remark_rehype.default).use(customRenderer).use(import_rehype_slug.default).use(import_rehype_prism_plus.default).use(import_rehype_sanitize.default, {
|
|
416
|
+
let processor = (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_github_blockquote_alert.default).use(import_remark_rehype.default).use(customRenderer, getLinkTargetOptions(config)).use(import_rehype_slug.default).use(import_rehype_prism_plus.default).use(import_rehype_sanitize.default, {
|
|
367
417
|
attributes: {
|
|
368
418
|
"*": ["className", "class", "id"],
|
|
369
419
|
a: ["href", "target", "rel", "id"],
|
|
@@ -539,7 +589,67 @@ function parseHowToSteps(raw) {
|
|
|
539
589
|
);
|
|
540
590
|
return steps.length ? steps : void 0;
|
|
541
591
|
}
|
|
542
|
-
function
|
|
592
|
+
function parseAuthors(raw) {
|
|
593
|
+
if (!Array.isArray(raw)) return void 0;
|
|
594
|
+
const authors = raw.filter(
|
|
595
|
+
(author) => typeof author === "string" && author.trim().length > 0
|
|
596
|
+
);
|
|
597
|
+
return authors;
|
|
598
|
+
}
|
|
599
|
+
function getAuthorBySlug(slug, config) {
|
|
600
|
+
var _a, _b;
|
|
601
|
+
const profile = (_a = config.authors) == null ? void 0 : _a[slug];
|
|
602
|
+
if (!profile) return null;
|
|
603
|
+
return __spreadProps(__spreadValues({}, profile), {
|
|
604
|
+
url: (_b = profile.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${profile.slug}`
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
function getConfiguredAuthorByName(name, config) {
|
|
608
|
+
var _a;
|
|
609
|
+
const normalizedName = name.trim().toLowerCase();
|
|
610
|
+
const profile = Object.values((_a = config.authors) != null ? _a : {}).find(
|
|
611
|
+
(author) => author.name.toLowerCase() === normalizedName
|
|
612
|
+
);
|
|
613
|
+
return profile ? getAuthorBySlug(profile.slug, config) : null;
|
|
614
|
+
}
|
|
615
|
+
function resolveArticleAuthorName(rawAuthor, rawAuthors, config) {
|
|
616
|
+
var _a, _b, _c, _d;
|
|
617
|
+
const authorArray = parseAuthors(rawAuthors);
|
|
618
|
+
const firstAuthor = authorArray == null ? void 0 : authorArray[0];
|
|
619
|
+
const rawAuthorValue = typeof rawAuthor === "string" && rawAuthor.trim() ? rawAuthor : void 0;
|
|
620
|
+
const author = firstAuthor != null ? firstAuthor : rawAuthorValue;
|
|
621
|
+
const resolved = author != null ? author : config == null ? void 0 : config.defaultAuthor;
|
|
622
|
+
if (!resolved) return "";
|
|
623
|
+
if (!config) return resolved;
|
|
624
|
+
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;
|
|
625
|
+
}
|
|
626
|
+
function getArticleAuthors(article, config) {
|
|
627
|
+
var _a;
|
|
628
|
+
const fallbackAuthors = Array.from(
|
|
629
|
+
new Set(
|
|
630
|
+
[article.author, config.defaultAuthor].filter(
|
|
631
|
+
(author) => typeof author === "string" && author.trim().length > 0
|
|
632
|
+
)
|
|
633
|
+
)
|
|
634
|
+
);
|
|
635
|
+
const authorValues = (_a = article.authors) != null ? _a : fallbackAuthors;
|
|
636
|
+
if (authorValues.length === 0) return [];
|
|
637
|
+
const resolvedAuthors = authorValues.map((author) => {
|
|
638
|
+
var _a2;
|
|
639
|
+
return (_a2 = getAuthorBySlug(author, config)) != null ? _a2 : getConfiguredAuthorByName(author, config);
|
|
640
|
+
}).filter((author) => author !== null).filter((author, index, all) => all.findIndex((a) => a.slug === author.slug) === index);
|
|
641
|
+
if (resolvedAuthors.length > 0) return resolvedAuthors;
|
|
642
|
+
return authorValues.map((fallbackName) => ({
|
|
643
|
+
name: fallbackName,
|
|
644
|
+
slug: categoryToSlug(fallbackName),
|
|
645
|
+
bio: ""
|
|
646
|
+
}));
|
|
647
|
+
}
|
|
648
|
+
function getAllAuthors(config) {
|
|
649
|
+
var _a;
|
|
650
|
+
return Object.keys((_a = config.authors) != null ? _a : {}).map((slug) => getAuthorBySlug(slug, config)).filter((author) => author !== null);
|
|
651
|
+
}
|
|
652
|
+
function getArticleSummary(slug, config) {
|
|
543
653
|
return __async(this, null, function* () {
|
|
544
654
|
try {
|
|
545
655
|
const found = findArticleFile(slug);
|
|
@@ -555,7 +665,8 @@ function getArticleSummary(slug) {
|
|
|
555
665
|
excerpt: data.excerpt || "",
|
|
556
666
|
date: parseDateField(data.date),
|
|
557
667
|
lastmod: parseDateField(data.lastmod),
|
|
558
|
-
author: data.author
|
|
668
|
+
author: resolveArticleAuthorName(data.author, data.authors, config),
|
|
669
|
+
authors: parseAuthors(data.authors),
|
|
559
670
|
category: categories[0],
|
|
560
671
|
categories,
|
|
561
672
|
readTime,
|
|
@@ -581,36 +692,38 @@ function getArticleSummary(slug) {
|
|
|
581
692
|
}
|
|
582
693
|
});
|
|
583
694
|
}
|
|
584
|
-
var getArticleMetadata = (0, import_react.cache)(
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
695
|
+
var getArticleMetadata = (0, import_react.cache)(
|
|
696
|
+
(slug, config) => __async(void 0, null, function* () {
|
|
697
|
+
try {
|
|
698
|
+
const summary = yield getArticleSummary(slug, config);
|
|
699
|
+
if (!summary) return null;
|
|
700
|
+
const found = findArticleFile(slug);
|
|
701
|
+
if (!found) return null;
|
|
702
|
+
const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
|
|
703
|
+
const { content: markdownContent } = (0, import_gray_matter.default)(fileContent);
|
|
704
|
+
const toc = yield extractToc(markdownContent);
|
|
705
|
+
let htmlContent;
|
|
706
|
+
let mdxSource;
|
|
707
|
+
if (found.contentType === "mdx") {
|
|
708
|
+
mdxSource = markdownContent;
|
|
709
|
+
} else {
|
|
710
|
+
htmlContent = yield markdownToHtml(markdownContent, slug, config);
|
|
711
|
+
}
|
|
712
|
+
return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
|
|
713
|
+
} catch (error) {
|
|
714
|
+
reportArticlesError({
|
|
715
|
+
code: "article-load-failed",
|
|
716
|
+
message: "Unable to load article metadata.",
|
|
717
|
+
error,
|
|
718
|
+
context: { slug }
|
|
719
|
+
});
|
|
720
|
+
return null;
|
|
599
721
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
code: "article-load-failed",
|
|
604
|
-
message: "Unable to load article metadata.",
|
|
605
|
-
error,
|
|
606
|
-
context: { slug }
|
|
607
|
-
});
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
}));
|
|
611
|
-
var getAllArticles = (0, import_react.cache)(() => __async(void 0, null, function* () {
|
|
722
|
+
})
|
|
723
|
+
);
|
|
724
|
+
var getAllArticles = (0, import_react.cache)((config) => __async(void 0, null, function* () {
|
|
612
725
|
const slugs = getAvailableArticleSlugs();
|
|
613
|
-
const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
|
|
726
|
+
const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug, config)));
|
|
614
727
|
const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
615
728
|
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) => {
|
|
616
729
|
if (!a.date && !b.date) return 0;
|
|
@@ -700,8 +813,8 @@ function getAiRobotsTxtRules() {
|
|
|
700
813
|
}
|
|
701
814
|
function searchArticles(query, config) {
|
|
702
815
|
return __async(this, null, function* () {
|
|
703
|
-
if (!(query == null ? void 0 : query.trim())) return getAllArticles();
|
|
704
|
-
const articles = yield getAllArticles();
|
|
816
|
+
if (!(query == null ? void 0 : query.trim())) return getAllArticles(config);
|
|
817
|
+
const articles = yield getAllArticles(config);
|
|
705
818
|
const searchTerm = query.toLowerCase().trim();
|
|
706
819
|
const includeAuthor = (config == null ? void 0 : config.showAuthor) !== false;
|
|
707
820
|
return articles.filter((article) => {
|
|
@@ -738,16 +851,72 @@ function getAllCategories() {
|
|
|
738
851
|
})).sort((a, b) => b.count - a.count);
|
|
739
852
|
});
|
|
740
853
|
}
|
|
741
|
-
function getArticlesByCategory(categorySlug) {
|
|
854
|
+
function getArticlesByCategory(categorySlug, config) {
|
|
742
855
|
return __async(this, null, function* () {
|
|
743
|
-
const articles = yield getAllArticles();
|
|
856
|
+
const articles = yield getAllArticles(config);
|
|
744
857
|
return articles.filter(
|
|
745
858
|
(article) => article.categories.some((cat) => categoryToSlug(cat) === categorySlug)
|
|
746
859
|
);
|
|
747
860
|
});
|
|
748
861
|
}
|
|
862
|
+
function getArticlesByAuthor(authorSlug, config) {
|
|
863
|
+
return __async(this, null, function* () {
|
|
864
|
+
const articles = yield getAllArticles(config);
|
|
865
|
+
return articles.filter(
|
|
866
|
+
(article) => getArticleAuthors(article, config).some((author) => author.slug === authorSlug)
|
|
867
|
+
);
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// src/articlesConfig.ts
|
|
872
|
+
function breadcrumbsAreEnabled(config) {
|
|
873
|
+
var _a;
|
|
874
|
+
return config.breadcrumbs !== false && ((_a = config.breadcrumbs) == null ? void 0 : _a.show) !== false;
|
|
875
|
+
}
|
|
876
|
+
function getBreadcrumbsConfig(config) {
|
|
877
|
+
var _a;
|
|
878
|
+
if (config.breadcrumbs === false) return {};
|
|
879
|
+
return (_a = config.breadcrumbs) != null ? _a : {};
|
|
880
|
+
}
|
|
749
881
|
|
|
750
882
|
// src/seoUtils.ts
|
|
883
|
+
function escapeXml(str) {
|
|
884
|
+
return str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
885
|
+
}
|
|
886
|
+
function generateRssFeed(articles, config) {
|
|
887
|
+
var _a;
|
|
888
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
889
|
+
const showAuthor = config.showAuthor !== false;
|
|
890
|
+
const items = articles.map((article) => {
|
|
891
|
+
const url = `${siteUrl}/articles/${article.slug}`;
|
|
892
|
+
const pubDate = article.date ? new Date(article.date).toUTCString() : "";
|
|
893
|
+
const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : "";
|
|
894
|
+
return [
|
|
895
|
+
" <item>",
|
|
896
|
+
` <title><![CDATA[${article.title}]]></title>`,
|
|
897
|
+
` <link>${url}</link>`,
|
|
898
|
+
` <guid isPermaLink="true">${url}</guid>`,
|
|
899
|
+
pubDate ? ` <pubDate>${pubDate}</pubDate>` : "",
|
|
900
|
+
article.excerpt ? ` <description><![CDATA[${article.excerpt}]]></description>` : "",
|
|
901
|
+
showAuthor && article.author ? ` <author>${escapeXml(article.author)}</author>` : "",
|
|
902
|
+
article.category ? ` <category><![CDATA[${article.category}]]></category>` : "",
|
|
903
|
+
imageUrl ? ` <media:content url="${imageUrl}" medium="image" width="1200" height="630"/>` : "",
|
|
904
|
+
" </item>"
|
|
905
|
+
].filter(Boolean).join("\n");
|
|
906
|
+
}).join("\n");
|
|
907
|
+
const description = (_a = config.description) != null ? _a : `${config.siteName} articles`;
|
|
908
|
+
return `<?xml version="1.0" encoding="UTF-8" ?>
|
|
909
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
|
|
910
|
+
<channel>
|
|
911
|
+
<title><![CDATA[${config.siteName}]]></title>
|
|
912
|
+
<link>${siteUrl}/articles</link>
|
|
913
|
+
<description><![CDATA[${description}]]></description>
|
|
914
|
+
<language>en</language>
|
|
915
|
+
<atom:link href="${siteUrl}/articles/feed.xml" rel="self" type="application/rss+xml" />
|
|
916
|
+
${items}
|
|
917
|
+
</channel>
|
|
918
|
+
</rss>`;
|
|
919
|
+
}
|
|
751
920
|
function generateArticleStaticParams() {
|
|
752
921
|
return getAvailableArticleSlugs().map((slug) => ({ slug }));
|
|
753
922
|
}
|
|
@@ -757,6 +926,10 @@ function generateCategoryStaticParams() {
|
|
|
757
926
|
return categories.map((cat) => ({ category: cat.slug }));
|
|
758
927
|
});
|
|
759
928
|
}
|
|
929
|
+
function generateAuthorStaticParams(config) {
|
|
930
|
+
if (config.showAuthorPage === false) return [];
|
|
931
|
+
return getAllAuthors(config).map((author) => ({ author: author.slug }));
|
|
932
|
+
}
|
|
760
933
|
function resolveImageUrl(featuredImage, siteUrl) {
|
|
761
934
|
const base = siteUrl.replace(/\/$/, "");
|
|
762
935
|
if (featuredImage.startsWith("http://") || featuredImage.startsWith("https://")) {
|
|
@@ -767,7 +940,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
|
|
|
767
940
|
function generateArticleMetadata(slug, config) {
|
|
768
941
|
return __async(this, null, function* () {
|
|
769
942
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
770
|
-
const article = yield getArticleMetadata(slug);
|
|
943
|
+
const article = yield getArticleMetadata(slug, config);
|
|
771
944
|
if (!article) {
|
|
772
945
|
return {
|
|
773
946
|
title: "Article Not Found",
|
|
@@ -781,6 +954,7 @@ function generateArticleMetadata(slug, config) {
|
|
|
781
954
|
const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
|
|
782
955
|
const showAuthor = config.showAuthor !== false;
|
|
783
956
|
const markdownUrl = getArticleMarkdownUrl(article, config);
|
|
957
|
+
const authorNames = getArticleAuthors(article, config).map((author) => author.name);
|
|
784
958
|
return {
|
|
785
959
|
title: `${article.title} | ${config.siteName}`,
|
|
786
960
|
description,
|
|
@@ -793,7 +967,7 @@ function generateArticleMetadata(slug, config) {
|
|
|
793
967
|
images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
|
|
794
968
|
locale: "en_US",
|
|
795
969
|
type: "article"
|
|
796
|
-
}, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), showAuthor && { authors:
|
|
970
|
+
}, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), showAuthor && authorNames.length > 0 && { authors: authorNames }), {
|
|
797
971
|
tags: (_d = article.tags) != null ? _d : []
|
|
798
972
|
}),
|
|
799
973
|
twitter: {
|
|
@@ -820,7 +994,7 @@ function generateArticleMetadata(slug, config) {
|
|
|
820
994
|
"max-snippet": -1
|
|
821
995
|
}
|
|
822
996
|
},
|
|
823
|
-
other: __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, showAuthor && { "article:author":
|
|
997
|
+
other: __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, showAuthor && authorNames.length > 0 && { "article:author": authorNames.join(", ") }), article.date && {
|
|
824
998
|
"article:published_time": new Date(article.date).toISOString()
|
|
825
999
|
}), article.lastmod && {
|
|
826
1000
|
"article:modified_time": new Date(article.lastmod).toISOString()
|
|
@@ -919,11 +1093,162 @@ function generateCategoryMetadata(categorySlug, config) {
|
|
|
919
1093
|
};
|
|
920
1094
|
});
|
|
921
1095
|
}
|
|
1096
|
+
function generateAuthorMetadata(authorSlug, config) {
|
|
1097
|
+
return __async(this, null, function* () {
|
|
1098
|
+
var _a;
|
|
1099
|
+
const author = getAuthorBySlug(authorSlug, config);
|
|
1100
|
+
if (!author || config.showAuthorPage === false) return { title: "Author Not Found" };
|
|
1101
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1102
|
+
const authorUrl = (_a = author.url) != null ? _a : `${siteUrl}/articles/authors/${author.slug}`;
|
|
1103
|
+
const title = `${author.name} Articles | ${config.siteName}`;
|
|
1104
|
+
return {
|
|
1105
|
+
title,
|
|
1106
|
+
description: author.bio,
|
|
1107
|
+
openGraph: __spreadValues({
|
|
1108
|
+
title,
|
|
1109
|
+
description: author.bio,
|
|
1110
|
+
url: authorUrl,
|
|
1111
|
+
siteName: config.siteName,
|
|
1112
|
+
type: "profile",
|
|
1113
|
+
locale: "en_US"
|
|
1114
|
+
}, author.avatar && { images: [{ url: resolveAuthorAvatar(author, config) }] }),
|
|
1115
|
+
twitter: __spreadValues({
|
|
1116
|
+
card: "summary_large_image",
|
|
1117
|
+
title,
|
|
1118
|
+
description: author.bio
|
|
1119
|
+
}, author.avatar && { images: [resolveAuthorAvatar(author, config)] }),
|
|
1120
|
+
alternates: {
|
|
1121
|
+
canonical: authorUrl
|
|
1122
|
+
},
|
|
1123
|
+
robots: {
|
|
1124
|
+
index: true,
|
|
1125
|
+
follow: true
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
function formatCategoryName(category) {
|
|
1131
|
+
return category.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
1132
|
+
}
|
|
1133
|
+
function buildArticleBreadcrumbs(article, config) {
|
|
1134
|
+
var _a, _b;
|
|
1135
|
+
if (!breadcrumbsAreEnabled(config)) return [];
|
|
1136
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1137
|
+
const breadcrumbConfig = getBreadcrumbsConfig(config);
|
|
1138
|
+
const labels = (_a = breadcrumbConfig.labels) != null ? _a : {};
|
|
1139
|
+
const categorySlug = categoryToSlug(article.category);
|
|
1140
|
+
const trail = (_b = breadcrumbConfig.article) != null ? _b : ["home", "articles", "primaryCategory", "articleTitle"];
|
|
1141
|
+
const folderSegments = article.slug.split("/").filter(Boolean).slice(0, -1);
|
|
1142
|
+
return trail.flatMap(
|
|
1143
|
+
(token) => buildArticleBreadcrumbToken(token, {
|
|
1144
|
+
article,
|
|
1145
|
+
siteUrl,
|
|
1146
|
+
categorySlug,
|
|
1147
|
+
folderSegments,
|
|
1148
|
+
labels
|
|
1149
|
+
})
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
function buildCategoryBreadcrumbs(category, config, categoryName = formatCategoryName(category)) {
|
|
1153
|
+
var _a, _b;
|
|
1154
|
+
if (!breadcrumbsAreEnabled(config)) return [];
|
|
1155
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1156
|
+
const breadcrumbConfig = getBreadcrumbsConfig(config);
|
|
1157
|
+
const labels = (_a = breadcrumbConfig.labels) != null ? _a : {};
|
|
1158
|
+
const trail = (_b = breadcrumbConfig.category) != null ? _b : ["home", "articles", "category"];
|
|
1159
|
+
return trail.flatMap(
|
|
1160
|
+
(entry) => buildCategoryBreadcrumbEntry(entry, { categoryName, siteUrl, labels })
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
function buildAuthorBreadcrumbs(author, config) {
|
|
1164
|
+
var _a, _b;
|
|
1165
|
+
if (!breadcrumbsAreEnabled(config)) return [];
|
|
1166
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1167
|
+
const breadcrumbConfig = getBreadcrumbsConfig(config);
|
|
1168
|
+
const labels = (_a = breadcrumbConfig.labels) != null ? _a : {};
|
|
1169
|
+
const trail = (_b = breadcrumbConfig.author) != null ? _b : ["home", "articles", "authors", "authorName"];
|
|
1170
|
+
return trail.flatMap(
|
|
1171
|
+
(entry) => buildAuthorBreadcrumbEntry(entry, { author, siteUrl, labels })
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
function isCustomBreadcrumbItem(entry) {
|
|
1175
|
+
return typeof entry === "object" && entry !== null && "name" in entry && "url" in entry;
|
|
1176
|
+
}
|
|
1177
|
+
function resolveCustomBreadcrumbItem(item, siteUrl) {
|
|
1178
|
+
if (item.url.startsWith("/")) return { name: item.name, url: `${siteUrl}${item.url}` };
|
|
1179
|
+
return { name: item.name, url: item.url };
|
|
1180
|
+
}
|
|
1181
|
+
function buildArticleBreadcrumbToken(entry, context) {
|
|
1182
|
+
var _a, _b;
|
|
1183
|
+
if (isCustomBreadcrumbItem(entry)) {
|
|
1184
|
+
return [resolveCustomBreadcrumbItem(entry, context.siteUrl)];
|
|
1185
|
+
}
|
|
1186
|
+
if (entry === "home") return [{ name: (_a = context.labels.home) != null ? _a : "Home", url: context.siteUrl }];
|
|
1187
|
+
if (entry === "articles") {
|
|
1188
|
+
return [{ name: (_b = context.labels.articles) != null ? _b : "Articles", url: `${context.siteUrl}/articles` }];
|
|
1189
|
+
}
|
|
1190
|
+
if (entry === "primaryCategory") {
|
|
1191
|
+
return [
|
|
1192
|
+
{
|
|
1193
|
+
name: context.article.category,
|
|
1194
|
+
url: `${context.siteUrl}/articles/category/${context.categorySlug}`
|
|
1195
|
+
}
|
|
1196
|
+
];
|
|
1197
|
+
}
|
|
1198
|
+
if (entry === "folderPath") {
|
|
1199
|
+
return context.folderSegments.map((segment, index) => ({
|
|
1200
|
+
name: formatCategoryName(segment),
|
|
1201
|
+
url: `${context.siteUrl}/articles/${context.folderSegments.slice(0, index + 1).join("/")}`
|
|
1202
|
+
}));
|
|
1203
|
+
}
|
|
1204
|
+
return [{ name: context.article.title }];
|
|
1205
|
+
}
|
|
1206
|
+
function buildCategoryBreadcrumbEntry(entry, context) {
|
|
1207
|
+
var _a, _b;
|
|
1208
|
+
if (isCustomBreadcrumbItem(entry)) {
|
|
1209
|
+
return [resolveCustomBreadcrumbItem(entry, context.siteUrl)];
|
|
1210
|
+
}
|
|
1211
|
+
if (entry === "home") return [{ name: (_a = context.labels.home) != null ? _a : "Home", url: context.siteUrl }];
|
|
1212
|
+
if (entry === "articles") {
|
|
1213
|
+
return [{ name: (_b = context.labels.articles) != null ? _b : "Articles", url: `${context.siteUrl}/articles` }];
|
|
1214
|
+
}
|
|
1215
|
+
return [{ name: context.categoryName }];
|
|
1216
|
+
}
|
|
1217
|
+
function buildAuthorBreadcrumbEntry(entry, context) {
|
|
1218
|
+
var _a, _b, _c;
|
|
1219
|
+
if (isCustomBreadcrumbItem(entry)) {
|
|
1220
|
+
return [resolveCustomBreadcrumbItem(entry, context.siteUrl)];
|
|
1221
|
+
}
|
|
1222
|
+
if (entry === "home") return [{ name: (_a = context.labels.home) != null ? _a : "Home", url: context.siteUrl }];
|
|
1223
|
+
if (entry === "articles") {
|
|
1224
|
+
return [{ name: (_b = context.labels.articles) != null ? _b : "Articles", url: `${context.siteUrl}/articles` }];
|
|
1225
|
+
}
|
|
1226
|
+
if (entry === "authors") {
|
|
1227
|
+
return [
|
|
1228
|
+
{
|
|
1229
|
+
name: (_c = context.labels.authors) != null ? _c : "Authors",
|
|
1230
|
+
url: `${context.siteUrl}/articles/authors`
|
|
1231
|
+
}
|
|
1232
|
+
];
|
|
1233
|
+
}
|
|
1234
|
+
return [{ name: context.author.name }];
|
|
1235
|
+
}
|
|
1236
|
+
function resolveAuthorAvatar(author, config) {
|
|
1237
|
+
if (!author.avatar) return "";
|
|
1238
|
+
if (author.avatar.startsWith("http://") || author.avatar.startsWith("https://")) {
|
|
1239
|
+
return author.avatar;
|
|
1240
|
+
}
|
|
1241
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1242
|
+
return `${siteUrl}/articles/authors/${author.slug}/${author.avatar.replace(/^\/+/, "")}`;
|
|
1243
|
+
}
|
|
922
1244
|
function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
923
1245
|
return __async(this, null, function* () {
|
|
924
1246
|
const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
|
|
925
1247
|
try {
|
|
926
|
-
const [articles, categories] = yield Promise.all([
|
|
1248
|
+
const [articles, categories] = yield Promise.all([
|
|
1249
|
+
getAllArticles(typeof baseUrlOrConfig === "string" ? void 0 : baseUrlOrConfig),
|
|
1250
|
+
getAllCategories()
|
|
1251
|
+
]);
|
|
927
1252
|
const articleEntries = articles.map((article) => {
|
|
928
1253
|
var _a;
|
|
929
1254
|
const dateStr = (_a = article.lastmod) != null ? _a : article.date;
|
|
@@ -941,7 +1266,12 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
|
941
1266
|
changeFrequency: "weekly",
|
|
942
1267
|
priority: 0.7
|
|
943
1268
|
}));
|
|
944
|
-
|
|
1269
|
+
const authorEntries = typeof baseUrlOrConfig === "string" || baseUrlOrConfig.showAuthorPage === false ? [] : getAllAuthors(baseUrlOrConfig).map((author) => ({
|
|
1270
|
+
url: `${baseUrl}/articles/authors/${author.slug}`,
|
|
1271
|
+
changeFrequency: "monthly",
|
|
1272
|
+
priority: 0.6
|
|
1273
|
+
}));
|
|
1274
|
+
return [...articleEntries, ...categoryEntries, ...authorEntries];
|
|
945
1275
|
} catch (e) {
|
|
946
1276
|
return [];
|
|
947
1277
|
}
|
|
@@ -961,18 +1291,18 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
961
1291
|
function makeImgComponent(basePath) {
|
|
962
1292
|
return function MdxImage(_a) {
|
|
963
1293
|
var _b = _a, { src, alt } = _b, props = __objRest(_b, ["src", "alt"]);
|
|
964
|
-
const resolvedSrc = src && !src.startsWith("http") && !src.startsWith("/") ? `${basePath}/${src}` : src;
|
|
1294
|
+
const resolvedSrc = typeof src === "string" && !src.startsWith("http") && !src.startsWith("/") ? `${basePath}/${src}` : src;
|
|
965
1295
|
return import_react2.default.createElement("img", __spreadValues({ src: resolvedSrc, alt }, props));
|
|
966
1296
|
};
|
|
967
1297
|
}
|
|
968
|
-
function renderMdxSource(source, basePath) {
|
|
1298
|
+
function renderMdxSource(source, basePath, config) {
|
|
969
1299
|
return __async(this, null, function* () {
|
|
970
1300
|
const isDevelopment = process.env.NODE_ENV === "development";
|
|
971
1301
|
const mdxModule = yield (0, import_mdx.evaluate)(source, __spreadProps(__spreadValues({}, isDevelopment ? devRuntime : runtime), {
|
|
972
1302
|
development: isDevelopment,
|
|
973
1303
|
remarkPlugins: [import_remark_gfm2.default, import_remark_github_blockquote_alert2.default],
|
|
974
1304
|
rehypePlugins: [
|
|
975
|
-
customRenderer,
|
|
1305
|
+
[customRenderer, { strategy: config == null ? void 0 : config.linkTargetStrategy, siteUrl: config == null ? void 0 : config.siteUrl }],
|
|
976
1306
|
import_rehype_slug2.default,
|
|
977
1307
|
// @ts-ignore
|
|
978
1308
|
import_rehype_prism_plus2.default
|
|
@@ -987,9 +1317,9 @@ function renderMdxSource(source, basePath) {
|
|
|
987
1317
|
// src/ArticleContent.tsx
|
|
988
1318
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
989
1319
|
function ArticleContent(_0) {
|
|
990
|
-
return __async(this, arguments, function* ({ article, className }) {
|
|
1320
|
+
return __async(this, arguments, function* ({ article, className, config }) {
|
|
991
1321
|
if (article.contentType === "mdx" && article.mdxSource) {
|
|
992
|
-
const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}
|
|
1322
|
+
const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config);
|
|
993
1323
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, children: content });
|
|
994
1324
|
}
|
|
995
1325
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
|
|
@@ -1023,26 +1353,38 @@ function ArticleTOC({ toc, className }) {
|
|
|
1023
1353
|
0 && (module.exports = {
|
|
1024
1354
|
ArticleContent,
|
|
1025
1355
|
ArticleTOC,
|
|
1356
|
+
buildArticleBreadcrumbs,
|
|
1357
|
+
buildAuthorBreadcrumbs,
|
|
1358
|
+
buildCategoryBreadcrumbs,
|
|
1026
1359
|
categoryToSlug,
|
|
1027
1360
|
extractToc,
|
|
1028
1361
|
generateArticleMetadata,
|
|
1029
1362
|
generateArticleStaticParams,
|
|
1030
1363
|
generateArticlesIndexMetadata,
|
|
1364
|
+
generateAuthorMetadata,
|
|
1365
|
+
generateAuthorStaticParams,
|
|
1031
1366
|
generateCategoryMetadata,
|
|
1032
1367
|
generateCategoryStaticParams,
|
|
1368
|
+
generateRssFeed,
|
|
1033
1369
|
getAdjacentArticles,
|
|
1034
1370
|
getAiRobotsTxtRules,
|
|
1035
1371
|
getAllArticles,
|
|
1372
|
+
getAllAuthors,
|
|
1036
1373
|
getAllCategories,
|
|
1037
1374
|
getArticleAiHeaders,
|
|
1375
|
+
getArticleAuthors,
|
|
1038
1376
|
getArticleMarkdown,
|
|
1039
1377
|
getArticleMarkdownResponse,
|
|
1040
1378
|
getArticleMarkdownUrl,
|
|
1041
1379
|
getArticleMetadata,
|
|
1042
1380
|
getArticleSitemapEntries,
|
|
1381
|
+
getArticlesByAuthor,
|
|
1043
1382
|
getArticlesByCategory,
|
|
1383
|
+
getAuthorBySlug,
|
|
1044
1384
|
getAvailableArticleSlugs,
|
|
1385
|
+
getBreadcrumbsConfig,
|
|
1045
1386
|
markdownToHtml,
|
|
1387
|
+
resolveAuthorAvatar,
|
|
1046
1388
|
sanitizeImagePath,
|
|
1047
1389
|
searchArticles,
|
|
1048
1390
|
setArticlesErrorHandler
|