├── .bowerrc ├── .buildignore ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── .yo-rc.json ├── Gruntfile.js ├── README.md ├── bower.json ├── client ├── common │ ├── assets │ │ └── images │ │ │ └── yeoman.png │ ├── auto.scss │ └── scss │ │ ├── _colours.scss │ │ ├── _fonts.scss │ │ ├── _layout.scss │ │ └── common.scss ├── ionic │ ├── scss │ │ ├── _colours.scss │ │ ├── _fonts.scss │ │ ├── _layout.scss │ │ ├── _variable_overrides.scss │ │ └── ionic.app.scss │ └── www │ │ ├── img │ │ └── ionic.png │ │ ├── index.html │ │ ├── js │ │ ├── app.js │ │ ├── controllers.js │ │ └── services.js │ │ ├── lib │ │ └── ionic │ │ │ ├── css │ │ │ ├── ionic.css │ │ │ └── ionic.min.css │ │ │ ├── fonts │ │ │ ├── ionicons.eot │ │ │ ├── ionicons.svg │ │ │ ├── ionicons.ttf │ │ │ └── ionicons.woff │ │ │ ├── js │ │ │ ├── angular-ui │ │ │ │ ├── angular-ui-router.js │ │ │ │ └── angular-ui-router.min.js │ │ │ ├── angular │ │ │ │ ├── angular-animate.js │ │ │ │ ├── angular-animate.min.js │ │ │ │ ├── angular-resource.js │ │ │ │ ├── angular-resource.min.js │ │ │ │ ├── angular-sanitize.js │ │ │ │ ├── angular-sanitize.min.js │ │ │ │ ├── angular.js │ │ │ │ └── angular.min.js │ │ │ ├── ionic-angular.js │ │ │ ├── ionic-angular.min.js │ │ │ ├── ionic.bundle.js │ │ │ ├── ionic.bundle.min.js │ │ │ ├── ionic.js │ │ │ └── ionic.min.js │ │ │ ├── scss │ │ │ ├── _action-sheet.scss │ │ │ ├── _animations.scss │ │ │ ├── _backdrop.scss │ │ │ ├── _badge.scss │ │ │ ├── _bar.scss │ │ │ ├── _button-bar.scss │ │ │ ├── _button.scss │ │ │ ├── _checkbox.scss │ │ │ ├── _form.scss │ │ │ ├── _grid.scss │ │ │ ├── _items.scss │ │ │ ├── _list.scss │ │ │ ├── _loading.scss │ │ │ ├── _menu.scss │ │ │ ├── _mixins.scss │ │ │ ├── _modal.scss │ │ │ ├── _platform.scss │ │ │ ├── _popover.scss │ │ │ ├── _popup.scss │ │ │ ├── _progress.scss │ │ │ ├── _radio.scss │ │ │ ├── _range.scss │ │ │ ├── _reset.scss │ │ │ ├── _scaffolding.scss │ │ │ ├── _select.scss │ │ │ ├── _slide-box.scss │ │ │ ├── _tabs.scss │ │ │ ├── _toggle.scss │ │ │ ├── _transitions.scss │ │ │ ├── _type.scss │ │ │ ├── _util.scss │ │ │ ├── _variables.scss │ │ │ ├── ionic.scss │ │ │ └── ionicons │ │ │ │ ├── _ionicons-animation.scss │ │ │ │ ├── _ionicons-font.scss │ │ │ │ ├── _ionicons-icons.scss │ │ │ │ ├── _ionicons-variables.scss │ │ │ │ └── ionicons.scss │ │ │ └── version.json │ │ └── templates │ │ ├── tab-account.html │ │ ├── tab-dash.html │ │ ├── tab-things.html │ │ ├── tabs.html │ │ └── thing-detail.html └── webapp │ ├── .htaccess │ ├── .jshintrc │ ├── app │ ├── account │ │ ├── account.js │ │ ├── login │ │ │ ├── login.controller.js │ │ │ ├── login.jade │ │ │ └── login.scss │ │ ├── settings │ │ │ ├── settings.controller.js │ │ │ └── settings.jade │ │ └── signup │ │ │ ├── signup.controller.js │ │ │ └── signup.jade │ ├── admin │ │ ├── admin.controller.js │ │ ├── admin.jade │ │ ├── admin.js │ │ └── admin.scss │ ├── app.js │ ├── app.scss │ └── main │ │ ├── main.controller.js │ │ ├── main.controller.spec.js │ │ ├── main.jade │ │ ├── main.js │ │ └── main.scss │ ├── components │ ├── auth │ │ ├── auth.service.js │ │ └── user.service.js │ ├── modal │ │ ├── modal.jade │ │ ├── modal.scss │ │ └── modal.service.js │ ├── mongoose-error │ │ └── mongoose-error.directive.js │ └── navbar │ │ ├── navbar.controller.js │ │ └── navbar.jade │ ├── favicon.ico │ ├── index.html │ ├── robots.txt │ └── scss │ ├── _colours.scss │ ├── _fonts.scss │ ├── _layout.scss │ └── webapp.scss ├── dist-ionic ├── .bowerrc ├── .gitignore ├── bower.json ├── config.xml ├── gulpfile.js ├── hooks │ ├── README.md │ └── after_prepare │ │ └── 010_add_platform_class.js ├── ionic.project └── package.json ├── e2e └── main │ ├── main.po.js │ └── main.spec.js ├── karma.conf.js ├── notes.txt ├── package.json ├── protractor.conf.js └── server ├── .jshintrc ├── .jshintrc-spec ├── api ├── thing │ ├── index.js │ ├── thing.controller.js │ ├── thing.model.js │ └── thing.spec.js └── user │ ├── index.js │ ├── user.controller.js │ ├── user.model.js │ └── user.model.spec.js ├── app.js ├── auth ├── auth.service.js ├── facebook │ ├── index.js │ └── passport.js ├── google │ ├── index.js │ └── passport.js ├── index.js ├── local │ ├── index.js │ └── passport.js └── twitter │ ├── index.js │ └── passport.js ├── components └── errors │ └── index.js ├── config ├── environment │ ├── development.js │ ├── index.js │ ├── production.js │ └── test.js ├── express.js ├── local.env.sample.js └── seed.js ├── routes.js └── views └── 404.jade /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | .tmp 4 | .sass-cache 5 | .idea 6 | client/bower_components 7 | dist 8 | /server/config/local.env.js 9 | /dist-ionic/www 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.11' 5 | before_script: 6 | - npm install -g bower grunt-cli 7 | - gem install sass 8 | - bower install 9 | services: mongodb -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-angular-fullstack-ionic": { 3 | "insertRoutes": true, 4 | "registerRoutesFile": "server/routes.js", 5 | "routesNeedle": "// Insert routes below", 6 | "routesBase": "/api/", 7 | "pluralizeRoutes": true, 8 | "insertSockets": true, 9 | "registerSocketsFile": "server/config/socketio.js", 10 | "socketsNeedle": "// Insert sockets below", 11 | "filters": { 12 | "js": true, 13 | "jade": true, 14 | "sass": true, 15 | "uirouter": true, 16 | "bootstrap": true, 17 | "uibootstrap": true, 18 | "mongoose": true, 19 | "auth": true, 20 | "oauth": true, 21 | "googleAuth": true, 22 | "facebookAuth": true, 23 | "twitterAuth": true 24 | } 25 | }, 26 | "generator-ng-component": { 27 | "routeDirectory": "client/app/", 28 | "directiveDirectory": "client/app/", 29 | "filterDirectory": "client/app/", 30 | "serviceDirectory": "client/app/", 31 | "basePath": "client", 32 | "moduleName": "", 33 | "filters": [ 34 | "uirouter" 35 | ], 36 | "extensions": [ 37 | "js", 38 | "jade", 39 | "scss" 40 | ], 41 | "directiveSimpleTemplates": "", 42 | "directiveComplexTemplates": "", 43 | "filterTemplates": "", 44 | "serviceTemplates": "", 45 | "factoryTemplates": "", 46 | "controllerTemplates": "", 47 | "decoratorTemplates": "", 48 | "providerTemplates": "", 49 | "routeTemplates": "" 50 | } 51 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-fullstack-ionic-starter 2 | 3 | The motivation behind Angular Fullstack Ionic is to streamline the development of projects that include an API, Angular webapp and Ionic app. It’s core design principles include sharing code and assets wherever possible, creating an efficient workflow and making it super easy to start off a project with handy components available out of the box (e.g. user signup/login). 4 | 5 | It is based on the brilliant Yeoman angular-fullstack project. 6 | 7 | ##### Get started 8 | *Note this project in the early stages - so please read the points below before jumping in!* 9 | ``` 10 | npm install 11 | bower install 12 | grunt serve 13 | ``` 14 | ##### Dependencies 15 | If you don't have it already, you'll need Sass: ```gem install sass``` 16 | 17 | You'll also need MongoDB to be up and running: http://docs.mongodb.org/manual/installation/ 18 | 19 | ### Implemented so far 20 | * Grunt development workflow to serve webapp and ionic app in two browser tabs 21 | * Frontend (client) directory with 22 | * webapp - Jade, JS, SCSS and assets specific to webapp 23 | * ionic - HTML, JS, SCSS and assets specific to ionic app 24 | * common - SCSS and assets shared between webapp and ionic app 25 | 26 | ### Next steps 27 | * Wire up grunt dist tasks to work with new client folder structure 28 | * Implement user login in Ionic app 29 | * Implement shared components (e.g. angular directives, etc) 30 | * Implement shared fonts 31 | * Support plain HTML as well as Jade for webapp 32 | * Support Jade as well as plain HTML for ionic app 33 | 34 | ### Get involved 35 | The eventual aim of this project is to create an extension to the Yeoman generator-angular-fullstack 36 | 37 | Please contact @richardgsands if you'd like to help out! 38 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tmp", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": ">=1.2.*", 6 | "json3": "~3.3.1", 7 | "es5-shim": "~3.0.1", 8 | "bootstrap-sass-official": "~3.1.1", 9 | "bootstrap": "~3.1.1", 10 | "angular-resource": ">=1.2.*", 11 | "angular-cookies": ">=1.2.*", 12 | "angular-sanitize": ">=1.2.*", 13 | "angular-bootstrap": "~0.11.0", 14 | "font-awesome": ">=4.1.0", 15 | "lodash": "~2.4.1", 16 | "angular-ui-router": "~0.2.10" 17 | }, 18 | "devDependencies": { 19 | "angular-mocks": ">=1.2.*", 20 | "angular-scenario": ">=1.2.*" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/common/assets/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/common/assets/images/yeoman.png -------------------------------------------------------------------------------- /client/common/auto.scss: -------------------------------------------------------------------------------- 1 | /** 2 | Create common styles in client/common/scss folder 3 | There should be no need to edit this file 4 | */ 5 | 6 | // Common component styles are injected through grunt 7 | // injector 8 | // endinjector 9 | 10 | @import 'scss/common.scss'; -------------------------------------------------------------------------------- /client/common/scss/_colours.scss: -------------------------------------------------------------------------------- 1 | .common-class { 2 | background: #FF8AAC !important; 3 | color: #F5F5F2; 4 | } -------------------------------------------------------------------------------- /client/common/scss/_fonts.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/common/scss/_fonts.scss -------------------------------------------------------------------------------- /client/common/scss/_layout.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/common/scss/_layout.scss -------------------------------------------------------------------------------- /client/common/scss/common.scss: -------------------------------------------------------------------------------- 1 | /** 2 | Include all your common styles in the files below 3 | */ 4 | 5 | @import 'colours'; 6 | @import 'fonts'; 7 | @import 'layout'; -------------------------------------------------------------------------------- /client/ionic/scss/_colours.scss: -------------------------------------------------------------------------------- 1 | .ionic-class { 2 | background: #FFC384; 3 | color: #000000; 4 | } -------------------------------------------------------------------------------- /client/ionic/scss/_fonts.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/ionic/scss/_fonts.scss -------------------------------------------------------------------------------- /client/ionic/scss/_layout.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/ionic/scss/_layout.scss -------------------------------------------------------------------------------- /client/ionic/scss/_variable_overrides.scss: -------------------------------------------------------------------------------- 1 | /* 2 | To customize the look and feel of Ionic, you can override variables in this file. 3 | (See all the variables you can override in client/ionic/www/lib/ionic/scss/_variables.scss.) 4 | 5 | NB: Don't include the !default flag here 6 | 7 | For example: 8 | 9 | $stable: #000000; 10 | 11 | */ 12 | -------------------------------------------------------------------------------- /client/ionic/scss/ionic.app.scss: -------------------------------------------------------------------------------- 1 | 2 | // The path for our ionicons font files, relative to the built CSS in www/css 3 | $ionicons-font-path: "../lib/ionic/fonts" !default; 4 | 5 | // Import any app variable overrides 6 | @import 'variable_overrides'; 7 | 8 | // Include all of Ionic 9 | @import "../www/lib/ionic/scss/ionic"; 10 | 11 | // Include app scss 12 | @import 'colours'; 13 | @import 'fonts'; 14 | @import 'layout'; -------------------------------------------------------------------------------- /client/ionic/www/img/ionic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/ionic/www/img/ionic.png -------------------------------------------------------------------------------- /client/ionic/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 | 27 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /client/ionic/www/js/app.js: -------------------------------------------------------------------------------- 1 | // Ionic Starter App 2 | 3 | // angular.module is a global place for creating, registering and retrieving Angular modules 4 | // 'starter' is the name of this angular module example (also set in a attribute in index.html) 5 | // the 2nd parameter is an array of 'requires' 6 | // 'starter.services' is found in services.js 7 | // 'starter.controllers' is found in controllers.js 8 | angular.module('starter', ['ionic', 'starter.controllers', 'starter.services']) 9 | 10 | .run(function($ionicPlatform) { 11 | $ionicPlatform.ready(function() { 12 | // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard 13 | // for form inputs) 14 | if (window.cordova && window.cordova.plugins.Keyboard) { 15 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); 16 | } 17 | if (window.StatusBar) { 18 | // org.apache.cordova.statusbar required 19 | StatusBar.styleDefault(); 20 | } 21 | }); 22 | }) 23 | 24 | .config(function($stateProvider, $urlRouterProvider) { 25 | 26 | // Ionic uses AngularUI Router which uses the concept of states 27 | // Learn more here: https://github.com/angular-ui/ui-router 28 | // Set up the various states which the app can be in. 29 | // Each state's controller can be found in controllers.js 30 | $stateProvider 31 | 32 | // setup an abstract state for the tabs directive 33 | .state('tab', { 34 | url: "/tab", 35 | abstract: true, 36 | templateUrl: "templates/tabs.html" 37 | }) 38 | 39 | // Each tab has its own nav history stack: 40 | 41 | .state('tab.dash', { 42 | url: '/dash', 43 | views: { 44 | 'tab-dash': { 45 | templateUrl: 'templates/tab-dash.html', 46 | controller: 'DashCtrl' 47 | } 48 | } 49 | }) 50 | 51 | .state('tab.things', { 52 | url: '/things', 53 | views: { 54 | 'tab-things': { 55 | templateUrl: 'templates/tab-things.html', 56 | controller: 'ThingsCtrl' 57 | } 58 | } 59 | }) 60 | .state('tab.thing-detail', { 61 | url: '/thing/:thingId', 62 | views: { 63 | 'tab-things': { 64 | templateUrl: 'templates/thing-detail.html', 65 | controller: 'ThingDetailCtrl' 66 | } 67 | } 68 | }) 69 | 70 | .state('tab.account', { 71 | url: '/account', 72 | views: { 73 | 'tab-account': { 74 | templateUrl: 'templates/tab-account.html', 75 | controller: 'AccountCtrl' 76 | } 77 | } 78 | }); 79 | 80 | // if none of the above states are matched, use this as the fallback 81 | $urlRouterProvider.otherwise('/tab/dash'); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /client/ionic/www/js/controllers.js: -------------------------------------------------------------------------------- 1 | angular.module('starter.controllers', []) 2 | 3 | .controller('DashCtrl', function($scope) {}) 4 | 5 | .controller('ThingsCtrl', function($scope, Things) { 6 | Things.all(function(err, things) { 7 | $scope.things = things 8 | }); 9 | }) 10 | 11 | .controller('ThingDetailCtrl', function($scope, $stateParams, Things) { 12 | Things.get($stateParams.thingId, function(err, thing) { 13 | $scope.thing = thing 14 | }); 15 | }) 16 | 17 | .controller('AccountCtrl', function($scope) { 18 | $scope.settings = { 19 | enableSomething: true 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /client/ionic/www/js/services.js: -------------------------------------------------------------------------------- 1 | angular.module('starter.services', []) 2 | 3 | /** 4 | * A simple example service that returns some data. 5 | */ 6 | .factory('Things', function($http) { 7 | // Might use a resource here that returns a JSON array 8 | 9 | return { 10 | all: function(cb) { 11 | $http.get('/api/things') 12 | .success(function(data, status, headers, config) { 13 | console.log(data); 14 | cb(null, data); 15 | }); 16 | }, 17 | get: function(thingId, cb) { 18 | console.log('GET /api/things/' + thingId); 19 | $http.get('/api/things/' + thingId) 20 | .success(function(data, status, headers, config) { 21 | console.log(thingId, data); 22 | cb(null, data); 23 | }); 24 | } 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /client/ionic/www/lib/ionic/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/ionic/www/lib/ionic/fonts/ionicons.eot -------------------------------------------------------------------------------- /client/ionic/www/lib/ionic/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/ionic/www/lib/ionic/fonts/ionicons.ttf -------------------------------------------------------------------------------- /client/ionic/www/lib/ionic/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/ionic/www/lib/ionic/fonts/ionicons.woff -------------------------------------------------------------------------------- /client/ionic/www/lib/ionic/js/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.6 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,d,B){'use strict';function D(f,q){q=q||{};d.forEach(q,function(d,h){delete q[h]});for(var h in f)!f.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(q[h]=f[h]);return q}var w=d.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;d.module("ngResource",["ng"]).provider("$resource",function(){var f=this;this.defaults={stripTrailingSlashes:!0,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}}; 7 | this.$get=["$http","$q",function(q,h){function t(d,g){this.template=d;this.defaults=s({},f.defaults,g);this.urlParams={}}function v(x,g,l,m){function c(b,k){var c={};k=s({},g,k);r(k,function(a,k){u(a)&&(a=a());var d;if(a&&a.charAt&&"@"==a.charAt(0)){d=b;var e=a.substr(1);if(null==e||""===e||"hasOwnProperty"===e||!C.test("."+e))throw w("badmember",e);for(var e=e.split("."),n=0,g=e.length;n=c;e--)d.end&&d.end(f[e]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,f=[],m=a,l;for(f.last=function(){return f[f.length-1]};a;){l="";k=!0;if(f.last()&&x[f.last()])a=a.replace(new RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");d.chars&&d.chars(r(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),k=!1);else if(y.test(a)){if(b=a.match(y))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(z))a=a.substring(b[0].length),b[0].replace(z,e),k=!1}else K.test(a)&&((b=a.match(A))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(A,c)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(r(l)))}if(a==m)throw L("badparse",a);m=a}e()}function r(a){if(!a)return"";var d=M.exec(a);a=d[1];var c=d[3];if(d=d[2])q.innerHTML= 9 | d.replace(//g,">")}function s(a,d){var c=!1,e=h.bind(a,a.push);return{start:function(a,k,f){a=h.lowercase(a);!c&&x[a]&&(c=a);c||!0!==C[a]||(e("<"),e(a),h.forEach(k,function(c,f){var k= 10 | h.lowercase(f),g="img"===a&&"src"===k||"background"===k;!0!==P[k]||!0===D[k]&&!d(c,g)||(e(" "),e(f),e('="'),e(B(c)),e('"'))}),e(f?"/>":">"))},end:function(a){a=h.lowercase(a);c||!0!==C[a]||(e(""));a==c&&(c=!1)},chars:function(a){c||e(B(a))}}}var L=h.$$minErr("$sanitize"),A=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,z=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^]*?)>/i,I=/"\u201d\u2019]/,c=/^mailto:/;return function(e,b){function k(a){a&&g.push(E(a))} 15 | function f(a,c){g.push("');k(c);g.push("")}if(!e)return e;for(var m,l=e,g=[],n,p;m=l.match(d);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),f(n,m[0].replace(c,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular); 16 | //# sourceMappingURL=angular-sanitize.min.js.map 17 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/www/lib/ionic/scss/_animations.scss: -------------------------------------------------------------------------------- 1 | 2 | // Slide up from the bottom, used for modals 3 | // ------------------------------- 4 | 5 | .slide-in-up { 6 | @include translate3d(0, 100%, 0); 7 | } 8 | .slide-in-up.ng-enter, 9 | .slide-in-up > .ng-enter { 10 | @include transition(all cubic-bezier(.1, .7, .1, 1) 400ms); 11 | } 12 | .slide-in-up.ng-enter-active, 13 | .slide-in-up > .ng-enter-active { 14 | @include translate3d(0, 0, 0); 15 | } 16 | 17 | .slide-in-up.ng-leave, 18 | .slide-in-up > .ng-leave { 19 | @include transition(all ease-in-out 250ms); 20 | } 21 | 22 | 23 | // Scale Out 24 | // Scale from hero (1 in this case) to zero 25 | // ------------------------------- 26 | 27 | @-webkit-keyframes scaleOut { 28 | from { -webkit-transform: scale(1); opacity: 1; } 29 | to { -webkit-transform: scale(0.8); opacity: 0; } 30 | } 31 | @keyframes scaleOut { 32 | from { transform: scale(1); opacity: 1; } 33 | to { transform: scale(0.8); opacity: 0; } 34 | } 35 | 36 | 37 | // Super Scale In 38 | // Scale from super (1.x) to duper (1 in this case) 39 | // ------------------------------- 40 | 41 | @-webkit-keyframes superScaleIn { 42 | from { -webkit-transform: scale(1.2); opacity: 0; } 43 | to { -webkit-transform: scale(1); opacity: 1 } 44 | } 45 | @keyframes superScaleIn { 46 | from { transform: scale(1.2); opacity: 0; } 47 | to { transform: scale(1); opacity: 1; } 48 | } 49 | -------------------------------------------------------------------------------- /client/ionic/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: $loading-backdrop-bg-color; 12 | 13 | visibility: hidden; 14 | opacity: 0; 15 | 16 | &.visible { 17 | visibility: visible; 18 | } 19 | &.active { 20 | opacity: 1; 21 | } 22 | 23 | @include transition($loading-backdrop-fadein-duration opacity linear); 24 | } 25 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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: $button-border-radius 0px 0px $button-border-radius; 49 | } 50 | &:last-child { 51 | border-right-width: 1px; 52 | border-radius: 0px $button-border-radius $button-border-radius 0px; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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, $checkbox-on-border-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, $checkbox-off-border-light); 18 | } 19 | .checkbox-stable { 20 | @include checkbox-style($checkbox-off-border-stable, $checkbox-on-bg-stable, $checkbox-off-border-stable); 21 | } 22 | .checkbox-positive { 23 | @include checkbox-style($checkbox-off-border-positive, $checkbox-on-bg-positive, $checkbox-off-border-positive); 24 | } 25 | .checkbox-calm { 26 | @include checkbox-style($checkbox-off-border-calm, $checkbox-on-bg-calm, $checkbox-off-border-calm); 27 | } 28 | .checkbox-assertive { 29 | @include checkbox-style($checkbox-off-border-assertive, $checkbox-on-bg-assertive, $checkbox-off-border-assertive); 30 | } 31 | .checkbox-balanced { 32 | @include checkbox-style($checkbox-off-border-balanced, $checkbox-on-bg-balanced, $checkbox-off-border-balanced); 33 | } 34 | .checkbox-energized{ 35 | @include checkbox-style($checkbox-off-border-energized, $checkbox-on-bg-energized, $checkbox-off-border-energized); 36 | } 37 | .checkbox-royal { 38 | @include checkbox-style($checkbox-off-border-royal, $checkbox-on-bg-royal, $checkbox-off-border-royal); 39 | } 40 | .checkbox-dark { 41 | @include checkbox-style($checkbox-off-border-dark, $checkbox-on-bg-dark, $checkbox-off-border-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 | @include 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: 33%; 96 | left: 25%; 97 | display: table; 98 | width: ($checkbox-width / 2); 99 | height: ($checkbox-width / 4) - 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 | .platform-android .checkbox-platform input:before, 108 | .platform-android .checkbox-platform .checkbox-icon:before, 109 | .checkbox-square input:before, 110 | .checkbox-square .checkbox-icon:before { 111 | border-radius: 2px; 112 | width: 72%; 113 | height: 72%; 114 | margin-top: 14%; 115 | margin-left: 14%; 116 | border-width: 2px; 117 | } 118 | 119 | .platform-android .checkbox-platform input:after, 120 | .platform-android .checkbox-platform .checkbox-icon:after, 121 | .checkbox-square input:after, 122 | .checkbox-square .checkbox-icon:after { 123 | border-width: 2px; 124 | top: 19%; 125 | left: 25%; 126 | width: ($checkbox-width / 2) - 1; 127 | height: 7px; 128 | } 129 | 130 | .grade-c .checkbox input:after, 131 | .grade-c .checkbox-icon:after { 132 | @include rotate(0); 133 | top: 3px; 134 | left: 4px; 135 | border: none; 136 | color: $checkbox-check-color; 137 | content: '\2713'; 138 | font-weight: bold; 139 | font-size: 20px; 140 | } 141 | 142 | // what the checkmark looks like when its checked 143 | .checkbox input:checked:after, 144 | input:checked + .checkbox-icon:after { 145 | opacity: 1; 146 | } 147 | 148 | // make sure item content have enough padding on left to fit the checkbox 149 | .item-checkbox { 150 | padding-left: ($item-padding * 2) + $checkbox-width; 151 | 152 | &.active { 153 | box-shadow: none; 154 | } 155 | } 156 | 157 | // position the checkbox to the left within an item 158 | .item-checkbox .checkbox { 159 | position: absolute; 160 | top: 50%; 161 | right: $item-padding / 2; 162 | left: $item-padding / 2; 163 | z-index: $z-index-item-checkbox; 164 | margin-top: (($checkbox-height + ($checkbox-height / 2)) / 2) * -1; 165 | } 166 | 167 | 168 | .item-checkbox.item-checkbox-right { 169 | padding-right: ($item-padding * 2) + $checkbox-width; 170 | padding-left: $item-padding; 171 | } 172 | 173 | .item-checkbox-right .checkbox input, 174 | .item-checkbox-right .checkbox-icon { 175 | float: right; 176 | } 177 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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: $card-box-shadow; 60 | 61 | .item { 62 | border-left: 0; 63 | border-right: 0; 64 | } 65 | .item:first-child { 66 | border-top: 0; 67 | } 68 | .item:last-child { 69 | border-bottom: 0; 70 | } 71 | } 72 | 73 | .padding { 74 | .card, .list-inset { 75 | margin-left: 0; 76 | margin-right: 0; 77 | } 78 | } 79 | 80 | .card .item, 81 | .list-inset .item, 82 | .padding > .list .item 83 | { 84 | &:first-child { 85 | border-top-left-radius: $card-border-radius; 86 | border-top-right-radius: $card-border-radius; 87 | 88 | .item-content { 89 | border-top-left-radius: $card-border-radius; 90 | border-top-right-radius: $card-border-radius; 91 | } 92 | } 93 | &:last-child { 94 | border-bottom-right-radius: $card-border-radius; 95 | border-bottom-left-radius: $card-border-radius; 96 | 97 | .item-content { 98 | border-bottom-right-radius: $card-border-radius; 99 | border-bottom-left-radius: $card-border-radius; 100 | } 101 | } 102 | } 103 | 104 | .card .item:last-child, 105 | .list-inset .item:last-child { 106 | margin-bottom: $item-border-width * -1; 107 | } 108 | 109 | .card .item, 110 | .list-inset .item, 111 | .padding > .list .item, 112 | .padding-horizontal > .list .item { 113 | margin-right: 0; 114 | margin-left: 0; 115 | 116 | &.item-input input { 117 | padding-right: 44px; 118 | } 119 | } 120 | .padding-left > .list .item { 121 | margin-left: 0; 122 | } 123 | .padding-right > .list .item { 124 | margin-right: 0; 125 | } 126 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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: $bar-height + $bar-subheader-height; 69 | } 70 | .has-tabs-top { 71 | top: $bar-height + $tabs-height; 72 | } 73 | .has-header.has-subheader.has-tabs-top { 74 | top: $bar-height + $bar-subheader-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 | -------------------------------------------------------------------------------- /client/ionic/www/lib/ionic/scss/_platform.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Platform 4 | * -------------------------------------------------- 5 | * Platform specific tweaks 6 | */ 7 | 8 | .platform-ios.platform-cordova { 9 | // iOS7/8 has a status bar which sits on top of the header. 10 | // Bump down everything to make room for it. However, if 11 | // if its in Cordova, and set to fullscreen, then disregard the bump. 12 | &:not(.fullscreen) { 13 | .bar-header:not(.bar-subheader) { 14 | height: $bar-height + $ios-statusbar-height; 15 | 16 | &.item-input-inset .item-input-wrapper { 17 | margin-top: 19px !important; 18 | } 19 | 20 | > * { 21 | margin-top: $ios-statusbar-height; 22 | } 23 | } 24 | .tabs-top > .tabs, 25 | .tabs.tabs-top { 26 | top: $bar-height + $ios-statusbar-height; 27 | } 28 | 29 | .has-header, 30 | .bar-subheader { 31 | top: $bar-height + $ios-statusbar-height; 32 | } 33 | .has-subheader { 34 | top: $bar-height + $bar-subheader-height + $ios-statusbar-height; 35 | } 36 | .has-tabs-top { 37 | top: $bar-height + $tabs-height + $ios-statusbar-height; 38 | } 39 | .has-header.has-subheader.has-tabs-top { 40 | top: $bar-height + $bar-subheader-height + $tabs-height + $ios-statusbar-height; 41 | } 42 | } 43 | &.status-bar-hide { 44 | // Cordova doesn't adjust the body height correctly, this makes up for it 45 | margin-bottom: 20px; 46 | } 47 | } 48 | 49 | @media (orientation:landscape) { 50 | .platform-ios.platform-browser.platform-ipad { 51 | position: fixed; // required for iPad 7 Safari 52 | } 53 | } 54 | 55 | .platform-c:not(.enable-transitions) * { 56 | // disable transitions on grade-c devices (Android 2) 57 | -webkit-transition: none !important; 58 | transition: none !important; 59 | } 60 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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: $range-slider-box-shadow; 31 | cursor: pointer; 32 | -webkit-appearance: none; 33 | border: 0; 34 | } 35 | 36 | &::-webkit-slider-thumb:before { 37 | /* what creates the colorful line on the left side of the slider */ 38 | position: absolute; 39 | top: ($range-slider-height / 2) - ($range-track-height / 2); 40 | left: -2001px; 41 | width: 2000px; 42 | height: $range-track-height; 43 | background: $dark; 44 | content: ' '; 45 | } 46 | 47 | &::-webkit-slider-thumb:after { 48 | /* create a larger (but hidden) hit area */ 49 | position: absolute; 50 | top: -15px; 51 | left: -15px; 52 | padding: 30px; 53 | content: ' '; 54 | //background: red; 55 | //opacity: .5; 56 | } 57 | 58 | } 59 | 60 | .range { 61 | @include display-flex(); 62 | @include align-items(center); 63 | padding: 2px 11px; 64 | 65 | &.range-light { 66 | input { @include range-style($range-light-track-bg); } 67 | } 68 | &.range-stable { 69 | input { @include range-style($range-stable-track-bg); } 70 | } 71 | &.range-positive { 72 | input { @include range-style($range-positive-track-bg); } 73 | } 74 | &.range-calm { 75 | input { @include range-style($range-calm-track-bg); } 76 | } 77 | &.range-balanced { 78 | input { @include range-style($range-balanced-track-bg); } 79 | } 80 | &.range-assertive { 81 | input { @include range-style($range-assertive-track-bg); } 82 | } 83 | &.range-energized { 84 | input { @include range-style($range-energized-track-bg); } 85 | } 86 | &.range-royal { 87 | input { @include range-style($range-royal-track-bg); } 88 | } 89 | &.range-dark { 90 | input { @include range-style($range-dark-track-bg); } 91 | } 92 | } 93 | 94 | .range .icon { 95 | @include flex(0); 96 | display: block; 97 | min-width: $range-icon-size; 98 | text-align: center; 99 | font-size: $range-icon-size; 100 | } 101 | 102 | .range input { 103 | @include flex(1); 104 | display: block; 105 | margin-right: 10px; 106 | margin-left: 10px; 107 | } 108 | 109 | .range-label { 110 | @include flex(0, 0, auto); 111 | display: block; 112 | white-space: nowrap; 113 | } 114 | 115 | .range-label:first-child { 116 | padding-left: 5px; 117 | } 118 | .range input + .range-label { 119 | padding-right: 5px; 120 | padding-left: 0; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/www/lib/ionic/scss/_transitions.scss: -------------------------------------------------------------------------------- 1 | 2 | // iOS View Transitions 3 | // ------------------------------- 4 | 5 | $ios-transition-duration: 450ms !default; 6 | $ios-transition-timing-function: cubic-bezier(.3, .9, .4, 1) !default; 7 | $ios-transition-container-bg-color: #000 !default; 8 | 9 | 10 | [nav-view-transition="ios"] { 11 | 12 | [nav-view="entering"], 13 | [nav-view="leaving"] { 14 | @include transition-duration( $ios-transition-duration ); 15 | @include transition-timing-function( $ios-transition-timing-function ); 16 | -webkit-transition-property: opacity, -webkit-transform; 17 | transition-property: opacity, transform; 18 | } 19 | 20 | &[nav-view-direction="forward"], 21 | &[nav-view-direction="back"] { 22 | background-color: $ios-transition-container-bg-color; 23 | } 24 | 25 | [nav-view="active"], 26 | &[nav-view-direction="forward"] [nav-view="entering"], 27 | &[nav-view-direction="back"] [nav-view="leaving"] { 28 | z-index: $z-index-view-above; 29 | } 30 | 31 | &[nav-view-direction="back"] [nav-view="entering"], 32 | &[nav-view-direction="forward"] [nav-view="leaving"] { 33 | z-index: $z-index-view-below; 34 | } 35 | 36 | } 37 | 38 | 39 | 40 | // iOS Nav Bar Transitions 41 | // ------------------------------- 42 | 43 | [nav-bar-transition="ios"] { 44 | 45 | .title, 46 | .buttons, 47 | .back-text { 48 | @include transition-duration( $ios-transition-duration ); 49 | @include transition-timing-function( $ios-transition-timing-function ); 50 | -webkit-transition-property: opacity, -webkit-transform; 51 | transition-property: opacity, transform; 52 | } 53 | 54 | [nav-bar="active"], 55 | [nav-bar="entering"] { 56 | z-index: $z-index-bar-above; 57 | 58 | .bar { 59 | background: transparent; 60 | } 61 | } 62 | 63 | [nav-bar="cached"] { 64 | display: block; 65 | 66 | .header-item { 67 | display: none; 68 | } 69 | } 70 | 71 | } 72 | 73 | 74 | 75 | // Android View Transitions 76 | // ------------------------------- 77 | 78 | $android-transition-duration: 200ms !default; 79 | $android-transition-timing-function: cubic-bezier(0.4, 0.6, 0.2, 1) !default; 80 | 81 | 82 | [nav-view-transition="android"] { 83 | 84 | [nav-view="entering"], 85 | [nav-view="leaving"] { 86 | @include transition-duration( $android-transition-duration ); 87 | @include transition-timing-function( $android-transition-timing-function ); 88 | -webkit-transition-property: -webkit-transform; 89 | transition-property: transform; 90 | } 91 | 92 | [nav-view="active"], 93 | &[nav-view-direction="forward"] [nav-view="entering"], 94 | &[nav-view-direction="back"] [nav-view="leaving"] { 95 | z-index: $z-index-view-above; 96 | } 97 | 98 | &[nav-view-direction="back"] [nav-view="entering"], 99 | &[nav-view-direction="forward"] [nav-view="leaving"] { 100 | z-index: $z-index-view-below; 101 | } 102 | 103 | } 104 | 105 | 106 | 107 | // Android Nav Bar Transitions 108 | // ------------------------------- 109 | 110 | [nav-bar-transition="android"] { 111 | 112 | .title, 113 | .buttons { 114 | @include transition-duration( $android-transition-duration ); 115 | @include transition-timing-function( $android-transition-timing-function ); 116 | -webkit-transition-property: opacity; 117 | transition-property: opacity; 118 | } 119 | 120 | [nav-bar="active"], 121 | [nav-bar="entering"] { 122 | z-index: $z-index-bar-above; 123 | 124 | .bar { 125 | background: transparent; 126 | } 127 | } 128 | 129 | [nav-bar="cached"] { 130 | display: block; 131 | 132 | .header-item { 133 | display: none; 134 | } 135 | } 136 | 137 | } 138 | 139 | 140 | 141 | // Transition Settings 142 | // ------------------------------- 143 | 144 | [nav-view="cached"], 145 | [nav-bar="cached"] { 146 | display: none; 147 | } 148 | 149 | [nav-view="stage"] { 150 | opacity: 0; 151 | @include transition-duration( 0 ); 152 | } 153 | 154 | [nav-bar="stage"] { 155 | .title, 156 | .buttons, 157 | .back-text { 158 | position: absolute; 159 | opacity: 0; 160 | @include transition-duration(0s); 161 | } 162 | } 163 | 164 | -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/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 | opacity: 0; 73 | @include translate3d(0, 0, 0); 74 | } 75 | .click-block-hide { 76 | @include translate3d(-9999px, 0, 0); 77 | } 78 | 79 | .no-resize { 80 | resize: none; 81 | } 82 | 83 | .block { 84 | display: block; 85 | clear: both; 86 | &:after { 87 | display: block; 88 | visibility: hidden; 89 | clear: both; 90 | height: 0; 91 | content: "."; 92 | } 93 | } 94 | 95 | .full-image { 96 | width: 100%; 97 | } 98 | 99 | .clearfix { 100 | *zoom: 1; 101 | &:before, 102 | &:after { 103 | display: table; 104 | content: ""; 105 | // Fixes Opera/contenteditable bug: 106 | // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 107 | line-height: 0; 108 | } 109 | &:after { 110 | clear: both; 111 | } 112 | } 113 | 114 | /** 115 | * Content Padding 116 | * -------------------------------------------------- 117 | */ 118 | 119 | .padding { 120 | padding: $content-padding; 121 | } 122 | 123 | .padding-top, 124 | .padding-vertical { 125 | padding-top: $content-padding; 126 | } 127 | 128 | .padding-right, 129 | .padding-horizontal { 130 | padding-right: $content-padding; 131 | } 132 | 133 | .padding-bottom, 134 | .padding-vertical { 135 | padding-bottom: $content-padding; 136 | } 137 | 138 | .padding-left, 139 | .padding-horizontal { 140 | padding-left: $content-padding; 141 | } 142 | 143 | 144 | /** 145 | * Rounded 146 | * -------------------------------------------------- 147 | */ 148 | 149 | .rounded { 150 | border-radius: $border-radius-base; 151 | } 152 | 153 | 154 | /** 155 | * Utility Colors 156 | * -------------------------------------------------- 157 | * Utility colors are added to help set a naming convention. You'll 158 | * notice we purposely do not use words like "red" or "blue", but 159 | * instead have colors which represent an emotion or generic theme. 160 | */ 161 | 162 | .light, a.light { 163 | color: $light; 164 | } 165 | .light-bg { 166 | background-color: $light; 167 | } 168 | .light-border { 169 | border-color: $button-light-border; 170 | } 171 | 172 | .stable, a.stable { 173 | color: $stable; 174 | } 175 | .stable-bg { 176 | background-color: $stable; 177 | } 178 | .stable-border { 179 | border-color: $button-stable-border; 180 | } 181 | 182 | .positive, a.positive { 183 | color: $positive; 184 | } 185 | .positive-bg { 186 | background-color: $positive; 187 | } 188 | .positive-border { 189 | border-color: $button-positive-border; 190 | } 191 | 192 | .calm, a.calm { 193 | color: $calm; 194 | } 195 | .calm-bg { 196 | background-color: $calm; 197 | } 198 | .calm-border { 199 | border-color: $button-calm-border; 200 | } 201 | 202 | .assertive, a.assertive { 203 | color: $assertive; 204 | } 205 | .assertive-bg { 206 | background-color: $assertive; 207 | } 208 | .assertive-border { 209 | border-color: $button-assertive-border; 210 | } 211 | 212 | .balanced, a.balanced { 213 | color: $balanced; 214 | } 215 | .balanced-bg { 216 | background-color: $balanced; 217 | } 218 | .balanced-border { 219 | border-color: $button-balanced-border; 220 | } 221 | 222 | .energized, a.energized { 223 | color: $energized; 224 | } 225 | .energized-bg { 226 | background-color: $energized; 227 | } 228 | .energized-border { 229 | border-color: $button-energized-border; 230 | } 231 | 232 | .royal, a.royal { 233 | color: $royal; 234 | } 235 | .royal-bg { 236 | background-color: $royal; 237 | } 238 | .royal-border { 239 | border-color: $button-royal-border; 240 | } 241 | 242 | .dark, a.dark { 243 | color: $dark; 244 | } 245 | .dark-bg { 246 | background-color: $dark; 247 | } 248 | .dark-border { 249 | border-color: $button-dark-border; 250 | } 251 | -------------------------------------------------------------------------------- /client/ionic/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 | "grid", 46 | "util", 47 | "platform", 48 | 49 | // Animations 50 | "animations", 51 | "transitions"; 52 | -------------------------------------------------------------------------------- /client/ionic/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 | // must spin entire element for android 4.3 and below 42 | @extend .#{$ionicons-prefix}spin; 43 | } 44 | 45 | .#{$ionicons-prefix}loading-a { 46 | -webkit-animation-timing-function: steps(8, start); 47 | -moz-animation-timing-function: steps(8, start); 48 | animation-timing-function: steps(8, start); 49 | } 50 | 51 | .#{$ionicons-prefix}loading-a:before { 52 | @extend .#{$ionicons-prefix}load-a:before; 53 | } 54 | 55 | .#{$ionicons-prefix}loading-b:before { 56 | @extend .#{$ionicons-prefix}load-b:before; 57 | } 58 | 59 | .#{$ionicons-prefix}loading-c:before { 60 | @extend .#{$ionicons-prefix}load-c:before; 61 | } 62 | 63 | .#{$ionicons-prefix}loading-d:before { 64 | @extend .#{$ionicons-prefix}load-d:before; 65 | } 66 | 67 | .#{$ionicons-prefix}looping:before { 68 | @extend .#{$ionicons-prefix}loop:before; 69 | } 70 | 71 | .#{$ionicons-prefix}refreshing:before { 72 | @extend .#{$ionicons-prefix}refresh:before; 73 | } 74 | 75 | .#{$ionicons-prefix}ios7-reloading:before { 76 | @extend .#{$ionicons-prefix}ios7-reload:before; 77 | } 78 | -------------------------------------------------------------------------------- /client/ionic/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 | } -------------------------------------------------------------------------------- /client/ionic/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 | -------------------------------------------------------------------------------- /client/ionic/www/lib/ionic/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-beta.14", 3 | "codename": "magnesium-mongoose", 4 | "date": "2014-12-15", 5 | "time": "20:15:38" 6 | } 7 | -------------------------------------------------------------------------------- /client/ionic/www/templates/tab-account.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Enable Something 6 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /client/ionic/www/templates/tab-dash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
This is your Ionic app
5 |
6 |
7 | It includes ionic styles (client/ionic/scss) 8 |
9 |
10 |
11 |
12 |
Common
13 |
14 |
15 | It also includes common styles (client/common/scss) 16 |
17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /client/ionic/www/templates/tab-things.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

