Restart numbering from inside for loop on certain elements

Viewed 144

I have a plugin that numbers heading tags from a custom starting number a user sets.

It creates the headings like:

1. Heading level one
1.1. Heading level two
1.1.1. Heading level three

And if a user manually sets the start number:

20. Heading level one
20.3. Heading level two
20.3.6. Heading level three

However, when the loop reiterates, the numbering doesn't reset to 1 and I can't seem to solve how to trigger that:

20. Heading level one
20.3. Heading level two
20.3.6. Heading level three

20. Heading level one
20.4. Heading level two
20.4.6. Heading level three <-- this should renumber to 20.4.1

I can't get the following snippet to reset back to 1 after the first loop. Everything I've tried has reset all iterations, causing header[3] to set as 1 but not only after the first iteration.

if( levelNumber <= elementLevel ) {
  numberText += startingNumbers[levelNumber] + ".";
}

Test code

const header = [10, 20, 30, 40, 50, 60],
      contentHeaders = document.querySelectorAll(
        'h1, h2, h3, h4, h5, h6'
      ),
      startingNumbers   = [ 0,             // null
                            header[0] - 1, // h1
                            header[1] - 1, // h2
                            header[2] - 1, // h3
                            header[3] - 1, // h4
                            header[4] - 1, // h5
                            header[5] - 1, // h6
                          ];
for( var item in contentHeaders ) {

  // this element from item number
  var element = contentHeaders[item],
      numberText = '';

  // limit the heading tag number in search
  const headingRegex = new RegExp('^H([1-6])$');

  // does the element match a heading regex
  if( !element || !element.tagName || !element.tagName.match( headingRegex ) ) {
        // return to beginning of loop
        continue;
  }

  // return the heading level number
  var elementLevel = RegExp.$1;

  // increment by 1
  startingNumbers[elementLevel]++;
      
  // loop through the headings
  for( var levelNumber = 1; levelNumber <= 6; levelNumber++ ) {
        // if the number is lt the element number
        if( levelNumber <= elementLevel ) {
          numberText += startingNumbers[levelNumber] + ".";
        }
  }

  element.innerHTML =  numberText + ' ' + element.innerText;

}
* { font-family: monospace; font-size: 16px !important; }
<h1>Heading 1</h1>
<p>I should be <code>10. Heading 1</code></p>
<h2>Heading 2</h2>
<p>I should be <code>10.20. Heading 2</code></p>
<h3>Heading 3</h3>
<p>I should be <code>10.20.30. Heading 3</code></p>
<h4>Heading 4</h4>
<p>I should be <code>10.20.30.40. Heading 4</code></p>
<h5>Heading 5</h5>
<p>I should be <code>10.20.30.40.50. Heading 5</code></p>
<h6>Heading 6</h6>
<p>I should be <code>10.20.30.40.50.60. Heading 6</code></p>

<hr>

<h2>Heading 2</h2>
<p>I should be <code>10.21. Heading 2</code></p>
<h3>Heading 3</h3>
<p>I should be <code>10.21.1 Heading 3</code></p>

<hr>

<h2>Heading 2</h2>
<p>I should be <code>10.22. Heading 2</code></p>
<h3>Heading 3</h3>
<p>I should be <code>10.22.1. Heading 3</code></p>
<h4>Heading 4</h4>
<p>I should be <code>10.22.1.1. Heading 4</code></p>

1 Answers

You can create an array to track the first run of each heading level. If it's not the first run then reset all below levels.

const header = [10, 20, 30, 40, 50, 60],
      contentHeaders = document.querySelectorAll(
        'h1, h2, h3, h4, h5, h6'
      ),
      startingNumbers   = [ 0,             // null
                            header[0] - 1, // h1
                            header[1] - 1, // h2
                            header[2] - 1, // h3
                            header[3] - 1, // h4
                            header[4] - 1, // h5
                            header[5] - 1, // h6
                          ];
// track the first run                      
let first_run = [true, true, true, true, true, true, true];

for( var item in contentHeaders ) {

  // this element from item number
  var element = contentHeaders[item],
      numberText = '';

  // limit the heading tag number in search
  const headingRegex = new RegExp('^H([1-6])$');

  // does the element match a heading regex
  if( !element || !element.tagName || !element.tagName.match( headingRegex ) ) {
        // return to beginning of loop
        continue;
  }

  // return the heading level number
  var elementLevel = RegExp.$1;

  // increment by 1
  startingNumbers[elementLevel]++;
  
  // reset all level below except for the first run
  if (!first_run[elementLevel]) resetBelowLevels(elementLevel);
  first_run[elementLevel] = false;
      
  // loop through the headings
  for( var levelNumber = 1; levelNumber <= 6; levelNumber++ ) {
        // if the number is lt the element number
        if( levelNumber <= elementLevel ) {
          numberText += startingNumbers[levelNumber] + ".";
        }
  }

  element.innerHTML =  numberText + ' ' + element.innerText;

}

function resetBelowLevels(current_level) {
  // current_level is string so need to convert it to number
  for(let i = +current_level + 1; i <= 6; i++) {
    startingNumbers[i] = 0;
  }
}
* { font-family: monospace; font-size: 16px !important; }
<h1>Heading 1</h1>
<p>I should be <code>10. Heading 1</code></p>
<h2>Heading 2</h2>
<p>I should be <code>10.20. Heading 2</code></p>
<h3>Heading 3</h3>
<p>I should be <code>10.20.30. Heading 3</code></p>
<h4>Heading 4</h4>
<p>I should be <code>10.20.30.40. Heading 4</code></p>
<h5>Heading 5</h5>
<p>I should be <code>10.20.30.40.50. Heading 5</code></p>
<h6>Heading 6</h6>
<p>I should be <code>10.20.30.40.50.60. Heading 6</code></p>

<hr>

<h2>Heading 2</h2>
<p>I should be <code>10.21. Heading 2</code></p>
<h3>Heading 3</h3>
<p>I should be <code>10.21.1 Heading 3</code></p>

<hr>

<h2>Heading 2</h2>
<p>I should be <code>10.22. Heading 2</code></p>
<h3>Heading 3</h3>
<p>I should be <code>10.22.1. Heading 3</code></p>
<h4>Heading 4</h4>
<p>I should be <code>10.22.1.1. Heading 4</code></p>

Related