Commit 6b261ad25f33db14361fe26eb915e990e1aa2981
1 parent
1653dec577
Exists in
master
and in
1 other branch
don't require login for email verification
Showing 5 changed files with 8 additions and 2325 deletions Inline Diff
Makefile
View file @
6b261ad
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 |
config.js
View file @
6b261ad
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 | } |
home.html
View file @
6b261ad
<!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">--> | |
<!--© 2015 Team Swag--> | 134 | 134 | <!--© 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 |