├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── chromedriver ├── config ├── config │ ├── config.protractor.js │ └── protractor.conf.bs.js ├── deploy.sh ├── dist-assets │ ├── README.md │ └── manifest.json ├── dogen.js ├── e2e-test.js ├── prod-config.json └── templates │ ├── _ngmodule_ │ ├── _ngmodule_.component.js │ ├── _ngmodule_.component.spec.js │ ├── _ngmodule_.html │ ├── _ngmodule_.less │ └── index.js │ └── _ngservice_ │ └── _ngservice_.srv.js ├── gulpfile.babel.js ├── index-screenshot.png ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── specs.bundle.js ├── src ├── app.component.js ├── app.html ├── app.js ├── assets │ ├── favicon.ico │ └── icon-128x128.png ├── components │ └── home │ │ ├── home.component.js │ │ ├── home.html │ │ ├── home.less │ │ ├── home.spec.js │ │ └── index.js ├── config │ ├── dev.config.js │ ├── production.config.js │ └── test.config.js ├── core │ ├── components │ │ ├── e-dropdown │ │ │ ├── e-dropdown.component.js │ │ │ ├── e-dropdown.less │ │ │ ├── e-dropdown.spec.js │ │ │ ├── e-dropdown.tpl.html │ │ │ └── index.js │ │ └── index.js │ ├── index.js │ └── services │ │ ├── index.js │ │ ├── other.service.srv.js │ │ ├── some.service.srv.js │ │ └── some.service.srv.spec.js ├── css │ ├── core │ │ ├── global.less │ │ └── utils.less │ ├── echoes-variables.less │ ├── layout │ │ ├── container.less │ │ ├── navbar.less │ │ └── sidebar.less │ └── style.less └── index.html ├── tests ├── e2e │ └── app.spec.js └── mocks │ └── home.mock.js ├── webpack.config.js └── webpack.config.production.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "babel-preset-env" ], 3 | "plugins": [ 4 | "transform-object-rest-spread", 5 | "transform-class-properties", 6 | "transform-es2015-classes" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "sourceType": "module", 4 | "ecmaVersion": 8, 5 | "ecmaFeatures": { 6 | "modules": true, 7 | "arrowFunctions": true, 8 | "blockBindings": true, 9 | "destructuring": true, 10 | "classes": true, 11 | "spread": true, 12 | "restParams": true, 13 | "templateStrings": true, 14 | "experimentalObjectRestSpread": true, 15 | "objectLiteralShorthandMethods": true, 16 | "objectLiteralShorthandProperties": true, 17 | } 18 | }, 19 | "rules": { 20 | "indent": [ 21 | "error", 22 | 2 23 | ], 24 | "quotes": [ 25 | 2, 26 | "single" 27 | ], 28 | "linebreak-style": [ 29 | 2, 30 | "unix" 31 | ], 32 | "semi": [ 33 | 2, 34 | "always" 35 | ] 36 | }, 37 | "env": { 38 | "es6": true, 39 | "browser": true, 40 | "jasmine": true, 41 | "commonjs": true 42 | }, 43 | "globals": { 44 | "angular": true, 45 | "inject": true, 46 | "__dirname": true, 47 | "process": true 48 | }, 49 | "extends": "eslint:recommended" 50 | } 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | 4 | # Folder config file 5 | Desktop.ini 6 | 7 | #Translations 8 | *.mo 9 | 10 | # Mac crap 11 | .DS_Store 12 | 13 | *.sublime-* 14 | 15 | # custom echoes files 16 | node_modules 17 | .tmp/ 18 | bower_components/ 19 | bundle*.js 20 | bundle*.map 21 | *.css.map 22 | *.tmp.* 23 | style.css 24 | npm-debug.log 25 | vendors*.js 26 | templates*.mdl*.js 27 | dist/ 28 | .tags* 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '5.1' 5 | 6 | env: 7 | global: 8 | - GH_REF: github.com/your-user/repo.git 9 | - secure: add-secure-hash 10 | install: 11 | - npm install karma-es6-shim 12 | - npm install 13 | before_script: 14 | # - ./BrowserStackLocal-linux $bs_key localhost,9001,0 & 15 | # - npm start & 16 | # - sleep 5 17 | after_script: 18 | - process.exit() 19 | after_success: 20 | - chmod +x ./gulp/deploy.sh 21 | - ./gulp/deploy.sh 22 | cache: 23 | directories: 24 | - $HOME/.nvm 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Oren Farhi 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SuperNova Starter 2 | ![](./src/assets/icon-128x128.png) 3 | orizens.com logo 4 | An opinionated boilerplate by [Angular ES6/ES2015 Style Guide](https://github.com/orizens/angular-es6-styleguide) by [orizens](http://orizens.com): 5 | 6 | ![](./index-screenshot.png) 7 | ## What's Inside 8 | - Angular 1.5.x 9 | - ui-router 1.x (component support) 10 | - Karma & Jasmine 11 | - Webpack - configured with: 12 | - ES6 13 | - LESS 14 | - ng-templates 15 | - ng-inject 16 | - sourcemaps for development and production 17 | - production build 18 | - fonts loader 19 | - bootstrap 20 | - font-awesome 21 | - npm scripts for development, testing (bdd) and production 22 | - modules scaffold with gulp-dogen 23 | - FULLSTACK: json-server for mocking backend api during development - available in [fullstack branch](https://github.com/orizens/supernova-angular-1.5.x-es6-starter/tree/fullstack) 24 | 25 | ## Quick Start 26 | - nodejs >= 5, npm > 3 27 | - clone this repo 28 | 29 | ```shell 30 | # clone repo 31 | # --depth 1 removes all but one .git commit history 32 | git clone --depth 1 https://github.com/orizens/supernova-angular-1.5.x-es6-starter.git 33 | 34 | # change directory to our repo 35 | cd supernova-angular-1.5.x-es6-starter 36 | 37 | # install dependencies with npm 38 | npm install 39 | 40 | # start dev server 41 | npm start 42 | navigate to http://localhost:9001 in your browser for the front end 43 | 44 | ``` 45 | 46 | ## with fullstack branch 47 | in the **fullstack** branch, the ```npm start``` command also starts the json-server process. 48 | navigate to http://localhost:3000 in your browser to view the available routes in the local server api which is served from **[tests/mocks/db.json](tests/mocks/db.json)**. 49 | For more info: [json-server documentation](https://github.com/typicode/json-server) 50 | 51 | ## File Structure 52 | ``` 53 | supernova-angular-1.5.x-es6-starter/ 54 | ├──config/ * our configuration 55 | | ├──config/ * saved for e2e and remote e2e testing 56 | | ├──dist-assets/ * static files for production - these are copied to dist after with prod task 57 | | ├──templates/ * gulp-dogen modules templates for scaffolding modules 58 | | ├──deploy.sh * script used to deploy dist to github:gh-pages branch with TravisCI 59 | │ ├──dogen.js * gulp task to configure templates for scaffolding 60 | │ ├──e2e-test.js * saved for configuring e2e test runner 61 | │ └──server.js * gulp helper task to run server for dist 62 | │ 63 | ├──src/ * source files of the whole app 64 | | ├──app.js * entry file for the app 65 | │ │ 66 | | ├──index.html * main index page (app.js and vendors.js are added automatically ) 67 | │ │ 68 | │ ├──components/ * all SMART components should be created here 69 | │ │ ├──home/ * an example for a smart component: includes less 70 | │ │ 71 | │ └──core/ * core layer of the app 72 | │ │ ├──components/ * all dumb components should be created here 73 | │ │ ├──services/ * all services of the app should be created here so SMART components can import it 74 | │ │ ├──css/ * app wide less styles 75 | │ │ ├──config/ * config phase for each environment 76 | │ │ 77 | ├──tests/ * currently saved for mock files and e2e tests 78 | │ 79 | ├──.eslintrc.json * eslint config 80 | ├──package.json * what npm uses to manage it's dependencies 81 | └──specs.bundle.js * used by karma to collect spec files to run tests 82 | └──webpack.config.js * webpack configuration file for development 83 | └──webpack.config.production.js * webpack configuration file for production 84 | 85 | ``` 86 | 87 | ## Modules Scaffolding 88 | this package uses **gulp-dogen** for scaffolding. 89 | There are 2 templates in **config/templates**: 90 | 1. ngmodule 91 | 2. ngservice 92 | 93 | to scaffold, run in terminal: 94 | ```shell 95 | # to scaffold new ng component in src/components 96 | npm run dogen -- --ngmodule the-name-of-module 97 | # to scaffold new ng service in src/core/services 98 | npm run dogen -- --ngservice the-name-of-service 99 | ``` 100 | 101 | ## Adding 3rd party / vendors 102 | the build creates 2 bundled files: 103 | 1. app.bundle.js - includes the files created by you 104 | 2. vendors.bundle.js - includes the files created by the list configured in **webpack.config.js**. Simply add the npm package name to the list of vendors. 105 | 106 | ## Testing with BDD 107 | to run tests while developing use: 108 | ```npm run bdd``` 109 | to run tests once use: 110 | ```npm test``` 111 | running unit tests in debug mode - ```npm run testd``` 112 | 113 | ## Creating A Production Build 114 | A production build is configured by webpack.config.production.js. 115 | to create a build, simply run: 116 | ```npm run prod``` 117 | This creates a **dist** directory with a minified and bundled version of the app. 118 | 119 | You can run a local server to check the dist build by ```npm run prod:serve```. It is based on lite-server and can be extended through **config/prod-config.json** by the [various options available to it](https://github.com/johnpapa/lite-server). 120 | 121 | ## Plans To Add 122 | - ~~testing for all files - Currently testing only core directory~~ 123 | - protractor / e2e 124 | - browserstack configuration / remote e2e 125 | - ngMaterial 126 | - ~~component router~~ no version for ng1 yet 127 | - rxjs (?) - still uses **$scope** to create observables 128 | - ngRedux 129 | 130 | ## Contributing 131 | To request a feature - please open an issue. 132 | Better than this, open a PR with your proposed update. 133 | 134 | Thanks. 135 | -------------------------------------------------------------------------------- /chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orizens/supernova-angular-1.5.x-es6-starter/adabcdf68b10152c9a66f76de0ab19bb7095b5f5/chromedriver -------------------------------------------------------------------------------- /config/config/config.protractor.js: -------------------------------------------------------------------------------- 1 | // adopted from Wishtack 2 | // NOTICE: 3 | // add these to dev dependencies if testing will be done 4 | // with browserstack 5 | // "wt-protractor-runner": "^0.2.2", 6 | // "wt-protractor-utils": "^1.0.0" 7 | 8 | import extend from 'node.extend'; 9 | import protractorUtils from 'wt-protractor-utils'; 10 | 11 | export function configProtractor() { 12 | var browserstack = extend(true /* Deep copy. */, {}, protractorUtils.platform.browserstack); 13 | 14 | var protractorBaseConfig = { 15 | specs: __dirname + '/../../tests/e2e/*.js' 16 | }; 17 | 18 | // browsertstack.capabilities['browserstack.user'] = process.env.BROWSERSTACK_USER; 19 | browserstack.capabilities['browserstack.user'] = process.env.bs_user; 20 | // browserstack.capabilities['browserstack.key'] = process.env.BROWSERSTACK_KEY; 21 | browserstack.capabilities['browserstack.key'] = process.env.bs_key; 22 | browserstack.capabilities['browserstack.local'] = 'true'; 23 | 24 | return { 25 | configList: [ 26 | /* OS X / Chrome. */ 27 | protractorUtils.mergeConfig({ 28 | configList: [ 29 | protractorBaseConfig, 30 | browserstack, 31 | protractorUtils.os.osx, 32 | protractorUtils.browser.chrome 33 | ] 34 | }), 35 | // /* OS X / Safari. */ 36 | // protractorUtils.mergeConfig({ 37 | // configList: [ 38 | // protractorBaseConfig, 39 | // browserstack, 40 | // protractorUtils.os.osx, 41 | // protractorUtils.browser.safari 42 | // ] 43 | // }), 44 | /* Windows / Chrome. */ 45 | protractorUtils.mergeConfig({ 46 | configList: [ 47 | protractorBaseConfig, 48 | browserstack, 49 | protractorUtils.os.windows, 50 | protractorUtils.browser.chrome 51 | ] 52 | }) 53 | /* Windows / Internet Explorer. */ 54 | // protractorUtils.mergeConfig({ 55 | // configList: [ 56 | // protractorBaseConfig, 57 | // browserstack, 58 | // protractorUtils.os.windows, 59 | // protractorUtils.browser.internetExplorer 60 | // ] 61 | // }), 62 | // /* Android. */ 63 | // protractorUtils.mergeConfig({ 64 | // configList: [ 65 | // protractorBaseConfig, 66 | // browserstack, 67 | // protractorUtils.os.android 68 | // ] 69 | // }), 70 | /* iPad. */ 71 | // protractorUtils.mergeConfig({ 72 | // configList: [ 73 | // protractorBaseConfig, 74 | // browserstack, 75 | // protractorUtils.device.iPad 76 | // ] 77 | // }) 78 | // /* iPhone. */ 79 | // protractorUtils.mergeConfig({ 80 | // configList: [ 81 | // protractorBaseConfig, 82 | // browserstack, 83 | // protractorUtils.device.iPhone 84 | // ] 85 | // }) 86 | ] 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /config/config/protractor.conf.bs.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | capabilities: { 3 | 'browserstack.user' : process.env.bs_user, 4 | 'browserstack.key' : process.env.bs_key, 5 | 6 | // Needed for testing localhost 7 | 'browserstack.local' : 'true', 8 | 'browserstack.debug': 'true', 9 | 10 | // Settings for the browser you want to test 11 | 'browserName' : 'Chrome', 12 | 'browser_version' : '39.0', 13 | 'os' : 'OS X', 14 | 'os_version' : 'Mavericks', 15 | 'resolution' : '1024x768' 16 | }, 17 | 18 | framework: 'jasmine2', 19 | 20 | // Browserstack's selenium server address 21 | seleniumAddress: 'http://hub.browserstack.com/wd/hub', 22 | 23 | // Pattern for finding spec files 24 | specs: ['../../tests/e2e/**/*spec.js'], 25 | 26 | onPrepare: function() { 27 | var SpecReporter = require('jasmine-spec-reporter'); 28 | // add jasmine spec reporter 29 | jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: true})); 30 | }, 31 | 32 | jasmineNodeOpts: { 33 | print: function() {} 34 | } 35 | } -------------------------------------------------------------------------------- /config/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "$TRAVIS_BRANCH" == "master" ]; then 3 | git config --global user.email "your-github-email@email.com" 4 | git config --global user.name "travis-ci" 5 | npm run release 6 | cd dist 7 | git init 8 | git add . 9 | git commit -m "deployed commit ${TRAVIS_COMMIT} from travis" 10 | git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages > /dev/null 2>&1 11 | fi 12 | -------------------------------------------------------------------------------- /config/dist-assets/README.md: -------------------------------------------------------------------------------- 1 | # Assets For production 2 | various assets can be kept here - these will be added to production bundle through webpack config. 3 | -------------------------------------------------------------------------------- /config/dist-assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "My App", 3 | "short_name": "my app", 4 | "description": "the app features...", 5 | "icons": [{ 6 | "src": "assets/icon-128x128.png", 7 | "sizes": "128x128", 8 | "type": "image/png" 9 | }], 10 | "start_url": "/index.html?utm_source=web_app_manifest", 11 | "display": "standalone" 12 | } 13 | -------------------------------------------------------------------------------- /config/dogen.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var dogen = require('gulp-dogen'); 3 | 4 | dogen.config({ 5 | templatesPath: 'config/templates', 6 | gulp: gulp 7 | }); 8 | 9 | // This will create this gulp task as: 10 | // gulp dogen --ngmodule the-name-of-module 11 | dogen.task('ngmodule', 'src/components/'); 12 | dogen.task('ngservice', 'src/core/services'); 13 | -------------------------------------------------------------------------------- /config/e2e-test.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import protractorRunner from 'wt-protractor-runner'; 3 | 4 | gulp.task('test:e2e', (done) => { 5 | // Load config 6 | var config = require('./config/config.protractor')(); 7 | // Run tests 8 | protractorRunner(config)(done); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /config/prod-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 9002, 3 | "files": ["./dist/**/*.{html,htm,css,js}"], 4 | "server": { "baseDir": "./dist" } 5 | } -------------------------------------------------------------------------------- /config/templates/_ngmodule_/_ngmodule_.component.js: -------------------------------------------------------------------------------- 1 | import template from './_ngmodule_.html'; 2 | 3 | export let =ngmodule=Component = { 4 | templateUrl: template, 5 | selector: '=ngmodule=', 6 | bindings: { 7 | 8 | }, 9 | controller: class =ngmodule=Ctrl { 10 | /* @ngInject */ 11 | constructor () { 12 | // Object.assign(this, ...arguments); 13 | 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /config/templates/_ngmodule_/_ngmodule_.component.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import _ngmodule_Module, { =ngmodule=Component } from './index.js'; 3 | 4 | describe('_ngmodule_ Component', () => { 5 | var ctrl; 6 | 7 | beforeEach(window.module(_ngmodule_Module)); 8 | 9 | beforeEach(window.inject(($componentController) => { 10 | ctrl = $componentController(=ngmodule=Component.selector, { 11 | 12 | }); 13 | })); 14 | 15 | it('should do what it is supposed to do', () => { 16 | const expected = ''; 17 | const actual = ''; 18 | expect(actual).toMatch(expected); 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /config/templates/_ngmodule_/_ngmodule_.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Title

