├── mobile ├── .bowerrc ├── www │ ├── css │ │ └── style.css │ ├── js │ │ ├── controllers │ │ │ ├── controllers-module.js │ │ │ ├── get-started │ │ │ │ ├── welcome-ctrl.js │ │ │ │ ├── get-started-ctrl.js │ │ │ │ ├── user-created-ctrl.js │ │ │ │ └── create-profile-ctrl.js │ │ │ ├── app-ctrl.js │ │ │ └── login-ctrl.js │ │ ├── services │ │ │ ├── services-module.js │ │ │ ├── api-service.js │ │ │ ├── auth-interceptor-service.js │ │ │ ├── storage-service.js │ │ │ ├── user-service.js │ │ │ └── auth-retry-service.js │ │ ├── directives.js │ │ └── app.js │ ├── img │ │ └── ionic.png │ ├── lib │ │ └── ionic │ │ │ ├── fonts │ │ │ ├── ionicons.eot │ │ │ ├── ionicons.ttf │ │ │ └── ionicons.woff │ │ │ ├── version.json │ │ │ ├── scss │ │ │ ├── _progress.scss │ │ │ ├── ionicons │ │ │ │ ├── ionicons.scss │ │ │ │ ├── _ionicons-font.scss │ │ │ │ └── _ionicons-animation.scss │ │ │ ├── _backdrop.scss │ │ │ ├── ionic.scss │ │ │ ├── _button-bar.scss │ │ │ ├── _loading.scss │ │ │ ├── _slide-box.scss │ │ │ ├── _menu.scss │ │ │ ├── _radio.scss │ │ │ ├── _badge.scss │ │ │ ├── _action-sheet.scss │ │ │ ├── _modal.scss │ │ │ ├── _popup.scss │ │ │ ├── _list.scss │ │ │ ├── _select.scss │ │ │ ├── _range.scss │ │ │ ├── _grid.scss │ │ │ ├── _type.scss │ │ │ ├── _platform.scss │ │ │ ├── _popover.scss │ │ │ ├── _toggle.scss │ │ │ ├── _checkbox.scss │ │ │ ├── _util.scss │ │ │ ├── _form.scss │ │ │ ├── _button.scss │ │ │ ├── _reset.scss │ │ │ ├── _scaffolding.scss │ │ │ ├── _bar.scss │ │ │ ├── _tabs.scss │ │ │ └── _mixins.scss │ │ │ └── js │ │ │ ├── angular │ │ │ ├── angular-resource.min.js │ │ │ ├── angular-sanitize.min.js │ │ │ └── angular-animate.min.js │ │ │ └── angular-ui │ │ │ └── angular-ui-router.min.js │ ├── templates │ │ ├── get-started │ │ │ ├── welcome-view.html │ │ │ ├── get-started-view.html │ │ │ ├── user-created-view.html │ │ │ └── create-profile-view.html │ │ └── login-view.html │ └── index.html ├── bower.json ├── .gitignore ├── ionic.project ├── package.json ├── config.xml ├── scss │ └── ionic.app.scss ├── gulpfile.js └── hooks │ ├── after_prepare │ └── 010_add_platform_class.js │ └── README.md ├── server ├── .gitignore ├── util │ ├── error-messages.js │ └── application-auth │ │ ├── auth-token.js │ │ └── route-auth.js ├── api-manifest.js ├── gulpfile.js ├── api │ └── user │ │ └── base-user-api.js ├── package.json ├── conf.js ├── models │ └── user-model.js ├── server.js └── routes │ └── user │ └── base-user-route.js └── README.md /mobile/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "www/lib" 3 | } 4 | -------------------------------------------------------------------------------- /mobile/www/css/style.css: -------------------------------------------------------------------------------- 1 | /* Empty. Add your own CSS if you like */ 2 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Files 2 | 3 | npm-debug.log 4 | node_modules/ -------------------------------------------------------------------------------- /mobile/www/js/controllers/controllers-module.js: -------------------------------------------------------------------------------- 1 | angular.module('base-auth.controllers',[]); -------------------------------------------------------------------------------- /mobile/www/js/services/services-module.js: -------------------------------------------------------------------------------- 1 | angular.module("base-auth.services", ['http-auth-interceptor']); -------------------------------------------------------------------------------- /mobile/www/img/ionic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sean-hill/ionic-user-auth-express/HEAD/mobile/www/img/ionic.png -------------------------------------------------------------------------------- /mobile/www/lib/ionic/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sean-hill/ionic-user-auth-express/HEAD/mobile/www/lib/ionic/fonts/ionicons.eot -------------------------------------------------------------------------------- /mobile/www/lib/ionic/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sean-hill/ionic-user-auth-express/HEAD/mobile/www/lib/ionic/fonts/ionicons.ttf -------------------------------------------------------------------------------- /mobile/www/lib/ionic/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sean-hill/ionic-user-auth-express/HEAD/mobile/www/lib/ionic/fonts/ionicons.woff -------------------------------------------------------------------------------- /mobile/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-auth", 3 | "private": "true", 4 | "devDependencies": { 5 | "ionic": "driftyco/ionic-bower#1.0.0-beta.13" 6 | } 7 | } -------------------------------------------------------------------------------- /mobile/www/lib/ionic/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-beta.13", 3 | "codename": "lanthanum-leopard", 4 | "date": "2014-09-24", 5 | "time": "20:17:36" 6 | } 7 | -------------------------------------------------------------------------------- /mobile/.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | node_modules/ 5 | platforms/ 6 | plugins/ 7 | -------------------------------------------------------------------------------- /mobile/www/js/controllers/get-started/welcome-ctrl.js: -------------------------------------------------------------------------------- 1 | // Welcome Ctrl 2 | 3 | angular.module('base-auth.controllers') 4 | 5 | .controller('WelcomeCtrl', function($scope, User) { 6 | console.log('WelcomeCtrl'); 7 | }) -------------------------------------------------------------------------------- /server/util/error-messages.js: -------------------------------------------------------------------------------- 1 | // A map of error messages 2 | 3 | module.exports = { 4 | 5 | unknown: "Uh ohs, something broke. Please let us know!" 6 | , required: "Please fill in all required fields" 7 | 8 | } -------------------------------------------------------------------------------- /mobile/www/templates/get-started/welcome-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Create Profile 4 | 5 | -------------------------------------------------------------------------------- /mobile/ionic.project: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-auth", 3 | "app_id": "", 4 | "gulpStartupTasks": [ 5 | "sass", 6 | "watch" 7 | ], 8 | "watchPatterns": [ 9 | "www/**/*", 10 | "!www/lib/**/*" 11 | ] 12 | } -------------------------------------------------------------------------------- /server/api-manifest.js: -------------------------------------------------------------------------------- 1 | // Api Manifest 2 | 3 | var express = require('express'); 4 | var apiManifest = express.Router(); 5 | 6 | apiManifest.use('/user', require('./api/user/base-user-api')); 7 | 8 | module.exports = apiManifest; -------------------------------------------------------------------------------- /mobile/www/js/controllers/get-started/get-started-ctrl.js: -------------------------------------------------------------------------------- 1 | // Get Started Abstract Ctrl 2 | 3 | angular.module('base-auth.controllers') 4 | 5 | .controller('GetStartedCtrl', function($scope, User) { 6 | console.log('GetStartedCtrl'); 7 | }) -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_progress.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Progress 4 | * -------------------------------------------------- 5 | */ 6 | 7 | progress { 8 | display: block; 9 | margin: $progress-margin; 10 | width: $progress-width; 11 | } 12 | -------------------------------------------------------------------------------- /mobile/www/js/directives.js: -------------------------------------------------------------------------------- 1 | // Directives 2 | 3 | angular.module("base-auth.directives", []) 4 | 5 | .directive('login', function(){ 6 | 7 | return { 8 | restrict: 'E' 9 | , controller: 'LoginCtrl' 10 | }; 11 | 12 | }) 13 | 14 | ; -------------------------------------------------------------------------------- /mobile/www/templates/get-started/get-started-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Back 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /mobile/www/js/controllers/app-ctrl.js: -------------------------------------------------------------------------------- 1 | // Main App Ctrl 2 | 3 | angular.module('base-auth.controllers') 4 | 5 | .controller('AppCtrl', function($scope){ 6 | 7 | console.log('AppCtrl'); 8 | 9 | $scope.$on('user:set', function(event, user){ 10 | $scope.user = user; 11 | }); 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /server/gulpfile.js: -------------------------------------------------------------------------------- 1 | // Gulp Tasks 2 | 3 | var gulp = require('gulp'); 4 | var nodemon = require('gulp-nodemon'); 5 | 6 | // Nodemon task 7 | gulp.task('nodemon', function(){ 8 | 9 | nodemon({ script: 'server.js', ext: 'js', ignore: [] }) 10 | .on('restart', function(){ 11 | console.log('Server restarted'); 12 | }); 13 | 14 | }); 15 | 16 | // Default task 17 | gulp.task('default', ['nodemon']); -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/ionicons/ionicons.scss: -------------------------------------------------------------------------------- 1 | @import "ionicons-variables"; 2 | /*! 3 | Ionicons, v1.5.2 4 | Created by Ben Sperry for the Ionic Framework, http://ionicons.com/ 5 | https://twitter.com/benjsperry https://twitter.com/ionicframework 6 | MIT License: https://github.com/driftyco/ionicons 7 | */ 8 | 9 | @import "ionicons-font"; 10 | @import "ionicons-animation"; 11 | @import "ionicons-icons"; 12 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_backdrop.scss: -------------------------------------------------------------------------------- 1 | 2 | .backdrop { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | z-index: $z-index-backdrop; 7 | 8 | width: 100%; 9 | height: 100%; 10 | 11 | background-color: rgba(0,0,0,0.4); 12 | 13 | visibility: hidden; 14 | opacity: 0; 15 | 16 | &.visible { 17 | visibility: visible; 18 | } 19 | &.active { 20 | opacity: 1; 21 | } 22 | 23 | @include transition(0.1s opacity linear); 24 | } 25 | -------------------------------------------------------------------------------- /server/util/application-auth/auth-token.js: -------------------------------------------------------------------------------- 1 | // Auth token util 2 | 3 | var uuid = require('node-uuid'); 4 | var jwt = require('jwt-simple'); 5 | var jwtKey = 'Doo ts doo ts dance party.'; 6 | 7 | exports.create = function(email, userId) { 8 | 9 | var userObject = { 10 | email: email, 11 | userId: userId, 12 | issuedAt: new Date(), 13 | entropy: uuid.v4() 14 | }; 15 | 16 | return jwt.encode(userObject, jwtKey); 17 | } 18 | -------------------------------------------------------------------------------- /server/api/user/base-user-api.js: -------------------------------------------------------------------------------- 1 | // User Api 2 | 3 | var express = require('express'); 4 | var userApi = express.Router(); 5 | var userRoute = require('../../routes/user/base-user-route'); 6 | var RouteAuth = require('../../util/application-auth/route-auth'); 7 | 8 | // Base user api routes 9 | userApi.post('/create', userRoute.create); 10 | userApi.post('/login', userRoute.login); 11 | userApi.get('/get', RouteAuth.protect, userRoute.get); 12 | 13 | module.exports = userApi; -------------------------------------------------------------------------------- /mobile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-auth", 3 | "version": "1.0.0", 4 | "description": "Simple Auth for Ionic", 5 | "dependencies": { 6 | "gulp": "^3.5.6", 7 | "gulp-sass": "^0.7.1", 8 | "gulp-concat": "^2.2.0", 9 | "gulp-minify-css": "^0.3.0", 10 | "gulp-rename": "^1.2.0" 11 | }, 12 | "devDependencies": { 13 | "bower": "^1.3.3", 14 | "gulp-concat": "^2.4.2", 15 | "gulp-util": "^2.2.14", 16 | "shelljs": "^0.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mobile/www/js/services/api-service.js: -------------------------------------------------------------------------------- 1 | // Constants for the API 2 | 3 | angular.module('base-auth.services') 4 | 5 | .factory('API', function(){ 6 | 7 | // Url of Node API 8 | var apiUrl = 'http://localhost:5000/api'; 9 | 10 | // Base Routes 11 | var baseUserRoute = apiUrl + '/user'; 12 | 13 | return { 14 | 15 | // User Routes 16 | user: { 17 | get: baseUserRoute + '/get' 18 | , create: baseUserRoute + '/create' 19 | , login: baseUserRoute +'/login' 20 | } 21 | 22 | } 23 | 24 | }); -------------------------------------------------------------------------------- /mobile/www/templates/get-started/user-created-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

User has been created

4 | 5 |
 {{ user | json }} 
6 | 7 |
8 |
-------------------------------------------------------------------------------- /mobile/www/js/controllers/get-started/user-created-ctrl.js: -------------------------------------------------------------------------------- 1 | // User Created Ctrl 2 | 3 | angular.module('base-auth.controllers') 4 | 5 | .controller('UserCreatedCtrl', function($scope, $timeout, User, Storage) { 6 | console.log('UserCreatedCtrl'); 7 | 8 | $scope.clickToGetUser = function() { 9 | 10 | User.get().then( 11 | function(user){ 12 | $scope.$emit('user:set', user); 13 | }, function(err){ 14 | console.log(err); 15 | }) 16 | ; 17 | 18 | }; 19 | 20 | $scope.clearAuthAndGetUser = function() { 21 | 22 | Storage.clear('authToken'); 23 | 24 | $scope.clickToGetUser(); 25 | 26 | }; 27 | 28 | }) -------------------------------------------------------------------------------- /mobile/www/js/services/auth-interceptor-service.js: -------------------------------------------------------------------------------- 1 | // Auth token interceptor (appends the Authorization header to every request) 2 | 3 | angular.module('base-auth.services') 4 | 5 | .factory('AuthTokenInterceptor', function ($q, Storage) { 6 | return { 7 | request: function (config) { 8 | 9 | var authToken = Storage.get("authToken"); 10 | config.headers = config.headers || {}; 11 | 12 | if (authToken) { 13 | config.headers.Authorization = 'Bearer ' + authToken; 14 | } 15 | 16 | return config || $q.when(config); 17 | 18 | } 19 | }; 20 | }) -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-auth", 3 | "version": "0.1.0", 4 | "dependencies": { 5 | "body-parser": "^1.9.3", 6 | "cheerio": "^0.18.0", 7 | "cookie-parser": "^1.3.3", 8 | "crypto": "0.0.3", 9 | "express": "^4.10.3", 10 | "gulp": "^3.8.10", 11 | "gulp-jshint": "^1.9.0", 12 | "gulp-nodemon": "^1.0.4", 13 | "jwt-simple": "^0.2.0", 14 | "mongoose": "^3.8.19", 15 | "mongoose-unique-validator": "^0.4.1", 16 | "node-uuid": "^1.4.1", 17 | "passport": "^0.2.1", 18 | "passport-http-bearer": "^1.0.1", 19 | "request": "^2.48.0", 20 | "spooky": "^0.2.5", 21 | "validator": "^3.22.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mobile/www/js/controllers/get-started/create-profile-ctrl.js: -------------------------------------------------------------------------------- 1 | // Create Profile Ctrl 2 | 3 | angular.module('base-auth.controllers') 4 | 5 | .controller('CreateProfileCtrl', function($scope, $state, $ionicPopup, User, Storage){ 6 | 7 | console.log('CreateProfileCtrl'); 8 | $scope.profileForm = {}; 9 | 10 | $scope.createProfile = function(){ 11 | 12 | User.create($scope.profileForm).then( 13 | function(createdUser){ 14 | Storage.set('authToken', createdUser.authToken); 15 | $state.go('get-started.user-created'); 16 | }, 17 | function(err){ 18 | $ionicPopup.alert({ 19 | title: 'Signup Error', 20 | template: err.message 21 | }); 22 | }); 23 | 24 | }; 25 | 26 | }); -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/ionic.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import 4 | // Ionicons 5 | "ionicons/ionicons.scss", 6 | 7 | // Variables 8 | "mixins", 9 | "variables", 10 | 11 | // Base 12 | "reset", 13 | "scaffolding", 14 | "type", 15 | 16 | // Components 17 | "action-sheet", 18 | "backdrop", 19 | "bar", 20 | "tabs", 21 | "menu", 22 | "modal", 23 | "popover", 24 | "popup", 25 | "loading", 26 | "items", 27 | "list", 28 | "badge", 29 | "slide-box", 30 | 31 | // Forms 32 | "form", 33 | "checkbox", 34 | "toggle", 35 | "radio", 36 | "range", 37 | "select", 38 | "progress", 39 | 40 | // Buttons 41 | "button", 42 | "button-bar", 43 | 44 | // Util 45 | "animations", 46 | "grid", 47 | "util", 48 | "platform"; 49 | -------------------------------------------------------------------------------- /server/util/application-auth/route-auth.js: -------------------------------------------------------------------------------- 1 | // Used when an auth token is required for a route 2 | 3 | var passport = require("passport"); 4 | var path = require('path'); 5 | var BearerStrategy = require("passport-http-bearer").Strategy; 6 | var User = require(path.resolve(__dirname, '../../models/user-model')); 7 | 8 | // Use Bearer Strategy as authentication 9 | passport.use(new BearerStrategy( 10 | function(token, done) { 11 | User.findOne({ authToken: token }, function (err, user) { 12 | if (err) { return done(err); } 13 | if (!user) { return done(null, false); } 14 | return done(null, user, { scope: 'all' }); 15 | }); 16 | } 17 | )); 18 | 19 | module.exports = { 20 | protect: passport.authenticate('bearer', { session: false }) 21 | } -------------------------------------------------------------------------------- /mobile/www/templates/login-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Login

