├── src ├── app │ ├── home │ │ ├── home.css │ │ ├── index.ts │ │ ├── services │ │ │ └── title │ │ │ │ ├── index.ts │ │ │ │ ├── title.service.ts │ │ │ │ └── title.service.spec.ts │ │ ├── directives │ │ │ └── x-large │ │ │ │ ├── index.ts │ │ │ │ ├── x-large.directive.ts │ │ │ │ └── x-large.spec.ts │ │ ├── home.component.e2e.ts │ │ ├── home.component.spec.ts │ │ ├── home.html │ │ └── home.component.ts │ ├── about │ │ ├── index.ts │ │ ├── about.spec.ts │ │ └── about.component.ts │ ├── shared │ │ ├── directives │ │ │ └── router-active │ │ │ │ ├── index.ts │ │ │ │ └── router-active.directive.ts │ │ └── components │ │ │ └── accordion │ │ │ ├── accordion-group.html │ │ │ ├── accordion.component.ts │ │ │ └── accordion-group.component.ts │ ├── recipes │ │ ├── rating.html │ │ ├── recipes.html │ │ ├── recipe.store.ts │ │ ├── selected-recipe.reducer.ts │ │ ├── recipe-list.html │ │ ├── rating.component.ts │ │ ├── recipe-list.component.ts │ │ ├── recipes.reducer.ts │ │ ├── recipes.spec.ts │ │ ├── recipes.component.ts │ │ ├── recipe.service.ts │ │ ├── recipe-details.html │ │ └── recipe-details.component.ts │ ├── app.store.ts │ ├── app.component.spec.ts │ ├── app.component.e2e.ts │ ├── app.service.ts │ ├── index.ts │ ├── todo │ │ ├── todo.html │ │ ├── todo.service.ts │ │ └── todo.component.ts │ └── app.component.ts ├── assets │ ├── css │ │ └── .gitkeep │ ├── data.json │ ├── robots.txt │ ├── service-worker.js │ ├── icon │ │ ├── apple-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── ms-icon-70x70.png │ │ ├── apple-icon-57x57.png │ │ ├── apple-icon-60x60.png │ │ ├── apple-icon-72x72.png │ │ ├── apple-icon-76x76.png │ │ ├── ms-icon-144x144.png │ │ ├── ms-icon-150x150.png │ │ ├── ms-icon-310x310.png │ │ ├── android-icon-36x36.png │ │ ├── android-icon-48x48.png │ │ ├── android-icon-72x72.png │ │ ├── android-icon-96x96.png │ │ ├── apple-icon-114x114.png │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-144x144.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-180x180.png │ │ ├── android-icon-144x144.png │ │ ├── android-icon-192x192.png │ │ ├── apple-icon-precomposed.png │ │ ├── browserconfig.xml │ │ └── manifest.json │ ├── img │ │ └── angular-logo.png │ ├── humans.txt │ └── manifest.json ├── platform │ ├── browser │ │ ├── index.ts │ │ ├── pipes.ts │ │ ├── directives.ts │ │ ├── providers.ts │ │ └── angular2-material2 │ │ │ └── index.ts │ └── environment.ts ├── sass │ ├── base │ │ ├── _typography.scss │ │ ├── _fonts.scss │ │ ├── README.MD │ │ ├── _base.scss │ │ └── _helpers.scss │ ├── themes │ │ ├── _default.scss │ │ └── README.MD │ ├── components │ │ ├── _button.scss │ │ └── README.MD │ ├── README.md │ ├── layout │ │ ├── _footer.scss │ │ ├── README.MD │ │ └── _header.scss │ ├── _shame.scss │ ├── abstracts │ │ ├── README.md │ │ ├── _mixins.scss │ │ ├── _functions.scss │ │ └── _variables.scss │ ├── pages │ │ ├── README.MD │ │ └── _home.scss │ ├── main.scss │ └── vendor │ │ ├── README.md │ │ └── _normalize.scss ├── polyfills.ts ├── vendor.ts ├── main.browser.ts ├── index.html └── custom-typings.d.ts ├── test ├── mocha.opts ├── todo.model.spec.js ├── utils.js └── recipe.model.spec.js ├── vulgar.json ├── .babelrc ├── karma.conf.js ├── protractor.conf.js ├── .travis.yml ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── .editorconfig ├── typedoc.json ├── config ├── helpers.js ├── protractor.conf.js ├── mongoose.conf.js ├── ts-rules │ ├── importDestructuringSpacingRule.ts │ └── importDestructuringSpacingRule.js ├── karma.conf.js ├── spec-bundle.js ├── env.conf.js ├── webpack.dev.js ├── gulpfile.conf.js ├── webpack.test.config.js ├── webpack.common.js └── webpack.prod.config.js ├── server.js ├── gulpfile.js ├── sockets └── base.js ├── webpack.config.js ├── app ├── models │ ├── todo.model.js │ ├── recipe.model.js │ └── user.model.js ├── routes.js └── routes │ ├── _todo.router.js │ ├── _recipe.router.js │ └── _authentication.router.js ├── tsconfig.json ├── typings.json ├── .atom-build.json ├── LICENSE ├── .gitignore ├── .gitattributes ├── tslint.json ├── server.conf.js └── package.json /src/app/home/home.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/css/.gitkeep: -------------------------------------------------------------------------------- 1 | @datatypevoid 2 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require babel-register 2 | -------------------------------------------------------------------------------- /vulgar.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": {} 3 | } 4 | -------------------------------------------------------------------------------- /src/app/about/index.ts: -------------------------------------------------------------------------------- 1 | export * from './about.component'; 2 | -------------------------------------------------------------------------------- /src/app/home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './home.component'; 2 | -------------------------------------------------------------------------------- /src/assets/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "value": "datatypevoid" 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /src/app/home/services/title/index.ts: -------------------------------------------------------------------------------- 1 | export * from './title.service'; 2 | -------------------------------------------------------------------------------- /src/assets/service-worker.js: -------------------------------------------------------------------------------- 1 | // This file is intentionally without code. 2 | -------------------------------------------------------------------------------- /src/app/home/directives/x-large/index.ts: -------------------------------------------------------------------------------- 1 | export * from './x-large.directive'; 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [], 3 | "presets" : ['es2015', 'stage-0', 'react'] 4 | } -------------------------------------------------------------------------------- /src/app/shared/directives/router-active/index.ts: -------------------------------------------------------------------------------- 1 | export * from './router-active.directive'; 2 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Look in `./config` for `karma.conf.js` 2 | exports.config = require('./config/karma.conf.js'); 3 | -------------------------------------------------------------------------------- /src/assets/icon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon.png -------------------------------------------------------------------------------- /src/assets/img/angular-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/img/angular-logo.png -------------------------------------------------------------------------------- /src/assets/icon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/icon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/icon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/favicon-96x96.png -------------------------------------------------------------------------------- /src/assets/icon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/ms-icon-70x70.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-57x57.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-60x60.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-72x72.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-76x76.png -------------------------------------------------------------------------------- /src/assets/icon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/ms-icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/ms-icon-150x150.png -------------------------------------------------------------------------------- /src/assets/icon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/ms-icon-310x310.png -------------------------------------------------------------------------------- /src/platform/browser/index.ts: -------------------------------------------------------------------------------- 1 | export * from './directives'; 2 | export * from './pipes'; 3 | export * from './providers'; 4 | -------------------------------------------------------------------------------- /src/assets/icon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/android-icon-36x36.png -------------------------------------------------------------------------------- /src/assets/icon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/android-icon-48x48.png -------------------------------------------------------------------------------- /src/assets/icon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/android-icon-72x72.png -------------------------------------------------------------------------------- /src/assets/icon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/android-icon-96x96.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-114x114.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-120x120.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-152x152.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-180x180.png -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Look in `./config` for `protractor.conf.js` 2 | exports.config = require('./config/protractor.conf.js').config; 3 | -------------------------------------------------------------------------------- /src/assets/icon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/android-icon-144x144.png -------------------------------------------------------------------------------- /src/assets/icon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/android-icon-192x192.png -------------------------------------------------------------------------------- /src/assets/icon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datatypevoid/vulgar/HEAD/src/assets/icon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /src/sass/base/_typography.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic typography style for copy text 3 | */ 4 | body { 5 | color: $text-color; 6 | font: normal 125% / 1.4 $text-font-stack; 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - "5" 5 | - "4" 6 | sudo: false 7 | branches: 8 | only: 9 | - master 10 | script: npm test 11 | notifications: 12 | slack: vulgardisplayofpower:RQzt5qlhpuN4caO9OavVbmoi 13 | -------------------------------------------------------------------------------- /src/sass/base/_fonts.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains all @font-face declarations, if any. 3 | // ----------------------------------------------------------------------------- 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 2 | - **What is the current behavior?** (You can also link to an open issue here) 3 | - **What is the new behavior (if this is a feature change)?** 4 | - **Other information**: 5 | -------------------------------------------------------------------------------- /src/sass/themes/_default.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // When having several themes, this file contains everything related to the 3 | // default one. 4 | // ----------------------------------------------------------------------------- 5 | -------------------------------------------------------------------------------- /src/assets/icon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # @datatypevoid 2 | # http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/app/recipes/rating.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /src/app/recipes/recipes.html: -------------------------------------------------------------------------------- 1 |
2 | 4 | 5 |
6 |
7 | Pick your Fancy: 10 | 11 |
12 | -------------------------------------------------------------------------------- /src/assets/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | David Newman -- @datatype_void 12 | PatrickJS -- @gdi2290 13 | AngularClass -- @AngularClass 14 | 15 | # TECHNOLOGY COLOPHON 16 | 17 | HTML5, CSS3 18 | Angular2, TypeScript, Webpack 19 | -------------------------------------------------------------------------------- /src/sass/components/_button.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains all styles related to the button component. 3 | // ----------------------------------------------------------------------------- 4 | button.active{ 5 | background: #fff; 6 | color: #009688; 7 | } 8 | button.active:hover{ 9 | color: #fff; 10 | } 11 | -------------------------------------------------------------------------------- /src/sass/README.md: -------------------------------------------------------------------------------- 1 | # Main file 2 | 3 | The main file (usually labelled `main.scss`) should be the only Sass file from the whole code base not to begin with an underscore. This file should not contain anything but `@import` and comments. 4 | 5 | Reference: [Sass Guidelines](http://sass-guidelin.es/) > [Architecture](http://sass-guidelin.es/#architecture) > [Main file](http://sass-guidelin.es/#main-file) 6 | -------------------------------------------------------------------------------- /src/platform/browser/pipes.ts: -------------------------------------------------------------------------------- 1 | //# Global Pipes 2 | // 3 | //** These `pipes` are available in any template ** 4 | 5 | import {PLATFORM_PIPES} from '@angular/core'; 6 | 7 | //# APPLICATION_PIPES 8 | // 9 | //** Pipes that are global throughout our application ** 10 | export const APPLICATION_PIPES = [ 11 | 12 | ]; 13 | 14 | export const PIPES = [ 15 | {provide: PLATFORM_PIPES, multi: true, useValue: APPLICATION_PIPES } 16 | ]; 17 | -------------------------------------------------------------------------------- /src/sass/layout/_footer.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains all styles related to the footer of the site/application. 3 | // ----------------------------------------------------------------------------- 4 | 5 | footer{ 6 | flex: 0 0 60px; 7 | padding: 10px; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | background: #fff; 12 | } 13 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "mode": "modules", 3 | "out": "doc", 4 | "theme": "default", 5 | "ignoreCompilerErrors": "true", 6 | "experimentalDecorators": "true", 7 | "emitDecoratorMetadata": "true", 8 | "target": "ES5", 9 | "moduleResolution": "node", 10 | "preserveConstEnums": "true", 11 | "stripInternal": "true", 12 | "suppressExcessPropertyErrors": "true", 13 | "suppressImplicitAnyIndexErrors": "true", 14 | "module": "commonjs" 15 | } 16 | -------------------------------------------------------------------------------- /src/sass/base/README.MD: -------------------------------------------------------------------------------- 1 | # Base 2 | 3 | The `base/` folder holds what we might call the boilerplate code for the project. In there, you might find some typographic rules, and probably a stylesheet (that I’m used to calling `_base.scss`), defining some standard styles for commonly used HTML elements. 4 | 5 | Reference: [Sass Guidelines](http://sass-guidelin.es/) > [Architecture](http://sass-guidelin.es/#architecture) > [Base folder](http://sass-guidelin.es/#base-folder) 6 | -------------------------------------------------------------------------------- /src/platform/environment.ts: -------------------------------------------------------------------------------- 1 | // Angular 2 2 | import {enableProdMode} from '@angular/core'; 3 | 4 | // Environment Providers 5 | var PROVIDERS = []; 6 | 7 | if ('production' === ENV) { 8 | // Production 9 | enableProdMode(); 10 | 11 | PROVIDERS = [ 12 | ...PROVIDERS 13 | ]; 14 | 15 | } else { 16 | // Development 17 | PROVIDERS = [ 18 | ...PROVIDERS 19 | ]; 20 | 21 | } 22 | 23 | export const ENV_PROVIDERS = [ 24 | ...PROVIDERS 25 | ]; 26 | -------------------------------------------------------------------------------- /src/sass/layout/README.MD: -------------------------------------------------------------------------------- 1 | # Layout 2 | 3 | The `layout/` folder contains everything that takes part in laying out the site or application. This folder could have stylesheets for the main parts of the site (header, footer, navigation, sidebar…), the grid system or even CSS styles for all the forms. 4 | 5 | Reference: [Sass Guidelines](http://sass-guidelin.es/) > [Architecture](http://sass-guidelin.es/#architecture) > [Layout folder](http://sass-guidelin.es/#layout-folder) 6 | -------------------------------------------------------------------------------- /src/app/shared/components/accordion/accordion-group.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{heading}} 5 |

