@zeropress/build-core 0.1.12 → 0.1.13
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/package.json +3 -3
- package/src/api/build-site.js +374 -1
- package/src/index.d.ts +4 -0
- package/src/theme/load-theme-dir.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeropress/build-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Shared ZeroPress build core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@zeropress/slug-policy": "^0.1.0",
|
|
37
|
-
"@zeropress/preview-data-validator": "^0.4.
|
|
38
|
-
"@zeropress/theme-validator": "^0.3.
|
|
37
|
+
"@zeropress/preview-data-validator": "^0.4.3",
|
|
38
|
+
"@zeropress/theme-validator": "^0.3.2",
|
|
39
39
|
"highlight.js": "^11.11.1",
|
|
40
40
|
"markdown-it": "^14.1.0",
|
|
41
41
|
"markdown-it-anchor": "^9.2.0"
|
package/src/api/build-site.js
CHANGED
|
@@ -84,10 +84,15 @@ async function createBuildState(input, options) {
|
|
|
84
84
|
const summaries = [];
|
|
85
85
|
const themePackage = input.themePackage;
|
|
86
86
|
const previewData = normalizePreviewData(input.previewData);
|
|
87
|
+
const renderData = createRenderData(previewData);
|
|
87
88
|
|
|
88
89
|
engine.initialize(themePackage);
|
|
89
90
|
|
|
90
91
|
const assetOutputs = await buildAssetOutputs(themePackage.assets, assetProcessor, options);
|
|
92
|
+
const customCssAsset = await buildCustomCssAsset(previewData.custom_css, assetProcessor, options);
|
|
93
|
+
if (customCssAsset) {
|
|
94
|
+
assetOutputs.push(customCssAsset);
|
|
95
|
+
}
|
|
91
96
|
const assetMap = new Map(
|
|
92
97
|
assetOutputs.map((asset) => [`/assets/${asset.originalPath}`, `/${asset.path}`]),
|
|
93
98
|
);
|
|
@@ -95,12 +100,14 @@ async function createBuildState(input, options) {
|
|
|
95
100
|
return {
|
|
96
101
|
writer: input.writer,
|
|
97
102
|
previewData,
|
|
98
|
-
renderData
|
|
103
|
+
renderData,
|
|
104
|
+
renderedWidgets: renderWidgetAreas(previewData, renderData),
|
|
99
105
|
engine,
|
|
100
106
|
assetProcessor,
|
|
101
107
|
summaries,
|
|
102
108
|
assetOutputs,
|
|
103
109
|
assetMap,
|
|
110
|
+
customCssHref: customCssAsset ? `/${customCssAsset.path}` : '',
|
|
104
111
|
options,
|
|
105
112
|
generatedAt: new Date(),
|
|
106
113
|
emitted: {
|
|
@@ -142,6 +149,7 @@ async function renderRoute(state, templateName, route) {
|
|
|
142
149
|
templateName,
|
|
143
150
|
{
|
|
144
151
|
menus: state.previewData.menus,
|
|
152
|
+
widgets: state.renderedWidgets,
|
|
145
153
|
...route,
|
|
146
154
|
meta: buildPageMeta(state.previewData.site, {
|
|
147
155
|
currentUrl,
|
|
@@ -153,6 +161,7 @@ async function renderRoute(state, templateName, route) {
|
|
|
153
161
|
createRenderContext(state.previewData.site, currentUrl),
|
|
154
162
|
);
|
|
155
163
|
html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
|
|
164
|
+
html = injectCustomCssAssetLink(html, state.customCssHref);
|
|
156
165
|
await writeOutput(state.writer, state.summaries, routePathToOutputPath(route.path), html, 'text/html');
|
|
157
166
|
recordRouteEmission(state, templateName, route, currentUrl);
|
|
158
167
|
}
|
|
@@ -163,6 +172,7 @@ async function renderPost(state, post) {
|
|
|
163
172
|
'post',
|
|
164
173
|
{
|
|
165
174
|
menus: state.previewData.menus,
|
|
175
|
+
widgets: state.renderedWidgets,
|
|
166
176
|
post,
|
|
167
177
|
meta: buildPageMeta(state.previewData.site, {
|
|
168
178
|
currentUrl,
|
|
@@ -177,6 +187,7 @@ async function renderPost(state, post) {
|
|
|
177
187
|
createRenderContext(state.previewData.site, currentUrl),
|
|
178
188
|
);
|
|
179
189
|
html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
|
|
190
|
+
html = injectCustomCssAssetLink(html, state.customCssHref);
|
|
180
191
|
if (state.options.injectHtmx) {
|
|
181
192
|
html = injectHtmxScript(html);
|
|
182
193
|
}
|
|
@@ -197,6 +208,7 @@ async function renderPage(state, page) {
|
|
|
197
208
|
'page',
|
|
198
209
|
{
|
|
199
210
|
menus: state.previewData.menus,
|
|
211
|
+
widgets: state.renderedWidgets,
|
|
200
212
|
page,
|
|
201
213
|
meta: buildPageMeta(state.previewData.site, {
|
|
202
214
|
currentUrl,
|
|
@@ -209,6 +221,7 @@ async function renderPage(state, page) {
|
|
|
209
221
|
createRenderContext(state.previewData.site, currentUrl),
|
|
210
222
|
);
|
|
211
223
|
html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
|
|
224
|
+
html = injectCustomCssAssetLink(html, state.customCssHref);
|
|
212
225
|
await writeOutput(state.writer, state.summaries, `${encodeSlugSegment(page.slug)}/index.html`, html, 'text/html');
|
|
213
226
|
state.emitted.pages.push({
|
|
214
227
|
url: currentUrl,
|
|
@@ -227,6 +240,7 @@ async function maybeRenderNotFoundPage(state) {
|
|
|
227
240
|
'404',
|
|
228
241
|
{
|
|
229
242
|
menus: state.previewData.menus,
|
|
243
|
+
widgets: state.renderedWidgets,
|
|
230
244
|
meta: buildPageMeta(state.previewData.site, {
|
|
231
245
|
currentUrl: '/404.html',
|
|
232
246
|
title: state.previewData.site.title,
|
|
@@ -237,6 +251,7 @@ async function maybeRenderNotFoundPage(state) {
|
|
|
237
251
|
createRenderContext(state.previewData.site, '/404.html'),
|
|
238
252
|
);
|
|
239
253
|
html = state.assetProcessor.updateAssetReferences(html, state.assetMap);
|
|
254
|
+
html = injectCustomCssAssetLink(html, state.customCssHref);
|
|
240
255
|
await writeOutput(state.writer, state.summaries, '404.html', html, 'text/html');
|
|
241
256
|
}
|
|
242
257
|
|
|
@@ -257,6 +272,8 @@ function normalizePreviewData(previewData) {
|
|
|
257
272
|
return {
|
|
258
273
|
...previewData,
|
|
259
274
|
site: normalizedSite,
|
|
275
|
+
widgets: normalizeWidgetAreas(previewData.widgets),
|
|
276
|
+
custom_css: normalizeCustomCss(previewData.custom_css),
|
|
260
277
|
content: {
|
|
261
278
|
...previewData.content,
|
|
262
279
|
authors: previewData.content.authors.map((author) => ({
|
|
@@ -281,6 +298,33 @@ function normalizePreviewData(previewData) {
|
|
|
281
298
|
};
|
|
282
299
|
}
|
|
283
300
|
|
|
301
|
+
function normalizeWidgetAreas(widgetAreas) {
|
|
302
|
+
if (!widgetAreas || typeof widgetAreas !== 'object') {
|
|
303
|
+
return {};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return Object.fromEntries(
|
|
307
|
+
Object.entries(widgetAreas).map(([widgetAreaId, widgetArea]) => [
|
|
308
|
+
widgetAreaId,
|
|
309
|
+
{
|
|
310
|
+
...widgetArea,
|
|
311
|
+
name: normalizeNonEmptyString(widgetArea?.name, widgetAreaId),
|
|
312
|
+
items: Array.isArray(widgetArea?.items)
|
|
313
|
+
? widgetArea.items.map((item) => ({
|
|
314
|
+
...item,
|
|
315
|
+
title: normalizeNonEmptyString(item?.title, 'Widget'),
|
|
316
|
+
}))
|
|
317
|
+
: [],
|
|
318
|
+
},
|
|
319
|
+
]),
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function normalizeCustomCss(customCss) {
|
|
324
|
+
const content = normalizeOptionalString(customCss?.content);
|
|
325
|
+
return content ? { content } : undefined;
|
|
326
|
+
}
|
|
327
|
+
|
|
284
328
|
function createRenderData(previewData) {
|
|
285
329
|
const authorsById = new Map(previewData.content.authors.map((author) => [author.id, author]));
|
|
286
330
|
const categoriesBySlug = new Map(previewData.content.categories.map((category) => [category.slug, category]));
|
|
@@ -355,6 +399,253 @@ function createRenderData(previewData) {
|
|
|
355
399
|
};
|
|
356
400
|
}
|
|
357
401
|
|
|
402
|
+
function renderWidgetAreas(previewData, renderData) {
|
|
403
|
+
if (!previewData.widgets || typeof previewData.widgets !== 'object') {
|
|
404
|
+
return {};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return Object.fromEntries(
|
|
408
|
+
Object.entries(previewData.widgets).map(([widgetAreaId, widgetArea]) => [
|
|
409
|
+
widgetAreaId,
|
|
410
|
+
renderWidgetArea(widgetArea, previewData, renderData, widgetAreaId),
|
|
411
|
+
]),
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function renderWidgetArea(widgetArea, previewData, renderData, widgetAreaId) {
|
|
416
|
+
if (!widgetArea || !Array.isArray(widgetArea.items) || widgetArea.items.length === 0) {
|
|
417
|
+
return '';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return widgetArea.items
|
|
421
|
+
.map((item, index) => renderWidgetItem(item, previewData, renderData, widgetAreaId, index))
|
|
422
|
+
.filter(Boolean)
|
|
423
|
+
.join('\n');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function renderWidgetItem(item, previewData, renderData, widgetAreaId, index) {
|
|
427
|
+
if (!item || typeof item !== 'object') {
|
|
428
|
+
return '';
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const title = normalizeNonEmptyString(item.title, 'Widget');
|
|
432
|
+
|
|
433
|
+
switch (item.type) {
|
|
434
|
+
case 'recent-posts':
|
|
435
|
+
return renderRecentPostsWidget(title, item.settings, renderData);
|
|
436
|
+
case 'categories':
|
|
437
|
+
return renderCategoriesWidget(title, item.settings, previewData);
|
|
438
|
+
case 'tags':
|
|
439
|
+
return renderTagsWidget(title, item.settings, previewData);
|
|
440
|
+
case 'archives':
|
|
441
|
+
return renderArchivesWidget(title, item.settings, previewData);
|
|
442
|
+
case 'image':
|
|
443
|
+
return renderImageWidget(title, item.settings);
|
|
444
|
+
case 'text':
|
|
445
|
+
return renderTextWidget(title, item.settings);
|
|
446
|
+
case 'link-list':
|
|
447
|
+
return renderLinkListWidget(title, item.settings);
|
|
448
|
+
case 'search':
|
|
449
|
+
return renderSearchWidget(title, item.settings, widgetAreaId, index);
|
|
450
|
+
case 'profile':
|
|
451
|
+
return renderProfileWidget(title, item.settings);
|
|
452
|
+
default:
|
|
453
|
+
return '';
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function renderRecentPostsWidget(title, settings, renderData) {
|
|
458
|
+
const limit = clampInteger(settings?.limit, 5, 1, 20);
|
|
459
|
+
const showDate = settings?.show_date !== false;
|
|
460
|
+
const items = renderData.posts
|
|
461
|
+
.slice(0, limit)
|
|
462
|
+
.map((post) => {
|
|
463
|
+
const dateMeta = showDate
|
|
464
|
+
? `<span class="widget-list__meta">${escapeHtml(post.published_at)}</span>`
|
|
465
|
+
: '';
|
|
466
|
+
|
|
467
|
+
return `<li class="widget-list__item"><a href="/posts/${escapeHtml(encodeSlugSegment(post.slug))}/">${escapeHtml(post.title)}</a>${dateMeta}</li>`;
|
|
468
|
+
})
|
|
469
|
+
.join('');
|
|
470
|
+
|
|
471
|
+
if (!items) {
|
|
472
|
+
return '';
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return renderWidgetCard(title, 'recent-posts', `<ul class="widget-list">${items}</ul>`);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function renderCategoriesWidget(title, settings, previewData) {
|
|
479
|
+
const countBySlug = buildTaxonomyCountMap(previewData.content.posts, 'category_slugs');
|
|
480
|
+
const items = previewData.content.categories
|
|
481
|
+
.map((category) => {
|
|
482
|
+
const count = countBySlug.get(category.slug) || 0;
|
|
483
|
+
if (count === 0) {
|
|
484
|
+
return '';
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const suffix = settings?.show_count === true ? ` <span class="widget-taxonomy__count">(${count})</span>` : '';
|
|
488
|
+
return `<a href="/categories/${escapeHtml(encodeSlugSegment(category.slug))}/" class="category-link">${escapeHtml(category.name)}${suffix}</a>`;
|
|
489
|
+
})
|
|
490
|
+
.filter(Boolean)
|
|
491
|
+
.join('');
|
|
492
|
+
|
|
493
|
+
if (!items) {
|
|
494
|
+
return '';
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return renderWidgetCard(title, 'categories', `<div class="taxonomy-list taxonomy-list--stack widget-taxonomy">${items}</div>`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function renderTagsWidget(title, settings, previewData) {
|
|
501
|
+
const countBySlug = buildTaxonomyCountMap(previewData.content.posts, 'tag_slugs');
|
|
502
|
+
const limit = clampInteger(settings?.limit, 20, 1, 100);
|
|
503
|
+
const items = previewData.content.tags
|
|
504
|
+
.map((tag) => ({
|
|
505
|
+
...tag,
|
|
506
|
+
count: countBySlug.get(tag.slug) || 0,
|
|
507
|
+
}))
|
|
508
|
+
.filter((tag) => tag.count > 0)
|
|
509
|
+
.slice(0, limit)
|
|
510
|
+
.map((tag) => {
|
|
511
|
+
const suffix = settings?.show_count === true ? ` <span class="widget-taxonomy__count">(${tag.count})</span>` : '';
|
|
512
|
+
return `<a href="/tags/${escapeHtml(encodeSlugSegment(tag.slug))}/" class="tag-link">${escapeHtml(tag.name)}${suffix}</a>`;
|
|
513
|
+
})
|
|
514
|
+
.join('');
|
|
515
|
+
|
|
516
|
+
if (!items) {
|
|
517
|
+
return '';
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return renderWidgetCard(title, 'tags', `<div class="taxonomy-list widget-taxonomy">${items}</div>`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function renderArchivesWidget(title, settings, previewData) {
|
|
524
|
+
const limit = clampInteger(settings?.limit, 12, 1, 120);
|
|
525
|
+
const archiveEntries = buildArchiveEntries(previewData.content.posts, previewData.site).slice(0, limit);
|
|
526
|
+
const items = archiveEntries
|
|
527
|
+
.map((entry) => `<li class="widget-list__item"><a href="/archive/">${escapeHtml(entry.label)}</a><span class="widget-list__meta">${entry.count} posts</span></li>`)
|
|
528
|
+
.join('');
|
|
529
|
+
|
|
530
|
+
if (!items) {
|
|
531
|
+
return '';
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return renderWidgetCard(title, 'archives', `<ul class="widget-list">${items}</ul>`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function renderImageWidget(title, settings) {
|
|
538
|
+
const src = normalizeOptionalString(settings?.src);
|
|
539
|
+
if (!src) {
|
|
540
|
+
return '';
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const alt = escapeHtml(normalizeOptionalString(settings?.alt));
|
|
544
|
+
const caption = normalizeOptionalString(settings?.caption);
|
|
545
|
+
const href = normalizeOptionalString(settings?.href);
|
|
546
|
+
const target = normalizeLinkTarget(settings?.target);
|
|
547
|
+
const rel = target === '_blank' ? ' rel="noreferrer noopener"' : '';
|
|
548
|
+
const image = `<img class="widget-image__media" src="${escapeHtml(src)}" alt="${alt}">`;
|
|
549
|
+
const linkedImage = href ? `<a href="${escapeHtml(href)}" target="${target}"${rel}>${image}</a>` : image;
|
|
550
|
+
const captionHtml = caption ? `<figcaption class="widget-image__caption">${escapeHtml(caption)}</figcaption>` : '';
|
|
551
|
+
|
|
552
|
+
return renderWidgetCard(title, 'image', `<figure class="widget-image">${linkedImage}${captionHtml}</figure>`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function renderTextWidget(title, settings) {
|
|
556
|
+
const content = typeof settings?.content === 'string' ? settings.content : '';
|
|
557
|
+
const html = renderDocumentContent(content, normalizeDocumentType(settings?.document_type));
|
|
558
|
+
if (!normalizeOptionalString(html)) {
|
|
559
|
+
return '';
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return renderWidgetCard(title, 'text', `<div class="widget-copy">${html}</div>`);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function renderLinkListWidget(title, settings) {
|
|
566
|
+
const links = Array.isArray(settings?.links) ? settings.links : [];
|
|
567
|
+
const items = links
|
|
568
|
+
.map((link) => {
|
|
569
|
+
const label = normalizeOptionalString(link?.label);
|
|
570
|
+
const url = normalizeOptionalString(link?.url);
|
|
571
|
+
if (!label || !url) {
|
|
572
|
+
return '';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const target = normalizeLinkTarget(link?.target);
|
|
576
|
+
const rel = target === '_blank' ? ' rel="noreferrer noopener"' : '';
|
|
577
|
+
return `<a href="${escapeHtml(url)}" target="${target}"${rel}>${escapeHtml(label)}</a>`;
|
|
578
|
+
})
|
|
579
|
+
.filter(Boolean)
|
|
580
|
+
.join('');
|
|
581
|
+
|
|
582
|
+
if (!items) {
|
|
583
|
+
return '';
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return renderWidgetCard(title, 'link-list', `<div class="taxonomy-list taxonomy-list--stack widget-link-list">${items}</div>`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function renderSearchWidget(title, settings, widgetAreaId, index) {
|
|
590
|
+
const placeholder = escapeHtml(normalizeNonEmptyString(settings?.placeholder, 'Search...'));
|
|
591
|
+
const buttonLabel = escapeHtml(normalizeNonEmptyString(settings?.button_label, 'Search'));
|
|
592
|
+
const searchId = `widget-search-${widgetAreaId}-${index + 1}`;
|
|
593
|
+
|
|
594
|
+
return renderWidgetCard(title, 'search', [
|
|
595
|
+
'<div class="widget-search" data-search-root>',
|
|
596
|
+
` <label class="sr-only" for="${escapeHtml(searchId)}">${escapeHtml(title)}</label>`,
|
|
597
|
+
' <form class="search-theme-wrapper" data-search-form>',
|
|
598
|
+
` <input id="${escapeHtml(searchId)}" class="search-input" data-theme-search type="search" placeholder="${placeholder}" autocomplete="off" enterkeyhint="search">`,
|
|
599
|
+
` <button class="widget-search-button" type="submit">${buttonLabel}</button>`,
|
|
600
|
+
' </form>',
|
|
601
|
+
' <div class="search-results" data-search-results hidden></div>',
|
|
602
|
+
' <p class="search-feedback search-feedback--sidebar" data-search-feedback aria-live="polite"></p>',
|
|
603
|
+
'</div>',
|
|
604
|
+
].join('\n'));
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function renderProfileWidget(title, settings) {
|
|
608
|
+
const displayName = normalizeOptionalString(settings?.display_name);
|
|
609
|
+
const affiliation = normalizeOptionalString(settings?.affiliation);
|
|
610
|
+
const bioShort = normalizeOptionalString(settings?.bio_short);
|
|
611
|
+
const avatar = normalizeOptionalString(settings?.avatar);
|
|
612
|
+
|
|
613
|
+
if (!displayName && !bioShort && !avatar) {
|
|
614
|
+
return '';
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const avatarHtml = avatar
|
|
618
|
+
? `<img class="widget-profile__avatar" src="${escapeHtml(avatar)}" alt="${escapeHtml(displayName || title)}">`
|
|
619
|
+
: '';
|
|
620
|
+
const affiliationHtml = affiliation ? `<p class="widget-profile__affiliation">${escapeHtml(affiliation)}</p>` : '';
|
|
621
|
+
const bioHtml = bioShort ? `<p class="sidebar-copy">${escapeHtml(bioShort)}</p>` : '';
|
|
622
|
+
|
|
623
|
+
return renderWidgetCard(title, 'profile', [
|
|
624
|
+
'<div class="widget-profile">',
|
|
625
|
+
avatarHtml,
|
|
626
|
+
' <div class="widget-profile__body">',
|
|
627
|
+
displayName ? ` <p class="widget-profile__name">${escapeHtml(displayName)}</p>` : '',
|
|
628
|
+
affiliationHtml ? ` ${affiliationHtml}` : '',
|
|
629
|
+
bioHtml ? ` ${bioHtml}` : '',
|
|
630
|
+
' </div>',
|
|
631
|
+
'</div>',
|
|
632
|
+
].filter(Boolean).join('\n'));
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function renderWidgetCard(title, modifier, body) {
|
|
636
|
+
if (!normalizeOptionalString(body)) {
|
|
637
|
+
return '';
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return [
|
|
641
|
+
`<section class="content-card sidebar-card widget-card widget-card--${escapeHtml(modifier)}">`,
|
|
642
|
+
' <p class="section-kicker">Widget</p>',
|
|
643
|
+
` <h2>${escapeHtml(title)}</h2>`,
|
|
644
|
+
` ${body}`,
|
|
645
|
+
'</section>',
|
|
646
|
+
].join('\n');
|
|
647
|
+
}
|
|
648
|
+
|
|
358
649
|
function preparePage(page) {
|
|
359
650
|
const documentType = normalizeDocumentType(page.document_type);
|
|
360
651
|
|
|
@@ -641,6 +932,51 @@ function renderPagination(paginationData) {
|
|
|
641
932
|
return `<nav class="pagination">\n ${prevLink}\n <span class="pages">${pageLinks}</span>\n ${nextLink}\n</nav>`;
|
|
642
933
|
}
|
|
643
934
|
|
|
935
|
+
function buildTaxonomyCountMap(posts, fieldName) {
|
|
936
|
+
const counts = new Map();
|
|
937
|
+
|
|
938
|
+
for (const post of posts) {
|
|
939
|
+
const values = Array.isArray(post?.[fieldName]) ? post[fieldName] : [];
|
|
940
|
+
for (const value of values) {
|
|
941
|
+
counts.set(value, (counts.get(value) || 0) + 1);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
return counts;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function buildArchiveEntries(posts, site) {
|
|
949
|
+
const entries = new Map();
|
|
950
|
+
|
|
951
|
+
for (const post of posts) {
|
|
952
|
+
const publishedAt = normalizeIsoTimestamp(post?.published_at_iso);
|
|
953
|
+
if (!publishedAt) {
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
const date = toDate(publishedAt);
|
|
958
|
+
const key = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
|
|
959
|
+
const current = entries.get(key) || { date, count: 0 };
|
|
960
|
+
current.count += 1;
|
|
961
|
+
entries.set(key, current);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return Array.from(entries.values())
|
|
965
|
+
.sort((left, right) => right.date.getTime() - left.date.getTime())
|
|
966
|
+
.map((entry) => ({
|
|
967
|
+
label: formatArchiveLabel(entry.date, site),
|
|
968
|
+
count: entry.count,
|
|
969
|
+
}));
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function formatArchiveLabel(date, site) {
|
|
973
|
+
return new Intl.DateTimeFormat(normalizeLocale(site.locale || DEFAULT_LOCALE), {
|
|
974
|
+
timeZone: normalizeNonEmptyString(site.timezone, DEFAULT_TIMEZONE),
|
|
975
|
+
year: 'numeric',
|
|
976
|
+
month: 'long',
|
|
977
|
+
}).format(date);
|
|
978
|
+
}
|
|
979
|
+
|
|
644
980
|
function formatTimestamp(value, site) {
|
|
645
981
|
const date = toDate(value);
|
|
646
982
|
const locale = normalizeLocale(site.locale || DEFAULT_LOCALE);
|
|
@@ -779,6 +1115,8 @@ async function assertValidThemePackage(themePackage) {
|
|
|
779
1115
|
...(themePackage.metadata.author ? { author: themePackage.metadata.author } : {}),
|
|
780
1116
|
...(themePackage.metadata.description ? { description: themePackage.metadata.description } : {}),
|
|
781
1117
|
...(themePackage.metadata.thumbnail ? { thumbnail: themePackage.metadata.thumbnail } : {}),
|
|
1118
|
+
...(themePackage.metadata.menuSlots ? { menuSlots: themePackage.metadata.menuSlots } : {}),
|
|
1119
|
+
...(themePackage.metadata.widgetAreas ? { widgetAreas: themePackage.metadata.widgetAreas } : {}),
|
|
782
1120
|
settings: themePackage.metadata.settings || {},
|
|
783
1121
|
}));
|
|
784
1122
|
|
|
@@ -830,6 +1168,24 @@ async function buildAssetOutputs(assets, assetProcessor, options) {
|
|
|
830
1168
|
return outputs;
|
|
831
1169
|
}
|
|
832
1170
|
|
|
1171
|
+
async function buildCustomCssAsset(customCss, assetProcessor, options) {
|
|
1172
|
+
const content = normalizeOptionalString(customCss?.content);
|
|
1173
|
+
if (!content) {
|
|
1174
|
+
return null;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
const sourceBuffer = new TextEncoder().encode(content);
|
|
1178
|
+
const processedContent = await assetProcessor.processCSS(content);
|
|
1179
|
+
const hash = options.assetHashing ? `.${assetProcessor.generateAssetHash(sourceBuffer)}` : '';
|
|
1180
|
+
|
|
1181
|
+
return {
|
|
1182
|
+
originalPath: '__zeropress_custom.css',
|
|
1183
|
+
path: `assets/zeropress-custom${hash}.css`,
|
|
1184
|
+
content: processedContent,
|
|
1185
|
+
contentType: 'text/css',
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
833
1189
|
function createRenderContext(site, currentUrl) {
|
|
834
1190
|
return {
|
|
835
1191
|
site,
|
|
@@ -1097,6 +1453,14 @@ function injectHtmxScript(html) {
|
|
|
1097
1453
|
return html.replace('</body>', '<script src="https://unpkg.com/htmx.org@2.0.4" crossorigin="anonymous"></script>\n</body>');
|
|
1098
1454
|
}
|
|
1099
1455
|
|
|
1456
|
+
function injectCustomCssAssetLink(html, href) {
|
|
1457
|
+
if (!normalizeOptionalString(href)) {
|
|
1458
|
+
return html;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
return html.replace('</head>', ` <link rel="stylesheet" href="${escapeHtml(href)}">\n</head>`);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1100
1464
|
function buildSitemapXml(site, emitted, generatedAt) {
|
|
1101
1465
|
const entries = [
|
|
1102
1466
|
...emitted.indexRoutes
|
|
@@ -1263,3 +1627,12 @@ function escapeHtml(value) {
|
|
|
1263
1627
|
.replaceAll('"', '"')
|
|
1264
1628
|
.replaceAll("'", ''');
|
|
1265
1629
|
}
|
|
1630
|
+
|
|
1631
|
+
function clampInteger(value, fallback, min, max) {
|
|
1632
|
+
const normalized = Number.isInteger(value) ? value : fallback;
|
|
1633
|
+
return Math.min(max, Math.max(min, normalized));
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
function normalizeLinkTarget(value) {
|
|
1637
|
+
return value === '_blank' ? '_blank' : '_self';
|
|
1638
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export interface ThemePackage {
|
|
|
12
12
|
title: string;
|
|
13
13
|
description?: string;
|
|
14
14
|
}>;
|
|
15
|
+
widgetAreas?: Record<string, {
|
|
16
|
+
title: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
}>;
|
|
15
19
|
namespace?: string;
|
|
16
20
|
slug?: string;
|
|
17
21
|
license?: 'MIT' | 'Apache-2.0' | 'BSD-3-Clause' | 'GPL-3.0-only' | 'GPL-3.0-or-later';
|
|
@@ -51,6 +51,8 @@ export async function loadThemePackageFromDir(themeDir) {
|
|
|
51
51
|
description: themeJson.description,
|
|
52
52
|
thumbnail: themeJson.thumbnail,
|
|
53
53
|
settings: themeJson.settings || {},
|
|
54
|
+
menuSlots: themeJson.menuSlots,
|
|
55
|
+
widgetAreas: themeJson.widgetAreas,
|
|
54
56
|
namespace: themeJson.namespace,
|
|
55
57
|
slug: themeJson.slug,
|
|
56
58
|
license: themeJson.license,
|