├── .gitignore ├── README.md ├── data ├── bookmarks │ └── default.json └── categories │ └── default.json ├── gulp ├── gulp.config.js ├── module.prefix └── module.suffix ├── gulpfile.js ├── package.json ├── server ├── server-config.js └── server.js ├── src ├── app │ ├── categories │ │ ├── bookmarks │ │ │ ├── bookmarks.js │ │ │ ├── bookmarks.tmpl.html │ │ │ ├── create │ │ │ │ ├── bookmark-create.js │ │ │ │ └── bookmark-create.tmpl.html │ │ │ └── edit │ │ │ │ ├── bookmark-edit.js │ │ │ │ └── bookmark-edit.tmpl.html │ │ ├── categories.js │ │ └── categories.tmpl.html │ ├── common │ │ └── models │ │ │ ├── bookmarks-model.js │ │ │ └── categories-model.js │ └── eggly-app.js ├── assets │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── img │ │ └── eggly-logo.png ├── index.html └── less │ ├── animations.less │ ├── eggly.less │ └── main.less └── vendor └── angular-ui-router.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | build 3 | bin 4 | node_modules 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | eggly 2 | ===== 3 | 4 | AngularJS bookmark manager. 5 | -------------------------------------------------------------------------------- /data/bookmarks/default.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id":0, "title": "AngularJS", "url": "http://angularjs.org", "category": "Development" }, 3 | {"id":1, "title": "Egghead.io", "url": "http://egghead.io", "category": "Development" }, 4 | {"id":2, "title": "A List Apart", "url": "http://alistapart.com/", "category": "Design" }, 5 | {"id":3, "title": "One Page Love", "url": "http://onepagelove.com/", "category": "Design" }, 6 | {"id":4, "title": "MobilityWOD", "url": "http://www.mobilitywod.com/", "category": "Exercise" }, 7 | {"id":5, "title": "Robb Wolf", "url": "http://robbwolf.com/", "category": "Exercise" }, 8 | {"id":6, "title": "Senor Gif", "url": "http://memebase.cheezburger.com/senorgif", "category": "Humor" }, 9 | {"id":7, "title": "Wimp", "url": "http://wimp.com", "category": "Humor" }, 10 | {"id":8, "title": "Dump", "url": "http://dump.com", "category": "Humor" } 11 | ] -------------------------------------------------------------------------------- /data/categories/default.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id": 0, "name": "Development"}, 3 | {"id": 1, "name": "Design"}, 4 | {"id": 2, "name": "Exercise"}, 5 | {"id": 3, "name": "Humor"} 6 | ] -------------------------------------------------------------------------------- /gulp/gulp.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | build_dir: 'build', 4 | compile_dir: 'bin', 5 | server: 'server/server.js', 6 | 7 | app_files: { 8 | // source, but NO specs 9 | js: ['src/**/*.js', '!src/**/*.spec.js'], 10 | js_compile: ['gulp/module.prefix', 'build/app/**/*.js', 'build/vendor/**/*.js', 'gulp/module.suffix'], 11 | vendor: ['vendor/**/*.js'], 12 | jsunit: ['src/**/*.spec.js'], 13 | // our partial templates 14 | atpl: ['src/app/**/*.tmpl.html', 'src/common/**/*.tmpl.html'], 15 | tpl_src: ["./build/vendor/**/*.js", "./build/app/**/*.js", "./build/assets/css/**/*.css"], 16 | // the index.html 17 | html: ['src/index.html'], 18 | less: 'src/less/main.less', 19 | styles: ['src/less/**/*.less'], 20 | data_compile: ['build/data/**/*.*'], 21 | assets_compile: ['build/assets/**/*.*', '!build/assets/css/**/*.*'], 22 | ngmin_js: ['./bin/**/*.js'] 23 | }, 24 | 25 | test_files: { 26 | js: [ 27 | 'node_modules/jquery/dist/jquery.js', 28 | 'node_modules/angular-mocks/angular-mocks.js', 29 | 'node_modules/jasmine-jquery/lib/jasmine-jquery.js' 30 | ] 31 | }, 32 | 33 | vendor_files: { 34 | // the vendor/ needs to be prefixed by the task 35 | js: [ 36 | 'angular-ui-router.min.js' 37 | ], 38 | css: [], 39 | assets: [] 40 | } 41 | }; -------------------------------------------------------------------------------- /gulp/module.prefix: -------------------------------------------------------------------------------- 1 | ;(function(window, angular, undefined) { 2 | "use strict;" -------------------------------------------------------------------------------- /gulp/module.suffix: -------------------------------------------------------------------------------- 1 | 2 | 3 | }(window, angular, undefined)); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | del = require('del'), 3 | less = require('gulp-less'), 4 | inject = require("gulp-inject"), 5 | runSequence = require('run-sequence'), 6 | rename = require("gulp-rename"), 7 | nodemon = require('gulp-nodemon'), 8 | concat = require('gulp-concat'), 9 | html2js = require("gulp-ng-html2js"), 10 | ngmin = require("gulp-ng-annotate"), 11 | _ = require('lodash'), 12 | uglify = require('gulp-uglify'), 13 | pkg = require('./package.json'), 14 | jshint = require('gulp-jshint'); 15 | 16 | 17 | var files = require('./gulp/gulp.config.js'); 18 | 19 | /* 20 | The primary build task. We use runSequence to prevent any race-conditions. 21 | These conditions can occur because Gulp runs in parallel. 22 | 23 | We pass in a callback so that runSequence can notify gulp that the sequence is complete. 24 | */ 25 | gulp.task('build', function (callback) { 26 | runSequence('clean', 27 | 'copy-build', 28 | 'html2js', 29 | 'less', 30 | 'index', 31 | callback); 32 | }); 33 | 34 | /* 35 | The defualt task that runs when we type "gulp" 36 | */ 37 | gulp.task('default', function (callback) { 38 | runSequence('build', 39 | 'watch', 40 | 'serve', 41 | callback); 42 | }); 43 | 44 | /* 45 | Selectively build JUST the JS source. 46 | */ 47 | gulp.task('build-src', function (callback) { 48 | runSequence('copy-build', 'index', callback) 49 | }); 50 | 51 | /* 52 | Use 'del', a standard npm lib, to completely delete the build dir 53 | */ 54 | gulp.task('clean', function () { 55 | return del(['./build'], {force: true}); 56 | }); 57 | 58 | /* 59 | Use 'del', a standard npm lib, to completely delete the bin (production) dir 60 | */ 61 | gulp.task('clean-bin', function () { 62 | return del(['./bin'], {force: true}); 63 | }); 64 | 65 | gulp.task('copy-build', ['copy-assets', 'copy-app-js', 'copy-vendor-js']); 66 | 67 | gulp.task('copy-assets', function () { 68 | return gulp.src('./src/assets/**/*') 69 | .pipe(gulp.dest('./build/assets')); 70 | }); 71 | 72 | gulp.task('copy-app-js', function () { 73 | return gulp.src(files.app_files.js) 74 | .pipe(gulp.dest('./build')); 75 | }); 76 | 77 | gulp.task('copy-vendor-js', function () { 78 | return gulp.src(files.vendor_files.js, {cwd: 'vendor/**'}) 79 | .pipe(gulp.dest('./build/vendor')); 80 | }); 81 | 82 | gulp.task('copy-compile', ['copy-compile-assets']); 83 | 84 | gulp.task('copy-compile-assets', function () { 85 | return gulp.src(files.app_files.assets_compile) 86 | .pipe(gulp.dest('./bin/assets')); 87 | }); 88 | 89 | gulp.task('html2js', function () { 90 | return gulp.src(files.app_files.atpl) 91 | .pipe(html2js({ 92 | moduleName: "templates-app" 93 | })) 94 | .pipe(concat('templates-app.js')) 95 | .pipe(gulp.dest("./build/app")); 96 | }); 97 | 98 | gulp.task('less', function () { 99 | return gulp.src('./src/less/main.less') 100 | .pipe(less({ 101 | compile: true, 102 | compress: false, 103 | noUnderscores: false, 104 | noIDs: false, 105 | zeroUnits: false 106 | })) 107 | .pipe(rename(pkg.name + '-' + pkg.version + '.css')) 108 | .pipe(gulp.dest('./build/assets/css/')); 109 | }); 110 | 111 | gulp.task('less-compile', function () { 112 | return gulp.src('./src/less/main.less') 113 | .pipe(less({ 114 | compile: true, 115 | compress: true, 116 | noUnderscores: false, 117 | noIDs: false, 118 | zeroUnits: false 119 | })) 120 | .pipe(rename(pkg.name + '-' + pkg.version + '.css')) 121 | .pipe(gulp.dest('./bin/assets/css/')); 122 | }); 123 | 124 | /* 125 | Used to populate the index.html template with JS sources 126 | from the "build" dir. 127 | */ 128 | gulp.task('index', function () { 129 | return gulp.src('./src/index.html') 130 | .pipe(inject( 131 | gulp.src(files.app_files.tpl_src), { 132 | ignorePath: 'build' 133 | })) 134 | .pipe(gulp.dest("./build")); 135 | }); 136 | 137 | /* 138 | Used to populate the index.html template with JS sources 139 | from the "bin" folder during compile task. 140 | */ 141 | gulp.task('index-compile', function () { 142 | return gulp.src('./src/index.html') 143 | .pipe(inject(gulp.src(['./bin/**/*.js', './bin/**/*.css'], {read: false}), { 144 | ignorePath: files.compile_dir + '/' 145 | })) 146 | .pipe(gulp.dest("./" + files.compile_dir)); 147 | }); 148 | 149 | gulp.task('ngmin', function () { 150 | return gulp.src(files.app_files.ngmin_js) 151 | .pipe(ngmin()) 152 | .pipe(gulp.dest(files.compile_dir)); 153 | }); 154 | 155 | gulp.task('uglify', function () { 156 | return gulp.src(files.app_files.ngmin_js) 157 | .pipe(uglify()); 158 | }); 159 | 160 | gulp.task('concat', function () { 161 | 162 | return gulp.src(files.app_files.js_compile) 163 | .pipe(concat(pkg.name + '-' + pkg.version + '.min.js')) 164 | .pipe(gulp.dest('./bin/assets')) 165 | }); 166 | 167 | gulp.task('serve', function () { 168 | nodemon({script: files.server, watch: 'server/'}) 169 | .on('restart', function () { 170 | console.log('restarted!') 171 | }) 172 | }); 173 | 174 | gulp.task('compile', function (callback) { 175 | runSequence('build', 176 | 'clean-bin', 177 | 'copy-compile', 178 | 'concat', 179 | 'ngmin', 180 | 'uglify', 181 | 'less-compile', 182 | 'index-compile', 183 | callback); 184 | }); 185 | 186 | gulp.task('lint', function() { 187 | return gulp.src(files.app_files.js) 188 | .pipe(jshint()) 189 | .pipe(jshint.reporter('default')); 190 | }); 191 | 192 | gulp.task('watch', function () { 193 | gulp.watch(files.app_files.js, ['lint', 'build-src']); 194 | gulp.watch(files.app_files.atpl, ['html2js', 'index']); 195 | gulp.watch(files.app_files.html, ['index']); 196 | gulp.watch(files.app_files.styles, ['less', 'index']); 197 | 198 | gulp.watch('./src/config/**/*.json', ['config-build']); 199 | }); 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egghead-angularjs-eggly-build-automation", 3 | "version": "1.0.0", 4 | "description": "eggly =====", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "body-parser": "^1.11.0", 13 | "bootstrap": "^3.3.2", 14 | "cookie-parser": "^1.3.3", 15 | "del": "^2.2.0", 16 | "express": "^4.11.2", 17 | "fs": "0.0.2", 18 | "gulp": "^3.8.11", 19 | "gulp-concat": "^2.4.3", 20 | "gulp-inject": "^3.0.0", 21 | "gulp-jshint": "^2.0.0", 22 | "gulp-less": "^3.0.5", 23 | "gulp-ng-annotate": "^1.1.0", 24 | "gulp-ng-html2js": "^0.2.1", 25 | "gulp-nodemon": "^2.0.6", 26 | "gulp-rename": "^1.2.0", 27 | "gulp-uglify": "^1.1.0", 28 | "http": "0.0.0", 29 | "lodash": "^4.0.0", 30 | "run-sequence": "^1.0.2" 31 | }, 32 | "dependencies": { 33 | "normalize.less": "^1.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/server-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 3444, 3 | data_location: __dirname + '/../data/', 4 | rest_base_url: '/api', 5 | static_site_root: __dirname + '/../build' //up a dir and find build 6 | }; -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var config = require('./server-config'), 2 | express = require('express'), 3 | app = express(), 4 | server = require('http').createServer(app), 5 | mockFileRoot = config.data_location, 6 | fs = require('fs'); 7 | 8 | var cookieParser = require('cookie-parser'); 9 | var bodyParser = require('body-parser'); 10 | 11 | 12 | // CONFIG SERVER 13 | //allows us to write cookies 14 | app.use(cookieParser()); 15 | 16 | //allows server to run as proxy 17 | app.enable('trust proxy'); 18 | app.use(bodyParser.json()); 19 | app.use(express.static(config.static_site_root)); 20 | 21 | var responseCache = {}; 22 | 23 | function getMock(path, cacheResponse) { 24 | var mockResponse = responseCache[path]; 25 | 26 | cacheResponse = cacheResponse || false; 27 | 28 | if (!mockResponse) { 29 | mockResponse = fs.readFileSync(path); 30 | mockResponse = JSON.parse(mockResponse); 31 | if(cacheResponse) { 32 | responseCache[path] = mockResponse; 33 | } 34 | } 35 | 36 | return mockResponse; 37 | } 38 | 39 | /** 40 | * Sends `default.json` that matches the path of the request 41 | * from the data dir. Assumes a rigid directory structure that 42 | * matches the route exactly. 43 | * 44 | * @param req 45 | * @param res 46 | */ 47 | function sendDefault(req, res) { 48 | var endpoint, 49 | splitPath = req.params[0].split('?')[0].split("/"), 50 | mockPath = mockFileRoot + req.params[0] + '/' + 'default.json', 51 | mockResponse; 52 | 53 | if (splitPath.length > 2) 54 | endpoint = splitPath[splitPath.length - 2]; 55 | 56 | try { 57 | 58 | res.json(getMock(mockPath)) 59 | } catch (err) { 60 | console.log("something bad happened", err); 61 | res.status(500).send(JSON.parse(err)); 62 | } 63 | } 64 | 65 | app.get(config.rest_base_url + '/*', sendDefault); 66 | app.post(config.rest_base_url + '/*', sendDefault); 67 | 68 | // FIRE IT UP 69 | 70 | server.listen(config.port, function () { 71 | console.log("Express server listening on port %d", config.port); 72 | }); 73 | -------------------------------------------------------------------------------- /src/app/categories/bookmarks/bookmarks.js: -------------------------------------------------------------------------------- 1 | angular.module('categories.bookmarks', [ 2 | 'categories.bookmarks.create', 3 | 'categories.bookmarks.edit', 4 | 'eggly.models.categories', 5 | 'eggly.models.bookmarks' 6 | ]) 7 | .config(function ($stateProvider) { 8 | $stateProvider 9 | .state('eggly.categories.bookmarks', { 10 | url: 'categories/:category', 11 | //target the named 'ui-view' in ROOT (eggly) state named 'bookmarks' 12 | //to show bookmarks for a specific category 13 | views: { 14 | 'bookmarks@': { 15 | templateUrl: 'categories/bookmarks/bookmarks.tmpl.html', 16 | controller: 'BookmarksListCtrl as bookmarksListCtrl' 17 | } 18 | } 19 | }) 20 | ; 21 | }) 22 | .controller('BookmarksListCtrl', function ($stateParams, CategoriesModel, BookmarksModel) { 23 | var bookmarksListCtrl = this; 24 | 25 | CategoriesModel.setCurrentCategory($stateParams.category); 26 | 27 | BookmarksModel.getBookmarks() 28 | .then(function (bookmarks) { 29 | bookmarksListCtrl.bookmarks = bookmarks; 30 | }); 31 | 32 | bookmarksListCtrl.getCurrentCategory = CategoriesModel.getCurrentCategory; 33 | bookmarksListCtrl.getCurrentCategoryName = CategoriesModel.getCurrentCategoryName; 34 | bookmarksListCtrl.deleteBookmark = BookmarksModel.deleteBookmark; 35 | }) 36 | 37 | ; 38 | 39 | -------------------------------------------------------------------------------- /src/app/categories/bookmarks/bookmarks.tmpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 5 | {{bookmark.title}} 6 |
7 |
8 | 9 |
10 | 15 |
16 |
17 | -------------------------------------------------------------------------------- /src/app/categories/bookmarks/create/bookmark-create.js: -------------------------------------------------------------------------------- 1 | angular.module('categories.bookmarks.create', [ 2 | 3 | ]) 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('eggly.categories.bookmarks.create', { 7 | url: '/bookmarks/create', 8 | //target the un-named 'ui-view' in PARENT states template 9 | templateUrl: 'categories/bookmarks/create/bookmark-create.tmpl.html', 10 | controller: 'CreateBookMarkCtrl as createBookmarkCtrl' 11 | }) 12 | ; 13 | }) 14 | .controller('CreateBookMarkCtrl', function($state, $stateParams, BookmarksModel) { 15 | var createBookmarkCtrl = this; 16 | 17 | function returnToBookmarks() { 18 | $state.go('eggly.categories.bookmarks', { 19 | category: $stateParams.category 20 | }); 21 | } 22 | 23 | function cancelCreating() { 24 | returnToBookmarks(); 25 | } 26 | 27 | function createBookmark() { 28 | BookmarksModel.createBookmark(createBookmarkCtrl.newBookmark); 29 | returnToBookmarks(); 30 | } 31 | 32 | function resetForm() { 33 | createBookmarkCtrl.newBookmark = { 34 | title: '', 35 | url: '', 36 | category: $stateParams.category 37 | }; 38 | } 39 | 40 | createBookmarkCtrl.cancelCreating = cancelCreating; 41 | createBookmarkCtrl.createBookmark = createBookmark; 42 | 43 | resetForm(); 44 | }); -------------------------------------------------------------------------------- /src/app/categories/bookmarks/create/bookmark-create.tmpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 | 11 | 12 |
-------------------------------------------------------------------------------- /src/app/categories/bookmarks/edit/bookmark-edit.js: -------------------------------------------------------------------------------- 1 | angular.module('categories.bookmarks.edit', []) 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('eggly.categories.bookmarks.edit', { 5 | url: '/bookmarks/:bookmarkId/edit', 6 | //target the un-named 'ui-view' in PARENT states template 7 | templateUrl: 'categories/bookmarks/edit/bookmark-edit.tmpl.html', 8 | controller: 'EditBookmarkCtrl as editBookmarkCtrl' 9 | }) 10 | ; 11 | }) 12 | .controller('EditBookmarkCtrl', function ($state, $stateParams, BookmarksModel) { 13 | var editBookmarkCtrl = this; 14 | 15 | function returnToBookmarks() { 16 | $state.go('eggly.categories.bookmarks', { 17 | category: $stateParams.category 18 | }); 19 | } 20 | 21 | function updateBookmark() { 22 | editBookmarkCtrl.bookmark = angular.copy(editBookmarkCtrl.editedBookmark); 23 | BookmarksModel.updateBookmark(editBookmarkCtrl.editedBookmark); 24 | returnToBookmarks(); 25 | } 26 | 27 | function cancelEditing() { 28 | returnToBookmarks(); 29 | } 30 | 31 | BookmarksModel.getBookmarkById($stateParams.bookmarkId) 32 | .then(function (bookmark) { 33 | if (bookmark) { 34 | editBookmarkCtrl.bookmark = bookmark; 35 | editBookmarkCtrl.editedBookmark = angular.copy(editBookmarkCtrl.bookmark); 36 | } else { 37 | returnToBookmarks(); 38 | } 39 | }); 40 | 41 | editBookmarkCtrl.cancelEditing = cancelEditing; 42 | editBookmarkCtrl.updateBookmark = updateBookmark; 43 | }) 44 | ; 45 | -------------------------------------------------------------------------------- /src/app/categories/bookmarks/edit/bookmark-edit.tmpl.html: -------------------------------------------------------------------------------- 1 |

Editing {{editBookmarkCtrl.bookmark.title}}

2 | 3 |
4 |
5 | 6 | 7 |
8 |
9 | 10 | 11 |
12 | 13 | 14 |
-------------------------------------------------------------------------------- /src/app/categories/categories.js: -------------------------------------------------------------------------------- 1 | angular.module('categories', [ 2 | 'eggly.models.categories' 3 | ]) 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('eggly.categories', { 7 | url: '/', 8 | views: { 9 | //target the ui-view named 'categories' in ROOT state (eggly) 10 | 'categories@': { 11 | controller: 'CategoriesListCtrl as categoriesListCtrl', 12 | templateUrl: 'categories/categories.tmpl.html' 13 | }, 14 | //target the ui-view named 'bookmarks' in ROOT state (eggly) 15 | //to show all bookmarks for all categories 16 | 'bookmarks@': { 17 | controller: 'BookmarksListCtrl as bookmarksListCtrl', 18 | templateUrl: 'categories/bookmarks/bookmarks.tmpl.html' 19 | } 20 | } 21 | }) 22 | ; 23 | }) 24 | .controller('CategoriesListCtrl', function CategoriesListCtrl(CategoriesModel) { 25 | var categoriesListCtrl = this; 26 | 27 | CategoriesModel.getCategories() 28 | .then(function (result) { 29 | categoriesListCtrl.categories = result; 30 | }); 31 | }) 32 | ; 33 | -------------------------------------------------------------------------------- /src/app/categories/categories.tmpl.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /src/app/common/models/bookmarks-model.js: -------------------------------------------------------------------------------- 1 | angular.module('eggly.models.bookmarks', []) 2 | .service('BookmarksModel', function ($http, $q) { 3 | var model = this, 4 | URLS = { 5 | FETCH: '/api/bookmarks' 6 | }, 7 | bookmarks; 8 | 9 | function extract(result) { 10 | return result.data; 11 | } 12 | 13 | function cacheBookmarks(result) { 14 | bookmarks = extract(result); 15 | return bookmarks; 16 | } 17 | 18 | function findBookmark(bookmarkId) { 19 | return _.find(bookmarks, function (bookmark) { 20 | return bookmark.id === parseInt(bookmarkId, 10); 21 | }); 22 | } 23 | 24 | model.getBookmarks = function () { 25 | var deferred = $q.defer(); 26 | 27 | if (bookmarks) { 28 | deferred.resolve(bookmarks); 29 | } else { 30 | $http.get(URLS.FETCH).then(function(bookmarks){ 31 | deferred.resolve(cacheBookmarks(bookmarks)); 32 | }); 33 | } 34 | 35 | return deferred.promise; 36 | }; 37 | 38 | model.getBookmarkById = function (bookmarkId) { 39 | var deferred = $q.defer(); 40 | if (bookmarks) { 41 | deferred.resolve(findBookmark(bookmarkId)); 42 | } else { 43 | model.getBookmarks().then(function () { 44 | deferred.resolve(findBookmark(bookmarkId)); 45 | }); 46 | } 47 | return deferred.promise; 48 | }; 49 | 50 | model.createBookmark = function (bookmark) { 51 | bookmark.id = bookmarks.length; 52 | bookmarks.push(bookmark); 53 | }; 54 | 55 | model.updateBookmark = function (bookmark) { 56 | var index = _.findIndex(bookmarks, function (b) { 57 | return b.id == bookmark.id; 58 | }); 59 | 60 | bookmarks[index] = bookmark; 61 | }; 62 | 63 | model.deleteBookmark = function (bookmark) { 64 | _.remove(bookmarks, function (b) { 65 | return b.id == bookmark.id; 66 | }); 67 | }; 68 | }) 69 | 70 | ; 71 | -------------------------------------------------------------------------------- /src/app/common/models/categories-model.js: -------------------------------------------------------------------------------- 1 | angular.module('eggly.models.categories', [ 2 | 3 | ]) 4 | .service('CategoriesModel', function ($http, $q) { 5 | var model = this, 6 | URLS = { 7 | FETCH: '/api/categories' 8 | }, 9 | categories, 10 | currentCategory; 11 | 12 | function extract(result) { 13 | return result.data; 14 | } 15 | 16 | function cacheCategories(result) { 17 | categories = extract(result); 18 | return categories; 19 | } 20 | 21 | model.getCategories = function() { 22 | return (categories) ? $q.when(categories) : $http.get(URLS.FETCH).then(cacheCategories); 23 | }; 24 | 25 | model.setCurrentCategory = function(category) { 26 | return model.getCategoryByName(category).then(function(category) { 27 | currentCategory = category; 28 | }); 29 | }; 30 | 31 | model.getCurrentCategory = function() { 32 | return currentCategory; 33 | }; 34 | 35 | model.getCurrentCategoryName = function() { 36 | return currentCategory ? currentCategory.name : ''; 37 | }; 38 | 39 | model.getCategoryByName = function(categoryName) { 40 | var deferred = $q.defer(); 41 | 42 | function findCategory(){ 43 | return _.find(categories, function(c){ 44 | return c.name == categoryName; 45 | }); 46 | } 47 | 48 | if(categories) { 49 | deferred.resolve(findCategory()); 50 | } else { 51 | model.getCategories() 52 | .then(function() { 53 | deferred.resolve(findCategory()); 54 | }); 55 | } 56 | 57 | return deferred.promise; 58 | }; 59 | 60 | 61 | }) 62 | ; 63 | -------------------------------------------------------------------------------- /src/app/eggly-app.js: -------------------------------------------------------------------------------- 1 | angular.module('Eggly', [ 2 | 'ui.router', 3 | 'categories', 4 | 'categories.bookmarks', 5 | 'templates-app' 6 | ]) 7 | .config(function ($stateProvider, $urlRouterProvider) { 8 | $stateProvider 9 | //abstract state serves as a PLACEHOLDER or NAMESPACE for application states 10 | .state('eggly', { 11 | url: '', 12 | abstract: true 13 | }) 14 | ; 15 | 16 | $urlRouterProvider.otherwise('/'); 17 | }) 18 | ; -------------------------------------------------------------------------------- /src/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggheadio/egghead-angularjs-eggly-build-automation/c897ee1a274c883bc0f5c716176148e5a54115e2/src/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/assets/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /src/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggheadio/egghead-angularjs-eggly-build-automation/c897ee1a274c883bc0f5c716176148e5a54115e2/src/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggheadio/egghead-angularjs-eggly-build-automation/c897ee1a274c883bc0f5c716176148e5a54115e2/src/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggheadio/egghead-angularjs-eggly-build-automation/c897ee1a274c883bc0f5c716176148e5a54115e2/src/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/assets/img/eggly-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eggheadio/egghead-angularjs-eggly-build-automation/c897ee1a274c883bc0f5c716176148e5a54115e2/src/assets/img/eggly-logo.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Eggly 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/less/animations.less: -------------------------------------------------------------------------------- 1 | [ui-view].ng-enter, [ui-view].ng-leave { 2 | position: absolute; 3 | left: 0; 4 | right: 0; 5 | -webkit-transition:all .5s ease-in-out; 6 | -moz-transition:all .5s ease-in-out; 7 | -o-transition:all .5s ease-in-out; 8 | transition:all .5s ease-in-out; 9 | } 10 | 11 | [ui-view].ng-enter { 12 | opacity: 0; 13 | } 14 | 15 | [ui-view].ng-enter-active { 16 | opacity: 1; 17 | } 18 | 19 | [ui-view].ng-leave { 20 | opacity: 1; 21 | } 22 | 23 | [ui-view].ng-leave-active { 24 | opacity: 0; 25 | } -------------------------------------------------------------------------------- /src/less/eggly.less: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | background-color: #D5DBD8; 4 | width: 100%; 5 | height: 100%; 6 | color: #4F534E; 7 | } 8 | 9 | a { 10 | color: #373A36; 11 | font-size: 24px; 12 | text-decoration: none; 13 | } 14 | 15 | a:hover { 16 | color: #5bc0de; 17 | } 18 | 19 | .btn, .btn:focus { 20 | outline: none !important; 21 | } 22 | 23 | .btn-link { 24 | color: #373A36; 25 | font-size: 24px; 26 | text-decoration: none !important; 27 | } 28 | 29 | .btn-link:hover { 30 | color: #5bc0de; 31 | } 32 | 33 | .sidebar a { 34 | color: #5bc0de; 35 | font-size: 30px; 36 | text-decoration: none; 37 | } 38 | 39 | .sub-header { 40 | padding-bottom: 10px; 41 | border-bottom: 1px solid #EEE; 42 | } 43 | 44 | .logo { 45 | padding: 20px; 46 | } 47 | 48 | .sidebar { 49 | display: none; 50 | padding-left: 0px; 51 | padding-right: 0px; 52 | } 53 | 54 | .sidebar > ul { 55 | margin: 0px; 56 | padding: 0px; 57 | } 58 | 59 | .sidebar .active { 60 | background-color: #D5DBD8; 61 | } 62 | 63 | .sidebar .active > a { 64 | text-decoration: none; 65 | color: #2B2828; 66 | } 67 | 68 | .main .active span, 69 | .main .active > a { 70 | color: #5bc0de; 71 | } 72 | 73 | .editing > button { 74 | color: #5bc0de !important; 75 | } 76 | .editing > a { 77 | color: #5bc0de !important; 78 | } 79 | 80 | .nav > li > a:hover, .nav > li > a:focus { 81 | text-decoration: none; 82 | background-color: #D5DBD8; 83 | } 84 | 85 | @media (min-width: 768px) { 86 | .sidebar { 87 | position: fixed; 88 | top: 0; 89 | bottom: 0; 90 | left: 0; 91 | z-index: 1000; 92 | display: block; 93 | overflow-x: hidden; 94 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 95 | background-color: #2B2828; 96 | border-right: 1px solid #7B807E; 97 | } 98 | } 99 | 100 | .main { 101 | padding-top: 96px; 102 | padding-left: 24px; 103 | } 104 | 105 | .createBookmark { 106 | padding-left: 5px; 107 | padding-right: 5px; 108 | } 109 | 110 | .create-form, .edit-form { 111 | padding: 20px; 112 | } 113 | -------------------------------------------------------------------------------- /src/less/main.less: -------------------------------------------------------------------------------- 1 | @import "../../node_modules/normalize.less/normalize.less"; 2 | @import '../../node_modules/bootstrap/less/bootstrap'; 3 | 4 | @import "eggly"; 5 | @import "animations"; -------------------------------------------------------------------------------- /vendor/angular-ui-router.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * State-based routing for AngularJS 3 | * @version v0.2.10 4 | * @link http://angular-ui.github.com/ 5 | * @license MIT License, http://www.opensource.org/licenses/MIT 6 | */ 7 | "undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return I(new(I(function(){},{prototype:a})),b)}function e(a){return H(arguments,function(b){b!==a&&H(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function h(a,b,c,d){var e,h=f(c,d),i={},j=[];for(var k in h)if(h[k].params&&h[k].params.length){e=h[k].params;for(var l in e)g(j,e[l])>=0||(j.push(e[l]),i[e[l]]=a[e[l]])}return I({},i,b)}function i(a,b){var c={};return H(a,function(a){var d=b[a];c[a]=null!=d?String(d):null}),c}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(o[c]=d,E(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);H(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return F(a)&&a.then&&a.$$promises}if(!F(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return H(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!C(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;H(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!F(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=I({},d),s=1+m.length/3,t=!1;if(C(f.$$failure))return k(f.$$failure),p;f.$$values?(t=e(r,f.$$values),h()):(I(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return C(a.template)?this.fromString(a.template,b):C(a.templateUrl)?this.fromUrl(a.templateUrl,b):C(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return D(a)?a(b):a},this.fromUrl=function(c,d){return D(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),H(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function o(){this.compile=function(a){return new n(a)},this.isMatcher=function(a){return F(a)&&D(a.exec)&&D(a.format)&&D(a.concat)},this.$get=function(){return this}}function p(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return C(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!D(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(E(a)){var b=a;a=function(){return b}}else if(!D(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=E(f);if(E(e)&&(e=a.compile(e)),!h&&!D(f)&&!G(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),I(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:E(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),I(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(b){function d(b){var d=b(c,a);return d?(E(d)&&a.replace().url(d),!0):!1}if(!b||!b.defaultPrevented){var g,h=e.length;for(g=0;h>g;g++)if(d(e[g]))return;f&&d(f)}}return b.$on("$locationChangeSuccess",d),{sync:function(){d()}}}]}function q(a,e,f){function g(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){var d=E(a),e=d?a:a.name,f=g(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=w[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function m(a,b){x[a]||(x[a]=[]),x[a].push(b)}function n(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!E(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(w.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):E(b.parent)?b.parent:"";if(e&&!w[e])return m(e,b.self);for(var f in z)D(z[f])&&(b[f]=z[f](b,z.$delegates[f]));if(w[c]=b,!b[y]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){v.$current.navigable==b&&j(a,c)||v.transitionTo(b,a,{location:!1})}]),x[c])for(var g=0;g-1}function p(a){var b=a.split("."),c=v.$current.name.split(".");if("**"===b[0]&&(c=c.slice(c.indexOf(b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(c.indexOf(b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length)return!1;for(var d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return c.join("")===b.join("")}function q(a,b){return E(a)&&!C(b)?z[a]:D(b)&&E(a)?(z[a]&&!z.$delegates[a]&&(z.$delegates[a]=z[a]),z[a]=b,this):this}function r(a,b){return F(a)?b=a:b.name=a,n(b),this}function s(a,e,g,m,n,q,r,s,x){function z(){r.url()!==M&&(r.url(M),r.replace())}function A(a,c,d,f,h){var i=d?c:k(a.params,c),j={$stateParams:i};h.resolve=n.resolve(a.resolve,j,h.resolve,a);var l=[h.resolve.then(function(a){h.globals=a})];return f&&l.push(f),H(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return g.load(d,{view:c,locals:j,params:i,notify:!1})||""}],l.push(n.resolve(e,j,h.resolve,a).then(function(f){if(D(c.controllerProvider)||G(c.controllerProvider)){var g=b.extend({},e,j);f.$$controller=m.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,f.$$controllerAs=c.controllerAs,h[d]=f}))}),e.all(l).then(function(){return h})}var B=e.reject(new Error("transition superseded")),F=e.reject(new Error("transition prevented")),K=e.reject(new Error("transition aborted")),L=e.reject(new Error("transition failed")),M=r.url(),N=x.baseHref();return u.locals={resolve:null,globals:{$stateParams:{}}},v={params:{},current:u.self,$current:u,transition:null},v.reload=function(){v.transitionTo(v.current,q,{reload:!0,inherit:!1,notify:!1})},v.go=function(a,b,c){return this.transitionTo(a,b,I({inherit:!0,relative:v.$current},c))},v.transitionTo=function(b,c,f){c=c||{},f=I({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,k=v.$current,n=v.params,o=k.path,p=l(b,f.relative);if(!C(p)){var s={to:b,toParams:c,options:f};if(g=a.$broadcast("$stateNotFound",s,k.self,n),g.defaultPrevented)return z(),K;if(g.retry){if(f.$retry)return z(),L;var w=v.transition=e.when(g.retry);return w.then(function(){return w!==v.transition?B:(s.options.$retry=!0,v.transitionTo(s.to,s.toParams,s.options))},function(){return K}),z(),w}if(b=s.to,c=s.toParams,f=s.options,p=l(b,f.relative),!C(p)){if(f.relative)throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'");throw new Error("No such state '"+b+"'")}}if(p[y])throw new Error("Cannot transition to abstract state '"+b+"'");f.inherit&&(c=h(q,c||{},v.$current,p)),b=p;var x,D,E=b.path,G=u.locals,H=[];for(x=0,D=E[x];D&&D===o[x]&&j(c,n,D.ownParams)&&!f.reload;x++,D=E[x])G=H[x]=D.locals;if(t(b,k,G,f))return b.self.reloadOnSearch!==!1&&z(),v.transition=null,e.when(v.current);if(c=i(b.params,c||{}),f.notify&&(g=a.$broadcast("$stateChangeStart",b.self,c,k.self,n),g.defaultPrevented))return z(),F;for(var N=e.when(G),O=x;O=x;d--)g=o[d],g.self.onExit&&m.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=x;d1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target")||(c(function(){a.go(i.state,j,o)}),b.preventDefault())})}}}function y(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(d,e,f){function g(){a.$current.self===i&&h()?e.addClass(l):e.removeClass(l)}function h(){return!k||j(k,b)}var i,k,l;l=c(f.uiSrefActive||"",!1)(d),this.$$setStateInfo=function(b,c){i=a.get(b,w(e)),k=c,g()},d.$on("$stateChangeSuccess",g)}]}}function z(a){return function(b){return a.is(b)}}function A(a){return function(b){return a.includes(b)}}function B(a,b){function e(a){this.locals=a.locals.globals,this.params=this.locals.$stateParams}function f(){this.locals=null,this.params=null}function g(c,g){if(null!=g.redirectTo){var h,j=g.redirectTo;if(E(j))h=j;else{if(!D(j))throw new Error("Invalid 'redirectTo' in when()");h=function(a,b){return j(a,b.path(),b.search())}}b.when(c,h)}else a.state(d(g,{parent:null,name:"route:"+encodeURIComponent(c),url:c,onEnter:e,onExit:f}));return i.push(g),this}function h(a,b,d){function e(a){return""!==a.name?a:c}var f={routes:i,params:d,current:c};return b.$on("$stateChangeStart",function(a,c,d,f){b.$broadcast("$routeChangeStart",e(c),e(f))}),b.$on("$stateChangeSuccess",function(a,c,d,g){f.current=e(c),b.$broadcast("$routeChangeSuccess",e(c),e(g)),J(d,f.params)}),b.$on("$stateChangeError",function(a,c,d,f,g,h){b.$broadcast("$routeChangeError",e(c),e(f),h)}),f}var i=[];e.$inject=["$$state"],this.when=g,this.$get=h,h.$inject=["$state","$rootScope","$routeParams"]}var C=b.isDefined,D=b.isFunction,E=b.isString,F=b.isObject,G=b.isArray,H=b.forEach,I=b.extend,J=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),l.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",l),m.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",m),n.prototype.concat=function(a){return new n(this.sourcePath+a+this.sourceSearch)},n.prototype.toString=function(){return this.source},n.prototype.exec=function(a,b){var c=this.regexp.exec(a);if(!c)return null;var d,e=this.params,f=e.length,g=this.segments.length-1,h={};if(g!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(d=0;g>d;d++)h[e[d]]=c[d+1];for(;f>d;d++)h[e[d]]=b[e[d]];return h},n.prototype.parameters=function(){return this.params},n.prototype.format=function(a){var b=this.segments,c=this.params;if(!a)return b.join("");var d,e,f,g=b.length-1,h=c.length,i=b[0];for(d=0;g>d;d++)f=a[c[d]],null!=f&&(i+=encodeURIComponent(f)),i+=b[d+1];for(;h>d;d++)f=a[c[d]],null!=f&&(i+=(e?"&":"?")+c[d]+"="+encodeURIComponent(f),e=!0);return i},b.module("ui.router.util").provider("$urlMatcherFactory",o),p.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",p),q.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",q),r.$inject=[],b.module("ui.router.state").provider("$view",r),b.module("ui.router.state").provider("$uiViewScroll",s),t.$inject=["$state","$injector","$uiViewScroll"],u.$inject=["$compile","$controller","$state"],b.module("ui.router.state").directive("uiView",t),b.module("ui.router.state").directive("uiView",u),x.$inject=["$state","$timeout"],y.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",x).directive("uiSrefActive",y),z.$inject=["$state"],A.$inject=["$state"],b.module("ui.router.state").filter("isState",z).filter("includedByState",A),B.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",B).directive("ngView",t)}(window,window.angular); --------------------------------------------------------------------------------