4 |
5 | 6 |
7 | 8 |
9 |
-------------------------------------------------------------------------------- /config/templates/_ngmodule_/_ngmodule_.less: -------------------------------------------------------------------------------- 1 | // actions cell 2 | ._ngmodule_ { 3 | 4 | } 5 | -------------------------------------------------------------------------------- /config/templates/_ngmodule_/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import { =ngmodule=Component } from './_ngmodule_.component'; 3 | import '_ngmodule_.less'; 4 | 5 | export * from './_ngmodule_.component'; 6 | 7 | export default angular.module('_ngmodule_', [ 8 | 9 | ]) 10 | .config(config) 11 | .component(=ngmodule=Component.selector, =ngmodule=Component) 12 | .name; 13 | /* @ngInject */ 14 | function config () { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /config/templates/_ngservice_/_ngservice_.srv.js: -------------------------------------------------------------------------------- 1 | /* @ngInject */ 2 | export default function _ngservice_(dependencies) { 3 | var service = { 4 | func: func 5 | }; 6 | return service; 7 | 8 | //////////////// 9 | 10 | function func() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | 3 | // import './gulp/e2e-test.js'; 4 | import './config/dogen.js'; 5 | -------------------------------------------------------------------------------- /index-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orizens/supernova-angular-1.5.x-es6-starter/adabcdf68b10152c9a66f76de0ab19bb7095b5f5/index-screenshot.png -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const webpack = require('webpack'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const isDebug = process.env.DEBUG || false; 6 | const isTravis = process.env.TRAVIS || false; 7 | const KARMA_SINGLE_RUN_FLAG = process.argv.filter(s => s.includes('single-run')); 8 | const RUN_ONCE = process.env.BDD || isDebug ? false : KARMA_SINGLE_RUN_FLAG ? 9 | true : false; 10 | const browsers = isTravis ? ['PhantomJS'] : [isDebug ? 'Chrome' : 'PhantomJS']; 11 | 12 | const options = { 13 | basePath: '', 14 | browsers: browsers, 15 | frameworks: ['jasmine'], 16 | autoWatch: true, 17 | singleRun: RUN_ONCE, 18 | files: [ 19 | // es6-shim is currently needed for phantomjs 20 | './node_modules/es6-shim/es6-shim.js', 21 | 'specs.bundle.js', 22 | './src/core/**/*.spec.js', 23 | './src/components/**/*.spec.js' 24 | ], 25 | preprocessors: { 26 | 'specs.bundle.js': ['webpack', 'sourcemap'], 27 | './src/core/**/*.spec.js': ['webpack', 'sourcemap'], 28 | './src/components/**/*.spec.js': ['webpack', 'sourcemap'] 29 | }, 30 | webpack: { 31 | devtool: 'inline-source-map', 32 | module: { 33 | loaders: [{ 34 | test: /\.js$/, 35 | exclude: /(node_modules)/, 36 | loaders: ['babel'] 37 | }, { 38 | test: /\.html$/, 39 | loader: 'ngtemplate!html', 40 | exclude: /(index)/ 41 | }, { 42 | test: /\.less$/, 43 | loader: ExtractTextPlugin.extract('css?sourceMap!' + 44 | 'less?sourceMap') 45 | }] 46 | }, 47 | plugins: [ 48 | new ExtractTextPlugin('[name].[chunkhash].style.css') 49 | ], 50 | resolve: {} 51 | }, 52 | webpackMiddleware: { 53 | noInfo: true 54 | }, 55 | plugins: [ 56 | require('karma-webpack'), 57 | require('karma-sourcemap-loader'), 58 | 'karma-phantomjs-launcher', 59 | 'karma-chrome-launcher', 60 | 'karma-jasmine', 61 | // 'karma-html-reporter', 62 | // 'karma-spec-reporter', 63 | 'karma-mocha-reporter', 64 | 'karma-clear-screen-reporter' 65 | // 'karma-browserstack-launcher' 66 | ], 67 | reporters: [ 68 | // 'progress', 69 | // 'spec', 70 | // 'coverage', 71 | 'mocha', 72 | 'clear-screen' 73 | ], 74 | mochaReporter: { 75 | // output: 'autowatch' 76 | } 77 | // the default configuration 78 | // htmlReporter: { 79 | // outputDir: 'karma_html', 80 | // templatePath: './node_modules/karma-html-reporter/jasmine_template.html' 81 | // } 82 | }; 83 | 84 | // var browserStackOptions = { 85 | // // global config of your BrowserStack account 86 | // browserStack: { 87 | // username: process.env.bs_user, 88 | // accessKey: process.env.bs_key 89 | // }, 90 | 91 | // // define browsers 92 | // customLaunchers: { 93 | // bs_chrome_mac: { 94 | // base: 'BrowserStack', 95 | // browser: 'chrome', 96 | // browser_version: '39.0', 97 | // os: 'OS X', 98 | // os_version: 'Mountain Lion' 99 | // }, 100 | // bs_chrome_windows: { 101 | // base: 'BrowserStack', 102 | // browser : 'chrome', 103 | // browser_version : '39.0', 104 | // os : 'Windows', 105 | // os_version : '8' 106 | // } 107 | // }, 108 | 109 | // browsers: ['bs_chrome_mac', 'bs_chrome_windows'] 110 | // }; 111 | 112 | module.exports = function(config) { 113 | // if (isTravis) { 114 | // Object.keys(browserStackOptions).forEach(function (key) { 115 | // options[key] = browserStackOptions[key]; 116 | // }); 117 | // } 118 | config.set(options); 119 | }; 120 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supernova-angular-1.5.x-es6-starter", 3 | "version": "0.0.3", 4 | "description": "starter kit for angular > 1.5.x, > es6, karma and jasmine", 5 | "main": "src/index.html", 6 | "directories": {}, 7 | "scripts": { 8 | "e2e": "protractor protractor.conf.js", 9 | "e2ed": "protractor debug protractor.conf.js", 10 | "e2e:remote": "protractor ./gulp/config/protractor.conf.bs.js", 11 | "clean": "npm run clean:dist && rimraf node_modules/", 12 | "clean:dist": "rimraf dist/", 13 | "test": "karma start", 14 | "testd": "DEBUG=true npm test", 15 | "bdd": "BDD=true npm test", 16 | "start": "npm run dev", 17 | "wpw": "webpack -d --watch", 18 | "dogen": "gulp dogen", 19 | "dev": "webpack-dev-server --hot --inline --progress --colors --content-base src --port 9001", 20 | "prod": "npm run clean:dist && webpack --config=webpack.config.production.js -p", 21 | "prod:serve": "lite-server -c config/prod-config.json" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/orizens/supernova-angular-1.5.x-es6-starter.git" 26 | }, 27 | "keywords": [ 28 | "angular", 29 | "karma", 30 | "es6", 31 | "jasmine" 32 | ], 33 | "author": "Oren Farhi", 34 | "license": "MIT", 35 | "readmeFilename": "README.md", 36 | "devDependencies": { 37 | "angular": "^1.5.8", 38 | "angular-animate": "^1.5.8", 39 | "angular-local-storage": "^0.2.2", 40 | "angular-mocks": "^1.5.8", 41 | "angular-route": "^1.5.8", 42 | "angular-sanitize": "^1.5.8", 43 | "angular-ui-bootstrap": "^1.1.2", 44 | "angular-ui-router": "^1.0.0-beta.2", 45 | "babel-core": "^6.2.1", 46 | "babel-loader": "^6.2.1", 47 | "babel-plugin-transform-class-properties": "^6.24.1", 48 | "babel-plugin-transform-es2015-classes": "^6.24.1", 49 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 50 | "babel-preset-env": "^1.6.0", 51 | "bootstrap": "^3.3.6", 52 | "copy-webpack-plugin": "^3.0.1", 53 | "css-loader": "^0.23.1", 54 | "es6-shim": "^0.35.1", 55 | "eslint": "^2.8.0", 56 | "eslint-config-standard": "^4.4.0", 57 | "eslint-plugin-standard": "^1.3.1", 58 | "extract-text-webpack-plugin": "^1.0.1", 59 | "file-loader": "^0.8.5", 60 | "font-awesome": "^4.5.0", 61 | "font-awesome-webpack": "0.0.4", 62 | "gulp": "3.9.1", 63 | "gulp-dogen": "0.1.9", 64 | "html-loader": "^0.4.0", 65 | "html-webpack-plugin": "^2.17.0", 66 | "jasmine-core": "^2.4.1", 67 | "jasmine-spec-reporter": "^2.2.3", 68 | "karma": "^0.13.15", 69 | "karma-babel-preprocessor": "^6.0.1", 70 | "karma-chrome-launcher": "^0.2.1", 71 | "karma-clear-screen-reporter": "^1.0.0", 72 | "karma-jasmine": "^0.3.6", 73 | "karma-json-fixtures-preprocessor": "0.0.5", 74 | "karma-mocha-reporter": "^1.1.1", 75 | "karma-phantomjs-launcher": "^1.0.0", 76 | "karma-sourcemap-loader": "^0.3.7", 77 | "karma-spec-reporter": "0.0.13", 78 | "karma-webpack": "^1.7.0", 79 | "less": "^2.7.1", 80 | "less-loader": "^2.2.3", 81 | "lite-server": "^2.2.2", 82 | "ng-annotate-loader": "^0.1.0", 83 | "ng-annotate-webpack-plugin": "^0.1.2", 84 | "ngtemplate-loader": "^1.3.1", 85 | "phantomjs-prebuilt": "^2.1.7", 86 | "rimraf": "^2.5.4", 87 | "run-sequence": "^1.1.0", 88 | "selenium-webdriver": "^2.47.0", 89 | "style-loader": "^0.13.1", 90 | "url-loader": "^0.5.7", 91 | "webpack": "^1.13.1", 92 | "webpack-dev-server": "^1.14.1" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | framework: 'jasmine2', 3 | 4 | capabilities: { 5 | // 'browserName': 'phantomjs', 6 | 'browserName': 'chrome', 7 | }, 8 | // 'phantomjs.binary.path': require('phantomjs').path, 9 | seleniumAddress: 'http://localhost:4444/wd/hub', 10 | specs: ['tests/e2e/*spec.js'], 11 | directConnect: true, 12 | 13 | onPrepare: function() { 14 | var SpecReporter = require('jasmine-spec-reporter'); 15 | // add jasmine spec reporter 16 | jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: true})); 17 | }, 18 | jasmineNodeOpts: { 19 | print: function() {} 20 | } 21 | }; -------------------------------------------------------------------------------- /specs.bundle.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable */ 2 | import angular from 'angular'; 3 | import mocks from 'angular-mocks'; 4 | /*eslint-enable */ -------------------------------------------------------------------------------- /src/app.component.js: -------------------------------------------------------------------------------- 1 | import template from './app.html'; 2 | 3 | export let AppComponent = { 4 | templateUrl: template, 5 | selector: 'app', 6 | bindings: {}, 7 | controller: class AppCtrl { 8 | /* @ngInject */ 9 | constructor($state) { 10 | Object.assign(this, { $state }); 11 | } 12 | 13 | $onInit() { 14 | 15 | } 16 | } 17 | }; -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
-------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import './css/style.less'; 2 | require('font-awesome-webpack'); 3 | 4 | import angular from 'angular'; 5 | // import Angular2To1 from 'angular2to1'; 6 | import AngularUiRouter from 'angular-ui-router'; 7 | import AngularAnimate from 'angular-animate'; 8 | import AngularSanitize from 'angular-sanitize'; 9 | import AngularBootstrap from 'angular-ui-bootstrap'; 10 | /*eslint-disable */ 11 | import LocalStorageModule from 'angular-local-storage'; 12 | /*eslint-enable */ 13 | import AppCore from './core'; 14 | import { AppComponent } from './app.component'; 15 | 16 | import Home from './components/home'; 17 | 18 | const appName = 'myApp'; 19 | 20 | angular.module(appName, [ 21 | // framework wide components 22 | AngularUiRouter, 23 | AngularAnimate, 24 | AngularSanitize, 25 | AngularBootstrap, 26 | 27 | // services 28 | 'LocalStorageModule', 29 | AppCore, 30 | 31 | // ui-components 32 | Home 33 | ]) 34 | .config(config) 35 | .component(AppComponent.selector, AppComponent); 36 | 37 | /* @ngInject */ 38 | function config ($stateProvider, $urlRouterProvider, localStorageServiceProvider) { 39 | 40 | localStorageServiceProvider.setPrefix(appName); 41 | 42 | $stateProvider 43 | .state('home', { 44 | url: '/', 45 | component: 'home' 46 | }); 47 | 48 | $urlRouterProvider.otherwise('/'); 49 | } 50 | 51 | angular.element(document).ready(() => { 52 | angular.bootstrap(document, [appName]); 53 | }); 54 | -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orizens/supernova-angular-1.5.x-es6-starter/adabcdf68b10152c9a66f76de0ab19bb7095b5f5/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orizens/supernova-angular-1.5.x-es6-starter/adabcdf68b10152c9a66f76de0ab19bb7095b5f5/src/assets/icon-128x128.png -------------------------------------------------------------------------------- /src/components/home/home.component.js: -------------------------------------------------------------------------------- 1 | import './home.less'; 2 | import template from './home.html'; 3 | 4 | export let HomeComponent = { 5 | templateUrl: template, 6 | selector: 'home', 7 | bindings: {}, 8 | /* @ngInject */ 9 | controller: class HomeCtrl { 10 | /* @ngInject */ 11 | constructor($state) { 12 | // TODO - constructor arguments that should be available on "this" 13 | // should be added to the assign object 14 | Object.assign(this, { $state }); 15 | this.title = 'SuperNova'; 16 | this.note = 'Angular 1.5x, Es6, Karma, Jasmine & Webpack, ui-router'; 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/home/home.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 |
8 | 9 | 10 |
11 |
12 | {{ $ctrl.title }} 13 |
14 |
15 | {{ $ctrl.note }} 16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 |
27 | 49 |
50 | 51 |
52 |
53 |
54 |
55 |

