rails-tocify 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fa76b5be357dee687eb8879c4b455f4f85ded6b3
4
+ data.tar.gz: f80d39ef8bab3f965b1415960a7255c861b0b1ad
5
+ SHA512:
6
+ metadata.gz: a0a8c94e51629222b0cb2b4c7e53f860a2aa8e1123487e22cd0bcf7ee4c6f396fbf171c0d91e39ee96f922202b05e407ee9565778fac3a329a80ab93150b59c9
7
+ data.tar.gz: 085a1f9ad8a4eca6f24818d66d097661a861fcceeb1a24f2a88134297e11f74c6cdd26d823e420a00e5d23574f28ae9773cf95d8a58665eaad145e034f9126a4
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # rails-tocify
2
+ Rails gem of gfranko/jquery.tocify
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,8 @@
1
+ require "rails-tocify/version"
2
+
3
+ module Tocify
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Tocify
2
+ module Rails
3
+ VERSION = "1.9.0"
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rails-tocify/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['peterthecoon']
6
+ gem.email = ['pierre.prevoteau@askida.fr']
7
+ gem.description = %q{Rails gem of gfranko/jquery.tocify.js}
8
+ gem.summary = %q{gfranko/jquery.tocify.js}
9
+ gem.homepage = 'https://github.com/peterthecoon/rails-tocify'
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = 'rails-tocify'
15
+ gem.require_paths = ['lib']
16
+ gem.version = Tocify::Rails::VERSION
17
+ gem.license = 'MIT'
18
+
19
+ gem.add_runtime_dependency 'jquery-rails'
20
+ end
@@ -0,0 +1,987 @@
1
+ /* jquery Tocify - v1.9.0 - 2013-10-01
2
+ * http://www.gregfranko.com/jquery.tocify.js/
3
+ * Copyright (c) 2013 Greg Franko; Licensed MIT */
4
+
5
+ // Immediately-Invoked Function Expression (IIFE) [Ben Alman Blog Post](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) that calls another IIFE that contains all of the plugin logic. I used this pattern so that anyone viewing this code would not have to scroll to the bottom of the page to view the local parameters that were passed to the main IIFE.
6
+ (function(tocify) {
7
+
8
+ // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
9
+ "use strict";
10
+
11
+ // Calls the second IIFE and locally passes in the global jQuery, window, and document objects
12
+ tocify(window.jQuery, window, document);
13
+
14
+ }
15
+
16
+ // Locally passes in `jQuery`, the `window` object, the `document` object, and an `undefined` variable. The `jQuery`, `window` and `document` objects are passed in locally, to improve performance, since javascript first searches for a variable match within the local variables set before searching the global variables set. All of the global variables are also passed in locally to be minifier friendly. `undefined` can be passed in locally, because it is not a reserved word in JavaScript.
17
+ (function($, window, document, undefined) {
18
+
19
+ // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
20
+ "use strict";
21
+
22
+ var tocClassName = "tocify",
23
+ tocClass = "." + tocClassName,
24
+ tocFocusClassName = "tocify-focus",
25
+ tocHoverClassName = "tocify-hover",
26
+ hideTocClassName = "tocify-hide",
27
+ hideTocClass = "." + hideTocClassName,
28
+ headerClassName = "tocify-header",
29
+ headerClass = "." + headerClassName,
30
+ subheaderClassName = "tocify-subheader",
31
+ subheaderClass = "." + subheaderClassName,
32
+ itemClassName = "tocify-item",
33
+ itemClass = "." + itemClassName,
34
+ extendPageClassName = "tocify-extend-page",
35
+ extendPageClass = "." + extendPageClassName;
36
+
37
+ // Calling the jQueryUI Widget Factory Method
38
+ $.widget("toc.tocify", {
39
+
40
+ //Plugin version
41
+ version: "1.9.0",
42
+
43
+ // These options will be used as defaults
44
+ options: {
45
+
46
+ // **context**: Accepts String: Any jQuery selector
47
+ // The container element that holds all of the elements used to generate the table of contents
48
+ context: "body",
49
+
50
+ // **ignoreSelector**: Accepts String: Any jQuery selector
51
+ // A selector to any element that would be matched by selectors that you wish to be ignored
52
+ ignoreSelector: null,
53
+
54
+ // **selectors**: Accepts an Array of Strings: Any jQuery selectors
55
+ // The element's used to generate the table of contents. The order is very important since it will determine the table of content's nesting structure
56
+ selectors: "h1, h2, h3",
57
+
58
+ // **showAndHide**: Accepts a boolean: true or false
59
+ // Used to determine if elements should be shown and hidden
60
+ showAndHide: true,
61
+
62
+ // **showEffect**: Accepts String: "none", "fadeIn", "show", or "slideDown"
63
+ // Used to display any of the table of contents nested items
64
+ showEffect: "slideDown",
65
+
66
+ // **showEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
67
+ // The time duration of the show animation
68
+ showEffectSpeed: "medium",
69
+
70
+ // **hideEffect**: Accepts String: "none", "fadeOut", "hide", or "slideUp"
71
+ // Used to hide any of the table of contents nested items
72
+ hideEffect: "slideUp",
73
+
74
+ // **hideEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
75
+ // The time duration of the hide animation
76
+ hideEffectSpeed: "medium",
77
+
78
+ // **smoothScroll**: Accepts a boolean: true or false
79
+ // Determines if a jQuery animation should be used to scroll to specific table of contents items on the page
80
+ smoothScroll: true,
81
+
82
+ // **smoothScrollSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
83
+ // The time duration of the smoothScroll animation
84
+ smoothScrollSpeed: "medium",
85
+
86
+ // **scrollTo**: Accepts Number (pixels)
87
+ // The amount of space between the top of page and the selected table of contents item after the page has been scrolled
88
+ scrollTo: 0,
89
+
90
+ // **showAndHideOnScroll**: Accepts a boolean: true or false
91
+ // Determines if table of contents nested items should be shown and hidden while scrolling
92
+ showAndHideOnScroll: true,
93
+
94
+ // **highlightOnScroll**: Accepts a boolean: true or false
95
+ // Determines if table of contents nested items should be highlighted (set to a different color) while scrolling
96
+ highlightOnScroll: true,
97
+
98
+ // **highlightOffset**: Accepts a number
99
+ // The offset distance in pixels to trigger the next active table of contents item
100
+ highlightOffset: 40,
101
+
102
+ // **theme**: Accepts a string: "bootstrap", "jqueryui", or "none"
103
+ // Determines if Twitter Bootstrap, jQueryUI, or Tocify classes should be added to the table of contents
104
+ theme: "bootstrap",
105
+
106
+ // **extendPage**: Accepts a boolean: true or false
107
+ // If a user scrolls to the bottom of the page and the page is not tall enough to scroll to the last table of contents item, then the page height is increased
108
+ extendPage: true,
109
+
110
+ // **extendPageOffset**: Accepts a number: pixels
111
+ // How close to the bottom of the page a user must scroll before the page is extended
112
+ extendPageOffset: 100,
113
+
114
+ // **history**: Accepts a boolean: true or false
115
+ // Adds a hash to the page url to maintain history
116
+ history: true,
117
+
118
+ // **scrollHistory**: Accepts a boolean: true or false
119
+ // Adds a hash to the page url, to maintain history, when scrolling to a TOC item
120
+ scrollHistory: false,
121
+
122
+ // **hashGenerator**: How the hash value (the anchor segment of the URL, following the
123
+ // # character) will be generated.
124
+ //
125
+ // "compact" (default) - #CompressesEverythingTogether
126
+ // "pretty" - #looks-like-a-nice-url-and-is-easily-readable
127
+ // function(text, element){} - Your own hash generation function that accepts the text as an
128
+ // argument, and returns the hash value.
129
+ hashGenerator: "compact",
130
+
131
+ // **highlightDefault**: Accepts a boolean: true or false
132
+ // Set's the first TOC item as active if no other TOC item is active.
133
+ highlightDefault: true
134
+
135
+ },
136
+
137
+ // _Create
138
+ // -------
139
+ // Constructs the plugin. Only called once.
140
+ _create: function() {
141
+
142
+ var self = this;
143
+
144
+ self.extendPageScroll = true;
145
+
146
+ // Internal array that keeps track of all TOC items (Helps to recognize if there are duplicate TOC item strings)
147
+ self.items = [];
148
+
149
+ // Generates the HTML for the dynamic table of contents
150
+ self._generateToc();
151
+
152
+ // Adds CSS classes to the newly generated table of contents HTML
153
+ self._addCSSClasses();
154
+
155
+ self.webkit = (function() {
156
+
157
+ for(var prop in window) {
158
+
159
+ if(prop) {
160
+
161
+ if(prop.toLowerCase().indexOf("webkit") !== -1) {
162
+
163
+ return true;
164
+
165
+ }
166
+
167
+ }
168
+
169
+ }
170
+
171
+ return false;
172
+
173
+ }());
174
+
175
+ // Adds jQuery event handlers to the newly generated table of contents
176
+ self._setEventHandlers();
177
+
178
+ // Binding to the Window load event to make sure the correct scrollTop is calculated
179
+ $(window).load(function() {
180
+
181
+ // Sets the active TOC item
182
+ self._setActiveElement(true);
183
+
184
+ // Once all animations on the page are complete, this callback function will be called
185
+ $("html, body").promise().done(function() {
186
+
187
+ setTimeout(function() {
188
+
189
+ self.extendPageScroll = false;
190
+
191
+ },0);
192
+
193
+ });
194
+
195
+ });
196
+
197
+ },
198
+
199
+ // _generateToc
200
+ // ------------
201
+ // Generates the HTML for the dynamic table of contents
202
+ _generateToc: function() {
203
+
204
+ // _Local variables_
205
+
206
+ // Stores the plugin context in the self variable
207
+ var self = this,
208
+
209
+ // All of the HTML tags found within the context provided (i.e. body) that match the top level jQuery selector above
210
+ firstElem,
211
+
212
+ // Instantiated variable that will store the top level newly created unordered list DOM element
213
+ ul,
214
+ ignoreSelector = self.options.ignoreSelector;
215
+
216
+ // If the selectors option has a comma within the string
217
+ if(this.options.selectors.indexOf(",") !== -1) {
218
+
219
+ // Grabs the first selector from the string
220
+ firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,"").substr(0, this.options.selectors.indexOf(",")));
221
+
222
+ }
223
+
224
+ // If the selectors option does not have a comman within the string
225
+ else {
226
+
227
+ // Grabs the first selector from the string and makes sure there are no spaces
228
+ firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,""));
229
+
230
+ }
231
+
232
+ if(!firstElem.length) {
233
+
234
+ self.element.addClass(hideTocClassName);
235
+
236
+ return;
237
+
238
+ }
239
+
240
+ self.element.addClass(tocClassName);
241
+
242
+ // Loops through each top level selector
243
+ firstElem.each(function(index) {
244
+
245
+ //If the element matches the ignoreSelector then we skip it
246
+ if($(this).is(ignoreSelector)) {
247
+ return;
248
+ }
249
+
250
+ // Creates an unordered list HTML element and adds a dynamic ID and standard class name
251
+ ul = $("<ul/>", {
252
+ "id": headerClassName + index,
253
+ "class": headerClassName
254
+ }).
255
+
256
+ // Appends a top level list item HTML element to the previously created HTML header
257
+ append(self._nestElements($(this), index));
258
+
259
+ // Add the created unordered list element to the HTML element calling the plugin
260
+ self.element.append(ul);
261
+
262
+ // Finds all of the HTML tags between the header and subheader elements
263
+ $(this).nextUntil(this.nodeName.toLowerCase()).each(function() {
264
+
265
+ // If there are no nested subheader elemements
266
+ if($(this).find(self.options.selectors).length === 0) {
267
+
268
+ // Loops through all of the subheader elements
269
+ $(this).filter(self.options.selectors).each(function() {
270
+
271
+ //If the element matches the ignoreSelector then we skip it
272
+ if($(this).is(ignoreSelector)) {
273
+ return;
274
+ }
275
+
276
+ self._appendSubheaders.call(this, self, ul);
277
+
278
+ });
279
+
280
+ }
281
+
282
+ // If there are nested subheader elements
283
+ else {
284
+
285
+ // Loops through all of the subheader elements
286
+ $(this).find(self.options.selectors).each(function() {
287
+
288
+ //If the element matches the ignoreSelector then we skip it
289
+ if($(this).is(ignoreSelector)) {
290
+ return;
291
+ }
292
+
293
+ self._appendSubheaders.call(this, self, ul);
294
+
295
+ });
296
+
297
+ }
298
+
299
+ });
300
+
301
+ });
302
+
303
+ },
304
+
305
+ _setActiveElement: function(pageload) {
306
+
307
+ var self = this,
308
+
309
+ hash = window.location.hash.substring(1),
310
+
311
+ elem = self.element.find('li[data-unique="' + hash + '"]');
312
+
313
+ if(hash.length) {
314
+
315
+ // Removes highlighting from all of the list item's
316
+ self.element.find("." + self.focusClass).removeClass(self.focusClass);
317
+
318
+ // Highlights the current list item that was clicked
319
+ elem.addClass(self.focusClass);
320
+
321
+ // If the showAndHide option is true
322
+ if(self.options.showAndHide) {
323
+
324
+ // Triggers the click event on the currently focused TOC item
325
+ elem.click();
326
+
327
+ }
328
+
329
+ }
330
+
331
+ else {
332
+
333
+ // Removes highlighting from all of the list item's
334
+ self.element.find("." + self.focusClass).removeClass(self.focusClass);
335
+
336
+ if(!hash.length && pageload && self.options.highlightDefault) {
337
+
338
+ // Highlights the first TOC item if no other items are highlighted
339
+ self.element.find(itemClass).first().addClass(self.focusClass);
340
+
341
+ }
342
+
343
+ }
344
+
345
+ return self;
346
+
347
+ },
348
+
349
+ // _nestElements
350
+ // -------------
351
+ // Helps create the table of contents list by appending nested list items
352
+ _nestElements: function(self, index) {
353
+
354
+ var arr, item, hashValue;
355
+
356
+ arr = $.grep(this.items, function (item) {
357
+
358
+ return item === self.text();
359
+
360
+ });
361
+
362
+ // If there is already a duplicate TOC item
363
+ if(arr.length) {
364
+
365
+ // Adds the current TOC item text and index (for slight randomization) to the internal array
366
+ this.items.push(self.text() + index);
367
+
368
+ }
369
+
370
+ // If there not a duplicate TOC item
371
+ else {
372
+
373
+ // Adds the current TOC item text to the internal array
374
+ this.items.push(self.text());
375
+
376
+ }
377
+
378
+ hashValue = this._generateHashValue(arr, self, index);
379
+
380
+ // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
381
+ item = $("<li/>", {
382
+
383
+ // Sets a common class name to the list item
384
+ "class": itemClassName,
385
+
386
+ "data-unique": hashValue
387
+
388
+ }).append($("<a/>", {
389
+
390
+ "text": self.text()
391
+
392
+ }));
393
+
394
+ // Adds an HTML anchor tag before the currently traversed HTML element
395
+ self.before($("<div/>", {
396
+
397
+ // Sets a name attribute on the anchor tag to the text of the currently traversed HTML element (also making sure that all whitespace is replaced with an underscore)
398
+ "name": hashValue,
399
+
400
+ "data-unique": hashValue
401
+
402
+ }));
403
+
404
+ return item;
405
+
406
+ },
407
+
408
+ // _generateHashValue
409
+ // ------------------
410
+ // Generates the hash value that will be used to refer to each item.
411
+ _generateHashValue: function(arr, self, index) {
412
+
413
+ var hashValue = "",
414
+ hashGeneratorOption = this.options.hashGenerator;
415
+
416
+ if (hashGeneratorOption === "pretty") {
417
+
418
+ // prettify the text
419
+ hashValue = self.text().toLowerCase().replace(/\s/g, "-");
420
+
421
+ // fix double hyphens
422
+ while (hashValue.indexOf("--") > -1) {
423
+ hashValue = hashValue.replace(/--/g, "-");
424
+ }
425
+
426
+ // fix colon-space instances
427
+ while (hashValue.indexOf(":-") > -1) {
428
+ hashValue = hashValue.replace(/:-/g, "-");
429
+ }
430
+
431
+ } else if (typeof hashGeneratorOption === "function") {
432
+
433
+ // call the function
434
+ hashValue = hashGeneratorOption(self.text(), self);
435
+
436
+ } else {
437
+
438
+ // compact - the default
439
+ hashValue = self.text().replace(/\s/g, "");
440
+
441
+ }
442
+
443
+ // add the index if we need to
444
+ if (arr.length) { hashValue += ""+index; }
445
+
446
+ // return the value
447
+ return hashValue;
448
+
449
+ },
450
+
451
+ // _appendElements
452
+ // ---------------
453
+ // Helps create the table of contents list by appending subheader elements
454
+
455
+ _appendSubheaders: function(self, ul) {
456
+
457
+ // The current element index
458
+ var index = $(this).index(self.options.selectors),
459
+
460
+ // Finds the previous header DOM element
461
+ previousHeader = $(self.options.selectors).eq(index - 1),
462
+
463
+ currentTagName = +$(this).prop("tagName").charAt(1),
464
+
465
+ previousTagName = +previousHeader.prop("tagName").charAt(1),
466
+
467
+ lastSubheader;
468
+
469
+ // If the current header DOM element is smaller than the previous header DOM element or the first subheader
470
+ if(currentTagName < previousTagName) {
471
+
472
+ // Selects the last unordered list HTML found within the HTML element calling the plugin
473
+ self.element.find(subheaderClass + "[data-tag=" + currentTagName + "]").last().append(self._nestElements($(this), index));
474
+
475
+ }
476
+
477
+ // If the current header DOM element is the same type of header(eg. h4) as the previous header DOM element
478
+ else if(currentTagName === previousTagName) {
479
+
480
+ ul.find(itemClass).last().after(self._nestElements($(this), index));
481
+
482
+ }
483
+
484
+ else {
485
+
486
+ // Selects the last unordered list HTML found within the HTML element calling the plugin
487
+ ul.find(itemClass).last().
488
+
489
+ // Appends an unorderedList HTML element to the dynamic `unorderedList` variable and sets a common class name
490
+ after($("<ul/>", {
491
+
492
+ "class": subheaderClassName,
493
+
494
+ "data-tag": currentTagName
495
+
496
+ })).next(subheaderClass).
497
+
498
+ // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
499
+ append(self._nestElements($(this), index));
500
+ }
501
+
502
+ },
503
+
504
+ // _setEventHandlers
505
+ // ----------------
506
+ // Adds jQuery event handlers to the newly generated table of contents
507
+ _setEventHandlers: function() {
508
+
509
+ // _Local variables_
510
+
511
+ // Stores the plugin context in the self variable
512
+ var self = this,
513
+
514
+ // Instantiates a new variable that will be used to hold a specific element's context
515
+ $self,
516
+
517
+ // Instantiates a new variable that will be used to determine the smoothScroll animation time duration
518
+ duration;
519
+
520
+ // Event delegation that looks for any clicks on list item elements inside of the HTML element calling the plugin
521
+ this.element.on("click.tocify", "li", function(event) {
522
+
523
+ if(self.options.history) {
524
+
525
+ window.location.hash = $(this).attr("data-unique");
526
+
527
+ }
528
+
529
+ // Removes highlighting from all of the list item's
530
+ self.element.find("." + self.focusClass).removeClass(self.focusClass);
531
+
532
+ // Highlights the current list item that was clicked
533
+ $(this).addClass(self.focusClass);
534
+
535
+ // If the showAndHide option is true
536
+ if(self.options.showAndHide) {
537
+
538
+ var elem = $('li[data-unique="' + $(this).attr("data-unique") + '"]');
539
+
540
+ self._triggerShow(elem);
541
+
542
+ }
543
+
544
+ self._scrollTo($(this));
545
+
546
+ });
547
+
548
+ // Mouseenter and Mouseleave event handlers for the list item's within the HTML element calling the plugin
549
+ this.element.find("li").on({
550
+
551
+ // Mouseenter event handler
552
+ "mouseenter.tocify": function() {
553
+
554
+ // Adds a hover CSS class to the current list item
555
+ $(this).addClass(self.hoverClass);
556
+
557
+ // Makes sure the cursor is set to the pointer icon
558
+ $(this).css("cursor", "pointer");
559
+
560
+ },
561
+
562
+ // Mouseleave event handler
563
+ "mouseleave.tocify": function() {
564
+
565
+ if(self.options.theme !== "bootstrap") {
566
+
567
+ // Removes the hover CSS class from the current list item
568
+ $(this).removeClass(self.hoverClass);
569
+
570
+ }
571
+
572
+ }
573
+ });
574
+
575
+ // only attach handler if needed (expensive in IE)
576
+ if (self.options.extendPage || self.options.highlightOnScroll || self.options.scrollHistory || self.options.showAndHideOnScroll)
577
+ {
578
+ // Window scroll event handler
579
+ $(window).on("scroll.tocify", function() {
580
+
581
+ // Once all animations on the page are complete, this callback function will be called
582
+ $("html, body").promise().done(function() {
583
+
584
+ // Local variables
585
+
586
+ // Stores how far the user has scrolled
587
+ var winScrollTop = $(window).scrollTop(),
588
+
589
+ // Stores the height of the window
590
+ winHeight = $(window).height(),
591
+
592
+ // Stores the height of the document
593
+ docHeight = $(document).height(),
594
+
595
+ scrollHeight = $("body")[0].scrollHeight,
596
+
597
+ // Instantiates a variable that will be used to hold a selected HTML element
598
+ elem,
599
+
600
+ lastElem,
601
+
602
+ lastElemOffset,
603
+
604
+ currentElem;
605
+
606
+ if(self.options.extendPage) {
607
+
608
+ // If the user has scrolled to the bottom of the page and the last toc item is not focused
609
+ if((self.webkit && winScrollTop >= scrollHeight - winHeight - self.options.extendPageOffset) || (!self.webkit && winHeight + winScrollTop > docHeight - self.options.extendPageOffset)) {
610
+
611
+ if(!$(extendPageClass).length) {
612
+
613
+ lastElem = $('div[data-unique="' + $(itemClass).last().attr("data-unique") + '"]');
614
+
615
+ if(!lastElem.length) return;
616
+
617
+ // Gets the top offset of the page header that is linked to the last toc item
618
+ lastElemOffset = lastElem.offset().top;
619
+
620
+ // Appends a div to the bottom of the page and sets the height to the difference of the window scrollTop and the last element's position top offset
621
+ $(self.options.context).append($("<div />", {
622
+
623
+ "class": extendPageClassName,
624
+
625
+ "height": Math.abs(lastElemOffset - winScrollTop) + "px",
626
+
627
+ "data-unique": extendPageClassName
628
+
629
+ }));
630
+
631
+ if(self.extendPageScroll) {
632
+
633
+ currentElem = self.element.find('li.active');
634
+
635
+ self._scrollTo($('div[data-unique="' + currentElem.attr("data-unique") + '"]'));
636
+
637
+ }
638
+
639
+ }
640
+
641
+ }
642
+
643
+ }
644
+
645
+ // The zero timeout ensures the following code is run after the scroll events
646
+ setTimeout(function() {
647
+
648
+ // _Local variables_
649
+
650
+ // Stores the distance to the closest anchor
651
+ var closestAnchorDistance = null,
652
+
653
+ // Stores the index of the closest anchor
654
+ closestAnchorIdx = null,
655
+
656
+ // Keeps a reference to all anchors
657
+ anchors = $(self.options.context).find("div[data-unique]"),
658
+
659
+ anchorText;
660
+
661
+ // Determines the index of the closest anchor
662
+ anchors.each(function(idx) {
663
+ var distance = Math.abs(($(this).next().length ? $(this).next() : $(this)).offset().top - winScrollTop - self.options.highlightOffset);
664
+ if (closestAnchorDistance == null || distance < closestAnchorDistance) {
665
+ closestAnchorDistance = distance;
666
+ closestAnchorIdx = idx;
667
+ } else {
668
+ return false;
669
+ }
670
+ });
671
+
672
+ anchorText = $(anchors[closestAnchorIdx]).attr("data-unique");
673
+
674
+ // Stores the list item HTML element that corresponds to the currently traversed anchor tag
675
+ elem = $('li[data-unique="' + anchorText + '"]');
676
+
677
+ // If the `highlightOnScroll` option is true and a next element is found
678
+ if(self.options.highlightOnScroll && elem.length) {
679
+
680
+ // Removes highlighting from all of the list item's
681
+ self.element.find("." + self.focusClass).removeClass(self.focusClass);
682
+
683
+ // Highlights the corresponding list item
684
+ elem.addClass(self.focusClass);
685
+
686
+ }
687
+
688
+ if(self.options.scrollHistory) {
689
+
690
+ if(window.location.hash !== "#" + anchorText) {
691
+
692
+ window.location.replace("#" + anchorText);
693
+
694
+ }
695
+ }
696
+
697
+ // If the `showAndHideOnScroll` option is true
698
+ if(self.options.showAndHideOnScroll && self.options.showAndHide) {
699
+
700
+ self._triggerShow(elem, true);
701
+
702
+ }
703
+
704
+ }, 0);
705
+
706
+ });
707
+
708
+ });
709
+ }
710
+
711
+ },
712
+
713
+ // Show
714
+ // ----
715
+ // Opens the current sub-header
716
+ show: function(elem, scroll) {
717
+
718
+ // Stores the plugin context in the `self` variable
719
+ var self = this,
720
+ element = elem;
721
+
722
+ // If the sub-header is not already visible
723
+ if (!elem.is(":visible")) {
724
+
725
+ // If the current element does not have any nested subheaders, is not a header, and its parent is not visible
726
+ if(!elem.find(subheaderClass).length && !elem.parent().is(headerClass) && !elem.parent().is(":visible")) {
727
+
728
+ // Sets the current element to all of the subheaders within the current header
729
+ elem = elem.parents(subheaderClass).add(elem);
730
+
731
+ }
732
+
733
+ // If the current element does not have any nested subheaders and is not a header
734
+ else if(!elem.children(subheaderClass).length && !elem.parent().is(headerClass)) {
735
+
736
+ // Sets the current element to the closest subheader
737
+ elem = elem.closest(subheaderClass);
738
+
739
+ }
740
+
741
+ //Determines what jQuery effect to use
742
+ switch (self.options.showEffect) {
743
+
744
+ //Uses `no effect`
745
+ case "none":
746
+
747
+ elem.show();
748
+
749
+ break;
750
+
751
+ //Uses the jQuery `show` special effect
752
+ case "show":
753
+
754
+ elem.show(self.options.showEffectSpeed);
755
+
756
+ break;
757
+
758
+ //Uses the jQuery `slideDown` special effect
759
+ case "slideDown":
760
+
761
+ elem.slideDown(self.options.showEffectSpeed);
762
+
763
+ break;
764
+
765
+ //Uses the jQuery `fadeIn` special effect
766
+ case "fadeIn":
767
+
768
+ elem.fadeIn(self.options.showEffectSpeed);
769
+
770
+ break;
771
+
772
+ //If none of the above options were passed, then a `jQueryUI show effect` is expected
773
+ default:
774
+
775
+ elem.show();
776
+
777
+ break;
778
+
779
+ }
780
+
781
+ }
782
+
783
+ // If the current subheader parent element is a header
784
+ if(elem.parent().is(headerClass)) {
785
+
786
+ // Hides all non-active sub-headers
787
+ self.hide($(subheaderClass).not(elem));
788
+
789
+ }
790
+
791
+ // If the current subheader parent element is not a header
792
+ else {
793
+
794
+ // Hides all non-active sub-headers
795
+ self.hide($(subheaderClass).not(elem.closest(headerClass).find(subheaderClass).not(elem.siblings())));
796
+
797
+ }
798
+
799
+ // Maintains chainablity
800
+ return self;
801
+
802
+ },
803
+
804
+ // Hide
805
+ // ----
806
+ // Closes the current sub-header
807
+ hide: function(elem) {
808
+
809
+ // Stores the plugin context in the `self` variable
810
+ var self = this;
811
+
812
+ //Determines what jQuery effect to use
813
+ switch (self.options.hideEffect) {
814
+
815
+ // Uses `no effect`
816
+ case "none":
817
+
818
+ elem.hide();
819
+
820
+ break;
821
+
822
+ // Uses the jQuery `hide` special effect
823
+ case "hide":
824
+
825
+ elem.hide(self.options.hideEffectSpeed);
826
+
827
+ break;
828
+
829
+ // Uses the jQuery `slideUp` special effect
830
+ case "slideUp":
831
+
832
+ elem.slideUp(self.options.hideEffectSpeed);
833
+
834
+ break;
835
+
836
+ // Uses the jQuery `fadeOut` special effect
837
+ case "fadeOut":
838
+
839
+ elem.fadeOut(self.options.hideEffectSpeed);
840
+
841
+ break;
842
+
843
+ // If none of the above options were passed, then a `jqueryUI hide effect` is expected
844
+ default:
845
+
846
+ elem.hide();
847
+
848
+ break;
849
+
850
+ }
851
+
852
+ // Maintains chainablity
853
+ return self;
854
+ },
855
+
856
+ // _triggerShow
857
+ // ------------
858
+ // Determines what elements get shown on scroll and click
859
+ _triggerShow: function(elem, scroll) {
860
+
861
+ var self = this;
862
+
863
+ // If the current element's parent is a header element or the next element is a nested subheader element
864
+ if(elem.parent().is(headerClass) || elem.next().is(subheaderClass)) {
865
+
866
+ // Shows the next sub-header element
867
+ self.show(elem.next(subheaderClass), scroll);
868
+
869
+ }
870
+
871
+ // If the current element's parent is a subheader element
872
+ else if(elem.parent().is(subheaderClass)) {
873
+
874
+ // Shows the parent sub-header element
875
+ self.show(elem.parent(), scroll);
876
+
877
+ }
878
+
879
+ // Maintains chainability
880
+ return self;
881
+
882
+ },
883
+
884
+ // _addCSSClasses
885
+ // --------------
886
+ // Adds CSS classes to the newly generated table of contents HTML
887
+ _addCSSClasses: function() {
888
+
889
+ // If the user wants a jqueryUI theme
890
+ if(this.options.theme === "jqueryui") {
891
+
892
+ this.focusClass = "ui-state-default";
893
+
894
+ this.hoverClass = "ui-state-hover";
895
+
896
+ //Adds the default styling to the dropdown list
897
+ this.element.addClass("ui-widget").find(".toc-title").addClass("ui-widget-header").end().find("li").addClass("ui-widget-content");
898
+
899
+ }
900
+
901
+ // If the user wants a twitterBootstrap theme
902
+ else if(this.options.theme === "bootstrap") {
903
+
904
+ this.element.find(headerClass + "," + subheaderClass).addClass("nav nav-list");
905
+
906
+ this.focusClass = "active";
907
+
908
+ }
909
+
910
+ // If a user does not want a prebuilt theme
911
+ else {
912
+
913
+ // Adds more neutral classes (instead of jqueryui)
914
+
915
+ this.focusClass = tocFocusClassName;
916
+
917
+ this.hoverClass = tocHoverClassName;
918
+
919
+ }
920
+
921
+ //Maintains chainability
922
+ return this;
923
+
924
+ },
925
+
926
+ // setOption
927
+ // ---------
928
+ // Sets a single Tocify option after the plugin is invoked
929
+ setOption: function() {
930
+
931
+ // Calls the jQueryUI Widget Factory setOption method
932
+ $.Widget.prototype._setOption.apply(this, arguments);
933
+
934
+ },
935
+
936
+ // setOptions
937
+ // ----------
938
+ // Sets a single or multiple Tocify options after the plugin is invoked
939
+ setOptions: function() {
940
+
941
+ // Calls the jQueryUI Widget Factory setOptions method
942
+ $.Widget.prototype._setOptions.apply(this, arguments);
943
+
944
+ },
945
+
946
+ // _scrollTo
947
+ // ---------
948
+ // Scrolls to a specific element
949
+ _scrollTo: function(elem) {
950
+
951
+ var self = this,
952
+ duration = self.options.smoothScroll || 0,
953
+ scrollTo = self.options.scrollTo,
954
+ currentDiv = $('div[data-unique="' + elem.attr("data-unique") + '"]');
955
+
956
+ if(!currentDiv.length) {
957
+
958
+ return self;
959
+
960
+ }
961
+
962
+ // Once all animations on the page are complete, this callback function will be called
963
+ $("html, body").promise().done(function() {
964
+
965
+ // Animates the html and body element scrolltops
966
+ $("html, body").animate({
967
+
968
+ // Sets the jQuery `scrollTop` to the top offset of the HTML div tag that matches the current list item's `data-unique` tag
969
+ "scrollTop": currentDiv.offset().top - ($.isFunction(scrollTo) ? scrollTo.call() : scrollTo) + "px"
970
+
971
+ }, {
972
+
973
+ // Sets the smoothScroll animation time duration to the smoothScrollSpeed option
974
+ "duration": duration
975
+
976
+ });
977
+
978
+ });
979
+
980
+ // Maintains chainability
981
+ return self;
982
+
983
+ }
984
+
985
+ });
986
+
987
+ })); //end of plugin
@@ -0,0 +1,62 @@
1
+ /*
2
+ * jquery.tocify.css 1.9.0
3
+ * Author: @gregfranko
4
+ */
5
+
6
+ /* The Table of Contents container element */
7
+ .tocify {
8
+ width: 20%;
9
+ max-height: 90%;
10
+ overflow: auto;
11
+ margin-left: 2%;
12
+ position: fixed;
13
+ border: 1px solid #ccc;
14
+ webkit-border-radius: 6px;
15
+ moz-border-radius: 6px;
16
+ border-radius: 6px;
17
+ }
18
+
19
+ /* The Table of Contents is composed of multiple nested unordered lists. These styles remove the default styling of an unordered list because it is ugly. */
20
+ .tocify ul, .tocify li {
21
+ list-style: none;
22
+ margin: 0;
23
+ padding: 0;
24
+ border: none;
25
+ line-height: 30px;
26
+ }
27
+
28
+ /* Top level header elements */
29
+ .tocify-header {
30
+ text-indent: 10px;
31
+ }
32
+
33
+ /* Top level subheader elements. These are the first nested items underneath a header element. */
34
+ .tocify-subheader {
35
+ text-indent: 20px;
36
+ display: none;
37
+ }
38
+
39
+ /* Makes the font smaller for all subheader elements. */
40
+ .tocify-subheader li {
41
+ font-size: 12px;
42
+ }
43
+
44
+ /* Further indents second level subheader elements. */
45
+ .tocify-subheader .tocify-subheader {
46
+ text-indent: 30px;
47
+ }
48
+
49
+ /* Further indents third level subheader elements. You can continue this pattern if you have more nested elements. */
50
+ .tocify-subheader .tocify-subheader .tocify-subheader {
51
+ text-indent: 40px;
52
+ }
53
+
54
+ /* Twitter Bootstrap Override Style */
55
+ .nav-list > li > a, .nav-list .nav-header {
56
+ margin: 0px;
57
+ }
58
+
59
+ /* Twitter Bootstrap Override Style */
60
+ .nav-list > li > a {
61
+ padding: 5px;
62
+ }
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-tocify
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.9.0
5
+ platform: ruby
6
+ authors:
7
+ - peterthecoon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jquery-rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Rails gem of gfranko/jquery.tocify.js
28
+ email:
29
+ - pierre.prevoteau@askida.fr
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - Gemfile
36
+ - README.md
37
+ - Rakefile
38
+ - lib/rails-tocify.rb
39
+ - lib/rails-tocify/version.rb
40
+ - rails-tocify.gemspec
41
+ - vendor/assets/javascripts/tocify.js
42
+ - vendor/assets/stylesheets/tocify.css
43
+ homepage: https://github.com/peterthecoon/rails-tocify
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.2.2
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: gfranko/jquery.tocify.js
67
+ test_files: []