@fullstackdatasolutions/articles 1.2.2 → 1.3.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 +46 -0
- package/README.md +313 -1
- package/dist/index.cjs +308 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +267 -16
- package/dist/index.d.ts +267 -16
- package/dist/index.js +300 -79
- package/dist/index.js.map +1 -1
- package/dist/nextjs.cjs +324 -13
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.d.cts +179 -2
- package/dist/nextjs.d.ts +179 -2
- package/dist/nextjs.js +324 -13
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +677 -51
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +333 -12
- package/dist/server.d.ts +333 -12
- package/dist/server.js +662 -51
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/ArticleAnswer.tsx +35 -0
- package/src/ArticleSchemas.tsx +263 -23
- package/src/AuthorArticlesPage.tsx +38 -8
- package/src/__tests__/ArticleAnswer.test.tsx +25 -0
- package/src/__tests__/ArticleSchemas.test.tsx +516 -0
- package/src/__tests__/AuthorArticlesPage.test.tsx +76 -0
- package/src/__tests__/authorUtils.test.ts +50 -0
- package/src/__tests__/linkClassification.test.ts +55 -0
- package/src/__tests__/markdown.test.ts +77 -1
- package/src/__tests__/nextjs.test.ts +31 -15
- package/src/__tests__/renderMdx.test.tsx +162 -3
- package/src/__tests__/seoUtils.test.ts +279 -0
- package/src/__tests__/server-articles.test.ts +413 -1
- package/src/__tests__/validateArticles.test.ts +167 -6
- package/src/articleTypes.ts +57 -0
- package/src/articlesConfig.ts +176 -1
- package/src/authorUtils.ts +19 -1
- package/src/errorReporting.ts +1 -0
- package/src/index.ts +17 -1
- package/src/linkClassification.ts +30 -0
- package/src/markdown.ts +103 -25
- package/src/nextjs.ts +7 -4
- package/src/renderMdx.tsx +43 -6
- package/src/seoUtils.ts +247 -26
- package/src/server-articles.ts +375 -24
- package/src/server.ts +35 -4
- package/src/validateArticles.ts +157 -12
package/dist/nextjs.cjs
CHANGED
|
@@ -113,8 +113,7 @@ function reportArticlesError(report) {
|
|
|
113
113
|
articlesErrorHandler(report);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// src/
|
|
117
|
-
var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
|
|
116
|
+
// src/linkClassification.ts
|
|
118
117
|
function isNonBrowserNavigationLink(href) {
|
|
119
118
|
return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href) && !href.startsWith("http://") && !href.startsWith("https://");
|
|
120
119
|
}
|
|
@@ -132,6 +131,9 @@ function isExternalHttpLink(href, siteUrl) {
|
|
|
132
131
|
if (!siteOrigin) return true;
|
|
133
132
|
return getOrigin(href) !== siteOrigin;
|
|
134
133
|
}
|
|
134
|
+
|
|
135
|
+
// src/markdown.ts
|
|
136
|
+
var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
|
|
135
137
|
function shouldOpenInNewTab(href, options = {}) {
|
|
136
138
|
var _a;
|
|
137
139
|
if (!href || href.startsWith("#") || isNonBrowserNavigationLink(href)) return false;
|
|
@@ -431,6 +433,64 @@ function extractToc(markdown) {
|
|
|
431
433
|
return headings;
|
|
432
434
|
});
|
|
433
435
|
}
|
|
436
|
+
var QUESTION_OPENERS = /^(what|how|why|when|where|who|which|can|should|does|do|is|are|will|would|must)\b/i;
|
|
437
|
+
function deriveFaqFromHeadings(markdown) {
|
|
438
|
+
const items = [];
|
|
439
|
+
const collector = createFaqCollector(items);
|
|
440
|
+
let inFence = false;
|
|
441
|
+
for (const line of markdown.split("\n")) {
|
|
442
|
+
if (FENCE.test(line)) {
|
|
443
|
+
inFence = !inFence;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (inFence) continue;
|
|
447
|
+
collector.consume(line);
|
|
448
|
+
}
|
|
449
|
+
collector.flush();
|
|
450
|
+
return items;
|
|
451
|
+
}
|
|
452
|
+
var FENCE = /^\s*(```|~~~)/;
|
|
453
|
+
var ANY_HEADING = /^#{1,6}\s/;
|
|
454
|
+
function isSpace(char) {
|
|
455
|
+
return char === " " || char === " ";
|
|
456
|
+
}
|
|
457
|
+
function trimTrailingHashes(value) {
|
|
458
|
+
let end = value.length;
|
|
459
|
+
while (end > 0 && value[end - 1] === "#") end--;
|
|
460
|
+
return value.slice(0, end).trimEnd();
|
|
461
|
+
}
|
|
462
|
+
function readQuestionHeading(line) {
|
|
463
|
+
if (!line.startsWith("##") || line.startsWith("###")) return null;
|
|
464
|
+
if (!isSpace(line[2])) return null;
|
|
465
|
+
const text = trimTrailingHashes(line.slice(3).trim());
|
|
466
|
+
if (!text.endsWith("?") || !QUESTION_OPENERS.test(text)) return null;
|
|
467
|
+
return text;
|
|
468
|
+
}
|
|
469
|
+
function createFaqCollector(items) {
|
|
470
|
+
let pending = null;
|
|
471
|
+
let buffer = [];
|
|
472
|
+
const flush = () => {
|
|
473
|
+
if (pending && buffer.length > 0) {
|
|
474
|
+
items.push({ question: pending, answer: buffer.join(" ").trim() });
|
|
475
|
+
}
|
|
476
|
+
pending = null;
|
|
477
|
+
buffer = [];
|
|
478
|
+
};
|
|
479
|
+
const consume = (line) => {
|
|
480
|
+
if (ANY_HEADING.test(line)) {
|
|
481
|
+
flush();
|
|
482
|
+
pending = readQuestionHeading(line);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (!pending) return;
|
|
486
|
+
if (line.trim() === "") {
|
|
487
|
+
if (buffer.length > 0) flush();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
buffer.push(line.trim());
|
|
491
|
+
};
|
|
492
|
+
return { consume, flush };
|
|
493
|
+
}
|
|
434
494
|
|
|
435
495
|
// src/server-articles.ts
|
|
436
496
|
var articlesDirectory = import_node_path.default.join(
|
|
@@ -563,6 +623,58 @@ function parseHowToSteps(raw) {
|
|
|
563
623
|
function parseOptionalString(raw) {
|
|
564
624
|
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
565
625
|
}
|
|
626
|
+
function deriveFaq(markdownContent, config) {
|
|
627
|
+
if ((config == null ? void 0 : config.deriveFaqFromHeadings) !== true) return void 0;
|
|
628
|
+
const derived = deriveFaqFromHeadings(markdownContent);
|
|
629
|
+
return derived.length ? derived : void 0;
|
|
630
|
+
}
|
|
631
|
+
function resolveLastmod(rawLastmod, rawDate, filePath, config) {
|
|
632
|
+
const declared = parseDateField(rawLastmod);
|
|
633
|
+
if (declared) return declared;
|
|
634
|
+
if ((config == null ? void 0 : config.lastmodFallback) !== "fileMtime") return void 0;
|
|
635
|
+
try {
|
|
636
|
+
return parseDateField(import_node_fs.default.statSync(filePath).mtime);
|
|
637
|
+
} catch (e) {
|
|
638
|
+
return parseDateField(rawDate);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function resolveAiCrawl(raw, config) {
|
|
642
|
+
if (typeof raw === "boolean") return raw;
|
|
643
|
+
return (config == null ? void 0 : config.aiCrawlDefault) === true;
|
|
644
|
+
}
|
|
645
|
+
function parseEntityReferences(raw, config) {
|
|
646
|
+
if (!Array.isArray(raw)) return void 0;
|
|
647
|
+
const items = raw.map((item) => {
|
|
648
|
+
var _a, _b;
|
|
649
|
+
if (typeof item === "string") {
|
|
650
|
+
const key = item.trim();
|
|
651
|
+
if (!key) return null;
|
|
652
|
+
return (_b = (_a = config == null ? void 0 : config.entities) == null ? void 0 : _a[key]) != null ? _b : { name: key };
|
|
653
|
+
}
|
|
654
|
+
if (typeof item === "object" && item !== null && typeof item.name === "string") {
|
|
655
|
+
const entity = item;
|
|
656
|
+
const name = entity.name.trim();
|
|
657
|
+
if (!name) return null;
|
|
658
|
+
return entity.sameAs ? { name, sameAs: entity.sameAs } : { name };
|
|
659
|
+
}
|
|
660
|
+
return null;
|
|
661
|
+
}).filter((item) => item !== null);
|
|
662
|
+
return items.length ? items : void 0;
|
|
663
|
+
}
|
|
664
|
+
function parseCitations(raw) {
|
|
665
|
+
if (!Array.isArray(raw)) return void 0;
|
|
666
|
+
const items = raw.map((item) => {
|
|
667
|
+
if (typeof item === "string") return item.trim() ? { name: item.trim() } : null;
|
|
668
|
+
if (typeof item === "object" && item !== null && typeof item.name === "string") {
|
|
669
|
+
const citation = item;
|
|
670
|
+
const name = citation.name.trim();
|
|
671
|
+
if (!name) return null;
|
|
672
|
+
return citation.url ? { name, url: citation.url } : { name };
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
}).filter((item) => item !== null);
|
|
676
|
+
return items.length ? items : void 0;
|
|
677
|
+
}
|
|
566
678
|
function parseSeriesOrder(raw) {
|
|
567
679
|
return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
|
|
568
680
|
}
|
|
@@ -635,6 +747,7 @@ function getArticleAuthors(article, config) {
|
|
|
635
747
|
}
|
|
636
748
|
function getArticleSummary(slug, config) {
|
|
637
749
|
return __async(this, null, function* () {
|
|
750
|
+
var _a;
|
|
638
751
|
try {
|
|
639
752
|
const found = findArticleFile(slug);
|
|
640
753
|
if (!found) return null;
|
|
@@ -651,7 +764,7 @@ function getArticleSummary(slug, config) {
|
|
|
651
764
|
title: data.title || slug.replaceAll("-", " "),
|
|
652
765
|
excerpt: data.excerpt || "",
|
|
653
766
|
date: parseDateField(data.date),
|
|
654
|
-
lastmod:
|
|
767
|
+
lastmod: resolveLastmod(data.lastmod, data.date, found.filePath, config),
|
|
655
768
|
author,
|
|
656
769
|
authors,
|
|
657
770
|
authorSlug: primaryAuthorProfile == null ? void 0 : primaryAuthorProfile.slug,
|
|
@@ -664,14 +777,17 @@ function getArticleSummary(slug, config) {
|
|
|
664
777
|
tags: data.tags || [],
|
|
665
778
|
contentType: found.contentType,
|
|
666
779
|
draft: data.draft === true,
|
|
667
|
-
faq: parseFaqItems(data.faq),
|
|
780
|
+
faq: (_a = parseFaqItems(data.faq)) != null ? _a : deriveFaq(markdownContent, config),
|
|
668
781
|
howTo: parseHowToSteps(data.howTo),
|
|
782
|
+
answer: parseOptionalString(data.answer),
|
|
783
|
+
about: parseEntityReferences(data.about, config),
|
|
784
|
+
citation: parseCitations(data.citation),
|
|
669
785
|
canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
|
|
670
786
|
articleType: typeof data.articleType === "string" ? data.articleType : void 0,
|
|
671
787
|
series: typeof data.series === "string" ? data.series : void 0,
|
|
672
788
|
seriesSlug: parseOptionalString(data.seriesSlug),
|
|
673
789
|
seriesOrder: parseSeriesOrder(data.seriesOrder),
|
|
674
|
-
aiCrawl: data.aiCrawl
|
|
790
|
+
aiCrawl: resolveAiCrawl(data.aiCrawl, config),
|
|
675
791
|
searchTitle: parseOptionalString(data.searchTitle),
|
|
676
792
|
searchDescription: parseOptionalString(data.searchDescription),
|
|
677
793
|
socialTitle: parseOptionalString(data.socialTitle),
|
|
@@ -730,10 +846,10 @@ var getAllArticles = (0, import_react.cache)((config) => __async(void 0, null, f
|
|
|
730
846
|
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
|
731
847
|
});
|
|
732
848
|
}));
|
|
733
|
-
function getArticleMarkdown(slug) {
|
|
849
|
+
function getArticleMarkdown(slug, config) {
|
|
734
850
|
return __async(this, null, function* () {
|
|
735
851
|
try {
|
|
736
|
-
const summary = yield getArticleSummary(slug);
|
|
852
|
+
const summary = yield getArticleSummary(slug, config);
|
|
737
853
|
if (!(summary == null ? void 0 : summary.aiCrawl)) return null;
|
|
738
854
|
const found = findArticleFile(slug);
|
|
739
855
|
if (!found) return null;
|
|
@@ -751,12 +867,180 @@ function getArticleMarkdown(slug) {
|
|
|
751
867
|
}
|
|
752
868
|
});
|
|
753
869
|
}
|
|
754
|
-
|
|
870
|
+
var AI_CRAWLERS = [
|
|
871
|
+
"GPTBot",
|
|
872
|
+
"ChatGPT-User",
|
|
873
|
+
"OAI-SearchBot",
|
|
874
|
+
"CCBot",
|
|
875
|
+
"ClaudeBot",
|
|
876
|
+
"Claude-User",
|
|
877
|
+
"Claude-SearchBot",
|
|
878
|
+
"anthropic-ai",
|
|
879
|
+
"PerplexityBot",
|
|
880
|
+
"Perplexity-User",
|
|
881
|
+
"Google-Extended",
|
|
882
|
+
"Applebot-Extended",
|
|
883
|
+
"Bytespider",
|
|
884
|
+
"Amazonbot",
|
|
885
|
+
"meta-externalagent",
|
|
886
|
+
"cohere-ai",
|
|
887
|
+
"DuckAssistBot",
|
|
888
|
+
"MistralAI-User"
|
|
889
|
+
];
|
|
890
|
+
function matchAiCrawler(userAgent) {
|
|
891
|
+
var _a;
|
|
892
|
+
if (!userAgent) return null;
|
|
893
|
+
const normalized = userAgent.toLowerCase();
|
|
894
|
+
return (_a = AI_CRAWLERS.find((crawler) => normalized.includes(crawler.toLowerCase()))) != null ? _a : null;
|
|
895
|
+
}
|
|
896
|
+
function buildMarkdownTwinHeader(article, config, body) {
|
|
897
|
+
var _a, _b;
|
|
898
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
899
|
+
const firstLine = (_b = (_a = body.trimStart().split("\n", 1)[0]) == null ? void 0 : _a.trim()) != null ? _b : "";
|
|
900
|
+
const bodyRepeatsTitle = firstLine.toLowerCase() === `# ${article.title}`.toLowerCase();
|
|
901
|
+
const facts = [
|
|
902
|
+
`Source: ${siteUrl}/articles/${article.slug}`,
|
|
903
|
+
article.date ? `Published: ${article.date}` : "",
|
|
904
|
+
article.lastmod ? `Updated: ${article.lastmod}` : "",
|
|
905
|
+
config.showAuthor !== false && article.author ? `Author: ${article.author}` : "",
|
|
906
|
+
`Site: ${config.siteName}`
|
|
907
|
+
].filter(Boolean);
|
|
908
|
+
const blocks = [
|
|
909
|
+
bodyRepeatsTitle ? "" : `# ${article.title}`,
|
|
910
|
+
article.excerpt ? `> ${article.excerpt}` : "",
|
|
911
|
+
facts.join("\n"),
|
|
912
|
+
article.answer ? `**Short answer:** ${article.answer}` : "",
|
|
913
|
+
"---"
|
|
914
|
+
].filter((block) => block !== "");
|
|
915
|
+
return `${blocks.join("\n\n")}
|
|
916
|
+
|
|
917
|
+
`;
|
|
918
|
+
}
|
|
919
|
+
function reportAiCrawl(slug, config, headers) {
|
|
920
|
+
var _a, _b;
|
|
921
|
+
if (!config.onAiCrawl) return;
|
|
922
|
+
const userAgent = (_a = headers == null ? void 0 : headers.get("user-agent")) != null ? _a : "";
|
|
923
|
+
try {
|
|
924
|
+
config.onAiCrawl({ slug, crawler: (_b = matchAiCrawler(userAgent)) != null ? _b : "unknown", userAgent });
|
|
925
|
+
} catch (error) {
|
|
926
|
+
reportArticlesError({
|
|
927
|
+
code: "ai-crawl-handler-failed",
|
|
928
|
+
message: "onAiCrawl handler threw.",
|
|
929
|
+
error,
|
|
930
|
+
context: { slug }
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function buildListingMarkdown(heading, intro, articles, config) {
|
|
935
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
936
|
+
const crawlable = articles.filter((article) => article.aiCrawl === true);
|
|
937
|
+
const entries = crawlable.map((article) => {
|
|
938
|
+
var _a;
|
|
939
|
+
const summary = (_a = article.answer) != null ? _a : article.excerpt;
|
|
940
|
+
const line = `- [${article.title}](${siteUrl}/articles/${article.slug}.md)`;
|
|
941
|
+
return summary ? `${line}: ${summary}` : line;
|
|
942
|
+
});
|
|
943
|
+
return [
|
|
944
|
+
`# ${heading}`,
|
|
945
|
+
"",
|
|
946
|
+
...intro.flatMap((line) => [line, ""]),
|
|
947
|
+
`Source: ${siteUrl}`,
|
|
948
|
+
`Site: ${config.siteName}`,
|
|
949
|
+
"",
|
|
950
|
+
"---",
|
|
951
|
+
"",
|
|
952
|
+
...entries.length > 0 ? entries : ["_No articles available._"],
|
|
953
|
+
""
|
|
954
|
+
].join("\n");
|
|
955
|
+
}
|
|
956
|
+
function getCategoryMarkdown(categorySlug, config) {
|
|
957
|
+
return __async(this, null, function* () {
|
|
958
|
+
var _a;
|
|
959
|
+
const articles = yield getArticlesByCategory(categorySlug, config);
|
|
960
|
+
if (articles.length === 0) return null;
|
|
961
|
+
const name = (_a = articles[0].categories.find((c) => categoryToSlug(c) === categorySlug)) != null ? _a : categorySlug;
|
|
962
|
+
const description = resolveCategoryDescription(categorySlug, config);
|
|
963
|
+
return buildListingMarkdown(name, description ? [description] : [], articles, config);
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
function resolveCategoryDescription(categorySlug, config) {
|
|
967
|
+
var _a, _b;
|
|
968
|
+
const entry = (_a = config.categoryDescriptions) == null ? void 0 : _a[categorySlug];
|
|
969
|
+
if (!entry) return void 0;
|
|
970
|
+
return typeof entry === "string" ? entry : (_b = entry.long) != null ? _b : entry.short;
|
|
971
|
+
}
|
|
972
|
+
function getAuthorMarkdown(authorSlug, config) {
|
|
973
|
+
return __async(this, null, function* () {
|
|
974
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
975
|
+
const author = getAuthorBySlug(authorSlug, config);
|
|
976
|
+
if (!author) return null;
|
|
977
|
+
const articles = yield getArticlesByAuthor(authorSlug, config);
|
|
978
|
+
const intro = [
|
|
979
|
+
(_a = author.promise) != null ? _a : "",
|
|
980
|
+
(_b = author.bio) != null ? _b : "",
|
|
981
|
+
...((_c = author.servesWho) == null ? void 0 : _c.length) ? [`Writes for: ${author.servesWho.join(", ")}`] : [],
|
|
982
|
+
...((_d = author.knowsAbout) == null ? void 0 : _d.length) ? [`Writes about: ${author.knowsAbout.join(", ")}`] : [],
|
|
983
|
+
...((_e = author.credentials) == null ? void 0 : _e.length) ? ["## Stated experience", ...author.credentials.map((item) => `- ${item}`)] : [],
|
|
984
|
+
...((_f = author.proof) == null ? void 0 : _f.length) ? [
|
|
985
|
+
"## Proof points",
|
|
986
|
+
...author.proof.map(
|
|
987
|
+
(item) => item.url ? `- [${item.claim}](${item.url})` : `- ${item.claim}`
|
|
988
|
+
)
|
|
989
|
+
] : [],
|
|
990
|
+
...((_g = author.originStory) == null ? void 0 : _g.length) ? [
|
|
991
|
+
"## Background",
|
|
992
|
+
...author.originStory.flatMap((section) => [
|
|
993
|
+
...section.heading ? [`### ${section.heading}`] : [],
|
|
994
|
+
...section.paragraphs
|
|
995
|
+
])
|
|
996
|
+
] : []
|
|
997
|
+
].filter((line) => line.trim() !== "");
|
|
998
|
+
return buildListingMarkdown(author.name, intro, articles, config);
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
function getSeriesMarkdown(seriesSlug, config) {
|
|
1002
|
+
return __async(this, null, function* () {
|
|
1003
|
+
var _a;
|
|
1004
|
+
const articles = yield getArticlesBySeries(seriesSlug, config);
|
|
1005
|
+
if (articles.length === 0) return null;
|
|
1006
|
+
const name = (_a = articles[0].series) != null ? _a : seriesSlug;
|
|
1007
|
+
return buildListingMarkdown(name, [], articles, config);
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
function getMarkdownTwinResponse(slug, config, options) {
|
|
1011
|
+
return __async(this, null, function* () {
|
|
1012
|
+
const listing = yield resolveListingMarkdown(slug, config);
|
|
1013
|
+
if (listing !== void 0) {
|
|
1014
|
+
if (listing === null) return new Response("Not Found", { status: 404 });
|
|
1015
|
+
reportAiCrawl(slug, config, options == null ? void 0 : options.headers);
|
|
1016
|
+
return new Response(listing, { headers: LISTING_MARKDOWN_HEADERS });
|
|
1017
|
+
}
|
|
1018
|
+
return getArticleMarkdownResponse(slug, config, options);
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
var LISTING_MARKDOWN_HEADERS = {
|
|
1022
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
1023
|
+
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
1024
|
+
};
|
|
1025
|
+
function resolveListingMarkdown(slug, config) {
|
|
1026
|
+
return __async(this, null, function* () {
|
|
1027
|
+
const [prefix, ...rest] = slug.split("/");
|
|
1028
|
+
const key = rest.join("/");
|
|
1029
|
+
if (!key) return void 0;
|
|
1030
|
+
if (prefix === "category") return getCategoryMarkdown(key, config);
|
|
1031
|
+
if (prefix === "authors") return getAuthorMarkdown(key, config);
|
|
1032
|
+
if (prefix === "series") return getSeriesMarkdown(key, config);
|
|
1033
|
+
return void 0;
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
function getArticleMarkdownResponse(slug, config, options) {
|
|
755
1037
|
return __async(this, null, function* () {
|
|
756
|
-
const markdown = yield getArticleMarkdown(slug);
|
|
1038
|
+
const markdown = yield getArticleMarkdown(slug, config);
|
|
757
1039
|
if (markdown === null) return new Response("Not Found", { status: 404 });
|
|
758
|
-
const article = yield getArticleMetadata(slug);
|
|
759
|
-
|
|
1040
|
+
const article = yield getArticleMetadata(slug, config);
|
|
1041
|
+
reportAiCrawl(slug, config, options == null ? void 0 : options.headers);
|
|
1042
|
+
const body = article && config.markdownTwinHeader !== false ? `${buildMarkdownTwinHeader(article, config, markdown)}${markdown.trimStart()}` : markdown;
|
|
1043
|
+
return new Response(body, {
|
|
760
1044
|
headers: __spreadValues({
|
|
761
1045
|
"Content-Type": "text/markdown; charset=utf-8",
|
|
762
1046
|
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
@@ -784,16 +1068,43 @@ function getArticleAiHeaders(article, config) {
|
|
|
784
1068
|
function categoryToSlug(category) {
|
|
785
1069
|
return category.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
|
|
786
1070
|
}
|
|
1071
|
+
function getArticlesByCategory(categorySlug, config) {
|
|
1072
|
+
return __async(this, null, function* () {
|
|
1073
|
+
const articles = yield getAllArticles(config);
|
|
1074
|
+
return articles.filter(
|
|
1075
|
+
(article) => article.categories.some((cat) => categoryToSlug(cat) === categorySlug)
|
|
1076
|
+
);
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
function getArticlesByAuthor(authorSlug, config) {
|
|
1080
|
+
return __async(this, null, function* () {
|
|
1081
|
+
const articles = yield getAllArticles(config);
|
|
1082
|
+
return articles.filter(
|
|
1083
|
+
(article) => getArticleAuthors(article, config).some((author) => author.slug === authorSlug)
|
|
1084
|
+
);
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
function getArticlesBySeries(seriesSlug, config) {
|
|
1088
|
+
return __async(this, null, function* () {
|
|
1089
|
+
const articles = yield getAllArticles(config);
|
|
1090
|
+
return articles.filter((article) => article.seriesSlug === seriesSlug).sort((a, b) => {
|
|
1091
|
+
var _a, _b;
|
|
1092
|
+
const orderA = (_a = a.seriesOrder) != null ? _a : Number.POSITIVE_INFINITY;
|
|
1093
|
+
const orderB = (_b = b.seriesOrder) != null ? _b : Number.POSITIVE_INFINITY;
|
|
1094
|
+
return orderA - orderB;
|
|
1095
|
+
});
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
787
1098
|
|
|
788
1099
|
// src/nextjs.ts
|
|
789
1100
|
function createArticleMarkdownHandler(config) {
|
|
790
|
-
function GET(
|
|
1101
|
+
function GET(request, context) {
|
|
791
1102
|
return __async(this, null, function* () {
|
|
792
1103
|
const params = yield context.params;
|
|
793
1104
|
const slugParts = params["slug"];
|
|
794
1105
|
const slug = Array.isArray(slugParts) ? slugParts.join("/") : slugParts != null ? slugParts : "";
|
|
795
1106
|
const cleanSlug = slug.endsWith(".md") ? slug.slice(0, -3) : slug;
|
|
796
|
-
return
|
|
1107
|
+
return getMarkdownTwinResponse(cleanSlug, config, { headers: request.headers });
|
|
797
1108
|
});
|
|
798
1109
|
}
|
|
799
1110
|
return { GET };
|