mdorigin 0.4.1 → 0.5.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/dist/core/request-handler.js +228 -8
- package/dist/core/site-config.d.ts +15 -0
- package/dist/core/site-config.js +31 -0
- package/dist/core/site-url.d.ts +2 -0
- package/dist/core/site-url.js +6 -0
- package/dist/html/template.d.ts +1 -0
- package/dist/html/template.js +4 -0
- package/dist/search.js +1 -6
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { isIgnoredContentName } from './content-store.js';
|
|
3
|
-
import { inferDirectoryContentType } from './content-type.js';
|
|
3
|
+
import { inferDirectoryContentType, resolveContentType, } from './content-type.js';
|
|
4
4
|
import { getDirectoryIndexCandidates } from './directory-index.js';
|
|
5
|
-
import { extractManagedIndexEntries, getDocumentSummary, getDocumentTitle as getParsedDocumentTitle, parseMarkdownDocument, stripManagedIndexBlock, stripManagedIndexLinks, } from './markdown.js';
|
|
5
|
+
import { extractManagedIndexEntries, getDocumentSummary, getDocumentTitle as getParsedDocumentTitle, parseMarkdownDocument, stripManagedIndexBlock, stripManagedIndexLinks, stripMachineOnlyMarkdownComments, } from './markdown.js';
|
|
6
|
+
import { ensureTrailingSlash, trimLeadingSlash } from './site-url.js';
|
|
6
7
|
import { applyIndexTransforms, renderFooterOverride, renderHeaderOverride, renderPageWithPlugins, transformHtmlWithPlugins, } from './extensions.js';
|
|
7
8
|
import { handleApiRoute } from './api.js';
|
|
8
9
|
import { normalizeRequestPath, resolveRequest } from './router.js';
|
|
@@ -21,6 +22,9 @@ export async function handleSiteRequest(store, pathname, options) {
|
|
|
21
22
|
if (pathname === '/sitemap.xml') {
|
|
22
23
|
return renderSitemap(store, options);
|
|
23
24
|
}
|
|
25
|
+
if (pathname === '/feed.xml') {
|
|
26
|
+
return renderRssFeed(store, options);
|
|
27
|
+
}
|
|
24
28
|
const resolved = resolveRequest(pathname);
|
|
25
29
|
const listingFragmentRequest = getListingFragmentRequest(options.searchParams);
|
|
26
30
|
const negotiatedMarkdown = shouldServeMarkdownForRequest(resolved, options.acceptHeader);
|
|
@@ -29,7 +33,7 @@ export async function handleSiteRequest(store, pathname, options) {
|
|
|
29
33
|
if (aliasRedirect !== null) {
|
|
30
34
|
return aliasRedirect;
|
|
31
35
|
}
|
|
32
|
-
return
|
|
36
|
+
return renderNotFoundForRequest(store, pathname, options);
|
|
33
37
|
}
|
|
34
38
|
const entry = await store.get(resolved.sourcePath);
|
|
35
39
|
if (entry === null) {
|
|
@@ -45,6 +49,10 @@ export async function handleSiteRequest(store, pathname, options) {
|
|
|
45
49
|
if (alternateMarkdownRedirect !== null) {
|
|
46
50
|
return alternateMarkdownRedirect;
|
|
47
51
|
}
|
|
52
|
+
const canonicalDirectoryRedirect = await tryRedirectCanonicalDirectoryPath(store, resolved);
|
|
53
|
+
if (canonicalDirectoryRedirect !== null) {
|
|
54
|
+
return canonicalDirectoryRedirect;
|
|
55
|
+
}
|
|
48
56
|
if (resolved.kind === 'html' && resolved.requestPath.endsWith('/')) {
|
|
49
57
|
const directoryIndexResponse = await tryRenderAlternateDirectoryIndex(store, resolved.requestPath, options);
|
|
50
58
|
if (directoryIndexResponse !== null) {
|
|
@@ -52,18 +60,18 @@ export async function handleSiteRequest(store, pathname, options) {
|
|
|
52
60
|
}
|
|
53
61
|
return renderDirectoryListing(store, resolved.requestPath, options.siteConfig, searchEnabled);
|
|
54
62
|
}
|
|
55
|
-
return
|
|
63
|
+
return renderNotFoundForResolvedRequest(store, resolved, options, negotiatedMarkdown);
|
|
56
64
|
}
|
|
57
65
|
if (resolved.kind === 'asset') {
|
|
58
66
|
return serveAsset(entry);
|
|
59
67
|
}
|
|
60
68
|
if (entry.kind !== 'text' || entry.text === undefined) {
|
|
61
|
-
return
|
|
69
|
+
return renderNotFoundForResolvedRequest(store, resolved, options, negotiatedMarkdown);
|
|
62
70
|
}
|
|
63
71
|
if (resolved.kind === 'markdown' || negotiatedMarkdown) {
|
|
64
72
|
const parsed = await parseMarkdownDocument(resolved.sourcePath, entry.text);
|
|
65
73
|
if (parsed.meta.draft === true && options.draftMode === 'exclude') {
|
|
66
|
-
return
|
|
74
|
+
return renderNotFoundForResolvedRequest(store, resolved, options, negotiatedMarkdown);
|
|
67
75
|
}
|
|
68
76
|
return {
|
|
69
77
|
status: 200,
|
|
@@ -75,7 +83,7 @@ export async function handleSiteRequest(store, pathname, options) {
|
|
|
75
83
|
}
|
|
76
84
|
const parsed = await parseMarkdownDocument(resolved.sourcePath, entry.text);
|
|
77
85
|
if (parsed.meta.draft === true && options.draftMode === 'exclude') {
|
|
78
|
-
return
|
|
86
|
+
return renderNotFoundForResolvedRequest(store, resolved, options, negotiatedMarkdown);
|
|
79
87
|
}
|
|
80
88
|
const navigation = await resolveTopNav(store, options.siteConfig);
|
|
81
89
|
const renderedBody = isRootHomeRequest(resolved.requestPath) && !options.siteConfig.showHomeIndex
|
|
@@ -214,6 +222,7 @@ async function renderStructuredPage(options) {
|
|
|
214
222
|
stylesheetContent: currentPage.stylesheetContent,
|
|
215
223
|
canonicalPath: currentPage.canonicalPath,
|
|
216
224
|
alternateMarkdownPath: currentPage.alternateMarkdownPath,
|
|
225
|
+
rssFeedUrl: getRssFeedUrl(currentPage.siteUrl, options.siteConfig),
|
|
217
226
|
listingEntries: currentPage.listingEntries,
|
|
218
227
|
listingRequestPath: currentPage.listingRequestPath,
|
|
219
228
|
listingInitialPostCount: currentPage.listingInitialPostCount,
|
|
@@ -281,6 +290,60 @@ function notFound() {
|
|
|
281
290
|
body: 'Not Found',
|
|
282
291
|
};
|
|
283
292
|
}
|
|
293
|
+
async function renderNotFoundForRequest(store, pathname, options) {
|
|
294
|
+
const resolved = resolveRequest(pathname);
|
|
295
|
+
return renderNotFoundForResolvedRequest(store, resolved, options, false);
|
|
296
|
+
}
|
|
297
|
+
async function renderNotFoundForResolvedRequest(store, resolved, options, negotiatedMarkdown) {
|
|
298
|
+
const varyOnAccept = shouldVaryOnAccept(resolved);
|
|
299
|
+
if (resolved.kind !== 'html' || negotiatedMarkdown) {
|
|
300
|
+
return withNotFoundVary(varyOnAccept);
|
|
301
|
+
}
|
|
302
|
+
return renderHtmlNotFound(store, resolved.requestPath, options, varyOnAccept);
|
|
303
|
+
}
|
|
304
|
+
async function renderHtmlNotFound(store, requestPath, options, varyOnAccept) {
|
|
305
|
+
const navigation = await resolveTopNav(store, options.siteConfig);
|
|
306
|
+
const body = [
|
|
307
|
+
'<h1>Not Found</h1>',
|
|
308
|
+
`<p>No page was published at <code>${escapeHtml(requestPath)}</code>.</p>`,
|
|
309
|
+
].join('');
|
|
310
|
+
return {
|
|
311
|
+
status: 404,
|
|
312
|
+
headers: withVaryAcceptIfNeeded({
|
|
313
|
+
'content-type': 'text/html; charset=utf-8',
|
|
314
|
+
}, varyOnAccept),
|
|
315
|
+
body: renderDocument({
|
|
316
|
+
siteTitle: options.siteConfig.siteTitle,
|
|
317
|
+
siteDescription: options.siteConfig.siteDescription,
|
|
318
|
+
siteUrl: options.siteConfig.siteUrl,
|
|
319
|
+
favicon: options.siteConfig.favicon,
|
|
320
|
+
socialImage: options.siteConfig.socialImage,
|
|
321
|
+
logo: options.siteConfig.logo,
|
|
322
|
+
title: 'Not Found',
|
|
323
|
+
body,
|
|
324
|
+
showSummary: false,
|
|
325
|
+
showDate: false,
|
|
326
|
+
topNav: navigation.items,
|
|
327
|
+
footerNav: options.siteConfig.footerNav,
|
|
328
|
+
footerText: options.siteConfig.footerText,
|
|
329
|
+
socialLinks: options.siteConfig.socialLinks,
|
|
330
|
+
stylesheetContent: options.siteConfig.stylesheetContent,
|
|
331
|
+
rssFeedUrl: getRssFeedUrl(options.siteConfig.siteUrl, options.siteConfig),
|
|
332
|
+
searchEnabled: options.searchApi !== undefined,
|
|
333
|
+
}),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function withNotFoundVary(varyOnAccept) {
|
|
337
|
+
if (!varyOnAccept) {
|
|
338
|
+
return notFound();
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
...notFound(),
|
|
342
|
+
headers: withVaryAcceptIfNeeded({
|
|
343
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
344
|
+
}, true),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
284
347
|
function redirect(location) {
|
|
285
348
|
return {
|
|
286
349
|
status: 308,
|
|
@@ -317,6 +380,59 @@ async function renderSitemap(store, options) {
|
|
|
317
380
|
body,
|
|
318
381
|
};
|
|
319
382
|
}
|
|
383
|
+
async function renderRssFeed(store, options) {
|
|
384
|
+
if (!isRssEnabled(options.siteConfig) || !options.siteConfig.siteUrl) {
|
|
385
|
+
return notFound();
|
|
386
|
+
}
|
|
387
|
+
const items = await collectRssFeedItems(store, '', options);
|
|
388
|
+
const limitedItems = items.slice(0, options.siteConfig.rss?.maxItems ?? 20);
|
|
389
|
+
const rssFeedUrl = getRssFeedUrl(options.siteConfig.siteUrl, options.siteConfig);
|
|
390
|
+
const title = options.siteConfig.rss?.title ?? options.siteConfig.siteTitle;
|
|
391
|
+
const description = options.siteConfig.rss?.description ?? options.siteConfig.siteDescription;
|
|
392
|
+
const lastBuildDate = limitedItems[0]?.pubDate.toUTCString();
|
|
393
|
+
const body = [
|
|
394
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
395
|
+
'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">',
|
|
396
|
+
'<channel>',
|
|
397
|
+
` <title>${escapeHtml(title)}</title>`,
|
|
398
|
+
` <link>${escapeHtml(options.siteConfig.siteUrl)}</link>`,
|
|
399
|
+
description
|
|
400
|
+
? ` <description>${escapeHtml(description)}</description>`
|
|
401
|
+
: ' <description></description>',
|
|
402
|
+
' <generator>mdorigin</generator>',
|
|
403
|
+
rssFeedUrl
|
|
404
|
+
? ` <atom:link href="${escapeHtml(rssFeedUrl)}" rel="self" type="application/rss+xml" />`
|
|
405
|
+
: '',
|
|
406
|
+
options.siteConfig.rss?.author
|
|
407
|
+
? ` <managingEditor>${escapeHtml(options.siteConfig.rss.author)}</managingEditor>`
|
|
408
|
+
: '',
|
|
409
|
+
lastBuildDate ? ` <lastBuildDate>${escapeHtml(lastBuildDate)}</lastBuildDate>` : '',
|
|
410
|
+
...limitedItems.map((item) => [
|
|
411
|
+
' <item>',
|
|
412
|
+
` <title>${escapeHtml(item.title)}</title>`,
|
|
413
|
+
` <link>${escapeHtml(item.absoluteUrl)}</link>`,
|
|
414
|
+
` <guid isPermaLink="true">${escapeHtml(item.absoluteUrl)}</guid>`,
|
|
415
|
+
` <pubDate>${escapeHtml(item.pubDate.toUTCString())}</pubDate>`,
|
|
416
|
+
item.summary
|
|
417
|
+
? ` <description>${escapeHtml(item.summary)}</description>`
|
|
418
|
+
: '',
|
|
419
|
+
' </item>',
|
|
420
|
+
]
|
|
421
|
+
.filter((line) => line !== '')
|
|
422
|
+
.join('\n')),
|
|
423
|
+
'</channel>',
|
|
424
|
+
'</rss>',
|
|
425
|
+
]
|
|
426
|
+
.filter((line) => line !== '')
|
|
427
|
+
.join('\n');
|
|
428
|
+
return {
|
|
429
|
+
status: 200,
|
|
430
|
+
headers: {
|
|
431
|
+
'content-type': 'application/rss+xml; charset=utf-8',
|
|
432
|
+
},
|
|
433
|
+
body,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
320
436
|
function withVaryAcceptIfNeeded(headers, enabled) {
|
|
321
437
|
if (!enabled) {
|
|
322
438
|
return headers;
|
|
@@ -387,6 +503,74 @@ function dedupeSitemapEntries(entries) {
|
|
|
387
503
|
}
|
|
388
504
|
return Array.from(deduped.values());
|
|
389
505
|
}
|
|
506
|
+
async function collectRssFeedItems(store, directoryPath, options) {
|
|
507
|
+
const entries = await store.listDirectory(directoryPath);
|
|
508
|
+
if (entries === null) {
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
511
|
+
const feedItems = [];
|
|
512
|
+
const directoryShape = inspectDirectoryShapeEntries(entries);
|
|
513
|
+
for (const entry of entries) {
|
|
514
|
+
if (entry.kind === 'directory') {
|
|
515
|
+
feedItems.push(...(await collectRssFeedItems(store, entry.path, options)));
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
if (!isMarkdownEntry(entry)) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
const document = await store.get(entry.path);
|
|
522
|
+
if (document === null || document.kind !== 'text' || document.text === undefined) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const parsed = await parseMarkdownDocument(entry.path, document.text);
|
|
526
|
+
if (parsed.meta.draft === true && options.draftMode === 'exclude') {
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
const pubDate = parseFeedDate(parsed.meta.date);
|
|
530
|
+
if (pubDate === null) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
const contentType = inferFeedContentType(entry.path, parsed.meta, directoryShape);
|
|
534
|
+
if (contentType !== 'post') {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
const canonicalPath = getCanonicalHtmlPathForContentPath(entry.path);
|
|
538
|
+
feedItems.push({
|
|
539
|
+
title: getDocumentTitle(parsed),
|
|
540
|
+
canonicalPath,
|
|
541
|
+
absoluteUrl: new URL(trimLeadingSlash(canonicalPath), ensureTrailingSlash(options.siteConfig.siteUrl ?? '')).toString(),
|
|
542
|
+
summary: getFeedSummary(parsed),
|
|
543
|
+
pubDate,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
feedItems.sort((left, right) => {
|
|
547
|
+
const timeDelta = right.pubDate.getTime() - left.pubDate.getTime();
|
|
548
|
+
return timeDelta !== 0
|
|
549
|
+
? timeDelta
|
|
550
|
+
: left.canonicalPath.localeCompare(right.canonicalPath);
|
|
551
|
+
});
|
|
552
|
+
return feedItems;
|
|
553
|
+
}
|
|
554
|
+
function inferFeedContentType(contentPath, meta, directoryShape) {
|
|
555
|
+
const explicitType = resolveContentType(meta);
|
|
556
|
+
if (explicitType) {
|
|
557
|
+
return explicitType;
|
|
558
|
+
}
|
|
559
|
+
if (isDirectoryIndexContentPath(contentPath)) {
|
|
560
|
+
return inferDirectoryContentType(meta, directoryShape);
|
|
561
|
+
}
|
|
562
|
+
return typeof meta.date === 'string' && meta.date !== '' ? 'post' : 'page';
|
|
563
|
+
}
|
|
564
|
+
function getFeedSummary(parsed) {
|
|
565
|
+
return getDocumentSummary(parsed.meta, stripMachineOnlyMarkdownComments(stripManagedIndexBlock(parsed.body)));
|
|
566
|
+
}
|
|
567
|
+
function parseFeedDate(value) {
|
|
568
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
const parsed = new Date(value);
|
|
572
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
573
|
+
}
|
|
390
574
|
async function renderDirectoryListing(store, requestPath, siteConfig, searchEnabled) {
|
|
391
575
|
const directoryPath = requestPath === '/' ? '' : requestPath.slice(1).replace(/\/$/, '');
|
|
392
576
|
const entries = await store.listDirectory(directoryPath);
|
|
@@ -424,6 +608,7 @@ async function renderDirectoryListing(store, requestPath, siteConfig, searchEnab
|
|
|
424
608
|
stylesheetContent: siteConfig.stylesheetContent,
|
|
425
609
|
canonicalPath: requestPath,
|
|
426
610
|
alternateMarkdownPath: getMarkdownRequestPathForContentPath(getDirectoryIndexContentPathForRequestPath(requestPath)),
|
|
611
|
+
rssFeedUrl: getRssFeedUrl(siteConfig.siteUrl, siteConfig),
|
|
427
612
|
searchEnabled,
|
|
428
613
|
}),
|
|
429
614
|
};
|
|
@@ -558,6 +743,23 @@ async function tryRedirectAlternateDirectoryMarkdown(store, resolved, options) {
|
|
|
558
743
|
}
|
|
559
744
|
return null;
|
|
560
745
|
}
|
|
746
|
+
async function tryRedirectCanonicalDirectoryPath(store, resolved) {
|
|
747
|
+
if (resolved.kind !== 'html' || resolved.requestPath.endsWith('/')) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
if (path.posix.extname(resolved.requestPath) !== '') {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
const directoryPath = resolved.requestPath.slice(1);
|
|
754
|
+
if (directoryPath === '') {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
const directoryEntries = await store.listDirectory(directoryPath);
|
|
758
|
+
if (directoryEntries === null) {
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
return redirect(`${resolved.requestPath}/`);
|
|
762
|
+
}
|
|
561
763
|
function getMarkdownRequestPathForContentPath(contentPath) {
|
|
562
764
|
return `/${contentPath}`;
|
|
563
765
|
}
|
|
@@ -628,6 +830,10 @@ async function findAliasRedirectLocation(store, directoryPath, requestPath, opti
|
|
|
628
830
|
function isMarkdownEntry(entry) {
|
|
629
831
|
return path.posix.extname(entry.name).toLowerCase() === '.md';
|
|
630
832
|
}
|
|
833
|
+
function isDirectoryIndexContentPath(contentPath) {
|
|
834
|
+
const basename = path.posix.basename(contentPath).toLowerCase();
|
|
835
|
+
return basename === 'index.md' || basename === 'readme.md' || basename === 'skill.md';
|
|
836
|
+
}
|
|
631
837
|
function normalizeAliases(aliases) {
|
|
632
838
|
if (!Array.isArray(aliases)) {
|
|
633
839
|
return [];
|
|
@@ -642,12 +848,23 @@ function normalizeAliases(aliases) {
|
|
|
642
848
|
}
|
|
643
849
|
function getCanonicalHtmlPathForContentPath(contentPath) {
|
|
644
850
|
const basename = path.posix.basename(contentPath).toLowerCase();
|
|
645
|
-
if (basename === 'index.md' ||
|
|
851
|
+
if (basename === 'index.md' ||
|
|
852
|
+
basename === 'readme.md' ||
|
|
853
|
+
basename === 'skill.md') {
|
|
646
854
|
const directory = path.posix.dirname(contentPath);
|
|
647
855
|
return directory === '.' ? '/' : `/${directory}/`;
|
|
648
856
|
}
|
|
649
857
|
return `/${contentPath.slice(0, -'.md'.length)}`;
|
|
650
858
|
}
|
|
859
|
+
function isRssEnabled(siteConfig) {
|
|
860
|
+
return Boolean(siteConfig.siteUrl) && (siteConfig.rss?.enabled ?? true);
|
|
861
|
+
}
|
|
862
|
+
function getRssFeedUrl(siteUrl, siteConfig) {
|
|
863
|
+
if (!siteUrl || !isRssEnabled(siteConfig)) {
|
|
864
|
+
return undefined;
|
|
865
|
+
}
|
|
866
|
+
return new URL('feed.xml', ensureTrailingSlash(siteUrl)).toString();
|
|
867
|
+
}
|
|
651
868
|
function getEditLinkHref(siteConfig, sourcePath) {
|
|
652
869
|
if (!siteConfig.editLink || !sourcePath) {
|
|
653
870
|
return undefined;
|
|
@@ -731,6 +948,9 @@ async function inspectDirectoryShape(store, directoryPath) {
|
|
|
731
948
|
hasAssetFiles: false,
|
|
732
949
|
};
|
|
733
950
|
}
|
|
951
|
+
return inspectDirectoryShapeEntries(entries);
|
|
952
|
+
}
|
|
953
|
+
function inspectDirectoryShapeEntries(entries) {
|
|
734
954
|
let hasSkillIndex = false;
|
|
735
955
|
let hasChildDirectories = false;
|
|
736
956
|
let hasExtraMarkdownFiles = false;
|
|
@@ -17,6 +17,19 @@ export interface SiteSocialLink {
|
|
|
17
17
|
export interface EditLinkConfig {
|
|
18
18
|
baseUrl: string;
|
|
19
19
|
}
|
|
20
|
+
export interface SiteRssConfigInput {
|
|
21
|
+
title?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
author?: string;
|
|
24
|
+
maxItems?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface SiteRssConfig {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
title?: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
author?: string;
|
|
31
|
+
maxItems: number;
|
|
32
|
+
}
|
|
20
33
|
export interface SiteSearchRerankerConfig {
|
|
21
34
|
kind?: 'embedding-v1' | 'heuristic-v1';
|
|
22
35
|
candidatePoolSize?: number;
|
|
@@ -63,6 +76,7 @@ export interface SiteConfig {
|
|
|
63
76
|
footerText?: string;
|
|
64
77
|
socialLinks?: SiteSocialLink[];
|
|
65
78
|
editLink?: EditLinkConfig;
|
|
79
|
+
rss?: false | SiteRssConfigInput;
|
|
66
80
|
showHomeIndex?: boolean;
|
|
67
81
|
listingInitialPostCount?: number;
|
|
68
82
|
listingLoadMoreStep?: number;
|
|
@@ -85,6 +99,7 @@ export interface ResolvedSiteConfig {
|
|
|
85
99
|
footerText?: string;
|
|
86
100
|
socialLinks: SiteSocialLink[];
|
|
87
101
|
editLink?: EditLinkConfig;
|
|
102
|
+
rss?: SiteRssConfig;
|
|
88
103
|
showHomeIndex: boolean;
|
|
89
104
|
listingInitialPostCount: number;
|
|
90
105
|
listingLoadMoreStep: number;
|
package/dist/core/site-config.js
CHANGED
|
@@ -42,6 +42,7 @@ export async function loadUserSiteConfig(options = {}) {
|
|
|
42
42
|
: undefined,
|
|
43
43
|
socialLinks: normalizeSocialLinks(parsedConfig.socialLinks),
|
|
44
44
|
editLink: normalizeEditLink(parsedConfig.editLink),
|
|
45
|
+
rss: normalizeRssConfig(parsedConfig.rss),
|
|
45
46
|
showHomeIndex: typeof parsedConfig.showHomeIndex === 'boolean'
|
|
46
47
|
? parsedConfig.showHomeIndex
|
|
47
48
|
: normalizeTopNav(parsedConfig.topNav).length === 0,
|
|
@@ -222,6 +223,36 @@ function normalizeOptionalNumber(value) {
|
|
|
222
223
|
}
|
|
223
224
|
return undefined;
|
|
224
225
|
}
|
|
226
|
+
function normalizeRssConfig(value) {
|
|
227
|
+
if (value === false) {
|
|
228
|
+
return {
|
|
229
|
+
enabled: false,
|
|
230
|
+
title: undefined,
|
|
231
|
+
description: undefined,
|
|
232
|
+
author: undefined,
|
|
233
|
+
maxItems: 20,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (typeof value !== 'object' || value === null) {
|
|
237
|
+
return {
|
|
238
|
+
enabled: true,
|
|
239
|
+
title: undefined,
|
|
240
|
+
description: undefined,
|
|
241
|
+
author: undefined,
|
|
242
|
+
maxItems: 20,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const rss = value;
|
|
246
|
+
return {
|
|
247
|
+
enabled: true,
|
|
248
|
+
title: typeof rss.title === 'string' && rss.title !== '' ? rss.title : undefined,
|
|
249
|
+
description: typeof rss.description === 'string' && rss.description !== ''
|
|
250
|
+
? rss.description
|
|
251
|
+
: undefined,
|
|
252
|
+
author: typeof rss.author === 'string' && rss.author !== '' ? rss.author : undefined,
|
|
253
|
+
maxItems: normalizePositiveInteger(rss.maxItems, 20),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
225
256
|
function normalizeSearchConfig(value, configFilePath) {
|
|
226
257
|
if (typeof value !== 'object' || value === null) {
|
|
227
258
|
return undefined;
|
package/dist/html/template.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface RenderDocumentOptions {
|
|
|
21
21
|
stylesheetContent?: string;
|
|
22
22
|
canonicalPath?: string;
|
|
23
23
|
alternateMarkdownPath?: string;
|
|
24
|
+
rssFeedUrl?: string;
|
|
24
25
|
listingEntries?: ManagedIndexEntry[];
|
|
25
26
|
listingRequestPath?: string;
|
|
26
27
|
listingInitialPostCount?: number;
|
package/dist/html/template.js
CHANGED
|
@@ -25,6 +25,9 @@ export function renderDocument(options) {
|
|
|
25
25
|
const alternateMarkdownMeta = options.alternateMarkdownPath
|
|
26
26
|
? `<link rel="alternate" type="text/markdown" href="${escapeHtml(options.alternateMarkdownPath)}">`
|
|
27
27
|
: '';
|
|
28
|
+
const rssMeta = options.rssFeedUrl
|
|
29
|
+
? `<link rel="alternate" type="application/rss+xml" title="${siteTitle}" href="${escapeHtml(options.rssFeedUrl)}">`
|
|
30
|
+
: '';
|
|
28
31
|
const stylesheetBlock = `<style>${getDefaultThemeStyles()}${options.stylesheetContent ? `\n${options.stylesheetContent}` : ''}</style>`;
|
|
29
32
|
const navBlock = options.topNav && options.topNav.length > 0
|
|
30
33
|
? `<nav class="site-nav"><ul>${options.topNav
|
|
@@ -107,6 +110,7 @@ export function renderDocument(options) {
|
|
|
107
110
|
faviconMeta,
|
|
108
111
|
socialImageMeta,
|
|
109
112
|
alternateMarkdownMeta,
|
|
113
|
+
rssMeta,
|
|
110
114
|
stylesheetBlock,
|
|
111
115
|
'</head>',
|
|
112
116
|
'<body>',
|
package/dist/search.js
CHANGED
|
@@ -4,6 +4,7 @@ import { inferDirectoryContentType } from './core/content-type.js';
|
|
|
4
4
|
import { getDirectoryIndexCandidates } from './core/directory-index.js';
|
|
5
5
|
import { getDocumentSummary, getDocumentTitle, parseMarkdownDocument, stripMachineOnlyMarkdownComments, stripManagedIndexBlock, } from './core/markdown.js';
|
|
6
6
|
import { isIgnoredContentName } from './core/content-store.js';
|
|
7
|
+
import { ensureTrailingSlash, trimLeadingSlash } from './core/site-url.js';
|
|
7
8
|
const OVERVIEW_CONTENT_FILENAMES = new Set(['readme.md', 'index.md', 'skill.md']);
|
|
8
9
|
export async function buildSearchBundle(options) {
|
|
9
10
|
const buildModule = await loadIndexbindBuildModule();
|
|
@@ -276,12 +277,6 @@ function getCanonicalHtmlPathForContentPath(contentPath) {
|
|
|
276
277
|
}
|
|
277
278
|
return `/${contentPath.slice(0, -'.md'.length)}`;
|
|
278
279
|
}
|
|
279
|
-
function trimLeadingSlash(value) {
|
|
280
|
-
return value.startsWith('/') ? value.slice(1) : value;
|
|
281
|
-
}
|
|
282
|
-
function ensureTrailingSlash(value) {
|
|
283
|
-
return value.endsWith('/') ? value : `${value}/`;
|
|
284
|
-
}
|
|
285
280
|
async function pathExists(filePath) {
|
|
286
281
|
try {
|
|
287
282
|
await stat(filePath);
|