Commit f60d5698a796fd0bd27f044887940247a4140139
Exists in
master
and in
1 other branch
Fixed merge conflict in SettingsController.js
Showing 15 changed files Inline Diff
- .gitignore
- Makefile
- casper_test.js
- config.js
- home.html
- scripts/CardGridController.js
- scripts/ResetPasswordController.js
- scripts/SettingsController.js
- scripts/StudyController.js
- scripts/UserService.js
- scripts/bootstrap.js
- static/js/angular-contenteditable.js
- static/js/angular-websocket.js
- test.png
- ws_debug.html
.gitignore
View file @
f60d569
*~ | 1 | 1 | *~ | |
venv* | 2 | 2 | venv* | |
static* | 3 | |||
*.pyc | 4 | 3 | *.pyc | |
.idea* | 5 | 4 | .idea* | |
.*.swp | 6 | 5 | .*.swp | |
.*.swo | 7 | 6 | .*.swo | |
.*.swn | 8 | 7 | .*.swn | |
*.sqlite3 | 9 | 8 | *.sqlite3 | |
secrets | 10 | 9 | secrets | |
.coverage | 11 | 10 | .coverage | |
htmlcov* | 12 | 11 | htmlcov* | |
sass/*.css | 13 | 12 | sass/*.css |
Makefile
View file @
f60d569
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.js) $(wildcard scripts/*Directive.js) $(wildcard scripts/*Service.js) | |||
4 | ||||
5 | print-%: | |||
6 | @echo '$*=$($*)' | |||
7 | ||||
styles/materialize.css: .PHONY | 3 | 8 | styles/materialize.css: .PHONY | |
sassc sass/materialize.scss styles/materialize.css | 4 | 9 | sassc sass/materialize.scss styles/materialize.css | |
5 | 10 | |||
styles/flashier.css: sass/flashier.scss | 6 | 11 | styles/flashier.css: sass/flashier.scss | |
sassc sass/flashier.scss styles/flashier.css | 7 | 12 | sassc sass/flashier.scss styles/flashier.css | |
8 | 13 | |||
fixjsstyle: .PHONY | 9 | 14 | fixjsstyle: .PHONY |
casper_test.js
View file @
f60d569
phantom.casperPath = 'path/to/node_modules/casperjs'; | 1 | File was deleted | ||
phantom.injectJs('path/to/node_modules/casperjs/bin/bootstrap.js'); | 2 | |||
3 | ||||
phantom.casperTest = true; | 4 | |||
5 | ||||
6 | ||||
//var casper = require('casper'); | 7 | |||
var x = require('casper').selectXPath; | 8 | |||
9 | ||||
casper.start('http://google.com/', function() { | 10 |
config.js
View file @
f60d569
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 @
f60d569
<!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/CardGridController.js
View file @
f60d569
angular.module('flashy.CardGridController', ['ui.router', 'ngAnimate', 'ngWebSocket']).controller = | 1 | 1 | angular.module('flashy.CardGridController', ['ui.router', 'ngAnimate', 'ngWebSocket']).controller = | |
function($scope, $rootScope, $state, $http, $window, $timeout, $stateParams, $websocket, UserService) { | 2 | 2 | function($scope, $rootScope, $state, $http, $window, $timeout, $stateParams, $websocket, UserService) { | |
$scope.cards = false; | 3 | 3 | $scope.cards = false; | |
$scope.cardCols = []; // organized data | 4 | 4 | $scope.cardCols = []; // organized data | |
$scope.numCols = 0; | 5 | 5 | $scope.numCols = 0; | |
$scope.cardTable = {}; // look up table of cards, {'colNum':col, 'obj':card} | 6 | 6 | $scope.cardTable = {}; // look up table of cards, {'colNum':col, 'obj':card} | |
$scope.sectionId = parseInt($stateParams.sectionId); | 7 | 7 | $scope.sectionId = parseInt($stateParams.sectionId); | |
$scope.section = $rootScope.SectionResource.get({sectionId: $scope.sectionId}); | 8 | 8 | $scope.section = $rootScope.SectionResource.get({sectionId: $scope.sectionId}); | |
$rootScope.currentSection = $scope.section; | 9 | 9 | $rootScope.currentSection = $scope.section; | |
10 | 10 | |||
if (!UserService.isInSection($scope.sectionId)) { | 11 | 11 | if (!UserService.isInSection($scope.sectionId)) { | |
console.log('user is not enrolled in ' + $scope.sectionId); | 12 | 12 | console.log('user is not enrolled in ' + $scope.sectionId); | |
$state.go('addclass'); | 13 | 13 | $state.go('addclass'); | |
} | 14 | 14 | } | |
15 | 15 | |||
$scope.refreshColumnWidth = function() { | 16 | 16 | $scope.refreshColumnWidth = function() { | |
avail = $window.innerWidth - 17; | 17 | 17 | avail = $window.innerWidth - 17; | |
width = Math.floor(avail / Math.floor(avail / 250)); | 18 | 18 | width = Math.floor(avail / Math.floor(avail / 250)); | |
$('.cardColumn').css({ | 19 | 19 | $('.cardColumn').css({ | |
width: width + 'px', | 20 | 20 | width: width + 'px', | |
'font-size': 100 * width / 250 + '%' | 21 | 21 | 'font-size': 100 * width / 250 + '%' | |
}); | 22 | 22 | }); | |
$('.cardColumn .card.flashy').css({ | 23 | 23 | $('.cardColumn .card.flashy').css({ | |
width: width - 12 + 'px', | 24 | 24 | width: width - 12 + 'px', | |
height: (width * 3 / 5) + 'px' | 25 | 25 | height: (width * 3 / 5) + 'px' | |
}); | 26 | 26 | }); | |
}; | 27 | 27 | }; | |
28 | 28 | |||
$scope.pullCard = function(id) { | 29 | 29 | $scope.pullCard = function(id) { | |
if ($scope.deck_cards[id]) return console.log('Not pulling', id, "because it's already in deck"); | 30 | 30 | if ($scope.deck_cards[id]) return console.log('Not pulling', id, "because it's already in deck"); | |
$http.post('/api/flashcards/' + id + '/pull/'). | 31 | 31 | $http.post('/api/flashcards/' + id + '/pull/'). | |
success(function(data) { | 32 | 32 | success(function(data) { | |
console.log('pulled flashcard #' + id); | 33 | 33 | console.log('pulled flashcard #' + id); | |
}). | 34 | 34 | }). | |
error(function(data) { | 35 | 35 | error(function(data) { | |
console.log('failed to pull flashcard #' + id); | 36 | 36 | console.log('failed to pull flashcard #' + id); | |
}); | 37 | 37 | }); | |
}; | 38 | 38 | }; | |
$scope.unpullCard = function(id) { | 39 | 39 | $scope.unpullCard = function(id) { | |
if (!$scope.deck_cards[id]) return console.log('Not unpulling', id, "because it's not in deck"); | 40 | 40 | if (!$scope.deck_cards[id]) return console.log('Not unpulling', id, "because it's not in deck"); | |
$http.post('/api/flashcards/' + id + '/unpull/'). | 41 | 41 | $http.post('/api/flashcards/' + id + '/unpull/'). | |
success(function(data) { | 42 | 42 | success(function(data) { | |
console.log('unpulled flashcard #' + id); | 43 | 43 | console.log('unpulled flashcard #' + id); | |
}). | 44 | 44 | }). | |
error(function(data) { | 45 | 45 | error(function(data) { | |
console.log('failed to unpull flashcard #' + id); | 46 | 46 | console.log('failed to unpull flashcard #' + id); | |
}); | 47 | 47 | }); | |
}; | 48 | 48 | }; | |
$scope.refreshLayout = function() { | 49 | 49 | $scope.refreshLayout = function() { | |
numCols = Math.max(1, Math.floor(($window.innerWidth - 17) / 250)); | 50 | 50 | numCols = Math.max(1, Math.floor(($window.innerWidth - 17) / 250)); | |
51 | 51 | |||
// check if we actually need to refresh the whole layout | 52 | 52 | // check if we actually need to refresh the whole layout | |
if (numCols == $scope.numCols) return $scope.refreshColumnWidth(); | 53 | 53 | if (numCols == $scope.numCols) return $scope.refreshColumnWidth(); | |
$scope.numCols = numCols; | 54 | 54 | $scope.numCols = numCols; | |
console.log('refreshing layout for ' + $scope.numCols + ' columns'); | 55 | 55 | console.log('refreshing layout for ' + $scope.numCols + ' columns'); | |
$scope.cardCols = []; | 56 | 56 | $scope.cardCols = []; | |
var cols = []; | 57 | 57 | var cols = []; | |
for (i = 0; i < $scope.numCols; i++) cols.push([]); | 58 | 58 | for (i = 0; i < $scope.numCols; i++) cols.push([]); | |
$scope.cards.forEach(function(card, i) { | 59 | 59 | $scope.cards.forEach(function(card, i) { | |
cols[i % $scope.numCols].push(card); | 60 | 60 | cols[i % $scope.numCols].push(card); | |
$scope.cardTable[card.id] = {'colNum': (i % $scope.numCols), 'obj': card}; | 61 | 61 | $scope.cardTable[card.id] = {'colNum': (i % $scope.numCols), 'obj': card}; | |
card.isInDeck(); | 62 | 62 | card.isInDeck(); | |
}); | 63 | 63 | }); | |
// wait until the next digest cycle to update cardCols | 64 | 64 | // wait until the next digest cycle to update cardCols | |
console.log(cols); | 65 | 65 | console.log(cols); | |
$timeout(function() { | 66 | 66 | $timeout(function() { | |
$scope.cardCols = cols; | 67 | 67 | $scope.cardCols = cols; | |
$timeout($scope.refreshColumnWidth); | 68 | 68 | $timeout($scope.refreshColumnWidth); | |
}); | 69 | 69 | }); | |
70 | 70 | |||
}; | 71 | 71 | }; | |
72 | 72 | |||
angular.element($window).bind('resize', $scope.refreshLayout); | 73 | 73 | angular.element($window).bind('resize', $scope.refreshLayout); | |
74 | 74 | |||
$scope.ws_host = window.location.origin.replace('http', 'ws'); | 75 | 75 | $scope.ws_host = window.location.origin.replace('http', 'ws'); | |
$scope.deck_ws = $websocket($scope.ws_host + '/ws/deck/' + $scope.sectionId + '?subscribe-user'); | 76 | 76 | $scope.deck_ws = $websocket($scope.ws_host + '/ws/deck/' + $scope.sectionId + '?subscribe-user'); | |
$scope.deck_ws.onOpen(function() { | 77 | 77 | $scope.deck_ws.onOpen(function() { | |
console.log('deck ws open'); | 78 | 78 | console.log('deck ws open'); | |
}); | 79 | 79 | }); | |
80 | 80 | |||
$scope.deck_ws.onMessage(function(message) { | 81 | 81 | $scope.deck_ws.onMessage(function(message) { | |
data = JSON.parse(message.data); | 82 | 82 | data = JSON.parse(message.data); | |
console.log('message', data); | 83 | 83 | console.log('message', data); | |
if (data.event_type == 'pull_card') { | 84 | 84 | if (data.event_type == 'pull_card') { | |
card = data.flashcard; | 85 | 85 | card = data.flashcard; | |
console.log('pulling', card); | 86 | 86 | console.log('pulling', card); | |
$scope.deck_cards[card.id] = card; | 87 | 87 | $scope.deck_cards[card.id] = card; | |
} | 88 | 88 | } | |
if (data.event_type == 'unpull_card') { | 89 | 89 | if (data.event_type == 'unpull_card') { | |
card = data.flashcard; | 90 | 90 | card = data.flashcard; | |
$scope.deck_cards[card.id] = undefined; | 91 | 91 | $scope.deck_cards[card.id] = undefined; | |
} | 92 | 92 | } | |
}); | 93 | 93 | }); | |
94 | 94 | |||
$scope.cardInDeck = function(id) { | 95 | 95 | $scope.cardInDeck = function(id) { | |
return $scope.deck_cards[id]; | 96 | 96 | return $scope.deck_cards[id]; | |
}; | 97 | 97 | }; | |
$scope.add = function(card) { | 98 | 98 | $scope.add = function(card) { | |
var colNum = 0; | 99 | 99 | var colNum = 0; | |
var lowestCol = $scope.cardCols[0]; | 100 | 100 | var lowestCol = $scope.cardCols[0]; | |
var lowestColNum = 0; | 101 | 101 | var lowestColNum = 0; | |
while (colNum < $scope.numCols) { | 102 | 102 | while (colNum < $scope.numCols) { | |
if ($scope.cardCols[colNum].length == 0) { | 103 | 103 | if ($scope.cardCols[colNum].length == 0) { | |
lowestCol = $scope.cardCols[colNum]; | 104 | 104 | lowestCol = $scope.cardCols[colNum]; | |
break; | 105 | 105 | break; | |
} else if ($scope.cardCols[colNum].length < lowestCol.length) { | 106 | 106 | } else if ($scope.cardCols[colNum].length < lowestCol.length) { | |
lowestCol = $scope.cardCols[colNum]; | 107 | 107 | lowestCol = $scope.cardCols[colNum]; | |
lowestColNum = colNum; | 108 | 108 | lowestColNum = colNum; | |
lowestColLen = $scope.cardCols[colNum].length; | 109 | 109 | lowestColLen = $scope.cardCols[colNum].length; | |
} | 110 | 110 | } | |
colNum++; | 111 | 111 | colNum++; | |
} | 112 | 112 | } | |
console.log(card); | 113 | 113 | console.log(card); | |
$scope.cards.push(data); | 114 | 114 | $scope.cards.push(data); | |
lowestCol.unshift(card); | 115 | 115 | lowestCol.unshift(card); | |
$scope.cardTable[card.id] = {'colNum': lowestColNum, 'obj': card}; | 116 | 116 | $scope.cardTable[card.id] = {'colNum': lowestColNum, 'obj': card}; | |
$timeout($scope.refreshColumnWidth); | 117 | 117 | $timeout($scope.refreshColumnWidth); | |
}; | 118 | 118 | }; | |
119 | 119 | |||
120 | ||||
121 | $scope.deck_cards = []; | |||
$http.get('/api/sections/' + $scope.sectionId + '/deck/'). | 120 | 122 | $http.get('/api/sections/' + $scope.sectionId + '/deck/'). | |
success(function(data) { | 121 | 123 | success(function(data) { | |
$scope.deck_cards = []; | 122 | 124 | ||
for (i in data) $scope.deck_cards[data[i].id] = data[i]; | 123 | 125 | for (i in data) $scope.deck_cards[data[i].id] = data[i]; | |
console.log("got user's deck"); | 124 | 126 | console.log("got user's deck"); | |
}). | 125 | 127 | }). |
scripts/ResetPasswordController.js
View file @
f60d569
angular.module('flashy.ResetPasswordController', ['ui.router']). | 1 | 1 | angular.module('flashy.ResetPasswordController', ['ui.router']). | |
2 | 2 | |||
controller('ResetPasswordController', ['$scope', '$state', '$http', '$timeout', | 3 | 3 | controller('ResetPasswordController', ['$scope', '$state', '$http', '$timeout', | |
function($scope, $state, $http, $timeout) { | 4 | 4 | function($scope, $state, $http, $timeout) { | |
'use strict'; | 5 | 5 | 'use strict'; | |
var url = document.location.href.split('/'); | 6 | 6 | var url = document.location.href.split('/'); | |
var token = url[url.length - 1]; | 7 | 7 | var token = url[url.length - 1]; | |
var uid = url[url.length - 2]; | 8 | 8 | var uid = url[url.length - 2]; | |
$scope.error = false; | 9 | 9 | $scope.error = false; | |
$scope.success = false; | 10 | 10 | $scope.success = false; | |
$scope.mismatch = false; | 11 | 11 | $scope.mismatch = false; | |
$scope.unacceptable = false; | 12 | 12 | $scope.unacceptable = false; | |
/*if(token == 'resetpassword') { | 13 | 13 | /*if(token == 'resetpassword') { | |
$state.go('login'); | 14 | 14 | $state.go('login'); | |
}*/ | 15 | 15 | }*/ | |
console.log('RESETTING'); | 16 | 16 | console.log('RESETTING'); | |
$scope.confirmResetPass = function() { | 17 | 17 | $scope.confirmResetPass = function() { | |
if ($scope.newPassword.length < 8) { | 18 | 18 | if ($scope.newPassword.length < 8) { | |
$scope.unacceptable = true; | 19 | 19 | $scope.unacceptable = true; | |
return; | 20 | 20 | return; | |
} | 21 | 21 | } | |
if ($scope.newPassword != $scope.confirmPassword) { | 22 | 22 | if ($scope.newPassword != $scope.confirmPassword) { | |
$scope.mismatch = true; | 23 | 23 | $scope.mismatch = true; | |
$scope.confirmPassword.$setPristine(); | 24 | 24 | $scope.confirmPassword.$setPristine(); | |
console.log('mismatch'); | 25 | 25 | console.log('mismatch'); | |
return; | 26 | 26 | return; | |
} | 27 | 27 | } | |
/*password passes local tests*/ | 28 | 28 | /*password passes local tests*/ | |
$http.post('/api/reset_password/', JSON.stringify({ | 29 | 29 | $http.post('/api/reset_password/', JSON.stringify({ | |
'uid': uid, | 30 | 30 | 'uid': uid, | |
'token': token, | 31 | 31 | 'token': token, | |
'new_password': $scope.newPassword | 32 | 32 | 'new_password': $scope.newPassword | |
})) | 33 | 33 | })) | |
.success(function(data) { | 34 | 34 | .success(function(data) { | |
$scope.error = false; | 35 | 35 | $scope.error = false; | |
$scope.success = true; | 36 | 36 | $scope.success = true; | |
//$state.go('resetpasssuccess'); | 37 | 37 | //$state.go('resetpasssuccess'); | |
$timeout(function($state) { | 38 | 38 | $timeout(function($state) { | |
$state.go('login'); | 39 | 39 | $state.go('login'); | |
}, 1000); | 40 | 40 | }, 1000); | |
console.log(data); | 41 | 41 | console.log(data); | |
}) | 42 | 42 | }) | |
.error(function(data, status, header, config) { | 43 | 43 | .error(function(data, status, header, config) { | |
$scope.error = true; | 44 | 44 | $scope.error = true; | |
$scope.success = false; | 45 | 45 | $scope.success = false; | |
$scope.mismatch = false; | 46 | 46 | $scope.mismatch = false; | |
$scope.unacceptable = false; | 47 | 47 | $scope.unacceptable = false; | |
console.log(data); | 48 | 48 | console.log(data); | |
}); | 49 | 49 | }); | |
}; | 50 | 50 | }; | |
$scope.cancelReset = function() { | 51 | 51 | $scope.cancelReset = function() { | |
$state.go('login'); | 52 | 52 | $state.go('login'); | |
}; | 53 | 53 | }; | |
}]); | 54 | 54 | }]); | |
55 | 55 | |||
scripts/SettingsController.js
View file @
f60d569
angular.module('flashy.SettingsController', ['ui.router']). | 1 | 1 | angular.module('flashy.SettingsController', ['ui.router']). | |
2 | 2 | |||
controller('SettingsController', function($scope, $http) { | 3 | 3 | controller('SettingsController', function($scope, $http) { | |
$scope.changePassword = function(oldPassword, newPassword, confirmedNewPassword) { | 4 | 4 | $scope.changePassword = function(oldPassword, newPassword, confirmedNewPassword) { | |
5 | 5 | |||
6 | ||||
7 | ||||
8 | ||||
}; | 9 | 6 | }; | |
console.log("checking to see if chrome"); | 10 | 7 | console.log("checking to see if chrome"); | |
if (!chrome) { return; } | 11 | 8 | if (!chrome) { return; } | |
console.log("chrome"); | 12 | 9 | console.log("chrome"); | |
$scope.registerCallback = function(registrationId) { | 13 | 10 | $scope.registerCallback = function(registrationId) { | |
if (chrome.runtime.lastError) { | 14 | 11 | if (chrome.runtime.lastError) { | |
console.log("Registration failed") | 15 | 12 | console.log("Registration failed") | |
} | 16 | 13 | } | |
17 | 14 | |||
sendRegistrationId(registrationId, function(succeed) { | 18 | 15 | sendRegistrationId(registrationId, function(succeed) { | |
if (succeed) { | 19 | 16 | if (succeed) { | |
chrome.storage.local.set({registered: true}); | 20 | 17 | chrome.storage.local.set({registered: true}); | |
} | 21 | 18 | } | |
}); | 22 | 19 | }); | |
}; | 23 | 20 | }; | |
24 | 21 | |||
function sendRegistrationId(registrationId, callback) { | 25 | 22 | function sendRegistrationId(registrationId, callback) { | |
console.log("registration id: "+registrationId); | 26 | 23 | console.log("registration id: "+registrationId); | |
$http.post('/api/subscribe/', JSON.stringify({ | 27 | 24 | $http.post('/api/subscribe/', JSON.stringify({ | |
'registration_id': registrationId | 28 | 25 | 'registration_id': registrationId | |
})); | 29 | 26 | })); | |
callback(true); | 30 | 27 | callback(true); | |
} | 31 | 28 | } | |
32 | 29 | |||
console.log(chrome.runtime.onStartup); | 33 | 30 | console.log(chrome.runtime.onStartup); | |
chrome.runtime.onStartup.addListener(function() { | 34 | 31 | chrome.runtime.onStartup.addListener(function() { | |
chrome.storage.local.get("registered", function(result) { | 35 | 32 | chrome.storage.local.get("registered", function(result) { | |
// If already registered, bail out. | 36 | 33 | // If already registered, bail out. |
scripts/StudyController.js
View file @
f60d569
angular.module('flashy.StudyController', ['ui.router']). | 1 | 1 | angular.module('flashy.StudyController', ['ui.router']). | |
2 | 2 | |||
controller('StudyController', ['$scope', '$stateParams', '$state', '$http', 'UserService', | 3 | 3 | controller('StudyController', ['$scope', '$stateParams', '$state', '$http', 'UserService', | |
function($scope, $stateParams, $state, $http, UserService) { | 4 | 4 | function($scope, $stateParams, $state, $http, UserService) { | |
console.log('Flashy study controller content in this file. also hell0'); | 5 | 5 | console.log('Flashy study controller content in this file. also hell0'); | |
sectionId = $stateParams.sectionId; | 6 | 6 | sectionId = $stateParams.sectionId; | |
$scope.isParamOpen = true; | 7 | 7 | $scope.isParamOpen = true; | |
8 | 8 | |||
$(document).ready(function() { | 9 | 9 | $(document).ready(function() { | |
$('.datepicker').pickadate({ | 10 | 10 | $('.datepicker').pickadate({ | |
selectMonths: true, // Creates a dropdown to control month | 11 | 11 | selectMonths: true, // Creates a dropdown to control month | |
selectYears: 15 // Creates a dropdown of 15 years to control year | 12 | 12 | selectYears: 15 // Creates a dropdown of 15 years to control year | |
}); | 13 | 13 | }); | |
14 | 14 | |||
$('select').material_select(); | 15 | 15 | $('select').material_select(); | |
}); | 16 | 16 | }); | |
17 | 17 | |||
$scope.UserService = UserService; | 18 | 18 | $scope.UserService = UserService; | |
$scope.isParamOpen = true; | 19 | 19 | $scope.isParamOpen = true; | |
20 | 20 | |||
console.log($scope.UserService.getUserData().sections); | 21 | 21 | console.log($scope.UserService.getUserData().sections); | |
22 | 22 | |||
$scope.toggleSectionToStudy = function(id) { | 23 | 23 | $scope.toggleSectionToStudy = function(id) { | |
console.log('toggle sect', id); | 24 | 24 | console.log('toggle sect', id); | |
$scope.sectionToStudy = id; | 25 | 25 | $scope.sectionToStudy = id; | |
}; | 26 | 26 | }; | |
27 | 27 | |||
$scope.openParams = function() { | 28 | 28 | $scope.openParams = function() { | |
$scope.isParamOpen = !$scope.isParamOpen; | 29 | 29 | $scope.isParamOpen = !$scope.isParamOpen; | |
}; | 30 | 30 | }; | |
31 | 31 | |||
$scope.fetchQuiz = function(a, b) { | 32 | 32 | $scope.fetchQuiz = function(a, b) { | |
//console.log($scope.startDate, $scope.endDate); | 33 | 33 | //console.log($scope.startDate, $scope.endDate); | |
console.log(a, b); | 34 | 34 | console.log(a, b); | |
}; | 35 | 35 | }; | |
36 | 36 | |||
/* | 37 | 37 | /* | |
$scope.fetchQuiz = function() { | 38 | 38 | $scope.fetchQuiz = function() { | |
console.log('fetching quiz...'); | 39 | 39 | console.log('fetching quiz...'); | |
var studyRequest = { | 40 | 40 | var studyRequest = { | |
'sections': ($scope.sectionToStudy == null) ? [] : [$scope.sectionToStudy], | 41 | 41 | 'sections': ($scope.sectionToStudy == null) ? [] : [$scope.sectionToStudy], | |
'material_date_begin':, | 42 | 42 | 'material_date_begin':, | |
'material_date_end': | 43 | 43 | 'material_date_end': | |
}; | 44 | 44 | }; | |
45 | 45 | |||
// $http.post('/api/study/', studyRequest). | 46 | 46 | // $http.post('/api/study/', studyRequest). | |
//TODO | 47 | 47 | //TODO | |
}; | 48 | 48 | }; | |
*/ | 49 | 49 | */ | |
50 | 50 | |||
/* OLD STUFF :in case you still neeed it */ | 51 | 51 | /* OLD STUFF :in case you still neeed it */ | |
// Flashcard content | 52 | 52 | // Flashcard content | |
$scope.htmlContent = 'sample text here longwordddddddddddddddddddddddddddd hello there from js review ctrl alwkejflakewjflk awjkefjkwefjlkea jfkewjaweajkakwef jk fjeawkafj kaewjf jawekfj akwejfk '; | 53 | 53 | $scope.htmlContent = 'sample text here longwordddddddddddddddddddddddddddd hello there from js review ctrl alwkejflakewjflk awjkefjkwefjlkea jfkewjaweajkakwef jk fjeawkafj kaewjf jawekfj akwejfk '; | |
//single card | 54 | 54 | //single card | |
$scope.samples = | 55 | 55 | $scope.samples = | |
{ | 56 | 56 | { | |
'name': 'lol', | 57 | 57 | 'name': 'lol', | |
'text': 'sample text here 111111 woo hoo I think it works', | 58 | 58 | 'text': 'sample text here 111111 woo hoo I think it works', | |
'mask': [[0, 6], [16, 23]] | 59 | 59 | 'mask': [[0, 6], [16, 23]] | |
}; | 60 | 60 | }; | |
61 | 61 | |||
// get text to display as array | 62 | 62 | // get text to display as array | |
$scope.displayText = []; | 63 | 63 | $scope.displayText = []; | |
// get answers to blanks as array | 64 | 64 | // get answers to blanks as array | |
$scope.blankText = []; | 65 | 65 | $scope.blankText = []; | |
var start = 0; // where to start next string break | 66 | 66 | var start = 0; // where to start next string break | |
for (var i = 0; i < $scope.samples.mask.length; i++) { | 67 | 67 | for (var i = 0; i < $scope.samples.mask.length; i++) { | |
$scope.displayText.push($scope.samples.text.substring(start, $scope.samples.mask[i][0])); | 68 | 68 | $scope.displayText.push($scope.samples.text.substring(start, $scope.samples.mask[i][0])); | |
$scope.blankText.push($scope.samples.text.substring($scope.samples.mask[i][0], $scope.samples.mask[i][1])); | 69 | 69 | $scope.blankText.push($scope.samples.text.substring($scope.samples.mask[i][0], $scope.samples.mask[i][1])); | |
start = $scope.samples.mask[i][1]; | 70 | 70 | start = $scope.samples.mask[i][1]; | |
} | 71 | 71 | } | |
if (start != $scope.samples.mask.length - 1) | 72 | 72 | if (start != $scope.samples.mask.length - 1) | |
$scope.displayText.push($scope.samples.text.substring(start)); | 73 | 73 | $scope.displayText.push($scope.samples.text.substring(start)); |
scripts/UserService.js
View file @
f60d569
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 @
f60d569
/*! | 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 |
static/js/angular-contenteditable.js
View file @
f60d569
File was created | 1 | /** | ||
2 | * @see http://docs.angularjs.org/guide/concepts | |||
3 | * @see http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController | |||
4 | * @see https://github.com/angular/angular.js/issues/528#issuecomment-7573166 | |||
5 | */ | |||
6 | ||||
7 | angular.module('contenteditable', []) | |||
8 | .directive('contenteditable', ['$timeout', function($timeout) { return { | |||
9 | restrict: 'A', | |||
10 | require: '?ngModel', | |||
11 | link: function(scope, element, attrs, ngModel) { | |||
12 | // don't do anything unless this is actually bound to a model | |||
13 | if (!ngModel) { | |||
14 | return | |||
15 | } | |||
16 | ||||
17 | // options | |||
18 | var opts = {} | |||
19 | angular.forEach([ | |||
20 | 'stripBr', | |||
21 | 'noLineBreaks', | |||
22 | 'selectNonEditable', | |||
23 | 'moveCaretToEndOnChange', | |||
24 | ], function(opt) { | |||
25 | var o = attrs[opt] | |||
26 | opts[opt] = o && o !== 'false' | |||
27 | }) | |||
28 | ||||
29 | // view -> model | |||
30 | element.bind('input', function(e) { | |||
31 | scope.$apply(function() { | |||
32 | var html, html2, rerender | |||
33 | html = element.html() | |||
34 | rerender = false | |||
35 | if (opts.stripBr) { | |||
36 | html = html.replace(/<br>$/, '') | |||
37 | } | |||
38 | if (opts.noLineBreaks) { | |||
39 | html2 = html.replace(/<div>/g, '').replace(/<br>/g, '').replace(/<\/div>/g, '') | |||
40 | if (html2 !== html) { | |||
41 | rerender = true | |||
42 | html = html2 | |||
43 | } | |||
44 | } | |||
45 | ngModel.$setViewValue(html) | |||
46 | if (rerender) { | |||
47 | ngModel.$render() | |||
48 | } | |||
49 | if (html === '') { | |||
50 | // the cursor disappears if the contents is empty | |||
51 | // so we need to refocus | |||
52 | $timeout(function(){ | |||
53 | element[0].blur() | |||
54 | element[0].focus() | |||
55 | }) | |||
56 | } | |||
57 | }) | |||
58 | }) | |||
59 | ||||
60 | // model -> view | |||
61 | var oldRender = ngModel.$render | |||
62 | ngModel.$render = function() { | |||
63 | var el, el2, range, sel | |||
64 | if (!!oldRender) { | |||
65 | oldRender() | |||
66 | } | |||
67 | element.html(ngModel.$viewValue || '') | |||
68 | if (opts.moveCaretToEndOnChange) { | |||
69 | el = element[0] | |||
70 | range = document.createRange() | |||
71 | sel = window.getSelection() | |||
72 | if (el.childNodes.length > 0) { | |||
73 | el2 = el.childNodes[el.childNodes.length - 1] | |||
74 | range.setStartAfter(el2) | |||
75 | } else { | |||
76 | range.setStartAfter(el) | |||
77 | } | |||
78 | range.collapse(true) | |||
79 | sel.removeAllRanges() | |||
80 | sel.addRange(range) | |||
81 | } | |||
82 | } | |||
83 | if (opts.selectNonEditable) { | |||
84 | element.bind('click', function(e) { | |||
85 | var range, sel, target | |||
86 | target = e.toElement | |||
87 | if (target !== this && angular.element(target).attr('contenteditable') === 'false') { | |||
88 | range = document.createRange() | |||
89 | sel = window.getSelection() | |||
90 | range.setStartBefore(target) | |||
91 | range.setEndAfter(target) | |||
92 | sel.removeAllRanges() | |||
93 | sel.addRange(range) | |||
94 | } |
static/js/angular-websocket.js
View file @
f60d569
File was created | 1 | (function() { | ||
2 | 'use strict'; | |||
3 | ||||
4 | var noop = angular.noop; | |||
5 | var objectFreeze = (Object.freeze) ? Object.freeze : noop; | |||
6 | var objectDefineProperty = Object.defineProperty; | |||
7 | var isString = angular.isString; | |||
8 | var isFunction = angular.isFunction; | |||
9 | var isDefined = angular.isDefined; | |||
10 | var isObject = angular.isObject; | |||
11 | var isArray = angular.isArray; | |||
12 | var forEach = angular.forEach; | |||
13 | var arraySlice = Array.prototype.slice; | |||
14 | // ie8 wat | |||
15 | if (!Array.prototype.indexOf) { | |||
16 | Array.prototype.indexOf = function(elt /*, from*/) { | |||
17 | var len = this.length >>> 0; | |||
18 | var from = Number(arguments[1]) || 0; | |||
19 | from = (from < 0) ? Math.ceil(from) : Math.floor(from); | |||
20 | if (from < 0) { | |||
21 | from += len; | |||
22 | } | |||
23 | ||||
24 | for (; from < len; from++) { | |||
25 | if (from in this && this[from] === elt) { return from; } | |||
26 | } | |||
27 | return -1; | |||
28 | }; | |||
29 | } | |||
30 | ||||
31 | // $WebSocketProvider.$inject = ['$rootScope', '$q', '$timeout', '$websocketBackend']; | |||
32 | function $WebSocketProvider($rootScope, $q, $timeout, $websocketBackend) { | |||
33 | ||||
34 | function $WebSocket(url, protocols, options) { | |||
35 | if (!options && isObject(protocols) && !isArray(protocols)) { | |||
36 | options = protocols; | |||
37 | protocols = undefined; | |||
38 | } | |||
39 | ||||
40 | this.protocols = protocols; | |||
41 | this.url = url || 'Missing URL'; | |||
42 | this.ssl = /(wss)/i.test(this.url); | |||
43 | ||||
44 | // this.binaryType = ''; | |||
45 | // this.extensions = ''; | |||
46 | // this.bufferedAmount = 0; | |||
47 | // this.trasnmitting = false; | |||
48 | // this.buffer = []; | |||
49 | ||||
50 | // TODO: refactor options to use isDefined | |||
51 | this.scope = options && options.scope || $rootScope; | |||
52 | this.rootScopeFailover = options && options.rootScopeFailover && true; | |||
53 | this.useApplyAsync = options && options.useApplyAsync || false; | |||
54 | this.initialTimeout = options && options.initialTimeout || 500; // 500ms | |||
55 | this.maxTimeout = options && options.maxTimeout || 5 * 60 * 1000; // 5 minutes | |||
56 | this.reconnectIfNotNormalClose = options && options.reconnectIfNotNormalClose || false; | |||
57 | ||||
58 | this._reconnectAttempts = 0; | |||
59 | this.sendQueue = []; | |||
60 | this.onOpenCallbacks = []; | |||
61 | this.onMessageCallbacks = []; | |||
62 | this.onErrorCallbacks = []; | |||
63 | this.onCloseCallbacks = []; | |||
64 | ||||
65 | objectFreeze(this._readyStateConstants); | |||
66 | ||||
67 | if (url) { | |||
68 | this._connect(); | |||
69 | } else { | |||
70 | this._setInternalState(0); | |||
71 | } | |||
72 | ||||
73 | } | |||
74 | ||||
75 | ||||
76 | $WebSocket.prototype._readyStateConstants = { | |||
77 | 'CONNECTING': 0, | |||
78 | 'OPEN': 1, | |||
79 | 'CLOSING': 2, | |||
80 | 'CLOSED': 3, | |||
81 | 'RECONNECT_ABORTED': 4 | |||
82 | }; | |||
83 | ||||
84 | $WebSocket.prototype._normalCloseCode = 1000; | |||
85 | ||||
86 | $WebSocket.prototype._reconnectableStatusCodes = [ | |||
87 | 4000 | |||
88 | ]; | |||
89 | ||||
90 | $WebSocket.prototype.safeDigest = function safeDigest(autoApply) { | |||
91 | if (autoApply && !this.scope.$$phase) { | |||
92 | this.scope.$digest(); | |||
93 | } | |||
94 | }; | |||
95 | ||||
96 | $WebSocket.prototype.bindToScope = function bindToScope(scope) { | |||
97 | var self = this; | |||
98 | if (scope) { | |||
99 | this.scope = scope; | |||
100 | if (this.rootScopeFailover) { | |||
101 | this.scope.$on('$destroy', function() { | |||
102 | self.scope = $rootScope; | |||
103 | }); | |||
104 | } | |||
105 | } | |||
106 | return self; | |||
107 | }; | |||
108 | ||||
109 | $WebSocket.prototype._connect = function _connect(force) { | |||
110 | if (force || !this.socket || this.socket.readyState !== this._readyStateConstants.OPEN) { | |||
111 | this.socket = $websocketBackend.create(this.url, this.protocols); | |||
112 | this.socket.onmessage = angular.bind(this, this._onMessageHandler); | |||
113 | this.socket.onopen = angular.bind(this, this._onOpenHandler); | |||
114 | this.socket.onerror = angular.bind(this, this._onErrorHandler); | |||
115 | this.socket.onclose = angular.bind(this, this._onCloseHandler); | |||
116 | } | |||
117 | }; | |||
118 | ||||
119 | $WebSocket.prototype.fireQueue = function fireQueue() { | |||
120 | while (this.sendQueue.length && this.socket.readyState === this._readyStateConstants.OPEN) { | |||
121 | var data = this.sendQueue.shift(); | |||
122 | ||||
123 | this.socket.send( | |||
124 | isString(data.message) ? data.message : JSON.stringify(data.message) | |||
125 | ); | |||
126 | data.deferred.resolve(); | |||
127 | } | |||
128 | }; | |||
129 | ||||
130 | $WebSocket.prototype.notifyOpenCallbacks = function notifyOpenCallbacks(event) { | |||
131 | for (var i = 0; i < this.onOpenCallbacks.length; i++) { | |||
132 | this.onOpenCallbacks[i].call(this, event); | |||
133 | } | |||
134 | }; | |||
135 | ||||
136 | $WebSocket.prototype.notifyCloseCallbacks = function notifyCloseCallbacks(event) { | |||
137 | for (var i = 0; i < this.onCloseCallbacks.length; i++) { | |||
138 | this.onCloseCallbacks[i].call(this, event); | |||
139 | } | |||
140 | }; | |||
141 | ||||
142 | $WebSocket.prototype.notifyErrorCallbacks = function notifyErrorCallbacks(event) { | |||
143 | for (var i = 0; i < this.onErrorCallbacks.length; i++) { | |||
144 | this.onErrorCallbacks[i].call(this, event); | |||
145 | } | |||
146 | }; | |||
147 | ||||
148 | $WebSocket.prototype.onOpen = function onOpen(cb) { | |||
149 | this.onOpenCallbacks.push(cb); | |||
150 | return this; | |||
151 | }; | |||
152 | ||||
153 | $WebSocket.prototype.onClose = function onClose(cb) { | |||
154 | this.onCloseCallbacks.push(cb); | |||
155 | return this; | |||
156 | }; | |||
157 | ||||
158 | $WebSocket.prototype.onError = function onError(cb) { | |||
159 | this.onErrorCallbacks.push(cb); | |||
160 | return this; | |||
161 | }; | |||
162 | ||||
163 | ||||
164 | $WebSocket.prototype.onMessage = function onMessage(callback, options) { | |||
165 | if (!isFunction(callback)) { | |||
166 | throw new Error('Callback must be a function'); | |||
167 | } | |||
168 | ||||
169 | if (options && isDefined(options.filter) && !isString(options.filter) && !(options.filter instanceof RegExp)) { | |||
170 | throw new Error('Pattern must be a string or regular expression'); | |||
171 | } | |||
172 | ||||
173 | this.onMessageCallbacks.push({ | |||
174 | fn: callback, | |||
175 | pattern: options ? options.filter : undefined, | |||
176 | autoApply: options ? options.autoApply : true | |||
177 | }); | |||
178 | return this; | |||
179 | }; | |||
180 | ||||
181 | $WebSocket.prototype._onOpenHandler = function _onOpenHandler(event) { | |||
182 | this._reconnectAttempts = 0; | |||
183 | this.notifyOpenCallbacks(event); | |||
184 | this.fireQueue(); | |||
185 | }; | |||
186 | ||||
187 | $WebSocket.prototype._onCloseHandler = function _onCloseHandler(event) { | |||
188 | this.notifyCloseCallbacks(event); | |||
189 | if ((this.reconnectIfNotNormalClose && event.code !== this._normalCloseCode) || this._reconnectableStatusCodes.indexOf(event.code) > -1) { | |||
190 | this.reconnect(); | |||
191 | } | |||
192 | }; | |||
193 | ||||
194 | $WebSocket.prototype._onErrorHandler = function _onErrorHandler(event) { | |||
195 | this.notifyErrorCallbacks(event); | |||
196 | }; | |||
197 | ||||
198 | $WebSocket.prototype._onMessageHandler = function _onMessageHandler(message) { | |||
199 | var pattern; | |||
200 | var self = this; | |||
201 | var currentCallback; | |||
202 | for (var i = 0; i < self.onMessageCallbacks.length; i++) { | |||
203 | currentCallback = self.onMessageCallbacks[i]; | |||
204 | pattern = currentCallback.pattern; | |||
205 | if (pattern) { | |||
206 | if (isString(pattern) && message.data === pattern) { | |||
207 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); | |||
208 | } | |||
209 | else if (pattern instanceof RegExp && pattern.exec(message.data)) { | |||
210 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); | |||
211 | } | |||
212 | } | |||
213 | else { | |||
214 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); | |||
215 | } | |||
216 | } | |||
217 | ||||
218 | function applyAsyncOrDigest(callback, autoApply, args) { | |||
219 | args = arraySlice.call(arguments, 2); | |||
220 | if (self.useApplyAsync) { | |||
221 | self.scope.$applyAsync(function() { | |||
222 | callback.apply(self, args); | |||
223 | }); | |||
224 | } else { | |||
225 | callback.apply(self, args); | |||
226 | self.safeDigest(autoApply); | |||
227 | } | |||
228 | } | |||
229 | ||||
230 | }; | |||
231 | ||||
232 | $WebSocket.prototype.close = function close(force) { | |||
233 | if (force || !this.socket.bufferedAmount) { | |||
234 | this.socket.close(); | |||
235 | } | |||
236 | return this; | |||
237 | }; | |||
238 | ||||
239 | $WebSocket.prototype.send = function send(data) { | |||
240 | var deferred = $q.defer(); | |||
241 | var self = this; | |||
242 | var promise = cancelableify(deferred.promise); | |||
243 | ||||
244 | if (self.readyState === self._readyStateConstants.RECONNECT_ABORTED) { | |||
245 | deferred.reject('Socket connection has been closed'); | |||
246 | } | |||
247 | else { | |||
248 | self.sendQueue.push({ | |||
249 | message: data, | |||
250 | deferred: deferred | |||
251 | }); | |||
252 | self.fireQueue(); | |||
253 | } | |||
254 | ||||
255 | // Credit goes to @btford | |||
256 | function cancelableify(promise) { | |||
257 | promise.cancel = cancel; | |||
258 | var then = promise.then; | |||
259 | promise.then = function() { | |||
260 | var newPromise = then.apply(this, arguments); | |||
261 | return cancelableify(newPromise); | |||
262 | }; | |||
263 | return promise; | |||
264 | } | |||
265 | ||||
266 | function cancel(reason) { | |||
267 | self.sendQueue.splice(self.sendQueue.indexOf(data), 1); | |||
268 | deferred.reject(reason); | |||
269 | return self; | |||
270 | } | |||
271 | ||||
272 | if ($websocketBackend.isMocked && $websocketBackend.isMocked() && | |||
273 | $websocketBackend.isConnected(this.url)) { | |||
274 | this._onMessageHandler($websocketBackend.mockSend()); | |||
275 | } | |||
276 | ||||
277 | return promise; | |||
278 | }; | |||
279 | ||||
280 | $WebSocket.prototype.reconnect = function reconnect() { | |||
281 | this.close(); | |||
282 | ||||
283 | var backoffDelay = this._getBackoffDelay(++this._reconnectAttempts); | |||
284 | ||||
285 | var backoffDelaySeconds = backoffDelay / 1000; | |||
286 | console.log('Reconnecting in ' + backoffDelaySeconds + ' seconds'); | |||
287 | ||||
288 | $timeout(angular.bind(this, this._connect), backoffDelay); | |||
289 | ||||
290 | return this; | |||
291 | }; | |||
292 | // Exponential Backoff Formula by Prof. Douglas Thain | |||
293 | // http://dthain.blogspot.co.uk/2009/02/exponential-backoff-in-distributed.html | |||
294 | $WebSocket.prototype._getBackoffDelay = function _getBackoffDelay(attempt) { | |||
295 | var R = Math.random() + 1; | |||
296 | var T = this.initialTimeout; | |||
297 | var F = 2; | |||
298 | var N = attempt; | |||
299 | var M = this.maxTimeout; | |||
300 | ||||
301 | return Math.floor(Math.min(R * T * Math.pow(F, N), M)); | |||
302 | }; | |||
303 | ||||
304 | $WebSocket.prototype._setInternalState = function _setInternalState(state) { | |||
305 | if (Math.floor(state) !== state || state < 0 || state > 4) { | |||
306 | throw new Error('state must be an integer between 0 and 4, got: ' + state); | |||
307 | } | |||
308 | ||||
309 | // ie8 wat | |||
310 | if (!objectDefineProperty) { | |||
311 | this.readyState = state || this.socket.readyState; | |||
312 | } | |||
313 | this._internalConnectionState = state; | |||
314 | ||||
315 | ||||
316 | forEach(this.sendQueue, function(pending) { | |||
317 | pending.deferred.reject('Message cancelled due to closed socket connection'); | |||
318 | }); |
test.png
View file @
f60d569
396 Bytes
ws_debug.html
View file @
f60d569
<!DOCTYPE html> | 1 | File was deleted | ||
<html> | 2 | |||
<head lang="en"> | 3 | |||
<meta charset="UTF-8"> | 4 | |||
<title></title> | 5 | |||
</head> | 6 | |||
<body> | 7 | |||
<ul> | 8 | |||
9 | ||||
</ul> | 10 | |||
</body> | 11 | |||
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script> | 12 | |||
<script> | 13 | |||
var loc = window.location, new_uri; | 14 | |||
if (loc.protocol === "https:") { | 15 | |||
new_uri = "wss:"; | 16 | |||
} else { | 17 | |||
new_uri = "ws:"; | 18 | |||
} | 19 | |||
new_uri += "//" + loc.host; | 20 | |||
var ws = new WebSocket(new_uri + '/ws/deck/496?subscribe-user'); | 21 | |||
ws.onopen = function () { | 22 | |||
console.log("websocket connected"); | 23 | |||
}; | 24 | |||
ws.onmessage = function (e) { | 25 | |||
$('ul').append('<li>' + e.data + '</li>'); | 26 | |||
}; | 27 | |||
ws.onerror = function (e) { | 28 | |||
console.error(e); | 29 | |||
}; | 30 | |||
ws.onclose = function (e) { | 31 | |||
console.log("connection closed"); | 32 | |||
}; | 33 | |||
function send_message(msg) {1 | 34 | |||
ws.send(msg); | 35 | |||
} | 36 | |||
var ws = new WebSocket(new_uri + '/ws/feed/496?subscribe-broadcast'); | 37 | |||
ws.onopen = function () { | 38 | |||
console.log("websocket connected"); | 39 | |||
}; | 40 | |||
ws.onmessage = function (e) { | 41 | |||
$('ul').append('<li>' + e.data + '</li>'); | 42 | |||
}; | 43 | |||
ws.onerror = function (e) { | 44 | |||
console.error(e); | 45 | |||
}; | 46 | |||
ws.onclose = function (e) { | 47 | |||
console.log("connection closed"); | 48 | |||
}; | 49 | |||
function send_message(msg) { | 50 | |||
ws.send(msg); | 51 | |||
} | 52 | |||
</script> | 53 |