@mdfriday/foundry 25.9.3 → 25.9.5
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 +10 -0
- package/dist/internal/application/ssg.d.ts.map +1 -1
- package/dist/internal/application/ssg.js +12 -0
- package/dist/internal/application/ssg.js.map +1 -1
- package/dist/internal/application/test-incremental-ssg.d.ts +9 -0
- package/dist/internal/application/test-incremental-ssg.d.ts.map +1 -0
- package/dist/internal/application/test-incremental-ssg.js +80 -0
- package/dist/internal/application/test-incremental-ssg.js.map +1 -0
- package/dist/internal/application/test-ssg.d.ts +8 -0
- package/dist/internal/application/test-ssg.d.ts.map +1 -0
- package/dist/internal/application/test-ssg.js +30 -0
- package/dist/internal/application/test-ssg.js.map +1 -0
- package/dist/internal/domain/config/entity/config.d.ts +8 -2
- package/dist/internal/domain/config/entity/config.d.ts.map +1 -1
- package/dist/internal/domain/config/entity/config.js +10 -3
- package/dist/internal/domain/config/entity/config.js.map +1 -1
- package/dist/internal/domain/config/entity/social.d.ts +66 -0
- package/dist/internal/domain/config/entity/social.d.ts.map +1 -0
- package/dist/internal/domain/config/entity/social.js +126 -0
- package/dist/internal/domain/config/entity/social.js.map +1 -0
- package/dist/internal/domain/config/factory/config.d.ts.map +1 -1
- package/dist/internal/domain/config/factory/config.js +2 -1
- package/dist/internal/domain/config/factory/config.js.map +1 -1
- package/dist/internal/domain/config/index.d.ts +2 -0
- package/dist/internal/domain/config/index.d.ts.map +1 -1
- package/dist/internal/domain/config/index.js +5 -1
- package/dist/internal/domain/config/index.js.map +1 -1
- package/dist/internal/domain/config/tests/entity-config.test.js +16 -12
- package/dist/internal/domain/config/tests/entity-config.test.js.map +1 -1
- package/dist/internal/domain/config/tests/social.test.d.ts +2 -0
- package/dist/internal/domain/config/tests/social.test.d.ts.map +1 -0
- package/dist/internal/domain/config/tests/social.test.js +164 -0
- package/dist/internal/domain/config/tests/social.test.js.map +1 -0
- package/dist/internal/domain/config/type.d.ts +22 -0
- package/dist/internal/domain/config/type.d.ts.map +1 -1
- package/dist/internal/domain/config/type.js.map +1 -1
- package/dist/internal/domain/config/vo/social.d.ts +23 -0
- package/dist/internal/domain/config/vo/social.d.ts.map +1 -0
- package/dist/internal/domain/config/vo/social.js +212 -0
- package/dist/internal/domain/config/vo/social.js.map +1 -0
- package/dist/internal/domain/content/entity/page.d.ts +6 -1
- package/dist/internal/domain/content/entity/page.d.ts.map +1 -1
- package/dist/internal/domain/content/entity/page.js +14 -2
- package/dist/internal/domain/content/entity/page.js.map +1 -1
- package/dist/internal/domain/content/entity/pagebuilder.d.ts +3 -2
- package/dist/internal/domain/content/entity/pagebuilder.d.ts.map +1 -1
- package/dist/internal/domain/content/entity/pagebuilder.js +16 -5
- package/dist/internal/domain/content/entity/pagebuilder.js.map +1 -1
- package/dist/internal/domain/content/entity/pagemeta.d.ts +10 -2
- package/dist/internal/domain/content/entity/pagemeta.d.ts.map +1 -1
- package/dist/internal/domain/content/entity/pagemeta.js +17 -1
- package/dist/internal/domain/content/entity/pagemeta.js.map +1 -1
- package/dist/internal/domain/content/entity/paginator.d.ts +6 -0
- package/dist/internal/domain/content/entity/paginator.d.ts.map +1 -1
- package/dist/internal/domain/content/entity/paginator.js +24 -2
- package/dist/internal/domain/content/entity/paginator.js.map +1 -1
- package/dist/internal/domain/content/factory/hub.d.ts.map +1 -1
- package/dist/internal/domain/content/factory/hub.js +2 -1
- package/dist/internal/domain/content/factory/hub.js.map +1 -1
- package/dist/internal/domain/content/type.d.ts +59 -1
- package/dist/internal/domain/content/type.d.ts.map +1 -1
- package/dist/internal/domain/content/type.js.map +1 -1
- package/dist/internal/domain/content/vo/frontmatter.d.ts +29 -1
- package/dist/internal/domain/content/vo/frontmatter.d.ts.map +1 -1
- package/dist/internal/domain/content/vo/frontmatter.js +242 -1
- package/dist/internal/domain/content/vo/frontmatter.js.map +1 -1
- package/dist/internal/domain/markdown/test/external-adapter.test.d.ts +2 -0
- package/dist/internal/domain/markdown/test/external-adapter.test.d.ts.map +1 -0
- package/dist/internal/domain/markdown/test/external-adapter.test.js +150 -0
- package/dist/internal/domain/markdown/test/external-adapter.test.js.map +1 -0
- package/dist/internal/domain/markdown/test/markdown.test.d.ts +2 -0
- package/dist/internal/domain/markdown/test/markdown.test.d.ts.map +1 -0
- package/dist/internal/domain/markdown/test/markdown.test.js +88 -0
- package/dist/internal/domain/markdown/test/markdown.test.js.map +1 -0
- package/dist/internal/domain/markdown/vo/externaladapter.d.ts +63 -0
- package/dist/internal/domain/markdown/vo/externaladapter.d.ts.map +1 -0
- package/dist/internal/domain/markdown/vo/externaladapter.js +194 -0
- package/dist/internal/domain/markdown/vo/externaladapter.js.map +1 -0
- package/dist/internal/domain/markdown/vo/markdownit.d.ts +33 -0
- package/dist/internal/domain/markdown/vo/markdownit.d.ts.map +1 -0
- package/dist/internal/domain/markdown/vo/markdownit.js +175 -0
- package/dist/internal/domain/markdown/vo/markdownit.js.map +1 -0
- package/dist/internal/domain/module/entity/module.d.ts.map +1 -1
- package/dist/internal/domain/module/entity/module.js +10 -6
- package/dist/internal/domain/module/entity/module.js.map +1 -1
- package/dist/internal/domain/module/vo/themes.test.js +12 -8
- package/dist/internal/domain/module/vo/themes.test.js.map +1 -1
- package/dist/internal/domain/site/entity/menu-builder.d.ts +35 -21
- package/dist/internal/domain/site/entity/menu-builder.d.ts.map +1 -1
- package/dist/internal/domain/site/entity/menu-builder.js +194 -107
- package/dist/internal/domain/site/entity/menu-builder.js.map +1 -1
- package/dist/internal/domain/site/entity/page.d.ts +19 -7
- package/dist/internal/domain/site/entity/page.d.ts.map +1 -1
- package/dist/internal/domain/site/entity/page.js +58 -3
- package/dist/internal/domain/site/entity/page.js.map +1 -1
- package/dist/internal/domain/site/entity/pager.d.ts +2 -0
- package/dist/internal/domain/site/entity/pager.d.ts.map +1 -1
- package/dist/internal/domain/site/entity/pager.js +9 -0
- package/dist/internal/domain/site/entity/pager.js.map +1 -1
- package/dist/internal/domain/site/entity/site.d.ts +14 -8
- package/dist/internal/domain/site/entity/site.d.ts.map +1 -1
- package/dist/internal/domain/site/entity/site.js +35 -8
- package/dist/internal/domain/site/entity/site.js.map +1 -1
- package/dist/internal/domain/site/factory/navigation-factory.d.ts +4 -2
- package/dist/internal/domain/site/factory/navigation-factory.d.ts.map +1 -1
- package/dist/internal/domain/site/factory/navigation-factory.js +5 -4
- package/dist/internal/domain/site/factory/navigation-factory.js.map +1 -1
- package/dist/internal/domain/site/factory/site.d.ts.map +1 -1
- package/dist/internal/domain/site/factory/site.js +7 -9
- package/dist/internal/domain/site/factory/site.js.map +1 -1
- package/dist/internal/domain/site/type.d.ts +11 -0
- package/dist/internal/domain/site/type.d.ts.map +1 -1
- package/dist/internal/domain/site/valueobject/author.d.ts +4 -4
- package/dist/internal/domain/site/valueobject/author.d.ts.map +1 -1
- package/dist/internal/domain/site/valueobject/author.js +7 -4
- package/dist/internal/domain/site/valueobject/author.js.map +1 -1
- package/dist/internal/domain/site/valueobject/menu.d.ts +27 -115
- package/dist/internal/domain/site/valueobject/menu.d.ts.map +1 -1
- package/dist/internal/domain/site/valueobject/menu.js +42 -193
- package/dist/internal/domain/site/valueobject/menu.js.map +1 -1
- package/dist/internal/domain/site/valueobject/organization.d.ts +63 -0
- package/dist/internal/domain/site/valueobject/organization.d.ts.map +1 -0
- package/dist/internal/domain/site/valueobject/organization.js +94 -0
- package/dist/internal/domain/site/valueobject/organization.js.map +1 -0
- package/dist/internal/domain/site/valueobject/organization.test.d.ts +2 -0
- package/dist/internal/domain/site/valueobject/organization.test.d.ts.map +1 -0
- package/dist/internal/domain/site/valueobject/organization.test.js +78 -0
- package/dist/internal/domain/site/valueobject/organization.test.js.map +1 -0
- package/dist/internal/domain/template/vo/registry.d.ts +18 -0
- package/dist/internal/domain/template/vo/registry.d.ts.map +1 -1
- package/dist/internal/domain/template/vo/registry.js +343 -0
- package/dist/internal/domain/template/vo/registry.js.map +1 -1
- package/dist/pkg/log/manager.js +1 -1
- package/dist/pkg/maps/scratch.d.ts +1 -0
- package/dist/pkg/maps/scratch.d.ts.map +1 -1
- package/dist/pkg/maps/scratch.js +4 -0
- package/dist/pkg/maps/scratch.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../../../internal/domain/template/vo/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,mBAAmB,EAAE,eAAe,EAAC,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAsFlD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IACtD,yBAAyB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACzF,oBAAoB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACtF;AAED;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,gBAAgB;IAE9D;;;OAGG;IACH,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAgCrD;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAQ/B;;;OAGG;IACH,yBAAyB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAgBxF;;;OAGG;IACH,oBAAoB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,mBAAmB,GAAG,IAAI;IAiBpF;;;OAGG;IACH,OAAO,CAAC,gCAAgC;IAOxC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAgL5B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,0BAA0B;IAWlC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAuDjC;;;OAGG;IACH,OAAO,CAAC,uBAAuB;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../../../internal/domain/template/vo/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,mBAAmB,EAAE,eAAe,EAAC,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAsFlD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IACtD,yBAAyB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACzF,oBAAoB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACtF;AAED;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,gBAAgB;IAE9D;;;OAGG;IACH,qBAAqB,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAgCrD;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAQ/B;;;OAGG;IACH,yBAAyB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAgBxF;;;OAGG;IACH,oBAAoB,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,mBAAmB,GAAG,IAAI;IAiBpF;;;OAGG;IACH,OAAO,CAAC,gCAAgC;IAOxC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAgL5B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,0BAA0B;IAWlC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAuDjC;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAuX/B;;OAEG;IACH,OAAO,CAAC,UAAU;IASlB;;OAEG;IACH,OAAO,CAAC,YAAY;IAgCpB;;OAEG;IACH,OAAO,CAAC,YAAY;IAoGpB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAmO7B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAgM7B;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IA2ZnC;;;OAGG;IACH,OAAO,CAAC,OAAO;IAuCf;;OAEG;IACH,OAAO,CAAC,UAAU;IAelB;;OAEG;IACH,OAAO,CAAC,WAAW;IAenB;;OAEG;IACH,OAAO,CAAC,WAAW;IAkCnB;;OAEG;IACH,OAAO,CAAC,SAAS;IAoBjB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAmEtB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkC9B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA+I7B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAiI5B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAgDhC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAqH7B;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IA4ElC;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAuCjB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAmCxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAiCpB;;;OAGG;IACH,OAAO,CAAC,SAAS;IA0CjB;;;OAGG;IACH,OAAO,CAAC,SAAS;IA4DjB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAuChB;;OAEG;IACH,OAAO,CAAC,YAAY;IAgCpB;;;OAGG;IACH,OAAO,CAAC,cAAc;CAwDvB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,gBAAgB,CAEtD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,eAAe,CAKnD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,eAAe,CAKpF;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAQ3E"}
|
|
@@ -629,6 +629,265 @@ class DefaultTemplateRegistry {
|
|
|
629
629
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
630
630
|
}
|
|
631
631
|
}));
|
|
632
|
+
// Substr extracts parts of a string, beginning at the character at the specified
|
|
633
|
+
// position, and returns the specified number of characters.
|
|
634
|
+
// It normally takes two parameters: start and length.
|
|
635
|
+
// It can also take one parameter: start, i.e. length is omitted, in which case
|
|
636
|
+
// the substring starting from start until the end of the string will be returned.
|
|
637
|
+
// To extract characters from the end of the string, use a negative start number.
|
|
638
|
+
// If length is given and is negative, then that many characters will be omitted from
|
|
639
|
+
// the end of string.
|
|
640
|
+
// Equivalent to Go's Substr function in strings namespace
|
|
641
|
+
// Usage: {{ substr "hello world" 0 5 }} -> "hello"
|
|
642
|
+
// Usage: {{ substr "hello world" 6 }} -> "world"
|
|
643
|
+
// Usage: {{ substr "hello world" -5 }} -> "world"
|
|
644
|
+
// Usage: {{ substr "hello world" 0 -6 }} -> "hello"
|
|
645
|
+
funcMap.set('substr', (a, ...nums) => {
|
|
646
|
+
// Convert input to string
|
|
647
|
+
const s = String(a);
|
|
648
|
+
// Convert string to array of characters to handle Unicode properly
|
|
649
|
+
const chars = Array.from(s);
|
|
650
|
+
const rlen = chars.length;
|
|
651
|
+
let start;
|
|
652
|
+
let length;
|
|
653
|
+
// Parse arguments
|
|
654
|
+
switch (nums.length) {
|
|
655
|
+
case 0:
|
|
656
|
+
throw new Error('too few arguments');
|
|
657
|
+
case 1:
|
|
658
|
+
// Only start parameter provided
|
|
659
|
+
const startNum = Number(nums[0]);
|
|
660
|
+
if (isNaN(startNum) || !Number.isInteger(startNum)) {
|
|
661
|
+
throw new Error('start argument must be an integer');
|
|
662
|
+
}
|
|
663
|
+
start = startNum;
|
|
664
|
+
length = rlen;
|
|
665
|
+
break;
|
|
666
|
+
case 2:
|
|
667
|
+
// Both start and length parameters provided
|
|
668
|
+
const startNum2 = Number(nums[0]);
|
|
669
|
+
const lengthNum = Number(nums[1]);
|
|
670
|
+
if (isNaN(startNum2) || !Number.isInteger(startNum2)) {
|
|
671
|
+
throw new Error('start argument must be an integer');
|
|
672
|
+
}
|
|
673
|
+
if (isNaN(lengthNum) || !Number.isInteger(lengthNum)) {
|
|
674
|
+
throw new Error('length argument must be an integer');
|
|
675
|
+
}
|
|
676
|
+
start = startNum2;
|
|
677
|
+
length = lengthNum;
|
|
678
|
+
break;
|
|
679
|
+
default:
|
|
680
|
+
throw new Error('too many arguments');
|
|
681
|
+
}
|
|
682
|
+
// Handle empty string
|
|
683
|
+
if (rlen === 0) {
|
|
684
|
+
return '';
|
|
685
|
+
}
|
|
686
|
+
// Handle negative start (count from end)
|
|
687
|
+
if (start < 0) {
|
|
688
|
+
start += rlen;
|
|
689
|
+
}
|
|
690
|
+
// If start was originally negative beyond rlen, set to 0
|
|
691
|
+
if (start < 0) {
|
|
692
|
+
start = 0;
|
|
693
|
+
}
|
|
694
|
+
// If start is beyond the string length, return empty
|
|
695
|
+
if (start > rlen - 1) {
|
|
696
|
+
return '';
|
|
697
|
+
}
|
|
698
|
+
let end = rlen;
|
|
699
|
+
// Handle different length cases
|
|
700
|
+
if (length === 0) {
|
|
701
|
+
return '';
|
|
702
|
+
}
|
|
703
|
+
else if (length < 0) {
|
|
704
|
+
// Negative length: omit that many characters from end
|
|
705
|
+
end += length;
|
|
706
|
+
}
|
|
707
|
+
else if (length > 0) {
|
|
708
|
+
// Positive length: take that many characters from start
|
|
709
|
+
end = start + length;
|
|
710
|
+
}
|
|
711
|
+
// Validate bounds
|
|
712
|
+
if (start >= end) {
|
|
713
|
+
return '';
|
|
714
|
+
}
|
|
715
|
+
if (end < 0) {
|
|
716
|
+
return '';
|
|
717
|
+
}
|
|
718
|
+
// Clamp end to string length
|
|
719
|
+
if (end > rlen) {
|
|
720
|
+
end = rlen;
|
|
721
|
+
}
|
|
722
|
+
// Extract substring
|
|
723
|
+
return chars.slice(start, end).join('');
|
|
724
|
+
});
|
|
725
|
+
// Truncate truncates the string to the specified length
|
|
726
|
+
// Equivalent to Go's Truncate function in strings namespace
|
|
727
|
+
// Usage: {{ truncate 10 "hello world" }} -> "hello …"
|
|
728
|
+
// Usage: {{ truncate 10 "..." "hello world" }} -> "hello..."
|
|
729
|
+
// Supports HTML-aware truncation
|
|
730
|
+
funcMap.set('truncate', (lengthParam, ...options) => {
|
|
731
|
+
// Parse length parameter
|
|
732
|
+
const length = Number(lengthParam);
|
|
733
|
+
if (isNaN(length) || !Number.isInteger(length)) {
|
|
734
|
+
throw new Error('truncate length must be an integer');
|
|
735
|
+
}
|
|
736
|
+
let textParam;
|
|
737
|
+
let ellipsis = ' …';
|
|
738
|
+
// Parse options
|
|
739
|
+
switch (options.length) {
|
|
740
|
+
case 0:
|
|
741
|
+
throw new Error('truncate requires a length and a string');
|
|
742
|
+
case 1:
|
|
743
|
+
textParam = options[0];
|
|
744
|
+
ellipsis = ' …';
|
|
745
|
+
break;
|
|
746
|
+
case 2:
|
|
747
|
+
ellipsis = String(options[0]);
|
|
748
|
+
textParam = options[1];
|
|
749
|
+
break;
|
|
750
|
+
default:
|
|
751
|
+
throw new Error('too many arguments passed to truncate');
|
|
752
|
+
}
|
|
753
|
+
// Convert text to string
|
|
754
|
+
const text = String(textParam);
|
|
755
|
+
// Check if input is HTML (simple heuristic: contains HTML tags)
|
|
756
|
+
const isHTML = /<[^>]+>/.test(text);
|
|
757
|
+
// Convert to array of characters for proper Unicode handling
|
|
758
|
+
const chars = Array.from(text);
|
|
759
|
+
// If text is already shorter than or equal to length, return as-is
|
|
760
|
+
if (chars.length <= length) {
|
|
761
|
+
if (isHTML) {
|
|
762
|
+
return text; // Return as HTML
|
|
763
|
+
}
|
|
764
|
+
return this.escapeHTML(text);
|
|
765
|
+
}
|
|
766
|
+
if (isHTML) {
|
|
767
|
+
return this.truncateHTML(text, length, ellipsis);
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
return this.truncateText(text, length, ellipsis);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* HTML escape utility function
|
|
776
|
+
*/
|
|
777
|
+
escapeHTML(text) {
|
|
778
|
+
return text
|
|
779
|
+
.replace(/&/g, '&')
|
|
780
|
+
.replace(/</g, '<')
|
|
781
|
+
.replace(/>/g, '>')
|
|
782
|
+
.replace(/"/g, '"')
|
|
783
|
+
.replace(/'/g, ''');
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Truncate plain text, breaking at word boundaries when possible
|
|
787
|
+
*/
|
|
788
|
+
truncateText(text, length, ellipsis) {
|
|
789
|
+
const chars = Array.from(text);
|
|
790
|
+
if (chars.length <= length) {
|
|
791
|
+
return this.escapeHTML(text);
|
|
792
|
+
}
|
|
793
|
+
let lastWordIndex = 0;
|
|
794
|
+
let currentLen = 0;
|
|
795
|
+
// Find the best truncation point (word boundary)
|
|
796
|
+
for (let i = 0; i < chars.length && currentLen < length; i++) {
|
|
797
|
+
const char = chars[i];
|
|
798
|
+
currentLen++;
|
|
799
|
+
if (/\s/.test(char)) {
|
|
800
|
+
lastWordIndex = i;
|
|
801
|
+
}
|
|
802
|
+
// For CJK characters, each character can be a word boundary
|
|
803
|
+
if (/[\u4e00-\u9fff\u3400-\u4dbf\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(char)) {
|
|
804
|
+
lastWordIndex = i;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// Determine truncation point
|
|
808
|
+
let endPos = currentLen >= length ? (lastWordIndex > 0 ? lastWordIndex : length) : chars.length;
|
|
809
|
+
const truncated = chars.slice(0, endPos).join('');
|
|
810
|
+
return this.escapeHTML(truncated) + ellipsis;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Truncate HTML text while preserving tag structure
|
|
814
|
+
*/
|
|
815
|
+
truncateHTML(text, length, ellipsis) {
|
|
816
|
+
const tagRegex = /^<(\/)?([^ ]+?)(?:(\s*\/)| .*?)?>/;
|
|
817
|
+
const htmlSinglets = new Set(['br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input']);
|
|
818
|
+
const tags = [];
|
|
819
|
+
let lastWordIndex = 0;
|
|
820
|
+
let lastNonSpace = 0;
|
|
821
|
+
let currentLen = 0;
|
|
822
|
+
let endTextPos = 0;
|
|
823
|
+
let i = 0;
|
|
824
|
+
const chars = Array.from(text);
|
|
825
|
+
while (i < chars.length) {
|
|
826
|
+
const currentChar = chars[i];
|
|
827
|
+
// Check for HTML tag
|
|
828
|
+
if (currentChar === '<') {
|
|
829
|
+
const remainingText = chars.slice(i).join('');
|
|
830
|
+
const match = remainingText.match(tagRegex);
|
|
831
|
+
if (match) {
|
|
832
|
+
const fullMatch = match[0];
|
|
833
|
+
const isClosing = match[1] === '/';
|
|
834
|
+
const tagName = match[2].toLowerCase();
|
|
835
|
+
const isSelfClosing = match[3] === '/';
|
|
836
|
+
// Skip the entire tag
|
|
837
|
+
i += fullMatch.length;
|
|
838
|
+
lastWordIndex = lastNonSpace;
|
|
839
|
+
// Track non-singlet tags for proper closing
|
|
840
|
+
const isSinglet = htmlSinglets.has(tagName);
|
|
841
|
+
if (!isSinglet && !isSelfClosing) {
|
|
842
|
+
tags.push({
|
|
843
|
+
name: tagName,
|
|
844
|
+
pos: i - fullMatch.length,
|
|
845
|
+
openTag: !isClosing
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
// Count actual text characters
|
|
852
|
+
currentLen++;
|
|
853
|
+
if (/\s/.test(currentChar)) {
|
|
854
|
+
lastWordIndex = lastNonSpace;
|
|
855
|
+
}
|
|
856
|
+
else if (/[\u4e00-\u9fff\u3400-\u4dbf\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(currentChar)) {
|
|
857
|
+
// CJK characters can break at any character
|
|
858
|
+
lastWordIndex = i;
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
lastNonSpace = i + 1;
|
|
862
|
+
}
|
|
863
|
+
if (currentLen > length) {
|
|
864
|
+
endTextPos = lastWordIndex > 0 ? lastWordIndex : i;
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
i++;
|
|
868
|
+
}
|
|
869
|
+
if (currentLen <= length) {
|
|
870
|
+
return text; // No truncation needed
|
|
871
|
+
}
|
|
872
|
+
let output = chars.slice(0, endTextPos).join('') + ellipsis;
|
|
873
|
+
// Close any open HTML tags
|
|
874
|
+
let currentTag = null;
|
|
875
|
+
for (let j = tags.length - 1; j >= 0; j--) {
|
|
876
|
+
const tag = tags[j];
|
|
877
|
+
if (tag.pos >= endTextPos || currentTag !== null) {
|
|
878
|
+
if (currentTag !== null && currentTag.name === tag.name) {
|
|
879
|
+
currentTag = null;
|
|
880
|
+
}
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (tag.openTag) {
|
|
884
|
+
output += `</${tag.name}>`;
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
currentTag = tag;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return output;
|
|
632
891
|
}
|
|
633
892
|
/**
|
|
634
893
|
* Register math functions
|
|
@@ -1355,6 +1614,42 @@ class DefaultTemplateRegistry {
|
|
|
1355
1614
|
.join('&');
|
|
1356
1615
|
return queryString;
|
|
1357
1616
|
});
|
|
1617
|
+
// IsSet returns whether a given array, slice, or map has the given key defined
|
|
1618
|
+
// Equivalent to Go's IsSet function in collections namespace
|
|
1619
|
+
// Usage: {{ isset .array 0 }} or {{ isset .object "key" }}
|
|
1620
|
+
funcMap.set('isset', (c, key) => {
|
|
1621
|
+
if (c === null || c === undefined) {
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
// Handle arrays and slices
|
|
1625
|
+
if (Array.isArray(c)) {
|
|
1626
|
+
// Convert key to integer for array indexing
|
|
1627
|
+
const k = Number(key);
|
|
1628
|
+
if (isNaN(k) || !Number.isInteger(k)) {
|
|
1629
|
+
console.warn(`isset unable to use key of type ${typeof key} as index`);
|
|
1630
|
+
return false;
|
|
1631
|
+
}
|
|
1632
|
+
// Check if index is valid (within bounds)
|
|
1633
|
+
return c.length > k && k >= 0;
|
|
1634
|
+
}
|
|
1635
|
+
// Handle objects/maps
|
|
1636
|
+
if (typeof c === 'object') {
|
|
1637
|
+
// For objects, check if the key exists as a property
|
|
1638
|
+
return key in c;
|
|
1639
|
+
}
|
|
1640
|
+
// Handle strings (similar to arrays, but for character indexing)
|
|
1641
|
+
if (typeof c === 'string') {
|
|
1642
|
+
const k = Number(key);
|
|
1643
|
+
if (isNaN(k) || !Number.isInteger(k)) {
|
|
1644
|
+
console.warn(`isset unable to use key of type ${typeof key} as index`);
|
|
1645
|
+
return false;
|
|
1646
|
+
}
|
|
1647
|
+
return c.length > k && k >= 0;
|
|
1648
|
+
}
|
|
1649
|
+
// Unsupported types
|
|
1650
|
+
console.warn(`calling isset with unsupported type "${typeof c}" (${c.constructor?.name || typeof c}) will always return false.`);
|
|
1651
|
+
return false;
|
|
1652
|
+
});
|
|
1358
1653
|
}
|
|
1359
1654
|
/**
|
|
1360
1655
|
* Core indexing implementation following Go's doIndex logic
|
|
@@ -2023,6 +2318,14 @@ class DefaultTemplateRegistry {
|
|
|
2023
2318
|
return null;
|
|
2024
2319
|
}
|
|
2025
2320
|
return this.unmarshalContent(dataStr, options);
|
|
2321
|
+
},
|
|
2322
|
+
// Plainify returns a copy of s with all HTML tags removed
|
|
2323
|
+
// Equivalent to Go's Plainify function in transform namespace
|
|
2324
|
+
// Usage: {{ "Hello <strong>world</strong>!" | transform.Plainify }} -> "Hello world!"
|
|
2325
|
+
// Usage: {{ transform.Plainify "<p>Text with <br> tags</p>" }} -> "Text with tags"
|
|
2326
|
+
Plainify: (s) => {
|
|
2327
|
+
const str = String(s);
|
|
2328
|
+
return this.stripHTML(str);
|
|
2026
2329
|
}
|
|
2027
2330
|
}));
|
|
2028
2331
|
// Also register unmarshal as a standalone function for backward compatibility
|
|
@@ -2030,6 +2333,46 @@ class DefaultTemplateRegistry {
|
|
|
2030
2333
|
const transform = funcMap.get('transform')();
|
|
2031
2334
|
return transform.Unmarshal(...args);
|
|
2032
2335
|
});
|
|
2336
|
+
// Register plainify as a standalone function for convenience
|
|
2337
|
+
funcMap.set('plainify', (s) => {
|
|
2338
|
+
const str = String(s);
|
|
2339
|
+
return this.stripHTML(str);
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
/**
|
|
2343
|
+
* Strip HTML tags from a string
|
|
2344
|
+
* Equivalent to Go's helpers.StripHTML function
|
|
2345
|
+
* Preserves line breaks from block elements and normalizes whitespace
|
|
2346
|
+
*/
|
|
2347
|
+
stripHTML(html) {
|
|
2348
|
+
// Shortcut for strings with no HTML tags
|
|
2349
|
+
if (!html.includes('<') && !html.includes('>')) {
|
|
2350
|
+
return html;
|
|
2351
|
+
}
|
|
2352
|
+
// First pass: Replace block elements that should create line breaks
|
|
2353
|
+
let result = html
|
|
2354
|
+
.replace(/\n/g, ' ') // Convert newlines to spaces first
|
|
2355
|
+
.replace(/<\/p>/gi, '\n') // </p> creates line break
|
|
2356
|
+
.replace(/<br\s*\/?>/gi, '\n') // <br> and <br/> create line breaks
|
|
2357
|
+
.replace(/<\/div>/gi, '\n') // </div> creates line break
|
|
2358
|
+
.replace(/<\/h[1-6]>/gi, '\n') // Heading end tags create line breaks
|
|
2359
|
+
.replace(/<\/li>/gi, '\n') // List items create line breaks
|
|
2360
|
+
.replace(/<\/tr>/gi, '\n') // Table rows create line breaks
|
|
2361
|
+
.replace(/<\/blockquote>/gi, '\n'); // Blockquotes create line breaks
|
|
2362
|
+
// Second pass: Remove all HTML tags
|
|
2363
|
+
result = result.replace(/<[^>]*>/g, '');
|
|
2364
|
+
// Third pass: Normalize whitespace
|
|
2365
|
+
// Convert multiple spaces to single spaces, but preserve intentional line breaks
|
|
2366
|
+
const lines = result.split('\n');
|
|
2367
|
+
const normalizedLines = lines.map(line => line.trim().replace(/\s+/g, ' '));
|
|
2368
|
+
// Join lines back together, removing empty lines but preserving intentional breaks
|
|
2369
|
+
result = normalizedLines
|
|
2370
|
+
.filter(line => line.length > 0)
|
|
2371
|
+
.join('\n')
|
|
2372
|
+
.trim();
|
|
2373
|
+
// Final cleanup: ensure we don't have multiple consecutive newlines
|
|
2374
|
+
result = result.replace(/\n\s*\n/g, '\n');
|
|
2375
|
+
return result;
|
|
2033
2376
|
}
|
|
2034
2377
|
/**
|
|
2035
2378
|
* Unmarshal content from string based on format
|