Merge Request #10

Merged
noosfero-themes/angular-theme!10
Created by Michel Felipe

Sidebar menu

Created the Sidebar widget with two typescript parent and children components: SidebarComponent and SidebarSectionsComponent.

A lot of things were made. See the summary below:

  • A simple feature to allow theme skins (sidebar with two color palletes)
  • Push content to right when sidebar is opened (using .content-wrapper css class)
  • Unit tests helpers refactory

Screenshot

sidebar-noosfero

Assignee: None
Milestone: 2016.05

Merged by Michel Felipe

Source branch has been removed
Commits (7)
3 participants
src/app/index.scss
... ... @@ -28,12 +28,16 @@ $primary-color-dark: #0288d1;
28 28 $default-bg-hover-color: #f8f8f8;
29 29 $red-color: #e84e40;
30 30 $red-color-dark: #dd191d;
  31 +$green-color: #8bc34a;
  32 +$green-color-dark: #689f38;
31 33  
32 34 //GRID - media queries breakpoints
33 35 $break-xxs-min: 420px;
34 36 $break-xs-min: 768px;
  37 +$break-sm-min: 992px;
35 38  
36 39 $break-xxs-max: ($break-xxs-min - 1);
  40 +$break-sm-max: ($break-sm-min - 1);
37 41 $break-xs-max: ($break-xs-min - 1);
38 42  
39 43  
... ... @@ -76,4 +80,5 @@ h1, h2, h3, h4, h5 {
76 80 @import "layout/scss/mixins";
77 81 @import "layout/scss/bootstrap-overrides";
78 82 @import "layout/scss/layout";
  83 +@import "layout/scss/sidebar";
79 84 @import "layout/scss/tables";
... ...
src/app/index.ts
... ... @@ -5,7 +5,7 @@ import {noosferoAngularRunBlock} from "./index.run";
5 5 import {MainComponent} from "./main/main.component";
6 6 import {bootstrap, bundle} from "ng-forward";
7 7  
8   -import {AUTH_EVENTS} from "./login/auth-events";
  8 +import {AuthEvents} from "./login/auth-events";
9 9 import {AuthController} from "./login/auth.controller";
10 10  
11 11 import {Navbar} from "./layout/navbar/navbar";
... ... @@ -23,7 +23,7 @@ NoosferoApp.angularModule = noosferoApp;
23 23  
24 24  
25 25 NoosferoApp.addConstants("moment", moment);
26   -NoosferoApp.addConstants("AUTH_EVENTS", AUTH_EVENTS);
  26 +NoosferoApp.addConstants("AuthEvents", AuthEvents);
27 27  
28 28 NoosferoApp.addConfig(noosferoModuleConfig);
29 29 NoosferoApp.run(noosferoAngularRunBlock);
... ...
src/app/layout/blocks/people-block/people-block.component.spec.ts
... ... @@ -39,8 +39,8 @@ describe("Components", () => {
39 39 });
40 40  
41 41 /**
42   - * By default the helper will have the component, with all properties
43   - * ready to be used. Here the mock provider 'EnvironmentService' will
  42 + * By default the helper will have the component, with all properties
  43 + * ready to be used. Here the mock provider 'EnvironmentService' will
44 44 * return the given array with one person.
45 45 */
46 46 it("get block with one people", () => {
... ...
src/app/layout/boxes/boxes.component.spec.ts
... ... @@ -7,7 +7,6 @@ import {
7 7 quickCreateComponent,
8 8 provideEmptyObjects,
9 9 createProviderToValue,
10   - getAngularServiceFactory,
11 10 provideFilters
12 11 } from "../../../spec/helpers";
13 12  
... ...
src/app/layout/navbar/navbar.html
1 1 <nav class="navbar navbar-static-top navbar-inverse">
2 2 <div class="container-fluid">
3 3 <div class="navbar-header">
4   - <button type="button" class="navbar-toggle collapsed" ng-click="isCollapsed = !isCollapsed">
  4 + <button type="button" class="navbar-toggle collapsed" (click)="ctrl.toggleCollapse()" ng-show="ctrl.showHamburguer">
5 5 <span class="sr-only">{{"navbar.toggle_menu" | translate}}</span>
6   - <span class="icon-bar"></span>
7   - <span class="icon-bar"></span>
8   - <span class="icon-bar"></span>
  6 + <i class="fa fa-bars"></i>
9 7 </button>
10 8 <a class="navbar-brand" ui-sref="main.environment.home">
11   - <span class="noosfero-logo"> </span>
  9 + <span class="noosfero-logo"> </span>
12 10 <span class="noosfero-name">{{"noosfero.name" | translate}}</span>
13 11 </a>
14 12 </div>
... ...
src/app/layout/navbar/navbar.scss
1 1 .navbar {
2 2  
  3 + margin-bottom: 0px;
  4 +
3 5 .container-fluid {
4   - padding-right: 12%;
5   - padding-left: 12%;
6   - @media (max-width: 978px) {
7   - padding-right: 2%;
8   - padding-left: 2%;
9   - }
10 6  
11 7 .navbar-brand {
12 8 .noosfero-logo {
13   - background:url("../assets/images/logo-noosfero.png") no-repeat;
  9 + background: url("../assets/images/logo-noosfero.png") no-repeat;
14 10 padding: 0px 62px 64px 15px;
15 11 }
16 12 }
... ... @@ -28,5 +24,19 @@
28 24 }
29 25 }
30 26 }
  27 +
  28 + .navbar-toggle {
  29 + display: block;
  30 + color: #fff;
  31 + padding: 4px 12px;
  32 + margin: 10px 10px;
  33 + text-align: center;
  34 + }
  35 +
  36 + @media (max-width: $break-sm-max) {
  37 + .navbar-toggle .fa-bars{
  38 + font-size: 14pt;
  39 + }
  40 + }
31 41 }
32 42 }
... ...
src/app/layout/navbar/navbar.spec.ts
1 1 import * as helpers from "./../../../spec/helpers";
2 2 import {Navbar} from "./navbar";
3 3  
4   -import {Injectable, Provider, provide} from "ng-forward";
  4 +import {Injectable, Provider, provide, EventEmitter} from "ng-forward";
5 5  
6 6 import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder';
7 7  
8   -import {SessionService, AuthService, AuthController, IAuthEvents, AUTH_EVENTS} from "./../../login";
  8 +import {SessionService, AuthService, AuthController, AuthEvents} from "./../../login";
9 9  
  10 +import events from 'ng-forward/cjs/events/events';