6 |
7 |
8 |
9 | 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/sass/_shame.scss: -------------------------------------------------------------------------------- 1 | // ``` 2 | // _shame.scss 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // _shame.scss may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // */public/sass/_shame.scss* 9 | 10 | // ## Shameful Sass Import 11 | 12 | // This Sass file pulls in all declarations, hacks, etc. for code that the 13 | // authoring developer is not proud of. 14 | 15 | // Reference: 16 | // http://csswizardry.com/2013/04/shame-css-full-net-interview/ -------------------------------------------------------------------------------- /src/app/recipes/recipe.store.ts: -------------------------------------------------------------------------------- 1 | // ``` 2 | // recipes.store.js 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // recipes.store.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // # Redux store for `recipes` 9 | 10 | export interface Recipe { 11 | _id: number; 12 | tags: Array; 13 | title: string; 14 | description: string; 15 | rating: number; 16 | creator: string; 17 | ingredients: Array; 18 | directions: Array; 19 | }; 20 | -------------------------------------------------------------------------------- /src/sass/themes/README.MD: -------------------------------------------------------------------------------- 1 | # Theme 2 | 3 | On large sites and applications, it is not unusual to have different themes. There are certainly different ways of dealing with themes but I personally like having them all in a `themes/` folder. 4 | 5 | *Note — This is very project-specific and is likely to be non-existent on many projects.* 6 | 7 | Reference: [Sass Guidelines](http://sass-guidelin.es/) > [Architecture](http://sass-guidelin.es/#architecture) > [Themes folder](http://sass-guidelin.es/#themes-folder) 8 | -------------------------------------------------------------------------------- /config/helpers.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | // Helper functions 4 | var ROOT = path.resolve(__dirname, '..'); 5 | 6 | console.log('root directory:', root() + '\n'); 7 | 8 | function hasProcessFlag(flag) { 9 | return process.argv.join('').indexOf(flag) > -1; 10 | } 11 | 12 | function root(args) { 13 | args = Array.prototype.slice.call(arguments, 0); 14 | return path.join.apply(path, [ROOT].concat(args)); 15 | } 16 | 17 | exports.hasProcessFlag = hasProcessFlag; 18 | exports.root = root; 19 | -------------------------------------------------------------------------------- /src/app/home/services/title/title.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Http} from '@angular/http'; 3 | 4 | @Injectable() 5 | export class Title { 6 | value = 'Angular 2'; 7 | constructor(public http: Http) { 8 | 9 | } 10 | 11 | getData() { 12 | console.log('Title#getData(): Get Data'); 13 | // return this.http.get('/assets/data.json') 14 | // .map(res => res.json()); 15 | return { 16 | value: 'Angular 2 MEAN Webpack Starter' 17 | }; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // server.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // server.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // *server.js* 9 | 10 | // Require babel hook included to load all subsequent files required by node 11 | // with the extensions .es6, .es, .jsx, .js and transpile them with babel. 12 | // This will also automatically require the polyfill. 13 | require("babel-register"); 14 | 15 | // Load server configuration 16 | var app = require('./server.conf.js'); 17 | -------------------------------------------------------------------------------- /src/sass/abstracts/README.md: -------------------------------------------------------------------------------- 1 | # Abstracts 2 | 3 | The `abstracts/` folder gathers all Sass tools and helpers used across the project. Every global variable, function, mixin and placeholder should be put in here. 4 | 5 | The rule of thumb for this folder is that it should not output a single line of CSS when compiled on its own. These are nothing but Sass helpers. 6 | 7 | Reference: [Sass Guidelines](http://sass-guidelin.es/) > [Architecture](http://sass-guidelin.es/#architecture) > [Abstracts folder](http://sass-guidelin.es/#abstracts-folder) 8 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Read and contribute to the Wiki 2 | 3 | 4 | 5 | # Submitting Pull Requests 6 | 7 | If you're changing the structure of the repository please create an issue first 8 | 9 | # Submitting bug reports 10 | 11 | Make sure you are on latest changes and that you ran this command `npm run clean:install` after updating your local repository. If you can, please provide more information about your environment such as browser, operating system, `node` version, and `npm` version 12 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // Require babel hook included to load all subsequent files required by 2 | // gulp with the extensions .es6, .es, .jsx, .js and transpile them 3 | // with babel. This will also automatically require the polyfill. 4 | require("babel-register"); 5 | // ``` 6 | // gulpfile.js 7 | // (c) 2016 David Newman 8 | // david.r.niciforovic@gmail.com 9 | // gulpfile.js may be freely distributed under the MIT license 10 | // ``` 11 | 12 | // *gulpfile.js* 13 | 14 | // Load gulp configuration 15 | var conf = require('./config/gulpfile.conf.js'); 16 | -------------------------------------------------------------------------------- /src/sass/layout/_header.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains all styles related to the header of the site/application. 3 | // ----------------------------------------------------------------------------- 4 | 5 | md-toolbar ul { 6 | display: inline; 7 | list-style-type: none; 8 | margin: 0; 9 | padding: 0; 10 | width: 60px; 11 | } 12 | 13 | md-toolbar li { 14 | display: inline; 15 | } 16 | 17 | md-toolbar li.active { 18 | background-color: lightgray; 19 | } 20 | -------------------------------------------------------------------------------- /sockets/base.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // base.js 3 | // (c) 2015 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // base.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // *base.js* 9 | 10 | // This file contains the most basic functionality for server Socket.io 11 | // functionality. 12 | 13 | export default (io) => { 14 | 15 | io.sockets.on('connect', (socket) => { 16 | 17 | console.log('a user connected'); 18 | 19 | socket.on('disconnect', () => { 20 | 21 | console.log('a user disconnected'); 22 | }); 23 | }); 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/app/app.store.ts: -------------------------------------------------------------------------------- 1 | // Import our `Recipe` store 2 | import {Recipe} from './recipes/recipe.store'; 3 | 4 | // We are dealing with a single object that has: 5 | // * An `recipes` collection 6 | // * A `selectedRecipe` property holding a single `Recipe` 7 | export interface AppStore { 8 | 9 | recipes: Recipe[]; 10 | selectedRecipe: Recipe; 11 | 12 | // If ever you were to desire more functionality, you 13 | // could expand the `store` with new `key, value` pairs 14 | // to accomodate the updated model 15 | // 16 | // . . . 17 | // 18 | }; 19 | -------------------------------------------------------------------------------- /src/sass/components/README.MD: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | For small components, there is the `components/` folder. While `layout/` is macro (defining the global wireframe), `components/` is more focused on widgets. It contains all kind of specific modules like a slider, a loader, a widget, and basically anything along those lines. There are usually a lot of files in components/ since the whole site/application should be mostly composed of tiny modules. 4 | 5 | Reference: [Sass Guidelines](http://sass-guidelin.es/) > [Architecture](http://sass-guidelin.es/#architecture) > [Components folder](http://sass-guidelin.es/#components-folder) 6 | -------------------------------------------------------------------------------- /src/sass/pages/README.MD: -------------------------------------------------------------------------------- 1 | # Pages 2 | 3 | If you have page-specific styles, it is better to put them in a `pages/` folder, in a file named after the page. For instance, it’s not uncommon to have very specific styles for the home page hence the need for a `_home.scss` file in `pages/`. 4 | 5 | *Note — Depending on your deployment process, these files could be called on their own to avoid merging them with the others in the resulting stylesheet. It is really up to you.* 6 | 7 | Reference: [Sass Guidelines](http://sass-guidelin.es/) > [Architecture](http://sass-guidelin.es/#architecture) > [Pages folder](http://sass-guidelin.es/#pages-folder) 8 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // @datatype_void 3 | // david.r.niciforovic@gmail.com 4 | // webpack.config.js may be freely distributed under the MIT license 5 | // ``` 6 | 7 | // Look in `./config` folder for `webpack.*.config.js` 8 | switch (process.env.NODE_ENV) { 9 | case 'prod': 10 | case 'production': 11 | module.exports = require('./config/webpack.prod'); 12 | break; 13 | case 'test': 14 | case 'testing': 15 | module.exports = require('./config/webpack.test'); 16 | break; 17 | case 'dev': 18 | case 'development': 19 | default: 20 | module.exports = require('./config/webpack.dev'); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/home/home.component.e2e.ts: -------------------------------------------------------------------------------- 1 | describe('App', () => { 2 | 3 | beforeEach(() => { 4 | // change hash depending on router LocationStrategy 5 | browser.get('/#/home'); 6 | }); 7 | 8 | 9 | it('should have a title', () => { 10 | let subject = browser.getTitle(); 11 | let result = 'Angular 2 MEAN Webpack Starter Kit by @datatype_void'; 12 | expect(subject).toEqual(result); 13 | }); 14 | 15 | it('should have `your content here` x-large', () => { 16 | let subject = element(by.css('[x-large]')).getText(); 17 | let result = 'Your Content Here'; 18 | expect(subject).toEqual(result); 19 | }); 20 | 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | it, 3 | inject, 4 | injectAsync, 5 | beforeEachProviders, 6 | TestComponentBuilder 7 | } from '@angular/core/testing'; 8 | 9 | // Load the implementations that should be tested 10 | import {App} from './app.component'; 11 | import {AppState} from './app.service'; 12 | 13 | describe('App', () => { 14 | // provide our implementations or mocks to the dependency injector 15 | beforeEachProviders(() => [ 16 | AppState, 17 | App 18 | ]); 19 | 20 | it('should have a url', inject([ App ], (app) => { 21 | expect(app.url).toEqual('https://twitter.com/datatype_void'); 22 | })); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // Polyfills 2 | // (these modules are what are in 'angular2/bundles/angular2-polyfills' so don't use that here) 3 | 4 | // import 'ie-shim'; // Internet Explorer 5 | // import 'es6-shim'; 6 | // import 'es6-promise'; 7 | // import 'es7-reflect-metadata'; 8 | 9 | // Prefer CoreJS over the polyfills above 10 | import 'core-js/es6'; 11 | import 'core-js/es7/reflect'; 12 | require('zone.js/dist/zone'); 13 | 14 | // Typescript "emit helpers" polyfill 15 | require('ts-helpers'); 16 | 17 | if ('production' === ENV) { 18 | // Production 19 | 20 | } else { 21 | // Development 22 | 23 | Error.stackTraceLimit = Infinity; 24 | 25 | require('zone.js/dist/long-stack-trace-zone'); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/sass/main.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | // 1. Configuration and helpers 4 | @import 5 | 'abstracts/variables', 6 | 'abstracts/functions', 7 | 'abstracts/mixins'; 8 | 9 | // 2. Vendors 10 | @import 11 | 'vendor/normalize'; 12 | 13 | // 3. Base stuff 14 | @import 15 | 'base/base', 16 | 'base/fonts', 17 | 'base/typography', 18 | 'base/helpers'; 19 | 20 | // 4. Layout-related sections 21 | @import 22 | 'layout/header', 23 | 'layout/footer'; 24 | 25 | // 5. Components 26 | @import 27 | 'components/button'; 28 | 29 | // 6. Page-specific styles 30 | @import 31 | 'pages/home'; 32 | 33 | // 7. Themes 34 | @import 35 | 'themes/default'; 36 | 37 | // 8. Shame 38 | @import 39 | 'shame'; 40 | -------------------------------------------------------------------------------- /app/models/todo.model.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // todo.model.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // todo.model.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // */app/models/todo.model.js* 9 | 10 | // ## Todo Model 11 | 12 | // Note: MongoDB will autogenerate an _id for each Todo object created 13 | 14 | // Grab the Mongoose module 15 | import mongoose from 'mongoose'; 16 | 17 | // Create a `schema` for the `Todo` object 18 | let todoSchema = new mongoose.Schema({ 19 | text: { type : String } 20 | }); 21 | 22 | // Expose the model so that it can be imported and used in 23 | // the controller (to search, delete, etc.) 24 | export default mongoose.model('Todo', todoSchema); 25 | -------------------------------------------------------------------------------- /src/app/home/directives/x-large/x-large.directive.ts: -------------------------------------------------------------------------------- 1 | import {Directive, Component, ElementRef, Renderer} from '@angular/core'; 2 | /* 3 | * Directive 4 | * XLarge is a simple directive to show how one is made 5 | */ 6 | @Directive({ 7 | selector: '[x-large]' // using [ ] means selecting attributes 8 | }) 9 | export class XLarge { 10 | constructor(element: ElementRef, renderer: Renderer) { 11 | // simple DOM manipulation to set font size to x-large 12 | // `nativeElement` is the direct reference to the DOM element 13 | // element.nativeElement.style.fontSize = 'x-large'; 14 | 15 | // for server/webworker support use the renderer 16 | renderer.setElementStyle(element.nativeElement, 'fontSize', 'x-large'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "sourceMap": true, 8 | "noEmitHelpers": true 9 | }, 10 | "exclude": [ 11 | "./node_modules", 12 | "./typings/main.d.ts", 13 | "./typings/main" 14 | ], 15 | "filesGlob": [ 16 | "./src/app/**/*.ts", 17 | "./src/platform/**/*.ts", 18 | "!./node_modules/**", 19 | "./src/custom-typings.d.ts", 20 | "./typings/index.d.ts" 21 | ], 22 | "awesomeTypescriptLoaderOptions": { 23 | "resolveGlobs": true, 24 | "forkChecker": true 25 | }, 26 | "compileOnSave": false, 27 | "buildOnSave": false, 28 | "atom": { "rewriteTsconfig": false } 29 | } 30 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "angular-protractor": "registry:dt/angular-protractor#1.5.0+20160425143459", 4 | "core-js": "registry:dt/core-js#0.0.0+20160317120654", 5 | "hammerjs": "registry:dt/hammerjs#2.0.4+20160417130828", 6 | "jasmine": "registry:dt/jasmine#2.2.0+20160505161446", 7 | "node": "registry:dt/node#6.0.0+20160514165920", 8 | "selenium-webdriver": "registry:dt/selenium-webdriver#2.44.0+20160317120654", 9 | "source-map": "registry:dt/source-map#0.0.0+20160317120654", 10 | "uglify-js": "registry:dt/uglify-js#2.6.1+20160316155526", 11 | "webpack": "registry:dt/webpack#1.12.9+20160321060707", 12 | "angular2-beta-to-rc-alias": "file:./node_modules/@angularclass/angular2-beta-to-rc-alias/src/beta-17/typings.d.ts" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/platform/browser/directives.ts: -------------------------------------------------------------------------------- 1 | //# Global Directives 2 | // 3 | //** These `directives` are available in any template ** 4 | 5 | import {PLATFORM_DIRECTIVES} from '@angular/core'; 6 | 7 | // Angular 2 Router 8 | import {ROUTER_DIRECTIVES} from '@angular/router-deprecated'; 9 | 10 | // Angular 2 Material 2 11 | // 12 | // TODO(datatypevoid): replace with @angular2-material/all 13 | import {MATERIAL_DIRECTIVES} from './angular2-material2'; 14 | 15 | // APPLICATION_DIRECTIVES 16 | // 17 | // directives that are global through out the application 18 | export const APPLICATION_DIRECTIVES = [ 19 | 20 | ...ROUTER_DIRECTIVES, 21 | ...MATERIAL_DIRECTIVES 22 | ]; 23 | 24 | export const DIRECTIVES = [ 25 | 26 | {provide: PLATFORM_DIRECTIVES, multi: true, useValue: APPLICATION_DIRECTIVES} 27 | ]; 28 | -------------------------------------------------------------------------------- /src/sass/pages/_home.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains styles that are specific to the home page. 3 | // ----------------------------------------------------------------------------- 4 | 5 | .fill{ 6 | flex: 1 1 auto; 7 | } 8 | 9 | .app-state{ 10 | margin: 15px; 11 | flex: 1; 12 | max-height: 9.969rem; 13 | } 14 | 15 | .home{ 16 | flex: 1; 17 | } 18 | 19 | md-content{ 20 | display: flex; 21 | flex-direction: column; 22 | height: 100%; 23 | } 24 | 25 | .card-container{ 26 | display: flex; 27 | flex-direction: column; 28 | margin: 15px; 29 | border: 2px dashed #FBC02D; 30 | } 31 | 32 | .sample-content{ 33 | flex: 1; 34 | } 35 | 36 | .card-container md-card{ 37 | margin: 5px; 38 | } 39 | -------------------------------------------------------------------------------- /src/app/about/about.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | it, 3 | inject, 4 | injectAsync, 5 | describe, 6 | beforeEachProviders, 7 | TestComponentBuilder 8 | } from '@angular/core/testing'; 9 | 10 | import {TestComponentBuilder} from '@angular/compiler/testing'; 11 | 12 | import {Component, provide} from '@angular/core'; 13 | 14 | // Load the implementations that should be tested 15 | import {About} from './about.component'; 16 | 17 | describe('About', () => { 18 | // provide our implementations or mocks to the dependency injector 19 | beforeEachProviders(() => [ 20 | About 21 | ]); 22 | 23 | it('should log ngOnInit', inject([About], (about) => { 24 | spyOn(console, 'log'); 25 | expect(console.log).not.toHaveBeenCalled(); 26 | 27 | about.ngOnInit(); 28 | expect(console.log).toHaveBeenCalled(); 29 | })); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /.atom-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmd": "npm start", 3 | "name": "default", 4 | "targets": { 5 | "webpack-dev-server--hmr": { 6 | "cmd": "npm start" 7 | }, 8 | "build:dev": { 9 | "cmd": "npm run build:dev" 10 | }, 11 | "build:prod": { 12 | "cmd": "npm run build:prod" 13 | }, 14 | "build:docs": { 15 | "cmd": "gulp build:docs" 16 | }, 17 | "test": { 18 | "cmd": "npm test" 19 | }, 20 | "watch:test": { 21 | "cmd": "npm run watch:test" 22 | }, 23 | "e2e": { 24 | "cmd": "npm run e2e" 25 | }, 26 | "server:prod": { 27 | "cmd": "npm run server:prod" 28 | }, 29 | "watch:dev": { 30 | "cmd": "npm run watch:dev" 31 | }, 32 | "watch:prod": { 33 | "cmd": "npm run watch:prod" 34 | }, 35 | "watch:dev": { 36 | "cmd": "npm run watch:dev" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/assets/icon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/app/shared/components/accordion/accordion.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | import {AccordionGroup} from './accordion-group.component'; 4 | 5 | @Component({ 6 | selector: 'accordion, [accordion]', 7 | host: { 8 | 'class': 'panel-group' 9 | }, 10 | template: '' 11 | }) 12 | export class Accordion { 13 | private groups: Array = []; 14 | 15 | addGroup(group: AccordionGroup): void { 16 | this.groups.push(group); 17 | } 18 | 19 | closeOthers(openGroup: AccordionGroup): void { 20 | this.groups.forEach((group: AccordionGroup) => { 21 | if (group !== openGroup) { 22 | group.isOpen = false; 23 | } 24 | }); 25 | } 26 | 27 | removeGroup(group: AccordionGroup): void { 28 | const index = this.groups.indexOf(group); 29 | if (index !== -1) { 30 | this.groups.splice(index, 1); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/sass/vendor/README.md: -------------------------------------------------------------------------------- 1 | # Vendors 2 | 3 | Most projects will have a `vendors/` folder containing all the CSS files from external libraries and frameworks – Normalize, Bootstrap, jQueryUI, FancyCarouselSliderjQueryPowered, and so on. Putting those aside in the same folder is a good way to say “Hey, this is not from me, not my code, not my responsibility”. 4 | 5 | If you have to override a section of any vendor, I recommend you have an 8th folder called `vendors-extensions/` in which you may have files named exactly after the vendors they overwrite. For instance, `vendors-extensions/_bootstrap.scss` is a file containing all CSS rules intended to re-declare some of Bootstrap’s default CSS. This is to avoid editing the vendor files themselves, which is generally not a good idea. 6 | 7 | Reference: [Sass Guidelines](http://sass-guidelin.es/) > [Architecture](http://sass-guidelin.es/#architecture) > [Vendors folder](http://sass-guidelin.es/#vendors-folder) 8 | -------------------------------------------------------------------------------- /src/app/recipes/selected-recipe.reducer.ts: -------------------------------------------------------------------------------- 1 | // ``` 2 | // selected-recipe.reducer.js 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // selected-recipe.reducer.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // # Redux interface/reducer for `recipes` 9 | 10 | // The `selected recipe` reducer handles the currently 11 | // selected recipe 12 | export const selectedRecipe = (state: any = null, {type, payload}) => { 13 | 14 | // DEBUG 15 | console.log('selected recipe reducer hit! type: '); 16 | console.log(type); 17 | console.log('payload: '); 18 | console.log(payload); 19 | console.log('state: '); 20 | console.log(state); 21 | 22 | switch (type) { 23 | 24 | // When an `event` from our store is dispatched with an action 25 | // type of `SELECT_RECIPE`, it will hit this switch case 26 | case 'SELECT_RECIPE': 27 | return payload; 28 | 29 | default: 30 | return state; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "/assets/icon/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image/png", 8 | "density": 0.75 9 | }, 10 | { 11 | "src": "/assets/icon/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image/png", 14 | "density": 1.0 15 | }, 16 | { 17 | "src": "/assets/icon/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image/png", 20 | "density": 1.5 21 | }, 22 | { 23 | "src": "/assets/icon/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image/png", 26 | "density": 2.0 27 | }, 28 | { 29 | "src": "/assets/icon/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image/png", 32 | "density": 3.0 33 | }, 34 | { 35 | "src": "/assets/icon/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image/png", 38 | "density": 4.0 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/app/app.component.e2e.ts: -------------------------------------------------------------------------------- 1 | describe('App', () => { 2 | 3 | beforeEach(() => { 4 | browser.get('/'); 5 | }); 6 | 7 | it('should have a title', () => { 8 | let subject = browser.getTitle(); 9 | let result = 'Angular 2 MEAN Webpack Starter Kit by @datatype_void'; 10 | expect(subject).toEqual(result); 11 | }); 12 | 13 | it('should have ', () => { 14 | let subject = element(by.css('app md-toolbar')).isPresent(); 15 | let result = true; 16 | expect(subject).toEqual(result); 17 | }); 18 | 19 | it('should have ', () => { 20 | let subject = element(by.css('app md-content')).isPresent(); 21 | let result = true; 22 | expect(subject).toEqual(result); 23 | }); 24 | 25 | it('should have text in footer', () => { 26 | let subject = element(by.css('app #footerText')).getText(); 27 | let result = 'Angular 2 MEAN Webpack Starter by @datatype_void'; 28 | expect(subject).toEqual(result); 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /src/sass/abstracts/_mixins.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains all application-wide Sass mixins. 3 | // ----------------------------------------------------------------------------- 4 | 5 | /// Event wrapper 6 | /// @author Harry Roberts 7 | /// @param {Bool} $self [false] - Whether or not to include current selector 8 | /// @link https://twitter.com/csswizardry/status/478938530342006784 Original tweet from Harry Roberts 9 | @mixin on-event($self: false) { 10 | @if $self { 11 | &, 12 | &:hover, 13 | &:active, 14 | &:focus { 15 | @content; 16 | } 17 | } @else { 18 | &:hover, 19 | &:active, 20 | &:focus { 21 | @content; 22 | } 23 | } 24 | } 25 | 26 | /// Make a context based selector a little more friendly 27 | /// @author Hugo Giraudel 28 | /// @param {String} $context 29 | @mixin when-inside($context) { 30 | #{$context} & { 31 | @content; 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /test/todo.model.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // import the `mongoose` helper utilities 4 | let utils = require('./utils'); 5 | import chai from 'chai'; 6 | let should = chai.should(); 7 | 8 | // import our `Todo` mongoose model 9 | import Todo from '../app/models/todo.model'; 10 | 11 | describe('Todo: models', () => { 12 | 13 | describe('create()', () => { 14 | 15 | it('should create a new Todo', (done) => { 16 | 17 | // Create a `Todo` object to pass to `Todo.create()`` 18 | let t = { 19 | 20 | text: 'Write better tests... <.<' 21 | }; 22 | 23 | Todo.create(t, (err, createdTodo) => { 24 | 25 | // Confirm that that an error does not exist 26 | should.not.exist(err); 27 | 28 | // verify that the returned `todo` is what we expect 29 | createdTodo.text.should.equal('Write better tests... <.<'); 30 | 31 | // Call done to tell mocha that we are done with this test 32 | done(); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {HmrState} from 'angular2-hmr'; 3 | 4 | @Injectable() 5 | export class AppState { 6 | 7 | // `HmrState` is used by `HMR` to track the any `state` during reloading 8 | @HmrState() _state = {}; 9 | 10 | constructor() { 11 | 12 | } 13 | 14 | // Already return a `clone` of the current `state` 15 | get state() { 16 | return this._state = this._clone(this._state); 17 | } 18 | 19 | // Never allow mutation 20 | set state(value) { 21 | throw new Error('Do not mutate the `.state` directly!'); 22 | } 23 | 24 | get(prop?: any) { 25 | // Use our `state` getter for the `clone` 26 | const state = this.state; 27 | return state[prop] || state; 28 | } 29 | 30 | set(prop: string, value: any) { 31 | // Internally mutate our `state` 32 | return this._state[prop] = value; 33 | } 34 | 35 | _clone(object) { 36 | // Simple object `clone` 37 | return JSON.parse(JSON.stringify( object )); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/platform/browser/providers.ts: -------------------------------------------------------------------------------- 1 | //# Global Providers 2 | // 3 | //** These `providers` are available in any template ** 4 | 5 | // Angular 2 6 | import {FORM_PROVIDERS, 7 | LocationStrategy, 8 | HashLocationStrategy} from '@angular/common'; 9 | 10 | // Angular 2 Http 11 | import {HTTP_PROVIDERS} from '@angular/http'; 12 | // Angular 2 Router 13 | import {ROUTER_PROVIDERS} from '@angular/router-deprecated'; 14 | 15 | // Angular 2 Material 2 16 | // 17 | // TODO:(datatypevoid): replace with @angular2-material/all 18 | import {MATERIAL_PROVIDERS} from './angular2-material2' 19 | 20 | //# Application Providers/Directives/Pipes 21 | // 22 | //** providers/directives/pipes that only live in our browser environment ** 23 | export const APPLICATION_PROVIDERS = [ 24 | ...FORM_PROVIDERS, 25 | ...HTTP_PROVIDERS, 26 | ...MATERIAL_PROVIDERS, 27 | ...ROUTER_PROVIDERS, 28 | {provide: LocationStrategy, useClass: HashLocationStrategy } 29 | ]; 30 | 31 | export const PROVIDERS = [ 32 | ...APPLICATION_PROVIDERS 33 | ]; 34 | -------------------------------------------------------------------------------- /src/app/shared/components/accordion/accordion-group.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | // Import NgClass directive 4 | import {NgClass} from '@angular/common'; 5 | 6 | import {Accordion} from './accordion.component'; 7 | 8 | @Component({ 9 | selector: 'accordion-group, [accordion-group]', 10 | inputs: ['heading', 'isOpen'], 11 | directives: [NgClass], 12 | template: require('./accordion-group.html') 13 | }) 14 | export class AccordionGroup { 15 | private _isOpen: boolean = false; 16 | 17 | constructor(private accordion: Accordion) { 18 | this.accordion.addGroup(this); 19 | } 20 | 21 | toggleOpen(event) { 22 | event.preventDefault(); 23 | this.isOpen = !this.isOpen; 24 | } 25 | 26 | onDestroy(): void { 27 | this.accordion.removeGroup(this); 28 | } 29 | 30 | public get isOpen(): boolean { 31 | return this._isOpen; 32 | } 33 | 34 | public set isOpen(value: boolean){ 35 | this._isOpen = value; 36 | if (value) { 37 | this.accordion.closeOthers(this); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/recipes/recipe-list.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |

{{recipe.title}}

5 |
6 |
    7 |
  • 8 | 9 | #{{tag.name}} 10 | 11 |
  • 12 |
13 |
14 | 15 | {{recipe.rating}} 16 |
17 |
18 | {{recipe.creator}} 19 |
20 |
21 | {{recipe.description}} 22 |
23 |
    24 |
  • 25 | {{ ingredient.amount }} {{ ingredient.unit}} {{ ingredient.name }} 26 |
  • 27 |
28 |
    29 |
  1. 30 | {{ direction.step }} 31 |
  2. 32 |
