Commit 97800d27bff11a6c13b8790c74945b1b13d28483

Authored by Leandro Santos
2 parents 58332ade f3cb9503
Exists in staging

Merge branch 'master' into staging

Showing 46 changed files with 1042 additions and 96 deletions   Show diff stats
@@ -39,7 +39,8 @@ @@ -39,7 +39,8 @@
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-tag-cloud": "^0.3.0", 41 "angular-tag-cloud": "^0.3.0",
42 - "angular-ui-switch": "^0.1.1" 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/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/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/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/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 { ProfileImagesPluginProfileImagesBlockComponent } from "../layout/blocks/profile-images-plugin-profile-images/profile-images-plugin-profile-images-block.component";
  25 +import { RegisterComponent } from "../account/register.component";
  26 +
  27 +import { MembersBlockComponent } from "../layout/blocks/members/members-block.component";
  28 +import { CommunitiesBlockComponent } from "../layout/blocks/communities/communities-block.component";
  29 +
  30 +import { LoginBlockComponent } from "../layout/blocks/login-block/login-block.component";
  31 +
  32 +import { NoosferoTemplate } from "../shared/pipes/noosfero-template.filter";
  33 +import { DateFormat } from "../shared/pipes/date-format.filter";
  34 +
  35 +import { AuthService } from "../login/auth.service";
  36 +import { SessionService } from "../login/session.service";
  37 +import { EnvironmentService } from "./../../lib/ng-noosfero-api/http/environment.service";
  38 +import { NotificationService } from "../shared/services/notification.service";
  39 +import { RegisterService } from "./../../lib/ng-noosfero-api/http/register.service";
  40 +
  41 +import { BodyStateClassesService } from "./../layout/services/body-state-classes.service";
  42 +
  43 +import { Navbar } from "../layout/navbar/navbar";
  44 +
  45 +import { SidebarComponent } from "../layout/sidebar/sidebar.component";
  46 +
  47 +import { MainBlockComponent } from "../layout/blocks/main/main-block.component";
  48 +import { HtmlEditorComponent } from "../shared/components/html-editor/html-editor.component";
  49 +import { PermissionDirective } from "../shared/components/permission/permission.directive";
  50 +import { SearchComponent } from "../search/search.component";
  51 +import { SearchFormComponent } from "../search/search-form/search-form.component";
  52 +
  53 +import { EVENTS_HUB_KNOW_EVENT_NAMES, EventsHubService } from "../shared/services/events-hub.service";
  54 +import { NoosferoKnownEvents } from "../known-events";
