diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..196c64b --- /dev/null +++ b/.jshintignore @@ -0,0 +1,4 @@ +node_modules/ +public/ +vendor/ +tests/ \ No newline at end of file diff --git a/README.md b/README.md index 1606f99..bd1be18 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,12 @@ Run tests: npm test ``` +Lint code: + +``` +jsxhint --jsx-only . +``` + ### Load unpacked in Chrome 1. Clone repository to disk diff --git a/assets/js/app.js b/assets/js/app.jsx similarity index 81% rename from assets/js/app.js rename to assets/js/app.jsx index 809c350..19cbf2a 100644 --- a/assets/js/app.js +++ b/assets/js/app.jsx @@ -5,7 +5,7 @@ require('bootstrap'); var React = require('react'); // React components -var WakaTime = require('./components/WakaTime.react'); +var WakaTime = require('./components/WakaTime.jsx'); React.render( , diff --git a/assets/js/components/Alert.react.js b/assets/js/components/Alert.jsx similarity index 100% rename from assets/js/components/Alert.react.js rename to assets/js/components/Alert.jsx diff --git a/assets/js/components/MainList.react.js b/assets/js/components/MainList.jsx similarity index 99% rename from assets/js/components/MainList.react.js rename to assets/js/components/MainList.jsx index 18313ec..537c122 100644 --- a/assets/js/components/MainList.react.js +++ b/assets/js/components/MainList.jsx @@ -1,3 +1,5 @@ +/* global chrome */ + var React = require('react'); var MainList = React.createClass({ diff --git a/assets/js/components/Navbar.react.js b/assets/js/components/Navbar.jsx similarity index 100% rename from assets/js/components/Navbar.react.js rename to assets/js/components/Navbar.jsx diff --git a/assets/js/components/Options.react.js b/assets/js/components/Options.jsx similarity index 97% rename from assets/js/components/Options.react.js rename to assets/js/components/Options.jsx index 6f0eac5..33d5bde 100644 --- a/assets/js/components/Options.react.js +++ b/assets/js/components/Options.jsx @@ -1,3 +1,5 @@ +/* global chrome */ + var React = require('react'); var ReactAddons = require('react/addons'); var ReactCSSTransitionGroup = ReactAddons.addons.CSSTransitionGroup; @@ -5,8 +7,8 @@ var ReactCSSTransitionGroup = ReactAddons.addons.CSSTransitionGroup; var config = require('../config'); // React components -var Alert = require('./Alert.react'); -var SitesList = require('./SitesList.react'); +var Alert = require('./Alert.jsx'); +var SitesList = require('./SitesList.jsx'); /** * One thing to keep in mind is that you cannot use this.refs.blacklist if @@ -113,7 +115,7 @@ var Options = React.createClass({ var that = this; var alert = function() { - if(that.state.displayAlert == true){ + if(that.state.displayAlert === true){ setTimeout(function () { that.setState({displayAlert:false}); diff --git a/assets/js/components/SitesList.react.js b/assets/js/components/SitesList.jsx similarity index 100% rename from assets/js/components/SitesList.react.js rename to assets/js/components/SitesList.jsx diff --git a/assets/js/components/WakaTime.react.js b/assets/js/components/WakaTime.jsx similarity index 94% rename from assets/js/components/WakaTime.react.js rename to assets/js/components/WakaTime.jsx index 82edb31..e773b96 100644 --- a/assets/js/components/WakaTime.react.js +++ b/assets/js/components/WakaTime.jsx @@ -1,11 +1,13 @@ +/* global chrome */ + var React = require("react"); var $ = require('jquery'); var config = require('../config'); // React components -var NavBar = require('./NavBar.react'); -var MainList = require('./MainList.react'); +var NavBar = require('./NavBar.jsx'); +var MainList = require('./MainList.jsx'); // Core var WakaTimeOriginal = require('../core/WakaTime'); @@ -30,7 +32,7 @@ var WakaTime = React.createClass({ componentDidMount: function() { - var wakatime = new WakaTimeOriginal; + var wakatime = new WakaTimeOriginal(); var that = this; @@ -62,7 +64,7 @@ var WakaTime = React.createClass({ wakatime.getTotalTimeLoggedToday().done(function(grand_total) { that.setState({ - totalTimeLoggedToday: grand_total['text'] + totalTimeLoggedToday: grand_total.text }); }); } @@ -86,7 +88,7 @@ var WakaTime = React.createClass({ deferredObject.resolve(that); }, - error: (xhr, status, err) => { + error: function(xhr, status, err) { console.error(config.logoutUserUrl, status, err.toString()); diff --git a/assets/js/config.js b/assets/js/config.js index 4d3fbc8..92ae7df 100644 --- a/assets/js/config.js +++ b/assets/js/config.js @@ -57,4 +57,4 @@ var config = { } }; -export default config; \ No newline at end of file +module.exports = config; \ No newline at end of file diff --git a/assets/js/core/WakaTime.js b/assets/js/core/WakaTime.js index 52ddb8a..98084f5 100644 --- a/assets/js/core/WakaTime.js +++ b/assets/js/core/WakaTime.js @@ -1,3 +1,6 @@ +/* global chrome */ +//jshint esnext:true + var $ = require('jquery'); var moment = require('moment'); @@ -34,7 +37,7 @@ class WakaTime { dataType: 'json', success: (data) => { - deferredObject.resolve(data.data[0]['grand_total']); + deferredObject.resolve(data.data[0].grand_total); }, error: (xhr, status, err) => { diff --git a/assets/js/devtools.js b/assets/js/devtools.js index 330130e..3638189 100644 --- a/assets/js/devtools.js +++ b/assets/js/devtools.js @@ -1,3 +1,5 @@ +/* global chrome */ + // Create a connection to the background page var backgroundPageConnection = chrome.runtime.connect({ name: "devtools-page" diff --git a/assets/js/events.js b/assets/js/events.js index 85ae37c..aef16d4 100644 --- a/assets/js/events.js +++ b/assets/js/events.js @@ -1,8 +1,10 @@ +/* global chrome */ + // Core var WakaTime = require("./core/WakaTime"); // initialize class -var wakatime = new WakaTime; +var wakatime = new WakaTime(); // Holds currently open connections (ports) with devtools // Uses tabId as index key. diff --git a/assets/js/helpers/changeExtensionIcon.js b/assets/js/helpers/changeExtensionIcon.js index 655fcff..7f7cdf9 100644 --- a/assets/js/helpers/changeExtensionIcon.js +++ b/assets/js/helpers/changeExtensionIcon.js @@ -1,3 +1,5 @@ +/* global chrome */ + var config = require('../config'); /** @@ -6,7 +8,9 @@ var config = require('../config'); * * @param color */ -function changeExtensionIcon(color = '') { +function changeExtensionIcon(color) { + + color = color ? color : ''; var path = null; @@ -43,4 +47,4 @@ function changeExtensionIcon(color = '') { } -export default changeExtensionIcon; +module.exports = changeExtensionIcon; diff --git a/assets/js/helpers/changeExtensionState.js b/assets/js/helpers/changeExtensionState.js index 0db5deb..08c68d2 100644 --- a/assets/js/helpers/changeExtensionState.js +++ b/assets/js/helpers/changeExtensionState.js @@ -10,7 +10,7 @@ var in_array = require('./in_array'); * * @param state */ -function changeExtensionState(state){ +function changeExtensionState(state) { if (! in_array(state, config.states)) { throw new Error('Not a valid state!'); } @@ -31,4 +31,4 @@ function changeExtensionState(state){ } } -export default changeExtensionState; \ No newline at end of file +module.exports = changeExtensionState; \ No newline at end of file diff --git a/assets/js/helpers/changeExtensionTooltip.js b/assets/js/helpers/changeExtensionTooltip.js index f1fe0fe..ac6620d 100644 --- a/assets/js/helpers/changeExtensionTooltip.js +++ b/assets/js/helpers/changeExtensionTooltip.js @@ -1,3 +1,5 @@ +/* global chrome */ + var config = require('../config'); /** @@ -17,4 +19,4 @@ function changeExtensionTooltip(text) { chrome.browserAction.setTitle({title: text}); } -export default changeExtensionTooltip; \ No newline at end of file +module.exports = changeExtensionTooltip; \ No newline at end of file diff --git a/assets/js/helpers/currentTimestamp.js b/assets/js/helpers/currentTimestamp.js index 4d99920..d04985f 100644 --- a/assets/js/helpers/currentTimestamp.js +++ b/assets/js/helpers/currentTimestamp.js @@ -3,8 +3,8 @@ * * @returns {number} */ -function currentTimestamp(){ +function currentTimestamp() { return Math.round((new Date()).getTime() / 1000); } -export default currentTimestamp; +module.exports = currentTimestamp; diff --git a/assets/js/helpers/getDomainFromUrl.js b/assets/js/helpers/getDomainFromUrl.js index 56ab249..4195875 100644 --- a/assets/js/helpers/getDomainFromUrl.js +++ b/assets/js/helpers/getDomainFromUrl.js @@ -10,4 +10,4 @@ function getDomainFromUrl(url) { return parts[0] + "//" + parts[2]; } -export default getDomainFromUrl; \ No newline at end of file +module.exports = getDomainFromUrl; \ No newline at end of file diff --git a/assets/js/helpers/in_array.js b/assets/js/helpers/in_array.js index e47cd1e..66ccbde 100644 --- a/assets/js/helpers/in_array.js +++ b/assets/js/helpers/in_array.js @@ -9,11 +9,10 @@ function in_array(needle, haystack) { for (var i = 0; i < haystack.length; i ++) { if (needle == haystack[i]) { return true; - break; } } return false; } -export default in_array; \ No newline at end of file +module.exports = in_array; \ No newline at end of file diff --git a/assets/js/options.js b/assets/js/options.jsx similarity index 82% rename from assets/js/options.js rename to assets/js/options.jsx index 71a55cf..20316d1 100644 --- a/assets/js/options.js +++ b/assets/js/options.jsx @@ -5,7 +5,7 @@ require('bootstrap'); var React = require('react'); // React components -var Options = require('./components/Options.react'); +var Options = require('./components/Options.jsx'); React.render( , diff --git a/gulpfile.js b/gulpfile.js index 34f9d67..6a9c93d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -19,8 +19,8 @@ elixir(function (mix) { mix.copy('vendor/bower_components/font-awesome/less', 'assets/less/font-awesome'); mix.copy('vendor/bower_components/font-awesome/fonts', 'public/fonts'); mix.less('app.less'); - mix.browserify('app.js', null, 'assets/js'); + mix.browserify('app.jsx', 'public/js/app.js', 'assets/js'); mix.browserify('events.js', 'public/js/events.js', 'assets/js'); - mix.browserify('options.js', 'public/js/options.js', 'assets/js'); + mix.browserify('options.jsx', 'public/js/options.js', 'assets/js'); mix.browserify('devtools.js', 'public/js/devtools.js', 'assets/js'); }); diff --git a/package.json b/package.json index e6482d0..2d2d0e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "scripts": { - "test": "jest && mocha --compilers js:mocha-traceur tests/**/*.spec.js", + "test": "jest && mocha --compilers js:mocha-traceur tests/**/*.spec.js && jsxhint --jsx-only .", "start": "npm install && bower install && gulp" }, "jest": { @@ -8,24 +8,23 @@ "jest.js" ], "testDirectoryName": "tests", - "scriptPreprocessor": "/node_modules/babel-jest", "unmockedModulePathPatterns": [ "/node_modules/react" ] }, "private": true, "devDependencies": { - "babel-jest": "^5.3.0", + "chai": "^3.0.0", "gulp": "^3.8.8", "jest-cli": "^0.4.12", + "jshint": "^2.8.0", + "jsxhint": "^0.15.1", "laravel-elixir": "*", - "react-tools": "^0.13.3", "mocha": "^2.2.5", "mocha-sinon": "^1.1.4", "mocha-traceur": "^2.1.0", - "sinon": "^1.14.1", - "babel": "^5.5.6", - "chai": "^3.0.0" + "react-tools": "^0.13.3", + "sinon": "^1.14.1" }, "dependencies": { "bootstrap": "^3.3.4", @@ -33,5 +32,17 @@ "jquery": "^2.1.3", "moment": "^2.10.3", "react": "^0.13.3" + }, + "jshintConfig": { + "asi": false, + "browser": true, + "curly": false, + "expr": true, + "indent": 4, + "loopfunc": true, + "node": true, + "trailing": true, + "undef": true, + "white": true } } diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..b3b109b --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,35395 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = $(document.body).height() + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); + +},{}],15:[function(require,module,exports){ +/* ======================================================================== + * Bootstrap: alert.js v3.3.4 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.4' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +},{}],16:[function(require,module,exports){ +/* ======================================================================== + * Bootstrap: button.js v3.3.4 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.4' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state = state + 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked') && this.$element.hasClass('active')) changed = false + else $parent.find('.active').removeClass('active') + } + if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + } + + if (changed) this.$element.toggleClass('active') + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + Plugin.call($btn, 'toggle') + e.preventDefault() + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +},{}],17:[function(require,module,exports){ +/* ======================================================================== + * Bootstrap: carousel.js v3.3.4 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.4' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +},{}],18:[function(require,module,exports){ +/* ======================================================================== + * Bootstrap: collapse.js v3.3.4 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.4' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +},{}],19:[function(require,module,exports){ +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.4 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.4' + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $('