Compare View

switch
from
...
to
 
Commits (42)
Showing 95 changed files   Show diff stats
@@ -39,12 +39,20 @@ @@ -39,12 +39,20 @@
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",
  44 + "ng-file-upload": "^12.0.4",
  45 + "ng-img-crop": "^0.3.2"
43 }, 46 },
44 "devDependencies": { 47 "devDependencies": {
45 "angular-mocks": "~1.5.0" 48 "angular-mocks": "~1.5.0"
46 }, 49 },
47 "overrides": { 50 "overrides": {
  51 + "ng-file-upload": {
  52 + "main": [
  53 + "ng-file-upload-all.js"
  54 + ]
  55 + },
48 "ng-ckeditor": { 56 "ng-ckeditor": {
49 "main": [ 57 "main": [
50 "ng-ckeditor.js", 58 "ng-ckeditor.js",
@@ -46,6 +46,7 @@ gulp.task('watch', ['inject'], function () { @@ -46,6 +46,7 @@ gulp.task('watch', ['inject'], function () {
46 watchPaths.push(path.join(src, '/app/**/*.html')); 46 watchPaths.push(path.join(src, '/app/**/*.html'));
47 watchPaths.push(path.join(src, conf.paths.plugins, '/**/*.html')); 47 watchPaths.push(path.join(src, conf.paths.plugins, '/**/*.html'));
48 }); 48 });
  49 + watchPaths.push(stylePaths);
49 gulp.watch(watchPaths, function(event) { 50 gulp.watch(watchPaths, function(event) {
50 browserSync.reload(event.path); 51 browserSync.reload(event.path);
51 }); 52 });
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,15 @@ @@ -0,0 +1,15 @@
  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 + TASK_CLOSED: string = 'TASK_CLOSED';
  8 +
  9 + constructor() {
  10 + }
  11 +
  12 + getNames() {
  13 + return Object.getOwnPropertyNames(this);
  14 + }
  15 +}
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
@@ -14,12 +14,22 @@ describe(&quot;Components&quot;, () =&gt; { @@ -14,12 +14,22 @@ describe(&quot;Components&quot;, () =&gt; {
14 describe("Profile Image Block Component", () => { 14 describe("Profile Image Block Component", () => {
15 15
16 beforeEach(angular.mock.module("templates")); 16 beforeEach(angular.mock.module("templates"));
  17 + let personService = jasmine.createSpyObj("personService", ["upload"]);
  18 +
  19 + let profileService = jasmine.createSpyObj("ProfileService", ["isMember", "addMember", "removeMember"]);
  20 + profileService.isMember = jasmine.createSpy("isMember").and.returnValue(Promise.resolve(false));
17 21
18 @Component({ 22 @Component({
19 selector: 'test-container-component', 23 selector: 'test-container-component',
20 template: htmlTemplate, 24 template: htmlTemplate,
21 directives: [ProfileImageBlockComponent], 25 directives: [ProfileImageBlockComponent],
22 - providers: helpers.provideFilters("translateFilter") 26 + providers: [
  27 + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})),
  28 + helpers.createProviderToValue("PersonService", personService),
  29 + helpers.createProviderToValue("$uibModal", helpers.mocks.$modal),
  30 + helpers.createProviderToValue('ProfileService', profileService),
  31 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService)
  32 + ].concat(helpers.provideFilters("translateFilter"))
23 }) 33 })
24 class BlockContainerComponent { 34 class BlockContainerComponent {
25 block = { type: 'Block' }; 35 block = { type: 'Block' };
@@ -42,5 +52,42 @@ describe(&quot;Components&quot;, () =&gt; { @@ -42,5 +52,42 @@ describe(&quot;Components&quot;, () =&gt; {
42 }); 52 });
43 }); 53 });
44 54
  55 + it("display button to join community", (done: Function) => {
  56 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  57 + let elProfile = fixture.debugElement.componentViewChildren[0];
  58 + expect(elProfile.query('.actions .join').length).toEqual(1);
  59 + done();
  60 + });
  61 + });
  62 +
  63 + it("display button to leave community", (done: Function) => {
  64 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  65 + let elProfile = fixture.debugElement.componentViewChildren[0];
  66 + elProfile.componentInstance['isMember'] = true;
  67 + fixture.detectChanges();
  68 + expect(elProfile.query('.actions .leave').length).toEqual(1);
  69 + done();
  70 + });
  71 + });
  72 +
  73 + it("join community", (done: Function) => {
  74 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  75 + let elProfile = fixture.debugElement.componentViewChildren[0];
  76 + profileService.addMember = jasmine.createSpy("addMember").and.returnValue(Promise.resolve({ data: {} }));
  77 + elProfile.componentInstance.join();
  78 + expect(profileService.addMember).toHaveBeenCalled();
  79 + done();
  80 + });
  81 + });
  82 +
  83 + it("leave community", (done: Function) => {
  84 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  85 + let elProfile = fixture.debugElement.componentViewChildren[0];
  86 + profileService.removeMember = jasmine.createSpy("removeMember").and.returnValue(Promise.resolve({ data: {} }));
  87 + elProfile.componentInstance.leave();
  88 + expect(profileService.removeMember).toHaveBeenCalled();
  89 + done();
  90 + });
  91 + });
45 }); 92 });
46 }); 93 });
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
1 <div class="center-block text-center profile-image-block"> 1 <div class="center-block text-center profile-image-block">
2 <a ui-sref="main.profile.info({profile: ctrl.owner.identifier})"> 2 <a ui-sref="main.profile.info({profile: ctrl.owner.identifier})">
3 - <noosfero-profile-image [profile]="ctrl.owner"></noosfero-profile-image> 3 + <noosfero-profile-image [profile]="ctrl.owner" [editable]="true" [edit-class]="'profile-image-block-editable'"></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-image/profile-image-block.scss
@@ -2,4 +2,22 @@ @@ -2,4 +2,22 @@
2 .settings-link { 2 .settings-link {
3 display: block; 3 display: block;
4 } 4 }
  5 + .upload-camera-container {
  6 + top: 77%;
  7 + left: 6%;
  8 + }
  9 +}
  10 +
  11 +.profile-image-block-editable {
  12 + top: 68%;
  13 + width: 284px;
  14 + font-weight: 700;
  15 + height: 43px;
  16 + padding-left: 30px;
  17 + padding-top: 13px;
5 } 18 }
  19 +
  20 +
  21 +
  22 +
  23 +
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/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>
@@ -39,10 +45,12 @@ @@ -39,10 +45,12 @@
39 </ul> 45 </ul>
40 </li> 46 </li>
41 </ul> 47 </ul>
42 -  
43 <ul class="nav navbar-nav navbar-right"> 48 <ul class="nav navbar-nav navbar-right">
44 <language-selector class="nav navbar-nav navbar-right"></language-selector> 49 <language-selector class="nav navbar-nav navbar-right"></language-selector>
45 </ul> 50 </ul>
  51 + <ul class="nav navbar-nav navbar-right">
  52 + <tasks-menu class="nav navbar-nav navbar-right"></tasks-menu>
  53 + </ul>
46 <div ui-view="actions"></div> 54 <div ui-view="actions"></div>
47 <div class="nav navbar-nav search navbar-right"> 55 <div class="nav navbar-nav search navbar-right">
48 <search-form></search-form> 56 <search-form></search-form>
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,15 @@ describe(&quot;MainComponent&quot;, function() { @@ -34,6 +35,15 @@ 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 + 'TASK_CLOSED'
  45 + ]
  46 + }),
