@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.js
CHANGED
|
@@ -57,6 +57,17 @@ import fs from "node:fs";
|
|
|
57
57
|
import path from "node:path";
|
|
58
58
|
import readingTime from "reading-time";
|
|
59
59
|
|
|
60
|
+
// src/authorUtils.ts
|
|
61
|
+
function getAuthorAvatar(author, config) {
|
|
62
|
+
if (!author.avatar) return void 0;
|
|
63
|
+
if (author.avatar.startsWith("http://") || author.avatar.startsWith("https://")) {
|
|
64
|
+
return author.avatar;
|
|
65
|
+
}
|
|
66
|
+
const path2 = `/articles/authors/${author.slug}/${author.avatar.replace(/^\/+/, "")}`;
|
|
67
|
+
if (!config) return path2;
|
|
68
|
+
return `${config.siteUrl.replace(/\/$/, "")}${path2}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
// src/markdown.ts
|
|
61
72
|
import rehypePrism from "rehype-prism-plus";
|
|
62
73
|
import rehypeSanitize from "rehype-sanitize";
|
|
@@ -379,6 +390,22 @@ function extractHeadingItem(node) {
|
|
|
379
390
|
if (!id || !text) return null;
|
|
380
391
|
return { id, depth: Number.parseInt(match[1], 10), text };
|
|
381
392
|
}
|
|
393
|
+
function getContentSlotBoundaries(markdown) {
|
|
394
|
+
var _a, _b, _c;
|
|
395
|
+
try {
|
|
396
|
+
const tree = remark().use(remarkParse).use(remarkGfm).parse(markdown);
|
|
397
|
+
const paragraphs = ((_a = tree.children) != null ? _a : []).filter(
|
|
398
|
+
(node) => node.type === "paragraph" && Boolean(node.position)
|
|
399
|
+
);
|
|
400
|
+
if (paragraphs.length === 0) return null;
|
|
401
|
+
const introEnd = (_b = paragraphs[0].position.end.offset) != null ? _b : 0;
|
|
402
|
+
const midIndex = Math.floor(paragraphs.length / 2);
|
|
403
|
+
const mid = (_c = paragraphs[midIndex].position.end.offset) != null ? _c : introEnd;
|
|
404
|
+
return { introEnd, mid: Math.max(mid, introEnd), paragraphCount: paragraphs.length };
|
|
405
|
+
} catch (e) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
382
409
|
function extractToc(markdown) {
|
|
383
410
|
return __async(this, null, function* () {
|
|
384
411
|
const headings = [];
|
|
@@ -399,8 +426,9 @@ var articlesDirectory = path.join(
|
|
|
399
426
|
process.cwd(),
|
|
400
427
|
"public/articles"
|
|
401
428
|
);
|
|
402
|
-
function
|
|
403
|
-
|
|
429
|
+
function getReadingStats(content) {
|
|
430
|
+
const stats = readingTime(content);
|
|
431
|
+
return { readTime: stats.text, wordCount: stats.words };
|
|
404
432
|
}
|
|
405
433
|
function findArticleImage(slug) {
|
|
406
434
|
try {
|
|
@@ -520,6 +548,23 @@ function parseHowToSteps(raw) {
|
|
|
520
548
|
);
|
|
521
549
|
return steps.length ? steps : void 0;
|
|
522
550
|
}
|
|
551
|
+
function parseOptionalString(raw) {
|
|
552
|
+
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
553
|
+
}
|
|
554
|
+
function parseSeriesOrder(raw) {
|
|
555
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
|
|
556
|
+
}
|
|
557
|
+
function parsePrimaryAction(raw) {
|
|
558
|
+
if (typeof raw === "string") {
|
|
559
|
+
const actionId = raw.trim();
|
|
560
|
+
return actionId ? { actionId } : void 0;
|
|
561
|
+
}
|
|
562
|
+
if (typeof raw === "object" && raw !== null && typeof raw.actionId === "string") {
|
|
563
|
+
const actionId = raw.actionId.trim();
|
|
564
|
+
return actionId ? { actionId } : void 0;
|
|
565
|
+
}
|
|
566
|
+
return void 0;
|
|
567
|
+
}
|
|
523
568
|
function parseAuthors(raw) {
|
|
524
569
|
if (!Array.isArray(raw)) return void 0;
|
|
525
570
|
const authors = raw.filter(
|
|
@@ -587,20 +632,26 @@ function getArticleSummary(slug, config) {
|
|
|
587
632
|
if (!found) return null;
|
|
588
633
|
const fileContent = fs.readFileSync(found.filePath, "utf8");
|
|
589
634
|
const { data, content: markdownContent } = matter(fileContent);
|
|
590
|
-
const readTime =
|
|
635
|
+
const { readTime, wordCount } = getReadingStats(markdownContent);
|
|
591
636
|
const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
|
|
592
637
|
const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
|
|
638
|
+
const author = resolveArticleAuthorName(data.author, data.authors, config);
|
|
639
|
+
const authors = parseAuthors(data.authors);
|
|
640
|
+
const primaryAuthorProfile = config ? getArticleAuthors({ author, authors }, config)[0] : void 0;
|
|
593
641
|
return {
|
|
594
642
|
slug,
|
|
595
643
|
title: data.title || slug.replaceAll("-", " "),
|
|
596
644
|
excerpt: data.excerpt || "",
|
|
597
645
|
date: parseDateField(data.date),
|
|
598
646
|
lastmod: parseDateField(data.lastmod),
|
|
599
|
-
author
|
|
600
|
-
authors
|
|
647
|
+
author,
|
|
648
|
+
authors,
|
|
649
|
+
authorSlug: primaryAuthorProfile == null ? void 0 : primaryAuthorProfile.slug,
|
|
650
|
+
authorAvatar: primaryAuthorProfile ? getAuthorAvatar(primaryAuthorProfile, config) : void 0,
|
|
601
651
|
category: categories[0],
|
|
602
652
|
categories,
|
|
603
653
|
readTime,
|
|
654
|
+
wordCount,
|
|
604
655
|
featuredImage: resolveFeaturedImage(data.featuredImage, slug),
|
|
605
656
|
tags: data.tags || [],
|
|
606
657
|
contentType: found.contentType,
|
|
@@ -610,7 +661,15 @@ function getArticleSummary(slug, config) {
|
|
|
610
661
|
canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
|
|
611
662
|
articleType: typeof data.articleType === "string" ? data.articleType : void 0,
|
|
612
663
|
series: typeof data.series === "string" ? data.series : void 0,
|
|
613
|
-
|
|
664
|
+
seriesSlug: parseOptionalString(data.seriesSlug),
|
|
665
|
+
seriesOrder: parseSeriesOrder(data.seriesOrder),
|
|
666
|
+
aiCrawl: data.aiCrawl === true,
|
|
667
|
+
searchTitle: parseOptionalString(data.searchTitle),
|
|
668
|
+
searchDescription: parseOptionalString(data.searchDescription),
|
|
669
|
+
socialTitle: parseOptionalString(data.socialTitle),
|
|
670
|
+
socialDescription: parseOptionalString(data.socialDescription),
|
|
671
|
+
socialImage: parseOptionalString(data.socialImage),
|
|
672
|
+
primaryAction: parsePrimaryAction(data.primaryAction)
|
|
614
673
|
};
|
|
615
674
|
} catch (error) {
|
|
616
675
|
reportArticlesError({
|
|
@@ -790,6 +849,12 @@ function getArticlesByCategory(categorySlug, config) {
|
|
|
790
849
|
);
|
|
791
850
|
});
|
|
792
851
|
}
|
|
852
|
+
function getRelatedArticlesByCategory(currentSlug, category, limit = 3, config) {
|
|
853
|
+
return __async(this, null, function* () {
|
|
854
|
+
const articles = yield getArticlesByCategory(categoryToSlug(category), config);
|
|
855
|
+
return articles.filter((article) => article.slug !== currentSlug).slice(0, limit);
|
|
856
|
+
});
|
|
857
|
+
}
|
|
793
858
|
function getArticlesByAuthor(authorSlug, config) {
|
|
794
859
|
return __async(this, null, function* () {
|
|
795
860
|
const articles = yield getAllArticles(config);
|
|
@@ -798,6 +863,79 @@ function getArticlesByAuthor(authorSlug, config) {
|
|
|
798
863
|
);
|
|
799
864
|
});
|
|
800
865
|
}
|
|
866
|
+
function getArticlesBySeries(seriesSlug, config) {
|
|
867
|
+
return __async(this, null, function* () {
|
|
868
|
+
const articles = yield getAllArticles(config);
|
|
869
|
+
return articles.filter((article) => article.seriesSlug === seriesSlug).sort((a, b) => {
|
|
870
|
+
var _a, _b;
|
|
871
|
+
const orderA = (_a = a.seriesOrder) != null ? _a : Number.POSITIVE_INFINITY;
|
|
872
|
+
const orderB = (_b = b.seriesOrder) != null ? _b : Number.POSITIVE_INFINITY;
|
|
873
|
+
return orderA - orderB;
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
function getAdjacentArticlesInSeries(currentSlug, seriesSlug, config) {
|
|
878
|
+
return __async(this, null, function* () {
|
|
879
|
+
const seriesArticles = yield getArticlesBySeries(seriesSlug, config);
|
|
880
|
+
const currentIndex = seriesArticles.findIndex((article) => article.slug === currentSlug);
|
|
881
|
+
if (currentIndex === -1) return { previous: null, next: null };
|
|
882
|
+
return {
|
|
883
|
+
previous: currentIndex > 0 ? seriesArticles[currentIndex - 1] : null,
|
|
884
|
+
next: currentIndex < seriesArticles.length - 1 ? seriesArticles[currentIndex + 1] : null
|
|
885
|
+
};
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
function getPath(pathKey, config) {
|
|
889
|
+
var _a, _b;
|
|
890
|
+
return (_b = (_a = config.paths) == null ? void 0 : _a[pathKey]) != null ? _b : null;
|
|
891
|
+
}
|
|
892
|
+
function getPathArticles(pathKey, config) {
|
|
893
|
+
return __async(this, null, function* () {
|
|
894
|
+
const path2 = getPath(pathKey, config);
|
|
895
|
+
if (!path2) return [];
|
|
896
|
+
const articles = yield getAllArticles(config);
|
|
897
|
+
const bySlug = new Map(articles.map((article) => [article.slug, article]));
|
|
898
|
+
return path2.articles.map((slug) => bySlug.get(slug)).filter((article) => Boolean(article));
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
function findPathForArticle(slug, config) {
|
|
902
|
+
var _a;
|
|
903
|
+
for (const [key, path2] of Object.entries((_a = config.paths) != null ? _a : {})) {
|
|
904
|
+
if (path2.articles.includes(slug)) return { key, path: path2 };
|
|
905
|
+
}
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
function getRelatedContent(article, config, limit = 3) {
|
|
909
|
+
return __async(this, null, function* () {
|
|
910
|
+
var _a;
|
|
911
|
+
const matchedPath = findPathForArticle(article.slug, config);
|
|
912
|
+
if (matchedPath) {
|
|
913
|
+
const pathArticles = yield getPathArticles(matchedPath.key, config);
|
|
914
|
+
return {
|
|
915
|
+
source: "path",
|
|
916
|
+
heading: matchedPath.path.name,
|
|
917
|
+
articles: pathArticles.filter((a) => a.slug !== article.slug),
|
|
918
|
+
pathKey: matchedPath.key,
|
|
919
|
+
nextAction: matchedPath.path.nextAction
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
if (article.seriesSlug) {
|
|
923
|
+
const seriesArticles = yield getArticlesBySeries(article.seriesSlug, config);
|
|
924
|
+
return {
|
|
925
|
+
source: "series",
|
|
926
|
+
heading: (_a = article.series) != null ? _a : "This series",
|
|
927
|
+
articles: seriesArticles.filter((a) => a.slug !== article.slug)
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
const categoryArticles = yield getRelatedArticlesByCategory(
|
|
931
|
+
article.slug,
|
|
932
|
+
article.category,
|
|
933
|
+
limit,
|
|
934
|
+
config
|
|
935
|
+
);
|
|
936
|
+
return { source: "category", heading: `More in ${article.category}`, articles: categoryArticles };
|
|
937
|
+
});
|
|
938
|
+
}
|
|
801
939
|
|
|
802
940
|
// src/articlesConfig.ts
|
|
803
941
|
function breadcrumbsAreEnabled(config) {
|
|
@@ -810,6 +948,48 @@ function getBreadcrumbsConfig(config) {
|
|
|
810
948
|
return (_a = config.breadcrumbs) != null ? _a : {};
|
|
811
949
|
}
|
|
812
950
|
|
|
951
|
+
// src/pagination.ts
|
|
952
|
+
function getTotalPages(totalCount, pageSize) {
|
|
953
|
+
if (totalCount <= 0 || pageSize <= 0) return 1;
|
|
954
|
+
return Math.max(1, Math.ceil(totalCount / pageSize));
|
|
955
|
+
}
|
|
956
|
+
function paginateArticles(articles, page, pageSize) {
|
|
957
|
+
const totalPages = getTotalPages(articles.length, pageSize);
|
|
958
|
+
const requestedPage = Math.trunc(page) || 1;
|
|
959
|
+
const clampedPage = Math.min(Math.max(requestedPage, 1), totalPages);
|
|
960
|
+
const start = (clampedPage - 1) * pageSize;
|
|
961
|
+
return {
|
|
962
|
+
articles: articles.slice(start, start + pageSize),
|
|
963
|
+
page: clampedPage,
|
|
964
|
+
totalPages,
|
|
965
|
+
hasPrevious: clampedPage > 1,
|
|
966
|
+
hasNext: clampedPage < totalPages
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function buildPageUrl(basePath, page) {
|
|
970
|
+
const base = basePath.replace(/\/$/, "");
|
|
971
|
+
return page > 1 ? `${base}/page/${page}` : base;
|
|
972
|
+
}
|
|
973
|
+
function buildPaginationLinks(basePath, page, totalPages) {
|
|
974
|
+
return {
|
|
975
|
+
canonicalUrl: buildPageUrl(basePath, page),
|
|
976
|
+
prevUrl: page > 1 ? buildPageUrl(basePath, page - 1) : null,
|
|
977
|
+
nextUrl: page < totalPages ? buildPageUrl(basePath, page + 1) : null
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
function generateListingPageStaticParams(totalPages) {
|
|
981
|
+
const params = [];
|
|
982
|
+
for (let page = 2; page <= totalPages; page++) params.push({ page: String(page) });
|
|
983
|
+
return params;
|
|
984
|
+
}
|
|
985
|
+
function parsePageParam(raw) {
|
|
986
|
+
const parsed = Number.parseInt(raw != null ? raw : "", 10);
|
|
987
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
|
|
988
|
+
}
|
|
989
|
+
function isPageOutOfRange(page, totalPages) {
|
|
990
|
+
return page < 1 || page > totalPages;
|
|
991
|
+
}
|
|
992
|
+
|
|
813
993
|
// src/seoUtils.ts
|
|
814
994
|
function escapeXml(str) {
|
|
815
995
|
return str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -861,6 +1041,15 @@ function generateAuthorStaticParams(config) {
|
|
|
861
1041
|
if (config.showAuthorPage === false) return [];
|
|
862
1042
|
return getAllAuthors(config).map((author) => ({ author: author.slug }));
|
|
863
1043
|
}
|
|
1044
|
+
function generateSeriesStaticParams(config) {
|
|
1045
|
+
return __async(this, null, function* () {
|
|
1046
|
+
const articles = yield getAllArticles(config);
|
|
1047
|
+
const seriesSlugs = new Set(
|
|
1048
|
+
articles.map((article) => article.seriesSlug).filter((slug) => Boolean(slug))
|
|
1049
|
+
);
|
|
1050
|
+
return [...seriesSlugs].map((series) => ({ series }));
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
864
1053
|
function resolveImageUrl(featuredImage, siteUrl) {
|
|
865
1054
|
const base = siteUrl.replace(/\/$/, "");
|
|
866
1055
|
if (featuredImage.startsWith("http://") || featuredImage.startsWith("https://")) {
|
|
@@ -868,9 +1057,25 @@ function resolveImageUrl(featuredImage, siteUrl) {
|
|
|
868
1057
|
}
|
|
869
1058
|
return `${base}/${featuredImage.replace(/^\/+/, "")}`;
|
|
870
1059
|
}
|
|
1060
|
+
function resolveSearchMetadata(article, config) {
|
|
1061
|
+
var _a, _b, _c;
|
|
1062
|
+
return {
|
|
1063
|
+
title: (_a = article.searchTitle) != null ? _a : article.title,
|
|
1064
|
+
description: (_c = (_b = article.searchDescription) != null ? _b : article.excerpt) != null ? _c : `Read ${article.title} on ${config.siteName}.`
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function resolveSocialMetadata(article, siteUrl) {
|
|
1068
|
+
var _a, _b, _c, _d;
|
|
1069
|
+
const image = (_a = article.socialImage) != null ? _a : article.featuredImage;
|
|
1070
|
+
return {
|
|
1071
|
+
title: (_b = article.socialTitle) != null ? _b : article.title,
|
|
1072
|
+
description: (_d = (_c = article.socialDescription) != null ? _c : article.excerpt) != null ? _d : "",
|
|
1073
|
+
imageUrl: image ? resolveImageUrl(image, siteUrl) : `${siteUrl}/placeholder-logo.png`
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
871
1076
|
function generateArticleMetadata(slug, config) {
|
|
872
1077
|
return __async(this, null, function* () {
|
|
873
|
-
var _a, _b, _c, _d, _e, _f
|
|
1078
|
+
var _a, _b, _c, _d, _e, _f;
|
|
874
1079
|
const article = yield getArticleMetadata(slug, config);
|
|
875
1080
|
if (!article) {
|
|
876
1081
|
return {
|
|
@@ -881,31 +1086,32 @@ function generateArticleMetadata(slug, config) {
|
|
|
881
1086
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
882
1087
|
const articleUrl = `${siteUrl}/articles/${slug}`;
|
|
883
1088
|
const canonicalUrl = (_a = article.canonicalUrl) != null ? _a : articleUrl;
|
|
884
|
-
const
|
|
885
|
-
const
|
|
1089
|
+
const search = resolveSearchMetadata(article, config);
|
|
1090
|
+
const social = resolveSocialMetadata(article, siteUrl);
|
|
1091
|
+
const description = search.description;
|
|
886
1092
|
const showAuthor = config.showAuthor !== false;
|
|
887
1093
|
const markdownUrl = getArticleMarkdownUrl(article, config);
|
|
888
1094
|
const authorNames = getArticleAuthors(article, config).map((author) => author.name);
|
|
889
1095
|
return {
|
|
890
|
-
title: `${
|
|
1096
|
+
title: `${search.title} | ${config.siteName}`,
|
|
891
1097
|
description,
|
|
892
|
-
keywords: [...((
|
|
1098
|
+
keywords: [...((_b = article.tags) != null ? _b : []).map((tag) => tag.toLowerCase())].join(", "),
|
|
893
1099
|
openGraph: __spreadProps(__spreadValues(__spreadValues(__spreadValues({
|
|
894
|
-
title:
|
|
895
|
-
description,
|
|
1100
|
+
title: social.title,
|
|
1101
|
+
description: social.description || description,
|
|
896
1102
|
url: articleUrl,
|
|
897
1103
|
siteName: config.siteName,
|
|
898
|
-
images: [{ url: imageUrl, width: 1200, height: 630, alt:
|
|
1104
|
+
images: [{ url: social.imageUrl, width: 1200, height: 630, alt: social.title }],
|
|
899
1105
|
locale: "en_US",
|
|
900
1106
|
type: "article"
|
|
901
1107
|
}, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), showAuthor && authorNames.length > 0 && { authors: authorNames }), {
|
|
902
|
-
tags: (
|
|
1108
|
+
tags: (_c = article.tags) != null ? _c : []
|
|
903
1109
|
}),
|
|
904
1110
|
twitter: {
|
|
905
1111
|
card: "summary_large_image",
|
|
906
|
-
title:
|
|
907
|
-
description,
|
|
908
|
-
images: [imageUrl]
|
|
1112
|
+
title: social.title,
|
|
1113
|
+
description: social.description || description,
|
|
1114
|
+
images: [social.imageUrl]
|
|
909
1115
|
},
|
|
910
1116
|
alternates: __spreadValues({
|
|
911
1117
|
canonical: canonicalUrl
|
|
@@ -931,8 +1137,8 @@ function generateArticleMetadata(slug, config) {
|
|
|
931
1137
|
"article:modified_time": new Date(article.lastmod).toISOString()
|
|
932
1138
|
}), {
|
|
933
1139
|
"article:section": article.category,
|
|
934
|
-
"article:tag": (
|
|
935
|
-
"linkedin:owner": (
|
|
1140
|
+
"article:tag": (_e = (_d = article.tags) == null ? void 0 : _d.join(",")) != null ? _e : "",
|
|
1141
|
+
"linkedin:owner": (_f = process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID) != null ? _f : ""
|
|
936
1142
|
})
|
|
937
1143
|
};
|
|
938
1144
|
});
|
|
@@ -1024,6 +1230,50 @@ function generateCategoryMetadata(categorySlug, config) {
|
|
|
1024
1230
|
};
|
|
1025
1231
|
});
|
|
1026
1232
|
}
|
|
1233
|
+
function generateSeriesMetadata(seriesSlug, config) {
|
|
1234
|
+
return __async(this, null, function* () {
|
|
1235
|
+
var _a;
|
|
1236
|
+
const articles = yield getArticlesBySeries(seriesSlug, config);
|
|
1237
|
+
if (articles.length === 0) return { title: "Series Not Found" };
|
|
1238
|
+
const seriesName = (_a = articles[0].series) != null ? _a : seriesSlug;
|
|
1239
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1240
|
+
const seriesUrl = `${siteUrl}/articles/series/${seriesSlug}`;
|
|
1241
|
+
const description = `Follow the ${seriesName} series - ${articles.length} article${articles.length === 1 ? "" : "s"} on ${config.siteName}.`;
|
|
1242
|
+
const title = `${seriesName} Series | ${config.siteName}`;
|
|
1243
|
+
return {
|
|
1244
|
+
title,
|
|
1245
|
+
description,
|
|
1246
|
+
openGraph: {
|
|
1247
|
+
title: `${seriesName} Series`,
|
|
1248
|
+
description,
|
|
1249
|
+
url: seriesUrl,
|
|
1250
|
+
siteName: config.siteName,
|
|
1251
|
+
images: [{ url: articles[0].featuredImage }],
|
|
1252
|
+
type: "website",
|
|
1253
|
+
locale: "en_US"
|
|
1254
|
+
},
|
|
1255
|
+
twitter: {
|
|
1256
|
+
card: "summary_large_image",
|
|
1257
|
+
title: `${seriesName} Series`,
|
|
1258
|
+
description
|
|
1259
|
+
},
|
|
1260
|
+
alternates: {
|
|
1261
|
+
canonical: seriesUrl
|
|
1262
|
+
},
|
|
1263
|
+
robots: {
|
|
1264
|
+
index: true,
|
|
1265
|
+
follow: true,
|
|
1266
|
+
googleBot: {
|
|
1267
|
+
index: true,
|
|
1268
|
+
follow: true,
|
|
1269
|
+
"max-video-preview": -1,
|
|
1270
|
+
"max-image-preview": "large",
|
|
1271
|
+
"max-snippet": -1
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1027
1277
|
function generateAuthorMetadata(authorSlug, config) {
|
|
1028
1278
|
return __async(this, null, function* () {
|
|
1029
1279
|
var _a;
|
|
@@ -1058,6 +1308,47 @@ function generateAuthorMetadata(authorSlug, config) {
|
|
|
1058
1308
|
};
|
|
1059
1309
|
});
|
|
1060
1310
|
}
|
|
1311
|
+
function withPaginationMeta(base, basePath, page, totalPages) {
|
|
1312
|
+
if (!base.alternates) return base;
|
|
1313
|
+
const { canonicalUrl } = buildPaginationLinks(basePath, page, totalPages);
|
|
1314
|
+
const pageSuffix = page > 1 ? ` - Page ${page}` : "";
|
|
1315
|
+
const title = typeof base.title === "string" ? `${base.title}${pageSuffix}` : base.title;
|
|
1316
|
+
const openGraph = base.openGraph ? __spreadProps(__spreadValues({}, base.openGraph), {
|
|
1317
|
+
title: typeof base.openGraph.title === "string" ? `${base.openGraph.title}${pageSuffix}` : base.openGraph.title,
|
|
1318
|
+
url: canonicalUrl
|
|
1319
|
+
}) : base.openGraph;
|
|
1320
|
+
const twitter = base.twitter ? __spreadProps(__spreadValues({}, base.twitter), {
|
|
1321
|
+
title: typeof base.twitter.title === "string" ? `${base.twitter.title}${pageSuffix}` : base.twitter.title
|
|
1322
|
+
}) : base.twitter;
|
|
1323
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
1324
|
+
title,
|
|
1325
|
+
openGraph,
|
|
1326
|
+
twitter,
|
|
1327
|
+
alternates: __spreadProps(__spreadValues({}, base.alternates), { canonical: canonicalUrl })
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
function generateArticlesIndexPageMetadata(page, totalPages, config) {
|
|
1331
|
+
const base = generateArticlesIndexMetadata(config);
|
|
1332
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1333
|
+
return withPaginationMeta(base, `${siteUrl}/articles`, page, totalPages);
|
|
1334
|
+
}
|
|
1335
|
+
function generateCategoryPageMetadata(categorySlug, page, totalPages, config) {
|
|
1336
|
+
return __async(this, null, function* () {
|
|
1337
|
+
const base = yield generateCategoryMetadata(categorySlug, config);
|
|
1338
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1339
|
+
return withPaginationMeta(base, `${siteUrl}/articles/category/${categorySlug}`, page, totalPages);
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
function generateAuthorPageMetadata(authorSlug, page, totalPages, config) {
|
|
1343
|
+
return __async(this, null, function* () {
|
|
1344
|
+
var _a;
|
|
1345
|
+
const base = yield generateAuthorMetadata(authorSlug, config);
|
|
1346
|
+
const author = getAuthorBySlug(authorSlug, config);
|
|
1347
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
1348
|
+
const basePath = (_a = author == null ? void 0 : author.url) != null ? _a : `${siteUrl}/articles/authors/${authorSlug}`;
|
|
1349
|
+
return withPaginationMeta(base, basePath, page, totalPages);
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1061
1352
|
function formatCategoryName(category) {
|
|
1062
1353
|
return category.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
1063
1354
|
}
|
|
@@ -1247,22 +1538,100 @@ function renderMdxSource(source, basePath, config) {
|
|
|
1247
1538
|
}
|
|
1248
1539
|
|
|
1249
1540
|
// src/ArticleContent.tsx
|
|
1250
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1541
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1542
|
+
function buildSlotContext(article) {
|
|
1543
|
+
var _a, _b;
|
|
1544
|
+
return {
|
|
1545
|
+
slug: article.slug,
|
|
1546
|
+
title: article.title,
|
|
1547
|
+
category: article.category,
|
|
1548
|
+
tags: (_a = article.tags) != null ? _a : [],
|
|
1549
|
+
readTime: article.readTime,
|
|
1550
|
+
wordCount: article.wordCount,
|
|
1551
|
+
authorSlug: article.authorSlug,
|
|
1552
|
+
seriesSlug: article.seriesSlug,
|
|
1553
|
+
primaryActionId: (_b = article.primaryAction) == null ? void 0 : _b.actionId
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
function resolveSlot(slot, context) {
|
|
1557
|
+
if (slot === void 0) return null;
|
|
1558
|
+
return typeof slot === "function" ? slot(context) : slot;
|
|
1559
|
+
}
|
|
1560
|
+
function renderSegment(markdown, contentType, slug, config) {
|
|
1561
|
+
return __async(this, null, function* () {
|
|
1562
|
+
if (!markdown.trim()) return null;
|
|
1563
|
+
if (contentType === "mdx") {
|
|
1564
|
+
return renderMdxSource(markdown, `/articles/${slug}`, config);
|
|
1565
|
+
}
|
|
1566
|
+
const html = yield markdownToHtml(markdown, slug, config);
|
|
1567
|
+
return /* @__PURE__ */ jsx2("div", { dangerouslySetInnerHTML: { __html: html } });
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1251
1570
|
function ArticleContent(_0) {
|
|
1252
|
-
return __async(this, arguments, function* ({
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1571
|
+
return __async(this, arguments, function* ({
|
|
1572
|
+
article,
|
|
1573
|
+
className,
|
|
1574
|
+
config,
|
|
1575
|
+
afterHero,
|
|
1576
|
+
afterIntro,
|
|
1577
|
+
midContent,
|
|
1578
|
+
afterContent
|
|
1579
|
+
}) {
|
|
1580
|
+
var _a;
|
|
1581
|
+
const hasAnySlot = afterHero !== void 0 || afterIntro !== void 0 || midContent !== void 0 || afterContent !== void 0;
|
|
1582
|
+
if (!hasAnySlot) {
|
|
1583
|
+
if (article.contentType === "mdx" && article.mdxSource) {
|
|
1584
|
+
const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config);
|
|
1585
|
+
return /* @__PURE__ */ jsx2("div", { className, children: content });
|
|
1586
|
+
}
|
|
1587
|
+
return /* @__PURE__ */ jsx2("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
|
|
1588
|
+
}
|
|
1589
|
+
const slotContext = buildSlotContext(article);
|
|
1590
|
+
const heroNode = resolveSlot(afterHero, slotContext);
|
|
1591
|
+
const introNode = resolveSlot(afterIntro, slotContext);
|
|
1592
|
+
const midNode = resolveSlot(midContent, slotContext);
|
|
1593
|
+
const contentNode = resolveSlot(afterContent, slotContext);
|
|
1594
|
+
const needsSplit = Boolean(introNode || midNode);
|
|
1595
|
+
const rawSource = article.contentType === "mdx" ? article.mdxSource : (_a = article.content) != null ? _a : void 0;
|
|
1596
|
+
if (needsSplit && rawSource) {
|
|
1597
|
+
const boundaries = getContentSlotBoundaries(rawSource);
|
|
1598
|
+
if (boundaries) {
|
|
1599
|
+
try {
|
|
1600
|
+
const introSegment = rawSource.slice(0, boundaries.introEnd);
|
|
1601
|
+
const midSegment = rawSource.slice(boundaries.introEnd, boundaries.mid);
|
|
1602
|
+
const restSegment = rawSource.slice(boundaries.mid);
|
|
1603
|
+
const [introHtml, midHtml, restHtml] = yield Promise.all([
|
|
1604
|
+
renderSegment(introSegment, article.contentType, article.slug, config),
|
|
1605
|
+
renderSegment(midSegment, article.contentType, article.slug, config),
|
|
1606
|
+
renderSegment(restSegment, article.contentType, article.slug, config)
|
|
1607
|
+
]);
|
|
1608
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
1609
|
+
heroNode,
|
|
1610
|
+
introHtml,
|
|
1611
|
+
introNode,
|
|
1612
|
+
midHtml,
|
|
1613
|
+
midNode,
|
|
1614
|
+
restHtml,
|
|
1615
|
+
contentNode
|
|
1616
|
+
] });
|
|
1617
|
+
} catch (e) {
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1256
1620
|
}
|
|
1257
|
-
|
|
1621
|
+
const wholeBody = article.contentType === "mdx" && article.mdxSource ? yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`, config) : /* @__PURE__ */ jsx2("div", { dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
|
|
1622
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
1623
|
+
heroNode,
|
|
1624
|
+
wholeBody,
|
|
1625
|
+
contentNode
|
|
1626
|
+
] });
|
|
1258
1627
|
});
|
|
1259
1628
|
}
|
|
1260
1629
|
|
|
1261
1630
|
// src/ArticleTOC.tsx
|
|
1262
|
-
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
1631
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1263
1632
|
function ArticleTOC({ toc, className }) {
|
|
1264
1633
|
if (!toc.length) return null;
|
|
1265
|
-
return /* @__PURE__ */
|
|
1634
|
+
return /* @__PURE__ */ jsxs2(
|
|
1266
1635
|
"nav",
|
|
1267
1636
|
{
|
|
1268
1637
|
"aria-label": "Table of contents",
|
|
@@ -1281,23 +1650,254 @@ function ArticleTOC({ toc, className }) {
|
|
|
1281
1650
|
}
|
|
1282
1651
|
);
|
|
1283
1652
|
}
|
|
1653
|
+
|
|
1654
|
+
// src/validateArticles.ts
|
|
1655
|
+
var UNSAFE_URL_SCHEME = /^\s*(javascript|data|vbscript):/i;
|
|
1656
|
+
var SEARCH_TITLE_MAX = 60;
|
|
1657
|
+
var SEARCH_DESCRIPTION_MAX = 160;
|
|
1658
|
+
var SOCIAL_TITLE_MAX = 95;
|
|
1659
|
+
var SOCIAL_DESCRIPTION_MAX = 200;
|
|
1660
|
+
function isUnsafeUrl(href) {
|
|
1661
|
+
return UNSAFE_URL_SCHEME.test(href);
|
|
1662
|
+
}
|
|
1663
|
+
function checkDuplicateCanonicalUrls(articles) {
|
|
1664
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1665
|
+
const issues = [];
|
|
1666
|
+
for (const article of articles) {
|
|
1667
|
+
if (!article.canonicalUrl) continue;
|
|
1668
|
+
const owner = seen.get(article.canonicalUrl);
|
|
1669
|
+
if (owner) {
|
|
1670
|
+
issues.push({
|
|
1671
|
+
severity: "error",
|
|
1672
|
+
code: "duplicate-canonical-url",
|
|
1673
|
+
message: `canonicalUrl "${article.canonicalUrl}" is also used by "${owner}".`,
|
|
1674
|
+
articleSlug: article.slug
|
|
1675
|
+
});
|
|
1676
|
+
} else {
|
|
1677
|
+
seen.set(article.canonicalUrl, article.slug);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return issues;
|
|
1681
|
+
}
|
|
1682
|
+
function checkAuthorReferences(articles, config) {
|
|
1683
|
+
if (!config.authors || Object.keys(config.authors).length === 0) return [];
|
|
1684
|
+
const issues = [];
|
|
1685
|
+
for (const article of articles) {
|
|
1686
|
+
for (const resolved of getArticleAuthors(article, config)) {
|
|
1687
|
+
if (!getAuthorBySlug(resolved.slug, config)) {
|
|
1688
|
+
issues.push({
|
|
1689
|
+
severity: "error",
|
|
1690
|
+
code: "unknown-author-reference",
|
|
1691
|
+
message: `Author "${resolved.name}" does not match any entry in config.authors.`,
|
|
1692
|
+
articleSlug: article.slug
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return issues;
|
|
1698
|
+
}
|
|
1699
|
+
function checkSeriesCollisions(articles) {
|
|
1700
|
+
const issues = [];
|
|
1701
|
+
const seenSlugOrder = /* @__PURE__ */ new Map();
|
|
1702
|
+
for (const article of articles) {
|
|
1703
|
+
if (!article.seriesSlug || article.seriesOrder === void 0) continue;
|
|
1704
|
+
const key = `${article.seriesSlug}::${article.seriesOrder}`;
|
|
1705
|
+
const owner = seenSlugOrder.get(key);
|
|
1706
|
+
if (owner) {
|
|
1707
|
+
issues.push({
|
|
1708
|
+
severity: "error",
|
|
1709
|
+
code: "duplicate-series-order",
|
|
1710
|
+
message: `seriesOrder ${article.seriesOrder} in series "${article.seriesSlug}" collides with "${owner}".`,
|
|
1711
|
+
articleSlug: article.slug
|
|
1712
|
+
});
|
|
1713
|
+
} else {
|
|
1714
|
+
seenSlugOrder.set(key, article.slug);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
return issues;
|
|
1718
|
+
}
|
|
1719
|
+
function checkPaths(articles, config) {
|
|
1720
|
+
var _a;
|
|
1721
|
+
const issues = [];
|
|
1722
|
+
const bySlug = new Map(articles.map((article) => [article.slug, article]));
|
|
1723
|
+
for (const [pathKey, path2] of Object.entries((_a = config.paths) != null ? _a : {})) {
|
|
1724
|
+
if (path2.articles.length === 0) {
|
|
1725
|
+
issues.push({
|
|
1726
|
+
severity: "error",
|
|
1727
|
+
code: "empty-path",
|
|
1728
|
+
message: `Path "${pathKey}" has no articles.`,
|
|
1729
|
+
pathKey
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
for (const slug of path2.articles) {
|
|
1733
|
+
const referenced = bySlug.get(slug);
|
|
1734
|
+
if (!referenced) {
|
|
1735
|
+
issues.push({
|
|
1736
|
+
severity: "error",
|
|
1737
|
+
code: "path-missing-article",
|
|
1738
|
+
message: `Path "${pathKey}" references missing article "${slug}".`,
|
|
1739
|
+
pathKey,
|
|
1740
|
+
articleSlug: slug
|
|
1741
|
+
});
|
|
1742
|
+
} else if (referenced.draft) {
|
|
1743
|
+
issues.push({
|
|
1744
|
+
severity: "error",
|
|
1745
|
+
code: "path-references-draft",
|
|
1746
|
+
message: `Path "${pathKey}" references unpublished (draft) article "${slug}".`,
|
|
1747
|
+
pathKey,
|
|
1748
|
+
articleSlug: slug
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
if (isUnsafeUrl(path2.nextAction.href)) {
|
|
1753
|
+
issues.push({
|
|
1754
|
+
severity: "error",
|
|
1755
|
+
code: "unsafe-url",
|
|
1756
|
+
message: `Path "${pathKey}" nextAction.href uses an unsafe URL scheme.`,
|
|
1757
|
+
pathKey
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
return issues;
|
|
1762
|
+
}
|
|
1763
|
+
function checkAuthorCtaUrls(config) {
|
|
1764
|
+
var _a;
|
|
1765
|
+
const issues = [];
|
|
1766
|
+
for (const author of Object.values((_a = config.authors) != null ? _a : {})) {
|
|
1767
|
+
if (author.primaryCta && isUnsafeUrl(author.primaryCta.href)) {
|
|
1768
|
+
issues.push({
|
|
1769
|
+
severity: "error",
|
|
1770
|
+
code: "unsafe-url",
|
|
1771
|
+
message: `Author "${author.slug}" primaryCta.href uses an unsafe URL scheme.`
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return issues;
|
|
1776
|
+
}
|
|
1777
|
+
function checkRequiredFrontmatter(articles) {
|
|
1778
|
+
const issues = [];
|
|
1779
|
+
for (const article of articles) {
|
|
1780
|
+
if (!article.excerpt) {
|
|
1781
|
+
issues.push({
|
|
1782
|
+
severity: "warning",
|
|
1783
|
+
code: "missing-excerpt",
|
|
1784
|
+
message: "Article has no excerpt.",
|
|
1785
|
+
articleSlug: article.slug
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
if (!article.date) {
|
|
1789
|
+
issues.push({
|
|
1790
|
+
severity: "warning",
|
|
1791
|
+
code: "missing-date",
|
|
1792
|
+
message: "Article has no date.",
|
|
1793
|
+
articleSlug: article.slug
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return issues;
|
|
1798
|
+
}
|
|
1799
|
+
function checkDiscoveryFieldLengths(articles) {
|
|
1800
|
+
const issues = [];
|
|
1801
|
+
for (const article of articles) {
|
|
1802
|
+
const checks = [
|
|
1803
|
+
[article.searchTitle, "search-title-too-long", SEARCH_TITLE_MAX],
|
|
1804
|
+
[article.searchDescription, "search-description-too-long", SEARCH_DESCRIPTION_MAX],
|
|
1805
|
+
[article.socialTitle, "social-title-too-long", SOCIAL_TITLE_MAX],
|
|
1806
|
+
[article.socialDescription, "social-description-too-long", SOCIAL_DESCRIPTION_MAX]
|
|
1807
|
+
];
|
|
1808
|
+
for (const [value, code, max] of checks) {
|
|
1809
|
+
if (value && value.length > max) {
|
|
1810
|
+
issues.push({
|
|
1811
|
+
severity: "warning",
|
|
1812
|
+
code,
|
|
1813
|
+
message: `${code.replaceAll("-", " ")} (${value.length} > ${max} recommended chars).`,
|
|
1814
|
+
articleSlug: article.slug
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
return issues;
|
|
1820
|
+
}
|
|
1821
|
+
function checkCategorySlugs(articles) {
|
|
1822
|
+
var _a;
|
|
1823
|
+
const issues = [];
|
|
1824
|
+
const slugToNames = /* @__PURE__ */ new Map();
|
|
1825
|
+
for (const article of articles) {
|
|
1826
|
+
for (const category of article.categories) {
|
|
1827
|
+
const slug = categoryToSlug(category);
|
|
1828
|
+
const names = (_a = slugToNames.get(slug)) != null ? _a : /* @__PURE__ */ new Set();
|
|
1829
|
+
names.add(category);
|
|
1830
|
+
slugToNames.set(slug, names);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
for (const [slug, names] of slugToNames) {
|
|
1834
|
+
if (names.size > 1) {
|
|
1835
|
+
issues.push({
|
|
1836
|
+
severity: "warning",
|
|
1837
|
+
code: "category-slug-collision",
|
|
1838
|
+
message: `Categories [${[...names].join(", ")}] all collapse to slug "${slug}".`
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
return issues;
|
|
1843
|
+
}
|
|
1844
|
+
function validateArticles(articles, config) {
|
|
1845
|
+
const errors = [
|
|
1846
|
+
...checkDuplicateCanonicalUrls(articles),
|
|
1847
|
+
...checkAuthorReferences(articles, config),
|
|
1848
|
+
...checkSeriesCollisions(articles),
|
|
1849
|
+
...checkPaths(articles, config),
|
|
1850
|
+
...checkAuthorCtaUrls(config)
|
|
1851
|
+
];
|
|
1852
|
+
const warnings = [
|
|
1853
|
+
...checkRequiredFrontmatter(articles),
|
|
1854
|
+
...checkDiscoveryFieldLengths(articles),
|
|
1855
|
+
...checkCategorySlugs(articles)
|
|
1856
|
+
];
|
|
1857
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
1858
|
+
}
|
|
1859
|
+
function validateAllArticles(config) {
|
|
1860
|
+
return __async(this, null, function* () {
|
|
1861
|
+
const articles = yield getAllArticles(config);
|
|
1862
|
+
return validateArticles(articles, config);
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// src/events.ts
|
|
1867
|
+
function emitArticleEvent(handler, event) {
|
|
1868
|
+
if (!handler) return;
|
|
1869
|
+
try {
|
|
1870
|
+
handler(__spreadProps(__spreadValues({}, event), { timestamp: Date.now() }));
|
|
1871
|
+
} catch (e) {
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1284
1874
|
export {
|
|
1285
1875
|
ArticleContent,
|
|
1286
1876
|
ArticleTOC,
|
|
1287
1877
|
buildArticleBreadcrumbs,
|
|
1288
1878
|
buildAuthorBreadcrumbs,
|
|
1289
1879
|
buildCategoryBreadcrumbs,
|
|
1880
|
+
buildPageUrl,
|
|
1881
|
+
buildPaginationLinks,
|
|
1290
1882
|
categoryToSlug,
|
|
1883
|
+
emitArticleEvent,
|
|
1291
1884
|
extractToc,
|
|
1292
1885
|
generateArticleMetadata,
|
|
1293
1886
|
generateArticleStaticParams,
|
|
1294
1887
|
generateArticlesIndexMetadata,
|
|
1888
|
+
generateArticlesIndexPageMetadata,
|
|
1295
1889
|
generateAuthorMetadata,
|
|
1890
|
+
generateAuthorPageMetadata,
|
|
1296
1891
|
generateAuthorStaticParams,
|
|
1297
1892
|
generateCategoryMetadata,
|
|
1893
|
+
generateCategoryPageMetadata,
|
|
1298
1894
|
generateCategoryStaticParams,
|
|
1895
|
+
generateListingPageStaticParams,
|
|
1299
1896
|
generateRssFeed,
|
|
1897
|
+
generateSeriesMetadata,
|
|
1898
|
+
generateSeriesStaticParams,
|
|
1300
1899
|
getAdjacentArticles,
|
|
1900
|
+
getAdjacentArticlesInSeries,
|
|
1301
1901
|
getAiRobotsTxtRules,
|
|
1302
1902
|
getAllArticles,
|
|
1303
1903
|
getAllAuthors,
|
|
@@ -1311,13 +1911,27 @@ export {
|
|
|
1311
1911
|
getArticleSitemapEntries,
|
|
1312
1912
|
getArticlesByAuthor,
|
|
1313
1913
|
getArticlesByCategory,
|
|
1914
|
+
getArticlesBySeries,
|
|
1314
1915
|
getAuthorBySlug,
|
|
1315
1916
|
getAvailableArticleSlugs,
|
|
1316
1917
|
getBreadcrumbsConfig,
|
|
1918
|
+
getContentSlotBoundaries,
|
|
1919
|
+
getPath,
|
|
1920
|
+
getPathArticles,
|
|
1921
|
+
getRelatedArticlesByCategory,
|
|
1922
|
+
getRelatedContent,
|
|
1923
|
+
getTotalPages,
|
|
1924
|
+
isPageOutOfRange,
|
|
1317
1925
|
markdownToHtml,
|
|
1926
|
+
paginateArticles,
|
|
1927
|
+
parsePageParam,
|
|
1318
1928
|
resolveAuthorAvatar,
|
|
1929
|
+
resolveSearchMetadata,
|
|
1930
|
+
resolveSocialMetadata,
|
|
1319
1931
|
sanitizeImagePath2 as sanitizeImagePath,
|
|
1320
1932
|
searchArticles,
|
|
1321
|
-
setArticlesErrorHandler
|
|
1933
|
+
setArticlesErrorHandler,
|
|
1934
|
+
validateAllArticles,
|
|
1935
|
+
validateArticles
|
|
1322
1936
|
};
|
|
1323
1937
|
//# sourceMappingURL=server.js.map
|