├── .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 |
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 |
148 | - a mistyped address
149 | - an out-of-date link
150 |
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 |
--------------------------------------------------------------------------------