1 | (function($) {
|
2 | var navbarHeight;
|
3 | var initialised = false;
|
4 | var navbarOffset;
|
5 |
|
6 | function elOffset($el) {
|
7 | return $el.offset().top - (navbarHeight + navbarOffset);
|
8 | }
|
9 |
|
10 | function scrollToHash(duringPageLoad) {
|
11 | var elScrollToId = location.hash.replace(/^#/, '');
|
12 | var $el;
|
13 |
|
14 | function doScroll() {
|
15 | var offsetTop = elOffset($el);
|
16 | window.scrollTo(window.pageXOffset || window.scrollX, offsetTop);
|
17 | }
|
18 |
|
19 | if (elScrollToId) {
|
20 | $el = $(document.getElementById(elScrollToId));
|
21 |
|
22 | if (!$el.length) {
|
23 | $el = $(document.getElementsByName(elScrollToId));
|
24 | }
|
25 |
|
26 | if ($el.length) {
|
27 | if (duringPageLoad) {
|
28 | $(window).one('scroll', function() {
|
29 | setTimeout(doScroll, 100);
|
30 | });
|
31 | } else {
|
32 | setTimeout(doScroll, 0);
|
33 | }
|
34 | }
|
35 | }
|
36 | }
|
37 |
|
38 | function init(opts) {
|
39 | if (initialised) {
|
40 | return;
|
41 | }
|
42 | initialised = true;
|
43 | navbarHeight = $('.navbar').height();
|
44 | navbarOffset = opts.navbarOffset;
|
45 |
|
46 |
|
47 |
|
48 | $(window).on("hashchange", scrollToHash.bind(null, false));
|
49 | $(scrollToHash.bind(null, true));
|
50 | }
|
51 |
|
52 | $.catchAnchorLinks = function(options) {
|
53 | var opts = $.extend({}, jQuery.fn.toc.defaults, options);
|
54 | init(opts);
|
55 | };
|
56 |
|
57 | $.fn.toc = function(options) {
|
58 | var self = this;
|
59 | var opts = $.extend({}, jQuery.fn.toc.defaults, options);
|
60 |
|
61 | var container = $(opts.container);
|
62 | var tocs = [];
|
63 | var headings = $(opts.selectors, container);
|
64 | var headingOffsets = [];
|
65 | var activeClassName = 'active';
|
66 | var ANCHOR_PREFIX = "__anchor";
|
67 | var maxScrollTo;
|
68 | var visibleHeight;
|
69 | var headerHeight = 10;
|
70 | init();
|
71 |
|
72 | var scrollTo = function(e) {
|
73 | e.preventDefault();
|
74 | var target = $(e.target);
|
75 | if (target.prop('tagName').toLowerCase() !== "a") {
|
76 | target = target.parent();
|
77 | }
|
78 | var elScrollToId = target.attr('href').replace(/^#/, '') + ANCHOR_PREFIX;
|
79 | var $el = $(document.getElementById(elScrollToId));
|
80 |
|
81 | var offsetTop = Math.min(maxScrollTo, elOffset($el));
|
82 |
|
83 | $('body,html').animate({ scrollTop: offsetTop }, 400, 'swing', function() {
|
84 | location.hash = '#' + elScrollToId;
|
85 | });
|
86 |
|
87 | $('a', self).removeClass(activeClassName);
|
88 | target.addClass(activeClassName);
|
89 | };
|
90 |
|
91 | var calcHadingOffsets = function() {
|
92 | maxScrollTo = $("body").height() - $(window).height();
|
93 | visibleHeight = $(window).height() - navbarHeight;
|
94 | headingOffsets = [];
|
95 | headings.each(function(i, heading) {
|
96 | var anchorSpan = $(heading).prev("span");
|
97 | var top = 0;
|
98 | if (anchorSpan.length) {
|
99 | top = elOffset(anchorSpan);
|
100 | }
|
101 | headingOffsets.push(top > 0 ? top : 0);
|
102 | });
|
103 | }
|
104 |
|
105 |
|
106 | var timeout;
|
107 | var highlightOnScroll = function(e) {
|
108 | if (!tocs.length) {
|
109 | return;
|
110 | }
|
111 | if (timeout) {
|
112 | clearTimeout(timeout);
|
113 | }
|
114 | timeout = setTimeout(function() {
|
115 | var top = $(window).scrollTop(),
|
116 | highlighted;
|
117 | for (var i = headingOffsets.length - 1; i >= 0; i--) {
|
118 | var isActive = tocs[i].hasClass(activeClassName);
|
119 |
|
120 | if (isActive && headingOffsets[i] >= maxScrollTo && top >= maxScrollTo) {
|
121 | return;
|
122 | }
|
123 |
|
124 | if (i === 0 || (headingOffsets[i] + headerHeight >= top && (headingOffsets[i - 1] + headerHeight <= top))) {
|
125 |
|
126 |
|
127 | if (i > 0 && headingOffsets[i] - visibleHeight >= top) {
|
128 | i--;
|
129 | }
|
130 | $('a', self).removeClass(activeClassName);
|
131 | if (i >= 0) {
|
132 | highlighted = tocs[i].addClass(activeClassName);
|
133 | opts.onHighlight(highlighted);
|
134 | }
|
135 | break;
|
136 | }
|
137 | }
|
138 | }, 50);
|
139 | };
|
140 | if (opts.highlightOnScroll) {
|
141 | $(window).bind('scroll', highlightOnScroll);
|
142 | $(window).bind('load resize', function() {
|
143 | calcHadingOffsets();
|
144 | highlightOnScroll();
|
145 | });
|
146 | }
|
147 |
|
148 | return this.each(function() {
|
149 |
|
150 | var el = $(this);
|
151 | var ul = $('<div class="list-group">');
|
152 |
|
153 | headings.each(function(i, heading) {
|
154 | var $h = $(heading);
|
155 |
|
156 | var anchor = $('<span/>').attr('id', opts.anchorName(i, heading, opts.prefix) + ANCHOR_PREFIX).insertBefore($h);
|
157 |
|
158 | var span = $('<span/>')
|
159 | .text(opts.headerText(i, heading, $h));
|
160 |
|
161 |
|
162 | var a = $('<a class="list-group-item"/>')
|
163 | .append(span)
|
164 | .attr('href', '#' + opts.anchorName(i, heading, opts.prefix))
|
165 | .bind('click', function(e) {
|
166 | scrollTo(e);
|
167 | el.trigger('selected', $(this).attr('href'));
|
168 | });
|
169 |
|
170 | span.addClass(opts.itemClass(i, heading, $h, opts.prefix));
|
171 |
|
172 | tocs.push(a);
|
173 |
|
174 | ul.append(a);
|
175 | });
|
176 | el.html(ul);
|
177 |
|
178 | calcHadingOffsets();
|
179 | });
|
180 | };
|
181 |
|
182 |
|
183 | jQuery.fn.toc.defaults = {
|
184 | container: 'body',
|
185 | selectors: 'h1,h2,h3',
|
186 | smoothScrolling: true,
|
187 | prefix: 'toc',
|
188 | onHighlight: function() {},
|
189 | highlightOnScroll: true,
|
190 | navbarOffset: 0,
|
191 | anchorName: function(i, heading, prefix) {
|
192 | return prefix+i;
|
193 | },
|
194 | headerText: function(i, heading, $heading) {
|
195 | return $heading.text();
|
196 | },
|
197 | itemClass: function(i, heading, $heading, prefix) {
|
198 | return prefix + '-' + $heading[0].tagName.toLowerCase();
|
199 | }
|
200 |
|
201 | };
|
202 |
|
203 | })(jQuery);
|