37 ] 47 ]
38 }) 48 })
39 class MainComponentParent { 49 class MainComponentParent {
@@ -57,7 +67,7 @@ describe(&quot;MainComponent&quot;, function() { @@ -57,7 +67,7 @@ describe(&quot;MainComponent&quot;, function() {
57 // navigates to the environment home 67 // navigates to the environment home
58 $state.go("main.environment.home"); 68 $state.go("main.environment.home");
59 localFixture.detectChanges(); 69 localFixture.detectChanges();
60 - // after changes were detected it checks the current $state route 70 + // after changes were detected it checks the current $state route
61 expect($state.current.name).toEqual("main.environment.home"); 71 expect($state.current.name).toEqual("main.environment.home");
62 done(); 72 done();
63 }); 73 });
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"; 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 +import { EVENTS_HUB_KNOW_EVENT_NAMES, EventsHubService } from "../shared/services/events-hub.service";
  53 +import { NoosferoKnownEvents } from "../known-events";
  54 +import { TasksMenuComponent } from "../task/tasks-menu/tasks-menu.component";
  55 +import { TaskListComponent } from "../task/task-list/task-list.component";
49 56
50 /** 57 /**
51 * @ngdoc controller 58 * @ngdoc controller
@@ -62,12 +69,16 @@ import {SearchFormComponent} from &quot;../search/search-form/search-form.component&quot;; @@ -62,12 +69,16 @@ import {SearchFormComponent} from &quot;../search/search-form/search-form.component&quot;;
62 templateUrl: "app/main/main.html", 69 templateUrl: "app/main/main.html",
63 providers: [AuthService, SessionService] 70 providers: [AuthService, SessionService]
64 }) 71 })
65 -@Inject(BodyStateClassesService) 72 +@Inject(BodyStateClassesService, EVENTS_HUB_KNOW_EVENT_NAMES)
66 export class MainContentComponent { 73 export class MainContentComponent {
67 74
68 public themeSkin: string = 'skin-whbl'; 75 public themeSkin: string = 'skin-whbl';
69 76
70 - constructor(private bodyStateClassesService: BodyStateClassesService) { 77 + constructor(
  78 + private bodyStateClassesService: BodyStateClassesService,
  79 + eventsNames: NoosferoKnownEvents,
  80 + eventsHubService: EventsHubService
  81 + ) {
71 bodyStateClassesService.start({ 82 bodyStateClassesService.start({
72 skin: this.themeSkin 83 skin: this.themeSkin
73 }); 84 });
@@ -88,7 +99,8 @@ export class EnvironmentContent { @@ -88,7 +99,8 @@ export class EnvironmentContent {
88 * @name main.Main 99 * @name main.Main
89 * @requires AuthService, Session, Notification, ArticleBlog, ArticleView, Boxes, Block, LinkListBlock, 100 * @requires AuthService, Session, Notification, ArticleBlog, ArticleView, Boxes, Block, LinkListBlock,
90 * MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock, 101 * MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock,
91 - * NoosferoTemplate, DateFormat, RawHTMLBlock, PersonTagsPluginInterestsBlock, RecentActivitiesPluginActivitiesBlock, 102 + * NoosferoTemplate, DateFormat, RawHTMLBlock, PersonTagsPluginInterestsBlock,
  103 + * RecentActivitiesPluginActivitiesBlock, ProfileImagesPluginProfileImages
92 * @description 104 * @description
93 * The Main controller for the Noosfero Angular Theme application. 105 * The Main controller for the Noosfero Angular Theme application.
94 * 106 *
@@ -107,7 +119,8 @@ export class EnvironmentContent { @@ -107,7 +119,8 @@ export class EnvironmentContent {
107 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, 119 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
108 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, 120 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
109 LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent, 121 LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent,
110 - PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, BlockComponent 122 + PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent,
  123 + ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent, TasksMenuComponent, TaskListComponent
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,
113 "ngAnimate", "ngCookies", "ngStorage", "ngTouch", 126 "ngAnimate", "ngCookies", "ngStorage", "ngTouch",
@@ -116,7 +129,7 @@ export class EnvironmentContent { @@ -116,7 +129,7 @@ export class EnvironmentContent {
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", "ngTagCloud", "noosfero.init", "uiSwitch"] 132 + "angular-click-outside", "ngTagCloud", "noosfero.init", "uiSwitch", "ngFileUpload", "ngImgCrop"]
120 }) 133 })
121 @StateConfig([ 134 @StateConfig([
122 { 135 {
@@ -147,6 +160,18 @@ export class EnvironmentContent { @@ -147,6 +160,18 @@ export class EnvironmentContent {
147 } 160 }
148 }, 161 },
149 { 162 {
  163 + url: '/account/signup',
  164 + component: RegisterComponent,
  165 + name: 'main.register',
  166 + views: {
  167 + "content": {
  168 + templateUrl: "app/account/register.html",
  169 + controller: RegisterComponent,
  170 + controllerAs: "vm"
  171 + }
  172 + }
  173 + },
  174 + {
150 url: "^/:profile", 175 url: "^/:profile",
151 abstract: true, 176 abstract: true,
152 component: ProfileComponent, 177 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/profile/image/image.component.spec.ts
@@ -4,22 +4,49 @@ @@ -4,22 +4,49 @@
4 * @description 4 * @description
5 * This file contains the tests for the {@link components.noosfero.profile-image.ProfileImage} component. 5 * This file contains the tests for the {@link components.noosfero.profile-image.ProfileImage} component.
6 */ 6 */
7 - 7 +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
8 import {TestComponentBuilder, ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; 8 import {TestComponentBuilder, ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder';
9 import {Pipe, Input, provide, Component} from 'ng-forward'; 9 import {Pipe, Input, provide, Component} from 'ng-forward';
  10 +import {PersonService} from "../../../lib/ng-noosfero-api/http/person.service";
10 11
11 import * as helpers from "../../../spec/helpers"; 12 import * as helpers from "../../../spec/helpers";
12 13
13 import {ProfileImageComponent} from "./image.component"; 14 import {ProfileImageComponent} from "./image.component";
14 15
15 -const tcb = new TestComponentBuilder(); 16 +const htmlTemplate: string = '<noosfero-profile-image [editable]="true" [edit-class]="editable-class" [profile]="ctrl.profile"></noosfero-profile-image>';
16 17
17 describe("Components", () => { 18 describe("Components", () => {
18 19
19 describe("Profile Image Component", () => { 20 describe("Profile Image Component", () => {
20 21
  22 + let helper: ComponentTestHelper<ProfileImageComponent>;
  23 +
21 beforeEach(angular.mock.module("templates")); 24 beforeEach(angular.mock.module("templates"));
22 25
  26 + beforeEach((done) => {
  27 + let scope = helpers.mocks.scopeWithEvents;
  28 + let personService = jasmine.createSpyObj("personService", ["upload"]);
  29 + let properties = { profile: { custom_footer: "footer" } };
  30 + let cls = createClass({
  31 + template: htmlTemplate,
  32 + directives: [ProfileImageComponent],
  33 + properties: properties,
  34 + providers: [
  35 + helpers.createProviderToValue("PersonService", personService),
  36 + helpers.createProviderToValue("$uibModal", helpers.mocks.$modal),
  37 + helpers.createProviderToValue("$scope", scope)
  38 + ]
  39 + });
  40 + helper = new ComponentTestHelper<ProfileImageComponent>(cls, done);
  41 + });
  42 +
  43 + it("set modal instance when select files modal", () => {
  44 + helper.component['$uibModal'].open = jasmine.createSpy("open");
  45 + helper.component.fileSelected("file", []);
  46 + expect(helper.component['$uibModal'].open).toHaveBeenCalled();
  47 + });
  48 +
  49 + /*
23 it("show community users image if profile is not Person", done => { 50 it("show community users image if profile is not Person", done => {
24 helpers.tcb.createAsync(ProfileImageComponent).then(fixture => { 51 helpers.tcb.createAsync(ProfileImageComponent).then(fixture => {
25 let profileImageComponent: ProfileImageComponent = fixture.componentInstance; 52 let profileImageComponent: ProfileImageComponent = fixture.componentInstance;
@@ -45,7 +72,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -45,7 +72,7 @@ describe(&quot;Components&quot;, () =&gt; {
45 expect(profileImageComponent.defaultIcon).toEqual("fa-user", "The default icon should be person user"); 72 expect(profileImageComponent.defaultIcon).toEqual("fa-user", "The default icon should be person user");
46 done(); 73 done();
47 }); 74 });
48 - }); 75 + });*/
49 76
50 }); 77 });
51 }); 78 });
52 \ No newline at end of file 79 \ No newline at end of file
src/app/profile/image/image.component.ts
1 -import {Inject, Input, Component} from "ng-forward";  
2 - 1 +import {Inject, Input, Component, provide} from "ng-forward";
  2 +import {PersonService} from "../../../lib/ng-noosfero-api/http/person.service";
  3 +import {ProfileImageEditorComponent} from "./profile-image-editor.component";
3 4
4 /** 5 /**
5 * @ngdoc controller 6 * @ngdoc controller
@@ -10,7 +11,9 @@ import {Inject, Input, Component} from &quot;ng-forward&quot;; @@ -10,7 +11,9 @@ import {Inject, Input, Component} from &quot;ng-forward&quot;;
10 @Component({ 11 @Component({
11 selector: "noosfero-profile-image", 12 selector: "noosfero-profile-image",
12 templateUrl: 'app/profile/image/image.html', 13 templateUrl: 'app/profile/image/image.html',
  14 + providers: [ provide('personService', { useClass: PersonService }) ]
13 }) 15 })
  16 +@Inject(PersonService, "$uibModal", "$scope")
14 export class ProfileImageComponent { 17 export class ProfileImageComponent {
15 18
16 /** 19 /**
@@ -30,6 +33,48 @@ export class ProfileImageComponent { @@ -30,6 +33,48 @@ export class ProfileImageComponent {
30 */ 33 */
31 defaultIcon: string; 34 defaultIcon: string;
32 35
  36 + @Input() editable: boolean;
  37 +
  38 + @Input() editClass: string;
  39 +
  40 + picFile: any;
  41 + croppedDataUrl: any;
  42 + modalInstance: any;
  43 +
  44 + constructor(private personService: PersonService, private $uibModal: any, private $scope: ng.IScope) {
  45 + }
  46 +
  47 + fileSelected(file: any, errFiles: any) {
  48 + console.log("File selected: ", file);
  49 + if (file) {
  50 + this.picFile = file;
  51 + this.modalInstance = this.$uibModal.open({
  52 + templateUrl: 'app/profile/image/profile-image-editor.html',
  53 + controller: ProfileImageEditorComponent,
  54 + controllerAs: 'ctrl',
  55 + scope: this.$scope,
  56 + bindToController: true,
  57 + backdrop: 'static',
  58 + resolve: {
  59 + picFile: this.picFile,
  60 + profile: this.profile,
  61 + personService: this.personService
  62 + }
  63 + });
  64 + }
  65 + }
  66 +
  67 + private _showCamera: boolean = false;
  68 +
  69 + showChange(show: boolean) {
  70 + this._showCamera = show;
  71 + }
  72 +
  73 + showCamera() {
  74 + return this._showCamera;
  75 + }
  76 +
  77 +
33 /** 78 /**
34 * @ngdoc method 79 * @ngdoc method
35 * @name ngOnInit 80 * @name ngOnInit
@@ -43,5 +88,6 @@ export class ProfileImageComponent { @@ -43,5 +88,6 @@ export class ProfileImageComponent {
43 this.defaultIcon = 'fa-user'; 88 this.defaultIcon = 'fa-user';
44 } 89 }
45 } 90 }
  91 +
46 } 92 }
47 93
src/app/profile/image/image.html
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>  
4 -</span> 1 +<div id="profile-image-container" style="">
  2 + <div class="profile-image-wrap" title="{{ctrl.profile.name}}" ng-mouseenter="ctrl.showChange(true)" ng-mouseleave="ctrl.showChange(false)">
  3 + <img ng-if="ctrl.profile.image" ng-src="{{ctrl.profile.image.url}}" class="img-responsive profile-image">
  4 + <i ng-if="!ctrl.profile.image" class="fa {{ctrl.defaultIcon}} fa-5x profile-image"></i>
  5 + <div ng-if="ctrl.editable" class="upload-camera-container">
  6 + <i id="camera" class="fa fa-camera upload-camera" aria-hidden="true"></i>
  7 + </div>
  8 + <div ng-if="ctrl.editable" id="select-photo-container" name="select-photo-container" class="select-photo-container container" ng-class="ctrl.editClass">
  9 + <a id="upload-container" class="upload-container" href="#" rel="dialog" role="button">
  10 + <!-- The upload button hidden behind the camera -->
  11 + <div class="upload-button" ngf-select="ctrl.fileSelected($file)"
  12 + ngf-pattern="'image/*'" ngf-accept="'image/*'"
  13 + ngf-max-size="20MB" ngf-resize="{width: 100, height: 100}"
  14 + data-toggle="modal" data-target=".crop-dialog">
  15 + {{"profile.image.upload" | translate}}
  16 + </div>
  17 + </a>
  18 + </div>
  19 +
  20 + </div>
  21 +</div>
src/app/profile/image/image.scss
@@ -5,3 +5,94 @@ i.profile-image { @@ -5,3 +5,94 @@ i.profile-image {
5 background-clip: padding-box; 5 background-clip: padding-box;
6 margin-bottom: 15px; 6 margin-bottom: 15px;
7 } 7 }
  8 +
  9 +.profile-image-wrap {
  10 + display: inline;
  11 +}
  12 +
  13 +#profile-image-container {
  14 + display: inline;
  15 +}
  16 +
  17 +#profile-image-container:hover {
  18 + .select-photo-container {
  19 + z-index: 1;
  20 + }
  21 + .upload-camera-container {
  22 + transform: scale(.75);
  23 + }
  24 +}
  25 +
  26 +.upload-camera-container {
  27 + text-align: left;
  28 + position: absolute;
  29 + z-index: 5;
  30 +}
  31 +
  32 +.upload-camera {
  33 + color: white;
  34 + position: absolute;
  35 + transition: all .3s cubic-bezier(.175, .885, .32, 1.275);
  36 + opacity: 1;
  37 +}
  38 +
  39 +.select-photo-container {
  40 + //overflow: hidden;
  41 + position: absolute;
  42 + z-index: -1;
  43 + background: #000;
  44 + background: rgba(0, 0, 0, .6);
  45 + background: linear-gradient(transparent, rgba(0, 0, 0, .6) 70%, rgba(0, 0, 0, .6) 100%);
  46 + transition: top .13s ease-out;
  47 +}
  48 +
  49 +#upload-container {
  50 + position: relative;
  51 + text-decoration: none;
  52 +}
  53 +
  54 +.upload-container a:hover {
  55 + text-decoration: none;
  56 +}
  57 +
  58 +.upload-button {
  59 + -webkit-font-smoothing: antialiased;
  60 + color: #fff;
  61 + //word-wrap: break-word;
  62 +}
  63 +
  64 +.upload-container {
  65 + color:#fff;
  66 + display: block;
  67 + overflow: hidden;
  68 + position: relative;
  69 + text-align: left;
  70 + min-width: 89px;
  71 +}
  72 +
  73 +.cropArea {
  74 + background: #E4E4E4;
  75 + overflow: hidden;
  76 + width:300px;
  77 + height:150px;
  78 +}
  79 +
  80 +.crop-area {
  81 + display: none;
  82 +}
  83 +
  84 +form .progress {
  85 + line-height: 15px;
  86 +}
  87 +
  88 +.progress {
  89 + display: inline-block;
  90 + width: 100px;
  91 + border: 3px groove #CCC;
  92 +}
  93 +.progress div {
  94 + font-size: smaller;
  95 + background: orange;
  96 + width: 0;
  97 +}
  98 +
src/app/profile/image/profile-image-editor.component.spec.ts 0 → 100644
@@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
  1 +import {Pipe, Input, provide, Component} from 'ng-forward';
  2 +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
  3 +import * as helpers from "../../../spec/helpers";
  4 +
  5 +import {ProfileImageEditorComponent} from "./profile-image-editor.component";
  6 +
  7 +describe("Components", () => {
  8 +
  9 + describe("Profile Image Editor Component", () => {
  10 +
  11 + beforeEach(angular.mock.module("templates"));
  12 +
  13 + let expectedData = "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAgAElEQ…Cm2OLHvfdNPte3zrH709Q0esN1LPQ0t7DL696ERpu+9/8BVPLIpElf7VYAAAAASUVORK5CYII=";
  14 +
  15 + let profile = <noosfero.Profile>{ name: "profile_name", id: 1, identifier: "test" };
  16 + let modal = helpers.mocks.$modal;
  17 + let modalInstance = jasmine.createSpyObj("$uibModalInstance", ["close"]);
  18 + let picFile = { type: "png" };
  19 + let $q: ng.IQService;
  20 + let personServiceMock: any;
  21 + let $rootScope: ng.IRootScopeService;
  22 +
  23 + beforeEach(inject((_$q_: ng.IQService, _$rootScope_: ng.IRootScopeService) => {
  24 + $q = _$q_;
  25 + $rootScope = _$rootScope_;
  26 + }));
  27 +
  28 + let comp = new ProfileImageEditorComponent(picFile, this.profile, personServiceMock, modalInstance);
  29 +
  30 + it("get data", done => {
  31 + let testDataUrl = "data:image/png;base64," + expectedData;
  32 + let result = comp.getData(testDataUrl);
  33 + expect(result).toBe(expectedData);
  34 + done();
  35 + });
  36 +
  37 + it("get image name", done => {
  38 + let imageName = "image1";
  39 + let expectedName = "profile_name_" + imageName;
  40 + comp['profile'] = profile;
  41 + let result = comp.getImageName(imageName);
  42 + expect(result).toBe(expectedName);
  43 + done();
  44 + });
  45 +
  46 + it("upload image", done => {
  47 + let testDataUrl = "data:image/png;base64," + expectedData;
  48 + let imageName = "image1";
  49 + personServiceMock = jasmine.createSpyObj("personServiceMock", ["uploadImage"]);
  50 + console.log("PersonServiceMock:", personServiceMock);
  51 + let deferredUploadImage = $q.defer();
  52 + personServiceMock.uploadImage = jasmine.createSpy('uploadImage').and.returnValue(deferredUploadImage.promise);
  53 + comp.personService = personServiceMock;
  54 + comp.uploadImage(testDataUrl, imageName);
  55 + deferredUploadImage.resolve();
  56 + $rootScope.$apply();
  57 + expect(comp.$uibModalInstance.close).toHaveBeenCalled();
  58 + done();
  59 + });
  60 +
  61 + });
  62 +});
src/app/profile/image/profile-image-editor.component.ts 0 → 100644
@@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
  1 +import {StateConfig, Component, Input, Output, Inject, provide} from 'ng-forward';
  2 +import {TranslateProfile} from "../../shared/pipes/translate-profile.filter";
  3 +import {PersonService} from "../../../lib/ng-noosfero-api/http/person.service";
  4 +
  5 +export class ProfileImageEditorComponent {
  6 +
  7 + activities: any;
  8 + croppedDataUrl: string;
  9 + static $inject = ["picFile", "profile", "personService", "$uibModalInstance"];
  10 +
  11 + constructor(public picFile: any, public profile: noosfero.Profile, public personService: PersonService,
  12 + public $uibModalInstance: any) {
  13 + }
  14 +
  15 + uploadImage(dataUrl: any, name: any) {
  16 + let base64_image_json = this.getBase64ImageJson(dataUrl, name);
  17 + this.personService.uploadImage(this.profile, base64_image_json).then( (result: any) => {
  18 + this.$uibModalInstance.close(name);
  19 + });
  20 + }
  21 +
  22 + getBase64ImageJson(dataUrl: any, name: any) {
  23 + let data = this.getData(dataUrl);
  24 + let image_name = this.getImageName(name);
  25 + return {
  26 + tempfile: data,
  27 + filename: image_name,
  28 + type: this.picFile.type
  29 + };
  30 + }
  31 +
  32 + getImageName(name: any) {
  33 + return this.profile.name + "_" + name;
  34 + }
  35 +
  36 + getData(dataUrl: any) {
  37 + return dataUrl.substring(dataUrl.indexOf('base64,') + 7);
  38 + }
  39 +
  40 + cancel() {
  41 + this.$uibModalInstance.close();
  42 + }
  43 +}
src/app/profile/image/profile-image-editor.html 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +<div class="modal-header">
  2 + <h3>{{"profile.image.edit" | translate}}</h3>
  3 +</div>
  4 +<div class="modal-body">
  5 + <form class="">
  6 + <div ngf-drop ng-model="ctrl.picFile" ngf-pattern="image/*" class="cropArea">
  7 + <img-crop image="ctrl.picFile | ngfDataUrl" area-type="square"
  8 + result-image="ctrl.croppedDataUrl" ng-init="ctrl.croppedDataUrl=''">
  9 + </img-crop>
  10 + </div>
  11 + <div>
  12 + <img ng-src="{{ctrl.croppedDataUrl}}" />
  13 + </div>
  14 + <span class="progress" ng-show="progress >= 0">
  15 + <div style="width: {{progress" ng-bind="progress + '%'"></div>
  16 + </span> <span ng-show="ctrl.result">Upload Successful</span> <span class="err"
  17 + ng-show="ctrl.errorMsg">{{errorMsg}}</span>
  18 + </form>
  19 +
  20 + <div class="actions">
  21 + <button type="submit" class="btn btn-default" ng-click="ctrl.uploadImage(ctrl.croppedDataUrl, ctrl.picFile.name)">Upload</button>
  22 + <button type="submit" class="btn btn-danger" ng-click="ctrl.cancel()">Cancel</button>
  23 + </div>
  24 +</div>
src/app/profile/info/profile-info.html
@@ -6,10 +6,12 @@ @@ -6,10 +6,12 @@
6 <h2>{{vm.profile.name}}</h2> 6 <h2>{{vm.profile.name}}</h2>
7 </header> 7 </header>
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>  
10 - <span class="label" ng-class="{'label-danger': vm.profile.type == 'Community', 'label-info': vm.profile.type == 'Person'}">{{vm.profile | translateProfile}}</span>  
11 - <div class="profile-since">  
12 - {{"profile.member_since" | translate}}: {{vm.profile.created_at | amDateFormat:'MMMM YYYY'}} 9 + <noosfero-profile-image [profile]="vm.profile" [editable]="true" [edit-class]="'profile-info-editable'" class="img-responsive center-block profile-info"></noosfero-profile-image>
  10 + <div id="profile-info-extrainfo" class="profile-info-extrainfo">
  11 + <span class="label" ng-class="{'label-danger': vm.profile.type == 'Community', 'label-info': vm.profile.type == 'Person'}">{{vm.profile | translateProfile}}</span>
  12 + <div class="profile-since">
  13 + {{"profile.member_since" | translate}}: {{vm.profile.created_at | amDateFormat:'MMMM YYYY'}}
  14 + </div>
13 </div> 15 </div>
14 </div> 16 </div>
15 </div> 17 </div>
src/app/profile/info/profile-info.scss 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +.profile-info .upload-camera-container {
  2 + top: 55%;
  3 + left: 39px;
  4 +}
  5 +
  6 +.profile-info-editable {
  7 + top: 51%;
  8 + width: 103px;
  9 + height: 28px;
  10 +}
  11 +
  12 +.profile-info-editable .upload-button {
  13 + font-size: 0.8em;
  14 + padding-top: 9px;
  15 + padding-left: 6px;
  16 + font-weight: bold;
  17 +}
  18 +
  19 +.profile-info-extrainfo {
  20 + margin-top: 10px;
  21 +}
0 \ No newline at end of file 22 \ No newline at end of file
src/app/profile/profile.component.ts
1 -import {StateConfig, Component, Inject, provide} from 'ng-forward';  
2 -import {ProfileInfoComponent} from './info/profile-info.component';  
3 -import {ProfileHomeComponent} from './profile-home.component';  
4 -import {BasicEditorComponent} from '../article/cms/basic-editor/basic-editor.component';  
5 -import {CmsComponent} from '../article/cms/cms.component';  
6 -import {ContentViewerComponent} from "../article/content-viewer/content-viewer.component";  
7 -import {ContentViewerActionsComponent} from "../article/content-viewer/content-viewer-actions.component";  
8 -import {ActivitiesComponent} from "./activities/activities.component";  
9 -import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service";  
10 -import {NotificationService} from "../shared/services/notification.service";  
11 -import {MyProfileComponent} from "./myprofile.component";  
12 -import {ProfileActionsComponent} from "./profile-actions.component";  
13 -import {ConfigBarComponent} from "./config-bar.component"; 1 +import { StateConfig, Component, Inject, provide } from 'ng-forward';
  2 +import { ProfileInfoComponent } from './info/profile-info.component';
  3 +import { ProfileHomeComponent } from './profile-home.component';
  4 +import { BasicEditorComponent } from '../article/cms/basic-editor/basic-editor.component';
  5 +import { CmsComponent } from '../article/cms/cms.component';
  6 +import { ContentViewerComponent } from "../article/content-viewer/content-viewer.component";
  7 +import { ContentViewerActionsComponent } from "../article/content-viewer/content-viewer-actions.component";
  8 +import { ActivitiesComponent } from "./activities/activities.component";
  9 +import { ProfileService } from "../../lib/ng-noosfero-api/http/profile.service";
  10 +import { NotificationService } from "../shared/services/notification.service";
  11 +import { MyProfileComponent } from "./myprofile.component";
  12 +import { ProfileActionsComponent } from "./profile-actions.component";
  13 +import { ConfigBarComponent } from "./config-bar.component";
  14 +import { TasksComponent } from "../task/tasks/tasks.component";
  15 +
14 /** 16 /**
15 * @ngdoc controller 17 * @ngdoc controller
16 * @name profile.Profile 18 * @name profile.Profile
@@ -92,6 +94,18 @@ import {ConfigBarComponent} from &quot;./config-bar.component&quot;; @@ -92,6 +94,18 @@ import {ConfigBarComponent} from &quot;./config-bar.component&quot;;
92 } 94 }
93 }, 95 },
94 { 96 {
  97 + name: 'main.profile.tasks',
  98 + url: "^/myprofile/:profile/tasks",
  99 + component: TasksComponent,
  100 + views: {
  101 + "mainBlockContent": {
  102 + templateUrl: "app/task/tasks/tasks.html",
  103 + controller: TasksComponent,
  104 + controllerAs: "vm"
  105 + }
  106 + }
  107 + },
  108 + {
95 name: 'main.profile.home', 109 name: 'main.profile.home',
96 url: "", 110 url: "",
97 component: ProfileHomeComponent, 111 component: ProfileHomeComponent,
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 +}
src/app/task/task-list/accept.html 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<div class="task-accept task-confirmation">
  2 + <div class="accept-title confirmation-title">{{"tasks.actions.accept.confirmation.title" | translate}}</div>
  3 + <div class="accept-fields confirmation-details">
  4 + <task-accept ng-if="ctrl.currentTask.accept_details" [task]="ctrl.currentTask" [confirmation-task]="ctrl.confirmationTask"></task-accept>
  5 + </div>
  6 + <div class="actions">
  7 + <button type="submit" class="btn btn-default" ng-click="ctrl.callAccept()">{{"tasks.actions.confirmation.yes" | translate}}</button>
  8 + <button type="button" class="btn btn-warning" ng-click="ctrl.cancel()">{{"tasks.actions.confirmation.cancel" | translate}}</button>
  9 + </div>
  10 +</div>
src/app/task/task-list/reject.html 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<div class="task-reject task-confirmation">
  2 + <div class="reject-title confirmation-title">{{"tasks.actions.reject.confirmation.title" | translate}}</div>
  3 + <div class="reject-fields confirmation-details">
  4 + <input ng-model="ctrl.confirmationTask.reject_explanation" id="rejectionExplanationInput" type="text" placeholder="{{'tasks.actions.reject.explanation.label' | translate}}" class="rejection-explanation form-control">
  5 + </div>
  6 + <div class="actions">
  7 + <button type="submit" class="btn btn-default" ng-click="ctrl.callReject()">{{"tasks.actions.confirmation.yes" | translate}}</button>
  8 + <button type="button" class="btn btn-warning" ng-click="ctrl.cancel()">{{"tasks.actions.confirmation.cancel" | translate}}</button>
  9 + </div>
  10 +</div>
src/app/task/task-list/task-accept.component.spec.ts 0 → 100644
@@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
  1 +import { Provider, provide, Component } from 'ng-forward';
  2 +import * as helpers from "../../../spec/helpers";
  3 +import { TaskAcceptComponent } from './task-accept.component';
  4 +
  5 +const htmlTemplate: string = '<task-accept [task]="ctrl.task"></task-accept>';
  6 +
  7 +describe("Components", () => {
  8 + describe("Task Accept Component", () => {
  9 +
  10 + let task = { id: 1, type: "AddMember" };
  11 + let roleService = jasmine.createSpyObj("roleService", ["getByProfile"]);
  12 +
  13 + beforeEach(angular.mock.module("templates"));
  14 +
  15 + function createComponent() {
  16 + return helpers.quickCreateComponent({
  17 + template: htmlTemplate,
  18 + directives: [TaskAcceptComponent],
  19 + properties: { task: task },
  20 + providers: [
  21 + helpers.createProviderToValue("RoleService", roleService)
  22 + ].concat(helpers.provideFilters("translateFilter"))
  23 + });
  24 + }
  25 +
  26 + it("replace element with the specific task accept component", (done: Function) => {
  27 + createComponent().then(fixture => {
  28 + expect(fixture.debugElement.queryAll("add-member-task-accept").length).toBe(1);
  29 + done();
  30 + });
  31 + });
  32 + });
  33 +});
src/app/task/task-list/task-accept.component.ts 0 → 100644
@@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
  1 +import { Input, Inject, Component } from 'ng-forward';
  2 +import { AddMemberTaskAcceptComponent } from "../types/add-member/add-member-task-accept.component";
  3 +
  4 +@Component({
  5 + selector: 'task-accept',
  6 + template: '<div></div>',
  7 + directives: [AddMemberTaskAcceptComponent]
  8 +})
  9 +@Inject("$element", "$scope", "$injector", "$compile")
  10 +export class TaskAcceptComponent {
  11 +
  12 + @Input() task: noosfero.Task;
  13 + @Input() confirmationTask: noosfero.Task;
  14 +
  15 + ngOnInit() {
  16 + let componentName = this.task.type.replace(/::/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  17 + componentName += "-task-accept";
  18 + this.$element.replaceWith(this.$compile(`<${componentName} [task]="ctrl.task" [confirmation-task]="ctrl.confirmationTask"></${componentName}>`)(this.$scope));
  19 + }
  20 +
  21 + constructor(private $element: any, private $scope: ng.IScope, private $injector: ng.auto.IInjectorService, private $compile: ng.ICompileService) { }
  22 +}
src/app/task/task-list/task-list.component.spec.ts 0 → 100644
@@ -0,0 +1,106 @@ @@ -0,0 +1,106 @@
  1 +import { Provider, provide, Component } from 'ng-forward';
  2 +import * as helpers from "../../../spec/helpers";
  3 +import { ComponentTestHelper, createClass } from '../../../spec/component-test-helper';
  4 +import { TaskListComponent } from './task-list.component';
  5 +
  6 +const htmlTemplate: string = '<task-list [task]="ctrl.task"></task-list>';
  7 +
  8 +describe("Components", () => {
  9 + describe("Task List Component", () => {
  10 +
  11 + let helper: ComponentTestHelper<TaskListComponent>;
  12 + let taskService = jasmine.createSpyObj("taskService", ["getAllPending"]);
  13 + let tasks = [{ id: 1 }, { id: 2 }];
  14 + let modal = helpers.mocks.$modal;
  15 + let eventsHubService = jasmine.createSpyObj("eventsHubService", ["subscribeToEvent", "emitEvent"]);
  16 + taskService.getAllPending = jasmine.createSpy("getAllPending").and.returnValue(Promise.resolve({ headers: () => { }, data: tasks }));
  17 +
  18 + beforeEach(angular.mock.module("templates"));
  19 +
  20 + beforeEach((done) => {
  21 + let cls = createClass({
  22 + template: htmlTemplate,
  23 + directives: [TaskListComponent],
  24 + providers: [
  25 + helpers.createProviderToValue("TaskService", taskService),
  26 + helpers.createProviderToValue("EventsHubService", eventsHubService),
  27 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService),
  28 + helpers.createProviderToValue('$uibModal', modal),
  29 + ].concat(helpers.provideFilters("groupByFilter")),
  30 + properties: { tasks: tasks }
  31 + });
  32 + helper = new ComponentTestHelper<TaskListComponent>(cls, done);
  33 + });
  34 +
  35 + it("return specific template for a task", () => {
  36 + let task = { type: "AddMember" };
  37 + expect(helper.component.getTaskTemplate(<any>task)).toEqual("app/task/types/add-member/add-member.html");
  38 + });
  39 +
  40 + it("return the default template for a task", () => {
  41 + let task = { type: "" };
  42 + expect(helper.component.getTaskTemplate(<any>task)).toEqual("app/task/types/default.html");
  43 + });
  44 +
  45 + it("open confirmation modal when it has details to accept a task", () => {
  46 + let task = { accept_details: true };
  47 + helper.component.accept(<any>task);
  48 + expect(modal.open).toHaveBeenCalled();
  49 + });
  50 +
  51 + it("open confirmation modal when it has details to reject a task", () => {
  52 + let task = { reject_details: true };
  53 + helper.component.reject(<any>task);
  54 + expect(modal.open).toHaveBeenCalled();
  55 + });
  56 +
  57 + it("call api directly when it has no details to accept a task", () => {
  58 + let task = { accept_details: false };
  59 + helper.component.callAccept = jasmine.createSpy("callAccept");
  60 + helper.component.accept(<any>task);
  61 + expect(helper.component.callAccept).toHaveBeenCalled();
  62 + });
  63 +
  64 + it("call api directly when it has no details to reject a task", () => {
  65 + let task = { accept_details: false };
  66 + helper.component.callReject = jasmine.createSpy("callReject");
  67 + helper.component.reject(<any>task);
  68 + expect(helper.component.callReject).toHaveBeenCalled();
  69 + });
  70 +
  71 + it("call cancel and emit event when accept was called successfully", () => {
  72 + helper.component.currentTask = <any>{ id: 1 };
  73 + let result = helpers.mocks.promiseResultTemplate({ data: { id: 1 } });
  74 + taskService.closeTask = jasmine.createSpy("closeTask").and.returnValue(result);
  75 + helper.component.cancel = jasmine.createSpy("cancel");
  76 + helper.component.callAccept();
  77 + expect(helper.component.cancel).toHaveBeenCalled();
  78 + expect((<any>helper.component)['eventsHubService'].emitEvent).toHaveBeenCalled();
  79 + });
  80 +
  81 + it("call cancel and emit event when reject was called successfully", () => {
  82 + helper.component.currentTask = <any>{ id: 1 };
  83 + let result = helpers.mocks.promiseResultTemplate({ data: { id: 1 } });
  84 + taskService.closeTask = jasmine.createSpy("closeTask").and.returnValue(result);
  85 + helper.component.cancel = jasmine.createSpy("cancel");
  86 + helper.component.callReject();
  87 + expect(helper.component.cancel).toHaveBeenCalled();
  88 + expect((<any>helper.component)['eventsHubService'].emitEvent).toHaveBeenCalled();
  89 + });
  90 +
  91 + it("reset currentTask and close modal when call cancel", () => {
  92 + let modalInstance = jasmine.createSpyObj("modalInstance", ["close"]);
  93 + helper.component["modalInstance"] = modalInstance;
  94 + helper.component.currentTask = <any>{ id: 1 };
  95 + helper.component.cancel();
  96 + expect(modalInstance.close).toHaveBeenCalled();
  97 + expect(helper.component.currentTask).toBeNull();
  98 + });
  99 +
  100 + it("not fail when call cancel with no modalInstance", () => {
  101 + helper.component["modalInstance"] = null;
  102 + helper.component.currentTask = null;
  103 + helper.component.cancel();
  104 + });
  105 + });
  106 +});
src/app/task/task-list/task-list.component.ts 0 → 100644
@@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
  1 +import { Component, Input, Inject, provide } from "ng-forward";
  2 +import { NotificationService } from "../../shared/services/notification.service";
  3 +import { TaskService } from "../../../lib/ng-noosfero-api/http/task.service";
  4 +import { TaskAcceptComponent } from "./task-accept.component";
  5 +import { Arrays } from "../../../lib/util/arrays";
  6 +import { EventsHubService } from "../../shared/services/events-hub.service";
  7 +import { NoosferoKnownEvents } from "../../known-events";
  8 +
  9 +@Component({
  10 + selector: "task-list",
  11 + templateUrl: "app/task/task-list/task-list.html",
  12 + directives: [TaskAcceptComponent],
  13 + providers: [
  14 + provide('eventsHubService', { useClass: EventsHubService })
  15 + ]
  16 +})
  17 +@Inject(NotificationService, "$scope", "$uibModal", TaskService, EventsHubService)
  18 +export class TaskListComponent {
  19 +
  20 + @Input() tasks: noosfero.Task[];
  21 +
  22 + private taskTemplates = ["AddFriend", "AddMember", "CreateCommunity", "SuggestArticle", "AbuseComplaint"];
  23 +
  24 + currentTask: noosfero.Task;
  25 + confirmationTask: noosfero.Task;
  26 + eventsNames: NoosferoKnownEvents;
  27 + private modalInstance: any = null;
  28 +
  29 + constructor(private notificationService: NotificationService,
  30 + private $scope: ng.IScope,
  31 + private $uibModal: any,
  32 + private taskService: TaskService,
  33 + private eventsHubService: EventsHubService) {
  34 +
  35 + this.eventsNames = new NoosferoKnownEvents();
  36 + }
  37 +
  38 + ngOnInit() {
  39 + this.eventsHubService.subscribeToEvent(this.eventsNames.TASK_CLOSED, (task: noosfero.Task) => {
  40 + Arrays.remove(this.tasks, task);
  41 + });
  42 + }
  43 +
  44 + getTaskTemplate(task: noosfero.Task) {
  45 + if (this.taskTemplates.indexOf(task.type) >= 0) {
  46 + let templateName = this.getTemplateName(task);
  47 + return `app/task/types/${templateName}/${templateName}.html`;
  48 + } else {
  49 + return 'app/task/types/default.html';
  50 + }
  51 + }
  52 +
  53 + accept(task: noosfero.Task) {
  54 + this.closeTask(task, task.accept_details, "app/task/task-list/accept.html", () => { this.callAccept(); });
  55 + }
  56 +
  57 + reject(task: noosfero.Task) {
  58 + this.closeTask(task, task.reject_details, "app/task/task-list/reject.html", () => { this.callReject(); });
  59 + }
  60 +
  61 + private closeTask(task: noosfero.Task, hasDetails: boolean, templateUrl: string, confirmationFunction: Function) {
  62 + this.currentTask = task;
  63 + this.confirmationTask = <any>{ id: task.id };
  64 + if (hasDetails) {
  65 + this.modalInstance = this.$uibModal.open({
  66 + templateUrl: templateUrl,
  67 + controller: TaskListComponent,
  68 + controllerAs: 'modal',
  69 + bindToController: true,
  70 + scope: this.$scope
  71 + });
  72 + } else {
  73 + confirmationFunction();
  74 + }
  75 + }
  76 +
  77 + callAccept() {
  78 + this.callCloseTask("finish", "tasks.actions.accept.title", "tasks.actions.accept.message");
  79 + }
  80 +
  81 + callReject() {
  82 + this.callCloseTask("cancel", "tasks.actions.reject.title", "tasks.actions.reject.message");
  83 + }
  84 +
  85 + private callCloseTask(action: string, title: string, message: string) {
  86 + this.taskService.closeTask(this.confirmationTask, action).then(() => {
  87 + this.eventsHubService.emitEvent(this.eventsNames.TASK_CLOSED, this.currentTask);
  88 + this.notificationService.success({ title: title, message: message });
  89 + }).finally(() => {
  90 + this.cancel();
  91 + });
  92 + }
  93 +
  94 + cancel() {
  95 + if (this.modalInstance) {
  96 + this.modalInstance.close();
  97 + this.modalInstance = null;
  98 + }
  99 + this.currentTask = null;
  100 + this.confirmationTask = null;
  101 + }
  102 +
  103 + private getTemplateName(task: noosfero.Task) {
  104 + return task.type.replace(/::/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  105 + }
  106 +
  107 +}
src/app/task/task-list/task-list.html 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +<ul class="task-list">
  2 + <li class="task-group" ng-repeat="(target, tasks) in ctrl.tasks | groupBy: 'target.name'">
  3 + <div class="task-target">
  4 + <noosfero-profile-image ng-if="tasks[0].target.type" [profile]="tasks[0].target"></noosfero-profile-image>
  5 + <div class="target-name">{{target}}</div>
  6 + </div>
  7 + <div class="task-body" ng-repeat="task in tasks | orderBy: 'created_at':true">
  8 + <div class="task">
  9 + <ng-include src="ctrl.getTaskTemplate(task)"></ng-include>
  10 + </div>
  11 + <div class="actions">
  12 + <a href="#" ng-if="!task.accept_disabled" ng-click="ctrl.accept(task)" class="accept" uib-tooltip="{{'tasks.actions.accept' | translate}}">
  13 + <i class="fa fa-check"></i>
  14 + </a>
  15 + <a href="#" ng-if="!task.reject_disabled" ng-click="ctrl.reject(task)" class="reject" uib-tooltip="{{'tasks.actions.reject' | translate}}">
  16 + <i class="fa fa-close"></i>
  17 + </a>
  18 + </div>
  19 + <span class="time">
  20 + <span class="bullet-separator">•</span> <span am-time-ago="task.created_at | dateFormat"></span>
  21 + </span>
  22 + </div>
  23 + </li>
  24 +</ul>
src/app/task/task-list/task-list.scss 0 → 100644
@@ -0,0 +1,86 @@ @@ -0,0 +1,86 @@
  1 +$task-action-accept-color: #77c123;
  2 +$task-action-reject-color: #d64e18;
  3 +
  4 +.task-list {
  5 + width: 100%;
  6 + padding: 0;
  7 + list-style-type: none;
  8 + .task-group {
  9 + border-top: 1px solid #f3f3f3;
  10 + padding: 4px 16px 8px 16px;
  11 + }
  12 + .task-target {
  13 + margin-top: 12px;
  14 + .profile-image {
  15 + color: #2c3e50;
  16 + font-size: 25px;
  17 + width: 25px;
  18 + display: inline-block;
  19 + @extend .img-rounded;
  20 + }
  21 + .target-name {
  22 + display: inline-block;
  23 + margin-left: 10px;
  24 + font-size: 18px;
  25 + font-weight: bold;
  26 + }
  27 + }
  28 + .task-body {
  29 + margin-left: 35px;
  30 + padding: 2px;
  31 + .task {
  32 + display: inline-block;
  33 + color: #949494;
  34 + .task-icon {
  35 + font-size: 18px;
  36 + color: #e84e40;
  37 + }
  38 + .requestor, .target {
  39 + font-style: italic;
  40 + color: #676767;
  41 + }
  42 + }
  43 + .actions {
  44 + display: inline-block;
  45 + font-size: 19px;
  46 + a {
  47 + text-decoration: none;
  48 + }
  49 + .accept {
  50 + color: $task-action-accept-color;
  51 + &:hover {
  52 + color: darken($task-action-accept-color, 10%);
  53 + }
  54 + }
  55 + .reject {
  56 + color: $task-action-reject-color;
  57 + &:hover {
  58 + color: darken($task-action-reject-color, 10%);
  59 + }
  60 + }
  61 + }
  62 + .time {
  63 + color: #c1c1c1;
  64 + font-size: 12px;
  65 + .bullet-separator {
  66 + font-size: 10px;
  67 + color: #d1d1d1;
  68 + }
  69 + }
  70 + }
  71 +}
  72 +.task-confirmation {
  73 + @extend .form-group;
  74 + .confirmation-details {
  75 + margin-bottom: 20px;
  76 + }
  77 + .confirmation-title {
  78 + text-align: center;
  79 + font-size: 30px;
  80 + font-weight: bold;
  81 + margin-bottom: 20px;
  82 + }
  83 + .actions {
  84 + text-align: center;
  85 + }
  86 +}
src/app/task/tasks-menu/tasks-menu.component.spec.ts 0 → 100644
@@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
  1 +import { Provider, provide, Component } from 'ng-forward';
  2 +import * as helpers from "../../../spec/helpers";
  3 +import { ComponentTestHelper, createClass } from '../../../spec/component-test-helper';
  4 +import { TasksMenuComponent } from './tasks-menu.component';
  5 +import { AuthEvents } from "./../../login";
  6 +
  7 +const htmlTemplate: string = '<tasks-menu></tasks-menu>';
  8 +
  9 +describe("Components", () => {
  10 + describe("Task Menu Component", () => {
  11 +
  12 + let helper: ComponentTestHelper<TasksMenuComponent>;
  13 + let taskService = jasmine.createSpyObj("taskService", ["getAllPending"]);
  14 + let tasks = [{ id: 1 }, { id: 2 }];
  15 + let eventsHubService = jasmine.createSpyObj("eventsHubService", ["subscribeToEvent", "emitEvent"]);
  16 + taskService.getAllPending = jasmine.createSpy("getAllPending").and.returnValue(Promise.resolve({ headers: () => { }, data: tasks }));
  17 +
  18 + beforeEach(angular.mock.module("templates"));
  19 +
  20 + beforeEach((done) => {
  21 + let cls = createClass({
  22 + template: htmlTemplate,
  23 + directives: [TasksMenuComponent],
  24 + providers: [
  25 + helpers.createProviderToValue("TaskService", taskService),
  26 + helpers.createProviderToValue("EventsHubService", eventsHubService),
  27 + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({}))
  28 + ]
  29 + });
  30 + helper = new ComponentTestHelper<TasksMenuComponent>(cls, done);
  31 + });
  32 +
  33 + it("load person tasks", () => {
  34 + expect(taskService.getAllPending).toHaveBeenCalled();
  35 + });
  36 +
  37 + it("load person tasks when receive a login event", () => {
  38 + helper.component.loadTasks = jasmine.createSpy("loadTasks");
  39 + helper.component.ngOnInit();
  40 + (<any>helper.component['authService'])[AuthEvents[AuthEvents.loginSuccess]].next({});
  41 + expect(helper.component.loadTasks).toHaveBeenCalled();
  42 + });
  43 + });
  44 +});
src/app/task/tasks-menu/tasks-menu.component.ts 0 → 100644
@@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
  1 +import { Component, Inject } from "ng-forward";
  2 +import { TaskService } from "../../../lib/ng-noosfero-api/http/task.service";
  3 +import { AuthService, SessionService, AuthEvents } from "./../../login";
  4 +import { EventsHubService } from "../../shared/services/events-hub.service";
  5 +import { NoosferoKnownEvents } from "../../known-events";
  6 +
  7 +@Component({
  8 + selector: "tasks-menu",
  9 + templateUrl: "app/task/tasks-menu/tasks-menu.html"
  10 +})
  11 +@Inject(TaskService, SessionService, AuthService, EventsHubService)
  12 +export class TasksMenuComponent {
  13 +
  14 + tasks: noosfero.Task[];
  15 + total: number;
  16 + perPage = 5;
  17 + person: noosfero.Person;
  18 + eventsNames: NoosferoKnownEvents;
  19 +
  20 + constructor(private taskService: TaskService,
  21 + private session: SessionService,
  22 + private authService: AuthService,
  23 + private eventsHubService: EventsHubService) {
  24 +
  25 + this.eventsNames = new NoosferoKnownEvents();
  26 + }
  27 +
  28 + ngOnInit() {
  29 + this.eventsHubService.subscribeToEvent(this.eventsNames.TASK_CLOSED, (task: noosfero.Task) => {
  30 + this.total--;
  31 + });
  32 + this.authService.subscribe(AuthEvents[AuthEvents.loginSuccess], () => {
  33 + this.loadTasks();
  34 + });
  35 + this.loadTasks();
  36 + }
  37 +
  38 + loadTasks() {
  39 + if (!this.session.currentUser()) return;
  40 + this.person = this.session.currentUser().person;
  41 + this.taskService.getAllPending({ per_page: this.perPage }).then((result: noosfero.RestResult<noosfero.Task[]>) => {
  42 + this.total = result.headers('total');
  43 + this.tasks = result.data;
  44 + });
  45 + }
  46 +}
src/app/task/tasks-menu/tasks-menu.html 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +<li class="btn-nav tasks-menu" uib-dropdown ng-show="ctrl.total > 0" ng-if="ctrl.total">
  2 + <a href="#" uib-dropdown-toggle>
  3 + <i class="fa fa-bell-o fa-fw fa-2x"></i>
  4 + <span class="badge">{{ctrl.total}}</span>
  5 + </a>
  6 + <div class="dropdown-menu task-panel" uib-dropdown-menu>
  7 + <div class="task-menu-header">{{"tasks.menu.header" | translate:{tasks: ctrl.total}:"messageformat"}}</div>
  8 + <task-list [tasks]="ctrl.tasks"></task-list>
  9 + <a ui-sref="main.profile.tasks({profile: ctrl.person.identifier})" class="all-tasks btn btn-default btn-xs">{{"tasks.menu.all" | translate}}</a>
  10 + </div>
  11 +</li>
src/app/task/tasks-menu/tasks-menu.scss 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +tasks-menu {
  2 + .tasks-menu {
  3 + position: relative;
  4 + margin-right: 0;
  5 + .badge {
  6 + position: absolute;
  7 + top: 1px;
  8 + right: 8px;
  9 + border-radius: 6px;
  10 + background-color: #e84e40;
  11 + color: #fff;
  12 + }
  13 + .all-tasks {
  14 + text-align: center;
  15 + width: 100%;
  16 + display: block;
  17 + line-height: 25px;
  18 + }
  19 + .task-panel {
  20 + width: 550px;
  21 + padding: 0;
  22 + }
  23 + .task-menu-header {
  24 + text-align: center;
  25 + padding: 7px;
  26 + font-weight: bold;
  27 + }
  28 + }
  29 +}
src/app/task/tasks/tasks.component.spec.ts 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +import { Provider, provide, Component } from 'ng-forward';
  2 +import * as helpers from "../../../spec/helpers";
  3 +import { ComponentTestHelper, createClass } from '../../../spec/component-test-helper';
  4 +import { TasksComponent } from './tasks.component';
  5 +import { AuthEvents } from "./../../login";
  6 +
  7 +const htmlTemplate: string = '<tasks></tasks>';
  8 +
  9 +describe("Components", () => {
  10 + describe("Task Menu Component", () => {
  11 +
  12 + let helper: ComponentTestHelper<TasksComponent>;
  13 + let taskService = jasmine.createSpyObj("taskService", ["getAllPending"]);
  14 + let tasks = [{ id: 1 }, { id: 2 }];
  15 + taskService.getAllPending = jasmine.createSpy("getAllPending").and.returnValue(Promise.resolve({ headers: () => { }, data: tasks }));
  16 +
  17 + beforeEach(angular.mock.module("templates"));
  18 +
  19 + beforeEach((done) => {
  20 + let cls = createClass({
  21 + template: htmlTemplate,
  22 + directives: [TasksComponent],
  23 + providers: [
  24 + helpers.createProviderToValue("TaskService", taskService)
  25 + ]
  26 + });
  27 + helper = new ComponentTestHelper<TasksComponent>(cls, done);
  28 + });
  29 +
  30 + it("load person tasks", () => {
  31 + expect(taskService.getAllPending).toHaveBeenCalled();
  32 + });
  33 + });
  34 +});
src/app/task/tasks/tasks.component.ts 0 → 100644
@@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
  1 +import { Component, Inject, provide } from "ng-forward";
  2 +import { TaskService } from "../../../lib/ng-noosfero-api/http/task.service";
  3 +
  4 +@Component({
  5 + selector: "tasks",
  6 + templateUrl: "app/task/tasks/tasks.html",
  7 + providers: [
  8 + provide('taskService', { useClass: TaskService })
  9 + ]
  10 +})
  11 +@Inject(TaskService)
  12 +export class TasksComponent {
  13 +
  14 + tasks: noosfero.Task[];
  15 + total: number;
  16 + currentPage: number;
  17 + perPage = 5;
  18 +
  19 + constructor(private taskService: TaskService) {
  20 + this.loadPage();
  21 + }
  22 +
  23 + loadPage() {
  24 + this.taskService.getAllPending({ page: this.currentPage, per_page: this.perPage }).then((result: noosfero.RestResult<noosfero.Task[]>) => {
  25 + this.total = result.headers('total');
  26 + this.tasks = result.data;
  27 + });
  28 + }
  29 +
  30 +}
src/app/task/tasks/tasks.html 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +<h3>{{"tasks.header" | translate}}</h3>
  2 +
  3 +<task-list [tasks]="vm.tasks"></task-list>
  4 +
  5 +<uib-pagination ng-model="vm.currentPage" total-items="vm.total" class="pagination-sm center-block"
  6 + boundary-links="true" items-per-page="vm.perPage" ng-change="vm.loadPage()"
  7 + first-text="«" last-text="»" previous-text="‹" next-text="›">
  8 +</uib-pagination>
src/app/task/types/abuse-complaint/abuse-complaint.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-user-times"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.abuse_complaint.message" | translate}}
src/app/task/types/add-friend/add-friend.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-user-plus"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.add_friend.message" | translate}} <span class="target">{{task.target.name}}</span>
src/app/task/types/add-member/add-member-accept.html 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +<div class="add-member-details">
  2 + <label>{{"tasks.add_member.accept.select_role" | translate}}</label>
  3 + <div class="form-group roles">
  4 + <div class="checkbox-nice" ng-repeat="role in ctrl.roles">
  5 + <input type="checkbox" id="role_{{role.name}}" (click)="ctrl.toggleSelection(role)">
  6 + <label for="role_{{role.name}}">
  7 + {{role.name}}
  8 + </label>
  9 + </div>
  10 + </div>
  11 +</div>
src/app/task/types/add-member/add-member-task-accept-component.spec.ts 0 → 100644
@@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
  1 +import { Provider, provide, Component } from 'ng-forward';
  2 +import * as helpers from "../../../../spec/helpers";
  3 +import { ComponentTestHelper, createClass } from '../../../../spec/component-test-helper';
  4 +import { AddMemberTaskAcceptComponent } from './add-member-task-accept.component';
  5 +
  6 +const htmlTemplate: string = '<add-member-task-accept [task]="ctrl.task" [confirmation-task]="ctrl.confirmationTask"></add-member-task-accept>';
  7 +
  8 +describe("Components", () => {
  9 + describe("Add Member Task Accept Component", () => {
  10 +
  11 + let helper: ComponentTestHelper<AddMemberTaskAcceptComponent>;
  12 + let roleService = jasmine.createSpyObj("roleService", ["getByProfile"]);
  13 + let roles = [{ id: 1 }, { id: 2 }];
  14 + let task = <any>{ target: { id: 5 } };
  15 + roleService.getByProfile = jasmine.createSpy("getByProfile").and.returnValue(Promise.resolve({ headers: () => { }, data: roles }));
  16 +
  17 + beforeEach(angular.mock.module("templates"));
  18 +
  19 + beforeEach((done) => {
  20 + let cls = createClass({
  21 + template: htmlTemplate,
  22 + directives: [AddMemberTaskAcceptComponent],
  23 + providers: [
  24 + helpers.createProviderToValue("RoleService", roleService)
  25 + ].concat(helpers.provideFilters("translateFilter")),
  26 + properties: { task: task, confirmationTask: task }
  27 + });
  28 + helper = new ComponentTestHelper<AddMemberTaskAcceptComponent>(cls, done);
  29 + });
  30 +
  31 + it("insert role id in roles list when toggle selection", () => {
  32 + let role = { id: 1 };
  33 + helper.component.toggleSelection(<any>role);
  34 + expect(helper.component.confirmationTask.roles).toEqual([role.id]);
  35 + });
  36 +
  37 + it("remove role id from roles list when toggle selection", () => {
  38 + let role = { id: 1 };
  39 + helper.component.confirmationTask.roles = [role.id];
  40 + helper.component.toggleSelection(<any>role);
  41 + expect(helper.component.confirmationTask.roles).toEqual([]);
  42 + });
  43 + });
  44 +});
src/app/task/types/add-member/add-member-task-accept.component.ts 0 → 100644
@@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
  1 +import { Component, Input, Inject } from "ng-forward";
  2 +import { RoleService } from "../../../../lib/ng-noosfero-api/http/role.service";
  3 +
  4 +@Component({
  5 + selector: "add-member-task-accept",
  6 + templateUrl: "app/task/types/add-member/add-member-accept.html",
  7 +})
  8 +@Inject(RoleService)
  9 +export class AddMemberTaskAcceptComponent {
  10 +
  11 + @Input() task: noosfero.Task;
  12 + @Input() confirmationTask: noosfero.AddMemberTask;
  13 + roles: noosfero.Role[];
  14 +
  15 + constructor(private roleService: RoleService) { }
  16 +
  17 + ngOnInit() {
  18 + if (!this.task.target) return;
  19 + this.confirmationTask.roles = [];
  20 + this.roleService.getByProfile(this.task.target.id).then((result: noosfero.RestResult<noosfero.Role[]>) => {
  21 + this.roles = result.data;
  22 + });
  23 + }
  24 +
  25 + toggleSelection(role: noosfero.Role) {
  26 + let index = this.confirmationTask.roles.indexOf(role.id);
  27 + if (index >= 0) {
  28 + this.confirmationTask.roles.splice(index, 1);
  29 + } else {
  30 + this.confirmationTask.roles.push(role.id);
  31 + }
  32 + }
  33 +}
src/app/task/types/add-member/add-member.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-user-plus"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.add_member.message" | translate}} <span class="target">{{task.target.name}}</span>
src/app/task/types/add-member/add-member.scss 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +.add-member-details {
  2 + .roles {
  3 + margin-left: 40px;
  4 + }
  5 +}
src/app/task/types/create-community/create-community.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-users"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.create_community.message" | translate}} <span class="target">{{task.data.name}}</span>
src/app/task/types/default.html 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<!-- not implemented yet -->
src/app/task/types/suggest-article/suggest-article.html 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +<i class="task-icon fa fa-fw fa-file-text"></i>
  2 +<span class="requestor">{{task.requestor.name}}</span> {{"tasks.suggest_article.message" | translate}} <span class="target">{{task.data.article.name}}</span>
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",
@@ -19,8 +23,10 @@ @@ -19,8 +23,10 @@
19 "profile.others_info": "Others", 23 "profile.others_info": "Others",
20 "profile.community.title": "Community", 24 "profile.community.title": "Community",
21 "profile.person.title": "Person", 25 "profile.person.title": "Person",
  26 + "profile.image.upload": "Upload Photo",
22 "activities.title": "Activities", 27 "activities.title": "Activities",
23 "activities.create_article.description": "has published on", 28 "activities.create_article.description": "has published on",
  29 + "activities.scrap.description": "wrote in its timeline",
24 "activities.add_member_in_community.description": "has joined the community", 30 "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}}:", 31 "activities.new_friendship.description": "has made {friends, plural, one{one new friend} other{# new friends}}:",
26 "auth.title": "Great to have you back!", 32 "auth.title": "Great to have you back!",
@@ -28,6 +34,7 @@ @@ -28,6 +34,7 @@
28 "auth.form.password": "Password", 34 "auth.form.password": "Password",
29 "auth.form.keepLoggedIn": "Keep me logged in", 35 "auth.form.keepLoggedIn": "Keep me logged in",
30 "auth.form.login_button": "Login", 36 "auth.form.login_button": "Login",
  37 + "auth.createAccount": "Create account",
31 "navbar.content_viewer_actions.new_item": "New Item", 38 "navbar.content_viewer_actions.new_item": "New Item",
32 "navbar.profile_actions.new_item": "New Item", 39 "navbar.profile_actions.new_item": "New Item",
33 "navbar.content_viewer_actions.new_post": "New Post", 40 "navbar.content_viewer_actions.new_post": "New Post",
@@ -97,5 +104,47 @@ @@ -97,5 +104,47 @@
97 "block.edition.display_user.all": "All users", 104 "block.edition.display_user.all": "All users",
98 "block.edition.display_user.logged": "Logged", 105 "block.edition.display_user.logged": "Logged",
99 "block.edition.display_user.not_logged": "Not logged", 106 "block.edition.display_user.not_logged": "Not logged",
100 - "block.edition.language.label": "Show for:" 107 + "block.edition.language.label": "Show for:",
  108 + "activities.event.description": "Event on",
  109 + "time.at": "at",
  110 + "date.on": "On",
  111 + "account.register.welcomeMessageTitle": "Nice to have you there!",
  112 + "account.register.fullNameLabel": "Full name",
  113 + "account.register.usernameLabel": "Username",
  114 + "account.register.emailLabel": "Email",
  115 + "account.register.passwordLabel": "Password",
  116 + "account.register.passwordConfirmationLabel": "Password confirmation",
  117 + "account.register.accountCreatingMessage": "By creating an account, you are agreeing with the ",
  118 + "account.register.signupMessage": "Register",
  119 + "account.register.haveAccountMessage": "Already have an account?",
  120 + "account.register.termsOfUseMessage": "terms of use",
  121 + "account.register.success.title": "Good job!",
  122 + "account.register.success.message": "Account created!",
  123 + "account.register.passwordConfirmation.failed": "Wrong password confirmation",
  124 + "messages.invalid.required": "This field is required",
  125 + "messages.invalid.maxlength": "This field is too long",
  126 + "messages.invalid.minlength": "This field is too short",
  127 + "messages.invalid.email": "This needs to be a valid email",
  128 + "messages.invalid.passwordMatch": "Your passwords did not match",
  129 + "tasks.menu.all": "All tasks",
  130 + "tasks.menu.all": "See all tasks",
  131 + "tasks.menu.header": "You have {tasks, plural, one{one pending task} other{# pending tasks}}",
  132 + "tasks.actions.accept": "Accept",
  133 + "tasks.actions.reject": "Reject",
  134 + "tasks.header": "Tasks",
  135 + "tasks.actions.accept.confirmation.title": "Confirm task acceptance?",
  136 + "tasks.actions.reject.confirmation.title": "Confirm task rejection?",
  137 + "tasks.actions.confirmation.yes": "Yes",
  138 + "tasks.actions.confirmation.cancel": "Cancel",
  139 + "tasks.actions.accept.title": "Good job!",
  140 + "tasks.actions.reject.title": "Good job!",
  141 + "tasks.actions.accept.message": "Task Accepted",
  142 + "tasks.actions.reject.message": "Task Rejected",
  143 + "tasks.actions.reject.explanation.label": "Rejection explanation",
  144 + "tasks.add_member.accept.select_role": "Select Roles:",
  145 + "tasks.abuse_complaint.message": "was reported due to inappropriate behavior",
  146 + "tasks.add_friend.message": "wants to be friend of",
  147 + "tasks.add_member.message": "wants to join",
  148 + "tasks.create_community.message": "wants to create a new community:",
  149 + "tasks.suggest_article.message": "suggested a new article:"
101 } 150 }
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",
@@ -19,8 +23,10 @@ @@ -19,8 +23,10 @@
19 "profile.others_info": "Outras informações", 23 "profile.others_info": "Outras informações",
20 "profile.community.title": "Comunidade", 24 "profile.community.title": "Comunidade",
21 "profile.person.title": "Pessoa", 25 "profile.person.title": "Pessoa",
  26 + "profile.image.upload": "Enviar photo",
22 "activities.title": "Atividades", 27 "activities.title": "Atividades",
23 "activities.create_article.description": "publicou em", 28 "activities.create_article.description": "publicou em",
  29 + "activities.scrap.description": "escreveu em sua linha do tempo",
24 "activities.add_member_in_community.description": "entrou na comunidade", 30 "activities.add_member_in_community.description": "entrou na comunidade",
25 "activities.new_friendship.description": "fez {friends, plural, one{um novo amigo} other{# novos amigos}}:", 31 "activities.new_friendship.description": "fez {friends, plural, one{um novo amigo} other{# novos amigos}}:",
26 "auth.title": "Legal ter você de volta!", 32 "auth.title": "Legal ter você de volta!",
@@ -28,9 +34,10 @@ @@ -28,9 +34,10 @@
28 "auth.form.password": "Senha", 34 "auth.form.password": "Senha",
29 "auth.form.keepLoggedIn": "Continuar logado", 35 "auth.form.keepLoggedIn": "Continuar logado",
30 "auth.form.login_button": "Login", 36 "auth.form.login_button": "Login",
  37 + "auth.createAccount": "Criar conta",
31 "navbar.content_viewer_actions.new_item": "Novo Item", 38 "navbar.content_viewer_actions.new_item": "Novo Item",
32 "navbar.profile_actions.new_item": "Novo Item", 39 "navbar.profile_actions.new_item": "Novo Item",
33 - "navbar.content_viewer_actions.new_post": "Novo Artigo", 40 + "navbar.content_viewer_actions.new_post": "Novo Post",
34 "navbar.content_viewer_actions.new_discussion": "Nova Consulta", 41 "navbar.content_viewer_actions.new_discussion": "Nova Consulta",
35 "navbar.profile_actions.new_discussion": "Nova Discussão", 42 "navbar.profile_actions.new_discussion": "Nova Discussão",
36 "notification.error.default.message": "Algo deu errado!", 43 "notification.error.default.message": "Algo deu errado!",
@@ -97,5 +104,50 @@ @@ -97,5 +104,50 @@
97 "block.edition.display_user.all": "Todos os usuários", 104 "block.edition.display_user.all": "Todos os usuários",
98 "block.edition.display_user.logged": "Logados", 105 "block.edition.display_user.logged": "Logados",
99 "block.edition.display_user.not_logged": "Não logados", 106 "block.edition.display_user.not_logged": "Não logados",
100 - "block.edition.language.label": "Exibir para:" 107 + "block.edition.language.label": "Exibir para:",
  108 + "activities.event.description": "Evento em",
  109 + "time.at": "às",
  110 + "date.on": "Em",
  111 + "account.register.welcomeMessageTitle": "Ótimo ter você aqui!",
  112 + "account.register.seeMoreMessage": "Saiba mais ",
  113 + "account.register.informationsMessage": "informações",
  114 + "account.register.fullNameLabel": "Nome completo",
  115 + "account.register.lastNameLabel": "Sobrenome",
  116 + "account.register.usernameLabel": "Nome de usuário",
  117 + "account.register.emailLabel": "Email",
  118 + "account.register.passwordLabel": "Senha",
  119 + "account.register.passwordConfirmationLabel": "Confirmar senha",
  120 + "account.register.accountCreatingMessage": "Ao criar uma conta, você está concordando com os ",
  121 + "account.register.termsOfUseMessage": "termos de uso",
  122 + "account.register.signupMessage": "Criar Conta",
  123 + "account.register.haveAccountMessage": "Já possui uma conta?",
  124 + "account.register.success.title": "Bom trabalho!",
  125 + "account.register.success.message": "Conta criada com sucesso!",
  126 + "account.register.passwordConfirmation.failed": "A confirmação de senha não corresponde à senha",
  127 + "messages.invalid.required": "Campo obrigatório",
  128 + "messages.invalid.maxlength": "O valor é muito longo",
  129 + "messages.invalid.minlength": "O valor é muito curto",
  130 + "messages.invalid.email": "Informe um email válido",
  131 + "messages.invalid.passwordMatch": "As senhas não coincidem",
  132 + "tasks.menu.all": "Todas as tarefas",
  133 + "tasks.menu.all": "Veja todas as tarefas",
  134 + "tasks.menu.header": "Você tem {tasks, plural, one{uma tarefa pendente} other{# tarefas pendentes}}",
  135 + "tasks.actions.accept": "Aceitar",
  136 + "tasks.actions.reject": "Rejeitar",
  137 + "tasks.header": "Tarefas",
  138 + "tasks.actions.accept.confirmation.title": "Confirmar aprovação da tarefa?",
  139 + "tasks.actions.reject.confirmation.title": "Confirmar reprovação da tarefa?",
  140 + "tasks.actions.confirmation.yes": "Sim",
  141 + "tasks.actions.confirmation.cancel": "Cancelar",
  142 + "tasks.actions.accept.title": "Bom trabalho!",
  143 + "tasks.actions.reject.title": "Bom trabalho!",
  144 + "tasks.actions.accept.message": "Tarefa Aceita",
  145 + "tasks.actions.reject.message": "Tarefa Rejeitada",
  146 + "tasks.actions.reject.explanation.label": "Motivo da rejeição",
  147 + "tasks.add_member.accept.select_role": "Selecionar papéis:",
  148 + "tasks.abuse_complaint.message": "foi notificado por comportamento inadequado",
  149 + "tasks.add_friend.message": "quer ser amigo de",
  150 + "tasks.add_member.message": "quer entrar em",
  151 + "tasks.create_community.message": "quer criar uma nova comunidade:",
  152 + "tasks.suggest_article.message": "sugeriu um novo artigo:"
101 } 153 }
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/person.service.ts
@@ -28,4 +28,19 @@ export class PersonService extends RestangularService&lt;noosfero.Person&gt; { @@ -28,4 +28,19 @@ export class PersonService extends RestangularService&lt;noosfero.Person&gt; {
28 p.catch(this.getHandleErrorFunction<noosfero.RestResult<any>>(deferred)); 28 p.catch(this.getHandleErrorFunction<noosfero.RestResult<any>>(deferred));
29 return deferred.promise; 29 return deferred.promise;
30 } 30 }
  31 +
  32 + uploadImage(profile: noosfero.Profile, base64_image_json: any) {
  33 + let headers = { 'Content-Type': 'application/json' };
  34 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Profile>>();
  35 + // TODO dynamically copy the selected attributes to update
  36 + let attributesToUpdate: any = {
  37 + person: { image_builder: base64_image_json }
  38 + };
  39 + let restRequest: ng.IPromise<noosfero.RestResult<any>> =
  40 + this.getElement(profile.id).customPOST(attributesToUpdate, null, null, headers);
  41 + restRequest.then(this.getHandleSuccessFunction(deferred))
  42 + .catch(this.getHandleErrorFunction(deferred));
  43 + return deferred.promise;
  44 + }
  45 +
31 } 46 }
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/http/role.service.spec.ts 0 → 100644
@@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
  1 +import { RoleService } from "./role.service";
  2 +
  3 +describe("Services", () => {
  4 +
  5 + describe("Role Service", () => {
  6 +
  7 + let $httpBackend: ng.IHttpBackendService;
  8 + let roleService: RoleService;
  9 +
  10 + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => {
  11 + $translateProvider.translations('en', {});
  12 + }));
  13 +
  14 + beforeEach(inject((_$httpBackend_: ng.IHttpBackendService, _RoleService_: RoleService) => {
  15 + $httpBackend = _$httpBackend_;
  16 + roleService = _RoleService_;
  17 + }));
  18 +
  19 +
  20 + describe("Succesfull requests", () => {
  21 +
  22 + it("list organization roles", (done) => {
  23 + $httpBackend.expectGET(`/api/v1/profiles/1/roles`).respond(200, { roles: [{ id: 1 }] });
  24 + roleService.getByProfile(1).then((result: noosfero.RestResult<noosfero.Role[]>) => {
  25 + expect(result.data).toEqual([{ id: 1 }]);
  26 + done();
  27 + });
  28 + $httpBackend.flush();
  29 + });
  30 + });
  31 +
  32 + });
  33 +});
src/lib/ng-noosfero-api/http/role.service.ts 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +import { Injectable, Inject } from "ng-forward";
  2 +import { RestangularService } from "./restangular_service";
  3 +
  4 +@Injectable()
  5 +@Inject("Restangular", "$q", "$log")
  6 +export class RoleService extends RestangularService<noosfero.Role> {
  7 +
  8 + constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService) {
  9 + super(Restangular, $q, $log);
  10 + }
  11 +
  12 + getResourcePath() {
  13 + return "roles";
  14 + }
  15 +
  16 + getDataKeys() {
  17 + return {
  18 + singular: 'role',
  19 + plural: 'roles'
  20 + };
  21 + }
  22 +
  23 + getByProfile(profileId: number, params: any = {}) {
  24 + return this.list(this.restangularService.one("profiles", profileId), params);
  25 + }
  26 +
  27 +}
src/lib/ng-noosfero-api/http/task.service.spec.ts 0 → 100644
@@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
  1 +import { TaskService } from "./task.service";
  2 +
  3 +
  4 +describe("Services", () => {
  5 +
  6 + describe("Task Service", () => {
  7 +
  8 + let $httpBackend: ng.IHttpBackendService;
  9 + let taskService: TaskService;
  10 +
  11 + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => {
  12 + $translateProvider.translations('en', {});
  13 + }));
  14 +
  15 + beforeEach(inject((_$httpBackend_: ng.IHttpBackendService, _TaskService_: TaskService) => {
  16 + $httpBackend = _$httpBackend_;
  17 + taskService = _TaskService_;
  18 + }));
  19 +
  20 +
  21 + describe("Succesfull requests", () => {
  22 +
  23 + it("list pending tasks", (done) => {
  24 + $httpBackend.expectGET(`/api/v1/tasks?all_pending=true&status=1`).respond(200, { tasks: [{ id: 1 }] });
  25 + taskService.getAllPending().then((result: noosfero.RestResult<noosfero.Task[]>) => {
  26 + expect(result.data).toEqual([{ id: 1 }]);
  27 + done();
  28 + });
  29 + $httpBackend.flush();
  30 + });
  31 +
  32 + it("finish a task", (done) => {
  33 + let taskId = 1;
  34 + let task: noosfero.Task = <any>{ id: taskId };
  35 + $httpBackend.expectPUT(`/api/v1/tasks/${taskId}/finish`).respond(200, { task: { id: taskId } });
  36 + taskService.finishTask(task).then((result: noosfero.RestResult<noosfero.Task>) => {
  37 + expect(result.data).toEqual({ id: 1 });
  38 + done();
  39 + });
  40 + $httpBackend.flush();
  41 + });
  42 +
  43 + it("cancel a task", (done) => {
  44 + let taskId = 1;
  45 + let task: noosfero.Task = <any>{ id: taskId };
  46 + $httpBackend.expectPUT(`/api/v1/tasks/${taskId}/cancel`).respond(200, { task: { id: taskId } });
  47 + taskService.cancelTask(task).then((result: noosfero.RestResult<noosfero.Task>) => {
  48 + expect(result.data).toEqual({ id: 1 });
  49 + done();
  50 + });
  51 + $httpBackend.flush();
  52 + });
  53 + });
  54 +
  55 + });
  56 +});
src/lib/ng-noosfero-api/http/task.service.ts 0 → 100644
@@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
  1 +import { Injectable, Inject } from "ng-forward";
  2 +import { RestangularService } from "./restangular_service";
  3 +
  4 +@Injectable()
  5 +@Inject("Restangular", "$q", "$log")
  6 +export class TaskService extends RestangularService<noosfero.Task> {
  7 +
  8 + constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService) {
  9 + super(Restangular, $q, $log);
  10 + }
  11 +
  12 + getResourcePath() {
  13 + return "tasks";
  14 + }
  15 +
  16 + getDataKeys() {
  17 + return {
  18 + singular: 'task',
  19 + plural: 'tasks'
  20 + };
  21 + }
  22 +
  23 + getAllPending(params: any = {}) {
  24 + params['all_pending'] = true;
  25 + params['status'] = 1;
  26 + return this.list(null, params);
  27 + }
  28 +
  29 + finishTask(task: noosfero.Task) {
  30 + return this.closeTask(task, "finish");
  31 + }
  32 +
  33 + cancelTask(task: noosfero.Task) {
  34 + return this.closeTask(task, "cancel");
  35 + }
  36 +
  37 + closeTask(task: noosfero.Task, action: string) {
  38 + let element = this.getElement(task.id);
  39 + delete task.id;
  40 + let put = element.customPUT({ task: task }, action);
  41 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Task>>();
  42 + put.then(this.getHandleSuccessFunction<noosfero.RestResult<noosfero.Task>>(deferred));
  43 + put.catch(this.getHandleErrorFunction<noosfero.RestResult<noosfero.Task>>(deferred));
  44 + return deferred.promise;
  45 + }
  46 +}
src/lib/ng-noosfero-api/interfaces/add_member_task.ts 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +namespace noosfero {
  2 + /**
  3 + * @ngdoc interface
  4 + * @name noosfero.AddMemberTask
  5 + * @description
  6 + * A representation of an AddMemberTask in Noosfero.
  7 + */
  8 + export interface AddMemberTask extends Task {
  9 + roles: number[];
  10 + }
  11 +}
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/lib/ng-noosfero-api/interfaces/role.ts 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +namespace noosfero {
  2 + /**
  3 + * @ngdoc interface
  4 + * @name noosfero.Role
  5 + * @description
  6 + * A representation of a Role in Noosfero.
  7 + */
  8 + export interface Role extends RestModel {
  9 + name: string;
  10 + key: string;
  11 + }
  12 +}
src/lib/ng-noosfero-api/interfaces/task.ts 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +namespace noosfero {
  2 + /**
  3 + * @ngdoc interface
  4 + * @name noosfero.Task
  5 + * @description
  6 + * A representation of a Task in Noosfero.
  7 + */
  8 + export interface Task extends RestModel {
  9 + type: string;
  10 + accept_details: boolean;
  11 + reject_details: boolean;
  12 + target: noosfero.Profile | noosfero.Environment;
  13 + }
  14 +}
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: {
@@ -223,11 +237,16 @@ export var mocks: any = { @@ -223,11 +237,16 @@ export var mocks: any = {
223 } 237 }
224 }, 238 },
225 promiseResultTemplate: (response?: {}) => { 239 promiseResultTemplate: (response?: {}) => {
226 -  
227 - return {  
228 - then: (func?: (response: any) => void) => {  
229 - if (func) { return func(response); } 240 + let callback = (func?: (response: any) => any) => {
  241 + if (func) {
  242 + let ret = func(response);
  243 + if (ret && typeof ret.then === "function") return ret;
230 } 244 }
  245 + return mocks.promiseResultTemplate(response);
  246 + };
  247 + return {
  248 + then: callback,
  249 + finally: callback
231 }; 250 };
232 }, 251 },
233 $log: { 252 $log: {
@@ -240,6 +259,7 @@ export var mocks: any = { @@ -240,6 +259,7 @@ export var mocks: any = {
240 }, 259 },
241 notificationService: { 260 notificationService: {
242 success: () => { }, 261 success: () => { },
243 - confirmation: () => { } 262 + confirmation: () => { },
  263 + error: () => { }
244 } 264 }
245 }; 265 };
@@ -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",