Compare View

switch
from
...
to
 
Commits (43)
Showing 81 changed files   Show diff stats
@@ -38,8 +38,9 @@ @@ -38,8 +38,9 @@
38 "angular-bind-html-compile": "^1.2.1", 38 "angular-bind-html-compile": "^1.2.1",
39 "angular-click-outside": "^2.7.1", 39 "angular-click-outside": "^2.7.1",
40 "ng-ckeditor": "^0.2.1", 40 "ng-ckeditor": "^0.2.1",
41 - "angular-bootstrap-toggle-switch": "^0.5.6",  
42 - "angular-tag-cloud": "^0.3.0" 41 + "angular-tag-cloud": "^0.3.0",
  42 + "angular-ui-switch": "^0.1.1",
  43 + "angular-password": "^1.0.1"
43 }, 44 },
44 "devDependencies": { 45 "devDependencies": {
45 "angular-mocks": "~1.5.0" 46 "angular-mocks": "~1.5.0"
src/app/account/index.ts 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +export * from "./register.component";
src/app/account/register-component.html 0 → 100644
@@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
  1 +<div class="register-page">
  2 + <div class="welcome-message">
  3 + <h1>{{"account.register.welcomeMessageTitle" | translate }}</h1>
  4 + <div class="environment-signup-intro" ng-bind-html="ctrl.environment.signup_intro"></div>
  5 + </div>
  6 + <form name="signupForm">
  7 + <div class="row">
  8 + <div class="col-md-12 register-field">
  9 + <div class="input-group">
  10 + <span class="input-group-addon"><i class="fa fa-male"></i><i class="fa fa-female"></i></span>
  11 + <input type="text" class="form-control" id="name" name="fullName" placeholder="{{'account.register.fullNameLabel' | translate }}" ng-model="ctrl.account.name">
  12 + </div>
  13 + </div>
  14 +
  15 + <div class="form-group col-md-12 register-field" ng-class="ctrl.isInvalid(signupForm.username)">
  16 + <div class="input-group">
  17 + <span class="input-group-addon" style="font-weight: bold;"><i class="fa fa-globe"></i>&nbsp;{{ ctrl.environment.host }}</span>
  18 + <input type="text" id="username" name="username" class="form-control" placeholder="{{'account.register.usernameLabel' | translate }}" ng-model="ctrl.account.login" required>
  19 + </div>
  20 + <div class="help-block" ng-show="signupForm.username.$touched" ng-messages="signupForm.username.$error">
  21 + <ul class="list-unstyled">
  22 + <li ng-messages-include="languages/messages.html"></li>
  23 + </ul>
  24 + </div>
  25 + </div>
  26 +
  27 + <div class="form-group col-md-12 register-field" ng-class="ctrl.isInvalid(signupForm.email)">
  28 + <div class="input-group">
  29 + <span class="input-group-addon"><i class="fa fa-envelope"></i></span>
  30 + <input type="email" class="form-control" id="email" name="email" placeholder="{{'account.register.emailLabel' | translate }}" ng-model="ctrl.account.email" required>
  31 + </div>
  32 + <div class="help-block" ng-show="signupForm.email.$touched" ng-messages="signupForm.email.$error">
  33 + <ul class="list-unstyled">
  34 + <li ng-messages-include="languages/messages.html"></li>
  35 + </ul>
  36 + </div>
  37 + </div>
  38 +
  39 + <div class="form-group col-md-6 register-field" ng-class="ctrl.isInvalid(signupForm.password)">
  40 + <div class="input-group">
  41 + <span class="input-group-addon"><i class="fa fa-lock"></i></span>
  42 + <input type="password" class="form-control" id="password" name="password" placeholder="{{'account.register.passwordLabel' | translate }}" ng-model="ctrl.account.password" required>
  43 + </div>
  44 + <div class="help-block" ng-show="signupForm.password.$touched" ng-messages="signupForm.password.$error">
  45 + <ul class="list-unstyled">
  46 + <li ng-messages-include="languages/messages.html"></li>
  47 + </ul>
  48 + </div>
  49 + </div>
  50 +
  51 + <div class="form-group col-md-6 register-field" ng-class="ctrl.isInvalid(signupForm.passwordConfirm)">
  52 + <div class="input-group">
  53 + <span class="input-group-addon"><i class="fa fa-unlock-alt"></i></span>
  54 + <input type="password" class="form-control" id="passwordConfirm" name="passwordConfirm" match-password="password" placeholder="{{'account.register.passwordConfirmationLabel' | translate}}" ng-model="ctrl.account.passwordConfirmation" required>
  55 + </div>
  56 + <div class="help-block" ng-show="signupForm.passwordConfirm.$touched" ng-messages="signupForm.passwordConfirm.$error">
  57 + <ul class="list-unstyled">
  58 + <li ng-messages-include="languages/messages.html"></li>
  59 + <li ng-message="passwordMatch" translate="messages.invalid.passwordMatch"></li>
  60 + </ul>
  61 + </div>
  62 + </div>
  63 +
  64 + <div class="col-md-12">
  65 + <p class="terms-info">{{"account.register.accountCreatingMessage" | translate}} <a (click)="ctrl.openTerms()" href="#">{{"account.register.termsOfUseMessage" | translate}}</a>.</p>
  66 + </div>
  67 +
  68 + <div class="col-md-12">
  69 + <button type="submit" class="btn btn-default" ng-disabled="signupForm.$invalid" ng-click="ctrl.signup()">{{"account.register.signupMessage" | translate}}</button>
  70 + </div>
  71 +
  72 + </div>
  73 + </form>
  74 +
  75 + <p class="already-registered-message">{{"account.register.haveAccountMessage" | translate}}</p>
  76 +
  77 +</div>
src/app/account/register-terms.html 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +<div class="modal-header">
  2 + <h3 class="modal-title">Register terms</h3>
  3 +</div>
  4 +<div class="modal-body modal-body-overflow" ng-bind-html="ctrl.environment.terms_of_use"></div>
  5 +<div class="modal-footer">
  6 + <button class="btn btn-primary" type="button" (click)="vm.closeTerms()">OK</button>
  7 +</div>
src/app/account/register.component.spec.ts 0 → 100644
@@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
  1 +import { ComponentTestHelper, createClass } from "../../spec/component-test-helper";
  2 +import * as helpers from "../../spec/helpers";
  3 +import { RegisterComponent } from "./register.component";
  4 +// import {RegisterService} from "../../lib/ng-noosfero-api/http/register.service"
  5 +
  6 +
  7 +describe("Register Component", () => {
  8 + const htmlTemplate: string = '<noosfero-register></noosfero-register>';
  9 +
  10 + let helper: ComponentTestHelper<RegisterComponent>;
  11 + let registerService = helpers.mocks.registerService;
  12 + let stateService = jasmine.createSpyObj("$state", ["transitionTo"]);
  13 + let notificationService = helpers.mocks.notificationService;
  14 + notificationService.success = jasmine.createSpy('success');
  15 + notificationService.error = jasmine.createSpy('error');
  16 +
  17 +
  18 + let account: any = {
  19 + id: 1,
  20 + login: 'test',
  21 + email: 'test@email.com',
  22 + password: 'xxx',
  23 + passwordConfirmation: 'xxx'
  24 + };
  25 +
  26 + beforeEach(() => {
  27 + angular.mock.module('templates');
  28 + angular.mock.module('ngSanitize');
  29 + angular.mock.module('ngMessages');
  30 + angular.mock.module('ngPassword');
  31 + });
  32 +
  33 + beforeEach((done) => {
  34 + let cls = createClass({
  35 + template: htmlTemplate,
  36 + directives: [RegisterComponent],
  37 + providers: [
  38 + helpers.createProviderToValue('$state', stateService),
  39 + helpers.createProviderToValue('$uibModal', helpers.mocks.$modal),
  40 + helpers.createProviderToValue('RegisterService', registerService),
  41 + helpers.createProviderToValue('NotificationService', notificationService),
  42 + helpers.createProviderToValue('EnvironmentService', helpers.mocks.environmentService)
  43 + ]
  44 + });
  45 + helper = new ComponentTestHelper<RegisterComponent>(cls, done);
  46 + });
  47 +
  48 + it('register page was rendered', () => {
  49 + expect(helper.debugElement.query('div.register-page').length).toEqual(1);
  50 + });
  51 +
  52 +});
src/app/account/register.component.ts 0 → 100644
@@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
  1 +import { Inject, Input, Component, Output, EventEmitter, provide } from 'ng-forward';
  2 +import { RegisterService } from "./../../lib/ng-noosfero-api/http/register.service";
  3 +import { NotificationService } from "./../shared/services/notification.service";
  4 +import { EnvironmentService } from "../../lib/ng-noosfero-api/http/environment.service";
  5 +import { RegisterController } from "./register.controller";
  6 +import { IModalComponent } from "../shared/components/interfaces";
  7 +
  8 +@Component({
  9 + selector: 'noosfero-register',
  10 + templateUrl: 'app/account/register-component.html',
  11 + providers: [
  12 + provide('registerService', { useClass: RegisterService })
  13 + ]
  14 +})
  15 +
  16 +@Inject('$state', '$uibModal', '$scope', RegisterService, NotificationService, EnvironmentService)
  17 +export class RegisterComponent {
  18 + @Input() account: any;
  19 + environment: noosfero.Environment;
  20 +
  21 + modalInstance: ng.ui.bootstrap.IModalServiceInstance;
  22 +
  23 + constructor(
  24 + private $state: ng.ui.IStateService,
  25 + private $uibModal: ng.ui.bootstrap.IModalService,
  26 + private $scope: ng.IScope,
  27 + public registerService: RegisterService,
  28 + private notificationService: NotificationService,
  29 + private environmentService: EnvironmentService
  30 + ) {
  31 + this.account = {};
  32 + this.environment = environmentService.getCurrentEnvironment();
  33 + }
  34 +
  35 + signup() {
  36 + if (this.account.password === this.account.passwordConfirmation) {
  37 + this.registerService.createAccount(this.account).then((response) => {
  38 +
  39 + if (response.status === 201) {
  40 + this.$state.transitionTo('main.environment');
  41 + this.notificationService.success({ title: "account.register.success.title", message: "account.register.success.message" });
  42 + } else {
  43 + throw new Error('Invalid attributes');
  44 + }
  45 + });
  46 + } else {
  47 + this.notificationService.error({ message: "account.register.passwordConfirmation.failed" });
  48 + }
  49 + }
  50 +
  51 + isInvalid(field: any): any {
  52 + return { 'has-error': field['$touched'] && field['$invalid'] };
  53 + }
  54 +
  55 + openTerms() {
  56 +
  57 + this.modalInstance = this.$uibModal.open({
  58 + templateUrl: 'app/account/register-terms.html',
  59 + size: 'lg',
  60 + controller: RegisterController,
  61 + controllerAs: 'vm',
  62 + bindToController: true,
  63 + scope: this.$scope
  64 + });
  65 + }
  66 +}
src/app/account/register.controller.ts 0 → 100644
@@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
  1 +import { Input } from "ng-forward";
  2 +import { IModalComponent } from "../shared/components/interfaces";
  3 +
  4 +export class RegisterController {
  5 +
  6 + static $inject = ["$log", "$stateParams", "$scope"];
  7 + ctrl: IModalComponent;
  8 +
  9 + constructor(
  10 + private $log: ng.ILogService,
  11 + private $stateParams: any
  12 + ) { }
  13 +
  14 + closeTerms() {
  15 + this.ctrl.modalInstance.dismiss('ok');
  16 + }
  17 +}
src/app/account/register.html 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<div class="row">
  2 + <div class="col-md-3"></div>
  3 +
  4 + <!-- component at center of page -->
  5 + <div class="col-md-6">
  6 + <noosfero-register></noosfero-register>
  7 + </div>
  8 +
  9 + <div class="col-md-3"></div>
  10 +</div>
src/app/account/scss/register.scss 0 → 100644
@@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
  1 +.modal .modal-body-overflow {
  2 + max-height: 420px;
  3 + overflow-y: auto;
  4 +}
  5 +
  6 +.register-page button {
  7 + width: 100%;
  8 + text-transform: uppercase;
  9 + font-weight: 600;
  10 +}
  11 +
  12 +.register-page .light-text {
  13 + color: #BBB;
  14 + margin-top: 0px;
  15 + margin-bottom: 0px;
  16 + font-weight: 600;
  17 +}
  18 +
  19 +.register-page .terms-info {
  20 + margin-top: 30px;
  21 + margin-bottom: 30px;
  22 + font-weight: bold;
  23 +}
  24 +
  25 +.register-page .welcome-message {
  26 + text-align: center;
  27 +}
  28 +
  29 +.register-page .already-registered-message {
  30 + margin-top: 60px;
  31 + text-align: center;
  32 +}
  33 +
  34 +.register-page input {
  35 + height: 40px;
  36 +}
  37 +
  38 +.register-page .register-field {
  39 + margin-bottom: 25px;
  40 +}
  41 +
  42 +.register-page input:focus {
  43 + border: 2px solid #bbb;
  44 +}
src/app/admin/layout-edit/designModeToggler.component.spec.ts
1 import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper'; 1 import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
  2 +import {INgForwardJQuery} from 'ng-forward/cjs/util/jqlite-extensions';
2 import * as helpers from '../../../spec/helpers'; 3 import * as helpers from '../../../spec/helpers';
3 import {DesignModeTogglerComponent} from './designModeToggler.component'; 4 import {DesignModeTogglerComponent} from './designModeToggler.component';
4 import {DesignModeService} from './designMode.service'; 5 import {DesignModeService} from './designMode.service';
5 import {INoosferoLocalStorage} from "./../../shared/models/interfaces"; 6 import {INoosferoLocalStorage} from "./../../shared/models/interfaces";
6 7
7 describe('DesignModeToggler Component', () => { 8 describe('DesignModeToggler Component', () => {
8 - const htmlTemplate: string = '<noosfero-design-toggler></noosfero-design-toggler>'; 9 + const htmlTemplate: string = '<design-toggler></design-toggler>';
9 10
10 let helper: ComponentTestHelper<DesignModeTogglerComponent>; 11 let helper: ComponentTestHelper<DesignModeTogglerComponent>;
11 beforeEach(() => { 12 beforeEach(() => {
12 angular.mock.module('templates'); 13 angular.mock.module('templates');
13 angular.mock.module('ngSanitize'); 14 angular.mock.module('ngSanitize');
14 - angular.mock.module('toggle-switch'); 15 + angular.mock.module('uiSwitch');
15 }); 16 });
16 17
17 let designModeService: DesignModeService; 18 let designModeService: DesignModeService;
@@ -30,12 +31,12 @@ describe(&#39;DesignModeToggler Component&#39;, () =&gt; { @@ -30,12 +31,12 @@ describe(&#39;DesignModeToggler Component&#39;, () =&gt; {
30 }); 31 });
31 32
32 it('changes css classes representing the switch is on or off', () => { 33 it('changes css classes representing the switch is on or off', () => {
33 - expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeTruthy();  
34 - expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeFalsy(); 34 + let switchEl: INgForwardJQuery = helper.debugElement.query('span.switch');
  35 +
  36 + expect(switchEl.hasClass('checked')).toBeFalsy();
35 helper.component.inDesignMode = true; 37 helper.component.inDesignMode = true;
36 helper.detectChanges(); 38 helper.detectChanges();
37 - expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeTruthy();  
38 - expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeFalsy(); 39 + expect(switchEl.hasClass('checked')).toBeTruthy();
39 }); 40 });
40 41
41 it('emits event with value "true" when changing inDesignMode to On', (done) => { 42 it('emits event with value "true" when changing inDesignMode to On', (done) => {
@@ -60,4 +61,4 @@ describe(&#39;DesignModeToggler Component&#39;, () =&gt; { @@ -60,4 +61,4 @@ describe(&#39;DesignModeToggler Component&#39;, () =&gt; {
60 helper.component.inDesignMode = false; 61 helper.component.inDesignMode = false;
61 helper.detectChanges(); 62 helper.detectChanges();
62 }); 63 });
63 -});  
64 \ No newline at end of file 64 \ No newline at end of file
  65 +});
