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 = $('
').addClass(this.options.rootUlClass)
+ .addClass(this.options.ulClass)
+ .appendTo(this.$element);
+ // Add heading
+ if (this.options.heading) {
+ $('').addClass('toc-heading')
+ .wrapInner($('').attr('href', '#').html(this.options.heading))
+ .appendTo($toc);
+ }
+
+ for (var i = 0; i < numHeadings; i++) {
+ // Generate Id
+ var id = this.generateHeadingId(headings[i]),
+ numbering = String($(headings[i]).data('numbering')).split('.'),
+ $a = $('').html($(headings[i]).text())
+ .addClass(this.options.prefixLinkClass + numbering.length)
+ .attr('href', '#' + id);
+
+ // Add anchor icon to heading
+ $('').addClass('toc-anchor')
+ .html('#')
+ .attr('href', '#' + id)
+ .hide()
+ .appendTo(headings[i]);
+ $(headings[i]).on('mouseover', function() {
+ $(this).find('.toc-anchor').show();
+ }).on('mouseout', function() {
+ $(this).find('.toc-anchor').hide();
+ });
+
+ if (numbering.length == 1) {
+ var $li = $('').wrapInner($a).appendTo($toc);
+ } else {
+ var last = numbering.pop(),
+ n = numbering.join('.'),
+ uls = numberingMap[n].find('ul'),
+ $ul = uls.length > 0 ? uls.get(0) : $('').addClass(this.options.ulClass).appendTo(numberingMap[n]),
+ $li = $('').wrapInner($a).appendTo($ul);
+
+ numbering.push(last);
+ }
+
+ numberingMap[numbering.join('.')] = $li;
+
+ this.prependIndexing(i, $a);
+ }
+ },
+
+ /**
+ * Generate heading Id
+ *
+ * @param {Number} heading
+ * @return {String}
+ */
+ generateHeadingId: function(heading) {
+ if (!$(heading).attr('id')) {
+ var id = $(heading)
+ .text()
+ .toLowerCase()
+ .replace(/\s+|\/|\\/g, '-')
+ .replace(/á|à|ạ|ả|ã|ă|ắ|ằ|ặ|ẳ|ẵ|â|ấ|ầ|ậ|ẩ|ẫ|ä/g, 'a')
+ .replace(/đ/g, 'd')
+ .replace(/é|è|ẹ|ẻ|ẽ|ê|ế|ề|ệ|ể|ễ/g, 'e')
+ .replace(/í|ì|ị|ỉ|ĩ/g, 'i')
+ .replace(/ó|ò|ọ|ỏ|õ|ô|ố|ồ|ộ|ổ|ỗ|ơ|ớ|ờ|ợ|ở|ỡ/g, 'o')
+ .replace(/ú|ù|ụ|ủ|ũ|ư|ứ|ừ|ự|ử|ữ/g, 'u')
+ .replace(/ý|ỳ|ỵ|ỷ|ỹ/g, 'y')
+ .replace(/[^a-z0-9-]/g, '');
+
+ var found = true, counter = 0;
+ while (found) {
+ found = $('#' + id + (counter == 0 ? '' : '-' + counter)).length > 0;
+ if (found) {
+ counter++;
+ } else {
+ id = id + (counter == 0 ? '' : '-' + counter);
+ }
+ }
+
+ $(heading).attr('id', id);
+ return id;
+ }
+
+ return $(heading).attr('id');
+ },
+
+ /**
+ * Prepend indexing string to link/heading
+ *
+ * @param {Number} index
+ * @param {HTMLElement} linkElement
+ */
+ prependIndexing: function(index, linkElement) {
+ var heading = this.headings[index],
+ tagNumber = parseInt($(heading).data('tagNumber')),
+ format = this.getIndexingFormat(tagNumber);
+ if (null == format) {
+ return;
+ }
+ var numbering = String($(heading).data('numbering')).split('.'), n = numbering.length, converted = [], j = 0;
+ for (var i = 0; i < n; i++) {
+ j = i + (tagNumber - n) + 1;
+ format = this.getIndexingFormat(j);
+ if (format) {
+ converted.push(this.convertIndexing(numbering[i], format));
+ }
+ }
+
+ if (converted.length > 0) {
+ var text = converted.join('. ') + '. ';
+ $(linkElement).prepend(text);
+ $(heading).prepend(text);
+ }
+ },
+
+ /**
+ * Get the indexing format for given heading level
+ *
+ * @param {Number} level Can be 1, 2, ..., 6
+ * @return {String} Can be null or one of 'number', 'lowerAlphabet', 'upperAlphabet', 'lowerRoman', 'upperRoman'
+ */
+ getIndexingFormat: function(level) {
+ if ('object' == typeof this.options.indexingFormats) {
+ return this.options.indexingFormats['h' + level] ? this.options.indexingFormats['h' + level] : null;
+ }
+
+ if ('string' == typeof this.options.indexingFormats) {
+ if (['upperAlphabet', 'lowerAlphabet', 'number', 'upperRoman', 'lowerRoman'].indexOf(this.options.indexingFormats) != -1) {
+ return this.options.indexingFormats;
+ }
+
+ // indexingFormats defines format for each heading level (1AaIi, 111111, for example)
+ if (this.options.indexingFormats.length < level) {
+ return null;
+ }
+ switch (this.options.indexingFormats[level - 1]) {
+ case '1':
+ case 1:
+ return 'number';
+ case 'A':
+ return 'upperAlphabet';
+ case 'a':
+ return 'lowerAlphabet';
+ case 'I':
+ return 'upperRoman';
+ case 'i':
+ return 'lowerRoman';
+ default:
+ return null;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Format an indexing number in given type
+ *
+ * @param {Number} number
+ * @param {String} type Can be one of supported formats: number, lowerAlphabet, upperAlphabet, lowerRoman, upperRoman
+ * @returns {String}
+ */
+ convertIndexing: function(number, type) {
+ var lowerChars = 'abcdefghijklmnopqrstuvwxyz', upperChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', length = lowerChars.length;
+ switch (type) {
+ case 'upperAlphabet':
+ case 'A':
+ return (number > length) ? upperChars[number % length - 1] : upperChars[number - 1];
+
+ case 'lowerAlphabet':
+ case 'a':
+ return (number > length) ? lowerChars[number % length - 1] : lowerChars[number - 1];
+
+ case 'number':
+ case '1':
+ case 1:
+ return number;
+
+ case 'upperRoman':
+ case 'I':
+ return this.convertToRomanNumeral(number);
+
+ case 'lowerRoman':
+ case 'i':
+ return this.convertToRomanNumeral(number).toLowerCase();
+
+ default:
+ return '_';
+ }
+ },
+
+ /**
+ * Convert a number to Roman numeral
+ *
+ * @param {Number} number
+ * @return {String}
+ */
+ convertToRomanNumeral: function(number) {
+ if (!+number) {
+ return '';
+ }
+ var digits = String(+number).split(''),
+ key = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM',
+ '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC',
+ '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
+ roman = '',
+ i = 3;
+ while (i--) {
+ roman = (key[+digits.pop() + (i * 10)] || '') + roman;
+ }
+ return Array(+digits.join('') + 1).join('M') + roman;
+ }
+ };
+
+ // Plugin definition
+
+ $.fn.toc = function(options) {
+ return this.each(function() {
+ var $this = $(this), data = $this.data('toc');
+ if (!data) {
+ $this.data('toc', (data = new Toc(this, options)));
+ }
+ });
+ };
+
+ $.fn.toc.Constructor = Toc;
+}(window.jQuery));
diff --git a/public/js/toc.min.js b/public/js/toc.min.js
new file mode 100644
index 0000000..6f8b43a
--- /dev/null
+++ b/public/js/toc.min.js
@@ -0,0 +1,11 @@
+/**
+ * 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
+ */
+
+!function(a){var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULT_OPTIONS,d),this.headings=[],this.$element.addClass(this.options.elementClass);var e=this;a(this.options.selector).each(function(b,c){a(c).data("tagNumber",parseInt(c.tagName.substring(1))).data("index",1).data("numbering","1"),e.headings.push(c)}),this.headings.length>0&&this.render()};b.DEFAULT_OPTIONS={selector:"h1, h2, h3, h4, h5, h6",elementClass:"toc",rootUlClass:"toc-ul-root",ulClass:"toc-ul",prefixLinkClass:"toc-link-",heading:null,indexingFormats:{}},b.prototype={constructor:b,render:function(){for(var b={},c=this.headings,d=this.headings.length,e=0;d>e;e++){var f=a(c[e]).data("tagNumber");if(0==e)b[c[0].tagName]=a(c[0]);else{var g=a(c[e-1]).data("tagNumber"),h=String(a(c[e-1]).data("numbering")).split(".");switch(!0){case f==g:var i=a(c[e-1]).data("index")+1;a(c[e]).data("index",i),1==h.length?a(c[e]).data("numbering",parseInt(h[0])+1):(h.pop(),h.push(i),a(c[e]).data("numbering",h.join("."))),b[c[e].tagName]=a(c[e]);break;case f>g:h.push("1"),a(c[e]).data("index",1).data("numbering",h.join(".")),b[c[e].tagName]=a(c[e]);break;case g>f:var j=b[c[e].tagName],k=String(a(j).data("numbering")).split("."),i=a(j).data("index")+1;a(c[e]).data("index",i),1==k.length?a(c[e]).data("numbering",parseInt(k[0])+1):(k.pop(),k.push(i),a(c[e]).data("numbering",k.join("."))),b[c[e].tagName]=a(c[e])}}}var l={},m=a("").addClass(this.options.rootUlClass).addClass(this.options.ulClass).appendTo(this.$element);this.options.heading&&a("").addClass("toc-heading").wrapInner(a("").attr("href","#").html(this.options.heading)).appendTo(m);for(var e=0;d>e;e++){var n=this.generateHeadingId(c[e]),o=String(a(c[e]).data("numbering")).split("."),p=a("").html(a(c[e]).text()).addClass(this.options.prefixLinkClass+o.length).attr("href","#"+n);if(a("").addClass("toc-anchor").html("#").attr("href","#"+n).hide().appendTo(c[e]),a(c[e]).on("mouseover",function(){a(this).find(".toc-anchor").show()}).on("mouseout",function(){a(this).find(".toc-anchor").hide()}),1==o.length)var q=a("").wrapInner(p).appendTo(m);else{var r=o.pop(),s=o.join("."),t=l[s].find("ul"),u=t.length>0?t.get(0):a("").addClass(this.options.ulClass).appendTo(l[s]),q=a("").wrapInner(p).appendTo(u);o.push(r)}l[o.join(".")]=q,this.prependIndexing(e,p)}},generateHeadingId:function(b){if(!a(b).attr("id")){for(var c=a(b).text().toLowerCase().replace(/\s+|\/|\\/g,"-").replace(/á|à|ạ|ả|ã|ă|ắ|ằ|ặ|ẳ|ẵ|â|ấ|ầ|ậ|ẩ|ẫ|ä/g,"a").replace(/đ/g,"d").replace(/é|è|ẹ|ẻ|ẽ|ê|ế|ề|ệ|ể|ễ/g,"e").replace(/í|ì|ị|ỉ|ĩ/g,"i").replace(/ó|ò|ọ|ỏ|õ|ô|ố|ồ|ộ|ổ|ỗ|ơ|ớ|ờ|ợ|ở|ỡ/g,"o").replace(/ú|ù|ụ|ủ|ũ|ư|ứ|ừ|ự|ử|ữ/g,"u").replace(/ý|ỳ|ỵ|ỷ|ỹ/g,"y").replace(/[^a-z0-9-]/g,""),d=!0,e=0;d;)d=a("#"+c+(0==e?"":"-"+e)).length>0,d?e++:c+=0==e?"":"-"+e;return a(b).attr("id",c),c}return a(b).attr("id")},prependIndexing:function(b,c){var d=this.headings[b],e=parseInt(a(d).data("tagNumber")),f=this.getIndexingFormat(e);if(null!=f){for(var g=String(a(d).data("numbering")).split("."),h=g.length,i=[],j=0,k=0;h>k;k++)j=k+(e-h)+1,f=this.getIndexingFormat(j),f&&i.push(this.convertIndexing(g[k],f));if(i.length>0){var l=i.join(". ")+". ";a(c).prepend(l),a(d).prepend(l)}}},getIndexingFormat:function(a){if("object"==typeof this.options.indexingFormats)return this.options.indexingFormats["h"+a]?this.options.indexingFormats["h"+a]:null;if("string"==typeof this.options.indexingFormats){if(-1!=["upperAlphabet","lowerAlphabet","number","upperRoman","lowerRoman"].indexOf(this.options.indexingFormats))return this.options.indexingFormats;if(this.options.indexingFormats.lengthe?d[a%e-1]:d[a-1];case"lowerAlphabet":case"a":return a>e?c[a%e-1]:c[a-1];case"number":case"1":case 1:return a;case"upperRoman":case"I":return this.convertToRomanNumeral(a);case"lowerRoman":case"i":return this.convertToRomanNumeral(a).toLowerCase();default:return"_"}},convertToRomanNumeral:function(a){if(!+a)return"";for(var b=String(+a).split(""),c=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM","","X","XX","XXX","XL","L","LX","LXX","LXXX","XC","","I","II","III","IV","V","VI","VII","VIII","IX"],d="",e=3;e--;)d=(c[+b.pop()+10*e]||"")+d;return Array(+b.join("")+1).join("M")+d}},a.fn.toc=function(c){return this.each(function(){var d=a(this),e=d.data("toc");e||d.data("toc",e=new b(this,c))})},a.fn.toc.Constructor=b}(window.jQuery);
\ No newline at end of file
diff --git a/templates/default/header.html.ep b/templates/default/header.html.ep
index 3f1e4e4..f149c0b 100644
--- a/templates/default/header.html.ep
+++ b/templates/default/header.html.ep
@@ -5,7 +5,7 @@
<%
- foreach my $css (qw(bootstrap.min.css bootstrap-switch.min.css vroom.css)){
+ foreach my $css (qw(bootstrap.min.css bootstrap-switch.min.css toc-scroll.min.css vroom.css)){
%>
/css/<%= $css %>" rel="stylesheet" type="text/css">
<% } %>
diff --git a/templates/default/js_common.html.ep b/templates/default/js_common.html.ep
index 51e8d14..dc4489b 100644
--- a/templates/default/js_common.html.ep
+++ b/templates/default/js_common.html.ep
@@ -4,7 +4,7 @@
var api_key = '<%= ($self->session('key')) ? $self->session('key') : '' %>';
var roomName;
- <% foreach my $js (qw(jquery-1.11.2.min.js bootstrap.min.js notify-combined.min.js bootstrap-switch.min.js jquery.bootpag.min.js vroom.js)){
+ <% foreach my $js (qw(jquery-1.11.2.min.js bootstrap.min.js notify-combined.min.js bootstrap-switch.min.js jquery.bootpag.min.js toc.min.js vroom.js)){
%>
<% } %>