@shevky/core 0.0.5 → 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.
@@ -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
  }
@@ -18,6 +18,10 @@ export class ContentHeader {
18
18
  : "";
19
19
  }
20
20
 
21
+ get fragment() {
22
+ return _fmt.boolean(this._frontMatter.fragment);
23
+ }
24
+
21
25
  get id() {
22
26
  return typeof this._frontMatter.id === "string"
23
27
  ? this._frontMatter.id.trim()
@@ -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.5",
3
+ "version": "0.0.6",
4
4
  "description": "A minimal, dependency-light static site generator.",
5
5
  "type": "module",
6
6
  "main": "shevky.js",
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 Mustache.render(
517
- template.content,
518
- {
519
- content: { html: decorateHtml(contentHtml, templateName) },
520
- front: normalizedFront,
521
- lang,
522
- listing,
523
- site,
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
@@ -4,7 +4,7 @@ import _cli from "./cli.js";
4
4
  import _build from "./build.js";
5
5
  import _init from "./init.js";
6
6
 
7
- const VERSION = "0.0.4";
7
+ const VERSION = "0.0.6";
8
8
 
9
9
  const __filename = _io.url.toPath(import.meta.url);
10
10
  const __dirname = _io.path.name(__filename);
package/scripts/social.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import { config as _cfg } from "@shevky/base";
2
2
 
3
3
  function getRss() {
4
+ if (!_cfg.identity.social.rss) {
5
+ return;
6
+ }
7
+
4
8
  return {
5
9
  key: "rss",
6
10
  tone: "rss",