src/app/admin/layout-edit/designModeToggler.component.ts
1 -import {Component, Inject} from 'ng-forward'; 1 +import {Component, Inject, Input} from 'ng-forward';
2 import {DesignModeService} from './designMode.service'; 2 import {DesignModeService} from './designMode.service';
3 import {AuthService, AuthEvents} from '../../login'; 3 import {AuthService, AuthEvents} from '../../login';
4 4
5 @Component({ 5 @Component({
6 - selector: 'noosfero-design-toggler', 6 + selector: 'design-toggler',
7 templateUrl: 'app/admin/layout-edit/designModeToggler.html' 7 templateUrl: 'app/admin/layout-edit/designModeToggler.html'
8 }) 8 })
9 -@Inject(DesignModeService, AuthService) 9 +@Inject(DesignModeService, AuthService, '$sce')
10 export class DesignModeTogglerComponent { 10 export class DesignModeTogglerComponent {
11 11
12 - icon: string = "&nbsp;<i class='glyphicon glyphicon-wrench'></i>&nbsp;"; 12 + private _inDesignMode: boolean = false;
13 13
14 - constructor(private designModeService: DesignModeService, private authService: AuthService) { 14 + constructor(private designModeService: DesignModeService, private authService: AuthService, private $sce: ng.ISCEService) {
15 this.authService.subscribe(AuthEvents[AuthEvents.logoutSuccess], () => { 15 this.authService.subscribe(AuthEvents[AuthEvents.logoutSuccess], () => {
16 this.designModeService.destroy(); 16 this.designModeService.destroy();
17 }); 17 });
18 } 18 }
19 19
20 - private _inDesignMode: boolean = false;  
21 -  
22 get inDesignMode(): boolean { 20 get inDesignMode(): boolean {
23 return this.designModeService.isInDesignMode(); 21 return this.designModeService.isInDesignMode();
24 }; 22 };
src/app/admin/layout-edit/designModeToggler.html
1 -<toggle-switch  
2 - html="true"  
3 - ng-model="ctrl.inDesignMode"  
4 - on-label="{{'designMode.toggle.ON' | translate}}"  
5 - off-label="{{'designMode.toggle.OFF' | translate}}"  
6 - class="switch-small"  
7 - knob-label="{{ ctrl.icon + ('designMode.label' | translate) }}">  
8 -</toggle-switch>  
9 \ No newline at end of file 1 \ No newline at end of file
  2 +<switch id="enabled" name="enabled" ng-model="ctrl.inDesignMode" on="ON" off="OFF" class="green"></switch>
src/app/admin/layout-edit/designModeToggler.scss 0 → 100644
@@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
  1 +$off-text: #949494;
  2 +$off-color: #EFEFEF;
  3 +$blue-color: #36B4F6;
  4 +$red-color: #DF3B3A;
  5 +
  6 +body.noosfero-design-on {
  7 +
  8 + div.content-wrapper {
  9 + opacity: 0.5;
  10 + }
  11 +}
  12 +
  13 +.switch {
  14 + @include not-select();
  15 + background-color: $off-color;
  16 +
  17 + &[on][off] {
  18 + width: 60px;
  19 + }
  20 +
  21 + &:focus {
  22 + outline: none;
  23 + }
  24 +
  25 + &.checked small {
  26 + left: initial;
  27 + right: 0px;
  28 + }
  29 +
  30 + &.blue.checked {
  31 + background: $blue-color;
  32 + border-color: $blue-color;
  33 + }
  34 +
  35 + &.red.checked {
  36 + background: $red-color;
  37 + border-color: $red-color;
  38 + }
  39 +
  40 + %switch-label {
  41 + font-weight: bold;
  42 + }
  43 +
  44 + .on {
  45 + @extend %switch-label;
  46 + }
  47 +
  48 + .off {
  49 + @extend %switch-label;
  50 + right: 2px;
  51 + top: 25%;
  52 + color: $off-text;
  53 + text-align: center;
  54 + }
  55 +}
src/app/index.scss
@@ -39,6 +39,12 @@ $break-sm-min: 992px; @@ -39,6 +39,12 @@ $break-sm-min: 992px;
39 $break-xxs-max: ($break-xxs-min - 1); 39 $break-xxs-max: ($break-xxs-min - 1);
40 $break-sm-max: ($break-sm-min - 1); 40 $break-sm-max: ($break-sm-min - 1);
41 $break-xs-max: ($break-xs-min - 1); 41 $break-xs-max: ($break-xs-min - 1);
  42 +// TODO: improve this method to collect all the paddings with a single var
  43 +$wrapper-padding: 15px 15px 35px 15px;
  44 +$wrapper-padding-top: 15px;
  45 +$wrapper-padding-right: 15px;
  46 +$wrapper-padding-bottom: 35px;
  47 +$wrapper-padding-left: 15px;
42 48
43 49
44 @import "../../bower_components/bootswatch/flatly/_variables.scss"; 50 @import "../../bower_components/bootswatch/flatly/_variables.scss";
src/app/index.ts
1 -import {bootstrap} from "ng-forward"; 1 +import {bootstrap, provide} from "ng-forward";
2 import {noosferoModuleConfig} from "./index.config"; 2 import {noosferoModuleConfig} from "./index.config";
3 import {noosferoAngularRunBlock} from "./index.run"; 3 import {noosferoAngularRunBlock} from "./index.run";
4 import {MainComponent} from "./main/main.component"; 4 import {MainComponent} from "./main/main.component";
@@ -22,4 +22,13 @@ angular.module(&#39;noosfero.init&#39;, [&#39;noosfero.templates.app&#39;, &#39;noosfero.templates.p @@ -22,4 +22,13 @@ angular.module(&#39;noosfero.init&#39;, [&#39;noosfero.templates.app&#39;, &#39;noosfero.templates.p
22 run(noosferoAngularRunBlock). 22 run(noosferoAngularRunBlock).
23 constant("moment", moment). 23 constant("moment", moment).
24 constant("AuthEvents", AuthEvents); 24 constant("AuthEvents", AuthEvents);
25 -bootstrap(MainComponent); 25 +
  26 +
  27 +import { EVENTS_HUB_KNOW_EVENT_NAMES } from './shared/services/events-hub.service';
  28 +import { NoosferoKnownEvents } from './known-events';
  29 +
  30 +bootstrap(MainComponent,
  31 + [
  32 + provide(EVENTS_HUB_KNOW_EVENT_NAMES, { useClass: NoosferoKnownEvents })
  33 + ]
  34 +);
src/app/known-events.ts 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +import { EventsHubKnownEventNames } from './shared/services/events-hub.service';
  2 +
  3 +export class NoosferoKnownEvents implements EventsHubKnownEventNames {
  4 + IMAGE_PROFILE_UPDATED: string = 'IMAGE_PROFILE_UPDATED';
  5 + PROFILE_INFO_UPDATED: string = 'PROFILE_INFO_UPDATED';
  6 + ARTICLE_UPDATED: string = 'ARTICLE_UPDATED';
  7 +
  8 + constructor() {
  9 + }
  10 +
  11 + getNames() {
  12 + return Object.getOwnPropertyNames(this);
  13 + }
  14 +}
0 \ No newline at end of file 15 \ No newline at end of file
src/app/layout/blocks/block.component.ts
@@ -18,7 +18,7 @@ export class BlockComponent { @@ -18,7 +18,7 @@ export class BlockComponent {
18 @Input() block: noosfero.Block; 18 @Input() block: noosfero.Block;
19 @Input() owner: noosfero.Profile | noosfero.Environment; 19 @Input() owner: noosfero.Profile | noosfero.Environment;
20 20
21 - private modalInstance: any = null; 21 + private modalInstance: ng.ui.bootstrap.IModalServiceInstance;
22 originalBlock: noosfero.Block; 22 originalBlock: noosfero.Block;
23 23
24 currentUser: noosfero.User; 24 currentUser: noosfero.User;
@@ -26,7 +26,8 @@ export class BlockComponent { @@ -26,7 +26,8 @@ export class BlockComponent {
26 editionMode = false; 26 editionMode = false;
27 designMode = false; 27 designMode = false;
28 28
29 - constructor(private $uibModal: any, 29 + constructor(
  30 + private $uibModal: ng.ui.bootstrap.IModalService,
30 private $scope: ng.IScope, 31 private $scope: ng.IScope,
31 private $state: ng.ui.IStateService, 32 private $state: ng.ui.IStateService,
32 private $rootScope: ng.IRootScopeService, 33 private $rootScope: ng.IRootScopeService,
src/app/layout/blocks/communities/communities-block.html
1 -<div class="communities-block">  
2 - <a ng-repeat="profile in ctrl.profiles" ui-sref="main.profile.home({profile: profile.identifier})" class="profile"> 1 +<div ng-repeat="profile in ctrl.profiles" class="col-xs-2 col-md-4 block-item">
  2 + <a ui-sref="main.profile.home({profile: profile.identifier})">
3 <noosfero-profile-image [profile]="profile"></noosfero-profile-image> 3 <noosfero-profile-image [profile]="profile"></noosfero-profile-image>
4 </a> 4 </a>
  5 + <p>{{profile.name}}</p>
5 </div> 6 </div>
src/app/layout/blocks/communities/communities-block.scss
1 -.communities-block {  
2 - .profile { 1 +.communitiesblock {
  2 + .panel-body {
  3 + text-align: center;
3 margin: 10px; 4 margin: 10px;
4 - img, i.profile-image {  
5 - width: 60px;  
6 - }  
7 img { 5 img {
8 display: inline-block; 6 display: inline-block;
9 vertical-align: top; 7 vertical-align: top;
10 } 8 }
11 i.profile-image { 9 i.profile-image {
12 text-align: center; 10 text-align: center;
13 - font-size: 4.5em; 11 + font-size: 7rem;
  12 + }
  13 + p {
  14 + overflow: hidden;
  15 + font-size: 1rem;
  16 + }
  17 + .block-item {
  18 + padding: 0px;
14 } 19 }
15 } 20 }
16 } 21 }
src/app/layout/blocks/container-block-plugin-container-block/container-block-plugin-container-block.component.ts 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +import { Input, Inject, Component } from 'ng-forward';
  2 +import {BlockService} from "../../../../lib/ng-noosfero-api/http/block.service";
  3 +import {Arrays} from "./../../../../lib/util/arrays";
  4 +
  5 +@Component({
  6 + selector: 'noosfero-container-block-plugin-container-block',
  7 + templateUrl: 'app/layout/blocks/container-block-plugin-container-block/container-block-plugin-container-block.html'
  8 +})
  9 +@Inject(BlockService, "$state")
  10 +export class ContainerBlockPluginContainerBlockComponent {
  11 +
  12 + @Input() block: any;
  13 + @Input() owner: any;
  14 +
  15 + profile: any;
  16 + blocks: any;
  17 +
  18 + constructor(private blockService: BlockService, private $state: any) { }
  19 +
  20 + ngOnInit() {
  21 + this.profile = this.owner;
  22 + this.blocks = [];
  23 + this.blockService.getApiContent(this.block).then((content: any) => {
  24 + this.blocks = content.blocks;
  25 + });
  26 + }
  27 +}
src/app/layout/blocks/container-block-plugin-container-block/container-block-plugin-container-block.html 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +<div>
  2 + <noosfero-block ng-repeat="block in ctrl.blocks | orderBy: 'position'"
  3 + [block]="block" [owner]="ctrl.owner">
  4 + </noosfero-block>
  5 +</div>
  6 +
src/app/layout/blocks/container-block-plugin-container-block/container-block-plugin-container-block.scss 0 → 100644
src/app/layout/blocks/container-block-plugin-container-block/index.ts 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./container-block-plugin-container-block.component";
src/app/layout/blocks/profile-image/profile-image-block.component.spec.ts
@@ -15,11 +15,18 @@ describe(&quot;Components&quot;, () =&gt; { @@ -15,11 +15,18 @@ describe(&quot;Components&quot;, () =&gt; {
15 15
16 beforeEach(angular.mock.module("templates")); 16 beforeEach(angular.mock.module("templates"));
17 17
  18 + let profileService = jasmine.createSpyObj("ProfileService", ["isMember", "addMember", "removeMember"]);
  19 + profileService.isMember = jasmine.createSpy("isMember").and.returnValue(Promise.resolve(false));
  20 +
18 @Component({ 21 @Component({
19 selector: 'test-container-component', 22 selector: 'test-container-component',
20 template: htmlTemplate, 23 template: htmlTemplate,
21 directives: [ProfileImageBlockComponent], 24 directives: [ProfileImageBlockComponent],
22 - providers: helpers.provideFilters("translateFilter") 25 + providers: [
  26 + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})),
  27 + helpers.createProviderToValue('ProfileService', profileService),
  28 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService)
  29 + ].concat(helpers.provideFilters("translateFilter"))
23 }) 30 })
24 class BlockContainerComponent { 31 class BlockContainerComponent {
25 block = { type: 'Block' }; 32 block = { type: 'Block' };
@@ -42,5 +49,42 @@ describe(&quot;Components&quot;, () =&gt; { @@ -42,5 +49,42 @@ describe(&quot;Components&quot;, () =&gt; {
42 }); 49 });
43 }); 50 });
44 51
  52 + it("display button to join community", (done: Function) => {
  53 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  54 + let elProfile = fixture.debugElement.componentViewChildren[0];
  55 + expect(elProfile.query('.actions .join').length).toEqual(1);
  56 + done();
  57 + });
  58 + });
  59 +
  60 + it("display button to leave community", (done: Function) => {
  61 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  62 + let elProfile = fixture.debugElement.componentViewChildren[0];
  63 + elProfile.componentInstance['isMember'] = true;
  64 + fixture.detectChanges();
  65 + expect(elProfile.query('.actions .leave').length).toEqual(1);
  66 + done();
  67 + });
  68 + });
  69 +
  70 + it("join community", (done: Function) => {
  71 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  72 + let elProfile = fixture.debugElement.componentViewChildren[0];
  73 + profileService.addMember = jasmine.createSpy("addMember").and.returnValue(Promise.resolve({ data: {} }));
  74 + elProfile.componentInstance.join();
  75 + expect(profileService.addMember).toHaveBeenCalled();
  76 + done();
  77 + });
  78 + });
  79 +
  80 + it("leave community", (done: Function) => {
  81 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  82 + let elProfile = fixture.debugElement.componentViewChildren[0];
  83 + profileService.removeMember = jasmine.createSpy("removeMember").and.returnValue(Promise.resolve({ data: {} }));
  84 + elProfile.componentInstance.leave();
  85 + expect(profileService.removeMember).toHaveBeenCalled();
  86 + done();
  87 + });
  88 + });
45 }); 89 });
46 }); 90 });
src/app/layout/blocks/profile-image/profile-image-block.component.ts
1 import {Inject, Input, Component} from "ng-forward"; 1 import {Inject, Input, Component} from "ng-forward";
2 import {ProfileImageComponent} from "./../../../profile/image/image.component"; 2 import {ProfileImageComponent} from "./../../../profile/image/image.component";
  3 +import {ProfileService} from "../../../../lib/ng-noosfero-api/http/profile.service";
  4 +import {SessionService} from "./../../../login";
  5 +import {NotificationService} from "../../../shared/services/notification.service";
3 6
4 @Component({ 7 @Component({
5 selector: "noosfero-profile-image-block", 8 selector: "noosfero-profile-image-block",
6 templateUrl: 'app/layout/blocks/profile-image/profile-image-block.html', 9 templateUrl: 'app/layout/blocks/profile-image/profile-image-block.html',
7 directives: [ProfileImageComponent] 10 directives: [ProfileImageComponent]
8 }) 11 })
  12 +@Inject(ProfileService, SessionService, NotificationService)
