├── .gitattributes ├── app ├── .buildignore ├── robots.txt ├── views │ ├── about.html │ ├── list.html │ ├── view_recipe.html │ ├── main.html │ └── recipe_form.html ├── favicon.ico ├── images │ └── yeoman.png ├── scripts │ ├── controllers │ │ ├── main.js │ │ ├── about.js │ │ └── controllers.js │ ├── directives │ │ └── directives.js │ ├── services │ │ └── services.js │ └── app.js ├── styles │ └── main.css ├── index.html ├── 404.html └── .htaccess ├── .bowerrc ├── .gitignore ├── .travis.yml ├── bower.json ├── test ├── spec │ ├── services │ │ └── services.js │ ├── directives │ │ └── directives.js │ └── controllers │ │ ├── main.js │ │ ├── about.js │ │ └── controllers.js ├── .jshintrc └── karma.conf.js ├── .jshintrc ├── .editorconfig ├── package.json ├── server └── index.js └── Gruntfile.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /app/views/about.html: -------------------------------------------------------------------------------- 1 |

This is the about view.

2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bob/guthub/master/app/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | bower_components 6 | -------------------------------------------------------------------------------- /app/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bob/guthub/master/app/images/yeoman.png -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /app/views/list.html: -------------------------------------------------------------------------------- 1 |

Recipe List

2 | 3 | 8 | -------------------------------------------------------------------------------- /app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name guthubApp.controller:MainCtrl 6 | * @description 7 | * # MainCtrl 8 | * Controller of the guthubApp 9 | */ 10 | angular.module('guthubApp') 11 | .controller('MainCtrl', function ($scope) { 12 | $scope.awesomeThings = [ 13 | 'HTML5 Boilerplate', 14 | 'AngularJS', 15 | 'Karma' 16 | ]; 17 | }); 18 | -------------------------------------------------------------------------------- /app/scripts/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name guthubApp.controller:AboutCtrl 6 | * @description 7 | * # AboutCtrl 8 | * Controller of the guthubApp 9 | */ 10 | angular.module('guthubApp') 11 | .controller('AboutCtrl', function ($scope) { 12 | $scope.awesomeThings = [ 13 | 'HTML5 Boilerplate', 14 | 'AngularJS', 15 | 'Karma' 16 | ]; 17 | }); 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guthub", 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-route": "1.2.16" 11 | }, 12 | "devDependencies": { 13 | "angular-mocks": "1.2.16", 14 | "angular-scenario": "1.2.16" 15 | }, 16 | "appPath": "app" 17 | } 18 | -------------------------------------------------------------------------------- /test/spec/services/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: Services', function () { 4 | 5 | // load the service's module 6 | beforeEach(module('guthubApp')); 7 | 8 | // instantiate service 9 | var Services; 10 | beforeEach(inject(function (_Services_) { 11 | Services = _Services_; 12 | })); 13 | 14 | it('should do something', function () { 15 | expect(!!Services).toBe(true); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /.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 | "angular": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /test/spec/directives/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Directive: directives', function () { 4 | 5 | // load the directive's module 6 | beforeEach(module('guthubApp')); 7 | 8 | var element, 9 | scope; 10 | 11 | beforeEach(inject(function ($rootScope) { 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should make hidden element visible', inject(function ($compile) { 16 | element = angular.element(''); 17 | element = $compile(element)(scope); 18 | expect(element.text()).toBe('this is the directives directive'); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('guthubApp')); 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 | -------------------------------------------------------------------------------- /test/spec/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AboutCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('guthubApp')); 7 | 8 | var AboutCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | AboutCtrl = $controller('AboutCtrl', { 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 | -------------------------------------------------------------------------------- /test/spec/controllers/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ControllersCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('guthubApp')); 7 | 8 | var ControllersCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | ControllersCtrl = $controller('ControllersCtrl', { 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 | -------------------------------------------------------------------------------- /app/views/view_recipe.html: -------------------------------------------------------------------------------- 1 |

{{recipe.title}}

2 | 3 |
{{recipe.description}}
4 | 5 |

Ingredients

6 | No ingredients 7 | 14 | 15 |

Instructions

16 |
{{recipe.instructions}}
17 | 18 |
19 |
20 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /app/views/main.html: -------------------------------------------------------------------------------- 1 |
2 |

'Allo, 'Allo!

3 |

4 | I'm Yeoman
5 | Always a pleasure scaffolding your apps. 6 |

