From ad882ac2de64a30d79d98d7588485d1d800422d3 Mon Sep 17 00:00:00 2001 From: KentarouTakeda Date: Mon, 1 Jul 2024 23:31:26 +0900 Subject: [PATCH] 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 <22849383+uiolee@users.noreply.github.com> Co-authored-by: yoshinorin --- lib/plugins/helper/toc.ts | 27 ++++++- test/scripts/helpers/toc.ts | 136 ++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/lib/plugins/helper/toc.ts b/lib/plugins/helper/toc.ts index 02a4b1ad7d..f933e177e8 100644 --- a/lib/plugins/helper/toc.ts +++ b/lib/plugins/helper/toc.ts @@ -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; @@ -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: '', @@ -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 ''; @@ -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; diff --git a/test/scripts/helpers/toc.ts b/test/scripts/helpers/toc.ts index 75b479b2ec..2070f1b6c9 100644 --- a/test/scripts/helpers/toc.ts +++ b/test/scripts/helpers/toc.ts @@ -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 = [ + '
    ', + '
  1. ', + '', + '1. ', // list_number enabled + 'Title 1', + '', + // '
      ', + // + // '
    ', + '
  2. ', + '
  3. ', + '', + '2. ', // list_number enabled + 'Title 2', + '', + // '
      ', + // + // '
    ', + '
  4. ', + '
  5. ', + '', + '3. ', // list_number enabled + 'Title should escape &, <, ', and "', + '', + '
  6. ', + '
  7. ', + '', + '4. ', // list_number enabled + 'Chapter 1 should be printed to toc', + '', + '
  8. ', + '
' + ].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 = [ + '
    ', + '
  1. ', + '', + '1. ', // list_number enabled + 'Title 1', + '', + '
      ', + '
    1. ', + '', + '1.1. ', // list_number enabled + 'Title 1.1', + '', + // '
        ', + // + // '
      ', + '
    2. ', + '
    3. ', + '', + '1.2. ', // list_number enabled + 'Title 1.2', + '', + '
    4. ', + '
    5. ', + '', + '1.3. ', // list_number enabled + 'Title 1.3', + '', + // '
        ', + // + // '
      ', + '
    6. ', + '
    ', + '
  2. ', + '
  3. ', + '', + '2. ', // list_number enabled + 'Title 2', + '', + '
      ', + '
    1. ', + '', + '2.1. ', // list_number enabled + 'Title 2.1', + '', + '
    2. ', + '
    ', + '
  4. ', + '
  5. ', + '', + '3. ', // list_number enabled + 'Title should escape &, <, ', and "', + '', + '
  6. ', + '
  7. ', + '', + '4. ', // list_number enabled + 'Chapter 1 should be printed to toc', + '', + '
  8. ', + '
' + ].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 = [ + '
    ', + '
  1. ', + '', + '1. ', // list_number enabled + 'Title 1', + '', + // '
      ', + // + // '
    ', + '
  2. ', + '
  3. ', + '', + '2. ', // list_number enabled + 'Title 2', + '', + '
  4. ', + // + '
' + ].join(''); + + toc(html, { max_items: 2}).should.eql(expected); // `h1` is truncated from the end + }); });