9 export class ProfileImageBlockComponent { 13 export class ProfileImageBlockComponent {
10 14
11 @Input() block: noosfero.Block; 15 @Input() block: noosfero.Block;
12 @Input() owner: noosfero.Profile; 16 @Input() owner: noosfero.Profile;
13 17
  18 + private isMember: boolean;
  19 +
  20 + constructor(private profileService: ProfileService, private session: SessionService, private notificationService: NotificationService) {
  21 + }
  22 +
  23 + ngOnInit() {
  24 + this.loadMembership();
  25 + }
  26 +
  27 + loadMembership() {
  28 + let person = this.session.currentUser() ? this.session.currentUser().person : null;
  29 + this.profileService.isMember(person, this.owner).then((val: boolean) => {
  30 + this.isMember = val;
  31 + });
  32 + }
  33 +
  34 + join() {
  35 + let person = this.session.currentUser() ? this.session.currentUser().person : null;
  36 + this.profileService.addMember(person, this.owner).then((result: any) => {
  37 + if (result.data.pending) {
  38 + this.notificationService.success({ title: "blocks.profile_image.join.moderation.title", message: "blocks.profile_image.join.moderation.message" });
  39 + } else {
  40 + this.loadMembership();
  41 + }
  42 + });
  43 + }
  44 +
  45 + leave() {
  46 + let person = this.session.currentUser() ? this.session.currentUser().person : null;
  47 + this.profileService.removeMember(person, this.owner).then(() => {
  48 + this.loadMembership();
  49 + });
  50 + }
14 } 51 }
src/app/layout/blocks/profile-image/profile-image-block.html
@@ -3,4 +3,8 @@ @@ -3,4 +3,8 @@
3 <noosfero-profile-image [profile]="ctrl.owner"></noosfero-profile-image> 3 <noosfero-profile-image [profile]="ctrl.owner"></noosfero-profile-image>
4 </a> 4 </a>
5 <a class="settings-link" target="_self" ui-sref="main.profile.settings({profile: ctrl.owner.identifier})">{{"blocks.profile_image.control_panel" | translate}}</a> 5 <a class="settings-link" target="_self" ui-sref="main.profile.settings({profile: ctrl.owner.identifier})">{{"blocks.profile_image.control_panel" | translate}}</a>
  6 + <div class="actions" ng-show="ctrl.isMember!=null">
  7 + <a ng-if="!ctrl.isMember" ng-click="ctrl.join()" class="btn btn-primary btn-sm join" href="#">{{"blocks.profile_image.join" | translate}}</a>
  8 + <a ng-if="ctrl.isMember" ng-click="ctrl.leave()" class="btn btn-warning btn-sm leave" href="#">{{"blocks.profile_image.leave" | translate}}</a>
  9 + </div>
6 </div> 10 </div>
src/app/layout/blocks/profile-images-plugin-profile-images/index.ts 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./profile-images-plugin-profile-images-block.component";
src/app/layout/blocks/profile-images-plugin-profile-images/profile-images-plugin-profile-images-block.component.spec.ts 0 → 100644
@@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Provider, Input, provide, Component} from 'ng-forward';
  3 +import {provideFilters} from '../../../../spec/helpers';
  4 +import {ProfileImagesPluginProfileImagesBlockComponent} from './profile-images-plugin-profile-images-block.component';
  5 +import * as helpers from "./../../../../spec/helpers";
  6 +
  7 +const htmlTemplate: string = '<noosfero-profile-images-plugin-profile-images-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-profile-images-plugin-profile-images-block>';
  8 +
  9 +const tcb = new TestComponentBuilder();
  10 +
  11 +const images = [
  12 + {
  13 + title: 'Test',
  14 + id: 1,
  15 + view_url: { host: 'localhost', page: ['image'] },
  16 + path: '/articles/0000/0001/test.png'
  17 + }
  18 +];
  19 +
  20 +describe("Components", () => {
  21 + describe("Profile Images Block Component", () => {
  22 +
  23 + let settingsObj = {};
  24 + let person = <noosfero.Person>{ name: "Person" };
  25 + let mockedService = {
  26 + getApiContent: (block: noosfero.Block): any => {
  27 + return Promise.resolve({ images: images, headers: (name: string) => { return name; } });
  28 + }
  29 + };
  30 + beforeEach(angular.mock.module("templates"));
  31 +
  32 + let state = jasmine.createSpyObj("state", ["go"]);
  33 +
  34 +
  35 + function getProviders() {
  36 + return [
  37 + new Provider('$state', { useValue: state }),
  38 + new Provider('BlockService', {
  39 + useValue: mockedService
  40 + })
  41 + ].concat(provideFilters("truncateFilter", "stripTagsFilter"));
  42 + }
  43 + let componentClass: any = null;
  44 +
  45 + function getComponent() {
  46 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [ProfileImagesPluginProfileImagesBlockComponent], providers: getProviders() })
  47 + class BlockContainerComponent {
  48 + block = { type: 'Block', settings: settingsObj };
  49 + owner = person;
  50 + constructor() {
  51 + }
  52 + }
  53 + return BlockContainerComponent;
  54 + }
  55 +
  56 + it("get images from the block service", done => {
  57 + tcb.createAsync(getComponent()).then(fixture => {
  58 + let ProfileImagesPluginProfileImagesBlock: ProfileImagesPluginProfileImagesBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  59 + expect(ProfileImagesPluginProfileImagesBlock.images).toEqual(images);
  60 + done();
  61 + });
  62 + });
  63 +
  64 + });
  65 +});
src/app/layout/blocks/profile-images-plugin-profile-images/profile-images-plugin-profile-images-block.component.ts 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +import {Component, Inject, Input} from "ng-forward";
  2 +import {BlockService} from "./../../../../lib/ng-noosfero-api/http/block.service";
  3 +import {Arrays} from "./../../../../lib/util/arrays";
  4 +
  5 +@Component({
  6 + selector: "noosfero-profile-images-plugin-profile-images-block",
  7 + templateUrl: 'app/layout/blocks/profile-images-plugin-profile-images/profile-images-plugin-profile-images-block.html'
  8 +})
  9 +@Inject(BlockService, "$state")
  10 +export class ProfileImagesPluginProfileImagesBlockComponent {
  11 +
  12 + @Input() block: any;
  13 + @Input() owner: any;
  14 +
  15 + profile: any;
  16 + images: any;
  17 +
  18 + constructor(private blockService: BlockService, private $state: any) { }
  19 +
  20 + urlFor(params: any) {
  21 + let url = '//' + params.host;
  22 + if (params.port) {
  23 + url += ':' + params.port;
  24 + }
  25 + url += '/' + params.profile + '/';
  26 + if (params.page) {
  27 + url += params.page.join('/');
  28 + }
  29 + return url;
  30 + }
  31 +
  32 + ngOnInit() {
  33 + this.profile = this.owner;
  34 + this.images = [];
  35 + this.blockService.getApiContent(this.block).then((content: any) => {
  36 + this.images = content.images;
  37 + });
  38 + }
  39 +}
src/app/layout/blocks/profile-images-plugin-profile-images/profile-images-plugin-profile-images-block.html 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +<ul class="profile-images-plugin-profile-images">
  2 + <li ng-repeat="image in ctrl.images">
  3 + <a ng-href="{{ctrl.urlFor(image.view_url)}}" ng-style="{'background-image': 'url(' + image.path + ')'}">
  4 + <span>{{image.title}}</span>
  5 + </a>
  6 + </li>
  7 +</ul>
  8 +<br style="clear: both;" />
src/app/layout/blocks/profile-images-plugin-profile-images/profile-images-plugin-profile-images-block.scss 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +.profile-images-plugin-profile-images {
  2 + padding: 0;
  3 + margin: 0;
  4 + display: block;
  5 + width: 100%;
  6 +
  7 + li {
  8 + list-style: none;
  9 + }
  10 +
  11 + a {
  12 + display: block;
  13 + width: 53px;
  14 + height: 53px;
  15 + float: left;
  16 + padding: 1px;
  17 + border: 1px solid #ccc;
  18 + margin: 4px;
  19 + background-size: cover;
  20 +
  21 + &:hover {
  22 + border: 1px solid #000;
  23 + }
  24 +
  25 + span {
  26 + display: none;
  27 + }
  28 + }
  29 +}
src/app/layout/blocks/recent-activities-plugin-activities/activities/event.html 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +<img ng-src="{{activity.params.first_image}}" ng-attr-alt="{{activity.params.name}}" class="event-image" />
  2 +<p class="event-description"><b>{{activity.params.name}}</b><br />{{activity.params.lead | stripTags}}</p>
  3 +<br style="clear: both;" />
src/app/layout/blocks/recent-activities-plugin-activities/recent-activities-plugin-activities-block.component.ts
@@ -18,7 +18,12 @@ export class RecentActivitiesPluginActivitiesBlockComponent { @@ -18,7 +18,12 @@ export class RecentActivitiesPluginActivitiesBlockComponent {
18 constructor(private blockService: BlockService, private $state: any) { } 18 constructor(private blockService: BlockService, private $state: any) { }
19 19
20 getActivityTemplate(activity: any) { 20 getActivityTemplate(activity: any) {
21 - return 'app/layout/blocks/recent-activities-plugin-activities/activities/' + activity.verb + '.html'; 21 + if (activity.label === 'events') {
  22 + return 'app/layout/blocks/recent-activities-plugin-activities/activities/event.html';
  23 + }
  24 + else {
  25 + return 'app/layout/blocks/recent-activities-plugin-activities/activities/' + activity.verb + '.html';
  26 + }
22 } 27 }
23 28
24 urlFor(params: any) { 29 urlFor(params: any) {
src/app/layout/blocks/recent-activities-plugin-activities/recent-activities-plugin-activities-block.html
1 <div class="deckgrid recent-activities-block"> 1 <div class="deckgrid recent-activities-block">
2 <div ng-repeat="activity in ctrl.activities" class="a-card panel media"> 2 <div ng-repeat="activity in ctrl.activities" class="a-card panel media">
  3 +
  4 + <div class="subheader">
  5 + <p ng-if="activity.label === 'events'">
  6 + {{ 'activities.event.description' | translate }} <b>{{ activity.start_date | date:longDate }}</b> {{ 'time.at' | translate }} {{ activity.start_date | date:'HH:mm' }} - <a ng-href="/{{activity.user.identifier}}">{{activity.user.name}}</a> <span class="activity-label">{{activity.label}}</span>
  7 + </p>
  8 +
  9 + <p ng-if="activity.label !== 'events'">
  10 + {{ 'date.on' | translate }} <b>{{ activity.created_at | date:longDate }}</b> {{ 'time.at' | translate }} {{ activity.created_at | date:'HH:mm' }} - <a ng-href="/{{activity.user.identifier}}">{{activity.user.name}}</a> <span class="activity-label">{{activity.label}}</span>
  11 + </p>
  12 + </div>
  13 +
3 <div class="header media-body"> 14 <div class="header media-body">
4 <h5 class="title media-heading"> 15 <h5 class="title media-heading">
5 - <a ng-href="/{{activity.user.identifier}}">{{activity.user.name}}</a>&nbsp;<ng-include src="ctrl.getActivityTemplate(activity)"></ng-include> 16 + <ng-include src="ctrl.getActivityTemplate(activity)"></ng-include>
6 </h5> 17 </h5>
7 </div> 18 </div>
8 - <div class="subheader">  
9 - <span class="time">  
10 - <i class="fa fa-clock-o"></i> <span am-time-ago="activity.created_at | dateFormat"></span>  
11 - </span>  
12 - </div> 19 +
  20 + <hr />
13 </div> 21 </div>
14 </div> 22 </div>
src/app/layout/blocks/recent-activities-plugin-activities/recent-activities-plugin-activities-block.scss
@@ -20,4 +20,53 @@ @@ -20,4 +20,53 @@
20 display: none; 20 display: none;
21 } 21 }
22 } 22 }
  23 +
  24 + .panel {
  25 + margin-bottom: 15px;
  26 + box-shadow: none;
  27 + border-radius: 0;
  28 + }
  29 +
  30 + h5 {
  31 + text-transform: capitalize;
  32 + }
  33 +
  34 + .subheader {
  35 + p {
  36 + margin: 2px 0;
  37 + font-size: 11px;
  38 + }
  39 + }
  40 +
  41 + hr {
  42 + border: 0;
  43 + height: 1px;
  44 + background: #ccc;
  45 + margin: 0;
  46 + margin-top: 15px;
  47 + }
  48 +
  49 + .activity-label {
  50 + @include border-radius(2px);
  51 + font-size: 11px;
  52 + text-transform: capitalize;
  53 + background: #333;
  54 + color: #fff;
  55 + padding: 2px;
  56 + margin-left: 5px;
  57 + display: inline-block;
  58 + }
  59 +
  60 + .event-image {
  61 + width: 15%;
  62 + height: auto;
  63 + float: left;
  64 + }
  65 +
  66 + .event-description {
  67 + width: 83%;
  68 + margin: 2px 0 0 2px;
  69 + float: left;
  70 + display: block;
  71 + }
23 } 72 }
src/app/layout/boxes/display-boxes.filter.ts
@@ -11,6 +11,8 @@ export class DisplayBoxes { @@ -11,6 +11,8 @@ export class DisplayBoxes {
11 11
12 if (layout === "rightbar") { 12 if (layout === "rightbar") {
13 valid_boxes = this.visible_on_right_bar(); 13 valid_boxes = this.visible_on_right_bar();
  14 + }else if(layout === "topleft") {
  15 + valid_boxes = this.visible_on_top_right_bar();
14 }else { 16 }else {
15 valid_boxes = this.visible_on_default(); 17 valid_boxes = this.visible_on_default();
16 } 18 }
@@ -31,5 +33,9 @@ export class DisplayBoxes { @@ -31,5 +33,9 @@ export class DisplayBoxes {
31 return [1, 3]; 33 return [1, 3];
32 } 34 }
33 35
  36 + private visible_on_top_right_bar() {
  37 + return [1, 2, 3];
  38 + }
  39 +
34 } 40 }
35 41
src/app/layout/boxes/set-box-layout.filter.ts
@@ -6,6 +6,8 @@ export class SetBoxLayout { @@ -6,6 +6,8 @@ export class SetBoxLayout {
6 transform(pos: number, layout: string) { 6 transform(pos: number, layout: string) {
7 if (layout === "rightbar") { 7 if (layout === "rightbar") {
8 return this.right_bar(pos); 8 return this.right_bar(pos);
  9 + }else if(layout === "topleft") {
  10 + return this.top_right_bar(pos);
9 }else { 11 }else {
10 return this.default(pos); 12 return this.default(pos);
11 } 13 }
@@ -29,4 +31,14 @@ export class SetBoxLayout { @@ -29,4 +31,14 @@ export class SetBoxLayout {
29 } 31 }
30 } 32 }
31 33
  34 + private top_right_bar(position: number) {
  35 + if (position === 1) {
  36 + return "col-sm-12 col-md-8";
  37 + }else if(position == 2) {
  38 + return "col-sm-12 col-md-4";
  39 + }else {
  40 + return "col-sm-12 col-md-12";
  41 + }
  42 + }
  43 +
32 } 44 }
src/app/layout/navbar/navbar.html
@@ -20,6 +20,12 @@ @@ -20,6 +20,12 @@
20 <a ng-href="#" ng-click="ctrl.openLogin()">{{"navbar.login" | translate}}</a> 20 <a ng-href="#" ng-click="ctrl.openLogin()">{{"navbar.login" | translate}}</a>
21 </li> 21 </li>
22 22
  23 + <li ng-show="!ctrl.currentUser">
  24 + <a ui-sref="main.register">
  25 + Sign Up
  26 + </a>
  27 + </li>
  28 +
23 <li class="dropdown profile-menu" ng-show="ctrl.currentUser" uib-dropdown> 29 <li class="dropdown profile-menu" ng-show="ctrl.currentUser" uib-dropdown>
24 <a href="#" class="dropdown-toggle" aria-expanded="false" uib-dropdown-toggle> 30 <a href="#" class="dropdown-toggle" aria-expanded="false" uib-dropdown-toggle>
25 <noosfero-profile-image [profile]="ctrl.currentUser.person" class="profile-image"></noosfero-profile-image> 31 <noosfero-profile-image [profile]="ctrl.currentUser.person" class="profile-image"></noosfero-profile-image>
src/app/layout/navbar/navbar.ts
1 -import {Component, Inject, EventEmitter, Input} from "ng-forward";  
2 -import {LanguageSelectorComponent} from "../language-selector/language-selector.component";  
3 -import {SessionService, AuthService, AuthController, AuthEvents} from "./../../login";  
4 -import {EnvironmentService} from "./../../../lib/ng-noosfero-api/http/environment.service";  
5 -import {SidebarNotificationService} from "../sidebar/sidebar.notification.service";  
6 -import {BodyStateClassesService} from '../services/body-state-classes.service';  
7 -import {DesignModeTogglerComponent} from './../../admin/layout-edit/designModeToggler.component';  
8 -import {BootstrapSwitcherComponent, BootstrapSwitcherItem} from './../../shared/components/bootstrap-switcher/bootstrap-switcher.component'; 1 +import { Component, Inject, EventEmitter, Input } from "ng-forward";
  2 +import { LanguageSelectorComponent } from "../language-selector/language-selector.component";
  3 +import { SessionService, AuthService, AuthController, AuthEvents } from "./../../login";
  4 +import { EnvironmentService } from "./../../../lib/ng-noosfero-api/http/environment.service";
  5 +import { SidebarNotificationService } from "../sidebar/sidebar.notification.service";
  6 +import { BodyStateClassesService } from '../services/body-state-classes.service';
  7 +import { DesignModeTogglerComponent } from './../../admin/layout-edit/designModeToggler.component';
  8 +import { BootstrapSwitcherComponent, BootstrapSwitcherItem } from './../../shared/components/bootstrap-switcher/bootstrap-switcher.component';