7 |

Splendid!

8 |
9 | 10 |
11 |

HTML5 Boilerplate

12 |

13 | HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites. 14 |

15 | 16 |

Angular

17 |

18 | AngularJS is a toolset for building the framework most suited to your application development. 19 |

20 | 21 |

Karma

22 |

Spectacular Test Runner for JavaScript.

23 |
24 | -------------------------------------------------------------------------------- /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/scripts/directives/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc directive 5 | * @name guthubApp.directive:directives 6 | * @description 7 | * # directives 8 | */ 9 | 10 | var directives = angular.module("guthub.directives", []); 11 | 12 | directives.directive("butterbar", function($rootScope) { 13 | return { 14 | link: function(scope, element, attrs) { 15 | element.addClass('hide'); 16 | 17 | $rootScope.$on('$routeChangeStart', function() { 18 | element.removeClass('hide'); 19 | }); 20 | 21 | $rootScope.$on('$routeChangeSuccess', function() { 22 | element.addClass('hide'); 23 | }); 24 | } 25 | }; 26 | }); 27 | 28 | directives.directive("focus", function() { 29 | return { 30 | link: function(scope, element, attrs) { 31 | element[0].focus(); 32 | } 33 | }; 34 | }); -------------------------------------------------------------------------------- /app/scripts/services/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc service 5 | * @name guthubApp.Services 6 | * @description 7 | * # Services 8 | * Service in the guthubApp. 9 | */ 10 | 11 | var services = angular.module("guthub.services", ["ngResource"]); 12 | 13 | services.factory("Recipe", function($resource) { 14 | return $resource('/recipes/:id', {id: '@id'}); 15 | }); 16 | 17 | services.factory("MultiRecipeLoader", function(Recipe, $q) { 18 | return function() { 19 | var delay = $q.defer(); 20 | Recipe.query(function(recipes) { 21 | delay.resolve(recipes); 22 | }, function() { 23 | delay.reject("Unable to fetch recipes"); 24 | }); 25 | 26 | return delay.promise; 27 | }; 28 | }); 29 | 30 | services.factory("RecipeLoader", function(Recipe, $route, $q) { 31 | return function() { 32 | var delay = $q.defer(); 33 | Recipe.get({id: $route.current.params.recipeId}, function(recipe) { 34 | delay.resolve(recipe); 35 | }, function() { 36 | delay.reject("Unable to fetch recipe " + $route.current.params.recipeId); 37 | }); 38 | 39 | return delay.promise; 40 | }; 41 | }); 42 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc overview 5 | * @name guthubApp 6 | * @description 7 | * # guthubApp 8 | * 9 | * Main module of the application. 10 | */ 11 | var app = angular.module('guthubApp', [ 12 | 'ngResource', 13 | 'ngRoute', 14 | 'guthub.directives', 15 | 'guthub.services', 16 | 'guthub.controllers' 17 | ]); 18 | 19 | app.config(function ($routeProvider) { 20 | $routeProvider 21 | .when('/', { 22 | templateUrl: 'views/list.html', 23 | controller: 'ListCtrl', 24 | resolve: { 25 | recipes: function(MultiRecipeLoader) { 26 | return MultiRecipeLoader(); 27 | } 28 | } 29 | }) 30 | .when('/edit/:recipeId', { 31 | templateUrl: 'views/recipe_form.html', 32 | controller: 'EditCtrl', 33 | resolve: { 34 | recipe: function(RecipeLoader) { 35 | return RecipeLoader(); 36 | } 37 | } 38 | }) 39 | .when('/view/:recipeId', { 40 | templateUrl: 'views/view_recipe.html', 41 | controller: 'ViewCtrl', 42 | resolve: { 43 | recipe: function(RecipeLoader) { 44 | return RecipeLoader(); 45 | } 46 | } 47 | }) 48 | .when('/new', { 49 | templateUrl: 'views/recipe_form.html', 50 | controller: 'NewCtrl' 51 | }) 52 | .otherwise({ 53 | redirectTo: '/' 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Greg Stewart", 3 | "name": "guthub", 4 | "version": "0.0.0", 5 | "homepage": "http://someurl.com", 6 | "dependencies": { 7 | "express": "3.1.0" 8 | }, 9 | "devDependencies": { 10 | "grunt": "^0.4.1", 11 | "grunt-autoprefixer": "^0.7.3", 12 | "grunt-concurrent": "^0.5.0", 13 | "grunt-contrib-clean": "^0.5.0", 14 | "grunt-contrib-concat": "^0.4.0", 15 | "grunt-contrib-connect": "^0.7.1", 16 | "grunt-contrib-copy": "^0.5.0", 17 | "grunt-contrib-cssmin": "^0.9.0", 18 | "grunt-contrib-htmlmin": "^0.3.0", 19 | "grunt-contrib-imagemin": "^0.7.0", 20 | "grunt-contrib-jshint": "^0.10.0", 21 | "grunt-contrib-uglify": "^0.4.0", 22 | "grunt-contrib-watch": "^0.6.1", 23 | "grunt-filerev": "^0.2.1", 24 | "grunt-google-cdn": "^0.4.0", 25 | "grunt-newer": "^0.7.0", 26 | "grunt-ngmin": "^0.0.3", 27 | "grunt-svgmin": "^0.4.0", 28 | "grunt-usemin": "^2.1.1", 29 | "grunt-wiredep": "^1.7.0", 30 | "jshint-stylish": "^0.2.0", 31 | "load-grunt-tasks": "^0.4.0", 32 | "time-grunt": "^0.3.1", 33 | "grunt-karma": "~0.8.3", 34 | "karma-phantomjs-launcher": "~0.1.4", 35 | "karma": "~0.12.22", 36 | "karma-jasmine": "~0.1.5", 37 | "grunt-express-server": "~0.4.19", 38 | 39 | "connect-livereload": "~0.2.0", 40 | "grunt-express": "~1.0.0", 41 | "grunt-open": "~0.2.0", 42 | "matchdep": "~0.1.2" 43 | }, 44 | "engines": { 45 | "node": ">=0.10.0" 46 | }, 47 | "scripts": { 48 | "test": "grunt test" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/scripts/controllers/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name guthubApp.controller:ControllersCtrl 6 | * @description 7 | * # ControllersCtrl 8 | * Controller of the guthubApp 9 | */ 10 | 11 | var app = angular.module("guthub.controllers", []); 12 | 13 | app.controller('ListCtrl', function($scope, recipes) { 14 | $scope.recipes = recipes; 15 | }); 16 | 17 | app.controller('ViewCtrl', function($scope, $location, recipe) { 18 | $scope.recipe = recipe; 19 | 20 | $scope.edit = function() { 21 | $location.path('/edit/' + recipe.id); 22 | }; 23 | }); 24 | 25 | app.controller('EditCtrl', function($scope, $location, recipe) { 26 | $scope.recipe = recipe; 27 | 28 | $scope.save = function() { 29 | $scope.recipe.$save(function(recipe) { 30 | $location.path('/view/' + recipe.id); 31 | }); 32 | }; 33 | 34 | $scope.remove = function() { 35 | delete $scope.recipe; 36 | $location.path('/'); 37 | }; 38 | }); 39 | 40 | app.controller('NewCtrl', function($scope, $location, Recipe) { 41 | $scope.recipe = new Recipe({ 42 | ingredients: [{}] 43 | }); 44 | 45 | $scope.save = function() { 46 | $scope.recipe.$save(function(recipe) { 47 | $location.path('/view/' + recipe.id); 48 | }); 49 | } 50 | }); 51 | 52 | app.controller('IngredientsCtrl', function($scope) { 53 | $scope.addIngredient = function() { 54 | var ingredients = $scope.recipe.ingredients; 55 | ingredients[ingredients.length] = {}; 56 | }; 57 | 58 | $scope.removeIngredient = function(index) { 59 | $scope.recipe.ingredients.splice(index, 1); 60 | }; 61 | }); -------------------------------------------------------------------------------- /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/views/recipe_form.html: -------------------------------------------------------------------------------- 1 |

