├── .babelrc ├── src ├── sass │ ├── _layout.scss │ ├── _utils.scss │ ├── _vendors.scss │ ├── vendors │ │ └── _vendor.scss │ ├── utils │ │ └── _mixins.scss │ ├── _base.scss │ ├── components │ │ ├── _contacts.scss │ │ ├── _root.scss │ │ ├── _auth.scss │ │ ├── _header.scss │ │ ├── _sidebar.scss │ │ ├── _contact-card.scss │ │ └── _contact.scss │ ├── _components.scss │ ├── base │ │ ├── _forms.scss │ │ ├── _scaffolding.scss │ │ ├── _colors.scss │ │ ├── _typography.scss │ │ └── _buttons.scss │ ├── style.scss │ └── layout │ │ └── _loading.scss ├── app │ ├── root.html │ ├── root.module.js │ ├── root.component.js │ ├── components │ │ ├── contact │ │ │ ├── contact-new │ │ │ │ ├── contact-new.html │ │ │ │ ├── contact-new.component.js │ │ │ │ ├── contact-new.controller.js │ │ │ │ └── contact-new.spec.js │ │ │ ├── contact-edit │ │ │ │ ├── contact-edit.html │ │ │ │ ├── contact-edit.component.js │ │ │ │ ├── contact-edit.controller.js │ │ │ │ └── contact-edit.spec.js │ │ │ ├── contact │ │ │ │ ├── contact.component.js │ │ │ │ ├── contact.controller.js │ │ │ │ ├── contact.html │ │ │ │ └── contact.spec.js │ │ │ ├── contact-tag │ │ │ │ ├── contact-tag.component.js │ │ │ │ ├── contact-tag.html │ │ │ │ ├── contact-tag.controller.js │ │ │ │ └── contact-tag.spec.js │ │ │ ├── contact.module.js │ │ │ ├── contact-detail │ │ │ │ ├── contact-detail.component.js │ │ │ │ ├── contact-detail.controller.js │ │ │ │ ├── contact-detail.spec.js │ │ │ │ └── contact-detail.html │ │ │ ├── contacts │ │ │ │ ├── contacts.filter.js │ │ │ │ ├── contacts.html │ │ │ │ ├── contacts.controller.js │ │ │ │ ├── contacts.component.js │ │ │ │ └── contacts.spec.js │ │ │ ├── length-check │ │ │ │ ├── length-check.spec.js │ │ │ │ └── length-check.directive.js │ │ │ ├── contact.service.js │ │ │ └── contacts.spec.js │ │ ├── auth │ │ │ ├── auth-form │ │ │ │ ├── auth-form.component.js │ │ │ │ ├── auth-form.controller.js │ │ │ │ ├── auth-form.html │ │ │ │ └── auth-form.spec.js │ │ │ ├── login │ │ │ │ ├── login.html │ │ │ │ ├── login.component.js │ │ │ │ ├── login.controller.js │ │ │ │ └── login.spec.js │ │ │ ├── register │ │ │ │ ├── register.html │ │ │ │ ├── register.component.js │ │ │ │ ├── register.controller.js │ │ │ │ └── register.spec.js │ │ │ ├── auth.module.js │ │ │ ├── auth.service.js │ │ │ └── auth.spec.js │ │ └── components.module.js │ └── common │ │ ├── app.html │ │ ├── app-sidebar.component.js │ │ ├── app-nav.component.js │ │ ├── app.module.js │ │ ├── app-nav.html │ │ ├── app-sidebar.html │ │ ├── app.component.js │ │ ├── app.controller.js │ │ ├── app-sidebar.controller.js │ │ └── app.spec.js ├── img │ ├── logo.png │ └── favicon.ico ├── fonts │ ├── MaterialIcons-Regular.eot │ ├── MaterialIcons-Regular.ttf │ ├── MaterialIcons-Regular.woff │ └── MaterialIcons-Regular.woff2 └── index.html ├── .firebaserc ├── docs ├── app │ ├── app.module.js │ ├── api.js │ ├── guide.js │ └── app.config.js ├── build │ ├── partials │ │ ├── api.html │ │ ├── guide.html │ │ ├── guide │ │ │ └── howTo.html │ │ └── api │ │ │ ├── components.html │ │ │ ├── components.auth │ │ │ ├── service.html │ │ │ └── service │ │ │ │ └── AuthService.html │ │ │ ├── components.contact │ │ │ ├── service.html │ │ │ ├── directive.html │ │ │ ├── type.html │ │ │ ├── service │ │ │ │ └── ContactService.html │ │ │ ├── directive │ │ │ │ └── lengthCheck.html │ │ │ └── type │ │ │ │ └── ContactEditController.html │ │ │ ├── common │ │ │ ├── directive.html │ │ │ ├── type │ │ │ │ ├── AppSidebarController.html │ │ │ │ └── AppController.html │ │ │ ├── type.html │ │ │ └── directive │ │ │ │ └── app.html │ │ │ ├── components.auth.html │ │ │ ├── common.html │ │ │ └── components.contact.html │ ├── src │ │ ├── app.module.js │ │ ├── api.js │ │ ├── guide.js │ │ ├── guide-data.js │ │ ├── api-data.js │ │ └── app.config.js │ └── index.html ├── config │ ├── templates │ │ ├── content.template.html │ │ ├── constant-data.template.js │ │ ├── module.template.html │ │ └── indexPage.template.html │ ├── processors │ │ ├── index-page.js │ │ ├── guide-data.js │ │ └── api-data.js │ └── index.js └── content │ ├── api │ └── index.md │ └── guide │ ├── howTo.md │ └── index.md ├── .gitignore ├── database.rules.json ├── firebase.json ├── .editorconfig ├── package.json ├── README.md ├── mocks └── firebase.mock.js ├── karma.conf.js └── gulpfile.babel.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /src/sass/_layout.scss: -------------------------------------------------------------------------------- 1 | @import "layout/loading"; 2 | -------------------------------------------------------------------------------- /src/sass/_utils.scss: -------------------------------------------------------------------------------- 1 | @import "utils/mixins.scss"; 2 | -------------------------------------------------------------------------------- /src/sass/_vendors.scss: -------------------------------------------------------------------------------- 1 | @import "vendors/vendor"; 2 | -------------------------------------------------------------------------------- /src/sass/vendors/_vendor.scss: -------------------------------------------------------------------------------- 1 | // Vendor placeholder file 2 | -------------------------------------------------------------------------------- /src/app/root.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "contacts-manager-e486f" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/img/logo.png -------------------------------------------------------------------------------- /docs/app/app.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular 4 | .module('docs', [ 5 | 'ui.router' 6 | ]); 7 | -------------------------------------------------------------------------------- /docs/build/partials/api.html: -------------------------------------------------------------------------------- 1 | 2 |

API

3 |

Select a link in side menu.

4 | 5 | 6 | -------------------------------------------------------------------------------- /src/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/img/favicon.ico -------------------------------------------------------------------------------- /docs/build/src/app.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular 4 | .module('docs', [ 5 | 'ui.router' 6 | ]); 7 | -------------------------------------------------------------------------------- /docs/config/templates/content.template.html: -------------------------------------------------------------------------------- 1 | {% block content %} 2 | {$ doc.description | marked $} 3 | {% endblock %} 4 | -------------------------------------------------------------------------------- /src/app/root.module.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('root', [ 3 | 'common', 4 | 'components', 5 | 'templates' 6 | ]); 7 | -------------------------------------------------------------------------------- /docs/build/partials/guide.html: -------------------------------------------------------------------------------- 1 | 2 |

Guide

3 |

This would be the base page for your guide section

4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/build/partials/guide/howTo.html: -------------------------------------------------------------------------------- 1 | 2 |

How to

3 |

Describe how to write your documentation

4 | 5 | 6 | -------------------------------------------------------------------------------- /src/fonts/MaterialIcons-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/fonts/MaterialIcons-Regular.eot -------------------------------------------------------------------------------- /src/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /docs/config/templates/constant-data.template.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('docs') 3 | .constant('{$ doc.name $}', {$ doc.items | json $}); 4 | -------------------------------------------------------------------------------- /src/fonts/MaterialIcons-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/fonts/MaterialIcons-Regular.woff -------------------------------------------------------------------------------- /src/fonts/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddmotto/angular-1-5-components-app/HEAD/src/fonts/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /src/app/root.component.js: -------------------------------------------------------------------------------- 1 | var root = { 2 | templateUrl: './root.html' 3 | }; 4 | 5 | angular 6 | .module('root') 7 | .component('root', root); 8 | -------------------------------------------------------------------------------- /src/sass/utils/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixin placeholder file 2 | // Here you should follow the same pattern as the other folders and separate your files 3 | -------------------------------------------------------------------------------- /docs/content/api/index.md: -------------------------------------------------------------------------------- 1 | @ngdoc content 2 | @module api 3 | @name Overview 4 | 5 | @description 6 | 7 | # API 8 | 9 | Select a link in side menu. 10 | -------------------------------------------------------------------------------- /docs/content/guide/howTo.md: -------------------------------------------------------------------------------- 1 | @ngdoc content 2 | @module guide 3 | @name How To 4 | @description 5 | 6 | # How to 7 | Describe how to write your documentation 8 | -------------------------------------------------------------------------------- /src/sass/_base.scss: -------------------------------------------------------------------------------- 1 | @import "base/colors"; 2 | @import "base/scaffolding"; 3 | @import "base/forms"; 4 | @import "base/buttons"; 5 | @import "base/typography"; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .DS_Store 4 | .tmp 5 | .sass-cache 6 | *.sublime-* 7 | .jekyll-metadata 8 | .idea 9 | npm-debug.log 10 | templates.js 11 | -------------------------------------------------------------------------------- /docs/content/guide/index.md: -------------------------------------------------------------------------------- 1 | @ngdoc content 2 | @module guide 3 | @name Overview 4 | 5 | @description 6 | 7 | # Guide 8 | This would be the base page for your guide section 9 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-new/contact-new.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/app/common/app.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-edit/contact-edit.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /src/sass/components/_contacts.scss: -------------------------------------------------------------------------------- 1 | .contacts__empty { 2 | text-align: center; 3 | padding: 30px 0; 4 | font-size: 24px; 5 | } 6 | .contacts__empty .material-icons { 7 | position: relative; 8 | top: 2px; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/common/app-sidebar.component.js: -------------------------------------------------------------------------------- 1 | var appSidebar = { 2 | templateUrl: './app-sidebar.html', 3 | controller: 'AppSidebarController' 4 | }; 5 | 6 | angular 7 | .module('common') 8 | .component('appSidebar', appSidebar); 9 | -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | ".read": true, 4 | "contacts": { 5 | "$uid": { 6 | ".read": "$uid === auth.uid", 7 | ".write": "$uid === auth.uid" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/common/app-nav.component.js: -------------------------------------------------------------------------------- 1 | var appNav = { 2 | bindings: { 3 | user: '<', 4 | onLogout: '&' 5 | }, 6 | templateUrl: './app-nav.html' 7 | }; 8 | 9 | angular 10 | .module('common') 11 | .component('appNav', appNav); 12 | -------------------------------------------------------------------------------- /src/sass/_components.scss: -------------------------------------------------------------------------------- 1 | @import "components/root"; 2 | @import "components/header"; 3 | @import "components/sidebar"; 4 | @import "components/auth"; 5 | @import "components/contacts"; 6 | @import "components/contact"; 7 | @import "components/contact-card"; 8 | -------------------------------------------------------------------------------- /src/sass/components/_root.scss: -------------------------------------------------------------------------------- 1 | root { 2 | display: block; 3 | } 4 | root .loader { 5 | position: absolute; 6 | top: 40%; 7 | left: 45%; 8 | font-size: 24px; 9 | display: block; 10 | text-align: center; 11 | font-weight: 900; 12 | } 13 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "hosting": { 6 | "public": "dist", 7 | "rewrites": [ 8 | { 9 | "source": "**", 10 | "destination": "/index.html" 11 | } 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/app/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular 4 | .module('docs') 5 | .controller('ApiController', ApiController); 6 | 7 | function ApiController(API_DATA) { 8 | 9 | var ctrl = this; 10 | ctrl.allPages = API_DATA; 11 | 12 | } 13 | 14 | ApiController.$inject = ["API_DATA"]; 15 | -------------------------------------------------------------------------------- /docs/build/src/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular 4 | .module('docs') 5 | .controller('ApiController', ApiController); 6 | 7 | function ApiController(API_DATA) { 8 | 9 | var ctrl = this; 10 | ctrl.allPages = API_DATA; 11 | 12 | } 13 | 14 | ApiController.$inject = ["API_DATA"]; 15 | -------------------------------------------------------------------------------- /docs/app/guide.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular 4 | .module('docs') 5 | .controller('GuideController', GuideController); 6 | 7 | function GuideController(GUIDE_DATA) { 8 | 9 | var ctrl = this; 10 | ctrl.allPages = GUIDE_DATA; 11 | 12 | } 13 | 14 | GuideController.$inject = ["GUIDE_DATA"]; 15 | -------------------------------------------------------------------------------- /docs/build/src/guide.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular 4 | .module('docs') 5 | .controller('GuideController', GuideController); 6 | 7 | function GuideController(GUIDE_DATA) { 8 | 9 | var ctrl = this; 10 | ctrl.allPages = GUIDE_DATA; 11 | 12 | } 13 | 14 | GuideController.$inject = ["GUIDE_DATA"]; 15 | -------------------------------------------------------------------------------- /src/app/components/contact/contact/contact.component.js: -------------------------------------------------------------------------------- 1 | var contact = { 2 | bindings: { 3 | contact: '<', 4 | onSelect: '&' 5 | }, 6 | templateUrl: './contact.html', 7 | controller: 'ContactController' 8 | }; 9 | 10 | angular 11 | .module('components.contact') 12 | .component('contact', contact); 13 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-tag/contact-tag.component.js: -------------------------------------------------------------------------------- 1 | var contactTag = { 2 | bindings: { 3 | tag: '<', 4 | onChange: '&' 5 | }, 6 | templateUrl: './contact-tag.html', 7 | controller: 'ContactTagController' 8 | }; 9 | 10 | angular 11 | .module('components.contact') 12 | .component('contactTag', contactTag); 13 | -------------------------------------------------------------------------------- /src/app/components/auth/auth-form/auth-form.component.js: -------------------------------------------------------------------------------- 1 | var authForm = { 2 | bindings: { 3 | user: '<', 4 | button: '@', 5 | message: '@', 6 | onSubmit: '&' 7 | }, 8 | templateUrl: './auth-form.html', 9 | controller: 'AuthFormController' 10 | }; 11 | 12 | angular 13 | .module('components.auth') 14 | .component('authForm', authForm); 15 | -------------------------------------------------------------------------------- /src/app/components/contact/contact.module.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * @ngdoc module 5 | * @name components.contact 6 | * 7 | * @requires ui.router 8 | * 9 | * @description 10 | * 11 | * This is the contact module. It includes all of our components for the contact feature. 12 | * 13 | **/ 14 | angular 15 | .module('components.contact', [ 16 | 'ui.router' 17 | ]); 18 | -------------------------------------------------------------------------------- /src/app/components/contact/contact/contact.controller.js: -------------------------------------------------------------------------------- 1 | function ContactController() { 2 | var ctrl = this; 3 | ctrl.selectContact = function () { 4 | ctrl.onSelect({ 5 | $event: { 6 | contactId: ctrl.contact.$id 7 | } 8 | }); 9 | }; 10 | } 11 | 12 | angular 13 | .module('components.contact') 14 | .controller('ContactController', ContactController); 15 | -------------------------------------------------------------------------------- /docs/build/src/guide-data.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('docs') 3 | .constant('GUIDE_DATA', [ 4 | { 5 | "name": "Overview", 6 | "type": "content", 7 | "outputPath": "partials/guide.html", 8 | "url": "guide" 9 | }, 10 | { 11 | "name": "How To", 12 | "type": "content", 13 | "outputPath": "partials/guide/howTo.html", 14 | "url": "guide/howTo" 15 | } 16 | ]); 17 | -------------------------------------------------------------------------------- /src/app/components/auth/login/login.html: -------------------------------------------------------------------------------- 1 |
2 |

