@fullstackdatasolutions/articles 0.12.0 → 1.1.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 +34 -0
- package/README.md +559 -11
- package/dist/index.cjs +984 -388
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +383 -21
- package/dist/index.d.ts +383 -21
- package/dist/index.js +968 -376
- package/dist/index.js.map +1 -1
- package/dist/nextjs.cjs +74 -6
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.d.cts +149 -0
- package/dist/nextjs.d.ts +149 -0
- package/dist/nextjs.js +74 -6
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +665 -27
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +357 -3
- package/dist/server.d.ts +357 -3
- package/dist/server.js +643 -29
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/ArticleCard.tsx +42 -6
- package/src/ArticleContent.tsx +144 -5
- package/src/ArticleDetailHero.tsx +33 -1
- package/src/ArticleNavigation.tsx +32 -1
- package/src/ArticleSchemas.tsx +43 -39
- package/src/ArticleSocialShare.tsx +54 -10
- package/src/ArticlesPage.tsx +56 -5
- package/src/AuthorArticlesPage.tsx +308 -14
- package/src/AuthorCard.tsx +1 -1
- package/src/CategoryArticlesPage.tsx +34 -2
- package/src/LatestArticles.tsx +28 -1
- package/src/LatestArticlesSection.tsx +15 -1
- package/src/PaginationNav.tsx +78 -0
- package/src/RelatedArticlesSection.tsx +58 -0
- package/src/SeriesArticlesPage.tsx +66 -0
- package/src/__tests__/ArticleCard.test.tsx +63 -3
- package/src/__tests__/ArticleContent.test.tsx +143 -0
- package/src/__tests__/ArticleDetailHero.test.tsx +30 -0
- package/src/__tests__/ArticleNavigation.test.tsx +81 -3
- package/src/__tests__/ArticleSchemas.test.tsx +155 -81
- package/src/__tests__/ArticleSocialShare.test.tsx +54 -0
- package/src/__tests__/ArticlesPage.test.tsx +131 -0
- package/src/__tests__/AuthorArticlesPage.test.tsx +304 -3
- package/src/__tests__/CategoryArticlesPage.test.tsx +116 -1
- package/src/__tests__/LatestArticles.test.tsx +52 -0
- package/src/__tests__/LatestArticlesSection.test.tsx +28 -0
- package/src/__tests__/PaginationNav.test.tsx +73 -0
- package/src/__tests__/RelatedArticlesSection.test.tsx +132 -0
- package/src/__tests__/SeriesArticlesPage.test.tsx +121 -0
- package/src/__tests__/eventTracking.test.tsx +145 -0
- package/src/__tests__/events.test.ts +82 -0
- package/src/__tests__/markdown.test.ts +78 -1
- package/src/__tests__/pagination.test.ts +178 -0
- package/src/__tests__/seoUtils-authors.test.ts +37 -0
- package/src/__tests__/seoUtils.test.ts +246 -0
- package/src/__tests__/server-articles.test.ts +356 -1
- package/src/__tests__/validateArticles.test.ts +312 -0
- package/src/articleTypes.ts +109 -0
- package/src/articlesConfig.ts +45 -1
- package/src/eventTracking.tsx +97 -0
- package/src/events.ts +105 -0
- package/src/index.ts +26 -1
- package/src/markdown.ts +41 -0
- package/src/pagination.ts +93 -0
- package/src/seoUtils.ts +198 -11
- package/src/server-articles.ts +199 -6
- package/src/server.ts +46 -2
- package/src/validateArticles.ts +260 -0
package/dist/server.cjs
CHANGED
|
@@ -84,17 +84,27 @@ __export(server_exports, {
|
|
|
84
84
|
buildArticleBreadcrumbs: () => buildArticleBreadcrumbs,
|
|
85
85
|
buildAuthorBreadcrumbs: () => buildAuthorBreadcrumbs,
|
|
86
86
|
buildCategoryBreadcrumbs: () => buildCategoryBreadcrumbs,
|
|
87
|
+
buildPageUrl: () => buildPageUrl,
|
|
88
|
+
buildPaginationLinks: () => buildPaginationLinks,
|
|
87
89
|
categoryToSlug: () => categoryToSlug,
|
|
90
|
+
emitArticleEvent: () => emitArticleEvent,
|
|
88
91
|
extractToc: () => extractToc,
|
|
89
92
|
generateArticleMetadata: () => generateArticleMetadata,
|
|
90
93
|
generateArticleStaticParams: () => generateArticleStaticParams,
|
|
91
94
|
generateArticlesIndexMetadata: () => generateArticlesIndexMetadata,
|
|
95
|
+
generateArticlesIndexPageMetadata: () => generateArticlesIndexPageMetadata,
|
|
92
96
|
generateAuthorMetadata: () => generateAuthorMetadata,
|
|
97
|
+
generateAuthorPageMetadata: () => generateAuthorPageMetadata,
|
|
93
98
|
generateAuthorStaticParams: () => generateAuthorStaticParams,
|
|
94
99
|
generateCategoryMetadata: () => generateCategoryMetadata,
|
|
100
|
+
generateCategoryPageMetadata: () => generateCategoryPageMetadata,
|
|
95
101
|
generateCategoryStaticParams: () => generateCategoryStaticParams,
|
|
102
|
+
generateListingPageStaticParams: () => generateListingPageStaticParams,
|
|
96
103
|
generateRssFeed: () => generateRssFeed,
|
|
104
|
+
generateSeriesMetadata: () => generateSeriesMetadata,
|
|
105
|
+
generateSeriesStaticParams: () => generateSeriesStaticParams,
|
|
97
106
|
getAdjacentArticles: () => getAdjacentArticles,
|
|
107
|
+
getAdjacentArticlesInSeries: () => getAdjacentArticlesInSeries,
|
|
98
108
|
getAiRobotsTxtRules: () => getAiRobotsTxtRules,
|
|
99
109
|
getAllArticles: () => getAllArticles,
|
|
100
110
|
getAllAuthors: () => getAllAuthors,
|
|
@@ -108,14 +118,28 @@ __export(server_exports, {
|
|
|
108
118
|
getArticleSitemapEntries: () => getArticleSitemapEntries,
|
|
109
119
|
getArticlesByAuthor: () => getArticlesByAuthor,
|
|
110
120
|
getArticlesByCategory: () => getArticlesByCategory,
|
|
121
|
+
getArticlesBySeries: () => getArticlesBySeries,
|
|
111
122
|
getAuthorBySlug: () => getAuthorBySlug,
|
|
112
123
|
getAvailableArticleSlugs: () => getAvailableArticleSlugs,
|
|
113
124
|
getBreadcrumbsConfig: () => getBreadcrumbsConfig,
|
|
125
|
+
getContentSlotBoundaries: () => getContentSlotBoundaries,
|
|
126
|
+
getPath: () => getPath,
|
|
127
|
+
getPathArticles: () => getPathArticles,
|
|
128
|
+
getRelatedArticlesByCategory: () => getRelatedArticlesByCategory,
|
|
129
|
+
getRelatedContent: () => getRelatedContent,
|
|
130
|
+
getTotalPages: () => getTotalPages,
|
|
131
|
+
isPageOutOfRange: () => isPageOutOfRange,
|
|
114
132
|
markdownToHtml: () => markdownToHtml,
|
|
133
|
+
paginateArticles: () => paginateArticles,
|
|
134
|
+
parsePageParam: () => parsePageParam,
|
|
115
135
|
resolveAuthorAvatar: () => resolveAuthorAvatar,
|
|
136
|
+
resolveSearchMetadata: () => resolveSearchMetadata,
|
|
137
|
+
resolveSocialMetadata: () => resolveSocialMetadata,
|
|
116
138
|
sanitizeImagePath: () => sanitizeImagePath2,
|
|
117
139
|
searchArticles: () => searchArticles,
|
|
118
|
-
setArticlesErrorHandler: () => setArticlesErrorHandler
|
|
140
|
+
setArticlesErrorHandler: () => setArticlesErrorHandler,
|
|
141
|
+
validateAllArticles: () => validateAllArticles,
|
|
142
|
+
validateArticles: () => validateArticles
|
|
119
143
|
});
|
|
120
144
|
module.exports = __toCommonJS(server_exports);
|
|
121
145
|
|
|
@@ -126,6 +150,17 @@ var import_node_fs = __toESM(require("fs"), 1);
|
|
|
126
150
|
var import_node_path = __toESM(require("path"), 1);
|
|
127
151
|
var import_reading_time = __toESM(require("reading-time"), 1);
|
|
128
152
|
|
|
153
|
+
// src/authorUtils.ts
|
|
154
|
+
function getAuthorAvatar(author, config) {
|
|
155
|
+
if (!author.avatar) return void 0;
|
|
156
|
+
if (author.avatar.startsWith("http://") || author.avatar.startsWith("https://")) {
|
|
157
|
+
return author.avatar;
|
|
158
|
+
}
|
|
159
|
+
const path2 = `/articles/authors/${author.slug}/${author.avatar.replace(/^\/+/, "")}`;
|
|
160
|
+
if (!config) return path2;
|
|
161
|
+
return `${config.siteUrl.replace(/\/$/, "")}${path2}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
129
164
|
// src/markdown.ts
|
|
130
165
|
var import_rehype_prism_plus = __toESM(require("rehype-prism-plus"), 1);
|
|
131
166
|
var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
|
|
@@ -448,6 +483,22 @@ function extractHeadingItem(node) {
|
|
|
448
483
|
if (!id || !text) return null;
|
|
449
484
|
return { id, depth: Number.parseInt(match[1], 10), text };
|
|
450
485
|
}
|
|
486
|
+
function getContentSlotBoundaries(markdown) {
|
|
487
|
+
var _a, _b, _c;
|
|
488
|
+
try {
|
|
489
|
+
const tree = (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).parse(markdown);
|
|
490
|
+
const paragraphs = ((_a = tree.children) != null ? _a : []).filter(
|
|
491
|
+
(node) => node.type === "paragraph" && Boolean(node.position)
|
|
492
|
+
);
|
|
493
|
+
if (paragraphs.length === 0) return null;
|
|
494
|
+
const introEnd = (_b = paragraphs[0].position.end.offset) != null ? _b : 0;
|
|
495
|
+
const midIndex = Math.floor(paragraphs.length / 2);
|
|
496
|
+
const mid = (_c = paragraphs[midIndex].position.end.offset) != null ? _c : introEnd;
|
|
497
|
+
return { introEnd, mid: Math.max(mid, introEnd), paragraphCount: paragraphs.length };
|
|
498
|
+
} catch (e) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
451
502
|
function extractToc(markdown) {
|
|
452
503
|
return __async(this, null, function* () {
|
|
453
504
|
const headings = [];
|
|
@@ -468,8 +519,9 @@ var articlesDirectory = import_node_path.default.join(
|
|
|
468
519
|
process.cwd(),
|
|
469
520
|
"public/articles"
|
|
470
521
|
);
|
|
471
|
-
function
|
|
472
|
-
|
|
522
|
+
function getReadingStats(content) {
|
|
523
|
+
const stats = (0, import_reading_time.default)(content);
|
|
524
|
+
return { readTime: stats.text, wordCount: stats.words };
|
|
473
525
|
}
|
|
474
526
|
function findArticleImage(slug) {
|
|
475
527
|
try {
|
|
@@ -589,6 +641,23 @@ function parseHowToSteps(raw) {
|
|
|
589
641
|
);
|
|
590
642
|
return steps.length ? steps : void 0;
|
|
591
643
|
}
|
|
644
|
+
function parseOptionalString(raw) {
|
|
645
|
+
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
646
|
+
}
|
|
647
|
+
function parseSeriesOrder(raw) {
|
|
648
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
|
|
649
|
+
}
|
|
650
|
+
function parsePrimaryAction(raw) {
|
|
651
|
+
if (typeof raw === "string") {
|
|
652
|
+
const actionId = raw.trim();
|
|
653
|
+
return actionId ? { actionId } : void 0;
|
|
654
|
+
}
|
|
655
|
+
if (typeof raw === "object" && raw !== null && typeof raw.actionId === "string") {
|
|
656
|
+
const actionId = raw.actionId.trim();
|
|
657
|
+
return actionId ? { actionId } : void 0;
|
|
658
|
+
}
|
|
659
|
+
return void 0;
|
|
660
|
+
}
|
|
592
661
|
function parseAuthors(raw) {
|
|
593
662
|
if (!Array.isArray(raw)) return void 0;
|
|
594
663
|
const authors = raw.filter(
|
|
@@ -656,20 +725,26 @@ function getArticleSummary(slug, config) {
|
|
|
656
725
|
if (!found) return null;
|
|
657
726
|
const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
|
|
658
727
|
const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
|
|
659
|
-
const readTime =
|
|
728
|
+
const { readTime, wordCount } = getReadingStats(markdownContent);
|
|
660
729
|
const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
|
|
661
730
|
const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
|
|
731
|
+
const author = resolveArticleAuthorName(data.author, data.authors, config);
|
|
732
|
+
const authors = parseAuthors(data.authors);
|
|
733
|
+
const primaryAuthorProfile = config ? getArticleAuthors({ author, authors }, config)[0] : void 0;
|
|
662
734
|
return {
|
|
663
735
|
slug,
|
|
664
736
|
title: data.title || slug.replaceAll("-", " "),
|
|
665
737
|
excerpt: data.excerpt || "",
|
|
666
738
|
date: parseDateField(data.date),
|
|
667
739
|
lastmod: parseDateField(data.lastmod),
|
|
668
|
-
author
|
|
669
|
-
authors
|
|
740
|
+
author,
|
|
741
|
+
authors,
|
|
742
|
+
authorSlug: primaryAuthorProfile == null ? void 0 : primaryAuthorProfile.slug,
|
|
743
|
+
authorAvatar: primaryAuthorProfile ? getAuthorAvatar(primaryAuthorProfile, config) : void 0,
|
|
670
744
|
category: categories[0],
|
|
671
745
|
categories,
|
|
672
746
|
readTime,
|
|
747
|
+
wordCount,
|
|
673
748
|
featuredImage: resolveFeaturedImage(data.featuredImage, slug),
|
|
674
749
|
tags: data.tags || [],
|
|
675
750
|
contentType: found.contentType,
|
|
@@ -679,7 +754,15 @@ function getArticleSummary(slug, config) {
|
|
|
679
754
|
canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
|
|
680
755
|
articleType: typeof data.articleType === "string" ? data.articleType : void 0,
|
|
681
756
|
series: typeof data.series === "string" ? data.series : void 0,
|
|
682
|
-
|
|
757
|
+
seriesSlug: parseOptionalString(data.seriesSlug),
|
|
758
|
+
seriesOrder: parseSeriesOrder(data.seriesOrder),
|
|
759
|
+
aiCrawl: data.aiCrawl === true,
|
|
760
|
+
searchTitle: parseOptionalString(data.searchTitle),
|
|
761
|
+
searchDescription: parseOptionalString(data.searchDescription),
|
|
762
|
+
socialTitle: parseOptionalString(data.socialTitle),
|
|
763
|
+
socialDescription: parseOptionalString(data.socialDescription),
|
|
764
|
+
socialImage: parseOptionalString(data.socialImage),
|
|
765
|
+
primaryAction: parsePrimaryAction(data.primaryAction)
|
|
683
766
|
};
|
|
684
767
|
} catch (error) {
|
|
685
768
|
reportArticlesError({
|
|
@@ -859,6 +942,12 @@ function getArticlesByCategory(categorySlug, config) {
|
|
|
859
942
|
);
|
|
860
943
|
});
|
|
861
944
|
}
|
|
945
|
+
function getRelatedArticlesByCategory(currentSlug, category, limit = 3, config) {
|
|
946
|
+
return __async(this, null, function* () {
|
|
947
|
+
const articles = yield getArticlesByCategory(categoryToSlug(category), config);
|
|
948
|
+
return articles.filter((article) => article.slug !== currentSlug).slice(0, limit);
|
|
949
|
+
});
|
|
950
|
+
}
|
|
862
951
|
function getArticlesByAuthor(authorSlug, config) {
|
|
863
952
|
return __async(this, null, function* () {
|
|
864
953
|
const articles = yield getAllArticles(config);
|
|
@@ -867,6 +956,79 @@ function getArticlesByAuthor(authorSlug, config) {
|
|
|
867
956
|
);
|
|
868
957
|
});
|
|
869
958
|
}
|
|
959
|
+
function getArticlesBySeries(seriesSlug, config) {
|
|
960
|
+
return __async(this, null, function* () {
|
|
961
|
+
const articles = yield getAllArticles(config);
|
|
962
|
+
return articles.filter((article) => article.seriesSlug === seriesSlug).sort((a, b) => {
|
|
963
|
+
var _a, _b;
|
|
964
|
+
const orderA = (_a = a.seriesOrder) != null ? _a : Number.POSITIVE_INFINITY;
|
|
965
|
+
const orderB = (_b = b.seriesOrder) != null ? _b : Number.POSITIVE_INFINITY;
|
|
966
|
+
return orderA - orderB;
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
function getAdjacentArticlesInSeries(currentSlug, seriesSlug, config) {
|
|
971
|
+
return __async(this, null, function* () {
|
|
972
|
+
const seriesArticles = yield getArticlesBySeries(seriesSlug, config);
|
|
973
|
+
const currentIndex = seriesArticles.findIndex((article) => article.slug === currentSlug);
|
|
974
|
+
if (currentIndex === -1) return { previous: null, next: null };
|
|
975
|
+
return {
|
|
976
|
+
previous: currentIndex > 0 ? seriesArticles[currentIndex - 1] : null,
|
|
977
|
+
next: currentIndex < seriesArticles.length - 1 ? seriesArticles[currentIndex + 1] : null
|
|
978
|
+
};
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
function getPath(pathKey, config) {
|
|
982
|
+
var _a, _b;
|
|
983
|
+
return (_b = (_a = config.paths) == null ? void 0 : _a[pathKey]) != null ? _b : null;
|
|
984
|
+
}
|
|
985
|
+
function getPathArticles(pathKey, config) {
|
|
986
|
+
return __async(this, null, function* () {
|
|
987
|
+
const path2 = getPath(pathKey, config);
|
|
988
|
+
if (!path2) return [];
|
|
989
|
+
const articles = yield getAllArticles(config);
|
|
990
|
+
const bySlug = new Map(articles.map((article) => [article.slug, article]));
|
|
991
|
+
return path2.articles.map((slug) => bySlug.get(slug)).filter((article) => Boolean(article));
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
function findPathForArticle(slug, config) {
|
|
995
|
+
var _a;
|
|
996
|
+
for (const [key, path2] of Object.entries((_a = config.paths) != null ? _a : {})) {
|
|
997
|
+
if (path2.articles.includes(slug)) return { key, path: path2 };
|
|
998
|
+
}
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
function getRelatedContent(article, config, limit = 3) {
|
|
1002
|
+
return __async(this, null, function* () {
|
|
1003
|
+
var _a;
|
|
1004
|
+
const matchedPath = findPathForArticle(article.slug, config);
|
|
1005
|
+
if (matchedPath) {
|
|
1006
|
+
const pathArticles = yield getPathArticles(matchedPath.key, config);
|
|
1007
|
+
return {
|
|
1008
|
+
source: "path",
|
|
1009
|
+
heading: matchedPath.path.name,
|
|
1010
|
+
articles: pathArticles.filter((a) => a.slug !== article.slug),
|
|
1011
|
+
pathKey: matchedPath.key,
|
|
1012
|
+
nextAction: matchedPath.path.nextAction
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
if (article.seriesSlug) {
|
|
1016
|
+
const seriesArticles = yield getArticlesBySeries(article.seriesSlug, config);
|
|
1017
|
+
return {
|
|
1018
|
+
source: "series",
|
|
1019
|
+
heading: (_a = article.series) != null ? _a : "This series",
|
|
1020
|
+
articles: seriesArticles.filter((a) => a.slug !== article.slug)
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
const categoryArticles = yield getRelatedArticlesByCategory(
|
|
1024
|
+
article.slug,
|
|
1025
|
+
article.category,
|
|
1026
|
+
limit,
|
|
1027
|
+
config
|
|
1028
|
+
);
|
|
1029
|
+
return { source: "category", heading: `More in ${article.category}`, articles: categoryArticles };
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
870
1032
|
|
|
871
1033
|
// src/articlesConfig.ts
|
|
872
1034
|
function breadcrumbsAreEnabled(config) {
|
|
@@ -879,6 +1041,48 @@ function getBreadcrumbsConfig(config) {
|
|
|
879
1041
|
return (_a = config.breadcrumbs) != null ? _a : {};
|
|
880
1042
|
}
|
|
881
1043
|
|
|
1044
|
+
// src/pagination.ts
|
|
1045
|
+
function getTotalPages(totalCount, pageSize) {
|
|
1046
|
+
if (totalCount <= 0 || pageSize <= 0) return 1;
|
|
1047
|
+
return Math.max(1, Math.ceil(totalCount / pageSize));
|
|
1048
|
+
}
|
|
1049
|
+
function paginateArticles(articles, page, pageSize) {
|
|
1050
|
+
const totalPages = getTotalPages(articles.length, pageSize);
|
|
1051
|
+
const requestedPage = Math.trunc(page) || 1;
|
|
1052
|
+
const clampedPage = Math.min(Math.max(requestedPage, 1), totalPages);
|
|
1053
|
+
const start = (clampedPage - 1) * pageSize;
|
|
1054
|
+
return {
|
|
1055
|
+
articles: articles.slice(start, start + pageSize),
|
|
1056
|
+
page: clampedPage,
|
|
1057
|
+
totalPages,
|
|
1058
|
+
hasPrevious: clampedPage > 1,
|
|
1059
|
+
hasNext: clampedPage < totalPages
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
function buildPageUrl(basePath, page) {
|
|
1063
|
+
const base = basePath.replace(/\/$/, "");
|
|
1064
|
+
return page > 1 ? `${base}/page/${page}` : base;
|
|
1065
|
+
}
|
|
1066
|
+
function buildPaginationLinks(basePath, page, totalPages) {
|
|
1067
|
+
return {
|
|
1068
|
+
canonicalUrl: buildPageUrl(basePath, page),
|
|
1069
|
+
prevUrl: page > 1 ? buildPageUrl(basePath, page - 1) : null,
|
|
1070
|
+
nextUrl: page < totalPages ? buildPageUrl(basePath, page + 1) : null
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
function generateListingPageStaticParams(totalPages) {
|
|
1074
|
+
const params = [];
|
|
1075
|
+
for (let page = 2; page <= totalPages; page++) params.push({ page: String(page) });
|
|
1076
|
+
return params;
|
|
1077
|
+
}
|
|
1078
|
+
function parsePageParam(raw) {
|
|
1079
|
+
const parsed = Number.parseInt(raw != null ? raw : "", 10);
|
|
1080
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
|
|
1081
|
+
}
|
|
1082
|
+
function isPageOutOfRange(page, totalPages) {
|
|
1083
|
+
return page < 1 || page > totalPages;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
882
1086
|
// src/seoUtils.ts
|
|
883
1087
|
function escapeXml(str) {
|
|
884
1088
|
return str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -930,6 +1134,15 @@ function generateAuthorStaticParams(config) {
|
|
|
930
1134
|
if (config.showAuthorPage === false) return [];
|
|
931
1135
|
return getAllAuthors(config).map((author) => ({ author: author.slug }));
|
|
932
1136
|
}
|
|
1137
|
+
function generateSeriesStaticParams(config) {
|
|
1138
|
+
return __async(this, null, function* () {
|
|
1139
|
+
const articles = yield getAllArticles(config);
|
|
1140
|
+
const seriesSlugs = new Set(
|
|
1141
|
+
articles.map((article) => article.seriesSlug).filter((slug) => Boolean(slug))
|
|
1142
|
+
);
|
|
1143
|
+
return [...seriesSlugs].map((series) => ({ series }));
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
933
1146
|
function resolveImageUrl(featuredImage, siteUrl) {
|
|
934
1147
|
const base = siteUrl.replace(/\/$/, "");
|
|
935
1148
|
if (featuredImage.startsWith("http://") || featuredImage.startsWith("https://")) {
|
|
@@ -937,9 +1150,25 @@ function resolveImageUrl(featuredImage, siteUrl) {
|
|
|
937
1150
|
}
|
|
938
1151
|
return `${base}/${featuredImage.replace(/^\/+/, "")}`;
|
|
939
1152
|
}
|
|
1153
|
+
function resolveSearchMetadata(article, config) {
|
|
1154
|
+
var _a, _b, _c;
|
|
1155
|
+
return {
|
|
1156
|
+
title: (_a = article.searchTitle) != null ? _a : article.title,
|
|
1157
|
+
description: (_c = (_b = article.searchDescription) != null ? _b : article.excerpt) != null ? _c : `Read ${article.title} on ${config.siteName}.`
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
function resolveSocialMetadata(article, siteUrl) {
|
|
1161
|
+
var _a, _b, _c, _d;
|
|
1162
|
+
const image = (_a = article.socialImage) != null ? _a : article.featuredImage;
|
|
1163
|
+
return {
|
|
1164
|
+
title: (_b = article.socialTitle) != null ? _b : article.title,
|
|
1165
|
+
description: (_d = (_c = article.socialDescription) != null ? _c : article.excerpt) != null ? _d : "",
|
|
1166
|
+
imageUrl: image ? resolveImageUrl(image, siteUrl) : `${siteUrl}/placeholder-logo.png`
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
940
1169
|
function generateArticleMetadata(slug, config) {
|
|
941
1170
|
return __async(this, null, function* () {
|
|
942
|
-
var _a, _b, _c, _d, _e, _f
|
|
1171
|
+
var _a, _b, _c, _d, _e, _f;
|
|
943
1172
|
const article = yield getArticleMetadata(slug, config);
|
|
944
1173
|
if (!article) {
|
|
945
1174
|
return {
|
|
@@ -950,31 +1179,32 @@ function generateArticleMetadata(slug, config) {
|
|
|
950
1179
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
951
1180
|
const articleUrl = `${siteUrl}/articles/${slug}`;
|
|
952
1181
|
const canonicalUrl = (_a = article.canonicalUrl) != null ? _a : articleUrl;
|
|
953
|
-
const
|
|
954
|
-
const
|
|
1182
|
+
const search = resolveSearchMetadata(article, config);
|
|
1183
|
+
const social = resolveSocialMetadata(article, siteUrl);
|
|
1184
|
+
const description = search.description;
|
|
955
1185
|
const showAuthor = config.showAuthor !== false;
|
|
956
1186
|
const markdownUrl = getArticleMarkdownUrl(article, config);
|
|
957
1187
|
const authorNames = getArticleAuthors(article, config).map((author) => author.name);
|
|
958
1188
|
return {
|
|
959
|
-
title: `${
|
|
1189
|
+
title: `${search.title} | ${config.siteName}`,
|
|
960
1190
|
description,
|
|
961
|
-
keywords: [...((
|
|
1191
|
+
keywords: [...((_b = article.tags) != null ? _b : []).map((tag) => tag.toLowerCase())].join(", "),
|
|
962
1192
|
openGraph: __spreadProps(__spreadValues(__spreadValues(__spreadValues({
|
|
963
|
-
title:
|
|
964
|
-
description,
|
|
1193
|
+
title: social.title,
|
|
1194
|
+
description: social.description || description,
|
|
965
1195
|
url: articleUrl,
|
|
966
1196
|
siteName: config.siteName,
|
|
967
|
-
images: [{ url: imageUrl, width: 1200, height: 630, alt:
|
|
1197
|
+
images: [{ url: social.imageUrl, width: 1200, height: 630, alt: social.title }],
|
|
968
1198
|
locale: "en_US",
|
|
969
1199
|
type: "article"
|
|
970
1200
|
}, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), showAuthor && authorNames.length > 0 && { authors: authorNames }), {
|
|
971
|
-
tags: (
|
|
1201
|
+
tags: (_c = article.tags) != null ? _c : []
|
|
972
1202
|
}),
|
|
973
1203
|
twitter: {
|
|
974
1204
|
card: "summary_large_image",
|
|
975
|
-
title:
|
|
976
|
-
description,
|
|
977
|
-
images: [imageUrl]
|
|
1205
|
+
title: social.title,
|
|
1206
|
+
description: social.description || description,
|
|
1207
|
+
images: [social.imageUrl]
|
|
978
1208
|
},
|
|
979
1209
|
alternates: __spreadValues({
|
|
980
1210
|
canonical: canonicalUrl
|
|
@@ -1000,8 +1230,8 @@ function generateArticleMetadata(slug, config) {
|
|
|
1000
1230
|
"article:modified_time": new Date(article.lastmod).toISOString()
|
|
1001
1231
|
}), {
|
|
1002
1232
|
"article:section": article.category,
|
|
1003
|
-
"article:tag": (
|
|
1004
|
-
"linkedin:owner": (
|
|
1233
|
+
"article:tag": (_e = (_d = article.tags) == null ? void 0 : _d.join(",")) != null ? _e : "",
|
|
1234
|
+
"linkedin:owner": (_f = process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID) != null ? _f : ""
|
|
1005
1235
|
})
|
|
1006
1236
|
};
|
|
1007
1237
|
});
|
|
@@ -1093,6 +1323,50 @@ function generateCategoryMetadata(categorySlug, config) {
|
|
|
1093
1323
|
};
|
|
1094
1324
|
});
|
|
1095
1325
|
}
|
|
1326
|
+
function generateSeriesMetadata(seriesSlug, config) {
|
|
1327
|
+
return __async(this, null, function* () {
|
|
1328
|
+
var _a;
|
|
1329
|
+
const articles = yield getArticlesBySeries(seriesSlug, config);
|
|
1330
|
+
if (articles.length === 0) return { title: "Series Not Found" };
|
|
1331
|
+
const seriesName = (_a = articles[0].series) != null ? _a : seriesSlug;
|
|
1332
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1333
|
+
const seriesUrl = `${siteUrl}/articles/series/${seriesSlug}`;
|
|
1334
|
+
const description = `Follow the ${seriesName} series - ${articles.length} article${articles.length === 1 ? "" : "s"} on ${config.siteName}.`;
|
|
1335
|
+
const title = `${seriesName} Series | ${config.siteName}`;
|
|
1336
|
+
return {
|
|
1337
|
+
title,
|
|
1338
|
+
description,
|
|
1339
|
+
openGraph: {
|
|
1340
|
+
title: `${seriesName} Series`,
|
|
1341
|
+
description,
|
|
1342
|
+
url: seriesUrl,
|
|
1343
|
+
siteName: config.siteName,
|
|
1344
|
+
images: [{ url: articles[0].featuredImage }],
|
|
1345
|
+
type: "website",
|
|
1346
|
+
locale: "en_US"
|
|
1347
|
+
},
|
|
1348
|
+
twitter: {
|
|
1349
|
+
card: "summary_large_image",
|
|
1350
|
+
title: `${seriesName} Series`,
|
|
1351
|
+
description
|
|
1352
|
+
},
|
|
1353
|
+
alternates: {
|
|
1354
|
+
canonical: seriesUrl
|
|
1355
|
+
},
|
|
1356
|
+
robots: {
|
|
1357
|
+
index: true,
|
|
1358
|
+
follow: true,
|
|
1359
|
+
googleBot: {
|
|
1360
|
+
index: true,
|
|
1361
|
+
follow: true,
|
|
1362
|
+
"max-video-preview": -1,
|
|
1363
|
+
"max-image-preview": "large",
|
|
1364
|
+
"max-snippet": -1
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1096
1370
|
function generateAuthorMetadata(authorSlug, config) {
|
|
1097
1371
|
return __async(this, null, function* () {
|
|
1098
1372
|
var _a;
|
|
@@ -1127,6 +1401,47 @@ function generateAuthorMetadata(authorSlug, config) {
|
|
|
1127
1401
|
};
|
|
1128
1402
|
});
|
|
1129
1403
|
}
|
|
1404
|
+
function withPaginationMeta(base, basePath, page, totalPages) {
|
|
1405
|
+
if (!base.alternates) return base;
|
|
1406
|
+
const { canonicalUrl } = buildPaginationLinks(basePath, page, totalPages);
|
|
1407
|
+
const pageSuffix = page > 1 ? ` - Page ${page}` : "";
|
|
1408
|
+
const title = typeof base.title === "string" ? `${base.title}${pageSuffix}` : base.title;
|
|
1409
|
+
const openGraph = base.openGraph ? __spreadProps(__spreadValues({}, base.openGraph), {
|
|
1410
|
+
title: typeof base.openGraph.title === "string" ? `${base.openGraph.title}${pageSuffix}` : base.openGraph.title,
|
|
1411
|
+
url: canonicalUrl
|
|
1412
|
+
}) : base.openGraph;
|
|
1413
|
+
const twitter = base.twitter ? __spreadProps(__spreadValues({}, base.twitter), {
|
|
1414
|
+
title: typeof base.twitter.title === "string" ? `${base.twitter.title}${pageSuffix}` : base.twitter.title
|
|
1415
|
+
}) : base.twitter;
|
|
1416
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
1417
|
+
title,
|
|
1418
|
+
openGraph,
|
|
1419
|
+
twitter,
|
|
1420
|
+
alternates: __spreadProps(__spreadValues({}, base.alternates), { canonical: canonicalUrl })
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
function generateArticlesIndexPageMetadata(page, totalPages, config) {
|
|
1424
|
+
const base = generateArticlesIndexMetadata(config);
|
|
1425
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1426
|
+
return withPaginationMeta(base, `${siteUrl}/articles`, page, totalPages);
|
|
1427
|
+
}
|
|
1428
|
+
function generateCategoryPageMetadata(categorySlug, page, totalPages, config) {
|
|
1429
|
+
return __async(this, null, function* () {
|
|
1430
|
+
const base = yield generateCategoryMetadata(categorySlug, config);
|
|
1431
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1432
|
+
return withPaginationMeta(base, `${siteUrl}/articles/category/${categorySlug}`, page, totalPages);
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
function generateAuthorPageMetadata(authorSlug, page, totalPages, config) {
|
|
1436
|
+
return __async(this, null, function* () {
|
|
1437
|
+
var _a;
|
|
1438
|
+
const base = yield generateAuthorMetadata(authorSlug, config);
|
|
1439
|
+
const author = getAuthorBySlug(authorSlug, config);
|
|
1440
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1441
|
+
const basePath = (_a = author == null ? void 0 : author.url) != null ? _a : `${siteUrl}/articles/authors/${authorSlug}`;
|
|
1442
|
+
return withPaginationMeta(base, basePath, page, totalPages);
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1130
1445
|
function formatCategoryName(category) {
|
|
1131
1446
|
return category.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
1132
1447
|
}
|
|
@@ -1317,13 +1632,91 @@ function renderMdxSource(source, basePath, config) {
|
|
|
1317
1632
|
|
|
1318
1633
|
// src/ArticleContent.tsx
|
|
1319
1634
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1635
|
+
function buildSlotContext(article) {
|
|
1636
|
+
var _a, _b;
|
|
1637
|
+
return {
|
|
1638
|
+
slug: article.slug,
|
|
1639
|
+
title: article.title,
|
|
1640
|
+
category: article.category,
|
|
1641
|
+
tags: (_a = article.tags) != null ? _a : [],
|
|
1642
|
+
readTime: article.readTime,
|
|
1643
|
+
wordCount: article.wordCount,
|
|
1644
|
+
authorSlug: article.authorSlug,
|
|
1645
|
+
seriesSlug: article.seriesSlug,
|
|
1646
|
+
primaryActionId: (_b = article.primaryAction) == null ? void 0 : _b.actionId
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
function resolveSlot(slot, context) {
|
|
1650
|
+
if (slot === void 0) return null;
|
|
1651
|
+
return typeof slot === "function" ? slot(context) : slot;
|
|
1652
|
+
}
|
|
1653
|
+
function renderSegment(markdown, contentType, slug, config) {
|
|
1654
|
+
return __async(this, null, function* () {
|
|
1655
|
+
if (!markdown.trim()) return null;
|
|
1656
|
+
if (contentType === "mdx") {
|
|
1657
|
+
return renderMdxSource(markdown, `/articles/${slug}`, config);
|
|
1658
|
+
}
|
|
1659
|
+
const html = yield markdownToHtml(markdown, slug, config);
|
|
1660
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { dangerouslySetInnerHTML: { __html: html } });
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1320
1663
|
function ArticleContent(_0) {
|
|
1321
|
-
return __async(this, arguments, function* ({
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1664
|
+
return __async(this, arguments, function* ({
|
|
1665
|
+
article,
|
|
1666
|
+
className,
|
|
1667
|
+
config,
|
|
1668
|
+
afterHero,
|
|
1669
|
+
afterIntro,
|
|
1670
|
+
midContent,
|
|
1671
|
+
afterContent
|
|
1672
|
+
}) {
|
|
1673
|
+
var _a;
|
|
1674
|
+
const hasAnySlot = afterHero !== void 0 || afterIntro !== void 0 || midContent !== void 0 || afterContent !== void 0;
|
|
1675
|
+
if (!hasAnySlot) {
|
|
1676
|
+
if (article.contentType === "mdx" && article.mdxSource) {
|
|
1677
|
+
const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config);
|
|
1678
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, children: content });
|
|
1679
|
+
}
|
|
1680
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
|
|
1681
|
+
}
|
|
1682
|
+
const slotContext = buildSlotContext(article);
|
|
1683
|
+
const heroNode = resolveSlot(afterHero, slotContext);
|
|
1684
|
+
const introNode = resolveSlot(afterIntro, slotContext);
|
|
1685
|
+
const midNode = resolveSlot(midContent, slotContext);
|
|
1686
|
+
const contentNode = resolveSlot(afterContent, slotContext);
|
|
1687
|
+
const needsSplit = Boolean(introNode || midNode);
|
|
1688
|
+
const rawSource = article.contentType === "mdx" ? article.mdxSource : (_a = article.content) != null ? _a : void 0;
|
|
1689
|
+
if (needsSplit && rawSource) {
|
|
1690
|
+
const boundaries = getContentSlotBoundaries(rawSource);
|
|
1691
|
+
if (boundaries) {
|
|
1692
|
+
try {
|
|
1693
|
+
const introSegment = rawSource.slice(0, boundaries.introEnd);
|
|
1694
|
+
const midSegment = rawSource.slice(boundaries.introEnd, boundaries.mid);
|
|
1695
|
+
const restSegment = rawSource.slice(boundaries.mid);
|
|
1696
|
+
const [introHtml, midHtml, restHtml] = yield Promise.all([
|
|
1697
|
+
renderSegment(introSegment, article.contentType, article.slug, config),
|
|
1698
|
+
renderSegment(midSegment, article.contentType, article.slug, config),
|
|
1699
|
+
renderSegment(restSegment, article.contentType, article.slug, config)
|
|
1700
|
+
]);
|
|
1701
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, children: [
|
|
1702
|
+
heroNode,
|
|
1703
|
+
introHtml,
|
|
1704
|
+
introNode,
|
|
1705
|
+
midHtml,
|
|
1706
|
+
midNode,
|
|
1707
|
+
restHtml,
|
|
1708
|
+
contentNode
|
|
1709
|
+
] });
|
|
1710
|
+
} catch (e) {
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1325
1713
|
}
|
|
1326
|
-
|
|
1714
|
+
const wholeBody = article.contentType === "mdx" && article.mdxSource ? yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
|
|
1715
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, children: [
|
|
1716
|
+
heroNode,
|
|
1717
|
+
wholeBody,
|
|
1718
|
+
contentNode
|
|
1719
|
+
] });
|
|
1327
1720
|
});
|
|
1328
1721
|
}
|
|
1329
1722
|
|
|
@@ -1350,6 +1743,227 @@ function ArticleTOC({ toc, className }) {
|
|
|
1350
1743
|
}
|
|
1351
1744
|
);
|
|
1352
1745
|
}
|
|
1746
|
+
|
|
1747
|
+
// src/validateArticles.ts
|
|
1748
|
+
var UNSAFE_URL_SCHEME = /^\s*(javascript|data|vbscript):/i;
|
|
1749
|
+
var SEARCH_TITLE_MAX = 60;
|
|
1750
|
+
var SEARCH_DESCRIPTION_MAX = 160;
|
|
1751
|
+
var SOCIAL_TITLE_MAX = 95;
|
|
1752
|
+
var SOCIAL_DESCRIPTION_MAX = 200;
|
|
1753
|
+
function isUnsafeUrl(href) {
|
|
1754
|
+
return UNSAFE_URL_SCHEME.test(href);
|
|
1755
|
+
}
|
|
1756
|
+
function checkDuplicateCanonicalUrls(articles) {
|
|
1757
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1758
|
+
const issues = [];
|
|
1759
|
+
for (const article of articles) {
|
|
1760
|
+
if (!article.canonicalUrl) continue;
|
|
1761
|
+
const owner = seen.get(article.canonicalUrl);
|
|
1762
|
+
if (owner) {
|
|
1763
|
+
issues.push({
|
|
1764
|
+
severity: "error",
|
|
1765
|
+
code: "duplicate-canonical-url",
|
|
1766
|
+
message: `canonicalUrl "${article.canonicalUrl}" is also used by "${owner}".`,
|
|
1767
|
+
articleSlug: article.slug
|
|
1768
|
+
});
|
|
1769
|
+
} else {
|
|
1770
|
+
seen.set(article.canonicalUrl, article.slug);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
return issues;
|
|
1774
|
+
}
|
|
1775
|
+
function checkAuthorReferences(articles, config) {
|
|
1776
|
+
if (!config.authors || Object.keys(config.authors).length === 0) return [];
|
|
1777
|
+
const issues = [];
|
|
1778
|
+
for (const article of articles) {
|
|
1779
|
+
for (const resolved of getArticleAuthors(article, config)) {
|
|
1780
|
+
if (!getAuthorBySlug(resolved.slug, config)) {
|
|
1781
|
+
issues.push({
|
|
1782
|
+
severity: "error",
|
|
1783
|
+
code: "unknown-author-reference",
|
|
1784
|
+
message: `Author "${resolved.name}" does not match any entry in config.authors.`,
|
|
1785
|
+
articleSlug: article.slug
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return issues;
|
|
1791
|
+
}
|
|
1792
|
+
function checkSeriesCollisions(articles) {
|
|
1793
|
+
const issues = [];
|
|
1794
|
+
const seenSlugOrder = /* @__PURE__ */ new Map();
|
|
1795
|
+
for (const article of articles) {
|
|
1796
|
+
if (!article.seriesSlug || article.seriesOrder === void 0) continue;
|
|
1797
|
+
const key = `${article.seriesSlug}::${article.seriesOrder}`;
|
|
1798
|
+
const owner = seenSlugOrder.get(key);
|
|
1799
|
+
if (owner) {
|
|
1800
|
+
issues.push({
|
|
1801
|
+
severity: "error",
|
|
1802
|
+
code: "duplicate-series-order",
|
|
1803
|
+
message: `seriesOrder ${article.seriesOrder} in series "${article.seriesSlug}" collides with "${owner}".`,
|
|
1804
|
+
articleSlug: article.slug
|
|
1805
|
+
});
|
|
1806
|
+
} else {
|
|
1807
|
+
seenSlugOrder.set(key, article.slug);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
return issues;
|
|
1811
|
+
}
|
|
1812
|
+
function checkPaths(articles, config) {
|
|
1813
|
+
var _a;
|
|
1814
|
+
const issues = [];
|
|
1815
|
+
const bySlug = new Map(articles.map((article) => [article.slug, article]));
|
|
1816
|
+
for (const [pathKey, path2] of Object.entries((_a = config.paths) != null ? _a : {})) {
|
|
1817
|
+
if (path2.articles.length === 0) {
|
|
1818
|
+
issues.push({
|
|
1819
|
+
severity: "error",
|
|
1820
|
+
code: "empty-path",
|
|
1821
|
+
message: `Path "${pathKey}" has no articles.`,
|
|
1822
|
+
pathKey
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
for (const slug of path2.articles) {
|
|
1826
|
+
const referenced = bySlug.get(slug);
|
|
1827
|
+
if (!referenced) {
|
|
1828
|
+
issues.push({
|
|
1829
|
+
severity: "error",
|
|
1830
|
+
code: "path-missing-article",
|
|
1831
|
+
message: `Path "${pathKey}" references missing article "${slug}".`,
|
|
1832
|
+
pathKey,
|
|
1833
|
+
articleSlug: slug
|
|
1834
|
+
});
|
|
1835
|
+
} else if (referenced.draft) {
|
|
1836
|
+
issues.push({
|
|
1837
|
+
severity: "error",
|
|
1838
|
+
code: "path-references-draft",
|
|
1839
|
+
message: `Path "${pathKey}" references unpublished (draft) article "${slug}".`,
|
|
1840
|
+
pathKey,
|
|
1841
|
+
articleSlug: slug
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
if (isUnsafeUrl(path2.nextAction.href)) {
|
|
1846
|
+
issues.push({
|
|
1847
|
+
severity: "error",
|
|
1848
|
+
code: "unsafe-url",
|
|
1849
|
+
message: `Path "${pathKey}" nextAction.href uses an unsafe URL scheme.`,
|
|
1850
|
+
pathKey
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
return issues;
|
|
1855
|
+
}
|
|
1856
|
+
function checkAuthorCtaUrls(config) {
|
|
1857
|
+
var _a;
|
|
1858
|
+
const issues = [];
|
|
1859
|
+
for (const author of Object.values((_a = config.authors) != null ? _a : {})) {
|
|
1860
|
+
if (author.primaryCta && isUnsafeUrl(author.primaryCta.href)) {
|
|
1861
|
+
issues.push({
|
|
1862
|
+
severity: "error",
|
|
1863
|
+
code: "unsafe-url",
|
|
1864
|
+
message: `Author "${author.slug}" primaryCta.href uses an unsafe URL scheme.`
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return issues;
|
|
1869
|
+
}
|
|
1870
|
+
function checkRequiredFrontmatter(articles) {
|
|
1871
|
+
const issues = [];
|
|
1872
|
+
for (const article of articles) {
|
|
1873
|
+
if (!article.excerpt) {
|
|
1874
|
+
issues.push({
|
|
1875
|
+
severity: "warning",
|
|
1876
|
+
code: "missing-excerpt",
|
|
1877
|
+
message: "Article has no excerpt.",
|
|
1878
|
+
articleSlug: article.slug
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
if (!article.date) {
|
|
1882
|
+
issues.push({
|
|
1883
|
+
severity: "warning",
|
|
1884
|
+
code: "missing-date",
|
|
1885
|
+
message: "Article has no date.",
|
|
1886
|
+
articleSlug: article.slug
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
return issues;
|
|
1891
|
+
}
|
|
1892
|
+
function checkDiscoveryFieldLengths(articles) {
|
|
1893
|
+
const issues = [];
|
|
1894
|
+
for (const article of articles) {
|
|
1895
|
+
const checks = [
|
|
1896
|
+
[article.searchTitle, "search-title-too-long", SEARCH_TITLE_MAX],
|
|
1897
|
+
[article.searchDescription, "search-description-too-long", SEARCH_DESCRIPTION_MAX],
|
|
1898
|
+
[article.socialTitle, "social-title-too-long", SOCIAL_TITLE_MAX],
|
|
1899
|
+
[article.socialDescription, "social-description-too-long", SOCIAL_DESCRIPTION_MAX]
|
|
1900
|
+
];
|
|
1901
|
+
for (const [value, code, max] of checks) {
|
|
1902
|
+
if (value && value.length > max) {
|
|
1903
|
+
issues.push({
|
|
1904
|
+
severity: "warning",
|
|
1905
|
+
code,
|
|
1906
|
+
message: `${code.replaceAll("-", " ")} (${value.length} > ${max} recommended chars).`,
|
|
1907
|
+
articleSlug: article.slug
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
return issues;
|
|
1913
|
+
}
|
|
1914
|
+
function checkCategorySlugs(articles) {
|
|
1915
|
+
var _a;
|
|
1916
|
+
const issues = [];
|
|
1917
|
+
const slugToNames = /* @__PURE__ */ new Map();
|
|
1918
|
+
for (const article of articles) {
|
|
1919
|
+
for (const category of article.categories) {
|
|
1920
|
+
const slug = categoryToSlug(category);
|
|
1921
|
+
const names = (_a = slugToNames.get(slug)) != null ? _a : /* @__PURE__ */ new Set();
|
|
1922
|
+
names.add(category);
|
|
1923
|
+
slugToNames.set(slug, names);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
for (const [slug, names] of slugToNames) {
|
|
1927
|
+
if (names.size > 1) {
|
|
1928
|
+
issues.push({
|
|
1929
|
+
severity: "warning",
|
|
1930
|
+
code: "category-slug-collision",
|
|
1931
|
+
message: `Categories [${[...names].join(", ")}] all collapse to slug "${slug}".`
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return issues;
|
|
1936
|
+
}
|
|
1937
|
+
function validateArticles(articles, config) {
|
|
1938
|
+
const errors = [
|
|
1939
|
+
...checkDuplicateCanonicalUrls(articles),
|
|
1940
|
+
...checkAuthorReferences(articles, config),
|
|
1941
|
+
...checkSeriesCollisions(articles),
|
|
1942
|
+
...checkPaths(articles, config),
|
|
1943
|
+
...checkAuthorCtaUrls(config)
|
|
1944
|
+
];
|
|
1945
|
+
const warnings = [
|
|
1946
|
+
...checkRequiredFrontmatter(articles),
|
|
1947
|
+
...checkDiscoveryFieldLengths(articles),
|
|
1948
|
+
...checkCategorySlugs(articles)
|
|
1949
|
+
];
|
|
1950
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
1951
|
+
}
|
|
1952
|
+
function validateAllArticles(config) {
|
|
1953
|
+
return __async(this, null, function* () {
|
|
1954
|
+
const articles = yield getAllArticles(config);
|
|
1955
|
+
return validateArticles(articles, config);
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// src/events.ts
|
|
1960
|
+
function emitArticleEvent(handler, event) {
|
|
1961
|
+
if (!handler) return;
|
|
1962
|
+
try {
|
|
1963
|
+
handler(__spreadProps(__spreadValues({}, event), { timestamp: Date.now() }));
|
|
1964
|
+
} catch (e) {
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1353
1967
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1354
1968
|
0 && (module.exports = {
|
|
1355
1969
|
ArticleContent,
|
|
@@ -1357,17 +1971,27 @@ function ArticleTOC({ toc, className }) {
|
|
|
1357
1971
|
buildArticleBreadcrumbs,
|
|
1358
1972
|
buildAuthorBreadcrumbs,
|
|
1359
1973
|
buildCategoryBreadcrumbs,
|
|
1974
|
+
buildPageUrl,
|
|
1975
|
+
buildPaginationLinks,
|
|
1360
1976
|
categoryToSlug,
|
|
1977
|
+
emitArticleEvent,
|
|
1361
1978
|
extractToc,
|
|
1362
1979
|
generateArticleMetadata,
|
|
1363
1980
|
generateArticleStaticParams,
|
|
1364
1981
|
generateArticlesIndexMetadata,
|
|
1982
|
+
generateArticlesIndexPageMetadata,
|
|
1365
1983
|
generateAuthorMetadata,
|
|
1984
|
+
generateAuthorPageMetadata,
|
|
1366
1985
|
generateAuthorStaticParams,
|
|
1367
1986
|
generateCategoryMetadata,
|
|
1987
|
+
generateCategoryPageMetadata,
|
|
1368
1988
|
generateCategoryStaticParams,
|
|
1989
|
+
generateListingPageStaticParams,
|
|
1369
1990
|
generateRssFeed,
|
|
1991
|
+
generateSeriesMetadata,
|
|
1992
|
+
generateSeriesStaticParams,
|
|
1370
1993
|
getAdjacentArticles,
|
|
1994
|
+
getAdjacentArticlesInSeries,
|
|
1371
1995
|
getAiRobotsTxtRules,
|
|
1372
1996
|
getAllArticles,
|
|
1373
1997
|
getAllAuthors,
|
|
@@ -1381,13 +2005,27 @@ function ArticleTOC({ toc, className }) {
|
|
|
1381
2005
|
getArticleSitemapEntries,
|
|
1382
2006
|
getArticlesByAuthor,
|
|
1383
2007
|
getArticlesByCategory,
|
|
2008
|
+
getArticlesBySeries,
|
|
1384
2009
|
getAuthorBySlug,
|
|
1385
2010
|
getAvailableArticleSlugs,
|
|
1386
2011
|
getBreadcrumbsConfig,
|
|
2012
|
+
getContentSlotBoundaries,
|
|
2013
|
+
getPath,
|
|
2014
|
+
getPathArticles,
|
|
2015
|
+
getRelatedArticlesByCategory,
|
|
2016
|
+
getRelatedContent,
|
|
2017
|
+
getTotalPages,
|
|
2018
|
+
isPageOutOfRange,
|
|
1387
2019
|
markdownToHtml,
|
|
2020
|
+
paginateArticles,
|
|
2021
|
+
parsePageParam,
|
|
1388
2022
|
resolveAuthorAvatar,
|
|
2023
|
+
resolveSearchMetadata,
|
|
2024
|
+
resolveSocialMetadata,
|
|
1389
2025
|
sanitizeImagePath,
|
|
1390
2026
|
searchArticles,
|
|
1391
|
-
setArticlesErrorHandler
|
|
2027
|
+
setArticlesErrorHandler,
|
|
2028
|
+
validateAllArticles,
|
|
2029
|
+
validateArticles
|
|
1392
2030
|
});
|
|
1393
2031
|
//# sourceMappingURL=server.cjs.map
|