Edit Recipe

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 | 33 |
34 |
35 |
36 | 37 |
38 | 39 |
40 | 41 |
42 |
43 | 44 |
45 | 46 | 47 |
48 |
49 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | // Generated on 2014-08-27 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/angular-resource/angular-resource.js', 24 | 'bower_components/angular-route/angular-route.js', 25 | 'app/scripts/**/*.js', 26 | 'test/mock/**/*.js', 27 | 'test/spec/**/*.js' 28 | ], 29 | 30 | // list of files / patterns to exclude 31 | exclude: [], 32 | 33 | // web server port 34 | port: 8080, 35 | 36 | // Start these browsers, currently available: 37 | // - Chrome 38 | // - ChromeCanary 39 | // - Firefox 40 | // - Opera 41 | // - Safari (only Mac) 42 | // - PhantomJS 43 | // - IE (only Windows) 44 | browsers: [ 45 | 'PhantomJS' 46 | ], 47 | 48 | // Which plugins to enable 49 | plugins: [ 50 | 'karma-phantomjs-launcher', 51 | 'karma-jasmine' 52 | ], 53 | 54 | // Continuous Integration mode 55 | // if true, it capture browsers, run tests and exit 56 | singleRun: false, 57 | 58 | colors: true, 59 | 60 | // level of logging 61 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 62 | logLevel: config.LOG_INFO, 63 | 64 | // Uncomment the following lines if you are using grunt's server to run the tests 65 | // proxies: { 66 | // '/': 'http://localhost:9000/' 67 | // }, 68 | // URL root prevent conflicts with the site root 69 | // urlRoot: '_karma_' 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /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 |

