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); |