diff --git a/.bowerrc b/.bowerrc
new file mode 100644
index 0000000..69fad35
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "bower_components"
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e717f5e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..5a33f9f
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,13 @@
+{
+ "extends": "eslint:recommended",
+ "plugins": ["angular"],
+ "env": {
+ "browser": true,
+ "jasmine": true
+ },
+ "globals": {
+ "angular": true,
+ "module": true,
+ "inject": true
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..01b919d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+node_modules/
+bower_components/
+coverage/
+.sass-cache/
+.idea/
+.tmp/
+dist/
diff --git a/.yo-rc.json b/.yo-rc.json
new file mode 100644
index 0000000..7da5de1
--- /dev/null
+++ b/.yo-rc.json
@@ -0,0 +1,78 @@
+{
+ "generator-gulp-angular": {
+ "version": "1.0.2",
+ "props": {
+ "angularVersion": "~1.4.2",
+ "angularModules": [
+ {
+ "key": "animate",
+ "module": "ngAnimate"
+ },
+ {
+ "key": "cookies",
+ "module": "ngCookies"
+ },
+ {
+ "key": "touch",
+ "module": "ngTouch"
+ },
+ {
+ "key": "sanitize",
+ "module": "ngSanitize"
+ },
+ {
+ "key": "messages",
+ "module": "ngMessages"
+ },
+ {
+ "key": "aria",
+ "module": "ngAria"
+ }
+ ],
+ "jQuery": {
+ "key": "jqLite"
+ },
+ "resource": {
+ "key": "angular-resource",
+ "module": "ngResource"
+ },
+ "router": {
+ "key": "angular-route",
+ "module": "ngRoute"
+ },
+ "ui": {
+ "key": "bootstrap",
+ "module": null
+ },
+ "bootstrapComponents": {
+ "key": "ui-bootstrap",
+ "module": "ui.bootstrap"
+ },
+ "cssPreprocessor": {
+ "key": "node-sass",
+ "extension": "scss"
+ },
+ "jsPreprocessor": {
+ "key": "noJsPrepro",
+ "extension": "js",
+ "srcExtension": "js"
+ },
+ "htmlPreprocessor": {
+ "key": "noHtmlPrepro",
+ "extension": "html"
+ },
+ "foundationComponents": {
+ "name": null,
+ "version": null,
+ "key": null,
+ "module": null
+ },
+ "paths": {
+ "src": "src",
+ "dist": "dist",
+ "e2e": "e2e",
+ "tmp": ".tmp"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..e8ad744
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,39 @@
+{
+ "name": "angular",
+ "version": "0.0.0",
+ "dependencies": {
+ "angular-animate": "~1.4.2",
+ "angular-cookies": "~1.4.2",
+ "angular-touch": "~1.4.2",
+ "angular-sanitize": "~1.4.2",
+ "angular-messages": "~1.4.2",
+ "angular-aria": "~1.4.2",
+ "angular-resource": "~1.4.2",
+ "angular-route": "~1.4.2",
+ "bootstrap-sass": "~3.3.5",
+ "angular-bootstrap": "~0.13.4",
+ "malarkey": "yuanqing/malarkey#~1.3.1",
+ "angular-toastr": "~1.5.0",
+ "moment": "~2.10.6",
+ "animate.css": "~3.4.0",
+ "angular": "~1.4.2"
+ },
+ "devDependencies": {
+ "angular-mocks": "~1.4.2"
+ },
+ "overrides": {
+ "bootstrap-sass": {
+ "main": [
+ "assets/stylesheets/_bootstrap.scss",
+ "assets/fonts/bootstrap/glyphicons-halflings-regular.eot",
+ "assets/fonts/bootstrap/glyphicons-halflings-regular.svg",
+ "assets/fonts/bootstrap/glyphicons-halflings-regular.ttf",
+ "assets/fonts/bootstrap/glyphicons-halflings-regular.woff",
+ "assets/fonts/bootstrap/glyphicons-halflings-regular.woff2"
+ ]
+ }
+ },
+ "resolutions": {
+ "angular": "~1.4.2"
+ }
+}
diff --git a/e2e/.eslintrc b/e2e/.eslintrc
new file mode 100644
index 0000000..d2c6f97
--- /dev/null
+++ b/e2e/.eslintrc
@@ -0,0 +1,9 @@
+{
+ "globals": {
+ "browser": false,
+ "element": false,
+ "by": false,
+ "$": false,
+ "$$": false
+ }
+}
diff --git a/e2e/main.po.js b/e2e/main.po.js
new file mode 100644
index 0000000..0f0428c
--- /dev/null
+++ b/e2e/main.po.js
@@ -0,0 +1,15 @@
+/**
+ * This file uses the Page Object pattern to define the main page for tests
+ * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ
+ */
+
+'use strict';
+
+var MainPage = function() {
+ this.jumbEl = element(by.css('.jumbotron'));
+ this.h1El = this.jumbEl.element(by.css('h1'));
+ this.imgEl = this.jumbEl.element(by.css('img'));
+ this.thumbnailEls = element(by.css('body')).all(by.repeater('awesomeThing in main.awesomeThings'));
+};
+
+module.exports = new MainPage();
diff --git a/e2e/main.spec.js b/e2e/main.spec.js
new file mode 100644
index 0000000..ef2e5c1
--- /dev/null
+++ b/e2e/main.spec.js
@@ -0,0 +1,21 @@
+'use strict';
+
+describe('The main view', function () {
+ var page;
+
+ beforeEach(function () {
+ browser.get('/index.html');
+ page = require('./main.po');
+ });
+
+ it('should include jumbotron with correct data', function() {
+ expect(page.h1El.getText()).toBe('\'Allo, \'Allo!');
+ expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/);
+ expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman');
+ });
+
+ it('should list more than 5 awesome things', function () {
+ expect(page.thumbnailEls.count()).toBeGreaterThan(5);
+ });
+
+});
diff --git a/gulp/.eslintrc b/gulp/.eslintrc
new file mode 100644
index 0000000..10d2238
--- /dev/null
+++ b/gulp/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "env": {
+ "node": true
+ }
+}
diff --git a/gulp/build.js b/gulp/build.js
new file mode 100644
index 0000000..2769817
--- /dev/null
+++ b/gulp/build.js
@@ -0,0 +1,98 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var $ = require('gulp-load-plugins')({
+ pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del']
+});
+
+gulp.task('partials', function () {
+ return gulp.src([
+ path.join(conf.paths.src, '/app/**/*.html'),
+ path.join(conf.paths.tmp, '/serve/app/**/*.html')
+ ])
+ .pipe($.minifyHtml({
+ empty: true,
+ spare: true,
+ quotes: true
+ }))
+ .pipe($.angularTemplatecache('templateCacheHtml.js', {
+ module: 'angular',
+ root: 'app'
+ }))
+ .pipe(gulp.dest(conf.paths.tmp + '/partials/'));
+});
+
+gulp.task('html', ['inject', 'partials'], function () {
+ var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false });
+ var partialsInjectOptions = {
+ starttag: '',
+ ignorePath: path.join(conf.paths.tmp, '/partials'),
+ addRootSlash: false
+ };
+
+ var htmlFilter = $.filter('*.html', { restore: true });
+ var jsFilter = $.filter('**/*.js', { restore: true });
+ var cssFilter = $.filter('**/*.css', { restore: true });
+ var assets;
+
+ return gulp.src(path.join(conf.paths.tmp, '/serve/*.html'))
+ .pipe($.inject(partialsInjectFile, partialsInjectOptions))
+ .pipe(assets = $.useref.assets())
+ .pipe($.rev())
+ .pipe(jsFilter)
+ .pipe($.sourcemaps.init())
+ .pipe($.ngAnnotate())
+ .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
+ .pipe($.sourcemaps.write('maps'))
+ .pipe(jsFilter.restore)
+ .pipe(cssFilter)
+ .pipe($.sourcemaps.init())
+ .pipe($.replace('../../bower_components/bootstrap-sass/assets/fonts/bootstrap/', '../fonts/'))
+ .pipe($.minifyCss({ processImport: false }))
+ .pipe($.sourcemaps.write('maps'))
+ .pipe(cssFilter.restore)
+ .pipe(assets.restore())
+ .pipe($.useref())
+ .pipe($.revReplace())
+ .pipe(htmlFilter)
+ .pipe($.minifyHtml({
+ empty: true,
+ spare: true,
+ quotes: true,
+ conditionals: true
+ }))
+ .pipe(htmlFilter.restore)
+ .pipe(gulp.dest(path.join(conf.paths.dist, '/')))
+ .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
+ });
+
+// Only applies for fonts from bower dependencies
+// Custom fonts are handled by the "other" task
+gulp.task('fonts', function () {
+ return gulp.src($.mainBowerFiles())
+ .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}'))
+ .pipe($.flatten())
+ .pipe(gulp.dest(path.join(conf.paths.dist, '/fonts/')));
+});
+
+gulp.task('other', function () {
+ var fileFilter = $.filter(function (file) {
+ return file.stat.isFile();
+ });
+
+ return gulp.src([
+ path.join(conf.paths.src, '/**/*'),
+ path.join('!' + conf.paths.src, '/**/*.{html,css,js,scss}')
+ ])
+ .pipe(fileFilter)
+ .pipe(gulp.dest(path.join(conf.paths.dist, '/')));
+});
+
+gulp.task('clean', function () {
+ return $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')]);
+});
+
+gulp.task('build', ['html', 'fonts', 'other']);
diff --git a/gulp/conf.js b/gulp/conf.js
new file mode 100644
index 0000000..fcec9c2
--- /dev/null
+++ b/gulp/conf.js
@@ -0,0 +1,41 @@
+/**
+ * This file contains the variables used in other gulp files
+ * which defines tasks
+ * By design, we only put there very generic config values
+ * which are used in several places to keep good readability
+ * of the tasks
+ */
+
+var gutil = require('gulp-util');
+
+/**
+ * The main paths of your project handle these with care
+ */
+exports.paths = {
+ src: 'src',
+ dist: 'dist',
+ tmp: '.tmp',
+ e2e: 'e2e'
+};
+
+/**
+ * Wiredep is the lib which inject bower dependencies in your project
+ * Mainly used to inject script tags in the index.html but also used
+ * to inject css preprocessor deps and js files in karma
+ */
+exports.wiredep = {
+ exclude: [/jquery/, /\/bootstrap\.js$/, /\/bootstrap-sass\/.*\.js/, /\/bootstrap\.css/],
+ directory: 'bower_components'
+};
+
+/**
+ * Common implementation for an error handler of a Gulp plugin
+ */
+exports.errorHandler = function(title) {
+ 'use strict';
+
+ return function(err) {
+ gutil.log(gutil.colors.red('[' + title + ']'), err.toString());
+ this.emit('end');
+ };
+};
diff --git a/gulp/e2e-tests.js b/gulp/e2e-tests.js
new file mode 100644
index 0000000..3a66702
--- /dev/null
+++ b/gulp/e2e-tests.js
@@ -0,0 +1,38 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+
+var $ = require('gulp-load-plugins')();
+
+// Downloads the selenium webdriver
+gulp.task('webdriver-update', $.protractor.webdriver_update);
+
+gulp.task('webdriver-standalone', $.protractor.webdriver_standalone);
+
+function runProtractor (done) {
+ var params = process.argv;
+ var args = params.length > 3 ? [params[3], params[4]] : [];
+
+ gulp.src(path.join(conf.paths.e2e, '/**/*.js'))
+ .pipe($.protractor.protractor({
+ configFile: 'protractor.conf.js',
+ args: args
+ }))
+ .on('error', function (err) {
+ // Make sure failed tests cause gulp to exit non-zero
+ throw err;
+ })
+ .on('end', function () {
+ // Close browser sync server
+ browserSync.exit();
+ done();
+ });
+}
+
+gulp.task('protractor', ['protractor:src']);
+gulp.task('protractor:src', ['serve:e2e', 'webdriver-update'], runProtractor);
+gulp.task('protractor:dist', ['serve:e2e-dist', 'webdriver-update'], runProtractor);
diff --git a/gulp/inject.js b/gulp/inject.js
new file mode 100644
index 0000000..2a5804b
--- /dev/null
+++ b/gulp/inject.js
@@ -0,0 +1,42 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var $ = require('gulp-load-plugins')();
+
+var wiredep = require('wiredep').stream;
+var _ = require('lodash');
+
+var browserSync = require('browser-sync');
+
+gulp.task('inject-reload', ['inject'], function() {
+ browserSync.reload();
+});
+
+gulp.task('inject', ['scripts', 'styles'], function () {
+ var injectStyles = gulp.src([
+ path.join(conf.paths.tmp, '/serve/app/**/*.css'),
+ path.join('!' + conf.paths.tmp, '/serve/app/vendor.css')
+ ], { read: false });
+
+ var injectScripts = gulp.src([
+ path.join(conf.paths.src, '/app/**/*.module.js'),
+ path.join(conf.paths.src, '/app/**/*.js'),
+ path.join('!' + conf.paths.src, '/app/**/*.spec.js'),
+ path.join('!' + conf.paths.src, '/app/**/*.mock.js'),
+ ])
+ .pipe($.angularFilesort()).on('error', conf.errorHandler('AngularFilesort'));
+
+ var injectOptions = {
+ ignorePath: [conf.paths.src, path.join(conf.paths.tmp, '/serve')],
+ addRootSlash: false
+ };
+
+ return gulp.src(path.join(conf.paths.src, '/*.html'))
+ .pipe($.inject(injectStyles, injectOptions))
+ .pipe($.inject(injectScripts, injectOptions))
+ .pipe(wiredep(_.extend({}, conf.wiredep)))
+ .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve')));
+});
diff --git a/gulp/scripts.js b/gulp/scripts.js
new file mode 100644
index 0000000..585e4fc
--- /dev/null
+++ b/gulp/scripts.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+
+var $ = require('gulp-load-plugins')();
+
+
+gulp.task('scripts-reload', function() {
+ return buildScripts()
+ .pipe(browserSync.stream());
+});
+
+gulp.task('scripts', function() {
+ return buildScripts();
+});
+
+function buildScripts() {
+ return gulp.src(path.join(conf.paths.src, '/app/**/*.js'))
+ .pipe($.eslint())
+ .pipe($.eslint.format())
+ .pipe($.size())
+};
diff --git a/gulp/server.js b/gulp/server.js
new file mode 100644
index 0000000..500b0c0
--- /dev/null
+++ b/gulp/server.js
@@ -0,0 +1,63 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+var browserSyncSpa = require('browser-sync-spa');
+
+var util = require('util');
+
+var proxyMiddleware = require('http-proxy-middleware');
+
+function browserSyncInit(baseDir, browser) {
+ browser = browser === undefined ? 'default' : browser;
+
+ var routes = null;
+ if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) {
+ routes = {
+ '/bower_components': 'bower_components'
+ };
+ }
+
+ var server = {
+ baseDir: baseDir,
+ routes: routes
+ };
+
+ /*
+ * You can add a proxy to your backend by uncommenting the line below.
+ * You just have to configure a context which will we redirected and the target url.
+ * Example: $http.get('/users') requests will be automatically proxified.
+ *
+ * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.9.0/README.md
+ */
+ // server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', changeOrigin: true});
+
+ browserSync.instance = browserSync.init({
+ startPath: '/',
+ server: server,
+ browser: browser
+ });
+}
+
+browserSync.use(browserSyncSpa({
+ selector: '[ng-app]'// Only needed for angular apps
+}));
+
+gulp.task('serve', ['watch'], function () {
+ browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]);
+});
+
+gulp.task('serve:dist', ['build'], function () {
+ browserSyncInit(conf.paths.dist);
+});
+
+gulp.task('serve:e2e', ['inject'], function () {
+ browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []);
+});
+
+gulp.task('serve:e2e-dist', ['build'], function () {
+ browserSyncInit(conf.paths.dist, []);
+});
diff --git a/gulp/styles.js b/gulp/styles.js
new file mode 100644
index 0000000..9eada3f
--- /dev/null
+++ b/gulp/styles.js
@@ -0,0 +1,54 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+
+var $ = require('gulp-load-plugins')();
+
+var wiredep = require('wiredep').stream;
+var _ = require('lodash');
+
+gulp.task('styles-reload', ['styles'], function() {
+ return buildStyles()
+ .pipe(browserSync.stream());
+});
+
+gulp.task('styles', function() {
+ return buildStyles();
+});
+
+var buildStyles = function() {
+ var sassOptions = {
+ style: 'expanded'
+ };
+
+ var injectFiles = gulp.src([
+ path.join(conf.paths.src, '/app/**/*.scss'),
+ path.join('!' + conf.paths.src, '/app/index.scss')
+ ], { read: false });
+
+ var injectOptions = {
+ transform: function(filePath) {
+ filePath = filePath.replace(conf.paths.src + '/app/', '');
+ return '@import "' + filePath + '";';
+ },
+ starttag: '// injector',
+ endtag: '// endinjector',
+ addRootSlash: false
+ };
+
+
+ return gulp.src([
+ path.join(conf.paths.src, '/app/index.scss')
+ ])
+ .pipe($.inject(injectFiles, injectOptions))
+ .pipe(wiredep(_.extend({}, conf.wiredep)))
+ .pipe($.sourcemaps.init())
+ .pipe($.sass(sassOptions)).on('error', conf.errorHandler('Sass'))
+ .pipe($.autoprefixer()).on('error', conf.errorHandler('Autoprefixer'))
+ .pipe($.sourcemaps.write())
+ .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve/app/')));
+};
diff --git a/gulp/unit-tests.js b/gulp/unit-tests.js
new file mode 100644
index 0000000..576887a
--- /dev/null
+++ b/gulp/unit-tests.js
@@ -0,0 +1,52 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var karma = require('karma');
+
+var pathSrcHtml = [
+ path.join(conf.paths.src, '/**/*.html')
+];
+
+var pathSrcJs = [
+ path.join(conf.paths.src, '/**/!(*.spec).js')
+];
+
+function runTests (singleRun, done) {
+ var reporters = ['progress'];
+ var preprocessors = {};
+
+ pathSrcHtml.forEach(function(path) {
+ preprocessors[path] = ['ng-html2js'];
+ });
+
+ if (singleRun) {
+ pathSrcJs.forEach(function(path) {
+ preprocessors[path] = ['coverage'];
+ });
+ reporters.push('coverage')
+ }
+
+ var localConfig = {
+ configFile: path.join(__dirname, '/../karma.conf.js'),
+ singleRun: singleRun,
+ autoWatch: !singleRun,
+ reporters: reporters,
+ preprocessors: preprocessors
+ };
+
+ var server = new karma.Server(localConfig, function(failCount) {
+ done(failCount ? new Error("Failed " + failCount + " tests.") : null);
+ })
+ server.start();
+}
+
+gulp.task('test', ['scripts'], function(done) {
+ runTests(true, done);
+});
+
+gulp.task('test:auto', ['watch'], function(done) {
+ runTests(false, done);
+});
diff --git a/gulp/watch.js b/gulp/watch.js
new file mode 100644
index 0000000..f748ccb
--- /dev/null
+++ b/gulp/watch.js
@@ -0,0 +1,39 @@
+'use strict';
+
+var path = require('path');
+var gulp = require('gulp');
+var conf = require('./conf');
+
+var browserSync = require('browser-sync');
+
+function isOnlyChange(event) {
+ return event.type === 'changed';
+}
+
+gulp.task('watch', ['inject'], function () {
+
+ gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject-reload']);
+
+ gulp.watch([
+ path.join(conf.paths.src, '/app/**/*.css'),
+ path.join(conf.paths.src, '/app/**/*.scss')
+ ], function(event) {
+ if(isOnlyChange(event)) {
+ gulp.start('styles-reload');
+ } else {
+ gulp.start('inject-reload');
+ }
+ });
+
+ gulp.watch(path.join(conf.paths.src, '/app/**/*.js'), function(event) {
+ if(isOnlyChange(event)) {
+ gulp.start('scripts-reload');
+ } else {
+ gulp.start('inject-reload');
+ }
+ });
+
+ gulp.watch(path.join(conf.paths.src, '/app/**/*.html'), function(event) {
+ browserSync.reload(event.path);
+ });
+});
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..5669b5d
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,29 @@
+/**
+ * Welcome to your gulpfile!
+ * The gulp tasks are splitted in several files in the gulp directory
+ * because putting all here was really too long
+ */
+
+'use strict';
+
+var gulp = require('gulp');
+var wrench = require('wrench');
+
+/**
+ * This will load all js or coffee files in the gulp directory
+ * in order to load all gulp tasks
+ */
+wrench.readdirSyncRecursive('./gulp').filter(function(file) {
+ return (/\.(js|coffee)$/i).test(file);
+}).map(function(file) {
+ require('./gulp/' + file);
+});
+
+
+/**
+ * Default task clean temporaries directories and launch the
+ * main optimization build task
+ */
+gulp.task('default', ['clean'], function () {
+ gulp.start('build');
+});
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 0000000..8e472cd
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,110 @@
+'use strict';
+
+var path = require('path');
+var conf = require('./gulp/conf');
+
+var _ = require('lodash');
+var wiredep = require('wiredep');
+
+var pathSrcHtml = [
+ path.join(conf.paths.src, '/**/*.html')
+];
+
+function listFiles() {
+ var wiredepOptions = _.extend({}, conf.wiredep, {
+ dependencies: true,
+ devDependencies: true
+ });
+
+ var patterns = wiredep(wiredepOptions).js
+ .concat([
+ path.join(conf.paths.src, '/app/**/*.module.js'),
+ path.join(conf.paths.src, '/app/**/*.js'),
+ path.join(conf.paths.src, '/**/*.spec.js'),
+ path.join(conf.paths.src, '/**/*.mock.js'),
+ ])
+ .concat(pathSrcHtml);
+
+ var files = patterns.map(function(pattern) {
+ return {
+ pattern: pattern
+ };
+ });
+ files.push({
+ pattern: path.join(conf.paths.src, '/assets/**/*'),
+ included: false,
+ served: true,
+ watched: false
+ });
+ return files;
+}
+
+module.exports = function(config) {
+
+ var configuration = {
+ files: listFiles(),
+
+ singleRun: true,
+
+ autoWatch: false,
+
+ ngHtml2JsPreprocessor: {
+ stripPrefix: conf.paths.src + '/',
+ moduleName: 'angular'
+ },
+
+ logLevel: 'WARN',
+
+ frameworks: ['jasmine', 'angular-filesort'],
+
+ angularFilesort: {
+ whitelist: [path.join(conf.paths.src, '/**/!(*.html|*.spec|*.mock).js')]
+ },
+
+ browsers : ['PhantomJS'],
+
+ plugins : [
+ 'karma-phantomjs-launcher',
+ 'karma-angular-filesort',
+ 'karma-coverage',
+ 'karma-jasmine',
+ 'karma-ng-html2js-preprocessor'
+ ],
+
+ coverageReporter: {
+ type : 'html',
+ dir : 'coverage/'
+ },
+
+ reporters: ['progress'],
+
+ proxies: {
+ '/assets/': path.join('/base/', conf.paths.src, '/assets/')
+ }
+ };
+
+ // This is the default preprocessors configuration for a usage with Karma cli
+ // The coverage preprocessor is added in gulp/unit-test.js only for single tests
+ // It was not possible to do it there because karma doesn't let us now if we are
+ // running a single test or not
+ configuration.preprocessors = {};
+ pathSrcHtml.forEach(function(path) {
+ configuration.preprocessors[path] = ['ng-html2js'];
+ });
+
+ // This block is needed to execute Chrome on Travis
+ // If you ever plan to use Chrome and Travis, you can keep it
+ // If not, you can safely remove it
+ // https://github.com/karma-runner/karma/issues/1144#issuecomment-53633076
+ if(configuration.browsers[0] === 'Chrome' && process.env.TRAVIS) {
+ configuration.customLaunchers = {
+ 'chrome-travis-ci': {
+ base: 'Chrome',
+ flags: ['--no-sandbox']
+ }
+ };
+ configuration.browsers = ['chrome-travis-ci'];
+ }
+
+ config.set(configuration);
+};
diff --git a/layouts/angular-layout.html.erb b/layouts/angular-layout.html.erb
new file mode 120000
index 0000000..ea3bc70
--- /dev/null
+++ b/layouts/angular-layout.html.erb
@@ -0,0 +1 @@
+../dist/index.html
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..714a5e1
--- /dev/null
+++ b/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "angular",
+ "version": "0.0.0",
+ "dependencies": {},
+ "scripts": {
+ "test": "gulp test"
+ },
+ "devDependencies": {
+ "estraverse": "~4.1.0",
+ "gulp": "~3.9.0",
+ "gulp-autoprefixer": "~3.0.2",
+ "gulp-angular-templatecache": "~1.8.0",
+ "del": "~2.0.2",
+ "lodash": "~3.10.1",
+ "gulp-minify-css": "~1.2.1",
+ "gulp-filter": "~3.0.1",
+ "gulp-flatten": "~0.2.0",
+ "gulp-eslint": "~1.0.0",
+ "eslint-plugin-angular": "~0.12.0",
+ "gulp-load-plugins": "~0.10.0",
+ "gulp-size": "~2.0.0",
+ "gulp-uglify": "~1.4.1",
+ "gulp-useref": "~1.3.0",
+ "gulp-util": "~3.0.6",
+ "gulp-ng-annotate": "~1.1.0",
+ "gulp-replace": "~0.5.4",
+ "gulp-rename": "~1.2.2",
+ "gulp-rev": "~6.0.1",
+ "gulp-rev-replace": "~0.4.2",
+ "gulp-minify-html": "~1.0.4",
+ "gulp-inject": "~3.0.0",
+ "gulp-protractor": "~1.0.0",
+ "gulp-sourcemaps": "~1.6.0",
+ "gulp-sass": "~2.0.4",
+ "gulp-angular-filesort": "~1.1.1",
+ "main-bower-files": "~2.9.0",
+ "wiredep": "~2.2.2",
+ "karma": "~0.13.10",
+ "karma-jasmine": "~0.3.6",
+ "karma-phantomjs-launcher": "~0.2.1",
+ "phantomjs": "~1.9.18",
+ "karma-angular-filesort": "~1.0.0",
+ "karma-coverage": "~0.5.2",
+ "karma-ng-html2js-preprocessor": "~0.2.0",
+ "browser-sync": "~2.9.11",
+ "browser-sync-spa": "~1.0.3",
+ "http-proxy-middleware": "~0.9.0",
+ "chalk": "~1.1.1",
+ "uglify-save-license": "~0.4.1",
+ "wrench": "~1.5.8"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+}
diff --git a/protractor.conf.js b/protractor.conf.js
new file mode 100644
index 0000000..862d068
--- /dev/null
+++ b/protractor.conf.js
@@ -0,0 +1,27 @@
+'use strict';
+
+var paths = require('./.yo-rc.json')['generator-gulp-angular'].props.paths;
+
+// An example configuration file.
+exports.config = {
+ // The address of a running selenium server.
+ //seleniumAddress: 'http://localhost:4444/wd/hub',
+ //seleniumServerJar: deprecated, this should be set on node_modules/protractor/config.json
+
+ // Capabilities to be passed to the webdriver instance.
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+
+ baseUrl: 'http://localhost:3000',
+
+ // Spec patterns are relative to the current working directory when
+ // protractor is called.
+ specs: [paths.e2e + '/**/*.js'],
+
+ // Options to be passed to Jasmine-node.
+ jasmineNodeOpts: {
+ showColors: true,
+ defaultTimeoutInterval: 30000
+ }
+};
diff --git a/src/app/components/githubContributor/githubContributor.service.js b/src/app/components/githubContributor/githubContributor.service.js
new file mode 100644
index 0000000..9e10661
--- /dev/null
+++ b/src/app/components/githubContributor/githubContributor.service.js
@@ -0,0 +1,37 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('angular')
+ .factory('githubContributor', githubContributor);
+
+ /** @ngInject */
+ function githubContributor($log, $http) {
+ var apiHost = 'https://api.github.com/repos/Swiip/generator-gulp-angular';
+
+ var service = {
+ apiHost: apiHost,
+ getContributors: getContributors
+ };
+
+ return service;
+
+ function getContributors(limit) {
+ if (!limit) {
+ limit = 30;
+ }
+
+ return $http.get(apiHost + '/contributors?per_page=' + limit)
+ .then(getContributorsComplete)
+ .catch(getContributorsFailed);
+
+ function getContributorsComplete(response) {
+ return response.data;
+ }
+
+ function getContributorsFailed(error) {
+ $log.error('XHR Failed for getContributors.\n' + angular.toJson(error.data, true));
+ }
+ }
+ }
+})();
diff --git a/src/app/components/githubContributor/githubContributor.service.spec.js b/src/app/components/githubContributor/githubContributor.service.spec.js
new file mode 100644
index 0000000..6f874d0
--- /dev/null
+++ b/src/app/components/githubContributor/githubContributor.service.spec.js
@@ -0,0 +1,62 @@
+(function() {
+ 'use strict';
+
+ describe('service githubContributor', function() {
+ var githubContributor;
+ var $httpBackend;
+ var $log;
+
+ beforeEach(module('angular'));
+ beforeEach(inject(function(_githubContributor_, _$httpBackend_, _$log_) {
+ githubContributor = _githubContributor_;
+ $httpBackend = _$httpBackend_;
+ $log = _$log_;
+ }));
+
+ it('should be registered', function() {
+ expect(githubContributor).not.toEqual(null);
+ });
+
+ describe('apiHost variable', function() {
+ it('should exist', function() {
+ expect(githubContributor.apiHost).not.toEqual(null);
+ });
+ });
+
+ describe('getContributors function', function() {
+ it('should exist', function() {
+ expect(githubContributor.getContributors).not.toEqual(null);
+ });
+
+ it('should return data', function() {
+ $httpBackend.when('GET', githubContributor.apiHost + '/contributors?per_page=1').respond(200, [{pprt: 'value'}]);
+ var data;
+ githubContributor.getContributors(1).then(function(fetchedData) {
+ data = fetchedData;
+ });
+ $httpBackend.flush();
+ expect(data).toEqual(jasmine.any(Array));
+ expect(data.length === 1).toBeTruthy();
+ expect(data[0]).toEqual(jasmine.any(Object));
+ });
+
+ it('should define a limit per page as default value', function() {
+ $httpBackend.when('GET', githubContributor.apiHost + '/contributors?per_page=30').respond(200, new Array(30));
+ var data;
+ githubContributor.getContributors().then(function(fetchedData) {
+ data = fetchedData;
+ });
+ $httpBackend.flush();
+ expect(data).toEqual(jasmine.any(Array));
+ expect(data.length === 30).toBeTruthy();
+ });
+
+ it('should log a error', function() {
+ $httpBackend.when('GET', githubContributor.apiHost + '/contributors?per_page=1').respond(500);
+ githubContributor.getContributors(1);
+ $httpBackend.flush();
+ expect($log.error.logs).toEqual(jasmine.stringMatching('XHR Failed for'));
+ });
+ });
+ });
+})();
diff --git a/src/app/components/malarkey/malarkey.directive.js b/src/app/components/malarkey/malarkey.directive.js
new file mode 100644
index 0000000..977262f
--- /dev/null
+++ b/src/app/components/malarkey/malarkey.directive.js
@@ -0,0 +1,75 @@
+(function() {
+ 'use strict';
+
+ angular
+ .module('angular')
+ .directive('acmeMalarkey', acmeMalarkey);
+
+ /** @ngInject */
+ function acmeMalarkey(malarkey) {
+ var directive = {
+ restrict: 'E',
+ scope: {
+ extraValues: '='
+ },
+ template: ' ',
+ link: linkFunc,
+ controller: MalarkeyController,
+ controllerAs: 'vm'
+ };
+
+ return directive;
+
+ function linkFunc(scope, el, attr, vm) {
+ var watcher;
+ var typist = malarkey(el[0], {
+ typeSpeed: 40,
+ deleteSpeed: 40,
+ pauseDelay: 800,
+ loop: true,
+ postfix: ' '
+ });
+
+ el.addClass('acme-malarkey');
+
+ angular.forEach(scope.extraValues, function(value) {
+ typist.type(value).pause().delete();
+ });
+
+ watcher = scope.$watch('vm.contributors', function() {
+ angular.forEach(vm.contributors, function(contributor) {
+ typist.type(contributor.login).pause().delete();
+ });
+ });
+
+ scope.$on('$destroy', function () {
+ watcher();
+ });
+ }
+
+ /** @ngInject */
+ function MalarkeyController($log, githubContributor) {
+ var vm = this;
+
+ vm.contributors = [];
+
+ activate();
+
+ function activate() {
+ return getContributors().then(function() {
+ $log.info('Activated Contributors View');
+ });
+ }
+
+ function getContributors() {
+ return githubContributor.getContributors(10).then(function(data) {
+ vm.contributors = data;
+
+ return vm.contributors;
+ });
+ }
+ }
+
+ }
+
+})();
diff --git a/src/app/components/malarkey/malarkey.directive.spec.js b/src/app/components/malarkey/malarkey.directive.spec.js
new file mode 100644
index 0000000..3be3309
--- /dev/null
+++ b/src/app/components/malarkey/malarkey.directive.spec.js
@@ -0,0 +1,45 @@
+(function() {
+ 'use strict';
+
+ /**
+ * @todo Complete the test
+ * This example is not perfect.
+ * The `link` function is not tested.
+ * (malarkey usage, addClass, $watch, $destroy)
+ */
+ describe('directive malarkey', function() {
+ var $log;
+ var vm;
+ var el;
+
+ beforeEach(module('angular'));
+ beforeEach(inject(function($compile, $rootScope, githubContributor, $q, _$log_) {
+ $log = _$log_;
+
+ spyOn(githubContributor, 'getContributors').and.callFake(function() {
+ return $q.when([{}, {}, {}, {}, {}, {}]);
+ });
+
+ el = angular.element('
+
+ Always a pleasure scaffolding your apps.
+
+ +
+
+ With ♥ thanks to the contributions of