Commit 6b261ad25f33db14361fe26eb915e990e1aa2981

Authored by Andrew Buss
1 parent 1653dec577

don't require login for email verification

Showing 5 changed files with 8 additions and 2325 deletions Inline Diff

all: styles/materialize.css styles/flashier.css fixjsstyle 1 1 all: styles/materialize.css styles/flashier.css fixjsstyle
2 2
3 JS_SOURCES = config.js $(wildcard scripts/*{Controller,Service,Directive}.js)
4
styles/materialize.css: .PHONY 3 5 styles/materialize.css: .PHONY
sassc sass/materialize.scss styles/materialize.css 4 6 sassc sass/materialize.scss styles/materialize.css
5 7
styles/flashier.css: sass/flashier.scss 6 8 styles/flashier.css: sass/flashier.scss
sassc sass/flashier.scss styles/flashier.css 7 9 sassc sass/flashier.scss styles/flashier.css
8 10
fixjsstyle: .PHONY 9 11 fixjsstyle: .PHONY
angular.module('flashy', [ 1 1 angular.module('flashy', [
'flashy.LoginController', 2 2 'flashy.LoginController',
'flashy.RootController', 3 3 'flashy.RootController',
'flashy.FeedController', 4 4 'flashy.FeedController',
'flashy.DeckController', 5 5 'flashy.DeckController',
'flashy.ClassAddController', 6 6 'flashy.ClassAddController',
'flashy.ClassDropController', 7 7 'flashy.ClassDropController',
'flashy.RequestResetController', 8 8 'flashy.RequestResetController',
'flashy.StudyController', 9 9 'flashy.StudyController',
'flashy.UserService', 10 10 'flashy.UserService',
'flashy.FlashcardDirective', 11 11 'flashy.FlashcardDirective',
'flashy.ResetPasswordController', 12 12 'flashy.ResetPasswordController',
'flashy.VerifyEmailController', 13 13 'flashy.VerifyEmailController',
'flashy.CardListController', 14 14 'flashy.CardListController',
'flashy.HelpController', 15 15 'flashy.HelpController',
'flashy.SettingsController', 16 16 'flashy.SettingsController',
'ngCookies']). 17 17 'ngCookies']).
config(function($stateProvider, $urlRouterProvider, $resourceProvider, $httpProvider, $locationProvider) { 18 18 config(function($stateProvider, $urlRouterProvider, $resourceProvider, $httpProvider, $locationProvider) {
'use strict'; 19 19 'use strict';
$httpProvider.defaults.withCredentials = true; 20 20 $httpProvider.defaults.withCredentials = true;
$httpProvider.defaults.xsrfCookieName = 'csrftoken'; 21 21 $httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; 22 22 $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$resourceProvider.defaults.stripTrailingSlashes = false; 23 23 $resourceProvider.defaults.stripTrailingSlashes = false;
var arrayMethods = Object.getOwnPropertyNames(Array.prototype); 24 24 var arrayMethods = Object.getOwnPropertyNames(Array.prototype);
arrayMethods.forEach(attachArrayMethodsToNodeList); 25 25 arrayMethods.forEach(attachArrayMethodsToNodeList);
function attachArrayMethodsToNodeList(methodName) { 26 26 function attachArrayMethodsToNodeList(methodName) {
if (methodName !== 'length') { 27 27 if (methodName !== 'length') {
NodeList.prototype[methodName] = Array.prototype[methodName]; 28 28 NodeList.prototype[methodName] = Array.prototype[methodName];
} 29 29 }
} 30 30 }
31 31
$httpProvider.interceptors.push(function($q, $rootScope) { 32 32 $httpProvider.interceptors.push(function($q, $rootScope) {
return { 33 33 return {
'responseError': function(rejection) { // need a better redirect 34 34 'responseError': function(rejection) { // need a better redirect
if (rejection.status >= 500) { 35 35 if (rejection.status >= 500) {
console.log('got error'); 36 36 console.log('got error');
console.log(rejection); 37 37 console.log(rejection);
$rootScope.$broadcast('server_error', rejection); 38 38 $rootScope.$broadcast('server_error', rejection);
} 39 39 }
if (rejection.status == 403) { 40 40 if (rejection.status == 403) {
console.log(rejection); 41 41 console.log(rejection);
if (rejection.data && rejection.data.detail == 'Please verify your email before continuing') { 42 42 if (rejection.data && rejection.data.detail == 'Please verify your email before continuing') {
UserService.showLockedMessage(); 43 43 UserService.showLockedMessage();
UserService.logout(); 44 44 UserService.logout();
} 45 45 }
} 46 46 }
return $q.reject(rejection); 47 47 return $q.reject(rejection);
} 48 48 }
}; 49 49 };
}); 50 50 });
$locationProvider.html5Mode(true); 51 51 $locationProvider.html5Mode(true);
$urlRouterProvider.otherwise('/404'); 52 52 $urlRouterProvider.otherwise('/404');
var auth_resolve = { 53 53 var auth_resolve = {
authorize: function($q, $rootScope, $state, $stateParams, UserService) { 54 54 authorize: function($q, $rootScope, $state, $stateParams, UserService) {
55
console.log('do we need to authorize a user for', $rootScope.nextState.name); 56 55 console.log('do we need to authorize a user for', $rootScope.nextState.name);
if (UserService.noAuthRequired($rootScope.nextState)) { 57 56 if (UserService.noAuthRequired($rootScope.nextState)) {
console.log('no auth required for', $rootScope.nextState.name); 58 57 console.log('no auth required for', $rootScope.nextState.name);
return UserService.getUserData(); 59 58 return UserService.getUserData();
} 60 59 }
console.log('resolving user before continuing to ' + $rootScope.nextState.name); 61 60 console.log('resolving user before continuing to ' + $rootScope.nextState.name);
var redirectAsNeeded = function() { 62 61 var redirectAsNeeded = function() {
if (!UserService.isLoggedIn()) { 63 62 if (!UserService.isLoggedIn()) {
console.log(UserService.getUserData()); 64 63 console.log(UserService.getUserData());
console.log('making the user log in'); 65 64 console.log('making the user log in');
$state.go('login'); 66 65 $state.go('login');
} 67 66 }
if (!UserService.authorizedFor($rootScope.nextState, $rootScope.nextStateParams)) { 68 67 if (!UserService.authorizedFor($rootScope.nextState, $rootScope.nextStateParams)) {
console.log('user not authorized for ' + $rootScope.nextState.name); 69 68 console.log('user not authorized for ' + $rootScope.nextState.name);
$state.go('addclass'); 70 69 $state.go('addclass');
} 71 70 }
}; 72 71 };
if (UserService.isResolved()) return redirectAsNeeded(); 73 72 if (UserService.isResolved()) return redirectAsNeeded();
return UserService.getUserData().then(redirectAsNeeded); 74 73 return UserService.getUserData().then(redirectAsNeeded);
} 75 74 }
}; 76 75 };
$stateProvider. 77 76 $stateProvider.
state('login', { 78 77 state('login', {
resolve: auth_resolve, 79 78 resolve: auth_resolve,
url: '/login', 80 79 url: '/login',
templateUrl: 'templates/login.html', 81 80 templateUrl: 'templates/login.html',
controller: 'LoginController' 82 81 controller: 'LoginController'
}). 83 82 }).
state('root', { 84 83 state('root', {
resolve: auth_resolve, 85 84 resolve: auth_resolve,
url: '', 86 85 url: '',
controller: 'RootController' 87 86 controller: 'RootController'
}). 88 87 }).
state('feed', { 89 88 state('feed', {
resolve: auth_resolve, 90 89 resolve: auth_resolve,
url: '/feed/{sectionId}', 91 90 url: '/feed/{sectionId}',
templateUrl: 'templates/feed.html', 92 91 templateUrl: 'templates/feed.html',
controller: 'FeedController' 93 92 controller: 'FeedController'
}). 94 93 }).
state('cardlist', { 95 94 state('cardlist', {
resolve: auth_resolve, 96 95 resolve: auth_resolve,
url: '/cards/{sectionId}', 97 96 url: '/cards/{sectionId}',
templateUrl: 'templates/cardlist.html', 98 97 templateUrl: 'templates/cardlist.html',
controller: 'CardListController' 99 98 controller: 'CardListController'
}). 100 99 }).
state('addclass', { 101 100 state('addclass', {
resolve: auth_resolve, 102 101 resolve: auth_resolve,
url: '/addclass', 103 102 url: '/addclass',
templateUrl: 'templates/addclass.html', 104 103 templateUrl: 'templates/addclass.html',
controller: 'ClassAddController' 105 104 controller: 'ClassAddController'
}). 106 105 }).
state('dropclass', { 107 106 state('dropclass', {
resolve: auth_resolve, 108 107 resolve: auth_resolve,
url: '/settings/dropclass', 109 108 url: '/settings/dropclass',
templateUrl: 'templates/dropclass.html', 110 109 templateUrl: 'templates/dropclass.html',
controller: 'ClassDropController' 111 110 controller: 'ClassDropController'
}). 112 111 }).
state('deck', { 113 112 state('deck', {
resolve: auth_resolve, 114 113 resolve: auth_resolve,
url: '/deck/{sectionId}', 115 114 url: '/deck/{sectionId}',
templateUrl: 'templates/deck.html', 116 115 templateUrl: 'templates/deck.html',
controller: 'DeckController' 117 116 controller: 'DeckController'
}). 118 117 }).
state('study', { 119 118 state('study', {
resolve: auth_resolve, 120 119 resolve: auth_resolve,
url: '/study', 121 120 url: '/study',
templateUrl: 'templates/study.html', 122 121 templateUrl: 'templates/study.html',
controller: 'StudyController' 123 122 controller: 'StudyController'
}). 124 123 }).
state('flashcard', { 125 124 state('flashcard', {
resolve: auth_resolve, 126 125 resolve: auth_resolve,
url: '/flashcard', 127 126 url: '/flashcard',
templateUrl: 'templates/flashcard.html', 128 127 templateUrl: 'templates/flashcard.html',
controller: 'FlashcardController' 129 128 controller: 'FlashcardController'
}). 130 129 }).
state('settings', { 131 130 state('settings', {
resolve: auth_resolve, 132 131 resolve: auth_resolve,
url: '/settings', 133 132 url: '/settings',
templateUrl: 'templates/settings.html', 134 133 templateUrl: 'templates/settings.html',
controller: 'SettingsController' 135 134 controller: 'SettingsController'
}). 136 135 }).
state('requestpasswordreset', { 137 136 state('requestpasswordreset', {
url: '/requestpasswordreset', 138 137 url: '/requestpasswordreset',
templateUrl: 'templates/requestpasswordreset.html', 139 138 templateUrl: 'templates/requestpasswordreset.html',
controller: 'RequestResetController' 140 139 controller: 'RequestResetController'
}). 141 140 }).
state('resetpassword', { 142 141 state('resetpassword', {
url: '/resetpassword/{uid}/{token}', 143 142 url: '/resetpassword/{uid}/{token}',
templateUrl: 'templates/resetpassword.html', 144 143 templateUrl: 'templates/resetpassword.html',
controller: 'ResetPasswordController' 145 144 controller: 'ResetPasswordController'
}). 146 145 }).
state('verifyemail', { 147 146 state('verifyemail', {
url: '/verifyemail/{key}', 148 147 url: '/verifyemail/{key}',
templateUrl: 'templates/verifyemail.html', 149 148 templateUrl: 'templates/verifyemail.html',
controller: 'VerifyEmailController' 150 149 controller: 'VerifyEmailController'
}). 151 150 }).
state('404', { 152 151 state('404', {
url: '/404', 153 152 url: '/404',
template: "<h1>This page doesn't exist!</h1>" 154 153 template: "<h1>This page doesn't exist!</h1>"
}). 155 154 }).
state('help', { 156 155 state('help', {
resolve: auth_resolve, 157 156 resolve: auth_resolve,
url: '/help', 158 157 url: '/help',
templateUrl: 'templates/help.html', 159 158 templateUrl: 'templates/help.html',
controller: 'HelpController' 160 159 controller: 'HelpController'
}); 161 160 });
}). 162 161 }).
run(function($rootScope, $state, $stateParams, $location, UserService) { 163 162 run(function($rootScope, $state, $stateParams, $location, UserService) {
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) { 164 163 $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
console.log('failed to change state: ' + error); 165 164 console.log('failed to change state: ' + error);
$state.go('login'); 166 165 $state.go('login');
}); 167 166 });
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { 168 167 $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
$rootScope.nextState = toState; 169 168 $rootScope.nextState = toState;
$rootScope.nextStateParams = toParams; 170 169 $rootScope.nextStateParams = toParams;
console.log('changing state to', toState); 171 170 console.log('changing state to', toState);
if (['feed', 'deck', 'cardlist'].indexOf(toState.name) >= 0) { 172 171 if (['feed', 'deck', 'cardlist'].indexOf(toState.name) >= 0) {
localStorage.setItem('last_state', toState.name); 173 172 localStorage.setItem('last_state', toState.name);
localStorage.setItem('last_state_params', JSON.stringify(toParams)); 174 173 localStorage.setItem('last_state_params', JSON.stringify(toParams));
} 175 174 }
<!DOCTYPE html> 1 1 <!DOCTYPE html>
<html ng-app="flashy"> 2 2 <html ng-app="flashy">
<base href="/app/"> 3 3 <base href="/app/">
<head> 4 4 <head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<link rel="stylesheet" 6 6 <link rel="stylesheet"
href="https://ajax.googleapis.com/ajax/libs/angular_material/0.9.0/angular-material.min.css"> 7 7 href="https://ajax.googleapis.com/ajax/libs/angular_material/0.9.0/angular-material.min.css">
<link rel="shortcut icon" href="flashy.ico"> 8 8 <link rel="shortcut icon" href="flashy.ico">
9 9
<link rel="stylesheet" href="styles/flashier.css"/> 10 10 <link rel="stylesheet" href="styles/flashier.css"/>
<link rel="stylesheet" href="styles/flashy.css"/> 11 11 <link rel="stylesheet" href="styles/flashy.css"/>
<link rel="manifest" href="manifest.json"> 12 12 <link rel="manifest" href="manifest.json">
<link 13 13 <link
href='https://fonts.googleapis.com/css?family=Satisfy|Titillium+Web:400,200,200italic,300,600,700,900,700italic,600italic,400italic,300italic' 14 14 href='https://fonts.googleapis.com/css?family=Satisfy|Titillium+Web:400,200,200italic,300,600,700,900,700italic,600italic,400italic,300italic'
rel='stylesheet' type='text/css'> 15 15 rel='stylesheet' type='text/css'>
<title>Flashy</title> 16 16 <title>Flashy</title>
</head> 17 17 </head>
<body ng-controller="RootController"> 18 18 <body ng-controller="RootController">
<header> 19 19 <header>
<nav> 20 20 <nav>
<div class="nav-wrapper"> 21 21 <div class="nav-wrapper">
<a ng-show="UserService.isLoggedIn()" href="#" data-activates="mobile-demo" 22 22 <a ng-show="UserService.isLoggedIn()" href="#" data-activates="mobile-demo"
class="left button-collapse hide-on-med-and-up"><i 23 23 class="left button-collapse hide-on-med-and-up"><i
class="mdi-navigation-menu"></i></a> 24 24 class="mdi-navigation-menu"></i></a>
<!-- User's classes dropdown --> 25 25 <!-- User's classes dropdown -->
<ul id="classDropdown" class="dropdown-content"> 26 26 <ul id="classDropdown" class="dropdown-content">
<li ui-sref-active="active" ng-repeat="section in UserService.getUserData().sections"> 27 27 <li ui-sref-active="active" ng-repeat="section in UserService.getUserData().sections">
<a ui-sref="feed({sectionId:section.id})">{{section.short_name}}</a> 28 28 <a ui-sref="feed({sectionId:section.id})">{{section.short_name}}</a>
</li> 29 29 </li>
<li class="divider"></li> 30 30 <li class="divider"></li>
<li><a ui-sref="addclass">Add Class</a></li> 31 31 <li><a ui-sref="addclass">Add Class</a></li>
</ul> 32 32 </ul>
<ul ng-show="UserService.isLoggedIn()" class="left hide-on-small-and-down"> 33 33 <ul ng-show="UserService.isLoggedIn()" class="left hide-on-small-and-down">
<li><a style="font-size:20px; font-weight:700" class="dropdown-button ng-cloak hide-on-small-and-down" 34 34 <li><a style="font-size:20px; font-weight:700" class="dropdown-button ng-cloak hide-on-small-and-down"
href="#!" 35 35 href="#!"
data-activates="classDropdown">{{currentSection.id?currentSection.short_name:"Classes"}}<i 36 36 data-activates="classDropdown">{{currentSection.id?currentSection.short_name:"Classes"}}<i
class="mdi-navigation-arrow-drop-down right"></i></a></li> 37 37 class="mdi-navigation-arrow-drop-down right"></i></a></li>
<li ng-show="currentSection.id" ui-sref-active="active"><a ui-sref="feed({sectionId:currentSection.id})" 38 38 <li ng-show="currentSection.id" ui-sref-active="active"><a ui-sref="feed({sectionId:currentSection.id})"
class="tooltipped" 39 39 class="tooltipped"
data-position="bottom" 40 40 data-position="bottom"
data-delay="50" data-tooltip="Feed"><i 41 41 data-delay="50" data-tooltip="Feed"><i
class="mdi-action-view-module"></i></a></li> 42 42 class="mdi-action-view-module"></i></a></li>
<li ng-show="currentSection.id" ui-sref-active="active"><a ui-sref="deck({sectionId:currentSection.id})" 43 43 <li ng-show="currentSection.id" ui-sref-active="active"><a ui-sref="deck({sectionId:currentSection.id})"
class="tooltipped" 44 44 class="tooltipped"
data-position="bottom" 45 45 data-position="bottom"
data-delay="50" data-tooltip="Deck"><i 46 46 data-delay="50" data-tooltip="Deck"><i
class="mdi-action-view-carousel"></i></a></li> 47 47 class="mdi-action-view-carousel"></i></a></li>
<li ng-show="currentSection.id" ui-sref-active="active"><a ui-sref="cardlist({sectionId:currentSection.id})" 48 48 <li ng-show="currentSection.id" ui-sref-active="active"><a ui-sref="cardlist({sectionId:currentSection.id})"
class="tooltipped" 49 49 class="tooltipped"
data-position="bottom" 50 50 data-position="bottom"
data-delay="50" data-tooltip="Card List"><i 51 51 data-delay="50" data-tooltip="Card List"><i
class="mdi-action-view-list"></i></a></li> 52 52 class="mdi-action-view-list"></i></a></li>
</ul> 53 53 </ul>
<a href="#" class="brand-logo center">Flashy</a> 54 54 <a href="#" class="brand-logo center">Flashy</a>
55 55
<ul ng-show="UserService.isLoggedIn()" ng-cloak id="nav-mobile" class="right hide-on-small-and-down"> 56 56 <ul ng-show="UserService.isLoggedIn()" ng-cloak id="nav-mobile" class="right hide-on-small-and-down">
57 57
<li ui-sref-active="active"><a ui-sref="study" class="tooltipped" data-position="bottom" data-delay="50" 58 58 <li ui-sref-active="active"><a ui-sref="study" class="tooltipped" data-position="bottom" data-delay="50"
data-tooltip="Study"> 59 59 data-tooltip="Study">
<i class="tiny mdi-action-pageview"></i></a></li> 60 60 <i class="tiny mdi-action-pageview"></i></a></li>
61 61
<!-- Settings Dropdown --> 62 62 <!-- Settings Dropdown -->
<ul id="settingsDropdown" class="dropdown-content"> 63 63 <ul id="settingsDropdown" class="dropdown-content">
64 64
65 65
</ul> 66 66 </ul>
67 67
<li ui-sref-active="active"><a ui-sref="help"><i class="tiny mdi-action-help tooltipped" 68 68 <li ui-sref-active="active"><a ui-sref="help"><i class="tiny mdi-action-help tooltipped"
data-position="bottom" 69 69 data-position="bottom"
data-delay="50" data-tooltip="Help"></i></a></li> 70 70 data-delay="50" data-tooltip="Help"></i></a></li>
<li ui-sref-active="active"><a ui-sref="settings"><i data-position="bottom" data-delay="50" 71 71 <li ui-sref-active="active"><a ui-sref="settings"><i data-position="bottom" data-delay="50"
data-tooltip="Settings" 72 72 data-tooltip="Settings"
class="mdi-action-settings tooltipped"></i></a></li> 73 73 class="mdi-action-settings tooltipped"></i></a></li>
<li><a ng-click="logout()" ui-sref="login"><i data-position="bottom" data-delay="50" data-tooltip="Logout" 74 74 <li><a ng-click="logout()" ui-sref="login"><i data-position="bottom" data-delay="50" data-tooltip="Logout"
class="mdi-content-forward tooltipped"></i></a></li> 75 75 class="mdi-content-forward tooltipped"></i></a></li>
76 76
77 77
</ul> 78 78 </ul>
79 79
<!-- Slide-in side-nav for small screens --> 80 80 <!-- Slide-in side-nav for small screens -->
<ul ng-show="UserService.isLoggedIn()" class="side-nav" id="mobile-demo"> 81 81 <ul ng-show="UserService.isLoggedIn()" class="side-nav" id="mobile-demo">
<span ng-show="currentSection.id"> 82 82 <span ng-show="currentSection.id">
<li ui-sref-active="active"><a ui-sref="feed({sectionId:currentSection.id})" class="tooltipped"> 83 83 <li ui-sref-active="active"><a ui-sref="feed({sectionId:currentSection.id})" class="tooltipped">
<i class="mdi-action-view-module left"></i> 84 84 <i class="mdi-action-view-module left"></i>
Feed</a> 85 85 Feed</a>
</li> 86 86 </li>
<li ui-sref-active="active"><a ui-sref="deck({sectionId:currentSection.id})" class="tooltipped"> 87 87 <li ui-sref-active="active"><a ui-sref="deck({sectionId:currentSection.id})" class="tooltipped">
<i class="mdi-action-view-carousel left"> </i> 88 88 <i class="mdi-action-view-carousel left"> </i>
Deck 89 89 Deck
</a> 90 90 </a>
</li> 91 91 </li>
<li ui-sref-active="active"><a ui-sref="cardlist({sectionId:currentSection.id})" class="tooltipped"> 92 92 <li ui-sref-active="active"><a ui-sref="cardlist({sectionId:currentSection.id})" class="tooltipped">
<i class="mdi-action-view-list left"></i> 93 93 <i class="mdi-action-view-list left"></i>
Card List 94 94 Card List
</a> 95 95 </a>
</li> 96 96 </li>
<hr> 97 97 <hr>
</span> 98 98 </span>
<!-- Collapsible menu for all the User's classes --> 99 99 <!-- Collapsible menu for all the User's classes -->
<ul class="collapsible" data-collapsible="accordion"> 100 100 <ul class="collapsible" data-collapsible="accordion">
<li class="bold"> 101 101 <li class="bold">
<a class="collapsible-header black-text"> 102 102 <a class="collapsible-header black-text">
Classes 103 103 Classes
<i class="mdi-navigation-arrow-drop-down right"></i> 104 104 <i class="mdi-navigation-arrow-drop-down right"></i>
</a> 105 105 </a>
</li> 106 106 </li>
<div class="collapsible-body" style="display: block"> 107 107 <div class="collapsible-body" style="display: block">
<ul> 108 108 <ul>
<li ui-sref-active="active" ng-repeat="section in UserService.getUserData().sections"> 109 109 <li ui-sref-active="active" ng-repeat="section in UserService.getUserData().sections">
<a class="class bold" ui-sref="feed({sectionId:section.id})">{{section.short_name}}</a> 110 110 <a class="class bold" ui-sref="feed({sectionId:section.id})">{{section.short_name}}</a>
</li> 111 111 </li>
<hr> 112 112 <hr>
<li><a ui-sref="addclass"><i class="tiny mdi-content-add">Add Class</i></a></li> 113 113 <li><a ui-sref="addclass"><i class="tiny mdi-content-add">Add Class</i></a></li>
</ul> 114 114 </ul>
</div> 115 115 </div>
</ul> 116 116 </ul>
<li><a ui-sref="study">Study</a></li> 117 117 <li><a ui-sref="study">Study</a></li>
<li><a ui-sref="settings">Settings</a></li> 118 118 <li><a ui-sref="settings">Settings</a></li>
<li><a ng-click="logout()">Logout</a></li> 119 119 <li><a ng-click="logout()">Logout</a></li>
</ul> 120 120 </ul>
</div> 121 121 </div>
</nav> 122 122 </nav>
123 123
</header> 124 124 </header>
125 125
126 126
<!-- Menu Bar --> 127 127 <!-- Menu Bar -->
<main ui-view></main> 128 128 <main ui-view></main>
129 129
130 130
<!--<footer class="page-footer">--> 131 131 <!--<footer class="page-footer">-->
<!--<div class="footer-copyright">--> 132 132 <!--<div class="footer-copyright">-->
<!--<div class="container">--> 133 133 <!--<div class="container">-->
<!--&copy; 2015 Team Swag--> 134 134 <!--&copy; 2015 Team Swag-->
<!--<a class="grey-text text-lighten-4 right" id="contact" href="mailto:halp@flashy.cards">Concerns? Contact us by--> 135 135 <!--<a class="grey-text text-lighten-4 right" id="contact" href="mailto:halp@flashy.cards">Concerns? Contact us by-->
<!--email!</a>--> 136 136 <!--email!</a>-->
<!--</div>--> 137 137 <!--</div>-->
138 138
<!--</div>--> 139 139 <!--</div>-->
<!--</footer>--> 140 140 <!--</footer>-->
141 141
</body> 142 142 </body>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script> 143 143 <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.14/angular-ui-router.js"></script> 144 144 <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.14/angular-ui-router.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-cookies.js"></script> 145 145 <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-cookies.js"></script>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script> 146 146 <script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="scripts/materialize.js"></script> 147 147 <script type="text/javascript" src="scripts/materialize.js"></script>
<script type="text/javascript" src="scripts/jquery.collapsible.js"></script> 148 148 <script type="text/javascript" src="scripts/jquery.collapsible.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angular_material/0.9.0/angular-material.min.js"></script> 149 149 <script src="https://ajax.googleapis.com/ajax/libs/angular_material/0.9.0/angular-material.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.min.js"></script> 150 150 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-aria.min.js"></script> 151 151 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-aria.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-resource.min.js"></script> 152 152 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-resource.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-sanitize.js"></script> 153 153 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-sanitize.js"></script>
<script src="https://cdn.rawgit.com/gdi2290/angular-websocket/v1.0.9/angular-websocket.min.js"></script> 154 154 <script src="static/js/angular-websocket.js"></script>
<script src="https://cdn.rawgit.com/akatov/angular-contenteditable/master/angular-contenteditable.js"></script> 155 155 <script src="static/js/angular-contenteditable.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-filter/0.5.4/angular-filter.js"></script> 156 156 <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-filter/0.5.4/angular-filter.js"></script>
157 157
158 158
<script src="config.js"></script> 159 159 <script src="config.js"></script>
160 160
<!-- Controllers --> 161 161 <!-- Controllers -->
<script src="scripts/FeedController.js"></script> 162 162 <script src="scripts/FeedController.js"></script>
<script src="scripts/RootController.js"></script> 163 163 <script src="scripts/RootController.js"></script>
<script src="scripts/SettingsController.js"></script> 164 164 <script src="scripts/SettingsController.js"></script>
scripts/UserService.js View file @ 6b261ad
angular.module('flashy.UserService', ['ui.router']). 1 1 angular.module('flashy.UserService', ['ui.router']).
service('UserService', function($rootScope, $http, $q) { 2 2 service('UserService', function($rootScope, $http, $q) {
var deferred = $q.defer(); 3 3 var deferred = $q.defer();
var _user = false; 4 4 var _user = false;
var login = function(data) { 5 5 var login = function(data) {
if (data.locked) { 6 6 if (data.locked) {
$rootScope.UserService.showLockedMessage(); 7 7 $rootScope.UserService.showLockedMessage();
return deferred.reject('account locked'); 8 8 return deferred.resolve(data);
} 9 9 }
if (!data.is_confirmed) { 10 10 if (!data.is_confirmed) {
Materialize.toast('Please verify your email address! ' + 11 11 Materialize.toast('Please verify your email address! ' +
'<a class="btn-flat cyan-text" onclick="rootscope.UserService.resendConfirmationEmail()">' + 12 12 '<a class="btn-flat cyan-text" onclick="rootscope.UserService.resendConfirmationEmail()">' +
'Resend Verification Email</a>', 4000); 13 13 'Resend Verification Email</a>', 4000);
} 14 14 }
_user = data; 15 15 _user = data;
_user.sectionIdList = _user.sections.map(function(x) { 16 16 _user.sectionIdList = _user.sections.map(function(x) {
return x.id; 17 17 return x.id;
}); 18 18 });
deferred.resolve(data); 19 19 deferred.resolve(data);
}; 20 20 };
this.login = login; 21 21 this.login = login;
$http.get('/api/me/').success(function(data) { 22 22 $http.get('/api/me/').success(function(data) {
console.log('user is logged in!'); 23 23 console.log('user is logged in!');
login(data); 24 24 login(data);
}).error(function(data) { 25 25 }).error(function(data) {
console.log(data); 26 26 console.log(data);
console.log('not logged in yet: ' + data.detail); 27 27 console.log('not logged in yet: ' + data.detail);
_user = {email: false}; 28 28 _user = {email: false};
deferred.resolve(_user); 29 29 deferred.resolve(_user);
}); 30 30 });
31 31
this.isResolved = function() { 32 32 this.isResolved = function() {
return !!_user; 33 33 return !!_user;
}; 34 34 };
this.getUserData = function() { 35 35 this.getUserData = function() {
if (this.isResolved()) return _user; 36 36 if (this.isResolved()) return _user;
else return deferred.promise; 37 37 else return deferred.promise;
}; 38 38 };
this.hasVerifiedEmail = function() { 39 39 this.hasVerifiedEmail = function() {
return this.isResolved() && _user.is_confirmed; 40 40 return this.isResolved() && _user.is_confirmed;
}; 41 41 };
42 42
this.logout = function($state) { 43 43 this.logout = function($state) {
$http.post('/api/logout/').success(function() { 44 44 $http.post('/api/logout/').success(function() {
if (!_user.locked)Materialize.toast('Logged out!', 1000); 45 45 if (!_user.locked)Materialize.toast('Logged out!', 1000);
}).error(function() { 46 46 }).error(function() {
console.log('Problem logging out'); 47 47 console.log('Problem logging out');
}); 48 48 });
_user = false; 49 49 _user = false;
deferred.resolve({}); 50 50 deferred.resolve({});
$state.go('login'); 51 51 $state.go('login');
}; 52 52 };
this.addClass = function(section) { 53 53 this.addClass = function(section) {
_user.sections.push(section); 54 54 _user.sections.push(section);
_user.sectionIdList.push(section.id); 55 55 _user.sectionIdList.push(section.id);
}; 56 56 };
this.isLoggedIn = function() { 57 57 this.isLoggedIn = function() {
rv = this.isResolved() && _user.email; 58 58 rv = this.isResolved() && _user.email;
return rv; 59 59 return rv;
}; 60 60 };
this.isInSection = function(sectionId) { 61 61 this.isInSection = function(sectionId) {
return (_user.sectionIdList.indexOf(sectionId) >= 0); 62 62 return (_user.sectionIdList.indexOf(sectionId) >= 0);
}; 63 63 };
this.redirectToDefaultState = function($state) { 64 64 this.redirectToDefaultState = function($state) {
console.log('redirecting user to their default state'); 65 65 console.log('redirecting user to their default state');
// if the user isn't logged in, log in! 66 66 // if the user isn't logged in, log in!
if (!this.isLoggedIn()) return $state.go('login'); 67 67 if (!this.isLoggedIn()) return $state.go('login');
// if the user isn't enrolled in any sections, go to addclass 68 68 // if the user isn't enrolled in any sections, go to addclass
if (!_user.sections.length) return $state.go('addclass'); 69 69 if (!_user.sections.length) return $state.go('addclass');
last_state = localStorage.getItem('last_state'); 70 70 last_state = localStorage.getItem('last_state');
if (last_state) { 71 71 if (last_state) {
// if there was a last state, get the parameters of the state 72 72 // if there was a last state, get the parameters of the state
last_state_params = JSON.parse(localStorage.getItem('last_state_params')); 73 73 last_state_params = JSON.parse(localStorage.getItem('last_state_params'));
if (last_state_params.sectionId && this.authorizedFor(last_state, last_state_params)) { 74 74 if (last_state_params.sectionId && this.authorizedFor(last_state, last_state_params)) {
// if we're authorized to access that state with those parameters, go there 75 75 // if we're authorized to access that state with those parameters, go there
return $state.go(last_state, JSON.parse(localStorage.getItem('last_state_params'))); 76 76 return $state.go(last_state, JSON.parse(localStorage.getItem('last_state_params')));
} 77 77 }
} 78 78 }
$state.go('feed', {sectionId: _user.sections[0].id}); 79 79 $state.go('feed', {sectionId: _user.sections[0].id});
}; 80 80 };
this.authorizedFor = function(state, stateParams) { 81 81 this.authorizedFor = function(state, stateParams) {
if (['feed', 'deck', 'cardlist'].indexOf(state.name) >= 0) { 82 82 if (['feed', 'deck', 'cardlist'].indexOf(state.name) >= 0) {
console.log('checking whether', stateParams, 'in', _user.sectionIdList); 83 83 console.log('checking whether', stateParams, 'in', _user.sectionIdList);
if (_user.sectionIdList.indexOf(parseInt(stateParams.sectionId)) < 0) { 84 84 if (_user.sectionIdList.indexOf(parseInt(stateParams.sectionId)) < 0) {
return false; 85 85 return false;
} 86 86 }
} 87 87 }
return true; 88 88 return true;
}; 89 89 };
this.showLockedMessage = function() { 90 90 this.showLockedMessage = function() {
Materialize.toast('You must verify your email address before continuing.' + 91 91 Materialize.toast('You must verify your email address before continuing.' +
'<a class="btn-flat cyan-text" onclick="rootscope.UserService.resendConfirmationEmail()">' + 92 92 '<a class="btn-flat cyan-text" onclick="rootscope.UserService.resendConfirmationEmail()">' +
'Resend Verification Email</a>', 4000); 93 93 'Resend Verification Email</a>', 4000);
}; 94 94 };
this.noAuthRequired = function(state) { 95 95 this.noAuthRequired = function(state) {
if (['verifyemail', 'login'].indexOf(state.name) >= 0) { 96 96 return ['verifyemail', 'login'].indexOf(state.name) >= 0;
return true; 97 97
} 98
return false; 99
}; 100 98 };
this.resendConfirmationEmail = function() { 101 99 this.resendConfirmationEmail = function() {
console.log('Requesting resend of confirmation email'); 102 100 console.log('Requesting resend of confirmation email');
$http.post('/api/resend_confirmation_email/').success(function() { 103 101 $http.post('/api/resend_confirmation_email/').success(function() {
Materialize.toast('Resent confirmation email! Check your spam folder too.', 4000); 104 102 Materialize.toast('Resent confirmation email! Check your spam folder too.', 4000);
}); 105 103 });
}; 106 104 };
}); 107 105 });
108 106
scripts/bootstrap.js View file @ 6b261ad
/*! 1 File was deleted
* Bootstrap v3.3.4 (http://getbootstrap.com) 2
* Copyright 2011-2015 Twitter, Inc. 3
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 4
*/ 5
6
if (typeof jQuery === 'undefined') { 7
throw new Error('Bootstrap\'s JavaScript requires jQuery') 8
} 9
10
+function ($) { 11
'use strict'; 12
var version = $.fn.jquery.split(' ')[0].split('.') 13
if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) { 14
throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher') 15
} 16
}(jQuery); 17
18
/* ======================================================================== 19
* Bootstrap: transition.js v3.3.4 20
* http://getbootstrap.com/javascript/#transitions 21
* ======================================================================== 22
* Copyright 2011-2015 Twitter, Inc. 23
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 24
* ======================================================================== */ 25
26
27
+function ($) { 28
'use strict'; 29
30
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) 31
// ============================================================ 32
33
function transitionEnd() { 34
var el = document.createElement('bootstrap') 35
36
var transEndEventNames = { 37
WebkitTransition : 'webkitTransitionEnd', 38
MozTransition : 'transitionend', 39
OTransition : 'oTransitionEnd otransitionend', 40
transition : 'transitionend' 41
} 42
43
for (var name in transEndEventNames) { 44
if (el.style[name] !== undefined) { 45
return { end: transEndEventNames[name] } 46
} 47
} 48
49
return false // explicit for ie8 ( ._.) 50
} 51
52
// http://blog.alexmaccaw.com/css-transitions 53
$.fn.emulateTransitionEnd = function (duration) { 54
var called = false 55
var $el = this 56
$(this).one('bsTransitionEnd', function () { called = true }) 57
var callback = function () { if (!called) $($el).trigger($.support.transition.end) } 58
setTimeout(callback, duration) 59
return this 60
} 61
62
$(function () { 63
$.support.transition = transitionEnd() 64
65
if (!$.support.transition) return 66
67
$.event.special.bsTransitionEnd = { 68
bindType: $.support.transition.end, 69
delegateType: $.support.transition.end, 70
handle: function (e) { 71
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) 72
} 73
} 74
}) 75
76
}(jQuery); 77
78
/* ======================================================================== 79
* Bootstrap: alert.js v3.3.4 80
* http://getbootstrap.com/javascript/#alerts 81
* ======================================================================== 82
* Copyright 2011-2015 Twitter, Inc. 83
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 84
* ======================================================================== */ 85
86
87
+function ($) { 88
'use strict'; 89
90
// ALERT CLASS DEFINITION 91
// ====================== 92
93
var dismiss = '[data-dismiss="alert"]' 94
var Alert = function (el) { 95
$(el).on('click', dismiss, this.close) 96
} 97
98
Alert.VERSION = '3.3.4' 99
100
Alert.TRANSITION_DURATION = 150 101
102
Alert.prototype.close = function (e) { 103
var $this = $(this) 104
var selector = $this.attr('data-target') 105
106
if (!selector) { 107
selector = $this.attr('href') 108
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 109
} 110
111
var $parent = $(selector) 112
113
if (e) e.preventDefault() 114
115
if (!$parent.length) { 116
$parent = $this.closest('.alert') 117
} 118
119
$parent.trigger(e = $.Event('close.bs.alert')) 120
121
if (e.isDefaultPrevented()) return 122
123
$parent.removeClass('in') 124
125
function removeElement() { 126
// detach from parent, fire event then clean up data 127
$parent.detach().trigger('closed.bs.alert').remove() 128
} 129
130
$.support.transition && $parent.hasClass('fade') ? 131
$parent 132
.one('bsTransitionEnd', removeElement) 133
.emulateTransitionEnd(Alert.TRANSITION_DURATION) : 134
removeElement() 135
} 136
137
138
// ALERT PLUGIN DEFINITION 139
// ======================= 140
141
function Plugin(option) { 142
return this.each(function () { 143
var $this = $(this) 144
var data = $this.data('bs.alert') 145
146
if (!data) $this.data('bs.alert', (data = new Alert(this))) 147
if (typeof option == 'string') data[option].call($this) 148
}) 149
} 150
151
var old = $.fn.alert 152
153
$.fn.alert = Plugin 154
$.fn.alert.Constructor = Alert 155
156
157
// ALERT NO CONFLICT 158
// ================= 159
160
$.fn.alert.noConflict = function () { 161
$.fn.alert = old 162
return this 163
} 164
165
166
// ALERT DATA-API 167
// ============== 168
169
$(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) 170
171
}(jQuery); 172
173
/* ======================================================================== 174
* Bootstrap: button.js v3.3.4 175
* http://getbootstrap.com/javascript/#buttons 176
* ======================================================================== 177
* Copyright 2011-2015 Twitter, Inc. 178
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 179
* ======================================================================== */ 180
181
182
+function ($) { 183
'use strict'; 184
185
// BUTTON PUBLIC CLASS DEFINITION 186
// ============================== 187
188
var Button = function (element, options) { 189
this.$element = $(element) 190
this.options = $.extend({}, Button.DEFAULTS, options) 191
this.isLoading = false 192
} 193
194
Button.VERSION = '3.3.4' 195
196
Button.DEFAULTS = { 197
loadingText: 'loading...' 198
} 199
200
Button.prototype.setState = function (state) { 201
var d = 'disabled' 202
var $el = this.$element 203
var val = $el.is('input') ? 'val' : 'html' 204
var data = $el.data() 205
206
state = state + 'Text' 207
208
if (data.resetText == null) $el.data('resetText', $el[val]()) 209
210
// push to event loop to allow forms to submit 211
setTimeout($.proxy(function () { 212
$el[val](data[state] == null ? this.options[state] : data[state]) 213
214
if (state == 'loadingText') { 215
this.isLoading = true 216
$el.addClass(d).attr(d, d) 217
} else if (this.isLoading) { 218
this.isLoading = false 219
$el.removeClass(d).removeAttr(d) 220
} 221
}, this), 0) 222
} 223
224
Button.prototype.toggle = function () { 225
var changed = true 226
var $parent = this.$element.closest('[data-toggle="buttons"]') 227
228
if ($parent.length) { 229
var $input = this.$element.find('input') 230
if ($input.prop('type') == 'radio') { 231
if ($input.prop('checked') && this.$element.hasClass('active')) changed = false 232
else $parent.find('.active').removeClass('active') 233
} 234
if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') 235
} else { 236
this.$element.attr('aria-pressed', !this.$element.hasClass('active')) 237
} 238
239
if (changed) this.$element.toggleClass('active') 240
} 241
242
243
// BUTTON PLUGIN DEFINITION 244
// ======================== 245
246
function Plugin(option) { 247
return this.each(function () { 248
var $this = $(this) 249
var data = $this.data('bs.button') 250
var options = typeof option == 'object' && option 251
252
if (!data) $this.data('bs.button', (data = new Button(this, options))) 253
254
if (option == 'toggle') data.toggle() 255
else if (option) data.setState(option) 256
}) 257
} 258
259
var old = $.fn.button 260
261
$.fn.button = Plugin 262
$.fn.button.Constructor = Button 263
264
265
// BUTTON NO CONFLICT 266
// ================== 267
268
$.fn.button.noConflict = function () { 269
$.fn.button = old 270
return this 271
} 272
273
274
// BUTTON DATA-API 275
// =============== 276
277
$(document) 278
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { 279
var $btn = $(e.target) 280
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 281
Plugin.call($btn, 'toggle') 282
e.preventDefault() 283
}) 284
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { 285
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) 286
}) 287
288
}(jQuery); 289
290
/* ======================================================================== 291
* Bootstrap: carousel.js v3.3.4 292
* http://getbootstrap.com/javascript/#carousel 293
* ======================================================================== 294
* Copyright 2011-2015 Twitter, Inc. 295
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 296
* ======================================================================== */ 297
298
299
+function ($) { 300
'use strict'; 301
302
// CAROUSEL CLASS DEFINITION 303
// ========================= 304
305
var Carousel = function (element, options) { 306
this.$element = $(element) 307
this.$indicators = this.$element.find('.carousel-indicators') 308
this.options = options 309
this.paused = null 310
this.sliding = null 311
this.interval = null 312
this.$active = null 313
this.$items = null 314
315
this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) 316
317
this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element 318
.on('mouseenter.bs.carousel', $.proxy(this.pause, this)) 319
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) 320
} 321
322
Carousel.VERSION = '3.3.4' 323
324
Carousel.TRANSITION_DURATION = 600 325
326
Carousel.DEFAULTS = { 327
interval: 5000, 328
pause: 'hover', 329
wrap: true, 330
keyboard: true 331
} 332
333
Carousel.prototype.keydown = function (e) { 334
if (/input|textarea/i.test(e.target.tagName)) return 335
switch (e.which) { 336
case 37: this.prev(); break 337
case 39: this.next(); break 338
default: return 339
} 340
341
e.preventDefault() 342
} 343
344
Carousel.prototype.cycle = function (e) { 345
e || (this.paused = false) 346
347
this.interval && clearInterval(this.interval) 348
349
this.options.interval 350
&& !this.paused 351
&& (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 352
353
return this 354
} 355
356
Carousel.prototype.getItemIndex = function (item) { 357
this.$items = item.parent().children('.item') 358
return this.$items.index(item || this.$active) 359
} 360
361
Carousel.prototype.getItemForDirection = function (direction, active) { 362
var activeIndex = this.getItemIndex(active) 363
var willWrap = (direction == 'prev' && activeIndex === 0) 364
|| (direction == 'next' && activeIndex == (this.$items.length - 1)) 365
if (willWrap && !this.options.wrap) return active 366
var delta = direction == 'prev' ? -1 : 1 367
var itemIndex = (activeIndex + delta) % this.$items.length 368
return this.$items.eq(itemIndex) 369
} 370
371
Carousel.prototype.to = function (pos) { 372
var that = this 373
var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) 374
375
if (pos > (this.$items.length - 1) || pos < 0) return 376
377
if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" 378
if (activeIndex == pos) return this.pause().cycle() 379
380
return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) 381
} 382
383
Carousel.prototype.pause = function (e) { 384
e || (this.paused = true) 385
386
if (this.$element.find('.next, .prev').length && $.support.transition) { 387
this.$element.trigger($.support.transition.end) 388
this.cycle(true) 389
} 390
391
this.interval = clearInterval(this.interval) 392
393
return this 394
} 395
396
Carousel.prototype.next = function () { 397
if (this.sliding) return 398
return this.slide('next') 399
} 400
401
Carousel.prototype.prev = function () { 402
if (this.sliding) return 403
return this.slide('prev') 404
} 405
406
Carousel.prototype.slide = function (type, next) { 407
var $active = this.$element.find('.item.active') 408
var $next = next || this.getItemForDirection(type, $active) 409
var isCycling = this.interval 410
var direction = type == 'next' ? 'left' : 'right' 411
var that = this 412
413
if ($next.hasClass('active')) return (this.sliding = false) 414
415
var relatedTarget = $next[0] 416
var slideEvent = $.Event('slide.bs.carousel', { 417
relatedTarget: relatedTarget, 418
direction: direction 419
}) 420
this.$element.trigger(slideEvent) 421
if (slideEvent.isDefaultPrevented()) return 422
423
this.sliding = true 424
425
isCycling && this.pause() 426
427
if (this.$indicators.length) { 428
this.$indicators.find('.active').removeClass('active') 429
var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) 430
$nextIndicator && $nextIndicator.addClass('active') 431
} 432
433
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" 434
if ($.support.transition && this.$element.hasClass('slide')) { 435
$next.addClass(type) 436
$next[0].offsetWidth // force reflow 437
$active.addClass(direction) 438
$next.addClass(direction) 439
$active 440
.one('bsTransitionEnd', function () { 441
$next.removeClass([type, direction].join(' ')).addClass('active') 442
$active.removeClass(['active', direction].join(' ')) 443
that.sliding = false 444
setTimeout(function () { 445
that.$element.trigger(slidEvent) 446
}, 0) 447
}) 448
.emulateTransitionEnd(Carousel.TRANSITION_DURATION) 449
} else { 450
$active.removeClass('active') 451
$next.addClass('active') 452
this.sliding = false 453
this.$element.trigger(slidEvent) 454
} 455
456
isCycling && this.cycle() 457
458
return this 459
} 460
461
462
// CAROUSEL PLUGIN DEFINITION 463
// ========================== 464
465
function Plugin(option) { 466
return this.each(function () { 467
var $this = $(this) 468
var data = $this.data('bs.carousel') 469
var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) 470
var action = typeof option == 'string' ? option : options.slide 471
472
if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) 473
if (typeof option == 'number') data.to(option) 474
else if (action) data[action]() 475
else if (options.interval) data.pause().cycle() 476
}) 477
} 478
479
var old = $.fn.carousel 480
481
$.fn.carousel = Plugin 482
$.fn.carousel.Constructor = Carousel 483
484
485
// CAROUSEL NO CONFLICT 486
// ==================== 487
488
$.fn.carousel.noConflict = function () { 489
$.fn.carousel = old 490
return this 491
} 492
493
494
// CAROUSEL DATA-API 495
// ================= 496
497
var clickHandler = function (e) { 498
var href 499
var $this = $(this) 500
var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 501
if (!$target.hasClass('carousel')) return 502
var options = $.extend({}, $target.data(), $this.data()) 503
var slideIndex = $this.attr('data-slide-to') 504
if (slideIndex) options.interval = false 505
506
Plugin.call($target, options) 507
508
if (slideIndex) { 509
$target.data('bs.carousel').to(slideIndex) 510
} 511
512
e.preventDefault() 513
} 514
515
$(document) 516
.on('click.bs.carousel.data-api', '[data-slide]', clickHandler) 517
.on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) 518
519
$(window).on('load', function () { 520
$('[data-ride="carousel"]').each(function () { 521
var $carousel = $(this) 522
Plugin.call($carousel, $carousel.data()) 523
}) 524
}) 525
526
}(jQuery); 527
528
/* ======================================================================== 529
* Bootstrap: collapse.js v3.3.4 530
* http://getbootstrap.com/javascript/#collapse 531
* ======================================================================== 532
* Copyright 2011-2015 Twitter, Inc. 533
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 534
* ======================================================================== */ 535
536
537
+function ($) { 538
'use strict'; 539
540
// COLLAPSE PUBLIC CLASS DEFINITION 541
// ================================ 542
543
var Collapse = function (element, options) { 544
this.$element = $(element) 545
this.options = $.extend({}, Collapse.DEFAULTS, options) 546
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + 547
'[data-toggle="collapse"][data-target="#' + element.id + '"]') 548
this.transitioning = null 549
550
if (this.options.parent) { 551
this.$parent = this.getParent() 552
} else { 553
this.addAriaAndCollapsedClass(this.$element, this.$trigger) 554
} 555
556
if (this.options.toggle) this.toggle() 557
} 558
559
Collapse.VERSION = '3.3.4' 560
561
Collapse.TRANSITION_DURATION = 350 562
563
Collapse.DEFAULTS = { 564
toggle: true 565
} 566
567
Collapse.prototype.dimension = function () { 568
var hasWidth = this.$element.hasClass('width') 569
return hasWidth ? 'width' : 'height' 570
} 571
572
Collapse.prototype.show = function () { 573
if (this.transitioning || this.$element.hasClass('in')) return 574
575
var activesData 576
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') 577
578
if (actives && actives.length) { 579
activesData = actives.data('bs.collapse') 580
if (activesData && activesData.transitioning) return 581
} 582
583
var startEvent = $.Event('show.bs.collapse') 584
this.$element.trigger(startEvent) 585
if (startEvent.isDefaultPrevented()) return 586
587
if (actives && actives.length) { 588
Plugin.call(actives, 'hide') 589
activesData || actives.data('bs.collapse', null) 590
} 591
592
var dimension = this.dimension() 593
594
this.$element 595
.removeClass('collapse') 596
.addClass('collapsing')[dimension](0) 597
.attr('aria-expanded', true) 598
599
this.$trigger 600
.removeClass('collapsed') 601
.attr('aria-expanded', true) 602
603
this.transitioning = 1 604
605
var complete = function () { 606
this.$element 607
.removeClass('collapsing') 608
.addClass('collapse in')[dimension]('') 609
this.transitioning = 0 610
this.$element 611
.trigger('shown.bs.collapse') 612
} 613
614
if (!$.support.transition) return complete.call(this) 615
616
var scrollSize = $.camelCase(['scroll', dimension].join('-')) 617
618
this.$element 619
.one('bsTransitionEnd', $.proxy(complete, this)) 620
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) 621
} 622
623
Collapse.prototype.hide = function () { 624
if (this.transitioning || !this.$element.hasClass('in')) return 625
626
var startEvent = $.Event('hide.bs.collapse') 627
this.$element.trigger(startEvent) 628
if (startEvent.isDefaultPrevented()) return 629
630
var dimension = this.dimension() 631
632
this.$element[dimension](this.$element[dimension]())[0].offsetHeight 633
634
this.$element 635
.addClass('collapsing') 636
.removeClass('collapse in') 637
.attr('aria-expanded', false) 638
639
this.$trigger 640
.addClass('collapsed') 641
.attr('aria-expanded', false) 642
643
this.transitioning = 1 644
645
var complete = function () { 646
this.transitioning = 0 647
this.$element 648
.removeClass('collapsing') 649
.addClass('collapse') 650
.trigger('hidden.bs.collapse') 651
} 652
653
if (!$.support.transition) return complete.call(this) 654
655
this.$element 656
[dimension](0) 657
.one('bsTransitionEnd', $.proxy(complete, this)) 658
.emulateTransitionEnd(Collapse.TRANSITION_DURATION) 659
} 660
661
Collapse.prototype.toggle = function () { 662
this[this.$element.hasClass('in') ? 'hide' : 'show']() 663
} 664
665
Collapse.prototype.getParent = function () { 666
return $(this.options.parent) 667
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') 668
.each($.proxy(function (i, element) { 669
var $element = $(element) 670
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) 671
}, this)) 672
.end() 673
} 674
675
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { 676
var isOpen = $element.hasClass('in') 677
678
$element.attr('aria-expanded', isOpen) 679
$trigger 680
.toggleClass('collapsed', !isOpen) 681
.attr('aria-expanded', isOpen) 682
} 683
684
function getTargetFromTrigger($trigger) { 685
var href 686
var target = $trigger.attr('data-target') 687
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 688
689
return $(target) 690
} 691
692
693
// COLLAPSE PLUGIN DEFINITION 694
// ========================== 695
696
function Plugin(option) { 697
return this.each(function () { 698
var $this = $(this) 699
var data = $this.data('bs.collapse') 700
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) 701
702
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false 703
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) 704
if (typeof option == 'string') data[option]() 705
}) 706
} 707
708
var old = $.fn.collapse 709
710
$.fn.collapse = Plugin 711
$.fn.collapse.Constructor = Collapse 712
713
714
// COLLAPSE NO CONFLICT 715
// ==================== 716
717
$.fn.collapse.noConflict = function () { 718
$.fn.collapse = old 719
return this 720
} 721
722
723
// COLLAPSE DATA-API 724
// ================= 725
726
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { 727
var $this = $(this) 728
729
if (!$this.attr('data-target')) e.preventDefault() 730
731
var $target = getTargetFromTrigger($this) 732
var data = $target.data('bs.collapse') 733
var option = data ? 'toggle' : $this.data() 734
735
Plugin.call($target, option) 736
}) 737
738
}(jQuery); 739
740
/* ======================================================================== 741
* Bootstrap: dropdown.js v3.3.4 742
* http://getbootstrap.com/javascript/#dropdowns 743
* ======================================================================== 744
* Copyright 2011-2015 Twitter, Inc. 745
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 746
* ======================================================================== */ 747
748
749
+function ($) { 750
'use strict'; 751
752
// DROPDOWN CLASS DEFINITION 753
// ========================= 754
755
var backdrop = '.dropdown-backdrop' 756
var toggle = '[data-toggle="dropdown"]' 757
var Dropdown = function (element) { 758
$(element).on('click.bs.dropdown', this.toggle) 759
} 760
761
Dropdown.VERSION = '3.3.4' 762
763
Dropdown.prototype.toggle = function (e) { 764
var $this = $(this) 765
766
if ($this.is('.disabled, :disabled')) return 767
768
var $parent = getParent($this) 769
var isActive = $parent.hasClass('open') 770
771
clearMenus() 772
773
if (!isActive) { 774
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 775
// if mobile we use a backdrop because click events don't delegate 776
$('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus) 777
} 778
779
var relatedTarget = { relatedTarget: this } 780
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) 781
782
if (e.isDefaultPrevented()) return 783
784
$this 785
.trigger('focus') 786
.attr('aria-expanded', 'true') 787
788
$parent 789
.toggleClass('open') 790
.trigger('shown.bs.dropdown', relatedTarget) 791
} 792
793
return false 794
} 795
796
Dropdown.prototype.keydown = function (e) { 797
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return 798
799
var $this = $(this) 800
801
e.preventDefault() 802
e.stopPropagation() 803
804
if ($this.is('.disabled, :disabled')) return 805
806
var $parent = getParent($this) 807
var isActive = $parent.hasClass('open') 808
809
if ((!isActive && e.which != 27) || (isActive && e.which == 27)) { 810
if (e.which == 27) $parent.find(toggle).trigger('focus') 811
return $this.trigger('click') 812
} 813
814
var desc = ' li:not(.disabled):visible a' 815
var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc) 816
817
if (!$items.length) return 818
819
var index = $items.index(e.target) 820
821
if (e.which == 38 && index > 0) index-- // up 822
if (e.which == 40 && index < $items.length - 1) index++ // down 823
if (!~index) index = 0 824
825
$items.eq(index).trigger('focus') 826
} 827
828
function clearMenus(e) { 829
if (e && e.which === 3) return 830
$(backdrop).remove() 831
$(toggle).each(function () { 832
var $this = $(this) 833
var $parent = getParent($this) 834
var relatedTarget = { relatedTarget: this } 835
836
if (!$parent.hasClass('open')) return 837
838
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) 839
840
if (e.isDefaultPrevented()) return 841
842
$this.attr('aria-expanded', 'false') 843
$parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) 844
}) 845
} 846
847
function getParent($this) { 848
var selector = $this.attr('data-target') 849
850
if (!selector) { 851
selector = $this.attr('href') 852
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 853
} 854
855
var $parent = selector && $(selector) 856
857
return $parent && $parent.length ? $parent : $this.parent() 858
} 859
860
861
// DROPDOWN PLUGIN DEFINITION 862
// ========================== 863
864
function Plugin(option) { 865
return this.each(function () { 866
var $this = $(this) 867
var data = $this.data('bs.dropdown') 868
869
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) 870
if (typeof option == 'string') data[option].call($this) 871
}) 872
} 873
874
var old = $.fn.dropdown 875
876
$.fn.dropdown = Plugin 877
$.fn.dropdown.Constructor = Dropdown 878
879
880
// DROPDOWN NO CONFLICT 881
// ==================== 882
883
$.fn.dropdown.noConflict = function () { 884
$.fn.dropdown = old 885
return this 886
} 887
888
889
// APPLY TO STANDARD DROPDOWN ELEMENTS 890
// =================================== 891
892
$(document) 893
.on('click.bs.dropdown.data-api', clearMenus) 894
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) 895
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) 896
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) 897
.on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown) 898
.on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown) 899
900
}(jQuery); 901
902
/* ======================================================================== 903
* Bootstrap: modal.js v3.3.4 904
* http://getbootstrap.com/javascript/#modals 905
* ======================================================================== 906
* Copyright 2011-2015 Twitter, Inc. 907
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 908
* ======================================================================== */ 909
910
911
+function ($) { 912
'use strict'; 913
914
// MODAL CLASS DEFINITION 915
// ====================== 916
917
var Modal = function (element, options) { 918
this.options = options 919
this.$body = $(document.body) 920
this.$element = $(element) 921
this.$dialog = this.$element.find('.modal-dialog') 922
this.$backdrop = null 923
this.isShown = null 924
this.originalBodyPad = null 925
this.scrollbarWidth = 0 926
this.ignoreBackdropClick = false 927
928
if (this.options.remote) { 929
this.$element 930
.find('.modal-content') 931
.load(this.options.remote, $.proxy(function () { 932
this.$element.trigger('loaded.bs.modal') 933
}, this)) 934
} 935
} 936
937
Modal.VERSION = '3.3.4' 938
939
Modal.TRANSITION_DURATION = 300 940
Modal.BACKDROP_TRANSITION_DURATION = 150 941
942
Modal.DEFAULTS = { 943
backdrop: true, 944
keyboard: true, 945
show: true 946
} 947
948
Modal.prototype.toggle = function (_relatedTarget) { 949
return this.isShown ? this.hide() : this.show(_relatedTarget) 950
} 951
952
Modal.prototype.show = function (_relatedTarget) { 953
var that = this 954
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) 955
956
this.$element.trigger(e) 957
958
if (this.isShown || e.isDefaultPrevented()) return 959
960
this.isShown = true 961
962
this.checkScrollbar() 963
this.setScrollbar() 964
this.$body.addClass('modal-open') 965
966
this.escape() 967
this.resize() 968
969
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) 970
971
this.$dialog.on('mousedown.dismiss.bs.modal', function () { 972
that.$element.one('mouseup.dismiss.bs.modal', function (e) { 973
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true 974
}) 975
}) 976
977
this.backdrop(function () { 978
var transition = $.support.transition && that.$element.hasClass('fade') 979
980
if (!that.$element.parent().length) { 981
that.$element.appendTo(that.$body) // don't move modals dom position 982
} 983
984
that.$element 985
.show() 986
.scrollTop(0) 987
988
that.adjustDialog() 989
990
if (transition) { 991
that.$element[0].offsetWidth // force reflow 992
} 993
994
that.$element 995
.addClass('in') 996
.attr('aria-hidden', false) 997
998
that.enforceFocus() 999
1000
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) 1001
1002
transition ? 1003
that.$dialog // wait for modal to slide in 1004
.one('bsTransitionEnd', function () { 1005
that.$element.trigger('focus').trigger(e) 1006
}) 1007
.emulateTransitionEnd(Modal.TRANSITION_DURATION) : 1008
that.$element.trigger('focus').trigger(e) 1009
}) 1010
} 1011
1012
Modal.prototype.hide = function (e) { 1013
if (e) e.preventDefault() 1014
1015
e = $.Event('hide.bs.modal') 1016
1017
this.$element.trigger(e) 1018
1019
if (!this.isShown || e.isDefaultPrevented()) return 1020
1021
this.isShown = false 1022
1023
this.escape() 1024
this.resize() 1025
1026
$(document).off('focusin.bs.modal') 1027
1028
this.$element 1029
.removeClass('in') 1030
.attr('aria-hidden', true) 1031
.off('click.dismiss.bs.modal') 1032
.off('mouseup.dismiss.bs.modal') 1033
1034
this.$dialog.off('mousedown.dismiss.bs.modal') 1035
1036
$.support.transition && this.$element.hasClass('fade') ? 1037
this.$element 1038
.one('bsTransitionEnd', $.proxy(this.hideModal, this)) 1039
.emulateTransitionEnd(Modal.TRANSITION_DURATION) : 1040
this.hideModal() 1041
} 1042
1043
Modal.prototype.enforceFocus = function () { 1044
$(document) 1045
.off('focusin.bs.modal') // guard against infinite focus loop 1046
.on('focusin.bs.modal', $.proxy(function (e) { 1047
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { 1048
this.$element.trigger('focus') 1049
} 1050
}, this)) 1051
} 1052
1053
Modal.prototype.escape = function () { 1054
if (this.isShown && this.options.keyboard) { 1055
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { 1056
e.which == 27 && this.hide() 1057
}, this)) 1058
} else if (!this.isShown) { 1059
this.$element.off('keydown.dismiss.bs.modal') 1060
} 1061
} 1062
1063
Modal.prototype.resize = function () { 1064
if (this.isShown) { 1065
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) 1066
} else { 1067
$(window).off('resize.bs.modal') 1068
} 1069
} 1070
1071
Modal.prototype.hideModal = function () { 1072
var that = this 1073
this.$element.hide() 1074
this.backdrop(function () { 1075
that.$body.removeClass('modal-open') 1076
that.resetAdjustments() 1077
that.resetScrollbar() 1078
that.$element.trigger('hidden.bs.modal') 1079
}) 1080
} 1081
1082
Modal.prototype.removeBackdrop = function () { 1083
this.$backdrop && this.$backdrop.remove() 1084
this.$backdrop = null 1085
} 1086
1087
Modal.prototype.backdrop = function (callback) { 1088
var that = this 1089
var animate = this.$element.hasClass('fade') ? 'fade' : '' 1090
1091
if (this.isShown && this.options.backdrop) { 1092
var doAnimate = $.support.transition && animate 1093
1094
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') 1095
.appendTo(this.$body) 1096
1097
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { 1098
if (this.ignoreBackdropClick) { 1099
this.ignoreBackdropClick = false 1100
return 1101
} 1102
if (e.target !== e.currentTarget) return 1103
this.options.backdrop == 'static' 1104
? this.$element[0].focus() 1105
: this.hide() 1106
}, this)) 1107
1108
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 1109
1110
this.$backdrop.addClass('in') 1111
1112
if (!callback) return 1113
1114
doAnimate ? 1115
this.$backdrop 1116
.one('bsTransitionEnd', callback) 1117
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 1118
callback() 1119
1120
} else if (!this.isShown && this.$backdrop) { 1121
this.$backdrop.removeClass('in') 1122
1123
var callbackRemove = function () { 1124
that.removeBackdrop() 1125
callback && callback() 1126
} 1127
$.support.transition && this.$element.hasClass('fade') ? 1128
this.$backdrop 1129
.one('bsTransitionEnd', callbackRemove) 1130
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 1131
callbackRemove() 1132
1133
} else if (callback) { 1134
callback() 1135
} 1136
} 1137
1138
// these following methods are used to handle overflowing modals 1139
1140
Modal.prototype.handleUpdate = function () { 1141
this.adjustDialog() 1142
} 1143
1144
Modal.prototype.adjustDialog = function () { 1145
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight 1146
1147
this.$element.css({ 1148
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', 1149
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' 1150
}) 1151
} 1152
1153
Modal.prototype.resetAdjustments = function () { 1154
this.$element.css({ 1155
paddingLeft: '', 1156
paddingRight: '' 1157
}) 1158
} 1159
1160
Modal.prototype.checkScrollbar = function () { 1161
var fullWindowWidth = window.innerWidth 1162
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 1163
var documentElementRect = document.documentElement.getBoundingClientRect() 1164
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) 1165
} 1166
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth 1167
this.scrollbarWidth = this.measureScrollbar() 1168
} 1169
1170
Modal.prototype.setScrollbar = function () { 1171
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) 1172
this.originalBodyPad = document.body.style.paddingRight || '' 1173
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) 1174
} 1175
1176
Modal.prototype.resetScrollbar = function () { 1177
this.$body.css('padding-right', this.originalBodyPad) 1178
} 1179
1180
Modal.prototype.measureScrollbar = function () { // thx walsh 1181
var scrollDiv = document.createElement('div') 1182
scrollDiv.className = 'modal-scrollbar-measure' 1183
this.$body.append(scrollDiv) 1184
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth 1185
this.$body[0].removeChild(scrollDiv) 1186
return scrollbarWidth 1187
} 1188
1189
1190
// MODAL PLUGIN DEFINITION 1191
// ======================= 1192
1193
function Plugin(option, _relatedTarget) { 1194
return this.each(function () { 1195
var $this = $(this) 1196
var data = $this.data('bs.modal') 1197
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) 1198
1199
if (!data) $this.data('bs.modal', (data = new Modal(this, options))) 1200
if (typeof option == 'string') data[option](_relatedTarget) 1201
else if (options.show) data.show(_relatedTarget) 1202
}) 1203
} 1204
1205
var old = $.fn.modal 1206
1207
$.fn.modal = Plugin 1208
$.fn.modal.Constructor = Modal 1209
1210
1211
// MODAL NO CONFLICT 1212
// ================= 1213
1214
$.fn.modal.noConflict = function () { 1215
$.fn.modal = old 1216
return this 1217
} 1218
1219
1220
// MODAL DATA-API 1221
// ============== 1222
1223
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { 1224
var $this = $(this) 1225
var href = $this.attr('href') 1226
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 1227
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) 1228
1229
if ($this.is('a')) e.preventDefault() 1230
1231
$target.one('show.bs.modal', function (showEvent) { 1232
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown 1233
$target.one('hidden.bs.modal', function () { 1234
$this.is(':visible') && $this.trigger('focus') 1235
}) 1236
}) 1237
Plugin.call($target, option, this) 1238
}) 1239
1240
}(jQuery); 1241
1242
/* ======================================================================== 1243
* Bootstrap: tooltip.js v3.3.4 1244
* http://getbootstrap.com/javascript/#tooltip 1245
* Inspired by the original jQuery.tipsy by Jason Frame 1246
* ======================================================================== 1247
* Copyright 2011-2015 Twitter, Inc. 1248
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1249
* ======================================================================== */ 1250
1251
1252
+function ($) { 1253
'use strict'; 1254
1255
// TOOLTIP PUBLIC CLASS DEFINITION 1256
// =============================== 1257
1258
var Tooltip = function (element, options) { 1259
this.type = null 1260
this.options = null 1261
this.enabled = null 1262
this.timeout = null 1263
this.hoverState = null 1264
this.$element = null 1265
1266
this.init('tooltip', element, options) 1267
} 1268
1269
Tooltip.VERSION = '3.3.4' 1270
1271
Tooltip.TRANSITION_DURATION = 150 1272
1273
Tooltip.DEFAULTS = { 1274
animation: true, 1275
placement: 'top', 1276
selector: false, 1277
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', 1278
trigger: 'hover focus', 1279
title: '', 1280
delay: 0, 1281
html: false, 1282
container: false, 1283
viewport: { 1284
selector: 'body', 1285
padding: 0 1286
} 1287
} 1288
1289
Tooltip.prototype.init = function (type, element, options) { 1290
this.enabled = true 1291
this.type = type 1292
this.$element = $(element) 1293
this.options = this.getOptions(options) 1294
this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport) 1295
1296
if (this.$element[0] instanceof document.constructor && !this.options.selector) { 1297
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') 1298
} 1299
1300
var triggers = this.options.trigger.split(' ') 1301
1302
for (var i = triggers.length; i--;) { 1303
var trigger = triggers[i] 1304
1305
if (trigger == 'click') { 1306
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) 1307
} else if (trigger != 'manual') { 1308
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' 1309
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' 1310
1311
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) 1312
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) 1313
} 1314
} 1315
1316
this.options.selector ? 1317
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : 1318
this.fixTitle() 1319
} 1320
1321
Tooltip.prototype.getDefaults = function () { 1322
return Tooltip.DEFAULTS 1323
} 1324
1325
Tooltip.prototype.getOptions = function (options) { 1326
options = $.extend({}, this.getDefaults(), this.$element.data(), options) 1327
1328
if (options.delay && typeof options.delay == 'number') { 1329
options.delay = { 1330
show: options.delay, 1331
hide: options.delay 1332
} 1333
} 1334
1335
return options 1336
} 1337
1338
Tooltip.prototype.getDelegateOptions = function () { 1339
var options = {} 1340
var defaults = this.getDefaults() 1341
1342
this._options && $.each(this._options, function (key, value) { 1343
if (defaults[key] != value) options[key] = value 1344
}) 1345
1346
return options 1347
} 1348
1349
Tooltip.prototype.enter = function (obj) { 1350
var self = obj instanceof this.constructor ? 1351
obj : $(obj.currentTarget).data('bs.' + this.type) 1352
1353
if (self && self.$tip && self.$tip.is(':visible')) { 1354
self.hoverState = 'in' 1355
return 1356
} 1357
1358
if (!self) { 1359
self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1360
$(obj.currentTarget).data('bs.' + this.type, self) 1361
} 1362
1363
clearTimeout(self.timeout) 1364
1365
self.hoverState = 'in' 1366
1367
if (!self.options.delay || !self.options.delay.show) return self.show() 1368
1369
self.timeout = setTimeout(function () { 1370
if (self.hoverState == 'in') self.show() 1371
}, self.options.delay.show) 1372
} 1373
1374
Tooltip.prototype.leave = function (obj) { 1375
var self = obj instanceof this.constructor ? 1376
obj : $(obj.currentTarget).data('bs.' + this.type) 1377
1378
if (!self) { 1379
self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1380
$(obj.currentTarget).data('bs.' + this.type, self) 1381
} 1382
1383
clearTimeout(self.timeout) 1384
1385
self.hoverState = 'out' 1386
1387
if (!self.options.delay || !self.options.delay.hide) return self.hide() 1388
1389
self.timeout = setTimeout(function () { 1390
if (self.hoverState == 'out') self.hide() 1391
}, self.options.delay.hide) 1392
} 1393
1394
Tooltip.prototype.show = function () { 1395
var e = $.Event('show.bs.' + this.type) 1396
1397
if (this.hasContent() && this.enabled) { 1398
this.$element.trigger(e) 1399
1400
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) 1401
if (e.isDefaultPrevented() || !inDom) return 1402
var that = this 1403
1404
var $tip = this.tip() 1405
1406
var tipId = this.getUID(this.type) 1407
1408
this.setContent() 1409
$tip.attr('id', tipId) 1410
this.$element.attr('aria-describedby', tipId) 1411
1412
if (this.options.animation) $tip.addClass('fade') 1413
1414
var placement = typeof this.options.placement == 'function' ? 1415
this.options.placement.call(this, $tip[0], this.$element[0]) : 1416
this.options.placement 1417
1418
var autoToken = /\s?auto?\s?/i 1419
var autoPlace = autoToken.test(placement) 1420
if (autoPlace) placement = placement.replace(autoToken, '') || 'top' 1421
1422
$tip 1423
.detach() 1424
.css({ top: 0, left: 0, display: 'block' }) 1425
.addClass(placement) 1426
.data('bs.' + this.type, this) 1427
1428
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) 1429
1430
var pos = this.getPosition() 1431
var actualWidth = $tip[0].offsetWidth 1432
var actualHeight = $tip[0].offsetHeight 1433
1434
if (autoPlace) { 1435
var orgPlacement = placement 1436
var $container = this.options.container ? $(this.options.container) : this.$element.parent() 1437
var containerDim = this.getPosition($container) 1438
1439
placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' : 1440
placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' : 1441
placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' : 1442
placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' : 1443
placement 1444
1445
$tip 1446
.removeClass(orgPlacement) 1447
.addClass(placement) 1448
} 1449
1450
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) 1451
1452
this.applyPlacement(calculatedOffset, placement) 1453
1454
var complete = function () { 1455
var prevHoverState = that.hoverState 1456
that.$element.trigger('shown.bs.' + that.type) 1457
that.hoverState = null 1458
1459
if (prevHoverState == 'out') that.leave(that) 1460
} 1461
1462
$.support.transition && this.$tip.hasClass('fade') ? 1463
$tip 1464
.one('bsTransitionEnd', complete) 1465
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : 1466
complete() 1467
} 1468
} 1469
1470
Tooltip.prototype.applyPlacement = function (offset, placement) { 1471
var $tip = this.tip() 1472
var width = $tip[0].offsetWidth 1473
var height = $tip[0].offsetHeight 1474
1475
// manually read margins because getBoundingClientRect includes difference 1476
var marginTop = parseInt($tip.css('margin-top'), 10) 1477
var marginLeft = parseInt($tip.css('margin-left'), 10) 1478
1479
// we must check for NaN for ie 8/9 1480
if (isNaN(marginTop)) marginTop = 0 1481
if (isNaN(marginLeft)) marginLeft = 0 1482
1483
offset.top = offset.top + marginTop 1484
offset.left = offset.left + marginLeft 1485
1486
// $.fn.offset doesn't round pixel values 1487
// so we use setOffset directly with our own function B-0 1488
$.offset.setOffset($tip[0], $.extend({ 1489
using: function (props) { 1490
$tip.css({ 1491
top: Math.round(props.top), 1492
left: Math.round(props.left) 1493
}) 1494
} 1495
}, offset), 0) 1496
1497
$tip.addClass('in') 1498
1499
// check to see if placing tip in new offset caused the tip to resize itself 1500
var actualWidth = $tip[0].offsetWidth 1501
var actualHeight = $tip[0].offsetHeight 1502
1503
if (placement == 'top' && actualHeight != height) { 1504
offset.top = offset.top + height - actualHeight 1505
} 1506
1507
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) 1508
1509
if (delta.left) offset.left += delta.left 1510
else offset.top += delta.top 1511
1512
var isVertical = /top|bottom/.test(placement) 1513
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight 1514
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' 1515
1516
$tip.offset(offset) 1517
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) 1518
} 1519
1520
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { 1521
this.arrow() 1522
.css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') 1523
.css(isVertical ? 'top' : 'left', '') 1524
} 1525
1526
Tooltip.prototype.setContent = function () { 1527
var $tip = this.tip() 1528
var title = this.getTitle() 1529
1530
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) 1531
$tip.removeClass('fade in top bottom left right') 1532
} 1533
1534
Tooltip.prototype.hide = function (callback) { 1535
var that = this 1536
var $tip = $(this.$tip) 1537
var e = $.Event('hide.bs.' + this.type) 1538
1539
function complete() { 1540
if (that.hoverState != 'in') $tip.detach() 1541
that.$element 1542
.removeAttr('aria-describedby') 1543
.trigger('hidden.bs.' + that.type) 1544
callback && callback() 1545
} 1546
1547
this.$element.trigger(e) 1548
1549
if (e.isDefaultPrevented()) return 1550
1551
$tip.removeClass('in') 1552
1553
$.support.transition && $tip.hasClass('fade') ? 1554
$tip 1555
.one('bsTransitionEnd', complete) 1556
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : 1557
complete() 1558
1559
this.hoverState = null 1560
1561
return this 1562
} 1563
1564
Tooltip.prototype.fixTitle = function () { 1565
var $e = this.$element 1566
if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') { 1567
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '') 1568
} 1569
} 1570
1571
Tooltip.prototype.hasContent = function () { 1572
return this.getTitle() 1573
} 1574
1575
Tooltip.prototype.getPosition = function ($element) { 1576
$element = $element || this.$element 1577
1578
var el = $element[0] 1579
var isBody = el.tagName == 'BODY' 1580
1581
var elRect = el.getBoundingClientRect() 1582
if (elRect.width == null) { 1583
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 1584
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) 1585
} 1586
var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() 1587
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } 1588
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null 1589
1590
return $.extend({}, elRect, scroll, outerDims, elOffset) 1591
} 1592
1593
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { 1594
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1595
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1596
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : 1597
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } 1598
1599
} 1600
1601
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { 1602
var delta = { top: 0, left: 0 } 1603
if (!this.$viewport) return delta 1604
1605
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 1606
var viewportDimensions = this.getPosition(this.$viewport) 1607
1608
if (/right|left/.test(placement)) { 1609
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll 1610
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight 1611
if (topEdgeOffset < viewportDimensions.top) { // top overflow 1612
delta.top = viewportDimensions.top - topEdgeOffset 1613
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow 1614
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset 1615
} 1616
} else { 1617
var leftEdgeOffset = pos.left - viewportPadding 1618
var rightEdgeOffset = pos.left + viewportPadding + actualWidth 1619
if (leftEdgeOffset < viewportDimensions.left) { // left overflow 1620
delta.left = viewportDimensions.left - leftEdgeOffset 1621
} else if (rightEdgeOffset > viewportDimensions.width) { // right overflow 1622
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset 1623
} 1624
} 1625
1626
return delta 1627
} 1628
1629
Tooltip.prototype.getTitle = function () { 1630
var title 1631
var $e = this.$element 1632
var o = this.options 1633
1634
title = $e.attr('data-original-title') 1635
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) 1636
1637
return title 1638
} 1639
1640
Tooltip.prototype.getUID = function (prefix) { 1641
do prefix += ~~(Math.random() * 1000000) 1642
while (document.getElementById(prefix)) 1643
return prefix 1644
} 1645
1646
Tooltip.prototype.tip = function () { 1647
return (this.$tip = this.$tip || $(this.options.template)) 1648
} 1649
1650
Tooltip.prototype.arrow = function () { 1651
return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) 1652
} 1653
1654
Tooltip.prototype.enable = function () { 1655
this.enabled = true 1656
} 1657
1658
Tooltip.prototype.disable = function () { 1659
this.enabled = false 1660
} 1661
1662
Tooltip.prototype.toggleEnabled = function () { 1663
this.enabled = !this.enabled 1664
} 1665
1666
Tooltip.prototype.toggle = function (e) { 1667
var self = this 1668
if (e) { 1669
self = $(e.currentTarget).data('bs.' + this.type) 1670
if (!self) { 1671
self = new this.constructor(e.currentTarget, this.getDelegateOptions()) 1672
$(e.currentTarget).data('bs.' + this.type, self) 1673
} 1674
} 1675
1676
self.tip().hasClass('in') ? self.leave(self) : self.enter(self) 1677
} 1678
1679
Tooltip.prototype.destroy = function () { 1680
var that = this 1681
clearTimeout(this.timeout) 1682
this.hide(function () { 1683
that.$element.off('.' + that.type).removeData('bs.' + that.type) 1684
}) 1685
} 1686
1687
1688
// TOOLTIP PLUGIN DEFINITION 1689
// ========================= 1690
1691
function Plugin(option) { 1692
return this.each(function () { 1693
var $this = $(this) 1694
var data = $this.data('bs.tooltip') 1695
var options = typeof option == 'object' && option 1696
1697
if (!data && /destroy|hide/.test(option)) return 1698
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) 1699
if (typeof option == 'string') data[option]() 1700
}) 1701
} 1702
1703
var old = $.fn.tooltip 1704
1705
$.fn.tooltip = Plugin 1706
$.fn.tooltip.Constructor = Tooltip 1707
1708
1709
// TOOLTIP NO CONFLICT 1710
// =================== 1711
1712
$.fn.tooltip.noConflict = function () { 1713
$.fn.tooltip = old 1714
return this 1715
} 1716
1717
}(jQuery); 1718
1719
/* ======================================================================== 1720
* Bootstrap: popover.js v3.3.4 1721
* http://getbootstrap.com/javascript/#popovers 1722
* ======================================================================== 1723
* Copyright 2011-2015 Twitter, Inc. 1724
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1725
* ======================================================================== */ 1726
1727
1728
+function ($) { 1729
'use strict'; 1730
1731
// POPOVER PUBLIC CLASS DEFINITION 1732
// =============================== 1733
1734
var Popover = function (element, options) { 1735
this.init('popover', element, options) 1736
} 1737
1738
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') 1739
1740