├── .gitattributes
├── .idea
├── .name
├── dictionaries
│ └── Mobile_Home.xml
├── scopes
│ └── scope_settings.xml
├── encodings.xml
├── vcs.xml
├── jsLibraryMappings.xml
├── modules.xml
├── case-studies-15.iml
├── libraries
│ └── case_studies_15_node_modules.xml
├── misc.xml
└── workspace.xml
├── app
├── .buildignore
├── robots.txt
├── favicon.ico
├── images
│ └── yeoman.png
├── scripts
│ ├── angularfire
│ │ ├── config.js
│ │ ├── firebase.utils.js
│ │ └── simpleLogin.js
│ ├── filters
│ │ └── reverse.js
│ ├── app.js
│ ├── services
│ │ ├── cases.js
│ │ └── case.js
│ ├── controllers
│ │ ├── chat.js
│ │ ├── login.js
│ │ ├── account.js
│ │ └── main.js
│ ├── directives
│ │ ├── ngShowAuth.js
│ │ └── ngHideAuth.js
│ └── routes.js
├── views
│ ├── chat.html
│ ├── main.html
│ ├── login.html
│ └── account.html
├── styles
│ └── main.css
├── 404.html
├── index.html
└── .htaccess
├── .bowerrc
├── .gitignore
├── .travis.yml
├── README.md
├── test
├── spec
│ ├── services
│ │ ├── case.js
│ │ └── cases.js
│ └── controllers
│ │ └── main.js
├── .jshintrc
└── karma.conf.js
├── .editorconfig
├── .jshintrc
├── bower.json
├── package.json
└── Gruntfile.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | case-studies-15
--------------------------------------------------------------------------------
/app/.buildignore:
--------------------------------------------------------------------------------
1 | *.coffee
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/app/robots.txt:
--------------------------------------------------------------------------------
1 | # robotstxt.org
2 |
3 | User-agent: *
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .tmp
4 | .sass-cache
5 | bower_components
6 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/runtime/case-study-15/master/app/favicon.ico
--------------------------------------------------------------------------------
/app/images/yeoman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/runtime/case-study-15/master/app/images/yeoman.png
--------------------------------------------------------------------------------
/.idea/dictionaries/Mobile_Home.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
4 | before_script:
5 | - 'npm install -g bower grunt-cli'
6 | - 'bower install'
7 |
--------------------------------------------------------------------------------
/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/scripts/angularfire/config.js:
--------------------------------------------------------------------------------
1 | angular.module('firebase.config', [])
2 | .constant('FBURL', 'https://case-studies.firebaseio.com')
3 | .constant('SIMPLE_LOGIN_PROVIDERS', ['password'])
4 |
5 | .constant('loginRedirectPath', '/login');
--------------------------------------------------------------------------------
/app/scripts/filters/reverse.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('caseStudiesApp')
4 | .filter('reverse', function() {
5 | return function(items) {
6 | return angular.isArray(items)? items.slice().reverse() : [];
7 | };
8 | });
9 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # case-study-15
2 | angularJS app with Firebase simplelogin
3 |
4 | Todo
5 |
6 | FB service
7 | get case studies across controllers (see Football 2)
8 | routeProvider (ifLoggedIn)
9 | main page show minimal content without being logged in/show thumbs if logged in
10 | nav: home login about
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/spec/services/case.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Service: case', function () {
4 |
5 | // load the service's module
6 | beforeEach(module('caseStudiesApp'));
7 |
8 | // instantiate service
9 | var case;
10 | beforeEach(inject(function (_case_) {
11 | case = _case_;
12 | }));
13 |
14 | it('should do something', function () {
15 | expect(!!case).toBe(true);
16 | });
17 |
18 | });
19 |
--------------------------------------------------------------------------------
/app/views/chat.html:
--------------------------------------------------------------------------------
1 |
2 |
Chat
3 |
4 |
8 |
9 |
10 | - {{message.text}}
11 |
12 |
13 | {{err}}
14 |
--------------------------------------------------------------------------------
/test/spec/services/cases.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Service: cases', function () {
4 |
5 | // load the service's module
6 | beforeEach(module('caseStudiesApp'));
7 |
8 | // instantiate service
9 | var cases;
10 | beforeEach(inject(function (_cases_) {
11 | cases = _cases_;
12 | }));
13 |
14 | it('should do something', function () {
15 | expect(!!cases).toBe(true);
16 | });
17 |
18 | });
19 |
--------------------------------------------------------------------------------
/.idea/case-studies-15.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc overview
5 | * @name caseStudiesApp
6 | * @description
7 | * # caseStudiesApp
8 | *
9 | * Main module of the application.
10 | */
11 | angular.module('caseStudiesApp', [
12 | 'ngAnimate',
13 | 'ngCookies',
14 | 'ngResource',
15 | 'ngRoute',
16 | 'ngSanitize',
17 | 'ngTouch',
18 | 'firebase',
19 | 'firebase.utils',
20 | 'simpleLogin'
21 | ]);
22 |
--------------------------------------------------------------------------------
/app/scripts/services/cases.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc service
5 | * @name caseStudiesApp.cases
6 | * @description
7 | * # cases
8 | * Service in the caseStudiesApp.
9 | */
10 |
11 | /**
12 | * * UNUSED CURRENTLY USE FACTORY CASE.JS instead
13 | */
14 | angular.module('caseStudiesApp')
15 | .service('cases', function cases() {
16 | // AngularJS will instantiate a singleton by calling "new" on this function
17 | });
18 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.idea/libraries/case_studies_15_node_modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.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": "nofunc",
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 | "angular": false,
23 | "firebase": false
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/test/spec/controllers/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Controller: MainCtrl', function () {
4 |
5 | // load the controller's module
6 | beforeEach(module('caseStudiesApp'));
7 |
8 | var MainCtrl,
9 | scope;
10 |
11 | // Initialize the controller and a mock scope
12 | beforeEach(inject(function ($controller, $rootScope) {
13 | scope = $rootScope.$new();
14 | MainCtrl = $controller('MainCtrl', {
15 | $scope: scope
16 | });
17 | }));
18 |
19 | it('should attach a list of awesomeThings to the scope', function () {
20 | expect(scope.awesomeThings.length).toBe(3);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "case-studies",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "angular": "1.2.16",
6 | "json3": "~3.3.1",
7 | "es5-shim": "~3.1.0",
8 | "bootstrap": "~3.2.0",
9 | "angular-resource": "1.2.16",
10 | "angular-cookies": "1.2.16",
11 | "angular-sanitize": "1.2.16",
12 | "angular-animate": "1.2.16",
13 | "angular-touch": "1.2.16",
14 | "angular-route": "1.2.16",
15 | "firebase": "1.0.x",
16 | "firebase-simple-login": "1.6.x",
17 | "angularfire": "0.8.x"
18 | },
19 | "devDependencies": {
20 | "angular-mocks": "1.2.16",
21 | "angular-scenario": "1.2.16",
22 | "mockfirebase": "0.2.x"
23 | },
24 | "appPath": "app"
25 | }
26 |
--------------------------------------------------------------------------------
/test/.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 | "after": false,
23 | "afterEach": false,
24 | "angular": false,
25 | "before": false,
26 | "beforeEach": false,
27 | "browser": false,
28 | "describe": false,
29 | "expect": false,
30 | "inject": false,
31 | "it": false,
32 | "jasmine": false,
33 | "spyOn": false
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/app/views/main.html:
--------------------------------------------------------------------------------
1 |
2 |
'Allo, 'Allo!
3 |
4 | 
5 | Always a pleasure scaffolding your apps.
6 |
7 |
Splendid!
8 |
9 |
10 |
11 |
Firebase
12 |
13 | Firebase is a powerful API to store and sync data in realtime.
14 |
15 |
16 |
HTML5 Boilerplate
17 |
18 | HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites.
19 |
20 |
21 |
Angular
22 |
23 | AngularJS is a toolset for building the framework most suited to your application development.
24 |
25 |
26 |
Karma
27 |
{{awesomeThings}}.
28 |
29 |
--------------------------------------------------------------------------------
/app/scripts/services/case.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc service
5 | * @name caseStudiesApp.case
6 | * @description
7 | * # case
8 | * Factory in the caseStudiesApp.
9 | */
10 | angular.module('caseStudiesApp')
11 | .factory('case', function () {
12 | // Service logic
13 | // ...
14 |
15 | var meaningOfLife = 42;
16 |
17 | // Public API here
18 | return {
19 | someMethod: function () {
20 | return meaningOfLife;
21 | }
22 | };
23 | });
24 |
25 |
26 |
27 | //angular.module('football2AngfireApp')
28 | // .factory('case', function ( $q, $firebase, FIREBASE_URL) {
29 | //
30 | // //var meaningOfLife = 42;
31 | //
32 | // var __data;
33 | // var __teams = [];
34 | // var __games = [];
35 | //
36 | // return {
37 | // getData: function() {
38 | // return $firebase(new Firebase('https://footballtest.firebaseio.com'));
39 | // }
40 | //
41 | // }
42 | //
43 | // });
44 |
--------------------------------------------------------------------------------
/app/scripts/controllers/chat.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @ngdoc function
4 | * @name caseStudiesApp.controller:ChatCtrl
5 | * @description
6 | * # ChatCtrl
7 | * A demo of using AngularFire to manage a synchronized list.
8 | */
9 | angular.module('caseStudiesApp')
10 | .controller('ChatCtrl', function ($scope, fbutil, $timeout) {
11 | // synchronize a read-only, synchronized array of messages, limit to most recent 10
12 | $scope.messages = fbutil.syncArray('messages', {limit: 10});
13 |
14 | // display any errors
15 | $scope.messages.$loaded().catch(alert);
16 |
17 | // provide a method for adding a message
18 | $scope.addMessage = function(newMessage) {
19 | if( newMessage ) {
20 | // push a message to the end of the array
21 | $scope.messages.$add({text: newMessage})
22 | // display any errors
23 | .catch(alert);
24 | }
25 | };
26 |
27 | function alert(msg) {
28 | $scope.err = msg;
29 | $timeout(function() {
30 | $scope.err = null;
31 | }, 5000);
32 | }
33 | });
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "casestudies",
3 | "version": "0.0.0",
4 | "dependencies": {},
5 | "devDependencies": {
6 | "grunt": "^0.4.1",
7 | "grunt-autoprefixer": "^0.7.3",
8 | "grunt-concurrent": "^0.5.0",
9 | "grunt-contrib-clean": "^0.5.0",
10 | "grunt-contrib-concat": "^0.4.0",
11 | "grunt-contrib-connect": "^0.7.1",
12 | "grunt-contrib-copy": "^0.5.0",
13 | "grunt-contrib-cssmin": "^0.9.0",
14 | "grunt-contrib-htmlmin": "^0.3.0",
15 | "grunt-contrib-imagemin": "^0.7.0",
16 | "grunt-contrib-jshint": "^0.10.0",
17 | "grunt-contrib-uglify": "^0.4.0",
18 | "grunt-contrib-watch": "^0.6.1",
19 | "grunt-filerev": "^0.2.1",
20 | "grunt-google-cdn": "^0.4.0",
21 | "grunt-newer": "^0.7.0",
22 | "grunt-ng-annotate": "^0.3.0",
23 | "grunt-svgmin": "^0.4.0",
24 | "grunt-usemin": "^2.1.1",
25 | "grunt-wiredep": "^1.7.0",
26 | "jshint-stylish": "^0.2.0",
27 | "load-grunt-tasks": "^0.4.0",
28 | "time-grunt": "^0.3.1",
29 | "chalk": "^0.4.0"
30 | },
31 | "engines": {
32 | "node": ">=0.10.0"
33 | },
34 | "scripts": {
35 | "test": "grunt test"
36 | }
37 | }
--------------------------------------------------------------------------------
/app/scripts/directives/ngShowAuth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @ngdoc function
3 | * @name caseStudiesApp.directive:ngShowAuth
4 | * @description
5 | * # ngShowAuthDirective
6 | * A directive that shows elements only when user is logged in. It also waits for simpleLogin
7 | * to be initialized so there is no initial flashing of incorrect state.
8 | */
9 | angular.module('caseStudiesApp')
10 | .directive('ngShowAuth', ['simpleLogin', '$timeout', function (simpleLogin, $timeout) {
11 | 'use strict';
12 | var isLoggedIn;
13 | simpleLogin.watch(function(user) {
14 | isLoggedIn = !!user;
15 | });
16 |
17 | return {
18 | restrict: 'A',
19 | link: function(scope, el) {
20 | el.addClass('ng-cloak'); // hide until we process it
21 |
22 | function update() {
23 | // sometimes if ngCloak exists on same element, they argue, so make sure that
24 | // this one always runs last for reliability
25 | $timeout(function () {
26 | el.toggleClass('ng-cloak', !isLoggedIn);
27 | }, 0);
28 | }
29 |
30 | simpleLogin.watch(update, scope);
31 | simpleLogin.getUser(update);
32 | }
33 | };
34 | }]);
35 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | /usr/local/bin/bower
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | CoffeeScript
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/scripts/directives/ngHideAuth.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @ngdoc function
4 | * @name caseStudiesApp.directive:ngHideAuth
5 | * @description
6 | * # ngHideAuthDirective
7 | * A directive that shows elements only when user is logged out. It also waits for simpleLogin
8 | * to be initialized so there is no initial flashing of incorrect state.
9 | */
10 | angular.module('caseStudiesApp')
11 | .directive('ngHideAuth', ['simpleLogin', '$timeout', function (simpleLogin, $timeout) {
12 | 'use strict';
13 | var isLoggedIn;
14 | simpleLogin.watch(function(user) {
15 | isLoggedIn = !!user;
16 | });
17 |
18 | return {
19 | restrict: 'A',
20 | link: function(scope, el) {
21 | el.addClass('ng-cloak'); // hide until we process it
22 | function update() {
23 | // sometimes if ngCloak exists on same element, they argue, so make sure that
24 | // this one always runs last for reliability
25 | $timeout(function () {
26 | el.toggleClass('ng-cloak', isLoggedIn !== false);
27 | }, 0);
28 | }
29 |
30 | simpleLogin.watch(update, scope);
31 | simpleLogin.getUser(update);
32 | }
33 | };
34 | }]);
35 |
--------------------------------------------------------------------------------
/app/views/login.html:
--------------------------------------------------------------------------------
1 |
2 | Login Page
3 |
4 |
26 | {{err}}
27 |
--------------------------------------------------------------------------------
/app/scripts/controllers/login.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @ngdoc function
4 | * @name caseStudiesApp.controller:LoginCtrl
5 | * @description
6 | * # LoginCtrl
7 | * Manages authentication to any active providers.
8 | */
9 | angular.module('caseStudiesApp')
10 | .controller('LoginCtrl', function ($scope, simpleLogin, $location) {
11 | $scope.passwordLogin = function(email, pass) {
12 | login('password', {
13 | email: email,
14 | password: pass,
15 | rememberMe: true
16 | });
17 | };
18 |
19 | $scope.createAccount = function(email, pass, confirm) {
20 | $scope.err = null;
21 | if( !pass ) {
22 | $scope.err = 'Please enter a password';
23 | }
24 | else if( pass !== confirm ) {
25 | $scope.err = 'Passwords do not match';
26 | }
27 | else {
28 | simpleLogin.createAccount(email, pass/*, name*/)
29 | .then(function() {
30 | $location.path('/account');
31 | console.log("login successful " + email);
32 | }, function(err) {
33 | $scope.err = err;
34 | });
35 | }
36 | };
37 |
38 | function login(provider, opts) {
39 | $scope.err = null;
40 | simpleLogin.login(provider, opts).then(
41 | function() {
42 | $location.path('/account');
43 | },
44 | function(err) {
45 | $scope.err = err;
46 | }
47 | );
48 | }
49 |
50 | });
--------------------------------------------------------------------------------
/app/styles/main.css:
--------------------------------------------------------------------------------
1 | /* Space out content a bit */
2 | body {
3 | padding-top: 20px;
4 | padding-bottom: 20px;
5 | }
6 |
7 | /* Everything but the jumbotron gets side spacing for mobile first views */
8 | .header,
9 | .marketing,
10 | .footer {
11 | padding-left: 15px;
12 | padding-right: 15px;
13 | }
14 |
15 | /* Custom page header */
16 | .header {
17 | border-bottom: 1px solid #e5e5e5;
18 | }
19 | /* Make the masthead heading the same height as the navigation */
20 | .header h3 {
21 | margin-top: 0;
22 | margin-bottom: 0;
23 | line-height: 40px;
24 | padding-bottom: 19px;
25 | }
26 |
27 | /* Custom page footer */
28 | .footer {
29 | padding-top: 19px;
30 | color: #777;
31 | border-top: 1px solid #e5e5e5;
32 | }
33 |
34 | /* Customize container */
35 | @media (min-width: 768px) {
36 | .container {
37 | max-width: 730px;
38 | }
39 | }
40 | .container-narrow > hr {
41 | margin: 30px 0;
42 | }
43 |
44 | /* Main marketing message and sign up button */
45 | .jumbotron {
46 | text-align: center;
47 | border-bottom: 1px solid #e5e5e5;
48 | }
49 | .jumbotron .btn {
50 | font-size: 21px;
51 | padding: 14px 24px;
52 | }
53 |
54 | /* Supporting marketing content */
55 | .marketing {
56 | margin: 40px 0;
57 | }
58 | .marketing p + h4 {
59 | margin-top: 28px;
60 | }
61 |
62 | /* Responsive: Portrait tablets and up */
63 | @media screen and (min-width: 768px) {
64 | /* Remove the padding we set earlier */
65 | .header,
66 | .marketing,
67 | .footer {
68 | padding-left: 0;
69 | padding-right: 0;
70 | }
71 | /* Space out the masthead */
72 | .header {
73 | margin-bottom: 30px;
74 | }
75 | /* Remove the bottom border on the jumbotron for visual effect */
76 | .jumbotron {
77 | border-bottom: 0;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/scripts/controllers/account.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @ngdoc function
4 | * @name muck2App.controller:AccountCtrl
5 | * @description
6 | * # AccountCtrl
7 | * Provides rudimentary account management functions.
8 | */
9 | angular.module('caseStudiesApp')
10 | .controller('AccountCtrl', function ($scope, user, simpleLogin, fbutil, $timeout) {
11 | $scope.user = user;
12 | $scope.logout = simpleLogin.logout;
13 | $scope.messages = [];
14 | loadProfile(user);
15 |
16 | $scope.changePassword = function(oldPass, newPass, confirm) {
17 | $scope.err = null;
18 | if( !oldPass || !newPass ) {
19 | error('Please enter all fields');
20 | }
21 | else if( newPass !== confirm ) {
22 | error('Passwords do not match');
23 | }
24 | else {
25 | simpleLogin.changePassword(user.email, oldPass, newPass)
26 | .then(function() {
27 | success('Password changed');
28 | }, error);
29 | }
30 | };
31 |
32 | $scope.changeEmail = function(pass, newEmail) {
33 | $scope.err = null;
34 | simpleLogin.changeEmail(pass, newEmail)
35 | .then(function(user) {
36 | loadProfile(user);
37 | success('Email changed');
38 | })
39 | .catch(error);
40 | };
41 |
42 | function error(err) {
43 | alert(err, 'danger');
44 | }
45 |
46 | function success(msg) {
47 | alert(msg, 'success');
48 | }
49 |
50 | function alert(msg, type) {
51 | var obj = {text: msg, type: type};
52 | $scope.messages.unshift(obj);
53 | $timeout(function() {
54 | $scope.messages.splice($scope.messages.indexOf(obj), 1);
55 | }, 10000);
56 | }
57 |
58 | function loadProfile(user) {
59 | if( $scope.profile ) {
60 | $scope.profile.$destroy();
61 | }
62 | fbutil.syncObject('users/'+user.uid).$bindTo($scope, 'profile');
63 | }
64 | });
--------------------------------------------------------------------------------
/app/scripts/controllers/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc function
5 | * @name caseStudiesApp.controller:MainCtrl
6 | * @description
7 | * # MainCtrl
8 | * Controller of the caseStudiesApp
9 | */
10 | angular.module('caseStudiesApp')
11 | .controller('MainCtrl', ['$scope', '$rootScope', '$location',
12 | function ($scope, $rootScope, $location) {
13 |
14 |
15 | $scope.awesomeThings = [
16 | 'HTML5 Boilerplate',
17 | 'AngularJS',
18 | 'Karma'
19 | ];
20 | }]);
21 |
22 |
23 |
24 |
25 |
26 |
27 | //angular.module('caseStudiesApp')
28 | // .controller('MainCtrl', ['$scope', '$rootScope', 'syncData', 'firebaseRef', '$firebaseSimpleLogin', '$location',
29 | // function ($scope, $rootScope, syncData, firebaseRef, $firebaseSimpleLogin, $location) {
30 | //
31 | //
32 | // $scope.awesomeThings = [
33 | // 'HTML5 Boilerplate',
34 | // 'AngularJS',
35 | // 'Karma'
36 | // ];
37 | // }]);
38 |
39 |
40 |
41 | //angular.module('nycdaAngularJsFinalProjApp')
42 | // .controller('MainCtrl', ['$scope', '$rootScope', 'syncData', 'firebaseRef', '$firebaseSimpleLogin', '$location',
43 | // function ($scope, $rootScope, syncData, firebaseRef, $firebaseSimpleLogin, $location) {
44 | //
45 | // $scope.theCaseStudies = syncData('/casestudies').$asObject();
46 | //
47 | // // assume we are loading asyncronous so we need to wait for it to be loaded
48 | // // before we try to do something
49 | // $scope.theCaseStudies.$loaded().then(function (caseStudies) {
50 | // //console.log(caseStudies.securitylevels);
51 | // //Do whatever you need to here
52 | // $scope.securitylevels = caseStudies.securitylevels;
53 | //
54 | // });
55 | //
56 | //
57 | // $scope.securitylevel = 0;
58 | // $rootScope.loggedIn = false;
59 |
60 |
--------------------------------------------------------------------------------
/app/views/account.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Profile
5 |
6 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
53 |
54 |
71 |
72 |
73 |
74 | {{message.text}}
75 |
--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.12/config/configuration-file.html
3 | // Generated on 2015-04-26 using
4 | // generator-karma 0.8.3
5 |
6 | module.exports = function(config) {
7 | 'use strict';
8 |
9 | config.set({
10 | // enable / disable watching file and executing tests whenever any file changes
11 | autoWatch: true,
12 |
13 | // base path, that will be used to resolve files and exclude
14 | basePath: '../',
15 |
16 | // testing framework to use (jasmine/mocha/qunit/...)
17 | frameworks: ['jasmine'],
18 |
19 | // list of files / patterns to load in the browser
20 | files: [
21 | 'bower_components/angular/angular.js',
22 | 'bower_components/angular-mocks/angular-mocks.js',
23 | 'bower_components/firebase/firebase.js',
24 | 'bower_components/angularfire/dist/angularfire.js',
25 | 'bower_components/angular-animate/angular-animate.js',
26 | 'bower_components/angular-cookies/angular-cookies.js',
27 | 'bower_components/angular-resource/angular-resource.js',
28 | 'bower_components/angular-route/angular-route.js',
29 | 'bower_components/angular-sanitize/angular-sanitize.js',
30 | 'bower_components/angular-touch/angular-touch.js',
31 | 'bower_components/firebase-simple-login/firebase-simple-login.js',
32 | 'app/scripts/**/*.js',
33 | 'test/mock/**/*.js',
34 | 'test/spec/**/*.js'
35 | ],
36 |
37 | // list of files / patterns to exclude
38 | exclude: [],
39 |
40 | // web server port
41 | port: 8080,
42 |
43 | // Start these browsers, currently available:
44 | // - Chrome
45 | // - ChromeCanary
46 | // - Firefox
47 | // - Opera
48 | // - Safari (only Mac)
49 | // - PhantomJS
50 | // - IE (only Windows)
51 | browsers: [
52 | 'PhantomJS'
53 | ],
54 |
55 | // Which plugins to enable
56 | plugins: [
57 | 'karma-phantomjs-launcher',
58 | 'karma-jasmine'
59 | ],
60 |
61 | // Continuous Integration mode
62 | // if true, it capture browsers, run tests and exit
63 | singleRun: false,
64 |
65 | colors: true,
66 |
67 | // level of logging
68 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
69 | logLevel: config.LOG_INFO,
70 |
71 | // Uncomment the following lines if you are using grunt's server to run the tests
72 | // proxies: {
73 | // '/': 'http://localhost:9000/'
74 | // },
75 | // URL root prevent conflicts with the site root
76 | // urlRoot: '_karma_'
77 | });
78 | };
79 |
--------------------------------------------------------------------------------
/app/scripts/angularfire/firebase.utils.js:
--------------------------------------------------------------------------------
1 |
2 | // a simple wrapper on Firebase and AngularFire to simplify deps and keep things DRY
3 | angular.module('firebase.utils', ['firebase', 'firebase.config'])
4 | .factory('fbutil', ['$window', 'FBURL', '$firebase', function($window, FBURL, $firebase) {
5 | 'use strict';
6 |
7 | return {
8 | syncObject: function(path, factoryConfig) { // jshint ignore:line
9 | return syncData.apply(null, arguments).$asObject();
10 | },
11 |
12 | syncArray: function(path, factoryConfig) { // jshint ignore:line
13 | return syncData.apply(null, arguments).$asArray();
14 | },
15 |
16 | ref: firebaseRef
17 | };
18 |
19 | function pathRef(args) {
20 | for (var i = 0; i < args.length; i++) {
21 | if (angular.isArray(args[i])) {
22 | args[i] = pathRef(args[i]);
23 | }
24 | else if( typeof args[i] !== 'string' ) {
25 | throw new Error('Argument '+i+' to firebaseRef is not a string: '+args[i]);
26 | }
27 | }
28 | return args.join('/');
29 | }
30 |
31 | /**
32 | * Example:
33 | *
34 | * function(firebaseRef) {
35 | * var ref = firebaseRef('path/to/data');
36 | * }
37 | *
38 | *
39 | * @function
40 | * @name firebaseRef
41 | * @param {String|Array...} path relative path to the root folder in Firebase instance
42 | * @return a Firebase instance
43 | */
44 | function firebaseRef(path) { // jshint ignore:line
45 | var ref = new $window.Firebase(FBURL);
46 | var args = Array.prototype.slice.call(arguments);
47 | if( args.length ) {
48 | ref = ref.child(pathRef(args));
49 | }
50 | return ref;
51 | }
52 |
53 | /**
54 | * Create a $firebase reference with just a relative path. For example:
55 | *
56 | *
57 | * function(syncData) {
58 | * // a regular $firebase ref
59 | * $scope.widget = syncData('widgets/alpha');
60 | *
61 | * // or automatic 3-way binding
62 | * syncData('widgets/alpha').$bind($scope, 'widget');
63 | * }
64 | *
65 | *
66 | * Props is the second param passed into $firebase. It can also contain limit, startAt, endAt,
67 | * and they will be applied to the ref before passing into $firebase
68 | *
69 | * @function
70 | * @name syncData
71 | * @param {String|Array...} path relative path to the root folder in Firebase instance
72 | * @param {object} [props]
73 | * @return a Firebase instance
74 | */
75 | function syncData(path, props) {
76 | var ref = firebaseRef(path);
77 | props = angular.extend({}, props);
78 | angular.forEach(['limit', 'startAt', 'endAt'], function(k) {
79 | if( props.hasOwnProperty(k) ) {
80 | var v = props[k];
81 | ref = ref[k].apply(ref, angular.isArray(v)? v : [v]);
82 | delete props[k];
83 | }
84 | });
85 | return $firebase(ref, props);
86 | }
87 | }]);
--------------------------------------------------------------------------------
/app/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Page Not Found :(
6 |
141 |
142 |
143 |
144 |
Not found :(
145 |
Sorry, but the page you were trying to view does not exist.
146 |
It looks like this was the result of either:
147 |
148 | - a mistyped address
149 | - an out-of-date link
150 |
151 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
37 |
38 |
39 |
40 |
43 |
44 |
45 |
46 |
47 |
56 |
57 |
58 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/app/scripts/routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /**
3 | * @ngdoc overview
4 | * @name caseStudiesApp:routes
5 | * @description
6 | * # routes.js
7 | *
8 | * Configure routes for use with Angular, and apply authentication security
9 | * Add new routes to the ROUTES constant or use yo angularfire:route to create them
10 | *
11 | * Any controller can be secured so that it will only load if user is logged in by
12 | * using `whenAuthenticated()` in place of `when()`. This requires the user to
13 | * be logged in to view this route, and adds the current user into the dependencies
14 | * which can be injected into the controller. If user is not logged in, the promise is
15 | * rejected, which is handled below by $routeChangeError
16 | *
17 | * Any controller can be forced to wait for authentication to resolve, without necessarily
18 | * requiring the user to be logged in, by adding a `resolve` block similar to the one below.
19 | * It would then inject `user` as a dependency. This could also be done in the controller,
20 | * but abstracting it makes things cleaner (controllers don't need to worry about auth state
21 | * or timing of displaying its UI components; it can assume it is taken care of when it runs)
22 | *
23 | * resolve: {
24 | * user: ['simpleLogin', function(simpleLogin) {
25 | * return simpleLogin.getUser();
26 | * }]
27 | * }
28 | *
29 | */
30 | angular.module('caseStudiesApp')
31 |
32 | /**
33 | * Adds a special `whenAuthenticated` method onto $routeProvider. This special method,
34 | * when called, invokes the authRequired() service (see simpleLogin.js).
35 | *
36 | * The promise either resolves to the authenticated user object and makes it available to
37 | * dependency injection (see AccountCtrl), or rejects the promise if user is not logged in,
38 | * forcing a redirect to the /login page
39 | */
40 | .config(['$routeProvider', 'SECURED_ROUTES', function($routeProvider, SECURED_ROUTES) {
41 | // credits for this idea: https://groups.google.com/forum/#!msg/angular/dPr9BpIZID0/MgWVluo_Tg8J
42 | // unfortunately, a decorator cannot be use here because they are not applied until after
43 | // the .config calls resolve, so they can't be used during route configuration, so we have
44 | // to hack it directly onto the $routeProvider object
45 | $routeProvider.whenAuthenticated = function(path, route) {
46 | route.resolve = route.resolve || {};
47 | route.resolve.user = ['authRequired', function(authRequired) {
48 | return authRequired();
49 | }];
50 | $routeProvider.when(path, route);
51 | SECURED_ROUTES[path] = true;
52 | return $routeProvider;
53 | };
54 | }])
55 |
56 | // configure views; the authRequired parameter is used for specifying pages
57 | // which should only be available while logged in
58 | .config(['$routeProvider', function($routeProvider) {
59 | $routeProvider
60 | .when('/', {
61 | templateUrl: 'views/main.html',
62 | controller: 'MainCtrl'
63 | })
64 |
65 | .when('/login', {
66 | templateUrl: 'views/login.html',
67 | controller: 'LoginCtrl'
68 | })
69 |
70 | .when('/chat', {
71 | templateUrl: 'views/chat.html',
72 | controller: 'ChatCtrl'
73 | })
74 |
75 | .whenAuthenticated('/account', {
76 | templateUrl: 'views/account.html',
77 | controller: 'AccountCtrl'
78 | })
79 |
80 | .when('/chat', {
81 | templateUrl: 'views/chat.html',
82 | controller: 'ChatCtrl'
83 | })
84 | .otherwise({redirectTo: '/'});
85 | }])
86 |
87 | /**
88 | * Apply some route security. Any route's resolve method can reject the promise with
89 | * { authRequired: true } to force a redirect. This method enforces that and also watches
90 | * for changes in auth status which might require us to navigate away from a path
91 | * that we can no longer view.
92 | */
93 | .run(['$rootScope', '$location', 'simpleLogin', 'SECURED_ROUTES', 'loginRedirectPath',
94 | function($rootScope, $location, simpleLogin, SECURED_ROUTES, loginRedirectPath) {
95 | // watch for login status changes and redirect if appropriate
96 | simpleLogin.watch(check, $rootScope);
97 |
98 | // some of our routes may reject resolve promises with the special {authRequired: true} error
99 | // this redirects to the login page whenever that is encountered
100 | $rootScope.$on('$routeChangeError', function(e, next, prev, err) {
101 | if( angular.isObject(err) && err.authRequired ) {
102 | $location.path(loginRedirectPath);
103 | }
104 | });
105 |
106 | function check(user) {
107 | if( !user && authRequired($location.path()) ) {
108 | $location.path(loginRedirectPath);
109 | }
110 | }
111 |
112 | function authRequired(path) {
113 | return SECURED_ROUTES.hasOwnProperty(path);
114 | }
115 | }
116 | ])
117 |
118 | // used by route security
119 | .constant('SECURED_ROUTES', {});
120 |
--------------------------------------------------------------------------------
/app/scripts/angularfire/simpleLogin.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 | angular.module('simpleLogin', ['firebase', 'firebase.utils', 'firebase.config'])
4 |
5 | // a simple wrapper on simpleLogin.getUser() that rejects the promise
6 | // if the user does not exists (i.e. makes user required), useful for
7 | // setting up secure routes that require authentication
8 | .factory('authRequired', function(simpleLogin, $q) {
9 | return function() {
10 | return simpleLogin.getUser().then(function (user) {
11 | return user ? user : $q.reject({ authRequired: true });
12 | });
13 | };
14 | })
15 |
16 | .factory('simpleLogin', function($firebaseSimpleLogin, fbutil, $q, $rootScope, createProfile, changeEmail) {
17 | var auth = $firebaseSimpleLogin(fbutil.ref());
18 | var listeners = [];
19 |
20 | function statusChange() {
21 | fns.initialized = true;
22 | fns.user = auth.user || null;
23 | angular.forEach(listeners, function(fn) {
24 | fn(fns.user);
25 | });
26 | }
27 |
28 | var fns = {
29 | user: null,
30 |
31 | initialized: false,
32 |
33 | getUser: function() {
34 | return auth.$getCurrentUser();
35 | },
36 |
37 | login: function(provider, opts) {
38 | return auth.$login(provider, opts);
39 | },
40 |
41 | logout: function() {
42 | auth.$logout();
43 | },
44 |
45 | createAccount: function(email, pass, name) {
46 | return auth.$createUser(email, pass)
47 | .then(function() {
48 | // authenticate so we have permission to write to Firebase
49 | return fns.login('password', {email: email, password: pass});
50 | })
51 | .then(function(user) {
52 | // store user data in Firebase after creating account
53 | return createProfile(user.uid, email, name).then(function() {
54 | return user;
55 | });
56 | });
57 | },
58 |
59 | changePassword: function(email, oldpass, newpass) {
60 | return auth.$changePassword(email, oldpass, newpass);
61 | },
62 |
63 | changeEmail: function(password, newEmail) {
64 | return changeEmail(password, fns.user.email, newEmail, this);
65 | },
66 |
67 | removeUser: function(email, pass) {
68 | return auth.$removeUser(email, pass);
69 | },
70 |
71 | watch: function(cb, $scope) {
72 | listeners.push(cb);
73 | fns.getUser().then(function(user) {
74 | cb(user);
75 | });
76 | var unbind = function() {
77 | var i = listeners.indexOf(cb);
78 | if( i > -1 ) { listeners.splice(i, 1); }
79 | };
80 | if( $scope ) {
81 | $scope.$on('$destroy', unbind);
82 | }
83 | return unbind;
84 | }
85 | };
86 |
87 | $rootScope.$on('$firebaseSimpleLogin:login', statusChange);
88 | $rootScope.$on('$firebaseSimpleLogin:logout', statusChange);
89 | $rootScope.$on('$firebaseSimpleLogin:error', statusChange);
90 | auth.$getCurrentUser(statusChange);
91 |
92 | return fns;
93 | })
94 |
95 | .factory('createProfile', function(fbutil, $q, $timeout) {
96 | return function(id, email, name) {
97 | var ref = fbutil.ref('users', id), def = $q.defer();
98 | ref.set({email: email, name: name||firstPartOfEmail(email)}, function(err) {
99 | $timeout(function() {
100 | if( err ) {
101 | def.reject(err);
102 | }
103 | else {
104 | def.resolve(ref);
105 | }
106 | });
107 | });
108 |
109 | function firstPartOfEmail(email) {
110 | return ucfirst(email.substr(0, email.indexOf('@'))||'');
111 | }
112 |
113 | function ucfirst (str) {
114 | // credits: http://kevin.vanzonneveld.net
115 | str += '';
116 | var f = str.charAt(0).toUpperCase();
117 | return f + str.substr(1);
118 | }
119 |
120 | return def.promise;
121 | };
122 | })
123 |
124 | .factory('changeEmail', function(fbutil, $q) {
125 | return function(password, oldEmail, newEmail, simpleLogin) {
126 | var ctx = { old: { email: oldEmail }, curr: { email: newEmail } };
127 |
128 | // execute activities in order; first we authenticate the user
129 | return authOldAccount()
130 | // then we fetch old account details
131 | .then( loadOldProfile )
132 | // then we create a new account
133 | .then( createNewAccount )
134 | // then we copy old account info
135 | .then( copyProfile )
136 | // and once they safely exist, then we can delete the old ones
137 | // we have to authenticate as the old user again
138 | .then( authOldAccount )
139 | .then( removeOldProfile )
140 | .then( removeOldLogin )
141 | // and now authenticate as the new user
142 | .then( authNewAccount )
143 | .catch(function(err) { console.error(err); return $q.reject(err); });
144 |
145 | function authOldAccount() {
146 | return simpleLogin.login('password', {email: ctx.old.email, password: password})
147 | .then(function(user) {
148 | ctx.old.uid = user.uid;
149 | });
150 | }
151 |
152 | function loadOldProfile() {
153 | var def = $q.defer();
154 | ctx.old.ref = fbutil.ref('users', ctx.old.uid);
155 | ctx.old.ref.once('value',
156 | function(snap){
157 | var dat = snap.val();
158 | if( dat === null ) {
159 | def.reject(oldEmail + ' not found');
160 | }
161 | else {
162 | ctx.old.name = dat.name;
163 | ctx.curr.name = dat.name;
164 | def.resolve();
165 | }
166 | },
167 | function(err){
168 | def.reject(err);
169 | });
170 | return def.promise;
171 | }
172 |
173 | function createNewAccount() {
174 | return simpleLogin.createAccount(ctx.curr.email, password, ctx.old.name).then(function(user) {
175 | ctx.curr.uid = user.uid;
176 | });
177 | }
178 |
179 | function copyProfile() {
180 | var d = $q.defer();
181 | ctx.curr.ref = fbutil.ref('users', ctx.curr.uid);
182 | var profile = {email: ctx.curr.email, name: ctx.curr.name};
183 | ctx.curr.ref.set(profile, function(err) {
184 | if (err) {
185 | d.reject(err);
186 | } else {
187 | d.resolve();
188 | }
189 | });
190 | return d.promise;
191 | }
192 |
193 | function removeOldProfile() {
194 | var d = $q.defer();
195 | ctx.old.ref.remove(function(err) {
196 | if (err) {
197 | d.reject(err);
198 | } else {
199 | d.resolve();
200 | }
201 | });
202 | return d.promise;
203 | }
204 |
205 | function removeOldLogin() {
206 | var def = $q.defer();
207 | simpleLogin.removeUser(ctx.old.email, password).then(function() {
208 | def.resolve();
209 | }, function(err) {
210 | def.reject(err);
211 | });
212 | return def.promise;
213 | }
214 |
215 | function authNewAccount() {
216 | return simpleLogin.login('password', {email: ctx.curr.email, password: password});
217 | }
218 | };
219 | });
220 | })();
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // Generated on 2015-04-26 using generator-angularfire 0.8.2-7
2 | 'use strict';
3 |
4 | // # Globbing
5 | // for performance reasons we're only matching one level down:
6 | // 'test/spec/{,*/}*.js'
7 | // use this if you want to recursively match all subfolders:
8 | // 'test/spec/**/*.js'
9 |
10 | module.exports = function (grunt) {
11 |
12 | // Load grunt tasks automatically
13 | require('load-grunt-tasks')(grunt);
14 |
15 | // Time how long tasks take. Can help when optimizing build times
16 | require('time-grunt')(grunt);
17 |
18 | // Configurable paths for the application
19 | var appConfig = {
20 | app: require('./bower.json').appPath || 'app',
21 | dist: 'dist'
22 | };
23 |
24 | // Define the configuration for all the tasks
25 | grunt.initConfig({
26 |
27 | // Project settings
28 | yeoman: appConfig,
29 |
30 | // Watches files for changes and runs tasks based on the changed files
31 | watch: {
32 | bower: {
33 | files: ['bower.json'],
34 | tasks: ['wiredep']
35 | },
36 | js: {
37 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
38 | tasks: ['newer:jshint:all'],
39 | options: {
40 | livereload: '<%= connect.options.livereload %>'
41 | }
42 | },
43 | jsTest: {
44 | files: ['test/spec/{,*/}*.js'],
45 | tasks: ['newer:jshint:test', 'karma']
46 | },
47 | styles: {
48 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
49 | tasks: ['newer:copy:styles', 'autoprefixer']
50 | },
51 | gruntfile: {
52 | files: ['Gruntfile.js']
53 | },
54 | livereload: {
55 | options: {
56 | livereload: '<%= connect.options.livereload %>'
57 | },
58 | files: [
59 | '<%= yeoman.app %>/{,*/}*.html',
60 | '.tmp/styles/{,*/}*.css',
61 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
62 | ]
63 | }
64 | },
65 |
66 | // The actual grunt server settings
67 | connect: {
68 | options: {
69 | port: 9000,
70 | // Change this to '0.0.0.0' to access the server from outside.
71 | hostname: 'localhost',
72 | livereload: 35729
73 | },
74 | livereload: {
75 | options: {
76 | open: true,
77 | middleware: function (connect) {
78 | return [
79 | connect.static('.tmp'),
80 | connect().use(
81 | '/bower_components',
82 | connect.static('./bower_components')
83 | ),
84 | connect.static(appConfig.app)
85 | ];
86 | }
87 | }
88 | },
89 | test: {
90 | options: {
91 | port: 9001,
92 | middleware: function (connect) {
93 | return [
94 | connect.static('.tmp'),
95 | connect.static('test'),
96 | connect().use(
97 | '/bower_components',
98 | connect.static('./bower_components')
99 | ),
100 | connect.static(appConfig.app)
101 | ];
102 | }
103 | }
104 | },
105 | dist: {
106 | options: {
107 | open: true,
108 | base: '<%= yeoman.dist %>'
109 | }
110 | }
111 | },
112 |
113 | // Make sure code styles are up to par and there are no obvious mistakes
114 | jshint: {
115 | options: {
116 | jshintrc: '.jshintrc',
117 | reporter: require('jshint-stylish')
118 | },
119 | all: {
120 | src: [
121 | 'Gruntfile.js',
122 | '<%= yeoman.app %>/scripts/{,*/}*.js'
123 | ]
124 | },
125 | test: {
126 | options: {
127 | jshintrc: 'test/.jshintrc'
128 | },
129 | src: ['test/spec/{,*/}*.js']
130 | }
131 | },
132 |
133 | // Empties folders to start fresh
134 | clean: {
135 | dist: {
136 | files: [{
137 | dot: true,
138 | src: [
139 | '.tmp',
140 | '<%= yeoman.dist %>/{,*/}*',
141 | '!<%= yeoman.dist %>/.git*'
142 | ]
143 | }]
144 | },
145 | server: '.tmp'
146 | },
147 |
148 | // Add vendor prefixed styles
149 | autoprefixer: {
150 | options: {
151 | browsers: ['last 1 version']
152 | },
153 | dist: {
154 | files: [{
155 | expand: true,
156 | cwd: '.tmp/styles/',
157 | src: '{,*/}*.css',
158 | dest: '.tmp/styles/'
159 | }]
160 | }
161 | },
162 |
163 | // Automatically inject Bower components into the app
164 | wiredep: {
165 | options: {
166 | // cwd: '<%= yeoman.app %>'
167 | },
168 | app: {
169 | src: ['<%= yeoman.app %>/index.html'],
170 | ignorePath: /\.\.\//
171 | }
172 | },
173 |
174 | // Renames files for browser caching purposes
175 | filerev: {
176 | dist: {
177 | src: [
178 | '<%= yeoman.dist %>/scripts/{,*/}*.js',
179 | '<%= yeoman.dist %>/styles/{,*/}*.css',
180 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
181 | '<%= yeoman.dist %>/styles/fonts/*'
182 | ]
183 | }
184 | },
185 |
186 | // Reads HTML for usemin blocks to enable smart builds that automatically
187 | // concat, minify and revision files. Creates configurations in memory so
188 | // additional tasks can operate on them
189 | useminPrepare: {
190 | html: '<%= yeoman.app %>/index.html',
191 | options: {
192 | dest: '<%= yeoman.dist %>',
193 | flow: {
194 | html: {
195 | steps: {
196 | js: ['concat', 'uglifyjs'],
197 | css: ['cssmin']
198 | },
199 | post: {}
200 | }
201 | }
202 | }
203 | },
204 |
205 | // Performs rewrites based on filerev and the useminPrepare configuration
206 | usemin: {
207 | html: ['<%= yeoman.dist %>/{,*/}*.html'],
208 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
209 | options: {
210 | assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images']
211 | }
212 | },
213 |
214 | // The following *-min tasks will produce minified files in the dist folder
215 | // By default, your `index.html`'s will take care of
216 | // minification. These next options are pre-configured if you do not wish
217 | // to use the Usemin blocks.
218 | // cssmin: {
219 | // dist: {
220 | // files: {
221 | // '<%= yeoman.dist %>/styles/main.css': [
222 | // '.tmp/styles/{,*/}*.css'
223 | // ]
224 | // }
225 | // }
226 | // },
227 | // uglify: {
228 | // dist: {
229 | // files: {
230 | // '<%= yeoman.dist %>/scripts/scripts.js': [
231 | // '<%= yeoman.dist %>/scripts/scripts.js'
232 | // ]
233 | // }
234 | // }
235 | // },
236 | // concat: {
237 | // dist: {}
238 | // },
239 |
240 | imagemin: {
241 | dist: {
242 | files: [{
243 | expand: true,
244 | cwd: '<%= yeoman.app %>/images',
245 | src: '{,*/}*.{png,jpg,jpeg,gif}',
246 | dest: '<%= yeoman.dist %>/images'
247 | }]
248 | }
249 | },
250 |
251 | svgmin: {
252 | dist: {
253 | files: [{
254 | expand: true,
255 | cwd: '<%= yeoman.app %>/images',
256 | src: '{,*/}*.svg',
257 | dest: '<%= yeoman.dist %>/images'
258 | }]
259 | }
260 | },
261 |
262 | htmlmin: {
263 | dist: {
264 | options: {
265 | collapseWhitespace: true,
266 | conservativeCollapse: true,
267 | collapseBooleanAttributes: true,
268 | removeCommentsFromCDATA: true,
269 | removeOptionalTags: true
270 | },
271 | files: [{
272 | expand: true,
273 | cwd: '<%= yeoman.dist %>',
274 | src: ['*.html', 'views/{,*/}*.html'],
275 | dest: '<%= yeoman.dist %>'
276 | }]
277 | }
278 | },
279 |
280 | // ng-annotate tries to make the code safe for minification automatically
281 | // by using the Angular long form for dependency injection.
282 | ngAnnotate: {
283 | dist: {
284 | files: [{
285 | expand: true,
286 | cwd: '.tmp/concat/scripts',
287 | src: '*.js',
288 | dest: '.tmp/concat/scripts'
289 | }]
290 | }
291 | },
292 |
293 | // Replace Google CDN references
294 | cdnify: {
295 | dist: {
296 | html: ['<%= yeoman.dist %>/*.html']
297 | }
298 | },
299 |
300 | // Copies remaining files to places other tasks can use
301 | copy: {
302 | dist: {
303 | files: [{
304 | expand: true,
305 | dot: true,
306 | cwd: '<%= yeoman.app %>',
307 | dest: '<%= yeoman.dist %>',
308 | src: [
309 | '*.{ico,png,txt}',
310 | '.htaccess',
311 | '*.html',
312 | 'views/{,*/}*.html',
313 | 'images/{,*/}*.{webp}',
314 | 'fonts/*'
315 | ]
316 | }, {
317 | expand: true,
318 | cwd: '.tmp/images',
319 | dest: '<%= yeoman.dist %>/images',
320 | src: ['generated/*']
321 | }, {
322 | expand: true,
323 | cwd: 'bower_components/bootstrap/dist',
324 | src: 'fonts/*',
325 | dest: '<%= yeoman.dist %>'
326 | }]
327 | },
328 | styles: {
329 | expand: true,
330 | cwd: '<%= yeoman.app %>/styles',
331 | dest: '.tmp/styles/',
332 | src: '{,*/}*.css'
333 | }
334 | },
335 |
336 | // Run some tasks in parallel to speed up the build process
337 | concurrent: {
338 | server: [
339 | 'copy:styles'
340 | ],
341 | test: [
342 | 'copy:styles'
343 | ],
344 | dist: [
345 | 'copy:styles',
346 | 'imagemin',
347 | 'svgmin'
348 | ]
349 | },
350 |
351 | // Test settings
352 | karma: {
353 | unit: {
354 | configFile: 'test/karma.conf.js',
355 | singleRun: true
356 | }
357 | }
358 | });
359 |
360 |
361 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
362 | if (target === 'dist') {
363 | return grunt.task.run(['build', 'connect:dist:keepalive']);
364 | }
365 |
366 | grunt.task.run([
367 | 'clean:server',
368 | 'wiredep',
369 | 'concurrent:server',
370 | 'autoprefixer',
371 | 'connect:livereload',
372 | 'watch'
373 | ]);
374 | });
375 |
376 | grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
377 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
378 | grunt.task.run(['serve:' + target]);
379 | });
380 |
381 | grunt.registerTask('test', [
382 | 'clean:server',
383 | 'concurrent:test',
384 | 'autoprefixer',
385 | 'connect:test',
386 | 'karma'
387 | ]);
388 |
389 | grunt.registerTask('build', [
390 | 'clean:dist',
391 | 'wiredep',
392 | 'useminPrepare',
393 | 'concurrent:dist',
394 | 'autoprefixer',
395 | 'concat',
396 | 'ngAnnotate',
397 | 'copy:dist',
398 | 'cdnify',
399 | 'cssmin',
400 | 'uglify',
401 | 'filerev',
402 | 'usemin',
403 | 'htmlmin'
404 | ]);
405 |
406 | grunt.registerTask('default', [
407 | 'newer:jshint',
408 | 'test',
409 | 'build'
410 | ]);
411 | };
412 |
--------------------------------------------------------------------------------
/app/.htaccess:
--------------------------------------------------------------------------------
1 | # Apache Configuration File
2 |
3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access
4 | # to the main server config file (usually called `httpd.conf`), you should add
5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
6 |
7 | # ##############################################################################
8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) #
9 | # ##############################################################################
10 |
11 | # ------------------------------------------------------------------------------
12 | # | Cross-domain AJAX requests |
13 | # ------------------------------------------------------------------------------
14 |
15 | # Enable cross-origin AJAX requests.
16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
17 | # http://enable-cors.org/
18 |
19 | #
20 | # Header set Access-Control-Allow-Origin "*"
21 | #
22 |
23 | # ------------------------------------------------------------------------------
24 | # | CORS-enabled images |
25 | # ------------------------------------------------------------------------------
26 |
27 | # Send the CORS header for images when browsers request it.
28 | # https://developer.mozilla.org/en/CORS_Enabled_Image
29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
31 |
32 |
33 |
34 |
35 | SetEnvIf Origin ":" IS_CORS
36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS
37 |
38 |
39 |
40 |
41 | # ------------------------------------------------------------------------------
42 | # | Web fonts access |
43 | # ------------------------------------------------------------------------------
44 |
45 | # Allow access from all domains for web fonts
46 |
47 |
48 |
49 | Header set Access-Control-Allow-Origin "*"
50 |
51 |
52 |
53 |
54 | # ##############################################################################
55 | # # ERRORS #
56 | # ##############################################################################
57 |
58 | # ------------------------------------------------------------------------------
59 | # | 404 error prevention for non-existing redirected folders |
60 | # ------------------------------------------------------------------------------
61 |
62 | # Prevent Apache from returning a 404 error for a rewrite if a directory
63 | # with the same name does not exist.
64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
65 | # http://www.webmasterworld.com/apache/3808792.htm
66 |
67 | Options -MultiViews
68 |
69 | # ------------------------------------------------------------------------------
70 | # | Custom error messages / pages |
71 | # ------------------------------------------------------------------------------
72 |
73 | # You can customize what Apache returns to the client in case of an error (see
74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
75 |
76 | ErrorDocument 404 /404.html
77 |
78 |
79 | # ##############################################################################
80 | # # INTERNET EXPLORER #
81 | # ##############################################################################
82 |
83 | # ------------------------------------------------------------------------------
84 | # | Better website experience |
85 | # ------------------------------------------------------------------------------
86 |
87 | # Force IE to render pages in the highest available mode in the various
88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
89 |
90 |
91 | Header set X-UA-Compatible "IE=edge"
92 | # `mod_headers` can't match based on the content-type, however, we only
93 | # want to send this header for HTML pages and not for the other resources
94 |
95 | Header unset X-UA-Compatible
96 |
97 |
98 |
99 | # ------------------------------------------------------------------------------
100 | # | Cookie setting from iframes |
101 | # ------------------------------------------------------------------------------
102 |
103 | # Allow cookies to be set from iframes in IE.
104 |
105 | #
106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
107 | #
108 |
109 | # ------------------------------------------------------------------------------
110 | # | Screen flicker |
111 | # ------------------------------------------------------------------------------
112 |
113 | # Stop screen flicker in IE on CSS rollovers (this only works in
114 | # combination with the `ExpiresByType` directives for images from below).
115 |
116 | # BrowserMatch "MSIE" brokenvary=1
117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
118 | # BrowserMatch "Opera" !brokenvary
119 | # SetEnvIf brokenvary 1 force-no-vary
120 |
121 |
122 | # ##############################################################################
123 | # # MIME TYPES AND ENCODING #
124 | # ##############################################################################
125 |
126 | # ------------------------------------------------------------------------------
127 | # | Proper MIME types for all files |
128 | # ------------------------------------------------------------------------------
129 |
130 |
131 |
132 | # Audio
133 | AddType audio/mp4 m4a f4a f4b
134 | AddType audio/ogg oga ogg
135 |
136 | # JavaScript
137 | # Normalize to standard type (it's sniffed in IE anyways):
138 | # http://tools.ietf.org/html/rfc4329#section-7.2
139 | AddType application/javascript js jsonp
140 | AddType application/json json
141 |
142 | # Video
143 | AddType video/mp4 mp4 m4v f4v f4p
144 | AddType video/ogg ogv
145 | AddType video/webm webm
146 | AddType video/x-flv flv
147 |
148 | # Web fonts
149 | AddType application/font-woff woff
150 | AddType application/vnd.ms-fontobject eot
151 |
152 | # Browsers usually ignore the font MIME types and sniff the content,
153 | # however, Chrome shows a warning if other MIME types are used for the
154 | # following fonts.
155 | AddType application/x-font-ttf ttc ttf
156 | AddType font/opentype otf
157 |
158 | # Make SVGZ fonts work on iPad:
159 | # https://twitter.com/FontSquirrel/status/14855840545
160 | AddType image/svg+xml svg svgz
161 | AddEncoding gzip svgz
162 |
163 | # Other
164 | AddType application/octet-stream safariextz
165 | AddType application/x-chrome-extension crx
166 | AddType application/x-opera-extension oex
167 | AddType application/x-shockwave-flash swf
168 | AddType application/x-web-app-manifest+json webapp
169 | AddType application/x-xpinstall xpi
170 | AddType application/xml atom rdf rss xml
171 | AddType image/webp webp
172 | AddType image/x-icon ico
173 | AddType text/cache-manifest appcache manifest
174 | AddType text/vtt vtt
175 | AddType text/x-component htc
176 | AddType text/x-vcard vcf
177 |
178 |
179 |
180 | # ------------------------------------------------------------------------------
181 | # | UTF-8 encoding |
182 | # ------------------------------------------------------------------------------
183 |
184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
185 | AddDefaultCharset utf-8
186 |
187 | # Force UTF-8 for certain file formats.
188 |
189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
190 |
191 |
192 |
193 | # ##############################################################################
194 | # # URL REWRITES #
195 | # ##############################################################################
196 |
197 | # ------------------------------------------------------------------------------
198 | # | Rewrite engine |
199 | # ------------------------------------------------------------------------------
200 |
201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is
202 | # necessary for the following directives to work.
203 |
204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to
205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
207 |
208 | # Also, some cloud hosting services require `RewriteBase` to be set:
209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
210 |
211 |
212 | Options +FollowSymlinks
213 | # Options +SymLinksIfOwnerMatch
214 | RewriteEngine On
215 | # RewriteBase /
216 |
217 |
218 | # ------------------------------------------------------------------------------
219 | # | Suppressing / Forcing the "www." at the beginning of URLs |
220 | # ------------------------------------------------------------------------------
221 |
222 | # The same content should never be available under two different URLs especially
223 | # not with and without "www." at the beginning. This can cause SEO problems
224 | # (duplicate content), therefore, you should choose one of the alternatives and
225 | # redirect the other one.
226 |
227 | # By default option 1 (no "www.") is activated:
228 | # http://no-www.org/faq.php?q=class_b
229 |
230 | # If you'd prefer to use option 2, just comment out all the lines from option 1
231 | # and uncomment the ones from option 2.
232 |
233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
234 |
235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
236 |
237 | # Option 1: rewrite www.example.com → example.com
238 |
239 |
240 | RewriteCond %{HTTPS} !=on
241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
243 |
244 |
245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
246 |
247 | # Option 2: rewrite example.com → www.example.com
248 |
249 | # Be aware that the following might not be a good idea if you use "real"
250 | # subdomains for certain parts of your website.
251 |
252 | #
253 | # RewriteCond %{HTTPS} !=on
254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
256 | #
257 |
258 |
259 | # ##############################################################################
260 | # # SECURITY #
261 | # ##############################################################################
262 |
263 | # ------------------------------------------------------------------------------
264 | # | Content Security Policy (CSP) |
265 | # ------------------------------------------------------------------------------
266 |
267 | # You can mitigate the risk of cross-site scripting and other content-injection
268 | # attacks by setting a Content Security Policy which whitelists trusted sources
269 | # of content for your site.
270 |
271 | # The example header below allows ONLY scripts that are loaded from the current
272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't
273 | # work as-is for your site!
274 |
275 | # To get all the details you'll need to craft a reasonable policy for your site,
276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
277 | # see the specification: http://w3.org/TR/CSP).
278 |
279 | #
280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
281 | #
282 | # Header unset Content-Security-Policy
283 | #
284 | #
285 |
286 | # ------------------------------------------------------------------------------
287 | # | File access |
288 | # ------------------------------------------------------------------------------
289 |
290 | # Block access to directories without a default document.
291 | # Usually you should leave this uncommented because you shouldn't allow anyone
292 | # to surf through every directory on your server (which may includes rather
293 | # private places like the CMS's directories).
294 |
295 |
296 | Options -Indexes
297 |
298 |
299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
300 |
301 | # Block access to hidden files and directories.
302 | # This includes directories used by version control systems such as Git and SVN.
303 |
304 |
305 | RewriteCond %{SCRIPT_FILENAME} -d [OR]
306 | RewriteCond %{SCRIPT_FILENAME} -f
307 | RewriteRule "(^|/)\." - [F]
308 |
309 |
310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
311 |
312 | # Block access to backup and source files.
313 | # These files may be left by some text editors and can pose a great security
314 | # danger when anyone has access to them.
315 |
316 |
317 | Order allow,deny
318 | Deny from all
319 | Satisfy All
320 |
321 |
322 | # ------------------------------------------------------------------------------
323 | # | Secure Sockets Layer (SSL) |
324 | # ------------------------------------------------------------------------------
325 |
326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
327 | # prevent `https://www.example.com` when your certificate only allows
328 | # `https://secure.example.com`.
329 |
330 | #
331 | # RewriteCond %{SERVER_PORT} !^443
332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
333 | #
334 |
335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
336 |
337 | # Force client-side SSL redirection.
338 |
339 | # If a user types "example.com" in his browser, the above rule will redirect him
340 | # to the secure version of the site. That still leaves a window of opportunity
341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the
342 | # request. The following header ensures that browser will ONLY connect to your
343 | # server via HTTPS, regardless of what the users type in the address bar.
344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
345 |
346 | #
347 | # Header set Strict-Transport-Security max-age=16070400;
348 | #
349 |
350 | # ------------------------------------------------------------------------------
351 | # | Server software information |
352 | # ------------------------------------------------------------------------------
353 |
354 | # Avoid displaying the exact Apache version number, the description of the
355 | # generic OS-type and the information about Apache's compiled-in modules.
356 |
357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
358 |
359 | # ServerTokens Prod
360 |
361 |
362 | # ##############################################################################
363 | # # WEB PERFORMANCE #
364 | # ##############################################################################
365 |
366 | # ------------------------------------------------------------------------------
367 | # | Compression |
368 | # ------------------------------------------------------------------------------
369 |
370 |
371 |
372 | # Force compression for mangled headers.
373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
374 |
375 |
376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
378 |
379 |
380 |
381 | # Compress all output labeled with one of the following MIME-types
382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
383 | # and can remove the `` and `` lines
384 | # as `AddOutputFilterByType` is still in the core directives).
385 |
386 | AddOutputFilterByType DEFLATE application/atom+xml \
387 | application/javascript \
388 | application/json \
389 | application/rss+xml \
390 | application/vnd.ms-fontobject \
391 | application/x-font-ttf \
392 | application/x-web-app-manifest+json \
393 | application/xhtml+xml \
394 | application/xml \
395 | font/opentype \
396 | image/svg+xml \
397 | image/x-icon \
398 | text/css \
399 | text/html \
400 | text/plain \
401 | text/x-component \
402 | text/xml
403 |
404 |
405 |
406 |
407 | # ------------------------------------------------------------------------------
408 | # | Content transformations |
409 | # ------------------------------------------------------------------------------
410 |
411 | # Prevent some of the mobile network providers from modifying the content of
412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
413 |
414 | #
415 | # Header set Cache-Control "no-transform"
416 | #
417 |
418 | # ------------------------------------------------------------------------------
419 | # | ETag removal |
420 | # ------------------------------------------------------------------------------
421 |
422 | # Since we're sending far-future expires headers (see below), ETags can
423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags.
424 |
425 | # `FileETag None` is not enough for every server.
426 |
427 | Header unset ETag
428 |
429 |
430 | FileETag None
431 |
432 | # ------------------------------------------------------------------------------
433 | # | Expires headers (for better cache control) |
434 | # ------------------------------------------------------------------------------
435 |
436 | # The following expires headers are set pretty far in the future. If you don't
437 | # control versioning with filename-based cache busting, consider lowering the
438 | # cache time for resources like CSS and JS to something like 1 week.
439 |
440 |
441 |
442 | ExpiresActive on
443 | ExpiresDefault "access plus 1 month"
444 |
445 | # CSS
446 | ExpiresByType text/css "access plus 1 year"
447 |
448 | # Data interchange
449 | ExpiresByType application/json "access plus 0 seconds"
450 | ExpiresByType application/xml "access plus 0 seconds"
451 | ExpiresByType text/xml "access plus 0 seconds"
452 |
453 | # Favicon (cannot be renamed!)
454 | ExpiresByType image/x-icon "access plus 1 week"
455 |
456 | # HTML components (HTCs)
457 | ExpiresByType text/x-component "access plus 1 month"
458 |
459 | # HTML
460 | ExpiresByType text/html "access plus 0 seconds"
461 |
462 | # JavaScript
463 | ExpiresByType application/javascript "access plus 1 year"
464 |
465 | # Manifest files
466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
467 | ExpiresByType text/cache-manifest "access plus 0 seconds"
468 |
469 | # Media
470 | ExpiresByType audio/ogg "access plus 1 month"
471 | ExpiresByType image/gif "access plus 1 month"
472 | ExpiresByType image/jpeg "access plus 1 month"
473 | ExpiresByType image/png "access plus 1 month"
474 | ExpiresByType video/mp4 "access plus 1 month"
475 | ExpiresByType video/ogg "access plus 1 month"
476 | ExpiresByType video/webm "access plus 1 month"
477 |
478 | # Web feeds
479 | ExpiresByType application/atom+xml "access plus 1 hour"
480 | ExpiresByType application/rss+xml "access plus 1 hour"
481 |
482 | # Web fonts
483 | ExpiresByType application/font-woff "access plus 1 month"
484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
485 | ExpiresByType application/x-font-ttf "access plus 1 month"
486 | ExpiresByType font/opentype "access plus 1 month"
487 | ExpiresByType image/svg+xml "access plus 1 month"
488 |
489 |
490 |
491 | # ------------------------------------------------------------------------------
492 | # | Filename-based cache busting |
493 | # ------------------------------------------------------------------------------
494 |
495 | # If you're not using a build process to manage your filename version revving,
496 | # you might want to consider enabling the following directives to route all
497 | # requests such as `/css/style.12345.css` to `/css/style.css`.
498 |
499 | # To understand why this is important and a better idea than `*.css?v231`, read:
500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
501 |
502 | #
503 | # RewriteCond %{REQUEST_FILENAME} !-f
504 | # RewriteCond %{REQUEST_FILENAME} !-d
505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
506 | #
507 |
508 | # ------------------------------------------------------------------------------
509 | # | File concatenation |
510 | # ------------------------------------------------------------------------------
511 |
512 | # Allow concatenation from within specific CSS and JS files, e.g.:
513 | # Inside of `script.combined.js` you could have
514 | #
515 | #
516 | # and they would be included into this single file.
517 |
518 | #
519 | #
520 | # Options +Includes
521 | # AddOutputFilterByType INCLUDES application/javascript application/json
522 | # SetOutputFilter INCLUDES
523 | #
524 | #
525 | # Options +Includes
526 | # AddOutputFilterByType INCLUDES text/css
527 | # SetOutputFilter INCLUDES
528 | #
529 | #
530 |
531 | # ------------------------------------------------------------------------------
532 | # | Persistent connections |
533 | # ------------------------------------------------------------------------------
534 |
535 | # Allow multiple requests to be sent over the same TCP connection:
536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
537 |
538 | # Enable if you serve a lot of static content but, be aware of the
539 | # possible disadvantages!
540 |
541 | #
542 | # Header set Connection Keep-Alive
543 | #
544 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
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 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | $PROJECT_DIR$/Gruntfile.js
119 |
120 |
121 | false
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 | 1430088438460
292 | 1430088438460
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
--------------------------------------------------------------------------------