@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.js
CHANGED
|
@@ -400,6 +400,64 @@ function extractToc(markdown) {
|
|
|
400
400
|
return headings;
|
|
401
401
|
});
|
|
402
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
|
+
}
|
|
403
461
|
|
|
404
462
|
// src/server-articles.ts
|
|
405
463
|
var articlesDirectory = path.join(
|
|
@@ -532,6 +590,58 @@ function parseHowToSteps(raw) {
|
|
|
532
590
|
function parseOptionalString(raw) {
|
|
533
591
|
return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : void 0;
|
|
534
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
|
+
}
|
|
535
645
|
function parseSeriesOrder(raw) {
|
|
536
646
|
return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
|
|
537
647
|
}
|
|
@@ -604,6 +714,7 @@ function getArticleAuthors(article, config) {
|
|
|
604
714
|
}
|
|
605
715
|
function getArticleSummary(slug, config) {
|
|
606
716
|
return __async(this, null, function* () {
|
|
717
|
+
var _a;
|
|
607
718
|
try {
|
|
608
719
|
const found = findArticleFile(slug);
|
|
609
720
|
if (!found) return null;
|
|
@@ -620,7 +731,7 @@ function getArticleSummary(slug, config) {
|
|
|
620
731
|
title: data.title || slug.replaceAll("-", " "),
|
|
621
732
|
excerpt: data.excerpt || "",
|
|
622
733
|
date: parseDateField(data.date),
|
|
623
|
-
lastmod:
|
|
734
|
+
lastmod: resolveLastmod(data.lastmod, data.date, found.filePath, config),
|
|
624
735
|
author,
|
|
625
736
|
authors,
|
|
626
737
|
authorSlug: primaryAuthorProfile == null ? void 0 : primaryAuthorProfile.slug,
|
|
@@ -633,14 +744,17 @@ function getArticleSummary(slug, config) {
|
|
|
633
744
|
tags: data.tags || [],
|
|
634
745
|
contentType: found.contentType,
|
|
635
746
|
draft: data.draft === true,
|
|
636
|
-
faq: parseFaqItems(data.faq),
|
|
747
|
+
faq: (_a = parseFaqItems(data.faq)) != null ? _a : deriveFaq(markdownContent, config),
|
|
637
748
|
howTo: parseHowToSteps(data.howTo),
|
|
749
|
+
answer: parseOptionalString(data.answer),
|
|
750
|
+
about: parseEntityReferences(data.about, config),
|
|
751
|
+
citation: parseCitations(data.citation),
|
|
638
752
|
canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
|
|
639
753
|
articleType: typeof data.articleType === "string" ? data.articleType : void 0,
|
|
640
754
|
series: typeof data.series === "string" ? data.series : void 0,
|
|
641
755
|
seriesSlug: parseOptionalString(data.seriesSlug),
|
|
642
756
|
seriesOrder: parseSeriesOrder(data.seriesOrder),
|
|
643
|
-
aiCrawl: data.aiCrawl
|
|
757
|
+
aiCrawl: resolveAiCrawl(data.aiCrawl, config),
|
|
644
758
|
searchTitle: parseOptionalString(data.searchTitle),
|
|
645
759
|
searchDescription: parseOptionalString(data.searchDescription),
|
|
646
760
|
socialTitle: parseOptionalString(data.socialTitle),
|
|
@@ -699,10 +813,10 @@ var getAllArticles = cache((config) => __async(void 0, null, function* () {
|
|
|
699
813
|
return new Date(b.date).getTime() - new Date(a.date).getTime();
|
|
700
814
|
});
|
|
701
815
|
}));
|
|
702
|
-
function getArticleMarkdown(slug) {
|
|
816
|
+
function getArticleMarkdown(slug, config) {
|
|
703
817
|
return __async(this, null, function* () {
|
|
704
818
|
try {
|
|
705
|
-
const summary = yield getArticleSummary(slug);
|
|
819
|
+
const summary = yield getArticleSummary(slug, config);
|
|
706
820
|
if (!(summary == null ? void 0 : summary.aiCrawl)) return null;
|
|
707
821
|
const found = findArticleFile(slug);
|
|
708
822
|
if (!found) return null;
|
|
@@ -720,49 +834,229 @@ function getArticleMarkdown(slug) {
|
|
|
720
834
|
}
|
|
721
835
|
});
|
|
722
836
|
}
|
|
723
|
-
|
|
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) {
|
|
724
924
|
return __async(this, null, function* () {
|
|
725
|
-
|
|
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) {
|
|
1004
|
+
return __async(this, null, function* () {
|
|
1005
|
+
const markdown = yield getArticleMarkdown(slug, config);
|
|
726
1006
|
if (markdown === null) return new Response("Not Found", { status: 404 });
|
|
727
|
-
const article = yield getArticleMetadata(slug);
|
|
728
|
-
|
|
729
|
-
|
|
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
|
+
const canonicalUrl = `${config.siteUrl.replace(/\/$/, "")}/articles/${slug}`;
|
|
1011
|
+
return new Response(body, {
|
|
1012
|
+
headers: {
|
|
730
1013
|
"Content-Type": "text/markdown; charset=utf-8",
|
|
731
|
-
"Cache-Control": "public, max-age=3600, s-maxage=3600"
|
|
732
|
-
|
|
1014
|
+
"Cache-Control": "public, max-age=3600, s-maxage=3600",
|
|
1015
|
+
Link: `<${canonicalUrl}>; rel="canonical"`
|
|
1016
|
+
}
|
|
733
1017
|
});
|
|
734
1018
|
});
|
|
735
1019
|
}
|
|
736
|
-
function getArticleMarkdownUrl(article, config) {
|
|
737
|
-
if (article.aiCrawl !== true) return void 0;
|
|
738
|
-
const pathname = `/articles/${article.slug}.md`;
|
|
739
|
-
if (!config) return pathname;
|
|
740
|
-
return `${config.siteUrl.replace(/\/$/, "")}${pathname}`;
|
|
741
|
-
}
|
|
742
|
-
function getArticleAiHeaders(article, config) {
|
|
743
|
-
const markdownUrl = getArticleMarkdownUrl(article, config);
|
|
744
|
-
if (markdownUrl) {
|
|
745
|
-
return {
|
|
746
|
-
Link: `<${markdownUrl}>; rel="alternate"; type="text/markdown"`
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
return {
|
|
750
|
-
"X-Robots-Tag": "noai, noimageai"
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
1020
|
function categoryToSlug(category) {
|
|
754
1021
|
return category.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
|
|
755
1022
|
}
|
|
1023
|
+
function getArticlesByCategory(categorySlug, config) {
|
|
1024
|
+
return __async(this, null, function* () {
|
|
1025
|
+
const articles = yield getAllArticles(config);
|
|
1026
|
+
return articles.filter(
|
|
1027
|
+
(article) => article.categories.some((cat) => categoryToSlug(cat) === categorySlug)
|
|
1028
|
+
);
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
function getArticlesByAuthor(authorSlug, config) {
|
|
1032
|
+
return __async(this, null, function* () {
|
|
1033
|
+
const articles = yield getAllArticles(config);
|
|
1034
|
+
return articles.filter(
|
|
1035
|
+
(article) => getArticleAuthors(article, config).some((author) => author.slug === authorSlug)
|
|
1036
|
+
);
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
function getArticlesBySeries(seriesSlug, config) {
|
|
1040
|
+
return __async(this, null, function* () {
|
|
1041
|
+
const articles = yield getAllArticles(config);
|
|
1042
|
+
return articles.filter((article) => article.seriesSlug === seriesSlug).sort((a, b) => {
|
|
1043
|
+
var _a, _b;
|
|
1044
|
+
const orderA = (_a = a.seriesOrder) != null ? _a : Number.POSITIVE_INFINITY;
|
|
1045
|
+
const orderB = (_b = b.seriesOrder) != null ? _b : Number.POSITIVE_INFINITY;
|
|
1046
|
+
return orderA - orderB;
|
|
1047
|
+
});
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
756
1050
|
|
|
757
1051
|
// src/nextjs.ts
|
|
758
1052
|
function createArticleMarkdownHandler(config) {
|
|
759
|
-
function GET(
|
|
1053
|
+
function GET(request, context) {
|
|
760
1054
|
return __async(this, null, function* () {
|
|
761
1055
|
const params = yield context.params;
|
|
762
1056
|
const slugParts = params["slug"];
|
|
763
1057
|
const slug = Array.isArray(slugParts) ? slugParts.join("/") : slugParts != null ? slugParts : "";
|
|
764
1058
|
const cleanSlug = slug.endsWith(".md") ? slug.slice(0, -3) : slug;
|
|
765
|
-
return
|
|
1059
|
+
return getMarkdownTwinResponse(cleanSlug, config, { headers: request.headers });
|
|
766
1060
|
});
|
|
767
1061
|
}
|
|
768
1062
|
return { GET };
|