Guthub

25 |
26 | 27 |
Loading...
28 | 29 |
30 |
31 |
32 | 33 |
Recipe List
34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | //var express = require("express"), 2 | // app = express(), 3 | // port = parseInt(process.env.PORT, 10) || 8080; 4 | 5 | var express = require('express'); 6 | var path = require('path'); 7 | 8 | var app = express(); 9 | 10 | app.use(function middlewarePlaceholder(req, res, next) { 11 | return next(); 12 | }); 13 | 14 | app.configure(function(){ 15 | app.use(express.methodOverride()); 16 | app.use(express.bodyParser()); 17 | // app.use(express.static(__dirname + '/app')); 18 | app.use(app.router); 19 | app.use(express.logger('dev')); 20 | }); 21 | 22 | //connect.static('.tmp'), 23 | //express().use('/bower_components', express.static('./bower_components')), 24 | //connect.static(appConfig.app) 25 | 26 | // marker for `grunt-express` to inject static folder/contents 27 | app.use(function staticsPlaceholder(req, res, next) { 28 | return next(); 29 | }); 30 | 31 | 32 | 33 | var recipes_map = { 34 | '1': { 35 | "id": "1", 36 | "title": "Cookies", 37 | "description": "Delicious, crisp on the outside, chewy on the outside, oozing with chocolatey goodness cookies. The best kind", 38 | "ingredients": [ 39 | { 40 | "amount": "1", 41 | "amountUnits": "packet", 42 | "ingredientName": "Chips Ahoy" 43 | } 44 | ], 45 | "instructions": "1. Go buy a packet of Chips Ahoy\n2. Heat it up in an oven\n3. Enjoy warm cookies\n4. Learn how to bake cookies from somewhere else" 46 | }, 47 | '2': { 48 | id: 2, 49 | 'title': 'Recipe 2', 50 | 'description': 'Description 2', 51 | 'instructions': 'Instruction 2', 52 | ingredients: [ 53 | {amount: 13, amountUnits: 'pounds', ingredientName: 'Awesomeness'} 54 | ] 55 | } 56 | }; 57 | var next_id = 3; 58 | 59 | app.get('/recipes', function(req, res) { 60 | var recipes = []; 61 | 62 | for (var key in recipes_map) { 63 | recipes.push(recipes_map[key]); 64 | } 65 | 66 | // Simulate delay in server 67 | setTimeout(function() { 68 | res.send(recipes); 69 | }, 500); 70 | }); 71 | 72 | app.get('/recipes/:id', function(req, res) { 73 | console.log('Requesting recipe with id', req.params.id); 74 | res.send(recipes_map[req.params.id]); 75 | }); 76 | 77 | app.post('/recipes', function(req, res) { 78 | var recipe = {}; 79 | recipe.id = next_id++; 80 | recipe.title = req.body.title; 81 | recipe.description = req.body.description; 82 | recipe.ingredients = req.body.ingredients; 83 | recipe.instructions = req.body.instructions; 84 | 85 | recipes_map[recipe.id] = recipe; 86 | 87 | res.send(recipe); 88 | }); 89 | 90 | app.post('/recipes/:id', function(req, res) { 91 | var recipe = {}; 92 | recipe.id = req.params.id; 93 | recipe.title = req.body.title; 94 | recipe.description = req.body.description; 95 | recipe.ingredients = req.body.ingredients; 96 | recipe.instructions = req.body.instructions; 97 | 98 | recipes_map[recipe.id] = recipe; 99 | 100 | res.send(recipe); 101 | }); 102 | 103 | //app.listen(port); 104 | //console.log('Now serving the app at http://localhost:' + port + '/'); 105 | module.exports = app; -------------------------------------------------------------------------------- /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 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2014-08-27 using generator-angular 0.9.5 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | 6 | // # Globbing 7 | // for performance reasons we're only matching one level down: 8 | // 'test/spec/{,*/}*.js' 9 | // use this if you want to recursively match all subfolders: 10 | // 'test/spec/**/*.js' 11 | 12 | module.exports = function (grunt) { 13 | // load all grunt tasks 14 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 15 | 16 | // Load grunt tasks automatically 17 | require('load-grunt-tasks')(grunt); 18 | 19 | // Time how long tasks take. Can help when optimizing build times 20 | require('time-grunt')(grunt); 21 | 22 | // Configurable paths for the application 23 | var appConfig = { 24 | app: require('./bower.json').appPath || 'app', 25 | dist: 'dist' 26 | }; 27 | 28 | // Define the configuration for all the tasks 29 | grunt.initConfig({ 30 | 31 | // Project settings 32 | yeoman: appConfig, 33 | 34 | // Watches files for changes and runs tasks based on the changed files 35 | watch: { 36 | bower: { 37 | files: ['bower.json'], 38 | tasks: ['wiredep'] 39 | }, 40 | js: { 41 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], 42 | tasks: ['newer:jshint:all'], 43 | options: { 44 | livereload: '<%= connect.options.livereload %>' 45 | } 46 | }, 47 | jsTest: { 48 | files: ['test/spec/{,*/}*.js'], 49 | tasks: ['newer:jshint:test', 'karma'] 50 | }, 51 | styles: { 52 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'], 53 | tasks: ['newer:copy:styles', 'autoprefixer'] 54 | }, 55 | gruntfile: { 56 | files: ['Gruntfile.js'] 57 | }, 58 | livereload: { 59 | options: { 60 | livereload: '<%= connect.options.livereload %>' 61 | }, 62 | files: [ 63 | '<%= yeoman.app %>/{,*/}*.html', 64 | '.tmp/styles/{,*/}*.css', 65 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 66 | ] 67 | } 68 | }, 69 | 70 | express: { 71 | options: { 72 | port: 9000, 73 | hostname: 'localhost', 74 | livereload: 35729, 75 | bases: path.resolve('./bower_components') 76 | // middleware: function (connect) { 77 | // return [ 78 | // connect().use('/bower_components', connect.static('./bower_components')) 79 | // ]; 80 | // }, 81 | }, 82 | livereload: { 83 | options: { 84 | server: path.resolve('./server'), 85 | livereload: true, 86 | serverreload: true, 87 | bases: [path.resolve('./.tmp'), path.resolve(__dirname, appConfig.app)] 88 | 89 | } 90 | }, 91 | test: { 92 | options: { 93 | server: path.resolve('./server'), 94 | bases: [path.resolve('./.tmp'), path.resolve(__dirname, 'test')] 95 | } 96 | }, 97 | dist: { 98 | options: { 99 | server: path.resolve('./server'), 100 | bases: path.resolve(__dirname, appConfig.dist) 101 | } 102 | } 103 | }, 104 | open: { 105 | server: { 106 | url: 'http://localhost:<%= express.options.port %>' 107 | } 108 | }, 109 | 110 | // The actual grunt server settings 111 | connect: { 112 | options: { 113 | port: 9000, 114 | // Change this to '0.0.0.0' to access the server from outside. 115 | hostname: 'localhost', 116 | livereload: 35729 117 | }, 118 | livereload: { 119 | options: { 120 | open: true, 121 | middleware: function (connect) { 122 | return [ 123 | connect.static('.tmp'), 124 | connect().use( 125 | '/bower_components', 126 | connect.static('./bower_components') 127 | ), 128 | connect.static(appConfig.app) 129 | ]; 130 | } 131 | } 132 | }, 133 | test: { 134 | options: { 135 | port: 9001, 136 | middleware: function (connect) { 137 | return [ 138 | connect.static('.tmp'), 139 | connect.static('test'), 140 | connect().use( 141 | '/bower_components', 142 | connect.static('./bower_components') 143 | ), 144 | connect.static(appConfig.app) 145 | ]; 146 | } 147 | } 148 | }, 149 | dist: { 150 | options: { 151 | open: true, 152 | base: '<%= yeoman.dist %>' 153 | } 154 | } 155 | }, 156 | 157 | // Make sure code styles are up to par and there are no obvious mistakes 158 | jshint: { 159 | options: { 160 | jshintrc: '.jshintrc', 161 | reporter: require('jshint-stylish') 162 | }, 163 | all: { 164 | src: [ 165 | 'Gruntfile.js', 166 | '<%= yeoman.app %>/scripts/{,*/}*.js' 167 | ] 168 | }, 169 | test: { 170 | options: { 171 | jshintrc: 'test/.jshintrc' 172 | }, 173 | src: ['test/spec/{,*/}*.js'] 174 | } 175 | }, 176 | 177 | // Empties folders to start fresh 178 | clean: { 179 | dist: { 180 | files: [{ 181 | dot: true, 182 | src: [ 183 | '.tmp', 184 | '<%= yeoman.dist %>/{,*/}*', 185 | '!<%= yeoman.dist %>/.git*' 186 | ] 187 | }] 188 | }, 189 | server: '.tmp' 190 | }, 191 | 192 | // Add vendor prefixed styles 193 | autoprefixer: { 194 | options: { 195 | browsers: ['last 1 version'] 196 | }, 197 | dist: { 198 | files: [{ 199 | expand: true, 200 | cwd: '.tmp/styles/', 201 | src: '{,*/}*.css', 202 | dest: '.tmp/styles/' 203 | }] 204 | } 205 | }, 206 | 207 | // Automatically inject Bower components into the app 208 | wiredep: { 209 | options: { 210 | cwd: '<%= yeoman.app %>' 211 | }, 212 | app: { 213 | src: ['<%= yeoman.app %>/index.html'], 214 | ignorePath: /\.\.\// 215 | } 216 | }, 217 | 218 | // Renames files for browser caching purposes 219 | filerev: { 220 | dist: { 221 | src: [ 222 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 223 | '<%= yeoman.dist %>/styles/{,*/}*.css', 224 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 225 | '<%= yeoman.dist %>/styles/fonts/*' 226 | ] 227 | } 228 | }, 229 | 230 | // Reads HTML for usemin blocks to enable smart builds that automatically 231 | // concat, minify and revision files. Creates configurations in memory so 232 | // additional tasks can operate on them 233 | useminPrepare: { 234 | html: '<%= yeoman.app %>/index.html', 235 | options: { 236 | dest: '<%= yeoman.dist %>', 237 | flow: { 238 | html: { 239 | steps: { 240 | js: ['concat', 'uglifyjs'], 241 | css: ['cssmin'] 242 | }, 243 | post: {} 244 | } 245 | } 246 | } 247 | }, 248 | 249 | // Performs rewrites based on filerev and the useminPrepare configuration 250 | usemin: { 251 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 252 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 253 | options: { 254 | assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images'] 255 | } 256 | }, 257 | 258 | // The following *-min tasks will produce minified files in the dist folder 259 | // By default, your `index.html`'s will take care of 260 | // minification. These next options are pre-configured if you do not wish 261 | // to use the Usemin blocks. 262 | // cssmin: { 263 | // dist: { 264 | // files: { 265 | // '<%= yeoman.dist %>/styles/main.css': [ 266 | // '.tmp/styles/{,*/}*.css' 267 | // ] 268 | // } 269 | // } 270 | // }, 271 | // uglify: { 272 | // dist: { 273 | // files: { 274 | // '<%= yeoman.dist %>/scripts/scripts.js': [ 275 | // '<%= yeoman.dist %>/scripts/scripts.js' 276 | // ] 277 | // } 278 | // } 279 | // }, 280 | // concat: { 281 | // dist: {} 282 | // }, 283 | 284 | imagemin: { 285 | dist: { 286 | files: [{ 287 | expand: true, 288 | cwd: '<%= yeoman.app %>/images', 289 | src: '{,*/}*.{png,jpg,jpeg,gif}', 290 | dest: '<%= yeoman.dist %>/images' 291 | }] 292 | } 293 | }, 294 | 295 | svgmin: { 296 | dist: { 297 | files: [{ 298 | expand: true, 299 | cwd: '<%= yeoman.app %>/images', 300 | src: '{,*/}*.svg', 301 | dest: '<%= yeoman.dist %>/images' 302 | }] 303 | } 304 | }, 305 | 306 | htmlmin: { 307 | dist: { 308 | options: { 309 | collapseWhitespace: true, 310 | conservativeCollapse: true, 311 | collapseBooleanAttributes: true, 312 | removeCommentsFromCDATA: true, 313 | removeOptionalTags: true 314 | }, 315 | files: [{ 316 | expand: true, 317 | cwd: '<%= yeoman.dist %>', 318 | src: ['*.html', 'views/{,*/}*.html'], 319 | dest: '<%= yeoman.dist %>' 320 | }] 321 | } 322 | }, 323 | 324 | // ngmin tries to make the code safe for minification automatically by 325 | // using the Angular long form for dependency injection. It doesn't work on 326 | // things like resolve or inject so those have to be done manually. 327 | ngmin: { 328 | dist: { 329 | files: [{ 330 | expand: true, 331 | cwd: '.tmp/concat/scripts', 332 | src: '*.js', 333 | dest: '.tmp/concat/scripts' 334 | }] 335 | } 336 | }, 337 | 338 | // Replace Google CDN references 339 | cdnify: { 340 | dist: { 341 | html: ['<%= yeoman.dist %>/*.html'] 342 | } 343 | }, 344 | 345 | // Copies remaining files to places other tasks can use 346 | copy: { 347 | dist: { 348 | files: [{ 349 | expand: true, 350 | dot: true, 351 | cwd: '<%= yeoman.app %>', 352 | dest: '<%= yeoman.dist %>', 353 | src: [ 354 | '*.{ico,png,txt}', 355 | '.htaccess', 356 | '*.html', 357 | 'views/{,*/}*.html', 358 | 'images/{,*/}*.{webp}', 359 | 'fonts/*' 360 | ] 361 | }, { 362 | expand: true, 363 | cwd: '.tmp/images', 364 | dest: '<%= yeoman.dist %>/images', 365 | src: ['generated/*'] 366 | }] 367 | }, 368 | styles: { 369 | expand: true, 370 | cwd: '<%= yeoman.app %>/styles', 371 | dest: '.tmp/styles/', 372 | src: '{,*/}*.css' 373 | } 374 | }, 375 | 376 | // Run some tasks in parallel to speed up the build process 377 | concurrent: { 378 | server: [ 379 | 'copy:styles' 380 | ], 381 | test: [ 382 | 'copy:styles' 383 | ], 384 | dist: [ 385 | 'copy:styles', 386 | 'imagemin', 387 | 'svgmin' 388 | ] 389 | }, 390 | 391 | // Test settings 392 | karma: { 393 | unit: { 394 | configFile: 'test/karma.conf.js', 395 | singleRun: true 396 | } 397 | } 398 | }); 399 | 400 | 401 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 402 | if (target === 'dist') { 403 | return grunt.task.run(['build', 'connect:dist:keepalive']); 404 | } 405 | 406 | grunt.task.run([ 407 | 'clean:server', 408 | 'wiredep', 409 | 'concurrent:server', 410 | 'autoprefixer', 411 | 'connect:livereload', 412 | 'watch' 413 | ]); 414 | }); 415 | 416 | grunt.registerTask('server', 'Server with express script', function (target) { 417 | if (target === 'dist') { 418 | return grunt.task.run(['build', 'open', 'express:dist:keepalive']); 419 | } 420 | 421 | grunt.task.run([ 422 | 'clean:server', 423 | 'concurrent:server', 424 | 'autoprefixer', 425 | 'express:livereload', 426 | // 'open', 427 | 'watch' 428 | ]); 429 | }); 430 | 431 | grunt.registerTask('test', [ 432 | 'clean:server', 433 | 'concurrent:test', 434 | 'autoprefixer', 435 | 'connect:test', 436 | 'karma' 437 | ]); 438 | 439 | grunt.registerTask('build', [ 440 | 'clean:dist', 441 | 'wiredep', 442 | 'useminPrepare', 443 | 'concurrent:dist', 444 | 'autoprefixer', 445 | 'concat', 446 | 'ngmin', 447 | 'copy:dist', 448 | 'cdnify', 449 | 'cssmin', 450 | 'uglify', 451 | 'filerev', 452 | 'usemin', 453 | 'htmlmin' 454 | ]); 455 | 456 | grunt.registerTask('default', [ 457 | 'newer:jshint', 458 | 'test', 459 | 'build' 460 | ]); 461 | }; 462 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------