@fullstackdatasolutions/articles 1.2.3 → 1.3.1
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 +325 -31
- 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 +325 -31
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +660 -50
- 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 +645 -50
- 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__/markdown.test.ts +77 -1
- package/src/__tests__/nextjs.test.ts +31 -15
- package/src/__tests__/seoUtils.test.ts +279 -0
- package/src/__tests__/server-articles.test.ts +434 -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/markdown.ts +100 -1
- package/src/nextjs.ts +7 -4
- package/src/seoUtils.ts +247 -26
- package/src/server-articles.ts +385 -25
- package/src/server.ts +35 -4
- package/src/validateArticles.ts +157 -12
package/dist/nextjs.cjs
CHANGED
|
@@ -433,6 +433,64 @@ function extractToc(markdown) {
|
|
|
433
433
|
return headings;
|
|
434
434
|
});
|
|
435
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
|
+
}
|
|
436
494
|
|
|
437
495
|
// src/server-articles.ts
|
|
438
496
|
var articlesDirectory = import_node_path.default.join(
|
|
@@ -565,6 +623,58 @@ function parseHowToSteps(raw) {
|
|
|
565
623
|
function parseOptionalString(raw) {
|
|
566
624
|
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
567
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
|
+
}
|
|
568
678
|
function parseSeriesOrder(raw) {
|
|
569
679
|
return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
|
|
570
680
|
}
|
|
@@ -637,6 +747,7 @@ function getArticleAuthors(article, config) {
|
|
|
637
747
|
}
|
|
638
748
|
function getArticleSummary(slug, config) {
|
|
639
749
|
return __async(this, null, function* () {
|
|
750
|
+
var _a;
|
|
640
751
|
try {
|
|
641
752
|
const found = findArticleFile(slug);
|
|
642
753
|
if (!found) return null;
|
|
@@ -653,7 +764,7 @@ function getArticleSummary(slug, config) {
|
|
|
653
764
|
title: data.title || slug.replaceAll("-", " "),
|
|
654
765
|
excerpt: data.excerpt || "",
|
|
655
766
|
date: parseDateField(data.date),
|
|
656
|
-
lastmod:
|
|
767
|
+
lastmod: resolveLastmod(data.lastmod, data.date, found.filePath, config),
|
|
657
768
|
author,
|
|
658
769
|
authors,
|
|
659
770
|
authorSlug: primaryAuthorProfile == null ? void 0 : primaryAuthorProfile.slug,
|
|
@@ -666,14 +777,17 @@ function getArticleSummary(slug, config) {
|
|
|
666
777
|
tags: data.tags || [],
|
|
667
778
|
contentType: found.contentType,
|
|
668
779
|
draft: data.draft === true,
|
|
669
|
-
faq: parseFaqItems(data.faq),
|
|
780
|
+
faq: (_a = parseFaqItems(data.faq)) != null ? _a : deriveFaq(markdownContent, config),
|
|
670
781
|
howTo: parseHowToSteps(data.howTo),
|
|
782
|
+
answer: parseOptionalString(data.answer),
|
|
783
|
+
about: parseEntityReferences(data.about, config),
|
|
784
|
+
citation: parseCitations(data.citation),
|
|
671
785
|
canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
|
|
672
786
|
articleType: typeof data.articleType === "string" ? data.articleType : void 0,
|
|
673
787
|
series: typeof data.series === "string" ? data.series : void 0,
|
|
674
788
|
seriesSlug: parseOptionalString(data.seriesSlug),
|
|
675
789
|
seriesOrder: parseSeriesOrder(data.seriesOrder),
|
|
676
|
-
aiCrawl: data.aiCrawl
|
|
790
|
+
aiCrawl: resolveAiCrawl(data.aiCrawl, config),
|
|
677
791
|
searchTitle: parseOptionalString(data.searchTitle),
|
|
678
792
|
searchDescription: parseOptionalString(data.searchDescription),
|
|
679
793
|
socialTitle: parseOptionalString(data.socialTitle),
|
|
@@ -732,10 +846,10 @@ var getAllArticles = (0, import_react.cache)((config) => __async(void 0, null, f
|
|
|
732
846
|
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
|
733
847
|
});
|
|
734
848
|
}));
|
|
735
|
-
function getArticleMarkdown(slug) {
|
|
849
|
+
function getArticleMarkdown(slug, config) {
|
|
736
850
|
return __async(this, null, function* () {
|
|
737
851
|
try {
|
|
738
|
-
const summary = yield getArticleSummary(slug);
|
|
852
|
+
const summary = yield getArticleSummary(slug, config);
|
|
739
853
|
if (!(summary == null ? void 0 : summary.aiCrawl)) return null;
|
|
740
854
|
const found = findArticleFile(slug);
|
|
741
855
|
if (!found) return null;
|
|
@@ -753,49 +867,229 @@ function getArticleMarkdown(slug) {
|
|
|
753
867
|
}
|
|
754
868
|
});
|
|
755
869
|
}
|
|
756
|
-
|
|
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) {
|
|
757
957
|
return __async(this, null, function* () {
|
|
758
|
-
|
|
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) {
|
|
1037
|
+
return __async(this, null, function* () {
|
|
1038
|
+
const markdown = yield getArticleMarkdown(slug, config);
|
|
759
1039
|
if (markdown === null) return new Response("Not Found", { status: 404 });
|
|
760
|
-
const article = yield getArticleMetadata(slug);
|
|
761
|
-
|
|
762
|
-
|
|
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
|
+
const canonicalUrl = `${config.siteUrl.replace(/\/$/, "")}/articles/${slug}`;
|
|
1044
|
+
return new Response(body, {
|
|
1045
|
+
headers: {
|
|
763
1046
|
"Content-Type": "text/markdown; charset=utf-8",
|
|
764
|
-
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
765
|
-
|
|
1047
|
+
"Cache-Control": "public, max-age=3600, s-maxage=3600",
|
|
1048
|
+
Link: `<${canonicalUrl}>; rel="canonical"`
|
|
1049
|
+
}
|
|
766
1050
|
});
|
|
767
1051
|
});
|
|
768
1052
|
}
|
|
769
|
-
function getArticleMarkdownUrl(article, config) {
|
|
770
|
-
if (article.aiCrawl !== true) return void 0;
|
|
771
|
-
const pathname = `/articles/${article.slug}.md`;
|
|
772
|
-
if (!config) return pathname;
|
|
773
|
-
return `${config.siteUrl.replace(/\/$/, "")}${pathname}`;
|
|
774
|
-
}
|
|
775
|
-
function getArticleAiHeaders(article, config) {
|
|
776
|
-
const markdownUrl = getArticleMarkdownUrl(article, config);
|
|
777
|
-
if (markdownUrl) {
|
|
778
|
-
return {
|
|
779
|
-
Link: `<${markdownUrl}>; rel="alternate"; type="text/markdown"`
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
return {
|
|
783
|
-
"X-Robots-Tag": "noai, noimageai"
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
1053
|
function categoryToSlug(category) {
|
|
787
1054
|
return category.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
|
|
788
1055
|
}
|
|
1056
|
+
function getArticlesByCategory(categorySlug, config) {
|
|
1057
|
+
return __async(this, null, function* () {
|
|
1058
|
+
const articles = yield getAllArticles(config);
|
|
1059
|
+
return articles.filter(
|
|
1060
|
+
(article) => article.categories.some((cat) => categoryToSlug(cat) === categorySlug)
|
|
1061
|
+
);
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
function getArticlesByAuthor(authorSlug, config) {
|
|
1065
|
+
return __async(this, null, function* () {
|
|
1066
|
+
const articles = yield getAllArticles(config);
|
|
1067
|
+
return articles.filter(
|
|
1068
|
+
(article) => getArticleAuthors(article, config).some((author) => author.slug === authorSlug)
|
|
1069
|
+
);
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
function getArticlesBySeries(seriesSlug, config) {
|
|
1073
|
+
return __async(this, null, function* () {
|
|
1074
|
+
const articles = yield getAllArticles(config);
|
|
1075
|
+
return articles.filter((article) => article.seriesSlug === seriesSlug).sort((a, b) => {
|
|
1076
|
+
var _a, _b;
|
|
1077
|
+
const orderA = (_a = a.seriesOrder) != null ? _a : Number.POSITIVE_INFINITY;
|
|
1078
|
+
const orderB = (_b = b.seriesOrder) != null ? _b : Number.POSITIVE_INFINITY;
|
|
1079
|
+
return orderA - orderB;
|
|
1080
|
+
});
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
789
1083
|
|
|
790
1084
|
// src/nextjs.ts
|
|
791
1085
|
function createArticleMarkdownHandler(config) {
|
|
792
|
-
function GET(
|
|
1086
|
+
function GET(request, context) {
|
|
793
1087
|
return __async(this, null, function* () {
|
|
794
1088
|
const params = yield context.params;
|
|
795
1089
|
const slugParts = params["slug"];
|
|
796
1090
|
const slug = Array.isArray(slugParts) ? slugParts.join("/") : slugParts != null ? slugParts : "";
|
|
797
1091
|
const cleanSlug = slug.endsWith(".md") ? slug.slice(0, -3) : slug;
|
|
798
|
-
return
|
|
1092
|
+
return getMarkdownTwinResponse(cleanSlug, config, { headers: request.headers });
|
|
799
1093
|
});
|
|
800
1094
|
}
|
|
801
1095
|
return { GET };
|