9 9
10 @Component({ 10 @Component({
11 selector: "acme-navbar", 11 selector: "acme-navbar",
@@ -17,14 +17,14 @@ import {BootstrapSwitcherComponent, BootstrapSwitcherItem} from &#39;./../../shared/ @@ -17,14 +17,14 @@ import {BootstrapSwitcherComponent, BootstrapSwitcherItem} from &#39;./../../shared/
17 export class Navbar { 17 export class Navbar {
18 18
19 private currentUser: noosfero.User; 19 private currentUser: noosfero.User;
20 - private modalInstance: any = null; 20 + private modalInstance: ng.ui.bootstrap.IModalServiceInstance;
21 public showHamburger: boolean = false; 21 public showHamburger: boolean = false;
22 public currentEnvironment: noosfero.Environment = <any>{ name: '' }; 22 public currentEnvironment: noosfero.Environment = <any>{ name: '' };
23 /** 23 /**
24 * 24 *
25 */ 25 */
26 constructor( 26 constructor(
27 - private $uibModal: any, 27 + private $uibModal: ng.ui.bootstrap.IModalService,
28 public authService: AuthService, 28 public authService: AuthService,
29 private session: SessionService, 29 private session: SessionService,
30 private $state: ng.ui.IStateService, 30 private $state: ng.ui.IStateService,
src/app/layout/scss/_forms.scss
@@ -2,6 +2,10 @@ label { @@ -2,6 +2,10 @@ label {
2 cursor: pointer; 2 cursor: pointer;
3 } 3 }
4 4
  5 +.has-error .help-block {
  6 + font-weight: bold;
  7 +}
  8 +
5 .checkbox-nice { 9 .checkbox-nice {
6 position: relative; 10 position: relative;
7 padding-left: 15px; 11 padding-left: 15px;
src/app/layout/scss/_layout.scss
@@ -34,17 +34,4 @@ @@ -34,17 +34,4 @@
34 .main-box-body { 34 .main-box-body {
35 padding: 0 20px 20px 20px; 35 padding: 0 20px 20px 20px;
36 } 36 }
37 -}  
38 -  
39 -ul.nav > li.toggler-container {  
40 - position: relative;  
41 - padding-top: 12px;  
42 -}  
43 -  
44 -.noosfero-main-toolbar {  
45 - padding: 5px;  
46 - @include make-row();  
47 - margin-left: 0px;  
48 - margin-right: 0px;  
49 - background-color: #edecec;  
50 -} 37 +}
51 \ No newline at end of file 38 \ No newline at end of file
src/app/layout/scss/_mixins.scss
@@ -4,6 +4,12 @@ @@ -4,6 +4,12 @@
4 background-clip: padding-box; /* stops bg color from leaking outside the border: */ 4 background-clip: padding-box; /* stops bg color from leaking outside the border: */
5 } 5 }
6 6
  7 +@mixin box-shadow($args...) {
  8 + -webkit-box-shadow: $args;
  9 + -moz-box-shadow: $args;
  10 + box-shadow: $args;
  11 +}
  12 +
7 @mixin checkbox-mark($width, $height, $top, $left, $bg, $border, $content: "", $cursor: pointer, $position: absolute) { 13 @mixin checkbox-mark($width, $height, $top, $left, $bg, $border, $content: "", $cursor: pointer, $position: absolute) {
8 width: $width; 14 width: $width;
9 height: $height; 15 height: $height;
@@ -48,3 +54,31 @@ @@ -48,3 +54,31 @@
48 outline-width: $width; 54 outline-width: $width;
49 } 55 }
50 } 56 }
  57 +
  58 +@mixin not-select() {
  59 + -webkit-touch-callout: none; /* iOS Safari */
  60 + -webkit-user-select: none; /* Chrome/Safari/Opera */
  61 + -khtml-user-select: none; /* Konqueror */
  62 + -moz-user-select: none; /* Firefox */
  63 + -ms-user-select: none; /* Internet Explorer/Edge */
  64 + user-select: none; /* Non-prefixed version, currently
  65 + not supported by any browser */
  66 +}
  67 +
  68 +@mixin keyframes($animation-name) {
  69 + @-webkit-keyframes #{$animation-name} {
  70 + @content;
  71 + }
  72 + @-moz-keyframes #{$animation-name} {
  73 + @content;
  74 + }
  75 + @-ms-keyframes #{$animation-name} {
  76 + @content;
  77 + }
  78 + @-o-keyframes #{$animation-name} {
  79 + @content;
  80 + }
  81 + @keyframes #{$animation-name} {
  82 + @content;
  83 + }
  84 +}
src/app/layout/scss/skins/_whbl.scss
@@ -122,7 +122,7 @@ $whbl-font-color: #16191c; @@ -122,7 +122,7 @@ $whbl-font-color: #16191c;
122 margin-bottom: 0; 122 margin-bottom: 0;
123 position: relative; 123 position: relative;
124 min-height: 1200px; 124 min-height: 1200px;
125 - padding: 15px 15px 35px 15px; 125 + padding: $wrapper-padding;
126 margin-left: 220px; 126 margin-left: 220px;
127 // border-left: 2px solid #e7ebee; 127 // border-left: 2px solid #e7ebee;
128 } 128 }
@@ -291,11 +291,7 @@ $whbl-font-color: #16191c; @@ -291,11 +291,7 @@ $whbl-font-color: #16191c;
291 background-color: #fff; 291 background-color: #fff;
292 } 292 }
293 293
294 - noosfero-design-toggler .ats-switch .knob i {  
295 - color: #999999;  
296 - }  
297 -  
298 - .ats-switch .knob { 294 + .ats-switch .knob {
299 padding-right: 15px; 295 padding-right: 15px;
300 } 296 }
301 297
src/app/layout/services/body-state-classes.service.spec.ts
@@ -193,7 +193,6 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; { @@ -193,7 +193,6 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
193 193
194 service.start(); 194 service.start();
195 195
196 - debugger;  
197 designModeService.setInDesignMode(true); 196 designModeService.setInDesignMode(true);
198 197
199 198
src/app/login/login.html
@@ -21,4 +21,7 @@ @@ -21,4 +21,7 @@
21 </div> 21 </div>
22 <button type="submit" class="btn btn-default btn-block" ng-click="vm.login()">{{"auth.form.login_button" | translate}}</button> 22 <button type="submit" class="btn btn-default btn-block" ng-click="vm.login()">{{"auth.form.login_button" | translate}}</button>
23 </form> 23 </form>
  24 + <div class="text-center">
  25 + <a ui-sref="main.register" ng-click="$close()">{{"auth.createAccount" | translate}}</a>
  26 + </div>
24 </div> 27 </div>
src/app/login/session.service.ts
@@ -23,4 +23,4 @@ export class SessionService { @@ -23,4 +23,4 @@ export class SessionService {
23 return this.$localStorage.currentUser; 23 return this.$localStorage.currentUser;
24 }; 24 };
25 25
26 -}  
27 \ No newline at end of file 26 \ No newline at end of file
  27 +}
src/app/main/main.component.spec.ts
@@ -4,8 +4,9 @@ import {TestComponentBuilder, ComponentFixture} from &quot;ng-forward/cjs/testing/tes @@ -4,8 +4,9 @@ import {TestComponentBuilder, ComponentFixture} from &quot;ng-forward/cjs/testing/tes
4 4
5 import {quickCreateComponent} from "../../spec/helpers"; 5 import {quickCreateComponent} from "../../spec/helpers";
6 import {getAngularServiceFactory} from "../../spec/helpers"; 6 import {getAngularServiceFactory} from "../../spec/helpers";
  7 +import { EVENTS_HUB_KNOW_EVENT_NAMES } from "../shared/services/events-hub.service";