Login

3 | 8 | 9 |
10 |
11 | 12 | Don't have an account? Create one here. 13 | 14 |
15 | -------------------------------------------------------------------------------- /src/app/components/auth/register/register.html: -------------------------------------------------------------------------------- 1 |
2 |

Register

3 | 8 | 9 |
10 |
11 | 12 | Already have an account? Login here. 13 | 14 |
15 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-detail/contact-detail.component.js: -------------------------------------------------------------------------------- 1 | var contactDetail = { 2 | bindings: { 3 | contact: '<', 4 | onSave: '&', 5 | onUpdate: '&', 6 | onDelete: '&' 7 | }, 8 | templateUrl: './contact-detail.html', 9 | controller: 'ContactDetailController' 10 | }; 11 | 12 | angular 13 | .module('components.contact') 14 | .component('contactDetail', contactDetail); 15 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | components 4 |

5 | 6 |

This is the components module. It includes all of our components.

7 | 8 | 9 | components 10 | 11 | Requires components.contact,components.auth 12 | 13 |
14 |

Module Components

15 | 16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/app/components/components.module.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * @ngdoc module 5 | * @name components 6 | * 7 | * @requires components.contact 8 | * @requires components.auth 9 | * 10 | * @description 11 | * 12 | * This is the components module. It includes all of our components. 13 | * 14 | **/ 15 | 16 | angular 17 | .module('components', [ 18 | 'components.contact', 19 | 'components.auth' 20 | ]); 21 | -------------------------------------------------------------------------------- /src/app/components/contact/contacts/contacts.filter.js: -------------------------------------------------------------------------------- 1 | function contactsFilter() { 2 | return function (collection, params) { 3 | return collection.filter(function (item) { 4 | return item.tag === ( 5 | params.filter === 'none' ? item.tag : params.filter 6 | ); 7 | }); 8 | }; 9 | } 10 | 11 | angular 12 | .module('components.contact') 13 | .filter('contactsFilter', contactsFilter); 14 | -------------------------------------------------------------------------------- /src/app/components/auth/register/register.component.js: -------------------------------------------------------------------------------- 1 | var register = { 2 | templateUrl: './register.html', 3 | controller: 'RegisterController' 4 | }; 5 | 6 | angular 7 | .module('components.auth') 8 | .component('register', register) 9 | .config(function ($stateProvider) { 10 | $stateProvider 11 | .state('auth.register', { 12 | url: '/register', 13 | component: 'register' 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-new/contact-new.component.js: -------------------------------------------------------------------------------- 1 | var contactNew = { 2 | templateUrl: './contact-new.html', 3 | controller: 'ContactNewController' 4 | }; 5 | 6 | angular 7 | .module('components.contact') 8 | .component('contactNew', contactNew) 9 | .config(function ($stateProvider) { 10 | $stateProvider 11 | .state('new', { 12 | parent: 'app', 13 | url: '/new', 14 | component: 'contactNew' 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Contacts Manager 6 | 7 | 8 | 9 | 10 | 11 | Loading... 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/components/contact/contacts/contacts.html: -------------------------------------------------------------------------------- 1 |
2 | 10 |
13 | face 14 | There's no one here... 15 |
16 |
17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /src/app/components/auth/auth-form/auth-form.controller.js: -------------------------------------------------------------------------------- 1 | function AuthFormController() { 2 | var ctrl = this; 3 | ctrl.$onChanges = function (changes) { 4 | if (changes.user) { 5 | ctrl.user = angular.copy(ctrl.user); 6 | } 7 | }; 8 | ctrl.submitForm = function () { 9 | ctrl.onSubmit({ 10 | $event: { 11 | user: ctrl.user 12 | } 13 | }); 14 | }; 15 | } 16 | 17 | angular 18 | .module('components.auth') 19 | .controller('AuthFormController', AuthFormController); 20 | -------------------------------------------------------------------------------- /src/app/components/contact/contacts/contacts.controller.js: -------------------------------------------------------------------------------- 1 | function ContactsController($filter, $state) { 2 | var ctrl = this; 3 | 4 | ctrl.$onInit = function() { 5 | ctrl.filteredContacts = $filter('contactsFilter')(ctrl.contacts, ctrl.filter); 6 | }; 7 | 8 | ctrl.goToContact = function (event) { 9 | $state.go('contact', { 10 | id: event.contactId 11 | }); 12 | }; 13 | } 14 | 15 | angular 16 | .module('components.contact') 17 | .controller('ContactsController', ContactsController); 18 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-tag/contact-tag.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 17 |
18 | -------------------------------------------------------------------------------- /docs/config/processors/index-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function indexPageProcessor() { 4 | return { 5 | $runAfter: ['adding-extra-docs'], 6 | $runBefore: ['extra-docs-added'], 7 | $process: process 8 | }; 9 | 10 | function process(docs) { 11 | 12 | docs.push({ 13 | docType: 'indexPage', 14 | template: 'indexPage.template.html', 15 | outputPath: 'index.html', 16 | path: 'index.html', 17 | id: 'index' 18 | }); 19 | 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/app/common/app.module.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * @ngdoc module 5 | * @name common 6 | * 7 | * @requires ui.router 8 | * @requires angular-loading-bar 9 | * 10 | * @description 11 | * 12 | * This is the common module. It includes a run method that setups the loading bar. 13 | * 14 | **/ 15 | angular 16 | .module('common', [ 17 | 'ui.router', 18 | 'angular-loading-bar' 19 | ]) 20 | .run(function ($transitions, cfpLoadingBar) { 21 | $transitions.onStart({}, cfpLoadingBar.start); 22 | $transitions.onSuccess({}, cfpLoadingBar.complete); 23 | }); 24 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.auth/service.html: -------------------------------------------------------------------------------- 1 | 2 |

Service components in components.auth

3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 |
NameDescription
AuthService

Provides HTTP methods for our firebase authentification.

17 |
21 |
22 |
23 | 24 | -------------------------------------------------------------------------------- /src/app/components/contact/contact/contact.html: -------------------------------------------------------------------------------- 1 |
2 | 15 |
16 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.contact/service.html: -------------------------------------------------------------------------------- 1 | 2 |

Service components in components.contact

3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 |
NameDescription
ContactService

Provides HTTP methods for our firebase connection.

17 |
21 |
22 |
23 | 24 | -------------------------------------------------------------------------------- /src/app/components/auth/login/login.component.js: -------------------------------------------------------------------------------- 1 | var login = { 2 | templateUrl: './login.html', 3 | controller: 'LoginController' 4 | }; 5 | 6 | angular 7 | .module('components.auth') 8 | .component('login', login) 9 | .config(function ($stateProvider, $urlRouterProvider) { 10 | $stateProvider 11 | .state('auth', { 12 | redirectTo: 'auth.login', 13 | url: '/auth', 14 | template: '
' 15 | }) 16 | .state('auth.login', { 17 | url: '/login', 18 | component: 'login' 19 | }); 20 | $urlRouterProvider.otherwise('/auth/login'); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/components/auth/login/login.controller.js: -------------------------------------------------------------------------------- 1 | function LoginController(AuthService, $state) { 2 | var ctrl = this; 3 | ctrl.$onInit = function () { 4 | ctrl.error = null; 5 | ctrl.user = { 6 | email: '', 7 | password: '' 8 | }; 9 | }; 10 | ctrl.loginUser = function (event) { 11 | return AuthService 12 | .login(event.user) 13 | .then(function () { 14 | $state.go('app'); 15 | }, function (reason) { 16 | ctrl.error = reason.message; 17 | }); 18 | }; 19 | } 20 | 21 | angular 22 | .module('components.auth') 23 | .controller('LoginController', LoginController); 24 | -------------------------------------------------------------------------------- /src/app/components/auth/register/register.controller.js: -------------------------------------------------------------------------------- 1 | function RegisterController(AuthService, $state) { 2 | var ctrl = this; 3 | ctrl.$onInit = function () { 4 | ctrl.error = null; 5 | ctrl.user = { 6 | email: '', 7 | password: '' 8 | }; 9 | }; 10 | ctrl.createUser = function (event) { 11 | return AuthService 12 | .register(event.user) 13 | .then(function () { 14 | $state.go('app'); 15 | }, function (reason) { 16 | ctrl.error = reason.message; 17 | }); 18 | }; 19 | } 20 | 21 | angular 22 | .module('components.auth') 23 | .controller('RegisterController', RegisterController); 24 | -------------------------------------------------------------------------------- /src/sass/base/_forms.scss: -------------------------------------------------------------------------------- 1 | input { 2 | font-size: 16px; 3 | padding: 10px 15px; 4 | border: 1px solid rgba(0, 0, 0, 0.1); 5 | border-radius: 3px; 6 | 7 | &:hover { 8 | border-color: colors(button); 9 | } 10 | } 11 | 12 | input:focus { 13 | border-color: colors(button, hover); 14 | outline: none; 15 | } 16 | 17 | 18 | 19 | 20 | .dynamic-input { 21 | border-color: transparent; 22 | } 23 | 24 | .dynamic-input--no-contents { 25 | border-color: rgba(0, 0, 0, 0.1); 26 | } 27 | 28 | .dynamic-input:hover { 29 | border-color: rgba(0, 0, 0, 0.1); 30 | } 31 | 32 | .dynamic-input:focus { 33 | border-color: rgba(0, 133, 215, 1); 34 | } 35 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-tag/contact-tag.controller.js: -------------------------------------------------------------------------------- 1 | function ContactTagController() { 2 | var ctrl = this; 3 | ctrl.$onInit = function () { 4 | ctrl.tags = [ 5 | 'friends', 'family', 'acquaintances', 'following' 6 | ]; 7 | }; 8 | ctrl.$onChanges = function (changes) { 9 | if (changes.tag) { 10 | ctrl.tag = angular.copy(ctrl.tag); 11 | } 12 | }; 13 | ctrl.updateTag = function (tag) { 14 | ctrl.onChange({ 15 | $event: { 16 | tag: tag 17 | } 18 | }); 19 | }; 20 | } 21 | 22 | angular 23 | .module('components.contact') 24 | .controller('ContactTagController', ContactTagController); 25 | -------------------------------------------------------------------------------- /src/sass/components/_auth.scss: -------------------------------------------------------------------------------- 1 | .auth { 2 | background: #fff; 3 | border: 1px solid rgba(0, 0, 0, 0.1); 4 | box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1); 5 | padding: 20px 30px; 6 | width: 600px; 7 | margin: 50px auto 0; 8 | } 9 | 10 | .auth__info { 11 | text-align: center; 12 | margin-top: 20px; 13 | } 14 | 15 | .auth__info a { 16 | font-size: 12px; 17 | } 18 | 19 | .auth__info a:hover { 20 | text-decoration: underline; 21 | } 22 | 23 | .auth h1 { 24 | margin: 0 0 10px; 25 | } 26 | 27 | .auth label { 28 | margin-bottom: 10px; 29 | display: block; 30 | } 31 | 32 | .auth input { 33 | width: 100%; 34 | } 35 | 36 | .auth-button { 37 | text-align: center; 38 | } 39 | -------------------------------------------------------------------------------- /docs/build/partials/api/common/directive.html: -------------------------------------------------------------------------------- 1 | 2 |

Directive components in common

3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 |
NameDescription
app

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 17 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

18 |
22 |
23 |
24 | 25 | -------------------------------------------------------------------------------- /src/app/common/app-nav.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 11 |
12 | {{ ::$ctrl.user.email }} 13 | 14 | 15 | power_settings_new 16 | Logout 17 | 18 | 19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /src/app/common/app-sidebar.html: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /src/sass/base/_scaffolding.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | -webkit-box-sizing: border-box; 4 | -moz-box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | body { 10 | font: 300 14px/1.4 'Helvetica Neue', Helvetica, Arial, sans-serif; 11 | color: colors(scaffold); 12 | background: #FFFFFF; 13 | } 14 | a { 15 | color: colors(scaffold); 16 | text-decoration: none; 17 | } 18 | ul { 19 | list-style: none; 20 | } 21 | img { 22 | max-width: 100%; 23 | } 24 | 25 | 26 | 27 | 28 | .app { 29 | margin-left: 250px; 30 | } 31 | 32 | 33 | 34 | .message { 35 | &:not(:first-child) { 36 | margin-top: 15px; 37 | } 38 | &.error { 39 | color: colors(scaffold, error); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/sass/style.scss: -------------------------------------------------------------------------------- 1 | 2 | // Utils 3 | // Contains all mixins, functions and helpers 4 | @import "utils"; 5 | 6 | // Base 7 | // Contains all the global styles for the application as well as the resets, typography, colors, buttons, etc... 8 | // You should also define a _variables.scss to define all app variables 9 | @import "base"; 10 | 11 | 12 | // Components 13 | // Contains each isolated component. Should ideally mimic the AngularJS component structure 14 | @import "components"; 15 | 16 | 17 | // Layout 18 | // Contains styling elements for the larger 'core' modules like loading bars / indicators, wrappers, containers, etc... 19 | @import "layout"; 20 | 21 | 22 | // Vendors 23 | // Contains all the 3rd party styles 24 | @import "vendors"; 25 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.contact/directive.html: -------------------------------------------------------------------------------- 1 | 2 |

Directive components in components.contact

3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 |
NameDescription
lengthCheck

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 17 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

18 |
22 |
23 |
24 | 25 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-edit/contact-edit.component.js: -------------------------------------------------------------------------------- 1 | var contactEdit = { 2 | bindings: { 3 | contact: '<' 4 | }, 5 | templateUrl: './contact-edit.html', 6 | controller: 'ContactEditController' 7 | }; 8 | 9 | angular 10 | .module('components.contact') 11 | .component('contactEdit', contactEdit) 12 | .config(function ($stateProvider) { 13 | $stateProvider 14 | .state('contact', { 15 | parent: 'app', 16 | url: '/contact/:id', 17 | component: 'contactEdit', 18 | resolve: { 19 | contact: function ($transition$, ContactService) { 20 | var key = $transition$.params().id; 21 | return ContactService.getContactById(key).$loaded(); 22 | } 23 | } 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.contact/type.html: -------------------------------------------------------------------------------- 1 | 2 |

Type components in components.contact

3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 |
NameDescription
ContactEditController

Lorem Ipsum 1

17 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 18 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

19 |
23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /src/app/components/auth/auth-form/auth-form.html: -------------------------------------------------------------------------------- 1 |
2 | 10 | 18 |
19 | 22 |
23 |
24 | {{ $ctrl.message }} 25 |
26 |
27 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-new/contact-new.controller.js: -------------------------------------------------------------------------------- 1 | function ContactNewController(ContactService, $state) { 2 | var ctrl = this; 3 | ctrl.$onInit = function () { 4 | ctrl.contact = { 5 | name: '', 6 | email: '', 7 | job: '', 8 | location: '', 9 | social: { 10 | facebook: '', 11 | github: '', 12 | twitter: '', 13 | linkedin: '' 14 | }, 15 | tag: 'none' 16 | }; 17 | }; 18 | ctrl.createNewContact = function (event) { 19 | return ContactService 20 | .createNewContact(event.contact) 21 | .then(function (contact) { 22 | $state.go('contact', { 23 | id: contact.key 24 | }); 25 | }); 26 | }; 27 | } 28 | 29 | angular 30 | .module('components.contact') 31 | .controller('ContactNewController', ContactNewController); 32 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-detail/contact-detail.controller.js: -------------------------------------------------------------------------------- 1 | function ContactDetailController() { 2 | var ctrl = this; 3 | ctrl.$onInit = function () { 4 | ctrl.isNewContact = !ctrl.contact.$id; 5 | }; 6 | ctrl.saveContact = function () { 7 | ctrl.onSave({ 8 | $event: { 9 | contact: ctrl.contact 10 | } 11 | }); 12 | }; 13 | ctrl.updateContact = function () { 14 | ctrl.onUpdate({ 15 | $event: { 16 | contact: ctrl.contact 17 | } 18 | }); 19 | }; 20 | ctrl.deleteContact = function () { 21 | ctrl.onDelete({ 22 | $event: { 23 | contact: ctrl.contact 24 | } 25 | }); 26 | }; 27 | ctrl.tagChange = function (event) { 28 | ctrl.contact.tag = event.tag; 29 | ctrl.updateContact(); 30 | } 31 | } 32 | 33 | angular 34 | .module('components.contact') 35 | .controller('ContactDetailController', ContactDetailController); 36 | -------------------------------------------------------------------------------- /src/app/components/contact/contacts/contacts.component.js: -------------------------------------------------------------------------------- 1 | var contacts = { 2 | bindings: { 3 | contacts: '<', 4 | filter: '<' 5 | }, 6 | templateUrl: './contacts.html', 7 | controller: 'ContactsController' 8 | }; 9 | 10 | angular 11 | .module('components.contact') 12 | .component('contacts', contacts) 13 | .config(function ($stateProvider) { 14 | $stateProvider 15 | .state('contacts', { 16 | parent: 'app', 17 | url: '/contacts?filter', 18 | component: 'contacts', 19 | params: { 20 | filter: { 21 | value: 'none' 22 | } 23 | }, 24 | resolve: { 25 | contacts: function (ContactService) { 26 | return ContactService.getContactList().$loaded(); 27 | }, 28 | filter: function ($transition$) { 29 | return $transition$.params(); 30 | } 31 | } 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/components/auth/auth-form/auth-form.spec.js: -------------------------------------------------------------------------------- 1 | describe('Auth', function () { 2 | beforeEach(module('components.auth')); 3 | 4 | describe('AuthFormController', function () { 5 | var $componentController, 6 | controller, 7 | mockUser = { $id: 1 }, 8 | mockSubmit = angular.noop; 9 | 10 | beforeEach(inject(function ($injector) { 11 | $componentController = $injector.get('$componentController'); 12 | 13 | controller = $componentController('authForm', 14 | { $scope: {} }, 15 | { user: mockUser, button: '', message: '', onSubmit: mockSubmit } 16 | ); 17 | })); 18 | 19 | it('should call onSelect with the correct payload', function () { 20 | var payload = { $event: { user: mockUser } }; 21 | 22 | spyOn(controller, 'onSubmit'); 23 | controller.submitForm(); 24 | expect(controller.onSubmit).toHaveBeenCalledWith(payload); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/sass/base/_colors.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | // Purpose: These maps are meant to group color variables together and to help simplify the variable names when calling them. 3 | 4 | $brandColors: ( 5 | scaffold: ( 6 | base: #5c5c5c, 7 | error: #B72B2C, 8 | newContact: #50D749, 9 | logout: #E32E30 10 | ), 11 | button: ( 12 | base: rgba(0, 133, 215, 0.6), 13 | shadow: rgba(0, 133, 215, 1), 14 | hover: #0085d7, 15 | hoverShadow: darken(#0085d7, 8%) 16 | ), 17 | buttonDelete: ( 18 | base: rgba(227, 46, 48, 0.6), 19 | shadow: rgba(227, 46, 48, 1), 20 | hover: #e32e30, 21 | hoverShadow: darken(#e32e30, 10%) 22 | ), 23 | pills: ( 24 | base: #008FED, 25 | family: #FF387E, 26 | acquaintances: #B5D158, 27 | following: #DB5A3D 28 | ) 29 | ); 30 | 31 | @function colors($color, $tone: "base") { 32 | @return map-get(map-get($brandColors, $color), $tone); 33 | } 34 | -------------------------------------------------------------------------------- /src/app/common/app.component.js: -------------------------------------------------------------------------------- 1 | var app = { 2 | templateUrl: './app.html', 3 | controller: 'AppController' 4 | }; 5 | 6 | /** 7 | * @ngdoc directive 8 | * @name app 9 | * @module common 10 | * 11 | * @description 12 | * 13 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 14 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 15 | * 16 | * @usage 17 | * 18 | * ### How to use 19 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 20 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 21 | **/ 22 | angular 23 | .module('common') 24 | .component('app', app) 25 | .config(function ($stateProvider) { 26 | $stateProvider 27 | .state('app', { 28 | redirectTo: 'contacts', 29 | url: '/app', 30 | data: { 31 | requiredAuth: true 32 | }, 33 | component: 'app' 34 | }) 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/components/contact/contact/contact.spec.js: -------------------------------------------------------------------------------- 1 | describe('Contact', function () { 2 | beforeEach(module('components.contact')); 3 | 4 | describe('Controller', function () { 5 | var $componentController, 6 | controller, 7 | mockContact = { $id: 1 }, 8 | mockSelect = angular.noop; 9 | 10 | beforeEach(inject(function ($injector) { 11 | $componentController = $injector.get('$componentController'); 12 | controller = $componentController('contact', 13 | { $scope: {} }, 14 | { contact: mockContact, onSelect: mockSelect } 15 | ); 16 | })); 17 | 18 | it('should bind to the correct contact', function () { 19 | expect(controller.contact.$id).toEqual(mockContact.$id); 20 | }); 21 | 22 | it('should call onSelect with the correct payload', function () { 23 | var payload = { $event: { contactId: mockContact.$id } }; 24 | 25 | spyOn(controller, 'onSelect'); 26 | controller.selectContact(); 27 | expect(controller.onSelect).toHaveBeenCalledWith(payload); 28 | }); 29 | }); 30 | }); -------------------------------------------------------------------------------- /src/sass/components/_header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 60px; 3 | } 4 | .header__fixed { 5 | background: #fff; 6 | height: inherit; 7 | position: fixed; 8 | top: 0; 9 | left: 250px; 10 | right: 0; 11 | border-bottom: 1px solid #E9EEF3; 12 | padding: 13px 20px; 13 | font-size: 16px; 14 | } 15 | .header__brand { 16 | font-size: 24px; 17 | float: left; 18 | } 19 | .header__logout { 20 | float: right; 21 | } 22 | .header__logout a { 23 | display: inline-block; 24 | color: colors(scaffold, logout); 25 | padding: 5px 10px; 26 | margin-left: 10px; 27 | &:hover { 28 | color: darken(colors(scaffold, logout), 15%); 29 | } 30 | } 31 | .header__button { 32 | font-size: 16px; 33 | } 34 | .header__button .material-icons { 35 | font-size: inherit; 36 | position: relative; 37 | top: 2px; 38 | margin-right: 3px; 39 | } 40 | .header__button--new-contact { 41 | color: colors(scaffold, newContact); 42 | margin-left: 30px; 43 | position: relative; 44 | top: -2px; 45 | &:hover { 46 | color: darken(colors(scaffold, newContact), 15%); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/common/app.controller.js: -------------------------------------------------------------------------------- 1 | function AppController(AuthService, $state) { 2 | var ctrl = this; 3 | ctrl.user = AuthService.getUser(); 4 | 5 | /** 6 | * @ngdoc method 7 | * @name AppController#logout 8 | * 9 | * @description Logout :) 10 | */ 11 | ctrl.logout = function () { 12 | AuthService.logout().then(function () { 13 | $state.go('auth.login'); 14 | }); 15 | }; 16 | } 17 | 18 | /** 19 | * @ngdoc type 20 | * @module common 21 | * @name AppController 22 | * 23 | * @description 24 | * 25 | * ## Lorem Ipsum 1 26 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 27 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 28 | * 29 | * ## Lorem Ipsum 2 30 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 31 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 32 | */ 33 | angular 34 | .module('common') 35 | .controller('AppController', AppController); 36 | -------------------------------------------------------------------------------- /docs/config/processors/guide-data.js: -------------------------------------------------------------------------------- 1 | 2 | var _ = require('lodash'); 3 | 4 | 5 | function buildDocData(doc) { 6 | return _.assign({ 7 | name: doc.name, 8 | type: doc.docType, 9 | outputPath: doc.outputPath, 10 | url: doc.path, 11 | }); 12 | } 13 | 14 | module.exports = function guidePagesProcessor(moduleMap) { 15 | return { 16 | $runAfter: ['paths-computed'], 17 | $runBefore: ['rendering-docs'], 18 | $process: process 19 | }; 20 | 21 | function process(docs) { 22 | 23 | var guides = _(docs).filter(function(doc) { 24 | return doc.docType == 'content' && doc.module == 'guide'; 25 | }) 26 | 27 | .sortBy(function(page) { 28 | return page.sortOrder || page.path; 29 | }) 30 | 31 | .map(buildDocData) 32 | 33 | .value(); 34 | 35 | docs.push({ 36 | name: 'GUIDE_DATA', 37 | template: 'constant-data.template.js', 38 | outputPath: 'src/guide-data.js', 39 | items: guides 40 | }); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.auth.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | components.auth 4 |

5 | 6 |

This is the auth module. It includes our auth components

7 | 8 | 9 | componentsAuth 10 | 11 | Requires ui.router,firebase 12 | 13 |
14 |

Module Components

15 | 16 |
17 |

Service

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 |
NameDescription
AuthService

Provides HTTP methods for our firebase authentification.

27 |
31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/sass/components/_sidebar.scss: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | background: #F7FAFC; 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | bottom: 0; 7 | width: 250px; 8 | border-right: 1px solid #DEE8EF; 9 | } 10 | .sidebar__logo { 11 | margin-top: 15px; 12 | } 13 | .sidebar__logo a { 14 | display: block; 15 | width: 160px; 16 | margin: 0 auto; 17 | } 18 | .sidebar__list { 19 | margin-top: 20px; 20 | } 21 | .sidebar__item { 22 | 23 | } 24 | .sidebar__link { 25 | display: block; 26 | color: #909090; 27 | font-size: 14px; 28 | padding: 5px 20px; 29 | margin-bottom: 5px; 30 | font-weight: 500; 31 | } 32 | .sidebar__link:hover { 33 | color: #2F3030; 34 | } 35 | .sidebar__link--active { 36 | color: #2F3030; 37 | } 38 | .sidebar__link .material-icons { 39 | width: 24px; 40 | margin-right: 7px; 41 | font-size: 18px; 42 | text-align: center; 43 | color: #74A7DC; 44 | position: relative; 45 | top: 3px; 46 | } 47 | .sidebar__header { 48 | font-size: 12px; 49 | color: #A4B4C4; 50 | font-weight: 500; 51 | text-transform: uppercase; 52 | padding: 5px 20px; 53 | margin: 0 0 54 | } 55 | -------------------------------------------------------------------------------- /docs/build/partials/api/common/type/AppSidebarController.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

AppSidebarController

6 |
    7 | 8 |
  1. 9 | - type in module common 10 |
  2. 11 |
12 |
13 | 14 | 15 | 16 |
17 |

Lorem Ipsum 1

18 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 19 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

20 |

Lorem Ipsum 2

21 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

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 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-tag/contact-tag.spec.js: -------------------------------------------------------------------------------- 1 | describe('Contact', function () { 2 | beforeEach(module('components.contact')); 3 | 4 | describe('ContactController', function () { 5 | var $componentController, 6 | controller, 7 | mockTag = 'friends', 8 | mockChange = angular.noop; 9 | 10 | beforeEach(inject(function ($injector) { 11 | $componentController = $injector.get('$componentController'); 12 | controller = $componentController('contactTag', 13 | { $scope: {} }, 14 | { tag: mockTag, onChange: mockChange } 15 | ); 16 | })); 17 | 18 | it('should bind to the correct tag', function () { 19 | var mockTag = 'football'; 20 | controller.tag = mockTag; 21 | expect(controller.tag).toEqual(mockTag); 22 | }); 23 | 24 | it('should call onSelect with the correct payload', function () { 25 | var tag = 'mate', 26 | payload = { $event: { tag: tag }}; 27 | 28 | spyOn(controller, 'onChange'); 29 | controller.updateTag(tag); 30 | expect(controller.onChange).toHaveBeenCalledWith(payload); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/sass/components/_contact-card.scss: -------------------------------------------------------------------------------- 1 | .contact-card { 2 | background: #fff; 3 | border-bottom: 1px solid #e5e5e5; 4 | } 5 | .contact-card__link { 6 | display: block; 7 | padding: 15px 20px; 8 | font-size: 16px; 9 | overflow: hidden; 10 | } 11 | .contact-card__column { 12 | float: left; 13 | } 14 | .contact-card__column--name { 15 | width: 30%; 16 | } 17 | .contact-card__pill { 18 | opacity: 0.4; 19 | display: inline-block; 20 | font-size: 12px; 21 | border-radius: 3px; 22 | text-transform: uppercase; 23 | padding: 2px 5px 1px; 24 | font-weight: 500; 25 | margin-left: 10px; 26 | color: #fff; 27 | &:hover { 28 | opacity: 0.65; 29 | } 30 | } 31 | .contact-card__column .contact-card__pill { 32 | opacity: 1 33 | } 34 | .contact-card__pill a { 35 | color: #fff; 36 | } 37 | .contact-card__pill--friends { 38 | background: colors(pills); 39 | } 40 | .contact-card__pill--family { 41 | background: colors(pills, family); 42 | } 43 | .contact-card__pill--acquaintances { 44 | background: colors(pills, acquaintances); 45 | } 46 | .contact-card__pill--following { 47 | background: colors(pills, following); 48 | } 49 | -------------------------------------------------------------------------------- /docs/build/partials/api/common/type.html: -------------------------------------------------------------------------------- 1 | 2 |

Type components in common

3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 |
NameDescription
AppSidebarController

Lorem Ipsum 1

17 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 18 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

19 |
AppController

Lorem Ipsum 1

25 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 26 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

27 |
31 |
32 |
33 | 34 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.auth/service/AuthService.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

AuthService

6 |
    7 | 8 | 9 | 10 |
  1. 11 | - service in module components.auth 12 |
  2. 13 |
14 |
15 | 16 | 17 | 18 |
19 |

Provides HTTP methods for our firebase authentification.

20 |

Lorem Ipsum 1

21 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

23 |

Lorem Ipsum 2

24 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 25 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

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 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.contact/service/ContactService.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

ContactService

6 |
    7 | 8 | 9 | 10 |
  1. 11 | - service in module components.contact 12 |
  2. 13 |
14 |
15 | 16 | 17 | 18 |
19 |

Provides HTTP methods for our firebase connection.

20 |

Lorem Ipsum 1

21 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

23 |

Lorem Ipsum 2

24 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 25 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

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 | -------------------------------------------------------------------------------- /src/sass/base/_typography.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url(../../fonts/MaterialIcons-Regular.eot); /* For IE6-8 */ 6 | src: local('Material Icons'), 7 | local('MaterialIcons-Regular'), 8 | url(../../fonts/MaterialIcons-Regular.woff2) format('woff2'), 9 | url(../../fonts/MaterialIcons-Regular.woff) format('woff'), 10 | url(../../fonts/MaterialIcons-Regular.ttf) format('truetype'); 11 | } 12 | 13 | .material-icons { 14 | font-family: 'Material Icons'; 15 | font-weight: normal; 16 | font-style: normal; 17 | font-size: 24px; /* Preferred icon size */ 18 | display: inline-block; 19 | width: 1em; 20 | height: 1em; 21 | line-height: 1; 22 | text-transform: none; 23 | letter-spacing: normal; 24 | word-wrap: normal; 25 | white-space: nowrap; 26 | direction: ltr; 27 | 28 | /* Support for all WebKit browsers. */ 29 | -webkit-font-smoothing: antialiased; 30 | /* Support for Safari and Chrome. */ 31 | text-rendering: optimizeLegibility; 32 | 33 | /* Support for Firefox. */ 34 | -moz-osx-font-smoothing: grayscale; 35 | 36 | /* Support for IE. */ 37 | font-feature-settings: 'liga'; 38 | } 39 | -------------------------------------------------------------------------------- /docs/build/partials/api/common/directive/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

app

6 |
    7 | 8 |
  1. 9 | - directive in module common 10 |
  2. 11 |
12 |
13 | 14 | 15 | 16 |
17 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 18 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 |

Directive Info

32 | 37 | 38 | 39 |

Usage

40 |
41 | 42 |

How to use

43 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 44 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

45 | 46 | 47 |
48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.contact/directive/lengthCheck.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

lengthCheck

6 |
    7 | 8 |
  1. 9 | - directive in module components.contact 10 |
  2. 11 |
12 |
13 | 14 | 15 | 16 |
17 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 18 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 |

Directive Info

32 | 37 | 38 | 39 |

Usage

40 |
41 | 42 |

How to use

43 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 44 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

45 | 46 | 47 |
48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /src/app/common/app-sidebar.controller.js: -------------------------------------------------------------------------------- 1 | function AppSidebarController() { 2 | var ctrl = this; 3 | ctrl.contactTags = [{ 4 | label: 'All contacts', 5 | icon: 'star', 6 | state: 'none' 7 | }, { 8 | label: 'Friends', 9 | icon: 'people', 10 | state: 'friends' 11 | }, { 12 | label: 'Family', 13 | icon: 'child_care', 14 | state: 'family' 15 | }, { 16 | label: 'Acquaintances', 17 | icon: 'accessibility', 18 | state: 'acquaintances' 19 | }, { 20 | label: 'Following', 21 | icon: 'remove_red_eye', 22 | state: 'following' 23 | }]; 24 | } 25 | 26 | /** 27 | * @ngdoc type 28 | * @module common 29 | * @name AppSidebarController 30 | * 31 | * @description 32 | * 33 | * ## Lorem Ipsum 1 34 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 35 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 36 | * 37 | * ## Lorem Ipsum 2 38 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 39 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 40 | */ 41 | angular 42 | .module('common') 43 | .controller('AppSidebarController', AppSidebarController); 44 | -------------------------------------------------------------------------------- /src/sass/base/_buttons.scss: -------------------------------------------------------------------------------- 1 | 2 | button { 3 | background: colors(button); 4 | color: #fff; 5 | border-radius: 3px; 6 | box-shadow: 0 3px 0px 0px colors(button, shadow); 7 | font-size: 16px; 8 | border: none; 9 | cursor: pointer; 10 | padding: 10px 15px; 11 | margin-bottom: 3px; 12 | 13 | &:hover { 14 | background: colors(button, hover); 15 | box-shadow: 0 3px 0 0 colors(button, hoverShadow); 16 | } 17 | } 18 | 19 | button:focus { 20 | outline: none; 21 | } 22 | 23 | button:active, 24 | button:focus { 25 | outline: none; 26 | background: colors(button, hoverShadow); 27 | box-shadow: 0 3px 0 0 darken(colors(button, hoverShadow), 8%); 28 | margin-bottom: 1px; 29 | } 30 | button[disabled] { 31 | background: rgba(0, 0, 0, 0.05); 32 | color: rgba(0, 0, 0, 0.4); 33 | cursor: not-allowed; 34 | box-shadow: 0 3px 0px 0px rgba(0, 0, 0, 0.1); 35 | } 36 | button.delete { 37 | background: colors(buttonDelete); 38 | box-shadow: 0 3px 0px 0px colors(buttonDelete, shadow); 39 | 40 | &:hover { 41 | background: colors(buttonDelete, hover); 42 | box-shadow: 0 3px 0 0 colors(buttonDelete, hoverShadow); 43 | } 44 | } 45 | button.delete:active, 46 | button.delete:focus { 47 | background: colors(buttonDelete, hoverShadow); 48 | box-shadow: 0 3px 0 0 darken(colors(buttonDelete, hoverShadow), 8%); 49 | } 50 | -------------------------------------------------------------------------------- /docs/build/partials/api/common/type/AppController.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

AppController

6 |
    7 | 8 |
  1. 9 | - type in module common 10 |
  2. 11 |
12 |
13 | 14 | 15 | 16 |
17 |

Lorem Ipsum 1

18 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 19 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

20 |

Lorem Ipsum 2

21 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |

Methods

40 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
63 | 64 | 65 | -------------------------------------------------------------------------------- /src/app/components/contact/length-check/length-check.spec.js: -------------------------------------------------------------------------------- 1 | describe('Contact', function () { 2 | beforeEach(module('components.contact')); 3 | 4 | describe('lengthCheck', function () { 5 | var $rootScope, 6 | $compile, 7 | element; 8 | 9 | beforeEach(inject(function ($injector) { 10 | $rootScope = $injector.get('$rootScope'); 11 | $compile = $injector.get('$compile'); 12 | 13 | $rootScope.contact = { name: '' }; 14 | element = angular.element(''); 15 | $compile(element)($rootScope); 16 | $rootScope.$digest(); 17 | })); 18 | 19 | it('should contain dynamic-input class', function() { 20 | expect(element.hasClass('dynamic-input')).toEqual(true); 21 | }); 22 | 23 | it('should dynamically add dynamic-input--no-contents class', function() { 24 | var scope = element.scope(); 25 | 26 | element.val('John Doe').triggerHandler('input'); 27 | scope.$apply(); 28 | 29 | expect(scope.contact.name).toBe('John Doe'); 30 | expect(element.hasClass('dynamic-input--no-contents')).toEqual(false); 31 | 32 | element.val('').triggerHandler('input'); 33 | scope.$apply(); 34 | 35 | expect(scope.contact.name).toBe(''); 36 | expect(element.hasClass('dynamic-input--no-contents')).toEqual(true); 37 | }); 38 | }); 39 | }); -------------------------------------------------------------------------------- /src/app/components/contact/length-check/length-check.directive.js: -------------------------------------------------------------------------------- 1 | function lengthCheck() { 2 | return { 3 | restrict: 'A', 4 | require: 'ngModel', 5 | compile: function ($element) { 6 | $element.addClass('dynamic-input'); 7 | return function ($scope, $element, $attrs, $ctrl) { 8 | var dynamicClass = 'dynamic-input--no-contents'; 9 | $scope.$watch(function () { 10 | return $ctrl.$viewValue; 11 | }, function (newValue) { 12 | if (newValue) { 13 | $element.removeClass(dynamicClass); 14 | } else { 15 | $element.addClass(dynamicClass); 16 | } 17 | }); 18 | }; 19 | } 20 | }; 21 | } 22 | 23 | /** 24 | * @ngdoc directive 25 | * @name lengthCheck 26 | * @module components.contact 27 | * 28 | * @description 29 | * 30 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 31 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 32 | * 33 | * @usage 34 | * 35 | * ### How to use 36 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 37 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 38 | **/ 39 | angular 40 | .module('components.contact') 41 | .directive('lengthCheck', lengthCheck); 42 | -------------------------------------------------------------------------------- /docs/config/templates/module.template.html: -------------------------------------------------------------------------------- 1 | {% extends "base.template.html" %} 2 | 3 | {% block content %} 4 |

5 | {% if doc.title %}{$ doc.title | marked $}{% else %}{$ doc.name | code $}{% endif %} 6 |

7 | 8 | {$ doc.description | marked $} 9 | 10 | {$ doc.stateName $} 11 | 12 | Requires {$ doc.requires $} 13 | 14 |
15 |

Module Components

16 | {% for componentGroup in doc.componentGroups %} 17 |
18 |

{$ componentGroup.groupType | title $}

19 | 20 | 21 | 22 | 23 | 24 | {% for component in componentGroup.components %} 25 | 26 | 27 | 28 | 29 | {% endfor %} 30 |
NameDescription
{$ component.id | link(component.name, component) $}{$ component.description | firstParagraph | marked $}
31 |
32 | {% endfor %} 33 |
34 | 35 | {% if doc.usage %} 36 |

Usage

37 | {$ doc.usage | marked $} 38 | {% endif %} 39 | 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /src/app/components/contact/contact.service.js: -------------------------------------------------------------------------------- 1 | function ContactService(AuthService, $firebaseRef, $firebaseArray, $firebaseObject) { 2 | var ref = $firebaseRef.contacts; 3 | var uid = AuthService.getUser().uid; 4 | return { 5 | createNewContact: function (contact) { 6 | return $firebaseArray(ref.child(uid)).$add(contact); 7 | }, 8 | getContactById: function (id) { 9 | return $firebaseObject(ref.child(uid).child(id)); 10 | }, 11 | getContactList: function () { 12 | return $firebaseArray(ref.child(uid)); 13 | }, 14 | updateContact: function (contact) { 15 | return contact.$save(); 16 | }, 17 | deleteContact: function (contact) { 18 | return contact.$remove(); 19 | } 20 | }; 21 | } 22 | 23 | /** 24 | * @ngdoc service 25 | * @name ContactService 26 | * @module components.contact 27 | * 28 | * @description Provides HTTP methods for our firebase connection. 29 | * 30 | * ## Lorem Ipsum 1 31 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 32 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 33 | * 34 | * ## Lorem Ipsum 2 35 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 36 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 37 | */ 38 | 39 | angular 40 | .module('components.contact') 41 | .factory('ContactService', ContactService); 42 | -------------------------------------------------------------------------------- /src/sass/components/_contact.scss: -------------------------------------------------------------------------------- 1 | .contact { 2 | padding: 10px 15px; 3 | } 4 | .contact__saving { 5 | position: absolute; 6 | left: 50%; 7 | transform: translate(-50%, 0); 8 | background: rgba(0, 0, 0, 0.1); 9 | border: 1px solid rgba(0, 0, 0, 0.1); 10 | padding: 10px 15px; 11 | font-size: 16px; 12 | border-radius: 3px; 13 | } 14 | .contact__field { 15 | margin-bottom: 15px; 16 | padding-right: 30px; 17 | } 18 | .contact button { 19 | float: right; 20 | } 21 | .contact__required { 22 | color: colors(scaffold, error); 23 | font-size: 16px; 24 | line-height: 0px; 25 | position: relative; 26 | top: 4px; 27 | margin-left: 5px; 28 | } 29 | .contact__field--long { 30 | width: 600px; 31 | } 32 | .contact__error { 33 | float: right; 34 | color: colors(scaffold, error); 35 | } 36 | .contact__field label { 37 | font-size: 12px; 38 | color: #A4B4C4; 39 | font-weight: 500; 40 | text-transform: uppercase; 41 | padding: 5px 15px; 42 | display: block; 43 | margin-bottom: 3px; 44 | } 45 | .contact__field input { 46 | width: 100%; 47 | } 48 | .contact__box { 49 | float: left; 50 | width: 50%; 51 | max-width: 770px; 52 | } 53 | .contact__box--no-margin { 54 | margin-right: 0; 55 | } 56 | .contact__pill { 57 | opacity: 0.4; 58 | cursor: pointer; 59 | font-size: 16px; 60 | } 61 | .contact__pill--active { 62 | opacity: 1; 63 | &:hover { 64 | opacity: 1; 65 | } 66 | } 67 | .contact__sub-title { 68 | font-size: 24px; 69 | font-weight: 300; 70 | margin: 10px 15px; 71 | } 72 | -------------------------------------------------------------------------------- /src/app/components/auth/auth.module.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @ngdoc module 4 | * @name components.auth 5 | * 6 | * @requires ui.router 7 | * @requires firebase 8 | * 9 | * @description 10 | * 11 | * This is the auth module. It includes our auth components 12 | * 13 | **/ 14 | angular 15 | .module('components.auth', [ 16 | 'ui.router', 17 | 'firebase' 18 | ]) 19 | .config(function ($firebaseRefProvider) { 20 | 21 | var config = { 22 | apiKey: "AIzaSyCsNISt3dFx7dy5AImIIk62jDDd0OLvZK0", 23 | authDomain: "contacts-manager-e486f.firebaseapp.com", 24 | databaseURL: "https://contacts-manager-e486f.firebaseio.com", 25 | storageBucket: "contacts-manager-e486f.appspot.com", 26 | }; 27 | 28 | $firebaseRefProvider 29 | .registerUrl({ 30 | default: config.databaseURL, 31 | contacts: config.databaseURL + '/contacts' 32 | }); 33 | 34 | firebase.initializeApp(config); 35 | }) 36 | .run(function ($transitions, $state, AuthService) { 37 | $transitions.onStart({ 38 | to: function (state) { 39 | return !!(state.data && state.data.requiredAuth); 40 | } 41 | }, function() { 42 | return AuthService 43 | .requireAuthentication() 44 | .catch(function () { 45 | return $state.target('auth.login'); 46 | }); 47 | }); 48 | $transitions.onStart({ 49 | to: 'auth.*' 50 | }, function () { 51 | if (AuthService.isAuthenticated()) { 52 | return $state.target('app'); 53 | } 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contacts", 3 | "version": "1.0.0", 4 | "author": "@toddmotto", 5 | "description": "Contacts manager app for Ultimate AngularJS", 6 | "scripts": { 7 | "start": "gulp", 8 | "test": "karma start", 9 | "deploy": "gulp production --deploy" 10 | }, 11 | "license": "MIT", 12 | "repository": "https://github.com/toddmotto/angular-1-5-components-app", 13 | "dependencies": { 14 | "angular": "^1.5.8", 15 | "angular-loading-bar": "^0.9.0", 16 | "angular-messages": "^1.5.8", 17 | "angular-ui-router": "1.0.0-beta.2", 18 | "angularfire": "^2.0.2", 19 | "firebase": "3.3.0" 20 | }, 21 | "devDependencies": { 22 | "angular-mocks": "^1.5.8", 23 | "babel-core": "^6.11.4", 24 | "babel-preset-es2015": "^6.9.0", 25 | "browser-sync": "^2.14.0", 26 | "canonical-path": "0.0.2", 27 | "del": "^2.2.1", 28 | "dgeni": "^0.4.2", 29 | "dgeni-packages": "^0.16.0", 30 | "gulp": "^3.9.1", 31 | "gulp-angular-templatecache": "^2.0.0", 32 | "gulp-concat": "^2.6.0", 33 | "gulp-htmlmin": "^2.0.0", 34 | "gulp-if": "^2.0.1", 35 | "gulp-ng-annotate": "^2.0.0", 36 | "gulp-sass": "^2.3.2", 37 | "gulp-uglify": "^2.0.0", 38 | "gulp-wrap": "^0.13.0", 39 | "jasmine-core": "^2.4.1", 40 | "karma": "^0.13.19", 41 | "karma-chrome-launcher": "^0.2.2", 42 | "karma-jasmine": "^0.3.6", 43 | "karma-phantomjs-launcher": "^1.0.1", 44 | "karma-spec-reporter": "0.0.23", 45 | "lodash": "^4.16.2", 46 | "path": "^0.12.7", 47 | "yargs": "^5.0.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 |
4 | 5 |
6 | --- 7 | 8 | # AngularJS 1.5 component architecture app 9 | 10 | > Try the [Contacts Manager](https://contacts-manager-e486f.firebaseapp.com) app! You'll need to register and create an account. 11 | 12 | --- 13 | 14 | > Want the ES2015 version? [Check it out here](https://github.com/toddmotto/angular-1-5-components-app/tree/ES2015). 15 | 16 | --- 17 | 18 | A [Contacts Manager](https://contacts-manager-e486f.firebaseapp.com) application built on AngularJS 1.5 components, ui-router 1.0.0, Firebase. Want to build it? Jump on my [AngularJS 1.5 Pro course](https://ultimateangular.com). 19 | 20 | ### List of features: 21 | 22 | - AngularJS 1.5 `.component()` method 23 | - Stateful/stateless and routed components 24 | - One-way dataflow 25 | - Lifecycle hooks 26 | - ui-router 1.0.0 27 | - Firebase auth and database/hosting 28 | - Fully tested, including spec files 29 | - Built against my component architecture [styleguide](https://github.com/toddmotto/angular-styleguide) 30 | - Proper SCSS architecture to provide maintainable, scalable and well-organized code 31 | 32 | ### Setup and install 33 | 34 | To run this app, follow the [instructions here](https://github.com/toddmotto/ultimate-angular-master-seed). Basically `npm install` and `npm start`. Run tests with `npm test`. Have fun! 35 | 36 | ![Contacts Manager](https://cloud.githubusercontent.com/assets/1655968/17981372/5907ffb0-6afb-11e6-9bb4-7e223b56e0d4.gif) 37 | -------------------------------------------------------------------------------- /docs/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 | -------------------------------------------------------------------------------- /src/app/components/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | function AuthService($firebaseAuth) { 2 | var auth = $firebaseAuth(); 3 | var authData = null; 4 | function storeAuthData(response) { 5 | authData = response; 6 | return authData; 7 | } 8 | function onSignIn(user) { 9 | authData = user; 10 | return auth.$requireSignIn(); 11 | } 12 | function clearAuthData() { 13 | authData = null; 14 | } 15 | this.login = function (user) { 16 | return auth 17 | .$signInWithEmailAndPassword(user.email, user.password) 18 | .then(storeAuthData); 19 | }; 20 | this.register = function (user) { 21 | return auth 22 | .$createUserWithEmailAndPassword(user.email, user.password) 23 | .then(storeAuthData); 24 | }; 25 | this.logout = function () { 26 | return auth 27 | .$signOut() 28 | .then(clearAuthData); 29 | }; 30 | this.requireAuthentication = function () { 31 | return auth 32 | .$waitForSignIn().then(onSignIn); 33 | }; 34 | this.isAuthenticated = function () { 35 | return !!authData; 36 | }; 37 | this.getUser = function () { 38 | if (authData) { 39 | return authData; 40 | } 41 | }; 42 | } 43 | 44 | /** 45 | * @ngdoc service 46 | * @name AuthService 47 | * @module components.auth 48 | * 49 | * @description Provides HTTP methods for our firebase authentification. 50 | * 51 | * ## Lorem Ipsum 1 52 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 53 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 54 | * 55 | * ## Lorem Ipsum 2 56 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 57 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 58 | */ 59 | angular 60 | .module('components.auth') 61 | .service('AuthService', AuthService); 62 | -------------------------------------------------------------------------------- /docs/config/templates/indexPage.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-edit/contact-edit.controller.js: -------------------------------------------------------------------------------- 1 | function ContactEditController($state, ContactService, cfpLoadingBar, $window) { 2 | var ctrl = this; 3 | /** 4 | * @ngdoc method 5 | * @name ContactEditController#updateContact 6 | * 7 | * @param {event} event Receive the emitted event 8 | * Updates the contact information 9 | * 10 | * @return {method} ContactService returns the updateContact method and a promise 11 | */ 12 | ctrl.updateContact = function (event) { 13 | cfpLoadingBar.start(); 14 | return ContactService 15 | .updateContact(event.contact) 16 | .then(cfpLoadingBar.complete, cfpLoadingBar.complete); 17 | }; 18 | /** 19 | * @ngdoc method 20 | * @name ContactEditController#deleteContact 21 | * 22 | * @param {event} event Delete the contact 23 | * 24 | * @return {method} ContactService returns the deleteContact method and a promise 25 | */ 26 | ctrl.deleteContact = function (event) { 27 | var message = 'Delete ' + event.contact.name + ' from contacts?'; 28 | if ($window.confirm(message)) { 29 | return ContactService 30 | .deleteContact(event.contact) 31 | .then(function () { 32 | $state.go('contacts'); 33 | }); 34 | } 35 | }; 36 | } 37 | 38 | /** 39 | * @ngdoc type 40 | * @module components.contact 41 | * @name ContactEditController 42 | * 43 | * @description 44 | * 45 | * ## Lorem Ipsum 1 46 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 47 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 48 | * 49 | * ## Lorem Ipsum 2 50 | * Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 51 | * Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at. 52 | */ 53 | angular 54 | .module('components.contact') 55 | .controller('ContactEditController', ContactEditController); 56 | -------------------------------------------------------------------------------- /mocks/firebase.mock.js: -------------------------------------------------------------------------------- 1 | function MockFirebase () {} 2 | MockFirebase.prototype.initializeApp = function () {}; 3 | window.firebase = new MockFirebase(); 4 | 5 | angular 6 | .module('firebase', []) 7 | .provider('$firebaseRef', function () { 8 | this.registerUrl = function (urls) { 9 | for (var key in urls) { 10 | this[key] = urls[key]; 11 | } 12 | }; 13 | this.$get = function () { 14 | return angular.noop; 15 | }; 16 | }) 17 | .factory('$firebaseArray', function ($q) { 18 | function FirebaseArray(ref) {} 19 | 20 | FirebaseArray.prototype = { 21 | constructor: FirebaseArray, 22 | $add: function (newData) { 23 | return $q.resolve({ key: 1 }); 24 | } 25 | }; 26 | 27 | return function () { 28 | return new FirebaseArray(); 29 | }; 30 | }) 31 | .factory('$firebaseObject', function ($q) { 32 | function FirebaseObject() { 33 | return $q.when({ key: 1 }); 34 | } 35 | 36 | return function () { 37 | return new FirebaseObject(); 38 | }; 39 | }) 40 | .factory('$firebaseAuth', function ($q) { 41 | 42 | var fakeUser = { $id: 1 }; 43 | 44 | function FirebaseAuth() { 45 | this.auth = null; 46 | } 47 | 48 | FirebaseAuth.prototype = { 49 | constructor: FirebaseAuth, 50 | $requireSignIn: function () { 51 | this.auth = fakeUser; 52 | return $q.resolve(this.auth); 53 | }, 54 | $signInWithEmailAndPassword: function (email, password) { 55 | this.auth = fakeUser; 56 | return $q.resolve(this.auth); 57 | }, 58 | $createUserWithEmailAndPassword: function () { 59 | this.auth = fakeUser; 60 | return $q.resolve(this.auth); 61 | }, 62 | $waitForSignIn: function () { 63 | this.auth = fakeUser; 64 | return $q.resolve(this.auth); 65 | }, 66 | $signOut: function () { 67 | this.auth = null; 68 | return $q.resolve(this.auth); 69 | } 70 | }; 71 | 72 | return function () { 73 | return new FirebaseAuth(); 74 | }; 75 | }); 76 | -------------------------------------------------------------------------------- /src/sass/layout/_loading.scss: -------------------------------------------------------------------------------- 1 | #loading-bar,#loading-bar-spinner{pointer-events:none;-webkit-pointer-events:none;-webkit-transition:350ms linear all;-moz-transition:350ms linear all;-o-transition:350ms linear all;transition:350ms linear all}#loading-bar-spinner.ng-enter,#loading-bar-spinner.ng-leave.ng-leave-active,#loading-bar.ng-enter,#loading-bar.ng-leave.ng-leave-active{opacity:0}#loading-bar-spinner.ng-enter.ng-enter-active,#loading-bar-spinner.ng-leave,#loading-bar.ng-enter.ng-enter-active,#loading-bar.ng-leave{opacity:1}#loading-bar .bar{-webkit-transition:width 350ms;-moz-transition:width 350ms;-o-transition:width 350ms;transition:width 350ms;background:#29d;position:fixed;z-index:10002;top:0;left:0;width:100%;height:2px;border-bottom-right-radius:1px;border-top-right-radius:1px}#loading-bar .peg{position:absolute;width:70px;right:0;top:0;height:2px;opacity:.45;-moz-box-shadow:#29d 1px 0 6px 1px;-ms-box-shadow:#29d 1px 0 6px 1px;-webkit-box-shadow:#29d 1px 0 6px 1px;box-shadow:#29d 1px 0 6px 1px;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%}#loading-bar-spinner{display:block;position:fixed;z-index:10002;top:10px;left:10px}#loading-bar-spinner .spinner-icon{width:14px;height:14px;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:loading-bar-spinner 400ms linear infinite;-moz-animation:loading-bar-spinner 400ms linear infinite;-ms-animation:loading-bar-spinner 400ms linear infinite;-o-animation:loading-bar-spinner 400ms linear infinite;animation:loading-bar-spinner 400ms linear infinite}@-webkit-keyframes loading-bar-spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes loading-bar-spinner{0%{-moz-transform:rotate(0);transform:rotate(0)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes loading-bar-spinner{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes loading-bar-spinner{0%{-ms-transform:rotate(0);transform:rotate(0)}100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-bar-spinner{0%{transform:rotate(0)}100%{transform:rotate(360deg)}} 2 | -------------------------------------------------------------------------------- /src/app/components/contact/contacts.spec.js: -------------------------------------------------------------------------------- 1 | describe('Contact', function () { 2 | beforeEach(module('components.auth', function($provide){ 3 | $provide.value('AuthService', { 4 | getUser: function() { 5 | return { uid: 1 } 6 | }, 7 | isAuthenticated: function() { 8 | return true; 9 | } 10 | }); 11 | 12 | var child = function(record) { 13 | return { child: child } 14 | }; 15 | 16 | $provide.value('$firebaseRef', { 17 | contacts: { 18 | child: child 19 | } 20 | }) 21 | })); 22 | 23 | beforeEach(module(function ($stateProvider) { 24 | $stateProvider.state('app', { url: '/' }); 25 | })); 26 | 27 | beforeEach(module('components.contact')); 28 | 29 | describe('ContactService', function () { 30 | var ContactService, 31 | $rootScope; 32 | 33 | beforeEach(inject(function ($injector) { 34 | ContactService = $injector.get('ContactService'); 35 | $rootScope = $injector.get('$rootScope'); 36 | })); 37 | 38 | it('should get contacts', function () { 39 | // Pending 40 | }); 41 | 42 | it('should get a contact', function () { 43 | var id = 1, 44 | promise = ContactService.getContactById(id); 45 | 46 | promise.then(function(ref){ 47 | expect(ref.key).toEqual(id); 48 | }); 49 | 50 | $rootScope.$digest(); 51 | }); 52 | 53 | it('should create a contact', function () { 54 | var contact = { email: 'test@test.com', password: 'insecure' } 55 | promise = ContactService.createNewContact(contact); 56 | 57 | promise.then(function(ref){ 58 | expect(ref.key).toEqual(1); 59 | }); 60 | 61 | $rootScope.$digest(); 62 | }); 63 | 64 | it('should update a contact', function () { 65 | var contact = { $save: angular.noop }; 66 | 67 | spyOn(contact, '$save'); 68 | ContactService.updateContact(contact); 69 | expect(contact.$save).toHaveBeenCalled(); 70 | }); 71 | 72 | it('should delete a contact', function () { 73 | var contact = { $remove: angular.noop }; 74 | 75 | spyOn(contact, '$remove'); 76 | ContactService.deleteContact(contact); 77 | expect(contact.$remove).toHaveBeenCalled(); 78 | }); 79 | }); 80 | }); -------------------------------------------------------------------------------- /docs/config/processors/api-data.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | 4 | function buildDocData(doc, extraData) { 5 | 6 | // So that we can create states even though our module names contain dots(.) 7 | // in UI-Router dotted notation means it's a child state, so this is problematic 8 | // if we are following AngularJS styleguides and conventions regarding 9 | // naming of our Modules 10 | // #hack #lazy 11 | var splitName = doc.name.split('.'); 12 | doc.stateName = _.camelCase(splitName); 13 | 14 | return _.assign({ 15 | name: doc.name, 16 | stateName: doc.stateName, 17 | type: doc.docType, 18 | outputPath: doc.outputPath, 19 | url: doc.path, 20 | }, extraData); 21 | } 22 | 23 | module.exports = function apiPagesProcessor(moduleMap) { 24 | return { 25 | $runAfter: ['paths-computed'], 26 | $runBefore: ['rendering-docs'], 27 | $process: process 28 | }; 29 | 30 | function process(docs) { 31 | 32 | var apiPages = _(docs) 33 | 34 | .filter(function(doc) { 35 | // We are not interested in docs that are not in a module 36 | // We are only interested in pages that are not landing pages 37 | return doc.docType !== 'componentGroup'; 38 | }) 39 | 40 | .filter('module') 41 | 42 | .groupBy('module') 43 | 44 | .map(function(moduleDocs, moduleName) { 45 | 46 | var moduleDoc = _.find(docs, { 47 | docType: 'module', 48 | name: moduleName 49 | }); 50 | 51 | if (!moduleDoc) return; 52 | 53 | return buildDocData(moduleDoc, { 54 | docs: moduleDocs 55 | 56 | .filter(function(doc) { 57 | // Private isn't set to true, just to an empty string if @private is supplied 58 | return doc.docType !== 'module'; 59 | }) 60 | 61 | .map(buildDocData) 62 | }); 63 | 64 | }) 65 | 66 | .filter() //remove null items 67 | 68 | .value(); 69 | 70 | docs.push({ 71 | name: 'API_DATA', 72 | template: 'constant-data.template.js', 73 | outputPath: 'src/api-data.js', 74 | items: apiPages 75 | }); 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | 4 | // base path that will be used to resolve all patterns (eg. files, exclude) 5 | basePath: '', 6 | 7 | 8 | // frameworks to use 9 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 10 | frameworks: ['jasmine'], 11 | 12 | 13 | // list of files / patterns to load in the browser 14 | files: [ 15 | 'node_modules/angular/angular.js', 16 | 'node_modules/angular-ui-router/release/angular-ui-router.js', 17 | 'node_modules/angular-loading-bar/build/loading-bar.min.js', 18 | // 'node_modules/firebase/firebase.js', 19 | // 'node_modules/angularfire/dist/angularfire.js', 20 | 'node_modules/angular-mocks/angular-mocks.js', 21 | 'mocks/firebase.mock.js', 22 | 'src/app/**/*.spec.js', 23 | 'dist/js/bundle.js', 24 | ], 25 | 26 | // list of files to exclude 27 | exclude: [ 28 | ], 29 | 30 | plugins: [ 31 | require('karma-jasmine'), 32 | require('karma-chrome-launcher'), 33 | require('karma-phantomjs-launcher'), 34 | require('karma-spec-reporter') 35 | ], 36 | 37 | // preprocess matching files before serving them to the browser 38 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 39 | preprocessors: { 40 | }, 41 | 42 | 43 | // test results reporter to use 44 | // possible values: 'dots', 'progress' 45 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 46 | reporters: ['spec'], 47 | 48 | 49 | // web server port 50 | port: 9876, 51 | 52 | 53 | // enable / disable colors in the output (reporters and logs) 54 | colors: true, 55 | 56 | 57 | // level of logging 58 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 59 | logLevel: config.LOG_INFO, 60 | 61 | 62 | // enable / disable watching file and executing tests whenever any file changes 63 | autoWatch: true, 64 | 65 | // start these browsers 66 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 67 | browsers: ['Chrome'], 68 | 69 | 70 | // Continuous Integration mode 71 | // if true, Karma captures browsers, runs the tests and exits 72 | singleRun: false, 73 | 74 | // Concurrency level 75 | // how many browser should be started simultaneous 76 | concurrency: Infinity 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /docs/build/partials/api/common.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | common 4 |

5 | 6 |

This is the common module. It includes a run method that setups the loading bar.

7 | 8 | 9 | common 10 | 11 | Requires ui.router,angular-loading-bar 12 | 13 |
14 |

Module Components

15 | 16 |
17 |

Type

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 |
NameDescription
AppSidebarController

Lorem Ipsum 1

27 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 28 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

29 |
AppController

Lorem Ipsum 1

35 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 36 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

37 |
41 |
42 | 43 |
44 |

Directive

45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 |
NameDescription
app

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 54 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

55 |
59 |
60 | 61 |
62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-detail/contact-detail.spec.js: -------------------------------------------------------------------------------- 1 | describe('Contact', function () { 2 | beforeEach(module('components.contact')); 3 | 4 | describe('ContactDetailController', function () { 5 | var $componentController, 6 | controller, 7 | mockContact = { $id: 1 }, 8 | mockSave = angular.noop, 9 | mockUpdate = angular.noop, 10 | mockDelete = angular.noop; 11 | 12 | beforeEach(inject(function ($injector) { 13 | $componentController = $injector.get('$componentController'); 14 | controller = $componentController('contactDetail', 15 | { $scope: {} }, 16 | { contact: mockContact, onSave: mockSave, onUpdate: mockUpdate, onDelete: mockDelete } 17 | ); 18 | })); 19 | 20 | it('should bind to the correct contact', function () { 21 | expect(controller.contact.$id).toEqual(mockContact.$id); 22 | controller.$onInit(); 23 | 24 | expect(controller.isNewContact).toBe(false); 25 | }); 26 | 27 | it('should initialize isNewContact if no $id is present', function() { 28 | controller = $componentController('contactDetail', 29 | { $scope: {} }, 30 | { contact: {}, onSave: mockSave, onUpdate: mockUpdate, onDelete: mockDelete } 31 | ); 32 | controller.$onInit(); 33 | 34 | expect(controller.isNewContact).toBe(true); 35 | }); 36 | 37 | it('should call onSave when saveContact is called', function () { 38 | var payload = { $event: { contact: mockContact } }; 39 | 40 | spyOn(controller, 'onSave'); 41 | controller.saveContact(); 42 | expect(controller.onSave).toHaveBeenCalledWith(payload); 43 | }); 44 | 45 | it('should call onUpdate when updateContact is called', function () { 46 | var payload = { $event: { contact: mockContact } }; 47 | 48 | spyOn(controller, 'onUpdate'); 49 | controller.updateContact(); 50 | expect(controller.onUpdate).toHaveBeenCalledWith(payload); 51 | }); 52 | 53 | it('should call onDelete when deleteContact is called', function () { 54 | var payload = { $event: { contact: mockContact } }; 55 | 56 | spyOn(controller, 'onDelete'); 57 | controller.deleteContact(); 58 | expect(controller.onDelete).toHaveBeenCalledWith(payload); 59 | }); 60 | 61 | it('should save tag when tagChange is called', function () { 62 | var event = { tag: 'friend' }; 63 | 64 | spyOn(controller, 'updateContact'); 65 | controller.tagChange(event); 66 | expect(controller.updateContact).toHaveBeenCalled(); 67 | expect(controller.contact.tag).toEqual(event.tag); 68 | }); 69 | }); 70 | }); -------------------------------------------------------------------------------- /src/app/components/contact/contact-new/contact-new.spec.js: -------------------------------------------------------------------------------- 1 | describe('Contact', function () { 2 | beforeEach(module('components.contact', function ($provide) { 3 | $provide.value('cfpLoadingBar', { 4 | start: angular.noop, 5 | complete: angular.noop 6 | }) 7 | 8 | $provide.value('ContactService', { 9 | createNewContact: angular.noop 10 | }); 11 | })); 12 | 13 | beforeEach(module('components.auth')); 14 | 15 | beforeEach(module(function ($stateProvider) { 16 | $stateProvider.state('app', { 17 | redirectTo: 'contacts', 18 | url: '/app', 19 | data: { 20 | requiredAuth: true 21 | } 22 | }); 23 | })); 24 | 25 | describe('Routes', function () { 26 | var $state, $location, $rootScope, AuthService; 27 | 28 | function goTo(url) { 29 | $location.url(url); 30 | $rootScope.$digest(); 31 | } 32 | 33 | beforeEach(inject(function ($injector) { 34 | $state = $injector.get('$state'); 35 | $location = $injector.get('$location'); 36 | $rootScope = $injector.get('$rootScope'); 37 | AuthService = $injector.get('AuthService'); 38 | })); 39 | 40 | it('should go to the contact state', function() { 41 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true); 42 | 43 | goTo('/app/new'); 44 | 45 | expect($state.current.name).toEqual('new') 46 | }); 47 | }); 48 | 49 | describe('ContactNewController', function () { 50 | var $componentController, 51 | controller, 52 | $state, 53 | ContactService, 54 | $rootScope, 55 | $q; 56 | 57 | beforeEach(inject(function ($injector) { 58 | $componentController = $injector.get('$componentController'); 59 | $state = $injector.get('$state'); 60 | ContactService = $injector.get('ContactService'); 61 | $rootScope = $injector.get('$rootScope'); 62 | $q = $injector.get('$q'); 63 | 64 | controller = $componentController('contactNew', 65 | { $scope: {}, $state: $state, ContactService: ContactService } 66 | ); 67 | })); 68 | 69 | it('should create a contact', function () { 70 | var event = { contact: { email: 'test@test.com', password: 'insecure' } }; 71 | spyOn(ContactService, 'createNewContact').and.callFake( 72 | function () { 73 | return $q.when({ key: 1}) 74 | } 75 | ); 76 | spyOn($state, 'go'); 77 | 78 | var promise = controller.createNewContact(event); 79 | 80 | promise.then(function () { 81 | expect(ContactService.createNewContact).toHaveBeenCalled(); 82 | expect($state.go).toHaveBeenCalledWith('contact', {id: 1}); 83 | }); 84 | 85 | $rootScope.$digest(); 86 | }); 87 | }); 88 | }); -------------------------------------------------------------------------------- /src/app/components/contact/contacts/contacts.spec.js: -------------------------------------------------------------------------------- 1 | describe('Contact', function () { 2 | beforeEach(module('components.contact', function($provide){ 3 | $provide.value('ContactService', { 4 | getContactList: function() { 5 | return { 6 | $loaded: angular.noop 7 | } 8 | } 9 | }); 10 | })); 11 | 12 | beforeEach(module('components.auth')); 13 | 14 | beforeEach(module(function ($stateProvider) { 15 | $stateProvider.state('app', { 16 | redirectTo: 'contacts', 17 | url: '/app', 18 | data: { 19 | requiredAuth: true 20 | } 21 | }); 22 | })); 23 | 24 | describe('Routes', function () { 25 | var $state, $location, $rootScope, AuthService; 26 | 27 | function goTo(url) { 28 | $location.url(url); 29 | $rootScope.$digest(); 30 | } 31 | 32 | beforeEach(inject(function ($injector) { 33 | $state = $injector.get('$state'); 34 | $location = $injector.get('$location'); 35 | $rootScope = $injector.get('$rootScope'); 36 | AuthService = $injector.get('AuthService'); 37 | })); 38 | 39 | it('should go to the contact state', function() { 40 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true); 41 | 42 | goTo('/app/contacts?friends'); 43 | 44 | expect($state.current.name).toEqual('contacts') 45 | }); 46 | }); 47 | 48 | describe('ContactsController', function () { 49 | var $componentController, 50 | controller, 51 | $filter, 52 | $state, 53 | mockFilter = { filter: 'friends'}, 54 | mockContacts = [ 55 | { 56 | name: 'John Doe', 57 | tag: 'friends' 58 | }, 59 | { 60 | name: 'Jane Smith', 61 | tag: 'family' 62 | } 63 | ]; 64 | 65 | beforeEach(inject(function ($injector) { 66 | $componentController = $injector.get('$componentController'); 67 | $filter = $injector.get('$filter'); 68 | $state = $injector.get('$state'); 69 | controller = $componentController('contacts', 70 | { $scope: {}, $filter: $filter, $state: $state }, 71 | { filter: mockFilter, contacts: mockContacts } 72 | ); 73 | controller.$onInit(); 74 | })); 75 | 76 | it('should filter contacts', function() { 77 | expect(controller.filteredContacts).toEqual([{ 78 | name: 'John Doe', 79 | tag: 'friends' 80 | }]); 81 | }); 82 | 83 | it('should route on goToContact call', function () { 84 | var event = { contactId: 1 }; 85 | 86 | spyOn($state, 'go'); 87 | controller.goToContact(event); 88 | expect($state.go).toHaveBeenCalledWith('contact', { id: event.contactId }); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /src/app/common/app.spec.js: -------------------------------------------------------------------------------- 1 | describe('App', function () { 2 | beforeEach(module('ui.router')); 3 | 4 | beforeEach(module('common', function ($provide) { 5 | $provide.value('AuthService', { 6 | getUser: angular.noop, 7 | logout: angular.noop 8 | }); 9 | })); 10 | 11 | beforeEach(module('components.auth')); 12 | 13 | beforeEach(module(function ($stateProvider) { 14 | $stateProvider.state('contacts', { url: '/app/contacts' }); 15 | })); 16 | 17 | describe('Routes', function () { 18 | var $state, $location, $rootScope, AuthService; 19 | 20 | function goTo(url) { 21 | $location.url(url); 22 | $rootScope.$digest(); 23 | } 24 | 25 | beforeEach(inject(function ($injector) { 26 | $state = $injector.get('$state'); 27 | $location = $injector.get('$location'); 28 | $rootScope = $injector.get('$rootScope'); 29 | AuthService = $injector.get('AuthService'); 30 | })); 31 | 32 | it('should redirect to contacts state', function () { 33 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true); 34 | 35 | goTo('/app'); 36 | 37 | expect($state.current.name).toEqual('contacts'); 38 | }); 39 | }); 40 | 41 | describe('AppController', function () { 42 | var $rootScope, $q, $componentController, controller, AuthService, $state; 43 | 44 | beforeEach(inject(function ($injector) { 45 | $rootScope = $injector.get('$rootScope'); 46 | $q = $injector.get('$q'); 47 | $componentController = $injector.get('$componentController'); 48 | AuthService = $injector.get('AuthService'); 49 | $state = $injector.get('$state'); 50 | })); 51 | 52 | it('should get user on instantiated', function () { 53 | var user = { $id: 1 } 54 | spyOn(AuthService, 'getUser').and.returnValue(user); 55 | 56 | controller = $componentController('app', 57 | { $scope: {}, AuthService: AuthService, $state: $state } 58 | ); 59 | 60 | expect(AuthService.getUser).toHaveBeenCalled(); 61 | expect(controller.user).toEqual(user); 62 | }); 63 | 64 | it('should go to the login state on logout', function () { 65 | spyOn(AuthService, 'logout') 66 | .and.callFake(function () { 67 | var deferred = $q.defer(); 68 | deferred.resolve(); 69 | return deferred.promise; 70 | }); 71 | spyOn($state, 'go').and.callThrough(); 72 | 73 | controller = $componentController('app', 74 | { $scope: {}, AuthService: AuthService, $state: $state } 75 | ); 76 | 77 | controller.logout(); 78 | $rootScope.$digest(); 79 | 80 | expect(AuthService.logout).toHaveBeenCalled(); 81 | expect($state.go).toHaveBeenCalledWith('auth.login'); 82 | }); 83 | }); 84 | 85 | }); 86 | -------------------------------------------------------------------------------- /docs/build/partials/api/components.contact.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | components.contact 4 |

5 | 6 |

This is the contact module. It includes all of our components for the contact feature.

7 | 8 | 9 | componentsContact 10 | 11 | Requires ui.router 12 | 13 |
14 |

Module Components

15 | 16 |
17 |

Type

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 |
NameDescription
ContactEditController

Lorem Ipsum 1

27 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 28 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

29 |
33 |
34 | 35 |
36 |

Service

37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 48 | 49 |
NameDescription
ContactService

Provides HTTP methods for our firebase connection.

46 |
50 |
51 | 52 |
53 |

Directive

54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | 67 |
NameDescription
lengthCheck

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 63 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

64 |
68 |
69 | 70 |
71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /docs/build/src/api-data.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('docs') 3 | .constant('API_DATA', [ 4 | { 5 | "name": "common", 6 | "stateName": "common", 7 | "type": "module", 8 | "outputPath": "partials/api/common.html", 9 | "url": "api/common", 10 | "docs": [ 11 | { 12 | "name": "AppSidebarController", 13 | "stateName": "appSidebarController", 14 | "type": "type", 15 | "outputPath": "partials/api/common/type/AppSidebarController.html", 16 | "url": "api/common/type/AppSidebarController" 17 | }, 18 | { 19 | "name": "app", 20 | "stateName": "app", 21 | "type": "directive", 22 | "outputPath": "partials/api/common/directive/app.html", 23 | "url": "api/common/directive/app" 24 | }, 25 | { 26 | "name": "AppController", 27 | "stateName": "appController", 28 | "type": "type", 29 | "outputPath": "partials/api/common/type/AppController.html", 30 | "url": "api/common/type/AppController" 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "components", 36 | "stateName": "components", 37 | "type": "module", 38 | "outputPath": "partials/api/components.html", 39 | "url": "api/components", 40 | "docs": [] 41 | }, 42 | { 43 | "name": "components.auth", 44 | "stateName": "componentsAuth", 45 | "type": "module", 46 | "outputPath": "partials/api/components.auth.html", 47 | "url": "api/components.auth", 48 | "docs": [ 49 | { 50 | "name": "AuthService", 51 | "stateName": "authService", 52 | "type": "service", 53 | "outputPath": "partials/api/components.auth/service/AuthService.html", 54 | "url": "api/components.auth/service/AuthService" 55 | } 56 | ] 57 | }, 58 | { 59 | "name": "components.contact", 60 | "stateName": "componentsContact", 61 | "type": "module", 62 | "outputPath": "partials/api/components.contact.html", 63 | "url": "api/components.contact", 64 | "docs": [ 65 | { 66 | "name": "ContactEditController", 67 | "stateName": "contactEditController", 68 | "type": "type", 69 | "outputPath": "partials/api/components.contact/type/ContactEditController.html", 70 | "url": "api/components.contact/type/ContactEditController" 71 | }, 72 | { 73 | "name": "ContactService", 74 | "stateName": "contactService", 75 | "type": "service", 76 | "outputPath": "partials/api/components.contact/service/ContactService.html", 77 | "url": "api/components.contact/service/ContactService" 78 | }, 79 | { 80 | "name": "lengthCheck", 81 | "stateName": "lengthCheck", 82 | "type": "directive", 83 | "outputPath": "partials/api/components.contact/directive/lengthCheck.html", 84 | "url": "api/components.contact/directive/lengthCheck" 85 | } 86 | ] 87 | } 88 | ]); 89 | -------------------------------------------------------------------------------- /docs/app/app.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular 4 | .module('docs') 5 | .config(config); 6 | 7 | function config($locationProvider, $stateProvider, API_DATA, GUIDE_DATA, $urlRouterProvider) { 8 | 9 | $locationProvider.html5Mode(true); 10 | 11 | $urlRouterProvider.otherwise('/api'); 12 | 13 | var sidebarTemplate = '

Contents

' + 14 | '
    ' + 15 | '
  1. ' + 16 | '{{page.name}}' + 17 | '
      ' + 18 | '
    1. ' + 19 | '{{child.name}}' + 20 | '
    2. ' + 21 | '
    ' + 22 | '
  2. ' + 23 | '
'; 24 | 25 | var apiState = { 26 | name: 'api', 27 | url: '/api', 28 | views: { 29 | 'main': { 30 | templateUrl: 'partials/api.html', 31 | }, 32 | 'sidebar': { 33 | template: sidebarTemplate, 34 | controller: 'ApiController as ctrl', 35 | } 36 | } 37 | } 38 | 39 | var guideState = { 40 | name: 'guide', 41 | url: '/guide', 42 | views: { 43 | 'main': { 44 | templateUrl: 'partials/guide.html' 45 | }, 46 | 'sidebar': { 47 | template: sidebarTemplate, 48 | controller: 'GuideController as ctrl', 49 | } 50 | } 51 | } 52 | 53 | $stateProvider.state(apiState); 54 | $stateProvider.state(guideState); 55 | 56 | angular.forEach(API_DATA, function(parent) { 57 | 58 | var newState = { 59 | name: parent.stateName, 60 | url: '/' + parent.url, 61 | views: { 62 | 'main': { 63 | templateUrl: parent.outputPath 64 | }, 65 | 'sidebar': { 66 | template: sidebarTemplate, 67 | controller: 'ApiController as ctrl' 68 | } 69 | } 70 | }; 71 | 72 | $stateProvider.state(newState); 73 | 74 | angular.forEach(parent.docs, function(doc) { 75 | 76 | var newState = { 77 | name: doc.stateName, 78 | url: '/' + doc.url, 79 | views: { 80 | 'main': { 81 | templateUrl: doc.outputPath 82 | }, 83 | 'sidebar': { 84 | template: sidebarTemplate, 85 | controller: 'ApiController as ctrl' 86 | } 87 | } 88 | }; 89 | 90 | $stateProvider.state(newState); 91 | }); 92 | }); 93 | 94 | angular.forEach(GUIDE_DATA, function(parent) { 95 | 96 | var newState = { 97 | name: parent.name, 98 | url: '/' + parent.url, 99 | views: { 100 | 'main': { 101 | templateUrl: parent.outputPath 102 | }, 103 | 'sidebar': { 104 | template: sidebarTemplate, 105 | controller: 'GuideController as ctrl' 106 | } 107 | } 108 | }; 109 | 110 | $stateProvider.state(newState); 111 | 112 | }); 113 | 114 | 115 | } 116 | config.$inject = ["$locationProvider", "$stateProvider", "API_DATA", "GUIDE_DATA", "$urlRouterProvider"]; 117 | -------------------------------------------------------------------------------- /docs/build/src/app.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular 4 | .module('docs') 5 | .config(config); 6 | 7 | function config($locationProvider, $stateProvider, API_DATA, GUIDE_DATA, $urlRouterProvider) { 8 | 9 | $locationProvider.html5Mode(true); 10 | 11 | $urlRouterProvider.otherwise('/api'); 12 | 13 | var sidebarTemplate = '

Contents

' + 14 | '
    ' + 15 | '
  1. ' + 16 | '{{page.name}}' + 17 | '
      ' + 18 | '
    1. ' + 19 | '{{child.name}}' + 20 | '
    2. ' + 21 | '
    ' + 22 | '
  2. ' + 23 | '
'; 24 | 25 | var apiState = { 26 | name: 'api', 27 | url: '/api', 28 | views: { 29 | 'main': { 30 | templateUrl: 'partials/api.html', 31 | }, 32 | 'sidebar': { 33 | template: sidebarTemplate, 34 | controller: 'ApiController as ctrl', 35 | } 36 | } 37 | } 38 | 39 | var guideState = { 40 | name: 'guide', 41 | url: '/guide', 42 | views: { 43 | 'main': { 44 | templateUrl: 'partials/guide.html' 45 | }, 46 | 'sidebar': { 47 | template: sidebarTemplate, 48 | controller: 'GuideController as ctrl', 49 | } 50 | } 51 | } 52 | 53 | $stateProvider.state(apiState); 54 | $stateProvider.state(guideState); 55 | 56 | angular.forEach(API_DATA, function(parent) { 57 | 58 | var newState = { 59 | name: parent.stateName, 60 | url: '/' + parent.url, 61 | views: { 62 | 'main': { 63 | templateUrl: parent.outputPath 64 | }, 65 | 'sidebar': { 66 | template: sidebarTemplate, 67 | controller: 'ApiController as ctrl' 68 | } 69 | } 70 | }; 71 | 72 | $stateProvider.state(newState); 73 | 74 | angular.forEach(parent.docs, function(doc) { 75 | 76 | var newState = { 77 | name: doc.stateName, 78 | url: '/' + doc.url, 79 | views: { 80 | 'main': { 81 | templateUrl: doc.outputPath 82 | }, 83 | 'sidebar': { 84 | template: sidebarTemplate, 85 | controller: 'ApiController as ctrl' 86 | } 87 | } 88 | }; 89 | 90 | $stateProvider.state(newState); 91 | }); 92 | }); 93 | 94 | angular.forEach(GUIDE_DATA, function(parent) { 95 | 96 | var newState = { 97 | name: parent.name, 98 | url: '/' + parent.url, 99 | views: { 100 | 'main': { 101 | templateUrl: parent.outputPath 102 | }, 103 | 'sidebar': { 104 | template: sidebarTemplate, 105 | controller: 'GuideController as ctrl' 106 | } 107 | } 108 | }; 109 | 110 | $stateProvider.state(newState); 111 | 112 | }); 113 | 114 | 115 | } 116 | config.$inject = ["$locationProvider", "$stateProvider", "API_DATA", "GUIDE_DATA", "$urlRouterProvider"]; 117 | -------------------------------------------------------------------------------- /src/app/components/auth/login/login.spec.js: -------------------------------------------------------------------------------- 1 | describe('Auth', function () { 2 | beforeEach(module('components.auth')); 3 | 4 | beforeEach(module(function ($stateProvider) { 5 | $stateProvider.state('app', { url: '/' }); 6 | })); 7 | 8 | describe('Routes', function () { 9 | var $state, $location, $rootScope; 10 | 11 | function goTo(url) { 12 | $location.url(url); 13 | $rootScope.$digest(); 14 | } 15 | 16 | beforeEach(inject(function ($injector) { 17 | $state = $injector.get('$state'); 18 | $location = $injector.get('$location'); 19 | $rootScope = $injector.get('$rootScope'); 20 | })); 21 | 22 | it('should redirect to auth.login state', function() { 23 | goTo('/auth'); 24 | expect($state.current.name).toEqual('auth.login') 25 | }); 26 | 27 | it('should go to auth.login state', function() { 28 | goTo('/login'); 29 | expect($state.current.name).toEqual('auth.login') 30 | }); 31 | }); 32 | 33 | describe('LoginController', function () { 34 | var $componentController, 35 | controller, 36 | AuthService, 37 | $state, 38 | $rootScope, 39 | $q; 40 | 41 | beforeEach(inject(function ($injector) { 42 | $componentController = $injector.get('$componentController'); 43 | AuthService = $injector.get('AuthService'); 44 | $state = $injector.get('$state'); 45 | $rootScope = $injector.get('$rootScope'); 46 | $q = $injector.get('$q'); 47 | 48 | controller = $componentController('login', 49 | { $scope: {}, AuthService: AuthService, $state: $state } 50 | ); 51 | })); 52 | 53 | it('should initialize with correct properties', function () { 54 | controller.$onInit(); 55 | 56 | expect(controller.error).toBeNull(); 57 | expect(controller.user.email).toEqual(''); 58 | expect(controller.user.password).toEqual(''); 59 | }); 60 | 61 | it('should redirect on successful login ', function () { 62 | var mockUser = { email: 'test@test.com', password: 'insecure' }, 63 | mockEvent = { $event: { user: mockUser } }; 64 | 65 | spyOn(AuthService, 'login').and.callFake(function() { 66 | return $q.when({$id: 1}); 67 | }); 68 | 69 | spyOn($state, 'go'); 70 | 71 | var promise = controller.loginUser(mockEvent); 72 | 73 | promise.then(function(result){ 74 | expect(AuthService.login).toHaveBeenCalledWith(mockEvent.user); 75 | expect($state.go).toHaveBeenCalledWith('app'); 76 | }); 77 | 78 | $rootScope.$digest(); 79 | }); 80 | 81 | it('should set error on failed login ', function () { 82 | var mockUser = { email: 'test@test.com', password: 'insecure' }, 83 | mockEvent = { $event: { user: mockUser } }, 84 | mockMessage = 'wrong username or password'; 85 | 86 | spyOn(AuthService, 'login').and.callFake(function() { 87 | return $q.reject({ message: mockMessage}); 88 | }); 89 | 90 | spyOn($state, 'go'); 91 | 92 | var promise = controller.loginUser({}); 93 | 94 | promise.then(function(result){ 95 | expect(AuthService.login).toHaveBeenCalledWith(mockEvent.user); 96 | expect(controller.error).toEqual(mockMessage); 97 | expect($state.go).not.toHaveBeenCalled(); 98 | }); 99 | 100 | $rootScope.$digest(); 101 | }); 102 | }); 103 | }); -------------------------------------------------------------------------------- /docs/build/partials/api/components.contact/type/ContactEditController.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

ContactEditController

6 |
    7 | 8 |
  1. 9 | - type in module components.contact 10 |
  2. 11 |
12 |
13 | 14 | 15 | 16 |
17 |

Lorem Ipsum 1

18 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 19 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

20 |

Lorem Ipsum 2

21 |

Aenean ornare odio elit, eget facilisis ipsum molestie ac. Nam bibendum a nibh ut ullamcorper. 22 | Donec non felis gravida, rutrum ante mattis, sagittis urna. Sed quam quam, facilisis vel cursus at.

23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |

Methods

40 | 152 | 153 | 154 | 155 | 156 | 157 | 158 |
159 | 160 | 161 | -------------------------------------------------------------------------------- /src/app/components/auth/register/register.spec.js: -------------------------------------------------------------------------------- 1 | describe('Auth', function () { 2 | beforeEach(module('components.auth')); 3 | 4 | beforeEach(module(function ($stateProvider) { 5 | $stateProvider.state('app', { url: '/' }); 6 | })); 7 | 8 | describe('Routes', function () { 9 | var $state, $location, $rootScope, AuthService; 10 | 11 | function goTo(url) { 12 | $location.url(url); 13 | $rootScope.$digest(); 14 | } 15 | 16 | beforeEach(inject(function ($injector) { 17 | $state = $injector.get('$state'); 18 | $location = $injector.get('$location'); 19 | $rootScope = $injector.get('$rootScope'); 20 | AuthService = $injector.get('AuthService'); 21 | })); 22 | 23 | it('should go to auth.register state', function() { 24 | spyOn(AuthService, 'requireAuthentication').and.callFake( 25 | function () { 26 | return $q.when('is authenticated'); 27 | } 28 | ); 29 | goTo('/auth/register'); 30 | expect($state.current.name).toEqual('auth.register') 31 | }); 32 | 33 | it('should redirect to app state', function() { 34 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true); 35 | goTo('/auth/register'); 36 | expect($state.current.name).toEqual('app') 37 | }); 38 | }); 39 | 40 | describe('RegisterController', function () { 41 | var $componentController, 42 | controller, 43 | AuthService, 44 | $state, 45 | $rootScope, 46 | $q; 47 | 48 | beforeEach(inject(function ($injector) { 49 | $componentController = $injector.get('$componentController'); 50 | AuthService = $injector.get('AuthService'); 51 | $state = $injector.get('$state'); 52 | $rootScope = $injector.get('$rootScope'); 53 | $q = $injector.get('$q'); 54 | 55 | controller = $componentController('register', 56 | { $scope: {}, AuthService: AuthService, $state: $state } 57 | ); 58 | })); 59 | 60 | it('should initialize with correct properties', function () { 61 | controller.$onInit(); 62 | 63 | expect(controller.error).toBeNull(); 64 | expect(controller.user.email).toEqual(''); 65 | expect(controller.user.password).toEqual(''); 66 | }); 67 | 68 | it('should redirect on successful registration ', function () { 69 | var mockUser = { email: 'test@test.com', password: 'insecure' }, 70 | mockEvent = { $event: { user: mockUser } }; 71 | 72 | spyOn(AuthService, 'register').and.callFake(function() { 73 | return $q.when({$id: 1}); 74 | }); 75 | 76 | spyOn($state, 'go'); 77 | 78 | var promise = controller.createUser(mockEvent); 79 | 80 | promise.then(function(result){ 81 | expect(AuthService.register).toHaveBeenCalledWith(mockEvent.user); 82 | expect($state.go).toHaveBeenCalledWith('app'); 83 | }); 84 | 85 | $rootScope.$digest(); 86 | }); 87 | 88 | it('should set error on failed login ', function () { 89 | var mockUser = { email: 'test@test.com', password: 'insecure' }, 90 | mockEvent = { $event: { user: mockUser } }, 91 | mockMessage = 'Oh bollocks!'; 92 | 93 | spyOn(AuthService, 'register').and.callFake(function() { 94 | return $q.reject({ message: mockMessage}); 95 | }); 96 | 97 | spyOn($state, 'go'); 98 | 99 | var promise = controller.createUser({}); 100 | 101 | promise.then(function(result){ 102 | expect(AuthService.register).toHaveBeenCalledWith(mockEvent.user); 103 | expect(controller.error).toEqual(mockMessage); 104 | expect($state.go).not.toHaveBeenCalled(); 105 | }); 106 | 107 | $rootScope.$digest(); 108 | }); 109 | }); 110 | }); -------------------------------------------------------------------------------- /docs/config/index.js: -------------------------------------------------------------------------------- 1 | // Canonical path provides a consistent path (i.e. always forward slashes) across different OSes 2 | var path = require('canonical-path'); 3 | 4 | var Package = require('dgeni').Package; 5 | var packagePath = __dirname; 6 | 7 | // Create and export a new Dgeni package 8 | // We will use Gulp later on to generate that package 9 | // Think of packages as containers, our 'myDoc' package contains other packages 10 | // which themselves include processors, services, templates... 11 | module.exports = new Package('cma-docs', [ 12 | require('dgeni-packages/ngdoc'), 13 | require('dgeni-packages/nunjucks') 14 | ]) 15 | 16 | 17 | .processor(require('./processors/index-page')) 18 | .processor(require('./processors/guide-data')) 19 | .processor(require('./processors/api-data')) 20 | 21 | 22 | .config(function(log, readFilesProcessor, templateFinder, writeFilesProcessor) { 23 | 24 | // Set the log level to 'info', switch to 'debug' when troubleshooting 25 | log.level = 'info'; 26 | 27 | // Specify the base path used when resolving relative paths to source and output files 28 | readFilesProcessor.basePath = path.resolve(packagePath, '../..'); 29 | 30 | // Specify our source files that we want to extract 31 | readFilesProcessor.sourceFiles = [ 32 | { include: 'src/app/**/**/*.js', basePath: 'src/app' }, // All of our application files 33 | 34 | // Our static Markdown documents 35 | // We are specifying the path and telling Dgeni to use the ngdocFileReader 36 | // to parse the Markdown files to HTMLs 37 | { include: 'docs/content/**/*.md', basePath: 'docs/content', fileReader: 'ngdocFileReader' } 38 | ]; 39 | 40 | // Use the writeFilesProcessor to specify the output folder for the extracted files 41 | writeFilesProcessor.outputFolder = 'docs/build'; 42 | 43 | }) 44 | 45 | .config(function(templateFinder) { 46 | // Specify where the templates are located 47 | templateFinder.templateFolders.unshift(path.resolve(packagePath, 'templates')); 48 | }) 49 | 50 | .config(function(computePathsProcessor, computeIdsProcessor) { 51 | 52 | computeIdsProcessor.idTemplates.push({ 53 | docTypes: ['content', 'indexPage'], 54 | getId: function(doc) { return doc.fileInfo.baseName; }, 55 | getAliases: function(doc) { return [doc.id]; } 56 | }); 57 | 58 | // Build custom paths and outputPaths for "content" pages (theming and CSS). 59 | computePathsProcessor.pathTemplates.push({ 60 | docTypes: ['content'], 61 | getPath: function(doc) { 62 | var docPath = path.dirname(doc.fileInfo.relativePath); 63 | if (doc.fileInfo.baseName !== 'index') { 64 | docPath = path.join(docPath, doc.fileInfo.baseName); 65 | } 66 | return docPath; 67 | }, 68 | outputPathTemplate: 'partials/${path}.html' 69 | }); 70 | 71 | 72 | // Here we are defining our docType Module 73 | // 74 | // Each angular module will be extracted to it's own partial 75 | // and will act as a container for the various Components, Controllers, Services in that Module 76 | // We are basically specifying where we want the output files to be located 77 | computePathsProcessor.pathTemplates.push({ 78 | docTypes: ['module'], 79 | pathTemplate: '${area}/${name}', 80 | outputPathTemplate: 'partials/${area}/${name}.html' 81 | }); 82 | 83 | // Doing the same thing but for regular types like Services, Controllers, etc... 84 | // By default they are grouped in a componentGroup and processed via the generateComponentGroupsProcessor internally in Dgeni 85 | computePathsProcessor.pathTemplates.push({ 86 | docTypes: ['componentGroup'], 87 | pathTemplate: '${area}/${moduleName}/${groupType}', 88 | outputPathTemplate: 'partials/${area}/${moduleName}/${groupType}.html' 89 | }); 90 | 91 | }) 92 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-edit/contact-edit.spec.js: -------------------------------------------------------------------------------- 1 | describe('Contact', function () { 2 | beforeEach(module('components.auth')); 3 | 4 | beforeEach(module('components.contact', function ($provide) { 5 | $provide.value('ContactService', { 6 | updateContact: angular.noop, 7 | deleteContact: angular.noop, 8 | getContactById: function() { 9 | return { 10 | $loaded: angular.noop 11 | } 12 | } 13 | }); 14 | 15 | $provide.value('cfpLoadingBar', { 16 | start: angular.noop, 17 | complete: angular.noop 18 | }) 19 | 20 | $provide.value('$window', { 21 | confirm: function() { return true; } 22 | }) 23 | })); 24 | 25 | beforeEach(module(function ($stateProvider) { 26 | $stateProvider.state('app', { 27 | redirectTo: 'contacts', 28 | url: '/app', 29 | data: { 30 | requiredAuth: true 31 | } 32 | }); 33 | })); 34 | 35 | describe('Routes', function () { 36 | var $state, $location, $rootScope, AuthService; 37 | 38 | function goTo(url) { 39 | $location.url(url); 40 | $rootScope.$digest(); 41 | } 42 | 43 | beforeEach(inject(function ($injector) { 44 | $state = $injector.get('$state'); 45 | $location = $injector.get('$location'); 46 | $rootScope = $injector.get('$rootScope'); 47 | AuthService = $injector.get('AuthService'); 48 | })); 49 | 50 | it('should go to the contact state', function() { 51 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true); 52 | 53 | goTo('/app/contact/1'); 54 | 55 | expect($state.current.name).toEqual('contact'); 56 | }); 57 | }); 58 | 59 | describe('ContactDetailController', function () { 60 | var $componentController, 61 | controller, 62 | $state, 63 | ContactService, 64 | cfpLoadingBar, 65 | $rootScope, 66 | $q, 67 | mockContact = { $id: 1 }; 68 | 69 | beforeEach(inject(function ($injector) { 70 | $componentController = $injector.get('$componentController'); 71 | $state = $injector.get('$state'); 72 | ContactService = $injector.get('ContactService'); 73 | cfpLoadingBar = $injector.get('cfpLoadingBar'); 74 | $window = $injector.get('$window'); 75 | $rootScope = $injector.get('$rootScope'); 76 | $q = $injector.get('$q'); 77 | 78 | controller = $componentController('contactEdit', 79 | { $scope: {}, $state: $state, ContactService: ContactService, cfpLoadingBar: cfpLoadingBar }, 80 | { contact: mockContact } 81 | ); 82 | })); 83 | 84 | it('should update contact', function () { 85 | var event = { contact: { $id: 1 }}; 86 | spyOn(cfpLoadingBar, 'start'); 87 | spyOn(cfpLoadingBar, 'complete'); 88 | spyOn(ContactService, 'updateContact').and.callFake( 89 | function () { 90 | return $q.when({}) 91 | } 92 | ); 93 | 94 | var promise = controller.updateContact(event); 95 | 96 | expect(cfpLoadingBar.start).toHaveBeenCalled(); 97 | 98 | promise.then(function () { 99 | expect(ContactService.updateContact).toHaveBeenCalled(); 100 | expect(cfpLoadingBar.complete).toHaveBeenCalled(); 101 | }); 102 | 103 | $rootScope.$digest(); 104 | }); 105 | 106 | it('should delete contact', function () { 107 | var event = { contact: { $id: 1, name: 'John Smith' }}; 108 | spyOn($state, 'go'); 109 | 110 | spyOn(ContactService, 'deleteContact').and.callFake( 111 | function () { 112 | return $q.when({}) 113 | } 114 | ); 115 | 116 | var promise = controller.deleteContact(event); 117 | 118 | promise.then(function () { 119 | expect($state.go).toHaveBeenCalledWith('contacts'); 120 | }); 121 | 122 | $rootScope.$digest(); 123 | }); 124 | }); 125 | }); -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import concat from 'gulp-concat'; 3 | import wrap from 'gulp-wrap'; 4 | import uglify from 'gulp-uglify'; 5 | import htmlmin from 'gulp-htmlmin'; 6 | import gulpif from 'gulp-if'; 7 | import sass from 'gulp-sass'; 8 | import yargs from 'yargs'; 9 | import ngAnnotate from 'gulp-ng-annotate'; 10 | import templateCache from 'gulp-angular-templatecache'; 11 | import server from 'browser-sync'; 12 | import del from 'del'; 13 | import path from 'path'; 14 | import child from 'child_process'; 15 | import Dgeni from 'dgeni'; 16 | 17 | const exec = child.exec; 18 | const argv = yargs.argv; 19 | const root = 'src/'; 20 | const paths = { 21 | dist: './dist/', 22 | distDocs: './docs/build', 23 | docs: './docs/app/*.js', 24 | scripts: [`${root}/app/**/*.js`, `!${root}/app/**/*.spec.js`], 25 | tests: `${root}/app/**/*.spec.js`, 26 | styles: `${root}/sass/*.scss`, 27 | templates: `${root}/app/**/*.html`, 28 | modules: [ 29 | 'angular/angular.js', 30 | 'angular-ui-router/release/angular-ui-router.js', 31 | 'firebase/firebase.js', 32 | 'angularfire/dist/angularfire.js', 33 | 'angular-loading-bar/build/loading-bar.min.js' 34 | ], 35 | static: [ 36 | `${root}/index.html`, 37 | `${root}/fonts/**/*`, 38 | `${root}/img/**/*` 39 | ] 40 | }; 41 | 42 | server.create(); 43 | 44 | gulp.task('clean', cb => del(paths.dist + '**/*', cb)); 45 | 46 | gulp.task('cleanDocs', cb => del(paths.distDocs + '**/**/*', cb)); 47 | 48 | gulp.task('templates', () => { 49 | return gulp.src(paths.templates) 50 | .pipe(htmlmin({ collapseWhitespace: true })) 51 | .pipe(templateCache({ 52 | root: 'app', 53 | standalone: true, 54 | transformUrl: function (url) { 55 | return url.replace(path.dirname(url), '.'); 56 | } 57 | })) 58 | .pipe(gulp.dest('./')); 59 | }); 60 | 61 | gulp.task('modules', ['templates'], () => { 62 | return gulp.src(paths.modules.map(item => 'node_modules/' + item)) 63 | .pipe(concat('vendor.js')) 64 | .pipe(gulpif(argv.deploy, uglify())) 65 | .pipe(gulp.dest(paths.dist + 'js/')); 66 | }); 67 | 68 | gulp.task('styles', () => { 69 | return gulp.src(paths.styles) 70 | .pipe(sass({outputStyle: 'compressed'})) 71 | .pipe(gulp.dest(paths.dist + 'css/')); 72 | }); 73 | 74 | gulp.task('scripts', ['modules'], () => { 75 | return gulp.src([ 76 | `!${root}/app/**/*.spec.js`, 77 | `${root}/app/**/*.module.js`, 78 | ...paths.scripts, 79 | './templates.js' 80 | ]) 81 | .pipe(wrap('(function(angular){\n\'use strict\';\n<%= contents %>})(window.angular);')) 82 | .pipe(concat('bundle.js')) 83 | .pipe(ngAnnotate()) 84 | .pipe(gulpif(argv.deploy, uglify())) 85 | .pipe(gulp.dest(paths.dist + 'js/')); 86 | }); 87 | 88 | gulp.task('serve', () => { 89 | return server.init({ 90 | files: [`${paths.dist}/**`], 91 | port: 4000, 92 | server: { 93 | baseDir: paths.dist 94 | } 95 | }); 96 | }); 97 | 98 | gulp.task('copy', ['clean'], () => { 99 | return gulp.src(paths.static, { base: 'src' }) 100 | .pipe(gulp.dest(paths.dist)); 101 | }); 102 | 103 | gulp.task('watch', ['serve', 'scripts'], () => { 104 | gulp.watch([paths.scripts, paths.templates], ['scripts']); 105 | gulp.watch(paths.styles, ['styles']); 106 | }); 107 | 108 | gulp.task('firebase', ['styles', 'scripts'], cb => { 109 | return exec('firebase deploy', function (err, stdout, stderr) { 110 | console.log(stdout); 111 | console.log(stderr); 112 | cb(err); 113 | }); 114 | }); 115 | 116 | gulp.task('default', [ 117 | 'copy', 118 | 'styles', 119 | 'serve', 120 | 'watch' 121 | ]); 122 | 123 | gulp.task('production', [ 124 | 'copy', 125 | 'scripts', 126 | 'firebase' 127 | ]); 128 | 129 | gulp.task('copyDocs', () => { 130 | return gulp.src(paths.docs) 131 | .pipe(gulp.dest(paths.distDocs + '/src')); 132 | }); 133 | 134 | gulp.task('dgeni', ['cleanDocs', 'copyDocs'], () => { 135 | var dgeni = new Dgeni([require('./docs/config')]); 136 | return dgeni.generate(); 137 | }); 138 | -------------------------------------------------------------------------------- /src/app/components/auth/auth.spec.js: -------------------------------------------------------------------------------- 1 | describe('Auth', function () { 2 | beforeEach(module('components.auth')); 3 | 4 | beforeEach(module(function ($stateProvider) { 5 | $stateProvider.state('app', { url: '/' }); 6 | })); 7 | 8 | describe('Routes', function () { 9 | var $state, AuthService, $location, $rootScope, $q; 10 | 11 | function goTo(url) { 12 | $location.url(url); 13 | $rootScope.$digest(); 14 | } 15 | 16 | beforeEach(inject(function ($injector) { 17 | $state = $injector.get('$state'); 18 | AuthService = $injector.get('AuthService'); 19 | $location = $injector.get('$location'); 20 | $rootScope = $injector.get('$rootScope'); 21 | $q = $injector.get('$q'); 22 | })); 23 | 24 | it('should redirect to auth.login state', function() { 25 | spyOn(AuthService, 'requireAuthentication').and.callFake( 26 | function () { 27 | return $q.reject('Not authenticated!'); 28 | } 29 | ); 30 | 31 | goTo('/app'); 32 | 33 | expect($state.current.name).toEqual('auth.login') 34 | }); 35 | 36 | it('should redirect to app state', function() { 37 | spyOn(AuthService, 'isAuthenticated').and.returnValue(true); 38 | 39 | goTo('/auth'); 40 | 41 | expect($state.current.name).toEqual('app') 42 | }); 43 | }); 44 | 45 | describe('AuthService', function () { 46 | var AuthService, $firebaseAuth, $rootScope; 47 | 48 | beforeEach(inject(function ($injector) { 49 | AuthService = $injector.get('AuthService'); 50 | $firebaseAuth = $injector.get('$firebaseAuth')(); 51 | $rootScope = $injector.get('$rootScope'); 52 | })); 53 | 54 | it('should return undefined for initial user', function () { 55 | expect(AuthService.getUser()).toBeUndefined(); 56 | }); 57 | 58 | it('should login and store the authenticated user', function () { 59 | var user = { email: 'test@test.com', password: 'insecure' }, 60 | response = { $id: 1 }, 61 | promise = AuthService.login(user); 62 | 63 | promise.then(function (result) { 64 | expect(result).toEqual(response); 65 | expect(AuthService.isAuthenticated()).toBe(true); 66 | expect(AuthService.getUser()).toEqual(response); 67 | }); 68 | 69 | $rootScope.$digest(); 70 | }); 71 | 72 | it('should properly store auth data when registering a user', function () { 73 | var user = { email: 'test@test.com', password: 'insecure' }, 74 | response = { $id: 1 }, 75 | promise = AuthService.register(user); 76 | 77 | promise.then(function (result) { 78 | expect(result).toEqual(response); 79 | expect(AuthService.isAuthenticated()).toBe(true); 80 | expect(AuthService.getUser()).toEqual(response); 81 | }); 82 | 83 | $rootScope.$digest(); 84 | }); 85 | 86 | it('should clear auth data when logout is called', function () { 87 | var promise = AuthService.logout(); 88 | 89 | promise.then(function (result) { 90 | expect(AuthService.isAuthenticated()).toBe(false); 91 | expect(AuthService.getUser()).toBeUndefined(); 92 | }); 93 | 94 | $rootScope.$digest(); 95 | }); 96 | 97 | it('should should return correct auth data on getUser', function () { 98 | var user = { email: 'test@test.com', password: 'insecure' }, 99 | response = { $id: 1 }, 100 | promise = AuthService.login(user); 101 | 102 | promise.then(function (result) { 103 | expect(AuthService.getUser()).toEqual(response); 104 | }); 105 | 106 | $rootScope.$digest(); 107 | 108 | promise = AuthService.logout(); 109 | 110 | promise.then(function (result) { 111 | expect(AuthService.getUser()).toBeUndefined(); 112 | }); 113 | 114 | $rootScope.$digest(); 115 | }); 116 | 117 | it('should should return correct response on requireAuthentication', function () { 118 | var response = { $id: 1 }, 119 | promise = AuthService.requireAuthentication(); 120 | 121 | promise.then(function (result) { 122 | expect(result).toEqual(response); 123 | }); 124 | 125 | $rootScope.$digest(); 126 | }); 127 | 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /src/app/components/contact/contact-detail/contact-detail.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | * field is required 5 |
6 |
7 |

8 | Personal 9 |

10 |
11 | 14 | 28 |
29 |
30 | 36 | 49 |
50 |
51 | 54 | 67 |
68 |
69 | 72 | 85 |
86 |
87 |
88 |

89 | Social 90 |

91 |
92 | 95 | 108 |
109 |
110 | 113 | 126 |
127 |
128 | 131 | 144 |
145 |
146 | 149 | 162 |
163 |
164 | 165 | 168 | 169 | 170 |
171 | 177 |
178 |
179 | 184 |
185 | 186 |
187 |
188 | --------------------------------------------------------------------------------