Skip to content

Commit

Permalink
feat(helper/toc): specify maximum number of items to output (hexojs#5487
Browse files Browse the repository at this point in the history
)

* 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]>
  • Loading branch information
3 people authored Jul 1, 2024
1 parent a3f27b8 commit c321bb6
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 1 deletion.
27 changes: 26 additions & 1 deletion lib/plugins/helper/toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { tocObj, escapeHTML, encodeURL } from 'hexo-util';
interface Options {
min_depth?: number;
max_depth?: number;
max_items?: number;
class?: string;
class_item?: string;
class_link?: string;
Expand All @@ -17,6 +18,7 @@ function tocHelper(str: string, options: Options = {}) {
options = Object.assign({
min_depth: 1,
max_depth: 6,
max_items: Infinity,
class: 'toc',
class_item: '',
class_link: '',
Expand All @@ -27,7 +29,7 @@ function tocHelper(str: string, options: Options = {}) {
list_number: true
}, options);

const data = tocObj(str, { min_depth: options.min_depth, max_depth: options.max_depth });
const data = getAndTruncateTocObj(str, { min_depth: options.min_depth, max_depth: options.max_depth }, options.max_items);

if (!data.length) return '';

Expand Down Expand Up @@ -102,4 +104,27 @@ function tocHelper(str: string, options: Options = {}) {
return result;
}

function getAndTruncateTocObj(str: string, options: {min_depth: number, max_depth: number}, max_items: number) {
let data = tocObj(str, { min_depth: options.min_depth, max_depth: options.max_depth });

if (data.length === 0) {
return data;
}
if (max_items < 1 || max_items === Infinity) {
return data;
}

const levels = data.map(item => item.level);
const min = Math.min(...levels);
const max = Math.max(...levels);

for (let currentLevel = max; data.length > max_items && currentLevel > min; currentLevel--) {
data = data.filter(item => item.level < currentLevel);
}

data = data.slice(0, max_items);

return data;
}

export = tocHelper;
136 changes: 136 additions & 0 deletions test/scripts/helpers/toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,4 +552,140 @@ describe('toc', () => {

toc(html, { class: 'foo', class_child: 'bar' }).should.eql(expected);
});

it('max_items - result contains only h1 items', () => {
const className = 'toc';
const expected = [
'<ol class="' + className + '">',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_1">',
'<span class="' + className + '-number">1.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 1</span>',
'</a>',
// '<ol class="' + className + '-child">',
// <!-- h2 is truncated -->
// '</ol>',
'</li>',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_2">',
'<span class="' + className + '-number">2.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 2</span>',
'</a>',
// '<ol class="' + className + '-child">',
// <!-- h2 is truncated -->
// '</ol>',
'</li>',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_3">',
'<span class="' + className + '-number">3.</span> ', // list_number enabled
'<span class="' + className + '-text">Title should escape &amp;, &lt;, &#39;, and &quot;</span>',
'</a>',
'</li>',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_4">',
'<span class="' + className + '-number">4.</span> ', // list_number enabled
'<span class="' + className + '-text">Chapter 1 should be printed to toc</span>',
'</a>',
'</li>',
'</ol>'
].join('');

toc(html, { max_items: 4}).should.eql(expected); // The number of `h1` is 4
toc(html, { max_items: 7}).should.eql(expected); // Maximum number 7 cannot display up to `h2`
});

it('max_items - result contains h1 and h2 items', () => {
const className = 'toc';
const expected = [
'<ol class="' + className + '">',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_1">',
'<span class="' + className + '-number">1.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 1</span>',
'</a>',
'<ol class="' + className + '-child">',
'<li class="' + className + '-item ' + className + '-level-2">',
'<a class="' + className + '-link" href="#title_1_1">',
'<span class="' + className + '-number">1.1.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 1.1</span>',
'</a>',
// '<ol class="' + className + '-child">',
// <!-- h3 is truncated -->
// '</ol>',
'</li>',
'<li class="' + className + '-item ' + className + '-level-2">',
'<a class="' + className + '-link" href="#title_1_2">',
'<span class="' + className + '-number">1.2.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 1.2</span>',
'</a>',
'</li>',
'<li class="' + className + '-item ' + className + '-level-2">',
'<a class="' + className + '-link" href="#title_1_3">',
'<span class="' + className + '-number">1.3.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 1.3</span>',
'</a>',
// '<ol class="' + className + '-child">',
// <!-- h3 is truncated -->
// '</ol>',
'</li>',
'</ol>',
'</li>',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_2">',
'<span class="' + className + '-number">2.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 2</span>',
'</a>',
'<ol class="' + className + '-child">',
'<li class="' + className + '-item ' + className + '-level-2">',
'<a class="' + className + '-link" href="#title_2_1">',
'<span class="' + className + '-number">2.1.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 2.1</span>',
'</a>',
'</li>',
'</ol>',
'</li>',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_3">',
'<span class="' + className + '-number">3.</span> ', // list_number enabled
'<span class="' + className + '-text">Title should escape &amp;, &lt;, &#39;, and &quot;</span>',
'</a>',
'</li>',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_4">',
'<span class="' + className + '-number">4.</span> ', // list_number enabled
'<span class="' + className + '-text">Chapter 1 should be printed to toc</span>',
'</a>',
'</li>',
'</ol>'
].join('');

toc(html, { max_items: 8}).should.eql(expected); // Maximum number 8 can display up to `h2`
toc(html, { max_items: 9}).should.eql(expected); // Maximum number 10 is required to display up to `h3`
});

it('max_items - result of h1 was truncated', () => {
const className = 'toc';
const expected = [
'<ol class="' + className + '">',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_1">',
'<span class="' + className + '-number">1.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 1</span>',
'</a>',
// '<ol class="' + className + '-child">',
// <!-- h2 is truncated -->
// '</ol>',
'</li>',
'<li class="' + className + '-item ' + className + '-level-1">',
'<a class="' + className + '-link" href="#title_2">',
'<span class="' + className + '-number">2.</span> ', // list_number enabled
'<span class="' + className + '-text">Title 2</span>',
'</a>',
'</li>',
// <!-- `h1` is truncated from the end -->
'</ol>'
].join('');

toc(html, { max_items: 2}).should.eql(expected); // `h1` is truncated from the end
});
});

0 comments on commit c321bb6

Please sign in to comment.