(html, maxHeadingLevel = 2, headingScope = '')
| 4 | import renderContent from './render-content/index.js' |
| 5 | |
| 6 | export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope = '') { |
| 7 | const $ = cheerio.load(html, { xmlMode: true }) |
| 8 | |
| 9 | // eg `h2, h3` or `h2, h3, h4` depending on maxHeadingLevel |
| 10 | const selector = range(2, maxHeadingLevel + 1) |
| 11 | .map((num) => `${headingScope} h${num}`) |
| 12 | .join(', ') |
| 13 | const headings = $(selector) |
| 14 | |
| 15 | // return an array of objects containing each heading's contents, level, and optional platform. |
| 16 | // Article layout uses these as follows: |
| 17 | // - `title` and `link` to render the mini TOC headings |
| 18 | // - `headingLevel` the `2` in `h2`; used for determining required indentation |
| 19 | // - `platform` to show or hide platform-specific headings via client JS |
| 20 | |
| 21 | // H1 = highest importance, H6 = lowest importance |
| 22 | let mostImportantHeadingLevel |
| 23 | const flatToc = headings |
| 24 | .get() |
| 25 | .filter((item) => { |
| 26 | if (!item.parent || !item.parent.attribs) return true |
| 27 | // Hide any items that belong to a hidden div |
| 28 | const { attribs } = item.parent |
| 29 | return !('hidden' in attribs) |
| 30 | }) |
| 31 | .map((item) => { |
| 32 | // remove any <span> tags including their content |
| 33 | $('span', item).remove() |
| 34 | |
| 35 | // Capture the anchor tag nested within the header, get its href and remove it |
| 36 | const anchor = $('a.doctocat-link', item) |
| 37 | const href = anchor.attr('href') |
| 38 | if (!href) { |
| 39 | // Can happen if the, for example, `<h2>` tag was put there |
| 40 | // manually with HTML into the Markdown content. Then it wouldn't |
| 41 | // be rendered with an expected `<a class="doctocat-link" href="#..."` |
| 42 | // link in front of it. |
| 43 | // The `return null` will be filtered after the `.map()` |
| 44 | return null |
| 45 | } |
| 46 | anchor.remove() |
| 47 | |
| 48 | // remove any <strong> tags but leave content |
| 49 | $('strong', item).map((i, el) => $(el).replaceWith($(el).contents())) |
| 50 | |
| 51 | const contents = { href, title: $(item).text().trim() } |
| 52 | const headingLevel = parseInt($(item)[0].name.match(/\d+/)[0], 10) || 0 // the `2` from `h2` |
| 53 | |
| 54 | const platform = $(item).parent('.extended-markdown').attr('class') || '' |
| 55 | |
| 56 | // track the most important heading level while we're looping through the items |
| 57 | if (headingLevel < mostImportantHeadingLevel || mostImportantHeadingLevel === undefined) { |
| 58 | mostImportantHeadingLevel = headingLevel |
| 59 | } |
| 60 | |
| 61 | return { contents, headingLevel, platform } |
| 62 | }) |
| 63 | .filter(Boolean) |
no test coverage detected