10 11  
11 12 describe("Components", () => {
12 13  
... ... @@ -53,22 +54,19 @@ describe(&quot;Components&quot;, () =&gt; {
53 54 provide('$uibModal', {
54 55 useValue: $modal
55 56 }),
56   - provide('AuthService', {
  57 + provide(AuthService, {
57 58 useValue: authService
58 59 }),
59 60 helpers.provideEmptyObjects('moment'),
60 61 provide('$state', {
61 62 useValue: stateService
62 63 }),
63   - provide("$scope", {
64   - useValue: scope
65   - }),
66 64 provide('SessionService', {
67 65 useValue: sessionService
68 66 }),
69   - provide('AUTH_EVENTS', {
  67 + provide('AuthEvents', {
70 68 useValue: {
71   - AUTH_EVENTS
  69 + AuthEvents
72 70 }
73 71 }),
74 72 provide('TranslatorService', {
... ... @@ -146,7 +144,7 @@ describe(&quot;Components&quot;, () =&gt; {
146 144  
147 145  
148 146 it('closes the modal after login', (done: Function) => {
149   - modalInstance = jasmine.createSpyObj("modalInstance", ["close"]);
  147 + modalInstance = {};
150 148 modalInstance.close = jasmine.createSpy("close");
151 149  
152 150 $modal.open = () => {
... ... @@ -155,19 +153,21 @@ describe(&quot;Components&quot;, () =&gt; {
155 153  
156 154 buildComponent().then((fixture: ComponentFixture) => {
157 155 let navbarComp: Navbar = <Navbar>fixture.debugElement.componentViewChildren[0].componentInstance;
158   - let localScope: ng.IScope = navbarComp["$scope"];
159 156  
160 157 navbarComp.openLogin();
161   - localScope.$emit(AUTH_EVENTS.loginSuccess);
  158 + let successEvent: string = AuthEvents[AuthEvents.loginSuccess];
  159 +
  160 + (<any>navbarComp.authService)[successEvent].next(user);
  161 +
162 162 expect(modalInstance.close).toHaveBeenCalled();
163 163 done();
164 164 });
  165 +
165 166 });
166 167  
167 168 it('updates current user on logout', (done: Function) => {
168 169 buildComponent().then((fixture: ComponentFixture) => {
169 170 let navbarComp: Navbar = <Navbar>fixture.debugElement.componentViewChildren[0].componentInstance;
170   - let localScope: ng.IScope = navbarComp["$scope"];
171 171  
172 172 // init navbar currentUser with some user
173 173 navbarComp["currentUser"] = user;
... ... @@ -176,12 +176,14 @@ describe(&quot;Components&quot;, () =&gt; {
176 176 // and emmit the 'logoutSuccess' event
177 177 // just what happens when user logsout
178 178 sessionService.currentUser = () => { return null; };
179   - localScope.$emit(AUTH_EVENTS.logoutSuccess);
180   - expect(navbarComp["currentUser"]).toBeNull();
  179 + let successEvent: string = AuthEvents[AuthEvents.logoutSuccess];
  180 +
  181 + (<any>navbarComp.authService)[successEvent].next(user);
  182 +
181 183 done();
  184 + expect(navbarComp["currentUser"]).toBeNull();
  185 +
182 186 });
183 187 });
184   -
185   -
186 188 });
187 189 });
... ...
src/app/layout/navbar/navbar.ts
1   -import {Component, Inject} from "ng-forward";
  1 +import {Component, Inject, EventEmitter, Input} from "ng-forward";
2 2 import {LanguageSelectorComponent} from "../language-selector/language-selector.component";
3   -
4   -
5   -import {SessionService, AuthService, AuthController, IAuthEvents, AUTH_EVENTS} from "./../../login";
  3 +import {SessionService, AuthService, AuthController, AuthEvents} from "./../../login";
  4 +import {SidebarNotificationService} from "../sidebar/sidebar.notification.service";
  5 +import {BodyStateClassesService} from '../services/body-state-classes.service';
6 6  
7 7 @Component({
8 8 selector: "acme-navbar",
9 9 templateUrl: "app/layout/navbar/navbar.html",
10 10 directives: [LanguageSelectorComponent],
11   - providers: [AuthService, SessionService]
  11 + providers: [AuthService, SessionService, SidebarNotificationService]
12 12 })
13   -@Inject("$uibModal", AuthService, "SessionService", "$scope", "$state")
  13 +@Inject("$uibModal", AuthService, "SessionService", "$state", SidebarNotificationService, BodyStateClassesService)
14 14 export class Navbar {
15 15  
16 16 private currentUser: noosfero.User;
17 17 private modalInstance: any = null;
  18 +
  19 + public showHamburguer: boolean = false;
  20 +
18 21 /**
19 22 *
20 23 */
21 24 constructor(
22 25 private $uibModal: any,
23   - private authService: AuthService,
  26 + public authService: AuthService,
24 27 private session: SessionService,
25   - private $scope: ng.IScope,
26   - private $state: ng.ui.IStateService
  28 + private $state: ng.ui.IStateService,
  29 + private sidebarNotificationService: SidebarNotificationService,
  30 + private bodyStateService: BodyStateClassesService
27 31 ) {
28 32 this.currentUser = this.session.currentUser();
29 33  
30   - this.$scope.$on(AUTH_EVENTS.loginSuccess, () => {
  34 + this.showHamburguer = this.authService.isAuthenticated();
  35 + this.bodyStateService.addContentClass(!this.sidebarNotificationService.sidebarVisible);
  36 +
  37 + this.authService.subscribe(AuthEvents[AuthEvents.loginSuccess], () => {
31 38 if (this.modalInstance) {
32 39 this.modalInstance.close();
33 40 this.modalInstance = null;
34 41 }
35 42  
36   - this.$state.go(this.$state.current, {}, { reload: true }); // TODO move to auth
  43 + this.currentUser = this.session.currentUser();
  44 + this.showHamburguer = true;
  45 +
  46 + this.$state.go(this.$state.current, {}, { reload: true }); // TODO move to auth
37 47 });
38 48  
39   - this.$scope.$on(AUTH_EVENTS.logoutSuccess, () => {
  49 + this.authService.subscribe(AuthEvents[AuthEvents.logoutSuccess], () => {
40 50 this.currentUser = this.session.currentUser();
41 51 });
  52 +
  53 + }
  54 +
  55 + public toggleCollapse() {
  56 + this.sidebarNotificationService.alternateVisibility();
  57 +
  58 + this.bodyStateService.addContentClass(!this.sidebarNotificationService.sidebarVisible);
42 59 }
43 60  
44 61 openLogin() {
... ... @@ -55,8 +72,6 @@ export class Navbar {
55 72 this.$state.go(this.$state.current, {}, { reload: true }); // TODO move to auth
56 73 };
57 74  
58   -
59   -
60 75 activate() {
61 76 if (!this.currentUser) {
62 77 this.openLogin();
... ...
src/app/layout/scss/_bootstrap-overrides.scss
... ... @@ -54,3 +54,19 @@
54 54 .tabs-wrapper.tabs-no-header .tab-content {
55 55 padding: 0 20px 20px;
56 56 }
  57 +
  58 +/* LABELS */
  59 +.label {
  60 + @include border-radius($border-radius-base);
  61 + font-size: 0.875em;
  62 + font-weight: 600;
  63 +}
  64 +.label-default,
  65 +.label.label-large {
  66 + font-size: 1em;
  67 + padding: 0.4em 0.8em 0.5em;
  68 +}
  69 +.label.label-circle {
  70 + @include border-radius(50%);
  71 + padding: 4px !important;
  72 +}
... ...
src/app/layout/scss/_sidebar.scss 0 → 100644
... ... @@ -0,0 +1,311 @@
  1 +#col-left {
  2 + position: relative;
  3 + color: #003940;
  4 + height: 100%;
  5 +
  6 + a {
  7 + color: #e1e1e1;
  8 + }
  9 + a:hover,
  10 + .nav-active a.nav-link,
  11 + a.active {
  12 + color: #fff;
  13 + }
  14 + * {
  15 + outline: none;
  16 + }
  17 +}
  18 +#nav-col {
  19 + padding: 0;
  20 + z-index: 100;
  21 + position: absolute;
  22 + background: #2c3e50;
  23 + width: 220px;
  24 + border-right: 2px solid #e7ebee;
  25 + height: 200%;
  26 +
  27 + @media (max-width: $break-sm-max) {
  28 + position: relative;
  29 + width: auto;
  30 + }
  31 +}
  32 +#sidebar-nav {
  33 + max-height: 100%;
  34 + padding-left: 0;
  35 + padding-right: 0;
  36 +
  37 + .nav {
  38 + > li {
  39 + margin: 0;
  40 +
  41 + &.nav-header {
  42 + color: lighten(#2c3e50, 40%);
  43 + font-size: 0.8em;
  44 + padding: 12px 15px 6px 14px;
  45 + border-top: 2px solid darken(#2c3e50, 4%);
  46 +
  47 + &.nav-header-first {
  48 + padding-top: 4px;
  49 + border-top: 0;
  50 + }
  51 + }
  52 +
  53 + > a {
  54 + color: #fff;
  55 + height: 44px;
  56 + line-height: 28px;
  57 + @include transition(border-color 0.1s ease-in-out 0s, background-color 0.1s ease-in-out 0s, box-shadow 0.1s ease-in-out 0s);
  58 + overflow: hidden;
  59 + padding: 8px 15px 8px 20px;
  60 + border-left: 0 solid transparent;
  61 +
  62 + &:hover {
  63 + border-left-color: $primary-color;
  64 + }
  65 + > i {
  66 + position: absolute;
  67 + margin-top: 6px;
  68 + }
  69 + > span {
  70 + margin-left: 35px;
  71 + font-size: 0.875em;
  72 + font-weight: 700;
  73 +
  74 + &.label {
  75 + font-size: 0.75em;
  76 + margin: 5px 0 0 0;
  77 + padding: 4px 0.6em;
  78 + }
  79 + &.label.label-circle {
  80 + margin-right: 5px;
  81 + }
  82 + }
  83 + }
  84 + &.open > a {
  85 + border-bottom-color: #252525;
  86 + outline: none;
  87 + text-decoration: none;
  88 + }
  89 + &.active > .submenu > li.active > .submenu {
  90 + display: block;
  91 + }
  92 + }
  93 + li {
  94 + @import "sidebar/submenu";
  95 +
  96 + &.active > .submenu {
  97 + display: block;
  98 + }
  99 + }
  100 + }
  101 +}
  102 +.navbar-nav .open .dropdown-menu {
  103 + background-color: #FFFFFF;
  104 + border: none;
  105 + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.176);
  106 + position: absolute;
  107 +}
  108 +#user-left-box {
  109 + padding: 20px 15px 20px 25px;
  110 +
  111 + img {
  112 + @include border-radius(18%);
  113 + border: 3px solid #fff;
  114 + float: left;
  115 + width: 70px;
  116 + }
  117 + .user-box {
  118 + color: #fff;
  119 + float: left;
  120 + padding-left: 15px;
  121 + padding-top: 18px;
  122 +
  123 + > .name {
  124 + display: block;
  125 + font-size: 1em;
  126 + font-weight: 600;
  127 + line-height: 1.2;
  128 +
  129 + > a {
  130 + color: #fff;
  131 +
  132 + &:hover,
  133 + &:focus {
  134 + color: #E1E1E1;
  135 + text-decoration: none;
  136 + }
  137 + }
  138 + }
  139 + > .status {
  140 + display: block;
  141 + font-size: 0.75em;
  142 + padding-top: 3px;
  143 + }
  144 + > .status > i {
  145 + color: $green-color;
  146 + margin-right: 4px;
  147 + }
  148 + }
  149 + &.dropdown {
  150 + .dropdown-menu {
  151 + top: 55px;
  152 + left: 30px;
  153 +
  154 + a {
  155 + color: #707070;
  156 + font-size: 0.875em;
  157 +
  158 + &:hover {
  159 + background-color: #f6f6f6;
  160 + color: #707070;
  161 + }
  162 + }
  163 + }
  164 + }
  165 +}
  166 +@media (min-width: $break-sm-min) {
  167 + .nav-small {
  168 + #nav-col {
  169 + width: 64px;
  170 + }
  171 + #content-wrapper {
  172 + margin-left: 64px;
  173 + }
  174 + #nav-col {
  175 + #user-left-box {
  176 + display: none;
  177 + }
  178 + #sidebar-nav {
  179 + .nav > li > a {
  180 + padding-left: 15px !important;
  181 + padding-right: 15px;
  182 + text-align: center;
  183 +
  184 + > i {
  185 + position: relative;
  186 + font-size: 1.25em;
  187 + }
  188 + > span {
  189 + display: none;
  190 + }
  191 + }
  192 + .nav > li.nav-header {
  193 + display: none;
  194 + }
  195 + .nav li > a.dropdown-toggle > .drop-icon {
  196 + display: none;
  197 + }
  198 + .nav .submenu > li > a.dropdown-toggle > .drop-icon {
  199 + display: block;
  200 + }
  201 + .nav li .submenu {
  202 + left: 64px;
  203 + position: absolute;
  204 + top: 0;
  205 + width: 210px;
  206 +
  207 + > li > a {
  208 + padding-left: 28px;
  209 + }
  210 + }
  211 + .nav > .active > .submenu > li > .submenu {
  212 + left: auto;
  213 + position: relative;
  214 + top: auto;
  215 + width: 100%;
  216 +
  217 + a {
  218 + padding-left: 48px
  219 + }
  220 + }
  221 + }
  222 + }
  223 +
  224 + #nav-col-submenu {
  225 + @import "sidebar/submenu";
  226 +
  227 + .submenu {
  228 + position: absolute;
  229 + top: 60px;
  230 + left: 64px;
  231 + width: 210px;
  232 +
  233 + > li > a {
  234 + padding-left: 28px;
  235 +
  236 + &.dropdown-toggle > .drop-icon {
  237 + display: block;
  238 + }
  239 + }
  240 + }
  241 + > .submenu {
  242 + display: block !important;
  243 + }
  244 + .submenu > li > .submenu,
  245 + .submenu > li > .submenu {
  246 + left: auto;
  247 + position: relative;
  248 + top: auto;
  249 + width: 100%;
  250 +
  251 + a {
  252 + padding-left: 48px
  253 + }
  254 + }
  255 + }
  256 + }
  257 +}
  258 +@media (max-width: $break-sm-max) {
  259 +
  260 + #sidebar-nav.navbar-collapse {
  261 + max-height: 336px;
  262 + }
  263 +}
  264 +
  265 +/* Extracted from original _header.scss */
  266 +#sidebar-nav .nav a {
  267 + > i {
  268 + font-size: 1.125em;
  269 + }
  270 + > .open > &, .open > &:focus {
  271 + background: inherit;
  272 + }
  273 + &:hover, .open > &:hover {
  274 + background: darken(#2c3e50, 4%);
  275 + color: #fff;
  276 + outline: none;
  277 + }
  278 +}
  279 +
  280 +#sidebar-nav .nav-pills > li.active > a,
  281 +#sidebar-nav .nav-pills > li.active > a:hover,
  282 +#sidebar-nav .nav-pills > li.active > a:focus,
  283 +.nav-pills > li.open > a,
  284 +.nav-pills > li.open > a:hover,
  285 +.nav-pills > li.open > a:focus,
  286 +#sidebar-nav .nav-pills > li.open > a,
  287 +#sidebar-nav .nav-pills > li.open > a:hover,
  288 +#sidebar-nav .nav-pills > li > a:focus,
  289 +.nav-small #nav-col #sidebar-nav .nav-pills > li.open > a {
  290 + background-color: darken(#2c3e50, 4%);
  291 + color: #fff;
  292 + border-left-color: $primary-color;
  293 +}
  294 +
  295 +#user-left-box {
  296 + .profile-image-wrap {
  297 + float:left;
  298 + width: 70px;
  299 + height: 70px;
  300 + }
  301 + i.profile-image {
  302 + border-radius: 18%;
  303 + border: 3px solid #fff;
  304 + background-color: #fff;
  305 + text-align: center;
  306 + }
  307 +}
  308 +
  309 +.submenu-count {
  310 + margin-right: 20px !important;
  311 +}
... ...
src/app/layout/scss/sidebar/_submenu.scss 0 → 100644
... ... @@ -0,0 +1,52 @@
  1 +a.dropdown-toggle > .drop-icon {
  2 + color: #868b98;
  3 + font-size: 12px;
  4 + margin-top: -6px;
  5 + position: absolute;
  6 + right: 25px;
  7 + top: 50%;
  8 + @include transition(transform 0.2s ease-in-out 0.1s);
  9 +}
  10 +&.open a.dropdown-toggle > .drop-icon,
  11 +&.active > a.dropdown-toggle > .drop-icon {
  12 + color: #fff;
  13 + transform:rotate(90deg);
  14 +}
  15 +
  16 +.submenu {
  17 + background: darken(#2c3e50, 4%);
  18 + padding-left: 0;
  19 + margin: 0;
  20 + list-style: none;
  21 +
  22 + li {
  23 + position: relative;
  24 +
  25 + > a {
  26 + display: block;
  27 + font-size: 0.875em;
  28 + line-height: 38px;
  29 + padding-left: 60px;
  30 + color: #fff;
  31 + outline: none;
  32 + text-decoration: none;
  33 + @include transition(border-color 0.1s ease-in-out 0s, background-color 0.1s ease-in-out 0s, box-shadow 0.1s ease-in-out 0s);
  34 +
  35 + &:hover {
  36 + background-color: #1f2c39;
  37 + }
  38 + }
  39 + &:first-of-type > a {
  40 + border-top: 0;
  41 + }
  42 + > a:hover,
  43 + > a:focus,
  44 + > a.active,
  45 + &.active > a,
  46 + &.open > a {
  47 + text-decoration: none;
  48 + color: #fff;
  49 + background-color: darken(#2c3e50, 7%);
  50 + }
  51 + }
  52 +}
... ...
src/app/layout/scss/skins/_whbl.scss 0 → 100644
... ... @@ -0,0 +1,310 @@
  1 +$whbl-nav-col-bg: #ffffff; //f3f5f6
  2 +$whbl-sidebar-linkColor: #484848;
  3 +$whbl-primary-color: #1E96D0; //blue
  4 +$whbl-font-color: #16191c;
  5 +
  6 +.skin-whbl {
  7 + #header-navbar {
  8 + background-color: $whbl-primary-color;
  9 + }
  10 + .navbar > .container .navbar-brand {
  11 + background-color: transparent;
  12 + width: 221px;
  13 + }
  14 + #nav-col,
  15 + #page-wrapper {
  16 + background-color: $whbl-nav-col-bg;
  17 + }
  18 + #sidebar-nav .nav > li > a {
  19 + color: $whbl-sidebar-linkColor;
  20 + /* border-bottom: 1px solid #e7ebee; */
  21 + }
  22 + #sidebar-nav .nav > .open > .submenu > li > .submenu,
  23 + #sidebar-nav .nav > .active > .submenu > li > .submenu,
  24 + #sidebar-nav .nav li .submenu > li.open a,
  25 + #nav-col-submenu .submenu > li > .submenu,
  26 + #nav-col-submenu li .submenu > li.open > a {
  27 + background-color: darken($whbl-nav-col-bg, 8%);
  28 + }
  29 + .nav-pills > li.active > a,
  30 + .nav-pills > li.active > a:hover,
  31 + .nav-pills > li.active > a:focus,
  32 + #sidebar-nav .nav-pills > li.active > a,
  33 + #sidebar-nav .nav-pills > li.active > a:hover,
  34 + #sidebar-nav .nav-pills > li.active > a:focus,
  35 + .nav-pills > li.open > a,
  36 + .nav-pills > li.open > a:hover,
  37 + .nav-pills > li.open > a:focus,
  38 + #sidebar-nav .nav-pills > li.open > a,
  39 + #sidebar-nav .nav-pills > li.open > a:hover,
  40 + #sidebar-nav .nav-pills > li.open > a:focus,
  41 + #sidebar-nav .nav-pills > li > a:focus {
  42 + background-color: darken($whbl-nav-col-bg, 4%);
  43 + border-color: $whbl-primary-color;
  44 + border-bottom-color: #e7ebee;
  45 + color: $whbl-sidebar-linkColor;
  46 + }
  47 + #sidebar-nav .nav-pills > li.active > a > i {
  48 + color: #2980b9;
  49 + }
  50 + #sidebar-nav .nav > li > a:hover {
  51 + background-color: darken($whbl-nav-col-bg, 4%);
  52 + border-color: $whbl-primary-color;
  53 + border-bottom-color: #e7ebee;
  54 + color: $whbl-sidebar-linkColor;
  55 + }
  56 +
  57 + #sidebar-nav .nav > li .submenu li > a:hover {
  58 + color: $whbl-font-color;
  59 + font-weight: bold;
  60 + }
  61 + #header-navbar .nav > li > a {
  62 + color: #fff;
  63 + }
  64 + #header-navbar .nav > li > a:hover,
  65 + #header-navbar .nav > li > a:focus,
  66 + #header-navbar .nav .open > a,
  67 + #header-navbar .nav .open > a:hover,
  68 + #header-navbar .nav .open > a:focus {
  69 + background-color: #2980b9;
  70 + }
  71 + #sidebar-nav .nav li .submenu,
  72 + #nav-col-submenu .submenu {
  73 + background-color: darken($whbl-nav-col-bg, 4%);
  74 + }
  75 + #sidebar-nav .nav li .submenu > li > a,
  76 + .nav-small #nav-col-submenu .submenu > li > a {
  77 + color: $whbl-font-color;
  78 + }
  79 + #sidebar-nav .nav > .open > .submenu > .open > a,
  80 + #sidebar-nav .nav > .active > .submenu > .open > a,
  81 + #sidebar-nav .nav > .active > .submenu > .active > a,
  82 + #nav-col-submenu .submenu > .open > a,
  83 + #nav-col-submenu .submenu > .active > a {
  84 + border-bottom-color: transparent;
  85 + box-shadow: 0 -1px 0 transparent inset;
  86 + }
  87 + #sidebar-nav .nav > .open > .submenu > .open > a {
  88 + border-bottom-color: #dcdfe6;
  89 + box-shadow: none;
  90 + }
  91 + #sidebar-nav .nav li.open > a.dropdown-toggle > .drop-icon,
  92 + #sidebar-nav .nav li.active > a.dropdown-toggle > .drop-icon {
  93 + color: $whbl-font-color;
  94 + }
  95 + #sidebar-nav .nav li .submenu > li > a:hover,
  96 + #sidebar-nav .nav li .submenu > li > a.active,
  97 + #sidebar-nav .nav li .submenu > li.active > a {
  98 + background-color: darken($whbl-nav-col-bg, 8%);
  99 + }
  100 + .navbar > .container .navbar-brand {
  101 + color: #fff;
  102 + }
  103 + .navbar-toggle {
  104 + color: #fff;
  105 + }
  106 + .graph-box {
  107 + background-color: $whbl-primary-color !important;
  108 + }
  109 + .content-wrapper {
  110 + background-color: #f9f9f9;
  111 + height: 100%;
  112 + margin-top: 0;
  113 + margin-bottom: 0;
  114 + position: relative;
  115 + min-height: 1200px;
  116 + padding: 15px 15px 35px 15px;
  117 + margin-left: 220px;
  118 + // border-left: 2px solid #e7ebee;
  119 + }
  120 + #user-left-box {
  121 +
  122 + .user-box {
  123 + color: $whbl-font-color;
  124 + > a {
  125 + color: $whbl-font-color;
  126 + }
  127 + > .name {
  128 + > a {
  129 + color: $whbl-font-color;
  130 +
  131 + &:hover, &:focus {
  132 + color: $whbl-font-color;
  133 + }
  134 + }
  135 + }
  136 + }
  137 + .user-box a:hover,
  138 + .user-box a:focus {
  139 + color: darken($whbl-font-color, 20%);
  140 + }
  141 + }
  142 + #sidebar-nav .nav > li.nav-header {
  143 + border-top-color: #e7ebee;
  144 + color: darken($whbl-nav-col-bg, 35%);
  145 + }
  146 + .nav-tabs {
  147 + background-color: #f9f9f9;
  148 + }
  149 + h1 {
  150 + color: $whbl-primary-color;
  151 + }
  152 + #header-navbar .nav > li > a:hover,
  153 + #header-navbar .nav > li > a:focus,
  154 + #header-navbar .nav .open > a,
  155 + #header-navbar .nav .open > a:hover,
  156 + #header-navbar .nav .open > a:focus,
  157 + .navbar-toggle:hover,
  158 + .navbar-toggle:focus,
  159 + .mobile-search.active > .btn {
  160 + background-color: #2980b9;
  161 + }
  162 + .main-box {
  163 + border: 1px solid $main-bg-color;
  164 + }
  165 + a,
  166 + .fc-state-default,
  167 + .jvectormap-zoomin,
  168 + .jvectormap-zoomout,
  169 + #user-profile .profile-details ul > li > span {
  170 + color: $whbl-primary-color;
  171 + }
  172 + a:hover,
  173 + a:focus,
  174 + .widget-users li > .details > .name > a:hover,
  175 + .widget-todo .actions > a:hover {
  176 + color: $whbl-primary-color;
  177 + }
  178 + .table a.table-link:hover {
  179 + color: #2980b9;
  180 + }
  181 + .pagination {
  182 + > li {
  183 + > a,
  184 + > span,
  185 + > a:hover,
  186 + > span:hover,
  187 + > a:focus,
  188 + > span:focus,
  189 + > a:active,
  190 + > span:active {
  191 + color: $whbl-primary-color;
  192 + }
  193 + }
  194 + > .active {
  195 + > a,
  196 + > span,
  197 + > a:hover,
  198 + > span:hover,
  199 + > a:focus,
  200 + > span:focus {
  201 + background-color: $whbl-primary-color;
  202 + border-color: $whbl-primary-color;
  203 + color: #fff;
  204 + }
  205 + }
  206 + }
  207 + .notifications-list {
  208 + .item-footer {
  209 + background-color: #272d33;
  210 +
  211 + a:hover {
  212 + background-color: #0f1114;
  213 + }
  214 + }
  215 + }
  216 + .btn-primary,
  217 + .btn-default,
  218 + .btn-info,
  219 + .btn-success,
  220 + .btn-warning,
  221 + .btn-danger,
  222 + .btn-primary:hover,
  223 + .btn-default:hover,
  224 + .btn-info:hover,
  225 + .btn-success:hover,
  226 + .btn-warning:hover,
  227 + .btn-danger:hover {
  228 + color: #fff;
  229 + }
  230 + .btn-primary {
  231 + background-color: $whbl-primary-color;
  232 + border-color: #2980b9;
  233 + }
  234 + .btn-primary:hover,
  235 + .btn-primary:focus,
  236 + .btn-primary:active,
  237 + .btn-primary.active,
  238 + .open .dropdown-toggle.btn-primary {
  239 + background-color: #2980b9;
  240 + border-color: #216897;
  241 + }
  242 + .btn-success {
  243 + background-color: $whbl-primary-color;
  244 + border-color: #2980b9;
  245 + }
  246 + .btn-success:hover,
  247 + .btn-success:focus,
  248 + .btn-success:active,
  249 + .btn-success.active,
  250 + .open .dropdown-toggle.btn-success {
  251 + background-color: #2980b9;
  252 + border-color: #1c5c87;
  253 + }
  254 + h1 {
  255 + color: $whbl-primary-color;
  256 + }
  257 + .widget-users li > .details > .time {
  258 + color: $whbl-primary-color;
  259 + }
  260 + blockquote,
  261 + blockquote.pull-right {
  262 + border-color: $whbl-primary-color;
  263 + }
  264 + a.list-group-item.active,
  265 + a.list-group-item.active:hover,
  266 + a.list-group-item.active:focus {
  267 + background-color: $whbl-primary-color;
  268 + border-color: $whbl-primary-color;
  269 + }
  270 + .nav .caret {
  271 + border-bottom-color: $whbl-primary-color;
  272 + border-top-color: $whbl-primary-color;
  273 + }
  274 + .panel-default > .panel-heading,
  275 + .notifications-list .item-footer {
  276 + background-color: $whbl-primary-color;
  277 + }
  278 + .notifications-list .item-footer a:hover {
  279 + background-color: #2980b9;
  280 + }
  281 + #invoice-companies .invoice-dates .invoice-number > span,
  282 + .notifications-list .item a .time {
  283 + color: $whbl-primary-color;
  284 + }
  285 + .table thead > tr > th > a:hover span {
  286 + color: $whbl-primary-color;
  287 + border-color: $whbl-primary-color;
  288 + }
  289 + .pace .pace-progress {
  290 + background-color: #fff;
  291 + }
  292 +}
  293 +.rtl.skin-whbl #content-wrapper {
  294 + border-left: 0;
  295 + border-right: 2px solid #e7ebee;
  296 +}
  297 +
  298 +@media (max-width: $break-sm-max) {
  299 + .skin-whbl {
  300 + #logo.navbar-brand > img.normal-logo.logo-white {
  301 + display: block;
  302 + }
  303 + #logo.navbar-brand > img.normal-logo.logo-black {
  304 + display: none;
  305 + }
  306 + .navbar > .container .navbar-brand {
  307 + background-color: $whbl-primary-color;
  308 + }
  309 + }
  310 +}
