@zeropress/build-core 0.5.2 → 0.6.2
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/README.md +34 -40
- package/package.json +3 -3
- package/src/api/build-site.js +805 -135
- package/src/index.d.ts +4 -3
- package/src/render/content-renderer.js +20 -2
- package/src/render/control-flow-renderer.js +251 -39
- package/src/render/partial-resolver.js +42 -6
- package/src/render/variable-resolver.js +4 -0
- package/src/render/zeropress-engine.js +2 -2
- package/src/theme/load-theme-dir.js +56 -1
package/src/api/build-site.js
CHANGED
|
@@ -14,10 +14,13 @@ const DEFAULT_OPTIONS = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
const DEFAULT_POSTS_PER_PAGE = 10;
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const DEFAULT_DATETIME_DISPLAY = 'static';
|
|
18
|
+
const DEFAULT_DATE_STYLE = 'medium';
|
|
19
|
+
const DEFAULT_TIME_STYLE = 'none';
|
|
19
20
|
const DEFAULT_TIMEZONE = 'UTC';
|
|
20
21
|
const DEFAULT_LOCALE = 'en-US';
|
|
22
|
+
const DATETIME_DISPLAY_MODES = new Set(['static', 'client']);
|
|
23
|
+
const DATETIME_STYLES = new Set(['none', 'short', 'medium', 'long', 'full']);
|
|
21
24
|
const DEFAULT_PERMALINKS = Object.freeze({
|
|
22
25
|
output_style: 'directory',
|
|
23
26
|
posts: '/posts/:slug/',
|
|
@@ -35,12 +38,25 @@ const DEFAULT_POST_INDEX = Object.freeze({
|
|
|
35
38
|
});
|
|
36
39
|
const PERMALINK_OUTPUT_STYLES = new Set(['directory', 'html-extension']);
|
|
37
40
|
const COMMENT_POLICY_OUTPUT_PATH = '_zeropress/comment-policy.json';
|
|
41
|
+
const SEARCH_INDEX_OUTPUT_PATH = '_zeropress/search.json';
|
|
42
|
+
const SEARCH_ADAPTER_OUTPUT_PATH = '_zeropress/search.js';
|
|
43
|
+
const SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH = '_zeropress/search_pagefind.js';
|
|
38
44
|
const OUTPUT_PATH_CONTROL_CHAR_PATTERN = /[\u0000-\u001F\u007F]/;
|
|
39
45
|
const SAFE_MEDIA_PROTOCOLS = new Set(['http:', 'https:']);
|
|
40
46
|
const SAFE_LINK_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
|
|
41
47
|
const MEDIA_DELIVERY_MODES = new Set(['none', 'media_domain']);
|
|
48
|
+
const DISCOVERABILITY_VALUES = new Set(['default', 'noindex', 'delist']);
|
|
42
49
|
const RESPONSIVE_IMAGE_WIDTHS = [320, 480, 768, 1024, 1280, 1600, 1920];
|
|
43
50
|
const RESPONSIVE_IMAGE_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'webp', 'avif']);
|
|
51
|
+
const SEARCH_FIELD_WEIGHTS = Object.freeze({
|
|
52
|
+
title: 5,
|
|
53
|
+
headings: 3,
|
|
54
|
+
tags: 2.5,
|
|
55
|
+
categories: 2,
|
|
56
|
+
excerpt: 1.5,
|
|
57
|
+
content_text: 1,
|
|
58
|
+
});
|
|
59
|
+
const SEARCH_RECENCY_BOOST_MAX = 0.15;
|
|
44
60
|
export async function buildSite(input) {
|
|
45
61
|
const options = { ...DEFAULT_OPTIONS, ...(input.options || {}) };
|
|
46
62
|
const state = await createBuildState(input, options);
|
|
@@ -92,10 +108,22 @@ export async function buildSite(input) {
|
|
|
92
108
|
'application/json',
|
|
93
109
|
);
|
|
94
110
|
|
|
111
|
+
if (shouldGenerateSearchArtifacts(state, options)) {
|
|
112
|
+
await writeOutput(state.writer, state.summaries, SEARCH_INDEX_OUTPUT_PATH, buildSearchIndexJson(state), 'application/json');
|
|
113
|
+
await writeOutput(state.writer, state.summaries, SEARCH_ADAPTER_OUTPUT_PATH, buildSearchAdapterJs(), 'application/javascript');
|
|
114
|
+
await writeOutput(state.writer, state.summaries, SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH, buildSearchPagefindAdapterJs(), 'application/javascript');
|
|
115
|
+
}
|
|
116
|
+
|
|
95
117
|
if (options.generateSpecialFiles) {
|
|
96
118
|
await maybeRenderNotFoundPage(state);
|
|
97
119
|
if (hasCanonicalSiteUrl(state.previewData.site.url)) {
|
|
98
|
-
await writeOutput(
|
|
120
|
+
await writeOutput(
|
|
121
|
+
state.writer,
|
|
122
|
+
state.summaries,
|
|
123
|
+
'sitemap.xml',
|
|
124
|
+
buildSitemapXml(state.previewData.site, state.emitted, state.generatedAt, options.sitemapStylesheetHref),
|
|
125
|
+
'application/xml',
|
|
126
|
+
);
|
|
99
127
|
await writeOutput(state.writer, state.summaries, 'feed.xml', buildFeedXml(state.previewData.site, state.emitted, state.generatedAt), 'application/rss+xml');
|
|
100
128
|
}
|
|
101
129
|
if (shouldGenerateRobotsTxt(options)) {
|
|
@@ -144,6 +172,7 @@ async function createBuildState(input, options) {
|
|
|
144
172
|
customCssHref: customCssAsset ? `/${customCssAsset.path}` : '',
|
|
145
173
|
customHtml: previewData.custom_html,
|
|
146
174
|
favicon: previewData.site.favicon,
|
|
175
|
+
exposeGenerator: previewData.site.expose_generator !== false,
|
|
147
176
|
commentPolicyContent: buildCommentPolicyManifest(renderData.posts),
|
|
148
177
|
options,
|
|
149
178
|
generatedAt: new Date(),
|
|
@@ -261,6 +290,7 @@ async function renderFrontPage(state, route) {
|
|
|
261
290
|
description: page.excerpt,
|
|
262
291
|
ogType: 'website',
|
|
263
292
|
image: page.featured_image,
|
|
293
|
+
robotsNoindex: shouldNoindexDocument(page),
|
|
264
294
|
}),
|
|
265
295
|
},
|
|
266
296
|
createRenderContext(state.previewData.site, currentUrl),
|
|
@@ -273,6 +303,7 @@ async function renderFrontPage(state, route) {
|
|
|
273
303
|
title: page.title,
|
|
274
304
|
description: page.excerpt,
|
|
275
305
|
includeInFeed: false,
|
|
306
|
+
includeInSitemap: !isDelistedDocument(page),
|
|
276
307
|
};
|
|
277
308
|
return;
|
|
278
309
|
}
|
|
@@ -325,6 +356,7 @@ async function renderPost(state, post) {
|
|
|
325
356
|
image: post.featured_image,
|
|
326
357
|
publishedTime: post.published_at_iso,
|
|
327
358
|
modifiedTime: post.updated_at_iso,
|
|
359
|
+
robotsNoindex: shouldNoindexDocument(post),
|
|
328
360
|
}),
|
|
329
361
|
},
|
|
330
362
|
createRenderContext(state.previewData.site, currentUrl),
|
|
@@ -332,14 +364,16 @@ async function renderPost(state, post) {
|
|
|
332
364
|
html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
|
|
333
365
|
html = injectSiteCustomizations(html, state);
|
|
334
366
|
await writeOutput(state.writer, state.summaries, routePathToOutputPath(post.url, state.previewData.site.permalinks.output_style), html, 'text/html');
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
367
|
+
if (!isDelistedDocument(post)) {
|
|
368
|
+
state.emitted.posts.push({
|
|
369
|
+
url: currentUrl,
|
|
370
|
+
title: post.title,
|
|
371
|
+
description: post.excerpt,
|
|
372
|
+
publishedAt: post.published_at_iso,
|
|
373
|
+
updatedAt: post.updated_at_iso,
|
|
374
|
+
status: post.status,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
343
377
|
}
|
|
344
378
|
|
|
345
379
|
async function renderPage(state, page) {
|
|
@@ -362,6 +396,7 @@ async function renderPage(state, page) {
|
|
|
362
396
|
description: page.excerpt,
|
|
363
397
|
ogType: 'website',
|
|
364
398
|
image: page.featured_image,
|
|
399
|
+
robotsNoindex: shouldNoindexDocument(page),
|
|
365
400
|
}),
|
|
366
401
|
},
|
|
367
402
|
createRenderContext(state.previewData.site, currentUrl),
|
|
@@ -374,7 +409,7 @@ async function renderPage(state, page) {
|
|
|
374
409
|
title: page.title,
|
|
375
410
|
description: page.excerpt,
|
|
376
411
|
status: page.status,
|
|
377
|
-
includeInSitemap: page.omit_from_sitemap !== true,
|
|
412
|
+
includeInSitemap: page.omit_from_sitemap !== true && !isDelistedDocument(page),
|
|
378
413
|
});
|
|
379
414
|
}
|
|
380
415
|
|
|
@@ -408,19 +443,28 @@ async function maybeRenderNotFoundPage(state) {
|
|
|
408
443
|
function normalizePreviewData(previewData, options = {}) {
|
|
409
444
|
const normalizedSite = {
|
|
410
445
|
...previewData.site,
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
? previewData.site.
|
|
446
|
+
media_base_url: normalizeOptionalString(previewData.site.media_base_url),
|
|
447
|
+
media_delivery_mode: MEDIA_DELIVERY_MODES.has(previewData.site.media_delivery_mode)
|
|
448
|
+
? previewData.site.media_delivery_mode
|
|
414
449
|
: 'none',
|
|
415
450
|
favicon: normalizeSiteFavicon(previewData.site.favicon || options.favicon),
|
|
416
|
-
|
|
417
|
-
? previewData.site.
|
|
451
|
+
posts_per_page: Number.isInteger(previewData.site.posts_per_page) && previewData.site.posts_per_page > 0
|
|
452
|
+
? previewData.site.posts_per_page
|
|
418
453
|
: DEFAULT_POSTS_PER_PAGE,
|
|
419
|
-
|
|
420
|
-
|
|
454
|
+
datetime_display: DATETIME_DISPLAY_MODES.has(previewData.site.datetime_display)
|
|
455
|
+
? previewData.site.datetime_display
|
|
456
|
+
: DEFAULT_DATETIME_DISPLAY,
|
|
457
|
+
date_style: DATETIME_STYLES.has(previewData.site.date_style)
|
|
458
|
+
? previewData.site.date_style
|
|
459
|
+
: DEFAULT_DATE_STYLE,
|
|
460
|
+
time_style: DATETIME_STYLES.has(previewData.site.time_style)
|
|
461
|
+
? previewData.site.time_style
|
|
462
|
+
: DEFAULT_TIME_STYLE,
|
|
421
463
|
timezone: normalizeNonEmptyString(previewData.site.timezone, DEFAULT_TIMEZONE),
|
|
422
464
|
locale: normalizeLocale(previewData.site.locale || DEFAULT_LOCALE),
|
|
423
|
-
|
|
465
|
+
disallow_comments: previewData.site.disallow_comments === true,
|
|
466
|
+
expose_generator: previewData.site.expose_generator !== false,
|
|
467
|
+
search: previewData.site.search !== false,
|
|
424
468
|
indexing: previewData.site.indexing !== false,
|
|
425
469
|
permalinks: normalizePermalinks(previewData.site.permalinks),
|
|
426
470
|
front_page: normalizeFrontPage(previewData.site.front_page),
|
|
@@ -435,13 +479,13 @@ function normalizePreviewData(previewData, options = {}) {
|
|
|
435
479
|
site: normalizedSite,
|
|
436
480
|
menus: normalizeRecordMap(previewData.menus),
|
|
437
481
|
collections: normalizeCollections(previewData.collections),
|
|
438
|
-
widgets: normalizeWidgetAreas(previewData.widgets, normalizedSite.
|
|
482
|
+
widgets: normalizeWidgetAreas(previewData.widgets, normalizedSite.media_base_url),
|
|
439
483
|
custom_css: normalizeCustomCss(previewData.custom_css),
|
|
440
484
|
custom_html: normalizeCustomHtml(previewData.custom_html),
|
|
441
485
|
content: {
|
|
442
486
|
...previewData.content,
|
|
443
487
|
authors: previewData.content.authors.map((author) => {
|
|
444
|
-
const avatar = normalizeMediaField(author.avatar, normalizedSite.
|
|
488
|
+
const avatar = normalizeMediaField(author.avatar, normalizedSite.media_base_url);
|
|
445
489
|
const avatarMedia = deriveManagedMedia(avatar, mediaRegistry, normalizedSite);
|
|
446
490
|
return {
|
|
447
491
|
...author,
|
|
@@ -451,22 +495,24 @@ function normalizePreviewData(previewData, options = {}) {
|
|
|
451
495
|
}),
|
|
452
496
|
posts: previewData.content.posts
|
|
453
497
|
.map((post) => {
|
|
454
|
-
const featuredImage = normalizeMediaField(post.featured_image, normalizedSite.
|
|
498
|
+
const featuredImage = normalizeMediaField(post.featured_image, normalizedSite.media_base_url);
|
|
455
499
|
const featuredMedia = deriveManagedMedia(featuredImage, mediaRegistry, normalizedSite);
|
|
456
500
|
return {
|
|
457
501
|
...post,
|
|
458
502
|
published_at_iso: normalizeIsoTimestamp(post.published_at_iso),
|
|
459
503
|
updated_at_iso: normalizeIsoTimestamp(post.updated_at_iso),
|
|
504
|
+
discoverability: normalizeDiscoverability(post.discoverability),
|
|
460
505
|
featured_image: featuredImage,
|
|
461
506
|
...(featuredMedia ? { featured_media: featuredMedia } : {}),
|
|
462
507
|
};
|
|
463
508
|
})
|
|
464
509
|
.sort((left, right) => toDate(right.published_at_iso).getTime() - toDate(left.published_at_iso).getTime()),
|
|
465
510
|
pages: previewData.content.pages.map((page) => {
|
|
466
|
-
const featuredImage = normalizeMediaField(page.featured_image, normalizedSite.
|
|
511
|
+
const featuredImage = normalizeMediaField(page.featured_image, normalizedSite.media_base_url);
|
|
467
512
|
const featuredMedia = deriveManagedMedia(featuredImage, mediaRegistry, normalizedSite);
|
|
468
513
|
return {
|
|
469
514
|
...page,
|
|
515
|
+
discoverability: normalizeDiscoverability(page.discoverability),
|
|
470
516
|
featured_image: featuredImage,
|
|
471
517
|
...(featuredMedia ? { featured_media: featuredMedia } : {}),
|
|
472
518
|
};
|
|
@@ -486,6 +532,18 @@ function normalizeRecordMap(value) {
|
|
|
486
532
|
return { ...value };
|
|
487
533
|
}
|
|
488
534
|
|
|
535
|
+
function normalizeDiscoverability(value) {
|
|
536
|
+
return DISCOVERABILITY_VALUES.has(value) ? value : 'default';
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function isDelistedDocument(document) {
|
|
540
|
+
return document?.discoverability === 'delist';
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function shouldNoindexDocument(document) {
|
|
544
|
+
return document?.discoverability === 'noindex' || document?.discoverability === 'delist';
|
|
545
|
+
}
|
|
546
|
+
|
|
489
547
|
function normalizeContentMedia(mediaItems, site) {
|
|
490
548
|
if (!Array.isArray(mediaItems)) {
|
|
491
549
|
return [];
|
|
@@ -496,7 +554,7 @@ function normalizeContentMedia(mediaItems, site) {
|
|
|
496
554
|
if (!item || typeof item !== 'object') {
|
|
497
555
|
return null;
|
|
498
556
|
}
|
|
499
|
-
const src = normalizeMediaField(item.src, site.
|
|
557
|
+
const src = normalizeMediaField(item.src, site.media_base_url);
|
|
500
558
|
const width = Number.isInteger(item.width) && item.width > 0 ? item.width : 0;
|
|
501
559
|
const height = Number.isInteger(item.height) && item.height > 0 ? item.height : 0;
|
|
502
560
|
if (!src || !width || !height) {
|
|
@@ -538,12 +596,12 @@ function deriveManagedMedia(src, mediaRegistry, site) {
|
|
|
538
596
|
}
|
|
539
597
|
|
|
540
598
|
function buildResponsiveImageSrcset(media, site) {
|
|
541
|
-
if (site.
|
|
599
|
+
if (site.media_delivery_mode !== 'media_domain') {
|
|
542
600
|
return '';
|
|
543
601
|
}
|
|
544
602
|
|
|
545
|
-
const
|
|
546
|
-
if (!
|
|
603
|
+
const media_base_url = normalizeOptionalString(site.media_base_url);
|
|
604
|
+
if (!media_base_url || !isUrlUnderMediaBase(media.src, media_base_url) || !isResponsiveRasterImage(media.src)) {
|
|
547
605
|
return '';
|
|
548
606
|
}
|
|
549
607
|
|
|
@@ -570,10 +628,10 @@ function buildResponsiveImageVariantUrl(src, width) {
|
|
|
570
628
|
}
|
|
571
629
|
}
|
|
572
630
|
|
|
573
|
-
function isUrlUnderMediaBase(src,
|
|
631
|
+
function isUrlUnderMediaBase(src, media_base_url) {
|
|
574
632
|
try {
|
|
575
633
|
const sourceUrl = new URL(src);
|
|
576
|
-
const baseUrl = new URL(
|
|
634
|
+
const baseUrl = new URL(media_base_url);
|
|
577
635
|
const basePath = baseUrl.pathname.endsWith('/') ? baseUrl.pathname : `${baseUrl.pathname}/`;
|
|
578
636
|
return sourceUrl.origin === baseUrl.origin && sourceUrl.pathname.startsWith(basePath);
|
|
579
637
|
} catch {
|
|
@@ -610,19 +668,19 @@ function normalizeCollections(collections) {
|
|
|
610
668
|
);
|
|
611
669
|
}
|
|
612
670
|
|
|
613
|
-
function normalizeWidgetAreas(
|
|
614
|
-
if (!
|
|
671
|
+
function normalizeWidgetAreas(widget_areas, media_base_url) {
|
|
672
|
+
if (!widget_areas || typeof widget_areas !== 'object') {
|
|
615
673
|
return {};
|
|
616
674
|
}
|
|
617
675
|
|
|
618
676
|
return Object.fromEntries(
|
|
619
|
-
Object.entries(
|
|
677
|
+
Object.entries(widget_areas).map(([widgetAreaId, widgetArea]) => [
|
|
620
678
|
widgetAreaId,
|
|
621
679
|
{
|
|
622
680
|
...widgetArea,
|
|
623
681
|
name: normalizeNonEmptyString(widgetArea?.name, widgetAreaId),
|
|
624
682
|
items: Array.isArray(widgetArea?.items)
|
|
625
|
-
? widgetArea.items.map((item) => normalizeWidgetItem(item,
|
|
683
|
+
? widgetArea.items.map((item) => normalizeWidgetItem(item, media_base_url))
|
|
626
684
|
: [],
|
|
627
685
|
},
|
|
628
686
|
]),
|
|
@@ -631,21 +689,15 @@ function normalizeWidgetAreas(widgetAreas, mediaBaseUrl) {
|
|
|
631
689
|
|
|
632
690
|
function normalizeSiteFooter(footer) {
|
|
633
691
|
const source = footer && typeof footer === 'object' && !Array.isArray(footer) ? footer : {};
|
|
634
|
-
const attribution = source.attribution && typeof source.attribution === 'object' && !Array.isArray(source.attribution)
|
|
635
|
-
? source.attribution
|
|
636
|
-
: {};
|
|
637
692
|
|
|
638
693
|
return {
|
|
639
694
|
...source,
|
|
640
695
|
copyright_text: normalizeOptionalString(source.copyright_text),
|
|
641
|
-
attribution:
|
|
642
|
-
...attribution,
|
|
643
|
-
enabled: attribution.enabled !== false,
|
|
644
|
-
},
|
|
696
|
+
attribution: source.attribution !== false,
|
|
645
697
|
};
|
|
646
698
|
}
|
|
647
699
|
|
|
648
|
-
function normalizeWidgetItem(item,
|
|
700
|
+
function normalizeWidgetItem(item, media_base_url) {
|
|
649
701
|
const normalizedItem = {
|
|
650
702
|
...item,
|
|
651
703
|
title: typeof item?.title === 'string' ? item.title.trim() : '',
|
|
@@ -656,7 +708,7 @@ function normalizeWidgetItem(item, mediaBaseUrl) {
|
|
|
656
708
|
...normalizedItem,
|
|
657
709
|
settings: {
|
|
658
710
|
...item.settings,
|
|
659
|
-
avatar: normalizeMediaField(item.settings.avatar,
|
|
711
|
+
avatar: normalizeMediaField(item.settings.avatar, media_base_url),
|
|
660
712
|
},
|
|
661
713
|
};
|
|
662
714
|
}
|
|
@@ -733,21 +785,23 @@ function normalizeFrontPage(frontPage) {
|
|
|
733
785
|
};
|
|
734
786
|
}
|
|
735
787
|
|
|
736
|
-
function normalizePostIndex(
|
|
737
|
-
if (!
|
|
788
|
+
function normalizePostIndex(post_index) {
|
|
789
|
+
if (!post_index || typeof post_index !== 'object') {
|
|
738
790
|
return { ...DEFAULT_POST_INDEX };
|
|
739
791
|
}
|
|
740
792
|
|
|
741
793
|
return {
|
|
742
|
-
enabled:
|
|
743
|
-
path: normalizeNonEmptyString(
|
|
744
|
-
paginate:
|
|
794
|
+
enabled: post_index.enabled !== false,
|
|
795
|
+
path: normalizeNonEmptyString(post_index.path, DEFAULT_POST_INDEX.path),
|
|
796
|
+
paginate: post_index.paginate !== false,
|
|
745
797
|
};
|
|
746
798
|
}
|
|
747
799
|
|
|
748
800
|
function createRenderData(previewData, themeMetadata = {}) {
|
|
749
801
|
const themeSupportsComments = themeMetadata?.features?.comments === true;
|
|
750
|
-
const themeSupportsPostIndex = themeMetadata?.features?.
|
|
802
|
+
const themeSupportsPostIndex = themeMetadata?.features?.post_index !== false;
|
|
803
|
+
const themeSupportsSearch = themeMetadata?.features?.search === true;
|
|
804
|
+
previewData.site.search = previewData.site.search !== false && themeSupportsSearch;
|
|
751
805
|
const authorsById = new Map(previewData.content.authors.map((author) => [author.id, author]));
|
|
752
806
|
const categoriesBySlug = new Map(previewData.content.categories.map((category) => [category.slug, category]));
|
|
753
807
|
const tagsBySlug = new Map(previewData.content.tags.map((tag) => [tag.slug, tag]));
|
|
@@ -755,8 +809,9 @@ function createRenderData(previewData, themeMetadata = {}) {
|
|
|
755
809
|
const tagPostsBySlug = new Map();
|
|
756
810
|
const categoryCountBySlug = new Map();
|
|
757
811
|
const tagCountBySlug = new Map();
|
|
812
|
+
const discoverableSourcePosts = previewData.content.posts.filter((post) => !isDelistedDocument(post));
|
|
758
813
|
|
|
759
|
-
for (const post of
|
|
814
|
+
for (const post of discoverableSourcePosts) {
|
|
760
815
|
for (const slug of post.category_slugs) {
|
|
761
816
|
pushToSlugMap(categoryPostsBySlug, slug, post);
|
|
762
817
|
categoryCountBySlug.set(slug, (categoryCountBySlug.get(slug) || 0) + 1);
|
|
@@ -769,25 +824,34 @@ function createRenderData(previewData, themeMetadata = {}) {
|
|
|
769
824
|
}
|
|
770
825
|
|
|
771
826
|
const preparedPosts = previewData.content.posts.map((post) => preparePost(post, previewData.site, authorsById, categoriesBySlug, tagsBySlug, themeSupportsComments));
|
|
772
|
-
const
|
|
827
|
+
const discoverablePreparedPosts = preparedPosts.filter((post) => !isDelistedDocument(post));
|
|
828
|
+
const adjacentPostsBySlug = new Map(
|
|
829
|
+
discoverablePreparedPosts.map((post, index) => [post.slug, {
|
|
830
|
+
prev: index > 0 ? buildAdjacentPostSummary(discoverablePreparedPosts[index - 1]) : null,
|
|
831
|
+
next: index < discoverablePreparedPosts.length - 1 ? buildAdjacentPostSummary(discoverablePreparedPosts[index + 1]) : null,
|
|
832
|
+
}]),
|
|
833
|
+
);
|
|
834
|
+
const posts = preparedPosts.map((post) => ({
|
|
773
835
|
...post,
|
|
774
|
-
prev:
|
|
775
|
-
next:
|
|
836
|
+
prev: adjacentPostsBySlug.get(post.slug)?.prev || null,
|
|
837
|
+
next: adjacentPostsBySlug.get(post.slug)?.next || null,
|
|
776
838
|
}));
|
|
777
839
|
const pages = previewData.content.pages.map((page) => preparePage(page, previewData.site));
|
|
778
840
|
const postBySlug = new Map(posts.map((post) => [post.slug, post]));
|
|
779
841
|
const pageBySlug = new Map(pages.map((page) => [page.slug, page]));
|
|
780
842
|
const frontPage = previewData.site.front_page;
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
const
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
843
|
+
const collections = resolveCollections(previewData.collections, postBySlug, pageBySlug, frontPage);
|
|
844
|
+
attachCollectionCursors(posts, pages, collections);
|
|
845
|
+
const post_index = previewData.site.post_index;
|
|
846
|
+
const effectivePostIndexEnabled = post_index.enabled !== false && themeSupportsPostIndex;
|
|
847
|
+
const effectivePostIndexPaginate = effectivePostIndexEnabled && post_index.paginate !== false;
|
|
848
|
+
const post_indexBasePath = normalizeRoutePath(post_index.path || DEFAULT_POST_INDEX.path);
|
|
849
|
+
|
|
850
|
+
if (frontPage.type !== 'theme_index' && effectivePostIndexEnabled && post_indexBasePath === '/') {
|
|
787
851
|
throw new Error('Invalid front page configuration: site.front_page occupies "/" so site.post_index.path must not be "/". Set site.post_index.path to a non-root path or disable site.post_index.');
|
|
788
852
|
}
|
|
789
853
|
|
|
790
|
-
const frontPageRoute = buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled,
|
|
854
|
+
const frontPageRoute = buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, post_indexBasePath);
|
|
791
855
|
const pageFrontPageSlug = frontPage.type === 'page' ? frontPage.page_slug : '';
|
|
792
856
|
const preparedPages = pageFrontPageSlug
|
|
793
857
|
? pages.filter((page) => page.slug !== pageFrontPageSlug)
|
|
@@ -797,22 +861,22 @@ function createRenderData(previewData, themeMetadata = {}) {
|
|
|
797
861
|
posts,
|
|
798
862
|
pages: preparedPages,
|
|
799
863
|
postBySlug,
|
|
800
|
-
collections
|
|
864
|
+
collections,
|
|
801
865
|
taxonomies: buildGlobalTaxonomies(previewData, categoryCountBySlug, tagCountBySlug),
|
|
802
866
|
frontPageRoute,
|
|
803
867
|
indexRoutes: buildPostIndexRoutes({
|
|
804
868
|
enabled: effectivePostIndexEnabled,
|
|
805
869
|
paginate: effectivePostIndexPaginate,
|
|
806
|
-
items:
|
|
807
|
-
|
|
808
|
-
basePath:
|
|
870
|
+
items: discoverableSourcePosts,
|
|
871
|
+
posts_per_page: previewData.site.posts_per_page,
|
|
872
|
+
basePath: post_indexBasePath,
|
|
809
873
|
outputStyle: previewData.site.permalinks.output_style,
|
|
810
874
|
postBySlug,
|
|
811
875
|
frontPage,
|
|
812
876
|
}),
|
|
813
877
|
archiveRoutes: buildPaginatedCollection({
|
|
814
|
-
items:
|
|
815
|
-
|
|
878
|
+
items: discoverableSourcePosts,
|
|
879
|
+
posts_per_page: previewData.site.posts_per_page,
|
|
816
880
|
basePath: '/archive/',
|
|
817
881
|
outputStyle: previewData.site.permalinks.output_style,
|
|
818
882
|
}).map((entry) => ({
|
|
@@ -828,7 +892,7 @@ function createRenderData(previewData, themeMetadata = {}) {
|
|
|
828
892
|
items: previewData.content.categories,
|
|
829
893
|
postsBySlug: categoryPostsBySlug,
|
|
830
894
|
postBySlug,
|
|
831
|
-
|
|
895
|
+
posts_per_page: previewData.site.posts_per_page,
|
|
832
896
|
outputStyle: previewData.site.permalinks.output_style,
|
|
833
897
|
buildBasePath: (category) => resolvePermalink(previewData.site, 'categories', category).path,
|
|
834
898
|
renderExtras: (category) => ({
|
|
@@ -839,7 +903,7 @@ function createRenderData(previewData, themeMetadata = {}) {
|
|
|
839
903
|
items: previewData.content.tags,
|
|
840
904
|
postsBySlug: tagPostsBySlug,
|
|
841
905
|
postBySlug,
|
|
842
|
-
|
|
906
|
+
posts_per_page: previewData.site.posts_per_page,
|
|
843
907
|
outputStyle: previewData.site.permalinks.output_style,
|
|
844
908
|
buildBasePath: (tag) => resolvePermalink(previewData.site, 'tags', tag).path,
|
|
845
909
|
renderExtras: (tag) => ({
|
|
@@ -862,15 +926,19 @@ function resolveCollections(collections, postBySlug, pageBySlug, frontPage) {
|
|
|
862
926
|
}
|
|
863
927
|
|
|
864
928
|
return Object.fromEntries(
|
|
865
|
-
Object.entries(collections).map(([collectionId, collection]) =>
|
|
866
|
-
collectionId,
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
929
|
+
Object.entries(collections).map(([collectionId, collection]) => {
|
|
930
|
+
const items = resolveCollectionItems(collectionId, collection?.items, postBySlug, pageBySlug, frontPage);
|
|
931
|
+
return [
|
|
932
|
+
collectionId,
|
|
933
|
+
{
|
|
934
|
+
id: collectionId,
|
|
935
|
+
title: normalizeOptionalString(collection?.title),
|
|
936
|
+
description: normalizeOptionalString(collection?.description),
|
|
937
|
+
count: items.length,
|
|
938
|
+
items,
|
|
939
|
+
},
|
|
940
|
+
];
|
|
941
|
+
}),
|
|
874
942
|
);
|
|
875
943
|
}
|
|
876
944
|
|
|
@@ -916,12 +984,70 @@ function buildCollectionPageSummary(page, frontPage) {
|
|
|
916
984
|
featured_image: page.featured_image || '',
|
|
917
985
|
...(page.featured_media ? { featured_media: { ...page.featured_media } } : {}),
|
|
918
986
|
meta: page.meta,
|
|
987
|
+
data: page.data,
|
|
919
988
|
};
|
|
920
989
|
}
|
|
921
990
|
|
|
922
|
-
function
|
|
991
|
+
function attachCollectionCursors(posts, pages, collections) {
|
|
992
|
+
const postTargets = new Map(posts.map((post) => [post.slug, post]));
|
|
993
|
+
const pageTargets = new Map(pages.map((page) => [page.slug, page]));
|
|
994
|
+
|
|
995
|
+
for (const [collectionId, collection] of Object.entries(collections || {})) {
|
|
996
|
+
const items = Array.isArray(collection.items) ? collection.items : [];
|
|
997
|
+
|
|
998
|
+
items.forEach((item, index) => {
|
|
999
|
+
const target = item.type === 'post'
|
|
1000
|
+
? postTargets.get(item.slug)
|
|
1001
|
+
: item.type === 'page'
|
|
1002
|
+
? pageTargets.get(item.slug)
|
|
1003
|
+
: null;
|
|
1004
|
+
|
|
1005
|
+
if (!target) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
target.collection_cursors = target.collection_cursors || {};
|
|
1010
|
+
target.collection_cursors[collectionId] = buildCollectionCursor(collectionId, collection, items, index);
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function buildCollectionCursor(collectionId, collection, items, index) {
|
|
1016
|
+
const count = items.length;
|
|
1017
|
+
|
|
1018
|
+
return {
|
|
1019
|
+
collection_id: collectionId,
|
|
1020
|
+
collection_title: collection.title || '',
|
|
1021
|
+
index,
|
|
1022
|
+
position: index + 1,
|
|
1023
|
+
count,
|
|
1024
|
+
first: index === 0,
|
|
1025
|
+
last: index === count - 1,
|
|
1026
|
+
prev: index > 0 ? buildCollectionCursorItemSummary(items[index - 1]) : null,
|
|
1027
|
+
next: index < count - 1 ? buildCollectionCursorItemSummary(items[index + 1]) : null,
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function buildCollectionCursorItemSummary(item) {
|
|
1032
|
+
if (!item) {
|
|
1033
|
+
return null;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
return {
|
|
1037
|
+
type: item.type,
|
|
1038
|
+
title: item.title,
|
|
1039
|
+
slug: item.slug,
|
|
1040
|
+
url: item.url,
|
|
1041
|
+
excerpt: item.excerpt || '',
|
|
1042
|
+
featured_image: item.featured_image || '',
|
|
1043
|
+
meta: item.meta,
|
|
1044
|
+
data: item.data,
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function buildFrontPageRoute(frontPage, pages, effectivePostIndexEnabled, post_indexBasePath) {
|
|
923
1049
|
if (frontPage.type === 'theme_index') {
|
|
924
|
-
if (effectivePostIndexEnabled &&
|
|
1050
|
+
if (effectivePostIndexEnabled && post_indexBasePath === '/') {
|
|
925
1051
|
return null;
|
|
926
1052
|
}
|
|
927
1053
|
|
|
@@ -967,7 +1093,7 @@ function buildPostIndexRoutes(options) {
|
|
|
967
1093
|
}
|
|
968
1094
|
|
|
969
1095
|
if (!options.paginate) {
|
|
970
|
-
const items = options.items.slice(0, options.
|
|
1096
|
+
const items = options.items.slice(0, options.posts_per_page);
|
|
971
1097
|
return [{
|
|
972
1098
|
path: options.basePath,
|
|
973
1099
|
route_type: 'post_index',
|
|
@@ -982,7 +1108,7 @@ function buildPostIndexRoutes(options) {
|
|
|
982
1108
|
|
|
983
1109
|
return buildPaginatedCollection({
|
|
984
1110
|
items: options.items,
|
|
985
|
-
|
|
1111
|
+
posts_per_page: options.posts_per_page,
|
|
986
1112
|
basePath: options.basePath,
|
|
987
1113
|
outputStyle: options.outputStyle,
|
|
988
1114
|
}).map((entry) => ({
|
|
@@ -1073,6 +1199,7 @@ function resolveWidgetItem(item, previewData, renderData, widgetAreaId, index) {
|
|
|
1073
1199
|
function resolveRecentPostsWidget(baseWidget, settings, renderData) {
|
|
1074
1200
|
const limit = clampInteger(settings?.limit, 5, 1, 20);
|
|
1075
1201
|
const items = renderData.posts
|
|
1202
|
+
.filter((post) => !isDelistedDocument(post))
|
|
1076
1203
|
.slice(0, limit)
|
|
1077
1204
|
.map((post) => ({
|
|
1078
1205
|
title: post.title,
|
|
@@ -1284,7 +1411,9 @@ function preparePost(post, site, authorsById, categoriesBySlug, tagsBySlug, them
|
|
|
1284
1411
|
featured_image: post.featured_image,
|
|
1285
1412
|
...(post.featured_media ? { featured_media: { ...post.featured_media } } : {}),
|
|
1286
1413
|
meta: post.meta,
|
|
1414
|
+
data: post.data,
|
|
1287
1415
|
status: post.status,
|
|
1416
|
+
discoverability: post.discoverability,
|
|
1288
1417
|
allow_comments: post.allow_comments,
|
|
1289
1418
|
category_slugs: post.category_slugs,
|
|
1290
1419
|
tag_slugs: post.tag_slugs,
|
|
@@ -1301,7 +1430,7 @@ function preparePost(post, site, authorsById, categoriesBySlug, tagsBySlug, them
|
|
|
1301
1430
|
published_at: formatTimestamp(post.published_at_iso, site),
|
|
1302
1431
|
updated_at: formatTimestamp(post.updated_at_iso, site),
|
|
1303
1432
|
reading_time: calculateReadingTime(renderedDocument.html),
|
|
1304
|
-
comments_enabled: themeSupportsComments && site.
|
|
1433
|
+
comments_enabled: themeSupportsComments && site.disallow_comments !== true && post.allow_comments === true,
|
|
1305
1434
|
};
|
|
1306
1435
|
}
|
|
1307
1436
|
|
|
@@ -1328,7 +1457,7 @@ function buildTaxonomyRoutes(options) {
|
|
|
1328
1457
|
|
|
1329
1458
|
const paginated = buildPaginatedCollection({
|
|
1330
1459
|
items: matchedPosts,
|
|
1331
|
-
|
|
1460
|
+
posts_per_page: options.posts_per_page,
|
|
1332
1461
|
basePath: options.buildBasePath(item),
|
|
1333
1462
|
outputStyle: options.outputStyle,
|
|
1334
1463
|
});
|
|
@@ -1352,14 +1481,14 @@ function buildTaxonomyRoutes(options) {
|
|
|
1352
1481
|
function buildPaginatedCollection(options) {
|
|
1353
1482
|
const basePath = normalizePaginationBasePath(options.basePath);
|
|
1354
1483
|
const outputStyle = PERMALINK_OUTPUT_STYLES.has(options.outputStyle) ? options.outputStyle : DEFAULT_PERMALINKS.output_style;
|
|
1355
|
-
const
|
|
1484
|
+
const posts_per_page = Number.isInteger(options.posts_per_page) && options.posts_per_page > 0 ? options.posts_per_page : DEFAULT_POSTS_PER_PAGE;
|
|
1356
1485
|
const totalPosts = options.items.length;
|
|
1357
|
-
const totalPages = Math.max(1, Math.ceil(totalPosts /
|
|
1486
|
+
const totalPages = Math.max(1, Math.ceil(totalPosts / posts_per_page));
|
|
1358
1487
|
const pages = [];
|
|
1359
1488
|
|
|
1360
1489
|
for (let page = 1; page <= totalPages; page += 1) {
|
|
1361
|
-
const start = (page - 1) *
|
|
1362
|
-
const end = start +
|
|
1490
|
+
const start = (page - 1) * posts_per_page;
|
|
1491
|
+
const end = start + posts_per_page;
|
|
1363
1492
|
|
|
1364
1493
|
pages.push({
|
|
1365
1494
|
path: buildPaginatedPath(basePath, page),
|
|
@@ -1535,6 +1664,8 @@ function buildStructuredPostSummary(post) {
|
|
|
1535
1664
|
reading_time: post.reading_time,
|
|
1536
1665
|
featured_image: post.featured_image,
|
|
1537
1666
|
...(post.featured_media ? { featured_media: { ...post.featured_media } } : {}),
|
|
1667
|
+
meta: post.meta,
|
|
1668
|
+
data: post.data,
|
|
1538
1669
|
author: {
|
|
1539
1670
|
display_name: post.author?.display_name || '',
|
|
1540
1671
|
avatar: post.author?.avatar || '',
|
|
@@ -1557,6 +1688,7 @@ function buildAdjacentPostSummary(post) {
|
|
|
1557
1688
|
excerpt: post.excerpt,
|
|
1558
1689
|
published_at: post.published_at,
|
|
1559
1690
|
published_at_iso: post.published_at_iso,
|
|
1691
|
+
data: post.data,
|
|
1560
1692
|
};
|
|
1561
1693
|
}
|
|
1562
1694
|
|
|
@@ -1646,50 +1778,25 @@ function formatArchiveLabel(date, site) {
|
|
|
1646
1778
|
function formatTimestamp(value, site) {
|
|
1647
1779
|
const date = toDate(value);
|
|
1648
1780
|
const locale = normalizeLocale(site.locale || DEFAULT_LOCALE);
|
|
1649
|
-
const
|
|
1650
|
-
const
|
|
1781
|
+
const dateStyle = DATETIME_STYLES.has(site.date_style) ? site.date_style : DEFAULT_DATE_STYLE;
|
|
1782
|
+
const timeStyle = DATETIME_STYLES.has(site.time_style) ? site.time_style : DEFAULT_TIME_STYLE;
|
|
1651
1783
|
const siteTimezone = normalizeNonEmptyString(site.timezone, DEFAULT_TIMEZONE);
|
|
1652
1784
|
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
year: 'numeric',
|
|
1656
|
-
month: dateFormat === 'MMMM D, YYYY' ? 'long' : '2-digit',
|
|
1657
|
-
day: '2-digit',
|
|
1658
|
-
}).formatToParts(date);
|
|
1659
|
-
|
|
1660
|
-
const year = dateParts.find((part) => part.type === 'year')?.value || '';
|
|
1661
|
-
const month = dateParts.find((part) => part.type === 'month')?.value || '';
|
|
1662
|
-
const day = dateParts.find((part) => part.type === 'day')?.value || '';
|
|
1663
|
-
|
|
1664
|
-
let formattedDate = `${year}-${month}-${day}`;
|
|
1665
|
-
if (dateFormat === 'DD/MM/YYYY') {
|
|
1666
|
-
formattedDate = `${day}/${month}/${year}`;
|
|
1667
|
-
} else if (dateFormat === 'MM/DD/YYYY') {
|
|
1668
|
-
formattedDate = `${month}/${day}/${year}`;
|
|
1669
|
-
} else if (dateFormat === 'MMMM D, YYYY') {
|
|
1670
|
-
formattedDate = `${month} ${String(Number(day))}, ${year}`;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
if (!timeFormat) {
|
|
1674
|
-
return formattedDate;
|
|
1785
|
+
if (dateStyle === 'none' && timeStyle === 'none') {
|
|
1786
|
+
return '';
|
|
1675
1787
|
}
|
|
1676
1788
|
|
|
1677
|
-
const
|
|
1789
|
+
const options = {
|
|
1678
1790
|
timeZone: siteTimezone,
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
const dayPeriod = (timeParts.find((part) => part.type === 'dayPeriod')?.value || '').toUpperCase();
|
|
1687
|
-
|
|
1688
|
-
const formattedTime = timeFormat === 'hh:mm A'
|
|
1689
|
-
? `${hour}:${minute}${dayPeriod ? ` ${dayPeriod}` : ''}`
|
|
1690
|
-
: `${hour}:${minute}`;
|
|
1791
|
+
};
|
|
1792
|
+
if (dateStyle !== 'none') {
|
|
1793
|
+
options.dateStyle = dateStyle;
|
|
1794
|
+
}
|
|
1795
|
+
if (timeStyle !== 'none') {
|
|
1796
|
+
options.timeStyle = timeStyle;
|
|
1797
|
+
}
|
|
1691
1798
|
|
|
1692
|
-
return
|
|
1799
|
+
return new Intl.DateTimeFormat(locale, options).format(date);
|
|
1693
1800
|
}
|
|
1694
1801
|
|
|
1695
1802
|
function calculateReadingTime(html) {
|
|
@@ -1785,11 +1892,12 @@ async function normalizeAndValidateThemePackage(themePackage) {
|
|
|
1785
1892
|
...(themePackage.metadata.author ? { author: themePackage.metadata.author } : {}),
|
|
1786
1893
|
...(themePackage.metadata.description ? { description: themePackage.metadata.description } : {}),
|
|
1787
1894
|
...(themePackage.metadata.thumbnail ? { thumbnail: themePackage.metadata.thumbnail } : {}),
|
|
1895
|
+
...(themePackage.metadata.links ? { links: themePackage.metadata.links } : {}),
|
|
1788
1896
|
...(themePackage.metadata.features ? { features: themePackage.metadata.features } : {}),
|
|
1789
|
-
...(themePackage.metadata.
|
|
1790
|
-
...(themePackage.metadata.
|
|
1791
|
-
...(themePackage.metadata.
|
|
1792
|
-
...(themePackage.metadata.
|
|
1897
|
+
...(themePackage.metadata.menu_slots ? { menu_slots: themePackage.metadata.menu_slots } : {}),
|
|
1898
|
+
...(themePackage.metadata.widget_areas ? { widget_areas: themePackage.metadata.widget_areas } : {}),
|
|
1899
|
+
...(themePackage.metadata.site_meta ? { site_meta: themePackage.metadata.site_meta } : {}),
|
|
1900
|
+
...(themePackage.metadata.collection_slots ? { collection_slots: themePackage.metadata.collection_slots } : {}),
|
|
1793
1901
|
}));
|
|
1794
1902
|
|
|
1795
1903
|
for (const [templateName, templateContent] of themePackage.templates.entries()) {
|
|
@@ -1806,7 +1914,7 @@ async function normalizeAndValidateThemePackage(themePackage) {
|
|
|
1806
1914
|
|
|
1807
1915
|
const validation = await validateThemeFiles(fileMap);
|
|
1808
1916
|
if (!validation.ok) {
|
|
1809
|
-
throw new Error(
|
|
1917
|
+
throw new Error(formatThemeValidationFailure(validation));
|
|
1810
1918
|
}
|
|
1811
1919
|
|
|
1812
1920
|
if (!validation.manifest) {
|
|
@@ -1821,6 +1929,61 @@ async function normalizeAndValidateThemePackage(themePackage) {
|
|
|
1821
1929
|
};
|
|
1822
1930
|
}
|
|
1823
1931
|
|
|
1932
|
+
function formatThemeValidationFailure(validation) {
|
|
1933
|
+
const blocks = [
|
|
1934
|
+
[
|
|
1935
|
+
'Theme validation failed',
|
|
1936
|
+
`Errors: ${validation.errors.length}`,
|
|
1937
|
+
`Checked files: ${validation.checkedFiles}`,
|
|
1938
|
+
].join('\n'),
|
|
1939
|
+
...validation.errors.map((issue) => formatThemeValidationIssue(issue)),
|
|
1940
|
+
];
|
|
1941
|
+
return blocks.join('\n\n');
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
function formatThemeValidationIssue(issue) {
|
|
1945
|
+
if (!issue) {
|
|
1946
|
+
return 'Reason: Unknown error';
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
const lines = [`ERROR ${issue.code || 'THEME_VALIDATION_ERROR'}`];
|
|
1950
|
+
const location = splitIssuePath(issue.path);
|
|
1951
|
+
if (location.file) {
|
|
1952
|
+
lines.push(`File: ${location.file}`);
|
|
1953
|
+
}
|
|
1954
|
+
if (location.path) {
|
|
1955
|
+
lines.push(`Path: ${location.path}`);
|
|
1956
|
+
}
|
|
1957
|
+
if (Number.isInteger(issue.line) && Number.isInteger(issue.column)) {
|
|
1958
|
+
lines.push(`Line: ${issue.line}, Column: ${issue.column}`);
|
|
1959
|
+
}
|
|
1960
|
+
if (issue.category) {
|
|
1961
|
+
lines.push(`Category: ${issue.category}`);
|
|
1962
|
+
}
|
|
1963
|
+
lines.push(`Reason: ${issue.message || 'Unknown error'}`);
|
|
1964
|
+
if (issue.snippet) {
|
|
1965
|
+
const lineLabel = Number.isInteger(issue.line) ? String(issue.line) : '';
|
|
1966
|
+
lines.push('', `${lineLabel} | ${issue.snippet.line}`, `${' '.repeat(lineLabel.length)} | ${issue.snippet.pointer}`);
|
|
1967
|
+
}
|
|
1968
|
+
if (issue.hint) {
|
|
1969
|
+
lines.push('', 'Hint:', issue.hint);
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
return lines.join('\n');
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
function splitIssuePath(issuePath) {
|
|
1976
|
+
const normalizedPath = String(issuePath || '');
|
|
1977
|
+
if (normalizedPath.startsWith('theme.json.')) {
|
|
1978
|
+
return {
|
|
1979
|
+
file: 'theme.json',
|
|
1980
|
+
path: normalizedPath.slice('theme.json.'.length),
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
return { file: normalizedPath, path: '' };
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1824
1987
|
function normalizeThemePackageMetadata(sourceMetadata, manifest) {
|
|
1825
1988
|
return {
|
|
1826
1989
|
...manifest,
|
|
@@ -1902,6 +2065,7 @@ function buildPageMeta(site, options = {}) {
|
|
|
1902
2065
|
const ogType = normalizeNonEmptyString(options.ogType, 'website');
|
|
1903
2066
|
const publishedTime = normalizeOptionalString(options.publishedTime);
|
|
1904
2067
|
const modifiedTime = normalizeOptionalString(options.modifiedTime);
|
|
2068
|
+
const robotsNoindex = options.robotsNoindex === true;
|
|
1905
2069
|
|
|
1906
2070
|
const meta = {
|
|
1907
2071
|
title: escapeHtml(resolvedTitle),
|
|
@@ -1915,6 +2079,7 @@ function buildPageMeta(site, options = {}) {
|
|
|
1915
2079
|
og_image: ogImage ? escapeHtml(ogImage) : '',
|
|
1916
2080
|
article_published_time: publishedTime ? escapeHtml(publishedTime) : '',
|
|
1917
2081
|
article_modified_time: modifiedTime ? escapeHtml(modifiedTime) : '',
|
|
2082
|
+
robots_noindex: robotsNoindex,
|
|
1918
2083
|
};
|
|
1919
2084
|
|
|
1920
2085
|
return {
|
|
@@ -1935,6 +2100,9 @@ function buildMetaHeadTags(meta) {
|
|
|
1935
2100
|
if (meta.description) {
|
|
1936
2101
|
tags.push(`<meta name="description" content="${meta.description}">`);
|
|
1937
2102
|
}
|
|
2103
|
+
if (meta.robots_noindex) {
|
|
2104
|
+
tags.push('<meta name="robots" content="noindex">');
|
|
2105
|
+
}
|
|
1938
2106
|
if (meta.canonical_url) {
|
|
1939
2107
|
tags.push(`<link rel="canonical" href="${meta.canonical_url}">`);
|
|
1940
2108
|
}
|
|
@@ -1982,7 +2150,7 @@ function resolveMetaImageUrl(image) {
|
|
|
1982
2150
|
return '';
|
|
1983
2151
|
}
|
|
1984
2152
|
|
|
1985
|
-
function normalizeMediaField(value,
|
|
2153
|
+
function normalizeMediaField(value, media_base_url) {
|
|
1986
2154
|
if (value === undefined) {
|
|
1987
2155
|
return undefined;
|
|
1988
2156
|
}
|
|
@@ -1996,7 +2164,7 @@ function normalizeMediaField(value, mediaBaseUrl) {
|
|
|
1996
2164
|
return normalizeAbsoluteUrl(normalizedValue, SAFE_MEDIA_PROTOCOLS);
|
|
1997
2165
|
}
|
|
1998
2166
|
|
|
1999
|
-
const normalizedBaseUrl = normalizeOptionalString(
|
|
2167
|
+
const normalizedBaseUrl = normalizeOptionalString(media_base_url);
|
|
2000
2168
|
if (!normalizedBaseUrl) {
|
|
2001
2169
|
return normalizedValue;
|
|
2002
2170
|
}
|
|
@@ -2233,6 +2401,10 @@ function assertPlannedOutputPathsSafe(state) {
|
|
|
2233
2401
|
COMMENT_POLICY_OUTPUT_PATH,
|
|
2234
2402
|
];
|
|
2235
2403
|
|
|
2404
|
+
if (shouldGenerateSearchArtifacts(state, state.options)) {
|
|
2405
|
+
plannedPaths.push(SEARCH_INDEX_OUTPUT_PATH, SEARCH_ADAPTER_OUTPUT_PATH, SEARCH_PAGEFIND_ADAPTER_OUTPUT_PATH);
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2236
2408
|
if (state.options.generateSpecialFiles) {
|
|
2237
2409
|
plannedPaths.push('404.html');
|
|
2238
2410
|
if (shouldGenerateRobotsTxt(state.options)) {
|
|
@@ -2338,6 +2510,7 @@ function sha256(content) {
|
|
|
2338
2510
|
|
|
2339
2511
|
function injectSiteCustomizations(html, state) {
|
|
2340
2512
|
let next = injectFaviconLinks(html, state.favicon);
|
|
2513
|
+
next = injectGeneratorMeta(next, state.exposeGenerator);
|
|
2341
2514
|
next = injectCustomCssAssetLink(next, state.customCssHref);
|
|
2342
2515
|
next = injectCustomHtml(next, state.customHtml);
|
|
2343
2516
|
return next;
|
|
@@ -2374,6 +2547,14 @@ function buildFaviconLinks(favicon) {
|
|
|
2374
2547
|
return lines.join('\n');
|
|
2375
2548
|
}
|
|
2376
2549
|
|
|
2550
|
+
function injectGeneratorMeta(html, exposeGenerator) {
|
|
2551
|
+
if (exposeGenerator === false) {
|
|
2552
|
+
return html;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
return html.replace('</head>', ' <meta name="generator" content="ZeroPress">\n</head>');
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2377
2558
|
function injectCustomCssAssetLink(html, href) {
|
|
2378
2559
|
if (!normalizeOptionalString(href)) {
|
|
2379
2560
|
return html;
|
|
@@ -2397,9 +2578,489 @@ function injectCustomHtml(html, customHtml) {
|
|
|
2397
2578
|
return next;
|
|
2398
2579
|
}
|
|
2399
2580
|
|
|
2400
|
-
function
|
|
2581
|
+
function buildSearchIndexJson(state) {
|
|
2582
|
+
return `${JSON.stringify(buildSearchIndexItems(state), null, 2)}\n`;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
function buildSearchIndexItems(state) {
|
|
2586
|
+
const posts = state.renderData.posts
|
|
2587
|
+
.filter((post) => post.status === 'published' && !isDelistedDocument(post))
|
|
2588
|
+
.map((post) => ({
|
|
2589
|
+
id: `post:${post.slug}`,
|
|
2590
|
+
type: 'post',
|
|
2591
|
+
title: post.title,
|
|
2592
|
+
url: post.url,
|
|
2593
|
+
excerpt: normalizeSearchText(post.excerpt),
|
|
2594
|
+
headings: buildSearchHeadings(post.toc),
|
|
2595
|
+
categories: Array.isArray(post.categories) ? post.categories.map((category) => category.name).filter(Boolean) : [],
|
|
2596
|
+
tags: Array.isArray(post.tags) ? post.tags.map((tag) => tag.name).filter(Boolean) : [],
|
|
2597
|
+
published_at_iso: normalizeIsoTimestamp(post.published_at_iso),
|
|
2598
|
+
updated_at_iso: normalizeIsoTimestamp(post.updated_at_iso),
|
|
2599
|
+
content_text: htmlToSearchText(post.html),
|
|
2600
|
+
}));
|
|
2601
|
+
|
|
2602
|
+
const frontPagePage = state.renderData.frontPageRoute?.front_page_type === 'page'
|
|
2603
|
+
? state.renderData.frontPageRoute.page
|
|
2604
|
+
: null;
|
|
2605
|
+
const frontPageItems = frontPagePage && frontPagePage.status === 'published' && !isDelistedDocument(frontPagePage)
|
|
2606
|
+
? [buildSearchPageItem(frontPagePage, '/')]
|
|
2607
|
+
: [];
|
|
2608
|
+
const pageItems = state.renderData.pages
|
|
2609
|
+
.filter((page) => page.status === 'published' && !isDelistedDocument(page))
|
|
2610
|
+
.map((page) => buildSearchPageItem(page, page.url));
|
|
2611
|
+
|
|
2612
|
+
return [...posts, ...frontPageItems, ...pageItems];
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
function buildSearchPageItem(page, url) {
|
|
2616
|
+
return {
|
|
2617
|
+
id: `page:${page.slug}`,
|
|
2618
|
+
type: 'page',
|
|
2619
|
+
title: page.title,
|
|
2620
|
+
url,
|
|
2621
|
+
excerpt: normalizeSearchText(page.excerpt),
|
|
2622
|
+
headings: buildSearchHeadings(page.toc),
|
|
2623
|
+
categories: [],
|
|
2624
|
+
tags: [],
|
|
2625
|
+
published_at_iso: '',
|
|
2626
|
+
updated_at_iso: '',
|
|
2627
|
+
content_text: htmlToSearchText(page.html),
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
function buildSearchHeadings(toc) {
|
|
2632
|
+
return Array.isArray(toc)
|
|
2633
|
+
? toc.map((item) => normalizeSearchText(item?.title)).filter(Boolean)
|
|
2634
|
+
: [];
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
function htmlToSearchText(html) {
|
|
2638
|
+
return normalizeSearchText(decodeHtmlEntities(
|
|
2639
|
+
String(html || '')
|
|
2640
|
+
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
|
|
2641
|
+
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
|
|
2642
|
+
.replace(/<!--[\s\S]*?-->/g, ' ')
|
|
2643
|
+
.replace(/<[^>]+>/g, ' '),
|
|
2644
|
+
));
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
function decodeHtmlEntities(value) {
|
|
2648
|
+
const namedEntities = {
|
|
2649
|
+
amp: '&',
|
|
2650
|
+
lt: '<',
|
|
2651
|
+
gt: '>',
|
|
2652
|
+
quot: '"',
|
|
2653
|
+
apos: "'",
|
|
2654
|
+
nbsp: ' ',
|
|
2655
|
+
};
|
|
2656
|
+
|
|
2657
|
+
return String(value || '').replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z][a-zA-Z0-9]+);/g, (match, entity) => {
|
|
2658
|
+
if (entity.startsWith('#x')) {
|
|
2659
|
+
const codePoint = Number.parseInt(entity.slice(2), 16);
|
|
2660
|
+
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
|
|
2661
|
+
}
|
|
2662
|
+
if (entity.startsWith('#')) {
|
|
2663
|
+
const codePoint = Number.parseInt(entity.slice(1), 10);
|
|
2664
|
+
return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
|
|
2665
|
+
}
|
|
2666
|
+
return Object.prototype.hasOwnProperty.call(namedEntities, entity) ? namedEntities[entity] : match;
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
function normalizeSearchText(value) {
|
|
2671
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
function buildSearchAdapterJs() {
|
|
2675
|
+
const fieldWeightsJson = JSON.stringify(SEARCH_FIELD_WEIGHTS, null, 2);
|
|
2676
|
+
return `const FIELD_WEIGHTS = ${fieldWeightsJson};
|
|
2677
|
+
const FIELD_NAMES = Object.keys(FIELD_WEIGHTS);
|
|
2678
|
+
const RECENCY_BOOST_MAX = ${SEARCH_RECENCY_BOOST_MAX};
|
|
2679
|
+
const DEFAULT_LIMIT = 20;
|
|
2680
|
+
const BM25_K1 = 1.2;
|
|
2681
|
+
const BM25_B = 0.75;
|
|
2682
|
+
|
|
2683
|
+
let preparedIndexPromise;
|
|
2684
|
+
|
|
2685
|
+
export async function preload() {
|
|
2686
|
+
await loadPreparedIndex();
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
export async function search(query, options = {}) {
|
|
2690
|
+
const prepared = await loadPreparedIndex();
|
|
2691
|
+
const terms = tokenize(query);
|
|
2692
|
+
const phrase = normalizeText(query);
|
|
2693
|
+
if (terms.length === 0 && !phrase) {
|
|
2694
|
+
return { results: [] };
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
const hits = [];
|
|
2698
|
+
for (const document of prepared.documents) {
|
|
2699
|
+
const score = scoreDocument(document, terms, phrase, prepared);
|
|
2700
|
+
if (score > 0) {
|
|
2701
|
+
hits.push({ document, score });
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
const limit = normalizeLimit(options.limit);
|
|
2706
|
+
hits.sort((left, right) => right.score - left.score || left.document.raw.title.localeCompare(right.document.raw.title));
|
|
2707
|
+
|
|
2708
|
+
return {
|
|
2709
|
+
results: hits.slice(0, limit).map((hit) => ({
|
|
2710
|
+
id: hit.document.raw.id,
|
|
2711
|
+
score: Number(hit.score.toFixed(6)),
|
|
2712
|
+
data: async () => buildResultData(hit.document.raw, query),
|
|
2713
|
+
})),
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
async function loadPreparedIndex() {
|
|
2718
|
+
if (!preparedIndexPromise) {
|
|
2719
|
+
preparedIndexPromise = fetch(new URL('./search.json', import.meta.url))
|
|
2720
|
+
.then((response) => {
|
|
2721
|
+
if (!response.ok) {
|
|
2722
|
+
throw new Error('ZeroPress search index not found');
|
|
2723
|
+
}
|
|
2724
|
+
return response.json();
|
|
2725
|
+
})
|
|
2726
|
+
.then((items) => prepareIndex(Array.isArray(items) ? items : []));
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
return preparedIndexPromise;
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
function prepareIndex(items) {
|
|
2733
|
+
const documents = items.map((item) => prepareDocument(item));
|
|
2734
|
+
const documentFrequencies = new Map();
|
|
2735
|
+
const averageLengths = Object.fromEntries(FIELD_NAMES.map((fieldName) => [fieldName, 1]));
|
|
2736
|
+
const newestPostTime = documents.reduce((newest, document) => {
|
|
2737
|
+
if (document.raw.type !== 'post') {
|
|
2738
|
+
return newest;
|
|
2739
|
+
}
|
|
2740
|
+
return Math.max(newest, document.publishedTime || 0);
|
|
2741
|
+
}, 0);
|
|
2742
|
+
|
|
2743
|
+
for (const document of documents) {
|
|
2744
|
+
const seenTerms = new Set();
|
|
2745
|
+
for (const fieldName of FIELD_NAMES) {
|
|
2746
|
+
for (const term of document.fieldTokens[fieldName]) {
|
|
2747
|
+
seenTerms.add(term);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
for (const term of seenTerms) {
|
|
2751
|
+
documentFrequencies.set(term, (documentFrequencies.get(term) || 0) + 1);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
for (const fieldName of FIELD_NAMES) {
|
|
2756
|
+
const total = documents.reduce((sum, document) => sum + document.fieldLengths[fieldName], 0);
|
|
2757
|
+
averageLengths[fieldName] = documents.length > 0 ? Math.max(1, total / documents.length) : 1;
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
return {
|
|
2761
|
+
documents,
|
|
2762
|
+
documentFrequencies,
|
|
2763
|
+
averageLengths,
|
|
2764
|
+
documentCount: documents.length,
|
|
2765
|
+
newestPostTime,
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
function prepareDocument(item) {
|
|
2770
|
+
const raw = normalizeItem(item);
|
|
2771
|
+
const fields = {
|
|
2772
|
+
title: raw.title,
|
|
2773
|
+
headings: raw.headings.join(' '),
|
|
2774
|
+
tags: raw.tags.join(' '),
|
|
2775
|
+
categories: raw.categories.join(' '),
|
|
2776
|
+
excerpt: raw.excerpt,
|
|
2777
|
+
content_text: raw.content_text,
|
|
2778
|
+
};
|
|
2779
|
+
const fieldTexts = {};
|
|
2780
|
+
const fieldTokens = {};
|
|
2781
|
+
const fieldTermCounts = {};
|
|
2782
|
+
const fieldLengths = {};
|
|
2783
|
+
|
|
2784
|
+
for (const [fieldName, value] of Object.entries(fields)) {
|
|
2785
|
+
const normalizedText = normalizeText(value);
|
|
2786
|
+
const tokens = tokenize(normalizedText);
|
|
2787
|
+
fieldTexts[fieldName] = normalizedText;
|
|
2788
|
+
fieldTokens[fieldName] = tokens;
|
|
2789
|
+
fieldTermCounts[fieldName] = countTerms(tokens);
|
|
2790
|
+
fieldLengths[fieldName] = Math.max(1, tokens.length);
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
return {
|
|
2794
|
+
raw,
|
|
2795
|
+
fieldTexts,
|
|
2796
|
+
fieldTokens,
|
|
2797
|
+
fieldTermCounts,
|
|
2798
|
+
fieldLengths,
|
|
2799
|
+
publishedTime: Date.parse(raw.published_at_iso) || 0,
|
|
2800
|
+
};
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
function normalizeItem(item) {
|
|
2804
|
+
return {
|
|
2805
|
+
id: String(item && item.id || ''),
|
|
2806
|
+
type: item && item.type === 'page' ? 'page' : 'post',
|
|
2807
|
+
title: String(item && item.title || ''),
|
|
2808
|
+
url: String(item && item.url || ''),
|
|
2809
|
+
excerpt: String(item && item.excerpt || ''),
|
|
2810
|
+
headings: Array.isArray(item && item.headings) ? item.headings.map(String) : [],
|
|
2811
|
+
categories: Array.isArray(item && item.categories) ? item.categories.map(String) : [],
|
|
2812
|
+
tags: Array.isArray(item && item.tags) ? item.tags.map(String) : [],
|
|
2813
|
+
published_at_iso: String(item && item.published_at_iso || ''),
|
|
2814
|
+
updated_at_iso: String(item && item.updated_at_iso || ''),
|
|
2815
|
+
content_text: String(item && item.content_text || ''),
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
function scoreDocument(document, terms, phrase, prepared) {
|
|
2820
|
+
let score = 0;
|
|
2821
|
+
const uniqueTerms = Array.from(new Set(terms));
|
|
2822
|
+
|
|
2823
|
+
for (const term of uniqueTerms) {
|
|
2824
|
+
const documentFrequency = prepared.documentFrequencies.get(term) || 0;
|
|
2825
|
+
const idf = Math.log(1 + (prepared.documentCount - documentFrequency + 0.5) / (documentFrequency + 0.5));
|
|
2826
|
+
|
|
2827
|
+
for (const fieldName of FIELD_NAMES) {
|
|
2828
|
+
const termFrequency = document.fieldTermCounts[fieldName].get(term) || 0;
|
|
2829
|
+
if (termFrequency === 0) {
|
|
2830
|
+
continue;
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
const fieldLength = document.fieldLengths[fieldName];
|
|
2834
|
+
const averageLength = prepared.averageLengths[fieldName];
|
|
2835
|
+
const denominator = termFrequency + BM25_K1 * (1 - BM25_B + BM25_B * (fieldLength / averageLength));
|
|
2836
|
+
score += FIELD_WEIGHTS[fieldName] * idf * ((termFrequency * (BM25_K1 + 1)) / denominator);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
if (phrase && phrase.length > 1) {
|
|
2841
|
+
for (const fieldName of FIELD_NAMES) {
|
|
2842
|
+
if (document.fieldTexts[fieldName].includes(phrase)) {
|
|
2843
|
+
score += FIELD_WEIGHTS[fieldName] * 0.6;
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
if (score <= 0) {
|
|
2849
|
+
return 0;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
return score * (1 + recencyBoost(document, prepared.newestPostTime));
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
function recencyBoost(document, newestPostTime) {
|
|
2856
|
+
if (document.raw.type !== 'post' || !document.publishedTime || !newestPostTime) {
|
|
2857
|
+
return 0;
|
|
2858
|
+
}
|
|
2859
|
+
const ageMs = Math.max(0, newestPostTime - document.publishedTime);
|
|
2860
|
+
const halfLifeMs = 180 * 24 * 60 * 60 * 1000;
|
|
2861
|
+
return RECENCY_BOOST_MAX * Math.exp(-ageMs / halfLifeMs);
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
function buildResultData(item, query) {
|
|
2865
|
+
const excerpt = buildExcerpt(item, query);
|
|
2866
|
+
return {
|
|
2867
|
+
url: item.url,
|
|
2868
|
+
excerpt,
|
|
2869
|
+
plain_excerpt: excerpt,
|
|
2870
|
+
meta: {
|
|
2871
|
+
title: item.title,
|
|
2872
|
+
type: item.type,
|
|
2873
|
+
published_at_iso: item.published_at_iso,
|
|
2874
|
+
updated_at_iso: item.updated_at_iso,
|
|
2875
|
+
categories: item.categories,
|
|
2876
|
+
tags: item.tags,
|
|
2877
|
+
},
|
|
2878
|
+
sub_results: [],
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
function buildExcerpt(item, query) {
|
|
2883
|
+
const explicitExcerpt = String(item.excerpt || '').trim();
|
|
2884
|
+
if (explicitExcerpt) {
|
|
2885
|
+
return explicitExcerpt;
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
const text = String(item.content_text || '').replace(/\\s+/g, ' ').trim();
|
|
2889
|
+
if (!text) {
|
|
2890
|
+
return '';
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
const normalizedText = normalizeText(text);
|
|
2894
|
+
const terms = tokenize(query);
|
|
2895
|
+
const firstMatch = terms.map((term) => normalizedText.indexOf(term)).filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
2896
|
+
const start = Math.max(0, (firstMatch || 0) - 80);
|
|
2897
|
+
const end = Math.min(text.length, start + 180);
|
|
2898
|
+
const prefix = start > 0 ? '...' : '';
|
|
2899
|
+
const suffix = end < text.length ? '...' : '';
|
|
2900
|
+
return prefix + text.slice(start, end).trim() + suffix;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
function countTerms(tokens) {
|
|
2904
|
+
const counts = new Map();
|
|
2905
|
+
for (const token of tokens) {
|
|
2906
|
+
counts.set(token, (counts.get(token) || 0) + 1);
|
|
2907
|
+
}
|
|
2908
|
+
return counts;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
function tokenize(value) {
|
|
2912
|
+
const text = normalizeText(value);
|
|
2913
|
+
if (!text) {
|
|
2914
|
+
return [];
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
const tokens = [];
|
|
2918
|
+
if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {
|
|
2919
|
+
try {
|
|
2920
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: 'word' });
|
|
2921
|
+
for (const part of segmenter.segment(text)) {
|
|
2922
|
+
if (part.isWordLike && isUsefulToken(part.segment)) {
|
|
2923
|
+
tokens.push(part.segment);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
} catch {
|
|
2927
|
+
// Fall through to regex tokenization.
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
for (const match of text.matchAll(/[\\p{Letter}\\p{Number}]+/gu)) {
|
|
2932
|
+
if (isUsefulToken(match[0])) {
|
|
2933
|
+
tokens.push(match[0]);
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
for (const match of text.matchAll(/[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]+/gu)) {
|
|
2938
|
+
tokens.push(...buildNgrams(match[0], 2));
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
return tokens;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
function buildNgrams(value, size) {
|
|
2945
|
+
const normalized = Array.from(value);
|
|
2946
|
+
if (normalized.length <= size) {
|
|
2947
|
+
return isUsefulToken(value) ? [value] : [];
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
const tokens = [];
|
|
2951
|
+
for (let index = 0; index <= normalized.length - size; index += 1) {
|
|
2952
|
+
tokens.push(normalized.slice(index, index + size).join(''));
|
|
2953
|
+
}
|
|
2954
|
+
tokens.push(value);
|
|
2955
|
+
return tokens;
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
function isUsefulToken(value) {
|
|
2959
|
+
const token = String(value || '').trim();
|
|
2960
|
+
return token.length > 1 || /^\\d$/.test(token);
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
function normalizeText(value) {
|
|
2964
|
+
return String(value || '')
|
|
2965
|
+
.normalize('NFKC')
|
|
2966
|
+
.toLowerCase()
|
|
2967
|
+
.replace(/[\\u2018\\u2019]/g, "'")
|
|
2968
|
+
.replace(/[_-]+/g, ' ')
|
|
2969
|
+
.replace(/\\s+/g, ' ')
|
|
2970
|
+
.trim();
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
function normalizeLimit(value) {
|
|
2974
|
+
return Number.isInteger(value) && value > 0 ? Math.min(value, 100) : DEFAULT_LIMIT;
|
|
2975
|
+
}
|
|
2976
|
+
`;
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
function buildSearchPagefindAdapterJs() {
|
|
2980
|
+
return `let pagefindPromise;
|
|
2981
|
+
|
|
2982
|
+
export async function preload() {
|
|
2983
|
+
if (!pagefindPromise) {
|
|
2984
|
+
pagefindPromise = import(new URL('./pagefind/pagefind.js', import.meta.url).href).then(async (pagefind) => {
|
|
2985
|
+
if (typeof pagefind.options === 'function') {
|
|
2986
|
+
await pagefind.options({ baseUrl: '/' });
|
|
2987
|
+
}
|
|
2988
|
+
return pagefind;
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
return pagefindPromise;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
export async function search(query, options = {}) {
|
|
2996
|
+
const pagefind = await preload();
|
|
2997
|
+
const result = await pagefind.search(query, options);
|
|
2998
|
+
const limit = normalizeLimit(options.limit);
|
|
2999
|
+
if (!Array.isArray(result?.results)) {
|
|
3000
|
+
return result;
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
const results = result.results.map(normalizeResult);
|
|
3004
|
+
return {
|
|
3005
|
+
...result,
|
|
3006
|
+
results: limit ? results.slice(0, limit) : results,
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
function normalizeResult(result) {
|
|
3011
|
+
if (!result || typeof result.data !== 'function') {
|
|
3012
|
+
return result;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
return {
|
|
3016
|
+
...result,
|
|
3017
|
+
data: async () => normalizeResultData(await result.data()),
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
function normalizeResultData(data) {
|
|
3022
|
+
if (!data || typeof data !== 'object') {
|
|
3023
|
+
return data;
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
return {
|
|
3027
|
+
...data,
|
|
3028
|
+
url: normalizeUrl(data.url),
|
|
3029
|
+
sub_results: Array.isArray(data.sub_results)
|
|
3030
|
+
? data.sub_results.map((item) => ({ ...item, url: normalizeUrl(item.url) }))
|
|
3031
|
+
: data.sub_results,
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
function normalizeUrl(value) {
|
|
3036
|
+
const url = String(value || '');
|
|
3037
|
+
if (url.startsWith('/_zeropress/') && !url.startsWith('/_zeropress/pagefind/')) {
|
|
3038
|
+
return url.replace(/^\\/_zeropress/, '') || '/';
|
|
3039
|
+
}
|
|
3040
|
+
if (url.startsWith('_zeropress/') && !url.startsWith('_zeropress/pagefind/')) {
|
|
3041
|
+
return url.replace(/^_zeropress/, '') || '/';
|
|
3042
|
+
}
|
|
3043
|
+
return url;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
function normalizeLimit(value) {
|
|
3047
|
+
if (value === undefined || value === null) {
|
|
3048
|
+
return null;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
const limit = Number(value);
|
|
3052
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
3053
|
+
return null;
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
return Math.floor(limit);
|
|
3057
|
+
}
|
|
3058
|
+
`;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
function buildSitemapXml(site, emitted, generatedAt, stylesheetHref = '') {
|
|
2401
3062
|
const entries = [
|
|
2402
|
-
...(emitted.frontPage
|
|
3063
|
+
...(emitted.frontPage && emitted.frontPage.includeInSitemap !== false
|
|
2403
3064
|
? [{
|
|
2404
3065
|
url: emitted.frontPage.url,
|
|
2405
3066
|
changefreq: 'daily',
|
|
@@ -2436,7 +3097,12 @@ function buildSitemapXml(site, emitted, generatedAt) {
|
|
|
2436
3097
|
return ` <url>\n <loc>${loc}</loc>${lastmod}\n <changefreq>${entry.changefreq}</changefreq>\n <priority>${entry.priority.toFixed(1)}</priority>\n </url>`;
|
|
2437
3098
|
}).join('\n');
|
|
2438
3099
|
|
|
2439
|
-
|
|
3100
|
+
const normalizedStylesheetHref = normalizeOptionalString(stylesheetHref);
|
|
3101
|
+
const stylesheet = normalizedStylesheetHref
|
|
3102
|
+
? `\n<?xml-stylesheet type="text/xsl" href="${escapeXml(normalizedStylesheetHref)}"?>`
|
|
3103
|
+
: '';
|
|
3104
|
+
|
|
3105
|
+
return `<?xml version="1.0" encoding="UTF-8"?>${stylesheet}\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${body}\n</urlset>`;
|
|
2440
3106
|
}
|
|
2441
3107
|
|
|
2442
3108
|
function buildFeedXml(site, emitted, generatedAt) {
|
|
@@ -2472,6 +3138,10 @@ function shouldGenerateRobotsTxt(options) {
|
|
|
2472
3138
|
return options.generateSpecialFiles && options.generateRobotsTxt !== false;
|
|
2473
3139
|
}
|
|
2474
3140
|
|
|
3141
|
+
function shouldGenerateSearchArtifacts(state, options) {
|
|
3142
|
+
return options.generateSpecialFiles && state.previewData.site.search === true;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
2475
3145
|
function getContentType(assetPath) {
|
|
2476
3146
|
const ext = assetPath.split('.').pop()?.toLowerCase();
|
|
2477
3147
|
const contentTypes = {
|