33 |
34 | 37 |
38 |
39 | -------------------------------------------------------------------------------- /config/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @datatype_void 2 | require('ts-node/register'); 3 | 4 | exports.config = { 5 | baseUrl: 'http://localhost:3000/', 6 | 7 | // use `npm run e2e` 8 | specs: [ 9 | 'src/**/**.e2e.ts', 10 | 'src/**/*.e2e.ts' 11 | ], 12 | exclude: [], 13 | 14 | framework: 'jasmine2', 15 | 16 | allScriptsTimeout: 110000, 17 | 18 | jasmineNodeOpts: { 19 | showTiming: true, 20 | showColors: true, 21 | isVerbose: false, 22 | includeStackTrace: false, 23 | defaultTimeoutInterval: 400000 24 | }, 25 | directConnect: true, 26 | 27 | capabilities: { 28 | 'browserName': 'chrome', 29 | 'chromeOptions': { 30 | 'args': ['show-fps-counter=true'] 31 | } 32 | }, 33 | 34 | onPrepare: function() { 35 | browser.ignoreSynchronization = true; 36 | }, 37 | 38 | /** 39 | * Angular 2 configuration 40 | * 41 | * useAllAngular2AppRoots: tells Protractor to wait for any angular2 apps on the page instead of just the one matching 42 | * `rootEl` 43 | * 44 | */ 45 | useAllAngular2AppRoots: true 46 | }; 47 | -------------------------------------------------------------------------------- /app/models/recipe.model.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // recipe.model.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // recipe.model.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // */app/models/recipe.model.js* 9 | 10 | // # Recipe Model 11 | 12 | // Note: MongoDB will autogenerate an _id for each Recipe object created 13 | 14 | // Grab the Mongoose module 15 | import mongoose from 'mongoose'; 16 | 17 | // Create a `schema` for the `Todo` object 18 | let recipeSchema = new mongoose.Schema({ 19 | title: { type : String }, 20 | tags: { type: Array }, 21 | rating: { type: Number}, 22 | creator: { type: String}, 23 | description: { type : String }, 24 | ingredients: [{ 25 | amount: { 26 | type: String 27 | }, 28 | 29 | unit: { 30 | type: String 31 | }, 32 | 33 | name: { 34 | type: String 35 | } 36 | }], 37 | directions: { type: Array } 38 | }); 39 | 40 | // Expose the model so that it can be imported and used in 41 | // the controller (to search, delete, etc.) 42 | export default mongoose.model('Recipe', recipeSchema); 43 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | // test/utils.js 2 | 3 | 'use strict'; 4 | 5 | import config from '../config/config.json'; 6 | import mongoose from 'mongoose'; 7 | 8 | const ENV = 'test'; 9 | 10 | // ensure the NODE_ENV is set to 'test' 11 | // this is helpful when you would like to change behavior when testing 12 | process.env.NODE_ENV = ENV; 13 | 14 | let mongoUri = config.MONGO_URI[ENV.toUpperCase()]; 15 | 16 | beforeEach((done) => { 17 | 18 | function clearDB() { 19 | 20 | for (var i in mongoose.connection.collections) { 21 | 22 | mongoose.connection.collections[i].remove((error, status) => { 23 | 24 | if(error) 25 | console.error(error); 26 | }); 27 | } 28 | 29 | return done(); 30 | } 31 | 32 | if (mongoose.connection.readyState === 0) { 33 | 34 | mongoose.connect(mongoUri, (err) => { 35 | 36 | if (err) { 37 | 38 | throw err; 39 | } 40 | 41 | return clearDB(); 42 | }); 43 | } else { 44 | 45 | return clearDB(); 46 | } 47 | }); 48 | 49 | afterEach((done) => { 50 | 51 | mongoose.disconnect(); 52 | return done(); 53 | }); 54 | -------------------------------------------------------------------------------- /src/app/home/directives/x-large/x-large.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | it, 3 | inject, 4 | async, 5 | describe, 6 | beforeEachProviders, 7 | TestComponentBuilder 8 | } from '@angular/core/testing'; 9 | 10 | import {Component, provide} from '@angular/core'; 11 | import {BaseRequestOptions, Http} from '@angular/http'; 12 | import {MockBackend} from '@angular/http/testing'; 13 | 14 | // Load the implementations that should be tested 15 | import {XLarge} from './x-large.directive'; 16 | 17 | describe('x-large directive', () => { 18 | // Create a test component to test directives 19 | @Component({ 20 | template: '', 21 | directives: [ XLarge ] 22 | }) 23 | class TestComponent {} 24 | 25 | it('should sent font-size to x-large', async(Inject([TestComponentBuilder], (tcb) => { 26 | return tcb.overrideTemplate(TestComponent, '
Content
') 27 | .createAsync(TestComponent).then((fixture: any) => { 28 | fixture.detectChanges(); 29 | let compiled = fixture.debugElement.nativeElement.children[0]; 30 | expect(compiled.style.fontSize).toBe('x-large'); 31 | }); 32 | }))); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 David Newman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/sass/base/_base.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains very basic styles. 3 | // ----------------------------------------------------------------------------- 4 | html, 5 | body { 6 | height: 100%; 7 | background: #F4FAFA; 8 | } 9 | 10 | body { 11 | margin: 0; 12 | } 13 | 14 | md-card{ 15 | margin: 25px; 16 | } 17 | 18 | /** 19 | * Set up a decent box model on the root element 20 | */ 21 | html { 22 | box-sizing: border-box; 23 | } 24 | 25 | /** 26 | * Make all elements from the DOM inherit from the parent box-sizing 27 | * Since `*` has a specificity of 0, it does not override the `html` value 28 | * making all elements inheriting from the root box-sizing value 29 | * See: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ 30 | */ 31 | *, *::before, *::after { 32 | box-sizing: inherit; 33 | } 34 | 35 | /** 36 | * Basic styles for links 37 | */ 38 | a { 39 | color: $brand-color; 40 | text-decoration: none; 41 | 42 | @include on-event { 43 | color: $text-color; 44 | text-decoration: underline; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/sass/abstracts/_functions.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains all application-wide Sass functions. 3 | // ----------------------------------------------------------------------------- 4 | 5 | /// Native `url(..)` function wrapper 6 | /// @param {String} $base - base URL for the asset 7 | /// @param {String} $type - asset type folder (e.g. `fonts/`) 8 | /// @param {String} $path - asset path 9 | /// @return {Url} 10 | @function asset($base, $type, $path) { 11 | @return url($base + $type + $path); 12 | } 13 | 14 | /// Returns URL to an image based on its path 15 | /// @param {String} $path - image path 16 | /// @param {String} $base [$base-url] - base URL 17 | /// @return {Url} 18 | /// @require $base-url 19 | @function image($path, $base: $base-url) { 20 | @return asset($base, 'images/', $path); 21 | } 22 | 23 | /// Returns URL to a font based on its path 24 | /// @param {String} $path - font path 25 | /// @param {String} $base [$base-url] - base URL 26 | /// @return {Url} 27 | /// @require $base-url 28 | @function font($path, $base: $base-url) { 29 | @return asset($base, 'fonts/', $path); 30 | } 31 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | // App 2 | export * from './app.component'; 3 | export * from './app.service'; 4 | 5 | import {AppState} from './app.service'; 6 | 7 | // Application wide providers 8 | export const APP_PROVIDERS = [ 9 | AppState 10 | ]; 11 | 12 | //# Global Redux Stores 13 | // 14 | //** These `redux` `stores` are available in any template ** 15 | 16 | // Import module to provide an app `store` for the life-cycle of the app 17 | import {provideStore} from '@ngrx/store'; 18 | 19 | // Import all of the files necessary for our `recipes` component 20 | import {RecipeService} from './recipes/recipe.service'; 21 | import {recipes} from './recipes/recipes.reducer'; 22 | import {selectedRecipe} from './recipes/selected-recipe.reducer'; 23 | 24 | //# Application Redux Stores 25 | // 26 | //** Redux stores for use with our Angular 2 app ** 27 | export const APP_STORES = [ 28 | // These are the primary consumers of our app store 29 | RecipeService, 30 | // Inititialize app store available to entire app 31 | // and pass in our reducers. 32 | // Notice that we are passing in an object that matches the 33 | // `AppStore` interface 34 | provideStore({ recipes, selectedRecipe }) 35 | ]; 36 | -------------------------------------------------------------------------------- /src/app/recipes/rating.component.ts: -------------------------------------------------------------------------------- 1 | // ``` 2 | // rating.component.js 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // rating.component.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // # Rating Component 9 | 10 | import {Component, 11 | Input, 12 | Output, 13 | EventEmitter} from '@angular/core'; 14 | 15 | @Component({ 16 | selector: 'rating', 17 | template: require('./rating.html'), 18 | directives: [] 19 | }) 20 | 21 | export class Rating { 22 | 23 | @Input() rate: number; 24 | 25 | @Input() interactive: boolean; 26 | 27 | @Output() updateRate = new EventEmitter(); 28 | 29 | private range: Array = [1, 2, 3, 4, 5]; 30 | 31 | update(value) { 32 | 33 | // Check to see if this component should be interactive or not 34 | if (this.interactive) { 35 | 36 | this.rate = value; 37 | // push a new value every time we click on a star 38 | // this is thanks to the fact that the `NG2` `EventEmitter` 39 | // is using `Rx` thus this is an `Observable` 40 | this.updateRate.next(value); 41 | } else { 42 | // DEBUG 43 | console.log('This rating component is not interactive.'); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Note: for support questions, please use one of these channels:** [Chat: VulgarDisplayOf2^2.slack](http://www.davidniciforovic.com/wp-login.php?action=slack-invitation) or [Twitter: @datatype_void](https://twitter.com/datatype_void) 2 | 3 | - **I'm submitting a ... ** 4 | 5 | [ ] bug report 6 | 7 | [ ] feature request 8 | 9 | [ ] question about the decisions made in the repository 10 | 11 | - **Do you want to request a _feature_ or report a _bug_?** 12 | - **What is the current behavior?** 13 | - **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem** via 14 | - , , or similar. 15 | - **What is the expected behavior?** 16 | - **What is the motivation / use case for changing the behavior?** 17 | - **Please tell us about your environment:** 18 | - Angular version: 2.0.0-beta.X 19 | - Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ] 20 | - **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc) 21 | -------------------------------------------------------------------------------- /src/app/home/services/title/title.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | it, 3 | inject, 4 | injectAsync, 5 | beforeEachProviders, 6 | TestComponentBuilder 7 | } from '@angular/core/testing'; 8 | 9 | import {Component, provide} from '@angular/core'; 10 | import {BaseRequestOptions, Http} from '@angular/http'; 11 | import {MockBackend} from '@angular/http/testing'; 12 | 13 | 14 | import {Title} from './title.service'; 15 | 16 | describe('Title', () => { 17 | beforeEachProviders(() => [ 18 | BaseRequestOptions, 19 | MockBackend, 20 | provide(Http, { 21 | useFactory: function(backend, defaultOptions) { 22 | return new Http(backend, defaultOptions); 23 | }, 24 | deps: [MockBackend, BaseRequestOptions] 25 | }), 26 | 27 | Title 28 | ]); 29 | 30 | 31 | it('should have http', inject([ Title ], (title) => { 32 | expect(!!title.http).toEqual(true); 33 | })); 34 | 35 | it('should get data from the server', inject([ Title ], (title) => { 36 | spyOn(console, 'log'); 37 | expect(console.log).not.toHaveBeenCalled(); 38 | 39 | title.getData(); 40 | expect(console.log).toHaveBeenCalled(); 41 | expect(title.getData()) 42 | .toEqual({ value: 'Angular 2 MEAN Webpack Starter' }); 43 | })); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /src/app/recipes/recipe-list.component.ts: -------------------------------------------------------------------------------- 1 | // ``` 2 | // recipe-list.component.js 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // recipe-list.component.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // # Recipe List 9 | 10 | import {Component, 11 | Input, 12 | Output, 13 | EventEmitter, 14 | ChangeDetectionStrategy} from '@angular/core'; 15 | 16 | import {Observable} from 'rxjs/Observable'; 17 | import {Store} from '@ngrx/store'; 18 | 19 | import {RecipeService} from './recipe.service'; 20 | import {Recipe} from './recipe.store'; 21 | import {AppStore} from '../app.store'; 22 | 23 | import {Rating} from './rating.component'; 24 | 25 | @Component({ 26 | selector: 'recipe-list', 27 | template: require('./recipe-list.html'), 28 | directives: [Rating] 29 | }) 30 | export class RecipeList { 31 | // The `recipe` component hands off `recipes` and `selectedrecipe` 32 | // via property bindings to its child components 33 | // Here we pick up the `recipes` collection by annotating our local 34 | // `recipes` property with `@Input()` 35 | @Input() recipes: Recipe[]; 36 | // Two event outputs for when a recipe is selected or deleted 37 | @Output() selected = new EventEmitter(); 38 | @Output() deleted = new EventEmitter(); 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # @datatype_void 2 | 3 | # Ignore Documentation 4 | docs 5 | 6 | # Ignore Node env variables 7 | config/config.json 8 | 9 | # Ignore file-loader directory 10 | res 11 | 12 | # Ignore Photoshop Files 13 | *.psd 14 | 15 | # Logs 16 | logs 17 | *.log 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | 30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Users Environment Variables 37 | .lock-wscript 38 | 39 | # OS generated files # 40 | .DS_Store 41 | ehthumbs.db 42 | Icon? 43 | Thumbs.db 44 | 45 | # Node Files # 46 | /node_modules 47 | /bower_components 48 | npm-debug.log 49 | 50 | # Coverage # 51 | /coverage/ 52 | 53 | # Typing # 54 | /src/typings/tsd/ 55 | /typings/ 56 | /tsd_typings/ 57 | 58 | # Dist # 59 | /dist 60 | /public/__build__/ 61 | /src/*/__build__/ 62 | /__build__/** 63 | /public/dist/ 64 | /src/*/dist/ 65 | /dist/** 66 | .webpack.json 67 | 68 | # Doc # 69 | /doc/ 70 | 71 | # IDE # 72 | .idea/ 73 | 74 | # Temporary Files # 75 | .tmp 76 | .sass-cache 77 | *.swp 78 | -------------------------------------------------------------------------------- /app/models/user.model.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // user.model.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // user.model.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // */app/models/user.model.js* 9 | 10 | // ## User Model 11 | 12 | // Note: MongoDB will autogenerate an _id for each User object created 13 | 14 | // Grab the Mongoose module 15 | import mongoose from 'mongoose'; 16 | 17 | // Import library to hash passwords 18 | import bcrypt from 'bcrypt-nodejs'; 19 | 20 | // Define the schema for the showcase item 21 | let userSchema = mongoose.Schema({ 22 | 23 | local : { 24 | 25 | username : { type : String, unique : true }, 26 | 27 | password : String, 28 | 29 | email : { type : String, unique : true } 30 | }, 31 | 32 | role : { type : String } 33 | }); 34 | 35 | // ## Methods 36 | 37 | // ### Generate a hash 38 | userSchema.methods.generateHash = function(password) { 39 | 40 | return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); 41 | }; 42 | 43 | // ### Check if password is valid 44 | userSchema.methods.validPassword = function(password) { 45 | 46 | return bcrypt.compareSync(password, this.local.password); 47 | }; 48 | 49 | // Create the model for users and expose it to the app 50 | export default mongoose.model('User', userSchema); 51 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # 5 | # The above will handle all files NOT found below 6 | # 7 | 8 | # Documents 9 | *.doc diff=astextplain 10 | *.DOC diff=astextplain 11 | *.docx diff=astextplain 12 | *.DOCX diff=astextplain 13 | *.dot diff=astextplain 14 | *.DOT diff=astextplain 15 | *.pdf diff=astextplain 16 | *.PDF diff=astextplain 17 | *.rtf diff=astextplain 18 | *.RTF diff=astextplain 19 | *.md text 20 | *.adoc text 21 | *.textile text 22 | *.mustache text 23 | *.csv text 24 | *.tab text 25 | *.tsv text 26 | *.sql text 27 | *.cs -text=auto diff=csharp 28 | *.vb -text=auto 29 | *.c -text=auto 30 | *.cpp -text=auto 31 | *.cxx -text=auto 32 | *.h -text=auto 33 | *.hxx -text=auto 34 | *.py -text=auto 35 | *.rb -text=auto 36 | *.java -text=auto 37 | *.html -text=auto 38 | *.htm -text=auto 39 | *.css -text=auto 40 | *.scss -text=auto 41 | *.sass -text=auto 42 | *.less -text=auto 43 | *.js -text=auto 44 | *.ts -text=auto 45 | *.lisp -text=auto 46 | *.clj -text=auto 47 | *.sql -text=auto 48 | *.php -text=auto 49 | *.lua -text=auto 50 | *.m -text=auto 51 | *.asm -text=auto 52 | *.erl -text=auto 53 | *.fs -text=auto 54 | *.fsx -text=auto 55 | *.hs -text=auto 56 | 57 | # Graphics 58 | *.png binary 59 | *.jpg binary 60 | *.jpeg binary 61 | *.gif binary 62 | *.tif binary 63 | *.tiff binary 64 | *.ico binary 65 | *.svg binary 66 | *.eps binary 67 | -------------------------------------------------------------------------------- /src/vendor.ts: -------------------------------------------------------------------------------- 1 | // For vendors for example jQuery, Lodash, angular2-jwt just import them here unless you plan on 2 | // chunking vendors files for async loading. You would need to import the async loaded vendors 3 | // at the entry point of the async loaded file. Also see custom-typings.d.ts as you also need to 4 | // run `typings install x` where `x` is your module 5 | 6 | // Angular 2 7 | import '@angular/platform-browser'; 8 | import '@angular/platform-browser-dynamic'; 9 | import '@angular/core'; 10 | import '@angular/common'; 11 | import '@angular/http'; 12 | import '@angular/router-deprecated'; 13 | 14 | // RxJS 15 | import 'rxjs/add/operator/map'; 16 | import 'rxjs/add/operator/mergeMap'; 17 | 18 | // Angular 2 Material 2 19 | import '@angular2-material/button'; 20 | import '@angular2-material/card'; 21 | import '@angular2-material/checkbox'; 22 | import '@angular2-material/grid-list'; 23 | import '@angular2-material/input'; 24 | import '@angular2-material/list'; 25 | import '@angular2-material/radio'; 26 | import '@angular2-material/progress-bar'; 27 | import '@angular2-material/progress-circle'; 28 | import '@angular2-material/sidenav'; 29 | import '@angular2-material/slide-toggle'; 30 | import '@angular2-material/tabs'; 31 | import '@angular2-material/toolbar'; 32 | // look in src/platform/angular2-material2 and src/platform/providers 33 | 34 | if ('production' === ENV) { 35 | // Production 36 | 37 | 38 | } else { 39 | // Development 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/app/todo/todo.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |

Todo {{todos.length}}

6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 | 18 |
19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 |
32 | 33 | 34 | 36 |
37 |
38 |
39 | 40 |
41 | -------------------------------------------------------------------------------- /src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | it, 3 | inject, 4 | injectAsync, 5 | describe, 6 | beforeEachProviders, 7 | TestComponentBuilder 8 | } from '@angular/core/testing'; 9 | 10 | import {Component, provide} from '@angular/core'; 11 | import {BaseRequestOptions, Http} from '@angular/http'; 12 | import {MockBackend} from '@angular/http/testing'; 13 | 14 | // Load the implementations that should be tested 15 | import {Home} from './home.component'; 16 | import {Title} from './services/title'; 17 | import {AppState} from '../app.service'; 18 | 19 | describe('Home', () => { 20 | // provide our implementations or mocks to the dependency injector 21 | beforeEachProviders(() => [ 22 | BaseRequestOptions, 23 | MockBackend, 24 | provide(Http, { 25 | useFactory: function(backend, defaultOptions) { 26 | return new Http(backend, defaultOptions); 27 | }, 28 | deps: [MockBackend, BaseRequestOptions] 29 | }), 30 | 31 | AppState, 32 | Title, 33 | Home 34 | ]); 35 | 36 | it('should have default data', inject([ Home ], (home) => { 37 | expect(home.localState).toEqual({ value: '' }); 38 | })); 39 | 40 | it('should have a title', inject([ Home ], (home) => { 41 | expect(!!home.title).toEqual(true); 42 | })); 43 | 44 | it('should log ngOnInit', inject([ Home ], (home) => { 45 | spyOn(console, 'log'); 46 | expect(console.log).not.toHaveBeenCalled(); 47 | 48 | home.ngOnInit(); 49 | expect(console.log).toHaveBeenCalled(); 50 | })); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /config/mongoose.conf.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // mongoose.conf.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // mongoose.conf.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // *mongoose.conf.js* 9 | 10 | export default (mongoose) => { 11 | 12 | let gracefulExit = function() { 13 | 14 | mongoose.connection.close(() => { 15 | 16 | console.log(`Mongoose connection ` + 17 | `has disconnected through app termination`); 18 | 19 | process.exit(0); 20 | }); 21 | }; 22 | 23 | mongoose.connection.on("connected", (ref) => { 24 | 25 | console.log(`Successfully connected to ${process.env.NODE_ENV}` + 26 | ` database on startup `); 27 | }); 28 | 29 | // If the connection throws an error 30 | mongoose.connection.on("error", (err) => { 31 | 32 | console.error(`Failed to connect to ${process.env.NODE_ENV} ` + 33 | ` database on startup `, err); 34 | }); 35 | 36 | // When the connection is disconnected 37 | mongoose.connection.on('disconnected', () => { 38 | 39 | console.log(`Mongoose default connection to ${process.env.NODE_ENV}` + 40 | ` database disconnected`); 41 | }); 42 | 43 | // If the Node process ends, close the Mongoose connection 44 | process.on('SIGINT', gracefulExit).on('SIGTERM', gracefulExit); 45 | 46 | // Connect to our MongoDB database using the MongoDB 47 | // connection URI from our predefined environment variable 48 | mongoose.connect(process.env.MONGO_URI, (error) => { 49 | 50 | if (error) 51 | throw error; 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /src/app/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | /* 4 | * We're loading this component asynchronously 5 | * We are using some magic with es6-promise-loader that will wrap the module with a Promise 6 | * see https://github.com/gdi2290/es6-promise-loader for more info 7 | */ 8 | 9 | console.log('`About` component loaded asynchronously'); 10 | 11 | @Component({ 12 | selector: 'about', 13 | template: ` 14 | 15 |

16 | david.r.niciforovic@gmail.com 17 |

18 |
19 | `, 20 | }) 21 | export class About { 22 | constructor() { 23 | 24 | } 25 | 26 | ngOnInit() { 27 | console.log('hello `About` component'); 28 | // static data that is bundled 29 | //var mockData = require('assets/mock-data/mock-data.json'); 30 | //console.log('mockData', mockData); 31 | // if you're working with mock data you can also use http.get('assets/mock-data/mock-data.json') 32 | //this.asyncDataWithWebpack(); 33 | } 34 | asyncDataWithWebpack() { 35 | // you can also async load mock data with 'es6-promise-loader' 36 | // you would do this if you don't want the mock-data bundled 37 | // remember that 'es6-promise-loader' is a promise 38 | //var asyncMockDataPromiseFactory = require('es6-promise!assets/mock-data/mock-data.json'); 39 | //setTimeout(() => { 40 | 41 | // let asyncDataPromise = asyncMockDataPromiseFactory(); 42 | // asyncDataPromise.then(json => { 43 | // console.log('async mockData', json); 44 | // }); 45 | //}); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/sass/base/_helpers.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains CSS helper classes. 3 | // ----------------------------------------------------------------------------- 4 | 5 | /** 6 | * Clear inner floats 7 | */ 8 | .clearfix::after { 9 | clear: both; 10 | content: ''; 11 | display: table; 12 | } 13 | 14 | /** 15 | * Main content containers 16 | * 1. Make the container full-width with a maximum width 17 | * 2. Center it in the viewport 18 | * 3. Leave some space on the edges, especially valuable on small screens 19 | */ 20 | .container { 21 | max-width: $max-width; /* 1 */ 22 | margin-left: auto; /* 2 */ 23 | margin-right: auto; /* 2 */ 24 | padding-left: 20px; /* 3 */ 25 | padding-right: 20px; /* 3 */ 26 | width: 100%; /* 1 */ 27 | } 28 | 29 | /** 30 | * Hide text while making it readable for screen readers 31 | * 1. Needed in WebKit-based browsers because of an implementation bug; 32 | * See: https://code.google.com/p/chromium/issues/detail?id=457146 33 | */ 34 | .hide-text { 35 | overflow: hidden; 36 | padding: 0; /* 1 */ 37 | text-indent: 101%; 38 | white-space: nowrap; 39 | } 40 | 41 | /** 42 | * Hide element while making it readable for screen readers 43 | * Shamelessly borrowed from HTML5Boilerplate: 44 | * https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css#L119-L133 45 | */ 46 | .visually-hidden { 47 | border: 0; 48 | clip: rect(0 0 0 0); 49 | height: 1px; 50 | margin: -1px; 51 | overflow: hidden; 52 | padding: 0; 53 | position: absolute; 54 | width: 1px; 55 | } 56 | -------------------------------------------------------------------------------- /src/main.browser.ts: -------------------------------------------------------------------------------- 1 | //# Providers provided by Angular 2 | import {bootstrap} from '@angular/platform-browser-dynamic'; 3 | 4 | //## Platform and Environment 5 | // 6 | //** our providers/directives/pipes ** 7 | import {DIRECTIVES, PIPES, PROVIDERS} from './platform/browser'; 8 | import {ENV_PROVIDERS} from './platform/environment'; 9 | 10 | //## App Component 11 | // 12 | //** our top level component that holds all of our components ** 13 | import {App, APP_PROVIDERS, APP_STORES} from './app'; 14 | 15 | // Bootstrap our Angular app with a top level component `App` and inject 16 | // our Services and Providers into Angular's dependency injection 17 | 18 | export function main(initialHmrState?: any): Promise { 19 | 20 | return bootstrap(App, [ 21 | 22 | ...PROVIDERS, 23 | ...ENV_PROVIDERS, 24 | ...DIRECTIVES, 25 | ...PIPES, 26 | ...APP_PROVIDERS, 27 | ...APP_STORES 28 | ]) 29 | .catch(err => console.error(err)); 30 | } 31 | 32 | //## Vendors 33 | // 34 | // For vendors for example jQuery, Lodash, angular2-jwt just import them anywhere in your app 35 | // You can also import them in vendors to ensure that they are bundled in one file 36 | // Also see custom-typings.d.ts as you also need to do `typings install x` where `x` is your module 37 | 38 | //## Hot Module Reload 39 | // 40 | // experimental version 41 | 42 | if ('development' === ENV && HMR === true) { 43 | 44 | // activate hot module reload 45 | let ngHmr = require('angular2-hmr'); 46 | ngHmr.hotModuleReplacement(main, module); 47 | } else { 48 | 49 | // bootstrap when documetn is ready 50 | document.addEventListener('DOMContentLoaded', () => main()); 51 | } 52 | -------------------------------------------------------------------------------- /src/platform/browser/angular2-material2/index.ts: -------------------------------------------------------------------------------- 1 | import { MdAnchor, MdButton } from '@angular2-material/button'; 2 | import { MD_CARD_DIRECTIVES } from '@angular2-material/card'; 3 | import { MdCheckbox } from '@angular2-material/checkbox'; 4 | import { MD_GRID_LIST_DIRECTIVES } from '@angular2-material/grid-list'; 5 | import { MdIcon, MdIconRegistry } from '@angular2-material/icon'; 6 | import { MD_INPUT_DIRECTIVES } from '@angular2-material/input'; 7 | import { MD_LIST_DIRECTIVES } from '@angular2-material/list'; 8 | import { MdProgressBar } from '@angular2-material/progress-bar'; 9 | import { MdProgressCircle, MdSpinner } from '@angular2-material/progress-circle'; 10 | import { MdRadioButton, MdRadioDispatcher, MdRadioGroup } from '@angular2-material/radio'; 11 | import { MD_SIDENAV_DIRECTIVES } from '@angular2-material/sidenav'; 12 | import { MD_SLIDE_TOGGLE_DIRECTIVES } from '@angular2-material/slide-toggle'; 13 | import { MD_TABS_DIRECTIVES } from '@angular2-material/tabs'; 14 | import { MdToolbar } from '@angular2-material/toolbar'; 15 | /* 16 | * we are grouping the module so we only need to manage the imports in one location 17 | */ 18 | 19 | export const MATERIAL_PIPES = [ 20 | 21 | ]; 22 | 23 | export const MATERIAL_DIRECTIVES = [ 24 | ...[ 25 | MdAnchor, 26 | MdButton, 27 | MdCheckbox, 28 | MdIcon, 29 | MdProgressBar, 30 | MdProgressCircle, 31 | MdRadioButton, 32 | MdRadioGroup, 33 | MdSpinner, 34 | MdToolbar 35 | ], 36 | ...MD_CARD_DIRECTIVES, 37 | ...MD_GRID_LIST_DIRECTIVES, 38 | ...MD_INPUT_DIRECTIVES, 39 | ...MD_LIST_DIRECTIVES, 40 | ...MD_SIDENAV_DIRECTIVES, 41 | ...MD_SLIDE_TOGGLE_DIRECTIVES, 42 | ...MD_TABS_DIRECTIVES 43 | ]; 44 | 45 | export const MATERIAL_PROVIDERS = [ 46 | MdIconRegistry, 47 | MdRadioDispatcher 48 | ]; 49 | -------------------------------------------------------------------------------- /src/app/home/home.html: -------------------------------------------------------------------------------- 1 |
2 | Your Content Here 3 | 4 | Local State 5 | 6 | 7 |
8 | 9 | 14 | 15 | 16 | 17 |
18 | 24 | 25 |
this.localState = {{ localState | json }}
26 | 27 |
28 |
29 | 30 | 31 |

32 | 35 |

36 | 37 | 38 | 39 | This is the content 40 | 41 | 42 | {{group.content}} 43 | 44 | 45 | More content 46 | 47 | 48 |
49 | 50 |
51 | -------------------------------------------------------------------------------- /src/app/todo/todo.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Http, Headers} from '@angular/http'; 3 | 4 | // `Injectable` is usually used with `Dart` metadata 5 | // generation; it has no special meaning within `TypeScript` 6 | // This makes sure `TypeScript` emits the needed metadata 7 | // Reference : http://blog.thoughtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html 8 | @Injectable() 9 | export class TodoService { 10 | // The `public` keyword denotes that the constructor parameter will 11 | // be retained as a field. 12 | // Reference: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#336-members 13 | // Add `Http` type annotation to the `http` function argument 14 | // Type annotations in TypeScript are used to record the 15 | // intended contract of the function or variable. 16 | // Reference: https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3-types 17 | // Here we intend the constructor function to be called with the 18 | // `Http` parameter 19 | constructor(public http:Http) { 20 | 21 | } 22 | 23 | getAll() { 24 | return this.http.get('/api/todo') 25 | // map the `HTTP` response from `raw` to `JSON` format 26 | // using `RxJs` 27 | // Reference: https://github.com/Reactive-Extensions/RxJS 28 | .map(res => res.json()); 29 | } 30 | 31 | createTodo(data) { 32 | 33 | let headers = new Headers(); 34 | 35 | headers.append('Content-Type', 'application/json'); 36 | 37 | return this.http.post('/api/todo', JSON.stringify(data), 38 | {headers: headers}) 39 | .map(res => res.json()); 40 | } 41 | 42 | deleteTodo(id) { 43 | 44 | return this.http.delete(`/api/todo/${id}`) 45 | .map(res => res.json()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/sass/abstracts/_variables.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // This file contains all application-wide Sass variables. 3 | // ----------------------------------------------------------------------------- 4 | 5 | 6 | 7 | 8 | 9 | /// Regular font family 10 | /// @type List 11 | $text-font-stack: 'Open Sans', 'Helvetica Neue Light', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif !default; 12 | 13 | /// Code (monospace) font family 14 | /// @type List 15 | $code-font-stack: 'Courier New', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Monaco', monospace !default; 16 | 17 | 18 | 19 | 20 | 21 | /// Copy text color 22 | /// @type Color 23 | $text-color: rgb(34, 34, 34) !default; 24 | 25 | /// Main brand color 26 | /// @type Color 27 | $brand-color: rgb(229, 0, 80) !default; 28 | 29 | /// Light grey 30 | /// @type Color 31 | $light-grey: rgb(237, 237, 237) !default; 32 | 33 | /// Medium grey 34 | /// @type Color 35 | $mid-grey: rgb(153, 153, 153) !default; 36 | 37 | /// Dark grey 38 | /// @type Color 39 | $dark-grey: rgb(68, 68, 68) !default; 40 | 41 | 42 | 43 | 44 | 45 | /// Container's maximum width 46 | /// @type Length 47 | $max-width: 1180px !default; 48 | 49 | 50 | 51 | 52 | 53 | /// Breakpoints map 54 | /// @prop {String} keys - Keys are identifiers mapped to a given length 55 | /// @prop {Map} values - Values are actual breakpoints expressed in pixels 56 | /// @see {mixin} respond-to 57 | $breakpoints: ( 58 | 'small': (min-width: 320px), 59 | 'medium': (min-width: 768px), 60 | 'large': (min-width: 1024px), 61 | ) !default; 62 | 63 | 64 | 65 | 66 | 67 | 68 | /// Relative or absolute URL where all assets are served from 69 | /// @type String 70 | /// @example scss - When using a CDN 71 | /// $base-url: 'http://cdn.example.com/assets/'; 72 | $base-url: '/assets/' !default; 73 | -------------------------------------------------------------------------------- /src/app/todo/todo.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | import {TodoService} from './todo.service'; 4 | 5 | // We `import` `http` into our `TodoService` but we can only 6 | // specify providers within our component 7 | import {HTTP_PROVIDERS} from '@angular/http'; 8 | 9 | // Import NgFor directive 10 | import {NgFor} from '@angular/common'; 11 | 12 | // Create metadata with the `@Component` decorator 13 | @Component({ 14 | // HTML tag for specifying this component 15 | selector: 'todo', 16 | // Let Angular 2 know about `Http` and `TodoService` 17 | providers: [...HTTP_PROVIDERS, TodoService], 18 | template: require('./todo.html') 19 | }) 20 | export class Todo { 21 | 22 | // Initialize our `todoData.text` to an empty `string` 23 | todoData = { 24 | text: '' 25 | }; 26 | 27 | private todos: Array = []; 28 | 29 | constructor(public todoService: TodoService) { 30 | console.log('Todo constructor go!'); 31 | 32 | //this.todos = []; 33 | todoService.getAll() 34 | // `Rxjs`; we subscribe to the response 35 | .subscribe((res) => { 36 | 37 | // Populate our `todo` array with the `response` data 38 | this.todos = res; 39 | // Reset `todo` input 40 | this.todoData.text = ''; 41 | }); 42 | } 43 | 44 | createTodo() { 45 | 46 | this.todoService.createTodo(this.todoData) 47 | .subscribe((res) => { 48 | 49 | // Populate our `todo` array with the `response` data 50 | this.todos = res; 51 | // Reset `todo` input 52 | this.todoData.text = ''; 53 | }); 54 | } 55 | 56 | deleteTodo(id) { 57 | 58 | this.todoService.deleteTodo(id) 59 | .subscribe((res) => { 60 | 61 | // Populate our `todo` array with the `response` data 62 | this.todos = res; 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /config/ts-rules/importDestructuringSpacingRule.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * custom TSLint rule: Import Destructuring Spacing Rule 3 | * rules written in TypeScript must be compiled to JavaScript before invoking TSLint. to compile: 4 | * tsc -m commonjs --noImplicitAny importDestructuringSpacingRule.ts ../../node_modules/tslint/lib/tslint.d.ts 5 | * 6 | * enforces Angular Style Guide Rule 03-05: Import Destructuring Spacing 7 | * -- Do leave one whitespace character inside of the import statements' curly braces when destructuring. 8 | * -- Why? Whitespace makes it easier to read the imports. 9 | */ 10 | 11 | import * as ts from 'typescript'; 12 | import * as Lint from 'tslint/lib/lint'; 13 | 14 | export class Rule extends Lint.Rules.AbstractRule { 15 | public static FAILURE_STRING = "Style 03-05 Import Destructuring Spacing"; 16 | 17 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { 18 | return this.applyWithWalker(new ImportDestructuringSpacingWalker(sourceFile, this.getOptions())); 19 | } 20 | } 21 | 22 | // The walker takes care of all the work. 23 | class ImportDestructuringSpacingWalker extends Lint.SkippableTokenAwareRuleWalker { 24 | private scanner: ts.Scanner; 25 | 26 | constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) { 27 | super(sourceFile, options); 28 | this.scanner = ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, sourceFile.text); 29 | } 30 | 31 | public visitImportDeclaration(node: ts.ImportDeclaration) { 32 | const importClause = node.importClause; 33 | if (importClause != null && importClause.namedBindings != null) { 34 | const text = importClause.namedBindings.getText(); 35 | 36 | if (!this.checkForWhiteSpace(text)) { 37 | this.addFailure(this.createFailure(importClause.namedBindings.getStart(), importClause.namedBindings.getWidth(), Rule.FAILURE_STRING)); 38 | } 39 | } 40 | // call the base version of this visitor to actually parse this node 41 | super.visitImportDeclaration(node); 42 | } 43 | 44 | private checkForWhiteSpace(text: string) { 45 | return /{\s[^]*\s}/.test(text); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/app/recipes/recipes.reducer.ts: -------------------------------------------------------------------------------- 1 | // ``` 2 | // recipes.reducer.js 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // recipes.reducer.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // ** Import our `recipe` store 9 | import {Recipe} from './recipe.store'; 10 | 11 | // # Redux reducer for `recipes` 12 | 13 | // A traditional `reducer` is a function which takes a `state` 14 | // object and an action to perform. 15 | 16 | // `ngrx` reducers work differently: 17 | // * the second parameter is an object with the type of 18 | // action to perform and the payload for that action 19 | 20 | // The `recipes` reducer performs actions on our list of `recipes` 21 | // Notice that we set `state` to a default value to initialize 22 | // smoothly 23 | export const recipes = (state: any = [], {type, payload}) => { 24 | 25 | // DEBUG 26 | console.log('Recipes reducer hit! type: '); 27 | console.log(type); 28 | console.log('payload: '); 29 | console.log(payload); 30 | console.log('state: '); 31 | console.log(state); 32 | 33 | switch (type) { 34 | 35 | // `ADD_RECIPES` returns whatever collection passed in as a 36 | // new array 37 | case 'ADD_RECIPES': 38 | return payload; 39 | 40 | // `CREATE_RECIPE` returns a new array by concatenating the 41 | // existing recipe array with our new recipe 42 | case 'CREATE_RECIPE': 43 | return [...state, payload]; 44 | 45 | // `UPDATE_RECIPE` returns a new array by mapping to the current 46 | // array, locating the recipe to update and cloning to create 47 | // a new object using `Object.assign` 48 | case 'UPDATE_RECIPE': 49 | return state.map(recipe => { 50 | 51 | return recipe._id === payload._id 52 | ? Object.assign({}, recipe, payload) : recipe; 53 | }); 54 | 55 | // `DELETE_RECIPE` returns a new array by filtering out the 56 | // `recipe` that we want to delete 57 | case 'DELETE_RECIPE': 58 | 59 | return state.filter(recipe => { 60 | 61 | return recipe._id !== payload._id; 62 | }); 63 | 64 | default: 65 | return state; 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /src/app/shared/directives/router-active/router-active.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | Query, 4 | QueryList, 5 | Attribute, 6 | ElementRef, 7 | Renderer, 8 | Optional, 9 | Input 10 | } from '@angular/core'; 11 | import {isPresent} from '@angular/core/src/facade/lang'; 12 | import {Instruction, Router, RouterLink} from '@angular/router-deprecated'; 13 | 14 | /* 15 | * RouterActive dynamically finds the first element with routerLink and toggles the active class 16 | * 17 | * ## Use 18 | * 19 | * ``` 20 | *
  • Home
  • 21 | *
  • Home
  • 22 | * ``` 23 | */ 24 | @Directive({ 25 | selector: '[router-active]' 26 | }) 27 | export class RouterActive { 28 | @Input() routerActive: string = undefined; 29 | routerActiveAttr: string = 'active'; 30 | 31 | constructor( 32 | public router: Router, 33 | public element: ElementRef, 34 | public renderer: Renderer, 35 | @Query(RouterLink) public routerLink: QueryList, 36 | @Optional() @Attribute('router-active') routerActiveAttr?: string) { 37 | 38 | this.routerActiveAttr = this._defaultAttrValue(routerActiveAttr); 39 | } 40 | 41 | ngOnInit() { 42 | this.routerLink.changes.subscribe(() => { 43 | if (this.routerLink.first) { 44 | this._updateClass(); 45 | this._findRootRouter().subscribe(() => { 46 | this._updateClass(); 47 | }); 48 | } 49 | }); 50 | } 51 | 52 | private _findRootRouter(): Router { 53 | var router: Router = this.router; 54 | while (isPresent(router.parent)) { 55 | router = router.parent; 56 | } 57 | return router; 58 | } 59 | 60 | private _updateClass() { 61 | let active = this.routerLink.first.isRouteActive; 62 | this.renderer.setElementClass(this.element.nativeElement, this._attrOrProp(), active); 63 | } 64 | 65 | private _defaultAttrValue(attr?: string) { 66 | return this.routerActiveAttr = attr || this.routerActiveAttr; 67 | } 68 | 69 | private _attrOrProp() { 70 | return isPresent(this.routerActive) ? this.routerActive : this.routerActiveAttr; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // routes.js 3 | // (c) 2015 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // routes.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // */app/routes.js* 9 | 10 | // ## Node API Routes 11 | 12 | // Define routes for the Node backend 13 | 14 | // Load our API routes for user authentication 15 | import authRoutes from './routes/_authentication.router.js'; 16 | 17 | // Load our API routes for the `todo` component 18 | import todoRoutes from './routes/_todo.router.js'; 19 | 20 | // Load our API routes for the `recipe` component 21 | import recipeRoutes from './routes/_recipe.router.js'; 22 | 23 | export default (app, router, passport) => { 24 | 25 | // ### Express Middlware to use for all requests 26 | router.use((req, res, next) => { 27 | 28 | console.log('I sense a disturbance in the force...'); // DEBUG 29 | 30 | // Make sure we go to the next routes and don't stop here... 31 | next(); 32 | }); 33 | 34 | // Define a middleware function to be used for all secured routes 35 | let auth = (req, res, next) => { 36 | 37 | if (!req.isAuthenticated()) 38 | res.send(401); 39 | 40 | else 41 | next(); 42 | }; 43 | 44 | // Define a middleware function to be used for all secured administration 45 | // routes 46 | let admin = (req, res, next) => { 47 | 48 | if (!req.isAuthenticated() || req.user.role !== 'admin') 49 | res.send(401); 50 | 51 | else 52 | next(); 53 | }; 54 | 55 | // ### Server Routes 56 | 57 | // Handle things like API calls, 58 | 59 | // #### Authentication routes 60 | 61 | // Pass in our Express app and Router. 62 | // Also pass in auth & admin middleware and Passport instance 63 | authRoutes(app, router, passport, auth, admin); 64 | 65 | // #### RESTful API Routes 66 | 67 | // Pass in our Express app and Router 68 | todoRoutes(app, router); 69 | 70 | recipeRoutes(app, router); 71 | 72 | // All of our routes will be prefixed with /api 73 | app.use('/api', router); 74 | 75 | // ### Frontend Routes 76 | 77 | // Route to handle all Angular requests 78 | app.get('*', (req, res) => { 79 | 80 | // Load our src/app.html file 81 | //** Note that the root is set to the parent of this folder, ie the app root ** 82 | res.sendFile('/dist/index.html', { root: __dirname + "/../"}); 83 | }); 84 | }; 85 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {AppState} from '../app.service'; 3 | 4 | import {Title} from './services/title'; 5 | import {XLarge} from './directives/x-large'; 6 | 7 | import {Accordion} from '../shared/components/accordion/accordion.component'; 8 | 9 | import {AccordionGroup} from 10 | '../shared/components/accordion/accordion-group.component'; 11 | 12 | // Import NgFor directive 13 | import {NgFor} from '@angular/common'; 14 | 15 | @Component({ 16 | // The selector is what angular internally uses 17 | // for `document.querySelectorAll(selector)` in our index.html 18 | // where, in this case, selector is the string 'home' 19 | selector: 'home', // 20 | // We need to tell Angular's Dependency Injection which providers are in our app. 21 | providers: [ 22 | Title 23 | ], 24 | // We need to tell Angular's compiler which directives are in our template. 25 | // Doing so will allow Angular to attach our behavior to an element 26 | directives: [ 27 | XLarge, 28 | Accordion, 29 | AccordionGroup, 30 | NgFor 31 | ], 32 | // We need to tell Angular's compiler which custom pipes are in our template. 33 | pipes: [ ], 34 | // Our list of styles in our component. We may add more to compose many styles together 35 | styles: [ require('./home.css') ], 36 | // Every Angular template is first compiled by the browser before Angular runs it's compiler 37 | template: require('./home.html') 38 | }) 39 | export class Home { 40 | // Set our default values 41 | localState = { value: '' }; 42 | 43 | // TypeScript public modifiers 44 | constructor(public appState: AppState, public title: Title) { 45 | 46 | } 47 | 48 | isOpen: boolean = false; 49 | 50 | groups: Array = [ 51 | { 52 | heading: 'Dynamic 1', 53 | content: 'I am dynamic!' 54 | }, 55 | { 56 | heading: 'Dynamic 2', 57 | content: 'Dynamic as well' 58 | } 59 | ]; 60 | 61 | removeDynamic() { 62 | this.groups.pop(); 63 | } 64 | 65 | ngOnInit() { 66 | console.log('hello `Home` component'); 67 | // this.title.getData().subscribe(data => this.data = data); 68 | } 69 | 70 | submitState(value) { 71 | console.log('submitState', value); 72 | this.appState.set('value', value); 73 | this.localState.value = ''; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/app/recipes/recipes.spec.ts: -------------------------------------------------------------------------------- 1 | import {recipes} from './recipes.reducer'; 2 | 3 | import {selectedRecipe} from './selected-recipe.reducer'; 4 | 5 | import { 6 | it, 7 | describe, 8 | expect 9 | } from '@angular/core/testing'; 10 | 11 | describe('Recipes', () => { 12 | describe('`selectedRecipe` store', () => { 13 | it('returns null by default', () => { 14 | let defaultState = selectedRecipe(undefined, {type: 'random', payload: {}}); 15 | 16 | expect(defaultState).toBeNull(); 17 | }); 18 | 19 | it('`SELECT_RECIPE` returns the provided payload', () => { 20 | let selectRecipe = selectedRecipe(undefined, {type: 'SELECT_RECIPE', payload: 'payload'}); 21 | 22 | expect(selectRecipe).toBe('payload'); 23 | }); 24 | }); 25 | 26 | describe('`recipes` store', () => { 27 | let initialState = [ 28 | { _id: 0, name: 'First Recipe' }, 29 | { _id: 1, name: 'Second Recipe' } 30 | ]; 31 | 32 | it('returns an empty array by default', () => { 33 | let defaultState = recipes(undefined, {type: 'random', payload: {}}); 34 | 35 | expect(defaultState).toEqual([]); 36 | }); 37 | 38 | it('`ADD_RECIPES`', () => { 39 | let payload = initialState, 40 | stateItems = recipes([], {type: 'ADD_RECIPES', payload: payload}); 41 | 42 | expect(stateItems).toEqual(payload); 43 | }); 44 | 45 | it('`CREATE_RECIPE`', () => { 46 | let payload = {_id: 2, name: 'added recipe'}, 47 | result = [...initialState, payload], 48 | stateItems = recipes(initialState, {type: 'CREATE_RECIPE', payload: payload}); 49 | 50 | expect(stateItems).toEqual(result); 51 | }); 52 | 53 | it('`UPDATE_RECIPE`', () => { 54 | let payload = { _id: 1, name: 'Updated Recipe' }, 55 | result = [ initialState[0], { _id: 1, name: 'Updated Recipe' } ], 56 | stateItems = recipes(initialState, {type: 'UPDATE_RECIPE', payload: payload}); 57 | 58 | expect(stateItems).toEqual(result); 59 | }); 60 | 61 | it('`DELETE_RECIPE`', () => { 62 | let payload = { _id: 0 }, 63 | result = [ initialState[1] ], 64 | stateItems = recipes(initialState, {type: 'DELETE_RECIPE', payload: payload}); 65 | 66 | // DEBUG 67 | console.log('result: '); 68 | console.log(result); 69 | 70 | expect(stateItems).toEqual(result); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /config/karma.conf.js: -------------------------------------------------------------------------------- 1 | // @datatype_void 2 | 3 | module.exports = function(config) { 4 | 5 | var testWebpackConfig = require('./config/webpack.test.config.js'); 6 | config.set({ 7 | 8 | // base path that will be used to resolve all patterns (e.g. files, exclude) 9 | basePath: '', 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | // list of files to exclude 16 | exclude: [ ], 17 | 18 | // list of files / patterns to load in the browser 19 | // we are building the test environment in ./spec-bundle.js 20 | files: [ { pattern: 'spec-bundle.js', watched: false } ], 21 | 22 | // preprocess matching files before serving them to the browser 23 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 24 | preprocessors: { 'spec-bundle.js': ['coverage', 'webpack', 'sourcemap'] }, 25 | 26 | // Webpack Config at ./webpack.test.config.js 27 | webpack: testWebpackConfig, 28 | 29 | coverageReporter: { 30 | dir : 'coverage/', 31 | reporters: [ 32 | { type: 'text-summary'}, 33 | { type: 'json' }, 34 | { type: 'html' } 35 | ] 36 | }, 37 | 38 | // Webpack please don't spam the console when running in karma! 39 | webpackServer: { noInfo: true }, 40 | 41 | // test results reporter to use 42 | // possible values: 'dots', 'progress', 'mocha' 43 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 44 | reporters: [ 'mocha', 'coverage' ], 45 | 46 | // web server port 47 | port: 9876, 48 | 49 | // enable / disable colors in the output (reporters and logs) 50 | colors: true, 51 | 52 | // level of logging 53 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 54 | logLevel: config.LOG_INFO, 55 | 56 | // enable / disable watching file and executing tests whenever any file changes 57 | autoWatch: false, 58 | 59 | // start these browsers 60 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 61 | browsers: [ 62 | // 'Chrome', 63 | 'PhantomJS' 64 | ], 65 | 66 | // Continuous Integration mode 67 | // if true, Karma captures browsers, runs the tests and exits 68 | singleRun: true 69 | }); 70 | 71 | }; 72 | -------------------------------------------------------------------------------- /config/spec-bundle.js: -------------------------------------------------------------------------------- 1 | // @datatype_void 2 | /* 3 | * When testing with webpack and ES6, we have to do some extra 4 | * things get testing to work right. Because we are gonna write test 5 | * in ES6 to, we have to compile those as well. That's handled in 6 | * karma.conf.js with the karma-webpack plugin. This is the entry 7 | * file for webpack test. Just like webpack will create a bundle.js 8 | * file for our client, when we run test, it well compile and bundle them 9 | * all here! Crazy huh. So we need to do some setup 10 | */ 11 | Error.stackTraceLimit = Infinity; 12 | //require('phantomjs-polyfill'); 13 | //require('es6-promise'); 14 | //require('es6-shim'); 15 | //require('es7-reflect-metadata'); 16 | 17 | // Prefer `CoreJS` over the polyfills commented out above 18 | require('core-js'); 19 | 20 | // Typescript "emit helpers" polyfill 21 | require('ts-helpers'); 22 | 23 | // Zone.js 24 | require('zone.js/dist/zone'); 25 | require('zone.js/dist/long-stack-trace-zone'); 26 | require('zone.js/dist/jasmine-patch'); 27 | require('zone.js/dist/async-test'); 28 | require('zone.js/dist/fake-async-test'); 29 | require('zone.js/dist/sync-test'); 30 | 31 | // RxJS 32 | require('rxjs/Rx'); 33 | 34 | var testing = require('@angular/core/testing'); 35 | var browser = require('@angular/platform/testing/browser'); 36 | 37 | testing.setBaseTestProviders( 38 | browser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, 39 | browser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS 40 | ); 41 | 42 | Object.assign(global, testing); 43 | 44 | /* 45 | Ok, this is kinda crazy. We can use the the context method on 46 | require that webpack created in order to tell webpack 47 | what files we actually want to require or import. 48 | Below, context will be an function/object with file names as keys. 49 | using that regex we are saying look in ./src/app and ./test then find 50 | any file that ends with spec.js and get its path. By passing in true 51 | we say do this recursively 52 | */ 53 | var testContext = require.context('../src', true, /\.spec\.ts/); 54 | 55 | // get all the files, for each file, call the context function 56 | // that will require the file and load it up here. Context will 57 | // loop and require those spec files here 58 | function requireAll(requireContext) { 59 | return requireContext.keys().map(requireContext); 60 | } 61 | 62 | // require and return all modules that match 63 | var modules = requireAll(testContext); 64 | -------------------------------------------------------------------------------- /src/app/recipes/recipes.component.ts: -------------------------------------------------------------------------------- 1 | // ``` 2 | // recipes.component.js 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // recipes.component.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // # Recipes Component 9 | 10 | import {Component, 11 | Input, 12 | Output, 13 | EventEmitter, 14 | ChangeDetectionStrategy} from '@angular/core'; 15 | 16 | import {Observable} from 'rxjs/Observable'; 17 | import {Store} from '@ngrx/store'; 18 | import {AppStore} from '../app.store'; 19 | 20 | import {Recipe} from './recipe.store'; 21 | import {RecipeService} from './recipe.service'; 22 | import {RecipeDetails} from './recipe-details.component'; 23 | import {RecipeList} from './recipe-list.component'; 24 | 25 | @Component({ 26 | selector: 'recipes', 27 | providers: [], 28 | template: require('./recipes.html'), 29 | directives: [RecipeList, RecipeDetails], 30 | changeDetection: ChangeDetectionStrategy.OnPush 31 | }) 32 | 33 | export class Recipes { 34 | 35 | recipes: Observable>; 36 | 37 | selectedRecipe: Observable; 38 | 39 | constructor(private recipeService: RecipeService, 40 | private store: Store) { 41 | 42 | // Bind to the `recipes` observable on `RecipeService` 43 | this.recipes = recipeService.recipes; 44 | 45 | // Bind the `selectedRecipe` observable from the store 46 | this.selectedRecipe = store.select('selectedRecipe'); 47 | 48 | // DEBUG 49 | this.selectedRecipe.subscribe(v => console.log(v)); 50 | 51 | // `recipeService.loadRecipes` dispatches the `ADD_RECIPES` event 52 | // to our store which in turn updates the `recipes` collection 53 | recipeService.loadRecipes(); 54 | } 55 | 56 | selectRecipe(recipe: Recipe) { 57 | 58 | this.store.dispatch({ 59 | 60 | type: 'SELECT_RECIPE', 61 | payload: recipe 62 | }); 63 | } 64 | 65 | deleteRecipe(recipe: Recipe) { 66 | 67 | this.recipeService.deleteRecipe(recipe); 68 | } 69 | 70 | resetRecipe() { 71 | 72 | let emptyRecipe: Recipe = { 73 | 74 | _id: null, 75 | tags: [], 76 | title: '', 77 | description: '', 78 | rating: null, 79 | creator: '', 80 | ingredients: [], 81 | directions: [] 82 | }; 83 | 84 | this.store.dispatch({ 85 | 86 | type: 'SELECT_RECIPE', 87 | payload: emptyRecipe 88 | }); 89 | } 90 | 91 | saveRecipe(recipe: Recipe) { 92 | 93 | this.recipeService.saveRecipe(recipe); 94 | this.resetRecipe(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /config/ts-rules/importDestructuringSpacingRule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * custom TSLint rule: Import Destructuring Spacing Rule 3 | * rules written in TypeScript must be compiled to JavaScript before invoking TSLint. to compile: 4 | * tsc -m commonjs --noImplicitAny importDestructuringSpacingRule.ts ../../node_modules/tslint/lib/tslint.d.ts 5 | * 6 | * enforces Angular Style Guide Rule 03-05: Import Destructuring Spacing 7 | * -- Do leave one whitespace character inside of the import statements' curly braces when destructuring. 8 | * -- Why? Whitespace makes it easier to read the imports. 9 | */ 10 | var __extends = (this && this.__extends) || function (d, b) { 11 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 12 | function __() { this.constructor = d; } 13 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 14 | }; 15 | var ts = require('typescript'); 16 | var Lint = require('tslint/lib/lint'); 17 | var Rule = (function (_super) { 18 | __extends(Rule, _super); 19 | function Rule() { 20 | _super.apply(this, arguments); 21 | } 22 | Rule.prototype.apply = function (sourceFile) { 23 | return this.applyWithWalker(new ImportDestructuringSpacingWalker(sourceFile, this.getOptions())); 24 | }; 25 | Rule.FAILURE_STRING = "Style 03-05 Import Destructuring Spacing"; 26 | return Rule; 27 | })(Lint.Rules.AbstractRule); 28 | exports.Rule = Rule; 29 | // The walker takes care of all the work. 30 | var ImportDestructuringSpacingWalker = (function (_super) { 31 | __extends(ImportDestructuringSpacingWalker, _super); 32 | function ImportDestructuringSpacingWalker(sourceFile, options) { 33 | _super.call(this, sourceFile, options); 34 | this.scanner = ts.createScanner(ts.ScriptTarget.ES5, false, ts.LanguageVariant.Standard, sourceFile.text); 35 | } 36 | ImportDestructuringSpacingWalker.prototype.visitImportDeclaration = function (node) { 37 | var importClause = node.importClause; 38 | if (importClause != null && importClause.namedBindings != null) { 39 | var text = importClause.namedBindings.getText(); 40 | if (!this.checkForWhiteSpace(text)) { 41 | this.addFailure(this.createFailure(importClause.namedBindings.getStart(), importClause.namedBindings.getWidth(), Rule.FAILURE_STRING)); 42 | } 43 | } 44 | // call the base version of this visitor to actually parse this node 45 | _super.prototype.visitImportDeclaration.call(this, node); 46 | }; 47 | ImportDestructuringSpacingWalker.prototype.checkForWhiteSpace = function (text) { 48 | return /{\s[^]*\s}/.test(text); 49 | }; 50 | return ImportDestructuringSpacingWalker; 51 | })(Lint.SkippableTokenAwareRuleWalker); 52 | -------------------------------------------------------------------------------- /src/app/recipes/recipe.service.ts: -------------------------------------------------------------------------------- 1 | // ``` 2 | // recipe.service.js 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // recipe.service.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // # Recipe Service 9 | 10 | import {Http, Headers} from '@angular/http'; 11 | import {Store} from '@ngrx/store'; 12 | import {Injectable} from '@angular/core'; 13 | import {Observable} from 'rxjs/Observable'; 14 | 15 | import {Recipe} from './recipe.store'; 16 | import {AppStore} from '../app.store'; 17 | 18 | const HEADER = { 19 | headers: new Headers({ 20 | 'Content-Type': 'application/json' 21 | }) 22 | }; 23 | 24 | @Injectable() 25 | export class RecipeService { 26 | 27 | recipes: Observable>; 28 | 29 | // Inject the `AppStore` into the constructor with a type of `AppStore` 30 | constructor(private http: Http, private store: Store) { 31 | 32 | // Bind an observable of our `recipes` to `RecipeService` 33 | // Since this is essentially a `key, value` system, we can 34 | // set our `recipes` by calling `store.select('recipes')` 35 | this.recipes = store.select('recipes'); 36 | } 37 | 38 | loadRecipes() { 39 | 40 | this.http.get('/api/recipe') 41 | // map the `HTTP` response from `raw` to `JSON` format 42 | // using `RxJs` 43 | // Reference: https://github.com/Reactive-Extensions/RxJS 44 | .map(res => res.json()) 45 | // call `map` again to create the object we want to dispatch 46 | // to our reducer 47 | // This combo of `map` method calls is an observable sequence 48 | // in that every result gets passed through this sequence of 49 | // operations 50 | .map(payload => ({ type: 'ADD_RECIPES', payload })) 51 | // Subscribe to this sequence and hand off control to the 52 | // reducer by dispatching the transformed results 53 | .subscribe(action => this.store.dispatch(action)); 54 | } 55 | 56 | saveRecipe(recipe: Recipe) { 57 | 58 | (recipe._id) ? this.updateRecipe(recipe) : this.createRecipe(recipe); 59 | } 60 | 61 | createRecipe(recipe: Recipe) { 62 | 63 | this.http.post('/api/recipe', JSON.stringify(recipe), HEADER) 64 | .map(res => res.json()) 65 | .map(payload => ({ type: 'CREATE_RECIPE', payload })) 66 | .subscribe(action => this.store.dispatch(action)); 67 | } 68 | 69 | updateRecipe(recipe: Recipe) { 70 | 71 | this.http.put(`/api/recipe/${recipe._id}`, JSON.stringify(recipe), HEADER) 72 | // Dispatch action to reducer in subscribe block here 73 | .subscribe(action => this.store.dispatch({ type: 'UPDATE_RECIPE', payload: recipe })); 74 | } 75 | 76 | deleteRecipe(recipe: Recipe) { 77 | 78 | this.http.delete(`/api/recipe/${recipe._id}`) 79 | .subscribe(action => this.store.dispatch({ type: 'DELETE_RECIPE', payload: recipe })); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app/recipes/recipe-details.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Editing {{originalTitle}}

    4 |

    Create New Recipe

    5 |
    6 |
    7 |
    8 |
    9 | 10 | 12 |
    13 |
    14 | 15 | 18 |
    19 | 21 | 24 |
    25 |
    26 |
    27 | 28 | 30 |
    31 |
    32 | 33 | 35 | 37 |
    38 |
    39 | 40 | 42 |
    43 |
    44 | 45 | 48 |
    49 | 51 | 53 | 55 | 57 | 58 |
    59 |
    60 |
    61 | 62 | 65 |
    66 | 68 | 71 |
    72 |
    73 |
    74 |
    75 |
    76 | 79 | 82 |
    83 |
    84 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= webpackConfig.metadata.title %> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Loading... 43 | 44 | 45 | 46 | 55 | 56 | <% if (webpackConfig.metadata.ENV === 'development') { %> 57 | 58 | 59 | <% } %> 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": "config/ts-rules/", 3 | "rules": { 4 | "member-access": false, 5 | "member-ordering": [ 6 | true, 7 | "public-before-private", 8 | "static-before-instance", 9 | "variables-before-functions" 10 | ], 11 | "no-any": false, 12 | "no-inferrable-types": false, 13 | "no-internal-module": true, 14 | "no-var-requires": false, 15 | "typedef": false, 16 | "typedef-whitespace": [ 17 | true, 18 | { 19 | "call-signature": "nospace", 20 | "index-signature": "nospace", 21 | "parameter": "nospace", 22 | "property-declaration": "nospace", 23 | "variable-declaration": "nospace" 24 | }, 25 | { 26 | "call-signature": "space", 27 | "index-signature": "space", 28 | "parameter": "space", 29 | "property-declaration": "space", 30 | "variable-declaration": "space" 31 | } 32 | ], 33 | 34 | "ban": false, 35 | "curly": false, 36 | "forin": true, 37 | "label-position": true, 38 | "label-undefined": true, 39 | "no-arg": true, 40 | "no-bitwise": true, 41 | "no-conditional-assignment": true, 42 | "no-console": [ 43 | true, 44 | "debug", 45 | "info", 46 | "time", 47 | "timeEnd", 48 | "trace" 49 | ], 50 | "no-construct": true, 51 | "no-debugger": true, 52 | "no-duplicate-key": true, 53 | "no-duplicate-variable": true, 54 | "no-empty": false, 55 | "no-eval": true, 56 | "no-null-keyword": true, 57 | "no-shadowed-variable": true, 58 | "no-string-literal": true, 59 | "no-switch-case-fall-through": true, 60 | "no-unreachable": true, 61 | "no-unused-expression": true, 62 | "no-unused-variable": false, 63 | "no-use-before-declare": true, 64 | "no-var-keyword": true, 65 | "radix": true, 66 | "switch-default": true, 67 | "triple-equals": [ 68 | true, 69 | "allow-null-check" 70 | ], 71 | "use-strict": [ 72 | true, 73 | "check-module" 74 | ], 75 | 76 | "eofline": true, 77 | "indent": [ 78 | true, 79 | "spaces" 80 | ], 81 | "max-line-length": [ 82 | true, 83 | 100 84 | ], 85 | "no-require-imports": false, 86 | "no-trailing-whitespace": true, 87 | "object-literal-sort-keys": false, 88 | "trailing-comma": [ 89 | true, 90 | { 91 | "multiline": "never", 92 | "singleline": "never" 93 | } 94 | ], 95 | 96 | "align": false, 97 | "class-name": true, 98 | "comment-format": [ 99 | true, 100 | "check-space" 101 | ], 102 | "interface-name": false, 103 | "jsdoc-format": true, 104 | "no-consecutive-blank-lines": false, 105 | "no-constructor-vars": false, 106 | "one-line": [ 107 | true, 108 | "check-open-brace", 109 | "check-catch", 110 | "check-else", 111 | "check-finally", 112 | "check-whitespace" 113 | ], 114 | "quotemark": [ 115 | true, 116 | "single", 117 | "avoid-escape" 118 | ], 119 | "semicolon": [true, "always"], 120 | "variable-name": [ 121 | true, 122 | "check-format", 123 | "allow-leading-underscore", 124 | "ban-keywords" 125 | ], 126 | "whitespace": [ 127 | true, 128 | "check-branch", 129 | "check-decl", 130 | "check-operator", 131 | "check-separator", 132 | "check-type" 133 | ], 134 | "import-destructuring-spacing": true 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /config/env.conf.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // env.conf.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // env.conf.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // *env.conf.js* 9 | 10 | // This is the file where we will configure our Node environmental 11 | // variables for production 12 | 13 | // Reference : http://thewebivore.com/super-simple-environment-variables-node-js/#comment-286662 14 | 15 | // # Node Env Variables 16 | 17 | import config from './config.json'; 18 | 19 | // Check each necessary node `environment variable` to see if a 20 | // value has been set and if not, use the `config` object to 21 | // supply appropriate values 22 | export function validateEnvVariables() { 23 | 24 | // If no value has been assigned to our environment variables, 25 | // set them up... 26 | 27 | if(!process.env.NODE_ENV) { 28 | process.env.NODE_ENV = config.ENV; 29 | } 30 | 31 | // Check to see if `process.env.NODE_ENV` is valid 32 | validateNodeEnvironment(); 33 | 34 | // For Express/Passport 35 | if (!process.env.SESSION_SECRET) 36 | process.env.SESSION_SECRET = config.SESSION_SECRET; 37 | 38 | if (!process.env.PORT) 39 | process.env.PORT = config.PORT; 40 | 41 | // Set the appropriate MongoDB URI 42 | validateMongoUri(); 43 | 44 | return; 45 | } 46 | 47 | function validateNodeEnvironment() { 48 | // Check to see that the `process.env.NODE_ENV has been 49 | // set to an appropriate value of `development`, `production` 50 | // or `test`. If not, alert the user and default to `development` 51 | 52 | switch(process.env.NODE_ENV) { 53 | 54 | case 'development': 55 | 56 | console.log(`Node environment set for ${process.env.NODE_ENV}`); 57 | break; 58 | 59 | case 'production': 60 | 61 | console.log(`Node environment set for ${process.env.NODE_ENV}`); 62 | break; 63 | 64 | case 'test': 65 | 66 | console.log(`Node environment set for ${process.env.NODE_ENV}`); 67 | break; 68 | 69 | default: 70 | 71 | console.log('Error: process.env.NODE_ENV should be set to a valid ' 72 | + ' value such as \'production\', \'development\', or \'test\'.'); 73 | console.log('Value received: ' + process.env.NODE_ENV); 74 | console.log('Defaulting value for: development'); 75 | process.env.NODE_ENV = 'development'; 76 | break; 77 | } 78 | 79 | return; 80 | } 81 | 82 | // Set the appropriate MongoDB URI with the `config` object 83 | // based on the value in `process.env.NODE_ENV 84 | function validateMongoUri() { 85 | 86 | if (!process.env.MONGO_URI) { 87 | 88 | console.log('No value set for MONGO_URI...'); 89 | console.log('Using the supplied value from config object...') 90 | 91 | switch(process.env.NODE_ENV) { 92 | 93 | case 'development': 94 | 95 | process.env.MONGO_URI = config.MONGO_URI.DEVELOPMENT; 96 | console.log(`MONGO_URI set for ${process.env.NODE_ENV}`); 97 | break; 98 | 99 | case 'production': 100 | 101 | process.env.MONGO_URI = config.MONGO_URI.PRODUCTION; 102 | console.log(`MONGO_URI set for ${process.env.NODE_ENV}`); 103 | break; 104 | 105 | case 'test': 106 | 107 | process.env.MONGO_URI = config.MONGO_URI.TEST; 108 | console.log(`MONGO_URI set for ${process.env.NODE_ENV}`); 109 | break; 110 | 111 | default: 112 | 113 | console.log('Unexpected behavior! process.env.NODE_ENV set to ' + 114 | 'unexpected value!'); 115 | break; 116 | } 117 | } 118 | 119 | return; 120 | } 121 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | // ``` 2 | // app.ts 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // app.ts may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // *src/app/app.ts* 9 | 10 | // This file contains the main class as well as the necessary 11 | // decorators for creating the primary `app` `component` 12 | 13 | /* 14 | * Angular 2 decorators and services 15 | */ 16 | import {Component, ViewEncapsulation} from '@angular/core'; 17 | import {RouteConfig, Router} from '@angular/router-deprecated'; 18 | 19 | import {AppState} from './app.service'; 20 | 21 | import {RouterActive} from './shared/directives/router-active/router-active.directive'; 22 | 23 | import {Home} from './home'; 24 | 25 | // Import NgFor directive 26 | import {NgFor} from '@angular/common'; 27 | 28 | // Import Todo component 29 | import {Todo} from './todo/todo.component'; 30 | 31 | // Import Recipes component 32 | import {Recipes} from './recipes/recipes.component'; 33 | 34 | /* 35 | * App Component 36 | * Top Level Component 37 | */ 38 | @Component({ 39 | selector: 'app', 40 | providers: [ ], 41 | directives: [ Todo, 42 | NgFor, 43 | RouterActive], 44 | encapsulation: ViewEncapsulation.None, 45 | pipes: [], 46 | // Load our main `Sass` file into our `app` `component` 47 | styleUrls: [require('!style!css!sass!../sass/main.scss')], 48 | template: ` 49 | 50 | 51 | {{ name }} 52 | 53 | 56 | 59 | 62 | 65 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
    this.appState.state = {{ appState.state | json }}
    76 | 77 | 81 |
    82 | ` 83 | }) 84 | @RouteConfig([ 85 | { path: '/', name: 'Index', component: Home, useAsDefault: true }, 86 | { path: '/home', name: 'Home', component: Home }, 87 | { path: '/todo', component: Todo, name: 'Todo' }, 88 | { path: '/redux', component: Recipes, name: 'Recipes' }, 89 | // Async load a component using Webpack's require with 90 | // es6-promise-loader and webpack `require` 91 | { path: '/about', name: 'About', loader: () => require('es6-promise!./about')('About') }, 92 | ]) 93 | export class App { 94 | angularLogo = 'assets/img/angular-logo.png'; 95 | name = 'Angular 2 MEAN Webpack Starter'; 96 | url = 'https://twitter.com/datatype_void'; 97 | 98 | // Pass in our application `state` 99 | // Alternative to using `redux` 100 | constructor(public appState: AppState) {} 101 | 102 | // Fire off upon initialization 103 | ngOnInit() { 104 | 105 | console.log('Initial App State', this.appState.state); 106 | } 107 | } 108 | 109 | /* 110 | * For help or questions please contact us at @datatype_void on twitter 111 | * or our chat on Slack at http://www.davidniciforovic.com/wp-login.php?action=slack-invitation 112 | */ 113 | -------------------------------------------------------------------------------- /app/routes/_todo.router.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // _todo.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // _todo.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // */app/routes/_todo.router.js* 9 | 10 | // ## Todo API object 11 | 12 | // HTTP Verb Route Description 13 | 14 | // GET /api/todo Get all of the todo items 15 | // GET /api/todo/:todo_id Get a single todo item by todo item id 16 | // POST /api/todo Create a single todo item 17 | // DELETE /api/todo/:todo_id Delete a single todo item 18 | // PUT /api/todo/:todo_id Update a todo item with new info 19 | 20 | // Load the todo model 21 | import Todo from '../models/todo.model'; 22 | 23 | export default (app, router) => { 24 | 25 | // ### Todo API Routes 26 | 27 | // Define routes for the todo item API 28 | 29 | router.route('/todo') 30 | 31 | // ### Create a todo item 32 | 33 | // Accessed at POST http://localhost:8080/api/todo 34 | 35 | // Create a todo item 36 | .post((req, res) => { 37 | 38 | Todo.create({ 39 | 40 | text : req.body.text 41 | 42 | }, (err, todo) => { 43 | 44 | if (err) 45 | res.send(err); 46 | 47 | // DEBUG 48 | console.log(`Todo created: ${todo}`); 49 | 50 | Todo.find((err, todos) => { 51 | if(err) 52 | res.send(err); 53 | 54 | res.json(todos); 55 | }); 56 | }); 57 | }) 58 | 59 | // ### Get all of the todo items 60 | 61 | // Accessed at GET http://localhost:8080/api/todo 62 | .get((req, res) => { 63 | 64 | // Use mongoose to get all todo items in the database 65 | Todo.find((err, todo) => { 66 | 67 | if(err) 68 | res.send(err); 69 | 70 | else 71 | res.json(todo); 72 | }); 73 | }); 74 | 75 | router.route('/todo/:todo_id') 76 | 77 | // ### Get a todo item by ID 78 | 79 | // Accessed at GET http://localhost:8080/api/todo/:todo_id 80 | .get((req, res) => { 81 | 82 | // Use mongoose to a single todo item by id in the database 83 | Todo.findOne(req.params.todo_id, (err, todo) => { 84 | 85 | if(err) 86 | res.send(err); 87 | 88 | else 89 | res.json(todo); 90 | }); 91 | }) 92 | 93 | // ### Update a todo item by ID 94 | 95 | // Accessed at PUT http://localhost:8080/api/todo/:todo_id 96 | .put((req, res) => { 97 | 98 | // use our todo model to find the todo item we want 99 | Todo.findOne({ 100 | 101 | '_id' : req.params.todo_id 102 | 103 | }, (err, todo) => { 104 | 105 | if (err) 106 | res.send(err); 107 | 108 | // Only update a field if a new value has been passed in 109 | if (req.body.text) 110 | todo.text = req.body.text; 111 | 112 | // save the todo item 113 | return todo.save((err) => { 114 | 115 | if (err) 116 | res.send(err); 117 | 118 | return res.send(todo); 119 | 120 | }); 121 | }); 122 | }) 123 | 124 | // ### Delete a todo item by ID 125 | 126 | // Accessed at DELETE http://localhost:8080/api/todo/:todo_id 127 | .delete((req, res) => { 128 | 129 | // DEBUG 130 | console.log(`Attempting to delete todo with id: ${req.params.todo_id}`); 131 | 132 | Todo.remove({ 133 | 134 | _id : req.params.todo_id 135 | }, (err, todo) => { 136 | 137 | if(err) 138 | res.send(err); 139 | 140 | console.log('Todo successfully deleted!'); 141 | 142 | Todo.find((err, todos) => { 143 | if(err) 144 | res.send(err); 145 | 146 | res.json(todos); 147 | }); 148 | }); 149 | }); 150 | }; 151 | -------------------------------------------------------------------------------- /server.conf.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // server.conf.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // server.conf.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // *server.conf.js* 9 | 10 | // This is the file where we will: 11 | // - Configure our application 12 | // - Connect to our database 13 | // - Create our Mongoose models 14 | // - Define routes for our RESTful API 15 | // - Define routes for our frontend Angular application 16 | // - Set the app to listen on a port so we can view it in our browser 17 | 18 | // # Node Env Variables 19 | 20 | // Load Node environment variable configuration file 21 | import {validateEnvVariables} from './config/env.conf.js'; 22 | 23 | // Set up appropriate environment variables if necessary 24 | validateEnvVariables(); 25 | 26 | // # Modules 27 | 28 | // Load Express 29 | import express from 'express'; 30 | // Load Socket.io 31 | import socketio from 'socket.io'; 32 | // Load Node http module 33 | import http from 'http'; 34 | // Create our app with Express 35 | let app = express(); 36 | // Create a Node server for our Express app 37 | let server = http.createServer(app); 38 | // Integrate Socket.io 39 | let io = socketio.listen(server); 40 | // Load Mongoose for MongoDB interactions 41 | import mongoose from 'mongoose'; 42 | // Log requests to the console (Express 4) 43 | import morgan from 'morgan'; 44 | // Pull information from HTML POST (express 4) 45 | import bodyParser from 'body-parser'; 46 | // Simulate DELETE and PUT (Express 4) 47 | import methodOverride from 'method-override'; 48 | // PassportJS 49 | import passport from 'passport'; 50 | import cookieParser from 'cookie-parser'; 51 | import session from 'express-session'; 52 | 53 | // # Configuration 54 | 55 | // Load Socket.io server functionality 56 | import base from './sockets/base'; 57 | 58 | base(io); 59 | 60 | // Set the port for this app 61 | let port = process.env.PORT || 8080; 62 | 63 | // Load Mongoose config file for connecting to MongoDB instance 64 | import mongooseConf from './config/mongoose.conf.js'; 65 | 66 | // Pass Mongoose configuration Mongoose instance 67 | mongooseConf(mongoose); 68 | 69 | // Import PassportJS configuration 70 | import passportConf from './config/passport.conf.js'; 71 | 72 | // Pass Passport configuration our PassportJS instance 73 | passportConf(passport); 74 | 75 | if (process.env.NODE_ENV === 'development' || 76 | process.env.NODE_ENV === 'test') 77 | // Log every request to the console 78 | app.use(morgan('dev')); 79 | 80 | // Read cookies (needed for authentication) 81 | app.use(cookieParser()); 82 | 83 | // ## Get all data/stuff of the body (POST) parameters 84 | 85 | // Parse application/json 86 | app.use(bodyParser.json()); 87 | // Parse application/vnd.api+json as json 88 | app.use(bodyParser.json({ type: 'application/vnd.api+json' })); 89 | // Parse application/x-www-form-urlencoded 90 | app.use(bodyParser.urlencoded({ extended: true })); 91 | 92 | // Override with the X-HTTP-Method-Override header in the request. Simulate DELETE/PUT 93 | app.use(methodOverride('X-HTTP-Method-Override')); 94 | // Set the static files location /public/img will be /img for users 95 | app.use(express.static(__dirname + '/dist')); 96 | 97 | // ## Passport JS 98 | 99 | // Session secret 100 | app.use(session({ 101 | 102 | secret : process.env.SESSION_SECRET, 103 | 104 | resave : true, 105 | 106 | saveUninitialized : true 107 | })); 108 | 109 | app.use(passport.initialize()); 110 | 111 | // Persistent login sessions 112 | app.use(passport.session()); 113 | 114 | // ## Routes 115 | 116 | // Get an instance of the express Router 117 | let router = express.Router(); 118 | 119 | // Load our application API routes 120 | // Pass in our express and express router instances 121 | import routes from './app/routes'; 122 | 123 | // Pass in instances of the express app, router, and passport 124 | routes(app, router, passport); 125 | 126 | // ### Ignition Phase 127 | 128 | server.listen(port); 129 | 130 | // Shoutout to the user 131 | console.log(`Wizardry is afoot on port ${port}`); 132 | 133 | // Expose app 134 | export {app}; 135 | -------------------------------------------------------------------------------- /app/routes/_recipe.router.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // _recipe.router.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // _recipe.router.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // */app/routes/_recipe.router.js* 9 | 10 | // # Recipe API object 11 | 12 | // HTTP Verb Route Description 13 | 14 | // GET /api/recipe Get all of the recipes 15 | // GET /api/recipe/:recipe_id Get a single recipe by recipe id 16 | // POST /api/recipe Create a single recipe 17 | // DELETE /api/recipe/:recipe_id Delete a single recipe 18 | // PUT /api/recipe/:recipe_id Update a recipe with new info 19 | 20 | // Load the `recipe` model 21 | import Recipe from '../models/recipe.model'; 22 | 23 | export default (app, router) => { 24 | 25 | // ## Recipe API Routes 26 | 27 | // Define routes for the `recipe` API 28 | 29 | router.route('/recipe') 30 | 31 | // ### Create a `recipe` 32 | 33 | // Accessed at POST http://localhost:8080/api/recipe 34 | 35 | // Create a `recipe` 36 | .post((req, res) => { 37 | 38 | Recipe.create({ 39 | 40 | title : req.body.title, 41 | 42 | tags : req.body.tags, 43 | 44 | rating : req.body.rating, 45 | 46 | description : req.body.description, 47 | 48 | ingredients : req.body.ingredients, 49 | 50 | directions : req.body.directions, 51 | 52 | }, (err, recipe) => { 53 | 54 | if (err) 55 | res.send(err); 56 | 57 | // DEBUG 58 | console.log(`Recipe created: ${recipe}`); 59 | 60 | // return the new `recipe` to our front-end 61 | res.json(recipe); 62 | }); 63 | }) 64 | 65 | // ### Get all of the `recipes` 66 | 67 | // Accessed at GET http://localhost:8080/api/recipe 68 | .get((req, res) => { 69 | 70 | // Use mongoose to get all recipes in the database 71 | Recipe.find((err, recipe) => { 72 | 73 | if(err) 74 | res.send(err); 75 | 76 | else 77 | res.json(recipe); 78 | }); 79 | }); 80 | 81 | router.route('/recipe/:recipe_id') 82 | 83 | // ### Get a `recipe` by ID 84 | 85 | // Accessed at GET http://localhost:8080/api/recipe/:recipe_id 86 | .get((req, res) => { 87 | 88 | // Use mongoose to fetch a single `recipe` by id in the database 89 | Recipe.findOne(req.params.recipe_id, (err, recipe) => { 90 | 91 | if(err) 92 | res.send(err); 93 | 94 | else 95 | res.json(recipe); 96 | }); 97 | }) 98 | 99 | // ### Update a `recipe` by ID 100 | 101 | // Accessed at PUT http://localhost:8080/api/recipe/:recipe_id 102 | .put((req, res) => { 103 | 104 | // use our `recipe` model to find the `recipe` we want 105 | Recipe.findOne({ 106 | 107 | '_id' : req.params.recipe_id 108 | 109 | }, (err, recipe) => { 110 | 111 | if (err) 112 | res.send(err); 113 | 114 | // Only update a field if a new value has been passed in 115 | if (req.body.title) 116 | recipe.title = req.body.title; 117 | 118 | if (req.body.tags) 119 | recipe.tags = req.body.tags; 120 | 121 | if (req.body.rating) 122 | recipe.rating = req.body.rating; 123 | 124 | if (req.body.creator) 125 | recipe.creator = req.body.creator; 126 | 127 | if (req.body.description) 128 | recipe.description = req.body.description; 129 | 130 | if (req.body.ingredients) 131 | recipe.ingredients = req.body.ingredients; 132 | 133 | if (req.body.directions) 134 | recipe.directions = req.body.directions; 135 | 136 | // save the `recipe` 137 | return recipe.save((err) => { 138 | 139 | if (err) 140 | res.send(err); 141 | 142 | return res.send(recipe); 143 | 144 | }); 145 | }); 146 | }) 147 | 148 | // ### Delete a `recipe` by ID 149 | 150 | // Accessed at DELETE http://localhost:8080/api/recipe/:recipe_id 151 | .delete((req, res) => { 152 | 153 | // DEBUG 154 | console.log(`Attempting to delete recipe with id: ${req.params.recipe_id}`); 155 | 156 | Recipe.remove({ 157 | 158 | _id : req.params.recipe_id 159 | }, (err, recipe) => { 160 | 161 | if(err) 162 | res.send(err); 163 | 164 | else 165 | console.log('Recipe successfully deleted!'); 166 | 167 | Recipe.find((err, recipes) => { 168 | if(err) 169 | res.send(err); 170 | 171 | res.json(recipes); 172 | }); 173 | }); 174 | }); 175 | }; 176 | -------------------------------------------------------------------------------- /src/custom-typings.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Custom Type Definitions 3 | * When including 3rd party modules you also need to include the type definition for the module 4 | * if they don't provide one within the module. You can try to install it with typings 5 | typings install node --save 6 | * If you can't find the type definition in the registry we can make an ambient definition in 7 | * this file for now. For example 8 | declare module "my-module" { 9 | export function doesSomething(value: string): string; 10 | } 11 | * 12 | * If you're prototying and you will fix the types later you can also declare it as type any 13 | * 14 | declare var assert: any; 15 | * 16 | * If you're importing a module that uses Node.js modules which are CommonJS you need to import as 17 | * 18 | import * as _ from 'lodash' 19 | * You can include your type definitions in this file until you create one for the typings registry 20 | * see https://github.com/typings/registry 21 | * 22 | */ 23 | 24 | // Extra variables that live on Global that will be replaced by webpack DefinePlugin 25 | declare var ENV: string; 26 | declare var HMR: boolean; 27 | interface GlobalEnvironment { 28 | ENV; 29 | HMR; 30 | } 31 | 32 | interface WebpackModule { 33 | hot: { 34 | data?: any, 35 | idle: any, 36 | accept(dependencies?: string | string[], callback?: (updatedDependencies?: any) => void): void; 37 | decline(dependencies?: string | string[]): void; 38 | dispose(callback?: (data?: any) => void): void; 39 | addDisposeHandler(callback?: (data?: any) => void): void; 40 | removeDisposeHandler(callback?: (data?: any) => void): void; 41 | check(autoApply?: any, callback?: (err?: Error, outdatedModules?: any[]) => void): void; 42 | apply(options?: any, callback?: (err?: Error, outdatedModules?: any[]) => void): void; 43 | status(callback?: (status?: string) => void): void | string; 44 | removeStatusHandler(callback?: (status?: string) => void): void; 45 | }; 46 | } 47 | 48 | interface WebpackRequire { 49 | context(file: string, flag?: boolean, exp?: RegExp): any; 50 | } 51 | 52 | 53 | interface ErrorStackTraceLimit { 54 | stackTraceLimit: number; 55 | } 56 | 57 | // Extend typings 58 | interface NodeRequire extends WebpackRequire {} 59 | interface ErrorConstructor extends ErrorStackTraceLimit {} 60 | interface NodeModule extends WebpackModule {} 61 | interface Global extends GlobalEnvironment {} 62 | 63 | declare namespace Reflect { 64 | function decorate(decorators: ClassDecorator[], target: Function): Function; 65 | function decorate( 66 | decorators: (PropertyDecorator | MethodDecorator)[], 67 | target: Object, 68 | targetKey: string | symbol, 69 | descriptor?: PropertyDescriptor): PropertyDescriptor; 70 | 71 | function metadata(metadataKey: any, metadataValue: any): { 72 | (target: Function): void; 73 | (target: Object, propertyKey: string | symbol): void; 74 | }; 75 | function defineMetadata(metadataKey: any, metadataValue: any, target: Object): void; 76 | function defineMetadata( 77 | metadataKey: any, 78 | metadataValue: any, 79 | target: Object, 80 | targetKey: string | symbol): void; 81 | function hasMetadata(metadataKey: any, target: Object): boolean; 82 | function hasMetadata(metadataKey: any, target: Object, targetKey: string | symbol): boolean; 83 | function hasOwnMetadata(metadataKey: any, target: Object): boolean; 84 | function hasOwnMetadata(metadataKey: any, target: Object, targetKey: string | symbol): boolean; 85 | function getMetadata(metadataKey: any, target: Object): any; 86 | function getMetadata(metadataKey: any, target: Object, targetKey: string | symbol): any; 87 | function getOwnMetadata(metadataKey: any, target: Object): any; 88 | function getOwnMetadata(metadataKey: any, target: Object, targetKey: string | symbol): any; 89 | function getMetadataKeys(target: Object): any[]; 90 | function getMetadataKeys(target: Object, targetKey: string | symbol): any[]; 91 | function getOwnMetadataKeys(target: Object): any[]; 92 | function getOwnMetadataKeys(target: Object, targetKey: string | symbol): any[]; 93 | function deleteMetadata(metadataKey: any, target: Object): boolean; 94 | function deleteMetadata(metadataKey: any, target: Object, targetKey: string | symbol): boolean; 95 | } 96 | 97 | // We need this here since there is a problem with Zone.js typings 98 | interface Thenable { 99 | then( 100 | onFulfilled?: (value: T) => U | Thenable, 101 | onRejected?: (error: any) => U | Thenable): Thenable; 102 | then( 103 | onFulfilled?: (value: T) => U | Thenable, 104 | onRejected?: (error: any) => void): Thenable; 105 | catch(onRejected?: (error: any) => U | Thenable): Thenable; 106 | } 107 | -------------------------------------------------------------------------------- /app/routes/_authentication.router.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // _authentication.router.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // _authentication.router.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // */app/routes/_authentication.router.js* 9 | 10 | // GET */api/auth/user* Get user data from session object in 11 | // Node 12 | 13 | // GET */api/auth/loggedin* Route to test if the user is logged in 14 | // or not 15 | 16 | // POST */api/auth/login* Route to login 17 | 18 | // POST */api/auth/logout* Route to logout and redirect to the 19 | // appropriate view 20 | 21 | // ## Authentication API object 22 | 23 | // Load user model 24 | import User from '../models/user.model.js'; 25 | 26 | // Load the Mongoose ObjectId function to cast string as 27 | // ObjectId 28 | let ObjectId = require('mongoose').Types.ObjectId; 29 | 30 | export default (app, router, passport, auth, admin) => { 31 | 32 | // ### Authentication API Routes 33 | 34 | // Route to test if the user is logged in or not 35 | router.get('/auth/loggedIn', (req, res) => { 36 | 37 | // If the user is authenticated, return a user object 38 | // else return 0 39 | res.send(req.isAuthenticated() ? req.user : '0'); 40 | }); 41 | 42 | // Route to log a user in 43 | router.post('/auth/login', (req, res, next) => { 44 | 45 | // Call `authenticate()` from within the route handler, rather than 46 | // as a route middleware. This gives the callback access to the `req` 47 | // and `res` object through closure. 48 | 49 | // If authentication fails, `user` will be set to `false`. If an 50 | // exception occured, `err` will be set. `info` contains a message 51 | // set within the Local Passport strategy. 52 | passport.authenticate('local-login', (err, user, info) => { 53 | 54 | if (err) 55 | return next(err); 56 | 57 | // If no user is returned... 58 | if (!user) { 59 | 60 | // Set HTTP status code `401 Unauthorized` 61 | res.status(401); 62 | 63 | // Return the info message 64 | return next(info.loginMessage); 65 | } 66 | 67 | // Use login function exposed by Passport to establish a login 68 | // session 69 | req.login(user, (err) => { 70 | 71 | if (err) 72 | return next(err); 73 | 74 | // Set HTTP status code `200 OK` 75 | res.status(200); 76 | 77 | // Return the user object 78 | res.send(req.user); 79 | }); 80 | 81 | }) (req, res, next); 82 | }); 83 | 84 | router.post('/auth/signup', (req, res, next) => { 85 | 86 | // Call `authenticate()` from within the route handler, rather than 87 | // as a route middleware. This gives the callback access to the `req` 88 | // and `res` object through closure. 89 | 90 | // If authentication fails, `user` will be set to `false`. If an 91 | // exception occured, `err` will be set. `info` contains a message 92 | // set within the Local Passport strategy. 93 | passport.authenticate('local-signup', (err, user, info) => { 94 | 95 | if (err) 96 | return next(err); 97 | 98 | // If no user is returned... 99 | if (!user) { 100 | 101 | // Set HTTP status code `401 Unauthorized` 102 | res.status(401); 103 | 104 | // Return the info message 105 | return next(info.signupMessage); 106 | } 107 | 108 | // Set HTTP status code `204 No Content` 109 | res.sendStatus(204); 110 | 111 | }) (req, res, next); 112 | }); 113 | 114 | // Route to log a user out 115 | router.post('/auth/logout', (req, res) => { 116 | 117 | req.logOut(); 118 | 119 | // Even though the logout was successful, send the status code 120 | // `401` to be intercepted and reroute the user to the appropriate 121 | // page 122 | res.sendStatus(401); 123 | }); 124 | 125 | // Route to get the current user 126 | // The `auth` middleware was passed in to this function from `routes.js` 127 | router.get('/auth/user', auth, (req, res) => { 128 | 129 | // Send response in JSON to allow disassembly of object by functions 130 | res.json(req.user); 131 | }); 132 | 133 | // Route to delete a user. Accepts a url parameter in the form of a 134 | // username, user email, or mongoose object id. 135 | // The `admin` Express middleware was passed in from `routes.js` 136 | router.delete('/auth/delete/:uid', admin, (req, res) => { 137 | 138 | User.remove({ 139 | 140 | // Model.find `$or` Mongoose condition 141 | $or : [ 142 | 143 | { 'local.username' : req.params.uid }, 144 | 145 | { 'local.email' : req.params.uid }, 146 | 147 | { '_id' : ObjectId(req.params.uid) } 148 | ] 149 | }, (err) => { 150 | 151 | // If there are any errors, return them 152 | if (err) 153 | return next(err); 154 | 155 | // HTTP Status code `204 No Content` 156 | res.sendStatus(204); 157 | }); 158 | }); 159 | }; 160 | -------------------------------------------------------------------------------- /src/app/recipes/recipe-details.component.ts: -------------------------------------------------------------------------------- 1 | // ``` 2 | // recipes.component.js 3 | // (c) 2016 David Newman 4 | // blackshuriken@hotmail.com 5 | // recipes.component.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // # Recipes Component 9 | 10 | import {Component, 11 | Input, 12 | Output, 13 | EventEmitter, 14 | ChangeDetectionStrategy 15 | } from '@angular/core'; 16 | 17 | import {Observable} from 'rxjs/Observable'; 18 | import {Store} from '@ngrx/store'; 19 | 20 | import {RecipeService} from './recipe.service'; 21 | import {Recipe} from './recipe.store'; 22 | import {AppStore} from '../app.store'; 23 | 24 | import {Rating} from './rating.component'; 25 | 26 | @Component({ 27 | selector: 'recipe-detail', 28 | template: require('./recipe-details.html'), 29 | directives: [Rating] 30 | }) 31 | export class RecipeDetails { 32 | 33 | originalTitle: string; 34 | selectedRecipe: Recipe; 35 | 36 | // Assign our `recipe` to a locally scoped property 37 | // Perform additional logic on every update via ES6 setter 38 | // Create a copy of `_recipe` and assign it to `this.selectedRecipe` 39 | // which we will use to bind our form to 40 | @Input('recipe') set _recipe(value: Recipe) { 41 | 42 | if (value) this.originalTitle = value.title; 43 | this.selectedRecipe = Object.assign({}, value); 44 | 45 | // DEBUG 46 | console.log('this.selectedRecipe: '); 47 | console.log(this.selectedRecipe); 48 | } 49 | 50 | // Allow the user to save/delete a `recipe or cancel the 51 | // operation. Flow events up from here. 52 | @Output() saved = new EventEmitter(); 53 | @Output() cancelled = new EventEmitter(); 54 | 55 | constructor() { 56 | 57 | } 58 | 59 | // Whenever the user needs to add a new `tag`, push an 60 | // empty `tag` object to the `tags` array on the 61 | // `selectedRecipe` 62 | newTag() { 63 | 64 | // blank `tag` object 65 | let tag = { 66 | name: '' 67 | }; 68 | 69 | // Check to see if the `tags` array exists before 70 | // attempting to push a `tag` to it 71 | if (!this.selectedRecipe.tags) 72 | this.selectedRecipe.tags = []; 73 | 74 | this.selectedRecipe.tags.push(tag); 75 | } 76 | 77 | // Whenever the user needs to add a new `ingredient`, push an 78 | // empty `ingredient` object to the `ingredient` array on the 79 | // `selectedRecipe` 80 | newIngredient() { 81 | 82 | // blank `ingredient` object 83 | let ingredient = { 84 | amount: '', 85 | unit: '', 86 | name: '' 87 | }; 88 | 89 | // Check to see if the `ingredients` array exists before 90 | // attempting to push an `ingredient` to it 91 | if (!this.selectedRecipe.ingredients) 92 | this.selectedRecipe.ingredients = []; 93 | 94 | this.selectedRecipe.ingredients.push(ingredient); 95 | } 96 | 97 | // Whenever the user needs to add a new `direction`, push an 98 | // empty `direction` object to the `direction` array on the 99 | // `selectedRecipe` 100 | newDirection() { 101 | 102 | // blank `direction` object 103 | let direction = { 104 | step: '' 105 | }; 106 | 107 | // Check to see if the `directions` array exists before 108 | // attempting to push a `direction` to it 109 | if (!this.selectedRecipe.directions) 110 | this.selectedRecipe.directions = []; 111 | 112 | this.selectedRecipe.directions.push(direction); 113 | } 114 | 115 | onUpdate(value) { 116 | 117 | // Set the value of the selected recipe's rating to the 118 | // value passed up from the `rating` component 119 | this.selectedRecipe.rating = value; 120 | } 121 | 122 | deleteTag(tag) { 123 | // loop through all of the `tags` in the `selectedRecipe` 124 | for (let i = 0; i < this.selectedRecipe.tags.length; i++) { 125 | // if the `tag` at the current index matches that of the one 126 | // the user is trying to delete 127 | if (this.selectedRecipe.tags[i] === tag) { 128 | // delete the `tag` at the current index 129 | this.selectedRecipe.tags.splice(i, 1); 130 | } 131 | } 132 | } 133 | 134 | deleteIngredient(ingredient) { 135 | // loop through all of the `ingredients` in the `selectedRecipe` 136 | for (let i = 0; i < this.selectedRecipe.ingredients.length; i++) { 137 | // if the `ingredient` at the current index matches that of the one 138 | // the user is trying to delete 139 | if (this.selectedRecipe.ingredients[i] === ingredient) { 140 | // delete the `ingredient` at the current index 141 | this.selectedRecipe.ingredients.splice(i, 1); 142 | } 143 | } 144 | } 145 | 146 | deleteDirection(step) { 147 | // loop through all of the `directions` in the `selectedRecipe` 148 | for (let i = 0; i < this.selectedRecipe.directions.length; i++) { 149 | // if the `direction` at the current index matches that of the one 150 | // the user is trying to delete 151 | if (this.selectedRecipe.directions[i] === step) { 152 | // delete the `direction` at the current index 153 | this.selectedRecipe.directions.splice(i, 1); 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /test/recipe.model.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // import the `mongoose` helper utilities 4 | let utils = require('./utils'); 5 | import chai from 'chai'; 6 | let should = chai.should(); 7 | 8 | // import our `Recipe` mongoose model 9 | import Recipe from '../app/models/recipe.model'; 10 | 11 | describe('Recipe: models', () => { 12 | 13 | describe('create()', () => { 14 | 15 | it('should create a new Recipe', (done) => { 16 | 17 | // Create a `Recipe` object to pass to `Recipe.create()`` 18 | let r = { 19 | 20 | title: 'Peanut Butter Cookies', 21 | 22 | tags: [{ 23 | name: 'tag1' 24 | },{ 25 | name: 'tag2' 26 | }], 27 | 28 | rating: 5, 29 | 30 | creator: 'datatype_void', 31 | 32 | description: 'Peanut butter... cookies... what else is there to say?', 33 | 34 | ingredients: [{ 35 | amount: '1/2', 36 | 37 | unit: 'cup', 38 | 39 | name: 'coconut flour', 40 | }, { 41 | amount: '1', 42 | 43 | unit: 'cup', 44 | 45 | name: 'crunchy peanut butter', 46 | }, { 47 | amount: '1/2', 48 | 49 | unit: 'cup', 50 | 51 | name: 'coconut sugar', 52 | }, { 53 | amount: '1/2', 54 | 55 | unit: 'teaspoon', 56 | 57 | name: 'magic', 58 | }], 59 | 60 | directions: [{ 61 | step: 'Mix everything' 62 | }, { 63 | step: 'Bake at 350F until satisfied' 64 | }, { 65 | step: 'Enjoy delectable cookies' 66 | }] 67 | }; 68 | 69 | Recipe.create(r, (err, createdRecipe) => { 70 | 71 | // Confirm that that an error does not exist 72 | should.not.exist(err); 73 | 74 | // verify that the returned `recipe` is what we expect 75 | createdRecipe.title.should.equal(r.title); 76 | 77 | // The `Recipe` object should have a tags property 78 | should.exist(createdRecipe.tags); 79 | 80 | // Which should be an array with a length equal to 81 | // that of the test object we created 82 | createdRecipe.tags.should.have.length(r.tags.length); 83 | 84 | // For each `tag` object in the `tags` array, 85 | // check if it has a property `name` and then 86 | // see if the `name` value is equal to the 87 | // one we passed in with the test object 88 | for (let i in createdRecipe.tags) { 89 | 90 | should.exist(createdRecipe.tags[i].name); 91 | 92 | createdRecipe.tags[i].should.equal(r.tags[i]); 93 | } 94 | 95 | // It should also have `rating`, `creator`, and `description` 96 | // properties equal to those that we passed in 97 | createdRecipe.rating.should.equal(r.rating); 98 | createdRecipe.creator.should.equal(r.creator); 99 | createdRecipe.description.should.equal(r.description); 100 | 101 | // The `Recipe` object should have an `ingredients` property 102 | should.exist(createdRecipe.ingredients); 103 | // `ingredients` should be an array with a length equal to 104 | // that of the test object's `ingredients` property 105 | createdRecipe.ingredients.should.have.length(r.ingredients.length); 106 | 107 | // For each `ingredient` object in the `ingredients` array, 108 | // check if it has a property `amount` and then 109 | // see if the `amount` value is equal to the 110 | // one we passed in with the test object 111 | // Repeat for the `unit` and `name` properties 112 | // for each `ingredient` 113 | for (let i in createdRecipe.ingredients) { 114 | 115 | should.exist(createdRecipe.ingredients[i].amount); 116 | should.exist(createdRecipe.ingredients[i].unit); 117 | should.exist(createdRecipe.ingredients[i].name); 118 | 119 | createdRecipe.ingredients[i].amount 120 | .should.equal(r.ingredients[i].amount); 121 | 122 | createdRecipe.ingredients[i].unit 123 | .should.equal(r.ingredients[i].unit); 124 | 125 | createdRecipe.ingredients[i].name 126 | .should.equal(r.ingredients[i].name); 127 | } 128 | 129 | // The `Recipe` object should have a `directions` property 130 | should.exist(createdRecipe.directions); 131 | 132 | // Which should be an array with a length equal to 133 | // that of the test object we created 134 | createdRecipe.directions.should.have.length(r.directions.length); 135 | 136 | // For each `direction` object in the `directions` array, 137 | // check if it has a property `step` and then 138 | // see if the `step` value is equal to the 139 | // one we passed in with the test object 140 | for (let i in createdRecipe.directions) { 141 | 142 | should.exist(createdRecipe.directions[i].step); 143 | 144 | createdRecipe.directions[i].should.equal(r.directions[i]); 145 | } 146 | 147 | // Call done to tell mocha that we are done with this test 148 | done(); 149 | }); 150 | }); 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // @datatype_void 3 | // david.r.niciforovic@gmail.com 4 | // webpack.dev.js may be freely distributed under the MIT license 5 | // ``` 6 | 7 | //# Common Configuration and Helpers 8 | var helpers = require('./helpers'); 9 | // Use `webpack-merge` to merge configs 10 | var webpackMerge = require('webpack-merge'); 11 | // Common `webpack` configuration for `dev` and `prod` 12 | var commonConfig = require('./webpack.common.js'); 13 | 14 | // Webpack Plugins 15 | var DefinePlugin = require('webpack/lib/DefinePlugin'); 16 | var HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin'); 17 | var NoErrorsPlugin = require('webpack/lib/NoErrorsPlugin'); 18 | 19 | //# Webpack Constants 20 | const ENV = process.env.ENV = process.env.NODE_ENV = 'development'; 21 | const HMR = helpers.hasProcessFlag('hot'); 22 | const METADATA = { 23 | host: process.env.HOST || '0.0.0.0', 24 | port: process.env.PORT || 8080, 25 | ENV: ENV, 26 | HMR: HMR 27 | }; 28 | 29 | //# Environment Config Object 30 | var envConfig = require('./config.json'); 31 | 32 | module.exports = webpackMerge(commonConfig, { 33 | // Merged metadata from webpack.common.js for index.html 34 | // 35 | // See: (custom attribute) 36 | metadata: METADATA, 37 | 38 | // Developer tool to enhance debugging 39 | // 40 | // See: http://webpack.github.io/docs/configuration.html#devtool 41 | // See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps 42 | devtool: 'cheap-module-source-map', 43 | // Switch loaders to debug mode. 44 | // 45 | // See: http://webpack.github.io/docs/configuration.html#debug 46 | debug: true, 47 | 48 | // Options affecting the output of the compilation. 49 | // 50 | // See: http://webpack.github.io/docs/configuration.html#output 51 | output: { 52 | // The output directory as absolute path (required). 53 | // 54 | // See: http://webpack.github.io/docs/configuration.html#output-path 55 | path: helpers.root('dist'), 56 | // Specifies the name of each output file on disk. 57 | // IMPORTANT: You must not specify an absolute path here! 58 | // 59 | // See: http://webpack.github.io/docs/configuration.html#output-filename 60 | filename: '[name].bundle.js', 61 | // The filename of the SourceMaps for the JavaScript files. 62 | // They are inside the output.path directory. 63 | // 64 | // See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename 65 | sourceMapFilename: '[name].map', 66 | // The filename of non-entry chunks as relative path 67 | // inside the output.path directory. 68 | // 69 | // See: http://webpack.github.io/docs/configuration.html#output-chunkfilename 70 | chunkFilename: '[id].chunk.js' 71 | }, 72 | 73 | // Add additional plugins to the compiler. 74 | // 75 | // See: http://webpack.github.io/docs/configuration.html#plugins 76 | plugins: [ 77 | 78 | // TODO(datatypevoid): investigate the necessity of these two 79 | // following lines 80 | new HotModuleReplacementPlugin(), 81 | new NoErrorsPlugin(), 82 | // Plugin: DefinePlugin 83 | // Description: Define free variables. 84 | // Useful for having development builds with debug logging or adding global constants. 85 | // 86 | // Environment helpers 87 | // 88 | // See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin 89 | // NOTE: when adding more properties make sure you include them in custom-typings.d.ts 90 | new DefinePlugin({ 91 | 'ENV': JSON.stringify(METADATA.ENV), 92 | 'HMR': HMR, 93 | 'process.env': { 94 | 'ENV': JSON.stringify(METADATA.ENV), 95 | 'NODE_ENV': JSON.stringify(METADATA.ENV), 96 | 'HMR': METADATA.HMR 97 | } 98 | }) 99 | ], 100 | 101 | // Other module loader config 102 | 103 | // Static analysis linter for TypeScript advanced options configuration 104 | // Description: An extensible linter for the TypeScript language. 105 | // 106 | // See: https://github.com/wbuchwalter/tslint-loader 107 | tslint: { 108 | emitErrors: false, 109 | failOnHint: false, 110 | resourcePath: 'src', 111 | }, 112 | 113 | // Webpack Development Server configuration 114 | // Description: The webpack-dev-server is a little node.js Express server. 115 | // The server emits information about the compilation state to the client, 116 | // which reacts to those events. 117 | // 118 | // See: https://webpack.github.io/docs/webpack-dev-server.html 119 | devServer: { 120 | // Proxy requests to our `Express` server 121 | proxy: { 122 | '*': { 123 | target: 'http://localhost:' + envConfig.PORT, 124 | secure: false 125 | }, 126 | }, 127 | port: METADATA.port, 128 | host: METADATA.host, 129 | historyApiFallback: true, 130 | watchOptions: { 131 | aggregateTimeout: 300, 132 | poll: 1000 133 | }, 134 | outputPath: helpers.root('dist') 135 | }, 136 | 137 | node: { 138 | global: 'window', 139 | crypto: 'empty', 140 | process: true, 141 | module: false, 142 | clearImmediate: false, 143 | setImmediate: false 144 | } 145 | }); 146 | -------------------------------------------------------------------------------- /config/gulpfile.conf.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // gulpfile.conf.js 3 | // (c) 2016 David Newman 4 | // david.r.niciforovic@gmail.com 5 | // gulpfile.conf.js may be freely distributed under the MIT license 6 | // ``` 7 | 8 | // *gulpfile.js* 9 | 10 | // Import gulp packages 11 | import gulp from 'gulp'; 12 | import gutil from 'gulp-util'; 13 | import rename from 'gulp-rename'; 14 | import nodemon from 'gulp-nodemon'; 15 | import docco from 'gulp-docco'; 16 | import scsslint from 'gulp-scss-lint'; 17 | import path from 'path'; 18 | import del from 'del'; 19 | import globby from 'globby'; 20 | 21 | // Define `JavaScript` files to watch/ignore 22 | let jsGlob = ['**/*.js', '!{node_modules,node_modules/**}', '!{docs,doc/**}', 23 | '!{dist,dist/**}', '!{coverage,coverage/**}', '!src/{res,res/**}', 24 | '!config/env.conf.js']; 25 | 26 | // Define `TypeScript` files to watch/ignore 27 | let tsGlob = ['**/*.ts', '!{node_modules,node_modules/**}', '!{docs,doc/**}', 28 | '!{dist,dist/**}', '!{coverage,coverage/**}', '!src/{res,res/**}']; 29 | 30 | // Define `Sass` files to watch/ignore 31 | let scssGlob = ['**/*.scss', '!{node_modules,node_modules/**}', 32 | '!{dist,dist/**}', '!{docs,doc/**}', '!{coverage,coverage/**}', '!src/{res,res/**}']; 33 | 34 | // Create the default task and have it clear out all existing 35 | // documentation; watch all neccessary files for automatic 36 | // documentation generation as well as linting all `sass` styles. 37 | gulp.task('default', ['clean:docs', 38 | 'watch:docs', 39 | 'watch:sass']); 40 | 41 | // Watch `Sass` files for changes and lint 42 | gulp.task('watch:sass', () => { 43 | 44 | gulp.watch(scssGlob, function (event) { 45 | return gulp.src(event.path) 46 | .pipe(scsslint()); 47 | }); 48 | }); 49 | 50 | gulp.task('build:docs', () => { 51 | 52 | // Take a file `glob` pattern and a file extension matching 53 | // the extension of the files you are trying to generate 54 | // documentation for 55 | function generateDocs(fileSrc, ext) { 56 | 57 | console.log(ext); 58 | 59 | if(ext == '') { 60 | 61 | throw new Error('Extension must be passed in for documentation to be generated properly!') 62 | } 63 | return gulp.src(fileSrc) 64 | .pipe(docco()) 65 | .pipe(gulp.dest(`docs/${ext}`)); 66 | } 67 | 68 | generateDocs(jsGlob, '.js'); 69 | 70 | generateDocs(tsGlob, '.ts'); 71 | 72 | generateDocs(scssGlob, '.scss'); 73 | 74 | }); 75 | 76 | // Create documentation for Javascript, Typescript, and Sass files 77 | // on the fly 78 | gulp.task('watch:docs', () => { 79 | 80 | // For `gulp-docco` if the need arises 81 | // Default configuration options. All of these may be extended by user-specified options. 82 | // 83 | // defaults = 84 | // layout: 'parallel' 85 | // output: 'docs' 86 | // template: null 87 | // css: null 88 | // extension: null 89 | // languages: {} 90 | // marked: null 91 | // 92 | // Example: 93 | // 94 | // let docco = require("gulp-docco"); 95 | // 96 | // gulp.src("./src/*.js") 97 | // .pipe(docco(options)) 98 | // .pipe(gulp.dest('./documentation-output')) 99 | // 100 | // Reference: https://www.npmjs.com/package/gulp-docco 101 | // Also see: https://jashkenas.github.io/docco/ 102 | // 103 | let options = { 104 | layout: 'parallel', 105 | output: 'docs', 106 | template: null, 107 | css: null, 108 | extension: null, 109 | languages: {}, 110 | marked: null 111 | } 112 | 113 | // Alert the user whenever changes have been detected and documentation 114 | // generation is occurring 115 | function generateUserAlert(ext) { 116 | 117 | switch(ext) { 118 | 119 | case '.js': 120 | console.log('A JavaScript file has changed; documentation will now be generated...'); 121 | 122 | break; 123 | 124 | case '.scss': 125 | console.log('A Sass file has changed; documentation will now be generated...'); 126 | 127 | break; 128 | 129 | case '.ts': 130 | console.log('A TypeScript file has changed; documentation will now be generated...'); 131 | 132 | break; 133 | 134 | default: 135 | console.log('Generating appropriate folders and styles...'); 136 | 137 | break; 138 | } 139 | 140 | return; 141 | } 142 | 143 | // Watch files specified and generate the documentation 144 | // whenever changes are detected. 145 | function generateDocs(fileSrc) { 146 | gulp.watch(fileSrc, function (event, ext = path.extname(event.path)) { 147 | 148 | generateUserAlert(ext); 149 | 150 | // Ignore docs, bower_components and node_modules 151 | return gulp.src(fileSrc) 152 | .pipe(docco()) 153 | .pipe(gulp.dest(`docs/${ext}`)) 154 | .on('error', gutil.log); 155 | }); 156 | } 157 | 158 | // Generate documentation for files specified in `glob` vars at top 159 | // of file 160 | generateDocs(jsGlob); 161 | 162 | generateDocs(tsGlob); 163 | 164 | generateDocs(scssGlob); 165 | }); 166 | 167 | // Sugar for `gulp serve:watch` 168 | gulp.task('serve', ['serve:watch']); 169 | 170 | // Configure gulp-nodemon 171 | // This watches the files belonging to the app for changes 172 | // and restarts the server whenever a change is detected 173 | gulp.task('serve:watch', () => { 174 | 175 | nodemon({ 176 | script : 'server.js', 177 | ext : 'js' 178 | }); 179 | }); 180 | 181 | // Use the 'del' module to clear all traces of documentation 182 | // Useful before regenerating documentation 183 | // Not currently working due to a globbing issue 184 | // See: https://github.com/sindresorhus/del/issues/50 185 | gulp.task('clean:docs', (callback) => { 186 | del(['./docs/**/*']).then(function (paths) { 187 | callback(); // ok 188 | }, function (reason) { 189 | callback('Failed to delete files: ' + reason); // fail 190 | }); 191 | }); 192 | -------------------------------------------------------------------------------- /config/webpack.test.config.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // @datatype_void 3 | // david.r.niciforovic@gmail.com 4 | // webpack.config.js may be freely distributed under the MIT license 5 | // ``` 6 | 7 | var helpers = require('./helpers'); 8 | //# Webpack Plugins 9 | var ProvidePlugin = require('webpack/lib/ProvidePlugin'); 10 | var DefinePlugin = require('webpack/lib/DefinePlugin'); 11 | //# Webpack Constants 12 | const ENV = process.env.ENV = process.env.NODE_ENV = 'test'; 13 | 14 | //# Webpack Configuration 15 | // See: http://webpack.github.io/docs/configuration.html#cli 16 | module.exports = { 17 | // Developer tool to enhance debugging 18 | // Source map for Karma from the help of `karma-sourcemap-loader` & 19 | // `karma-webpack` 20 | // 21 | // Do not change, leave as is or it wont work. 22 | // See: https://github.com/webpack/karma-webpack#source-maps 23 | devtool: 'inline-source-map', 24 | // Options affecting the resolving of modules. 25 | // 26 | // See: http://webpack.github.io/docs/configuration.html#resolve 27 | resolve: { 28 | // An array of extensions that should be used to resolve modules. 29 | // 30 | // See: http://webpack.github.io/docs/configuration.html#resolve-extensions 31 | 32 | extensions: ['', '.ts', '.js', '.scss'] 33 | }, 34 | // Options affecting the normal modules. 35 | // 36 | // See: http://webpack.github.io/docs/configuration.html#module 37 | module: { 38 | // An array of applied pre and post loaders. 39 | // 40 | // See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders 41 | preLoaders: [ 42 | // Tslint loader support for *.ts files 43 | // 44 | // See: https://github.com/wbuchwalter/tslint-loader 45 | { 46 | test: /\.ts$/, 47 | loader: 'tslint-loader', 48 | exclude: [ 49 | helpers.root('node_modules') 50 | ] 51 | }, 52 | // Source map loader support for *.js files 53 | // Extracts SourceMaps for source files that as added as sourceMappingURL comment. 54 | // 55 | // See: https://github.com/webpack/source-map-loader 56 | { 57 | test: /\.js$/, loader: 'source-map-loader', exclude: [ 58 | // These packages have problems with their `sourcemaps` 59 | helpers.root('node_modules/rxjs'), 60 | helpers.root('node_modules/@angular2-material'), 61 | helpers.root('node_modules/@angular') 62 | ]} 63 | ], 64 | // An array of automatically applied loaders. 65 | // 66 | // IMPORTANT: The loaders here are resolved relative to the resource which they are applied to. 67 | // This means they are not resolved relative to the configuration file. 68 | // 69 | // See: http://webpack.github.io/docs/configuration.html#module-loaders 70 | loaders: [ 71 | // Typescript loader support for .ts and Angular 2 async routes via .async.ts 72 | // 73 | // See: https://github.com/s-panferov/awesome-typescript-loader 74 | { 75 | test: /\.ts$/, 76 | loader: 'awesome-typescript-loader', 77 | query: { 78 | compilerOptions: { 79 | removeComments: true, 80 | } 81 | }, 82 | exclude: [ /\.e2e\.ts$/ ] 83 | }, 84 | // Json loader support for *.json files. 85 | // 86 | // See: https://github.com/webpack/json-loader 87 | { 88 | test: /\.json$/, 89 | loader: 'json-loader', 90 | exclude: [ helpers.root('src/index.html') ] }, 91 | // Raw loader support for *.html 92 | // Returns file content as string 93 | // 94 | // See: https://github.com/webpack/raw-loader 95 | { 96 | test: /\.html$/, 97 | loader: 'raw-loader', 98 | exclude: [ helpers.root('src/index.html') ] }, 99 | // Raw loader support for *.css files 100 | // Returns file content as string 101 | // 102 | // See: https://github.com/webpack/raw-loader 103 | { 104 | test: /\.css$/, 105 | loader: 'raw-loader', 106 | exclude: [ helpers.root('src/index.html') ] }, 107 | // Support for sass imports 108 | // Add CSS rules to your document: 109 | // `require("!style!css!sass!./file.scss");` 110 | { 111 | test: /\.scss$/, 112 | loader: 'style!css!autoprefixer-loader?browsers=last 2 versions!sass', 113 | exclude: [ helpers.root('src/index.html') ] }, 114 | ], 115 | // An array of applied pre and post loaders. 116 | // 117 | // See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders 118 | postLoaders: [ 119 | // Instruments JS files with Istanbul for subsequent code coverage reporting. 120 | // Instrument only testing sources. 121 | // 122 | // See: https://github.com/deepsweet/istanbul-instrumenter-loader 123 | { 124 | test: /\.(js|ts)$/, 125 | include: helpers.root('src'), 126 | loader: 'istanbul-instrumenter-loader', 127 | exclude: [ 128 | /\.(e2e|spec)\.ts$/, 129 | /node_modules/ 130 | ] 131 | } 132 | ] 133 | }, 134 | // Add additional plugins to the compiler. 135 | // 136 | // See: http://webpack.github.io/docs/configuration.html#plugins 137 | plugins: [ 138 | // Plugin: DefinePlugin 139 | // Description: Define free variables. 140 | // Useful for having development builds with debug logging or adding global constants. 141 | // 142 | // Environment helpers 143 | // 144 | // See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin 145 | // NOTE: when adding more properties make sure you include them in custom-typings.d.t new webpack.DefinePlugin({ 146 | new DefinePlugin({ 147 | // Environment helpers 148 | 'ENV': JSON.stringify(ENV), 149 | 'HMR': false, 150 | 'process.env': { 151 | 'ENV': JSON.stringify(ENV), 152 | 'NODE_ENV': JSON.stringify(ENV), 153 | 'HMR': false 154 | } 155 | }) 156 | ], 157 | 158 | // Other module loader config 159 | 160 | // Include polyfills or mocks for various node stuff 161 | // Description: Node configuration 162 | // 163 | // See: https://webpack.github.io/docs/configuration.html#node 164 | node: { 165 | global: 'window', 166 | process: false, 167 | crypto: 'empty', 168 | module: false, 169 | clearImmediate: false, 170 | setImmediate: false 171 | }, 172 | // Static analysis linter for TypeScript advanced options configuration 173 | // Description: An extensible linter for the TypeScript language. 174 | // 175 | // See: https://github.com/wbuchwalter/tslint-loader 176 | tslint: { 177 | emitErrors: false, 178 | failOnHint: false, 179 | resourcePath: 'src', 180 | } 181 | }; 182 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vulgar", 3 | "version": "2.0.4", 4 | "description": "A simple and scalable MEAN stack development kit featuring Angular 2 (Router, Http, Forms, Services, Tests, E2E, Coverage, Dev/Prod), Express, MongoDB, Mongoose, Node, Redux, @ngrx/store, PassportJS, Socket.io, Karma, Protractor, Jasmine, Istanbul, SASS Support, TypeScript, TSLint, NG2Lint, Hot Module Replacement, Docco, Gulp, and Webpack by @datatype_void", 5 | "keywords": [ 6 | "angular2", 7 | "webpack", 8 | "typescript", 9 | "node", 10 | "express", 11 | "mongodb" 12 | ], 13 | "author": "David Newman ", 14 | "homepage": "https://github.com/datatypevoid/vulgar", 15 | "license": "MIT", 16 | "scripts": { 17 | "rimraf": "rimraf", 18 | "tslint": "tslint", 19 | "typedoc": "typedoc", 20 | "typings": "typings", 21 | "webpack": "webpack", 22 | "webpack-dev-server": "webpack-dev-server", 23 | "webdriver-manager": "webdriver-manager", 24 | "protractor": "protractor", 25 | 26 | "clean": "npm cache clean && npm run rimraf -- node_modules doc typings coverage dist", 27 | "clean:dist": "npm run rimraf -- dist", 28 | "preclean:install": "npm run clean", 29 | "clean:install": "npm set progress=false && npm install", 30 | "preclean:start": "npm run clean", 31 | "clean:start": "npm start", 32 | 33 | "watch": "npm run watch:dev", 34 | "watch:dev": "npm run build:dev -- --watch", 35 | "watch:dev:hmr": "npm run watch:dev -- --hot", 36 | "watch:test": "npm run test -- --auto-watch --no-single-run", 37 | "watch:prod": "npm run build:prod -- --watch", 38 | 39 | "build": "npm run build:dev", 40 | "prebuild:dev": "npm run clean:dist", 41 | "build:dev": "webpack --config config/webpack.dev.js --progress --profile --colors --display-error-details --display-cached", 42 | "prebuild:prod": "npm run clean:dist", 43 | "build:prod": "webpack --config config/webpack.prod.config.js --progress --profile --colors --display-error-details --display-cached --bail", 44 | 45 | "server": "npm run server:dev", 46 | "server:dev": "webpack-dev-server --inline --progress --profile --colors --watch --display-error-details --display-cached --content-base src/", 47 | "server:dev:hmr": "npm run server:dev -- --hot", 48 | "server:prod": "./node_modules/.bin/http-server dist --cors", 49 | 50 | "webdriver:update": "npm run webdriver-manager update", 51 | "webdriver:start": "npm run webdriver-manager start", 52 | 53 | "fix:line-endings": "find . \\( -type f -and -not -path '*/node_modules/*' \\) -exec dos2unix {} \\;", 54 | 55 | "lint": "npm run tslint src/**/*.ts", 56 | 57 | "pree2e": "npm run webdriver:update -- --standalone", 58 | "e2e": "npm run protractor", 59 | "e2e:live": "npm run e2e -- --elementExplorer", 60 | 61 | "pretest": "npm run lint", 62 | "test": "node --max-old-space-size=4096 node_modules/karma/bin/karma start", 63 | 64 | "ci": "npm run test && npm run e2e", 65 | 66 | "docs": "npm run typedoc --options typedoc.json --exclude '**/*.spec.ts' ./src/", 67 | 68 | "start": "concurrently --kill-others --raw \"npm run server:dev\" \"npm run watch\"", 69 | 70 | "postinstall": "npm run typings -- install", 71 | 72 | "preversion": "npm test", 73 | "version": "npm run build", 74 | "postversion": "git push && git push --tags" 75 | }, 76 | "dependencies": { 77 | "@angular2-material/button": "2.0.0-alpha.5", 78 | "@angular2-material/card": "2.0.0-alpha.5", 79 | "@angular2-material/checkbox": "2.0.0-alpha.5", 80 | "@angular2-material/grid-list": "2.0.0-alpha.5", 81 | "@angular2-material/icon": "2.0.0-alpha.5", 82 | "@angular2-material/core": "2.0.0-alpha.5", 83 | "@angular2-material/input": "2.0.0-alpha.5", 84 | "@angular2-material/list": "2.0.0-alpha.5", 85 | "@angular2-material/progress-bar": "2.0.0-alpha.5", 86 | "@angular2-material/progress-circle": "2.0.0-alpha.5", 87 | "@angular2-material/radio": "2.0.0-alpha.5", 88 | "@angular2-material/sidenav": "2.0.0-alpha.5", 89 | "@angular2-material/slide-toggle": "2.0.0-alpha.5", 90 | "@angular2-material/tabs": "2.0.0-alpha.5", 91 | "@angular2-material/toolbar": "2.0.0-alpha.5", 92 | "@ngrx/store": "1.5.0", 93 | "@angular/http": "2.0.0-rc.1", 94 | "@angular/common": "2.0.0-rc.1", 95 | "@angular/compiler": "2.0.0-rc.1", 96 | "@angular/core": "2.0.0-rc.1", 97 | "@angular/platform-browser": "2.0.0-rc.1", 98 | "@angular/platform-browser-dynamic": "2.0.0-rc.1", 99 | "@angular/platform-server": "2.0.0-rc.1", 100 | "@angular/router": "2.0.0-rc.1", 101 | "@angular/router-deprecated": "2.0.0-rc.1", 102 | "bcrypt-nodejs": "0.0.3", 103 | "body-parser": "^1.15.1", 104 | "cookie-parser": "^1.4.3", 105 | "core-js": "^2.4.0", 106 | "express": "^4.13.4", 107 | "express-session": "^1.13.0", 108 | "http": "0.0.0", 109 | "method-override": "^2.3.6", 110 | "mongoose": "^4.4.19", 111 | "passport": "^0.3.2", 112 | "passport-local": "^1.0.0", 113 | "rxjs": "5.0.0-beta.6", 114 | "socket.io": "^1.4.6", 115 | "zone.js": "~0.6.12" 116 | }, 117 | "devDependencies": { 118 | "@angularclass/angular2-beta-to-rc-alias": "~0.0.3", 119 | "angular2-hmr": "~0.7.0", 120 | "awesome-typescript-loader": "~0.17.0", 121 | "babel-cli": "^6.9.0", 122 | "babel-core": "^6.9.1", 123 | "babel-loader": "^6.2.4", 124 | "babel-preset-es2015": "^6.9.0", 125 | "babel-preset-react": "^6.5.0", 126 | "babel-preset-stage-0": "^6.5.0", 127 | "babel-register": "^6.9.0", 128 | "babel-runtime": "^6.9.2", 129 | "chai": "^3.5.0", 130 | "codelyzer": "~0.0.19", 131 | "compression-webpack-plugin": "^0.3.1", 132 | "concurrently": "^2.1.0", 133 | "copy-webpack-plugin": "^3.0.0", 134 | "css-loader": "^0.23.1", 135 | "del": "^2.2.0", 136 | "es6-promise": "^3.1.2", 137 | "es6-promise-loader": "^1.0.1", 138 | "es6-shim": "^0.35.0", 139 | "es7-reflect-metadata": "^1.6.0", 140 | "exports-loader": "^0.6.3", 141 | "expose-loader": "^0.7.1", 142 | "file-loader": "^0.8.5", 143 | "gulp": "^3.9.1", 144 | "gulp-docco": "0.0.4", 145 | "gulp-nodemon": "^2.0.7", 146 | "gulp-rename": "^1.2.2", 147 | "gulp-scss-lint": "^0.4.0", 148 | "gulp-util": "^3.0.7", 149 | "html-webpack-plugin": "^2.17.0", 150 | "http-server": "^0.9.0", 151 | "imports-loader": "^0.6.5", 152 | "istanbul-instrumenter-loader": "^0.2.0", 153 | "json-loader": "^0.5.4", 154 | "karma": "^0.13.22", 155 | "karma-chrome-launcher": "^1.0.1", 156 | "karma-coverage": "^1.0.0", 157 | "karma-jasmine": "^1.0.2", 158 | "karma-mocha-reporter": "^2.0.0", 159 | "karma-phantomjs-launcher": "^1.0.0", 160 | "karma-sourcemap-loader": "^0.3.7", 161 | "karma-webpack": "1.7.0", 162 | "mocha": "^2.5.3", 163 | "morgan": "^1.7.0", 164 | "node-sass": "^3.4.2", 165 | "parse5": "^1.5.1", 166 | "phantomjs-polyfill": "0.0.2", 167 | "phantomjs-prebuilt": "^2.1.7", 168 | "protractor": "^3.2.2", 169 | "raw-loader": "^0.5.1", 170 | "remap-istanbul": "^0.6.3", 171 | "rimraf": "^2.5.2", 172 | "sass-loader": "^3.2.0", 173 | "source-map-loader": "^0.1.5", 174 | "style-loader": "^0.13.1", 175 | "ts-helpers": "1.1.1", 176 | "ts-node": "^0.7.3", 177 | "tslint": "^3.7.1", 178 | "tslint-loader": "^2.1.3", 179 | "typedoc": "^0.3.12", 180 | "typescript": "~1.8.9", 181 | "typings": "~1.0.3", 182 | "url-loader": "^0.5.7", 183 | "webpack": "^1.12.14", 184 | "webpack-dev-server": "^1.14.1", 185 | "webpack-md5-hash": "^0.0.5", 186 | "webpack-merge": "^0.13.0" 187 | }, 188 | "repository": { 189 | "type": "git", 190 | "url": "https://github.com/datatypevoid/vulgar.git" 191 | }, 192 | "bugs": { 193 | "url": "https://github.com/datatypevoid/vulgar/issues" 194 | }, 195 | "engines": { 196 | "node": ">= 4.2.1", 197 | "npm": ">= 3" 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // @datatype_void 3 | // david.r.niciforovic@gmail.com 4 | // webpack.common.js may be freely distributed under the MIT license 5 | // ``` 6 | 7 | var webpack = require('webpack'); 8 | var helpers = require('./helpers'); 9 | 10 | //# Webpack Plugins 11 | var CopyWebpackPlugin = (CopyWebpackPlugin = require('copy-webpack-plugin'), CopyWebpackPlugin.default || CopyWebpackPlugin); 12 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 13 | const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin; 14 | 15 | //# Webpack Constants 16 | const ENV = process.env.ENV = process.env.NODE_ENV = 'development'; 17 | const HMR = helpers.hasProcessFlag('hot'); 18 | const METADATA = { 19 | title: 'Angular 2 MEAN Webpack Starter Kit by @datatype_void', 20 | baseUrl: '/', 21 | host: process.env.HOST || '0.0.0.0', 22 | port: process.env.PORT || 8080, 23 | ENV: ENV, 24 | HMR: HMR 25 | }; 26 | 27 | //# Webpack Configuration 28 | // 29 | // See: http://webpack.github.io/docs/configuration.html#cli 30 | module.exports = { 31 | 32 | // Static metadata for index.html 33 | // 34 | // See: (custom attribute) 35 | metadata: METADATA, 36 | 37 | // Cache generated modules and chunks to improve performance for multiple incremental builds. 38 | // This is enabled by default in watch mode. 39 | // You can pass false to disable it. 40 | // 41 | // See: http://webpack.github.io/docs/configuration.html#cache 42 | // cache: false, 43 | 44 | // The entry point for the bundle 45 | // Our Angular.js app 46 | // 47 | // See: http://webpack.github.io/docs/configuration.html#entry 48 | entry: { 49 | 50 | 'polyfills': './src/polyfills.ts', 51 | 'vendor': './src/vendor.ts', 52 | // Our primary Angular 2 application 53 | 'main': './src/main.browser.ts', 54 | 55 | }, 56 | 57 | // Options affecting the resolving of modules. 58 | // 59 | // See: http://webpack.github.io/docs/configuration.html#resolve 60 | resolve: { 61 | 62 | // An array of extensions that should be used to resolve modules. 63 | // 64 | // See: http://webpack.github.io/docs/configuration.html#resolve-extensions 65 | extensions: ['', '.ts', '.js', '.scss'], 66 | 67 | // Ensure that root is `src` 68 | root: helpers.root('src'), 69 | 70 | // Remove other default values 71 | modulesDirectories: ['node_modules'], 72 | 73 | alias: { 74 | // legacy imports pre-rc releases 75 | 'angular2': helpers.root('node_modules/@angularclass/angular2-beta-to-rc-alias/dist/beta-17') 76 | } 77 | }, 78 | 79 | // Options affecting the normal modules. 80 | // 81 | // See: http://webpack.github.io/docs/configuration.html#module 82 | module: { 83 | 84 | // An array of applied pre and post loaders. 85 | // 86 | // See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders 87 | preLoaders: [ 88 | 89 | // Tslint loader support for *.ts files 90 | // 91 | // See: https://github.com/wbuchwalter/tslint-loader 92 | // { test: /\.ts$/, loader: 'tslint-loader', exclude: [ helpers.root('node_modules') ] }, 93 | 94 | // Source map loader support for *.js files 95 | // Extracts SourceMaps for source files that as added as sourceMappingURL comment. 96 | // 97 | // See: https://github.com/webpack/source-map-loader 98 | { 99 | test: /\.js$/, 100 | loader: 'source-map-loader', 101 | exclude: [ 102 | // these packages have problems with their sourcemaps 103 | helpers.root('node_modules/rxjs'), 104 | helpers.root('node_modules/@angular2-material'), 105 | helpers.root('node_modules/@angular') 106 | ] 107 | } 108 | 109 | ], 110 | 111 | // An array of automatically applied loaders. 112 | // 113 | // IMPORTANT: The loaders here are resolved relative to the resource which they are applied to. 114 | // This means they are not resolved relative to the configuration file. 115 | // 116 | // See: http://webpack.github.io/docs/configuration.html#module-loaders 117 | loaders: [ 118 | 119 | // Typescript loader support for .ts and Angular 2 async routes via .async.ts 120 | // 121 | // See: https://github.com/s-panferov/awesome-typescript-loader 122 | { 123 | test: /\.ts$/, 124 | loader: 'awesome-typescript-loader', 125 | exclude: [/\.(spec|e2e)\.ts$/] 126 | }, 127 | 128 | // Json loader support for *.json files. 129 | // 130 | // See: https://github.com/webpack/json-loader 131 | { 132 | test: /\.json$/, 133 | loader: 'json-loader' 134 | }, 135 | 136 | // Raw loader support for *.css files 137 | // Returns file content as string 138 | // 139 | // See: https://github.com/webpack/raw-loader 140 | { 141 | test: /\.css$/, 142 | loader: 'raw-loader' 143 | }, 144 | 145 | // Raw loader support for *.html 146 | // Returns file content as string 147 | // 148 | // See: https://github.com/webpack/raw-loader 149 | { 150 | test: /\.html$/, 151 | loader: 'raw-loader', 152 | exclude: [helpers.root('src/index.html')] 153 | }, 154 | 155 | // Support for sass imports 156 | // Add CSS rules to your document: 157 | // `require("!style!css!sass!./file.scss");` 158 | { 159 | test: /\.scss$/, 160 | loader: 'style!css!autoprefixer-loader?browsers=last 2 versions!sass', 161 | exclude: [ helpers.root('node_modules') ] 162 | } 163 | 164 | ] 165 | 166 | }, 167 | 168 | // Add additional plugins to the compiler. 169 | // 170 | // See: http://webpack.github.io/docs/configuration.html#plugins 171 | plugins: [ 172 | 173 | // Plugin: ForkCheckerPlugin 174 | // Description: Do type checking in a separate process, so webpack don't need to wait. 175 | // 176 | // See: https://github.com/s-panferov/awesome-typescript-loader#forkchecker-boolean-defaultfalse 177 | new ForkCheckerPlugin(), 178 | 179 | // Plugin: OccurenceOrderPlugin 180 | // Description: Varies the distribution of the ids to get the smallest id length 181 | // for often used ids. 182 | // 183 | // See: https://webpack.github.io/docs/list-of-plugins.html#occurrenceorderplugin 184 | // See: https://github.com/webpack/docs/wiki/optimization#minimize 185 | new webpack.optimize.OccurenceOrderPlugin(true), 186 | 187 | // Plugin: CommonsChunkPlugin 188 | // Description: Shares common code between the pages. 189 | // It identifies common modules and put them into a commons chunk. 190 | // 191 | // See: https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin 192 | // See: https://github.com/webpack/docs/wiki/optimization#multi-page-app 193 | new webpack.optimize.CommonsChunkPlugin({ 194 | name: ['polyfills', 'vendor'].reverse() 195 | }), 196 | 197 | // Plugin: CopyWebpackPlugin 198 | // Description: Copy files and directories in webpack. 199 | // 200 | // Copies project static assets. 201 | // 202 | // See: https://www.npmjs.com/package/copy-webpack-plugin 203 | new CopyWebpackPlugin([{ 204 | from: 'src/assets', 205 | to: 'assets' 206 | }]), 207 | 208 | // Plugin: HtmlWebpackPlugin 209 | // Description: Simplifies creation of HTML files to serve your webpack bundles. 210 | // This is especially useful for webpack bundles that include a hash in the filename 211 | // which changes every compilation. 212 | // 213 | // See: https://github.com/ampedandwired/html-webpack-plugin 214 | new HtmlWebpackPlugin({ 215 | template: 'src/index.html', 216 | chunksSortMode: 'dependency' 217 | }) 218 | 219 | ], 220 | 221 | // Include polyfills or mocks for various node stuff 222 | // Description: Node configuration 223 | // 224 | // See: https://webpack.github.io/docs/configuration.html#node 225 | node: { 226 | global: 'window', 227 | crypto: 'empty', 228 | module: false, 229 | clearImmediate: false, 230 | setImmediate: false 231 | } 232 | }; 233 | -------------------------------------------------------------------------------- /config/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | // ``` 2 | // @datatype_void 3 | // david.r.niciforovic@gmail.com 4 | // webpack.prod.js may be freely distributed under the MIT license 5 | // ``` 6 | 7 | //# Common Configuration and Helpers 8 | var helpers = require('./helpers'); 9 | // Use `webpack-merge` to merge configs 10 | var webpackMerge = require('webpack-merge'); 11 | // Common `webpack` configuration for `dev` and `prod` 12 | var commonConfig = require('./webpack.common.js'); 13 | 14 | // Webpack Plugins 15 | var ProvidePlugin = require('webpack/lib/ProvidePlugin'); 16 | var DedupePlugin = require('webpack/lib/optimize/DedupePlugin'); 17 | var DefinePlugin = require('webpack/lib/DefinePlugin'); 18 | var UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin'); 19 | var CompressionPlugin = require('compression-webpack-plugin'); 20 | var WebpackMd5Hash = require('webpack-md5-hash'); 21 | 22 | //# Webpack Constants 23 | const ENV = process.env.ENV = process.env.NODE_ENV = 'production'; 24 | const HOST = process.env.HOST || '0.0.0.0'; 25 | const PORT = process.env.PORT || 8080; 26 | const METADATA = { 27 | host: HOST, 28 | port: PORT, 29 | ENV: ENV, 30 | HMR: false 31 | }; 32 | 33 | module.exports = webpackMerge(commonConfig, { 34 | // Developer tool to enhance debugging 35 | // 36 | // See: http://webpack.github.io/docs/configuration.html#devtool 37 | // See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps 38 | devtool: 'source-map', 39 | // Switch loaders to debug mode. 40 | // 41 | // See: http://webpack.github.io/docs/configuration.html#debug 42 | debug: false, 43 | 44 | // Options affecting the output of the compilation. 45 | // 46 | // See: http://webpack.github.io/docs/configuration.html#output 47 | output: { 48 | // The output directory as absolute path (required). 49 | // 50 | // See: http://webpack.github.io/docs/configuration.html#output-path 51 | path: helpers.root('dist'), 52 | // Specifies the name of each output file on disk. 53 | // IMPORTANT: You must not specify an absolute path here! 54 | // 55 | // See: http://webpack.github.io/docs/configuration.html#output-filename 56 | filename: '[name].[chunkhash].bundle.js', 57 | // The filename of the SourceMaps for the JavaScript files. 58 | // They are inside the output.path directory. 59 | // 60 | // See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename 61 | sourceMapFilename: '[name].[chunkhash].bundle.map', 62 | // The filename of non-entry chunks as relative path 63 | // inside the output.path directory. 64 | // 65 | // See: http://webpack.github.io/docs/configuration.html#output-chunkfilename 66 | chunkFilename: '[id].[chunkhash].chunk.js' 67 | }, 68 | 69 | // Add additional plugins to the compiler. 70 | // 71 | // See: http://webpack.github.io/docs/configuration.html#plugins 72 | plugins: [ 73 | // Plugin: WebpackMd5Hash 74 | // Description: Plugin to replace a standard webpack chunkhash with md5. 75 | // 76 | // See: https://www.npmjs.com/package/webpack-md5-hash 77 | new WebpackMd5Hash(), 78 | 79 | // Plugin: DedupePlugin 80 | // Description: Prevents the inclusion of duplicate code into your bundle 81 | // and instead applies a copy of the function at runtime. 82 | // 83 | // See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin 84 | // See: https://github.com/webpack/docs/wiki/optimization#deduplication 85 | new DedupePlugin(), 86 | 87 | // Plugin: DefinePlugin 88 | // Description: Define free variables. 89 | // Useful for having development builds with debug logging or adding global constants. 90 | // 91 | // Environment helpers 92 | // 93 | // See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin 94 | // NOTE: when adding more properties make sure you include them in custom-typings.d.ts 95 | new DefinePlugin({ 96 | 'ENV': JSON.stringify(METADATA.ENV), 97 | 'HMR': METADATA.HMR, 98 | 'process.env': { 99 | 'ENV': JSON.stringify(METADATA.ENV), 100 | 'NODE_ENV': JSON.stringify(METADATA.ENV), 101 | 'HMR': METADATA.HMR 102 | } 103 | }), 104 | 105 | // Plugin: UglifyJsPlugin 106 | // Description: Minimize all JavaScript output of chunks. 107 | // Loaders are switched into minimizing mode. 108 | // 109 | // See: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin 110 | // NOTE: To debug prod builds uncomment //debug lines and comment //prod lines 111 | new UglifyJsPlugin({ 112 | // beautify: true, //debug 113 | // mangle: false, //debug 114 | // dead_code: false, //debug 115 | // unused: false, //debug 116 | // deadCode: false, //debug 117 | // compress: { 118 | // screw_ie8: true, 119 | // keep_fnames: true, 120 | // drop_debugger: false, 121 | // dead_code: false, 122 | // unused: false 123 | // }, // debug 124 | // comments: true, //debug 125 | 126 | beautify: false, //prod 127 | 128 | mangle: { 129 | screw_ie8 : true, 130 | keep_fnames: true 131 | }, // Production 132 | /* 133 | mangle: { 134 | screw_ie8: true, 135 | except: [ 136 | 'App', 137 | 'About', 138 | 'Contact', 139 | 'Home', 140 | 'Menu', 141 | 'Footer', 142 | 'XLarge', 143 | 'RouterActive', 144 | 'RouterLink', 145 | 'RouterOutlet', 146 | 'NgFor', 147 | 'NgIf', 148 | 'NgClass', 149 | 'NgSwitch', 150 | 'NgStyle', 151 | 'NgSwitchDefault', 152 | 'NgControl', 153 | 'NgControlName', 154 | 'NgControlGroup', 155 | 'NgFormControl', 156 | 'NgModel', 157 | 'NgFormModel', 158 | 'NgForm', 159 | 'NgSelectOption', 160 | 'DefaultValueAccessor', 161 | 'NumberValueAccessor', 162 | 'CheckboxControlValueAccessor', 163 | 'SelectControlValueAccessor', 164 | 'RadioControlValueAccessor', 165 | 'NgControlStatus', 166 | 'RequiredValidator', 167 | 'MinLengthValidator', 168 | 'MaxLengthValidator', 169 | 'PatternValidator', 170 | 'AsyncPipe', 171 | 'DatePipe', 172 | 'JsonPipe', 173 | 'NumberPipe', 174 | 'DecimalPipe', 175 | 'PercentPipe', 176 | 'CurrencyPipe', 177 | 'LowerCasePipe', 178 | 'UpperCasePipe', 179 | 'SlicePipe', 180 | 'ReplacePipe', 181 | 'I18nPluralPipe', 182 | 'I18nSelectPipe' 183 | ] // Needed for uglify RouterLink problem 184 | }, // prod 185 | */ 186 | compress: { 187 | screw_ie8: true 188 | }, //prod 189 | comments: false //prod 190 | }), 191 | 192 | // Plugin: CompressionPlugin 193 | // Description: Prepares compressed versions of assets to serve 194 | // them with Content-Encoding 195 | // 196 | // See: https://github.com/webpack/compression-webpack-plugin 197 | new CompressionPlugin({ 198 | regExp: /\.css$|\.html$|\.js$|\.map$/, 199 | threshold: 2 * 1024 200 | }) 201 | ], 202 | 203 | // Static analysis linter for TypeScript advanced options configuration 204 | // Description: An extensible linter for the TypeScript language. 205 | // 206 | // See: https://github.com/wbuchwalter/tslint-loader 207 | tslint: { 208 | emitErrors: true, 209 | failOnHint: true, 210 | resourcePath: 'src' 211 | }, 212 | 213 | // Html loader advanced options 214 | // 215 | // See: https://github.com/webpack/html-loader#advanced-options 216 | // TODO: Need to workaround Angular 2's html syntax => #id [bind] (event) *ngFor 217 | htmlLoader: { 218 | minimize: true, 219 | removeAttributeQuotes: false, 220 | caseSensitive: true, 221 | customAttrSurround: [ 222 | [/#/, /(?:)/], 223 | [/\*/, /(?:)/], 224 | [/\[?\(?/, /(?:)/] 225 | ], 226 | customAttrAssign: [/\)?\]?=/] 227 | }, 228 | 229 | node: { 230 | global: 'window', 231 | crypto: 'empty', 232 | process: false, 233 | module: false, 234 | clearImmediate: false, 235 | setImmediate: false 236 | } 237 | }); 238 | -------------------------------------------------------------------------------- /src/sass/vendor/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS and IE text size adjust after device orientation change, 6 | * without disabling user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability of focused elements when they are also in an 95 | * active/hover state. 96 | */ 97 | 98 | a:active, 99 | a:hover { 100 | outline: 0; 101 | } 102 | 103 | /* Text-level semantics 104 | ========================================================================== */ 105 | 106 | /** 107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 108 | */ 109 | 110 | abbr[title] { 111 | border-bottom: 1px dotted; 112 | } 113 | 114 | /** 115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bold; 121 | } 122 | 123 | /** 124 | * Address styling not present in Safari and Chrome. 125 | */ 126 | 127 | dfn { 128 | font-style: italic; 129 | } 130 | 131 | /** 132 | * Address variable `h1` font-size and margin within `section` and `article` 133 | * contexts in Firefox 4+, Safari, and Chrome. 134 | */ 135 | 136 | h1 { 137 | font-size: 2em; 138 | margin: 0.67em 0; 139 | } 140 | 141 | /** 142 | * Address styling not present in IE 8/9. 143 | */ 144 | 145 | mark { 146 | background: #ff0; 147 | color: #000; 148 | } 149 | 150 | /** 151 | * Address inconsistent and variable font size in all browsers. 152 | */ 153 | 154 | small { 155 | font-size: 80%; 156 | } 157 | 158 | /** 159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 160 | */ 161 | 162 | sub, 163 | sup { 164 | font-size: 75%; 165 | line-height: 0; 166 | position: relative; 167 | vertical-align: baseline; 168 | } 169 | 170 | sup { 171 | top: -0.5em; 172 | } 173 | 174 | sub { 175 | bottom: -0.25em; 176 | } 177 | 178 | /* Embedded content 179 | ========================================================================== */ 180 | 181 | /** 182 | * Remove border when inside `a` element in IE 8/9/10. 183 | */ 184 | 185 | img { 186 | border: 0; 187 | } 188 | 189 | /** 190 | * Correct overflow not hidden in IE 9/10/11. 191 | */ 192 | 193 | svg:not(:root) { 194 | overflow: hidden; 195 | } 196 | 197 | /* Grouping content 198 | ========================================================================== */ 199 | 200 | /** 201 | * Address margin not present in IE 8/9 and Safari. 202 | */ 203 | 204 | figure { 205 | margin: 1em 40px; 206 | } 207 | 208 | /** 209 | * Address differences between Firefox and other browsers. 210 | */ 211 | 212 | hr { 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. 354 | */ 355 | 356 | input[type="search"] { 357 | -webkit-appearance: textfield; /* 1 */ 358 | box-sizing: content-box; /* 2 */ 359 | } 360 | 361 | /** 362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 363 | * Safari (but not Chrome) clips the cancel button when the search input has 364 | * padding (and `textfield` appearance). 365 | */ 366 | 367 | input[type="search"]::-webkit-search-cancel-button, 368 | input[type="search"]::-webkit-search-decoration { 369 | -webkit-appearance: none; 370 | } 371 | 372 | /** 373 | * Define consistent border, margin, and padding. 374 | */ 375 | 376 | fieldset { 377 | border: 1px solid #c0c0c0; 378 | margin: 0 2px; 379 | padding: 0.35em 0.625em 0.75em; 380 | } 381 | 382 | /** 383 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 385 | */ 386 | 387 | legend { 388 | border: 0; /* 1 */ 389 | padding: 0; /* 2 */ 390 | } 391 | 392 | /** 393 | * Remove default vertical scrollbar in IE 8/9/10/11. 394 | */ 395 | 396 | textarea { 397 | overflow: auto; 398 | } 399 | 400 | /** 401 | * Don't inherit the `font-weight` (applied by a rule above). 402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 403 | */ 404 | 405 | optgroup { 406 | font-weight: bold; 407 | } 408 | 409 | /* Tables 410 | ========================================================================== */ 411 | 412 | /** 413 | * Remove most spacing between table cells. 414 | */ 415 | 416 | table { 417 | border-collapse: collapse; 418 | border-spacing: 0; 419 | } 420 | 421 | td, 422 | th { 423 | padding: 0; 424 | } 425 | --------------------------------------------------------------------------------