... ...
src/app/layout/services/body-state-classes.service.spec.ts
1 1 import * as helpers from '../../../spec/helpers';
2 2 import {BodyStateClassesService} from "./body-state-classes.service";
3 3 import {AuthService} from "./../../login/auth.service";
4   -import {AUTH_EVENTS} from "./../../login/auth-events";
  4 +import {AuthEvents} from "./../../login/auth-events";
  5 +
  6 +import {EventEmitter} from 'ng-forward';
  7 +
5 8  
6 9 describe("BodyStateClasses Service", () => {
  10 +
7 11 let currentStateName = "main";
8 12 let bodyStateClasseService: BodyStateClassesService;
9 13 let $rootScope: ng.IRootScopeService = <any>{},
... ... @@ -13,20 +17,23 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
13 17 name: currentStateName
14 18 }
15 19 },
16   - authService: AuthService,
  20 + authService: any = helpers.mocks.authService,
17 21 bodyEl: { className: string },
18   - bodyElJq: any;
19   -
  22 + bodyElJq: any,
  23 + contentWrapperEl: { className: string },
  24 + contentWrapperElJq: any;
20 25  
21 26 let getService = (): BodyStateClassesService => {
22 27 return new BodyStateClassesService($rootScope, $document, $state, authService);
23 28 };
24 29  
25 30 beforeEach(() => {
26   - authService = <any>{};
27 31 authService.isAuthenticated = jasmine.createSpy("isAuthenticated").and.returnValue(true);
28 32 bodyEl = { className: "" };
29 33 bodyElJq = [bodyEl];
  34 +
  35 + contentWrapperEl = { className: "" };
  36 + contentWrapperElJq = [contentWrapperEl];
30 37 });
31 38  
32 39 it("should add the class noosfero-user-logged to the body element if the user is authenticated", () => {
... ... @@ -58,8 +65,9 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
58 65  
59 66 it("should capture loginSuccess event and add noosfero-user-logged class to the body element", () => {
60 67 let userLoggedClassName = BodyStateClassesService.USER_LOGGED_CLASSNAME;
61   - $rootScope = <any>helpers.mocks.scopeWithEvents();
  68 +
62 69 bodyElJq.addClass = jasmine.createSpy("addClass");
  70 + bodyElJq.removeClass = jasmine.createSpy("removeClass");
63 71 authService.isAuthenticated = jasmine.createSpy("isAuthenticated").and.returnValue(false);
64 72 let service = getService();
65 73  
... ... @@ -68,11 +76,11 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
68 76 // triggers the service start
69 77 service.start();
70 78 // check if the the body element addClass was not called yet,
71   - // because the user is not authenticated
  79 + // because the user is not authenticated
72 80 expect(bodyElJq.addClass).not.toHaveBeenCalledWith(userLoggedClassName);
73 81  
74 82 // emit the event loginSuccess
75   - $rootScope.$emit(AUTH_EVENTS.loginSuccess);
  83 + authService.loginSuccess.next(null);
76 84  
77 85 // and check now if the addClass was called passing the userLogged class
78 86 // to the body Element
... ... @@ -83,7 +91,6 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
83 91 let userLoggedClassName = BodyStateClassesService.USER_LOGGED_CLASSNAME;
84 92  
85 93 authService.isAuthenticated = jasmine.createSpy("isAuthenticated").and.returnValue(true);
86   - $rootScope = <any>helpers.mocks.scopeWithEvents();
87 94  
88 95 bodyElJq.addClass = jasmine.createSpy("addClass");
89 96 bodyElJq.removeClass = jasmine.createSpy("removeClass");
... ... @@ -95,11 +102,11 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
95 102 service.start();
96 103  
97 104 // check if the the body element addClass was called
98   - // because the user is already authenticated
  105 + // because the user is already authenticated
99 106 expect(bodyElJq.addClass).toHaveBeenCalledWith(userLoggedClassName);
100 107  
101 108 // emit the event logoutSuccess
102   - $rootScope.$emit(AUTH_EVENTS.logoutSuccess);
  109 + authService.logoutSuccess.next(null);
103 110  
104 111 // and check now if the removeClass was called passing the userLogged class
105 112 // to the body Element
... ... @@ -126,9 +133,48 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
126 133 expect(bodyEl.className).toEqual(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + currentStateName);
127 134  
128 135 // emit the event $stateChangeSuccess
129   - $rootScope.$emit("$stateChangeSuccess", null, {name: "new-route"});
  136 + $rootScope.$emit("$stateChangeSuccess", null, { name: "new-route" });
130 137  
131 138 // and check now if the bodyEl has a class indicating the new state route
132 139 expect(bodyEl.className).toEqual(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + "new-route");
133 140 });
134   -});
135 141 \ No newline at end of file
  142 +
  143 + it("add a css class theme skin to body element", () => {
  144 + let service = getService();
  145 + let skinClass: string = 'skin-test';
  146 +
  147 + bodyElJq.addClass = jasmine.createSpy("addClass");
  148 + bodyElJq.removeClass = jasmine.createSpy("removeClass");
  149 + service["bodyElement"] = bodyElJq;
  150 +
  151 + service.start({
  152 + skin: skinClass
  153 + });
  154 +
  155 + expect(bodyElJq.addClass).toHaveBeenCalledWith(skinClass);
  156 + });
  157 +
  158 + it("add a css class to content wrapper element", () => {
  159 + let service = getService();
  160 +
  161 + contentWrapperElJq.addClass = jasmine.createSpy("addClass");
  162 + contentWrapperElJq.removeClass = jasmine.createSpy("removeClass");
  163 +
  164 + service["contentWrapperElement"] = contentWrapperElJq;
  165 + service.addContentClass(true);
  166 +
  167 + expect(contentWrapperElJq.addClass).toHaveBeenCalledWith(BodyStateClassesService.CONTENT_WRAPPER_FULL);
  168 + });
  169 +
  170 + it("remove a css class from content wrapper element", () => {
  171 + let service = getService();
  172 +
  173 + contentWrapperElJq.addClass = jasmine.createSpy("addClass");
  174 + contentWrapperElJq.removeClass = jasmine.createSpy("removeClass");
  175 +
  176 + service["contentWrapperElement"] = contentWrapperElJq;
  177 + service.addContentClass(false);
  178 +
  179 + expect(contentWrapperElJq.removeClass).toHaveBeenCalledWith(BodyStateClassesService.CONTENT_WRAPPER_FULL);
  180 + });
  181 +});
