Create a custom page TOC ("mini TOC") with Javascript


#1

On our site, we use custom JS code to create a page TOC (“mini TOC”) from h2 headings on the page, instead of Hugo’s TableofContents partial (unless the custom toc page param is set to hugo). To my recollection, the main incentive for the custom implementation was to also include in the mini TOC headings created with shortcodes. I originally shared our implementation in response to a question in Shortcode processing order, as this implementation also works to include in the TOC headings from a file that is included using a shortcode.

list.html & single.html Theme Default Partials

The theme _default partials include this code:

{{ partial "mini-toc" . }}

mini-toc.html Theme Partial

{{ $headers := findRE "<h[2].*?>(.|\n])+?</h[2]>" .Content }}

{{ if and (ge (len $headers) 1) (ne $.Params.toc "none") }}
  <div class="toc">
    <div class="mini-toc">
      <div class="mini-toc-header">
        <i class="fa fa-caret-right laptop" aria-hidden="true"></i>
        <span>On This Page</span>
        <i class="fa fa-caret-right desktop" aria-hidden="true"></i>
      </div>
    {{ if eq $.Params.toc "hugo" }}
      {{ .TableOfContents }}
    {{ else }}
      <nav role="navigation" id="TableOfContents">
        <ul class="toc-js"></ul>
      </nav>
  {{ end }}
    </div>
  </div>
{{ end }}

mini-toc.js

// Create a custom page TOC ("mini TOC")
function buildMiniToc() {
  var ToC;
  $('.content h2').each(function (i, el) {
    // Replace &lt;` and `&gt;` in the HTML headings with `<` and `>` to
    // support using these character entities in the source heading text and
    // avoid interpreting them as HTML tags. The current drawback is that
    // escaped `\&lt;` or `\&gt;` uses in the source heading text will appear
    // in them mini-TOC as `<` and `>` instead of `&lt;` and `&gt;`.
    var title = $(el).text().replace(/</g, '&lt;').replace(/>/g, '&gt');
    var link = '#' + $(el).attr('id');
    ToC = '<li><a href="' + link + '">' + title + '</a></li>';

    // Display the mini TOC only if page has TOC-level headings (currently, h2)
    $('ul.toc-js').append(ToC);
  });

  $(".mini-toc-header").click(function() {
    $("#TableOfContents").slideToggle(200);
    $(".mini-toc i").toggleClass("fa-caret-right");
    $(".mini-toc i").toggleClass("fa-caret-down");
  });

  $(window).scroll(function() {
    var windScroll  = $(this).scrollTop();
    windScroll > 200 ? $('#scroll-top').show() : $('#scroll-top').hide();
    if (windScroll) {
      $(".doc-content > h2, h3, h4").each(function() {
        if ($(this).position().top <= windScroll + 56) {
          var activeHeader = $("#TableOfContents").find('[href="#' + $(this).attr('id') + '"]');

          if (activeHeader.length) {
            $("#TableOfContents").find("a").removeClass("active");
            activeHeader.addClass("active");
          }
        }
      })
    }
  }).scroll();

  $(window).resize(function() {
   if ( $(window).width() > 1143 ){
       $('.mini-toc-header i').removeClass('fa-caret-right').addClass('fa-caret-down');
       $('#TableOfContents').show();
   } else {
       $('.mini-toc-header i').removeClass('fa-caret-down').addClass('fa-caret-right');
       $('#TableOfContents').hide();
   }
  }).resize();
}

Shortcode processing order
Shortcode processing order
#2

@sharonl thanks for the comprehensive write up! I moved it to a #tips-tricks topic. I was wondering, could you share the site where it is deployed? So we can point folks to a live demo. :slight_smile:


#3

This is our doc site: https://www.iguazio.com/docs/. It’s not an open-source site, though.
You can see an example of the mini TOC on most pages — for example, https://www.iguazio.com/docs/tutorials/latest-release/getting-started/containers/.
The TOC is very similar to the Hugo mini TOC.