Skip to content

Commit c321bb6

Browse files
KentarouTakedauioleeyoshinorin
authored
feat(helper/toc): specify maximum number of items to output (#5487)
* feat(helper/toc): specify maximum number of items to output * perf: skip evaluation if `max_items` is not specified * perf: streamline getting min and max heading level --------- Co-authored-by: Uiolee <[email protected]> Co-authored-by: yoshinorin <[email protected]>
1 parent a3f27b8 commit c321bb6

File tree

2 files changed

+162
-1
lines changed

2 files changed

+162
-1
lines changed

lib/plugins/helper/toc.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { tocObj, escapeHTML, encodeURL } from 'hexo-util';
33
interface Options {
44
min_depth?: number;
55
max_depth?: number;
6+
max_items?: number;
67
class?: string;
78
class_item?: string;
89
class_link?: string;
@@ -17,6 +18,7 @@ function tocHelper(str: string, options: Options = {}) {
1718
options = Object.assign({
1819
min_depth: 1,
1920
max_depth: 6,
21+
max_items: Infinity,
2022
class: 'toc',
2123
class_item: '',
2224
class_link: '',
@@ -27,7 +29,7 @@ function tocHelper(str: string, options: Options = {}) {
2729
list_number: true
2830
}, options);
2931

30-
const data = tocObj(str, { min_depth: options.min_depth, max_depth: options.max_depth });
32+
const data = getAndTruncateTocObj(str, { min_depth: options.min_depth, max_depth: options.max_depth }, options.max_items);
3133

3234
if (!data.length) return '';
3335

@@ -102,4 +104,27 @@ function tocHelper(str: string, options: Options = {}) {
102104
return result;
103105
}
104106

107+
function getAndTruncateTocObj(str: string, options: {min_depth: number, max_depth: number}, max_items: number) {
108+
let data = tocObj(str, { min_depth: options.min_depth, max_depth: options.max_depth });
109+
110+
if (data.length === 0) {
111+
return data;
112+
}
113+
if (max_items < 1 || max_items === Infinity) {
114+
return data;
115+
}
116+
117+
const levels = data.map(item => item.level);
118+
const min = Math.min(...levels);
119+
const max = Math.max(...levels);
120+
121+
for (let currentLevel = max; data.length > max_items && currentLevel > min; currentLevel--) {
122+
data = data.filter(item => item.level < currentLevel);
123+
}
124+
125+
data = data.slice(0, max_items);
126+
127+
return data;
128+
}
129+
105130
export = tocHelper;

test/scripts/helpers/toc.ts

+136
Original file line numberDiff line numberDiff line change
@@ -552,4 +552,140 @@ describe('toc', () => {
552552

553553
toc(html, { class: 'foo', class_child: 'bar' }).should.eql(expected);
554554
});
555+
556+
it('max_items - result contains only h1 items', () => {
557+
const className = 'toc';
558+
const expected = [
559+
'<ol class="' + className + '">',
560+
'<li class="' + className + '-item ' + className + '-level-1">',
561+
'<a class="' + className + '-link" href="#title_1">',
562+
'<span class="' + className + '-number">1.</span> ', // list_number enabled
563+
'<span class="' + className + '-text">Title 1</span>',
564+
'</a>',
565+
// '<ol class="' + className + '-child">',
566+
// <!-- h2 is truncated -->
567+
// '</ol>',
568+
'</li>',
569+
'<li class="' + className + '-item ' + className + '-level-1">',
570+
'<a class="' + className + '-link" href="#title_2">',
571+
'<span class="' + className + '-number">2.</span> ', // list_number enabled
572+
'<span class="' + className + '-text">Title 2</span>',
573+
'</a>',
574+
// '<ol class="' + className + '-child">',
575+
// <!-- h2 is truncated -->
576+
// '</ol>',
577+
'</li>',
578+
'<li class="' + className + '-item ' + className + '-level-1">',
579+
'<a class="' + className + '-link" href="#title_3">',
580+
'<span class="' + className + '-number">3.</span> ', // list_number enabled
581+
'<span class="' + className + '-text">Title should escape &amp;, &lt;, &#39;, and &quot;</span>',
582+
'</a>',
583+
'</li>',
584+
'<li class="' + className + '-item ' + className + '-level-1">',
585+
'<a class="' + className + '-link" href="#title_4">',
586+
'<span class="' + className + '-number">4.</span> ', // list_number enabled
587+
'<span class="' + className + '-text">Chapter 1 should be printed to toc</span>',
588+
'</a>',
589+
'</li>',
590+
'</ol>'
591+
].join('');
592+
593+
toc(html, { max_items: 4}).should.eql(expected); // The number of `h1` is 4
594+
toc(html, { max_items: 7}).should.eql(expected); // Maximum number 7 cannot display up to `h2`
595+
});
596+
597+
it('max_items - result contains h1 and h2 items', () => {
598+
const className = 'toc';
599+
const expected = [
600+
'<ol class="' + className + '">',
601+
'<li class="' + className + '-item ' + className + '-level-1">',
602+
'<a class="' + className + '-link" href="#title_1">',
603+
'<span class="' + className + '-number">1.</span> ', // list_number enabled
604+
'<span class="' + className + '-text">Title 1</span>',
605+
'</a>',
606+
'<ol class="' + className + '-child">',
607+
'<li class="' + className + '-item ' + className + '-level-2">',
608+
'<a class="' + className + '-link" href="#title_1_1">',
609+
'<span class="' + className + '-number">1.1.</span> ', // list_number enabled
610+
'<span class="' + className + '-text">Title 1.1</span>',
611+
'</a>',
612+
// '<ol class="' + className + '-child">',
613+
// <!-- h3 is truncated -->
614+
// '</ol>',
615+
'</li>',
616+
'<li class="' + className + '-item ' + className + '-level-2">',
617+
'<a class="' + className + '-link" href="#title_1_2">',
618+
'<span class="' + className + '-number">1.2.</span> ', // list_number enabled
619+
'<span class="' + className + '-text">Title 1.2</span>',
620+
'</a>',
621+
'</li>',
622+
'<li class="' + className + '-item ' + className + '-level-2">',
623+
'<a class="' + className + '-link" href="#title_1_3">',
624+
'<span class="' + className + '-number">1.3.</span> ', // list_number enabled
625+
'<span class="' + className + '-text">Title 1.3</span>',
626+
'</a>',
627+
// '<ol class="' + className + '-child">',
628+
// <!-- h3 is truncated -->
629+
// '</ol>',
630+
'</li>',
631+
'</ol>',
632+
'</li>',
633+
'<li class="' + className + '-item ' + className + '-level-1">',
634+
'<a class="' + className + '-link" href="#title_2">',
635+
'<span class="' + className + '-number">2.</span> ', // list_number enabled
636+
'<span class="' + className + '-text">Title 2</span>',
637+
'</a>',
638+
'<ol class="' + className + '-child">',
639+
'<li class="' + className + '-item ' + className + '-level-2">',
640+
'<a class="' + className + '-link" href="#title_2_1">',
641+
'<span class="' + className + '-number">2.1.</span> ', // list_number enabled
642+
'<span class="' + className + '-text">Title 2.1</span>',
643+
'</a>',
644+
'</li>',
645+
'</ol>',
646+
'</li>',
647+
'<li class="' + className + '-item ' + className + '-level-1">',
648+
'<a class="' + className + '-link" href="#title_3">',
649+
'<span class="' + className + '-number">3.</span> ', // list_number enabled
650+
'<span class="' + className + '-text">Title should escape &amp;, &lt;, &#39;, and &quot;</span>',
651+
'</a>',
652+
'</li>',
653+
'<li class="' + className + '-item ' + className + '-level-1">',
654+
'<a class="' + className + '-link" href="#title_4">',
655+
'<span class="' + className + '-number">4.</span> ', // list_number enabled
656+
'<span class="' + className + '-text">Chapter 1 should be printed to toc</span>',
657+
'</a>',
658+
'</li>',
659+
'</ol>'
660+
].join('');
661+
662+
toc(html, { max_items: 8}).should.eql(expected); // Maximum number 8 can display up to `h2`
663+
toc(html, { max_items: 9}).should.eql(expected); // Maximum number 10 is required to display up to `h3`
664+
});
665+
666+
it('max_items - result of h1 was truncated', () => {
667+
const className = 'toc';
668+
const expected = [
669+
'<ol class="' + className + '">',
670+
'<li class="' + className + '-item ' + className + '-level-1">',
671+
'<a class="' + className + '-link" href="#title_1">',
672+
'<span class="' + className + '-number">1.</span> ', // list_number enabled
673+
'<span class="' + className + '-text">Title 1</span>',
674+
'</a>',
675+
// '<ol class="' + className + '-child">',
676+
// <!-- h2 is truncated -->
677+
// '</ol>',
678+
'</li>',
679+
'<li class="' + className + '-item ' + className + '-level-1">',
680+
'<a class="' + className + '-link" href="#title_2">',
681+
'<span class="' + className + '-number">2.</span> ', // list_number enabled
682+
'<span class="' + className + '-text">Title 2</span>',
683+
'</a>',
684+
'</li>',
685+
// <!-- `h1` is truncated from the end -->
686+
'</ol>'
687+
].join('');
688+
689+
toc(html, { max_items: 2}).should.eql(expected); // `h1` is truncated from the end
690+
});
555691
});

0 commit comments

Comments
 (0)