SuperNova Starter

56 |

What's Inside?

57 |
    58 |
  • 59 | Opinionated structure by Angular ES6/ES2015 Style Guide 60 |
  • 61 |
  • Angular 1.5.x
  • 62 |
  • Karma & Jasmine
  • 63 |
  • 64 | Webpack - configured with: 65 |
      66 |
    • ES6
    • 67 |
    • LESS
    • 68 |
    • ng-templates
    • 69 |
    • ng-inject
    • 70 |
    • sourcemaps for development and production
    • 71 |
    • production build
    • 72 |
    • fonts loader
    • 73 |
    74 |
  • 75 |
  • Bootstrap
  • 76 |
  • Font Awesome
  • 77 |
  • npm scripts for development, testing (bdd) and production
  • 78 |
  • modules scaffold with gulp-dogen
  • 79 |
80 |
81 |
82 |
83 |
84 | Template Design By KeenThemes 85 |
favicon made by Madebyoliver from www.flaticon.com is licensed by CC 3.0 BY
86 |
87 | -------------------------------------------------------------------------------- /src/components/home/home.less: -------------------------------------------------------------------------------- 1 | @import url(../../css/core/global.less); 2 | 3 | @media (min-width: 320px) { 4 | .home { 5 | 6 | } 7 | } 8 | 9 | @media (min-width: 768px) { 10 | .home { 11 | 12 | } 13 | } 14 | 15 | /*** 16 | User Profile Sidebar by @keenthemes 17 | A component of Metronic Theme - #1 Selling Bootstrap 3 Admin Theme in Themeforest: http://j.mp/metronictheme 18 | Licensed under MIT 19 | ***/ 20 | 21 | body { 22 | background: #F1F3FA; 23 | } 24 | 25 | /* Profile container */ 26 | .profile { 27 | margin: 20px 0; 28 | } 29 | 30 | /* Profile sidebar */ 31 | .profile-sidebar { 32 | padding: 20px 0 10px 0; 33 | background: #fff; 34 | } 35 | 36 | .profile-userpic img { 37 | float: none; 38 | margin: 0 auto; 39 | width: 50%; 40 | height: 50%; 41 | -webkit-border-radius: 50% !important; 42 | -moz-border-radius: 50% !important; 43 | border-radius: 50% !important; 44 | } 45 | 46 | .profile-usertitle { 47 | text-align: center; 48 | margin-top: 20px; 49 | } 50 | 51 | .profile-usertitle-name { 52 | color: #5a7391; 53 | font-size: 16px; 54 | font-weight: 600; 55 | margin-bottom: 7px; 56 | } 57 | 58 | .profile-usertitle-job { 59 | text-transform: uppercase; 60 | color: #5b9bd1; 61 | font-size: 12px; 62 | font-weight: 600; 63 | margin-bottom: 15px; 64 | } 65 | 66 | .profile-userbuttons { 67 | text-align: center; 68 | margin-top: 10px; 69 | } 70 | 71 | .profile-userbuttons .btn { 72 | text-transform: uppercase; 73 | font-size: 11px; 74 | font-weight: 600; 75 | padding: 6px 15px; 76 | margin-right: 5px; 77 | } 78 | 79 | .profile-userbuttons .btn:last-child { 80 | margin-right: 0px; 81 | } 82 | 83 | .profile-usermenu { 84 | margin-top: 30px; 85 | } 86 | 87 | .profile-usermenu ul li { 88 | border-bottom: 1px solid #f0f4f7; 89 | } 90 | 91 | .profile-usermenu ul li:last-child { 92 | border-bottom: none; 93 | } 94 | 95 | .profile-usermenu ul li a { 96 | color: #93a3b5; 97 | font-size: 14px; 98 | font-weight: 400; 99 | } 100 | 101 | .profile-usermenu ul li a i { 102 | margin-right: 8px; 103 | font-size: 14px; 104 | } 105 | 106 | .profile-usermenu ul li a:hover { 107 | background-color: #fafcfd; 108 | color: #5b9bd1; 109 | } 110 | 111 | .profile-usermenu ul li.active { 112 | border-bottom: none; 113 | } 114 | 115 | .profile-usermenu ul li.active a { 116 | color: #5b9bd1; 117 | background-color: #f6f9fb; 118 | border-left: 2px solid #5b9bd1; 119 | margin-left: -2px; 120 | } 121 | 122 | /* Profile Content */ 123 | .profile-content { 124 | padding: 20px; 125 | background: #fff; 126 | min-height: 460px; 127 | } 128 | -------------------------------------------------------------------------------- /src/components/home/home.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import HomeModule, { HomeComponent } from './index.js'; 3 | // if a mock json object is needed for tests 4 | // import HomeMock from '../../../tests/mocks/home.mock.json'; 5 | 6 | describe('Home Component', () => { 7 | let ctrl; 8 | 9 | beforeEach(window.module(HomeModule)); 10 | 11 | beforeEach(window.inject(($componentController) => { 12 | ctrl = $componentController(HomeComponent.selector, { 13 | $state: {} 14 | }); 15 | })); 16 | 17 | it('should have a title', () => { 18 | const expected = 'SuperNova'; 19 | const actual = ctrl.title; 20 | expect(actual).toMatch(expected); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/components/home/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import uiRouter from 'angular-ui-router'; 3 | import AppCore from '../../core'; 4 | import { HomeComponent } from './home.component'; 5 | 6 | export * from './home.component'; 7 | 8 | export default angular.module('home', [ 9 | AppCore, 10 | uiRouter 11 | ]) 12 | .config(config) 13 | .component(HomeComponent.selector, HomeComponent) 14 | .name; 15 | // .config(config); 16 | 17 | /* @ngInject */ 18 | function config ($stateProvider) { 19 | // $stateProvider 20 | // .state('home', { 21 | // url: '/home', 22 | // template: '' 23 | // }); 24 | } 25 | -------------------------------------------------------------------------------- /src/config/dev.config.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | // TODO - make app name a nodejs ENV variable 4 | angular.module('myApp') 5 | .config(config); 6 | 7 | /* @ngInject */ 8 | function config ($compileProvider) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/config/production.config.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | angular.module('myApp') 4 | .config(config); 5 | 6 | /* @ngInject */ 7 | function config ($compileProvider) { 8 | $compileProvider.debugInfoEnabled(false); 9 | } 10 | -------------------------------------------------------------------------------- /src/config/test.config.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | angular.module('myApp') 4 | .config(config); 5 | 6 | /* @ngInject */ 7 | function config ($compileProvider) { 8 | $compileProvider.debugInfoEnabled(true); 9 | } 10 | -------------------------------------------------------------------------------- /src/core/components/e-dropdown/e-dropdown.component.js: -------------------------------------------------------------------------------- 1 | import './e-dropdown.less'; 2 | import template from './e-dropdown.tpl.html'; 3 | 4 | // Usage: 5 | // 8 | // Creates: 9 | // 10 | export let eDropdown = { 11 | selector: 'eDropdown', 12 | replace: true, 13 | templateUrl: template, 14 | bindings: { 15 | label: '@', 16 | icon: '@', 17 | items: '<', 18 | onSelect: '&', 19 | selected: '@' 20 | }, 21 | controllerAs: 'vm', 22 | /* @ngInject */ 23 | controller: function() { 24 | // var vm = this; 25 | this.activeIndex = this.selected !== '' ? parseInt(this.selected) : 0; 26 | this.handleClick = handleClick; 27 | this.status = { 28 | isOpen: false 29 | }; 30 | this.displayLabel = this.items[this.activeIndex]; 31 | 32 | function handleClick(item, $index) { 33 | this.activeIndex = $index; 34 | this.displayLabel = this.items[this.activeIndex]; 35 | this.onSelect({ 36 | item: item, 37 | index: $index 38 | }); 39 | } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/core/components/e-dropdown/e-dropdown.less: -------------------------------------------------------------------------------- 1 | .e-dropdown { 2 | padding: 10px 15px; 3 | color: #777; 4 | } 5 | .uib-dropdown-menu { 6 | li { 7 | cursor: pointer; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/core/components/e-dropdown/e-dropdown.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import eDropdownModule from './index.js'; 3 | // import eDropdownComponent from './e-dropdown.component'; 4 | 5 | describe('Unit: dropdown directive - ', () => { 6 | let element, scope, compile, find; 7 | let dropdownHtml = ` 8 | 13 | `; 14 | beforeEach(window.module(eDropdownModule)); 15 | 16 | beforeEach(window.inject(($compile, $rootScope) => { 17 | compile = $compile; 18 | scope = $rootScope.$new(); 19 | scope.onPresetChange = (item) => item; 20 | scope.presets = ['All', 'Albums', 'Live']; 21 | element = angular.element(dropdownHtml); 22 | $compile(element)(scope); 23 | find = (s) => element[0].querySelectorAll(s); 24 | scope.$digest(); 25 | })); 26 | 27 | it('should render a dropdown element', () => { 28 | const actual = find('.e-dropdown').length; 29 | const expected = 1; 30 | expect(actual).toEqual(expected); 31 | }); 32 | 33 | it('should render items if given presets', () => { 34 | expect(find('li').length).toBe(scope.presets.length); 35 | }); 36 | 37 | it('should render a "tag" icon', () => { 38 | expect(find('i[class*="-tag"]').length).toBe(1); 39 | }); 40 | 41 | it('should render the label according to the "label" attribute', () => { 42 | expect(find('.dropdown-toggle')[0].innerText.trim()).toBe('Preset'); 43 | }); 44 | 45 | it('should call a function when select has changed', () => { 46 | spyOn(scope, 'onPresetChange'); 47 | element.isolateScope().vm.handleClick([scope.presets[0], 0]); 48 | expect(scope.onPresetChange).toHaveBeenCalled(); 49 | }); 50 | 51 | it('should call a function with the selected item when select has changed', () => { 52 | spyOn(scope, 'onPresetChange'); 53 | element.isolateScope().vm.handleClick([scope.presets[0], 0]); 54 | expect(scope.onPresetChange).toHaveBeenCalledWith([scope.presets[0], 0]); 55 | }); 56 | 57 | it('should set the selected item as active', () => { 58 | let index = 1; 59 | element.isolateScope().vm.handleClick(scope.presets[index], index); 60 | scope.$digest(); 61 | expect(element.find('li').eq(index).hasClass('active')).toBeTruthy(); 62 | }); 63 | 64 | it('should set a predefined selected index from attribute', () => { 65 | const dropdownWithSelectedIndex = dropdownHtml; 66 | element = angular.element(dropdownWithSelectedIndex); 67 | element[0].setAttribute('selected', 1); 68 | compile(element)(scope); 69 | scope.$digest(); 70 | expect(element.find('li').eq(1).hasClass('active')).toBeTruthy(); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/core/components/e-dropdown/e-dropdown.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 14 |
15 | -------------------------------------------------------------------------------- /src/core/components/e-dropdown/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import { eDropdown } from './e-dropdown.component'; 3 | 4 | export default angular.module('eDropdown', []) 5 | .component(eDropdown.selector, eDropdown) 6 | .name; 7 | -------------------------------------------------------------------------------- /src/core/components/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import eDropdown from './e-dropdown'; 3 | 4 | export default angular.module('core.components', [ 5 | eDropdown 6 | ]); 7 | -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import components from './components'; 3 | import services from './services'; 4 | 5 | export default angular.module('app.core', [ 6 | components.name, 7 | services.name 8 | ]) 9 | .name; 10 | -------------------------------------------------------------------------------- /src/core/services/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | // uncomment if you need a local storage solution 3 | // import LocalStorageModule from 'angular-local-storage'; 4 | import SomeService from './some.service.srv.js'; 5 | import OtherService from './other.service.srv.js'; 6 | 7 | export default angular 8 | .module('core.services', [ 9 | // 'LocalStorageModule' 10 | ]) 11 | .factory('SomeService', SomeService) 12 | .service('OtherService', OtherService) 13 | ; 14 | -------------------------------------------------------------------------------- /src/core/services/other.service.srv.js: -------------------------------------------------------------------------------- 1 | export default class OtherService { 2 | constructor() { 3 | this.data = { 4 | url: 'some-url.com' 5 | }; 6 | } 7 | 8 | fetch () { 9 | return this.data.url; 10 | } 11 | } -------------------------------------------------------------------------------- /src/core/services/some.service.srv.js: -------------------------------------------------------------------------------- 1 | /* @ngInject */ 2 | export default function SomeService ($http) { 3 | const url = 'https://www.googleapis.com/youtube/v3/search'; 4 | const exports = { 5 | search 6 | }; 7 | 8 | return exports; 9 | 10 | /////////////// 11 | 12 | function search (query){ 13 | return $http.get(url, { 14 | params: { q: query } 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/core/services/some.service.srv.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import HomeMockJson from '../../../tests/mocks/home.mock'; 3 | import CoreServicesModule from './index.js'; 4 | 5 | describe('Some Service', () => { 6 | let httpBackend, someService; 7 | 8 | beforeEach(window.module(CoreServicesModule.name)); 9 | 10 | beforeEach(window.inject(($controller, $injector, $httpBackend) => { 11 | someService = $injector.get('SomeService'); 12 | httpBackend = $httpBackend; 13 | httpBackend 14 | .whenGET(/www.googleapis.com/) 15 | .respond(HomeMockJson); 16 | // add spies here 17 | })); 18 | 19 | it('should have a search function', () => { 20 | expect(someService.search).toBeDefined(); 21 | }); 22 | 23 | it('should open a get request when search', () => { 24 | someService.search(); 25 | httpBackend.flush(); 26 | httpBackend.expectGET(); 27 | }); 28 | 29 | it('should search with a query', () => { 30 | const query = 'music albums'; 31 | const expected = new RegExp(`q=${query}`); 32 | someService.search(query); 33 | httpBackend.flush(); 34 | httpBackend.expectGET(expected); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/css/core/global.less: -------------------------------------------------------------------------------- 1 | @import url(../echoes-variables.less); 2 | 3 | .transform(@prop) { 4 | -webkit-transform: @prop; 5 | -moz-transform: @prop; 6 | transform: @prop; 7 | } 8 | 9 | .active-link-style(@color: #555, @bg: #e5e5e5, @shadow: rgba(0,0,0,0.125)){ 10 | color: @color; 11 | text-decoration: none; 12 | background-color: @bg; 13 | box-shadow: inset 0 3px 8px @shadow; 14 | } -------------------------------------------------------------------------------- /src/css/core/utils.less: -------------------------------------------------------------------------------- 1 | /* applying a nicer UX*/ 2 | .nicer-ux *, 3 | .ux-maker { 4 | -webkit-transition: all 0.3s ease-out; 5 | -moz-transition: all 0.3s ease-out; 6 | -ms-transition: all 0.3s ease-out; 7 | -o-transition: all 0.3s ease-out; 8 | transition: all 0.3s ease-out; 9 | } 10 | 11 | .ellipsis { 12 | white-space: nowrap; 13 | overflow: hidden; 14 | text-overflow: ellipsis; 15 | } 16 | 17 | .btn-transparent { 18 | background-color: transparent !important; 19 | } 20 | .text-primary { 21 | color: @brand-primary !important; 22 | } 23 | -------------------------------------------------------------------------------- /src/css/echoes-variables.less: -------------------------------------------------------------------------------- 1 | // bootstrap variables for flat design 2 | // 3 | // Variables 4 | // -------------------------------------------------- 5 | 6 | // == Colors 7 | // 8 | //## 9 | 10 | // Color swatches 11 | @turquoise: #1abc9c; 12 | @green-sea: #16a085; 13 | 14 | @emerald: #2ecc71; 15 | @nephritis: #27ae60; 16 | 17 | @peter-river: #3498db; 18 | @belize-hole: #2980b9; 19 | 20 | @amethyst: #9b59b6; 21 | @wisteria: #8e44ad; 22 | 23 | @wet-asphalt: #34495e; 24 | @midnight-blue: #2c3e50; 25 | 26 | @sun-flower: #f1c40f; 27 | @orange: #f39c12; 28 | 29 | @carrot: #e67e22; 30 | @pumpkin: #d35400; 31 | 32 | @alizarin: #e74c3c; 33 | @pomegranate: #c0392b; 34 | 35 | @clouds: #ecf0f1; 36 | @silver: #bdc3c7; 37 | 38 | @concrete: #95a5a6; 39 | @asbestos: #7f8c8d; 40 | 41 | // Grays 42 | // @gray: @concrete; 43 | // @gray-light: @silver; 44 | @inverse: white; 45 | 46 | @gray-darker: lighten(#000, 13.5%); // #222 47 | @gray-dark: lighten(#000, 20%); // #333 48 | @gray: lighten(#000, 33.5%); // #555 49 | @gray-light: lighten(#000, 46.7%); // #777 50 | @gray-lighter: lighten(#000, 93.5%); // #eee 51 | @dark: #000; 52 | // Brand colors 53 | @brand-primary: @turquoise; 54 | @brand-secondary: @green-sea; 55 | @brand-success: @emerald; 56 | @brand-warning: @sun-flower; 57 | @brand-danger: @alizarin; 58 | @brand-info: @peter-river; 59 | 60 | 61 | //== Scaffolding 62 | // 63 | //## Settings for some of the most global styles. 64 | 65 | @body-bg: #ecf0f1; 66 | @text-color: @gray-dark; 67 | 68 | //** Global textual link color. 69 | @link-color: @brand-primary; 70 | @link-hover-color: @turquoise; 71 | 72 | 73 | //== Typography 74 | // 75 | //## Font, line-height for body text, headings, and more. 76 | 77 | @font-family-base: 'Open Sans', Helvetica, Arial, sans-serif; 78 | @font-family-demo: "Helvetica Neue", Helvetica, Arial, sans-serif; 79 | @font-family-monospace: Monaco, Menlo, Consolas, "Courier New", monospace; 80 | @font-family-narrow: 'Open Sans Condensed', 'Open Sans', Helvetica; 81 | @font-size-base: 14px; 82 | 83 | @font-size-h1: floor((@font-size-base * 2.6)); // ~36px 84 | @font-size-h2: floor((@font-size-base * 2.15)); // ~30px 85 | @font-size-h3: ceil((@font-size-base * 1.7)); // ~24px 86 | @font-size-h4: ceil((@font-size-base * 1.25)); // ~18px 87 | @font-size-h5: @font-size-base; 88 | @font-size-h6: ceil((@font-size-base * 0.85)); // ~12px 89 | 90 | @line-height-base: 1.428571429; // 20/14 91 | @line-height-computed: floor(@font-size-base * @line-height-base); // ~31px 92 | 93 | @headings-font-family: inherit; 94 | @headings-font-weight: 700; 95 | @headings-line-height: 1.1; 96 | @headings-color: inherit; 97 | 98 | 99 | //== Iconography 100 | // 101 | //## Specify custom locations of the include Glyphicons icon font. 102 | 103 | @icon-font-path: "../fonts/"; 104 | @icon-font-name: "glyphicons-halflings-regular"; 105 | @icon-font-svg-id: "glyphicons_halflingsregular"; 106 | 107 | //** Icon sizes for use in components 108 | @icon-normal: 16px; 109 | @icon-medium: 18px; 110 | @icon-large: 32px; 111 | 112 | 113 | //== Components 114 | // 115 | //## Define common padding and border radius sizes and more. 116 | @padding-base-vertical: 0px; 117 | @padding-base-horizontal: 6px; 118 | 119 | @padding-large-vertical: 10px; 120 | @padding-large-horizontal: 16px; 121 | 122 | @padding-small-vertical: 5px; 123 | @padding-small-horizontal: 10px; 124 | 125 | @padding-xs-vertical: 1px; 126 | @padding-xs-horizontal: 5px; 127 | 128 | @line-height-large: 1.33; 129 | @line-height-small: 1.5; 130 | 131 | @border-radius-base: 1px; 132 | @border-radius-large: 3px; 133 | @border-radius-small: 0px; 134 | 135 | //** Default font-size in components 136 | @component-font-size-base: ceil(@font-size-base * 0.833); // ~15px 137 | 138 | // Border-radius 139 | @border-radius-base: 4px; 140 | @border-radius-large: 6px; 141 | @border-radius-small: 3px; 142 | 143 | 144 | //== Buttons 145 | // 146 | //## For each of Flat UI's buttons, define text, background, font size and height. 147 | 148 | @btn-font-size-base: @component-font-size-base; 149 | @btn-font-size-xs: ceil(@component-font-size-base * 0.80); // ~12px 150 | @btn-font-size-sm: floor(@component-font-size-base * 0.867); // ~13px 151 | @btn-font-size-lg: ceil(@component-font-size-base * 1.133); // ~17px 152 | @btn-font-size-hg: floor(@component-font-size-base * 1.467); // ~22px 153 | 154 | @btn-line-height-base: 1.4; // ~21px 155 | @btn-line-height-hg: 1.227; // ~27px 156 | @btn-line-height-lg: 1.471; // ~25px 157 | @btn-line-height-sm: 1.385; // ~16px 158 | @btn-line-height-xs: 1.083; // ~13px 159 | 160 | @btn-social-font-size-base: floor(@component-font-size-base * 0.867); // ~13px 161 | @btn-social-line-height-base: 1.077; // ~14px 162 | 163 | @btn-font-weight: normal; 164 | 165 | @btn-default-color: #333; 166 | @btn-default-bg: #fff; 167 | @btn-default-border: #ccc; 168 | 169 | @btn-primary-color: #fff; 170 | @btn-primary-bg: @brand-primary; 171 | @btn-primary-border: darken(@btn-primary-bg, 5%); 172 | 173 | @btn-success-color: #fff; 174 | @btn-success-bg: @brand-success; 175 | @btn-success-border: darken(@btn-success-bg, 5%); 176 | 177 | @btn-info-color: #fff; 178 | @btn-info-bg: @brand-info; 179 | @btn-info-border: darken(@btn-info-bg, 5%); 180 | 181 | @btn-warning-color: #fff; 182 | @btn-warning-bg: @brand-warning; 183 | @btn-warning-border: darken(@btn-warning-bg, 5%); 184 | 185 | @btn-danger-color: #fff; 186 | @btn-danger-bg: @brand-danger; 187 | @btn-danger-border: darken(@btn-danger-bg, 5%); 188 | 189 | @btn-link-disabled-color: @gray-light; 190 | 191 | 192 | //== Forms 193 | // 194 | //## 195 | 196 | @input-font-size-base: @component-font-size-base; 197 | @input-font-size-small: floor(@component-font-size-base * 0.867); // ~13px 198 | @input-font-size-large: ceil(@component-font-size-base * 1.133); // ~17px 199 | @input-font-size-huge: floor(@component-font-size-base * 1.467); // ~22px 200 | 201 | @input-line-height-base: 1.467; // ~22px 202 | @input-line-height-small: 1.462; // ~19px 203 | @input-line-height-large: 1.235; // ~21px 204 | @input-line-height-huge: 1.318; // ~29px 205 | 206 | @input-icon-font-size: ceil(@component-font-size-base * 1.333); // ~20px 207 | 208 | @input-bg: @inverse; 209 | @input-bg-disabled: mix(@gray, white, 10%); 210 | 211 | @input-height-small: 35px; 212 | @input-height-base: 41px; 213 | @input-height-large: 45px; 214 | @input-height-huge: 53px; 215 | 216 | @input-border-radius: @border-radius-large; 217 | 218 | @legend-color: inherit; 219 | 220 | 221 | //== Forms 222 | // 223 | //## 224 | 225 | @input-font-size-base: @component-font-size-base; 226 | @input-font-size-small: floor(@component-font-size-base * 0.867); // ~13px 227 | @input-font-size-large: ceil(@component-font-size-base * 1.133); // ~17px 228 | @input-font-size-huge: floor(@component-font-size-base * 1.467); // ~22px 229 | 230 | @input-line-height-base: 1.467; // ~22px 231 | @input-line-height-small: 1.462; // ~19px 232 | @input-line-height-large: 1.235; // ~21px 233 | @input-line-height-huge: 1.318; // ~29px 234 | 235 | @input-icon-font-size: ceil(@component-font-size-base * 1.333); // ~20px 236 | 237 | @input-bg: @inverse; 238 | @input-bg-disabled: mix(@gray, white, 10%); 239 | 240 | @input-height-small: 35px; 241 | @input-height-base: 41px; 242 | @input-height-large: 45px; 243 | @input-height-huge: 53px; 244 | 245 | @input-border-radius: @border-radius-large; 246 | 247 | @legend-color: inherit; 248 | 249 | 250 | //== Pagination 251 | // 252 | //## 253 | 254 | @pagination-color: mix(@brand-primary, white, 20%); 255 | 256 | 257 | //== Pager 258 | // 259 | //## 260 | 261 | @pager-padding: 9px 15px 10px; 262 | 263 | 264 | //== Navbar 265 | // 266 | //## 267 | 268 | // Basics of a navbar 269 | @zindex-navbar: 1000; 270 | @zindex-dropdown: 1000; 271 | @zindex-popover: 1060; 272 | @zindex-tooltip: 1070; 273 | @zindex-navbar-fixed: 1030; 274 | @zindex-modal-background: 1040; 275 | @zindex-modal: 1050; 276 | 277 | @navbar-height-base: 53px; 278 | @navbar-height-large: 76px; 279 | @navbar-input-line-height: 1.4; // ~21px 280 | @navbar-margin-bottom: @line-height-computed; 281 | @navbar-border-radius: @border-radius-small; 282 | 283 | @navbar-height: 40px; 284 | // @navbar-border-radius: @border-radius-base; 285 | @navbar-padding-horizontal: floor((@grid-gutter-width / 2)) - 2; 286 | @navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2); 287 | // @navbar-collapse-max-height: 340px; 288 | 289 | @navbar-default-color: #777; 290 | @navbar-default-bg: #f8f8f8; 291 | @navbar-default-border: darken(@navbar-default-bg, 6.5%); 292 | 293 | // Navbar links 294 | @navbar-default-link-color: #777; 295 | @navbar-default-link-hover-color: #333; 296 | @navbar-default-link-hover-bg: transparent; 297 | @navbar-default-link-active-color: #555; 298 | @navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%); 299 | @navbar-default-link-disabled-color: #ccc; 300 | @navbar-default-link-disabled-bg: transparent; 301 | 302 | // Navbar brand label 303 | @navbar-default-brand-color: @navbar-default-link-color; 304 | @navbar-default-brand-hover-color: darken(@navbar-default-brand-color, 10%); 305 | @navbar-default-brand-hover-bg: transparent; 306 | 307 | // Navbar toggle 308 | @navbar-default-toggle-hover-bg: #ddd; 309 | @navbar-default-toggle-icon-bar-bg: #888; 310 | @navbar-default-toggle-border-color: #ddd; 311 | 312 | // customed 313 | // Navbar nav carets 314 | @navbar-default-caret-color: @navbar-default-link-color; 315 | @navbar-default-caret-hover-color: @navbar-default-link-hover-color; 316 | @navbar-default-caret-active-color: @navbar-default-link-active-color; 317 | 318 | // Navbar form 319 | @navbar-default-form-placeholder: spin(tint(@brand-primary, 60%), 2); 320 | @navbar-default-form-icon: desaturate(tint(@brand-primary, 45%), 2%); 321 | @navbar-default-form-border: shade(@navbar-default-bg, 3%); 322 | 323 | 324 | // Inverted navbar 325 | // Reset inverted navbar basics 326 | @navbar-inverse-divider: darken(@brand-primary, 3%); 327 | 328 | // Reset inverted navbar basics 329 | @navbar-inverse-color: @gray-light; 330 | @navbar-inverse-bg: #222; 331 | @navbar-inverse-border: darken(@navbar-inverse-bg, 10%); 332 | 333 | // Inverted navbar links 334 | @navbar-inverse-link-color: @inverse; 335 | @navbar-inverse-link-hover-color: @brand-secondary; 336 | @navbar-inverse-link-hover-bg: transparent; 337 | @navbar-inverse-link-active-color: @navbar-inverse-link-color; 338 | @navbar-inverse-link-active-bg: @brand-secondary; 339 | @navbar-inverse-link-disabled-color: #444; 340 | @navbar-inverse-link-disabled-bg: transparent; 341 | 342 | // Navbar nav carets 343 | @navbar-inverse-caret-color: lighten(desaturate(@brand-primary, 7%), 9%); 344 | @navbar-inverse-caret-hover-color: @navbar-inverse-link-hover-color; 345 | @navbar-inverse-caret-active-color: @navbar-inverse-link-active-color; 346 | 347 | // Inverted navbar brand label 348 | @navbar-inverse-brand-color: @navbar-inverse-link-color; 349 | @navbar-inverse-brand-hover-color: @navbar-inverse-link-hover-color; 350 | @navbar-inverse-brand-hover-bg: transparent; 351 | 352 | // Inverted navbar toggle 353 | @navbar-inverse-toggle-color: @navbar-inverse-link-color; 354 | @navbar-inverse-toggle-hover-color: @navbar-inverse-link-hover-color; 355 | 356 | // Navbar form 357 | @navbar-inverse-form-bg: darken(@brand-primary, 6%); 358 | @navbar-inverse-form-placeholder: desaturate(lighten(@brand-primary, 13%), 7%); 359 | @navbar-inverse-form-icon: desaturate(lighten(@brand-primary, 13%), 6%); 360 | @navbar-inverse-form-border: @navbar-inverse-divider; 361 | 362 | // Dropdown menu 363 | @navbar-inverse-dropdown-arrow: @navbar-inverse-bg; 364 | @navbar-inverse-dropdown-bg: @navbar-inverse-bg; 365 | @navbar-inverse-dropdown-link-color: mix(@navbar-inverse-bg, @navbar-inverse-color, 15%); 366 | @navbar-inverse-dropdown-link-hover-color: @inverse; 367 | @navbar-inverse-dropdown-link-hover-bg: @brand-secondary; 368 | 369 | 370 | //== Dropdown Menu 371 | // 372 | //## 373 | @bg-dark-color: #323538; 374 | @dropdown-background: @bg-dark-color; 375 | @dropdown-bg: @bg-dark-color; 376 | @dropdown-link-hover-color: @inverse; 377 | @dropdown-link-hover-bg: @brand-primary; 378 | @dropdown-link-color: @inverse; 379 | // @dropdown-fallback-border; 380 | // @dropdown-border; 381 | // @border-radius-base; 382 | 383 | 384 | //== Iconbar 385 | // 386 | //## 387 | 388 | @iconbar-background: mix(@brand-primary, black, 85%); 389 | 390 | 391 | //== Progress bars 392 | // 393 | //## 394 | 395 | @progress-height: 12px; 396 | 397 | 398 | //== Slider 399 | // 400 | //## 401 | 402 | @slider-height: 12px; 403 | @slider-value-font-size: floor(@component-font-size-base * 0.867); // ~13px; 404 | 405 | @slider-handle-bg: mix(@brand-secondary, black, 85%); 406 | @slider-handle-hover-bg: mix(@brand-secondary, white, 80%); 407 | @slider-handle-active-bg: mix(@brand-secondary, black, 85%); 408 | 409 | @slider-range-bg: @brand-secondary; 410 | 411 | @slider-segment-bg: mix(desaturate(@brand-primary, 15%), white, 20%); 412 | 413 | 414 | //== Switch 415 | // 416 | //## 417 | 418 | @switch-border-radius: 30px; 419 | @switch-width: 80px; 420 | 421 | 422 | //== Thumbnails 423 | // 424 | //## 425 | 426 | //** Padding around the thumbnail image 427 | @thumbnail-padding: 4px; 428 | //** Thumbnail background color 429 | @thumbnail-bg: @body-bg; 430 | //** Thumbnail border color 431 | @thumbnail-border: @gray-light; 432 | //** Thumbnail border radius 433 | @thumbnail-border-radius: @border-radius-large; 434 | 435 | //** Custom text color for thumbnail captions 436 | @thumbnail-caption-color: @text-color; 437 | //** Padding around the thumbnail caption 438 | @thumbnail-caption-padding: 9px; 439 | 440 | 441 | //== Media queries breakpoints 442 | // 443 | //## Define the breakpoints at which your layout will change, adapting to different screen sizes. 444 | 445 | // Extra small screen / phone 446 | @screen-xs-min: 480px; 447 | 448 | // Small screen / tablet 449 | @screen-sm-min: 768px; 450 | 451 | // Medium screen / desktop 452 | @screen-md-min: 992px; 453 | 454 | // Large screen / wide desktop 455 | @screen-lg-min: 1200px; 456 | 457 | // So media queries don't overlap when required, provide a maximum 458 | @screen-xs-max: (@screen-sm-min - 1); 459 | @screen-sm-max: (@screen-md-min - 1); 460 | @screen-md-max: (@screen-lg-min - 1); 461 | 462 | 463 | //== Grid system 464 | // 465 | //## Define your custom responsive grid. 466 | 467 | //** Number of columns in the grid. 468 | @grid-columns: 12; 469 | //** Padding between columns. Gets divided in half for the left and right. 470 | @grid-gutter-width: 2rem; 471 | // Navbar collapse 472 | //** Point at which the navbar becomes uncollapsed. 473 | @grid-float-breakpoint: @screen-sm-min; 474 | //** Point at which the navbar begins collapsing. 475 | @grid-float-breakpoint-max: (@grid-float-breakpoint - 1); 476 | 477 | // Form states and alerts 478 | // 479 | //## Define colors for form feedback states and, by default, alerts. 480 | 481 | @state-success-text: @brand-success; 482 | @state-success-bg: #dff0d8; 483 | @state-success-border: darken(spin(@state-success-bg, -10), 5%); 484 | 485 | @state-info-text: @brand-info; 486 | @state-info-bg: #d9edf7; 487 | @state-info-border: darken(spin(@state-info-bg, -10), 7%); 488 | 489 | @state-warning-text: @brand-warning; 490 | @state-warning-bg: #fcf8e3; 491 | @state-warning-border: darken(spin(@state-warning-bg, -10), 5%); 492 | 493 | @state-danger-text: @brand-danger; 494 | @state-danger-bg: #f2dede; 495 | @state-danger-border: darken(spin(@state-danger-bg, -10), 5%); 496 | 497 | 498 | // Code 499 | // 500 | //## 501 | 502 | @code-color: #c7254e; 503 | @code-bg: #f9f2f4; 504 | 505 | @kbd-color: @inverse; 506 | @kbd-bg: @brand-primary; 507 | 508 | @pre-bg: @inverse; 509 | @pre-color: inherit; 510 | @pre-border-color: mix(@brand-primary, @inverse, 12%); 511 | @pre-scrollable-max-height: 340px; 512 | @pre-border-radius: @border-radius-large; 513 | 514 | 515 | // Type 516 | // 517 | //## 518 | 519 | //** Text muted color 520 | @text-muted: @gray-light; 521 | //** Abbreviations and acronyms border color 522 | @abbr-border-color: @gray-light; 523 | //** Headings small color 524 | @headings-small-color: mix(@brand-primary, @inverse, 12%); 525 | //** Blockquote small color 526 | @blockquote-small-color: inherit; 527 | //** Blockquote border color 528 | @blockquote-border-color: mix(@brand-primary, @inverse, 12%); 529 | //** Page header border color 530 | @page-header-border-color: mix(@brand-primary, @inverse, 12%); 531 | 532 | 533 | // Miscellaneous 534 | // 535 | //## 536 | 537 | //** Hr border color 538 | @hr-border: mix(@brand-primary, @inverse, 63%); 539 | 540 | //** Horizontal forms & lists 541 | @component-offset-horizontal: 180px; 542 | 543 | @drawer-width: 29.5rem; -------------------------------------------------------------------------------- /src/css/layout/container.less: -------------------------------------------------------------------------------- 1 | @import url(../echoes-variables.less); 2 | 3 | @media (min-width: 320px) { 4 | .container { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/css/layout/navbar.less: -------------------------------------------------------------------------------- 1 | @import url(../core/global.less); 2 | 3 | @media (min-width: 320px) { 4 | .navbar { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/css/layout/sidebar.less: -------------------------------------------------------------------------------- 1 | @import url(../echoes-variables.less); 2 | 3 | @media (min-width: 320px) { 4 | .sidebar { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/css/style.less: -------------------------------------------------------------------------------- 1 | /* ===== Primary Styles ======================================================== 2 | Author: Oren Farhi, http://orizens.com 3 | ========================================================================== */ 4 | /* Application */ 5 | @import url(../../node_modules/bootstrap/less/bootstrap.less); 6 | // @import url(../../node_modules/font-awesome/less/font-awesome.less); 7 | @import url(./core/global.less); 8 | @import url(./core/utils.less); 9 | // @fa-font-path: "/"; 10 | 11 | @import url(./layout/container.less); 12 | @import url(./layout/navbar.less); 13 | @import url(./layout/sidebar.less); 14 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | My App 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
loading...
32 |
33 | 34 | 35 | 47 | 48 | -------------------------------------------------------------------------------- /tests/e2e/app.spec.js: -------------------------------------------------------------------------------- 1 | describe('My App', () => { 2 | it('should search and display results', () => { 3 | const actual = {}; 4 | const expected = {}; 5 | expect(actual).toBe(expected); 6 | }); 7 | }); -------------------------------------------------------------------------------- /tests/mocks/home.mock.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'title': 'feels like home' 3 | }; 4 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ngAnnotatePlugin = require('ng-annotate-webpack-plugin'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const getPath = (pathToFile) => path.resolve(__dirname, pathToFile); 7 | 8 | module.exports = { 9 | devtool: 'inline-source-map', 10 | entry: { 11 | app: [ 12 | getPath('./src/app.js'), 13 | getPath('./src/config/dev.config.js') 14 | ], 15 | vendors: [ 16 | 'angular', 17 | 'angular-ui-router', 18 | 'angular-animate', 19 | 'angular-sanitize', 20 | 'angular-ui-bootstrap', 21 | 'angular-local-storage' 22 | ] 23 | }, 24 | output: { 25 | path: getPath('./dist'), 26 | filename: '[name].[hash].bundle.js', 27 | sourceMapFilename: '[name].[hash].bundle.map' 28 | }, 29 | 30 | resolve: { 31 | modulesDirectories: [ 32 | 'node_modules', 'src/component', 'src/core', 'src/css' 33 | ], 34 | extensions: [ '', '.js', 'less', 'html' ] 35 | }, 36 | module: { 37 | loaders: [{ 38 | test: /\.js$/, 39 | exclude: /(node_modules)/, 40 | loaders: ['babel'] 41 | }, { 42 | test: /\.html$/, 43 | loader: 'ngtemplate!html', 44 | exclude: /(index)/ 45 | }, { 46 | test: /\.less$/, 47 | loader: ExtractTextPlugin.extract('css?sourceMap!' + 'less?sourceMap') 48 | }, 49 | // FONTS 50 | { 51 | test: /\.woff$/, 52 | loader: 'url?limit=100000&name=./fonts/[name]/[hash].[ext]' 53 | }, { 54 | test: /\.eot$/, 55 | loader: 'file' 56 | }, { 57 | test: /\.svg$/, 58 | loader: 'url?limit=100000&name=./fonts/[name]/[hash].[ext]' 59 | }, 60 | // the url-loader uses DataUrls. 61 | // the file-loader emits files. 62 | { 63 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 64 | loader: 'url?limit=10000&minetype=application/font-woff' 65 | }, { 66 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 67 | loader: 'file' 68 | }, 69 | { 70 | test: /\.(jpg|png|gif)$/, 71 | loader: 'file' 72 | }] 73 | }, 74 | 75 | plugins: [ 76 | // this is a performance hit by ~10 seconds 77 | // new ngAnnotatePlugin({ 78 | // add: true 79 | // // other ng-annotate options here 80 | // }), 81 | new webpack.optimize.CommonsChunkPlugin({ 82 | name: 'vendors', 83 | fileName: 'vendors.[hash].js', 84 | minChunks: Infinity 85 | }), 86 | new webpack.optimize.AggressiveMergingPlugin({}), 87 | new webpack.optimize.OccurenceOrderPlugin(true), 88 | new ExtractTextPlugin('[name].[hash].style.css'), 89 | // HtmlWebpackPlugin 90 | // See: https://github.com/ampedandwired/html-webpack-plugin 91 | new HtmlWebpackPlugin({ 92 | template: 'html!./src/index.html' 93 | }) 94 | ] 95 | }; 96 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ngAnnotatePlugin = require('ng-annotate-webpack-plugin'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 7 | const getPath = (pathToFile) => path.resolve(__dirname, pathToFile); 8 | 9 | module.exports = { 10 | devtool: 'source-map', 11 | entry: { 12 | app: [ 13 | getPath('./src/app.js'), 14 | getPath('./src/config/production.config.js') 15 | ], 16 | vendors: [ 17 | 'angular', 18 | 'angular-ui-router', 19 | 'angular-animate', 20 | 'angular-sanitize', 21 | 'angular-ui-bootstrap', 22 | 'angular-local-storage' 23 | ] 24 | }, 25 | output: { 26 | path: getPath('./dist'), 27 | filename: '[name].[chunkhash].bundle.js' 28 | }, 29 | module: { 30 | loaders: [{ 31 | test: /\.js$/, 32 | exclude: /(node_modules)/, 33 | loaders: ['babel'] 34 | }, { 35 | test: /\.html$/, 36 | loader: 'ngtemplate!html', 37 | exclude: /(index)/ 38 | }, { 39 | test: /\.less$/, 40 | loader: ExtractTextPlugin.extract('css?sourceMap!less?sourceMap') 41 | }, 42 | // FONTS 43 | { 44 | test: /\.woff$/, 45 | loader: 'url?limit=100000&name=./fonts/[name]/[hash].[ext]' 46 | }, { 47 | test: /\.eot$/, 48 | loader: 'file' 49 | }, { 50 | test: /\.svg$/, 51 | loader: 'url?limit=100000&name=./fonts/[name]/[hash].[ext]' 52 | }, 53 | // the url-loader uses DataUrls. 54 | // the file-loader emits files. 55 | { 56 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 57 | loader: 'url?limit=10000&minetype=application/font-woff' 58 | }, { 59 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 60 | loader: 'file' 61 | }, { 62 | test: /\.(jpg|png|gif)$/, 63 | loader: 'file' 64 | } 65 | ]}, 66 | plugins: [ 67 | new ngAnnotatePlugin({ 68 | add: true 69 | // other ng-annotate options here 70 | }), 71 | new webpack.optimize.CommonsChunkPlugin('vendors', 72 | '[name].[chunkhash].vendors.js'), 73 | new ExtractTextPlugin('[name].[chunkhash].style.css'), 74 | // See: https://github.com/ampedandwired/html-webpack-plugin 75 | new HtmlWebpackPlugin({ 76 | template: 'html!./src/index.html' 77 | }), 78 | new CopyWebpackPlugin([{ 79 | from: 'config/dist-assets/CNAME' 80 | }, { 81 | from: 'config/dist-assets/manifest.json' 82 | }, { 83 | from: 'src/assets', 84 | to: 'assets' 85 | }]) 86 | ] 87 | }; 88 | --------------------------------------------------------------------------------