{{thing.name}}

6 |
7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /client/ionic/www/templates/tabs.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /client/ionic/www/templates/thing-detail.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 |

{{thing.name}}

10 |

11 | {{thing.info}} 12 |

13 |
14 |
15 | -------------------------------------------------------------------------------- /client/webapp/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "jQuery": true, 23 | "angular": true, 24 | "console": true, 25 | "$": true, 26 | "_": true, 27 | "moment": true, 28 | "describe": true, 29 | "beforeEach": true, 30 | "module": true, 31 | "inject": true, 32 | "it": true, 33 | "expect": true, 34 | "browser": true, 35 | "element": true, 36 | "by": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/webapp/app/account/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('login', { 7 | url: '/login', 8 | templateUrl: 'app/account/login/login.html', 9 | controller: 'LoginCtrl' 10 | }) 11 | .state('signup', { 12 | url: '/signup', 13 | templateUrl: 'app/account/signup/signup.html', 14 | controller: 'SignupCtrl' 15 | }) 16 | .state('settings', { 17 | url: '/settings', 18 | templateUrl: 'app/account/settings/settings.html', 19 | controller: 'SettingsCtrl', 20 | authenticate: true 21 | }); 22 | }); -------------------------------------------------------------------------------- /client/webapp/app/account/login/login.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .controller('LoginCtrl', function ($scope, Auth, $location, $window) { 5 | $scope.user = {}; 6 | $scope.errors = {}; 7 | 8 | $scope.login = function(form) { 9 | $scope.submitted = true; 10 | 11 | if(form.$valid) { 12 | Auth.login({ 13 | email: $scope.user.email, 14 | password: $scope.user.password 15 | }) 16 | .then( function() { 17 | // Logged in, redirect to home 18 | $location.path('/'); 19 | }) 20 | .catch( function(err) { 21 | $scope.errors.other = err.message; 22 | }); 23 | } 24 | }; 25 | 26 | $scope.loginOauth = function(provider) { 27 | $window.location.href = '/auth/' + provider; 28 | }; 29 | }); 30 | -------------------------------------------------------------------------------- /client/webapp/app/account/login/login.jade: -------------------------------------------------------------------------------- 1 | div(ng-include='"components/navbar/navbar.html"') 2 | .container 3 | .row 4 | .col-sm-12 5 | h1 Login 6 | p 7 | | Accounts are reset on server restart from 8 | code server/config/seed.js 9 | | . Default account is 10 | code test@test.com 11 | | / 12 | code test 13 | p 14 | | Admin account is 15 | code admin@admin.com 16 | | / 17 | code admin 18 | 19 | .col-sm-12 20 | form.form(name='form', ng-submit='login(form)', novalidate='') 21 | .form-group 22 | label Email 23 | input.form-control(type='text', name='email', ng-model='user.email') 24 | .form-group 25 | label Password 26 | input.form-control(type='password', name='password', ng-model='user.password') 27 | 28 | .form-group.has-error 29 | p.help-block(ng-show='form.email.$error.required && form.password.$error.required && submitted') 30 | | Please enter your email and password. 31 | p.help-block {{ errors.other }} 32 | 33 | div 34 | button.btn.btn-inverse.btn-lg.btn-login(type='submit') 35 | | Login 36 | = ' ' 37 | a.btn.btn-default.btn-lg.btn-register(href='/signup') 38 | | Register 39 | 40 | hr 41 | 42 | div 43 | a.btn.btn-facebook(href='', ng-click='loginOauth("facebook")') 44 | i.fa.fa-facebook 45 | | Connect with Facebook 46 | = ' ' 47 | a.btn.btn-google-plus(href='', ng-click='loginOauth("google")') 48 | i.fa.fa-google-plus 49 | | Connect with Google+ 50 | = ' ' 51 | a.btn.btn-twitter(href='', ng-click='loginOauth("twitter")') 52 | i.fa.fa-twitter 53 | | Connect with Twitter 54 | hr 55 | -------------------------------------------------------------------------------- /client/webapp/app/account/login/login.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | // -------------------------------------------------- 3 | 4 | $btnText: #fff; 5 | $btnTextAlt: #000; 6 | 7 | $btnFacebookBackground: #3B5998; 8 | $btnFacebookBackgroundHighlight: #133783; 9 | $btnTwitterBackground: #2daddc; 10 | $btnTwitterBackgroundHighlight: #0271bf; 11 | $btnGooglePlusBackground: #dd4b39; 12 | $btnGooglePlusBackgroundHighlight: #c53727; 13 | $btnGithubBackground: #fafafa; 14 | $btnGithubBackgroundHighlight: #ccc; 15 | 16 | // Social buttons 17 | // -------------------------------------------------- 18 | 19 | .btn-facebook { 20 | @include button-variant($btnText, $btnFacebookBackgroundHighlight, $btnFacebookBackgroundHighlight); 21 | } 22 | .btn-twitter { 23 | @include button-variant($btnText, $btnTwitterBackground, $btnTwitterBackgroundHighlight); 24 | } 25 | .btn-google-plus { 26 | @include button-variant($btnText, $btnGooglePlusBackground, $btnGooglePlusBackgroundHighlight); 27 | } 28 | .btn-github { 29 | @include button-variant($btnTextAlt, $btnGithubBackground, $btnGithubBackgroundHighlight); 30 | } 31 | -------------------------------------------------------------------------------- /client/webapp/app/account/settings/settings.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .controller('SettingsCtrl', function ($scope, User, Auth) { 5 | $scope.errors = {}; 6 | 7 | $scope.changePassword = function(form) { 8 | $scope.submitted = true; 9 | if(form.$valid) { 10 | Auth.changePassword( $scope.user.oldPassword, $scope.user.newPassword ) 11 | .then( function() { 12 | $scope.message = 'Password successfully changed.'; 13 | }) 14 | .catch( function() { 15 | form.password.$setValidity('mongoose', false); 16 | $scope.errors.other = 'Incorrect password'; 17 | $scope.message = ''; 18 | }); 19 | } 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /client/webapp/app/account/settings/settings.jade: -------------------------------------------------------------------------------- 1 | div(ng-include='"components/navbar/navbar.html"') 2 | .container 3 | .row 4 | .col-sm-12 5 | h1 Change Password 6 | .col-sm-12 7 | form.form(name='form', ng-submit='changePassword(form)', novalidate='') 8 | .form-group 9 | label Current Password 10 | input.form-control(type='password', name='password', ng-model='user.oldPassword', mongoose-error='') 11 | p.help-block(ng-show='form.password.$error.mongoose') 12 | | {{ errors.other }} 13 | .form-group 14 | label New Password 15 | input.form-control(type='password', name='newPassword', ng-model='user.newPassword', ng-minlength='3', required='') 16 | p.help-block(ng-show='(form.newPassword.$error.minlength || form.newPassword.$error.required) && (form.newPassword.$dirty || submitted)') 17 | | Password must be at least 3 characters. 18 | 19 | p.help-block {{ message }} 20 | 21 | button.btn.btn-lg.btn-primary(type='submit') Save changes 22 | -------------------------------------------------------------------------------- /client/webapp/app/account/signup/signup.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .controller('SignupCtrl', function ($scope, Auth, $location, $window) { 5 | $scope.user = {}; 6 | $scope.errors = {}; 7 | 8 | $scope.register = function(form) { 9 | $scope.submitted = true; 10 | 11 | if(form.$valid) { 12 | Auth.createUser({ 13 | name: $scope.user.name, 14 | email: $scope.user.email, 15 | password: $scope.user.password 16 | }) 17 | .then( function() { 18 | // Account created, redirect to home 19 | $location.path('/'); 20 | }) 21 | .catch( function(err) { 22 | err = err.data; 23 | $scope.errors = {}; 24 | 25 | // Update validity of form fields that match the mongoose errors 26 | angular.forEach(err.errors, function(error, field) { 27 | form[field].$setValidity('mongoose', false); 28 | $scope.errors[field] = error.message; 29 | }); 30 | }); 31 | } 32 | }; 33 | 34 | $scope.loginOauth = function(provider) { 35 | $window.location.href = '/auth/' + provider; 36 | }; 37 | }); 38 | -------------------------------------------------------------------------------- /client/webapp/app/account/signup/signup.jade: -------------------------------------------------------------------------------- 1 | div(ng-include='"components/navbar/navbar.html"') 2 | .container 3 | .row 4 | .col-sm-12 5 | h1 Sign up 6 | .col-sm-12 7 | form.form(name='form', ng-submit='register(form)', novalidate='') 8 | .form-group(ng-class='{ "has-success": form.name.$valid && submitted,\ 9 | "has-error": form.name.$invalid && submitted }') 10 | label Name 11 | input.form-control(type='text', name='name', ng-model='user.name', required='') 12 | p.help-block(ng-show='form.name.$error.required && submitted') 13 | | A name is required 14 | 15 | .form-group(ng-class='{ "has-success": form.email.$valid && submitted,\ 16 | "has-error": form.email.$invalid && submitted }') 17 | label Email 18 | input.form-control(type='email', name='email', ng-model='user.email', required='', mongoose-error='') 19 | p.help-block(ng-show='form.email.$error.email && submitted') 20 | | Doesn't look like a valid email. 21 | p.help-block(ng-show='form.email.$error.required && submitted') 22 | | What's your email address? 23 | p.help-block(ng-show='form.email.$error.mongoose') 24 | | {{ errors.email }} 25 | 26 | .form-group(ng-class='{ "has-success": form.password.$valid && submitted,\ 27 | "has-error": form.password.$invalid && submitted }') 28 | label Password 29 | input.form-control(type='password', name='password', ng-model='user.password', ng-minlength='3', required='', mongoose-error='') 30 | p.help-block(ng-show='(form.password.$error.minlength || form.password.$error.required) && submitted') 31 | | Password must be at least 3 characters. 32 | p.help-block(ng-show='form.password.$error.mongoose') 33 | | {{ errors.password }} 34 | 35 | div 36 | button.btn.btn-inverse.btn-lg.btn-login(type='submit') 37 | | Sign up 38 | = ' ' 39 | a.btn.btn-default.btn-lg.btn-register(href='/login') 40 | | Login 41 | 42 | 43 | hr 44 | 45 | div 46 | a.btn.btn-facebook(href='', ng-click='loginOauth("facebook")') 47 | i.fa.fa-facebook 48 | | Connect with Facebook 49 | = ' ' 50 | a.btn.btn-google-plus(href='', ng-click='loginOauth("google")') 51 | i.fa.fa-google-plus 52 | | Connect with Google+ 53 | = ' ' 54 | a.btn.btn-twitter(href='', ng-click='loginOauth("twitter")') 55 | i.fa.fa-twitter 56 | | Connect with Twitter 57 | hr 58 | -------------------------------------------------------------------------------- /client/webapp/app/admin/admin.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .controller('AdminCtrl', function ($scope, $http, Auth, User) { 5 | 6 | // Use the User $resource to fetch all users 7 | $scope.users = User.query(); 8 | 9 | $scope.delete = function(user) { 10 | User.remove({ id: user._id }); 11 | angular.forEach($scope.users, function(u, i) { 12 | if (u === user) { 13 | $scope.users.splice(i, 1); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /client/webapp/app/admin/admin.jade: -------------------------------------------------------------------------------- 1 | div(ng-include='"components/navbar/navbar.html"') 2 | .container 3 | p 4 | | The delete user and user index api routes are restricted to users with the 'admin' role. 5 | ul.list-group 6 | li.list-group-item(ng-repeat='user in users') 7 | strong {{user.name}} 8 | br 9 | span.text-muted {{user.email}} 10 | a.trash(ng-click='delete(user)') 11 | span.glyphicon.glyphicon-trash.pull-right -------------------------------------------------------------------------------- /client/webapp/app/admin/admin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('admin', { 7 | url: '/admin', 8 | templateUrl: 'app/admin/admin.html', 9 | controller: 'AdminCtrl' 10 | }); 11 | }); -------------------------------------------------------------------------------- /client/webapp/app/admin/admin.scss: -------------------------------------------------------------------------------- 1 | .trash { color:rgb(209, 91, 71); } 2 | -------------------------------------------------------------------------------- /client/webapp/app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp', [ 4 | 'ngCookies', 5 | 'ngResource', 6 | 'ngSanitize', 7 | 'ui.router', 8 | 'ui.bootstrap' 9 | ]) 10 | .config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) { 11 | $urlRouterProvider 12 | .otherwise('/'); 13 | 14 | $locationProvider.html5Mode(true); 15 | $httpProvider.interceptors.push('authInterceptor'); 16 | }) 17 | 18 | .factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) { 19 | return { 20 | // Add authorization token to headers 21 | request: function (config) { 22 | config.headers = config.headers || {}; 23 | if ($cookieStore.get('token')) { 24 | config.headers.Authorization = 'Bearer ' + $cookieStore.get('token'); 25 | } 26 | return config; 27 | }, 28 | 29 | // Intercept 401s and redirect you to login 30 | responseError: function(response) { 31 | if(response.status === 401) { 32 | $location.path('/login'); 33 | // remove any stale tokens 34 | $cookieStore.remove('token'); 35 | return $q.reject(response); 36 | } 37 | else { 38 | return $q.reject(response); 39 | } 40 | } 41 | }; 42 | }) 43 | 44 | .run(function ($rootScope, $location, Auth) { 45 | // Redirect to login if route requires auth and you're not logged in 46 | $rootScope.$on('$stateChangeStart', function (event, next) { 47 | Auth.isLoggedInAsync(function(loggedIn) { 48 | if (next.authenticate && !loggedIn) { 49 | $location.path('/login'); 50 | } 51 | }); 52 | }); 53 | }); -------------------------------------------------------------------------------- /client/webapp/app/app.scss: -------------------------------------------------------------------------------- 1 | /** 2 | Create webapp wide styles in client/webapp/scss folder 3 | There should be no need to edit this file 4 | */ 5 | 6 | $icon-font-path: "/bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/"; 7 | $fa-font-path: "/bower_components/font-awesome/fonts"; 8 | 9 | @import 'bootstrap-sass-official/vendor/assets/stylesheets/bootstrap'; 10 | @import 'font-awesome/scss/font-awesome'; 11 | 12 | // Webapp component (including folders in app directory) styles are injected through grunt 13 | // injector 14 | @import 'account/login/login.scss'; 15 | @import 'admin/admin.scss'; 16 | @import 'main/main.scss'; 17 | @import 'modal/modal.scss'; 18 | // endinjector 19 | 20 | @import '../scss/webapp.scss'; 21 | 22 | .browsehappy { 23 | margin: 0.2em 0; 24 | background: #ccc; 25 | color: #000; 26 | padding: 0.2em 0; 27 | } -------------------------------------------------------------------------------- /client/webapp/app/main/main.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .controller('MainCtrl', function ($scope, $http) { 5 | $scope.awesomeThings = []; 6 | 7 | $http.get('/api/things').success(function(awesomeThings) { 8 | $scope.awesomeThings = awesomeThings; 9 | }); 10 | 11 | $scope.addThing = function() { 12 | if($scope.newThing === '') { 13 | return; 14 | } 15 | $http.post('/api/things', { name: $scope.newThing }); 16 | $scope.newThing = ''; 17 | }; 18 | 19 | $scope.deleteThing = function(thing) { 20 | $http.delete('/api/things/' + thing._id); 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /client/webapp/app/main/main.controller.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('starterApp')); 7 | 8 | var MainCtrl, 9 | scope, 10 | $httpBackend; 11 | 12 | // Initialize the controller and a mock scope 13 | beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) { 14 | $httpBackend = _$httpBackend_; 15 | $httpBackend.expectGET('/api/things') 16 | .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']); 17 | 18 | scope = $rootScope.$new(); 19 | MainCtrl = $controller('MainCtrl', { 20 | $scope: scope 21 | }); 22 | })); 23 | 24 | it('should attach a list of things to the scope', function () { 25 | $httpBackend.flush(); 26 | expect(scope.awesomeThings.length).toBe(4); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /client/webapp/app/main/main.jade: -------------------------------------------------------------------------------- 1 | div(ng-include='"components/navbar/navbar.html"') 2 | 3 | header#banner.hero-unit 4 | .container 5 | h1 'Allo, 'Allo! 6 | p.lead Kick-start your next web and Ionic app with Angular Fullstack Ionic 7 | img(src='common/assets/images/yeoman.png', alt='I\'m Yeoman') 8 | 9 | .container 10 | .row 11 | .col-lg-12 12 | h1.page-header Angular Fullstack Ionic 13 | h2 This is your webapp 14 | .panel.webapp-class 15 | .panel-body Your webapp includes webapp styles (client/webapp/scss) 16 | .panel.common-class 17 | .panel-body It also includes common styles (client/common/scss) 18 | .row 19 | .col-lg-12 20 | h3 Based on Angular Fullstack 21 | ul.nav.nav-tabs.nav-stacked.col-md-4.col-lg-4.col-sm-6(ng-repeat='thing in awesomeThings') 22 | li 23 | a(href='#', tooltip='{{thing.info}}') 24 | | {{thing.name}} 25 | 26 | footer.footer 27 | .container 28 | p 29 | | Angular Fullstack v2.0.13 30 | = ' | ' 31 | a(href='https://twitter.com/tyhenkel') @tyhenkel 32 | = ' | ' 33 | a(href='https://github.com/DaftMonk/generator-angular-fullstack/issues?state=open') Issues 34 | p 35 | | Angular Fullstack Ionic v0.0.1 36 | = ' | ' 37 | a(href='https://twitter.com/richardgsands') @richardgsands 38 | = ' | ' 39 | a(href='https://github.com/richardgsands/generator-angular-fullstack-ionic/issues?state=open') Issues -------------------------------------------------------------------------------- /client/webapp/app/main/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('main', { 7 | url: '/', 8 | templateUrl: 'app/main/main.html', 9 | controller: 'MainCtrl' 10 | }); 11 | }); -------------------------------------------------------------------------------- /client/webapp/app/main/main.scss: -------------------------------------------------------------------------------- 1 | .thing-form { 2 | margin: 20px 0; 3 | } 4 | 5 | #banner { 6 | border-bottom: none; 7 | margin-top: -20px; 8 | } 9 | 10 | #banner h1 { 11 | font-size: 60px; 12 | line-height: 1; 13 | letter-spacing: -1px; 14 | } 15 | 16 | .hero-unit { 17 | position: relative; 18 | padding: 30px 15px; 19 | color: #F5F5F5; 20 | text-align: center; 21 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); 22 | background: #4393B9; 23 | } 24 | 25 | .footer { 26 | text-align: center; 27 | padding: 30px 0; 28 | margin-top: 70px; 29 | border-top: 1px solid #E5E5E5; 30 | } -------------------------------------------------------------------------------- /client/webapp/components/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .factory('Auth', function Auth($location, $rootScope, $http, User, $cookieStore, $q) { 5 | var currentUser = {}; 6 | if($cookieStore.get('token')) { 7 | currentUser = User.get(); 8 | } 9 | 10 | return { 11 | 12 | /** 13 | * Authenticate user and save token 14 | * 15 | * @param {Object} user - login info 16 | * @param {Function} callback - optional 17 | * @return {Promise} 18 | */ 19 | login: function(user, callback) { 20 | var cb = callback || angular.noop; 21 | var deferred = $q.defer(); 22 | 23 | $http.post('/auth/local', { 24 | email: user.email, 25 | password: user.password 26 | }). 27 | success(function(data) { 28 | $cookieStore.put('token', data.token); 29 | currentUser = User.get(); 30 | deferred.resolve(data); 31 | return cb(); 32 | }). 33 | error(function(err) { 34 | this.logout(); 35 | deferred.reject(err); 36 | return cb(err); 37 | }.bind(this)); 38 | 39 | return deferred.promise; 40 | }, 41 | 42 | /** 43 | * Delete access token and user info 44 | * 45 | * @param {Function} 46 | */ 47 | logout: function() { 48 | $cookieStore.remove('token'); 49 | currentUser = {}; 50 | }, 51 | 52 | /** 53 | * Create a new user 54 | * 55 | * @param {Object} user - user info 56 | * @param {Function} callback - optional 57 | * @return {Promise} 58 | */ 59 | createUser: function(user, callback) { 60 | var cb = callback || angular.noop; 61 | 62 | return User.save(user, 63 | function(data) { 64 | $cookieStore.put('token', data.token); 65 | currentUser = User.get(); 66 | return cb(user); 67 | }, 68 | function(err) { 69 | this.logout(); 70 | return cb(err); 71 | }.bind(this)).$promise; 72 | }, 73 | 74 | /** 75 | * Change password 76 | * 77 | * @param {String} oldPassword 78 | * @param {String} newPassword 79 | * @param {Function} callback - optional 80 | * @return {Promise} 81 | */ 82 | changePassword: function(oldPassword, newPassword, callback) { 83 | var cb = callback || angular.noop; 84 | 85 | return User.changePassword({ id: currentUser._id }, { 86 | oldPassword: oldPassword, 87 | newPassword: newPassword 88 | }, function(user) { 89 | return cb(user); 90 | }, function(err) { 91 | return cb(err); 92 | }).$promise; 93 | }, 94 | 95 | /** 96 | * Gets all available info on authenticated user 97 | * 98 | * @return {Object} user 99 | */ 100 | getCurrentUser: function() { 101 | return currentUser; 102 | }, 103 | 104 | /** 105 | * Check if a user is logged in 106 | * 107 | * @return {Boolean} 108 | */ 109 | isLoggedIn: function() { 110 | return currentUser.hasOwnProperty('role'); 111 | }, 112 | 113 | /** 114 | * Waits for currentUser to resolve before checking if user is logged in 115 | */ 116 | isLoggedInAsync: function(cb) { 117 | if(currentUser.hasOwnProperty('$promise')) { 118 | currentUser.$promise.then(function() { 119 | cb(true); 120 | }).catch(function() { 121 | cb(false); 122 | }); 123 | } else if(currentUser.hasOwnProperty('role')) { 124 | cb(true); 125 | } else { 126 | cb(false); 127 | } 128 | }, 129 | 130 | /** 131 | * Check if a user is an admin 132 | * 133 | * @return {Boolean} 134 | */ 135 | isAdmin: function() { 136 | return currentUser.role === 'admin'; 137 | }, 138 | 139 | /** 140 | * Get auth token 141 | */ 142 | getToken: function() { 143 | return $cookieStore.get('token'); 144 | } 145 | }; 146 | }); 147 | -------------------------------------------------------------------------------- /client/webapp/components/auth/user.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .factory('User', function ($resource) { 5 | return $resource('/api/users/:id/:controller', { 6 | id: '@_id' 7 | }, 8 | { 9 | changePassword: { 10 | method: 'PUT', 11 | params: { 12 | controller:'password' 13 | } 14 | }, 15 | get: { 16 | method: 'GET', 17 | params: { 18 | id:'me' 19 | } 20 | } 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /client/webapp/components/modal/modal.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(ng-if='modal.dismissable', type='button', ng-click='$dismiss()') × 3 | h4.modal-title(ng-if='modal.title', ng-bind='modal.title') 4 | .modal-body 5 | p(ng-if='modal.text', ng-bind='modal.text') 6 | div(ng-if='modal.html', ng-bind-html='modal.html') 7 | .modal-footer 8 | button.btn(ng-repeat='button in modal.buttons', ng-class='button.classes', ng-click='button.click($event)', ng-bind='button.text') 9 | -------------------------------------------------------------------------------- /client/webapp/components/modal/modal.scss: -------------------------------------------------------------------------------- 1 | .modal-primary, 2 | .modal-info, 3 | .modal-success, 4 | .modal-warning, 5 | .modal-danger { 6 | .modal-header { 7 | color: #fff; 8 | border-radius: 5px 5px 0 0; 9 | } 10 | } 11 | .modal-primary .modal-header { 12 | background: $brand-primary; 13 | } 14 | .modal-info .modal-header { 15 | background: $brand-info; 16 | } 17 | .modal-success .modal-header { 18 | background: $brand-success; 19 | } 20 | .modal-warning .modal-header { 21 | background: $brand-warning; 22 | } 23 | .modal-danger .modal-header { 24 | background: $brand-danger; 25 | } 26 | -------------------------------------------------------------------------------- /client/webapp/components/modal/modal.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .factory('Modal', function ($rootScope, $modal) { 5 | /** 6 | * Opens a modal 7 | * @param {Object} scope - an object to be merged with modal's scope 8 | * @param {String} modalClass - (optional) class(es) to be applied to the modal 9 | * @return {Object} - the instance $modal.open() returns 10 | */ 11 | function openModal(scope, modalClass) { 12 | var modalScope = $rootScope.$new(); 13 | scope = scope || {}; 14 | modalClass = modalClass || 'modal-default'; 15 | 16 | angular.extend(modalScope, scope); 17 | 18 | return $modal.open({ 19 | templateUrl: 'components/modal/modal.html', 20 | windowClass: modalClass, 21 | scope: modalScope 22 | }); 23 | } 24 | 25 | // Public API here 26 | return { 27 | 28 | /* Confirmation modals */ 29 | confirm: { 30 | 31 | /** 32 | * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)') 33 | * @param {Function} del - callback, ran when delete is confirmed 34 | * @return {Function} - the function to open the modal (ex. myModalFn) 35 | */ 36 | delete: function(del) { 37 | del = del || angular.noop; 38 | 39 | /** 40 | * Open a delete confirmation modal 41 | * @param {String} name - name or info to show on modal 42 | * @param {All} - any additional args are passed staight to del callback 43 | */ 44 | return function() { 45 | var args = Array.prototype.slice.call(arguments), 46 | name = args.shift(), 47 | deleteModal; 48 | 49 | deleteModal = openModal({ 50 | modal: { 51 | dismissable: true, 52 | title: 'Confirm Delete', 53 | html: '

Are you sure you want to delete ' + name + ' ?

', 54 | buttons: [{ 55 | classes: 'btn-danger', 56 | text: 'Delete', 57 | click: function(e) { 58 | deleteModal.close(e); 59 | } 60 | }, { 61 | classes: 'btn-default', 62 | text: 'Cancel', 63 | click: function(e) { 64 | deleteModal.dismiss(e); 65 | } 66 | }] 67 | } 68 | }, 'modal-danger'); 69 | 70 | deleteModal.result.then(function(event) { 71 | del.apply(event, args); 72 | }); 73 | }; 74 | } 75 | } 76 | }; 77 | }); 78 | -------------------------------------------------------------------------------- /client/webapp/components/mongoose-error/mongoose-error.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Removes server error when user updates input 5 | */ 6 | angular.module('starterApp') 7 | .directive('mongooseError', function () { 8 | return { 9 | restrict: 'A', 10 | require: 'ngModel', 11 | link: function(scope, element, attrs, ngModel) { 12 | element.on('keydown', function() { 13 | return ngModel.$setValidity('mongoose', true); 14 | }); 15 | } 16 | }; 17 | }); -------------------------------------------------------------------------------- /client/webapp/components/navbar/navbar.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('starterApp') 4 | .controller('NavbarCtrl', function ($scope, $location, Auth) { 5 | $scope.menu = [{ 6 | 'title': 'Home', 7 | 'link': '/' 8 | }]; 9 | 10 | $scope.isCollapsed = true; 11 | $scope.isLoggedIn = Auth.isLoggedIn; 12 | $scope.isAdmin = Auth.isAdmin; 13 | $scope.getCurrentUser = Auth.getCurrentUser; 14 | 15 | $scope.logout = function() { 16 | Auth.logout(); 17 | $location.path('/login'); 18 | }; 19 | 20 | $scope.isActive = function(route) { 21 | return route === $location.path(); 22 | }; 23 | }); -------------------------------------------------------------------------------- /client/webapp/components/navbar/navbar.jade: -------------------------------------------------------------------------------- 1 | div.navbar.navbar-default.navbar-static-top(ng-controller='NavbarCtrl') 2 | div.container 3 | div.navbar-header 4 | button.navbar-toggle(type='button', ng-click='isCollapsed = !isCollapsed') 5 | span.sr-only Toggle navigation 6 | span.icon-bar 7 | span.icon-bar 8 | span.icon-bar 9 | a.navbar-brand(href='/') Starter 10 | 11 | div#navbar-main.navbar-collapse.collapse(collapse='isCollapsed') 12 | ul.nav.navbar-nav 13 | li(ng-repeat='item in menu', ng-class='{active: isActive(item.link)}') 14 | a(ng-href='{{item.link}}') {{item.title}} 15 | 16 | li(ng-show='isAdmin()', ng-class='{active: isActive("/admin")}') 17 | a(href='/admin') Admin 18 | 19 | ul.nav.navbar-nav.navbar-right 20 | li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/signup")}') 21 | a(href='/signup') Sign up 22 | 23 | li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/login")}') 24 | a(href='/login') Login 25 | 26 | li(ng-show='isLoggedIn()') 27 | p.navbar-text Hello {{ getCurrentUser().name }} 28 | 29 | li(ng-show='isLoggedIn()', ng-class='{active: isActive("/settings")}') 30 | a(href='/settings') 31 | span.glyphicon.glyphicon-cog 32 | 33 | li(ng-show='isLoggedIn()', ng-class='{active: isActive("/logout")}') 34 | a(href='', ng-click='logout()') Logout -------------------------------------------------------------------------------- /client/webapp/favicon.ico: -------------------------------------------------------------------------------- 1 |   �( @   -2Op"=p�Jt��Jt��b���������������������������������������������������b���Jt��Jt��"=p�Op-2O`O�O�O�O�O�O�O� $\�Jt��������������v���v���������������Jt�� $\�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O� ;n�s���>���>���>���>���s��� ;n�O�O�O�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O� $\�]���^n��^n��]��� $\�O�O�O�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O�O�n�*��*��n�O�O�O�O�O�O�O�O�O�O�O�  O�O�O�O�O�O�O�O�O�O�O�5>Y�5>Y�O�O�O�O�O�O�O�O�O�O�O�  -2O�O�O�O�O�O�O�O�O�O�&6e�&6e�O�O�O�O�O�O�O�O�O�O�-25r�4���E��� $\�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O� $\�E���4���5r�5r�E���M���M���v���0\��O�O�O�O�O�O�O� $\� $\�O�O�O�O�O�O�O�0\��v���M���M���E���5r�)��p&��p��&��������������b���Jt��Jt��Jt��0\��#i��.r��.r��#i��0\��Jt��Jt��Jt��b���������������&��p��&��)��p4���&��-���_������������������]���]�������7���p�����������p���7�������]���]�������������������_��-���-���4���qֈp��p��p����������������������p���7���#i��p�����������p���#i��7���p�����������������������p��&��-���qֈ8��(p��p��I���v���v���]���7���n���v���p���#i��]���v���v���]���#i��p���v���n���7���]���v���v���I���-���-���8��(;��`-���M���7���7���7���.r��R��E��R��E��7���7���7���7���E��R��E��R��.r��7���7���7���M���M���;��`���������������������������z��������������������������� 2 | �  ��� 3 | � 9� 9� 9� 9� 9� 9� 9� 9� 4 |  �n�n� 5 |  � 9� 9� 9� 9� 9� 9� 9� 9� 6 | ����*�x*��*��*��*��*��*��*��n�&��#��&��&��n�*��*��*��*��*��*��*��*�x*ݟ*��*��*��*��*��*��!��#��&��#��&��*��!��!��*��*��*��*��*��*��*ݟ*ݿ*��*��*��*��*��*��n�*��*�� 9� 9�*��*���*��*��*��*��*��*��*ݿ*��*��*��*��*��*��*��!��#��&��&��&��*��#��!��*��*��*��*��*��*��*��  ��������I�&��&��&��&��I���������  U��������� 7 |  �n�n� 8 |  ����������-2z����������������������z������������������������ 9 | ����������������������� 10 | ������������������������� 11 | ������������������������-2����������������������U�������������������z5r������������������-25r�U�����������z  ������������������������������?��� -------------------------------------------------------------------------------- /client/webapp/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 | 29 | 30 | 31 |
32 | 33 | 34 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /client/webapp/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /client/webapp/scss/_colours.scss: -------------------------------------------------------------------------------- 1 | .webapp-class { 2 | background: #4393B9; 3 | color: #F5F5F2; 4 | } -------------------------------------------------------------------------------- /client/webapp/scss/_fonts.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/webapp/scss/_fonts.scss -------------------------------------------------------------------------------- /client/webapp/scss/_layout.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardgsands/angular-fullstack-ionic-starter/d50735b8db0f55a0992a9b373bde5ce78e5d29e4/client/webapp/scss/_layout.scss -------------------------------------------------------------------------------- /client/webapp/scss/webapp.scss: -------------------------------------------------------------------------------- 1 | /** 2 | Include all your webapp wide styles in the files below 3 | */ 4 | 5 | @import 'colours'; 6 | @import 'fonts'; 7 | @import 'layout'; 8 | -------------------------------------------------------------------------------- /dist-ionic/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "www/lib" 3 | } 4 | -------------------------------------------------------------------------------- /dist-ionic/.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 | -------------------------------------------------------------------------------- /dist-ionic/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic", 3 | "private": "true", 4 | "devDependencies": { 5 | "ionic": "driftyco/ionic-bower#1.0.0-beta.14" 6 | } 7 | } -------------------------------------------------------------------------------- /dist-ionic/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ionic 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 | -------------------------------------------------------------------------------- /dist-ionic/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 | -------------------------------------------------------------------------------- /dist-ionic/hooks/README.md: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /dist-ionic/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 grunt serve 11 | Running "serve" task 12 | 13 | Running "clean:server" (clean) task 14 | Cleaning .tmp...OK 15 | 16 | Running "env:all" (env) task 17 | 18 | Running "injector:sass" (injector) task 19 | Missing option `template`, using `dest` as template instead 20 | Injecting scss files (4 files) 21 | 22 | Running "concurrent:server" (concurrent) task 23 | 24 | Running "jade:compile" (jade) task 25 | File .tmp/app/account/login/login.html created. 26 | File .tmp/app/account/settings/settings.html created. 27 | File .tmp/app/account/signup/signup.html created. 28 | File .tmp/app/admin/admin.html created. 29 | File .tmp/app/main/main.html created. 30 | File .tmp/components/modal/modal.html created. 31 | File .tmp/components/navbar/navbar.html created. 32 | 33 | Done, without errors. 34 | 35 | 36 | Execution Time (2015-01-28 14:08:46 UTC) 37 | loading tasks 90ms ▇▇▇▇▇▇▇▇▇▇ 20% 38 | jade:compile 357ms ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 80% 39 | Total 448ms 40 | 41 | Running "sass:server" (sass) task 42 | 43 | Done, without errors. 44 | 45 | 46 | Execution Time (2015-01-28 14:08:46 UTC) 47 | loading tasks 97ms ▇▇▇▇ 8% 48 | sass:server 1.1s ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 92% 49 | Total 1.2s 50 | 51 | Running "injector:scripts" (injector) task 52 | Missing option `template`, using `dest` as template instead 53 | Injecting js files (13 files) 54 | 55 | Running "injector:sass" (injector) task 56 | Missing option `template`, using `dest` as template instead 57 | Injecting scss files (4 files) 58 | 59 | Running "injector:css" (injector) task 60 | Missing option `template`, using `dest` as template instead 61 | >> Nothing changed 62 | 63 | Running "wiredep:target" (wiredep) task 64 | 65 | Running "autoprefixer:dist" (autoprefixer) task 66 | File .tmp/app/app.css created. 67 | 68 | Running "express:dev" (express) task 69 | Starting background Express server 70 | debugger listening on port 5858 71 | Express server listening on 9000, in development mode 72 | 73 | Running "wait" task 74 | >> Waiting for server reload... 75 | finished populating users 76 | Done waiting! 77 | 78 | Running "open:server" (open) task 79 | 80 | Running "watch" task 81 | Waiting... 82 | GET /api/things 200 58ms 83 | 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tmp", 3 | "version": "0.0.0", 4 | "main": "server/app.js", 5 | "dependencies": { 6 | "express": "~4.9.0", 7 | "morgan": "~1.0.0", 8 | "body-parser": "~1.5.0", 9 | "method-override": "~1.0.0", 10 | "serve-favicon": "~2.0.1", 11 | "cookie-parser": "~1.0.1", 12 | "express-session": "~1.0.2", 13 | "errorhandler": "~1.0.0", 14 | "compression": "~1.0.1", 15 | "lodash": "~2.4.1", 16 | "jade": "~1.2.0", 17 | "mongoose": "~3.8.8", 18 | "jsonwebtoken": "^0.3.0", 19 | "express-jwt": "^0.1.3", 20 | "passport": "~0.2.0", 21 | "passport-local": "~0.1.6", 22 | "passport-facebook": "latest", 23 | "passport-twitter": "latest", 24 | "passport-google-oauth": "latest", 25 | "composable-middleware": "^0.3.0", 26 | "connect-mongo": "^0.4.1" 27 | }, 28 | "devDependencies": { 29 | "connect-livereload": "~0.4.0", 30 | "grunt": "~0.4.4", 31 | "grunt-angular-templates": "^0.5.4", 32 | "grunt-autoprefixer": "~0.7.2", 33 | "grunt-build-control": "DaftMonk/grunt-build-control", 34 | "grunt-concurrent": "~0.5.0", 35 | "grunt-contrib-clean": "~0.5.0", 36 | "grunt-contrib-concat": "~0.4.0", 37 | "grunt-contrib-copy": "~0.5.0", 38 | "grunt-contrib-cssmin": "~0.9.0", 39 | "grunt-contrib-htmlmin": "~0.2.0", 40 | "grunt-contrib-imagemin": "~0.7.1", 41 | "grunt-contrib-jade": "^0.11.0", 42 | "grunt-contrib-jshint": "~0.10.0", 43 | "grunt-contrib-sass": "^0.7.3", 44 | "grunt-contrib-uglify": "~0.4.0", 45 | "grunt-contrib-watch": "~0.6.1", 46 | "grunt-debug-task": "^0.1.5", 47 | "grunt-dom-munger": "^3.4.0", 48 | "grunt-env": "~0.4.1", 49 | "grunt-express-server": "~0.4.17", 50 | "grunt-google-cdn": "~0.4.0", 51 | "grunt-injector": "~0.5.4", 52 | "grunt-karma": "~0.8.2", 53 | "grunt-mocha-test": "~0.10.2", 54 | "grunt-newer": "~0.7.0", 55 | "grunt-ng-annotate": "^0.2.3", 56 | "grunt-node-inspector": "~0.1.5", 57 | "grunt-nodemon": "~0.2.0", 58 | "grunt-open": "~0.2.3", 59 | "grunt-protractor-runner": "^1.1.0", 60 | "grunt-rev": "~0.1.0", 61 | "grunt-svgmin": "~0.4.0", 62 | "grunt-usemin": "~2.1.1", 63 | "grunt-wiredep": "~1.8.0", 64 | "jit-grunt": "^0.5.0", 65 | "jshint-stylish": "~0.1.5", 66 | "karma": "~0.12.9", 67 | "karma-chrome-launcher": "~0.1.3", 68 | "karma-coffee-preprocessor": "~0.2.1", 69 | "karma-firefox-launcher": "~0.1.3", 70 | "karma-html2js-preprocessor": "~0.1.0", 71 | "karma-jade-preprocessor": "0.0.11", 72 | "karma-jasmine": "~0.1.5", 73 | "karma-ng-html2js-preprocessor": "~0.1.0", 74 | "karma-ng-jade2js-preprocessor": "^0.1.2", 75 | "karma-ng-scenario": "~0.1.0", 76 | "karma-phantomjs-launcher": "~0.1.4", 77 | "karma-requirejs": "~0.2.1", 78 | "karma-script-launcher": "~0.1.0", 79 | "open": "~0.0.4", 80 | "requirejs": "~2.1.11", 81 | "should": "~3.3.1", 82 | "supertest": "~0.11.0", 83 | "time-grunt": "~0.3.1" 84 | }, 85 | "engines": { 86 | "node": ">=0.10.0" 87 | }, 88 | "scripts": { 89 | "start": "node server/app.js", 90 | "test": "grunt test", 91 | "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update" 92 | }, 93 | "private": true 94 | } 95 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration 2 | // https://github.com/angular/protractor/blob/master/referenceConf.js 3 | 4 | 'use strict'; 5 | 6 | exports.config = { 7 | // The timeout for each script run on the browser. This should be longer 8 | // than the maximum time your application needs to stabilize between tasks. 9 | allScriptsTimeout: 110000, 10 | 11 | // A base URL for your application under test. Calls to protractor.get() 12 | // with relative paths will be prepended with this. 13 | baseUrl: 'http://localhost:' + (process.env.PORT || '9000'), 14 | 15 | // If true, only chromedriver will be started, not a standalone selenium. 16 | // Tests for browsers other than chrome will not run. 17 | chromeOnly: true, 18 | 19 | // list of files / patterns to load in the browser 20 | specs: [ 21 | 'e2e/**/*.spec.js' 22 | ], 23 | 24 | // Patterns to exclude. 25 | exclude: [], 26 | 27 | // ----- Capabilities to be passed to the webdriver instance ---- 28 | // 29 | // For a full list of available capabilities, see 30 | // https://code.google.com/p/selenium/wiki/DesiredCapabilities 31 | // and 32 | // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js 33 | capabilities: { 34 | 'browserName': 'chrome' 35 | }, 36 | 37 | // ----- The test framework ----- 38 | // 39 | // Jasmine and Cucumber are fully supported as a test and assertion framework. 40 | // Mocha has limited beta support. You will need to include your own 41 | // assertion framework if working with mocha. 42 | framework: 'jasmine', 43 | 44 | // ----- Options to be passed to minijasminenode ----- 45 | // 46 | // See the full list at https://github.com/juliemr/minijasminenode 47 | jasmineNodeOpts: { 48 | defaultTimeoutInterval: 30000 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /server/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "eqeqeq": true, 6 | "immed": true, 7 | "latedef": "nofunc", 8 | "newcap": true, 9 | "noarg": true, 10 | "regexp": true, 11 | "undef": true, 12 | "smarttabs": true, 13 | "asi": true, 14 | "debug": true 15 | } 16 | -------------------------------------------------------------------------------- /server/.jshintrc-spec: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ".jshintrc", 3 | "globals": { 4 | "describe": true, 5 | "it": true, 6 | "before": true, 7 | "beforeEach": true, 8 | "after": true, 9 | "afterEach": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /server/api/thing/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./thing.controller'); 5 | 6 | var router = express.Router(); 7 | 8 | router.get('/', controller.index); 9 | router.get('/:id', controller.show); 10 | router.post('/', controller.create); 11 | router.put('/:id', controller.update); 12 | router.patch('/:id', controller.update); 13 | router.delete('/:id', controller.destroy); 14 | 15 | module.exports = router; -------------------------------------------------------------------------------- /server/api/thing/thing.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Using Rails-like standard naming convention for endpoints. 3 | * GET /things -> index 4 | * POST /things -> create 5 | * GET /things/:id -> show 6 | * PUT /things/:id -> update 7 | * DELETE /things/:id -> destroy 8 | */ 9 | 10 | 'use strict'; 11 | 12 | var _ = require('lodash'); 13 | var Thing = require('./thing.model'); 14 | 15 | // Get list of things 16 | exports.index = function(req, res) { 17 | Thing.find(function (err, things) { 18 | if(err) { return handleError(res, err); } 19 | return res.status(200).json(things); 20 | }); 21 | }; 22 | 23 | // Get a single thing 24 | exports.show = function(req, res) { 25 | Thing.findById(req.params.id, function (err, thing) { 26 | if(err) { return handleError(res, err); } 27 | if(!thing) { return res.status(404).send('Not Found'); } 28 | return res.json(thing); 29 | }); 30 | }; 31 | 32 | // Creates a new thing in the DB. 33 | exports.create = function(req, res) { 34 | Thing.create(req.body, function(err, thing) { 35 | if(err) { return handleError(res, err); } 36 | return res.status(201).json(thing); 37 | }); 38 | }; 39 | 40 | // Updates an existing thing in the DB. 41 | exports.update = function(req, res) { 42 | if(req.body._id) { delete req.body._id; } 43 | Thing.findById(req.params.id, function (err, thing) { 44 | if (err) { return handleError(res, err); } 45 | if(!thing) { return res.status(404).send('Not Found'); } 46 | var updated = _.merge(thing, req.body); 47 | updated.save(function (err) { 48 | if (err) { return handleError(res, err); } 49 | return res.status(200).json(thing); 50 | }); 51 | }); 52 | }; 53 | 54 | // Deletes a thing from the DB. 55 | exports.destroy = function(req, res) { 56 | Thing.findById(req.params.id, function (err, thing) { 57 | if(err) { return handleError(res, err); } 58 | if(!thing) { return res.status(404).send('Not Found'); } 59 | thing.remove(function(err) { 60 | if(err) { return handleError(res, err); } 61 | return res.status(204).send('No Content'); 62 | }); 63 | }); 64 | }; 65 | 66 | function handleError(res, err) { 67 | return res.status(500).send(err); 68 | } -------------------------------------------------------------------------------- /server/api/thing/thing.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | Schema = mongoose.Schema; 5 | 6 | var ThingSchema = new Schema({ 7 | name: String, 8 | info: String, 9 | active: Boolean 10 | }); 11 | 12 | module.exports = mongoose.model('Thing', ThingSchema); -------------------------------------------------------------------------------- /server/api/thing/thing.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var app = require('../../app'); 5 | var request = require('supertest'); 6 | 7 | describe('GET /api/things', function() { 8 | 9 | it('should respond with JSON array', function(done) { 10 | request(app) 11 | .get('/api/things') 12 | .expect(200) 13 | .expect('Content-Type', /json/) 14 | .end(function(err, res) { 15 | if (err) return done(err); 16 | res.body.should.be.instanceof(Array); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /server/api/user/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./user.controller'); 5 | var config = require('../../config/environment'); 6 | var auth = require('../../auth/auth.service'); 7 | 8 | var router = express.Router(); 9 | 10 | router.get('/', auth.hasRole('admin'), controller.index); 11 | router.delete('/:id', auth.hasRole('admin'), controller.destroy); 12 | router.get('/me', auth.isAuthenticated(), controller.me); 13 | router.put('/:id/password', auth.isAuthenticated(), controller.changePassword); 14 | router.get('/:id', auth.isAuthenticated(), controller.show); 15 | router.post('/', controller.create); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /server/api/user/user.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var User = require('./user.model'); 4 | var passport = require('passport'); 5 | var config = require('../../config/environment'); 6 | var jwt = require('jsonwebtoken'); 7 | 8 | var validationError = function(res, err) { 9 | return res.status(422).json(err); 10 | }; 11 | 12 | /** 13 | * Get list of users 14 | * restriction: 'admin' 15 | */ 16 | exports.index = function(req, res) { 17 | User.find({}, '-salt -hashedPassword', function (err, users) { 18 | if(err) return res.status(500).send(err); 19 | res.status(200).json(users); 20 | }); 21 | }; 22 | 23 | /** 24 | * Creates a new user 25 | */ 26 | exports.create = function (req, res, next) { 27 | var newUser = new User(req.body); 28 | newUser.provider = 'local'; 29 | newUser.role = 'user'; 30 | newUser.save(function(err, user) { 31 | if (err) return validationError(res, err); 32 | var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60*5 }); 33 | res.json({ token: token }); 34 | }); 35 | }; 36 | 37 | /** 38 | * Get a single user 39 | */ 40 | exports.show = function (req, res, next) { 41 | var userId = req.params.id; 42 | 43 | User.findById(userId, function (err, user) { 44 | if (err) return next(err); 45 | if (!user) return res.status(401).send('Unauthorized'); 46 | res.json(user.profile); 47 | }); 48 | }; 49 | 50 | /** 51 | * Deletes a user 52 | * restriction: 'admin' 53 | */ 54 | exports.destroy = function(req, res) { 55 | User.findByIdAndRemove(req.params.id, function(err, user) { 56 | if(err) return res.status(500).send(err); 57 | return res.status(204).send('No Content'); 58 | }); 59 | }; 60 | 61 | /** 62 | * Change a users password 63 | */ 64 | exports.changePassword = function(req, res, next) { 65 | var userId = req.user._id; 66 | var oldPass = String(req.body.oldPassword); 67 | var newPass = String(req.body.newPassword); 68 | 69 | User.findById(userId, function (err, user) { 70 | if(user.authenticate(oldPass)) { 71 | user.password = newPass; 72 | user.save(function(err) { 73 | if (err) return validationError(res, err); 74 | res.status(200).send('OK'); 75 | }); 76 | } else { 77 | res.status(403).send('Forbidden'); 78 | } 79 | }); 80 | }; 81 | 82 | /** 83 | * Get my info 84 | */ 85 | exports.me = function(req, res, next) { 86 | var userId = req.user._id; 87 | User.findOne({ 88 | _id: userId 89 | }, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt 90 | if (err) return next(err); 91 | if (!user) return res.status(401).send('Unauthorized'); 92 | res.json(user); 93 | }); 94 | }; 95 | 96 | /** 97 | * Authentication callback 98 | */ 99 | exports.authCallback = function(req, res, next) { 100 | res.redirect('/'); 101 | }; 102 | -------------------------------------------------------------------------------- /server/api/user/user.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | var crypto = require('crypto'); 6 | var authTypes = ['github', 'twitter', 'facebook', 'google']; 7 | 8 | var UserSchema = new Schema({ 9 | name: String, 10 | email: { type: String, lowercase: true }, 11 | role: { 12 | type: String, 13 | default: 'user' 14 | }, 15 | hashedPassword: String, 16 | provider: String, 17 | salt: String, 18 | facebook: {}, 19 | twitter: {}, 20 | google: {}, 21 | github: {} 22 | }); 23 | 24 | /** 25 | * Virtuals 26 | */ 27 | UserSchema 28 | .virtual('password') 29 | .set(function(password) { 30 | this._password = password; 31 | this.salt = this.makeSalt(); 32 | this.hashedPassword = this.encryptPassword(password); 33 | }) 34 | .get(function() { 35 | return this._password; 36 | }); 37 | 38 | // Public profile information 39 | UserSchema 40 | .virtual('profile') 41 | .get(function() { 42 | return { 43 | 'name': this.name, 44 | 'role': this.role 45 | }; 46 | }); 47 | 48 | // Non-sensitive info we'll be putting in the token 49 | UserSchema 50 | .virtual('token') 51 | .get(function() { 52 | return { 53 | '_id': this._id, 54 | 'role': this.role 55 | }; 56 | }); 57 | 58 | /** 59 | * Validations 60 | */ 61 | 62 | // Validate empty email 63 | UserSchema 64 | .path('email') 65 | .validate(function(email) { 66 | if (authTypes.indexOf(this.provider) !== -1) return true; 67 | return email.length; 68 | }, 'Email cannot be blank'); 69 | 70 | // Validate empty password 71 | UserSchema 72 | .path('hashedPassword') 73 | .validate(function(hashedPassword) { 74 | if (authTypes.indexOf(this.provider) !== -1) return true; 75 | return hashedPassword.length; 76 | }, 'Password cannot be blank'); 77 | 78 | // Validate email is not taken 79 | UserSchema 80 | .path('email') 81 | .validate(function(value, respond) { 82 | var self = this; 83 | this.constructor.findOne({email: value}, function(err, user) { 84 | if(err) throw err; 85 | if(user) { 86 | if(self.id === user.id) return respond(true); 87 | return respond(false); 88 | } 89 | respond(true); 90 | }); 91 | }, 'The specified email address is already in use.'); 92 | 93 | var validatePresenceOf = function(value) { 94 | return value && value.length; 95 | }; 96 | 97 | /** 98 | * Pre-save hook 99 | */ 100 | UserSchema 101 | .pre('save', function(next) { 102 | if (!this.isNew) return next(); 103 | 104 | if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1) 105 | next(new Error('Invalid password')); 106 | else 107 | next(); 108 | }); 109 | 110 | /** 111 | * Methods 112 | */ 113 | UserSchema.methods = { 114 | /** 115 | * Authenticate - check if the passwords are the same 116 | * 117 | * @param {String} plainText 118 | * @return {Boolean} 119 | * @api public 120 | */ 121 | authenticate: function(plainText) { 122 | return this.encryptPassword(plainText) === this.hashedPassword; 123 | }, 124 | 125 | /** 126 | * Make salt 127 | * 128 | * @return {String} 129 | * @api public 130 | */ 131 | makeSalt: function() { 132 | return crypto.randomBytes(16).toString('base64'); 133 | }, 134 | 135 | /** 136 | * Encrypt password 137 | * 138 | * @param {String} password 139 | * @return {String} 140 | * @api public 141 | */ 142 | encryptPassword: function(password) { 143 | if (!password || !this.salt) return ''; 144 | var salt = new Buffer(this.salt, 'base64'); 145 | return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); 146 | } 147 | }; 148 | 149 | module.exports = mongoose.model('User', UserSchema); 150 | -------------------------------------------------------------------------------- /server/api/user/user.model.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var app = require('../../app'); 5 | var User = require('./user.model'); 6 | 7 | var user = new User({ 8 | provider: 'local', 9 | name: 'Fake User', 10 | email: 'test@test.com', 11 | password: 'password' 12 | }); 13 | 14 | describe('User Model', function() { 15 | before(function(done) { 16 | // Clear users before testing 17 | User.remove().exec().then(function() { 18 | done(); 19 | }); 20 | }); 21 | 22 | afterEach(function(done) { 23 | User.remove().exec().then(function() { 24 | done(); 25 | }); 26 | }); 27 | 28 | it('should begin with no users', function(done) { 29 | User.find({}, function(err, users) { 30 | users.should.have.length(0); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('should fail when saving a duplicate user', function(done) { 36 | user.save(function() { 37 | var userDup = new User(user); 38 | userDup.save(function(err) { 39 | should.exist(err); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | it('should fail when saving without an email', function(done) { 46 | user.email = ''; 47 | user.save(function(err) { 48 | should.exist(err); 49 | done(); 50 | }); 51 | }); 52 | 53 | it("should authenticate user if password is valid", function() { 54 | return user.authenticate('password').should.be.true; 55 | }); 56 | 57 | it("should not authenticate user if password is invalid", function() { 58 | return user.authenticate('blah').should.not.be.true; 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application file 3 | */ 4 | 5 | 'use strict'; 6 | 7 | // Set default node environment to development 8 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 9 | 10 | var express = require('express'); 11 | var mongoose = require('mongoose'); 12 | var config = require('./config/environment'); 13 | 14 | // Connect to database 15 | mongoose.connect(config.mongo.uri, config.mongo.options); 16 | 17 | // Populate DB with sample data 18 | if(config.seedDB) { require('./config/seed'); } 19 | 20 | // Setup server 21 | var app = express(); 22 | var server = require('http').createServer(app); 23 | require('./config/express')(app); 24 | require('./routes')(app); 25 | 26 | // Start server 27 | server.listen(config.port, config.ip, function () { 28 | console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); 29 | }); 30 | 31 | // Expose app 32 | exports = module.exports = app; 33 | -------------------------------------------------------------------------------- /server/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var passport = require('passport'); 5 | var config = require('../config/environment'); 6 | var jwt = require('jsonwebtoken'); 7 | var expressJwt = require('express-jwt'); 8 | var compose = require('composable-middleware'); 9 | var User = require('../api/user/user.model'); 10 | var validateJwt = expressJwt({ secret: config.secrets.session }); 11 | 12 | /** 13 | * Attaches the user object to the request if authenticated 14 | * Otherwise returns 403 15 | */ 16 | function isAuthenticated() { 17 | return compose() 18 | // Validate jwt 19 | .use(function(req, res, next) { 20 | // allow access_token to be passed through query parameter as well 21 | if(req.query && req.query.hasOwnProperty('access_token')) { 22 | req.headers.authorization = 'Bearer ' + req.query.access_token; 23 | } 24 | validateJwt(req, res, next); 25 | }) 26 | // Attach user to request 27 | .use(function(req, res, next) { 28 | User.findById(req.user._id, function (err, user) { 29 | if (err) return next(err); 30 | if (!user) return res.status(401).send('Unauthorized'); 31 | 32 | req.user = user; 33 | next(); 34 | }); 35 | }); 36 | } 37 | 38 | /** 39 | * Checks if the user role meets the minimum requirements of the route 40 | */ 41 | function hasRole(roleRequired) { 42 | if (!roleRequired) throw new Error('Required role needs to be set'); 43 | 44 | return compose() 45 | .use(isAuthenticated()) 46 | .use(function meetsRequirements(req, res, next) { 47 | if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) { 48 | next(); 49 | } 50 | else { 51 | res.status(403).send('Forbidden'); 52 | } 53 | }); 54 | } 55 | 56 | /** 57 | * Returns a jwt token signed by the app secret 58 | */ 59 | function signToken(id) { 60 | return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*5 }); 61 | } 62 | 63 | /** 64 | * Set token cookie directly for oAuth strategies 65 | */ 66 | function setTokenCookie(req, res) { 67 | if (!req.user) return res.status(404).json({ message: 'Something went wrong, please try again.'}); 68 | var token = signToken(req.user._id, req.user.role); 69 | res.cookie('token', JSON.stringify(token)); 70 | res.redirect('/'); 71 | } 72 | 73 | exports.isAuthenticated = isAuthenticated; 74 | exports.hasRole = hasRole; 75 | exports.signToken = signToken; 76 | exports.setTokenCookie = setTokenCookie; -------------------------------------------------------------------------------- /server/auth/facebook/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router 10 | .get('/', passport.authenticate('facebook', { 11 | scope: ['email', 'user_about_me'], 12 | failureRedirect: '/signup', 13 | session: false 14 | })) 15 | 16 | .get('/callback', passport.authenticate('facebook', { 17 | failureRedirect: '/signup', 18 | session: false 19 | }), auth.setTokenCookie); 20 | 21 | module.exports = router; -------------------------------------------------------------------------------- /server/auth/facebook/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var FacebookStrategy = require('passport-facebook').Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new FacebookStrategy({ 6 | clientID: config.facebook.clientID, 7 | clientSecret: config.facebook.clientSecret, 8 | callbackURL: config.facebook.callbackURL 9 | }, 10 | function(accessToken, refreshToken, profile, done) { 11 | User.findOne({ 12 | 'facebook.id': profile.id 13 | }, 14 | function(err, user) { 15 | if (err) { 16 | return done(err); 17 | } 18 | if (!user) { 19 | user = new User({ 20 | name: profile.displayName, 21 | email: profile.emails[0].value, 22 | role: 'user', 23 | username: profile.username, 24 | provider: 'facebook', 25 | facebook: profile._json 26 | }); 27 | user.save(function(err) { 28 | if (err) done(err); 29 | return done(err, user); 30 | }); 31 | } else { 32 | return done(err, user); 33 | } 34 | }) 35 | } 36 | )); 37 | }; -------------------------------------------------------------------------------- /server/auth/google/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router 10 | .get('/', passport.authenticate('google', { 11 | failureRedirect: '/signup', 12 | scope: [ 13 | 'https://www.googleapis.com/auth/userinfo.profile', 14 | 'https://www.googleapis.com/auth/userinfo.email' 15 | ], 16 | session: false 17 | })) 18 | 19 | .get('/callback', passport.authenticate('google', { 20 | failureRedirect: '/signup', 21 | session: false 22 | }), auth.setTokenCookie); 23 | 24 | module.exports = router; -------------------------------------------------------------------------------- /server/auth/google/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new GoogleStrategy({ 6 | clientID: config.google.clientID, 7 | clientSecret: config.google.clientSecret, 8 | callbackURL: config.google.callbackURL 9 | }, 10 | function(accessToken, refreshToken, profile, done) { 11 | User.findOne({ 12 | 'google.id': profile.id 13 | }, function(err, user) { 14 | if (!user) { 15 | user = new User({ 16 | name: profile.displayName, 17 | email: profile.emails[0].value, 18 | role: 'user', 19 | username: profile.username, 20 | provider: 'google', 21 | google: profile._json 22 | }); 23 | user.save(function(err) { 24 | if (err) done(err); 25 | return done(err, user); 26 | }); 27 | } else { 28 | return done(err, user); 29 | } 30 | }); 31 | } 32 | )); 33 | }; 34 | -------------------------------------------------------------------------------- /server/auth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var config = require('../config/environment'); 6 | var User = require('../api/user/user.model'); 7 | 8 | // Passport Configuration 9 | require('./local/passport').setup(User, config); 10 | require('./facebook/passport').setup(User, config); 11 | require('./google/passport').setup(User, config); 12 | require('./twitter/passport').setup(User, config); 13 | 14 | var router = express.Router(); 15 | 16 | router.use('/local', require('./local')); 17 | router.use('/facebook', require('./facebook')); 18 | router.use('/twitter', require('./twitter')); 19 | router.use('/google', require('./google')); 20 | 21 | module.exports = router; -------------------------------------------------------------------------------- /server/auth/local/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router.post('/', function(req, res, next) { 10 | passport.authenticate('local', function (err, user, info) { 11 | var error = err || info; 12 | if (error) return res.status(401).json(error); 13 | if (!user) return res.status(404).json({message: 'Something went wrong, please try again.'}); 14 | 15 | var token = auth.signToken(user._id, user.role); 16 | res.json({token: token}); 17 | })(req, res, next) 18 | }); 19 | 20 | module.exports = router; -------------------------------------------------------------------------------- /server/auth/local/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var LocalStrategy = require('passport-local').Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new LocalStrategy({ 6 | usernameField: 'email', 7 | passwordField: 'password' // this is the virtual field on the model 8 | }, 9 | function(email, password, done) { 10 | User.findOne({ 11 | email: email.toLowerCase() 12 | }, function(err, user) { 13 | if (err) return done(err); 14 | 15 | if (!user) { 16 | return done(null, false, { message: 'This email is not registered.' }); 17 | } 18 | if (!user.authenticate(password)) { 19 | return done(null, false, { message: 'This password is not correct.' }); 20 | } 21 | return done(null, user); 22 | }); 23 | } 24 | )); 25 | }; -------------------------------------------------------------------------------- /server/auth/twitter/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router 10 | .get('/', passport.authenticate('twitter', { 11 | failureRedirect: '/signup', 12 | session: false 13 | })) 14 | 15 | .get('/callback', passport.authenticate('twitter', { 16 | failureRedirect: '/signup', 17 | session: false 18 | }), auth.setTokenCookie); 19 | 20 | module.exports = router; -------------------------------------------------------------------------------- /server/auth/twitter/passport.js: -------------------------------------------------------------------------------- 1 | exports.setup = function (User, config) { 2 | var passport = require('passport'); 3 | var TwitterStrategy = require('passport-twitter').Strategy; 4 | 5 | passport.use(new TwitterStrategy({ 6 | consumerKey: config.twitter.clientID, 7 | consumerSecret: config.twitter.clientSecret, 8 | callbackURL: config.twitter.callbackURL 9 | }, 10 | function(token, tokenSecret, profile, done) { 11 | User.findOne({ 12 | 'twitter.id_str': profile.id 13 | }, function(err, user) { 14 | if (err) { 15 | return done(err); 16 | } 17 | if (!user) { 18 | user = new User({ 19 | name: profile.displayName, 20 | username: profile.username, 21 | role: 'user', 22 | provider: 'twitter', 23 | twitter: profile._json 24 | }); 25 | user.save(function(err) { 26 | if (err) return done(err); 27 | return done(err, user); 28 | }); 29 | } else { 30 | return done(err, user); 31 | } 32 | }); 33 | } 34 | )); 35 | }; -------------------------------------------------------------------------------- /server/components/errors/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Error responses 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports[404] = function pageNotFound(req, res) { 8 | var viewFilePath = '404'; 9 | var statusCode = 404; 10 | var result = { 11 | status: statusCode 12 | }; 13 | 14 | res.status(result.status); 15 | res.render(viewFilePath, function (err) { 16 | if (err) { return res.json(result, result.status); } 17 | 18 | res.render(viewFilePath); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /server/config/environment/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Development specific configuration 4 | // ================================== 5 | module.exports = { 6 | // MongoDB connection options 7 | mongo: { 8 | uri: 'mongodb://localhost/tmp-dev' 9 | }, 10 | 11 | seedDB: true 12 | }; 13 | -------------------------------------------------------------------------------- /server/config/environment/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var _ = require('lodash'); 5 | 6 | function requiredProcessEnv(name) { 7 | if(!process.env[name]) { 8 | throw new Error('You must set the ' + name + ' environment variable'); 9 | } 10 | return process.env[name]; 11 | } 12 | 13 | // All configurations will extend these options 14 | // ============================================ 15 | var all = { 16 | env: process.env.NODE_ENV, 17 | 18 | // Root path of server 19 | root: path.normalize(__dirname + '/../../..'), 20 | 21 | // Server port 22 | port: process.env.PORT || 9000, 23 | 24 | // Should we populate the DB with sample data? 25 | seedDB: false, 26 | 27 | // Secret for session, you will want to change this and make it an environment variable 28 | secrets: { 29 | session: 'tmp-secret' 30 | }, 31 | 32 | // List of user roles 33 | userRoles: ['guest', 'user', 'admin'], 34 | 35 | // MongoDB connection options 36 | mongo: { 37 | options: { 38 | db: { 39 | safe: true 40 | } 41 | } 42 | }, 43 | 44 | facebook: { 45 | clientID: process.env.FACEBOOK_ID || 'id', 46 | clientSecret: process.env.FACEBOOK_SECRET || 'secret', 47 | callbackURL: (process.env.DOMAIN || '') + '/auth/facebook/callback' 48 | }, 49 | 50 | twitter: { 51 | clientID: process.env.TWITTER_ID || 'id', 52 | clientSecret: process.env.TWITTER_SECRET || 'secret', 53 | callbackURL: (process.env.DOMAIN || '') + '/auth/twitter/callback' 54 | }, 55 | 56 | google: { 57 | clientID: process.env.GOOGLE_ID || 'id', 58 | clientSecret: process.env.GOOGLE_SECRET || 'secret', 59 | callbackURL: (process.env.DOMAIN || '') + '/auth/google/callback' 60 | } 61 | }; 62 | 63 | // Export the config object based on the NODE_ENV 64 | // ============================================== 65 | module.exports = _.merge( 66 | all, 67 | require('./' + process.env.NODE_ENV + '.js') || {}); -------------------------------------------------------------------------------- /server/config/environment/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Production specific configuration 4 | // ================================= 5 | module.exports = { 6 | // Server IP 7 | ip: process.env.OPENSHIFT_NODEJS_IP || 8 | process.env.IP || 9 | undefined, 10 | 11 | // Server port 12 | port: process.env.OPENSHIFT_NODEJS_PORT || 13 | process.env.PORT || 14 | 8080, 15 | 16 | // MongoDB connection options 17 | mongo: { 18 | uri: process.env.MONGOLAB_URI || 19 | process.env.MONGOHQ_URL || 20 | process.env.OPENSHIFT_MONGODB_DB_URL+process.env.OPENSHIFT_APP_NAME || 21 | 'mongodb://localhost/tmp' 22 | } 23 | }; -------------------------------------------------------------------------------- /server/config/environment/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Test specific configuration 4 | // =========================== 5 | module.exports = { 6 | // MongoDB connection options 7 | mongo: { 8 | uri: 'mongodb://localhost/tmp-test' 9 | } 10 | }; -------------------------------------------------------------------------------- /server/config/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Express configuration 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var express = require('express'); 8 | var favicon = require('serve-favicon'); 9 | var morgan = require('morgan'); 10 | var compression = require('compression'); 11 | var bodyParser = require('body-parser'); 12 | var methodOverride = require('method-override'); 13 | var cookieParser = require('cookie-parser'); 14 | var errorHandler = require('errorhandler'); 15 | var path = require('path'); 16 | var config = require('./environment'); 17 | var passport = require('passport'); 18 | var session = require('express-session'); 19 | var mongoStore = require('connect-mongo')(session); 20 | var mongoose = require('mongoose'); 21 | 22 | module.exports = function(app) { 23 | var env = app.get('env'); 24 | 25 | app.set('views', config.root + '/server/views'); 26 | app.set('view engine', 'jade'); 27 | app.use(compression()); 28 | app.use(bodyParser.urlencoded({ extended: false })); 29 | app.use(bodyParser.json()); 30 | app.use(methodOverride()); 31 | app.use(cookieParser()); 32 | app.use(passport.initialize()); 33 | 34 | // Persist sessions with mongoStore 35 | // We need to enable sessions for passport twitter because its an oauth 1.0 strategy 36 | app.use(session({ 37 | secret: config.secrets.session, 38 | resave: true, 39 | saveUninitialized: true, 40 | store: new mongoStore({ mongoose_connection: mongoose.connection }) 41 | })); 42 | 43 | if ('production' === env) { 44 | app.use(favicon(path.join(config.root, 'public', 'favicon.ico'))); 45 | app.use(express.static(path.join(config.root, 'public'))); 46 | app.set('appPath', path.join(config.root, 'public')); 47 | app.use(morgan('dev')); 48 | } 49 | 50 | if ('development' === env || 'test' === env) { 51 | app.use(require('connect-livereload')()); 52 | app.use(express.static(path.join(config.root, '.tmp'))); 53 | // RGS: Include common 54 | app.use(express.static(path.join(config.root, 'client'))); 55 | app.use('/ionic', express.static(path.join(config.root, 'dist-ionic/www'))); 56 | // app.use(express.static(path.join(config.root, 'client/common'))); 57 | // app.use(express.static(path.join(config.root, 'client/webapp'))); 58 | app.set('appPath', path.join(config.root, 'client/webapp')); 59 | app.use(morgan('dev')); 60 | app.use(errorHandler()); // Error handler - has to be last 61 | } 62 | }; -------------------------------------------------------------------------------- /server/config/local.env.sample.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use local.env.js for environment variables that grunt will set when the server starts locally. 4 | // Use for your api keys, secrets, etc. This file should not be tracked by git. 5 | // 6 | // You will need to set these on the server you deploy to. 7 | 8 | module.exports = { 9 | DOMAIN: 'http://localhost:9000', 10 | SESSION_SECRET: 'tmp-secret', 11 | 12 | FACEBOOK_ID: 'app-id', 13 | FACEBOOK_SECRET: 'secret', 14 | 15 | TWITTER_ID: 'app-id', 16 | TWITTER_SECRET: 'secret', 17 | 18 | GOOGLE_ID: 'app-id', 19 | GOOGLE_SECRET: 'secret', 20 | 21 | // Control debug level for modules using visionmedia/debug 22 | DEBUG: '' 23 | }; 24 | -------------------------------------------------------------------------------- /server/config/seed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Populate DB with sample data on server start 3 | * to disable, edit config/environment/index.js, and set `seedDB: false` 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var Thing = require('../api/thing/thing.model'); 9 | var User = require('../api/user/user.model'); 10 | 11 | Thing.find({}).remove(function() { 12 | Thing.create({ 13 | name : 'Development Tools', 14 | info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.' 15 | }, { 16 | name : 'Server and Client integration', 17 | info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.' 18 | }, { 19 | name : 'Smart Build System', 20 | info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html' 21 | }, { 22 | name : 'Modular Structure', 23 | info : 'Best practice client and server structures allow for more code reusability and maximum scalability' 24 | }, { 25 | name : 'Optimized Build', 26 | info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.' 27 | },{ 28 | name : 'Deployment Ready', 29 | info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators' 30 | }); 31 | }); 32 | 33 | User.find({}).remove(function() { 34 | User.create({ 35 | provider: 'local', 36 | name: 'Test User', 37 | email: 'test@test.com', 38 | password: 'test' 39 | }, { 40 | provider: 'local', 41 | role: 'admin', 42 | name: 'Admin', 43 | email: 'admin@admin.com', 44 | password: 'admin' 45 | }, function() { 46 | console.log('finished populating users'); 47 | } 48 | ); 49 | }); -------------------------------------------------------------------------------- /server/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application routes 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var errors = require('./components/errors'); 8 | var path = require('path'); 9 | 10 | module.exports = function(app) { 11 | 12 | // Insert routes below 13 | app.use('/api/things', require('./api/thing')); 14 | app.use('/api/users', require('./api/user')); 15 | 16 | app.use('/auth', require('./auth')); 17 | 18 | // All undefined asset or api routes should return a 404 19 | app.route('/:url(api|auth|components|app|bower_components|assets)/*') 20 | .get(errors[404]); 21 | 22 | // All other routes should redirect to the index.html 23 | app.route('/*') 24 | .get(function(req, res) { 25 | res.sendFile(path.resolve(app.get('appPath') + '/index.html')); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /server/views/404.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang='en') 3 | head 4 | meta(charset='utf-8') 5 | title Page Not Found :( 6 | style. 7 | ::-moz-selection { 8 | background: #b3d4fc; 9 | text-shadow: none; 10 | } 11 | ::selection { 12 | background: #b3d4fc; 13 | text-shadow: none; 14 | } 15 | html { 16 | padding: 30px 10px; 17 | font-size: 20px; 18 | line-height: 1.4; 19 | color: #737373; 20 | background: #f0f0f0; 21 | -webkit-text-size-adjust: 100%; 22 | -ms-text-size-adjust: 100%; 23 | } 24 | html, 25 | input { 26 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 27 | } 28 | body { 29 | max-width: 500px; 30 | _width: 500px; 31 | padding: 30px 20px 50px; 32 | border: 1px solid #b3b3b3; 33 | border-radius: 4px; 34 | margin: 0 auto; 35 | box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff; 36 | background: #fcfcfc; 37 | } 38 | h1 { 39 | margin: 0 10px; 40 | font-size: 50px; 41 | text-align: center; 42 | } 43 | h1 span { 44 | color: #bbb; 45 | } 46 | h3 { 47 | margin: 1.5em 0 0.5em; 48 | } 49 | p { 50 | margin: 1em 0; 51 | } 52 | ul { 53 | padding: 0 0 0 40px; 54 | margin: 1em 0; 55 | } 56 | .container { 57 | max-width: 380px; 58 | _width: 380px; 59 | margin: 0 auto; 60 | } 61 | /* google search */ 62 | #goog-fixurl ul { 63 | list-style: none; 64 | padding: 0; 65 | margin: 0; 66 | } 67 | #goog-fixurl form { 68 | margin: 0; 69 | } 70 | #goog-wm-qt, 71 | #goog-wm-sb { 72 | border: 1px solid #bbb; 73 | font-size: 16px; 74 | line-height: normal; 75 | vertical-align: top; 76 | color: #444; 77 | border-radius: 2px; 78 | } 79 | #goog-wm-qt { 80 | width: 220px; 81 | height: 20px; 82 | padding: 5px; 83 | margin: 5px 10px 0 0; 84 | box-shadow: inset 0 1px 1px #ccc; 85 | } 86 | #goog-wm-sb { 87 | display: inline-block; 88 | height: 32px; 89 | padding: 0 10px; 90 | margin: 5px 0 0; 91 | white-space: nowrap; 92 | cursor: pointer; 93 | background-color: #f5f5f5; 94 | background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1); 95 | background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1); 96 | background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1); 97 | background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1); 98 | -webkit-appearance: none; 99 | -moz-appearance: none; 100 | appearance: none; 101 | *overflow: visible; 102 | *display: inline; 103 | *zoom: 1; 104 | } 105 | #goog-wm-sb:hover, 106 | #goog-wm-sb:focus { 107 | border-color: #aaa; 108 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); 109 | background-color: #f8f8f8; 110 | } 111 | #goog-wm-qt:hover, 112 | #goog-wm-qt:focus { 113 | border-color: #105cb6; 114 | outline: 0; 115 | color: #222; 116 | } 117 | input::-moz-focus-inner { 118 | padding: 0; 119 | border: 0; 120 | } 121 | body 122 | .container 123 | h1 124 | | Not found 125 | span :( 126 | p Sorry, but the page you were trying to view does not exist. 127 | p It looks like this was the result of either: 128 | ul 129 | li a mistyped address 130 | li an out-of-date link 131 | script. 132 | var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host; 133 | script(src='//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js') 134 | --------------------------------------------------------------------------------