├── .gitattributes ├── app ├── .buildignore ├── robots.txt ├── favicon.ico ├── styles │ └── main.css ├── scripts │ ├── app.js │ └── controllers │ │ └── main.js ├── views │ └── main.html ├── index.html ├── 404.html └── .htaccess ├── .bowerrc ├── .gitignore ├── test ├── runner.html ├── spec │ └── controllers │ │ └── main.js └── mock │ └── feed.js ├── component.json ├── .jshintrc ├── .editorconfig ├── package.json ├── karma-e2e.conf.js ├── karma.conf.js ├── README.md └── Gruntfile.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/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 | app/components 6 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/djsreader/master/app/favicon.ico -------------------------------------------------------------------------------- /app/styles/main.css: -------------------------------------------------------------------------------- 1 | /* Will be compiled down to a single stylesheet with your sass files */ -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('djsreaderApp', []) 4 | .config(function($routeProvider) { 5 | $routeProvider 6 | .when('/', { 7 | templateUrl: 'views/main.html', 8 | controller: 'MainCtrl' 9 | }) 10 | .otherwise({ 11 | redirectTo: '/' 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "djsreader", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.0.5", 6 | "json3": "~3.2.4", 7 | "es5-shim": "~2.0.8", 8 | "angular-resource": "~1.0.5", 9 | "angular-cookies": "~1.0.5", 10 | "angular-sanitize": "~1.0.5", 11 | "sass-bootstrap": "2.3.x" 12 | }, 13 | "devDependencies": { 14 | "angular-mocks": "~1.0.5", 15 | "angular-scenario": "~1.0.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "es5": true, 5 | "esnext": true, 6 | "bitwise": true, 7 | "camelcase": true, 8 | "curly": true, 9 | "eqeqeq": true, 10 | "immed": true, 11 | "indent": 2, 12 | "latedef": true, 13 | "newcap": true, 14 | "noarg": true, 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/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function() { 4 | // load the controller's module 5 | beforeEach(module('djsreaderApp', 'mockedFeed')); 6 | 7 | var MainCtrl, scope, mockedFeed, httpBackend; 8 | 9 | // Initialize the controller and a mock scope 10 | beforeEach(inject(function($controller, $rootScope, $httpBackend, defaultJSON) { 11 | // Set up the expected feed data 12 | httpBackend = $httpBackend; 13 | $httpBackend.whenJSONP(/query.yahooapis.com/).respond(defaultJSON); 14 | 15 | scope = $rootScope.$new(); 16 | MainCtrl = $controller('MainCtrl', { 17 | $scope: scope 18 | }); 19 | })); 20 | 21 | it('should have a list of feeds', function() { 22 | expect(scope.feeds.length).toBe(1); 23 | httpBackend.flush(); 24 | expect(scope.feeds[0].items[0].title).toBe('Node Roundup: 0.11.2, 0.10.6, subscribe, Omelette'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/mock/feed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mockedFeed', []) 4 | .value('defaultJSON', { 5 | query: { 6 | count: 2, 7 | created: '2013-05-16T15:01:31Z', 8 | lang: 'en-US', 9 | results: { 10 | entry: [ 11 | { 12 | title: 'Node Roundup: 0.11.2, 0.10.6, subscribe, Omelette', 13 | link: { href: 'http://dailyjs.com/2013/05/15/node-roundup' }, 14 | updated: '2013-05-15T00:00:00+01:00', 15 | id: 'http://dailyjs.com/2013/05/15/node-roundup', 16 | content: { type: 'html', content: 'example' } 17 | }, 18 | { 19 | title: 'jQuery Roundup: 1.10, jquery-markup, zelect', 20 | link: { href: 'http://dailyjs.com/2013/05/14/jquery-roundup' }, 21 | updated: '2013-05-14T00:00:00+01:00', 22 | id: 'http://dailyjs.com/2013/05/14/jquery-roundup', 23 | content: { type: 'html', content: 'example 2' } 24 | } 25 | ] 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "djsreader", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-contrib-copy": "~0.4.0", 8 | "grunt-contrib-concat": "~0.1.3", 9 | "grunt-contrib-coffee": "~0.6.4", 10 | "grunt-contrib-uglify": "~0.2.0", 11 | "grunt-contrib-compass": "~0.1.3", 12 | "grunt-contrib-jshint": "~0.3.0", 13 | "grunt-contrib-cssmin": "~0.5.0", 14 | "grunt-contrib-connect": "~0.2.0", 15 | "grunt-contrib-clean": "~0.4.0", 16 | "grunt-contrib-htmlmin": "~0.1.1", 17 | "grunt-contrib-imagemin": "~0.1.2", 18 | "grunt-contrib-livereload": "~0.1.2", 19 | "grunt-bower-requirejs": "~0.4.1", 20 | "grunt-usemin": "~0.1.10", 21 | "grunt-regarde": "~0.1.1", 22 | "grunt-rev": "~0.1.0", 23 | "grunt-karma": "~0.3.0", 24 | "grunt-open": "~0.2.0", 25 | "matchdep": "~0.1.1", 26 | "grunt-google-cdn": "~0.1.1", 27 | "grunt-ngmin": "~0.0.2" 28 | }, 29 | "engines": { 30 | "node": ">=0.8.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/views/main.html: -------------------------------------------------------------------------------- 1 |

djsreader

2 | 3 |

Refresh (seconds):

4 | 5 |

All Stories

6 | 7 | 10 | 11 |
12 | 13 |

Add Another Feed

14 | 15 |
16 | URL: 17 | 18 | Required! 19 | Invalid URL format! 20 |
21 | 22 |
23 | 24 |
25 | 28 | URL: 29 | 30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma E2E configuration 2 | 3 | // base path, that will be used to resolve files and exclude 4 | basePath = ''; 5 | 6 | // list of files / patterns to load in the browser 7 | files = [ 8 | ANGULAR_SCENARIO, 9 | ANGULAR_SCENARIO_ADAPTER, 10 | 'test/e2e/**/*.js' 11 | ]; 12 | 13 | // list of files to exclude 14 | exclude = []; 15 | 16 | // test results reporter to use 17 | // possible values: dots || progress || growl 18 | reporters = ['progress']; 19 | 20 | // web server port 21 | port = 8080; 22 | 23 | // cli runner port 24 | runnerPort = 9100; 25 | 26 | // enable / disable colors in the output (reporters and logs) 27 | colors = true; 28 | 29 | // level of logging 30 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 31 | logLevel = LOG_INFO; 32 | 33 | // enable / disable watching file and executing tests whenever any file changes 34 | autoWatch = false; 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 = ['Chrome']; 45 | 46 | // If browser does not capture in given timeout [ms], kill it 47 | captureTimeout = 5000; 48 | 49 | // Continuous Integration mode 50 | // if true, it capture browsers, run tests and exit 51 | singleRun = false; 52 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | // base path, that will be used to resolve files and exclude 4 | basePath = ''; 5 | 6 | // list of files / patterns to load in the browser 7 | files = [ 8 | JASMINE, 9 | JASMINE_ADAPTER, 10 | 'app/components/angular/angular.js', 11 | 'app/components/angular-mocks/angular-mocks.js', 12 | 'app/scripts/*.js', 13 | 'app/scripts/**/*.js', 14 | 'test/mock/**/*.js', 15 | 'test/spec/**/*.js' 16 | ]; 17 | 18 | // list of files to exclude 19 | exclude = []; 20 | 21 | // test results reporter to use 22 | // possible values: dots || progress || growl 23 | reporters = ['progress']; 24 | 25 | // web server port 26 | port = 8080; 27 | 28 | // cli runner port 29 | runnerPort = 9100; 30 | 31 | // enable / disable colors in the output (reporters and logs) 32 | colors = true; 33 | 34 | // level of logging 35 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 36 | logLevel = LOG_INFO; 37 | 38 | // enable / disable watching file and executing tests whenever any file changes 39 | autoWatch = false; 40 | 41 | // Start these browsers, currently available: 42 | // - Chrome 43 | // - ChromeCanary 44 | // - Firefox 45 | // - Opera 46 | // - Safari (only Mac) 47 | // - PhantomJS 48 | // - IE (only Windows) 49 | browsers = ['Chrome']; 50 | 51 | // If browser does not capture in given timeout [ms], kill it 52 | captureTimeout = 5000; 53 | 54 | // Continuous Integration mode 55 | // if true, it capture browsers, run tests and exit 56 | singleRun = false; 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DailyJS Reader 2 | 3 | This project is part of a tutorial series for AngularJS and Yeoman. 4 | 5 | Go to [DailyJS](http://dailyjs.com) for more information on this project. 6 | 7 | ### Installation 8 | 9 | You will need: 10 | 11 | * [Node 0.10.x](http://nodejs.org/) 12 | * [Bower](http://bower.io/) (`npm install -g bower`) 13 | * [Grunt](http://gruntjs.com/) (`npm install -g grunt-cli`) 14 | * Compass (`gem install compass`) 15 | 16 | 1. `git clone git@github.com:alexyoung/djsreader.git` 17 | 2. `cd djsreader` 18 | 4. `npm install` 19 | 5. `bower install` 20 | 6. `grunt build` 21 | 22 | You should see "Done, without errors." in green. 23 | 24 | ### Usage 25 | 26 | Run `grunt server` to run a local server that will serve a development version of the project. 27 | 28 | Run `grunt test` to run the unit tests. 29 | 30 | ### Tutorials 31 | 32 | * [Part 1: Google, Twitter, and AngularJS](http://dailyjs.com/2013/04/11/angularjs-1/) 33 | * [Part 2: Let's Make a Feed Reader](http://dailyjs.com/2013/04/18/angularjs-2/) 34 | * [Part 3: Rendering Feeds](http://dailyjs.com/2013/04/25/angularjs-3/) 35 | * [Part 4: Managing Feeds](http://dailyjs.com/2013/05/09/angularjs-4/) 36 | * [Part 5: Tests](http://dailyjs.com/2013/05/16/angularjs-5/) 37 | * [Part 6: Adding Dependencies](http://dailyjs.com/2013/05/30/angularjs-6/) 38 | * [Part 7: Form Validation](http://dailyjs.com/2013/06/06/angularjs-7/) 39 | * [Part 8: Iterators and Data](http://dailyjs.com/2013/06/13/angularjs-8/) 40 | * [Part 9: Installation](http://dailyjs.com/2013/07/18/angularjs-9/) 41 | -------------------------------------------------------------------------------- /app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('djsreaderApp') 4 | .controller('MainCtrl', function($scope, $http, $timeout, $filter) { 5 | function storyInCollection(story) { 6 | for (var i = 0; i < $scope.stories.length; i++) { 7 | if ($scope.stories[i].id === story.id) { 8 | return true; 9 | } 10 | } 11 | return false; 12 | } 13 | 14 | function addStories(stories) { 15 | var changed = false; 16 | angular.forEach(stories, function(story) { 17 | if (!storyInCollection(story)) { 18 | $scope.stories.push(story); 19 | changed = true; 20 | } 21 | }); 22 | 23 | if (changed) { 24 | $scope.stories = $filter('orderBy')($scope.stories, 'date'); 25 | } 26 | } 27 | 28 | $scope.refreshInterval = 60; 29 | $scope.feeds = [{ 30 | url: 'http://dailyjs.com/atom.xml' 31 | }]; 32 | $scope.stories = []; 33 | 34 | $scope.fetchFeed = function(feed) { 35 | feed.items = []; 36 | 37 | var apiUrl = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D'"; 38 | apiUrl += encodeURIComponent(feed.url); 39 | apiUrl += "'%20and%20itemPath%3D'feed.entry'&format=json&diagnostics=true&callback=JSON_CALLBACK"; 40 | 41 | $http.jsonp(apiUrl). 42 | success(function(data) { 43 | if (data.query.results) { 44 | feed.items = data.query.results.entry; 45 | } 46 | addStories(feed.items); 47 | }). 48 | error(function(data) { 49 | console.error('Error fetching feed:', data); 50 | }); 51 | 52 | $timeout(function() { $scope.fetchFeed(feed); }, $scope.refreshInterval * 1000); 53 | }; 54 | 55 | $scope.addFeed = function(feed) { 56 | if (feed.$valid) { 57 | // Copy this feed instance and reset the URL in the form 58 | var newFeed = angular.copy(feed); 59 | $scope.feeds.push(newFeed); 60 | $scope.fetchFeed(newFeed); 61 | $scope.newFeed.url = ''; 62 | } 63 | }; 64 | 65 | $scope.deleteFeed = function(feed) { 66 | $scope.feeds.splice($scope.feeds.indexOf(feed), 1); 67 | }; 68 | 69 | $scope.fetchFeed($scope.feeds[0]); 70 | }); 71 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /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 | 'use strict'; 2 | var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet; 3 | var mountFolder = function (connect, dir) { 4 | return connect.static(require('path').resolve(dir)); 5 | }; 6 | 7 | module.exports = function (grunt) { 8 | // load all grunt tasks 9 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 10 | 11 | // configurable paths 12 | var yeomanConfig = { 13 | app: 'app', 14 | dist: 'dist' 15 | }; 16 | 17 | try { 18 | yeomanConfig.app = require('./component.json').appPath || yeomanConfig.app; 19 | } catch (e) {} 20 | 21 | grunt.initConfig({ 22 | yeoman: yeomanConfig, 23 | watch: { 24 | coffee: { 25 | files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'], 26 | tasks: ['coffee:dist'] 27 | }, 28 | coffeeTest: { 29 | files: ['test/spec/{,*/}*.coffee'], 30 | tasks: ['coffee:test'] 31 | }, 32 | compass: { 33 | files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 34 | tasks: ['compass'] 35 | }, 36 | livereload: { 37 | files: [ 38 | '<%= yeoman.app %>/{,*/}*.html', 39 | '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css', 40 | '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', 41 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 42 | ], 43 | tasks: ['livereload'] 44 | } 45 | }, 46 | connect: { 47 | options: { 48 | port: 9000, 49 | // Change this to '0.0.0.0' to access the server from outside. 50 | hostname: 'localhost' 51 | }, 52 | livereload: { 53 | options: { 54 | middleware: function (connect) { 55 | return [ 56 | lrSnippet, 57 | mountFolder(connect, '.tmp'), 58 | mountFolder(connect, yeomanConfig.app) 59 | ]; 60 | } 61 | } 62 | }, 63 | test: { 64 | options: { 65 | middleware: function (connect) { 66 | return [ 67 | mountFolder(connect, '.tmp'), 68 | mountFolder(connect, 'test') 69 | ]; 70 | } 71 | } 72 | } 73 | }, 74 | open: { 75 | server: { 76 | url: 'http://localhost:<%= connect.options.port %>' 77 | } 78 | }, 79 | clean: { 80 | dist: { 81 | files: [{ 82 | dot: true, 83 | src: [ 84 | '.tmp', 85 | '<%= yeoman.dist %>/*', 86 | '!<%= yeoman.dist %>/.git*' 87 | ] 88 | }] 89 | }, 90 | server: '.tmp' 91 | }, 92 | jshint: { 93 | options: { 94 | jshintrc: '.jshintrc' 95 | }, 96 | all: [ 97 | 'Gruntfile.js', 98 | '<%= yeoman.app %>/scripts/{,*/}*.js' 99 | ] 100 | }, 101 | karma: { 102 | unit: { 103 | configFile: 'karma.conf.js', 104 | singleRun: true 105 | } 106 | }, 107 | coffee: { 108 | dist: { 109 | files: [{ 110 | expand: true, 111 | cwd: '<%= yeoman.app %>/scripts', 112 | src: '{,*/}*.coffee', 113 | dest: '.tmp/scripts', 114 | ext: '.js' 115 | }] 116 | }, 117 | test: { 118 | files: [{ 119 | expand: true, 120 | cwd: 'test/spec', 121 | src: '{,*/}*.coffee', 122 | dest: '.tmp/spec', 123 | ext: '.js' 124 | }] 125 | } 126 | }, 127 | compass: { 128 | options: { 129 | sassDir: '<%= yeoman.app %>/styles', 130 | cssDir: '.tmp/styles', 131 | imagesDir: '<%= yeoman.app %>/images', 132 | javascriptsDir: '<%= yeoman.app %>/scripts', 133 | fontsDir: '<%= yeoman.app %>/styles/fonts', 134 | importPath: '<%= yeoman.app %>/components', 135 | relativeAssets: true 136 | }, 137 | dist: {}, 138 | server: { 139 | options: { 140 | debugInfo: true 141 | } 142 | }, 143 | bootstrap: { 144 | options: { 145 | sassDir: '<%= yeoman.app %>/components/sass-bootstrap/lib', 146 | cssDir: '.tmp/styles' 147 | } 148 | } 149 | }, 150 | concat: { 151 | dist: { 152 | files: { 153 | '<%= yeoman.dist %>/scripts/scripts.js': [ 154 | '.tmp/scripts/{,*/}*.js', 155 | '<%= yeoman.app %>/scripts/{,*/}*.js' 156 | ] 157 | } 158 | } 159 | }, 160 | useminPrepare: { 161 | html: '<%= yeoman.app %>/index.html', 162 | options: { 163 | dest: '<%= yeoman.dist %>' 164 | } 165 | }, 166 | usemin: { 167 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 168 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 169 | options: { 170 | dirs: ['<%= yeoman.dist %>'] 171 | } 172 | }, 173 | imagemin: { 174 | dist: { 175 | files: [{ 176 | expand: true, 177 | cwd: '<%= yeoman.app %>/images', 178 | src: '{,*/}*.{png,jpg,jpeg}', 179 | dest: '<%= yeoman.dist %>/images' 180 | }] 181 | } 182 | }, 183 | cssmin: { 184 | dist: { 185 | files: { 186 | '<%= yeoman.dist %>/styles/main.css': [ 187 | '.tmp/styles/{,*/}*.css', 188 | '<%= yeoman.app %>/styles/{,*/}*.css' 189 | ] 190 | } 191 | } 192 | }, 193 | htmlmin: { 194 | dist: { 195 | options: { 196 | /*removeCommentsFromCDATA: true, 197 | // https://github.com/yeoman/grunt-usemin/issues/44 198 | //collapseWhitespace: true, 199 | collapseBooleanAttributes: true, 200 | removeAttributeQuotes: true, 201 | removeRedundantAttributes: true, 202 | useShortDoctype: true, 203 | removeEmptyAttributes: true, 204 | removeOptionalTags: true*/ 205 | }, 206 | files: [{ 207 | expand: true, 208 | cwd: '<%= yeoman.app %>', 209 | src: ['*.html', 'views/*.html'], 210 | dest: '<%= yeoman.dist %>' 211 | }] 212 | } 213 | }, 214 | cdnify: { 215 | dist: { 216 | html: ['<%= yeoman.dist %>/*.html'] 217 | } 218 | }, 219 | ngmin: { 220 | dist: { 221 | files: [{ 222 | expand: true, 223 | cwd: '<%= yeoman.dist %>/scripts', 224 | src: '*.js', 225 | dest: '<%= yeoman.dist %>/scripts' 226 | }] 227 | } 228 | }, 229 | uglify: { 230 | dist: { 231 | files: { 232 | '<%= yeoman.dist %>/scripts/scripts.js': [ 233 | '<%= yeoman.dist %>/scripts/scripts.js' 234 | ], 235 | } 236 | } 237 | }, 238 | rev: { 239 | dist: { 240 | files: { 241 | src: [ 242 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 243 | '<%= yeoman.dist %>/styles/{,*/}*.css', 244 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp}', 245 | '<%= yeoman.dist %>/styles/fonts/*' 246 | ] 247 | } 248 | } 249 | }, 250 | copy: { 251 | dist: { 252 | files: [{ 253 | expand: true, 254 | dot: true, 255 | cwd: '<%= yeoman.app %>', 256 | dest: '<%= yeoman.dist %>', 257 | src: [ 258 | '*.{ico,txt}', 259 | '.htaccess', 260 | 'components/**/*', 261 | 'images/{,*/}*.{gif,webp}' 262 | ] 263 | }] 264 | } 265 | } 266 | }); 267 | 268 | grunt.renameTask('regarde', 'watch'); 269 | 270 | grunt.registerTask('server', [ 271 | 'clean:server', 272 | 'coffee:dist', 273 | 'compass:server', 274 | 'compass:bootstrap', 275 | 'livereload-start', 276 | 'connect:livereload', 277 | 'open', 278 | 'watch' 279 | ]); 280 | 281 | grunt.registerTask('test', [ 282 | 'clean:server', 283 | 'coffee', 284 | 'compass', 285 | 'connect:test', 286 | 'karma' 287 | ]); 288 | 289 | grunt.registerTask('build', [ 290 | 'clean:dist', 291 | 'jshint', 292 | 'test', 293 | 'coffee', 294 | 'compass:bootstrap', 295 | 'compass:dist', 296 | 'useminPrepare', 297 | 'imagemin', 298 | 'cssmin', 299 | 'htmlmin', 300 | 'concat', 301 | 'copy', 302 | 'cdnify', 303 | 'ngmin', 304 | 'uglify', 305 | 'rev', 306 | 'usemin' 307 | ]); 308 | 309 | grunt.registerTask('default', ['build']); 310 | }; 311 | -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache configuration file 2 | # httpd.apache.org/docs/2.2/mod/quickreference.html 3 | 4 | # Note .htaccess files are an overhead, this logic should be in your Apache 5 | # config if possible: httpd.apache.org/docs/2.2/howto/htaccess.html 6 | 7 | # Techniques in here adapted from all over, including: 8 | # Kroc Camen: camendesign.com/.htaccess 9 | # perishablepress.com/press/2006/01/10/stupid-htaccess-tricks/ 10 | # Sample .htaccess file of CMS MODx: modxcms.com 11 | 12 | 13 | # ---------------------------------------------------------------------- 14 | # Better website experience for IE users 15 | # ---------------------------------------------------------------------- 16 | 17 | # Force the latest IE version, in various cases when it may fall back to IE7 mode 18 | # github.com/rails/rails/commit/123eb25#commitcomment-118920 19 | # Use ChromeFrame if it's installed for a better experience for the poor IE folk 20 | 21 | 22 | Header set X-UA-Compatible "IE=Edge,chrome=1" 23 | # mod_headers can't match by content-type, but we don't want to send this header on *everything*... 24 | 25 | Header unset X-UA-Compatible 26 | 27 | 28 | 29 | 30 | # ---------------------------------------------------------------------- 31 | # Cross-domain AJAX requests 32 | # ---------------------------------------------------------------------- 33 | 34 | # Serve cross-domain Ajax requests, disabled by default. 35 | # enable-cors.org 36 | # code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 37 | 38 | # 39 | # Header set Access-Control-Allow-Origin "*" 40 | # 41 | 42 | 43 | # ---------------------------------------------------------------------- 44 | # CORS-enabled images (@crossorigin) 45 | # ---------------------------------------------------------------------- 46 | 47 | # Send CORS headers if browsers request them; enabled by default for images. 48 | # developer.mozilla.org/en/CORS_Enabled_Image 49 | # blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 50 | # hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 51 | # wiki.mozilla.org/Security/Reviews/crossoriginAttribute 52 | 53 | 54 | 55 | # mod_headers, y u no match by Content-Type?! 56 | 57 | SetEnvIf Origin ":" IS_CORS 58 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 59 | 60 | 61 | 62 | 63 | 64 | # ---------------------------------------------------------------------- 65 | # Webfont access 66 | # ---------------------------------------------------------------------- 67 | 68 | # Allow access from all domains for webfonts. 69 | # Alternatively you could only whitelist your 70 | # subdomains like "subdomain.example.com". 71 | 72 | 73 | 74 | Header set Access-Control-Allow-Origin "*" 75 | 76 | 77 | 78 | 79 | # ---------------------------------------------------------------------- 80 | # Proper MIME type for all files 81 | # ---------------------------------------------------------------------- 82 | 83 | # JavaScript 84 | # Normalize to standard type (it's sniffed in IE anyways) 85 | # tools.ietf.org/html/rfc4329#section-7.2 86 | AddType application/javascript js jsonp 87 | AddType application/json json 88 | 89 | # Audio 90 | AddType audio/ogg oga ogg 91 | AddType audio/mp4 m4a f4a f4b 92 | 93 | # Video 94 | AddType video/ogg ogv 95 | AddType video/mp4 mp4 m4v f4v f4p 96 | AddType video/webm webm 97 | AddType video/x-flv flv 98 | 99 | # SVG 100 | # Required for svg webfonts on iPad 101 | # twitter.com/FontSquirrel/status/14855840545 102 | AddType image/svg+xml svg svgz 103 | AddEncoding gzip svgz 104 | 105 | # Webfonts 106 | AddType application/vnd.ms-fontobject eot 107 | AddType application/x-font-ttf ttf ttc 108 | AddType font/opentype otf 109 | AddType application/x-font-woff woff 110 | 111 | # Assorted types 112 | AddType image/x-icon ico 113 | AddType image/webp webp 114 | AddType text/cache-manifest appcache manifest 115 | AddType text/x-component htc 116 | AddType application/xml rss atom xml rdf 117 | AddType application/x-chrome-extension crx 118 | AddType application/x-opera-extension oex 119 | AddType application/x-xpinstall xpi 120 | AddType application/octet-stream safariextz 121 | AddType application/x-web-app-manifest+json webapp 122 | AddType text/x-vcard vcf 123 | AddType application/x-shockwave-flash swf 124 | AddType text/vtt vtt 125 | 126 | 127 | # ---------------------------------------------------------------------- 128 | # Allow concatenation from within specific js and css files 129 | # ---------------------------------------------------------------------- 130 | 131 | # e.g. Inside of script.combined.js you could have 132 | # 133 | # 134 | # and they would be included into this single file. 135 | 136 | # This is not in use in the boilerplate as it stands. You may 137 | # choose to use this technique if you do not have a build process. 138 | 139 | # 140 | # Options +Includes 141 | # AddOutputFilterByType INCLUDES application/javascript application/json 142 | # SetOutputFilter INCLUDES 143 | # 144 | 145 | # 146 | # Options +Includes 147 | # AddOutputFilterByType INCLUDES text/css 148 | # SetOutputFilter INCLUDES 149 | # 150 | 151 | 152 | # ---------------------------------------------------------------------- 153 | # Gzip compression 154 | # ---------------------------------------------------------------------- 155 | 156 | 157 | 158 | # Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/ 159 | 160 | 161 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 162 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 163 | 164 | 165 | 166 | # HTML, TXT, CSS, JavaScript, JSON, XML, HTC: 167 | 168 | FilterDeclare COMPRESS 169 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/html 170 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/css 171 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/plain 172 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/xml 173 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/x-component 174 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/javascript 175 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/json 176 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xml 177 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xhtml+xml 178 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/rss+xml 179 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/atom+xml 180 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/vnd.ms-fontobject 181 | FilterProvider COMPRESS DEFLATE resp=Content-Type $image/svg+xml 182 | FilterProvider COMPRESS DEFLATE resp=Content-Type $image/x-icon 183 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/x-font-ttf 184 | FilterProvider COMPRESS DEFLATE resp=Content-Type $font/opentype 185 | FilterChain COMPRESS 186 | FilterProtocol COMPRESS DEFLATE change=yes;byteranges=no 187 | 188 | 189 | 190 | # Legacy versions of Apache 191 | AddOutputFilterByType DEFLATE text/html text/plain text/css application/json 192 | AddOutputFilterByType DEFLATE application/javascript 193 | AddOutputFilterByType DEFLATE text/xml application/xml text/x-component 194 | AddOutputFilterByType DEFLATE application/xhtml+xml application/rss+xml application/atom+xml 195 | AddOutputFilterByType DEFLATE image/x-icon image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype 196 | 197 | 198 | 199 | 200 | 201 | # ---------------------------------------------------------------------- 202 | # Expires headers (for better cache control) 203 | # ---------------------------------------------------------------------- 204 | 205 | # These are pretty far-future expires headers. 206 | # They assume you control versioning with filename-based cache busting 207 | # Additionally, consider that outdated proxies may miscache 208 | # www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ 209 | 210 | # If you don't use filenames to version, lower the CSS and JS to something like 211 | # "access plus 1 week". 212 | 213 | 214 | ExpiresActive on 215 | 216 | # Perhaps better to whitelist expires rules? Perhaps. 217 | ExpiresDefault "access plus 1 month" 218 | 219 | # cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5) 220 | ExpiresByType text/cache-manifest "access plus 0 seconds" 221 | 222 | # Your document html 223 | ExpiresByType text/html "access plus 0 seconds" 224 | 225 | # Data 226 | ExpiresByType text/xml "access plus 0 seconds" 227 | ExpiresByType application/xml "access plus 0 seconds" 228 | ExpiresByType application/json "access plus 0 seconds" 229 | 230 | # Feed 231 | ExpiresByType application/rss+xml "access plus 1 hour" 232 | ExpiresByType application/atom+xml "access plus 1 hour" 233 | 234 | # Favicon (cannot be renamed) 235 | ExpiresByType image/x-icon "access plus 1 week" 236 | 237 | # Media: images, video, audio 238 | ExpiresByType image/gif "access plus 1 month" 239 | ExpiresByType image/png "access plus 1 month" 240 | ExpiresByType image/jpeg "access plus 1 month" 241 | ExpiresByType video/ogg "access plus 1 month" 242 | ExpiresByType audio/ogg "access plus 1 month" 243 | ExpiresByType video/mp4 "access plus 1 month" 244 | ExpiresByType video/webm "access plus 1 month" 245 | 246 | # HTC files (css3pie) 247 | ExpiresByType text/x-component "access plus 1 month" 248 | 249 | # Webfonts 250 | ExpiresByType application/x-font-ttf "access plus 1 month" 251 | ExpiresByType font/opentype "access plus 1 month" 252 | ExpiresByType application/x-font-woff "access plus 1 month" 253 | ExpiresByType image/svg+xml "access plus 1 month" 254 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 255 | 256 | # CSS and JavaScript 257 | ExpiresByType text/css "access plus 1 year" 258 | ExpiresByType application/javascript "access plus 1 year" 259 | 260 | 261 | 262 | 263 | # ---------------------------------------------------------------------- 264 | # Prevent mobile network providers from modifying your site 265 | # ---------------------------------------------------------------------- 266 | 267 | # The following header prevents modification of your code over 3G on some 268 | # European providers. 269 | # This is the official 'bypass' suggested by O2 in the UK. 270 | 271 | # 272 | # Header set Cache-Control "no-transform" 273 | # 274 | 275 | 276 | # ---------------------------------------------------------------------- 277 | # ETag removal 278 | # ---------------------------------------------------------------------- 279 | 280 | # FileETag None is not enough for every server. 281 | 282 | Header unset ETag 283 | 284 | 285 | # Since we're sending far-future expires, we don't need ETags for 286 | # static content. 287 | # developer.yahoo.com/performance/rules.html#etags 288 | FileETag None 289 | 290 | 291 | # ---------------------------------------------------------------------- 292 | # Stop screen flicker in IE on CSS rollovers 293 | # ---------------------------------------------------------------------- 294 | 295 | # The following directives stop screen flicker in IE on CSS rollovers - in 296 | # combination with the "ExpiresByType" rules for images (see above). 297 | 298 | # BrowserMatch "MSIE" brokenvary=1 299 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 300 | # BrowserMatch "Opera" !brokenvary 301 | # SetEnvIf brokenvary 1 force-no-vary 302 | 303 | 304 | # ---------------------------------------------------------------------- 305 | # Set Keep-Alive Header 306 | # ---------------------------------------------------------------------- 307 | 308 | # Keep-Alive allows the server to send multiple requests through one 309 | # TCP-connection. Be aware of possible disadvantages of this setting. Turn on 310 | # if you serve a lot of static content. 311 | 312 | # 313 | # Header set Connection Keep-Alive 314 | # 315 | 316 | 317 | # ---------------------------------------------------------------------- 318 | # Cookie setting from iframes 319 | # ---------------------------------------------------------------------- 320 | 321 | # Allow cookies to be set from iframes (for IE only) 322 | # If needed, specify a path or regex in the Location directive. 323 | 324 | # 325 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 326 | # 327 | 328 | 329 | # ---------------------------------------------------------------------- 330 | # Start rewrite engine 331 | # ---------------------------------------------------------------------- 332 | 333 | # Turning on the rewrite engine is necessary for the following rules and 334 | # features. FollowSymLinks must be enabled for this to work. 335 | 336 | # Some cloud hosting services require RewriteBase to be set: goo.gl/HOcPN 337 | # If using the h5bp in a subdirectory, use `RewriteBase /foo` instead where 338 | # 'foo' is your directory. 339 | 340 | # If your web host doesn't allow the FollowSymlinks option, you may need to 341 | # comment it out and use `Options +SymLinksOfOwnerMatch`, but be aware of the 342 | # performance impact: http://goo.gl/Mluzd 343 | 344 | 345 | Options +FollowSymlinks 346 | # Options +SymLinksIfOwnerMatch 347 | Options +FollowSymlinks 348 | RewriteEngine On 349 | # RewriteBase / 350 | 351 | 352 | 353 | # ---------------------------------------------------------------------- 354 | # Suppress or force the "www." at the beginning of URLs 355 | # ---------------------------------------------------------------------- 356 | 357 | # The same content should never be available under two different URLs - 358 | # especially not with and without "www." at the beginning, since this can cause 359 | # SEO problems (duplicate content). That's why you should choose one of the 360 | # alternatives and redirect the other one. 361 | 362 | # By default option 1 (no "www.") is activated. 363 | # no-www.org/faq.php?q=class_b 364 | 365 | # If you'd prefer to use option 2, just comment out all option 1 lines 366 | # and uncomment option 2. 367 | 368 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 369 | 370 | # ---------------------------------------------------------------------- 371 | 372 | # Option 1: 373 | # Rewrite "www.example.com -> example.com". 374 | 375 | 376 | RewriteCond %{HTTPS} !=on 377 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 378 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 379 | 380 | 381 | # ---------------------------------------------------------------------- 382 | 383 | # Option 2: 384 | # Rewrite "example.com -> www.example.com". 385 | # Be aware that the following rule might not be a good idea if you use "real" 386 | # subdomains for certain parts of your website. 387 | 388 | # 389 | # RewriteCond %{HTTPS} !=on 390 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 391 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 392 | # 393 | 394 | 395 | # ---------------------------------------------------------------------- 396 | # Built-in filename-based cache busting 397 | # ---------------------------------------------------------------------- 398 | 399 | # If you're not using the build script to manage your filename version revving, 400 | # you might want to consider enabling this, which will route requests for 401 | # /css/style.20110203.css to /css/style.css 402 | 403 | # To understand why this is important and a better idea than all.css?v1231, 404 | # read: github.com/h5bp/html5-boilerplate/wiki/cachebusting 405 | 406 | # 407 | # RewriteCond %{REQUEST_FILENAME} !-f 408 | # RewriteCond %{REQUEST_FILENAME} !-d 409 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 410 | # 411 | 412 | 413 | # ---------------------------------------------------------------------- 414 | # Prevent SSL cert warnings 415 | # ---------------------------------------------------------------------- 416 | 417 | # Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent 418 | # https://www.example.com when your cert only allows https://secure.example.com 419 | 420 | # 421 | # RewriteCond %{SERVER_PORT} !^443 422 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 423 | # 424 | 425 | 426 | # ---------------------------------------------------------------------- 427 | # Prevent 404 errors for non-existing redirected folders 428 | # ---------------------------------------------------------------------- 429 | 430 | # without -MultiViews, Apache will give a 404 for a rewrite if a folder of the 431 | # same name does not exist. 432 | # webmasterworld.com/apache/3808792.htm 433 | 434 | Options -MultiViews 435 | 436 | 437 | # ---------------------------------------------------------------------- 438 | # Custom 404 page 439 | # ---------------------------------------------------------------------- 440 | 441 | # You can add custom pages to handle 500 or 403 pretty easily, if you like. 442 | # If you are hosting your site in subdirectory, adjust this accordingly 443 | # e.g. ErrorDocument 404 /subdir/404.html 444 | ErrorDocument 404 /404.html 445 | 446 | 447 | # ---------------------------------------------------------------------- 448 | # UTF-8 encoding 449 | # ---------------------------------------------------------------------- 450 | 451 | # Use UTF-8 encoding for anything served text/plain or text/html 452 | AddDefaultCharset utf-8 453 | 454 | # Force UTF-8 for a number of file formats 455 | AddCharset utf-8 .atom .css .js .json .rss .vtt .xml 456 | 457 | 458 | # ---------------------------------------------------------------------- 459 | # A little more security 460 | # ---------------------------------------------------------------------- 461 | 462 | # To avoid displaying the exact version number of Apache being used, add the 463 | # following to httpd.conf (it will not work in .htaccess): 464 | # ServerTokens Prod 465 | 466 | # "-Indexes" will have Apache block users from browsing folders without a 467 | # default document Usually you should leave this activated, because you 468 | # shouldn't allow everybody to surf through every folder on your server (which 469 | # includes rather private places like CMS system folders). 470 | 471 | Options -Indexes 472 | 473 | 474 | # Block access to "hidden" directories or files whose names begin with a 475 | # period. This includes directories used by version control systems such as 476 | # Subversion or Git. 477 | 478 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 479 | RewriteCond %{SCRIPT_FILENAME} -f 480 | RewriteRule "(^|/)\." - [F] 481 | 482 | 483 | # Block access to backup and source files. These files may be left by some 484 | # text/html editors and pose a great security danger, when anyone can access 485 | # them. 486 | 487 | Order allow,deny 488 | Deny from all 489 | Satisfy All 490 | 491 | 492 | # If your server is not already configured as such, the following directive 493 | # should be uncommented in order to set PHP's register_globals option to OFF. 494 | # This closes a major security hole that is abused by most XSS (cross-site 495 | # scripting) attacks. For more information: http://php.net/register_globals 496 | # 497 | # IF REGISTER_GLOBALS DIRECTIVE CAUSES 500 INTERNAL SERVER ERRORS: 498 | # 499 | # Your server does not allow PHP directives to be set via .htaccess. In that 500 | # case you must make this change in your php.ini file instead. If you are 501 | # using a commercial web host, contact the administrators for assistance in 502 | # doing this. Not all servers allow local php.ini files, and they should 503 | # include all PHP configurations (not just this one), or you will effectively 504 | # reset everything to PHP defaults. Consult www.php.net for more detailed 505 | # information about setting PHP directives. 506 | 507 | # php_flag register_globals Off 508 | 509 | # Rename session cookie to something else, than PHPSESSID 510 | # php_value session.name sid 511 | 512 | # Disable magic quotes (This feature has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0.) 513 | # php_flag magic_quotes_gpc Off 514 | 515 | # Do not show you are using PHP 516 | # Note: Move this line to php.ini since it won't work in .htaccess 517 | # php_flag expose_php Off 518 | 519 | # Level of log detail - log all errors 520 | # php_value error_reporting -1 521 | 522 | # Write errors to log file 523 | # php_flag log_errors On 524 | 525 | # Do not display errors in browser (production - Off, development - On) 526 | # php_flag display_errors Off 527 | 528 | # Do not display startup errors (production - Off, development - On) 529 | # php_flag display_startup_errors Off 530 | 531 | # Format errors in plain text 532 | # Note: Leave this setting 'On' for xdebug's var_dump() output 533 | # php_flag html_errors Off 534 | 535 | # Show multiple occurrence of error 536 | # php_flag ignore_repeated_errors Off 537 | 538 | # Show same errors from different sources 539 | # php_flag ignore_repeated_source Off 540 | 541 | # Size limit for error messages 542 | # php_value log_errors_max_len 1024 543 | 544 | # Don't precede error with string (doesn't accept empty string, use whitespace if you need) 545 | # php_value error_prepend_string " " 546 | 547 | # Don't prepend to error (doesn't accept empty string, use whitespace if you need) 548 | # php_value error_append_string " " 549 | 550 | # Increase cookie security 551 | 552 | php_value session.cookie_httponly true 553 | 554 | --------------------------------------------------------------------------------