50 /** 55 /**
51 * @ngdoc controller 56 * @ngdoc controller
52 * @name main.MainContentComponent 57 * @name main.MainContentComponent
@@ -62,12 +67,16 @@ import {SearchFormComponent} from &quot;../search/search-form/search-form.component&quot;; @@ -62,12 +67,16 @@ import {SearchFormComponent} from &quot;../search/search-form/search-form.component&quot;;
62 templateUrl: "app/main/main.html", 67 templateUrl: "app/main/main.html",
63 providers: [AuthService, SessionService] 68 providers: [AuthService, SessionService]
64 }) 69 })
65 -@Inject(BodyStateClassesService) 70 +@Inject(BodyStateClassesService, EVENTS_HUB_KNOW_EVENT_NAMES)
66 export class MainContentComponent { 71 export class MainContentComponent {
67 72
68 public themeSkin: string = 'skin-whbl'; 73 public themeSkin: string = 'skin-whbl';
69 74
70 - constructor(private bodyStateClassesService: BodyStateClassesService) { 75 + constructor(
  76 + private bodyStateClassesService: BodyStateClassesService,
  77 + eventsNames: NoosferoKnownEvents,
  78 + eventsHubService: EventsHubService
  79 + ) {
71 bodyStateClassesService.start({ 80 bodyStateClassesService.start({
72 skin: this.themeSkin 81 skin: this.themeSkin
73 }); 82 });
@@ -88,7 +97,8 @@ export class EnvironmentContent { @@ -88,7 +97,8 @@ export class EnvironmentContent {
88 * @name main.Main 97 * @name main.Main
89 * @requires AuthService, Session, Notification, ArticleBlog, ArticleView, Boxes, Block, LinkListBlock, 98 * @requires AuthService, Session, Notification, ArticleBlog, ArticleView, Boxes, Block, LinkListBlock,
90 * MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock, 99 * MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock,
91 - * NoosferoTemplate, DateFormat, RawHTMLBlock, PersonTagsPluginInterestsBlock, RecentActivitiesPluginActivitiesBlock, 100 + * NoosferoTemplate, DateFormat, RawHTMLBlock, PersonTagsPluginInterestsBlock,
  101 + * RecentActivitiesPluginActivitiesBlock, ProfileImagesPluginProfileImages
92 * @description 102 * @description
93 * The Main controller for the Noosfero Angular Theme application. 103 * The Main controller for the Noosfero Angular Theme application.
94 * 104 *
@@ -107,16 +117,17 @@ export class EnvironmentContent { @@ -107,16 +117,17 @@ export class EnvironmentContent {
107 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, 117 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
108 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, 118 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
109 LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent, 119 LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent,
110 - PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, BlockComponent 120 + PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent,
  121 + ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent
111 ].concat(plugins.mainComponents).concat(plugins.hotspots), 122 ].concat(plugins.mainComponents).concat(plugins.hotspots),
112 - providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, 123 + providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, RegisterService,
113 "ngAnimate", "ngCookies", "ngStorage", "ngTouch", 124 "ngAnimate", "ngCookies", "ngStorage", "ngTouch",
114 "ngSanitize", "ngMessages", "ngAria", "restangular", 125 "ngSanitize", "ngMessages", "ngAria", "restangular",
115 "ui.router", "ui.bootstrap", "toastr", "ngCkeditor", 126 "ui.router", "ui.bootstrap", "toastr", "ngCkeditor",
116 "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid", 127 "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid",
117 "angular-timeline", "duScroll", "oitozero.ngSweetAlert", 128 "angular-timeline", "duScroll", "oitozero.ngSweetAlert",
118 "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad", 129 "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad",
119 - "angular-click-outside", "ngTagCloud", "noosfero.init", "uiSwitch"] 130 + "angular-click-outside", "ngTagCloud", "noosfero.init", "uiSwitch", "ngPassword"]
120 }) 131 })
121 @StateConfig([ 132 @StateConfig([
122 { 133 {
@@ -137,7 +148,6 @@ export class EnvironmentContent { @@ -137,7 +148,6 @@ export class EnvironmentContent {
137 url: '/', 148 url: '/',
138 component: EnvironmentComponent, 149 component: EnvironmentComponent,
139 name: 'main.environment', 150 name: 'main.environment',
140 - abstract: true,  
141 views: { 151 views: {
142 "content": { 152 "content": {
143 templateUrl: "app/environment/environment.html", 153 templateUrl: "app/environment/environment.html",
@@ -147,6 +157,18 @@ export class EnvironmentContent { @@ -147,6 +157,18 @@ export class EnvironmentContent {
147 } 157 }
148 }, 158 },
149 { 159 {
  160 + url: '/account/signup',
  161 + component: RegisterComponent,
  162 + name: 'main.register',
  163 + views: {
  164 + "content": {
  165 + templateUrl: "app/account/register.html",
  166 + controller: RegisterComponent,
  167 + controllerAs: "vm"
  168 + }
  169 + }
  170 + },
  171 + {
150 url: "^/:profile", 172 url: "^/:profile",
151 abstract: true, 173 abstract: true,
152 component: ProfileComponent, 174 component: ProfileComponent,
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/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/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",
@@ -21,6 +25,7 @@ @@ -21,6 +25,7 @@
21 "profile.person.title": "Person", 25 "profile.person.title": "Person",
22 "activities.title": "Activities", 26 "activities.title": "Activities",
23 "activities.create_article.description": "has published on", 27 "activities.create_article.description": "has published on",
  28 + "activities.scrap.description": "wrote in its timeline",
24 "activities.add_member_in_community.description": "has joined the community", 29 "activities.add_member_in_community.description": "has joined the community",
25 "activities.new_friendship.description": "has made {friends, plural, one{one new friend} other{# new friends}}:", 30 "activities.new_friendship.description": "has made {friends, plural, one{one new friend} other{# new friends}}:",
26 "auth.title": "Great to have you back!", 31 "auth.title": "Great to have you back!",
@@ -28,6 +33,7 @@ @@ -28,6 +33,7 @@
28 "auth.form.password": "Password", 33 "auth.form.password": "Password",
29 "auth.form.keepLoggedIn": "Keep me logged in", 34 "auth.form.keepLoggedIn": "Keep me logged in",
30 "auth.form.login_button": "Login", 35 "auth.form.login_button": "Login",
  36 + "auth.createAccount": "Create account",
31 "navbar.content_viewer_actions.new_item": "New Item", 37 "navbar.content_viewer_actions.new_item": "New Item",
32 "navbar.profile_actions.new_item": "New Item", 38 "navbar.profile_actions.new_item": "New Item",
33 "navbar.content_viewer_actions.new_post": "New Post", 39 "navbar.content_viewer_actions.new_post": "New Post",
@@ -100,5 +106,23 @@ @@ -100,5 +106,23 @@
100 "block.edition.language.label": "Show for:", 106 "block.edition.language.label": "Show for:",
101 "activities.event.description": "Event on", 107 "activities.event.description": "Event on",
102 "time.at": "at", 108 "time.at": "at",
103 - "date.on": "On" 109 + "date.on": "On",
  110 + "account.register.welcomeMessageTitle": "Nice to have you there!",
  111 + "account.register.fullNameLabel": "Full name",
  112 + "account.register.usernameLabel": "Username",
  113 + "account.register.emailLabel": "Email",
  114 + "account.register.passwordLabel": "Password",
  115 + "account.register.passwordConfirmationLabel": "Password confirmation",
  116 + "account.register.accountCreatingMessage": "By creating an account, you are agreeing with the ",
  117 + "account.register.signupMessage": "Register",
  118 + "account.register.haveAccountMessage": "Already have an account?",
  119 + "account.register.termsOfUseMessage": "terms of use",
  120 + "account.register.success.title": "Good job!",
  121 + "account.register.success.message": "Account created!",
  122 + "account.register.passwordConfirmation.failed": "Wrong password confirmation",
  123 + "messages.invalid.required": "This field is required",
  124 + "messages.invalid.maxlength": "This field is too long",
  125 + "messages.invalid.minlength": "This field is too short",
  126 + "messages.invalid.email": "This needs to be a valid email",
  127 + "messages.invalid.passwordMatch": "Your passwords did not match"
104 } 128 }
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",
@@ -21,6 +25,7 @@ @@ -21,6 +25,7 @@
21 "profile.person.title": "Pessoa", 25 "profile.person.title": "Pessoa",
22 "activities.title": "Atividades", 26 "activities.title": "Atividades",
23 "activities.create_article.description": "publicou em", 27 "activities.create_article.description": "publicou em",
  28 + "activities.scrap.description": "escreveu em sua linha do tempo",
24 "activities.add_member_in_community.description": "entrou na comunidade", 29 "activities.add_member_in_community.description": "entrou na comunidade",
25 "activities.new_friendship.description": "fez {friends, plural, one{um novo amigo} other{# novos amigos}}:", 30 "activities.new_friendship.description": "fez {friends, plural, one{um novo amigo} other{# novos amigos}}:",
26 "auth.title": "Legal ter você de volta!", 31 "auth.title": "Legal ter você de volta!",
@@ -28,9 +33,10 @@ @@ -28,9 +33,10 @@
28 "auth.form.password": "Senha", 33 "auth.form.password": "Senha",
29 "auth.form.keepLoggedIn": "Continuar logado", 34 "auth.form.keepLoggedIn": "Continuar logado",
30 "auth.form.login_button": "Login", 35 "auth.form.login_button": "Login",
  36 + "auth.createAccount": "Criar conta",
31 "navbar.content_viewer_actions.new_item": "Novo Item", 37 "navbar.content_viewer_actions.new_item": "Novo Item",
32 "navbar.profile_actions.new_item": "Novo Item", 38 "navbar.profile_actions.new_item": "Novo Item",
33 - "navbar.content_viewer_actions.new_post": "Novo Artigo", 39 + "navbar.content_viewer_actions.new_post": "Novo Post",
34 "navbar.content_viewer_actions.new_discussion": "Nova Consulta", 40 "navbar.content_viewer_actions.new_discussion": "Nova Consulta",
35 "navbar.profile_actions.new_discussion": "Nova Discussão", 41 "navbar.profile_actions.new_discussion": "Nova Discussão",
36 "notification.error.default.message": "Algo deu errado!", 42 "notification.error.default.message": "Algo deu errado!",
@@ -100,5 +106,26 @@ @@ -100,5 +106,26 @@
100 "block.edition.language.label": "Exibir para:", 106 "block.edition.language.label": "Exibir para:",
101 "activities.event.description": "Evento em", 107 "activities.event.description": "Evento em",
102 "time.at": "às", 108 "time.at": "às",
103 - "date.on": "Em" 109 + "date.on": "Em",
  110 + "account.register.welcomeMessageTitle": "Ótimo ter você aqui!",
  111 + "account.register.seeMoreMessage": "Saiba mais ",
  112 + "account.register.informationsMessage": "informações",
  113 + "account.register.fullNameLabel": "Nome completo",
  114 + "account.register.lastNameLabel": "Sobrenome",
  115 + "account.register.usernameLabel": "Nome de usuário",
  116 + "account.register.emailLabel": "Email",
  117 + "account.register.passwordLabel": "Senha",
  118 + "account.register.passwordConfirmationLabel": "Confirmar senha",
  119 + "account.register.accountCreatingMessage": "Ao criar uma conta, você está concordando com os ",
  120 + "account.register.termsOfUseMessage": "termos de uso",
  121 + "account.register.signupMessage": "Criar Conta",
  122 + "account.register.haveAccountMessage": "Já possui uma conta?",
  123 + "account.register.success.title": "Bom trabalho!",
  124 + "account.register.success.message": "Conta criada com sucesso!",
  125 + "account.register.passwordConfirmation.failed": "A confirmação de senha não corresponde à senha",
  126 + "messages.invalid.required": "Campo obrigatório",
  127 + "messages.invalid.maxlength": "O valor é muito longo",
  128 + "messages.invalid.minlength": "O valor é muito curto",
  129 + "messages.invalid.email": "Informe um email válido",
  130 + "messages.invalid.passwordMatch": "As senhas não coincidem"
104 } 131 }
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 };
@@ -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",