@zeropress/build-core 0.1.12 → 0.1.14
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 +390 -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.14",
|
|
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, normalizedSite.mediaBaseUrl),
|
|
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,49 @@ function normalizePreviewData(previewData) {
|
|
|
281
298
|
};
|
|
282
299
|
}
|
|
283
300
|
|
|
301
|
+
function normalizeWidgetAreas(widgetAreas, mediaBaseUrl) {
|
|
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) => normalizeWidgetItem(item, mediaBaseUrl))
|
|
314
|
+
: [],
|
|
315
|
+
},
|
|
316
|
+
]),
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function normalizeWidgetItem(item, mediaBaseUrl) {
|
|
321
|
+
const normalizedItem = {
|
|
322
|
+
...item,
|
|
323
|
+
title: normalizeNonEmptyString(item?.title, 'Widget'),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
if (item?.type === 'profile' && item?.settings && typeof item.settings === 'object') {
|
|
327
|
+
return {
|
|
328
|
+
...normalizedItem,
|
|
329
|
+
settings: {
|
|
330
|
+
...item.settings,
|
|
331
|
+
avatar: normalizeMediaField(item.settings.avatar, mediaBaseUrl),
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return normalizedItem;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function normalizeCustomCss(customCss) {
|
|
340
|
+
const content = normalizeOptionalString(customCss?.content);
|
|
341
|
+
return content ? { content } : undefined;
|
|
342
|
+
}
|
|
343
|
+
|
|
284
344
|
function createRenderData(previewData) {
|
|
285
345
|
const authorsById = new Map(previewData.content.authors.map((author) => [author.id, author]));
|
|
286
346
|
const categoriesBySlug = new Map(previewData.content.categories.map((category) => [category.slug, category]));
|
|
@@ -355,6 +415,253 @@ function createRenderData(previewData) {
|
|
|
355
415
|
};
|
|
356
416
|
}
|
|
357
417
|
|
|
418
|
+
function renderWidgetAreas(previewData, renderData) {
|
|
419
|
+
if (!previewData.widgets || typeof previewData.widgets !== 'object') {
|
|
420
|
+
return {};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return Object.fromEntries(
|
|
424
|
+
Object.entries(previewData.widgets).map(([widgetAreaId, widgetArea]) => [
|
|
425
|
+
widgetAreaId,
|
|
426
|
+
renderWidgetArea(widgetArea, previewData, renderData, widgetAreaId),
|
|
427
|
+
]),
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function renderWidgetArea(widgetArea, previewData, renderData, widgetAreaId) {
|
|
432
|
+
if (!widgetArea || !Array.isArray(widgetArea.items) || widgetArea.items.length === 0) {
|
|
433
|
+
return '';
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return widgetArea.items
|
|
437
|
+
.map((item, index) => renderWidgetItem(item, previewData, renderData, widgetAreaId, index))
|
|
438
|
+
.filter(Boolean)
|
|
439
|
+
.join('\n');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function renderWidgetItem(item, previewData, renderData, widgetAreaId, index) {
|
|
443
|
+
if (!item || typeof item !== 'object') {
|
|
444
|
+
return '';
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const title = normalizeNonEmptyString(item.title, 'Widget');
|
|
448
|
+
|
|
449
|
+
switch (item.type) {
|
|
450
|
+
case 'recent-posts':
|
|
451
|
+
return renderRecentPostsWidget(title, item.settings, renderData);
|
|
452
|
+
case 'categories':
|
|
453
|
+
return renderCategoriesWidget(title, item.settings, previewData);
|
|
454
|
+
case 'tags':
|
|
455
|
+
return renderTagsWidget(title, item.settings, previewData);
|
|
456
|
+
case 'archives':
|
|
457
|
+
return renderArchivesWidget(title, item.settings, previewData);
|
|
458
|
+
case 'image':
|
|
459
|
+
return renderImageWidget(title, item.settings);
|
|
460
|
+
case 'text':
|
|
461
|
+
return renderTextWidget(title, item.settings);
|
|
462
|
+
case 'link-list':
|
|
463
|
+
return renderLinkListWidget(title, item.settings);
|
|
464
|
+
case 'search':
|
|
465
|
+
return renderSearchWidget(title, item.settings, widgetAreaId, index);
|
|
466
|
+
case 'profile':
|
|
467
|
+
return renderProfileWidget(title, item.settings);
|
|
468
|
+
default:
|
|
469
|
+
return '';
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function renderRecentPostsWidget(title, settings, renderData) {
|
|
474
|
+
const limit = clampInteger(settings?.limit, 5, 1, 20);
|
|
475
|
+
const showDate = settings?.show_date !== false;
|
|
476
|
+
const items = renderData.posts
|
|
477
|
+
.slice(0, limit)
|
|
478
|
+
.map((post) => {
|
|
479
|
+
const dateMeta = showDate
|
|
480
|
+
? `<span class="widget-list__meta">${escapeHtml(post.published_at)}</span>`
|
|
481
|
+
: '';
|
|
482
|
+
|
|
483
|
+
return `<li class="widget-list__item"><a href="/posts/${escapeHtml(encodeSlugSegment(post.slug))}/">${escapeHtml(post.title)}</a>${dateMeta}</li>`;
|
|
484
|
+
})
|
|
485
|
+
.join('');
|
|
486
|
+
|
|
487
|
+
if (!items) {
|
|
488
|
+
return '';
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return renderWidgetCard(title, 'recent-posts', `<ul class="widget-list">${items}</ul>`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function renderCategoriesWidget(title, settings, previewData) {
|
|
495
|
+
const countBySlug = buildTaxonomyCountMap(previewData.content.posts, 'category_slugs');
|
|
496
|
+
const items = previewData.content.categories
|
|
497
|
+
.map((category) => {
|
|
498
|
+
const count = countBySlug.get(category.slug) || 0;
|
|
499
|
+
if (count === 0) {
|
|
500
|
+
return '';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const suffix = settings?.show_count === true ? ` <span class="widget-taxonomy__count">(${count})</span>` : '';
|
|
504
|
+
return `<a href="/categories/${escapeHtml(encodeSlugSegment(category.slug))}/" class="category-link">${escapeHtml(category.name)}${suffix}</a>`;
|
|
505
|
+
})
|
|
506
|
+
.filter(Boolean)
|
|
507
|
+
.join('');
|
|
508
|
+
|
|
509
|
+
if (!items) {
|
|
510
|
+
return '';
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return renderWidgetCard(title, 'categories', `<div class="taxonomy-list taxonomy-list--stack widget-taxonomy">${items}</div>`);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function renderTagsWidget(title, settings, previewData) {
|
|
517
|
+
const countBySlug = buildTaxonomyCountMap(previewData.content.posts, 'tag_slugs');
|
|
518
|
+
const limit = clampInteger(settings?.limit, 20, 1, 100);
|
|
519
|
+
const items = previewData.content.tags
|
|
520
|
+
.map((tag) => ({
|
|
521
|
+
...tag,
|
|
522
|
+
count: countBySlug.get(tag.slug) || 0,
|
|
523
|
+
}))
|
|
524
|
+
.filter((tag) => tag.count > 0)
|
|
525
|
+
.slice(0, limit)
|
|
526
|
+
.map((tag) => {
|
|
527
|
+
const suffix = settings?.show_count === true ? ` <span class="widget-taxonomy__count">(${tag.count})</span>` : '';
|
|
528
|
+
return `<a href="/tags/${escapeHtml(encodeSlugSegment(tag.slug))}/" class="tag-link">${escapeHtml(tag.name)}${suffix}</a>`;
|
|
529
|
+
})
|
|
530
|
+
.join('');
|
|
531
|
+
|
|
532
|
+
if (!items) {
|
|
533
|
+
return '';
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return renderWidgetCard(title, 'tags', `<div class="taxonomy-list widget-taxonomy">${items}</div>`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function renderArchivesWidget(title, settings, previewData) {
|
|
540
|
+
const limit = clampInteger(settings?.limit, 12, 1, 120);
|
|
541
|
+
const archiveEntries = buildArchiveEntries(previewData.content.posts, previewData.site).slice(0, limit);
|
|
542
|
+
const items = archiveEntries
|
|
543
|
+
.map((entry) => `<li class="widget-list__item"><a href="/archive/">${escapeHtml(entry.label)}</a><span class="widget-list__meta">${entry.count} posts</span></li>`)
|
|
544
|
+
.join('');
|
|
545
|
+
|
|
546
|
+
if (!items) {
|
|
547
|
+
return '';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return renderWidgetCard(title, 'archives', `<ul class="widget-list">${items}</ul>`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function renderImageWidget(title, settings) {
|
|
554
|
+
const src = normalizeOptionalString(settings?.src);
|
|
555
|
+
if (!src) {
|
|
556
|
+
return '';
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const alt = escapeHtml(normalizeOptionalString(settings?.alt));
|
|
560
|
+
const caption = normalizeOptionalString(settings?.caption);
|
|
561
|
+
const href = normalizeOptionalString(settings?.href);
|
|
562
|
+
const target = normalizeLinkTarget(settings?.target);
|
|
563
|
+
const rel = target === '_blank' ? ' rel="noreferrer noopener"' : '';
|
|
564
|
+
const image = `<img class="widget-image__media" src="${escapeHtml(src)}" alt="${alt}">`;
|
|
565
|
+
const linkedImage = href ? `<a href="${escapeHtml(href)}" target="${target}"${rel}>${image}</a>` : image;
|
|
566
|
+
const captionHtml = caption ? `<figcaption class="widget-image__caption">${escapeHtml(caption)}</figcaption>` : '';
|
|
567
|
+
|
|
568
|
+
return renderWidgetCard(title, 'image', `<figure class="widget-image">${linkedImage}${captionHtml}</figure>`);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function renderTextWidget(title, settings) {
|
|
572
|
+
const content = typeof settings?.content === 'string' ? settings.content : '';
|
|
573
|
+
const html = renderDocumentContent(content, normalizeDocumentType(settings?.document_type));
|
|
574
|
+
if (!normalizeOptionalString(html)) {
|
|
575
|
+
return '';
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return renderWidgetCard(title, 'text', `<div class="widget-copy">${html}</div>`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function renderLinkListWidget(title, settings) {
|
|
582
|
+
const links = Array.isArray(settings?.links) ? settings.links : [];
|
|
583
|
+
const items = links
|
|
584
|
+
.map((link) => {
|
|
585
|
+
const label = normalizeOptionalString(link?.label);
|
|
586
|
+
const url = normalizeOptionalString(link?.url);
|
|
587
|
+
if (!label || !url) {
|
|
588
|
+
return '';
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const target = normalizeLinkTarget(link?.target);
|
|
592
|
+
const rel = target === '_blank' ? ' rel="noreferrer noopener"' : '';
|
|
593
|
+
return `<a href="${escapeHtml(url)}" target="${target}"${rel}>${escapeHtml(label)}</a>`;
|
|
594
|
+
})
|
|
595
|
+
.filter(Boolean)
|
|
596
|
+
.join('');
|
|
597
|
+
|
|
598
|
+
if (!items) {
|
|
599
|
+
return '';
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return renderWidgetCard(title, 'link-list', `<div class="taxonomy-list taxonomy-list--stack widget-link-list">${items}</div>`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function renderSearchWidget(title, settings, widgetAreaId, index) {
|
|
606
|
+
const placeholder = escapeHtml(normalizeNonEmptyString(settings?.placeholder, 'Search...'));
|
|
607
|
+
const buttonLabel = escapeHtml(normalizeNonEmptyString(settings?.button_label, 'Search'));
|
|
608
|
+
const searchId = `widget-search-${widgetAreaId}-${index + 1}`;
|
|
609
|
+
|
|
610
|
+
return renderWidgetCard(title, 'search', [
|
|
611
|
+
'<div class="widget-search" data-search-root>',
|
|
612
|
+
` <label class="sr-only" for="${escapeHtml(searchId)}">${escapeHtml(title)}</label>`,
|
|
613
|
+
' <form class="search-theme-wrapper" data-search-form>',
|
|
614
|
+
` <input id="${escapeHtml(searchId)}" class="search-input" data-theme-search type="search" placeholder="${placeholder}" autocomplete="off" enterkeyhint="search">`,
|
|
615
|
+
` <button class="widget-search-button" type="submit">${buttonLabel}</button>`,
|
|
616
|
+
' </form>',
|
|
617
|
+
' <div class="search-results" data-search-results hidden></div>',
|
|
618
|
+
' <p class="search-feedback search-feedback--sidebar" data-search-feedback aria-live="polite"></p>',
|
|
619
|
+
'</div>',
|
|
620
|
+
].join('\n'));
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function renderProfileWidget(title, settings) {
|
|
624
|
+
const displayName = normalizeOptionalString(settings?.display_name);
|
|
625
|
+
const affiliation = normalizeOptionalString(settings?.affiliation);
|
|
626
|
+
const bioShort = normalizeOptionalString(settings?.bio_short);
|
|
627
|
+
const avatar = normalizeOptionalString(settings?.avatar);
|
|
628
|
+
|
|
629
|
+
if (!displayName && !bioShort && !avatar) {
|
|
630
|
+
return '';
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const avatarHtml = avatar
|
|
634
|
+
? `<img class="widget-profile__avatar" src="${escapeHtml(avatar)}" alt="${escapeHtml(displayName || title)}">`
|
|
635
|
+
: '';
|
|
636
|
+
const affiliationHtml = affiliation ? `<p class="widget-profile__affiliation">${escapeHtml(affiliation)}</p>` : '';
|
|
637
|
+
const bioHtml = bioShort ? `<p class="sidebar-copy">${escapeHtml(bioShort)}</p>` : '';
|
|
638
|
+
|
|
639
|
+
return renderWidgetCard(title, 'profile', [
|
|
640
|
+
'<div class="widget-profile">',
|
|
641
|
+
avatarHtml,
|
|
642
|
+
' <div class="widget-profile__body">',
|
|
643
|
+
displayName ? ` <p class="widget-profile__name">${escapeHtml(displayName)}</p>` : '',
|
|
644
|
+
affiliationHtml ? ` ${affiliationHtml}` : '',
|
|
645
|
+
bioHtml ? ` ${bioHtml}` : '',
|
|
646
|
+
' </div>',
|
|
647
|
+
'</div>',
|
|
648
|
+
].filter(Boolean).join('\n'));
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function renderWidgetCard(title, modifier, body) {
|
|
652
|
+
if (!normalizeOptionalString(body)) {
|
|
653
|
+
return '';
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return [
|
|
657
|
+
`<section class="content-card sidebar-card widget-card widget-card--${escapeHtml(modifier)}">`,
|
|
658
|
+
' <p class="section-kicker">Widget</p>',
|
|
659
|
+
` <h2>${escapeHtml(title)}</h2>`,
|
|
660
|
+
` ${body}`,
|
|
661
|
+
'</section>',
|
|
662
|
+
].join('\n');
|
|
663
|
+
}
|
|
664
|
+
|
|
358
665
|
function preparePage(page) {
|
|
359
666
|
const documentType = normalizeDocumentType(page.document_type);
|
|
360
667
|
|
|
@@ -641,6 +948,51 @@ function renderPagination(paginationData) {
|
|
|
641
948
|
return `<nav class="pagination">\n ${prevLink}\n <span class="pages">${pageLinks}</span>\n ${nextLink}\n</nav>`;
|
|
642
949
|
}
|
|
643
950
|
|
|
951
|
+
function buildTaxonomyCountMap(posts, fieldName) {
|
|
952
|
+
const counts = new Map();
|
|
953
|
+
|
|
954
|
+
for (const post of posts) {
|
|
955
|
+
const values = Array.isArray(post?.[fieldName]) ? post[fieldName] : [];
|
|
956
|
+
for (const value of values) {
|
|
957
|
+
counts.set(value, (counts.get(value) || 0) + 1);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
return counts;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function buildArchiveEntries(posts, site) {
|
|
965
|
+
const entries = new Map();
|
|
966
|
+
|
|
967
|
+
for (const post of posts) {
|
|
968
|
+
const publishedAt = normalizeIsoTimestamp(post?.published_at_iso);
|
|
969
|
+
if (!publishedAt) {
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const date = toDate(publishedAt);
|
|
974
|
+
const key = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
|
|
975
|
+
const current = entries.get(key) || { date, count: 0 };
|
|
976
|
+
current.count += 1;
|
|
977
|
+
entries.set(key, current);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return Array.from(entries.values())
|
|
981
|
+
.sort((left, right) => right.date.getTime() - left.date.getTime())
|
|
982
|
+
.map((entry) => ({
|
|
983
|
+
label: formatArchiveLabel(entry.date, site),
|
|
984
|
+
count: entry.count,
|
|
985
|
+
}));
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function formatArchiveLabel(date, site) {
|
|
989
|
+
return new Intl.DateTimeFormat(normalizeLocale(site.locale || DEFAULT_LOCALE), {
|
|
990
|
+
timeZone: normalizeNonEmptyString(site.timezone, DEFAULT_TIMEZONE),
|
|
991
|
+
year: 'numeric',
|
|
992
|
+
month: 'long',
|
|
993
|
+
}).format(date);
|
|
994
|
+
}
|
|
995
|
+
|
|
644
996
|
function formatTimestamp(value, site) {
|
|
645
997
|
const date = toDate(value);
|
|
646
998
|
const locale = normalizeLocale(site.locale || DEFAULT_LOCALE);
|
|
@@ -779,6 +1131,8 @@ async function assertValidThemePackage(themePackage) {
|
|
|
779
1131
|
...(themePackage.metadata.author ? { author: themePackage.metadata.author } : {}),
|
|
780
1132
|
...(themePackage.metadata.description ? { description: themePackage.metadata.description } : {}),
|
|
781
1133
|
...(themePackage.metadata.thumbnail ? { thumbnail: themePackage.metadata.thumbnail } : {}),
|
|
1134
|
+
...(themePackage.metadata.menuSlots ? { menuSlots: themePackage.metadata.menuSlots } : {}),
|
|
1135
|
+
...(themePackage.metadata.widgetAreas ? { widgetAreas: themePackage.metadata.widgetAreas } : {}),
|
|
782
1136
|
settings: themePackage.metadata.settings || {},
|
|
783
1137
|
}));
|
|
784
1138
|
|
|
@@ -830,6 +1184,24 @@ async function buildAssetOutputs(assets, assetProcessor, options) {
|
|
|
830
1184
|
return outputs;
|
|
831
1185
|
}
|
|
832
1186
|
|
|
1187
|
+
async function buildCustomCssAsset(customCss, assetProcessor, options) {
|
|
1188
|
+
const content = normalizeOptionalString(customCss?.content);
|
|
1189
|
+
if (!content) {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const sourceBuffer = new TextEncoder().encode(content);
|
|
1194
|
+
const processedContent = await assetProcessor.processCSS(content);
|
|
1195
|
+
const hash = options.assetHashing ? `.${assetProcessor.generateAssetHash(sourceBuffer)}` : '';
|
|
1196
|
+
|
|
1197
|
+
return {
|
|
1198
|
+
originalPath: '__zeropress_custom.css',
|
|
1199
|
+
path: `assets/zeropress-custom${hash}.css`,
|
|
1200
|
+
content: processedContent,
|
|
1201
|
+
contentType: 'text/css',
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
|
|
833
1205
|
function createRenderContext(site, currentUrl) {
|
|
834
1206
|
return {
|
|
835
1207
|
site,
|
|
@@ -1097,6 +1469,14 @@ function injectHtmxScript(html) {
|
|
|
1097
1469
|
return html.replace('</body>', '<script src="https://unpkg.com/htmx.org@2.0.4" crossorigin="anonymous"></script>\n</body>');
|
|
1098
1470
|
}
|
|
1099
1471
|
|
|
1472
|
+
function injectCustomCssAssetLink(html, href) {
|
|
1473
|
+
if (!normalizeOptionalString(href)) {
|
|
1474
|
+
return html;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
return html.replace('</head>', ` <link rel="stylesheet" href="${escapeHtml(href)}">\n</head>`);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1100
1480
|
function buildSitemapXml(site, emitted, generatedAt) {
|
|
1101
1481
|
const entries = [
|
|
1102
1482
|
...emitted.indexRoutes
|
|
@@ -1263,3 +1643,12 @@ function escapeHtml(value) {
|
|
|
1263
1643
|
.replaceAll('"', '"')
|
|
1264
1644
|
.replaceAll("'", ''');
|
|
1265
1645
|
}
|
|
1646
|
+
|
|
1647
|
+
function clampInteger(value, fallback, min, max) {
|
|
1648
|
+
const normalized = Number.isInteger(value) ? value : fallback;
|
|
1649
|
+
return Math.min(max, Math.max(min, normalized));
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
function normalizeLinkTarget(value) {
|
|
1653
|
+
return value === '_blank' ? '_blank' : '_self';
|
|
1654
|
+
}
|
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,
|