Here is a way to do Table of Contents (ToC)

After about 2 hours of fiddling with it and finding other peoples anwers, I got it to work (using jQuery).

Steps:

  1. Add script at the bottom of this post in code injection in {{ghost_foot}}
  2. Use a markdown card to create a ToC like so (copy-paste this):

.

# Table of contents
1. [Go to Header](#header-name)

EDIT (important): You can also do it inline, if you want to link to something in one of your other posts or cross-reference in your current post. You would just highlight any word and click the link icon (or press CTRL+K), then insert as described in the next paragraph…

Whenever you create a header, you give that header a header name like Header Name, which translates to a link of #header-name. I hope it makes sense with the naming scheme, else feel free to comment.

<script>
(function(document, history, location) {
  var HISTORY_SUPPORT = !!(history && history.pushState);

  var anchorScrolls = {
    ANCHOR_REGEX: /^#[^ ]+$/,
    OFFSET_HEIGHT_PX: 70,

    /**
     * Establish events, and fix initial scroll position if a hash is provided.
     */
    init: function() {
      this.scrollToCurrent();
      $(window).on('hashchange', $.proxy(this, 'scrollToCurrent'));
      $('body').on('click', 'a', $.proxy(this, 'delegateAnchors'));
    },

    /**
     * Return the offset amount to deduct from the normal scroll position.
     * Modify as appropriate to allow for dynamic calculations
     */
    getFixedOffset: function() {
      return this.OFFSET_HEIGHT_PX;
    },

    /**
     * If the provided href is an anchor which resolves to an element on the
     * page, scroll to it.
     * @param  {String} href
     * @return {Boolean} - Was the href an anchor.
     */
    scrollIfAnchor: function(href, pushToHistory) {
      var match, anchorOffset;

      if(!this.ANCHOR_REGEX.test(href)) {
        return false;
      }

      match = document.getElementById(href.slice(1));

      if(match) {
        anchorOffset = $(match).offset().top - this.getFixedOffset();
        $('html, body').animate({ scrollTop: anchorOffset});

        // Add the state to history as-per normal anchor links
        if(HISTORY_SUPPORT && pushToHistory) {
          history.pushState({}, document.title, location.pathname + href);
        }
      }

      return !!match;
    },
    
    /**
     * Attempt to scroll to the current location's hash.
     */
    scrollToCurrent: function(e) { 
      if(this.scrollIfAnchor(window.location.hash) && e) {
      	e.preventDefault();
      }
    },

    /**
     * If the click event's target was an anchor, fix the scroll position.
     */
    delegateAnchors: function(e) {
      var elem = e.target;

      if(this.scrollIfAnchor(elem.getAttribute('href'), true)) {
        e.preventDefault();
      }
    }
  };
	$(window).on( "load", $.proxy(anchorScrolls, 'init'));
	//$(document).ready($.proxy(anchorScrolls, 'init'));
})(window.document, window.history, window.location);
</script>
11 Likes

This is great. Thanks for sharing this

1 Like

I had this request for the longest time. It really works wonders and pretty smoothly.
Let me know if there is anything else you need to know about this ToC :slight_smile:

I found a bug, where you could not click on the subscribe button on the front page. Here is the updated script

<script>
(function(document, history, location) {
  var HISTORY_SUPPORT = !!(history && history.pushState);

  var anchorScrolls = {
    ANCHOR_REGEX: /^#[^ ]+$/,
    OFFSET_HEIGHT_PX: 130,

    /**
     * Establish events, and fix initial scroll position if a hash is provided.
     */
    init: function() {
      this.scrollToCurrent();
      $(window).on('hashchange', $.proxy(this, 'scrollToCurrent'));
      $('body').on('click', 'a', $.proxy(this, 'delegateAnchors'));
    },

    /**
     * Return the offset amount to deduct from the normal scroll position.
     * Modify as appropriate to allow for dynamic calculations
     */
    getFixedOffset: function() {
      return this.OFFSET_HEIGHT_PX;
    },

    /**
     * If the provided href is an anchor which resolves to an element on the
     * page, scroll to it.
     * @param  {String} href
     * @return {Boolean} - Was the href an anchor.
     */
    scrollIfAnchor: function(href, pushToHistory) {
      var match, anchorOffset;

      if(!this.ANCHOR_REGEX.test(href)) {
        return false;
      }

      match = document.getElementById(href.slice(1));

      if(match) {
        anchorOffset = $(match).offset().top - this.getFixedOffset();
        $('html, body').animate({ scrollTop: anchorOffset});

        // Add the state to history as-per normal anchor links
        if(HISTORY_SUPPORT && pushToHistory) {
          history.pushState({}, document.title, location.pathname + href);
        }
      }

      return !!match;
    },
    
    /**
     * Attempt to scroll to the current location's hash.
     */
    scrollToCurrent: function(e) { 
      if(this.scrollIfAnchor(window.location.hash) && e) {
      	e.preventDefault();
      }
    },

    /**
     * If the click event's target was an anchor, fix the scroll position.
     */
    delegateAnchors: function(e) {
      var elem = e.target;
      if(elem.getAttribute('href') != '#subscribe'){
          if(this.scrollIfAnchor(elem.getAttribute('href'), true)) {
        		e.preventDefault();
      	  	}
      }
    }
  };
	$(window).on( "load", $.proxy(anchorScrolls, 'init'));
	//$(document).ready($.proxy(anchorScrolls, 'init'));
})(window.document, window.history, window.location);
</script>

Hey,
Do I need to install JQuery for this to work?

Nope. Just follow the steps and you will be fine. If you want a preview of it, you can visit my website. Here is a post that you can try it out

Brilliant, thanks!!!
Just a small note that I didn’t know: caps matters :slight_smile:

It seems that the generated id is in fact “headername” rather than “header-name”.