... ...
src/app/layout/services/body-state-classes.service.ts
1 1 import {Directive, Inject, Injectable} from "ng-forward";
2   -import {AUTH_EVENTS} from "./../../login/auth-events";
  2 +import {AuthEvents} from "./../../login/auth-events";
3 3 import {AuthService} from "./../../login/auth.service";
4 4 import {HtmlUtils} from "../html-utils";
  5 +import {INgForwardJQuery} from 'ng-forward/cjs/util/jqlite-extensions';
  6 +
  7 +export interface StartParams {
  8 + skin?: string;
  9 +}
5 10  
6 11 /**
7 12 * This is a service which adds classes to the body element
... ... @@ -9,20 +14,26 @@ import {HtmlUtils} from &quot;../html-utils&quot;;
9 14 * eg:
10 15 * User Logged:
11 16 * - noosfero-user-logged
12   - * Route States:
  17 + * Route States:
13 18 * - noosfero-route-main
14 19 * - noosfero-route-main.profile.info
  20 + *
  21 + * Show the all content in full mode:
  22 + * - full-content
15 23 */
16 24 @Injectable()
17 25 @Inject("$rootScope", "$document", "$state", AuthService)
18 26 export class BodyStateClassesService {
19 27  
20 28 private started: boolean = false;
  29 + private skin: string;
21 30  
22 31 public static get USER_LOGGED_CLASSNAME(): string { return "noosfero-user-logged"; }
23 32 public static get ROUTE_STATE_CLASSNAME_PREFIX(): string { return "noosfero-route-"; }
  33 + public static get CONTENT_WRAPPER_FULL(): string { return "full-content"; }
24 34  
25 35 private bodyElement: ng.IAugmentedJQuery = null;
  36 + private contentWrapperElement: INgForwardJQuery = null;
26 37  
27 38 constructor(
28 39 private $rootScope: ng.IRootScopeService,
... ... @@ -33,14 +44,37 @@ export class BodyStateClassesService {
33 44  
34 45 }
35 46  
36   - start() {
  47 + start(config?: StartParams) {
37 48 if (!this.started) {
38 49 this.setupUserLoggedClassToggle();
39 50 this.setupStateClassToggle();
  51 +
  52 + if (config) {
  53 + this.setThemeSkin(config.skin);
  54 + }
40 55 this.started = true;
41 56 }
42 57 }
43 58  
  59 + setThemeSkin(skin: string) {
  60 + this.getBodyElement().addClass(skin);
  61 + }
  62 +
  63 + addContentClass(addClass: boolean, className?: string): BodyStateClassesService {
  64 +
  65 + let fullContentClass: string = className || BodyStateClassesService.CONTENT_WRAPPER_FULL;
  66 + let contentWrapper = this.getContentWrapper();
  67 +
  68 + if (contentWrapper) {
  69 + if (addClass) {
  70 + contentWrapper.addClass(fullContentClass);
  71 + } else {
  72 + contentWrapper.removeClass(fullContentClass);
  73 + }
  74 + }
  75 + return this;
  76 + }
  77 +
44 78 private getStateChangeSuccessHandlerFunction(bodyElement: ng.IAugmentedJQuery): (event: ng.IAngularEvent, toState: ng.ui.IState) => void {
45 79 let self = this;
46 80 return (event: ng.IAngularEvent, toState: ng.ui.IState) => {
... ... @@ -65,7 +99,7 @@ export class BodyStateClassesService {
65 99  
66 100 /**
67 101 * Setup the initial state of the user-logged css class
68   - * and adds events handlers to switch this class when the login events happens
  102 + * and adds events handlers to switch this class when the login events happens
69 103 */
70 104 private setupUserLoggedClassToggle() {
71 105 let bodyElement = this.getBodyElement();
... ... @@ -76,18 +110,18 @@ export class BodyStateClassesService {
76 110 bodyElement.addClass(BodyStateClassesService.USER_LOGGED_CLASSNAME);
77 111 }
78 112  
79   - // listen to the AUTH_EVENTS.loginSuccess and AUTH_EVENTS.logoutSuccess
80   - // to switch the css class which indicates user logged in
81   - this.$rootScope.$on(AUTH_EVENTS.loginSuccess, () => {
  113 + // to switch the css class which indicates user logged in
  114 + this.authService.subscribe(AuthEvents[AuthEvents.loginSuccess], () => {
82 115 bodyElement.addClass(BodyStateClassesService.USER_LOGGED_CLASSNAME);
83 116 });
84   - this.$rootScope.$on(AUTH_EVENTS.logoutSuccess, () => {
  117 +
  118 + this.authService.subscribe(AuthEvents[AuthEvents.logoutSuccess], () => {
85 119 bodyElement.removeClass(BodyStateClassesService.USER_LOGGED_CLASSNAME);
86 120 });
87 121 }
88 122  
89 123 /**
90   - * Returns the user 'body' html Element
  124 + * Returns the user 'body' html Element
91 125 */
92 126 private getBodyElement(): ng.IAugmentedJQuery {
93 127 if (this.bodyElement === null) {
... ... @@ -95,4 +129,15 @@ export class BodyStateClassesService {
95 129 }
96 130 return this.bodyElement;
97 131 }
98   -}
99 132 \ No newline at end of file
  133 +
  134 + private getContentWrapper(selector?: string): INgForwardJQuery {
  135 +
  136 + if (this.contentWrapperElement === null) {
  137 +
  138 + let doc = <INgForwardJQuery>angular.element(this.$document);
  139 + this.contentWrapperElement = doc.query(selector || '.content-wrapper');
  140 + }
  141 +
  142 + return this.contentWrapperElement;
  143 + }
  144 +}
... ...
src/app/layout/sidebar/sidebar-section.component.ts 0 → 100644
... ... @@ -0,0 +1,126 @@
  1 +import {Component, Input} from "ng-forward";
  2 +
  3 +@Component({
  4 + selector: 'sidebar-section',
  5 + templateUrl: 'app/layout/sidebar/sidebar-section.html'
  6 +})
  7 +/**
  8 + * @ngdoc object
  9 + * @name sidebar.SidebarSectionComponent
  10 + * @description
  11 + * This is a widget to render sections to
  12 + * SidebarComponent.
  13 + *
  14 + * <b>Usage example:</b>
  15 + * @example
  16 + * <pre>
  17 + * let section: SidebarSectionComponent = new SidebarSectionComponent('MySection');
  18 + * section.addItem({
  19 + * title: 'Friends',
  20 + * count: 0,
  21 + * url: '#',
  22 + * className: 'active',
  23 + * icon: 'fa-users', //A font-awesome icon class
  24 + * subitems: [
  25 + * { title: 'Example' }
  26 + * ]
  27 + * });
  28 + * </pre>
  29 + */
  30 +export class SidebarSectionComponent {
  31 +
  32 + /**
  33 + * @ngdoc property
  34 + * @name name
  35 + * @propertyOf sidebar.SidebarComponent
  36 + * @description
  37 + * The name of the section
  38 + */
  39 + @Input()
  40 + public name: string;
  41 +
  42 + /**
  43 + * @ngdoc property
  44 + * @name items
  45 + * @propertyOf sidebar.SidebarComponent
  46 + * @description
  47 + * Array of items to render into this sidebar menu
  48 + */
  49 + @Input()
  50 + public items: any[] = [
  51 + {
  52 + title: 'Friends',
  53 + count: 0,
  54 + url: '#',
  55 + className: 'active',
  56 + icon: 'fa-users'
  57 + }
  58 + ];
  59 +
  60 + /**
  61 + * @ngdoc method
  62 + * @name constructor
  63 + * @methodOf sidebar.SidebarSectionComponent
  64 + * @param {string} name The name of the section (optional)
  65 + * @description
  66 + * The constructor for this component. The name of section
  67 + * can be assigned here, optionally
  68 + */
  69 + constructor(name?: string) {
  70 + this.name = name;
  71 + }
  72 +
  73 + /**
  74 + * @ngdoc method
  75 + * @name addItem
  76 + * @methodOf sidebar.SidebarSectionComponent
  77 + * @param {Object} item Literal object with properties to render a menu item
  78 + * @returns {SidebarSectionComponent} This own component type, using the "Fluent Interface" pattern
  79 + * @description
  80 + * Use this method to add new items for a section instance
  81 + *
  82 + * <b>Usage example:</b>
  83 + * @example
  84 + * <pre>
  85 + * section.addItem({
  86 + * title: 'Friends',
  87 + * count: 0,
  88 + * url: '#',
  89 + * className: 'active',
  90 + * icon: 'fa-users', //A font-awesome icon class
  91 + * subitems: [
  92 + * { title: 'Example' } //A subitem literal object
  93 + * ]
  94 + * });
  95 + * </pre>
  96 + */
  97 + addItem(item: any): SidebarSectionComponent {
  98 + this.items.push(item);
  99 + return this;
  100 + }
  101 +
  102 + /**
  103 + * @ngdoc method
  104 + * @name setName
  105 + * @methodOf sidebar.SidebarSectionComponent
  106 + * @param {string} name The name of the section
  107 + * @returns {SidebarSectionComponent} This own component type, using the "Fluent Interface" pattern
  108 + * @description
  109 + * Change the name of the section assigned on constructor
  110 + *
  111 + * <b>Usage example:</b>
  112 + * @example
  113 + * <pre>
  114 + * section.setName('MyAnotherSection')
  115 + * .addItem({
  116 + * //Item here
  117 + * ...
  118 + * });
  119 + * </pre>
  120 + */
  121 + setName(name: string): SidebarSectionComponent {
  122 + this.name = name;
  123 + return this;
  124 + }
  125 +
  126 +}
... ...
src/app/layout/sidebar/sidebar-section.html 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +<ul class="nav nav-pills nav-stacked">
  2 + <li class="nav-header nav-header-first hidden-sm hidden-xs">
  3 + {{ctrl.name}}
  4 + </li>
  5 +
  6 + <li ng-click="widgetExpanded = !widgetExpanded" ng-repeat="item in ctrl.items" class="{{item.className}}">
  7 + <a href="{{item.url}}" class="dropdown-toggle">
  8 + <i class="fa {{item.icon}}"></i>
  9 + <span>{{item.title}}</span>
  10 + <span class="label label-primary label-circle pull-right" ng-class="{'submenu-count': item.subitems}" ng-show="item.count != undefined">{{item.count}}</span>
  11 + <i class="fa fa-angle-right drop-icon" ng-show="item.subitems"></i>
  12 + </a>
  13 + <ul class="submenu" ng-show="widgetExpanded && item.subitems">
  14 + <li ng-repeat="subitem in item.subitems">
  15 + <a href="{{subitem.url}}">
  16 + {{subitem.title}}
  17 + </a>
  18 + </li>
  19 + </ul>
  20 + </li>
  21 +</ul>
... ...
src/app/layout/sidebar/sidebar.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,86 @@
  1 +import {provide} from 'ng-forward';
  2 +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
  3 +import {providers} from 'ng-forward/cjs/testing/providers';
  4 +import {SidebarComponent} from './sidebar.component';
  5 +import {SidebarSectionComponent} from './sidebar-section.component';
  6 +import * as helpers from '../../../spec/helpers';
  7 +
  8 +const htmlTemplate: string = '<sidebar [visible]="false"></sidebar>';
  9 +
  10 +describe('Sidebar Component', () => {
  11 +
  12 + let helper: ComponentTestHelper<SidebarComponent>;
  13 + let notifyService: any = {
  14 + event: Function,
  15 + subscribe: (fn: (visible: boolean) => void) => {
  16 + notifyService.event = fn;
  17 + },
  18 + next: (value: any) => {
  19 + notifyService.event(value);
  20 + },
  21 + setVisibility: (visible: boolean) => {
  22 + notifyService.event(visible);
  23 + }
  24 + };
  25 +
  26 + let sessionService: any = {
  27 + currentUser: (): any => {
  28 + return {
  29 + person: { name: 'test' }
  30 + };
  31 + }
  32 + };
  33 +
  34 +
  35 + beforeEach(angular.mock.module("templates"));
  36 +
  37 + beforeEach((done: Function) => {
  38 + providers((provide: any) => {
  39 + return <any>[
  40 + provide('SidebarNotificationService', {
  41 + useValue: notifyService
  42 + }),
  43 + provide('SessionService', {
  44 + useValue: sessionService
  45 + }),
  46 + provide('SidebarSectionComponent', {
  47 + useValue: SidebarSectionComponent
  48 + })
  49 + ];
  50 + });
  51 +
  52 + let cls = createClass({
  53 + template: htmlTemplate,
  54 + directives: [SidebarComponent],
  55 + properties: {
  56 + visible: false
  57 + }
  58 + });
  59 +
  60 + helper = new ComponentTestHelper<SidebarComponent>(cls, done);
  61 + });
  62 +
  63 + it('render the sidebar html content', () => {
  64 + expect(helper.all('div#nav-col').length).toEqual(1);
  65 + });
  66 +
  67 + it('show sidebar only if a service emit a visibility event', () => {
  68 +
  69 + notifyService.setVisibility(true);
  70 + expect(helper.component.isVisible()).toBeTruthy();
  71 + });
  72 +
  73 + it('show user name into sidebar', () => {
  74 +
  75 + notifyService.setVisibility(true);
  76 + expect(helper.component.user.name).toEqual(sessionService.currentUser().person.name);
  77 + expect(helper.debugElement.query('div.user-box .name a').text()).toMatch(sessionService.currentUser().person.name);
  78 + });
  79 +
  80 + it('show sidebar section with a menu itens', () => {
  81 +
  82 + notifyService.setVisibility(true);
  83 + expect(helper.debugElement.query('li.active a span').text()).toMatch('Friends');
  84 + });
  85 +
  86 +});
... ...
src/app/layout/sidebar/sidebar.component.ts 0 → 100644
... ... @@ -0,0 +1,104 @@
  1 +import {Component, Inject, Input} from "ng-forward";
  2 +import {SidebarNotificationService} from "./sidebar.notification.service";
  3 +import {SessionService} from '../../login/session.service';
  4 +import {SidebarSectionComponent} from './sidebar-section.component';
  5 +
  6 +@Component({
  7 + selector: 'sidebar',
  8 + templateUrl: 'app/layout/sidebar/sidebar.html',
  9 + directives: [SidebarSectionComponent]
  10 +})
  11 +@Inject(SidebarNotificationService, SessionService)
  12 +/**
  13 + * @ngdoc object
  14 + * @name sidebar.SidebarComponent
  15 + * @requires [SidebarNotificationService, SessionService]
  16 + * @description
  17 + * This is a widget to a sidebar with visible control.
  18 + * Needs a SidebarSectionComponent to show sections/items/subitems
  19 + * menu
  20 + *
  21 + * <b>Usage example:</b>
  22 + * @example
  23 + * <pre>
  24 + * let sidebar: SidebarComponent = new SidebarComponent(SidebarNotificationService, SessionService);
  25 + * sidebar.visible = true;
  26 + * </pre>
  27 + */
  28 +export class SidebarComponent {
  29 +
  30 + /**
  31 + * @ngdoc property
  32 + * @name visible
  33 + * @propertyOf sidebar.SidebarComponent
  34 + * @description
  35 + * Controls if this component is show/hide
  36 + */
  37 + @Input()
  38 + private visible: boolean = false;
  39 +
  40 + /**
  41 + * @ngdoc property
  42 + * @name showStatus
  43 + * @propertyOf sidebar.SidebarComponent
  44 + * @description
  45 + * Controls the show/hide state of the circle user status
  46 + */
  47 + @Input('showstatus')
  48 + public showStatus: boolean = false;
  49 +
  50 + /**
  51 + * @ngdoc property
  52 + * @name user
  53 + * @propertyOf sidebar.SidebarComponent
  54 + * @description
  55 + * The user data to show into sidebar
  56 + */
  57 + @Input()
  58 + public user: { name: string } = {
  59 + name: ''
  60 + };
  61 +
  62 + /**
  63 + * @ngdoc method
  64 + * @name constructor
  65 + * @methodOf sidebar.SidebarComponent
  66 + * @param {SidebarNotificationService} notificationService The service that emmits events to show/hide this component
  67 + * @param {SessionService} session The service that loads the user data when user is logged
  68 + * @description
  69 + * The constructor for this component. Loads the dependencies services
  70 + */
  71 + constructor(private notificationService: SidebarNotificationService, private session: SessionService) { }
  72 +
  73 + /**
  74 + * @ngdoc method
  75 + * @name ngOnInit
  76 + * @methodOf sidebar.SidebarComponent
  77 + * @description
  78 + * Check the initial visibility when this component is loaded
  79 + */
  80 + ngOnInit() {
  81 +
  82 + let userData: any = this.session.currentUser();
  83 + if (userData) {
  84 + this.user = userData.person;
  85 + }
  86 +
  87 + this.notificationService.setVisibility(this.visible);
  88 + this.notificationService.subscribe((visible: boolean) => {
  89 + this.visible = visible;
  90 + });
  91 + }
  92 +
  93 + /**
  94 + * @ngdoc method
  95 + * @name isVisible
  96 + * @methodOf sidebar.SidebarComponent
  97 + * @returns {boolean} True, whether this component is visible, otherwise returns false
  98 + * @description
  99 + * Verify whether sidebar is visible or not
  100 + */
  101 + isVisible(): boolean {
  102 + return <boolean>this.visible;
  103 + }
  104 +}
... ...
src/app/layout/sidebar/sidebar.html 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +<div id="nav-col" ng-show="ctrl.isVisible()" ng-class="{'sidebar-hide':!ctrl.isVisible()}">
  2 + <section id="col-left" class="col-left-nano">
  3 + <div id="col-left-inner" class="col-left-nano-content">
  4 + <div id="user-left-box" class="clearfix hidden-sm hidden-xs dropdown profile2-dropdown">
  5 + <noosfero-profile-image [profile]="ctrl.user"></noosfero-profile-image>
  6 + <div class="user-box">
  7 + <span class="name">
  8 + <a href="#" class="dropdown-toggle" data-toggle="dropdown">
  9 + {{ctrl.user.name}}
  10 + </a>
  11 + </span>
  12 + <span class="status" ng-show="ctrl.showStatus">
  13 + <i class="fa fa-circle"></i> {{ctrl.user.status}}
  14 + </span>
  15 + </div>
  16 + </div>
  17 +
  18 + <div class="collapse navbar-collapse navbar-ex1-collapse" id="sidebar-nav">
  19 + <sidebar-section [name]='Navigation'></sidebar-section>
  20 + </div>
  21 + </div>
  22 + </section>
  23 + </div>
... ...
src/app/layout/sidebar/sidebar.notification.service.ts 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +import {Injectable, EventEmitter} from "ng-forward";
  2 +
  3 +
  4 +@Injectable()
  5 +export class SidebarNotificationService {
  6 + private alternateVisibilityEvent: EventEmitter<boolean> = new EventEmitter<boolean>();
  7 + public sidebarVisible: boolean = false;
  8 +
  9 + getCurrentVisibility() {
  10 + return this.sidebarVisible;
  11 + }
  12 +
  13 + alternateVisibility() {
  14 + this.sidebarVisible = !this.sidebarVisible;
  15 + this.alternateVisibilityEvent.next(this.sidebarVisible);
  16 + }
  17 +
  18 + setVisibility(visibility: boolean) {
  19 + this.sidebarVisible = visibility;
  20 + this.alternateVisibilityEvent.next(this.sidebarVisible);
  21 + }
  22 +
  23 + subscribe(fn: (visible: boolean) => void) {
  24 + this.alternateVisibilityEvent.subscribe(fn);
  25 + }
  26 +
  27 +}
... ...
src/app/login/auth-events.ts
1   -export interface IAuthEvents {
2   - loginSuccess: string;
3   - loginFailed: string;
4   - logoutSuccess: string;
  1 +export enum AuthEvents {
  2 + loginSuccess, loginFailed, logoutSuccess
5 3 }
6   -
7   -export const AUTH_EVENTS: IAuthEvents = {
8   - loginSuccess: "auth-login-success",
9   - loginFailed: "auth-login-failed",
10   - logoutSuccess: "auth-logout-success"
11   -};
12 4 \ No newline at end of file
... ...
src/app/login/auth.service.spec.ts
1   -import {AuthService, AUTH_EVENTS} from "./";
  1 +import {AuthService, AuthEvents} from "./";
  2 +import {SessionService} from './session.service';
  3 +
  4 +import {Injectable, Provider, provide, EventEmitter} from "ng-forward";
  5 +import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder';
  6 +
  7 +import {getAngularServiceFactory, AngularServiceFactory} from "../../spec/helpers";
2 8  
3 9 describe("Services", () => {
4 10  
... ... @@ -15,23 +21,26 @@ describe(&quot;Services&quot;, () =&gt; {
15 21 $translateProvider.translations('en', {});
16 22 }));
17 23  
18   - beforeEach(inject((_$httpBackend_: ng.IHttpBackendService, _$rootScope_: ng.IRootScopeService, _AuthService_: AuthService) => {
19   - $httpBackend = _$httpBackend_;
20   - authService = _AuthService_;
21   - $rootScope = _$rootScope_;
  24 + beforeEach(() => {
22 25  
23 26 user = <noosfero.User>{
24 27 id: 1,
25 28 login: "user"
26 29 };
27   - }));
28   -
  30 + });
29 31  
30 32 describe("Succesffull login", () => {
31 33  
  34 + let factory: AngularServiceFactory;
  35 + let authService: AuthService;
  36 + let $log: ng.ILogService;
  37 + let $http: ng.IHttpBackendService;
  38 +
32 39 beforeEach(() => {
33 40 credentials = { username: "user", password: "password" };
34   -
  41 + factory = getAngularServiceFactory();
  42 + authService = factory.getAngularService("AuthService");
  43 + $httpBackend = factory.getHttpBackendService();
35 44 $httpBackend.expectPOST("/api/v1/login", "login=user&password=password").respond(200, { user: user });
36 45 });
37 46  
... ... @@ -44,20 +53,15 @@ describe(&quot;Services&quot;, () =&gt; {
44 53 expect($httpBackend.verifyNoOutstandingRequest());
45 54 });
46 55  
47   -
48   - it("should emit event loggin successful with user logged data", () => {
49   -
50   - authService.login(credentials);
51   -
52   - let eventEmmited: boolean = false;
53   - $rootScope.$on(AUTH_EVENTS.loginSuccess, (event: ng.IAngularEvent, userThroughEvent: noosfero.User) => {
54   - eventEmmited = true;
  56 + it("should emit event loggin successful with user logged data", (done: Function) => {
  57 + let successEvent: any = AuthEvents[AuthEvents.loginSuccess];
  58 + (<any>authService)[successEvent].subscribe((userThroughEvent: noosfero.User): any => {
55 59 expect(userThroughEvent).toEqual(user);
  60 + done();
56 61 });
57   -
  62 + authService.login(credentials);
58 63 $httpBackend.flush();
59 64  
60   - expect(eventEmmited).toBeTruthy(AUTH_EVENTS.loginSuccess + " was not emmited!");
61 65 });
62 66  
63 67 it("should return the current logged in user", () => {
... ... @@ -74,6 +78,5 @@ describe(&quot;Services&quot;, () =&gt; {
74 78 });
75 79 });
76 80  
77   -
78 81 });
79 82 });
... ...
src/app/login/auth.service.ts
1   -import {Injectable, Inject} from "ng-forward";
  1 +import {Injectable, Inject, EventEmitter} from "ng-forward";
2 2  
3 3 import {NoosferoRootScope, UserResponse} from "./../shared/models/interfaces";
4 4 import {SessionService} from "./session.service";
5 5  
6   -import {AUTH_EVENTS, IAuthEvents} from "./auth-events";
  6 +import {AuthEvents} from "./auth-events";
7 7  
8 8 @Injectable()
9   -@Inject("$q", "$http", "$rootScope", "SessionService", "$log", "AUTH_EVENTS")
  9 +@Inject("$http", SessionService, "$log")
10 10 export class AuthService {
11 11  
12   - constructor(private $q: ng.IQService,
13   - private $http: ng.IHttpService,
14   - private $rootScope: NoosferoRootScope,
15   - private sessionService: SessionService,
16   - private $log: ng.ILogService,
17   - private auth_events: IAuthEvents) {
  12 + public loginSuccess: EventEmitter<noosfero.User> = new EventEmitter<noosfero.User>();
  13 + public loginFailed: EventEmitter<ng.IHttpPromiseCallbackArg<any>> = new EventEmitter<ng.IHttpPromiseCallbackArg<any>>();
  14 + public logoutSuccess: EventEmitter<noosfero.User> = new EventEmitter<noosfero.User>();
18 15  
  16 + constructor(private $http: ng.IHttpService,
  17 + private sessionService: SessionService,
  18 + private $log: ng.ILogService) {
19 19 }
20 20  
21 21 loginFromCookie() {
... ... @@ -27,8 +27,8 @@ export class AuthService {
27 27 private loginSuccessCallback(response: ng.IHttpPromiseCallbackArg<UserResponse>) {
28 28 this.$log.debug('AuthService.login [SUCCESS] response', response);
29 29 let currentUser: noosfero.User = this.sessionService.create(response.data);
30   - this.$rootScope.currentUser = currentUser;
31   - this.$rootScope.$broadcast(this.auth_events.loginSuccess, currentUser);
  30 + this.loginSuccess.next(currentUser);
  31 +
32 32 return currentUser;
33 33 }
34 34  
... ... @@ -40,15 +40,15 @@ export class AuthService {
40 40  
41 41 private loginFailedCallback(response: ng.IHttpPromiseCallbackArg<any>): any {
42 42 this.$log.debug('AuthService.login [FAIL] response', response);
43   - this.$rootScope.$broadcast(this.auth_events.loginFailed);
44   - // return $q.reject(response);
  43 + this.loginFailed.next(response);
45 44 return null;
46 45 }
47 46  
48 47 public logout() {
  48 + let user: noosfero.User = this.sessionService.currentUser();
49 49 this.sessionService.destroy();
50   - this.$rootScope.currentUser = undefined;
51   - this.$rootScope.$broadcast(this.auth_events.logoutSuccess);
  50 +
  51 + this.logoutSuccess.next(user);
52 52 this.$http.jsonp('/account/logout'); // FIXME logout from noosfero to sync login state
53 53 }
54 54  
... ... @@ -66,4 +66,14 @@ export class AuthService {
66 66 }
67 67 return (this.isAuthenticated() && authorizedRoles.indexOf(this.sessionService.currentUser().userRole) !== -1);
68 68 }
69   -}
70 69 \ No newline at end of file
  70 +
  71 + subscribe(eventName: string, fn: Function) {
  72 +
  73 + let event: EventEmitter<any> = <EventEmitter<any>>(<any>this)[eventName];
  74 + if (event) {
  75 + event.subscribe(fn);
  76 + } else {
  77 + throw new Error(`The event: ${eventName} not exists`);
  78 + }
  79 + }
  80 +}
... ...
src/app/main/main.component.ts
... ... @@ -28,6 +28,8 @@ import {BodyStateClassesService} from &quot;./../layout/services/body-state-classes.s
28 28  
29 29 import {Navbar} from "../layout/navbar/navbar";
30 30  
  31 +import {SidebarComponent} from "../layout/sidebar/sidebar.component";
  32 +
31 33 import {MainBlockComponent} from "../layout/blocks/main-block/main-block.component";
32 34 import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component";
33 35  
... ... @@ -49,8 +51,13 @@ import {HtmlEditorComponent} from &quot;../shared/components/html-editor/html-editor.
49 51 })
50 52 @Inject(BodyStateClassesService)
51 53 export class MainContentComponent {
  54 +
  55 + public themeSkin: string = 'skin-whbl';
  56 +
52 57 constructor(private bodyStateClassesService: BodyStateClassesService) {
53   - bodyStateClassesService.start();
  58 + bodyStateClassesService.start({
  59 + skin: this.themeSkin
  60 + });
54 61 }
55 62 }
56 63  
... ... @@ -84,7 +91,7 @@ export class EnvironmentContent {
84 91 ArticleBlogComponent, ArticleViewComponent, BoxesComponent, BlockComponent,
85 92 EnvironmentComponent, PeopleBlockComponent,
86 93 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent,
87   - MainBlockComponent, RecentDocumentsBlockComponent, Navbar, ProfileImageBlockComponent,
  94 + MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
88 95 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent
89 96 ],
90 97 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService]
... ...
src/app/main/main.html
1 1 <acme-navbar></acme-navbar>
2   -<div ui-view="content"></div>
  2 +<!-- Sidebar Left -->
  3 +<sidebar [visible]="false"></sidebar>
  4 +<div class="content-wrapper" ui-view="content"></div>
... ...
src/app/main/main.scss 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +.content-wrapper {
  2 + margin-top: 32px;
  3 +}
  4 +
  5 +.full-content {
  6 + margin-left: 0px !important;
  7 +}
... ...
src/app/profile/image/image.html
1   -<span title="{{ctrl.profile.name}}">
  1 +<span class="profile-image-wrap" title="{{ctrl.profile.name}}">
2 2 <img ng-if="ctrl.profile.image" ng-src="{{ctrl.profile.image.url}}" class="img-responsive profile-image">
3 3 <i ng-if="!ctrl.profile.image" class="fa {{ctrl.defaultIcon}} fa-5x profile-image"></i>
4 4 </span>
... ...
src/spec/component-test-helper.ts
... ... @@ -6,12 +6,12 @@ import { ComponentFixture } from &#39;ng-forward/cjs/testing/test-component-builder&#39;
6 6 /**
7 7 * @ngdoc object
8 8 * @name spec.ComponentTestHelper
9   - * @description
10   - *
  9 + * @description
  10 + *
11 11 * Helper class for creating tests. It encapsulates the TestComponentBuilder initialization,
12 12 * allowing the test to be DRY. To use, one must declare a beforeEach function in the
13 13 * test, and inside construct this object like:
14   - *
  14 + *
15 15 * <pre>
16 16 * let helper = let helper : ComponentTestHelper;
17 17 * beforeEach( (done) => {
... ... @@ -35,7 +35,7 @@ export class ComponentTestHelper&lt;T extends any&gt; {
35 35 * @propertyOf spec.ComponentTestHelper
36 36 * @description
37 37 * The NgForward TestComponentBuilder
38   - */
  38 + */
39 39 tcb: TestComponentBuilder;
40 40 /**
41 41 * @ngdoc property
... ... @@ -52,7 +52,7 @@ export class ComponentTestHelper&lt;T extends any&gt; {
52 52 * @description
53 53 * The debugElement representing a JQuery element attached to the component
54 54 * on mock page.
55   - */
  55 + */
56 56 debugElement: INgForwardJQuery;
57 57  
58 58 /**
... ... @@ -145,3 +145,4 @@ export function createClass({
145 145 }
146 146 return Test;
147 147 }
  148 +
... ...
src/spec/helpers.ts
... ... @@ -73,6 +73,7 @@ class AngularServiceHookComponent {
73 73 }
74 74 }
75 75  
  76 +
76 77 /**
77 78 * This helper class allows get angular services to be used in integration tests
78 79 * i.e: '$http', '$q', '$location', etc...
... ...
src/spec/mocks.ts
... ... @@ -30,7 +30,7 @@ class ScopeWithEvents {
30 30 }
31 31 }
32 32 }
33   -export var mocks = {
  33 +export var mocks: any = {
34 34 scopeWithEvents: (): ScopeWithEvents => new ScopeWithEvents(),
35 35 modalInstance: {
36 36 close: () => { }
... ... @@ -41,7 +41,38 @@ export var mocks = {
41 41 }
42 42 },
43 43 authService: {
44   - logout: () => { }
  44 + loginSuccess: {
  45 + event: Function,
  46 + subscribe: (fn: Function) => {
  47 + mocks.authService['loginSuccess'].event = fn;
  48 + },
  49 + next: (param: any) => {
  50 + mocks.authService['loginSuccess'].event(param);
  51 + }
  52 + },
  53 + loginFailed: {
  54 + event: Function,
  55 + subscribe: (fn: Function) => {
  56 + mocks.authService['loginFailed'].event = fn;
  57 + },
  58 + next: (param: any) => {
  59 + mocks.authService['loginFailed'].event(param);
  60 + }
  61 + },
  62 + logoutSuccess: {
  63 + event: Function,
  64 + subscribe: (fn: Function) => {
  65 + mocks.authService['logoutSuccess'].event = fn;
  66 + },
  67 + next: (param: any) => {
  68 + mocks.authService['logoutSuccess'].event(param);
  69 + }
  70 + },
  71 + logout: () => { },
  72 + subscribe: (eventName: string, fn: Function) => {
  73 + mocks.authService[eventName].subscribe(fn);
  74 + },
  75 + isAuthenticated: () => { }
45 76 },
46 77 articleService: {
47 78 getByProfile: (profileId: number, params?: any) => {
... ...
  • 5bf9bf341e9d00ebd854cdaf1a4299b2?s=40&d=identicon
    Leandro Santos @leandronunes

    Milestone changed to 2016.05

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    mentioned in issue #5

    Choose File ...   File name...
    Cancel
  • 2c5c4299d62769e3da7d432cd2823dd6?s=40&d=identicon
    Carlos Purificação @carloseugenio

    Reassigned to @carloseugenio

    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Added 1 new commit:

    • 3ba3aa13 - Added @ngdoc documentation and refactor the SidebarSection component
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Added 1 new commit:

    • ebe67808 - Added @ngdoc documentation and refactor the SidebarSection component
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Added 25 new commits:

    • fc86b9f5 - Refactory into ComponentTestHelper and helpers.ts with improvements
    • 0e6cfa00 - Merge branch 'component-test-improvements' into 'master'
    • ed34cf65 - Support ckeditor in article edition
    • 28d86107 - Support storage of current object in generic restangular service
    • 2259690d - Create article and associate it to parent
    • a5984494 - Move basic editor to its own folder
    • ecac812c - Add cancel button in basic editor
    • ed2107bd - Translate basic editor component
    • 33ae65dd - Adapt basic editor to edit an existing article
    • 8f0e4db1 - Create component to encapsulate wysiwyg editor
    • 6b15acb7 - Add article edit button
    • ba389ecc - Edit visibility status of an article
    • 00eaab03 - Display basic error message when fail to save an article
    • 5f9ef2ae - Refactor tests of content viewer actions to use ComponentTestHelper
    • 1894afc0 - Merge branch 'create-article' into 'master'
    • 8616dd98 - Add missing type arguments from members and people blocks
    • dcc1120f - Fix styles watch from gulp
    • 8362948a - adding participa consulta theme
    • e59add5d - Created sidebar component with sass and a service to show/hide using Angular 2 EventEmitter sintax
    • 4c39bd75 - Added hamburguer sidebar toggle button to show only after login
    • 41cad538 - Refactory into unit tests to use EventEmitter mocks
    • f711ee9c - Refactory into ComponentTestHelper and sidebar unit tests enhancements
    • dd6a4819 - Added feature to sidebar pushing right content on show/hide
    • af427f22 - Created SidebarSectionsComponent with a dinamic menu/submenu
    • 7e4cecee - Added @ngdoc documentation and refactor the SidebarSection component
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper
    Choose File ...   File name...
    Cancel
  • Me
    Michel Felipe @mfdeveloper

    Assignee removed

    Choose File ...   File name...
    Cancel