4 |
5 | 6 |
7 |
8 | 12 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
-------------------------------------------------------------------------------- /mobile/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Unbill 4 | 5 | An Ionic Framework and Cordova project. 6 | 7 | 8 | Ionic Framework Team 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /mobile/www/js/services/storage-service.js: -------------------------------------------------------------------------------- 1 | // Service to use local storage 2 | 3 | angular.module('base-auth.services') 4 | 5 | .factory('Storage', function() { 6 | return { 7 | get: function(key, toJson) { 8 | var value = window.localStorage[key]; 9 | return toJson ? angular.fromJson(value) : value; 10 | }, 11 | set: function(key, value) { 12 | window.localStorage[key] = typeof value == "object" ? angular.toJson(value) : value; 13 | }, 14 | has: function(key) { 15 | return window.localStorage[key] != undefined ? true : false; 16 | }, 17 | clear: function(keys) { 18 | var keysToClear = keys.split(" "); 19 | angular.forEach(keysToClear, function(key){ 20 | window.localStorage.removeItem(key); 21 | }); 22 | } 23 | } 24 | }) -------------------------------------------------------------------------------- /mobile/scss/ionic.app.scss: -------------------------------------------------------------------------------- 1 | /* 2 | To customize the look and feel of Ionic, you can override the variables 3 | in ionic's _variables.scss file. 4 | 5 | For example, you might change some of the default colors: 6 | 7 | $light: #fff !default; 8 | $stable: #f8f8f8 !default; 9 | $positive: #387ef5 !default; 10 | $calm: #11c1f3 !default; 11 | $balanced: #33cd5f !default; 12 | $energized: #ffc900 !default; 13 | $assertive: #ef473a !default; 14 | $royal: #886aea !default; 15 | $dark: #444 !default; 16 | */ 17 | 18 | // The path for our ionicons font files, relative to the built CSS in www/css 19 | $ionicons-font-path: "../lib/ionic/fonts" !default; 20 | 21 | // Include all of Ionic 22 | @import "www/lib/ionic/scss/ionic"; 23 | 24 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/ionicons/_ionicons-font.scss: -------------------------------------------------------------------------------- 1 | // Ionicons Font Path 2 | // -------------------------- 3 | 4 | @font-face { 5 | font-family: $ionicons-font-family; 6 | src:url("#{$ionicons-font-path}/ionicons.eot?v=#{$ionicons-version}"); 7 | src:url("#{$ionicons-font-path}/ionicons.eot?v=#{$ionicons-version}#iefix") format("embedded-opentype"), 8 | url("#{$ionicons-font-path}/ionicons.ttf?v=#{$ionicons-version}") format("truetype"), 9 | url("#{$ionicons-font-path}/ionicons.woff?v=#{$ionicons-version}") format("woff"), 10 | url("#{$ionicons-font-path}/ionicons.svg?v=#{$ionicons-version}#Ionicons") format("svg"); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | 15 | .ion { 16 | display: inline-block; 17 | font-family: $ionicons-font-family; 18 | speak: none; 19 | font-style: normal; 20 | font-weight: normal; 21 | font-variant: normal; 22 | text-transform: none; 23 | text-rendering: auto; 24 | line-height: 1; 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_button-bar.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Button Bar 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .button-bar { 8 | @include display-flex(); 9 | @include flex(1); 10 | width: 100%; 11 | 12 | &.button-bar-inline { 13 | display: block; 14 | width: auto; 15 | 16 | @include clearfix(); 17 | 18 | > .button { 19 | width: auto; 20 | display: inline-block; 21 | float: left; 22 | } 23 | } 24 | } 25 | 26 | .button-bar > .button { 27 | @include flex(1); 28 | display: block; 29 | 30 | overflow: hidden; 31 | 32 | padding: 0 16px; 33 | 34 | width: 0; 35 | 36 | border-width: 1px 0px 1px 1px; 37 | border-radius: 0; 38 | text-align: center; 39 | text-overflow: ellipsis; 40 | white-space: nowrap; 41 | 42 | &:before, 43 | .icon:before { 44 | line-height: 44px; 45 | } 46 | 47 | &:first-child { 48 | border-radius: 2px 0px 0px 2px; 49 | } 50 | &:last-child { 51 | border-right-width: 1px; 52 | border-radius: 0px 2px 2px 0px; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_loading.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Loading 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .loading-container { 8 | position: absolute; 9 | left: 0; 10 | top: 0; 11 | right: 0; 12 | bottom: 0; 13 | 14 | z-index: $z-index-loading; 15 | 16 | @include display-flex(); 17 | @include justify-content(center); 18 | @include align-items(center); 19 | 20 | @include transition(0.2s opacity linear); 21 | visibility: hidden; 22 | opacity: 0; 23 | 24 | &:not(.visible) .icon { 25 | display: none; 26 | } 27 | &.visible { 28 | visibility: visible; 29 | } 30 | &.active { 31 | opacity: 1; 32 | } 33 | 34 | .loading { 35 | padding: $loading-padding; 36 | 37 | border-radius: $loading-border-radius; 38 | background-color: $loading-bg-color; 39 | 40 | color: $loading-text-color; 41 | 42 | text-align: center; 43 | text-overflow: ellipsis; 44 | font-size: $loading-font-size; 45 | 46 | h1, h2, h3, h4, h5, h6 { 47 | color: $loading-text-color; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_slide-box.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Slide Box 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .slider { 8 | position: relative; 9 | visibility: hidden; 10 | // Make sure items don't scroll over ever 11 | overflow: hidden; 12 | } 13 | 14 | .slider-slides { 15 | position: relative; 16 | height: 100%; 17 | } 18 | 19 | .slider-slide { 20 | position: relative; 21 | display: block; 22 | float: left; 23 | width: 100%; 24 | height: 100%; 25 | vertical-align: top; 26 | } 27 | 28 | .slider-slide-image { 29 | > img { 30 | width: 100%; 31 | } 32 | } 33 | 34 | .slider-pager { 35 | position: absolute; 36 | bottom: 20px; 37 | z-index: $z-index-slider-pager; 38 | width: 100%; 39 | height: 15px; 40 | text-align: center; 41 | 42 | .slider-pager-page { 43 | display: inline-block; 44 | margin: 0px 3px; 45 | width: 15px; 46 | color: #000; 47 | text-decoration: none; 48 | 49 | opacity: 0.3; 50 | 51 | &.active { 52 | @include transition(opacity 0.4s ease-in); 53 | opacity: 1; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /server/conf.js: -------------------------------------------------------------------------------- 1 | // Server Configuration 2 | 3 | var conf = {}; 4 | 5 | // Shared configuration between dev and production 6 | conf.joe = "HEY THERE"; 7 | 8 | // Development configuration 9 | if (process.env.NODE_ENV === 'development') { 10 | 11 | conf.mongo_config = { 12 | 'dbname' : 'base-auth-db', 13 | 'host' : 'localhost', 14 | 'port' : 27017, 15 | 'auth' : { 16 | 'name': 'XXXXXXXX', 17 | 'pass': 'XXXXXXXX' 18 | }, 19 | connect_string: function(){ 20 | return 'mongodb://' + this.auth.name + ':' + this.auth.pass + '@' + this.host + ':' + this.port + '/' + this.dbname; 21 | } 22 | }; 23 | 24 | } 25 | 26 | // Production configuration 27 | if (process.env.NODE_ENV === 'production') { 28 | 29 | conf.mongo_config = { 30 | 'dbname' : 'base-auth-prod', 31 | 'host' : 'some.prod.db', 32 | 'port' : 12345, 33 | 'auth' : { 34 | 'name': 'XXXXXXXX', 35 | 'pass': 'XXXXXXXX' 36 | }, 37 | connect_string: function(){ 38 | return 'mongodb://' + this.auth.name + ':' + this.auth.pass + '@' + this.host + ':' + this.port + '/' + this.dbname; 39 | } 40 | }; 41 | 42 | } 43 | 44 | module.exports = conf; -------------------------------------------------------------------------------- /mobile/www/templates/get-started/create-profile-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 9 | 13 | 17 | 21 |
22 |
23 | 24 |
25 |
26 |
27 |
-------------------------------------------------------------------------------- /mobile/www/js/controllers/login-ctrl.js: -------------------------------------------------------------------------------- 1 | // Login Ctrl 2 | 3 | angular.module('base-auth.controllers') 4 | 5 | .controller('LoginCtrl', function($scope, $ionicModal, $ionicPopup, AuthService, User, Storage){ 6 | 7 | console.log('LoginCtrl'); 8 | 9 | $scope.loginForm = {}; 10 | 11 | // Load the Login Modal 12 | $ionicModal.fromTemplateUrl('templates/login-view.html', { 13 | scope: $scope, 14 | }).then(function(modal) { 15 | $scope.modal = modal; 16 | }); 17 | 18 | // Remove the Modal when this scope is destroyed 19 | $scope.$on('$destroy', function() { 20 | $scope.modal.remove(); 21 | }); 22 | 23 | // Show the Modal if the auth-login-required function is fired 24 | $scope.$on('auth:login-required', function(){ 25 | console.log('Login required!'); 26 | $scope.modal.show(); 27 | }); 28 | 29 | $scope.login = function() { 30 | 31 | console.log($scope.loginForm); 32 | 33 | User.login($scope.loginForm).then( 34 | function(response) { 35 | Storage.set('authToken', response.authToken); 36 | AuthService.loginConfirmed(); 37 | $scope.modal.hide(); 38 | }, 39 | function(err) { 40 | $ionicPopup.alert({ 41 | title: 'Login Error', 42 | template: err.message 43 | }); 44 | }) 45 | ; 46 | 47 | }; 48 | 49 | }); -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_menu.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Menus 4 | * -------------------------------------------------- 5 | * Side panel structure 6 | */ 7 | 8 | .menu { 9 | position: absolute; 10 | top: 0; 11 | bottom: 0; 12 | z-index: $z-index-menu; 13 | overflow: hidden; 14 | 15 | min-height: 100%; 16 | max-height: 100%; 17 | width: $menu-width; 18 | 19 | background-color: $menu-bg; 20 | 21 | .scroll-content { 22 | z-index: $z-index-menu-scroll-content; 23 | } 24 | 25 | .bar-header { 26 | z-index: $z-index-menu-bar-header; 27 | } 28 | } 29 | 30 | .menu-content { 31 | @include transform(none); 32 | box-shadow: $menu-side-shadow; 33 | } 34 | 35 | .menu-open .menu-content .pane, 36 | .menu-open .menu-content .scroll-content { 37 | pointer-events: none; 38 | } 39 | 40 | .grade-b .menu-content, 41 | .grade-c .menu-content { 42 | @include box-sizing(content-box); 43 | right: -1px; 44 | left: -1px; 45 | border-right: 1px solid #ccc; 46 | border-left: 1px solid #ccc; 47 | box-shadow: none; 48 | } 49 | 50 | .menu-left { 51 | left: 0; 52 | } 53 | 54 | .menu-right { 55 | right: 0; 56 | } 57 | 58 | .aside-open.aside-resizing .menu-right { 59 | display: none; 60 | } 61 | 62 | .menu-animated { 63 | @include transition-transform($menu-animation-speed ease); 64 | } 65 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_radio.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Radio Button Inputs 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .item-radio { 8 | padding: 0; 9 | 10 | &:hover { 11 | cursor: pointer; 12 | } 13 | } 14 | 15 | .item-radio .item-content { 16 | /* give some room to the right for the checkmark icon */ 17 | padding-right: $item-padding * 4; 18 | } 19 | 20 | .item-radio .radio-icon { 21 | /* checkmark icon will be hidden by default */ 22 | position: absolute; 23 | top: 0; 24 | right: 0; 25 | z-index: $z-index-item-radio; 26 | visibility: hidden; 27 | padding: $item-padding - 2; 28 | height: 100%; 29 | font-size: 24px; 30 | } 31 | 32 | .item-radio input { 33 | /* hide any radio button inputs elements (the ugly circles) */ 34 | position: absolute; 35 | left: -9999px; 36 | 37 | &:checked ~ .item-content { 38 | /* style the item content when its checked */ 39 | background: #f7f7f7; 40 | } 41 | 42 | &:checked ~ .radio-icon { 43 | /* show the checkmark icon when its checked */ 44 | visibility: visible; 45 | } 46 | } 47 | 48 | // Hack for Android to correctly display the checked item 49 | // http://timpietrusky.com/advanced-checkbox-hack 50 | .platform-android.grade-b .item-radio, 51 | .platform-android.grade-c .item-radio { 52 | -webkit-animation: androidCheckedbugfix infinite 1s; 53 | } 54 | @-webkit-keyframes androidCheckedbugfix { 55 | from { padding: 0; } 56 | to { padding: 0; } 57 | } 58 | -------------------------------------------------------------------------------- /mobile/www/js/services/user-service.js: -------------------------------------------------------------------------------- 1 | // User Service 2 | 3 | angular.module('base-auth.services') 4 | 5 | .factory('User', function($q, $http, API) { 6 | 7 | return { 8 | 9 | get: function() { 10 | 11 | var deferred = $q.defer(); 12 | 13 | $http.get(API.user.get) 14 | .success(function(user){ 15 | deferred.resolve(user); 16 | }) 17 | .error(function(){ 18 | deferred.reject('You are not authenticated.'); 19 | }) 20 | ; 21 | 22 | return deferred.promise; 23 | 24 | }, 25 | 26 | login: function(loginForm) { 27 | 28 | var deferred = $q.defer(); 29 | 30 | $http.post(API.user.login, loginForm) 31 | .success(function(user){ 32 | deferred.resolve(user); 33 | }) 34 | .error(function(err){ 35 | deferred.reject(err); 36 | }) 37 | ; 38 | 39 | return deferred.promise; 40 | 41 | }, 42 | 43 | create: function(profileForm) { 44 | 45 | var deferred = $q.defer(); 46 | 47 | $http.post(API.user.create, profileForm) 48 | .success(function(createdUser){ 49 | deferred.resolve(createdUser); 50 | }) 51 | .error(function(err){ 52 | deferred.reject(err); 53 | }) 54 | ; 55 | 56 | return deferred.promise; 57 | 58 | } 59 | 60 | } 61 | 62 | }) 63 | 64 | ; 65 | -------------------------------------------------------------------------------- /mobile/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gutil = require('gulp-util'); 3 | var bower = require('bower'); 4 | var concat = require('gulp-concat'); 5 | var sass = require('gulp-sass'); 6 | var minifyCss = require('gulp-minify-css'); 7 | var rename = require('gulp-rename'); 8 | var sh = require('shelljs'); 9 | 10 | var paths = { 11 | sass: ['./scss/**/*.scss'] 12 | }; 13 | 14 | gulp.task('default', ['sass']); 15 | 16 | gulp.task('sass', function(done) { 17 | gulp.src('./scss/ionic.app.scss') 18 | .pipe(sass()) 19 | .pipe(gulp.dest('./www/css/')) 20 | .pipe(minifyCss({ 21 | keepSpecialComments: 0 22 | })) 23 | .pipe(rename({ extname: '.min.css' })) 24 | .pipe(gulp.dest('./www/css/')) 25 | .on('end', done); 26 | }); 27 | 28 | gulp.task('watch', function() { 29 | gulp.watch(paths.sass, ['sass']); 30 | }); 31 | 32 | gulp.task('install', ['git-check'], function() { 33 | return bower.commands.install() 34 | .on('log', function(data) { 35 | gutil.log('bower', gutil.colors.cyan(data.id), data.message); 36 | }); 37 | }); 38 | 39 | gulp.task('git-check', function(done) { 40 | if (!sh.which('git')) { 41 | console.log( 42 | ' ' + gutil.colors.red('Git is not installed.'), 43 | '\n Git, the version control system, is required to download Ionic.', 44 | '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.', 45 | '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.' 46 | ); 47 | process.exit(1); 48 | } 49 | done(); 50 | }); 51 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_badge.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Badges 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .badge { 8 | @include badge-style($badge-default-bg, $badge-default-text); 9 | z-index: $z-index-badge; 10 | display: inline-block; 11 | padding: 3px 8px; 12 | min-width: 10px; 13 | border-radius: $badge-border-radius; 14 | vertical-align: baseline; 15 | text-align: center; 16 | white-space: nowrap; 17 | font-weight: $badge-font-weight; 18 | font-size: $badge-font-size; 19 | line-height: $badge-line-height; 20 | 21 | &:empty { 22 | display: none; 23 | } 24 | } 25 | 26 | //Be sure to override specificity of rule that 'badge color matches tab color by default' 27 | .tabs .tab-item .badge, 28 | .badge { 29 | &.badge-light { 30 | @include badge-style($badge-light-bg, $badge-light-text); 31 | } 32 | &.badge-stable { 33 | @include badge-style($badge-stable-bg, $badge-stable-text); 34 | } 35 | &.badge-positive { 36 | @include badge-style($badge-positive-bg, $badge-positive-text); 37 | } 38 | &.badge-calm { 39 | @include badge-style($badge-calm-bg, $badge-calm-text); 40 | } 41 | &.badge-assertive { 42 | @include badge-style($badge-assertive-bg, $badge-assertive-text); 43 | } 44 | &.badge-balanced { 45 | @include badge-style($badge-balanced-bg, $badge-balanced-text); 46 | } 47 | &.badge-energized { 48 | @include badge-style($badge-energized-bg, $badge-energized-text); 49 | } 50 | &.badge-royal { 51 | @include badge-style($badge-royal-bg, $badge-royal-text); 52 | } 53 | &.badge-dark { 54 | @include badge-style($badge-dark-bg, $badge-dark-text); 55 | } 56 | } 57 | 58 | // Quick fix for labels/badges in buttons 59 | .button .badge { 60 | position: relative; 61 | top: -1px; 62 | } 63 | -------------------------------------------------------------------------------- /server/models/user-model.js: -------------------------------------------------------------------------------- 1 | // User Model 2 | 3 | var mongoose = require('mongoose'); 4 | var crypto = require('crypto'); 5 | var uniqueValidator = require('mongoose-unique-validator'); 6 | var Schema = mongoose.Schema; 7 | 8 | var UserSchema = new Schema({ 9 | 10 | email: { type: String, lowercase: true, trim: true, index: true, required: true, unique: true } 11 | 12 | , hash: String 13 | 14 | , salt: String 15 | 16 | , name: { first: String, last: String } 17 | 18 | , authToken: { type: String, required: true } 19 | 20 | , utilityProviders: [] 21 | 22 | }); 23 | 24 | // Virtual method for getting a full name 25 | UserSchema.virtual('name.full').get(function () { 26 | return this.name.first + ' ' + this.name.last; 27 | }); 28 | 29 | // Virtual method for password getting and setting 30 | UserSchema.virtual('password') 31 | .get(function() { 32 | return this.hash; 33 | }) 34 | .set(function(password) { 35 | this.salt = this.makeSalt(); 36 | this.hash = this.encryptPassword(password); 37 | }) 38 | ; 39 | 40 | // Schema methods 41 | UserSchema.methods = { 42 | 43 | authenticate: function(plainText) { 44 | return this.encryptPassword(plainText) === this.password; 45 | }, 46 | 47 | makeSalt: function() { 48 | return crypto.randomBytes(16).toString('base64'); 49 | }, 50 | 51 | encryptPassword: function(password) { 52 | if (!password || !this.salt) return ''; 53 | var salt = new Buffer(this.salt, 'base64'); 54 | return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); 55 | } 56 | 57 | }; 58 | 59 | // Unique field plugin (makes better error messages) 60 | UserSchema.plugin(uniqueValidator, { message: 'Sorry, someone is already using {VALUE} for their {PATH}.'}) 61 | 62 | module.exports = mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /mobile/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | // API Server 2 | 3 | if (!process.env.NODE_ENV) { 4 | process.env.NODE_ENV = 'development' 5 | } 6 | 7 | process.on("uncaughtException", function(error, stack){ 8 | 9 | console.log("Uncaught Error"); 10 | console.log(error.stack); 11 | 12 | }); 13 | 14 | 15 | 16 | // Server Variables 17 | var express = require('express'); 18 | var cookieParser = require('cookie-parser'); 19 | var bodyParser = require('body-parser'); 20 | var Conf = require('./conf'); 21 | var app = express(); 22 | var port = process.env.PORT || 5000; 23 | 24 | 25 | 26 | // Middlewares 27 | app.use(bodyParser.urlencoded({ extended: true })); 28 | app.use(bodyParser.json()); 29 | app.use(express.static(__dirname + '/public')); 30 | app.use(cookieParser()); 31 | 32 | 33 | 34 | // Database connection 35 | var mongoose = require('mongoose'); 36 | mongoose.connect(Conf.mongo_config.connect_string(), { db: { safe:true } }, function(err){ 37 | if (err) { 38 | throw err; 39 | } 40 | else { 41 | console.log('Mongoose Connected'); 42 | } 43 | }); 44 | 45 | mongoose.connection.on("error", function(err){ 46 | console.log("Mongoose error:", err); 47 | }); 48 | 49 | mongoose.connection.on("disconnected", function(){ 50 | console.log("Mongoose disconnected!"); 51 | }); 52 | 53 | mongoose.set('debug', true); 54 | 55 | 56 | 57 | // Server configuration 58 | // Development only 59 | if ('development' == app.get('env')) { 60 | console.log('STATE: Development') 61 | mongoose.set('debug', true); 62 | } 63 | 64 | // Production only 65 | if ('production' == app.get('env')) { 66 | console.log('STATE: Production') 67 | } 68 | 69 | 70 | 71 | // Api Middlewares 72 | app.use('/api', require('./api-manifest')); 73 | 74 | app.get('/', function(req, res){ 75 | res.send('base-auth'); 76 | }); 77 | 78 | 79 | 80 | // Start the server listening 81 | app.listen(port, function(){ 82 | console.log('base-auth listening on port', port); 83 | }); 84 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_action-sheet.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Action Sheets 3 | * -------------------------------------------------- 4 | */ 5 | 6 | .action-sheet-backdrop { 7 | @include transition(background-color 300ms ease-in-out); 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | z-index: $z-index-action-sheet; 12 | width: 100%; 13 | height: 100%; 14 | background-color: rgba(0,0,0,0); 15 | 16 | &.active { 17 | background-color: rgba(0,0,0,0.5); 18 | } 19 | } 20 | 21 | .action-sheet-wrapper { 22 | @include translate3d(0, 100%, 0); 23 | @include transition(all ease-in-out 300ms); 24 | position: absolute; 25 | bottom: 0; 26 | width: 100%; 27 | } 28 | 29 | .action-sheet-up { 30 | @include translate3d(0, 0, 0); 31 | } 32 | 33 | .action-sheet { 34 | margin-left: 15px; 35 | margin-right: 15px; 36 | width: auto; 37 | z-index: $z-index-action-sheet; 38 | overflow: hidden; 39 | 40 | .button { 41 | display: block; 42 | padding: 1px; 43 | width: 100%; 44 | border-radius: 0; 45 | 46 | background-color: transparent; 47 | 48 | color: $positive; 49 | font-size: 18px; 50 | 51 | &.destructive { 52 | color: $assertive; 53 | } 54 | } 55 | } 56 | 57 | .action-sheet-title { 58 | padding: 10px; 59 | color: lighten($base-color, 40%); 60 | text-align: center; 61 | font-size: 12px; 62 | } 63 | 64 | .action-sheet-group { 65 | margin-bottom: 5px; 66 | border-radius: $sheet-border-radius; 67 | background-color: #fff; 68 | .button { 69 | border-width: 1px 0px 0px 0px; 70 | border-radius: 0; 71 | 72 | &.active { 73 | background-color: transparent; 74 | color: inherit; 75 | } 76 | } 77 | .button:first-child:last-child { 78 | border-width: 0; 79 | } 80 | } 81 | 82 | .action-sheet-open { 83 | pointer-events: none; 84 | 85 | &.modal-open .modal { 86 | pointer-events: none; 87 | } 88 | 89 | .action-sheet-backdrop { 90 | pointer-events: auto; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /mobile/www/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | // Main Application JS 3 | 4 | angular.module("base-auth", ["ionic", "base-auth.controllers", "base-auth.services", "base-auth.directives"]) 5 | 6 | .run(function($ionicPlatform) { 7 | 8 | $ionicPlatform.ready(function() { 9 | 10 | // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard 11 | // for form inputs) 12 | if(window.cordova && window.cordova.plugins.Keyboard) { 13 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); 14 | } 15 | if(window.StatusBar) { 16 | // org.apache.cordova.statusbar required 17 | StatusBar.styleDefault(); 18 | } 19 | 20 | }); 21 | 22 | }) 23 | 24 | .config(function($stateProvider, $urlRouterProvider, $httpProvider) { 25 | 26 | $stateProvider 27 | 28 | .state("get-started", { 29 | abstract: true, 30 | url: "/get-started", 31 | controller: "GetStartedCtrl", 32 | templateUrl: "templates/get-started/get-started-view.html" 33 | }) 34 | 35 | .state("get-started.welcome", { 36 | url: "/welcome", 37 | views: { 38 | "get-started": { 39 | templateUrl: "templates/get-started/welcome-view.html", 40 | controller: "WelcomeCtrl" 41 | } 42 | } 43 | }) 44 | 45 | .state("get-started.create-profile", { 46 | url: "/create-profile", 47 | views: { 48 | "get-started": { 49 | templateUrl: "templates/get-started/create-profile-view.html", 50 | controller: "CreateProfileCtrl" 51 | } 52 | } 53 | }) 54 | 55 | .state("get-started.user-created", { 56 | url: "/user-created", 57 | views: { 58 | "get-started": { 59 | templateUrl: "templates/get-started/user-created-view.html", 60 | controller: "UserCreatedCtrl" 61 | } 62 | } 63 | }) 64 | 65 | ; 66 | 67 | // Use the auth token interceptor to append the auth_token to every request 68 | $httpProvider.interceptors.push('AuthTokenInterceptor'); 69 | 70 | // Fallback to this route 71 | $urlRouterProvider.otherwise("/get-started/welcome"); 72 | 73 | }); -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/ionicons/_ionicons-animation.scss: -------------------------------------------------------------------------------- 1 | // Animation Icons 2 | // -------------------------- 3 | 4 | .#{$ionicons-prefix}spin { 5 | -webkit-animation: spin 1s infinite linear; 6 | -moz-animation: spin 1s infinite linear; 7 | -o-animation: spin 1s infinite linear; 8 | animation: spin 1s infinite linear; 9 | } 10 | 11 | @-moz-keyframes spin { 12 | 0% { -moz-transform: rotate(0deg); } 13 | 100% { -moz-transform: rotate(359deg); } 14 | } 15 | @-webkit-keyframes spin { 16 | 0% { -webkit-transform: rotate(0deg); } 17 | 100% { -webkit-transform: rotate(359deg); } 18 | } 19 | @-o-keyframes spin { 20 | 0% { -o-transform: rotate(0deg); } 21 | 100% { -o-transform: rotate(359deg); } 22 | } 23 | @-ms-keyframes spin { 24 | 0% { -ms-transform: rotate(0deg); } 25 | 100% { -ms-transform: rotate(359deg); } 26 | } 27 | @keyframes spin { 28 | 0% { transform: rotate(0deg); } 29 | 100% { transform: rotate(359deg); } 30 | } 31 | 32 | 33 | .#{$ionicons-prefix}loading-a, 34 | .#{$ionicons-prefix}loading-b, 35 | .#{$ionicons-prefix}loading-c, 36 | .#{$ionicons-prefix}loading-d, 37 | .#{$ionicons-prefix}looping, 38 | .#{$ionicons-prefix}refreshing, 39 | .#{$ionicons-prefix}ios7-reloading { 40 | @extend .ion; 41 | @extend .#{$ionicons-prefix}spin; 42 | } 43 | 44 | .#{$ionicons-prefix}loading-a { 45 | -webkit-animation-timing-function: steps(8, start); 46 | -moz-animation-timing-function: steps(8, start); 47 | animation-timing-function: steps(8, start); 48 | } 49 | 50 | .#{$ionicons-prefix}loading-a:before { 51 | @extend .#{$ionicons-prefix}load-a:before; 52 | } 53 | 54 | .#{$ionicons-prefix}loading-b:before { 55 | @extend .#{$ionicons-prefix}load-b:before; 56 | } 57 | 58 | .#{$ionicons-prefix}loading-c:before { 59 | @extend .#{$ionicons-prefix}load-c:before; 60 | } 61 | 62 | .#{$ionicons-prefix}loading-d:before { 63 | @extend .#{$ionicons-prefix}load-d:before; 64 | } 65 | 66 | .#{$ionicons-prefix}looping:before { 67 | @extend .#{$ionicons-prefix}loop:before; 68 | } 69 | 70 | .#{$ionicons-prefix}refreshing:before { 71 | @extend .#{$ionicons-prefix}refresh:before; 72 | } 73 | 74 | .#{$ionicons-prefix}ios7-reloading:before { 75 | @extend .#{$ionicons-prefix}ios7-reload:before; 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ionic User Authentication with NodeJS 2 | 3 | For your enjoyment this project provides a basic Ionic app and Express server with functionality for user authentication using bearer tokens, as specified by [RFC 6750](http://tools.ietf.org/html/rfc6750). 4 | 5 | ## Prerequisites 6 | 7 | 1. Install [npm](https://github.com/npm/npm) 8 | 2. Install [Ionic CLI](http://ionicframework.com/getting-started/) 9 | 3. Install [MongoDB](http://www.mongodb.com/) 10 | 4. Install [Gulp](https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md#getting-started) 11 | 12 | ## Installation 13 | 14 | 1. Download project. 15 | 2. Run `npm install` in both `mobile` and `server` directories. 16 | 3. Modify `conf.js` in `server` directory to connect to your MongoDB. 17 | 4. Run `mongod`. 18 | 5. Run `gulp` in `server` directory to start server. 19 | 6. Run `ionic serve` or `ionic build ios; ionic emulate ios;` in `mobile` directory. 20 | 7. Eat cookies 21 | 22 | If using `ionic serve` I used this [solution](http://forum.ionicframework.com/t/solved-cors-with-ionic/7454/7?u=seanhill) to do HTTP requests right from the browser. 23 | 24 | This project has not been tested in an production enviroment, so the API may not work without the `Access-Control-Allow-Origin` being set properly. 25 | 26 | 27 | ## Mobile App 28 | 29 | The mobile app is very basic. It starts you off in a "Welcome" screen that has one button `Create Profile`. This transitions you into a `Create Profile` page. After creating a profile and storing the `authToken` in the device's local storage, it transitions you to a sample page where you can get a user from the protected `/api/user/get` route. You can also test the `401` server response on this page by clearing out the `authToken` and requesting a user from `/api/user/get`. This causes the mobile app to display a `Login Modal` that requires to first authenticate, and then proceeds to get the user. 30 | 31 | Thanks to [KD Moore Consulting](http://www.kdmooreconsulting.com/blogs/authentication-with-ionic-and-angular-js-in-a-cordovaphonegap-mobile-web-application/) which provides a more detailed look at this process. 32 | 33 | ## The Point 34 | 35 | I finally figured out how to link up my Ionic App with a backend using bearer tokens for authentication, so I wanted to provide a solution that will hopefully help you on your journey to creating mobile apps. 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_modal.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Modals 4 | * -------------------------------------------------- 5 | * Modals are independent windows that slide in from off-screen. 6 | */ 7 | 8 | .modal-backdrop { 9 | @include transition(background-color 300ms ease-in-out); 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | z-index: $z-index-modal; 14 | width: 100%; 15 | height: 100%; 16 | background-color: $modal-backdrop-bg-inactive; 17 | 18 | &.active { 19 | background-color: $modal-backdrop-bg-active; 20 | } 21 | } 22 | 23 | .modal { 24 | display: block; 25 | position: absolute; 26 | top: 0; 27 | z-index: $z-index-modal; 28 | overflow: hidden; 29 | min-height: 100%; 30 | width: 100%; 31 | background-color: $modal-bg-color; 32 | } 33 | 34 | @media (min-width: $modal-inset-mode-break-point) { 35 | // inset mode is when the modal doesn't fill the entire 36 | // display but instead is centered within a large display 37 | .modal { 38 | top: $modal-inset-mode-top; 39 | right: $modal-inset-mode-right; 40 | bottom: $modal-inset-mode-bottom; 41 | left: $modal-inset-mode-left; 42 | overflow: visible; 43 | min-height: $modal-inset-mode-min-height; 44 | width: (100% - $modal-inset-mode-left - $modal-inset-mode-right); 45 | } 46 | 47 | .modal.ng-leave-active { 48 | bottom: 0; 49 | } 50 | 51 | // remove ios header padding from inset header 52 | .platform-ios.platform-cordova .modal-wrapper .modal{ 53 | .bar-header:not(.bar-subheader) { 54 | height: $bar-height; 55 | > * { 56 | margin-top: 0; 57 | } 58 | } 59 | .tabs-top > .tabs, 60 | .tabs.tabs-top { 61 | top: $bar-height; 62 | } 63 | .has-header, 64 | .bar-subheader { 65 | top: $bar-height; 66 | } 67 | .has-subheader { 68 | top: (2 * $bar-height); 69 | } 70 | .has-tabs-top { 71 | top: $bar-height + $tabs-height; 72 | } 73 | .has-header.has-subheader.has-tabs-top { 74 | top: 2 * $bar-height + $tabs-height; 75 | } 76 | } 77 | } 78 | 79 | // disable clicks on all but the modal 80 | .modal-open { 81 | pointer-events: none; 82 | 83 | .modal, 84 | .modal-backdrop { 85 | pointer-events: auto; 86 | } 87 | // prevent clicks on modal when loading overlay is active though 88 | &.loading-active { 89 | .modal, 90 | .modal-backdrop { 91 | pointer-events: none; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_popup.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Popups 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .popup-container { 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | bottom: 0; 12 | right: 0; 13 | background: rgba(0,0,0,0); 14 | 15 | @include display-flex(); 16 | @include justify-content(center); 17 | @include align-items(center); 18 | 19 | z-index: $z-index-popup; 20 | 21 | // Start hidden 22 | visibility: hidden; 23 | &.popup-showing { 24 | visibility: visible; 25 | } 26 | 27 | &.popup-hidden .popup { 28 | @include animation-name(scaleOut); 29 | @include animation-duration($popup-leave-animation-duration); 30 | @include animation-timing-function(ease-in-out); 31 | @include animation-fill-mode(both); 32 | } 33 | 34 | &.active .popup { 35 | @include animation-name(superScaleIn); 36 | @include animation-duration($popup-enter-animation-duration); 37 | @include animation-timing-function(ease-in-out); 38 | @include animation-fill-mode(both); 39 | } 40 | 41 | .popup { 42 | width: $popup-width; 43 | max-width: 100%; 44 | max-height: 90%; 45 | 46 | border-radius: $popup-border-radius; 47 | background-color: $popup-background-color; 48 | 49 | @include display-flex(); 50 | @include flex-direction(column); 51 | } 52 | } 53 | 54 | .popup-head { 55 | padding: 15px 10px; 56 | border-bottom: 1px solid #eee; 57 | text-align: center; 58 | } 59 | .popup-title { 60 | margin: 0; 61 | padding: 0; 62 | font-size: 15px; 63 | } 64 | .popup-sub-title { 65 | margin: 5px 0 0 0; 66 | padding: 0; 67 | font-weight: normal; 68 | font-size: 11px; 69 | } 70 | .popup-body { 71 | padding: 10px; 72 | overflow: scroll; 73 | } 74 | 75 | .popup-buttons { 76 | @include display-flex(); 77 | @include flex-direction(row); 78 | padding: 10px; 79 | min-height: $popup-button-min-height + 20; 80 | 81 | .button { 82 | @include flex(1); 83 | display: block; 84 | min-height: $popup-button-min-height; 85 | border-radius: $popup-button-border-radius; 86 | line-height: $popup-button-line-height; 87 | 88 | margin-right: 5px; 89 | &:last-child { 90 | margin-right: 0px; 91 | } 92 | } 93 | } 94 | 95 | .popup-open { 96 | pointer-events: none; 97 | 98 | &.modal-open .modal { 99 | pointer-events: none; 100 | } 101 | 102 | .popup-backdrop, .popup { 103 | pointer-events: auto; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_list.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Lists 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .list { 8 | position: relative; 9 | padding-top: $item-border-width; 10 | padding-bottom: $item-border-width; 11 | padding-left: 0; // reset padding because ul and ol 12 | margin-bottom: 20px; 13 | } 14 | .list:last-child { 15 | margin-bottom: 0px; 16 | &.card{ 17 | margin-bottom:40px; 18 | } 19 | } 20 | 21 | 22 | /** 23 | * List Header 24 | * -------------------------------------------------- 25 | */ 26 | 27 | .list-header { 28 | margin-top: $list-header-margin-top; 29 | padding: $list-header-padding; 30 | background-color: $list-header-bg; 31 | color: $list-header-color; 32 | font-weight: bold; 33 | } 34 | 35 | // when its a card make sure it doesn't duplicate top and bottom borders 36 | .card.list .list-item { 37 | padding-right: 1px; 38 | padding-left: 1px; 39 | } 40 | 41 | 42 | /** 43 | * Cards and Inset Lists 44 | * -------------------------------------------------- 45 | * A card and list-inset are close to the same thing, except a card as a box shadow. 46 | */ 47 | 48 | .card, 49 | .list-inset { 50 | overflow: hidden; 51 | margin: ($content-padding * 2) $content-padding; 52 | border-radius: $card-border-radius; 53 | background-color: $card-body-bg; 54 | } 55 | 56 | .card { 57 | padding-top: $item-border-width; 58 | padding-bottom: $item-border-width; 59 | box-shadow: 0 1px 2px rgba(0, 0, 0, .10); 60 | } 61 | 62 | .padding { 63 | .card, .list-inset { 64 | margin-left: 0; 65 | margin-right: 0; 66 | } 67 | } 68 | 69 | .card .item, 70 | .list-inset .item, 71 | .padding > .list .item 72 | { 73 | &:first-child { 74 | border-top-left-radius: $card-border-radius; 75 | border-top-right-radius: $card-border-radius; 76 | 77 | .item-content { 78 | border-top-left-radius: $card-border-radius; 79 | border-top-right-radius: $card-border-radius; 80 | } 81 | } 82 | &:last-child { 83 | border-bottom-right-radius: $card-border-radius; 84 | border-bottom-left-radius: $card-border-radius; 85 | 86 | .item-content { 87 | border-bottom-right-radius: $card-border-radius; 88 | border-bottom-left-radius: $card-border-radius; 89 | } 90 | } 91 | } 92 | 93 | .card .item:last-child, 94 | .list-inset .item:last-child { 95 | margin-bottom: $item-border-width * -1; 96 | } 97 | 98 | .card .item, 99 | .list-inset .item, 100 | .padding > .list .item, 101 | .padding-horizontal > .list .item { 102 | margin-right: 0; 103 | margin-left: 0; 104 | 105 | &.item-input input { 106 | padding-right: 44px; 107 | } 108 | } 109 | .padding-left > .list .item { 110 | margin-left: 0; 111 | } 112 | .padding-right > .list .item { 113 | margin-right: 0; 114 | } 115 | -------------------------------------------------------------------------------- /mobile/hooks/after_prepare/010_add_platform_class.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Add Platform Class 4 | // v1.0 5 | // Automatically adds the platform class to the body tag 6 | // after the `prepare` command. By placing the platform CSS classes 7 | // directly in the HTML built for the platform, it speeds up 8 | // rendering the correct layout/style for the specific platform 9 | // instead of waiting for the JS to figure out the correct classes. 10 | 11 | var fs = require('fs'); 12 | var path = require('path'); 13 | 14 | var rootdir = process.argv[2]; 15 | 16 | function addPlatformBodyTag(indexPath, platform) { 17 | // add the platform class to the body tag 18 | try { 19 | var platformClass = 'platform-' + platform; 20 | var cordovaClass = 'platform-cordova platform-webview'; 21 | 22 | var html = fs.readFileSync(indexPath, 'utf8'); 23 | 24 | var bodyTag = findBodyTag(html); 25 | if(!bodyTag) return; // no opening body tag, something's wrong 26 | 27 | if(bodyTag.indexOf(platformClass) > -1) return; // already added 28 | 29 | var newBodyTag = bodyTag; 30 | 31 | var classAttr = findClassAttr(bodyTag); 32 | if(classAttr) { 33 | // body tag has existing class attribute, add the classname 34 | var endingQuote = classAttr.substring(classAttr.length-1); 35 | var newClassAttr = classAttr.substring(0, classAttr.length-1); 36 | newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote; 37 | newBodyTag = bodyTag.replace(classAttr, newClassAttr); 38 | 39 | } else { 40 | // add class attribute to the body tag 41 | newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">'); 42 | } 43 | 44 | html = html.replace(bodyTag, newBodyTag); 45 | 46 | fs.writeFileSync(indexPath, html, 'utf8'); 47 | 48 | process.stdout.write('add to body class: ' + platformClass + '\n'); 49 | } catch(e) { 50 | process.stdout.write(e); 51 | } 52 | } 53 | 54 | function findBodyTag(html) { 55 | // get the body tag 56 | try{ 57 | return html.match(/])(.*?)>/gi)[0]; 58 | }catch(e){} 59 | } 60 | 61 | function findClassAttr(bodyTag) { 62 | // get the body tag's class attribute 63 | try{ 64 | return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0]; 65 | }catch(e){} 66 | } 67 | 68 | if (rootdir) { 69 | 70 | // go through each of the platform directories that have been prepared 71 | var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []); 72 | 73 | for(var x=0; x 21 | # Cordova Hooks 22 | 23 | This directory may contain scripts used to customize cordova commands. This 24 | directory used to exist at `.cordova/hooks`, but has now been moved to the 25 | project root. Any scripts you add to these directories will be executed before 26 | and after the commands corresponding to the directory name. Useful for 27 | integrating your own build systems or integrating with version control systems. 28 | 29 | __Remember__: Make your scripts executable. 30 | 31 | ## Hook Directories 32 | The following subdirectories will be used for hooks: 33 | 34 | after_build/ 35 | after_compile/ 36 | after_docs/ 37 | after_emulate/ 38 | after_platform_add/ 39 | after_platform_rm/ 40 | after_platform_ls/ 41 | after_plugin_add/ 42 | after_plugin_ls/ 43 | after_plugin_rm/ 44 | after_plugin_search/ 45 | after_prepare/ 46 | after_run/ 47 | after_serve/ 48 | before_build/ 49 | before_compile/ 50 | before_docs/ 51 | before_emulate/ 52 | before_platform_add/ 53 | before_platform_rm/ 54 | before_platform_ls/ 55 | before_plugin_add/ 56 | before_plugin_ls/ 57 | before_plugin_rm/ 58 | before_plugin_search/ 59 | before_prepare/ 60 | before_run/ 61 | before_serve/ 62 | pre_package/ <-- Windows 8 and Windows Phone only. 63 | 64 | ## Script Interface 65 | 66 | All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables: 67 | 68 | * CORDOVA_VERSION - The version of the Cordova-CLI. 69 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios). 70 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer) 71 | * CORDOVA_HOOK - Path to the hook that is being executed. 72 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate) 73 | 74 | If a script returns a non-zero exit code, then the parent cordova command will be aborted. 75 | 76 | 77 | ## Writing hooks 78 | 79 | We highly recommend writting your hooks using Node.js so that they are 80 | cross-platform. Some good examples are shown here: 81 | 82 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/) 83 | 84 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_select.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Select 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .item-select { 8 | position: relative; 9 | 10 | select { 11 | @include appearance(none); 12 | position: absolute; 13 | top: 0; 14 | right: 0; 15 | padding: ($item-padding - 2) ($item-padding * 3) ($item-padding) $item-padding; 16 | max-width: 65%; 17 | 18 | border: none; 19 | background: $item-default-bg; 20 | color: #333; 21 | 22 | // hack to hide default dropdown arrow in FF 23 | text-indent: .01px; 24 | text-overflow: ''; 25 | 26 | white-space: nowrap; 27 | font-size: $font-size-base; 28 | 29 | cursor: pointer; 30 | direction: rtl; // right align the select text 31 | } 32 | 33 | select::-ms-expand { 34 | // hide default dropdown arrow in IE 35 | display: none; 36 | } 37 | 38 | option { 39 | direction: ltr; 40 | } 41 | 42 | &:after { 43 | position: absolute; 44 | top: 50%; 45 | right: $item-padding; 46 | margin-top: -3px; 47 | width: 0; 48 | height: 0; 49 | border-top: 5px solid; 50 | border-right: 5px solid rgba(0, 0, 0, 0); 51 | border-left: 5px solid rgba(0, 0, 0, 0); 52 | color: #999; 53 | content: ""; 54 | pointer-events: none; 55 | } 56 | &.item-light { 57 | select{ 58 | background:$item-light-bg; 59 | color:$item-light-text; 60 | } 61 | } 62 | &.item-stable { 63 | select{ 64 | background:$item-stable-bg; 65 | color:$item-stable-text; 66 | } 67 | &:after, .input-label{ 68 | color:darken($item-stable-border,30%); 69 | } 70 | } 71 | &.item-positive { 72 | select{ 73 | background:$item-positive-bg; 74 | color:$item-positive-text; 75 | } 76 | &:after, .input-label{ 77 | color:$item-positive-text; 78 | } 79 | } 80 | &.item-calm { 81 | select{ 82 | background:$item-calm-bg; 83 | color:$item-calm-text; 84 | } 85 | &:after, .input-label{ 86 | color:$item-calm-text; 87 | } 88 | } 89 | &.item-assertive { 90 | select{ 91 | background:$item-assertive-bg; 92 | color:$item-assertive-text; 93 | } 94 | &:after, .input-label{ 95 | color:$item-assertive-text; 96 | } 97 | } 98 | &.item-balanced { 99 | select{ 100 | background:$item-balanced-bg; 101 | color:$item-balanced-text; 102 | } 103 | &:after, .input-label{ 104 | color:$item-balanced-text; 105 | } 106 | } 107 | &.item-energized { 108 | select{ 109 | background:$item-energized-bg; 110 | color:$item-energized-text; 111 | } 112 | &:after, .input-label{ 113 | color:$item-energized-text; 114 | } 115 | } 116 | &.item-royal { 117 | select{ 118 | background:$item-royal-bg; 119 | color:$item-royal-text; 120 | } 121 | &:after, .input-label{ 122 | color:$item-royal-text; 123 | } 124 | } 125 | &.item-dark { 126 | select{ 127 | background:$item-dark-bg; 128 | color:$item-dark-text; 129 | } 130 | &:after, .input-label{ 131 | color:$item-dark-text; 132 | } 133 | } 134 | } 135 | 136 | select { 137 | &[multiple], 138 | &[size] { 139 | height: auto; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_range.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Range 4 | * -------------------------------------------------- 5 | */ 6 | 7 | input[type="range"] { 8 | display: inline-block; 9 | overflow: hidden; 10 | margin-top: 5px; 11 | margin-bottom: 5px; 12 | padding-right: 2px; 13 | padding-left: 1px; 14 | width: auto; 15 | height: $range-slider-height + 15; 16 | outline: none; 17 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, $range-default-track-bg), color-stop(100%, $range-default-track-bg)); 18 | background: linear-gradient(to right, $range-default-track-bg 0%, $range-default-track-bg 100%); 19 | background-position: center; 20 | background-size: 99% $range-track-height; 21 | background-repeat: no-repeat; 22 | -webkit-appearance: none; 23 | 24 | &::-webkit-slider-thumb { 25 | position: relative; 26 | width: $range-slider-width; 27 | height: $range-slider-height; 28 | border-radius: $range-slider-border-radius; 29 | background-color: $toggle-handle-off-bg-color; 30 | box-shadow: 0 0 2px rgba(0,0,0,.5), 1px 3px 5px rgba(0,0,0,0.25); 31 | cursor: pointer; 32 | -webkit-appearance: none; 33 | } 34 | 35 | &::-webkit-slider-thumb:before { 36 | /* what creates the colorful line on the left side of the slider */ 37 | position: absolute; 38 | top: ($range-slider-height / 2) - ($range-track-height / 2); 39 | left: -2001px; 40 | width: 2000px; 41 | height: $range-track-height; 42 | background: $dark; 43 | content: ' '; 44 | } 45 | 46 | &::-webkit-slider-thumb:after { 47 | /* create a larger (but hidden) hit area */ 48 | position: absolute; 49 | top: -20px; 50 | left: -20px; 51 | padding: 30px; 52 | content: ' '; 53 | //background: red; 54 | //opacity: .5; 55 | } 56 | 57 | } 58 | 59 | .range { 60 | @include display-flex(); 61 | @include align-items(center); 62 | padding: 2px 11px; 63 | 64 | &.range-light { 65 | input { @include range-style($range-light-track-bg); } 66 | } 67 | &.range-stable { 68 | input { @include range-style($range-stable-track-bg); } 69 | } 70 | &.range-positive { 71 | input { @include range-style($range-positive-track-bg); } 72 | } 73 | &.range-calm { 74 | input { @include range-style($range-calm-track-bg); } 75 | } 76 | &.range-balanced { 77 | input { @include range-style($range-balanced-track-bg); } 78 | } 79 | &.range-assertive { 80 | input { @include range-style($range-assertive-track-bg); } 81 | } 82 | &.range-energized { 83 | input { @include range-style($range-energized-track-bg); } 84 | } 85 | &.range-royal { 86 | input { @include range-style($range-royal-track-bg); } 87 | } 88 | &.range-dark { 89 | input { @include range-style($range-dark-track-bg); } 90 | } 91 | } 92 | 93 | .range .icon { 94 | @include flex(0); 95 | display: block; 96 | min-width: $range-icon-size; 97 | text-align: center; 98 | font-size: $range-icon-size; 99 | } 100 | 101 | .range input { 102 | @include flex(1); 103 | display: block; 104 | margin-right: 10px; 105 | margin-left: 10px; 106 | } 107 | 108 | .range-label { 109 | @include flex(0, 0, auto); 110 | display: block; 111 | white-space: nowrap; 112 | } 113 | 114 | .range-label:first-child { 115 | padding-left: 5px; 116 | } 117 | .range input + .range-label { 118 | padding-right: 5px; 119 | padding-left: 0; 120 | } 121 | 122 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_grid.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Grid 3 | * -------------------------------------------------- 4 | * Using flexbox for the grid, inspired by Philip Walton: 5 | * http://philipwalton.github.io/solved-by-flexbox/demos/grids/ 6 | * By default each .col within a .row will evenly take up 7 | * available width, and the height of each .col with take 8 | * up the height of the tallest .col in the same .row. 9 | */ 10 | 11 | .row { 12 | @include display-flex(); 13 | padding: ($grid-padding-width / 2); 14 | width: 100%; 15 | } 16 | 17 | .row-wrap { 18 | @include flex-wrap(wrap); 19 | } 20 | 21 | .row + .row { 22 | margin-top: ($grid-padding-width / 2) * -1; 23 | padding-top: 0; 24 | } 25 | 26 | .col { 27 | @include flex(1); 28 | display: block; 29 | padding: ($grid-padding-width / 2); 30 | width: 100%; 31 | } 32 | 33 | 34 | /* Vertically Align Columns */ 35 | /* .row-* vertically aligns every .col in the .row */ 36 | .row-top { 37 | @include align-items(flex-start); 38 | } 39 | .row-bottom { 40 | @include align-items(flex-end); 41 | } 42 | .row-center { 43 | @include align-items(center); 44 | } 45 | .row-stretch { 46 | @include align-items(stretch); 47 | } 48 | .row-baseline { 49 | @include align-items(baseline); 50 | } 51 | 52 | /* .col-* vertically aligns an individual .col */ 53 | .col-top { 54 | @include align-self(flex-start); 55 | } 56 | .col-bottom { 57 | @include align-self(flex-end); 58 | } 59 | .col-center { 60 | @include align-self(center); 61 | } 62 | 63 | /* Column Offsets */ 64 | .col-offset-10 { 65 | margin-left: 10%; 66 | } 67 | .col-offset-20 { 68 | margin-left: 20%; 69 | } 70 | .col-offset-25 { 71 | margin-left: 25%; 72 | } 73 | .col-offset-33, .col-offset-34 { 74 | margin-left: 33.3333%; 75 | } 76 | .col-offset-50 { 77 | margin-left: 50%; 78 | } 79 | .col-offset-66, .col-offset-67 { 80 | margin-left: 66.6666%; 81 | } 82 | .col-offset-75 { 83 | margin-left: 75%; 84 | } 85 | .col-offset-80 { 86 | margin-left: 80%; 87 | } 88 | .col-offset-90 { 89 | margin-left: 90%; 90 | } 91 | 92 | 93 | /* Explicit Column Percent Sizes */ 94 | /* By default each grid column will evenly distribute */ 95 | /* across the grid. However, you can specify individual */ 96 | /* columns to take up a certain size of the available area */ 97 | .col-10 { 98 | @include flex(0, 0, 10%); 99 | max-width: 10%; 100 | } 101 | .col-20 { 102 | @include flex(0, 0, 20%); 103 | max-width: 20%; 104 | } 105 | .col-25 { 106 | @include flex(0, 0, 25%); 107 | max-width: 25%; 108 | } 109 | .col-33, .col-34 { 110 | @include flex(0, 0, 33.3333%); 111 | max-width: 33.3333%; 112 | } 113 | .col-50 { 114 | @include flex(0, 0, 50%); 115 | max-width: 50%; 116 | } 117 | .col-66, .col-67 { 118 | @include flex(0, 0, 66.6666%); 119 | max-width: 66.6666%; 120 | } 121 | .col-75 { 122 | @include flex(0, 0, 75%); 123 | max-width: 75%; 124 | } 125 | .col-80 { 126 | @include flex(0, 0, 80%); 127 | max-width: 80%; 128 | } 129 | .col-90 { 130 | @include flex(0, 0, 90%); 131 | max-width: 90%; 132 | } 133 | 134 | 135 | /* Responsive Grid Classes */ 136 | /* Adding a class of responsive-X to a row */ 137 | /* will trigger the flex-direction to */ 138 | /* change to column and add some margin */ 139 | /* to any columns in the row for clearity */ 140 | 141 | @include responsive-grid-break('.responsive-sm', $grid-responsive-sm-break); 142 | @include responsive-grid-break('.responsive-md', $grid-responsive-md-break); 143 | @include responsive-grid-break('.responsive-lg', $grid-responsive-lg-break); 144 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_type.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Typography 4 | * -------------------------------------------------- 5 | */ 6 | 7 | 8 | // Body text 9 | // ------------------------- 10 | 11 | p { 12 | margin: 0 0 ($line-height-computed / 2); 13 | } 14 | 15 | 16 | // Emphasis & misc 17 | // ------------------------- 18 | 19 | small { font-size: 85%; } 20 | cite { font-style: normal; } 21 | 22 | 23 | // Alignment 24 | // ------------------------- 25 | 26 | .text-left { text-align: left; } 27 | .text-right { text-align: right; } 28 | .text-center { text-align: center; } 29 | 30 | 31 | // Headings 32 | // ------------------------- 33 | 34 | h1, h2, h3, h4, h5, h6, 35 | .h1, .h2, .h3, .h4, .h5, .h6 { 36 | color: $base-color; 37 | font-weight: $headings-font-weight; 38 | font-family: $headings-font-family; 39 | line-height: $headings-line-height; 40 | 41 | small { 42 | font-weight: normal; 43 | line-height: 1; 44 | } 45 | } 46 | 47 | h1, .h1, 48 | h2, .h2, 49 | h3, .h3 { 50 | margin-top: $line-height-computed; 51 | margin-bottom: ($line-height-computed / 2); 52 | 53 | &:first-child { 54 | margin-top: 0; 55 | } 56 | 57 | + h1, + .h1, 58 | + h2, + .h2, 59 | + h3, + .h3 { 60 | margin-top: ($line-height-computed / 2); 61 | } 62 | } 63 | 64 | h4, .h4, 65 | h5, .h5, 66 | h6, .h6 { 67 | margin-top: ($line-height-computed / 2); 68 | margin-bottom: ($line-height-computed / 2); 69 | } 70 | 71 | h1, .h1 { font-size: floor($font-size-base * 2.60); } // ~36px 72 | h2, .h2 { font-size: floor($font-size-base * 2.15); } // ~30px 73 | h3, .h3 { font-size: ceil($font-size-base * 1.70); } // ~24px 74 | h4, .h4 { font-size: ceil($font-size-base * 1.25); } // ~18px 75 | h5, .h5 { font-size: $font-size-base; } 76 | h6, .h6 { font-size: ceil($font-size-base * 0.85); } // ~12px 77 | 78 | h1 small, .h1 small { font-size: ceil($font-size-base * 1.70); } // ~24px 79 | h2 small, .h2 small { font-size: ceil($font-size-base * 1.25); } // ~18px 80 | h3 small, .h3 small, 81 | h4 small, .h4 small { font-size: $font-size-base; } 82 | 83 | 84 | // Description Lists 85 | // ------------------------- 86 | 87 | dl { 88 | margin-bottom: $line-height-computed; 89 | } 90 | dt, 91 | dd { 92 | line-height: $line-height-base; 93 | } 94 | dt { 95 | font-weight: bold; 96 | } 97 | 98 | 99 | // Blockquotes 100 | // ------------------------- 101 | 102 | blockquote { 103 | margin: 0 0 $line-height-computed; 104 | padding: ($line-height-computed / 2) $line-height-computed; 105 | border-left: 5px solid gray; 106 | 107 | p { 108 | font-weight: 300; 109 | font-size: ($font-size-base * 1.25); 110 | line-height: 1.25; 111 | } 112 | 113 | p:last-child { 114 | margin-bottom: 0; 115 | } 116 | 117 | small { 118 | display: block; 119 | line-height: $line-height-base; 120 | &:before { 121 | content: '\2014 \00A0';// EM DASH, NBSP; 122 | } 123 | } 124 | } 125 | 126 | 127 | // Quotes 128 | // ------------------------- 129 | 130 | q:before, 131 | q:after, 132 | blockquote:before, 133 | blockquote:after { 134 | content: ""; 135 | } 136 | 137 | 138 | // Addresses 139 | // ------------------------- 140 | 141 | address { 142 | display: block; 143 | margin-bottom: $line-height-computed; 144 | font-style: normal; 145 | line-height: $line-height-base; 146 | } 147 | 148 | 149 | // Links 150 | // ------------------------- 151 | 152 | a.subdued { 153 | padding-right: 10px; 154 | color: #888; 155 | text-decoration: none; 156 | 157 | &:hover { 158 | text-decoration: none; 159 | } 160 | &:last-child { 161 | padding-right: 0; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_platform.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Platform 4 | * -------------------------------------------------- 5 | * Platform specific tweaks 6 | */ 7 | 8 | 9 | /** 10 | * Apply roboto font 11 | */ 12 | 13 | .roboto { 14 | font-family: "Roboto", $font-family-base; 15 | 16 | input { 17 | font-family: "Roboto", $font-family-base; 18 | } 19 | } 20 | /* 21 | .platform-android { 22 | 23 | 24 | .bar { 25 | padding: 0; 26 | 27 | line-height: 40px; 28 | 29 | .button { 30 | line-height: 40px; 31 | } 32 | 33 | .button-icon:before { 34 | font-size: 24px; 35 | } 36 | } 37 | 38 | .back-button { 39 | &.button-icon:before { 40 | line-height: 40px; 41 | } 42 | margin-left: -3px; 43 | padding: 0px 2px !important; 44 | &.ion-android-arrow-back:before { 45 | font-size: 12px; 46 | } 47 | 48 | &.back-button.active, 49 | &.back-button.activated { 50 | background-color: rgba(0,0,0,0.1); 51 | } 52 | } 53 | 54 | .item-divider { 55 | background: none; 56 | border-top-width: 0; 57 | border-bottom-width: 2px; 58 | text-transform: uppercase; 59 | margin-top: 10px; 60 | font-size: 14px; 61 | } 62 | .item { 63 | border-left-width: 0; 64 | border-right-width: 0; 65 | } 66 | 67 | .item-divider ~ .item:not(.item-divider) { 68 | border-bottom-width: 0; 69 | } 70 | 71 | .back-button:not(.ng-hide) + .left-buttons + .title { 72 | // Don't allow normal titles in this mode 73 | display: none; 74 | } 75 | 76 | .bar .title { 77 | text-align: left; 78 | font-weight: normal; 79 | } 80 | 81 | font-family: 'Roboto'; 82 | 83 | h1, h2, h3, h4, h5 { 84 | font-family: 'Roboto', $font-family-base; 85 | } 86 | 87 | .tab-item { 88 | font-family: 'Roboto', $font-family-base; 89 | } 90 | 91 | 92 | input, button, select, textarea { 93 | font-family: 'Roboto', $font-family-base; 94 | } 95 | */ 96 | //} 97 | 98 | .platform-ios.platform-cordova { 99 | // iOS7/8 has a status bar which sits on top of the header. 100 | // Bump down everything to make room for it. However, if 101 | // if its in Cordova, and set to fullscreen, then disregard the bump. 102 | &:not(.fullscreen) { 103 | .bar-header:not(.bar-subheader) { 104 | height: $bar-height + $ios-statusbar-height; 105 | 106 | &.item-input-inset .item-input-wrapper { 107 | margin-top: 19px !important; 108 | } 109 | 110 | > * { 111 | margin-top: $ios-statusbar-height; 112 | } 113 | } 114 | .tabs-top > .tabs, 115 | .tabs.tabs-top { 116 | top: $bar-height + $ios-statusbar-height; 117 | } 118 | 119 | .has-header, 120 | .bar-subheader { 121 | top: $bar-height + $ios-statusbar-height; 122 | } 123 | .has-subheader { 124 | top: (2 * $bar-height) + $ios-statusbar-height; 125 | } 126 | .has-tabs-top { 127 | top: $bar-height + $tabs-height + $ios-statusbar-height; 128 | } 129 | .has-header.has-subheader.has-tabs-top { 130 | top: 2 * $bar-height + $tabs-height + $ios-statusbar-height; 131 | } 132 | } 133 | &.status-bar-hide { 134 | // Cordova doesn't adjust the body height correctly, this makes up for it 135 | margin-bottom: 20px; 136 | } 137 | } 138 | 139 | @media (orientation:landscape) { 140 | .platform-ios.platform-browser.platform-ipad { 141 | position: fixed; // required for iPad 7 Safari 142 | } 143 | } 144 | 145 | .platform-c:not(.enable-transitions) * { 146 | // disable transitions on grade-c devices (Android 2) 147 | -webkit-transition: none !important; 148 | transition: none !important; 149 | } 150 | 151 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_popover.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Popovers 4 | * -------------------------------------------------- 5 | * Popovers are independent views which float over content 6 | */ 7 | 8 | .popover-backdrop { 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | z-index: $z-index-popover; 13 | width: 100%; 14 | height: 100%; 15 | background-color: $popover-backdrop-bg-inactive; 16 | 17 | &.active { 18 | background-color: $popover-backdrop-bg-active; 19 | } 20 | } 21 | 22 | .popover { 23 | position: absolute; 24 | top: 25%; 25 | left: 50%; 26 | z-index: $z-index-popover; 27 | display: block; 28 | margin-top: 12px; 29 | margin-left: -$popover-width / 2; 30 | height: $popover-height; 31 | width: $popover-width; 32 | background-color: $popover-bg-color; 33 | box-shadow: $popover-box-shadow; 34 | opacity: 0; 35 | 36 | .item:first-child { 37 | border-top: 0; 38 | } 39 | 40 | .item:last-child { 41 | border-bottom: 0; 42 | } 43 | 44 | &.popover-bottom { 45 | margin-top: -12px; 46 | } 47 | } 48 | 49 | 50 | // Set popover border-radius 51 | .popover, 52 | .popover .bar-header { 53 | border-radius: $popover-border-radius; 54 | } 55 | .popover .scroll-content { 56 | z-index: 1; 57 | margin: 2px 0; 58 | } 59 | .popover .bar-header { 60 | border-bottom-right-radius: 0; 61 | border-bottom-left-radius: 0; 62 | } 63 | .popover .has-header { 64 | border-top-right-radius: 0; 65 | border-top-left-radius: 0; 66 | } 67 | .popover-arrow { 68 | display: none; 69 | } 70 | 71 | 72 | // iOS Popover 73 | .platform-ios { 74 | 75 | .popover { 76 | box-shadow: $popover-box-shadow-ios; 77 | } 78 | 79 | .popover, 80 | .popover .bar-header { 81 | border-radius: $popover-border-radius-ios; 82 | } 83 | .popover .scroll-content { 84 | margin: 8px 0; 85 | border-radius: $popover-border-radius-ios; 86 | } 87 | .popover .scroll-content.has-header { 88 | margin-top: 0; 89 | } 90 | .popover-arrow { 91 | position: absolute; 92 | display: block; 93 | top: -17px; 94 | width: 30px; 95 | height: 19px; 96 | overflow: hidden; 97 | 98 | &:after { 99 | position: absolute; 100 | top: 12px; 101 | left: 5px; 102 | width: 20px; 103 | height: 20px; 104 | background-color: $popover-bg-color; 105 | border-radius: 3px; 106 | content: ''; 107 | @include rotate(-45deg); 108 | } 109 | } 110 | .popover-bottom .popover-arrow { 111 | top: auto; 112 | bottom: -10px; 113 | &:after { 114 | top: -6px; 115 | } 116 | } 117 | } 118 | 119 | 120 | // Android Popover 121 | .platform-android { 122 | 123 | .popover { 124 | margin-top: -32px; 125 | background-color: $popover-bg-color-android; 126 | box-shadow: $popover-box-shadow-android; 127 | 128 | .item { 129 | border-color: $popover-bg-color-android; 130 | background-color: $popover-bg-color-android; 131 | color: #4d4d4d; 132 | } 133 | &.popover-bottom { 134 | margin-top: 32px; 135 | } 136 | } 137 | 138 | .popover-backdrop, 139 | .popover-backdrop.active { 140 | background-color: transparent; 141 | } 142 | } 143 | 144 | 145 | // disable clicks on all but the popover 146 | .popover-open { 147 | pointer-events: none; 148 | 149 | .popover, 150 | .popover-backdrop { 151 | pointer-events: auto; 152 | } 153 | // prevent clicks on popover when loading overlay is active though 154 | &.loading-active { 155 | .popover, 156 | .popover-backdrop { 157 | pointer-events: none; 158 | } 159 | } 160 | } 161 | 162 | 163 | // wider popover on larger viewports 164 | @media (min-width: $popover-large-break-point) { 165 | .popover { 166 | width: $popover-large-width; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_toggle.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Toggle 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .item-toggle { 8 | pointer-events: none; 9 | } 10 | 11 | .toggle { 12 | // set the color defaults 13 | @include toggle-style($toggle-on-default-border, $toggle-on-default-bg); 14 | 15 | position: relative; 16 | display: inline-block; 17 | pointer-events: auto; 18 | margin: -$toggle-hit-area-expansion; 19 | padding: $toggle-hit-area-expansion; 20 | 21 | &.dragging { 22 | .handle { 23 | background-color: $toggle-handle-dragging-bg-color !important; 24 | } 25 | } 26 | 27 | &.toggle-light { 28 | @include toggle-style($toggle-on-light-border, $toggle-on-light-bg); 29 | } 30 | &.toggle-stable { 31 | @include toggle-style($toggle-on-stable-border, $toggle-on-stable-bg); 32 | } 33 | &.toggle-positive { 34 | @include toggle-style($toggle-on-positive-border, $toggle-on-positive-bg); 35 | } 36 | &.toggle-calm { 37 | @include toggle-style($toggle-on-calm-border, $toggle-on-calm-bg); 38 | } 39 | &.toggle-assertive { 40 | @include toggle-style($toggle-on-assertive-border, $toggle-on-assertive-bg); 41 | } 42 | &.toggle-balanced { 43 | @include toggle-style($toggle-on-balanced-border, $toggle-on-balanced-bg); 44 | } 45 | &.toggle-energized { 46 | @include toggle-style($toggle-on-energized-border, $toggle-on-energized-bg); 47 | } 48 | &.toggle-royal { 49 | @include toggle-style($toggle-on-royal-border, $toggle-on-royal-bg); 50 | } 51 | &.toggle-dark { 52 | @include toggle-style($toggle-on-dark-border, $toggle-on-dark-bg); 53 | } 54 | } 55 | 56 | .toggle input { 57 | // hide the actual input checkbox 58 | display: none; 59 | } 60 | 61 | /* the track appearance when the toggle is "off" */ 62 | .toggle .track { 63 | @include transition-timing-function(ease-in-out); 64 | @include transition-duration($toggle-transition-duration); 65 | @include transition-property((background-color, border)); 66 | 67 | display: inline-block; 68 | box-sizing: border-box; 69 | width: $toggle-width; 70 | height: $toggle-height; 71 | border: solid $toggle-border-width $toggle-off-border-color; 72 | border-radius: $toggle-border-radius; 73 | background-color: $toggle-off-bg-color; 74 | content: ' '; 75 | cursor: pointer; 76 | pointer-events: none; 77 | } 78 | 79 | /* Fix to avoid background color bleeding */ 80 | /* (occured on (at least) Android 4.2, Asus MeMO Pad HD7 ME173X) */ 81 | .platform-android4_2 .toggle .track { 82 | -webkit-background-clip: padding-box; 83 | } 84 | 85 | /* the handle (circle) thats inside the toggle's track area */ 86 | /* also the handle's appearance when it is "off" */ 87 | .toggle .handle { 88 | @include transition($toggle-transition-duration ease-in-out); 89 | position: absolute; 90 | display: block; 91 | width: $toggle-handle-width; 92 | height: $toggle-handle-height; 93 | border-radius: $toggle-handle-radius; 94 | background-color: $toggle-handle-off-bg-color; 95 | top: $toggle-border-width + $toggle-hit-area-expansion; 96 | left: $toggle-border-width + $toggle-hit-area-expansion; 97 | 98 | &:before { 99 | // used to create a larger (but hidden) hit area to slide the handle 100 | position: absolute; 101 | top: -4px; 102 | left: ( ($toggle-handle-width / 2) * -1) - 8; 103 | padding: ($toggle-handle-height / 2) + 5 ($toggle-handle-width + 7); 104 | content: " "; 105 | } 106 | } 107 | 108 | .toggle input:checked + .track .handle { 109 | // the handle when the toggle is "on" 110 | @include translate3d($toggle-width - $toggle-handle-width - ($toggle-border-width * 2), 0, 0); 111 | background-color: $toggle-handle-on-bg-color; 112 | } 113 | 114 | .item-toggle.active { 115 | box-shadow: none; 116 | } 117 | 118 | .item-toggle, 119 | .item-toggle.item-complex .item-content { 120 | // make sure list item content have enough padding on right to fit the toggle 121 | padding-right: ($item-padding * 3) + $toggle-width; 122 | } 123 | 124 | .item-toggle.item-complex { 125 | padding-right: 0; 126 | } 127 | 128 | .item-toggle .toggle { 129 | // position the toggle to the right within a list item 130 | position: absolute; 131 | top: $item-padding / 2; 132 | right: $item-padding; 133 | z-index: $z-index-item-toggle; 134 | } 135 | 136 | .toggle input:disabled + .track { 137 | opacity: .6; 138 | } 139 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/js/angular/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.25 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(q,g,r){'use strict';function F(a){var d=[];t(d,g.noop).chars(a);return d.join("")}function m(a){var d={};a=a.split(",");var c;for(c=0;c=c;e--)d.end&&d.end(f[e]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,l,f=[],n=a,h;for(f.last=function(){return f[f.length-1]};a;){h="";l=!0;if(f.last()&&y[f.last()])a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(I,"$1").replace(J,"$1");d.chars&&d.chars(s(b));return""}),e("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(d.comment&&d.comment(a.substring(4, 8 | b)),a=a.substring(b+3),l=!1);else if(z.test(a)){if(b=a.match(z))a=a.replace(b[0],""),l=!1}else if(K.test(a)){if(b=a.match(A))a=a.substring(b[0].length),b[0].replace(A,e),l=!1}else L.test(a)&&((b=a.match(B))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(B,c)),l=!1):(h+="<",a=a.substring(1)));l&&(b=a.indexOf("<"),h+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(s(h)))}if(a==n)throw M("badparse",a);n=a}e()}function s(a){if(!a)return"";var d=N.exec(a);a=d[1];var c=d[3];if(d=d[2])p.innerHTML= 9 | d.replace(//g,">")}function t(a,d){var c=!1,e=g.bind(a,a.push);return{start:function(a,l,f){a=g.lowercase(a);!c&&y[a]&&(c=a);c||!0!==D[a]||(e("<"),e(a),g.forEach(l,function(c,f){var k= 10 | g.lowercase(f),l="img"===a&&"src"===k||"background"===k;!0!==Q[k]||!0===E[k]&&!d(c,l)||(e(" "),e(f),e('="'),e(C(c)),e('"'))}),e(f?"/>":">"))},end:function(a){a=g.lowercase(a);c||!0!==D[a]||(e(""));a==c&&(c=!1)},chars:function(a){c||e(C(a))}}}var M=g.$$minErr("$sanitize"),B=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,A=/^<\/\s*([\w:-]+)[^>]*>/,H=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,L=/^]*?)>/i,J=/"]/,c=/^mailto:/;return function(e,b){function l(a){a&&k.push(F(a))}function f(a,c){k.push("');l(c);k.push("")} 14 | if(!e)return e;for(var n,h=e,k=[],m,p;n=h.match(d);)m=n[0],n[2]==n[3]&&(m="mailto:"+m),p=n.index,l(h.substr(0,p)),f(m,n[0].replace(c,"")),h=h.substring(p+n[0].length);l(h);return a(k.join(""))}}])})(window,window.angular); 15 | //# sourceMappingURL=angular-sanitize.min.js.map 16 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_checkbox.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Checkbox 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .checkbox { 8 | // set the color defaults 9 | @include checkbox-style($checkbox-off-border-default, $checkbox-on-bg-default); 10 | 11 | position: relative; 12 | display: inline-block; 13 | padding: ($checkbox-height / 4) ($checkbox-width / 4); 14 | cursor: pointer; 15 | } 16 | .checkbox-light { 17 | @include checkbox-style($checkbox-off-border-light, $checkbox-on-bg-light); 18 | } 19 | .checkbox-stable { 20 | @include checkbox-style($checkbox-off-border-stable, $checkbox-on-bg-stable); 21 | } 22 | .checkbox-positive { 23 | @include checkbox-style($checkbox-off-border-positive, $checkbox-on-bg-positive); 24 | } 25 | .checkbox-calm { 26 | @include checkbox-style($checkbox-off-border-calm, $checkbox-on-bg-calm); 27 | } 28 | .checkbox-assertive { 29 | @include checkbox-style($checkbox-off-border-assertive, $checkbox-on-bg-assertive); 30 | } 31 | .checkbox-balanced { 32 | @include checkbox-style($checkbox-off-border-balanced, $checkbox-on-bg-balanced); 33 | } 34 | .checkbox-energized{ 35 | @include checkbox-style($checkbox-off-border-energized, $checkbox-on-bg-energized); 36 | } 37 | .checkbox-royal { 38 | @include checkbox-style($checkbox-off-border-royal, $checkbox-on-bg-royal); 39 | } 40 | .checkbox-dark { 41 | @include checkbox-style($checkbox-off-border-dark, $checkbox-on-bg-dark); 42 | } 43 | 44 | .checkbox input:disabled:before, 45 | .checkbox input:disabled + .checkbox-icon:before { 46 | border-color: $checkbox-off-border-light; 47 | } 48 | 49 | .checkbox input:disabled:checked:before, 50 | .checkbox input:disabled:checked + .checkbox-icon:before { 51 | background: $checkbox-on-bg-light; 52 | } 53 | 54 | 55 | .checkbox.checkbox-input-hidden input { 56 | display: none !important; 57 | } 58 | 59 | .checkbox input, 60 | .checkbox-icon { 61 | position: relative; 62 | width: $checkbox-width; 63 | height: $checkbox-height; 64 | display: block; 65 | border: 0; 66 | background: transparent; 67 | cursor: pointer; 68 | -webkit-appearance: none; 69 | 70 | &:before { 71 | // what the checkbox looks like when its not checked 72 | display: table; 73 | width: 100%; 74 | height: 100%; 75 | border-width: $checkbox-border-width; 76 | border-style: solid; 77 | border-radius: $checkbox-border-radius; 78 | background: $checkbox-off-bg-color; 79 | content: ' '; 80 | transition: background-color 20ms ease-in-out; 81 | } 82 | } 83 | 84 | .checkbox input:checked:before, 85 | input:checked + .checkbox-icon:before { 86 | border-width: $checkbox-border-width + 1; 87 | } 88 | 89 | // the checkmark within the box 90 | .checkbox input:after, 91 | .checkbox-icon:after { 92 | @include transition(opacity .05s ease-in-out); 93 | @include rotate(-45deg); 94 | position: absolute; 95 | top: 30%; 96 | left: 26%; 97 | display: table; 98 | width: ($checkbox-width / 2) + 1; 99 | height: ($checkbox-width / 3) + 1; 100 | border: $checkbox-check-width solid $checkbox-check-color; 101 | border-top: 0; 102 | border-right: 0; 103 | content: ' '; 104 | opacity: 0; 105 | } 106 | 107 | .grade-c .checkbox input:after, 108 | .grade-c .checkbox-icon:after { 109 | @include rotate(0); 110 | top: 3px; 111 | left: 4px; 112 | border: none; 113 | color: $checkbox-check-color; 114 | content: '\2713'; 115 | font-weight: bold; 116 | font-size: 20px; 117 | } 118 | 119 | // what the checkmark looks like when its checked 120 | .checkbox input:checked:after, 121 | input:checked + .checkbox-icon:after { 122 | opacity: 1; 123 | } 124 | 125 | // make sure item content have enough padding on left to fit the checkbox 126 | .item-checkbox { 127 | padding-left: ($item-padding * 2) + $checkbox-width; 128 | 129 | &.active { 130 | box-shadow: none; 131 | } 132 | } 133 | 134 | // position the checkbox to the left within an item 135 | .item-checkbox .checkbox { 136 | position: absolute; 137 | top: 50%; 138 | right: $item-padding / 2; 139 | left: $item-padding / 2; 140 | z-index: $z-index-item-checkbox; 141 | margin-top: (($checkbox-height + ($checkbox-height / 2)) / 2) * -1; 142 | } 143 | 144 | 145 | .item-checkbox.item-checkbox-right { 146 | padding-right: ($item-padding * 2) + $checkbox-width; 147 | padding-left: $item-padding; 148 | } 149 | 150 | .item-checkbox-right .checkbox input, 151 | .item-checkbox-right .checkbox-icon { 152 | float: right; 153 | } -------------------------------------------------------------------------------- /mobile/www/js/services/auth-retry-service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('http-auth-interceptor', ['http-auth-interceptor-buffer']) 5 | 6 | .factory('AuthService', ['$rootScope', 'httpBuffer', function($rootScope, httpBuffer) { 7 | return { 8 | /** 9 | * Call this function to indicate that authentication was successfull and trigger a 10 | * retry of all deferred requests. 11 | * @param data an optional argument to pass on to $broadcast which may be useful for 12 | * example if you need to pass through details of the user that was logged in 13 | */ 14 | loginConfirmed: function(data, configUpdater) { 15 | var updater = configUpdater || function(config) { 16 | return config; 17 | }; 18 | $rootScope.$broadcast('auth:login-confirmed', data); 19 | httpBuffer.retryAll(updater); 20 | }, 21 | 22 | /** 23 | * Call this function to indicate that authentication should not proceed. 24 | * All deferred requests will be abandoned or rejected (if reason is provided). 25 | * @param data an optional argument to pass on to $broadcast. 26 | * @param reason if provided, the requests are rejected; abandoned otherwise. 27 | */ 28 | loginCancelled: function(data, reason) { 29 | httpBuffer.rejectAll(reason); 30 | $rootScope.$broadcast('auth:login-cancelled', data); 31 | } 32 | }; 33 | }]) 34 | 35 | /** 36 | * $http interceptor. 37 | * On 401 response (without 'ignoreAuthModule' option) stores the request 38 | * and broadcasts 'event:angular-auth-loginRequired'. 39 | */ 40 | .config(['$httpProvider', function($httpProvider) { 41 | $httpProvider.interceptors.push(['$rootScope', '$q', 'httpBuffer', function($rootScope, $q, httpBuffer) { 42 | return { 43 | responseError: function(rejection) { 44 | if (rejection.status === 401 && !rejection.config.ignoreAuthModule) { 45 | var deferred = $q.defer(); 46 | httpBuffer.append(rejection.config, deferred); 47 | $rootScope.$broadcast('auth:login-required', rejection); 48 | return deferred.promise; 49 | } 50 | // otherwise, default behaviour 51 | return $q.reject(rejection); 52 | } 53 | }; 54 | }]); 55 | }]); 56 | 57 | /** 58 | * Private module, a utility, required internally by 'http-auth-interceptor'. 59 | */ 60 | angular.module('http-auth-interceptor-buffer', []) 61 | 62 | .factory('httpBuffer', ['$injector', function($injector) { 63 | /** Holds all the requests, so they can be re-requested in future. */ 64 | var buffer = []; 65 | 66 | /** Service initialized later because of circular dependency problem. */ 67 | var $http; 68 | 69 | function retryHttpRequest(config, deferred) { 70 | function successCallback(response) { 71 | deferred.resolve(response); 72 | } 73 | 74 | function errorCallback(response) { 75 | deferred.reject(response); 76 | } 77 | $http = $http || $injector.get('$http'); 78 | $http(config).then(successCallback, errorCallback); 79 | } 80 | 81 | return { 82 | /** 83 | * Appends HTTP request configuration object with deferred response attached to buffer. 84 | */ 85 | append: function(config, deferred) { 86 | buffer.push({ 87 | config: config, 88 | deferred: deferred 89 | }); 90 | }, 91 | 92 | /** 93 | * Abandon or reject (if reason provided) all the buffered requests. 94 | */ 95 | rejectAll: function(reason) { 96 | if (reason) { 97 | for (var i = 0; i < buffer.length; ++i) { 98 | buffer[i].deferred.reject(reason); 99 | } 100 | } 101 | buffer = []; 102 | }, 103 | 104 | /** 105 | * Retries all the buffered requests clears the buffer. 106 | */ 107 | retryAll: function(updater) { 108 | for (var i = 0; i < buffer.length; ++i) { 109 | retryHttpRequest(updater(buffer[i].config), buffer[i].deferred); 110 | } 111 | buffer = []; 112 | } 113 | }; 114 | }]); 115 | })(); -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_util.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Utility Classes 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .hide { 8 | display: none; 9 | } 10 | .opacity-hide { 11 | opacity: 0; 12 | } 13 | .grade-b .opacity-hide, 14 | .grade-c .opacity-hide { 15 | opacity: 1; 16 | display: none; 17 | } 18 | .show { 19 | display: block; 20 | } 21 | .opacity-show { 22 | opacity: 1; 23 | } 24 | .invisible { 25 | visibility: hidden; 26 | } 27 | 28 | .keyboard-open .hide-on-keyboard-open { 29 | display: none; 30 | } 31 | 32 | .keyboard-open .tabs.hide-on-keyboard-open + .pane .has-tabs, 33 | .keyboard-open .bar-footer.hide-on-keyboard-open + .pane .has-footer { 34 | bottom: 0; 35 | } 36 | 37 | .inline { 38 | display: inline-block; 39 | } 40 | 41 | .disable-pointer-events { 42 | pointer-events: none; 43 | } 44 | 45 | .enable-pointer-events { 46 | pointer-events: auto; 47 | } 48 | 49 | .disable-user-behavior { 50 | // used to prevent the browser from doing its native behavior. this doesnt 51 | // prevent the scrolling, but cancels the contextmenu, tap highlighting, etc 52 | 53 | @include user-select(none); 54 | @include touch-callout(none); 55 | @include tap-highlight-transparent(); 56 | 57 | -webkit-user-drag: none; 58 | 59 | -ms-touch-action: none; 60 | -ms-content-zooming: none; 61 | } 62 | 63 | // Fill the screen to block clicks (a better pointer-events: none) for the body 64 | // to avoid full-page reflows and paints which can cause flickers 65 | .click-block { 66 | position: absolute; 67 | top: 0; 68 | left: 0; 69 | z-index: $z-index-click-block; 70 | width: 100%; 71 | height: 100%; 72 | background: transparent; 73 | } 74 | 75 | .no-resize { 76 | resize: none; 77 | } 78 | 79 | .block { 80 | display: block; 81 | clear: both; 82 | &:after { 83 | display: block; 84 | visibility: hidden; 85 | clear: both; 86 | height: 0; 87 | content: "."; 88 | } 89 | } 90 | 91 | .full-image { 92 | width: 100%; 93 | } 94 | 95 | .clearfix { 96 | *zoom: 1; 97 | &:before, 98 | &:after { 99 | display: table; 100 | content: ""; 101 | // Fixes Opera/contenteditable bug: 102 | // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 103 | line-height: 0; 104 | } 105 | &:after { 106 | clear: both; 107 | } 108 | } 109 | 110 | /** 111 | * Content Padding 112 | * -------------------------------------------------- 113 | */ 114 | 115 | .padding { 116 | padding: $content-padding; 117 | } 118 | 119 | .padding-top, 120 | .padding-vertical { 121 | padding-top: $content-padding; 122 | } 123 | 124 | .padding-right, 125 | .padding-horizontal { 126 | padding-right: $content-padding; 127 | } 128 | 129 | .padding-bottom, 130 | .padding-vertical { 131 | padding-bottom: $content-padding; 132 | } 133 | 134 | .padding-left, 135 | .padding-horizontal { 136 | padding-left: $content-padding; 137 | } 138 | 139 | 140 | /** 141 | * Rounded 142 | * -------------------------------------------------- 143 | */ 144 | 145 | .rounded { 146 | border-radius: $border-radius-base; 147 | } 148 | 149 | 150 | /** 151 | * Utility Colors 152 | * -------------------------------------------------- 153 | * Utility colors are added to help set a naming convention. You'll 154 | * notice we purposely do not use words like "red" or "blue", but 155 | * instead have colors which represent an emotion or generic theme. 156 | */ 157 | 158 | .light, a.light { 159 | color: $light; 160 | } 161 | .light-bg { 162 | background-color: $light; 163 | } 164 | .light-border { 165 | border-color: $button-light-border; 166 | } 167 | 168 | .stable, a.stable { 169 | color: $stable; 170 | } 171 | .stable-bg { 172 | background-color: $stable; 173 | } 174 | .stable-border { 175 | border-color: $button-stable-border; 176 | } 177 | 178 | .positive, a.positive { 179 | color: $positive; 180 | } 181 | .positive-bg { 182 | background-color: $positive; 183 | } 184 | .positive-border { 185 | border-color: $button-positive-border; 186 | } 187 | 188 | .calm, a.calm { 189 | color: $calm; 190 | } 191 | .calm-bg { 192 | background-color: $calm; 193 | } 194 | .calm-border { 195 | border-color: $button-calm-border; 196 | } 197 | 198 | .assertive, a.assertive { 199 | color: $assertive; 200 | } 201 | .assertive-bg { 202 | background-color: $assertive; 203 | } 204 | .assertive-border { 205 | border-color: $button-assertive-border; 206 | } 207 | 208 | .balanced, a.balanced { 209 | color: $balanced; 210 | } 211 | .balanced-bg { 212 | background-color: $balanced; 213 | } 214 | .balanced-border { 215 | border-color: $button-balanced-border; 216 | } 217 | 218 | .energized, a.energized { 219 | color: $energized; 220 | } 221 | .energized-bg { 222 | background-color: $energized; 223 | } 224 | .energized-border { 225 | border-color: $button-energized-border; 226 | } 227 | 228 | .royal, a.royal { 229 | color: $royal; 230 | } 231 | .royal-bg { 232 | background-color: $royal; 233 | } 234 | .royal-border { 235 | border-color: $button-royal-border; 236 | } 237 | 238 | .dark, a.dark { 239 | color: $dark; 240 | } 241 | .dark-bg { 242 | background-color: $dark; 243 | } 244 | .dark-border { 245 | border-color: $button-dark-border; 246 | } 247 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_form.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Forms 3 | * -------------------------------------------------- 4 | */ 5 | 6 | // Make all forms have space below them 7 | form { 8 | margin: 0 0 $line-height-base; 9 | } 10 | 11 | // Groups of fields with labels on top (legends) 12 | legend { 13 | display: block; 14 | margin-bottom: $line-height-base; 15 | padding: 0; 16 | width: 100%; 17 | border: $input-border-width solid $input-border; 18 | color: $dark; 19 | font-size: $font-size-base * 1.5; 20 | line-height: $line-height-base * 2; 21 | 22 | small { 23 | color: $stable; 24 | font-size: $line-height-base * .75; 25 | } 26 | } 27 | 28 | // Set font for forms 29 | label, 30 | input, 31 | button, 32 | select, 33 | textarea { 34 | @include font-shorthand($font-size-base, normal, $line-height-base); // Set size, weight, line-height here 35 | } 36 | input, 37 | button, 38 | select, 39 | textarea { 40 | font-family: $font-family-base; // And only set font-family here for those that need it (note the missing label element) 41 | } 42 | 43 | 44 | // Input List 45 | // ------------------------------- 46 | 47 | .item-input { 48 | @include display-flex(); 49 | @include align-items(center); 50 | position: relative; 51 | overflow: hidden; 52 | padding: 6px 0 5px 16px; 53 | 54 | input { 55 | @include border-radius(0); 56 | @include flex(1, 0, 220px); 57 | @include appearance(none); 58 | margin: 0; 59 | padding-right: 24px; 60 | background-color: transparent; 61 | } 62 | 63 | .button .icon { 64 | @include flex(0, 0, 24px); 65 | position: static; 66 | display: inline-block; 67 | height: auto; 68 | text-align: center; 69 | font-size: 16px; 70 | } 71 | 72 | .button-bar { 73 | @include border-radius(0); 74 | @include flex(1, 0, 220px); 75 | @include appearance(none); 76 | } 77 | 78 | .icon { 79 | min-width: 14px; 80 | } 81 | } 82 | 83 | .item-input-inset { 84 | @include display-flex(); 85 | @include align-items(center); 86 | position: relative; 87 | overflow: hidden; 88 | padding: ($item-padding / 3) * 2; 89 | } 90 | 91 | .item-input-wrapper { 92 | @include display-flex(); 93 | @include flex(1, 0); 94 | @include align-items(center); 95 | @include border-radius(4px); 96 | padding-right: 8px; 97 | padding-left: 8px; 98 | background: #eee; 99 | } 100 | 101 | .item-input-inset .item-input-wrapper input { 102 | padding-left: 4px; 103 | height: 29px; 104 | background: transparent; 105 | line-height: 18px; 106 | } 107 | 108 | .item-input-wrapper ~ .button { 109 | margin-left: ($item-padding / 3) * 2; 110 | } 111 | 112 | .input-label { 113 | @include flex(1, 0, 100px); 114 | display: table; 115 | padding: 7px 10px 7px 0px; 116 | max-width: 200px; 117 | width: 35%; 118 | color: $input-label-color; 119 | font-size: 16px; 120 | } 121 | 122 | .placeholder-icon { 123 | color: #aaa; 124 | &:first-child { 125 | padding-right: 6px; 126 | } 127 | &:last-child { 128 | padding-left: 6px; 129 | } 130 | } 131 | 132 | .item-stacked-label { 133 | display: block; 134 | background-color: transparent; 135 | box-shadow: none; 136 | 137 | .input-label, .icon { 138 | display: inline-block; 139 | padding: 4px 0 0 0px; 140 | vertical-align: middle; 141 | } 142 | } 143 | 144 | .item-stacked-label input, 145 | .item-stacked-label textarea { 146 | @include border-radius(2px); 147 | padding: 4px 8px 3px 0; 148 | border: none; 149 | background-color: $input-bg; 150 | } 151 | .item-stacked-label input { 152 | overflow: hidden; 153 | height: $line-height-computed + $font-size-base + 12px; 154 | } 155 | 156 | .item-floating-label { 157 | display: block; 158 | background-color: transparent; 159 | box-shadow: none; 160 | 161 | .input-label { 162 | position: relative; 163 | padding: 5px 0 0 0; 164 | opacity: 0; 165 | top: 10px; 166 | @include transition(opacity .15s ease-in, top .2s linear); 167 | 168 | &.has-input { 169 | opacity: 1; 170 | top: 0; 171 | @include transition(opacity .15s ease-in, top .2s linear); 172 | } 173 | } 174 | } 175 | 176 | 177 | // Form Controls 178 | // ------------------------------- 179 | 180 | // Shared size and type resets 181 | textarea, 182 | input[type="text"], 183 | input[type="password"], 184 | input[type="datetime"], 185 | input[type="datetime-local"], 186 | input[type="date"], 187 | input[type="month"], 188 | input[type="time"], 189 | input[type="week"], 190 | input[type="number"], 191 | input[type="email"], 192 | input[type="url"], 193 | input[type="search"], 194 | input[type="tel"], 195 | input[type="color"] { 196 | display: block; 197 | padding-top: 2px; 198 | padding-left: 0; 199 | height: $line-height-computed + $font-size-base; 200 | color: $input-color; 201 | vertical-align: middle; 202 | font-size: $font-size-base; 203 | line-height: $font-size-base + 2; 204 | } 205 | 206 | .platform-ios, 207 | .platform-android { 208 | input[type="datetime-local"], 209 | input[type="date"], 210 | input[type="month"], 211 | input[type="time"], 212 | input[type="week"] { 213 | padding-top: 8px; 214 | } 215 | } 216 | 217 | input, 218 | textarea { 219 | width: 100%; 220 | } 221 | textarea { 222 | padding-left: 0; 223 | @include placeholder($input-color-placeholder, -3px); 224 | } 225 | 226 | // Reset height since textareas have rows 227 | textarea { 228 | height: auto; 229 | } 230 | 231 | // Everything else 232 | textarea, 233 | input[type="text"], 234 | input[type="password"], 235 | input[type="datetime"], 236 | input[type="datetime-local"], 237 | input[type="date"], 238 | input[type="month"], 239 | input[type="time"], 240 | input[type="week"], 241 | input[type="number"], 242 | input[type="email"], 243 | input[type="url"], 244 | input[type="search"], 245 | input[type="tel"], 246 | input[type="color"] { 247 | border: 0; 248 | } 249 | 250 | // Position radios and checkboxes better 251 | input[type="radio"], 252 | input[type="checkbox"] { 253 | margin: 0; 254 | line-height: normal; 255 | } 256 | 257 | // Reset width of input images, buttons, radios, checkboxes 258 | input[type="file"], 259 | input[type="image"], 260 | input[type="submit"], 261 | input[type="reset"], 262 | input[type="button"], 263 | input[type="radio"], 264 | input[type="checkbox"] { 265 | width: auto; // Override of generic input selector 266 | } 267 | 268 | // Set the height of file to match text inputs 269 | input[type="file"] { 270 | line-height: $input-height-base; 271 | } 272 | 273 | // Text input classes to hide text caret during scroll 274 | .previous-input-focus, 275 | .cloned-text-input + input, 276 | .cloned-text-input + textarea { 277 | position: absolute !important; 278 | left: -9999px; 279 | width: 200px; 280 | } 281 | 282 | 283 | // Placeholder 284 | // ------------------------------- 285 | input, 286 | textarea { 287 | @include placeholder(); 288 | } 289 | 290 | 291 | // DISABLED STATE 292 | // ------------------------------- 293 | 294 | // Disabled and read-only inputs 295 | input[disabled], 296 | select[disabled], 297 | textarea[disabled], 298 | input[readonly]:not(.cloned-text-input), 299 | textarea[readonly]:not(.cloned-text-input), 300 | select[readonly] { 301 | background-color: $input-bg-disabled; 302 | cursor: not-allowed; 303 | } 304 | // Explicitly reset the colors here 305 | input[type="radio"][disabled], 306 | input[type="checkbox"][disabled], 307 | input[type="radio"][readonly], 308 | input[type="checkbox"][readonly] { 309 | background-color: transparent; 310 | } 311 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_button.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Buttons 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .button { 8 | // set the color defaults 9 | @include button-style($button-default-bg, $button-default-border, $button-default-active-bg, $button-default-active-border, $button-default-text); 10 | 11 | position: relative; 12 | display: inline-block; 13 | margin: 0; 14 | padding: 0 $button-padding; 15 | 16 | min-width: ($button-padding * 3) + $button-font-size; 17 | min-height: $button-height + 5px; 18 | 19 | border-width: $button-border-width; 20 | border-style: solid; 21 | border-radius: $button-border-radius; 22 | 23 | vertical-align: top; 24 | text-align: center; 25 | 26 | text-overflow: ellipsis; 27 | font-size: $button-font-size; 28 | line-height: $button-height - $button-border-width + 1px; 29 | 30 | cursor: pointer; 31 | 32 | &:after { 33 | // used to create a larger button "hit" area 34 | position: absolute; 35 | top: -6px; 36 | right: -6px; 37 | bottom: -6px; 38 | left: -6px; 39 | content: ' '; 40 | } 41 | 42 | .icon { 43 | vertical-align: top; 44 | pointer-events: none; 45 | } 46 | 47 | .icon:before, 48 | &.icon:before, 49 | &.icon-left:before, 50 | &.icon-right:before { 51 | display: inline-block; 52 | padding: 0 0 $button-border-width 0; 53 | vertical-align: inherit; 54 | font-size: $button-icon-size; 55 | line-height: $button-height - $button-border-width; 56 | pointer-events: none; 57 | } 58 | &.icon-left:before { 59 | float: left; 60 | padding-right: .2em; 61 | padding-left: 0; 62 | } 63 | &.icon-right:before { 64 | float: right; 65 | padding-right: 0; 66 | padding-left: .2em; 67 | } 68 | 69 | &.button-block, &.button-full { 70 | margin-top: $button-block-margin; 71 | margin-bottom: $button-block-margin; 72 | } 73 | 74 | &.button-light { 75 | @include button-style($button-light-bg, $button-light-border, $button-light-active-bg, $button-light-active-border, $button-light-text); 76 | @include button-clear($button-light-border); 77 | @include button-outline($button-light-border); 78 | } 79 | 80 | &.button-stable { 81 | @include button-style($button-stable-bg, $button-stable-border, $button-stable-active-bg, $button-stable-active-border, $button-stable-text); 82 | @include button-clear($button-stable-border); 83 | @include button-outline($button-stable-border); 84 | } 85 | 86 | &.button-positive { 87 | @include button-style($button-positive-bg, $button-positive-border, $button-positive-active-bg, $button-positive-active-border, $button-positive-text); 88 | @include button-clear($button-positive-bg); 89 | @include button-outline($button-positive-bg); 90 | } 91 | 92 | &.button-calm { 93 | @include button-style($button-calm-bg, $button-calm-border, $button-calm-active-bg, $button-calm-active-border, $button-calm-text); 94 | @include button-clear($button-calm-bg); 95 | @include button-outline($button-calm-bg); 96 | } 97 | 98 | &.button-assertive { 99 | @include button-style($button-assertive-bg, $button-assertive-border, $button-assertive-active-bg, $button-assertive-active-border, $button-assertive-text); 100 | @include button-clear($button-assertive-bg); 101 | @include button-outline($button-assertive-bg); 102 | } 103 | 104 | &.button-balanced { 105 | @include button-style($button-balanced-bg, $button-balanced-border, $button-balanced-active-bg, $button-balanced-active-border, $button-balanced-text); 106 | @include button-clear($button-balanced-bg); 107 | @include button-outline($button-balanced-bg); 108 | } 109 | 110 | &.button-energized { 111 | @include button-style($button-energized-bg, $button-energized-border, $button-energized-active-bg, $button-energized-active-border, $button-energized-text); 112 | @include button-clear($button-energized-bg); 113 | @include button-outline($button-energized-bg); 114 | } 115 | 116 | &.button-royal { 117 | @include button-style($button-royal-bg, $button-royal-border, $button-royal-active-bg, $button-royal-active-border, $button-royal-text); 118 | @include button-clear($button-royal-bg); 119 | @include button-outline($button-royal-bg); 120 | } 121 | 122 | &.button-dark { 123 | @include button-style($button-dark-bg, $button-dark-border, $button-dark-active-bg, $button-dark-active-border, $button-dark-text); 124 | @include button-clear($button-dark-bg); 125 | @include button-outline($button-dark-bg); 126 | } 127 | } 128 | 129 | .button-small { 130 | padding: 2px $button-small-padding 1px; 131 | min-width: $button-small-height; 132 | min-height: $button-small-height + 2; 133 | font-size: $button-small-font-size; 134 | line-height: $button-small-height - $button-border-width - 1; 135 | 136 | .icon:before, 137 | &.icon:before, 138 | &.icon-left:before, 139 | &.icon-right:before { 140 | font-size: $button-small-icon-size; 141 | line-height: $button-small-icon-size + 3; 142 | margin-top: 3px; 143 | } 144 | } 145 | 146 | .button-large { 147 | padding: 0 $button-large-padding; 148 | min-width: ($button-large-padding * 3) + $button-large-font-size; 149 | min-height: $button-large-height + 5; 150 | font-size: $button-large-font-size; 151 | line-height: $button-large-height - $button-border-width; 152 | 153 | .icon:before, 154 | &.icon:before, 155 | &.icon-left:before, 156 | &.icon-right:before { 157 | padding-bottom: ($button-border-width * 2); 158 | font-size: $button-large-icon-size; 159 | line-height: $button-large-height - ($button-border-width * 2) - 1; 160 | } 161 | } 162 | 163 | .button-icon { 164 | @include transition(opacity .1s); 165 | padding: 0 6px; 166 | min-width: initial; 167 | border-color: transparent; 168 | background: none; 169 | 170 | &.button.active, 171 | &.button.activated { 172 | border-color: transparent; 173 | background: none; 174 | box-shadow: none; 175 | opacity: 0.3; 176 | } 177 | 178 | .icon:before, 179 | &.icon:before { 180 | font-size: $button-large-icon-size; 181 | } 182 | } 183 | 184 | .button-clear { 185 | @include button-clear($button-default-border); 186 | @include transition(opacity .1s); 187 | padding: 0 $button-clear-padding; 188 | max-height: $button-height; 189 | border-color: transparent; 190 | background: none; 191 | box-shadow: none; 192 | 193 | &.active, 194 | &.activated { 195 | opacity: 0.3; 196 | } 197 | } 198 | 199 | .button-outline { 200 | @include button-outline($button-default-border); 201 | @include transition(opacity .1s); 202 | background: none; 203 | box-shadow: none; 204 | } 205 | 206 | .padding > .button.button-block:first-child { 207 | margin-top: 0; 208 | } 209 | 210 | .button-block { 211 | display: block; 212 | clear: both; 213 | 214 | &:after { 215 | clear: both; 216 | } 217 | } 218 | 219 | .button-full, 220 | .button-full > .button { 221 | display: block; 222 | margin-right: 0; 223 | margin-left: 0; 224 | border-right-width: 0; 225 | border-left-width: 0; 226 | border-radius: 0; 227 | } 228 | 229 | button.button-block, 230 | button.button-full, 231 | .button-full > button.button, 232 | input.button.button-block { 233 | width: 100%; 234 | } 235 | 236 | a.button { 237 | text-decoration: none; 238 | 239 | .icon:before, 240 | &.icon:before, 241 | &.icon-left:before, 242 | &.icon-right:before { 243 | margin-top: 2px; 244 | } 245 | } 246 | 247 | .button.disabled, 248 | .button[disabled] { 249 | opacity: .4; 250 | cursor: default !important; 251 | pointer-events: none; 252 | } 253 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_reset.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Resets 4 | * -------------------------------------------------- 5 | * Adapted from normalize.css and some reset.css. We don't care even one 6 | * bit about old IE, so we don't need any hacks for that in here. 7 | * 8 | * There are probably other things we could remove here, as well. 9 | * 10 | * normalize.css v2.1.2 | MIT License | git.io/normalize 11 | 12 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) 13 | * http://cssreset.com 14 | */ 15 | 16 | html, body, div, span, applet, object, iframe, 17 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 18 | a, abbr, acronym, address, big, cite, code, 19 | del, dfn, em, img, ins, kbd, q, s, samp, 20 | small, strike, strong, sub, sup, tt, var, 21 | b, i, u, center, 22 | dl, dt, dd, ol, ul, li, 23 | fieldset, form, label, legend, 24 | table, caption, tbody, tfoot, thead, tr, th, td, 25 | article, aside, canvas, details, embed, fieldset, 26 | figure, figcaption, footer, header, hgroup, 27 | menu, nav, output, ruby, section, summary, 28 | time, mark, audio, video { 29 | margin: 0; 30 | padding: 0; 31 | border: 0; 32 | vertical-align: baseline; 33 | font: inherit; 34 | font-size: 100%; 35 | } 36 | 37 | ol, ul { 38 | list-style: none; 39 | } 40 | blockquote, q { 41 | quotes: none; 42 | } 43 | blockquote:before, blockquote:after, 44 | q:before, q:after { 45 | content: ''; 46 | content: none; 47 | } 48 | 49 | /** 50 | * Prevent modern browsers from displaying `audio` without controls. 51 | * Remove excess height in iOS 5 devices. 52 | */ 53 | 54 | audio:not([controls]) { 55 | display: none; 56 | height: 0; 57 | } 58 | 59 | /** 60 | * Hide the `template` element in IE, Safari, and Firefox < 22. 61 | */ 62 | 63 | [hidden], 64 | template { 65 | display: none; 66 | } 67 | 68 | script { 69 | display: none !important; 70 | } 71 | 72 | /* ========================================================================== 73 | Base 74 | ========================================================================== */ 75 | 76 | /** 77 | * 1. Set default font family to sans-serif. 78 | * 2. Prevent iOS text size adjust after orientation change, without disabling 79 | * user zoom. 80 | */ 81 | 82 | html { 83 | @include user-select(none); 84 | font-family: sans-serif; /* 1 */ 85 | -webkit-text-size-adjust: 100%; 86 | -ms-text-size-adjust: 100%; /* 2 */ 87 | -webkit-text-size-adjust: 100%; /* 2 */ 88 | } 89 | 90 | /** 91 | * Remove default margin. 92 | */ 93 | 94 | body { 95 | margin: 0; 96 | line-height: 1; 97 | } 98 | 99 | 100 | /** 101 | * Remove default outlines. 102 | */ 103 | a, 104 | button, 105 | :focus, 106 | a:focus, 107 | button:focus, 108 | a:active, 109 | a:hover { 110 | outline: 0; 111 | } 112 | 113 | /* * 114 | * Remove tap highlight color 115 | */ 116 | 117 | a { 118 | -webkit-user-drag: none; 119 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 120 | -webkit-tap-highlight-color: transparent; 121 | 122 | &[href]:hover { 123 | cursor: pointer; 124 | } 125 | } 126 | 127 | /* ========================================================================== 128 | Typography 129 | ========================================================================== */ 130 | 131 | 132 | /** 133 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 134 | */ 135 | 136 | b, 137 | strong { 138 | font-weight: bold; 139 | } 140 | 141 | /** 142 | * Address styling not present in Safari 5 and Chrome. 143 | */ 144 | 145 | dfn { 146 | font-style: italic; 147 | } 148 | 149 | /** 150 | * Address differences between Firefox and other browsers. 151 | */ 152 | 153 | hr { 154 | -moz-box-sizing: content-box; 155 | box-sizing: content-box; 156 | height: 0; 157 | } 158 | 159 | 160 | /** 161 | * Correct font family set oddly in Safari 5 and Chrome. 162 | */ 163 | 164 | code, 165 | kbd, 166 | pre, 167 | samp { 168 | font-size: 1em; 169 | font-family: monospace, serif; 170 | } 171 | 172 | /** 173 | * Improve readability of pre-formatted text in all browsers. 174 | */ 175 | 176 | pre { 177 | white-space: pre-wrap; 178 | } 179 | 180 | /** 181 | * Set consistent quote types. 182 | */ 183 | 184 | q { 185 | quotes: "\201C" "\201D" "\2018" "\2019"; 186 | } 187 | 188 | /** 189 | * Address inconsistent and variable font size in all browsers. 190 | */ 191 | 192 | small { 193 | font-size: 80%; 194 | } 195 | 196 | /** 197 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 198 | */ 199 | 200 | sub, 201 | sup { 202 | position: relative; 203 | vertical-align: baseline; 204 | font-size: 75%; 205 | line-height: 0; 206 | } 207 | 208 | sup { 209 | top: -0.5em; 210 | } 211 | 212 | sub { 213 | bottom: -0.25em; 214 | } 215 | 216 | /** 217 | * Define consistent border, margin, and padding. 218 | */ 219 | 220 | fieldset { 221 | margin: 0 2px; 222 | padding: 0.35em 0.625em 0.75em; 223 | border: 1px solid #c0c0c0; 224 | } 225 | 226 | /** 227 | * 1. Correct `color` not being inherited in IE 8/9. 228 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 229 | */ 230 | 231 | legend { 232 | padding: 0; /* 2 */ 233 | border: 0; /* 1 */ 234 | } 235 | 236 | /** 237 | * 1. Correct font family not being inherited in all browsers. 238 | * 2. Correct font size not being inherited in all browsers. 239 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 240 | * 4. Remove any default :focus styles 241 | * 5. Make sure webkit font smoothing is being inherited 242 | * 6. Remove default gradient in Android Firefox / FirefoxOS 243 | */ 244 | 245 | button, 246 | input, 247 | select, 248 | textarea { 249 | margin: 0; /* 3 */ 250 | font-size: 100%; /* 2 */ 251 | font-family: inherit; /* 1 */ 252 | outline-offset: 0; /* 4 */ 253 | outline-style: none; /* 4 */ 254 | outline-width: 0; /* 4 */ 255 | -webkit-font-smoothing: inherit; /* 5 */ 256 | background-image: none; /* 6 */ 257 | } 258 | 259 | /** 260 | * Address Firefox 4+ setting `line-height` on `input` using `importnt` in 261 | * the UA stylesheet. 262 | */ 263 | 264 | button, 265 | input { 266 | line-height: normal; 267 | } 268 | 269 | /** 270 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 271 | * All other form control elements do not inherit `text-transform` values. 272 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 273 | * Correct `select` style inheritance in Firefox 4+ and Opera. 274 | */ 275 | 276 | button, 277 | select { 278 | text-transform: none; 279 | } 280 | 281 | /** 282 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 283 | * and `video` controls. 284 | * 2. Correct inability to style clickable `input` types in iOS. 285 | * 3. Improve usability and consistency of cursor style between image-type 286 | * `input` and others. 287 | */ 288 | 289 | button, 290 | html input[type="button"], /* 1 */ 291 | input[type="reset"], 292 | input[type="submit"] { 293 | cursor: pointer; /* 3 */ 294 | -webkit-appearance: button; /* 2 */ 295 | } 296 | 297 | /** 298 | * Re-set default cursor for disabled elements. 299 | */ 300 | 301 | button[disabled], 302 | html input[disabled] { 303 | cursor: default; 304 | } 305 | 306 | /** 307 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 308 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 309 | * (include `-moz` to future-proof). 310 | */ 311 | 312 | input[type="search"] { 313 | -webkit-box-sizing: content-box; /* 2 */ 314 | -moz-box-sizing: content-box; 315 | box-sizing: content-box; 316 | -webkit-appearance: textfield; /* 1 */ 317 | } 318 | 319 | /** 320 | * Remove inner padding and search cancel button in Safari 5 and Chrome 321 | * on OS X. 322 | */ 323 | 324 | input[type="search"]::-webkit-search-cancel-button, 325 | input[type="search"]::-webkit-search-decoration { 326 | -webkit-appearance: none; 327 | } 328 | 329 | /** 330 | * Remove inner padding and border in Firefox 4+. 331 | */ 332 | 333 | button::-moz-focus-inner, 334 | input::-moz-focus-inner { 335 | padding: 0; 336 | border: 0; 337 | } 338 | 339 | /** 340 | * 1. Remove default vertical scrollbar in IE 8/9. 341 | * 2. Improve readability and alignment in all browsers. 342 | */ 343 | 344 | textarea { 345 | overflow: auto; /* 1 */ 346 | vertical-align: top; /* 2 */ 347 | } 348 | 349 | 350 | img { 351 | -webkit-user-drag: none; 352 | } 353 | 354 | /* ========================================================================== 355 | Tables 356 | ========================================================================== */ 357 | 358 | /** 359 | * Remove most spacing between table cells. 360 | */ 361 | 362 | table { 363 | border-spacing: 0; 364 | border-collapse: collapse; 365 | } 366 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_scaffolding.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Scaffolding 4 | * -------------------------------------------------- 5 | */ 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | @include box-sizing(border-box); 11 | } 12 | 13 | html { 14 | overflow: hidden; 15 | -ms-touch-action: pan-y; 16 | touch-action: pan-y; 17 | } 18 | 19 | body, 20 | .ionic-body { 21 | @include touch-callout(none); 22 | @include font-smoothing(antialiased); 23 | @include text-size-adjust(none); 24 | @include tap-highlight-transparent(); 25 | @include user-select(none); 26 | 27 | top: 0; 28 | right: 0; 29 | bottom: 0; 30 | left: 0; 31 | overflow: hidden; 32 | 33 | margin: 0; 34 | padding: 0; 35 | 36 | color: $base-color; 37 | word-wrap: break-word; 38 | font-size: $font-size-base; 39 | font-family: $font-family-base; 40 | line-height: $line-height-computed; 41 | text-rendering: optimizeLegibility; 42 | -webkit-backface-visibility: hidden; 43 | -webkit-user-drag: none; 44 | } 45 | 46 | body.grade-b, 47 | body.grade-c { 48 | // disable optimizeLegibility for low end devices 49 | text-rendering: auto; 50 | } 51 | 52 | .content { 53 | // used for content areas not using the content directive 54 | position: relative; 55 | } 56 | 57 | .scroll-content { 58 | position: absolute; 59 | top: 0; 60 | right: 0; 61 | bottom: 0; 62 | left: 0; 63 | overflow: hidden; 64 | 65 | // Hide the top border if any 66 | margin-top: -1px; 67 | 68 | // Prevents any distortion of lines 69 | padding-top:1px; 70 | 71 | width: auto; 72 | height: auto; 73 | } 74 | 75 | .scroll-content-false, 76 | .menu .scroll-content.scroll-content-false{ 77 | z-index: $z-index-scroll-content-false; 78 | } 79 | 80 | .scroll-view { 81 | position: relative; 82 | display: block; 83 | overflow: hidden; 84 | 85 | // Hide the top border if any 86 | margin-top: -1px; 87 | } 88 | 89 | /** 90 | * Scroll is the scroll view component available for complex and custom 91 | * scroll view functionality. 92 | */ 93 | .scroll { 94 | @include user-select(none); 95 | @include touch-callout(none); 96 | @include text-size-adjust(none); 97 | @include transform-origin(left, top); 98 | } 99 | 100 | // hide webkit scrollbars 101 | ::-webkit-scrollbar { 102 | display:none; 103 | } 104 | 105 | // Scroll bar styles 106 | .scroll-bar { 107 | position: absolute; 108 | z-index: $z-index-scroll-bar; 109 | } 110 | // hide the scroll-bar during animations 111 | .ng-animate .scroll-bar { 112 | visibility: hidden; 113 | } 114 | .scroll-bar-h { 115 | right: 2px; 116 | bottom: 3px; 117 | left: 2px; 118 | height: 3px; 119 | 120 | .scroll-bar-indicator { 121 | height: 100%; 122 | } 123 | } 124 | 125 | .scroll-bar-v { 126 | top: 2px; 127 | right: 3px; 128 | bottom: 2px; 129 | width: 3px; 130 | 131 | .scroll-bar-indicator { 132 | width: 100%; 133 | } 134 | } 135 | .scroll-bar-indicator { 136 | position: absolute; 137 | border-radius: 4px; 138 | background: rgba(0,0,0,0.3); 139 | opacity: 1; 140 | @include transition(opacity .3s linear); 141 | 142 | &.scroll-bar-fade-out { 143 | opacity: 0; 144 | } 145 | } 146 | .grade-b .scroll-bar-indicator, 147 | .grade-c .scroll-bar-indicator { 148 | // disable rgba background and border radius for low end devices 149 | border-radius: 0; 150 | background: #aaa; 151 | 152 | &.scroll-bar-fade-out { 153 | @include transition(none); 154 | } 155 | } 156 | 157 | @keyframes refresh-spin { 158 | 0% { transform: translate3d(0,0,0) rotate(0); } 159 | 100% { transform: translate3d(0,0,0) rotate(180deg); } 160 | } 161 | 162 | @-webkit-keyframes refresh-spin { 163 | 0% {-webkit-transform: translate3d(0,0,0) rotate(0); } 164 | 100% {-webkit-transform: translate3d(0,0,0) rotate(180deg); } 165 | } 166 | 167 | @keyframes refresh-spin-back { 168 | 0% { transform: translate3d(0,0,0) rotate(180deg); } 169 | 100% { transform: translate3d(0,0,0) rotate(0); } 170 | } 171 | 172 | @-webkit-keyframes refresh-spin-back { 173 | 0% {-webkit-transform: translate3d(0,0,0) rotate(180deg); } 174 | 100% {-webkit-transform: translate3d(0,0,0) rotate(0); } 175 | } 176 | 177 | // Scroll refresher (for pull to refresh) 178 | .scroll-refresher { 179 | position: absolute; 180 | top: -60px; 181 | right: 0; 182 | left: 0; 183 | overflow: hidden; 184 | margin: auto; 185 | height: 60px; 186 | 187 | .ionic-refresher-content { 188 | position: absolute; 189 | bottom: 15px; 190 | left: 0; 191 | width: 100%; 192 | color: $scroll-refresh-icon-color; 193 | text-align: center; 194 | 195 | font-size: 30px; 196 | 197 | .text-refreshing, 198 | .text-pulling { 199 | font-size: 16px; 200 | line-height: 16px; 201 | } 202 | &.ionic-refresher-with-text { 203 | bottom: 10px; 204 | } 205 | } 206 | 207 | .icon-refreshing, 208 | .icon-pulling { 209 | width: 100%; 210 | -webkit-backface-visibility: hidden; 211 | -webkit-transform-style: preserve-3d; 212 | backface-visibility: hidden; 213 | transform-style: preserve-3d; 214 | } 215 | .icon-pulling { 216 | @include animation-name(refresh-spin-back); 217 | @include animation-duration(200ms); 218 | @include animation-timing-function(linear); 219 | @include animation-fill-mode(none); 220 | -webkit-transform: translate3d(0,0,0) rotate(0deg); 221 | transform: translate3d(0,0,0) rotate(0deg); 222 | } 223 | .icon-refreshing, 224 | .text-refreshing { 225 | display: none; 226 | } 227 | .icon-refreshing { 228 | @include animation-duration(1.5s); 229 | } 230 | 231 | &.active { 232 | .icon-pulling:not(.pulling-rotation-disabled) { 233 | @include animation-name(refresh-spin); 234 | -webkit-transform: translate3d(0,0,0) rotate(-180deg); 235 | transform: translate3d(0,0,0) rotate(-180deg); 236 | } 237 | &.refreshing { 238 | @include transition(transform .2s); 239 | @include transition(-webkit-transform .2s); 240 | -webkit-transform: scale(1,1); 241 | transform: scale(1,1); 242 | .icon-pulling, 243 | .text-pulling { 244 | display: none; 245 | } 246 | .icon-refreshing, 247 | .text-refreshing { 248 | display: block; 249 | } 250 | &.refreshing-tail{ 251 | -webkit-transform: scale(0,0); 252 | transform: scale(0,0); 253 | } 254 | } 255 | } 256 | } 257 | 258 | ion-infinite-scroll { 259 | height: 60px; 260 | width: 100%; 261 | opacity: 0; 262 | display: block; 263 | 264 | @include transition(opacity 0.25s); 265 | @include display-flex(); 266 | @include flex-direction(row); 267 | @include justify-content(center); 268 | @include align-items(center); 269 | 270 | .icon { 271 | color: #666666; 272 | font-size: 30px; 273 | color: $scroll-refresh-icon-color; 274 | } 275 | 276 | &.active { 277 | opacity: 1; 278 | } 279 | } 280 | 281 | .overflow-scroll { 282 | overflow-x: hidden; 283 | overflow-y: scroll; 284 | -webkit-overflow-scrolling: touch; 285 | top: 0; 286 | right: 0; 287 | bottom: 0; 288 | left: 0; 289 | position: absolute; 290 | 291 | .scroll { 292 | position: static; 293 | height: 100%; 294 | -webkit-transform: translate3d(0, 0, 0); // fix iOS bug where relative children of scroller disapear while scrolling. see: http://stackoverflow.com/questions/9807620/ipad-safari-scrolling-causes-html-elements-to-disappear-and-reappear-with-a-dela 295 | } 296 | } 297 | 298 | 299 | // Pad top/bottom of content so it doesn't hide behind .bar-title and .bar-tab. 300 | // Note: For these to work, content must come after both bars in the markup 301 | /* If you change these, change platform.scss as well */ 302 | .has-header { 303 | top: $bar-height; 304 | } 305 | // Force no header 306 | .no-header { 307 | top: 0; 308 | } 309 | 310 | .has-subheader { 311 | top: $bar-height * 2; 312 | } 313 | .has-tabs-top { 314 | top: $bar-height + $tabs-height; 315 | } 316 | .has-header.has-subheader.has-tabs-top { 317 | top: 2 * $bar-height + $tabs-height; 318 | } 319 | 320 | .has-footer { 321 | bottom: $bar-height; 322 | } 323 | .has-subfooter { 324 | bottom: $bar-height * 2; 325 | } 326 | 327 | .has-tabs, 328 | .bar-footer.has-tabs { 329 | bottom: $tabs-height; 330 | } 331 | 332 | .has-footer.has-tabs { 333 | bottom: $tabs-height + $bar-height; 334 | } 335 | 336 | // A full screen section with a solid background 337 | .pane { 338 | @include translate3d(0,0,0); 339 | z-index: $z-index-pane; 340 | } 341 | .view { 342 | z-index: $z-index-view; 343 | } 344 | .pane, 345 | .view { 346 | position: absolute; 347 | top: 0; 348 | right: 0; 349 | bottom: 0; 350 | left: 0; 351 | width: 100%; 352 | height: 100%; 353 | background-color: $base-background-color; 354 | overflow: hidden; 355 | } 356 | 357 | ion-nav-view { 358 | position: absolute; 359 | top: 0; 360 | left: 0; 361 | width: 100%; 362 | height: 100%; 363 | background-color: #000; 364 | } 365 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_bar.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Bar (Headers and Footers) 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .bar { 8 | @include display-flex(); 9 | @include translate3d(0,0,0); 10 | @include user-select(none); 11 | position: absolute; 12 | right: 0; 13 | left: 0; 14 | z-index: $z-index-bar; 15 | 16 | box-sizing: border-box; 17 | padding: $bar-padding-portrait; 18 | 19 | width: 100%; 20 | height: $bar-height; 21 | border-width: 0; 22 | border-style: solid; 23 | border-top: 1px solid transparent; 24 | border-bottom: 1px solid $bar-default-border; 25 | 26 | background-color: $bar-default-bg; 27 | 28 | /* border-width: 1px will actually create 2 device pixels on retina */ 29 | /* this nifty trick sets an actual 1px border on hi-res displays */ 30 | background-size: 0; 31 | @media (min--moz-device-pixel-ratio: 1.5), 32 | (-webkit-min-device-pixel-ratio: 1.5), 33 | (min-device-pixel-ratio: 1.5), 34 | (min-resolution: 144dpi), 35 | (min-resolution: 1.5dppx) { 36 | border: none; 37 | background-image: linear-gradient(0deg, $bar-default-border, $bar-default-border 50%, transparent 50%); 38 | background-position: bottom; 39 | background-size: 100% 1px; 40 | background-repeat: no-repeat; 41 | } 42 | 43 | &.bar-clear { 44 | border: none; 45 | background: none; 46 | color: #fff; 47 | 48 | .button { 49 | color: #fff; 50 | } 51 | .title { 52 | color: #fff; 53 | } 54 | } 55 | 56 | &.item-input-inset { 57 | .item-input-wrapper { 58 | margin-top: -1px; 59 | 60 | input { 61 | padding-left: 8px; 62 | width: 94%; 63 | height: 28px; 64 | background: transparent; 65 | } 66 | } 67 | } 68 | 69 | &.bar-light { 70 | @include bar-style($bar-light-bg, $bar-light-border, $bar-light-text); 71 | &.bar-footer{ 72 | background-image: linear-gradient(180deg, $bar-light-border, $bar-light-border 50%, transparent 50%); 73 | } 74 | } 75 | &.bar-stable { 76 | @include bar-style($bar-stable-bg, $bar-stable-border, $bar-stable-text); 77 | &.bar-footer{ 78 | background-image: linear-gradient(180deg, $bar-stable-border, $bar-stable-border 50%, transparent 50%); 79 | } 80 | } 81 | &.bar-positive { 82 | @include bar-style($bar-positive-bg, $bar-positive-border, $bar-positive-text); 83 | &.bar-footer{ 84 | background-image: linear-gradient(180deg, $bar-positive-border, $bar-positive-border 50%, transparent 50%); 85 | } 86 | } 87 | &.bar-calm { 88 | @include bar-style($bar-calm-bg, $bar-calm-border, $bar-calm-text); 89 | &.bar-footer{ 90 | background-image: linear-gradient(180deg, $bar-calm-border, $bar-calm-border 50%, transparent 50%); 91 | } 92 | } 93 | &.bar-assertive { 94 | @include bar-style($bar-assertive-bg, $bar-assertive-border, $bar-assertive-text); 95 | &.bar-footer{ 96 | background-image: linear-gradient(180deg, $bar-assertive-border, $bar-assertive-border 50%, transparent 50%); 97 | } 98 | } 99 | &.bar-balanced { 100 | @include bar-style($bar-balanced-bg, $bar-balanced-border, $bar-balanced-text); 101 | &.bar-footer{ 102 | background-image: linear-gradient(180deg, $bar-balanced-border, $bar-positive-border 50%, transparent 50%); 103 | } 104 | } 105 | &.bar-energized { 106 | @include bar-style($bar-energized-bg, $bar-energized-border, $bar-energized-text); 107 | &.bar-footer{ 108 | background-image: linear-gradient(180deg, $bar-energized-border, $bar-energized-border 50%, transparent 50%); 109 | } 110 | } 111 | &.bar-royal { 112 | @include bar-style($bar-royal-bg, $bar-royal-border, $bar-royal-text); 113 | &.bar-footer{ 114 | background-image: linear-gradient(180deg, $bar-royal-border, $bar-royal-border 50%, transparent 50%); 115 | } 116 | } 117 | &.bar-dark { 118 | @include bar-style($bar-dark-bg, $bar-dark-border, $bar-dark-text); 119 | &.bar-footer{ 120 | background-image: linear-gradient(180deg, $bar-dark-border, $bar-dark-border 50%, transparent 50%); 121 | } 122 | } 123 | 124 | // Title inside of a bar is centered 125 | .title { 126 | position: absolute; 127 | 128 | top: 0; 129 | right: 0; 130 | left: 0; 131 | z-index: $z-index-bar-title; 132 | overflow: hidden; 133 | 134 | margin: 0 10px; 135 | 136 | min-width: 30px; 137 | height: $bar-height - 1; 138 | 139 | text-align: center; 140 | 141 | // Go into ellipsis if too small 142 | text-overflow: ellipsis; 143 | white-space: nowrap; 144 | 145 | font-size: $bar-title-font-size; 146 | 147 | line-height: $bar-height; 148 | 149 | &.title-left { 150 | text-align: left; 151 | } 152 | &.title-right { 153 | text-align: right; 154 | } 155 | } 156 | 157 | .title a { 158 | color: inherit; 159 | } 160 | 161 | .button { 162 | z-index: $z-index-bar-button; 163 | padding: 0 $button-bar-button-padding; 164 | min-width: initial; 165 | min-height: $button-bar-button-height - 1; 166 | font-weight: 400; 167 | font-size: $button-bar-button-font-size; 168 | line-height: $button-bar-button-height; 169 | 170 | &.button-icon:before, 171 | .icon:before, 172 | &.icon:before, 173 | &.icon-left:before, 174 | &.icon-right:before { 175 | padding-right: 2px; 176 | padding-left: 2px; 177 | font-size: $button-bar-button-icon-size; 178 | line-height: $button-bar-button-height; 179 | } 180 | 181 | &.button-icon { 182 | font-size: $bar-title-font-size; 183 | .icon:before, 184 | &:before, 185 | &.icon-left:before, 186 | &.icon-right:before { 187 | vertical-align: top; 188 | font-size: $button-large-icon-size; 189 | line-height: $button-bar-button-height; 190 | } 191 | } 192 | &.button-clear { 193 | padding-right: 2px; 194 | padding-left: 2px; 195 | font-weight: 300; 196 | font-size: $bar-title-font-size; 197 | 198 | .icon:before, 199 | &.icon:before, 200 | &.icon-left:before, 201 | &.icon-right:before { 202 | font-size: $button-large-icon-size; 203 | line-height: $button-bar-button-height; 204 | } 205 | } 206 | 207 | &.back-button { 208 | padding: 0; 209 | opacity: 0.8; 210 | .back-button-title { 211 | display: inline-block; 212 | vertical-align: middle; 213 | margin-left: 4px; 214 | } 215 | } 216 | 217 | &.back-button.active, 218 | &.back-button.activated { 219 | opacity: 1; 220 | } 221 | } 222 | 223 | .button-bar > .button, 224 | .buttons > .button { 225 | min-height: $button-bar-button-height - 1; 226 | line-height: $button-bar-button-height; 227 | } 228 | 229 | .button-bar + .button, 230 | .button + .button-bar { 231 | margin-left: 5px; 232 | } 233 | 234 | // Android 4.4 messes with the display property 235 | .buttons, 236 | .buttons.left-buttons, 237 | .buttons.right-buttons { 238 | display: inherit; 239 | } 240 | .buttons span { 241 | display: inline-flex; 242 | } 243 | 244 | // Place the last button in a bar on the right of the bar 245 | .title + .button:last-child, 246 | > .button + .button:last-child, 247 | > .button.pull-right, 248 | .buttons.pull-right, 249 | .title + .buttons { 250 | position: absolute; 251 | top: 5px; 252 | right: 5px; 253 | bottom: 5px; 254 | } 255 | 256 | } 257 | 258 | // Default styles for buttons inside of styled bars 259 | .bar-light { 260 | .button { 261 | @include button-style($bar-light-bg, $bar-light-border, $bar-light-active-bg, $bar-light-active-border, $bar-light-text); 262 | @include button-clear($bar-light-text, $bar-title-font-size); 263 | } 264 | } 265 | .bar-stable { 266 | .button { 267 | @include button-style($bar-stable-bg, $bar-stable-border, $bar-stable-active-bg, $bar-stable-active-border, $bar-stable-text); 268 | @include button-clear($bar-stable-text, $bar-title-font-size); 269 | } 270 | } 271 | .bar-positive { 272 | .button { 273 | @include button-style($bar-positive-bg, $bar-positive-border, $bar-positive-active-bg, $bar-positive-active-border, $bar-positive-text); 274 | @include button-clear(#fff, $bar-title-font-size); 275 | } 276 | } 277 | .bar-calm { 278 | .button { 279 | @include button-style($bar-calm-bg, $bar-calm-border, $bar-calm-active-bg, $bar-calm-active-border, $bar-calm-text); 280 | @include button-clear(#fff, $bar-title-font-size); 281 | } 282 | } 283 | .bar-assertive { 284 | .button { 285 | @include button-style($bar-assertive-bg, $bar-assertive-border, $bar-assertive-active-bg, $bar-assertive-active-border, $bar-assertive-text); 286 | @include button-clear(#fff, $bar-title-font-size); 287 | } 288 | } 289 | .bar-balanced { 290 | .button { 291 | @include button-style($bar-balanced-bg, $bar-balanced-border, $bar-balanced-active-bg, $bar-balanced-active-border, $bar-balanced-text); 292 | @include button-clear(#fff, $bar-title-font-size); 293 | } 294 | } 295 | .bar-energized { 296 | .button { 297 | @include button-style($bar-energized-bg, $bar-energized-border, $bar-energized-active-bg, $bar-energized-active-border, $bar-energized-text); 298 | @include button-clear(#fff, $bar-title-font-size); 299 | } 300 | } 301 | .bar-royal { 302 | .button { 303 | @include button-style($bar-royal-bg, $bar-royal-border, $bar-royal-active-bg, $bar-royal-active-border, $bar-royal-text); 304 | @include button-clear(#fff, $bar-title-font-size); 305 | } 306 | } 307 | .bar-dark { 308 | .button { 309 | @include button-style($bar-dark-bg, $bar-dark-border, $bar-dark-active-bg, $bar-dark-active-border, $bar-dark-text); 310 | @include button-clear(#fff, $bar-title-font-size); 311 | } 312 | } 313 | 314 | // Header at top 315 | .bar-header { 316 | top: 0; 317 | border-top-width: 0; 318 | border-bottom-width: 1px; 319 | &.has-tabs-top{ 320 | border-bottom-width: 0px; 321 | } 322 | } 323 | 324 | // Footer at bottom 325 | .bar-footer { 326 | bottom: 0; 327 | border-top-width: 1px; 328 | border-bottom-width: 0; 329 | background-position: top; 330 | 331 | &.item-input-inset { 332 | position: absolute; 333 | } 334 | } 335 | 336 | // Don't render padding if the bar is just for tabs 337 | .bar-tabs { 338 | padding: 0; 339 | } 340 | 341 | .bar-subheader { 342 | top: $bar-height; 343 | display: block; 344 | } 345 | .bar-subfooter { 346 | bottom: $bar-height; 347 | display: block; 348 | } 349 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/js/angular/angular-animate.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.25 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(F,e,O){'use strict';e.module("ngAnimate",["ng"]).directive("ngAnimateChildren",function(){return function(G,s,g){g=g.ngAnimateChildren;e.isString(g)&&0===g.length?s.data("$$ngAnimateChildren",!0):G.$watch(g,function(e){s.data("$$ngAnimateChildren",!!e)})}}).factory("$$animateReflow",["$$rAF","$document",function(e,s){return function(g){return e(function(){g()})}}]).config(["$provide","$animateProvider",function(G,s){function g(e){for(var g=0;g=y&&b>=v&&e()}var h=g(b);a=b.data(q);if(-1!=h.getAttribute("class").indexOf(d)&&a){var m="";u(d.split(" "),function(a,b){m+=(0 .tabs, 46 | .tabs.tabs-light { 47 | @include tab-style($tabs-light-bg, $tabs-light-border, $tabs-light-text); 48 | @include tab-badge-style($tabs-light-text, $tabs-light-bg); 49 | } 50 | .tabs-stable > .tabs, 51 | .tabs.tabs-stable { 52 | @include tab-style($tabs-stable-bg, $tabs-stable-border, $tabs-stable-text); 53 | @include tab-badge-style($tabs-stable-text, $tabs-stable-bg); 54 | } 55 | .tabs-positive > .tabs, 56 | .tabs.tabs-positive { 57 | @include tab-style($tabs-positive-bg, $tabs-positive-border, $tabs-positive-text); 58 | @include tab-badge-style($tabs-positive-text, $tabs-positive-bg); 59 | } 60 | .tabs-calm > .tabs, 61 | .tabs.tabs-calm { 62 | @include tab-style($tabs-calm-bg, $tabs-calm-border, $tabs-calm-text); 63 | @include tab-badge-style($tabs-calm-text, $tabs-calm-bg); 64 | } 65 | .tabs-assertive > .tabs, 66 | .tabs.tabs-assertive { 67 | @include tab-style($tabs-assertive-bg, $tabs-assertive-border, $tabs-assertive-text); 68 | @include tab-badge-style($tabs-assertive-text, $tabs-assertive-bg); 69 | } 70 | .tabs-balanced > .tabs, 71 | .tabs.tabs-balanced { 72 | @include tab-style($tabs-balanced-bg, $tabs-balanced-border, $tabs-balanced-text); 73 | @include tab-badge-style($tabs-balanced-text, $tabs-balanced-bg); 74 | } 75 | .tabs-energized > .tabs, 76 | .tabs.tabs-energized { 77 | @include tab-style($tabs-energized-bg, $tabs-energized-border, $tabs-energized-text); 78 | @include tab-badge-style($tabs-energized-text, $tabs-energized-bg); 79 | } 80 | .tabs-royal > .tabs, 81 | .tabs.tabs-royal { 82 | @include tab-style($tabs-royal-bg, $tabs-royal-border, $tabs-royal-text); 83 | @include tab-badge-style($tabs-royal-text, $tabs-royal-bg); 84 | } 85 | .tabs-dark > .tabs, 86 | .tabs.tabs-dark { 87 | @include tab-style($tabs-dark-bg, $tabs-dark-border, $tabs-dark-text); 88 | @include tab-badge-style($tabs-dark-text, $tabs-dark-bg); 89 | } 90 | 91 | @mixin tabs-striped($style, $color, $background) { 92 | &.#{$style} { 93 | .tabs{ 94 | background-color: $background; 95 | } 96 | .tab-item { 97 | color: rgba($color, $tabs-striped-off-opacity); 98 | opacity: 1; 99 | .badge{ 100 | opacity:$tabs-striped-off-opacity; 101 | } 102 | &.tab-item-active, 103 | &.active, 104 | &.activated { 105 | margin-top: -$tabs-striped-border-width; 106 | color: $color; 107 | border-style: solid; 108 | border-width: $tabs-striped-border-width 0 0 0; 109 | border-color: $color; 110 | .badge{ 111 | top:$tabs-striped-border-width; 112 | opacity: 1; 113 | } 114 | } 115 | } 116 | } 117 | &.tabs-top{ 118 | .tab-item { 119 | &.tab-item-active, 120 | &.active, 121 | &.activated { 122 | .badge { 123 | top: 4%; 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | @mixin tabs-background($style, $color) { 131 | &.#{$style} { 132 | .tabs { 133 | background-color: $color; 134 | } 135 | } 136 | } 137 | 138 | @mixin tabs-color($style, $color) { 139 | &.#{$style} { 140 | .tab-item { 141 | color: rgba($color, $tabs-striped-off-opacity); 142 | opacity: 1; 143 | .badge{ 144 | opacity:$tabs-striped-off-opacity; 145 | } 146 | &.tab-item-active, 147 | &.active, 148 | &.activated { 149 | margin-top: -$tabs-striped-border-width; 150 | color: $color; 151 | border: 0 solid $color; 152 | border-top-width: $tabs-striped-border-width; 153 | .badge{ 154 | top:$tabs-striped-border-width; 155 | opacity: 1; 156 | } 157 | } 158 | } 159 | } 160 | } 161 | 162 | .tabs-striped { 163 | .tabs { 164 | background-color: white; 165 | background-image: none; 166 | border: none; 167 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.15); 168 | padding-top: $tabs-striped-border-width; 169 | } 170 | .tab-item { 171 | // default android tab style 172 | &.tab-item-active, 173 | &.active, 174 | &.activated { 175 | margin-top: -$tabs-striped-border-width; 176 | border-style: solid; 177 | border-width: $tabs-striped-border-width 0 0 0; 178 | border-color: $dark; 179 | } 180 | } 181 | @include tabs-striped('tabs-light', $light, $dark); 182 | @include tabs-striped('tabs-stable', $stable, $dark); 183 | @include tabs-striped('tabs-positive', $positive, $light); 184 | @include tabs-striped('tabs-calm', $calm, $light); 185 | @include tabs-striped('tabs-assertive', $assertive, $light); 186 | @include tabs-striped('tabs-balanced', $balanced, $light); 187 | @include tabs-striped('tabs-energized', $energized, $light); 188 | @include tabs-striped('tabs-royal', $royal, $light); 189 | @include tabs-striped('tabs-dark', $dark, $light); 190 | 191 | 192 | @include tabs-background('tabs-background-light', $light); 193 | @include tabs-background('tabs-background-stable', $stable); 194 | @include tabs-background('tabs-background-positive', $positive); 195 | @include tabs-background('tabs-background-calm', $calm); 196 | @include tabs-background('tabs-background-assertive', $assertive); 197 | @include tabs-background('tabs-background-balanced', $balanced); 198 | @include tabs-background('tabs-background-energized',$energized); 199 | @include tabs-background('tabs-background-royal', $royal); 200 | @include tabs-background('tabs-background-dark', $dark); 201 | 202 | @include tabs-color('tabs-color-light', $light); 203 | @include tabs-color('tabs-color-stable', $stable); 204 | @include tabs-color('tabs-color-positive', $positive); 205 | @include tabs-color('tabs-color-calm', $calm); 206 | @include tabs-color('tabs-color-assertive', $assertive); 207 | @include tabs-color('tabs-color-balanced', $balanced); 208 | @include tabs-color('tabs-color-energized',$energized); 209 | @include tabs-color('tabs-color-royal', $royal); 210 | @include tabs-color('tabs-color-dark', $dark); 211 | } 212 | 213 | .tabs-top { 214 | &.tabs-striped { 215 | padding-bottom:0; 216 | .tab-item{ 217 | background: transparent; 218 | // animate the top bar, leave bottom for platform consistency 219 | -webkit-transition: all .1s ease; 220 | -moz-transition: all .1s ease; 221 | -ms-transition: all .1s ease; 222 | -o-transition: all .1s ease; 223 | transition: all .1s ease; 224 | &.tab-item-active, 225 | &.active, 226 | &.activated { 227 | margin-top: 0; 228 | margin-bottom: -$tabs-striped-border-width; 229 | border-width: 0px 0px $tabs-striped-border-width 0px !important; 230 | border-style: solid; 231 | } 232 | .badge{ 233 | -webkit-transition: all .2s ease; 234 | -moz-transition: all .2s ease; 235 | -ms-transition: all .2s ease; 236 | -o-transition: all .2s ease; 237 | transition: all .2s ease; 238 | } 239 | } 240 | } 241 | } 242 | 243 | /* Allow parent element to have tabs-top */ 244 | /* If you change this, change platform.scss as well */ 245 | .tabs-top > .tabs, 246 | .tabs.tabs-top { 247 | top: $bar-height; 248 | padding-top: 0; 249 | background-position: bottom; 250 | .tab-item { 251 | &.tab-item-active, 252 | &.active, 253 | &.activated { 254 | .badge { 255 | top: 4%; 256 | } 257 | } 258 | } 259 | } 260 | .tabs-top ~ .bar-header { 261 | border-bottom-width: 0; 262 | } 263 | 264 | .tab-item { 265 | @include flex(1); 266 | display: block; 267 | overflow: hidden; 268 | 269 | max-width: $tab-item-max-width; 270 | height: 100%; 271 | 272 | color: inherit; 273 | text-align: center; 274 | text-decoration: none; 275 | text-overflow: ellipsis; 276 | white-space: nowrap; 277 | 278 | font-weight: 400; 279 | font-size: $tabs-text-font-size; 280 | font-family: $font-family-light-sans-serif; 281 | 282 | opacity: 0.7; 283 | 284 | &:hover { 285 | cursor: pointer; 286 | } 287 | &.tab-hidden{ 288 | display:none; 289 | } 290 | } 291 | 292 | .tabs-item-hide > .tabs, 293 | .tabs.tabs-item-hide { 294 | display: none; 295 | } 296 | 297 | .tabs-icon-top > .tabs .tab-item, 298 | .tabs-icon-top.tabs .tab-item, 299 | .tabs-icon-bottom > .tabs .tab-item, 300 | .tabs-icon-bottom.tabs .tab-item { 301 | font-size: $tabs-text-font-size-side-icon; 302 | line-height: $tabs-text-font-size; 303 | } 304 | 305 | .tab-item .icon { 306 | display: block; 307 | margin: 0 auto; 308 | height: $tabs-icon-size; 309 | font-size: $tabs-icon-size; 310 | } 311 | 312 | .tabs-icon-left.tabs .tab-item, 313 | .tabs-icon-left > .tabs .tab-item, 314 | .tabs-icon-right.tabs .tab-item, 315 | .tabs-icon-right > .tabs .tab-item { 316 | font-size: $tabs-text-font-size-side-icon; 317 | 318 | .icon { 319 | display: inline-block; 320 | vertical-align: top; 321 | margin-top: -.1em; 322 | 323 | &:before { 324 | font-size: $tabs-icon-size - 8; 325 | line-height: $tabs-height; 326 | } 327 | } 328 | } 329 | 330 | .tabs-icon-left > .tabs .tab-item .icon, 331 | .tabs-icon-left.tabs .tab-item .icon { 332 | padding-right: 3px; 333 | } 334 | 335 | .tabs-icon-right > .tabs .tab-item .icon, 336 | .tabs-icon-right.tabs .tab-item .icon { 337 | padding-left: 3px; 338 | } 339 | 340 | .tabs-icon-only > .tabs .icon, 341 | .tabs-icon-only.tabs .icon { 342 | line-height: inherit; 343 | } 344 | 345 | 346 | .tab-item.has-badge { 347 | position: relative; 348 | } 349 | 350 | .tab-item .badge { 351 | position: absolute; 352 | top: 4%; 353 | right: 33%; // fallback 354 | right: calc(50% - 26px); 355 | padding: $tabs-badge-padding; 356 | height: auto; 357 | font-size: $tabs-badge-font-size; 358 | line-height: $tabs-badge-font-size + 4; 359 | } 360 | 361 | 362 | /* Navigational tab */ 363 | 364 | /* Active state for tab */ 365 | .tab-item.tab-item-active, 366 | .tab-item.active, 367 | .tab-item.activated { 368 | opacity: 1; 369 | 370 | &.tab-item-light { 371 | color: $light; 372 | } 373 | &.tab-item-stable { 374 | color: $stable; 375 | } 376 | &.tab-item-positive { 377 | color: $positive; 378 | } 379 | &.tab-item-calm { 380 | color: $calm; 381 | } 382 | &.tab-item-assertive { 383 | color: $assertive; 384 | } 385 | &.tab-item-balanced { 386 | color: $balanced; 387 | } 388 | &.tab-item-energized { 389 | color: $energized; 390 | } 391 | &.tab-item-royal { 392 | color: $royal; 393 | } 394 | &.tab-item-dark { 395 | color: $dark; 396 | } 397 | } 398 | 399 | .item.tabs { 400 | @include display-flex(); 401 | padding: 0; 402 | 403 | .icon:before { 404 | position: relative; 405 | } 406 | } 407 | 408 | .tab-item.disabled, 409 | .tab-item[disabled] { 410 | opacity: .4; 411 | cursor: default; 412 | pointer-events: none; 413 | } 414 | 415 | /** Platform styles **/ 416 | 417 | .tab-item.tab-item-ios { 418 | } 419 | .tab-item.tab-item-android { 420 | border-top: 2px solid inherit; 421 | } 422 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | 2 | // Button Mixins 3 | // -------------------------------------------------- 4 | 5 | @mixin button-style($bg-color, $border-color, $active-bg-color, $active-border-color, $color) { 6 | border-color: $border-color; 7 | background-color: $bg-color; 8 | color: $color; 9 | 10 | // Give desktop users something to play with 11 | &:hover { 12 | color: $color; 13 | text-decoration: none; 14 | } 15 | &.active, 16 | &.activated { 17 | border-color: $active-border-color; 18 | background-color: $active-bg-color; 19 | box-shadow: inset 0px 1px 3px rgba(0,0,0,0.15); 20 | } 21 | } 22 | 23 | @mixin button-clear($color, $font-size:"") { 24 | &.button-clear { 25 | border-color: transparent; 26 | background: none; 27 | box-shadow: none; 28 | color: $color; 29 | 30 | @if $font-size != "" { 31 | font-size: $font-size; 32 | } 33 | } 34 | &.button-icon { 35 | border-color: transparent; 36 | background: none; 37 | } 38 | } 39 | 40 | @mixin button-outline($color, $text-color:"") { 41 | &.button-outline { 42 | border-color: $color; 43 | background: transparent; 44 | @if $text-color == "" { 45 | $text-color: $color; 46 | } 47 | color: $text-color; 48 | &.active, 49 | &.activated { 50 | background-color: $color; 51 | box-shadow: none; 52 | color: #fff; 53 | } 54 | } 55 | } 56 | 57 | 58 | // Bar Mixins 59 | // -------------------------------------------------- 60 | 61 | @mixin bar-style($bg-color, $border-color, $color) { 62 | border-color: $border-color; 63 | background-color: $bg-color; 64 | background-image: linear-gradient(0deg, $border-color, $border-color 50%, transparent 50%); 65 | color: $color; 66 | 67 | .title { 68 | color: $color; 69 | } 70 | } 71 | 72 | 73 | // Tab Mixins 74 | // -------------------------------------------------- 75 | 76 | @mixin tab-style($bg-color, $border-color, $color) { 77 | border-color: $border-color; 78 | background-color: $bg-color; 79 | background-image: linear-gradient(0deg, $border-color, $border-color 50%, transparent 50%); 80 | color: $color; 81 | } 82 | 83 | @mixin tab-badge-style($bg-color, $color) { 84 | .tab-item .badge { 85 | background-color: $bg-color; 86 | color: $color; 87 | } 88 | } 89 | 90 | 91 | // Item Mixins 92 | // -------------------------------------------------- 93 | 94 | @mixin item-style($bg-color, $border-color, $color) { 95 | border-color: $border-color; 96 | background-color: $bg-color; 97 | color: $color; 98 | } 99 | 100 | @mixin item-active-style($active-bg-color, $active-border-color) { 101 | border-color: $active-border-color; 102 | background-color: $active-bg-color; 103 | } 104 | 105 | 106 | // Badge Mixins 107 | // -------------------------------------------------- 108 | 109 | @mixin badge-style($bg-color, $color) { 110 | background-color: $bg-color; 111 | color: $color; 112 | } 113 | 114 | 115 | // Range Mixins 116 | // -------------------------------------------------- 117 | 118 | @mixin range-style($track-bg-color) { 119 | &::-webkit-slider-thumb:before { 120 | background: $track-bg-color; 121 | } 122 | } 123 | 124 | 125 | // Checkbox Mixins 126 | // -------------------------------------------------- 127 | 128 | @mixin checkbox-style($off-border-color, $on-bg-color) { 129 | & input:before, 130 | & .checkbox-icon:before { 131 | border-color: $off-border-color; 132 | } 133 | 134 | // what the background looks like when its checked 135 | & input:checked:before, 136 | & input:checked + .checkbox-icon:before { 137 | background: $on-bg-color; 138 | } 139 | } 140 | 141 | 142 | // Toggle Mixins 143 | // -------------------------------------------------- 144 | 145 | @mixin toggle-style($on-border-color, $on-bg-color) { 146 | // the track when the toggle is "on" 147 | & input:checked + .track { 148 | border-color: $on-border-color; 149 | background-color: $on-bg-color; 150 | } 151 | } 152 | 153 | 154 | // Clearfix 155 | // -------------------------------------------------- 156 | 157 | @mixin clearfix { 158 | *zoom: 1; 159 | &:before, 160 | &:after { 161 | display: table; 162 | content: ""; 163 | line-height: 0; 164 | } 165 | &:after { 166 | clear: both; 167 | } 168 | } 169 | 170 | 171 | // Placeholder text 172 | // -------------------------------------------------- 173 | 174 | @mixin placeholder($color: $input-color-placeholder, $text-indent: 0) { 175 | &::-moz-placeholder { /* Firefox 19+ */ 176 | color: $color; 177 | } 178 | &:-ms-input-placeholder { 179 | color: $color; 180 | } 181 | &::-webkit-input-placeholder { 182 | color: $color; 183 | // Safari placeholder margin issue 184 | text-indent: $text-indent; 185 | } 186 | } 187 | 188 | 189 | // Text Mixins 190 | // -------------------------------------------------- 191 | 192 | @mixin text-size-adjust($value: none) { 193 | -webkit-text-size-adjust: $value; 194 | -moz-text-size-adjust: $value; 195 | text-size-adjust: $value; 196 | } 197 | @mixin tap-highlight-transparent() { 198 | -webkit-tap-highlight-color: rgba(0,0,0,0); 199 | -webkit-tap-highlight-color: transparent; // For some Androids 200 | } 201 | @mixin touch-callout($value: none) { 202 | -webkit-touch-callout: $value; 203 | } 204 | 205 | 206 | // Font Mixins 207 | // -------------------------------------------------- 208 | 209 | @mixin font-family-serif() { 210 | font-family: $serif-font-family; 211 | } 212 | @mixin font-family-sans-serif() { 213 | font-family: $sans-font-family; 214 | } 215 | @mixin font-family-monospace() { 216 | font-family: $mono-font-family; 217 | } 218 | @mixin font-shorthand($size: $base-font-size, $weight: normal, $line-height: $base-line-height) { 219 | font-weight: $weight; 220 | font-size: $size; 221 | line-height: $line-height; 222 | } 223 | @mixin font-serif($size: $base-font-size, $weight: normal, $line-height: $base-line-height) { 224 | @include font-family-serif(); 225 | @include font-shorthand($size, $weight, $line-height); 226 | } 227 | @mixin font-sans-serif($size: $base-font-size, $weight: normal, $line-height: $base-line-height) { 228 | @include font-family-sans-serif(); 229 | @include font-shorthand($size, $weight, $line-height); 230 | } 231 | @mixin font-monospace($size: $base-font-size, $weight: normal, $line-height: $base-line-height) { 232 | @include font-family-monospace(); 233 | @include font-shorthand($size, $weight, $line-height); 234 | } 235 | @mixin font-smoothing($font-smoothing) { 236 | -webkit-font-smoothing: $font-smoothing; 237 | font-smoothing: $font-smoothing; 238 | } 239 | 240 | 241 | // Appearance 242 | // -------------------------------------------------- 243 | 244 | @mixin appearance($val) { 245 | -webkit-appearance: $val; 246 | -moz-appearance: $val; 247 | appearance: $val; 248 | } 249 | 250 | 251 | // Border Radius Mixins 252 | // -------------------------------------------------- 253 | 254 | @mixin border-radius($radius) { 255 | -webkit-border-radius: $radius; 256 | -moz-border-radius: $radius; 257 | border-radius: $radius; 258 | } 259 | 260 | // Single Corner Border Radius 261 | @mixin border-top-left-radius($radius) { 262 | -webkit-border-top-left-radius: $radius; 263 | -moz-border-radius-topleft: $radius; 264 | border-top-left-radius: $radius; 265 | } 266 | @mixin border-top-right-radius($radius) { 267 | -webkit-border-top-right-radius: $radius; 268 | -moz-border-radius-topright: $radius; 269 | border-top-right-radius: $radius; 270 | } 271 | @mixin border-bottom-right-radius($radius) { 272 | -webkit-border-bottom-right-radius: $radius; 273 | -moz-border-radius-bottomright: $radius; 274 | border-bottom-right-radius: $radius; 275 | } 276 | @mixin border-bottom-left-radius($radius) { 277 | -webkit-border-bottom-left-radius: $radius; 278 | -moz-border-radius-bottomleft: $radius; 279 | border-bottom-left-radius: $radius; 280 | } 281 | 282 | // Single Side Border Radius 283 | @mixin border-top-radius($radius) { 284 | @include border-top-right-radius($radius); 285 | @include border-top-left-radius($radius); 286 | } 287 | @mixin border-right-radius($radius) { 288 | @include border-top-right-radius($radius); 289 | @include border-bottom-right-radius($radius); 290 | } 291 | @mixin border-bottom-radius($radius) { 292 | @include border-bottom-right-radius($radius); 293 | @include border-bottom-left-radius($radius); 294 | } 295 | @mixin border-left-radius($radius) { 296 | @include border-top-left-radius($radius); 297 | @include border-bottom-left-radius($radius); 298 | } 299 | 300 | 301 | // Box shadows 302 | // -------------------------------------------------- 303 | 304 | @mixin box-shadow($shadow...) { 305 | -webkit-box-shadow: $shadow; 306 | -moz-box-shadow: $shadow; 307 | box-shadow: $shadow; 308 | } 309 | 310 | 311 | // Transition Mixins 312 | // -------------------------------------------------- 313 | 314 | @mixin transition($transition...) { 315 | -webkit-transition: $transition; 316 | -moz-transition: $transition; 317 | transition: $transition; 318 | } 319 | @mixin transition-delay($transition-delay) { 320 | -webkit-transition-delay: $transition-delay; 321 | -moz-transition-delay: $transition-delay; 322 | transition-delay: $transition-delay; 323 | } 324 | @mixin transition-duration($transition-duration) { 325 | -webkit-transition-duration: $transition-duration; 326 | -moz-transition-duration: $transition-duration; 327 | transition-duration: $transition-duration; 328 | } 329 | @mixin transition-timing-function($transition-timing) { 330 | -webkit-transition-timing-function: $transition-timing; 331 | -moz-transition-timing-function: $transition-timing; 332 | transition-timing-function: $transition-timing; 333 | } 334 | @mixin transition-property($property) { 335 | -webkit-transition-property: $property; 336 | -moz-transition-property: $property; 337 | transition-property: $property; 338 | } 339 | @mixin transition-transform($properties...) { 340 | // special case cuz of transform vendor prefixes 341 | -webkit-transition: -webkit-transform $properties; 342 | -moz-transition: -moz-transform $properties; 343 | transition: transform $properties; 344 | } 345 | 346 | 347 | // Animation Mixins 348 | // -------------------------------------------------- 349 | 350 | @mixin animation($animation) { 351 | -webkit-animation: $animation; 352 | -moz-animation: $animation; 353 | animation: $animation; 354 | } 355 | @mixin animation-duration($duration) { 356 | -webkit-animation-duration: $duration; 357 | -moz-animation-duration: $duration; 358 | animation-duration: $duration; 359 | } 360 | @mixin animation-direction($direction) { 361 | -webkit-animation-direction: $direction; 362 | -moz-animation-direction: $direction; 363 | animation-direction: $direction; 364 | } 365 | @mixin animation-timing-function($animation-timing) { 366 | -webkit-animation-timing-function: $animation-timing; 367 | -moz-animation-timing-function: $animation-timing; 368 | animation-timing-function: $animation-timing; 369 | } 370 | @mixin animation-fill-mode($fill-mode) { 371 | -webkit-animation-fill-mode: $fill-mode; 372 | -moz-animation-fill-mode: $fill-mode; 373 | animation-fill-mode: $fill-mode; 374 | } 375 | @mixin animation-name($name) { 376 | -webkit-animation-name: $name; 377 | -moz-animation-name: $name; 378 | animation-name: $name; 379 | } 380 | @mixin animation-iteration-count($count) { 381 | -webkit-animation-iteration-count: $count; 382 | -moz-animation-iteration-count: $count; 383 | animation-iteration-count: $count; 384 | } 385 | 386 | 387 | // Transformation Mixins 388 | // -------------------------------------------------- 389 | 390 | @mixin rotate($degrees) { 391 | @include transform( rotate($degrees) ); 392 | } 393 | @mixin scale($ratio) { 394 | @include transform( scale($ratio) ); 395 | } 396 | @mixin translate($x, $y) { 397 | @include transform( translate($x, $y) ); 398 | } 399 | @mixin skew($x, $y) { 400 | @include transform( skew($x, $y) ); 401 | -webkit-backface-visibility: hidden; 402 | } 403 | @mixin translate3d($x, $y, $z) { 404 | @include transform( translate3d($x, $y, $z) ); 405 | } 406 | @mixin translateZ($z) { 407 | @include transform( translateZ($z) ); 408 | } 409 | @mixin transform($val) { 410 | -webkit-transform: $val; 411 | -moz-transform: $val; 412 | transform: $val; 413 | } 414 | 415 | @mixin transform-origin($left, $top) { 416 | -webkit-transform-origin: $left $top; 417 | -moz-transform-origin: $left $top; 418 | transform-origin: $left $top; 419 | } 420 | 421 | 422 | // Backface visibility 423 | // -------------------------------------------------- 424 | // Prevent browsers from flickering when using CSS 3D transforms. 425 | // Default value is `visible`, but can be changed to `hidden 426 | 427 | @mixin backface-visibility($visibility){ 428 | -webkit-backface-visibility: $visibility; 429 | backface-visibility: $visibility; 430 | } 431 | 432 | 433 | // Background clipping 434 | // -------------------------------------------------- 435 | 436 | @mixin background-clip($clip) { 437 | -webkit-background-clip: $clip; 438 | -moz-background-clip: $clip; 439 | background-clip: $clip; 440 | } 441 | 442 | 443 | // Background sizing 444 | // -------------------------------------------------- 445 | 446 | @mixin background-size($size) { 447 | -webkit-background-size: $size; 448 | -moz-background-size: $size; 449 | background-size: $size; 450 | } 451 | 452 | 453 | // Box sizing 454 | // -------------------------------------------------- 455 | 456 | @mixin box-sizing($boxmodel) { 457 | -webkit-box-sizing: $boxmodel; 458 | -moz-box-sizing: $boxmodel; 459 | box-sizing: $boxmodel; 460 | } 461 | 462 | 463 | // User select 464 | // -------------------------------------------------- 465 | 466 | @mixin user-select($select) { 467 | -webkit-user-select: $select; 468 | -moz-user-select: $select; 469 | -ms-user-select: $select; 470 | user-select: $select; 471 | } 472 | 473 | 474 | // Content Columns 475 | // -------------------------------------------------- 476 | 477 | @mixin content-columns($columnCount, $columnGap: $grid-gutter-width) { 478 | -webkit-column-count: $columnCount; 479 | -moz-column-count: $columnCount; 480 | column-count: $columnCount; 481 | -webkit-column-gap: $columnGap; 482 | -moz-column-gap: $columnGap; 483 | column-gap: $columnGap; 484 | } 485 | 486 | 487 | // Flexbox Mixins 488 | // -------------------------------------------------- 489 | // http://philipwalton.github.io/solved-by-flexbox/ 490 | // https://github.com/philipwalton/solved-by-flexbox 491 | 492 | @mixin display-flex { 493 | display: -webkit-box; 494 | display: -webkit-flex; 495 | display: -moz-box; 496 | display: -moz-flex; 497 | display: -ms-flexbox; 498 | display: flex; 499 | } 500 | 501 | @mixin dislay-inline-flex { 502 | display: -webkit-inline-box; 503 | display: -webkit-inline-flex; 504 | display: -moz-inline-flex; 505 | display: -ms-inline-flexbox; 506 | display: inline-flex; 507 | } 508 | 509 | @mixin flex-direction($value: row) { 510 | @if $value == row-reverse { 511 | -webkit-box-direction: reverse; 512 | -webkit-box-orient: horizontal; 513 | } @else if $value == column { 514 | -webkit-box-direction: normal; 515 | -webkit-box-orient: vertical; 516 | } @else if $value == column-reverse { 517 | -webkit-box-direction: reverse; 518 | -webkit-box-orient: vertical; 519 | } @else { 520 | -webkit-box-direction: normal; 521 | -webkit-box-orient: horizontal; 522 | } 523 | -webkit-flex-direction: $value; 524 | -moz-flex-direction: $value; 525 | -ms-flex-direction: $value; 526 | flex-direction: $value; 527 | } 528 | 529 | @mixin flex-wrap($value: nowrap) { 530 | // No Webkit Box fallback. 531 | -webkit-flex-wrap: $value; 532 | -moz-flex-wrap: $value; 533 | @if $value == nowrap { 534 | -ms-flex-wrap: none; 535 | } @else { 536 | -ms-flex-wrap: $value; 537 | } 538 | flex-wrap: $value; 539 | } 540 | 541 | @mixin flex($fg: 1, $fs: null, $fb: null) { 542 | -webkit-box-flex: $fg; 543 | -webkit-flex: $fg $fs $fb; 544 | -moz-box-flex: $fg; 545 | -moz-flex: $fg $fs $fb; 546 | -ms-flex: $fg $fs $fb; 547 | flex: $fg $fs $fb; 548 | } 549 | 550 | @mixin flex-flow($values: (row nowrap)) { 551 | // No Webkit Box fallback. 552 | -webkit-flex-flow: $values; 553 | -moz-flex-flow: $values; 554 | -ms-flex-flow: $values; 555 | flex-flow: $values; 556 | } 557 | 558 | @mixin align-items($value: stretch) { 559 | @if $value == flex-start { 560 | -webkit-box-align: start; 561 | -ms-flex-align: start; 562 | } @else if $value == flex-end { 563 | -webkit-box-align: end; 564 | -ms-flex-align: end; 565 | } @else { 566 | -webkit-box-align: $value; 567 | -ms-flex-align: $value; 568 | } 569 | -webkit-align-items: $value; 570 | -moz-align-items: $value; 571 | align-items: $value; 572 | } 573 | 574 | @mixin align-self($value: auto) { 575 | -webkit-align-self: $value; 576 | -moz-align-self: $value; 577 | @if $value == flex-start { 578 | -ms-flex-item-align: start; 579 | } @else if $value == flex-end { 580 | -ms-flex-item-align: end; 581 | } @else { 582 | -ms-flex-item-align: $value; 583 | } 584 | align-self: $value; 585 | } 586 | 587 | @mixin align-content($value: stretch) { 588 | -webkit-align-content: $value; 589 | -moz-align-content: $value; 590 | @if $value == flex-start { 591 | -ms-flex-line-pack: start; 592 | } @else if $value == flex-end { 593 | -ms-flex-line-pack: end; 594 | } @else { 595 | -ms-flex-line-pack: $value; 596 | } 597 | align-content: $value; 598 | } 599 | 600 | @mixin justify-content($value: stretch) { 601 | @if $value == flex-start { 602 | -webkit-box-pack: start; 603 | -ms-flex-pack: start; 604 | } @else if $value == flex-end { 605 | -webkit-box-pack: end; 606 | -ms-flex-pack: end; 607 | } @else if $value == space-between { 608 | -webkit-box-pack: justify; 609 | -ms-flex-pack: justify; 610 | } @else { 611 | -webkit-box-pack: $value; 612 | -ms-flex-pack: $value; 613 | } 614 | -webkit-justify-content: $value; 615 | -moz-justify-content: $value; 616 | justify-content: $value; 617 | } 618 | 619 | @mixin responsive-grid-break($selector, $max-width) { 620 | @media (max-width: $max-width) { 621 | #{$selector} { 622 | -webkit-box-direction: normal; 623 | -moz-box-direction: normal; 624 | -webkit-box-orient: vertical; 625 | -moz-box-orient: vertical; 626 | -webkit-flex-direction: column; 627 | -ms-flex-direction: column; 628 | flex-direction: column; 629 | 630 | .col, .col-10, .col-20, .col-25, .col-33, .col-34, .col-50, .col-66, .col-67, .col-75, .col-80, .col-90 { 631 | @include flex(1); 632 | margin-bottom: ($grid-padding-width * 3) / 2; 633 | margin-left: 0; 634 | max-width: 100%; 635 | width: 100%; 636 | } 637 | } 638 | } 639 | } 640 | -------------------------------------------------------------------------------- /mobile/www/lib/ionic/js/angular-ui/angular-ui-router.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * State-based routing for AngularJS 3 | * @version v0.2.10 4 | * @link http://angular-ui.github.com/ 5 | * @license MIT License, http://www.opensource.org/licenses/MIT 6 | */ 7 | "undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return I(new(I(function(){},{prototype:a})),b)}function e(a){return H(arguments,function(b){b!==a&&H(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function h(a,b,c,d){var e,h=f(c,d),i={},j=[];for(var k in h)if(h[k].params&&h[k].params.length){e=h[k].params;for(var l in e)g(j,e[l])>=0||(j.push(e[l]),i[e[l]]=a[e[l]])}return I({},i,b)}function i(a,b){var c={};return H(a,function(a){var d=b[a];c[a]=null!=d?String(d):null}),c}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(o[c]=d,E(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);H(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return F(a)&&a.then&&a.$$promises}if(!F(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return H(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!C(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;H(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!F(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=I({},d),s=1+m.length/3,t=!1;if(C(f.$$failure))return k(f.$$failure),p;f.$$values?(t=e(r,f.$$values),h()):(I(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return C(a.template)?this.fromString(a.template,b):C(a.templateUrl)?this.fromUrl(a.templateUrl,b):C(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return D(a)?a(b):a},this.fromUrl=function(c,d){return D(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),H(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function o(){this.compile=function(a){return new n(a)},this.isMatcher=function(a){return F(a)&&D(a.exec)&&D(a.format)&&D(a.concat)},this.$get=function(){return this}}function p(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return C(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!D(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(E(a)){var b=a;a=function(){return b}}else if(!D(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=E(f);if(E(e)&&(e=a.compile(e)),!h&&!D(f)&&!G(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),I(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:E(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),I(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(b){function d(b){var d=b(c,a);return d?(E(d)&&a.replace().url(d),!0):!1}if(!b||!b.defaultPrevented){var g,h=e.length;for(g=0;h>g;g++)if(d(e[g]))return;f&&d(f)}}return b.$on("$locationChangeSuccess",d),{sync:function(){d()}}}]}function q(a,e,f){function g(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){var d=E(a),e=d?a:a.name,f=g(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=w[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function m(a,b){x[a]||(x[a]=[]),x[a].push(b)}function n(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!E(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(w.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):E(b.parent)?b.parent:"";if(e&&!w[e])return m(e,b.self);for(var f in z)D(z[f])&&(b[f]=z[f](b,z.$delegates[f]));if(w[c]=b,!b[y]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){v.$current.navigable==b&&j(a,c)||v.transitionTo(b,a,{location:!1})}]),x[c])for(var g=0;g-1}function p(a){var b=a.split("."),c=v.$current.name.split(".");if("**"===b[0]&&(c=c.slice(c.indexOf(b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(c.indexOf(b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length)return!1;for(var d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return c.join("")===b.join("")}function q(a,b){return E(a)&&!C(b)?z[a]:D(b)&&E(a)?(z[a]&&!z.$delegates[a]&&(z.$delegates[a]=z[a]),z[a]=b,this):this}function r(a,b){return F(a)?b=a:b.name=a,n(b),this}function s(a,e,g,m,n,q,r,s,x){function z(){r.url()!==M&&(r.url(M),r.replace())}function A(a,c,d,f,h){var i=d?c:k(a.params,c),j={$stateParams:i};h.resolve=n.resolve(a.resolve,j,h.resolve,a);var l=[h.resolve.then(function(a){h.globals=a})];return f&&l.push(f),H(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return g.load(d,{view:c,locals:j,params:i,notify:!1})||""}],l.push(n.resolve(e,j,h.resolve,a).then(function(f){if(D(c.controllerProvider)||G(c.controllerProvider)){var g=b.extend({},e,j);f.$$controller=m.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,f.$$controllerAs=c.controllerAs,h[d]=f}))}),e.all(l).then(function(){return h})}var B=e.reject(new Error("transition superseded")),F=e.reject(new Error("transition prevented")),K=e.reject(new Error("transition aborted")),L=e.reject(new Error("transition failed")),M=r.url(),N=x.baseHref();return u.locals={resolve:null,globals:{$stateParams:{}}},v={params:{},current:u.self,$current:u,transition:null},v.reload=function(){v.transitionTo(v.current,q,{reload:!0,inherit:!1,notify:!1})},v.go=function(a,b,c){return this.transitionTo(a,b,I({inherit:!0,relative:v.$current},c))},v.transitionTo=function(b,c,f){c=c||{},f=I({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,k=v.$current,n=v.params,o=k.path,p=l(b,f.relative);if(!C(p)){var s={to:b,toParams:c,options:f};if(g=a.$broadcast("$stateNotFound",s,k.self,n),g.defaultPrevented)return z(),K;if(g.retry){if(f.$retry)return z(),L;var w=v.transition=e.when(g.retry);return w.then(function(){return w!==v.transition?B:(s.options.$retry=!0,v.transitionTo(s.to,s.toParams,s.options))},function(){return K}),z(),w}if(b=s.to,c=s.toParams,f=s.options,p=l(b,f.relative),!C(p)){if(f.relative)throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'");throw new Error("No such state '"+b+"'")}}if(p[y])throw new Error("Cannot transition to abstract state '"+b+"'");f.inherit&&(c=h(q,c||{},v.$current,p)),b=p;var x,D,E=b.path,G=u.locals,H=[];for(x=0,D=E[x];D&&D===o[x]&&j(c,n,D.ownParams)&&!f.reload;x++,D=E[x])G=H[x]=D.locals;if(t(b,k,G,f))return b.self.reloadOnSearch!==!1&&z(),v.transition=null,e.when(v.current);if(c=i(b.params,c||{}),f.notify&&(g=a.$broadcast("$stateChangeStart",b.self,c,k.self,n),g.defaultPrevented))return z(),F;for(var N=e.when(G),O=x;O=x;d--)g=o[d],g.self.onExit&&m.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=x;d1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target")||(c(function(){a.go(i.state,j,o)}),b.preventDefault())})}}}function y(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(d,e,f){function g(){a.$current.self===i&&h()?e.addClass(l):e.removeClass(l)}function h(){return!k||j(k,b)}var i,k,l;l=c(f.uiSrefActive||"",!1)(d),this.$$setStateInfo=function(b,c){i=a.get(b,w(e)),k=c,g()},d.$on("$stateChangeSuccess",g)}]}}function z(a){return function(b){return a.is(b)}}function A(a){return function(b){return a.includes(b)}}function B(a,b){function e(a){this.locals=a.locals.globals,this.params=this.locals.$stateParams}function f(){this.locals=null,this.params=null}function g(c,g){if(null!=g.redirectTo){var h,j=g.redirectTo;if(E(j))h=j;else{if(!D(j))throw new Error("Invalid 'redirectTo' in when()");h=function(a,b){return j(a,b.path(),b.search())}}b.when(c,h)}else a.state(d(g,{parent:null,name:"route:"+encodeURIComponent(c),url:c,onEnter:e,onExit:f}));return i.push(g),this}function h(a,b,d){function e(a){return""!==a.name?a:c}var f={routes:i,params:d,current:c};return b.$on("$stateChangeStart",function(a,c,d,f){b.$broadcast("$routeChangeStart",e(c),e(f))}),b.$on("$stateChangeSuccess",function(a,c,d,g){f.current=e(c),b.$broadcast("$routeChangeSuccess",e(c),e(g)),J(d,f.params)}),b.$on("$stateChangeError",function(a,c,d,f,g,h){b.$broadcast("$routeChangeError",e(c),e(f),h)}),f}var i=[];e.$inject=["$$state"],this.when=g,this.$get=h,h.$inject=["$state","$rootScope","$routeParams"]}var C=b.isDefined,D=b.isFunction,E=b.isString,F=b.isObject,G=b.isArray,H=b.forEach,I=b.extend,J=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),l.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",l),m.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",m),n.prototype.concat=function(a){return new n(this.sourcePath+a+this.sourceSearch)},n.prototype.toString=function(){return this.source},n.prototype.exec=function(a,b){var c=this.regexp.exec(a);if(!c)return null;var d,e=this.params,f=e.length,g=this.segments.length-1,h={};if(g!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(d=0;g>d;d++)h[e[d]]=c[d+1];for(;f>d;d++)h[e[d]]=b[e[d]];return h},n.prototype.parameters=function(){return this.params},n.prototype.format=function(a){var b=this.segments,c=this.params;if(!a)return b.join("");var d,e,f,g=b.length-1,h=c.length,i=b[0];for(d=0;g>d;d++)f=a[c[d]],null!=f&&(i+=encodeURIComponent(f)),i+=b[d+1];for(;h>d;d++)f=a[c[d]],null!=f&&(i+=(e?"&":"?")+c[d]+"="+encodeURIComponent(f),e=!0);return i},b.module("ui.router.util").provider("$urlMatcherFactory",o),p.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",p),q.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",q),r.$inject=[],b.module("ui.router.state").provider("$view",r),b.module("ui.router.state").provider("$uiViewScroll",s),t.$inject=["$state","$injector","$uiViewScroll"],u.$inject=["$compile","$controller","$state"],b.module("ui.router.state").directive("uiView",t),b.module("ui.router.state").directive("uiView",u),x.$inject=["$state","$timeout"],y.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",x).directive("uiSrefActive",y),z.$inject=["$state"],A.$inject=["$state"],b.module("ui.router.state").filter("isState",z).filter("includedByState",A),B.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",B).directive("ngView",t)}(window,window.angular); --------------------------------------------------------------------------------