7 8
8 -describe("MainComponent", function() { 9 +describe("MainComponent", function () {
9 10
10 let localFixture: ComponentFixture; 11 let localFixture: ComponentFixture;
11 let $state: angular.ui.IStateService; 12 let $state: angular.ui.IStateService;
@@ -34,6 +35,14 @@ describe(&quot;MainComponent&quot;, function() { @@ -34,6 +35,14 @@ describe(&quot;MainComponent&quot;, function() {
34 { 35 {
35 useValue: environmentService 36 useValue: environmentService
36 }), 37 }),
  38 + provide(EVENTS_HUB_KNOW_EVENT_NAMES,
  39 + {
  40 + useValue: [
  41 + 'IMAGE_PROFILE_UPDATED',
  42 + 'PROFILE_INFO_UPDATED',
  43 + 'ARTICLE_UPDATED'
  44 + ]
  45 + }),
37 ] 46 ]
38 }) 47 })
39 class MainComponentParent { 48 class MainComponentParent {
@@ -57,7 +66,7 @@ describe(&quot;MainComponent&quot;, function() { @@ -57,7 +66,7 @@ describe(&quot;MainComponent&quot;, function() {
57 // navigates to the environment home 66 // navigates to the environment home
58 $state.go("main.environment.home"); 67 $state.go("main.environment.home");
59 localFixture.detectChanges(); 68 localFixture.detectChanges();
60 - // after changes were detected it checks the current $state route 69 + // after changes were detected it checks the current $state route
61 expect($state.current.name).toEqual("main.environment.home"); 70 expect($state.current.name).toEqual("main.environment.home");
62 done(); 71 done();
63 }); 72 });
src/app/main/main.component.ts
1 import * as plugins from "../../plugins"; 1 import * as plugins from "../../plugins";
2 -import {bundle, Component, StateConfig, Inject} from "ng-forward";  
3 -import {ArticleBlogComponent} from "./../article/types/blog/blog.component";  
4 -  
5 -import {ArticleViewComponent} from "./../article/article-default-view.component";  
6 -  
7 -import {ProfileComponent} from "../profile/profile.component";  
8 -import {BoxesComponent} from "../layout/boxes/boxes.component";  
9 -import {BlockContentComponent} from "../layout/blocks/block-content.component";  
10 -import {BlockComponent} from "../layout/blocks/block.component";  
11 -import {EnvironmentComponent} from "../environment/environment.component";  
12 -import {EnvironmentHomeComponent} from "../environment/environment-home.component";  
13 -import {PeopleBlockComponent} from "../layout/blocks/people/people-block.component";  
14 -import {DisplayContentBlockComponent} from "../layout/blocks/display-content/display-content-block.component";  
15 -import {LinkListBlockComponent} from "../layout/blocks/link-list/link-list-block.component";  
16 -import {RecentDocumentsBlockComponent} from "../layout/blocks/recent-documents/recent-documents-block.component";  
17 -import {ProfileImageBlockComponent} from "../layout/blocks/profile-image/profile-image-block.component";  
18 -import {RawHTMLBlockComponent} from "../layout/blocks/raw-html/raw-html-block.component";  
19 -import {StatisticsBlockComponent} from "../layout/blocks/statistics/statistics-block.component";  
20 -import {PersonTagsPluginInterestsBlockComponent} from "../layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.component";  
21 -import {TagsBlockComponent} from "../layout/blocks/tags/tags-block.component";  
22 -import {CustomContentComponent} from "../profile/custom-content/custom-content.component";  
23 -import {RecentActivitiesPluginActivitiesBlockComponent} from "../layout/blocks/recent-activities-plugin-activities/recent-activities-plugin-activities-block.component";  
24 -  
25 -import {MembersBlockComponent} from "../layout/blocks/members/members-block.component";  
26 -import {CommunitiesBlockComponent} from "../layout/blocks/communities/communities-block.component";  
27 -  
28 -import {LoginBlockComponent} from "../layout/blocks/login-block/login-block.component";  
29 -  
30 -import {NoosferoTemplate} from "../shared/pipes/noosfero-template.filter";  
31 -import {DateFormat} from "../shared/pipes/date-format.filter";  
32 -  
33 -import {AuthService} from "../login/auth.service";  
34 -import {SessionService} from "../login/session.service";  
35 -import {EnvironmentService} from "./../../lib/ng-noosfero-api/http/environment.service";  
36 -import {NotificationService} from "../shared/services/notification.service";  
37 -  
38 -import {BodyStateClassesService} from "./../layout/services/body-state-classes.service";  
39 -  
40 -import {Navbar} from "../layout/navbar/navbar";  
41 -  
42 -import {SidebarComponent} from "../layout/sidebar/sidebar.component";  
43 -  
44 -import {MainBlockComponent} from "../layout/blocks/main/main-block.component";  
45 -import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component";  
46 -import {PermissionDirective} from "../shared/components/permission/permission.directive";  
47 -import {SearchComponent} from "../search/search.component";  
48 -import {SearchFormComponent} from "../search/search-form/search-form.component";  
49 - 2 +import { bundle, Component, StateConfig, Inject } from "ng-forward";
  3 +import { ArticleBlogComponent } from "./../article/types/blog/blog.component";
  4 +
  5 +import { ArticleViewComponent } from "./../article/article-default-view.component";
  6 +
  7 +import { ProfileComponent } from "../profile/profile.component";
  8 +import { BoxesComponent } from "../layout/boxes/boxes.component";
  9 +import { BlockContentComponent } from "../layout/blocks/block-content.component";
  10 +import { BlockComponent } from "../layout/blocks/block.component";
  11 +import { EnvironmentComponent } from "../environment/environment.component";
  12 +import { EnvironmentHomeComponent } from "../environment/environment-home.component";
  13 +import { PeopleBlockComponent } from "../layout/blocks/people/people-block.component";
  14 +import { DisplayContentBlockComponent } from "../layout/blocks/display-content/display-content-block.component";
  15 +import { LinkListBlockComponent } from "../layout/blocks/link-list/link-list-block.component";
  16 +import { RecentDocumentsBlockComponent } from "../layout/blocks/recent-documents/recent-documents-block.component";
  17 +import { ProfileImageBlockComponent } from "../layout/blocks/profile-image/profile-image-block.component";
  18 +import { RawHTMLBlockComponent } from "../layout/blocks/raw-html/raw-html-block.component";
  19 +import { StatisticsBlockComponent } from "../layout/blocks/statistics/statistics-block.component";
  20 +import { PersonTagsPluginInterestsBlockComponent } from "../layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.component";
  21 +import { TagsBlockComponent } from "../layout/blocks/tags/tags-block.component";
  22 +import { CustomContentComponent } from "../profile/custom-content/custom-content.component";
  23 +import { RecentActivitiesPluginActivitiesBlockComponent } from "../layout/blocks/recent-activities-plugin-activities/recent-activities-plugin-activities-block.component";
  24 +import { ContainerBlockPluginContainerBlockComponent } from "../layout/blocks/container-block-plugin-container-block/container-block-plugin-container-block.component";
  25 +import { ProfileImagesPluginProfileImagesBlockComponent } from "../layout/blocks/profile-images-plugin-profile-images/profile-images-plugin-profile-images-block.component";
  26 +import { RegisterComponent } from "../account/register.component";
  27 +
  28 +import { MembersBlockComponent } from "../layout/blocks/members/members-block.component";
  29 +import { CommunitiesBlockComponent } from "../layout/blocks/communities/communities-block.component";
  30 +
  31 +import { LoginBlockComponent } from "../layout/blocks/login-block/login-block.component";
  32 +
  33 +import { NoosferoTemplate } from "../shared/pipes/noosfero-template.filter";
  34 +import { DateFormat } from "../shared/pipes/date-format.filter";
  35 +
  36 +import { AuthService } from "../login/auth.service";
  37 +import { SessionService } from "../login/session.service";
  38 +import { EnvironmentService } from "./../../lib/ng-noosfero-api/http/environment.service";
  39 +import { NotificationService } from "../shared/services/notification.service";
  40 +import { RegisterService } from "./../../lib/ng-noosfero-api/http/register.service";
  41 +
  42 +import { BodyStateClassesService } from "./../layout/services/body-state-classes.service";
  43 +
  44 +import { Navbar } from "../layout/navbar/navbar";
  45 +
  46 +import { SidebarComponent } from "../layout/sidebar/sidebar.component";
  47 +
  48 +import { MainBlockComponent } from "../layout/blocks/main/main-block.component";
  49 +import { HtmlEditorComponent } from "../shared/components/html-editor/html-editor.component";
  50 +import { PermissionDirective } from "../shared/components/permission/permission.directive";
  51 +import { SearchComponent } from "../search/search.component";
  52 +import { SearchFormComponent } from "../search/search-form/search-form.component";
  53 +
  54 +import { EVENTS_HUB_KNOW_EVENT_NAMES, EventsHubService } from "../shared/services/events-hub.service";
  55 +import { NoosferoKnownEvents } from "../known-events";
50 /** 56 /**
51 * @ngdoc controller 57 * @ngdoc controller
52 * @name main.MainContentComponent 58 * @name main.MainContentComponent
@@ -62,12 +68,16 @@ import {SearchFormComponent} from &quot;../search/search-form/search-form.component&quot;; @@ -62,12 +68,16 @@ import {SearchFormComponent} from &quot;../search/search-form/search-form.component&quot;;
62 templateUrl: "app/main/main.html", 68 templateUrl: "app/main/main.html",
63 providers: [AuthService, SessionService] 69 providers: [AuthService, SessionService]
64 }) 70 })
65 -@Inject(BodyStateClassesService) 71 +@Inject(BodyStateClassesService, EVENTS_HUB_KNOW_EVENT_NAMES)
66 export class MainContentComponent { 72 export class MainContentComponent {
67 73
68 public themeSkin: string = 'skin-whbl'; 74 public themeSkin: string = 'skin-whbl';
69 75
70 - constructor(private bodyStateClassesService: BodyStateClassesService) { 76 + constructor(
  77 + private bodyStateClassesService: BodyStateClassesService,
  78 + eventsNames: NoosferoKnownEvents,
  79 + eventsHubService: EventsHubService
  80 + ) {
71 bodyStateClassesService.start({ 81 bodyStateClassesService.start({
72 skin: this.themeSkin 82 skin: this.themeSkin
73 }); 83 });
@@ -88,7 +98,8 @@ export class EnvironmentContent { @@ -88,7 +98,8 @@ export class EnvironmentContent {
88 * @name main.Main 98 * @name main.Main
89 * @requires AuthService, Session, Notification, ArticleBlog, ArticleView, Boxes, Block, LinkListBlock, 99 * @requires AuthService, Session, Notification, ArticleBlog, ArticleView, Boxes, Block, LinkListBlock,
90 * MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock, 100 * MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock,
91 - * NoosferoTemplate, DateFormat, RawHTMLBlock, PersonTagsPluginInterestsBlock, RecentActivitiesPluginActivitiesBlock, 101 + * NoosferoTemplate, DateFormat, RawHTMLBlock, PersonTagsPluginInterestsBlock,
  102 + * RecentActivitiesPluginActivitiesBlock, ContainerBlockPluginContainerBlockComponent, ProfileImagesPluginProfileImages
92 * @description 103 * @description
93 * The Main controller for the Noosfero Angular Theme application. 104 * The Main controller for the Noosfero Angular Theme application.
94 * 105 *
@@ -107,16 +118,18 @@ export class EnvironmentContent { @@ -107,16 +118,18 @@ export class EnvironmentContent {
107 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, 118 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
108 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, 119 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
109 LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent, 120 LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent,
110 - PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, BlockComponent 121 + PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent,
  122 + ContainerBlockPluginContainerBlockComponent,
  123 + ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent
111 ].concat(plugins.mainComponents).concat(plugins.hotspots), 124 ].concat(plugins.mainComponents).concat(plugins.hotspots),
112 - providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, 125 + providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, RegisterService,
113 "ngAnimate", "ngCookies", "ngStorage", "ngTouch", 126 "ngAnimate", "ngCookies", "ngStorage", "ngTouch",
114 "ngSanitize", "ngMessages", "ngAria", "restangular", 127 "ngSanitize", "ngMessages", "ngAria", "restangular",
115 "ui.router", "ui.bootstrap", "toastr", "ngCkeditor", 128 "ui.router", "ui.bootstrap", "toastr", "ngCkeditor",
116 "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid", 129 "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid",
117 "angular-timeline", "duScroll", "oitozero.ngSweetAlert", 130 "angular-timeline", "duScroll", "oitozero.ngSweetAlert",
118 "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad", 131 "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad",
119 - "angular-click-outside", "toggle-switch", "ngTagCloud", "noosfero.init"] 132 + "angular-click-outside", "ngTagCloud", "noosfero.init", "uiSwitch", "ngPassword"]
120 }) 133 })
121 @StateConfig([ 134 @StateConfig([
122 { 135 {
@@ -137,7 +150,6 @@ export class EnvironmentContent { @@ -137,7 +150,6 @@ export class EnvironmentContent {
137 url: '/', 150 url: '/',
138 component: EnvironmentComponent, 151 component: EnvironmentComponent,
139 name: 'main.environment', 152 name: 'main.environment',
140 - abstract: true,  
141 views: { 153 views: {
142 "content": { 154 "content": {
143 templateUrl: "app/environment/environment.html", 155 templateUrl: "app/environment/environment.html",
@@ -147,6 +159,18 @@ export class EnvironmentContent { @@ -147,6 +159,18 @@ export class EnvironmentContent {
147 } 159 }
148 }, 160 },
149 { 161 {
  162 + url: '/account/signup',
  163 + component: RegisterComponent,
  164 + name: 'main.register',
  165 + views: {
  166 + "content": {
  167 + templateUrl: "app/account/register.html",
  168 + controller: RegisterComponent,
  169 + controllerAs: "vm"
  170 + }
  171 + }
  172 + },
  173 + {
150 url: "^/:profile", 174 url: "^/:profile",
151 abstract: true, 175 abstract: true,
152 component: ProfileComponent, 176 component: ProfileComponent,
src/app/profile/activities/activities.html
1 -<timeline>  
2 - <timeline-event ng-repeat="activity in ctrl.activities | orderBy: 'created_at':true">  
3 - <noosfero-activity [activity]="activity"></noosfero-activity>  
4 - </timeline-event>  
5 -</timeline> 1 +<div class="row">
  2 + <hr class="divider"/>
  3 + <div id="atividades">
  4 + <h4>Atividades Recentes</h4>
  5 + <timeline>
  6 + <timeline-event ng-repeat="activity in vm.activities | orderBy: 'created_at':true">
  7 + <hr class="divider"/>
  8 + <noosfero-activity [activity]="activity"></noosfero-activity>
  9 + </timeline-event>
  10 + </timeline>
  11 + <button type="button" class="btn btn-sm btn-block">Carregar mais</button>
  12 + </div>
  13 +</div>
src/app/profile/activities/activity/activity.component.spec.ts
@@ -12,6 +12,7 @@ const htmlTemplate: string = &#39;&lt;noosfero-activity [activity]=&quot;ctrl.activity&quot;&gt;&lt;/no @@ -12,6 +12,7 @@ const htmlTemplate: string = &#39;&lt;noosfero-activity [activity]=&quot;ctrl.activity&quot;&gt;&lt;/no
12 describe("Components", () => { 12 describe("Components", () => {
13 13
14 describe("Noosfero Activity", () => { 14 describe("Noosfero Activity", () => {
  15 + let activity = { name: "activity1", verb: "create_article" };
15 16
16 beforeEach(angular.mock.module("templates")); 17 beforeEach(angular.mock.module("templates"));
17 18
@@ -21,18 +22,54 @@ describe(&quot;Components&quot;, () =&gt; { @@ -21,18 +22,54 @@ describe(&quot;Components&quot;, () =&gt; {
21 directives: [ActivityComponent], 22 directives: [ActivityComponent],
22 providers: provideFilters("truncateFilter", "stripTagsFilter", "translateFilter") 23 providers: provideFilters("truncateFilter", "stripTagsFilter", "translateFilter")
23 }) 24 })
  25 +
24 class BlockContainerComponent { 26 class BlockContainerComponent {
25 - activity = { name: "activity1", verb: "create_article" }; 27 + activity = activity;
26 } 28 }
27 29
28 it("render the specific template for an activity verb", done => { 30 it("render the specific template for an activity verb", done => {
29 tcb.createAsync(BlockContainerComponent).then(fixture => { 31 tcb.createAsync(BlockContainerComponent).then(fixture => {
30 let component: ActivityComponent = fixture.debugElement.componentViewChildren[0].componentInstance; 32 let component: ActivityComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
31 expect(component.getActivityTemplate()).toEqual('app/profile/activities/activity/create_article.html'); 33 expect(component.getActivityTemplate()).toEqual('app/profile/activities/activity/create_article.html');
  34 + done();
  35 + });
  36 + });
  37 +
  38 + it("render create article template correctly", done => {
  39 + activity = { name: "activity1", verb: "create_article" };
  40 + tcb.createAsync(BlockContainerComponent).then(fixture => {
  41 + let component: ActivityComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
32 expect(fixture.debugElement.queryAll(".activity.create_article").length).toEqual(1); 42 expect(fixture.debugElement.queryAll(".activity.create_article").length).toEqual(1);
33 done(); 43 done();
34 }); 44 });
35 }); 45 });
  46 +
  47 + it("render add_member_in_community template correctly", done => {
  48 + activity = { name: "add_member_in_community1", verb: "add_member_in_community" };
  49 + tcb.createAsync(BlockContainerComponent).then(fixture => {
  50 + let component: ActivityComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  51 + expect(fixture.debugElement.queryAll(".activity.add_member_in_community").length).toEqual(1);
  52 + done();
  53 + });
  54 + });
  55 +
  56 + it("render new_friendship template correctly", done => {
  57 + activity = { name: "new_friendship1", verb: "new_friendship" };
  58 + tcb.createAsync(BlockContainerComponent).then(fixture => {
  59 + let component: ActivityComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  60 + expect(fixture.debugElement.queryAll(".activity.new_friendship").length).toEqual(1);
  61 + done();
  62 + });
  63 + });
  64 +
  65 + it("render leave scrap template correctly", done => {
  66 + activity = { name: "scrap1", verb: "leave_scrap" };
  67 + tcb.createAsync(BlockContainerComponent).then(fixture => {
  68 + let component: ActivityComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  69 + expect(fixture.debugElement.queryAll(".activity.leave_scrap").length).toEqual(1);
  70 + done();
  71 + });
  72 + });
36 }); 73 });
37 74
38 }); 75 });
src/app/profile/activities/activity/leave_scrap.html 0 → 100644
@@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
  1 +<timeline-badge class="success">
  2 + <i class="fa fa-commenting-o"></i>
  3 +</timeline-badge>
  4 +<timeline-panel>
  5 + <timeline-heading>
  6 + <h4 class="timeline-title">
  7 + <a ui-sref="main.profile.info({profile: ctrl.activity.user.identifier})"><strong ng-bind="ctrl.activity.user.name"></strong></a>
  8 + <span> {{"activities.scrap.description" | translate}} </span>
  9 + </h4>
  10 + <p><small class="text-muted"><i class="fa fa-clock-o"></i> <span am-time-ago="ctrl.activity.created_at | dateFormat"></span></small></p>
  11 + </timeline-heading>
  12 + <div class="timeline-body">
  13 + <div class="scrap">
  14 + <div ng-bind-html="ctrl.activity.content | stripTags | truncate: 100 : '...': true"></div>
  15 + </div>
  16 + </div>
  17 +</timeline-panel>
src/app/profile/config-bar.component.ts 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +import {Component, Inject, provide} from "ng-forward";
  2 +import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service";
  3 +
  4 +@Component({
  5 + selector: "configbar",
  6 + templateUrl: "app/profile/configbar.html",
  7 + providers: [
  8 + provide('profileService', { useClass: ProfileService })
  9 + ]
  10 +})
  11 +@Inject(ProfileService)
  12 +export class ConfigBarComponent {
  13 + profile: noosfero.Profile;
  14 + parentId: number;
  15 +
  16 + constructor(profileService: ProfileService) {
  17 + profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
  18 + this.profile = profile;
  19 + });
  20 + }
  21 +}
src/app/profile/configbar.html 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +<div id="config-tool" ng-class="{'closed': !showConfig}">
  2 + <a id="config-tool-cog" ng-click="showConfig = !showConfig">
  3 + <i class="fa fa-cog" ng-class="{'anim-icon': showConfig}"></i>
  4 + </a>
  5 +
  6 + <div id="config-tool-options">
  7 + <h4>{{ 'configbar.label' | translate }}</h4>
  8 + <div class="section-title">{{ 'configbar.section.layout' | translate }}</div>
  9 + <ul>
  10 + <li>
  11 + <label class="pull-left">
  12 + <i class="fa fa-th-large"></i>
  13 + <span class="item-label">{{ 'designMode.label' | translate }}</span>
  14 + </label>
  15 + <label class="pull-right">
  16 + <design-toggler></design-toggler>
  17 + </label>
  18 + </li>
  19 + </ul>
  20 + </div>
  21 +</div>
src/app/profile/configbar.scss 0 → 100644
@@ -0,0 +1,110 @@ @@ -0,0 +1,110 @@
  1 +@import "../layout/scss/mixins";
  2 +
  3 +@include keyframes(rotating) {
  4 + from {
  5 + transform: rotate(0deg);
  6 + }
  7 + to {
  8 + transform: rotate(360deg);
  9 + }
  10 +}
  11 +
  12 +/* CONFIG TOOLS */
  13 +#config-tool {
  14 + @include transition(all 0.2s ease-in-out 0s);
  15 + @include box-shadow(0px 1px 4px rgba(0, 0, 0, 0.3));
  16 + position: fixed;
  17 + right: 0;
  18 + top: 120px;
  19 + min-width: 200px;
  20 + z-index: 1000;
  21 +
  22 + #config-tool-cog {
  23 + @include border-radius($border-radius-base 0 0 $border-radius-base);
  24 + @include transition(all 0.1s ease-in-out 0s);
  25 + @include box-shadow(-3px 3px 3px -2px rgba(0, 0, 0, 0.1));
  26 + background: #fff;
  27 + cursor: pointer;
  28 + left: -50px;
  29 + padding: 10px;
  30 + position: absolute;
  31 + text-align: center;
  32 + width: 50px;
  33 + top: 0;
  34 +
  35 + i {
  36 + font-size: 2.2em;
  37 + }
  38 +
  39 + .anim-icon {
  40 + @include animation(rotating 0.7s ease-in-out 0s);
  41 + }
  42 + }
  43 +
  44 + &.closed {
  45 + right: -280px;
  46 + box-shadow: none;
  47 +
  48 + #config-tool-cog {
  49 + &:hover {
  50 + background-color: $primary-color;
  51 + color: #fff;
  52 + }
  53 + }
  54 + }
  55 +
  56 + &.opened {
  57 + right: 0;
  58 + }
  59 +
  60 + .section-title {
  61 + margin: 30px 0 5px;
  62 + font: {
  63 + size: 15px;
  64 + weight: bold;
  65 + }
  66 +
  67 + }
  68 +
  69 + #config-tool-options {
  70 + @include box-shadow(-3px 3px 3px -2px rgba(0, 0, 0, 0.1));
  71 + background: #fff;
  72 + padding: 15px;
  73 +
  74 + h4 {
  75 + margin: 0;
  76 + font-size: 1.3em;
  77 + }
  78 +
  79 + ul {
  80 + list-style: none;
  81 + border-radius: 2px;
  82 + padding: 0;
  83 +
  84 + background: {
  85 + clip: padding-box;
  86 + color: #f1f3f2;
  87 + }
  88 +
  89 + li {
  90 + padding: 10px;
  91 + width: 250px;
  92 + min-height: 50px;
  93 + font: {
  94 + size: 13px;
  95 + weight: 300;
  96 + }
  97 +
  98 + .checkbox {
  99 + margin: 0;
  100 + }
  101 +
  102 + .pull-left {
  103 + margin-top: 8px;
  104 + font-weight: normal;
  105 + }
  106 +
  107 + }
  108 + }
  109 + }
  110 +}
