204 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			204 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
(function($) {
 | 
						|
  var navbarHeight;
 | 
						|
  var initialised = false;
 | 
						|
  var navbarOffset;
 | 
						|
 | 
						|
  function elOffset($el) {
 | 
						|
    return $el.offset().top - (navbarHeight + navbarOffset);
 | 
						|
  }
 | 
						|
 | 
						|
  function scrollToHash(duringPageLoad) {
 | 
						|
    var elScrollToId = location.hash.replace(/^#/, '');
 | 
						|
    var $el;
 | 
						|
 | 
						|
    function doScroll() {
 | 
						|
      var offsetTop = elOffset($el);
 | 
						|
      window.scrollTo(window.pageXOffset || window.scrollX, offsetTop);
 | 
						|
    }
 | 
						|
 | 
						|
    if (elScrollToId) {
 | 
						|
      $el = $(document.getElementById(elScrollToId));
 | 
						|
 | 
						|
      if (!$el.length) {
 | 
						|
        $el = $(document.getElementsByName(elScrollToId));
 | 
						|
      }
 | 
						|
 | 
						|
      if ($el.length) {
 | 
						|
        if (duringPageLoad) {
 | 
						|
          $(window).one('scroll', function() {
 | 
						|
            setTimeout(doScroll, 100);
 | 
						|
          });
 | 
						|
        } else {
 | 
						|
          setTimeout(doScroll, 0);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function init(opts) {
 | 
						|
    if (initialised) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    initialised = true;
 | 
						|
    navbarHeight = $('.navbar').height();
 | 
						|
    navbarOffset = opts.navbarOffset;
 | 
						|
 | 
						|
    // some browsers move the offset after changing location.
 | 
						|
    // also catch external links coming in
 | 
						|
    $(window).on("hashchange", scrollToHash.bind(null, false));
 | 
						|
    $(scrollToHash.bind(null, true));
 | 
						|
  }
 | 
						|
 | 
						|
  $.catchAnchorLinks = function(options) {
 | 
						|
    var opts = $.extend({}, jQuery.fn.toc.defaults, options);
 | 
						|
    init(opts);
 | 
						|
  };
 | 
						|
 | 
						|
  $.fn.toc = function(options) {
 | 
						|
    var self = this;
 | 
						|
    var opts = $.extend({}, jQuery.fn.toc.defaults, options);
 | 
						|
 | 
						|
    var container = $(opts.container);
 | 
						|
    var tocs = [];
 | 
						|
    var headings = $(opts.selectors, container);
 | 
						|
    var headingOffsets = [];
 | 
						|
    var activeClassName = 'active';
 | 
						|
    var ANCHOR_PREFIX = "__anchor";
 | 
						|
    var maxScrollTo;
 | 
						|
    var visibleHeight;
 | 
						|
    var headerHeight = 10; // so if the header is readable, its counted as shown
 | 
						|
    init();
 | 
						|
 | 
						|
    var scrollTo = function(e) {
 | 
						|
      e.preventDefault();
 | 
						|
      var target = $(e.target);
 | 
						|
      if (target.prop('tagName').toLowerCase() !== "a") {
 | 
						|
        target = target.parent();
 | 
						|
      }
 | 
						|
      var elScrollToId = target.attr('href').replace(/^#/, '') + ANCHOR_PREFIX;
 | 
						|
      var $el = $(document.getElementById(elScrollToId));
 | 
						|
 | 
						|
      var offsetTop = Math.min(maxScrollTo, elOffset($el));
 | 
						|
 | 
						|
      $('body,html').animate({ scrollTop: offsetTop }, 400, 'swing', function() {
 | 
						|
        location.hash = '#' + elScrollToId;
 | 
						|
      });
 | 
						|
 | 
						|
      $('a', self).removeClass(activeClassName);
 | 
						|
      target.addClass(activeClassName);
 | 
						|
    };
 | 
						|
 | 
						|
    var calcHadingOffsets = function() {
 | 
						|
      maxScrollTo = $("body").height() - $(window).height();
 | 
						|
      visibleHeight = $(window).height() - navbarHeight;
 | 
						|
      headingOffsets = [];
 | 
						|
      headings.each(function(i, heading) {
 | 
						|
        var anchorSpan = $(heading).prev("span");
 | 
						|
        var top = 0;
 | 
						|
        if (anchorSpan.length) {
 | 
						|
          top = elOffset(anchorSpan);
 | 
						|
        }
 | 
						|
        headingOffsets.push(top > 0 ? top : 0);
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    //highlight on scroll
 | 
						|
    var timeout;
 | 
						|
    var highlightOnScroll = function(e) {
 | 
						|
      if (!tocs.length) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      if (timeout) {
 | 
						|
        clearTimeout(timeout);
 | 
						|
      }
 | 
						|
      timeout = setTimeout(function() {
 | 
						|
        var top = $(window).scrollTop(),
 | 
						|
          highlighted;
 | 
						|
        for (var i = headingOffsets.length - 1; i >= 0; i--) {
 | 
						|
          var isActive = tocs[i].hasClass(activeClassName);
 | 
						|
          // at the end of the page, allow any shown header
 | 
						|
          if (isActive && headingOffsets[i] >= maxScrollTo && top >= maxScrollTo) {
 | 
						|
            return;
 | 
						|
          }
 | 
						|
          // if we have got to the first heading or the heading is the first one visible
 | 
						|
          if (i === 0 || (headingOffsets[i] + headerHeight >= top && (headingOffsets[i - 1] + headerHeight <= top))) {
 | 
						|
            // in the case that a heading takes up more than the visible height e.g. we are showing
 | 
						|
            // only the one above, highlight the one above
 | 
						|
            if (i > 0 && headingOffsets[i] - visibleHeight >= top) {
 | 
						|
              i--;
 | 
						|
            }
 | 
						|
            $('a', self).removeClass(activeClassName);
 | 
						|
            if (i >= 0) {
 | 
						|
              highlighted = tocs[i].addClass(activeClassName);
 | 
						|
              opts.onHighlight(highlighted);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }, 50);
 | 
						|
    };
 | 
						|
    if (opts.highlightOnScroll) {
 | 
						|
      $(window).bind('scroll', highlightOnScroll);
 | 
						|
      $(window).bind('load resize', function() {
 | 
						|
        calcHadingOffsets();
 | 
						|
        highlightOnScroll();
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    return this.each(function() {
 | 
						|
      //build TOC
 | 
						|
      var el = $(this);
 | 
						|
      var ul = $('<div class="list-group">');
 | 
						|
 | 
						|
      headings.each(function(i, heading) {
 | 
						|
        var $h = $(heading);
 | 
						|
 | 
						|
        var anchor = $('<span/>').attr('id', opts.anchorName(i, heading, opts.prefix) + ANCHOR_PREFIX).insertBefore($h);
 | 
						|
 | 
						|
        var span = $('<span/>')
 | 
						|
          .text(opts.headerText(i, heading, $h));
 | 
						|
 | 
						|
        //build TOC item
 | 
						|
        var a = $('<a class="list-group-item"/>')
 | 
						|
          .append(span)
 | 
						|
          .attr('href', '#' + opts.anchorName(i, heading, opts.prefix))
 | 
						|
          .bind('click', function(e) {
 | 
						|
            scrollTo(e);
 | 
						|
            el.trigger('selected', $(this).attr('href'));
 | 
						|
          });
 | 
						|
 | 
						|
        span.addClass(opts.itemClass(i, heading, $h, opts.prefix));
 | 
						|
 | 
						|
        tocs.push(a);
 | 
						|
 | 
						|
        ul.append(a);
 | 
						|
      });
 | 
						|
      el.html(ul);
 | 
						|
 | 
						|
      calcHadingOffsets();
 | 
						|
    });
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
jQuery.fn.toc.defaults = {
 | 
						|
  container: 'body',
 | 
						|
  selectors: 'h1,h2,h3',
 | 
						|
  smoothScrolling: true,
 | 
						|
  prefix: 'toc',
 | 
						|
  onHighlight: function() {},
 | 
						|
  highlightOnScroll: true,
 | 
						|
  navbarOffset: 0,
 | 
						|
  anchorName: function(i, heading, prefix) {
 | 
						|
    return prefix+i;
 | 
						|
  },
 | 
						|
  headerText: function(i, heading, $heading) {
 | 
						|
    return $heading.text();
 | 
						|
  },
 | 
						|
  itemClass: function(i, heading, $heading, prefix) {
 | 
						|
    return prefix + '-' + $heading[0].tagName.toLowerCase();
 | 
						|
  }
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
})(jQuery);
 |