I may be a bit late to the game but I've put together my own jQuery plugin...
Available via jsDeliver CDN: https://cdn.jsdelivr.net/npm/@thelevicole/toc.js@1/dist/toc.jquery.js
A quick example of page structure:
<div class="table-of-contents"></div>
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h2>Heading 2</h2>
<h2>Heading 2</h2>
<h1>Heading 1</h1>
Now initiate the plugin:
$('.table-of-contents').tableOfContents();
Which will output this list:
<ul class="toc-list">
<li class="toc-item">
<a href="#heading-1" class="toc-link">Heading 1</a>
<ul class="toc-list">
<li class="toc-item">
<a href="#heading-2" class="toc-link">Heading 2</a>
<ul class="toc-list">
<li class="toc-item">
<a href="#heading-3" class="toc-link">Heading 3</a>
</li>
</ul>
</li>
<li class="toc-item">
<a href="#heading-4" class="toc-link">Heading 2</a>
</li>
<li class="toc-item">
<a href="#heading-5" class="toc-link">Heading 2</a>
</li>
</ul>
</li>
<li class="toc-item">
<a href="#heading-6" class="toc-link">Heading 1</a>
</li>
</ul>
Screenshot for reference:

There are a number of options that can be passed...
$('.table-of-contents').tableOfContents({
contentTarget: $( document.body ), // The element with content.
selectors: 'h1$1; h2$2; h3$3; h4$4; h5$5; h6$6;', // Tree structure.
nestingDepth: -1, // How deep we'll allow nesting. -1 for infinate.
slugLength: 40, // The max number of chars in the hash slug.
anchors: true, // Add anchors to headings.
anchorText: '#', // The symbol added to headings.
orderedList: false // True to use <ol> instead of <ul>
});
The selectors
option allows you create a table of contents from other selectors not just h1, h2, h3's etc.
The selector option accepts a string, array or object of selectors and depths:
String
$('.table-of-contents').tableOfContents({
// '{selector}${depth}; {selector}${depth}; ...'
selectors: 'h1$1; h2$2; h3$3; p:not(.my-class)$2; ...'
});
The selector pattern is fairly straight forward, and follows a simple pattern. We first have the DOM/CSS selector {selector} followed by the nesting depth which starts with a dollar symbol ${depth} and finished with a semicolon ;
The default pattern used for nested headings looks like this: 'h1$1; h2$2; h3$3; h4$4; h5$5; h6$6;'
Array
$('.table-of-contents').tableOfContents({
selectors: [
// '{selector}${depth}'
'h1$1',
'h2$2',
'h3$3',
'p:not(.my-class)$2',
...
]
});
Object
$('.table-of-contents').tableOfContents({
selectors: {
// '{selector}': {depth}
'h1': 1,
'h2': 2,
'h3': 3,
'p:not(.my-class)': 2,
...
}
});
Custom selector example:
<div class="table-of-contents"></div>
<article>
<p class="level-1">I'm level 1</p>
<p class="level-2">I'm level 2</p>
<p class="level-1">I'm level 1 again</p>
<p class="level-2">I'm level 2 again</p>
<p class="level-3">I'm level 3</p>
<p><strong>I'm a div element</strong></p>
<p class="level-2">I'm level 2</p>
</article>
$('.table-of-contents').tableOfContents({
contentTarget: 'article',
selectors: '.level-1 $1; .level-2 $2; .level-3 $3; p > strong $4'
});
Will result in...

I haven't yet had chance to write proper documentation but you can follow this project on GitHub: https://github.com/thelevicole/toc.js or see the homepage here: https://thelevicole.com/toc.js/
` elements.