@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.js
CHANGED
|
@@ -80,8 +80,7 @@ function reportArticlesError(report) {
|
|
|
80
80
|
articlesErrorHandler(report);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// src/
|
|
84
|
-
var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
|
|
83
|
+
// src/linkClassification.ts
|
|
85
84
|
function isNonBrowserNavigationLink(href) {
|
|
86
85
|
return /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(href) && !href.startsWith("http://") && !href.startsWith("https://");
|
|
87
86
|
}
|
|
@@ -99,6 +98,9 @@ function isExternalHttpLink(href, siteUrl) {
|
|
|
99
98
|
if (!siteOrigin) return true;
|
|
100
99
|
return getOrigin(href) !== siteOrigin;
|
|
101
100
|
}
|
|
101
|
+
|
|
102
|
+
// src/markdown.ts
|
|
103
|
+
var DEFAULT_LINK_TARGET_STRATEGY = "external-new-tab";
|
|
102
104
|
function shouldOpenInNewTab(href, options = {}) {
|
|
103
105
|
var _a;
|
|
104
106
|
if (!href || href.startsWith("#") || isNonBrowserNavigationLink(href)) return false;
|
|
@@ -398,6 +400,64 @@ function extractToc(markdown) {
|
|
|
398
400
|
return headings;
|
|
399
401
|
});
|
|
400
402
|
}
|
|
403
|
+
var QUESTION_OPENERS = /^(what|how|why|when|where|who|which|can|should|does|do|is|are|will|would|must)\b/i;
|
|
404
|
+
function deriveFaqFromHeadings(markdown) {
|
|
405
|
+
const items = [];
|
|
406
|
+
const collector = createFaqCollector(items);
|
|
407
|
+
let inFence = false;
|
|
408
|
+
for (const line of markdown.split("\n")) {
|
|
409
|
+
if (FENCE.test(line)) {
|
|
410
|
+
inFence = !inFence;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (inFence) continue;
|
|
414
|
+
collector.consume(line);
|
|
415
|
+
}
|
|
416
|
+
collector.flush();
|
|
417
|
+
return items;
|
|
418
|
+
}
|
|
419
|
+
var FENCE = /^\s*(```|~~~)/;
|
|
420
|
+
var ANY_HEADING = /^#{1,6}\s/;
|
|
421
|
+
function isSpace(char) {
|
|
422
|
+
return char === " " || char === " ";
|
|
423
|
+
}
|
|
424
|
+
function trimTrailingHashes(value) {
|
|
425
|
+
let end = value.length;
|
|
426
|
+
while (end > 0 && value[end - 1] === "#") end--;
|
|
427
|
+
return value.slice(0, end).trimEnd();
|
|
428
|
+
}
|
|
429
|
+
function readQuestionHeading(line) {
|
|
430
|
+
if (!line.startsWith("##") || line.startsWith("###")) return null;
|
|
431
|
+
if (!isSpace(line[2])) return null;
|
|
432
|
+
const text = trimTrailingHashes(line.slice(3).trim());
|
|
433
|
+
if (!text.endsWith("?") || !QUESTION_OPENERS.test(text)) return null;
|
|
434
|
+
return text;
|
|
435
|
+
}
|
|
436
|
+
function createFaqCollector(items) {
|
|
437
|
+
let pending = null;
|
|
438
|
+
let buffer = [];
|
|
439
|
+
const flush = () => {
|
|
440
|
+
if (pending && buffer.length > 0) {
|
|
441
|
+
items.push({ question: pending, answer: buffer.join(" ").trim() });
|
|
442
|
+
}
|
|
443
|
+
pending = null;
|
|
444
|
+
buffer = [];
|
|
445
|
+
};
|
|
446
|
+
const consume = (line) => {
|
|
447
|
+
if (ANY_HEADING.test(line)) {
|
|
448
|
+
flush();
|
|
449
|
+
pending = readQuestionHeading(line);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (!pending) return;
|
|
453
|
+
if (line.trim() === "") {
|
|
454
|
+
if (buffer.length > 0) flush();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
buffer.push(line.trim());
|
|
458
|
+
};
|
|
459
|
+
return { consume, flush };
|
|
460
|
+
}
|
|
401
461
|
|
|
402
462
|
// src/server-articles.ts
|
|
403
463
|
var articlesDirectory = path.join(
|
|
@@ -530,6 +590,58 @@ function parseHowToSteps(raw) {
|
|
|
530
590
|
function parseOptionalString(raw) {
|
|
531
591
|
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
532
592
|
}
|
|
593
|
+
function deriveFaq(markdownContent, config) {
|
|
594
|
+
if ((config == null ? void 0 : config.deriveFaqFromHeadings) !== true) return void 0;
|
|
595
|
+
const derived = deriveFaqFromHeadings(markdownContent);
|
|
596
|
+
return derived.length ? derived : void 0;
|
|
597
|
+
}
|
|
598
|
+
function resolveLastmod(rawLastmod, rawDate, filePath, config) {
|
|
599
|
+
const declared = parseDateField(rawLastmod);
|
|
600
|
+
if (declared) return declared;
|
|
601
|
+
if ((config == null ? void 0 : config.lastmodFallback) !== "fileMtime") return void 0;
|
|
602
|
+
try {
|
|
603
|
+
return parseDateField(fs.statSync(filePath).mtime);
|
|
604
|
+
} catch (e) {
|
|
605
|
+
return parseDateField(rawDate);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
function resolveAiCrawl(raw, config) {
|
|
609
|
+
if (typeof raw === "boolean") return raw;
|
|
610
|
+
return (config == null ? void 0 : config.aiCrawlDefault) === true;
|
|
611
|
+
}
|
|
612
|
+
function parseEntityReferences(raw, config) {
|
|
613
|
+
if (!Array.isArray(raw)) return void 0;
|
|
614
|
+
const items = raw.map((item) => {
|
|
615
|
+
var _a, _b;
|
|
616
|
+
if (typeof item === "string") {
|
|
617
|
+
const key = item.trim();
|
|
618
|
+
if (!key) return null;
|
|
619
|
+
return (_b = (_a = config == null ? void 0 : config.entities) == null ? void 0 : _a[key]) != null ? _b : { name: key };
|
|
620
|
+
}
|
|
621
|
+
if (typeof item === "object" && item !== null && typeof item.name === "string") {
|
|
622
|
+
const entity = item;
|
|
623
|
+
const name = entity.name.trim();
|
|
624
|
+
if (!name) return null;
|
|
625
|
+
return entity.sameAs ? { name, sameAs: entity.sameAs } : { name };
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
}).filter((item) => item !== null);
|
|
629
|
+
return items.length ? items : void 0;
|
|
630
|
+
}
|
|
631
|
+
function parseCitations(raw) {
|
|
632
|
+
if (!Array.isArray(raw)) return void 0;
|
|
633
|
+
const items = raw.map((item) => {
|
|
634
|
+
if (typeof item === "string") return item.trim() ? { name: item.trim() } : null;
|
|
635
|
+
if (typeof item === "object" && item !== null && typeof item.name === "string") {
|
|
636
|
+
const citation = item;
|
|
637
|
+
const name = citation.name.trim();
|
|
638
|
+
if (!name) return null;
|
|
639
|
+
return citation.url ? { name, url: citation.url } : { name };
|
|
640
|
+
}
|
|
641
|
+
return null;
|
|
642
|
+
}).filter((item) => item !== null);
|
|
643
|
+
return items.length ? items : void 0;
|
|
644
|
+
}
|
|
533
645
|
function parseSeriesOrder(raw) {
|
|
534
646
|
return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
|
|
535
647
|
}
|
|
@@ -602,6 +714,7 @@ function getArticleAuthors(article, config) {
|
|
|
602
714
|
}
|
|
603
715
|
function getArticleSummary(slug, config) {
|
|
604
716
|
return __async(this, null, function* () {
|
|
717
|
+
var _a;
|
|
605
718
|
try {
|
|
606
719
|
const found = findArticleFile(slug);
|
|
607
720
|
if (!found) return null;
|
|
@@ -618,7 +731,7 @@ function getArticleSummary(slug, config) {
|
|
|
618
731
|
title: data.title || slug.replaceAll("-", " "),
|
|
619
732
|
excerpt: data.excerpt || "",
|
|
620
733
|
date: parseDateField(data.date),
|
|
621
|
-
lastmod:
|
|
734
|
+
lastmod: resolveLastmod(data.lastmod, data.date, found.filePath, config),
|
|
622
735
|
author,
|
|
623
736
|
authors,
|
|
624
737
|
authorSlug: primaryAuthorProfile == null ? void 0 : primaryAuthorProfile.slug,
|
|
@@ -631,14 +744,17 @@ function getArticleSummary(slug, config) {
|
|
|
631
744
|
tags: data.tags || [],
|
|
632
745
|
contentType: found.contentType,
|
|
633
746
|
draft: data.draft === true,
|
|
634
|
-
faq: parseFaqItems(data.faq),
|
|
747
|
+
faq: (_a = parseFaqItems(data.faq)) != null ? _a : deriveFaq(markdownContent, config),
|
|
635
748
|
howTo: parseHowToSteps(data.howTo),
|
|
749
|
+
answer: parseOptionalString(data.answer),
|
|
750
|
+
about: parseEntityReferences(data.about, config),
|
|
751
|
+
citation: parseCitations(data.citation),
|
|
636
752
|
canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
|
|
637
753
|
articleType: typeof data.articleType === "string" ? data.articleType : void 0,
|
|
638
754
|
series: typeof data.series === "string" ? data.series : void 0,
|
|
639
755
|
seriesSlug: parseOptionalString(data.seriesSlug),
|
|
640
756
|
seriesOrder: parseSeriesOrder(data.seriesOrder),
|
|
641
|
-
aiCrawl: data.aiCrawl
|
|
757
|
+
aiCrawl: resolveAiCrawl(data.aiCrawl, config),
|
|
642
758
|
searchTitle: parseOptionalString(data.searchTitle),
|
|
643
759
|
searchDescription: parseOptionalString(data.searchDescription),
|
|
644
760
|
socialTitle: parseOptionalString(data.socialTitle),
|
|
@@ -697,10 +813,10 @@ var getAllArticles = cache((config) => __async(void 0, null, function* () {
|
|
|
697
813
|
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
|
698
814
|
});
|
|
699
815
|
}));
|
|
700
|
-
function getArticleMarkdown(slug) {
|
|
816
|
+
function getArticleMarkdown(slug, config) {
|
|
701
817
|
return __async(this, null, function* () {
|
|
702
818
|
try {
|
|
703
|
-
const summary = yield getArticleSummary(slug);
|
|
819
|
+
const summary = yield getArticleSummary(slug, config);
|
|
704
820
|
if (!(summary == null ? void 0 : summary.aiCrawl)) return null;
|
|
705
821
|
const found = findArticleFile(slug);
|
|
706
822
|
if (!found) return null;
|
|
@@ -718,12 +834,180 @@ function getArticleMarkdown(slug) {
|
|
|
718
834
|
}
|
|
719
835
|
});
|
|
720
836
|
}
|
|
721
|
-
|
|
837
|
+
var AI_CRAWLERS = [
|
|
838
|
+
"GPTBot",
|
|
839
|
+
"ChatGPT-User",
|
|
840
|
+
"OAI-SearchBot",
|
|
841
|
+
"CCBot",
|
|
842
|
+
"ClaudeBot",
|
|
843
|
+
"Claude-User",
|
|
844
|
+
"Claude-SearchBot",
|
|
845
|
+
"anthropic-ai",
|
|
846
|
+
"PerplexityBot",
|
|
847
|
+
"Perplexity-User",
|
|
848
|
+
"Google-Extended",
|
|
849
|
+
"Applebot-Extended",
|
|
850
|
+
"Bytespider",
|
|
851
|
+
"Amazonbot",
|
|
852
|
+
"meta-externalagent",
|
|
853
|
+
"cohere-ai",
|
|
854
|
+
"DuckAssistBot",
|
|
855
|
+
"MistralAI-User"
|
|
856
|
+
];
|
|
857
|
+
function matchAiCrawler(userAgent) {
|
|
858
|
+
var _a;
|
|
859
|
+
if (!userAgent) return null;
|
|
860
|
+
const normalized = userAgent.toLowerCase();
|
|
861
|
+
return (_a = AI_CRAWLERS.find((crawler) => normalized.includes(crawler.toLowerCase()))) != null ? _a : null;
|
|
862
|
+
}
|
|
863
|
+
function buildMarkdownTwinHeader(article, config, body) {
|
|
864
|
+
var _a, _b;
|
|
865
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
866
|
+
const firstLine = (_b = (_a = body.trimStart().split("\n", 1)[0]) == null ? void 0 : _a.trim()) != null ? _b : "";
|
|
867
|
+
const bodyRepeatsTitle = firstLine.toLowerCase() === `# ${article.title}`.toLowerCase();
|
|
868
|
+
const facts = [
|
|
869
|
+
`Source: ${siteUrl}/articles/${article.slug}`,
|
|
870
|
+
article.date ? `Published: ${article.date}` : "",
|
|
871
|
+
article.lastmod ? `Updated: ${article.lastmod}` : "",
|
|
872
|
+
config.showAuthor !== false && article.author ? `Author: ${article.author}` : "",
|
|
873
|
+
`Site: ${config.siteName}`
|
|
874
|
+
].filter(Boolean);
|
|
875
|
+
const blocks = [
|
|
876
|
+
bodyRepeatsTitle ? "" : `# ${article.title}`,
|
|
877
|
+
article.excerpt ? `> ${article.excerpt}` : "",
|
|
878
|
+
facts.join("\n"),
|
|
879
|
+
article.answer ? `**Short answer:** ${article.answer}` : "",
|
|
880
|
+
"---"
|
|
881
|
+
].filter((block) => block !== "");
|
|
882
|
+
return `${blocks.join("\n\n")}
|
|
883
|
+
|
|
884
|
+
`;
|
|
885
|
+
}
|
|
886
|
+
function reportAiCrawl(slug, config, headers) {
|
|
887
|
+
var _a, _b;
|
|
888
|
+
if (!config.onAiCrawl) return;
|
|
889
|
+
const userAgent = (_a = headers == null ? void 0 : headers.get("user-agent")) != null ? _a : "";
|
|
890
|
+
try {
|
|
891
|
+
config.onAiCrawl({ slug, crawler: (_b = matchAiCrawler(userAgent)) != null ? _b : "unknown", userAgent });
|
|
892
|
+
} catch (error) {
|
|
893
|
+
reportArticlesError({
|
|
894
|
+
code: "ai-crawl-handler-failed",
|
|
895
|
+
message: "onAiCrawl handler threw.",
|
|
896
|
+
error,
|
|
897
|
+
context: { slug }
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function buildListingMarkdown(heading, intro, articles, config) {
|
|
902
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
903
|
+
const crawlable = articles.filter((article) => article.aiCrawl === true);
|
|
904
|
+
const entries = crawlable.map((article) => {
|
|
905
|
+
var _a;
|
|
906
|
+
const summary = (_a = article.answer) != null ? _a : article.excerpt;
|
|
907
|
+
const line = `- [${article.title}](${siteUrl}/articles/${article.slug}.md)`;
|
|
908
|
+
return summary ? `${line}: ${summary}` : line;
|
|
909
|
+
});
|
|
910
|
+
return [
|
|
911
|
+
`# ${heading}`,
|
|
912
|
+
"",
|
|
913
|
+
...intro.flatMap((line) => [line, ""]),
|
|
914
|
+
`Source: ${siteUrl}`,
|
|
915
|
+
`Site: ${config.siteName}`,
|
|
916
|
+
"",
|
|
917
|
+
"---",
|
|
918
|
+
"",
|
|
919
|
+
...entries.length > 0 ? entries : ["_No articles available._"],
|
|
920
|
+
""
|
|
921
|
+
].join("\n");
|
|
922
|
+
}
|
|
923
|
+
function getCategoryMarkdown(categorySlug, config) {
|
|
924
|
+
return __async(this, null, function* () {
|
|
925
|
+
var _a;
|
|
926
|
+
const articles = yield getArticlesByCategory(categorySlug, config);
|
|
927
|
+
if (articles.length === 0) return null;
|
|
928
|
+
const name = (_a = articles[0].categories.find((c) => categoryToSlug(c) === categorySlug)) != null ? _a : categorySlug;
|
|
929
|
+
const description = resolveCategoryDescription(categorySlug, config);
|
|
930
|
+
return buildListingMarkdown(name, description ? [description] : [], articles, config);
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
function resolveCategoryDescription(categorySlug, config) {
|
|
934
|
+
var _a, _b;
|
|
935
|
+
const entry = (_a = config.categoryDescriptions) == null ? void 0 : _a[categorySlug];
|
|
936
|
+
if (!entry) return void 0;
|
|
937
|
+
return typeof entry === "string" ? entry : (_b = entry.long) != null ? _b : entry.short;
|
|
938
|
+
}
|
|
939
|
+
function getAuthorMarkdown(authorSlug, config) {
|
|
940
|
+
return __async(this, null, function* () {
|
|
941
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
942
|
+
const author = getAuthorBySlug(authorSlug, config);
|
|
943
|
+
if (!author) return null;
|
|
944
|
+
const articles = yield getArticlesByAuthor(authorSlug, config);
|
|
945
|
+
const intro = [
|
|
946
|
+
(_a = author.promise) != null ? _a : "",
|
|
947
|
+
(_b = author.bio) != null ? _b : "",
|
|
948
|
+
...((_c = author.servesWho) == null ? void 0 : _c.length) ? [`Writes for: ${author.servesWho.join(", ")}`] : [],
|
|
949
|
+
...((_d = author.knowsAbout) == null ? void 0 : _d.length) ? [`Writes about: ${author.knowsAbout.join(", ")}`] : [],
|
|
950
|
+
...((_e = author.credentials) == null ? void 0 : _e.length) ? ["## Stated experience", ...author.credentials.map((item) => `- ${item}`)] : [],
|
|
951
|
+
...((_f = author.proof) == null ? void 0 : _f.length) ? [
|
|
952
|
+
"## Proof points",
|
|
953
|
+
...author.proof.map(
|
|
954
|
+
(item) => item.url ? `- [${item.claim}](${item.url})` : `- ${item.claim}`
|
|
955
|
+
)
|
|
956
|
+
] : [],
|
|
957
|
+
...((_g = author.originStory) == null ? void 0 : _g.length) ? [
|
|
958
|
+
"## Background",
|
|
959
|
+
...author.originStory.flatMap((section) => [
|
|
960
|
+
...section.heading ? [`### ${section.heading}`] : [],
|
|
961
|
+
...section.paragraphs
|
|
962
|
+
])
|
|
963
|
+
] : []
|
|
964
|
+
].filter((line) => line.trim() !== "");
|
|
965
|
+
return buildListingMarkdown(author.name, intro, articles, config);
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
function getSeriesMarkdown(seriesSlug, config) {
|
|
969
|
+
return __async(this, null, function* () {
|
|
970
|
+
var _a;
|
|
971
|
+
const articles = yield getArticlesBySeries(seriesSlug, config);
|
|
972
|
+
if (articles.length === 0) return null;
|
|
973
|
+
const name = (_a = articles[0].series) != null ? _a : seriesSlug;
|
|
974
|
+
return buildListingMarkdown(name, [], articles, config);
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
function getMarkdownTwinResponse(slug, config, options) {
|
|
978
|
+
return __async(this, null, function* () {
|
|
979
|
+
const listing = yield resolveListingMarkdown(slug, config);
|
|
980
|
+
if (listing !== void 0) {
|
|
981
|
+
if (listing === null) return new Response("Not Found", { status: 404 });
|
|
982
|
+
reportAiCrawl(slug, config, options == null ? void 0 : options.headers);
|
|
983
|
+
return new Response(listing, { headers: LISTING_MARKDOWN_HEADERS });
|
|
984
|
+
}
|
|
985
|
+
return getArticleMarkdownResponse(slug, config, options);
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
var LISTING_MARKDOWN_HEADERS = {
|
|
989
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
990
|
+
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
991
|
+
};
|
|
992
|
+
function resolveListingMarkdown(slug, config) {
|
|
993
|
+
return __async(this, null, function* () {
|
|
994
|
+
const [prefix, ...rest] = slug.split("/");
|
|
995
|
+
const key = rest.join("/");
|
|
996
|
+
if (!key) return void 0;
|
|
997
|
+
if (prefix === "category") return getCategoryMarkdown(key, config);
|
|
998
|
+
if (prefix === "authors") return getAuthorMarkdown(key, config);
|
|
999
|
+
if (prefix === "series") return getSeriesMarkdown(key, config);
|
|
1000
|
+
return void 0;
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
function getArticleMarkdownResponse(slug, config, options) {
|
|
722
1004
|
return __async(this, null, function* () {
|
|
723
|
-
const markdown = yield getArticleMarkdown(slug);
|
|
1005
|
+
const markdown = yield getArticleMarkdown(slug, config);
|
|
724
1006
|
if (markdown === null) return new Response("Not Found", { status: 404 });
|
|
725
|
-
const article = yield getArticleMetadata(slug);
|
|
726
|
-
|
|
1007
|
+
const article = yield getArticleMetadata(slug, config);
|
|
1008
|
+
reportAiCrawl(slug, config, options == null ? void 0 : options.headers);
|
|
1009
|
+
const body = article && config.markdownTwinHeader !== false ? `${buildMarkdownTwinHeader(article, config, markdown)}${markdown.trimStart()}` : markdown;
|
|
1010
|
+
return new Response(body, {
|
|
727
1011
|
headers: __spreadValues({
|
|
728
1012
|
"Content-Type": "text/markdown; charset=utf-8",
|
|
729
1013
|
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
@@ -751,16 +1035,43 @@ function getArticleAiHeaders(article, config) {
|
|
|
751
1035
|
function categoryToSlug(category) {
|
|
752
1036
|
return category.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
|
|
753
1037
|
}
|
|
1038
|
+
function getArticlesByCategory(categorySlug, config) {
|
|
1039
|
+
return __async(this, null, function* () {
|
|
1040
|
+
const articles = yield getAllArticles(config);
|
|
1041
|
+
return articles.filter(
|
|
1042
|
+
(article) => article.categories.some((cat) => categoryToSlug(cat) === categorySlug)
|
|
1043
|
+
);
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
function getArticlesByAuthor(authorSlug, config) {
|
|
1047
|
+
return __async(this, null, function* () {
|
|
1048
|
+
const articles = yield getAllArticles(config);
|
|
1049
|
+
return articles.filter(
|
|
1050
|
+
(article) => getArticleAuthors(article, config).some((author) => author.slug === authorSlug)
|
|
1051
|
+
);
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
function getArticlesBySeries(seriesSlug, config) {
|
|
1055
|
+
return __async(this, null, function* () {
|
|
1056
|
+
const articles = yield getAllArticles(config);
|
|
1057
|
+
return articles.filter((article) => article.seriesSlug === seriesSlug).sort((a, b) => {
|
|
1058
|
+
var _a, _b;
|
|
1059
|
+
const orderA = (_a = a.seriesOrder) != null ? _a : Number.POSITIVE_INFINITY;
|
|
1060
|
+
const orderB = (_b = b.seriesOrder) != null ? _b : Number.POSITIVE_INFINITY;
|
|
1061
|
+
return orderA - orderB;
|
|
1062
|
+
});
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
754
1065
|
|
|
755
1066
|
// src/nextjs.ts
|
|
756
1067
|
function createArticleMarkdownHandler(config) {
|
|
757
|
-
function GET(
|
|
1068
|
+
function GET(request, context) {
|
|
758
1069
|
return __async(this, null, function* () {
|
|
759
1070
|
const params = yield context.params;
|
|
760
1071
|
const slugParts = params["slug"];
|
|
761
1072
|
const slug = Array.isArray(slugParts) ? slugParts.join("/") : slugParts != null ? slugParts : "";
|
|
762
1073
|
const cleanSlug = slug.endsWith(".md") ? slug.slice(0, -3) : slug;
|
|
763
|
-
return
|
|
1074
|
+
return getMarkdownTwinResponse(cleanSlug, config, { headers: request.headers });
|
|
764
1075
|
});
|
|
765
1076
|
}
|
|
766
1077
|
return { GET };
|