src/app/profile/custom-content/custom-content.component.ts
1 -import {Component, Input, Inject} from 'ng-forward';  
2 -import {ProfileService} from '../../../lib/ng-noosfero-api/http/profile.service';  
3 -import {NotificationService} from '../../shared/services/notification.service';  
4 -import {PermissionDirective} from '../../shared/components/permission/permission.directive';  
5 -import {DesignModeService} from '../../admin/layout-edit/designMode.service'; 1 +import { Component, Input, Inject } from 'ng-forward';
  2 +import { ProfileService } from '../../../lib/ng-noosfero-api/http/profile.service';
  3 +import { NotificationService } from '../../shared/services/notification.service';
  4 +import { PermissionDirective } from '../../shared/components/permission/permission.directive';
  5 +import { DesignModeService } from '../../admin/layout-edit/designMode.service';
6 6
7 @Component({ 7 @Component({
8 selector: 'custom-content', 8 selector: 'custom-content',
@@ -21,9 +21,10 @@ export class CustomContentComponent { @@ -21,9 +21,10 @@ export class CustomContentComponent {
21 21
22 content: string; 22 content: string;
23 originalContent: string; 23 originalContent: string;
24 - private modalInstance: any = null; 24 + private modalInstance: ng.ui.bootstrap.IModalServiceInstance;
25 25
26 - constructor(private $uibModal: any, 26 + constructor(
  27 + private $uibModal: ng.ui.bootstrap.IModalService,
27 private $scope: ng.IScope, 28 private $scope: ng.IScope,
28 private profileService: ProfileService, 29 private profileService: ProfileService,
29 private notificationService: NotificationService, 30 private notificationService: NotificationService,
src/app/profile/image/image.html
1 <span class="profile-image-wrap" title="{{ctrl.profile.name}}"> 1 <span class="profile-image-wrap" title="{{ctrl.profile.name}}">
2 - <img ng-if="ctrl.profile.image" ng-src="{{ctrl.profile.image.url}}" class="img-responsive profile-image">  
3 - <i ng-if="!ctrl.profile.image" class="fa {{ctrl.defaultIcon}} fa-5x profile-image"></i> 2 + <img ng-if="ctrl.profile.image" ng-src="{{ctrl.profile.image.url}}" class="">
  3 + <i ng-if="!ctrl.profile.image" class="fa {{ctrl.defaultIcon}} fa-5x profile-image img-avatar"></i>
4 </span> 4 </span>
src/app/profile/info/profile-info.component.ts
@@ -16,17 +16,23 @@ export class ProfileInfoComponent { @@ -16,17 +16,23 @@ export class ProfileInfoComponent {
16 16
17 activities: any; 17 activities: any;
18 profile: noosfero.Profile; 18 profile: noosfero.Profile;
  19 + showInformation: boolean = false;
19 20
20 constructor(private profileService: ProfileService, private amDateFormatFilter: any) { 21 constructor(private profileService: ProfileService, private amDateFormatFilter: any) {
21 this.init(); 22 this.init();
  23 + this.showInformation = false;
  24 + }
  25 +
  26 + toggleInformation() {
  27 + console.log(this.showInformation);
  28 + console.log("argila");
  29 + this.showInformation = !this.showInformation;
  30 + console.log(this.showInformation);
22 } 31 }
23 32
24 init() { 33 init() {
25 this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => { 34 this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
26 this.profile = profile; 35 this.profile = profile;
27 - return this.profileService.getActivities(<number>this.profile.id);  
28 - }).then((response: restangular.IResponse) => {  
29 - this.activities = response.data.activities;  
30 }); 36 });
31 } 37 }
32 } 38 }
src/app/profile/info/profile-info.html
1 -<div class="profile-wall"> 1 +<!-- <div class="profile-wall mainbox clearfix">
2 2
3 <div class="col-lg-3 col-md-4 col-sm-4"> 3 <div class="col-lg-3 col-md-4 col-sm-4">
4 <div class="main-box clearfix"> 4 <div class="main-box clearfix">
@@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
8 <div id="profile-left" class="main-box-body clearfix"> 8 <div id="profile-left" class="main-box-body clearfix">
9 <noosfero-profile-image [profile]="vm.profile" class="img-responsive center-block"></noosfero-profile-image> 9 <noosfero-profile-image [profile]="vm.profile" class="img-responsive center-block"></noosfero-profile-image>
10 <span class="label" ng-class="{'label-danger': vm.profile.type == 'Community', 'label-info': vm.profile.type == 'Person'}">{{vm.profile | translateProfile}}</span> 10 <span class="label" ng-class="{'label-danger': vm.profile.type == 'Community', 'label-info': vm.profile.type == 'Person'}">{{vm.profile | translateProfile}}</span>
  11 +
11 <div class="profile-since"> 12 <div class="profile-since">
12 {{"profile.member_since" | translate}}: {{vm.profile.created_at | amDateFormat:'MMMM YYYY'}} 13 {{"profile.member_since" | translate}}: {{vm.profile.created_at | amDateFormat:'MMMM YYYY'}}
13 </div> 14 </div>
@@ -27,4 +28,38 @@ @@ -27,4 +28,38 @@
27 </uib-tabset> 28 </uib-tabset>
28 </div> 29 </div>
29 </div> 30 </div>
  31 +</div> -->
  32 +
  33 +<div class="bg-image"></div>
  34 +
  35 +<div class="container" id="noosfero-profile">
  36 + <div class="col-sm-2">
  37 + <noosfero-profile-image [profile]="vm.profile" class="img-responsive center-block" data-pin-nopin="true"></noosfero-profile-image>
  38 + <div class="social-links">
  39 + <ul>
  40 + <li>Facebook</li>
  41 + <li>Linkdin</li>
  42 + </ul>
  43 + </div>
  44 + </div>
  45 + <div class="col-sm-10">
  46 + <h3>{{ vm.profile.name }}</h3>
  47 + <div ng-switch="description">
  48 + <p ng-if="vm.profile.additional_data['description']">
  49 + {{ 'profile.person.description' | translate }}: {{ vm.profile.additional_data['description'] }}
  50 + </p>
  51 + <p class="pull-left">www.noosfero.gov.br/<strong>{{ vm.profile.identifier }}</strong></p>
  52 + <div class="pull-right info-contato">
  53 + <button ng-click="vm.toggleInformation()" class="btn btn-sm">{{ 'profile.person.contact_info' | translate }}</button>
  54 + <div clas="profile-contact" ng-show="vm.showInformation">
  55 + <p class="email" ng-if="vm.profile.additional_data['contact_email']">Email: {{ vm.profile.additional_data['contact_email'] }}</p>
  56 + <p class="phone" ng-if="vm.profile.additional_data['contact_phone']">
  57 + {{ 'profile.person.contact_phone' | translate }}: {{ vm.profile.additional_data['contact_phone'] }}
  58 + </p>
  59 + <p class="phone" ng-if="(!vm.profile.additional_data['contact_phone'] && !vm.profile.additional_data['contact_email'])">
  60 + {{ 'profile.person.no_contact_info' | translate }}
  61 + </p>
  62 + </div>
  63 + </div>
  64 + </div>
30 </div> 65 </div>
src/app/profile/info/profile-info.scss 0 → 100644
src/app/profile/profile.component.ts
@@ -6,22 +6,22 @@ import {CmsComponent} from &#39;../article/cms/cms.component&#39;; @@ -6,22 +6,22 @@ import {CmsComponent} from &#39;../article/cms/cms.component&#39;;
6 import {ContentViewerComponent} from "../article/content-viewer/content-viewer.component"; 6 import {ContentViewerComponent} from "../article/content-viewer/content-viewer.component";
7 import {ContentViewerActionsComponent} from "../article/content-viewer/content-viewer-actions.component"; 7 import {ContentViewerActionsComponent} from "../article/content-viewer/content-viewer-actions.component";
8 import {ActivitiesComponent} from "./activities/activities.component"; 8 import {ActivitiesComponent} from "./activities/activities.component";
  9 +import {ActivityComponent} from "./activities/activity/activity.component";
9 import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service"; 10 import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service";
10 import {NotificationService} from "../shared/services/notification.service"; 11 import {NotificationService} from "../shared/services/notification.service";
11 import {MyProfileComponent} from "./myprofile.component"; 12 import {MyProfileComponent} from "./myprofile.component";
12 import {ProfileActionsComponent} from "./profile-actions.component"; 13 import {ProfileActionsComponent} from "./profile-actions.component";
13 -import {ProfileToolbarComponent} from "./profile-toolbar.component"; 14 +import {ConfigBarComponent} from "./config-bar.component";
14 /** 15 /**
15 * @ngdoc controller 16 * @ngdoc controller
16 - * @name profile.Profile  
17 - * @description 17 + * @name profile.Profile * @description
18 * This is the profile controller. It provide routes to supported Noosfero Profiles. 18 * This is the profile controller. It provide routes to supported Noosfero Profiles.
19 */ 19 */
20 20
21 @Component({ 21 @Component({
22 selector: 'profile', 22 selector: 'profile',
23 templateUrl: "app/profile/profile.html", 23 templateUrl: "app/profile/profile.html",
24 - directives: [ActivitiesComponent], 24 + directives: [ActivityComponent],
25 providers: [ 25 providers: [
26 provide('profileService', { useClass: ProfileService }), 26 provide('profileService', { useClass: ProfileService }),
27 provide('notificationService', { useClass: NotificationService }) 27 provide('notificationService', { useClass: NotificationService })
@@ -33,19 +33,19 @@ import {ProfileToolbarComponent} from &quot;./profile-toolbar.component&quot;; @@ -33,19 +33,19 @@ import {ProfileToolbarComponent} from &quot;./profile-toolbar.component&quot;;
33 url: "^/profile/:profile", 33 url: "^/profile/:profile",
34 component: ProfileInfoComponent, 34 component: ProfileInfoComponent,
35 views: { 35 views: {
36 - "mainBlockContent": {  
37 - templateUrl: "app/profile/info/profile-info.html",  
38 - controller: ProfileInfoComponent,  
39 - controllerAs: "vm"  
40 - },  
41 "actions@main": { 36 "actions@main": {
42 templateUrl: "app/profile/navbar-actions.html", 37 templateUrl: "app/profile/navbar-actions.html",
43 controller: ProfileActionsComponent, 38 controller: ProfileActionsComponent,
44 controllerAs: "vm" 39 controllerAs: "vm"
45 }, 40 },
46 "toolbar@main": { 41 "toolbar@main": {
47 - templateUrl: "app/profile/toolbar.html",  
48 - controller: ProfileToolbarComponent, 42 + templateUrl: "app/profile/configbar.html",
  43 + controller: ConfigBarComponent,
  44 + controllerAs: "vm"
  45 + },
  46 + "mainBlockContent": {
  47 + templateUrl: "app/profile/activities/activities.html",
  48 + controller: ProfileInfoComponent,
49 controllerAs: "vm" 49 controllerAs: "vm"
50 } 50 }
51 } 51 }
@@ -61,8 +61,8 @@ import {ProfileToolbarComponent} from &quot;./profile-toolbar.component&quot;; @@ -61,8 +61,8 @@ import {ProfileToolbarComponent} from &quot;./profile-toolbar.component&quot;;
61 controllerAs: "vm" 61 controllerAs: "vm"
62 }, 62 },
63 "toolbar@main": { 63 "toolbar@main": {
64 - templateUrl: "app/profile/toolbar.html",  
65 - controller: ProfileToolbarComponent, 64 + templateUrl: "app/profile/configbar.html",
  65 + controller: ConfigBarComponent,
66 controllerAs: "vm" 66 controllerAs: "vm"
67 } 67 }
68 } 68 }
@@ -118,8 +118,8 @@ import {ProfileToolbarComponent} from &quot;./profile-toolbar.component&quot;; @@ -118,8 +118,8 @@ import {ProfileToolbarComponent} from &quot;./profile-toolbar.component&quot;;
118 controllerAs: "vm" 118 controllerAs: "vm"
119 }, 119 },
120 "toolbar@main": { 120 "toolbar@main": {
121 - templateUrl: "app/profile/toolbar.html",  
122 - controller: ProfileToolbarComponent, 121 + templateUrl: "app/profile/configbar.html",
  122 + controller: ConfigBarComponent,
123 controllerAs: "vm" 123 controllerAs: "vm"
124 } 124 }
125 } 125 }
@@ -142,4 +142,6 @@ export class ProfileComponent { @@ -142,4 +142,6 @@ export class ProfileComponent {
142 notificationService.error({ message: "notification.profile.not_found" }); 142 notificationService.error({ message: "notification.profile.not_found" });
143 }); 143 });
144 } 144 }
  145 +
  146 +
145 } 147 }
src/app/profile/profile.html
@@ -4,10 +4,14 @@ @@ -4,10 +4,14 @@
4 [attribute]="'custom_header'" 4 [attribute]="'custom_header'"
5 [profile]="vm.profile"> 5 [profile]="vm.profile">
6 </custom-content> 6 </custom-content>
7 - <noosfero-boxes ng-if="vm.boxes"  
8 - [layout]="vm.profile.layout_template"  
9 - [boxes]="vm.boxes"  
10 - [owner]="vm.profile" class="row">  
11 - </noosfero-boxes> 7 + <div class="row" ui-view="profile-info"></div>
  8 +
  9 + <div class="wrapper-container">
  10 + <noosfero-boxes ng-if="vm.boxes"
  11 + [layout]="vm.profile.layout_template"
  12 + [boxes]="vm.boxes"
  13 + [owner]="vm.profile" class="row">
  14 + </noosfero-boxes>
  15 + </div>
12 <custom-content class="profile-footer" [label]="'profile.custom_footer.label'" [attribute]="'custom_footer'" [profile]="vm.profile"></custom-content> 16 <custom-content class="profile-footer" [label]="'profile.custom_footer.label'" [attribute]="'custom_footer'" [profile]="vm.profile"></custom-content>
13 </div> 17 </div>
src/app/profile/profile.scss
  1 +
  2 +@mixin padding-sides {
  3 + padding-right: 15px;
  4 + padding-left: 15px;
  5 +}
  6 +
  7 +@mixin profile-img {
  8 + text-align: center;
  9 + margin: 10px;
  10 + border: 1px solid #eee;
  11 +}
  12 +
  13 +@media (min-width: 768px) {
  14 + .container {max-width: 980px;}
  15 +}
  16 +
1 .profile-container { 17 .profile-container {
2 @extend .container-fluid; 18 @extend .container-fluid;
3 - padding: 0 1%;  
4 - @media (max-width: 978px) {  
5 - padding: 0 2%; 19 + margin-top: -$wrapper-padding-top;
  20 + margin-left: -$wrapper-padding-left;
  21 + margin-bottom: -$wrapper-padding-bottom;
  22 + margin-right: -$wrapper-padding-right;
  23 + @media (max-width: 978px) {padding: 0 2%;}
  24 +}
  25 +
  26 +.wrapper-container {
  27 + margin: auto;
  28 + max-width: 980px;
  29 +}
  30 +
  31 +.dropdown-menu {
  32 + >li {
  33 + margin:5px 0;
  34 + >a {
  35 + padding:3px 10px 3px 0 !important;
  36 + &:hover {background-color:transparent !important;}
  37 + }
6 } 38 }
7 } 39 }
8 40
9 -#profile-left {  
10 - text-align: center;  
11 - margin-top: 15px; 41 +.bg-image {
  42 + background-color:#eee;
  43 + height: 260px;
  44 +}
  45 +
  46 +.social-links {
  47 + ul {
  48 + list-style:none;
  49 + text-align:center;
  50 + padding:0;
  51 + margin:0;
  52 + }
  53 +}
  54 +
  55 +#noosfero-profile {
  56 + background:#fff;
  57 + margin: -200px auto auto;
  58 + z-index:9999;
  59 + left:0;
  60 + right:0;
  61 + border:1px solid #eee;
  62 + padding:20px 0;
  63 + font-size: 14px;
  64 +
  65 + img,
  66 + i {@include profile-img;}
  67 +
  68 + .col-sm-2 {text-align: center;}
  69 +
  70 + h3 {margin-top: 16px;}
  71 +
  72 + .btn {
  73 + font-size: 12px;
  74 + padding: 5px 10px;
  75 + }
  76 +
  77 + .profile-contact {
  78 + padding-top: 10px;
  79 + p {margin: 0px;}
  80 + }
