diff --git a/lib/Vroom/Constants.pm b/lib/Vroom/Constants.pm index e59a43d..98866d9 100644 --- a/lib/Vroom/Constants.pm +++ b/lib/Vroom/Constants.pm @@ -55,6 +55,9 @@ use constant COMPONENTS => { }, "bootpag" => { url => "http://botmonster.com/jquery-bootpag/" + }, + "tocjs" => { + url => "https://github.com/nghuuphuoc/tocjs" } }; diff --git a/public/css/toc-scroll.css b/public/css/toc-scroll.css new file mode 100644 index 0000000..c2561ba --- /dev/null +++ b/public/css/toc-scroll.css @@ -0,0 +1,48 @@ +/** + * TocJS (https://github.com/nghuuphuoc/tocjs) + * + * Generate a table of contents based on headings + * + * @author http://twitter.com/nghuuphuoc + * @copyright (c) 2013 - 2014 Nguyen Huu Phuoc + * @license MIT + */ +.toc { + border: 1px solid #ddd; + border-radius: 4px; } + .toc.affix { + position: static; } + .toc .nav > .active > a, .toc .nav > .active:hover > a, + .toc .nav > .active :focus > a { + border-right: 2px solid #428bca; } + .toc .nav .nav { + /* + * If you want to hide the sub UL elements, then add the following line + * display: none; + */ + display: block; + margin-bottom: 8px; } + .toc .nav .nav > li > a { + font-size: 90%; } + .toc .toc-heading { + color: #333333; + background-color: #f5f5f5; + border-bottom: 1px solid #dddddd; } + .toc .toc-link-2 { + padding-left: 25px; } + .toc .toc-link-3 { + padding-left: 50px; } + .toc .toc-link-4 { + padding-left: 75px; } + .toc .toc-link-5 { + padding-left: 100px; } + .toc .toc-link-6 { + padding-left: 125px; } + +@media screen and (min-width: 992px) and (min-height: 700px) { + .toc.affix { + position: fixed; } + .toc .nav > .active > ul { + display: block; } } +.toc .nav .nav { + display: none; } diff --git a/public/css/toc-scroll.min.css b/public/css/toc-scroll.min.css new file mode 100644 index 0000000..3eb3423 --- /dev/null +++ b/public/css/toc-scroll.min.css @@ -0,0 +1,12 @@ +/** + * TocJS v1.1.2 (http://github.com/nghuuphuoc/tocjs) + * + * Generate a table of contents based on headings + * + * @author http://twitter.com/nghuuphuoc + * @copyright (c) 2013 - 2014 Nguyen Huu Phuoc + * @license MIT + */ + + +.toc{border:1px solid #ddd;border-radius:4px}.toc.affix{position:static}.toc .nav>.active>a,.toc .nav>.active:hover>a,.toc .nav>.active :focus>a{border-right:2px solid #428bca}.toc .nav .nav{display:block;margin-bottom:8px}.toc .nav .nav>li>a{font-size:90%}.toc .toc-heading{color:#333;background-color:#f5f5f5;border-bottom:1px solid #ddd}.toc .toc-link-2{padding-left:25px}.toc .toc-link-3{padding-left:50px}.toc .toc-link-4{padding-left:75px}.toc .toc-link-5{padding-left:100px}.toc .toc-link-6{padding-left:125px}@media screen and (min-width:992px) and (min-height:700px){.toc.affix{position:fixed}.toc .nav>.active>ul{display:block}}.toc .nav .nav{display:none} \ No newline at end of file diff --git a/public/js/toc.js b/public/js/toc.js new file mode 100644 index 0000000..2e8efab --- /dev/null +++ b/public/js/toc.js @@ -0,0 +1,381 @@ +/** + * TocJS (https://github.com/nghuuphuoc/tocjs) + * + * Generate a table of contents based on headings + * + * @author http://twitter.com/nghuuphuoc + * @copyright (c) 2013 - 2014 Nguyen Huu Phuoc + * @license MIT + */ + +(function($) { + var Toc = function(element, options) { + this.$element = $(element); + this.options = $.extend({}, Toc.DEFAULT_OPTIONS, options); + this.headings = []; + + this.$element.addClass(this.options.elementClass); + + var that = this; + $(this.options.selector).each(function(index, node) { + $(node) + .data('tagNumber', parseInt(node.tagName.substring(1))) // 1...6 + .data('index', 1) + .data('numbering', '1'); + that.headings.push(node); + }); + + if (this.headings.length > 0) { + this.render(); + } + }; + + /** + * The default options + */ + Toc.DEFAULT_OPTIONS = { + selector: 'h1, h2, h3, h4, h5, h6', + elementClass: 'toc', + rootUlClass: 'toc-ul-root', + ulClass: 'toc-ul', + prefixLinkClass: 'toc-link-', + heading: null, + + /** + * Define the indexing formats for each heading level + * indexingFormats: { + * headingLevel: formatter + * } + * + * headingLevel can be 'h1', 'h2', ..., 'h6' + * formatter can be: + * - 'number', '1': The headings will be prefixed with number (1, 2, 3, ...) + * - 'upperAlphabet', 'A': Prefix headings with uppercase alphabetical characters (A, B, C, ...) + * - 'lowerAlphabet', 'a': Prefix headings with lowercase alphabetical characters (a, b, c, ...) + * - 'upperRoman', 'I': Prefix headings with uppercase Roman numerals (I, II, III, ...) + * - 'lowerRoman', 'i': Prefix headings with lowercase Roman numerals (i, ii, iii, ...) + * + * You can define different formatter for each heading level: + * indexingFormats: { + * 'h1': 'upperAlphabet', // 'A' + * 'h2': 'number', // '1' + * 'h3': 'lowerAlphabet' // 'a' + * } + * + * If you want to set indexing formats for levels: + * indexingFormats: formatter + * + * Example: + * indexingFormats: 'number' => Prefix all headings by number + * indexingFormats: '1AaIi' => Prefix 1st level heading by number + * Prefix 2nd level heading by uppercase character, and so forth. + */ + indexingFormats: {} + }; + + Toc.prototype = { + constructor: Toc, + + /** + * Render table of content + */ + render: function() { + var h = {}, + headings = this.headings, + numHeadings = this.headings.length; + + for (var i = 0; i < numHeadings; i++) { + var currTagNumber = $(headings[i]).data('tagNumber'); + if (i == 0) { + h[headings[0].tagName] = $(headings[0]); + } else { + var prevTagNumber = $(headings[i - 1]).data('tagNumber'), + prevNumbering = String($(headings[i - 1]).data('numbering')).split('.'); + + switch (true) { + // Case 1: + // The current heading is at the same level with previous one + // h3___________ <== previous heading + // h3___________ <== current heading + case (currTagNumber == prevTagNumber): + var index = $(headings[i - 1]).data('index') + 1; + $(headings[i]).data('index', index); + if (prevNumbering.length == 1) { + $(headings[i]).data('numbering', parseInt(prevNumbering[0]) + 1); + } else { + prevNumbering.pop(); + prevNumbering.push(index); + $(headings[i]).data('numbering', prevNumbering.join('.')); + } + h[headings[i].tagName] = $(headings[i]); + break; + + // Case 2: + // The current heading is child of the previous one + // h3____________ <== previous heading + // h4________ <== current heading + case (currTagNumber > prevTagNumber): + prevNumbering.push('1'); + $(headings[i]).data('index', 1) + .data('numbering', prevNumbering.join('.')); + h[headings[i].tagName] = $(headings[i]); + break; + + // Case 3: + // h2____________ <== (*) the closest heading that is at the same level with current one + // ... + // h4________ <== previous heading + // h2____________ <== current heading + case (currTagNumber < prevTagNumber): + // Get the cloest heading (*) + var closestHeading = h[headings[i].tagName]; + + // Now I come back the case 1 + var closestNumbering = String($(closestHeading).data('numbering')).split('.'), + index = $(closestHeading).data('index') + 1; + $(headings[i]).data('index', index); + if (closestNumbering.length == 1) { + $(headings[i]).data('numbering', parseInt(closestNumbering[0]) + 1); + } else { + closestNumbering.pop(); + closestNumbering.push(index); + $(headings[i]).data('numbering', closestNumbering.join('.')); + } + + h[headings[i].tagName] = $(headings[i]); + break; + + default: + break; + } + } + } + + var numberingMap = {}, + $toc = $('