# All files / src/html headerProcessor.ts

79.06% Statements 34/43
57.69% Branches 15/26
83.33% Functions 5/6
80.48% Lines 33/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    1019x
    9x
    9x
    9x

    9x

    9x

    235x

    235x
    235x

    235x
    235x

    235x

    235x
    198x
    198x

    37x

    235x

    1x
    1x

    1x
    1x
    1x

    1x
    1x

    9x
    1x
    2x

    1x

    1x
    1x
    1x

    1x

    1x

     import cheerio from 'cheerio';
    import slugify from '@sindresorhus/slugify';
    import has from 'lodash/has';
    import { getVslotShorthandName } from './vueSlotSyntaxProcessor';
    import type { NodeProcessorConfig } from './NodeProcessor';
    import { MbNode, NodeOrText } from '../utils/node';

    const _ = {
      has,
    };

    /**
     * Increment the counter for same heading text, and set the id of the heading node
     *
     * If the addHeaderCount is false, the counter for the same heading text will not be incremented.
     * The heading id will also not have the count appended to it.
     *
     * So for, only SiteLinkManager uses this function with addHeaderCount set to false.
     * This is for validating intralinks in a side-effect free manner.
     *
     * Heading text refers to textContent of h1-h6 Nodes.
     */
    export function setHeadingId(node: MbNode,
                                 config: NodeProcessorConfig,
                                 addHeaderCount: boolean = true) {
      const textContent = cheerio(node).text();
      // remove the '&lt;' and '&gt;' symbols that markdown-it uses to escape '<' and '>'
      const cleanedContent = textContent.replace(/&lt;|&gt;/g, '');
      const slugifiedHeading = slugify(cleanedContent, { decamelize: false });

      let headerId = slugifiedHeading;
      Iif (!addHeaderCount) {
        node.attribs.id = headerId;
        return;
      }
      const { headerIdMap } = config;

      if (headerIdMap[slugifiedHeading]) {
        headerId = `${slugifiedHeading}-${headerIdMap[slugifiedHeading]}`;
        headerIdMap[slugifiedHeading] += 1;
      } else {
        headerIdMap[slugifiedHeading] = 2;
      }
      node.attribs.id = headerId;
    }

    /**
     * Traverses the dom breadth-first from the specified element to find a html heading child.
     * @param node Root element to search from
     * @returns  The header element, or undefined if none is found.
     */
    function _findHeaderElement(node: MbNode): undefined | NodeOrText {
      const elements = node.children;
      Iif (!elements || !elements.length) {
        return undefined;
      }

      const elementQueue = elements.slice(0);
      while (elementQueue.length) {
        const nextEl = elementQueue.shift();

        if (nextEl?.name && (/^h[1-6]$/).test(nextEl.name)) {
          return nextEl;
        }

        Iif (nextEl?.children) {
          nextEl.children.forEach(child => elementQueue.push(child));
        }
      }

      return undefined;
    }

    /**
     * Assigns an id to the root element of a panel component using the heading specified in the
     * panel's header slot or attribute (if any), with the header slot having priority if present.
     * This is to ensure anchors still work when panels are in their minimized form.
     * @param node The root panel element
     */
    export function assignPanelId(node: MbNode) {
      const slotChildren = node.children
        ? node.children.filter(child => getVslotShorthandName(child) !== '')
        : [];

      const headerSlot = slotChildren.find(child => getVslotShorthandName(child) === 'header');

      if (headerSlot) {
        const header = _findHeaderElement(headerSlot as MbNode);
        Iif (!header) {
          return;
        }

        Iif (!header.attribs || !_.has(header.attribs, 'id')) {
          throw new Error('Found a panel heading without an assigned id.\n'
              + 'Please report this to the MarkBind developers. Thank you!');
        }

        node.attribs.panelId = header.attribs.id;
      }
    }