12 } 81 }
src/app/shared/components/interfaces.ts
1 -  
2 -  
3 export interface IComponentWithPermissions { 1 export interface IComponentWithPermissions {
4 permissions: () => string[]; 2 permissions: () => string[];
5 -}  
6 \ No newline at end of file 3 \ No newline at end of file
  4 +}
  5 +
  6 +export interface IModalComponent {
  7 + $uibModal: ng.ui.bootstrap.IModalService;
  8 + modalInstance: ng.ui.bootstrap.IModalServiceInstance;
  9 +}
src/app/shared/services/events-hub.service.spec.ts 0 → 100644
@@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
  1 +import { OpaqueToken } from 'ng-forward';
  2 +import { EventsHubService, EventsHubKnownEventNames } from './events-hub.service';
  3 +
  4 +
  5 +describe("EventsHubService", () => {
  6 + let eventsHubService: EventsHubService;
  7 + let event1 = 'Event 1';
  8 + let eventsHubKnownEventNames = <EventsHubKnownEventNames>{ getNames: () => { return [ event1]; }};
  9 + it("emits events for the known events", (done) => {
  10 +
  11 + let eventListener = () => {
  12 + };
  13 + // creates the events hub service which known the event "Event1"
  14 + eventsHubService = new EventsHubService(eventsHubKnownEventNames);
  15 + // subscribe to the event passing the done Function as the eventListener
  16 + // if the event emits works the done function is called and the
  17 + // test will pass
  18 + eventsHubService.subscribeToEvent<any>(event1, done);
  19 + // emits the event
  20 + eventsHubService.emitEvent(event1, null);
  21 + });
  22 +
  23 + it("throws error when trying to emit an unknow event", () => {
  24 + let eventListener = () => {
  25 + };
  26 + // creates the events hub service which known the event "Event1"
  27 + eventsHubService = new EventsHubService(eventsHubKnownEventNames);
  28 +
  29 + // emits the event
  30 + expect(
  31 + () => { eventsHubService.emitEvent('NotKnownEvent', null); }
  32 + ).toThrowError('Unknown event named NotKnownEvent');
  33 + });
  34 +
  35 + it("throws error when trying to subscribe to an unknow event", () => {
  36 + let eventListener = () => {
  37 + };
  38 + // creates the events hub service which known the event "Event1"
  39 + eventsHubService = new EventsHubService(eventsHubKnownEventNames);
  40 +
  41 + // emits the event
  42 + expect(
  43 + () => { eventsHubService.subscribeToEvent<void>('NotKnownEvent', () => {}); }
  44 + ).toThrowError('Unknown event named NotKnownEvent');
  45 + });
  46 +});
0 \ No newline at end of file 47 \ No newline at end of file
src/app/shared/services/events-hub.service.ts 0 → 100644
@@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
  1 +import { Injectable, Inject, OpaqueToken, EventEmitter } from 'ng-forward';
  2 +
  3 +export const EVENTS_HUB_KNOW_EVENT_NAMES = new OpaqueToken('EVENTS_HUB_KNOW_EVENT_NAMES');
  4 +
  5 +export interface EventsHubKnownEventNames {
  6 + getNames(): string[];
  7 +}
  8 +
  9 +function isEventsHubKnownEventNames(object: any): object is EventsHubKnownEventNames {
  10 + return 'getNames' in object;
  11 +}
  12 +
  13 +@Injectable()
  14 +@Inject(EVENTS_HUB_KNOW_EVENT_NAMES)
  15 +export class EventsHubService {
  16 +
  17 + private emitters: Map<string, EventEmitter<any>>;
  18 + private knownEvents: string[] = [];
  19 +
  20 + constructor(private eventsHubKnownEventNames: EventsHubKnownEventNames | string[]) {
  21 + if (isEventsHubKnownEventNames(eventsHubKnownEventNames)) {
  22 + this.knownEvents = eventsHubKnownEventNames.getNames();
  23 + } else if (Array.isArray(eventsHubKnownEventNames)) {
  24 + this.knownEvents = eventsHubKnownEventNames;
  25 + }
  26 +
  27 + this.emitters = new Map<string, EventEmitter<any>>();
  28 + this.setupEmitters();
  29 + }
  30 +
  31 + emitEvent(eventType: string, payload?: any) {
  32 + this.checkKnownEvent(eventType);
  33 + let event = this.emitters.get(eventType);
  34 + if ( event ) this.emitters.get(eventType).next(payload);
  35 + }
  36 +
  37 + subscribeToEvent<T>(eventType: string, generatorOrNext?: ((p?: T) => void), error?: any, complete?: any) {
  38 + this.checkKnownEvent(eventType);
  39 + let event = this.emitters.get(eventType);
  40 + if (event) event.subscribe(generatorOrNext, error, complete);
  41 + }
  42 +
  43 + private setupEmitters() {
  44 + for (let i: number = 0; i < this.knownEvents.length; i++) {
  45 + this.emitters.set(this.knownEvents[i], new EventEmitter<any>());
  46 + }
  47 + }
  48 +
  49 + private checkKnownEvent(eventType: string) {
  50 + if (!this.emitters.has(eventType)) {
  51 + throw new Error('Unknown event named ' + eventType.toString());
  52 + }
  53 + }
  54 +
  55 +
  56 +}
0 \ No newline at end of file 57 \ No newline at end of file
src/languages/en.json
1 { 1 {
2 "noosfero.name" : "Noosfero", 2 "noosfero.name" : "Noosfero",
3 "blocks.profile_image.control_panel": "Control Panel", 3 "blocks.profile_image.control_panel": "Control Panel",
  4 + "blocks.profile_image.join": "Join community",
  5 + "blocks.profile_image.leave": "Leave community",
  6 + "blocks.profile_image.join.moderation.title": "Good job!",
  7 + "blocks.profile_image.join.moderation.message": "Your membership is waiting for approval",
4 "navbar.profile": "Profile", 8 "navbar.profile": "Profile",
5 "navbar.settings": "Settings", 9 "navbar.settings": "Settings",
6 "navbar.logout": "Log Out", 10 "navbar.logout": "Log Out",
7 "navbar.login": "Login", 11 "navbar.login": "Login",
8 "navbar.toggle_menu": "Toggle navigation", 12 "navbar.toggle_menu": "Toggle navigation",
  13 + "configbar.label": "Configuration",
  14 + "configbar.section.layout": "Layout",
9 "language.all": "All languages", 15 "language.all": "All languages",
10 "language.en": "English", 16 "language.en": "English",
11 "language.pt": "Portuguese", 17 "language.pt": "Portuguese",
@@ -17,8 +23,13 @@ @@ -17,8 +23,13 @@
17 "profile.others_info": "Others", 23 "profile.others_info": "Others",
18 "profile.community.title": "Community", 24 "profile.community.title": "Community",
19 "profile.person.title": "Person", 25 "profile.person.title": "Person",
  26 + "profile.person.description": "Description",
  27 + "profile.person.contact_info": "Contact Informations",
  28 + "profile.person.contact_phone": "Phone",
  29 + "profile.person.no_contact_info": "No contact informations",
20 "activities.title": "Activities", 30 "activities.title": "Activities",
21 "activities.create_article.description": "has published on", 31 "activities.create_article.description": "has published on",
  32 + "activities.scrap.description": "wrote in its timeline",
22 "activities.add_member_in_community.description": "has joined the community", 33 "activities.add_member_in_community.description": "has joined the community",
23 "activities.new_friendship.description": "has made {friends, plural, one{one new friend} other{# new friends}}:", 34 "activities.new_friendship.description": "has made {friends, plural, one{one new friend} other{# new friends}}:",
24 "auth.title": "Great to have you back!", 35 "auth.title": "Great to have you back!",
@@ -26,6 +37,7 @@ @@ -26,6 +37,7 @@
26 "auth.form.password": "Password", 37 "auth.form.password": "Password",
27 "auth.form.keepLoggedIn": "Keep me logged in", 38 "auth.form.keepLoggedIn": "Keep me logged in",
28 "auth.form.login_button": "Login", 39 "auth.form.login_button": "Login",
  40 + "auth.createAccount": "Create account",
29 "navbar.content_viewer_actions.new_item": "New Item", 41 "navbar.content_viewer_actions.new_item": "New Item",
30 "navbar.profile_actions.new_item": "New Item", 42 "navbar.profile_actions.new_item": "New Item",
31 "navbar.content_viewer_actions.new_post": "New Post", 43 "navbar.content_viewer_actions.new_post": "New Post",
@@ -77,9 +89,7 @@ @@ -77,9 +89,7 @@
77 "custom_content.title": "Edit content", 89 "custom_content.title": "Edit content",
78 "profile.custom_header.label": "Header", 90 "profile.custom_header.label": "Header",
79 "profile.custom_footer.label": "Footer", 91 "profile.custom_footer.label": "Footer",
80 - "designMode.label": "In Design",  
81 - "designMode.toggle.ON": "ON",  
82 - "designMode.toggle.OFF": "OFF", 92 + "designMode.label": "Design mode",
83 "search.results.summary": "{results, plural, one{result} other{# results}}", 93 "search.results.summary": "{results, plural, one{result} other{# results}}",
84 "search.results.query.label": "Search therm:", 94 "search.results.query.label": "Search therm:",
85 "search.label": "Search", 95 "search.label": "Search",
@@ -97,5 +107,26 @@ @@ -97,5 +107,26 @@
97 "block.edition.display_user.all": "All users", 107 "block.edition.display_user.all": "All users",
98 "block.edition.display_user.logged": "Logged", 108 "block.edition.display_user.logged": "Logged",
99 "block.edition.display_user.not_logged": "Not logged", 109 "block.edition.display_user.not_logged": "Not logged",
100 - "block.edition.language.label": "Show for:" 110 + "block.edition.language.label": "Show for:",
  111 + "activities.event.description": "Event on",
  112 + "time.at": "at",
  113 + "date.on": "On",
  114 + "account.register.welcomeMessageTitle": "Nice to have you there!",
  115 + "account.register.fullNameLabel": "Full name",
  116 + "account.register.usernameLabel": "Username",
  117 + "account.register.emailLabel": "Email",
  118 + "account.register.passwordLabel": "Password",
  119 + "account.register.passwordConfirmationLabel": "Password confirmation",
  120 + "account.register.accountCreatingMessage": "By creating an account, you are agreeing with the ",
  121 + "account.register.signupMessage": "Register",
  122 + "account.register.haveAccountMessage": "Already have an account?",
  123 + "account.register.termsOfUseMessage": "terms of use",
  124 + "account.register.success.title": "Good job!",
  125 + "account.register.success.message": "Account created!",
  126 + "account.register.passwordConfirmation.failed": "Wrong password confirmation",
  127 + "messages.invalid.required": "This field is required",
  128 + "messages.invalid.maxlength": "This field is too long",
  129 + "messages.invalid.minlength": "This field is too short",
  130 + "messages.invalid.email": "This needs to be a valid email",
  131 + "messages.invalid.passwordMatch": "Your passwords did not match"
101 } 132 }
src/languages/messages.html 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +<li ng-message="required" translate="messages.invalid.required"></li>
  2 +<li ng-message="minlength" translate="messages.invalid.minlength"></li>
  3 +<li ng-message="maxlength" translate="messages.invalid.maxlength"></li>
  4 +<li ng-message="email" translate="messages.invalid.email"></li>
