@shevky/core 0.0.4 → 0.0.6
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/engines/renderEngine.js +3 -1
- package/lib/contentFile.js +16 -0
- package/lib/contentHeader.js +4 -0
- package/lib/contentSummary.js +5 -0
- package/package.json +2 -2
- package/registries/contentRegistry.js +7 -2
- package/scripts/build.js +164 -19
- package/scripts/main.js +1 -1
- package/scripts/social.js +4 -0
- package/types/index.d.ts +5 -0
package/engines/renderEngine.js
CHANGED
|
@@ -517,7 +517,9 @@ export class RenderEngine {
|
|
|
517
517
|
return;
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
-
const configKeys = Object.keys(collectionsConfig)
|
|
520
|
+
const configKeys = Object.keys(collectionsConfig).filter(
|
|
521
|
+
(key) => key !== "includeContentFile",
|
|
522
|
+
);
|
|
521
523
|
for (const configKey of configKeys) {
|
|
522
524
|
const config = collectionsConfig[configKey];
|
|
523
525
|
if (!config || typeof config !== "object") {
|
package/lib/contentFile.js
CHANGED
|
@@ -40,6 +40,10 @@ export class ContentFile {
|
|
|
40
40
|
return this._header.status;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
get fragment() {
|
|
44
|
+
return this._header.fragment;
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
get id() {
|
|
44
48
|
return this._header.id;
|
|
45
49
|
}
|
|
@@ -167,4 +171,16 @@ export class ContentFile {
|
|
|
167
171
|
get isPostTemplate() {
|
|
168
172
|
return this.template === "post";
|
|
169
173
|
}
|
|
174
|
+
|
|
175
|
+
toObject() {
|
|
176
|
+
return {
|
|
177
|
+
header: this._header.raw,
|
|
178
|
+
body: {
|
|
179
|
+
content: this.content,
|
|
180
|
+
},
|
|
181
|
+
content: this.content,
|
|
182
|
+
isValid: this.isValid,
|
|
183
|
+
sourcePath: this.sourcePath,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
170
186
|
}
|
package/lib/contentHeader.js
CHANGED
package/lib/contentSummary.js
CHANGED
|
@@ -12,6 +12,10 @@ export class ContentSummary {
|
|
|
12
12
|
return this._file.id;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
get fragment() {
|
|
16
|
+
return this._file.fragment;
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
get title() {
|
|
16
20
|
return this._file.title;
|
|
17
21
|
}
|
|
@@ -67,6 +71,7 @@ export class ContentSummary {
|
|
|
67
71
|
toObject() {
|
|
68
72
|
return {
|
|
69
73
|
id: this.id,
|
|
74
|
+
fragment: this.fragment,
|
|
70
75
|
title: this.title,
|
|
71
76
|
slug: this.slug,
|
|
72
77
|
lang: this.lang,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shevky/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "A minimal, dependency-light static site generator.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "shevky.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/shevky/core#readme",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@shevky/base": "^0.0.
|
|
39
|
+
"@shevky/base": "^0.0.3",
|
|
40
40
|
"@types/node": "^20.11.30",
|
|
41
41
|
"@types/mustache": "^4.2.6",
|
|
42
42
|
"@types/html-minifier-terser": "^7.0.2",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { io as _io } from "@shevky/base";
|
|
1
|
+
import { io as _io, config as _cfg } from "@shevky/base";
|
|
2
2
|
import matter from "gray-matter";
|
|
3
3
|
|
|
4
4
|
import { ContentFile } from "../lib/contentFile.js";
|
|
@@ -202,6 +202,9 @@ export class ContentRegistry {
|
|
|
202
202
|
return this.#_collectionsCache;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
const includeContentFile = Boolean(
|
|
206
|
+
_cfg?.content?.collections?.includeContentFile,
|
|
207
|
+
);
|
|
205
208
|
/** @type {import("../types/index.d.ts").CollectionsByLang} */
|
|
206
209
|
const pagesByLang = {};
|
|
207
210
|
const contentFiles = this.files;
|
|
@@ -211,8 +214,10 @@ export class ContentRegistry {
|
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
const contentSummary = new ContentSummary(file);
|
|
217
|
+
const summaryBase = contentSummary.toObject();
|
|
214
218
|
const summary = /** @type {import("../types/index.d.ts").CollectionEntry} */ ({
|
|
215
|
-
...
|
|
219
|
+
...summaryBase,
|
|
220
|
+
...(includeContentFile ? file.toObject() : {}),
|
|
216
221
|
canonical: this.#_metaEngine.buildContentUrl(
|
|
217
222
|
file.canonical,
|
|
218
223
|
file.lang,
|
package/scripts/build.js
CHANGED
|
@@ -48,6 +48,7 @@ const TEMPLATES_DIR = _prj.templatesDir;
|
|
|
48
48
|
const ASSETS_DIR = _prj.assetsDir;
|
|
49
49
|
const SITE_CONFIG_PATH = _prj.siteConfig;
|
|
50
50
|
const I18N_CONFIG_PATH = _prj.i18nConfig;
|
|
51
|
+
const FRAGMENTS_DIR = "fragments";
|
|
51
52
|
|
|
52
53
|
const pluginRegistry = new PluginRegistry();
|
|
53
54
|
const templateRegistry = new TemplateRegistry();
|
|
@@ -461,6 +462,100 @@ async function renderContentTemplate(
|
|
|
461
462
|
listingOverride,
|
|
462
463
|
) {
|
|
463
464
|
const template = templateRegistry.getTemplate(TYPE_TEMPLATE, templateName);
|
|
465
|
+
const {
|
|
466
|
+
normalizedFront,
|
|
467
|
+
listing,
|
|
468
|
+
collectionFlags,
|
|
469
|
+
site,
|
|
470
|
+
languageFlags,
|
|
471
|
+
resolvedDictionary,
|
|
472
|
+
} = buildContentRenderContext(front, lang, dictionary, listingOverride);
|
|
473
|
+
|
|
474
|
+
return Mustache.render(
|
|
475
|
+
template.content,
|
|
476
|
+
{
|
|
477
|
+
content: { html: decorateHtml(contentHtml, templateName) },
|
|
478
|
+
front: normalizedFront,
|
|
479
|
+
lang,
|
|
480
|
+
listing,
|
|
481
|
+
site,
|
|
482
|
+
locale: languageFlags.locale,
|
|
483
|
+
isEnglish: languageFlags.isEnglish,
|
|
484
|
+
isTurkish: languageFlags.isTurkish,
|
|
485
|
+
i18n: resolvedDictionary,
|
|
486
|
+
...collectionFlags,
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
...templateRegistry.getFiles(TYPE_PARTIAL),
|
|
490
|
+
...templateRegistry.getFiles(TYPE_COMPONENT),
|
|
491
|
+
},
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* @param {string} contentHtml
|
|
497
|
+
* @param {FrontMatter} front
|
|
498
|
+
* @param {string} lang
|
|
499
|
+
* @param {Record<string, any>} dictionary
|
|
500
|
+
* @param {string} [templateName]
|
|
501
|
+
* @param {any} [listingOverride]
|
|
502
|
+
*/
|
|
503
|
+
async function renderFragmentTemplate(
|
|
504
|
+
contentHtml,
|
|
505
|
+
front,
|
|
506
|
+
lang,
|
|
507
|
+
dictionary,
|
|
508
|
+
templateName,
|
|
509
|
+
listingOverride,
|
|
510
|
+
) {
|
|
511
|
+
const template =
|
|
512
|
+
typeof templateName === "string" && templateName.trim().length > 0
|
|
513
|
+
? templateRegistry.getTemplate(TYPE_TEMPLATE, templateName.trim())
|
|
514
|
+
: null;
|
|
515
|
+
if (templateName && !template) {
|
|
516
|
+
_log.warn(`[build] Fragment template not found: ${templateName}`);
|
|
517
|
+
}
|
|
518
|
+
const {
|
|
519
|
+
normalizedFront,
|
|
520
|
+
listing,
|
|
521
|
+
collectionFlags,
|
|
522
|
+
site,
|
|
523
|
+
languageFlags,
|
|
524
|
+
resolvedDictionary,
|
|
525
|
+
} = buildContentRenderContext(front, lang, dictionary, listingOverride);
|
|
526
|
+
const decorated = decorateHtml(contentHtml, templateName ?? "");
|
|
527
|
+
const templateContent = template?.content ?? "{{{content.html}}}";
|
|
528
|
+
|
|
529
|
+
return Mustache.render(
|
|
530
|
+
templateContent,
|
|
531
|
+
{
|
|
532
|
+
content: { html: decorated },
|
|
533
|
+
contentHtml: decorated,
|
|
534
|
+
contentObject: { html: decorated },
|
|
535
|
+
front: normalizedFront,
|
|
536
|
+
lang,
|
|
537
|
+
listing,
|
|
538
|
+
site,
|
|
539
|
+
locale: languageFlags.locale,
|
|
540
|
+
isEnglish: languageFlags.isEnglish,
|
|
541
|
+
isTurkish: languageFlags.isTurkish,
|
|
542
|
+
i18n: resolvedDictionary,
|
|
543
|
+
...collectionFlags,
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
...templateRegistry.getFiles(TYPE_PARTIAL),
|
|
547
|
+
...templateRegistry.getFiles(TYPE_COMPONENT),
|
|
548
|
+
},
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* @param {FrontMatter} front
|
|
554
|
+
* @param {string} lang
|
|
555
|
+
* @param {Record<string, any>} dictionary
|
|
556
|
+
* @param {any} [listingOverride]
|
|
557
|
+
*/
|
|
558
|
+
function buildContentRenderContext(front, lang, dictionary, listingOverride) {
|
|
464
559
|
const baseFront = normalizeFrontMatter(front);
|
|
465
560
|
/** @type {string[]} */
|
|
466
561
|
const normalizedTags = Array.isArray(front.tags)
|
|
@@ -513,25 +608,14 @@ async function renderContentTemplate(
|
|
|
513
608
|
const site = metaEngine.buildSiteData(lang);
|
|
514
609
|
const languageFlags = _i18n.flags(lang);
|
|
515
610
|
|
|
516
|
-
return
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
locale: languageFlags.locale,
|
|
525
|
-
isEnglish: languageFlags.isEnglish,
|
|
526
|
-
isTurkish: languageFlags.isTurkish,
|
|
527
|
-
i18n: resolvedDictionary,
|
|
528
|
-
...collectionFlags,
|
|
529
|
-
},
|
|
530
|
-
{
|
|
531
|
-
...templateRegistry.getFiles(TYPE_PARTIAL),
|
|
532
|
-
...templateRegistry.getFiles(TYPE_COMPONENT),
|
|
533
|
-
},
|
|
534
|
-
);
|
|
611
|
+
return {
|
|
612
|
+
normalizedFront,
|
|
613
|
+
listing,
|
|
614
|
+
collectionFlags,
|
|
615
|
+
site,
|
|
616
|
+
languageFlags,
|
|
617
|
+
resolvedDictionary,
|
|
618
|
+
};
|
|
535
619
|
}
|
|
536
620
|
|
|
537
621
|
/**
|
|
@@ -597,6 +681,18 @@ function buildOutputPath(front, lang, slug) {
|
|
|
597
681
|
return _io.path.combine(...segments.filter(Boolean), "index.html");
|
|
598
682
|
}
|
|
599
683
|
|
|
684
|
+
/** @param {unknown} value */
|
|
685
|
+
function resolveFragmentId(value) {
|
|
686
|
+
if (typeof value !== "string") {
|
|
687
|
+
return "";
|
|
688
|
+
}
|
|
689
|
+
const trimmed = value.trim();
|
|
690
|
+
if (!trimmed) {
|
|
691
|
+
return "";
|
|
692
|
+
}
|
|
693
|
+
return trimmed.replace(/[\\/]/g, "-");
|
|
694
|
+
}
|
|
695
|
+
|
|
600
696
|
/** @param {string} value */
|
|
601
697
|
function toPosixPath(value) {
|
|
602
698
|
return value.split(_io.path.separator).join("/");
|
|
@@ -833,6 +929,13 @@ async function buildContentPages() {
|
|
|
833
929
|
markdownHtml ?? "",
|
|
834
930
|
placeholders,
|
|
835
931
|
);
|
|
932
|
+
const rawFront = normalizeFrontMatter(file.header);
|
|
933
|
+
const shouldBuildFragment = _fmt.boolean(rawFront.fragment);
|
|
934
|
+
const fragmentTemplateName =
|
|
935
|
+
typeof rawFront.fragmentTemplate === "string" &&
|
|
936
|
+
rawFront.fragmentTemplate.trim().length > 0
|
|
937
|
+
? rawFront.fragmentTemplate.trim()
|
|
938
|
+
: file.template;
|
|
836
939
|
|
|
837
940
|
if (file.template === "collection" || file.template === "home") {
|
|
838
941
|
await renderEngine.buildPaginatedCollectionPages({
|
|
@@ -918,6 +1021,48 @@ async function buildContentPages() {
|
|
|
918
1021
|
inputBytes: byteLength(file.content),
|
|
919
1022
|
},
|
|
920
1023
|
});
|
|
1024
|
+
|
|
1025
|
+
if (shouldBuildFragment) {
|
|
1026
|
+
const fragmentId = resolveFragmentId(rawFront.id ?? file.id);
|
|
1027
|
+
if (!fragmentId) {
|
|
1028
|
+
_log.warn(
|
|
1029
|
+
`[build] Fragment skipped for ${normalizeLogPath(file.sourcePath)} (missing id).`,
|
|
1030
|
+
);
|
|
1031
|
+
} else {
|
|
1032
|
+
const fragmentHtml = await renderFragmentTemplate(
|
|
1033
|
+
hydratedHtml,
|
|
1034
|
+
file.header,
|
|
1035
|
+
file.lang,
|
|
1036
|
+
dictionary,
|
|
1037
|
+
fragmentTemplateName,
|
|
1038
|
+
);
|
|
1039
|
+
const transformedFragment = await renderEngine.transformHtml(
|
|
1040
|
+
fragmentHtml,
|
|
1041
|
+
{
|
|
1042
|
+
versionToken,
|
|
1043
|
+
minifyHtml,
|
|
1044
|
+
},
|
|
1045
|
+
);
|
|
1046
|
+
const fragmentLang =
|
|
1047
|
+
typeof file.lang === "string" && file.lang.trim().length > 0
|
|
1048
|
+
? file.lang.trim()
|
|
1049
|
+
: _i18n.default;
|
|
1050
|
+
const fragmentPath = _io.path.combine(
|
|
1051
|
+
FRAGMENTS_DIR,
|
|
1052
|
+
fragmentLang,
|
|
1053
|
+
`frag_${fragmentId}.html`,
|
|
1054
|
+
);
|
|
1055
|
+
GENERATED_PAGES.add(toPosixPath(fragmentPath));
|
|
1056
|
+
await writeHtmlFile(fragmentPath, transformedFragment, {
|
|
1057
|
+
action: "BUILD_FRAGMENT",
|
|
1058
|
+
type: "fragment",
|
|
1059
|
+
source: file.sourcePath,
|
|
1060
|
+
lang: file.lang,
|
|
1061
|
+
template: fragmentTemplateName,
|
|
1062
|
+
inputBytes: byteLength(file.content),
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
921
1066
|
}
|
|
922
1067
|
|
|
923
1068
|
await renderEngine.buildDynamicCollectionPages({
|
package/scripts/main.js
CHANGED
package/scripts/social.js
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -91,6 +91,11 @@ export type CollectionEntry = ContentSummaryLike & {
|
|
|
91
91
|
type?: string;
|
|
92
92
|
seriesTitle?: string;
|
|
93
93
|
canonical?: string;
|
|
94
|
+
header?: ContentHeaderLike;
|
|
95
|
+
body?: ContentBodyLike;
|
|
96
|
+
content?: string;
|
|
97
|
+
isValid?: boolean;
|
|
98
|
+
sourcePath?: string;
|
|
94
99
|
};
|
|
95
100
|
|
|
96
101
|
export type CollectionsByLang = Record<string, Record<string, CollectionEntry[]>>;
|