src/languages/pt.json
1 { 1 {
2 "noosfero.name" : "Noosfero", 2 "noosfero.name" : "Noosfero",
3 "blocks.profile_image.control_panel": "Painel de Controle", 3 "blocks.profile_image.control_panel": "Painel de Controle",
  4 + "blocks.profile_image.join": "Entrar na comunidade",
  5 + "blocks.profile_image.leave": "Sair da comunidade",
  6 + "blocks.profile_image.join.moderation.title": "Bom trabalho!",
  7 + "blocks.profile_image.join.moderation.message": "Sua participação está pendente de aprovação",
4 "navbar.profile": "Perfil", 8 "navbar.profile": "Perfil",
5 "navbar.settings": "Configurações", 9 "navbar.settings": "Configurações",
6 "navbar.logout": "Sair", 10 "navbar.logout": "Sair",
7 "navbar.login": "Login", 11 "navbar.login": "Login",
8 "navbar.toggle_menu": "Abrir Menu", 12 "navbar.toggle_menu": "Abrir Menu",
  13 + "configbar.label": "Configurações",
  14 + "configbar.section.layout": "Visual",
9 "language.all": "Todos os idiomas", 15 "language.all": "Todos os idiomas",
10 "language.en": "Inglês", 16 "language.en": "Inglês",
11 "language.pt": "Português", 17 "language.pt": "Português",
@@ -17,8 +23,13 @@ @@ -17,8 +23,13 @@
17 "profile.others_info": "Outras informações", 23 "profile.others_info": "Outras informações",
18 "profile.community.title": "Comunidade", 24 "profile.community.title": "Comunidade",
19 "profile.person.title": "Pessoa", 25 "profile.person.title": "Pessoa",
  26 + "profile.person.description": "Descrição",
  27 + "profile.person.contact_info": "Informações de Contato",
  28 + "profile.person.contact_phone": "Telefone",
  29 + "profile.person.no_contact_info": "Sem informações de contato",
20 "activities.title": "Atividades", 30 "activities.title": "Atividades",
21 "activities.create_article.description": "publicou em", 31 "activities.create_article.description": "publicou em",
  32 + "activities.scrap.description": "escreveu em sua linha do tempo",
22 "activities.add_member_in_community.description": "entrou na comunidade", 33 "activities.add_member_in_community.description": "entrou na comunidade",
23 "activities.new_friendship.description": "fez {friends, plural, one{um novo amigo} other{# novos amigos}}:", 34 "activities.new_friendship.description": "fez {friends, plural, one{um novo amigo} other{# novos amigos}}:",
24 "auth.title": "Legal ter você de volta!", 35 "auth.title": "Legal ter você de volta!",
@@ -26,10 +37,11 @@ @@ -26,10 +37,11 @@
26 "auth.form.password": "Senha", 37 "auth.form.password": "Senha",
27 "auth.form.keepLoggedIn": "Continuar logado", 38 "auth.form.keepLoggedIn": "Continuar logado",
28 "auth.form.login_button": "Login", 39 "auth.form.login_button": "Login",
  40 + "auth.createAccount": "Criar conta",
29 "navbar.content_viewer_actions.new_item": "Novo Item", 41 "navbar.content_viewer_actions.new_item": "Novo Item",
30 "navbar.profile_actions.new_item": "Novo Item", 42 "navbar.profile_actions.new_item": "Novo Item",
31 - "navbar.content_viewer_actions.new_post": "Novo Artigo",  
32 - "navbar.content_viewer_actions.new_discussion": "Nova Discussão", 43 + "navbar.content_viewer_actions.new_post": "Novo Post",
  44 + "navbar.content_viewer_actions.new_discussion": "Nova Consulta",
33 "navbar.profile_actions.new_discussion": "Nova Discussão", 45 "navbar.profile_actions.new_discussion": "Nova Discussão",
34 "notification.error.default.message": "Algo deu errado!", 46 "notification.error.default.message": "Algo deu errado!",
35 "notification.error.default.title": "Oops...", 47 "notification.error.default.title": "Oops...",
@@ -77,9 +89,7 @@ @@ -77,9 +89,7 @@
77 "custom_content.title": "Editar conteúdo", 89 "custom_content.title": "Editar conteúdo",
78 "profile.custom_header.label": "Cabeçalho", 90 "profile.custom_header.label": "Cabeçalho",
79 "profile.custom_footer.label": "Rodapé", 91 "profile.custom_footer.label": "Rodapé",
80 - "designMode.label": "Modo de Edição",  
81 - "designMode.toggle.ON": "Ligado",  
82 - "designMode.toggle.OFF": "Desligado", 92 + "designMode.label": "Alterar design",
83 "search.results.summary": "{results, plural, one{# resultado} other{# resultados}}", 93 "search.results.summary": "{results, plural, one{# resultado} other{# resultados}}",
84 "search.results.query.label": "Termo da busca:", 94 "search.results.query.label": "Termo da busca:",
85 "search.label": "Pesquisar", 95 "search.label": "Pesquisar",
@@ -97,5 +107,29 @@ @@ -97,5 +107,29 @@
97 "block.edition.display_user.all": "Todos os usuários", 107 "block.edition.display_user.all": "Todos os usuários",
98 "block.edition.display_user.logged": "Logados", 108 "block.edition.display_user.logged": "Logados",
99 "block.edition.display_user.not_logged": "Não logados", 109 "block.edition.display_user.not_logged": "Não logados",
100 - "block.edition.language.label": "Exibir para:" 110 + "block.edition.language.label": "Exibir para:",
  111 + "activities.event.description": "Evento em",
  112 + "time.at": "às",
  113 + "date.on": "Em",
  114 + "account.register.welcomeMessageTitle": "Ótimo ter você aqui!",
  115 + "account.register.seeMoreMessage": "Saiba mais ",
  116 + "account.register.informationsMessage": "informações",
  117 + "account.register.fullNameLabel": "Nome completo",
  118 + "account.register.lastNameLabel": "Sobrenome",
  119 + "account.register.usernameLabel": "Nome de usuário",
  120 + "account.register.emailLabel": "Email",
  121 + "account.register.passwordLabel": "Senha",
  122 + "account.register.passwordConfirmationLabel": "Confirmar senha",
  123 + "account.register.accountCreatingMessage": "Ao criar uma conta, você está concordando com os ",
  124 + "account.register.termsOfUseMessage": "termos de uso",
  125 + "account.register.signupMessage": "Criar Conta",
  126 + "account.register.haveAccountMessage": "Já possui uma conta?",
  127 + "account.register.success.title": "Bom trabalho!",
  128 + "account.register.success.message": "Conta criada com sucesso!",
  129 + "account.register.passwordConfirmation.failed": "A confirmação de senha não corresponde à senha",
  130 + "messages.invalid.required": "Campo obrigatório",
  131 + "messages.invalid.maxlength": "O valor é muito longo",
  132 + "messages.invalid.minlength": "O valor é muito curto",
  133 + "messages.invalid.email": "Informe um email válido",
  134 + "messages.invalid.passwordMatch": "As senhas não coincidem"
101 } 135 }
src/lib/ng-noosfero-api/http/article.service.spec.ts
@@ -17,7 +17,6 @@ describe(&quot;Services&quot;, () =&gt; { @@ -17,7 +17,6 @@ describe(&quot;Services&quot;, () =&gt; {
17 articleService = _ArticleService_; 17 articleService = _ArticleService_;
18 })); 18 }));
19 19
20 -  
21 describe("Succesfull requests", () => { 20 describe("Succesfull requests", () => {
22 21
23 it("should remove article", (done) => { 22 it("should remove article", (done) => {
@@ -92,6 +91,5 @@ describe(&quot;Services&quot;, () =&gt; { @@ -92,6 +91,5 @@ describe(&quot;Services&quot;, () =&gt; {
92 }); 91 });
93 }); 92 });
94 93
95 -  
96 }); 94 });
97 }); 95 });
src/lib/ng-noosfero-api/http/profile.service.spec.ts
@@ -111,6 +111,56 @@ describe(&quot;Services&quot;, () =&gt; { @@ -111,6 +111,56 @@ describe(&quot;Services&quot;, () =&gt; {
111 }); 111 });
112 $httpBackend.flush(); 112 $httpBackend.flush();
113 }); 113 });
  114 +
  115 + it("should return the profile members", (done) => {
  116 + let profileId = 1;
  117 + $httpBackend.expectGET(`/api/v1/profiles/${profileId}/members`).respond(200, { people: [{ id: 2 }] });
  118 + profileService.getMembers(<any>{ id: profileId }).then((response: restangular.IResponse) => {
  119 + expect(response.data.people).toEqual([{ id: 2 }]);
  120 + done();
  121 + });
  122 + $httpBackend.flush();
  123 + });
  124 +
  125 + it("should return true if the person is a profile member", (done) => {
  126 + let profileId = 1;
  127 + $httpBackend.expectGET(`/api/v1/profiles/${profileId}/members`).respond(200, { people: [{ id: 2 }] });
  128 + profileService.isMember(<any>{ id: 2 }, <any>{ id: profileId }).then((response: restangular.IResponse) => {
  129 + expect(response).toEqual(true);
  130 + done();
  131 + });
  132 + $httpBackend.flush();
  133 + });
  134 +
  135 + it("should return false if the person is a profile member", (done) => {
  136 + let profileId = 1;
  137 + $httpBackend.expectGET(`/api/v1/profiles/${profileId}/members`).respond(200, { people: [] });
  138 + profileService.isMember(<any>{ id: 2 }, <any>{ id: profileId }).then((response: restangular.IResponse) => {
  139 + expect(response).toEqual(false);
  140 + done();
  141 + });
  142 + $httpBackend.flush();
  143 + });
  144 +
  145 + it("should add member to profile", (done) => {
  146 + let profileId = 1;
  147 + $httpBackend.expectPOST(`/api/v1/profiles/${profileId}/members`).respond(200, { pending: false });
  148 + profileService.addMember(<any>{ id: 2 }, <any>{ id: profileId }).then((response: restangular.IResponse) => {
  149 + expect(response.data.pending).toEqual(false);
  150 + done();
  151 + });
  152 + $httpBackend.flush();
  153 + });
  154 +
  155 + it("should remove member from profile", (done) => {
  156 + let profileId = 1;
  157 + $httpBackend.expectDELETE(`/api/v1/profiles/${profileId}/members`).respond(200, { person: { id: 2 } });
  158 + profileService.removeMember(<any>{ id: 2 }, <any>{ id: profileId }).then((response: restangular.IResponse) => {
  159 + expect(response.data.person).toEqual({ id: 2 });
  160 + done();
  161 + });
  162 + $httpBackend.flush();
  163 + });
114 }); 164 });
115 165
116 166
src/lib/ng-noosfero-api/http/profile.service.ts
@@ -64,4 +64,29 @@ export class ProfileService { @@ -64,4 +64,29 @@ export class ProfileService {
64 let headers = { 'Content-Type': 'application/json' }; 64 let headers = { 'Content-Type': 'application/json' };
65 return this.get(profile.id).customPOST({ profile: profile }, null, null, headers); 65 return this.get(profile.id).customPOST({ profile: profile }, null, null, headers);
66 } 66 }
  67 +
  68 + getMembers(profile: noosfero.Profile, params?: any) {
  69 + let p = this.get(profile.id);
  70 + return p.customGET('members', params);
  71 + }
  72 +
  73 + isMember(person: noosfero.Person, profile: noosfero.Profile) {
  74 + let deferred = this.$q.defer();
  75 + if (person) {
  76 + this.getMembers(profile, { identifier: person.identifier }).then((result: any) => {
  77 + deferred.resolve(result.data.people.length > 0);
  78 + });
  79 + } else {
  80 + deferred.resolve(false);
  81 + }
  82 + return deferred.promise;
  83 + }
  84 +
  85 + addMember(person: noosfero.Person, profile: noosfero.Profile) {
  86 + return this.get(profile.id).customPOST({}, "members", null, null);
  87 + }
  88 +
  89 + removeMember(person: noosfero.Person, profile: noosfero.Profile) {
  90 + return this.get(profile.id).customDELETE("members", null, null);
  91 + }
67 } 92 }
src/lib/ng-noosfero-api/http/register.service.spec.ts 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +import { RegisterService } from "./register.service";
  2 +
  3 +describe("Services", () => {
  4 +
  5 + describe("Register Service", () => {
  6 +
  7 + let $httpBackend: ng.IHttpBackendService;
  8 + let registerService: RegisterService;
  9 + let $rootScope: ng.IRootScopeService;
  10 + let user: any = {
  11 + id: 1,
  12 + login: 'test',
  13 + email: 'test@email.com'
  14 + };
  15 +
  16 + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => {
  17 + $translateProvider.translations('en', {});
  18 + }));
  19 +
  20 + beforeEach(inject((_$httpBackend_: ng.IHttpBackendService, _RegisterService_: RegisterService, _$rootScope_: ng.IRootScopeService) => {
  21 + $httpBackend = _$httpBackend_;
  22 + registerService = _RegisterService_;
  23 + $rootScope = _$rootScope_;
  24 + }));
  25 +
  26 + describe("Succesfull requests", () => {
  27 +
  28 + it("should creaet a new account", (done) => {
  29 +
  30 + $httpBackend.expectPOST(`/api/v1/register?email=${user.email}&id=${user.id}&login=${user.login}`).respond(201, [{ login: "test" }]);
  31 + registerService.createAccount(user).then((response: restangular.IResponse) => {
  32 + expect(response.data[0].login).toEqual("test");
  33 + done();
  34 + });
  35 + $httpBackend.flush();
  36 + });
  37 + });
  38 + });
  39 +});
src/lib/ng-noosfero-api/http/register.service.ts 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +import { Injectable, Inject } from "ng-forward";
  2 +import { RestangularService } from "./restangular_service";
  3 +
  4 +@Injectable()
  5 +@Inject("Restangular")
  6 +export class RegisterService {
  7 + constructor(private Restangular: restangular.IService) {
  8 + this.Restangular = Restangular;
  9 + }
  10 +
  11 + createAccount(user: noosfero.User): ng.IPromise<noosfero.RestResult<noosfero.User>> {
  12 + return this.Restangular.all("").customPOST(user, "register", user);
  13 + }
  14 +}
src/lib/ng-noosfero-api/http/restangular_service.ts
@@ -80,14 +80,16 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -80,14 +80,16 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
80 } 80 }
81 return { 81 return {
82 data: (response.data[dataKey] || response.data), 82 data: (response.data[dataKey] || response.data),
83 - headers: response.headers 83 + headers: response.headers,
  84 + status: response.status
84 }; 85 };
85 }; 86 };
86 87
87 protected buildResult(response: restangular.IResponse): noosfero.RestResult<T> { 88 protected buildResult(response: restangular.IResponse): noosfero.RestResult<T> {
88 return { 89 return {
89 data: response.data, 90 data: response.data,
90 - headers: response.headers 91 + headers: response.headers,
  92 + status: response.status
91 }; 93 };
92 }; 94 };
93 /** 95 /**
src/lib/ng-noosfero-api/interfaces/environment.ts
@@ -5,7 +5,7 @@ namespace noosfero { @@ -5,7 +5,7 @@ namespace noosfero {
5 * @name noofero.Environment 5 * @name noofero.Environment
6 * @description 6 * @description
7 * A representation of a Noosfero Environment. 7 * A representation of a Noosfero Environment.
8 - */ 8 + */
9 export interface Environment extends RestModel { 9 export interface Environment extends RestModel {
10 /** 10 /**
11 * @ngdoc property 11 * @ngdoc property
@@ -23,6 +23,21 @@ namespace noosfero { @@ -23,6 +23,21 @@ namespace noosfero {
23 * @returns {string} The Environment layout (e.g. default, rightbar) 23 * @returns {string} The Environment layout (e.g. default, rightbar)
24 */ 24 */
25 layout_template: string; 25 layout_template: string;
  26 +
  27 + /**
  28 + * @ngdoc property
  29 + * @name signup_intro
  30 + * @propertyOf noofero.Environment
  31 + * @returns {string} The Environment signup introduction HTML (e.g. Welcome to Noosfero...!!)
  32 + */
  33 + signup_intro: string;
  34 +
  35 + /**
  36 + * @ngdoc property
  37 + * @name host
  38 + * @propertyOf noofero.Environment
  39 + * @returns {string} The Environment default domain address with 'http://' prefix (e.g. http://localhost)
  40 + */
  41 + host: string;
26 } 42 }
27 } 43 }
28 -  
src/lib/ng-noosfero-api/interfaces/rest_result.ts
@@ -3,5 +3,6 @@ namespace noosfero { @@ -3,5 +3,6 @@ namespace noosfero {
3 export interface RestResult<T> { 3 export interface RestResult<T> {
4 data: T; 4 data: T;
5 headers: Function; 5 headers: Function;
  6 + status: Number;
6 } 7 }
7 -}  
8 \ No newline at end of file 8 \ No newline at end of file
  9 +}
src/spec/mocks.ts
@@ -40,6 +40,11 @@ export var mocks: any = { @@ -40,6 +40,11 @@ export var mocks: any = {
40 return this.modalInstance; 40 return this.modalInstance;
41 } 41 }
42 }, 42 },
  43 + registerService: {
  44 + createAccount: (user: noosfero.User) => {
  45 + return Promise.resolve({ status: 201 });
  46 + }
  47 + },
43 authService: { 48 authService: {
44 loginSuccess: { 49 loginSuccess: {
45 event: Function, 50 event: Function,
@@ -145,6 +150,15 @@ export var mocks: any = { @@ -145,6 +150,15 @@ export var mocks: any = {
145 return mocks.promiseResultTemplate({ 150 return mocks.promiseResultTemplate({
146 people: {} 151 people: {}
147 }); 152 });
  153 + },
  154 + getCurrentEnvironment: (): any => {
  155 + return {
  156 + id: 1,
  157 + settings: {},
  158 + layout_template: '',
  159 + signup_intro: 'Welcome to Noosfero',
  160 + host: 'http://localhost'
  161 + };
148 } 162 }
149 }, 163 },
150 profileService: { 164 profileService: {
@@ -240,6 +254,7 @@ export var mocks: any = { @@ -240,6 +254,7 @@ export var mocks: any = {
240 }, 254 },
241 notificationService: { 255 notificationService: {
242 success: () => { }, 256 success: () => { },
243 - confirmation: () => { } 257 + confirmation: () => { },
  258 + error: () => { }
244 } 259 }
245 }; 260 };
themes/angular-participa-consulta/app/layout/scss/skins/_yellow.scss
  1 +$yellow-hover: #f5b025;
  2 +$yellow-base: #f9c404;
  3 +
1 .skin-yellow { 4 .skin-yellow {
2 @extend %skin-base; 5 @extend %skin-base;
3 6
@@ -49,7 +52,7 @@ @@ -49,7 +52,7 @@
49 52
50 .container-fluid .navbar-header .navbar-toggle { 53 .container-fluid .navbar-header .navbar-toggle {
51 &:hover, &:focus { 54 &:hover, &:focus {
52 - background-color: #f5b025; 55 + background-color: $yellow-hover;
53 } 56 }
54 } 57 }
55 58
@@ -61,4 +64,26 @@ @@ -61,4 +64,26 @@
61 } 64 }
62 } 65 }
63 66
  67 + #config-tool {
  68 +
  69 + #config-tool-cog {
  70 + color: $yellow-hover;
  71 + }
  72 +
  73 + #config-tool-options {
  74 + h4 {
  75 + color: #2c3e50;
  76 + font-weight: bold;
  77 + }
  78 + }
  79 +
  80 + &.closed {
  81 + #config-tool-cog {
  82 + &:hover {
  83 + background-color: $yellow-hover;
  84 + }
  85 + }
  86 + }
  87 + }
  88 +
64 } 89 }
@@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
8 "angular-mocks": "github:DefinitelyTyped/DefinitelyTyped/angularjs/angular-mocks.d.ts", 8 "angular-mocks": "github:DefinitelyTyped/DefinitelyTyped/angularjs/angular-mocks.d.ts",
9 "angular-translate": "github:DefinitelyTyped/DefinitelyTyped/angular-translate/angular-translate.d.ts#19850bf86c876e0c2544842114878ece4664941a", 9 "angular-translate": "github:DefinitelyTyped/DefinitelyTyped/angular-translate/angular-translate.d.ts#19850bf86c876e0c2544842114878ece4664941a",
10 "angular-ui-router": "github:DefinitelyTyped/DefinitelyTyped/angular-ui-router/angular-ui-router.d.ts#655f8c1bf3c71b0e1ba415b36309604f79326ac8", 10 "angular-ui-router": "github:DefinitelyTyped/DefinitelyTyped/angular-ui-router/angular-ui-router.d.ts#655f8c1bf3c71b0e1ba415b36309604f79326ac8",
  11 + "angular-ui-bootstrap": "github:DefinitelyTyped/DefinitelyTyped/angular-ui-bootstrap/angular-ui-bootstrap.d.ts",
11 "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c", 12 "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c",
12 "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#dd638012d63e069f2c99d06ef4dcc9616a943ee4", 13 "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#dd638012d63e069f2c99d06ef4dcc9616a943ee4",
13 "jquery": "github:DefinitelyTyped/DefinitelyTyped/jquery/jquery.d.ts#470954c4f427e0805a2d633636a7c6aa7170def8", 14 "jquery": "github:DefinitelyTyped/DefinitelyTyped/jquery/jquery.d.ts#470954c4f427e0805a2d633636a7c6aa7170def8",