Compare View
Commits (42)
-
…T_NAMES to be an array of strings
-
Events hub Added a service called EventsHubService to serve as central point for events propagated through the Noosfero Angular Application. See merge request !53
-
Adjusts the style of the component according with its prototype Translates messages of the signup page. Fixes the post request, using the 2nd parameter as string instead of object. Signed-off-by: DylanGuedes <djmgguedes@gmail.com> Signed-off-by: Omar Junior <omarroinuj@gmail.com> Signed-off-by: Sabryna Sousa <sabryna.sousa1323@gmail.com> Signed-off-by: Victor Arnaud <victorhad@gmail.com>
-
Signed-off-by: DylanGuedes <djmgguedes@gmail.com> Signed-off-by: ArthurJahn <stutrzbecher@gmail.com> Signed-off-by: Fábio Teixeira <fabio1079@gmail.com>
-
Signed-off-by: DylanGuedes <djmgguedes@gmail.com> Signed-off-by: ArthurJahn <stutrzbecher@gmail.com>
-
Also added link to register on login modal
-
See merge request !47
-
Add scrap in profile's timeline  See merge request !56
-
Join community Add a button to join/leave communities. See merge request !54
-
Also change from ng-click to (click) when toggle role selection.
-
Add components that display tasks and allow accept/reject See merge request !55
Showing
95 changed files
Show diff stats
bower.json
... | ... | @@ -39,12 +39,20 @@ |
39 | 39 | "angular-click-outside": "^2.7.1", |
40 | 40 | "ng-ckeditor": "^0.2.1", |
41 | 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 | 47 | "devDependencies": { |
45 | 48 | "angular-mocks": "~1.5.0" |
46 | 49 | }, |
47 | 50 | "overrides": { |
51 | + "ng-file-upload": { | |
52 | + "main": [ | |
53 | + "ng-file-upload-all.js" | |
54 | + ] | |
55 | + }, | |
48 | 56 | "ng-ckeditor": { |
49 | 57 | "main": [ |
50 | 58 | "ng-ckeditor.js", | ... | ... |
gulp/watch.js
... | ... | @@ -46,6 +46,7 @@ gulp.task('watch', ['inject'], function () { |
46 | 46 | watchPaths.push(path.join(src, '/app/**/*.html')); |
47 | 47 | watchPaths.push(path.join(src, conf.paths.plugins, '/**/*.html')); |
48 | 48 | }); |
49 | + watchPaths.push(stylePaths); | |
49 | 50 | gulp.watch(watchPaths, function(event) { |
50 | 51 | browserSync.reload(event.path); |
51 | 52 | }); | ... | ... |
... | ... | @@ -0,0 +1 @@ |
1 | +export * from "./register.component"; | ... | ... |
... | ... | @@ -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> {{ 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> | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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 | +}); | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | 2 | import {noosferoModuleConfig} from "./index.config"; |
3 | 3 | import {noosferoAngularRunBlock} from "./index.run"; |
4 | 4 | import {MainComponent} from "./main/main.component"; |
... | ... | @@ -22,4 +22,13 @@ angular.module('noosfero.init', ['noosfero.templates.app', 'noosfero.templates.p |
22 | 22 | run(noosferoAngularRunBlock). |
23 | 23 | constant("moment", moment). |
24 | 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 | +); | ... | ... |
... | ... | @@ -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 | 18 | @Input() block: noosfero.Block; |
19 | 19 | @Input() owner: noosfero.Profile | noosfero.Environment; |
20 | 20 | |
21 | - private modalInstance: any = null; | |
21 | + private modalInstance: ng.ui.bootstrap.IModalServiceInstance; | |
22 | 22 | originalBlock: noosfero.Block; |
23 | 23 | |
24 | 24 | currentUser: noosfero.User; |
... | ... | @@ -26,7 +26,8 @@ export class BlockComponent { |
26 | 26 | editionMode = false; |
27 | 27 | designMode = false; |
28 | 28 | |
29 | - constructor(private $uibModal: any, | |
29 | + constructor( | |
30 | + private $uibModal: ng.ui.bootstrap.IModalService, | |
30 | 31 | private $scope: ng.IScope, |
31 | 32 | private $state: ng.ui.IStateService, |
32 | 33 | private $rootScope: ng.IRootScopeService, | ... | ... |
src/app/layout/blocks/profile-image/profile-image-block.component.spec.ts
... | ... | @@ -14,12 +14,22 @@ describe("Components", () => { |
14 | 14 | describe("Profile Image Block Component", () => { |
15 | 15 | |
16 | 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 | 22 | @Component({ |
19 | 23 | selector: 'test-container-component', |
20 | 24 | template: htmlTemplate, |
21 | 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 | 34 | class BlockContainerComponent { |
25 | 35 | block = { type: 'Block' }; |
... | ... | @@ -42,5 +52,42 @@ describe("Components", () => { |
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 | 1 | import {Inject, Input, Component} from "ng-forward"; |
2 | 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 | 7 | @Component({ |
5 | 8 | selector: "noosfero-profile-image-block", |
6 | 9 | templateUrl: 'app/layout/blocks/profile-image/profile-image-block.html', |
7 | 10 | directives: [ProfileImageComponent] |
8 | 11 | }) |
12 | +@Inject(ProfileService, SessionService, NotificationService) | |
9 | 13 | export class ProfileImageBlockComponent { |
10 | 14 | |
11 | 15 | @Input() block: noosfero.Block; |
12 | 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 | 1 | <div class="center-block text-center profile-image-block"> |
2 | 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 | 4 | </a> |
5 | 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 | 10 | </div> | ... | ... |
src/app/layout/blocks/profile-image/profile-image-block.scss
... | ... | @@ -2,4 +2,22 @@ |
2 | 2 | .settings-link { |
3 | 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
src/app/layout/blocks/profile-images-plugin-profile-images/profile-images-plugin-profile-images-block.component.spec.ts
0 → 100644
... | ... | @@ -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 @@ |
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 @@ |
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 @@ |
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
src/app/layout/blocks/recent-activities-plugin-activities/recent-activities-plugin-activities-block.component.ts
... | ... | @@ -18,7 +18,12 @@ export class RecentActivitiesPluginActivitiesBlockComponent { |
18 | 18 | constructor(private blockService: BlockService, private $state: any) { } |
19 | 19 | |
20 | 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 | 29 | urlFor(params: any) { | ... | ... |
src/app/layout/blocks/recent-activities-plugin-activities/recent-activities-plugin-activities-block.html
1 | 1 | <div class="deckgrid recent-activities-block"> |
2 | 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 | 14 | <div class="header media-body"> |
4 | 15 | <h5 class="title media-heading"> |
5 | - <a ng-href="/{{activity.user.identifier}}">{{activity.user.name}}</a> <ng-include src="ctrl.getActivityTemplate(activity)"></ng-include> | |
16 | + <ng-include src="ctrl.getActivityTemplate(activity)"></ng-include> | |
6 | 17 | </h5> |
7 | 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 | 21 | </div> |
14 | 22 | </div> | ... | ... |
src/app/layout/blocks/recent-activities-plugin-activities/recent-activities-plugin-activities-block.scss
... | ... | @@ -20,4 +20,53 @@ |
20 | 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 | 20 | <a ng-href="#" ng-click="ctrl.openLogin()">{{"navbar.login" | translate}}</a> |
21 | 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 | 29 | <li class="dropdown profile-menu" ng-show="ctrl.currentUser" uib-dropdown> |
24 | 30 | <a href="#" class="dropdown-toggle" aria-expanded="false" uib-dropdown-toggle> |
25 | 31 | <noosfero-profile-image [profile]="ctrl.currentUser.person" class="profile-image"></noosfero-profile-image> |
... | ... | @@ -39,10 +45,12 @@ |
39 | 45 | </ul> |
40 | 46 | </li> |
41 | 47 | </ul> |
42 | - | |
43 | 48 | <ul class="nav navbar-nav navbar-right"> |
44 | 49 | <language-selector class="nav navbar-nav navbar-right"></language-selector> |
45 | 50 | </ul> |
51 | + <ul class="nav navbar-nav navbar-right"> | |
52 | + <tasks-menu class="nav navbar-nav navbar-right"></tasks-menu> | |
53 | + </ul> | |
46 | 54 | <div ui-view="actions"></div> |
47 | 55 | <div class="nav navbar-nav search navbar-right"> |
48 | 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 | 10 | @Component({ |
11 | 11 | selector: "acme-navbar", |
... | ... | @@ -17,14 +17,14 @@ import {BootstrapSwitcherComponent, BootstrapSwitcherItem} from './../../shared/ |
17 | 17 | export class Navbar { |
18 | 18 | |
19 | 19 | private currentUser: noosfero.User; |
20 | - private modalInstance: any = null; | |
20 | + private modalInstance: ng.ui.bootstrap.IModalServiceInstance; | |
21 | 21 | public showHamburger: boolean = false; |
22 | 22 | public currentEnvironment: noosfero.Environment = <any>{ name: '' }; |
23 | 23 | /** |
24 | 24 | * |
25 | 25 | */ |
26 | 26 | constructor( |
27 | - private $uibModal: any, | |
27 | + private $uibModal: ng.ui.bootstrap.IModalService, | |
28 | 28 | public authService: AuthService, |
29 | 29 | private session: SessionService, |
30 | 30 | private $state: ng.ui.IStateService, | ... | ... |
src/app/layout/scss/_forms.scss
src/app/login/login.html
... | ... | @@ -21,4 +21,7 @@ |
21 | 21 | </div> |
22 | 22 | <button type="submit" class="btn btn-default btn-block" ng-click="vm.login()">{{"auth.form.login_button" | translate}}</button> |
23 | 23 | </form> |
24 | + <div class="text-center"> | |
25 | + <a ui-sref="main.register" ng-click="$close()">{{"auth.createAccount" | translate}}</a> | |
26 | + </div> | |
24 | 27 | </div> | ... | ... |
src/app/login/session.service.ts
src/app/main/main.component.spec.ts
... | ... | @@ -4,8 +4,9 @@ import {TestComponentBuilder, ComponentFixture} from "ng-forward/cjs/testing/tes |
4 | 4 | |
5 | 5 | import {quickCreateComponent} from "../../spec/helpers"; |
6 | 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 | 11 | let localFixture: ComponentFixture; |
11 | 12 | let $state: angular.ui.IStateService; |
... | ... | @@ -34,6 +35,15 @@ describe("MainComponent", function() { |
34 | 35 | { |
35 | 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 | 49 | class MainComponentParent { |
... | ... | @@ -57,7 +67,7 @@ describe("MainComponent", function() { |
57 | 67 | // navigates to the environment home |
58 | 68 | $state.go("main.environment.home"); |
59 | 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 | 71 | expect($state.current.name).toEqual("main.environment.home"); |
62 | 72 | done(); |
63 | 73 | }); | ... | ... |
src/app/main/main.component.ts
1 | 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 | 58 | * @ngdoc controller |
... | ... | @@ -62,12 +69,16 @@ import {SearchFormComponent} from "../search/search-form/search-form.component"; |
62 | 69 | templateUrl: "app/main/main.html", |
63 | 70 | providers: [AuthService, SessionService] |
64 | 71 | }) |
65 | -@Inject(BodyStateClassesService) | |
72 | +@Inject(BodyStateClassesService, EVENTS_HUB_KNOW_EVENT_NAMES) | |
66 | 73 | export class MainContentComponent { |
67 | 74 | |
68 | 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 | 82 | bodyStateClassesService.start({ |
72 | 83 | skin: this.themeSkin |
73 | 84 | }); |
... | ... | @@ -88,7 +99,8 @@ export class EnvironmentContent { |
88 | 99 | * @name main.Main |
89 | 100 | * @requires AuthService, Session, Notification, ArticleBlog, ArticleView, Boxes, Block, LinkListBlock, |
90 | 101 | * MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock, |
91 | - * NoosferoTemplate, DateFormat, RawHTMLBlock, PersonTagsPluginInterestsBlock, RecentActivitiesPluginActivitiesBlock, | |
102 | + * NoosferoTemplate, DateFormat, RawHTMLBlock, PersonTagsPluginInterestsBlock, | |
103 | + * RecentActivitiesPluginActivitiesBlock, ProfileImagesPluginProfileImages | |
92 | 104 | * @description |
93 | 105 | * The Main controller for the Noosfero Angular Theme application. |
94 | 106 | * |
... | ... | @@ -107,7 +119,8 @@ export class EnvironmentContent { |
107 | 119 | MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, |
108 | 120 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, |
109 | 121 | LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent, |
110 | - PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, BlockComponent | |
122 | + PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, RecentActivitiesPluginActivitiesBlockComponent, | |
123 | + ProfileImagesPluginProfileImagesBlockComponent, BlockComponent, RegisterComponent, TasksMenuComponent, TaskListComponent | |
111 | 124 | ].concat(plugins.mainComponents).concat(plugins.hotspots), |
112 | 125 | providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, |
113 | 126 | "ngAnimate", "ngCookies", "ngStorage", "ngTouch", |
... | ... | @@ -116,7 +129,7 @@ export class EnvironmentContent { |
116 | 129 | "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid", |
117 | 130 | "angular-timeline", "duScroll", "oitozero.ngSweetAlert", |
118 | 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 | 134 | @StateConfig([ |
122 | 135 | { |
... | ... | @@ -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 | 175 | url: "^/:profile", |
151 | 176 | abstract: true, |
152 | 177 | component: ProfileComponent, | ... | ... |
src/app/profile/activities/activity/activity.component.spec.ts
... | ... | @@ -12,6 +12,7 @@ const htmlTemplate: string = '<noosfero-activity [activity]="ctrl.activity"></no |
12 | 12 | describe("Components", () => { |
13 | 13 | |
14 | 14 | describe("Noosfero Activity", () => { |
15 | + let activity = { name: "activity1", verb: "create_article" }; | |
15 | 16 | |
16 | 17 | beforeEach(angular.mock.module("templates")); |
17 | 18 | |
... | ... | @@ -21,18 +22,54 @@ describe("Components", () => { |
21 | 22 | directives: [ActivityComponent], |
22 | 23 | providers: provideFilters("truncateFilter", "stripTagsFilter", "translateFilter") |
23 | 24 | }) |
25 | + | |
24 | 26 | class BlockContainerComponent { |
25 | - activity = { name: "activity1", verb: "create_article" }; | |
27 | + activity = activity; | |
26 | 28 | } |
27 | 29 | |
28 | 30 | it("render the specific template for an activity verb", done => { |
29 | 31 | tcb.createAsync(BlockContainerComponent).then(fixture => { |
30 | 32 | let component: ActivityComponent = fixture.debugElement.componentViewChildren[0].componentInstance; |
31 | 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 | 42 | expect(fixture.debugElement.queryAll(".activity.create_article").length).toEqual(1); |
33 | 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 | }); | ... | ... |
... | ... | @@ -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 | 7 | @Component({ |
8 | 8 | selector: 'custom-content', |
... | ... | @@ -21,9 +21,10 @@ export class CustomContentComponent { |
21 | 21 | |
22 | 22 | content: string; |
23 | 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 | 28 | private $scope: ng.IScope, |
28 | 29 | private profileService: ProfileService, |
29 | 30 | private notificationService: NotificationService, | ... | ... |
src/app/profile/image/image.component.spec.ts
... | ... | @@ -4,22 +4,49 @@ |
4 | 4 | * @description |
5 | 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 | 8 | import {TestComponentBuilder, ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; |
9 | 9 | import {Pipe, Input, provide, Component} from 'ng-forward'; |
10 | +import {PersonService} from "../../../lib/ng-noosfero-api/http/person.service"; | |
10 | 11 | |
11 | 12 | import * as helpers from "../../../spec/helpers"; |
12 | 13 | |
13 | 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 | 18 | describe("Components", () => { |
18 | 19 | |
19 | 20 | describe("Profile Image Component", () => { |
20 | 21 | |
22 | + let helper: ComponentTestHelper<ProfileImageComponent>; | |
23 | + | |
21 | 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 | 50 | it("show community users image if profile is not Person", done => { |
24 | 51 | helpers.tcb.createAsync(ProfileImageComponent).then(fixture => { |
25 | 52 | let profileImageComponent: ProfileImageComponent = fixture.componentInstance; |
... | ... | @@ -45,7 +72,7 @@ describe("Components", () => { |
45 | 72 | expect(profileImageComponent.defaultIcon).toEqual("fa-user", "The default icon should be person user"); |
46 | 73 | done(); |
47 | 74 | }); |
48 | - }); | |
75 | + });*/ | |
49 | 76 | |
50 | 77 | }); |
51 | 78 | }); |
52 | 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 | 6 | * @ngdoc controller |
... | ... | @@ -10,7 +11,9 @@ import {Inject, Input, Component} from "ng-forward"; |
10 | 11 | @Component({ |
11 | 12 | selector: "noosfero-profile-image", |
12 | 13 | templateUrl: 'app/profile/image/image.html', |
14 | + providers: [ provide('personService', { useClass: PersonService }) ] | |
13 | 15 | }) |
16 | +@Inject(PersonService, "$uibModal", "$scope") | |
14 | 17 | export class ProfileImageComponent { |
15 | 18 | |
16 | 19 | /** |
... | ... | @@ -30,6 +33,48 @@ export class ProfileImageComponent { |
30 | 33 | */ |
31 | 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 | 79 | * @ngdoc method |
35 | 80 | * @name ngOnInit |
... | ... | @@ -43,5 +88,6 @@ export class ProfileImageComponent { |
43 | 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 | 5 | background-clip: padding-box; |
6 | 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 @@ |
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 | +}); | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | 6 | <h2>{{vm.profile.name}}</h2> |
7 | 7 | </header> |
8 | 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 | 15 | </div> |
14 | 16 | </div> |
15 | 17 | </div> | ... | ... |
... | ... | @@ -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 | 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 | 17 | * @ngdoc controller |
16 | 18 | * @name profile.Profile |
... | ... | @@ -92,6 +94,18 @@ import {ConfigBarComponent} from "./config-bar.component"; |
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 | 109 | name: 'main.profile.home', |
96 | 110 | url: "", |
97 | 111 | component: ProfileHomeComponent, | ... | ... |
src/app/shared/components/interfaces.ts
... | ... | @@ -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 | 47 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,56 @@ |
1 | +import { Injectable, Inject, OpaqueToken, EventEmitter } from 'ng-forward'; | |
2 | + | |
3 | +export const EVENTS_HUB_KNOW_EVENT_NAMES = new OpaqueToken('EVENTS_HUB_KNOW_EVENT_NAMES'); | |
4 | + | |
5 | +export interface EventsHubKnownEventNames { | |
6 | + getNames(): string[]; | |
7 | +} | |
8 | + | |
9 | +function isEventsHubKnownEventNames(object: any): object is EventsHubKnownEventNames { | |
10 | + return 'getNames' in object; | |
11 | +} | |
12 | + | |
13 | +@Injectable() | |
14 | +@Inject(EVENTS_HUB_KNOW_EVENT_NAMES) | |
15 | +export class EventsHubService { | |
16 | + | |
17 | + private emitters: Map<string, EventEmitter<any>>; | |
18 | + private knownEvents: string[] = []; | |
19 | + | |
20 | + constructor(private eventsHubKnownEventNames: EventsHubKnownEventNames | string[]) { | |
21 | + if (isEventsHubKnownEventNames(eventsHubKnownEventNames)) { | |
22 | + this.knownEvents = eventsHubKnownEventNames.getNames(); | |
23 | + } else if (Array.isArray(eventsHubKnownEventNames)) { | |
24 | + this.knownEvents = eventsHubKnownEventNames; | |
25 | + } | |
26 | + | |
27 | + this.emitters = new Map<string, EventEmitter<any>>(); | |
28 | + this.setupEmitters(); | |
29 | + } | |
30 | + | |
31 | + emitEvent(eventType: string, payload?: any) { | |
32 | + this.checkKnownEvent(eventType); | |
33 | + let event = this.emitters.get(eventType); | |
34 | + if (event) this.emitters.get(eventType).next(payload); | |
35 | + } | |
36 | + | |
37 | + subscribeToEvent<T>(eventType: string, generatorOrNext?: ((p?: T) => void), error?: any, complete?: any) { | |
38 | + this.checkKnownEvent(eventType); | |
39 | + let event = this.emitters.get(eventType); | |
40 | + if (event) event.subscribe(generatorOrNext, error, complete); | |
41 | + } | |
42 | + | |
43 | + private setupEmitters() { | |
44 | + for (let i: number = 0; i < this.knownEvents.length; i++) { | |
45 | + this.emitters.set(this.knownEvents[i], new EventEmitter<any>()); | |
46 | + } | |
47 | + } | |
48 | + | |
49 | + private checkKnownEvent(eventType: string) { | |
50 | + if (!this.emitters.has(eventType)) { | |
51 | + throw new Error('Unknown event named ' + eventType.toString()); | |
52 | + } | |
53 | + } | |
54 | + | |
55 | + | |
56 | +} | ... | ... |
... | ... | @@ -0,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> | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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 | +}); | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | +}); | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | +}); | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | +}); | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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 @@ |
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 @@ |
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/create-community/create-community.html
0 → 100644
... | ... | @@ -0,0 +1 @@ |
1 | +<!-- not implemented yet --> | ... | ... |
src/languages/en.json
1 | 1 | { |
2 | 2 | "noosfero.name" : "Noosfero", |
3 | 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 | 8 | "navbar.profile": "Profile", |
5 | 9 | "navbar.settings": "Settings", |
6 | 10 | "navbar.logout": "Log Out", |
... | ... | @@ -19,8 +23,10 @@ |
19 | 23 | "profile.others_info": "Others", |
20 | 24 | "profile.community.title": "Community", |
21 | 25 | "profile.person.title": "Person", |
26 | + "profile.image.upload": "Upload Photo", | |
22 | 27 | "activities.title": "Activities", |
23 | 28 | "activities.create_article.description": "has published on", |
29 | + "activities.scrap.description": "wrote in its timeline", | |
24 | 30 | "activities.add_member_in_community.description": "has joined the community", |
25 | 31 | "activities.new_friendship.description": "has made {friends, plural, one{one new friend} other{# new friends}}:", |
26 | 32 | "auth.title": "Great to have you back!", |
... | ... | @@ -28,6 +34,7 @@ |
28 | 34 | "auth.form.password": "Password", |
29 | 35 | "auth.form.keepLoggedIn": "Keep me logged in", |
30 | 36 | "auth.form.login_button": "Login", |
37 | + "auth.createAccount": "Create account", | |
31 | 38 | "navbar.content_viewer_actions.new_item": "New Item", |
32 | 39 | "navbar.profile_actions.new_item": "New Item", |
33 | 40 | "navbar.content_viewer_actions.new_post": "New Post", |
... | ... | @@ -97,5 +104,47 @@ |
97 | 104 | "block.edition.display_user.all": "All users", |
98 | 105 | "block.edition.display_user.logged": "Logged", |
99 | 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 | } | ... | ... |
... | ... | @@ -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 | 2 | "noosfero.name" : "Noosfero", |
3 | 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 | 8 | "navbar.profile": "Perfil", |
5 | 9 | "navbar.settings": "Configurações", |
6 | 10 | "navbar.logout": "Sair", |
... | ... | @@ -19,8 +23,10 @@ |
19 | 23 | "profile.others_info": "Outras informações", |
20 | 24 | "profile.community.title": "Comunidade", |
21 | 25 | "profile.person.title": "Pessoa", |
26 | + "profile.image.upload": "Enviar photo", | |
22 | 27 | "activities.title": "Atividades", |
23 | 28 | "activities.create_article.description": "publicou em", |
29 | + "activities.scrap.description": "escreveu em sua linha do tempo", | |
24 | 30 | "activities.add_member_in_community.description": "entrou na comunidade", |
25 | 31 | "activities.new_friendship.description": "fez {friends, plural, one{um novo amigo} other{# novos amigos}}:", |
26 | 32 | "auth.title": "Legal ter você de volta!", |
... | ... | @@ -28,9 +34,10 @@ |
28 | 34 | "auth.form.password": "Senha", |
29 | 35 | "auth.form.keepLoggedIn": "Continuar logado", |
30 | 36 | "auth.form.login_button": "Login", |
37 | + "auth.createAccount": "Criar conta", | |
31 | 38 | "navbar.content_viewer_actions.new_item": "Novo Item", |
32 | 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 | 41 | "navbar.content_viewer_actions.new_discussion": "Nova Consulta", |
35 | 42 | "navbar.profile_actions.new_discussion": "Nova Discussão", |
36 | 43 | "notification.error.default.message": "Algo deu errado!", |
... | ... | @@ -97,5 +104,50 @@ |
97 | 104 | "block.edition.display_user.all": "Todos os usuários", |
98 | 105 | "block.edition.display_user.logged": "Logados", |
99 | 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("Services", () => { |
17 | 17 | articleService = _ArticleService_; |
18 | 18 | })); |
19 | 19 | |
20 | - | |
21 | 20 | describe("Succesfull requests", () => { |
22 | 21 | |
23 | 22 | it("should remove article", (done) => { |
... | ... | @@ -92,6 +91,5 @@ describe("Services", () => { |
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<noosfero.Person> { |
28 | 28 | p.catch(this.getHandleErrorFunction<noosfero.RestResult<any>>(deferred)); |
29 | 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("Services", () => { |
111 | 111 | }); |
112 | 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 | 64 | let headers = { 'Content-Type': 'application/json' }; |
65 | 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 | } | ... | ... |
... | ... | @@ -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 | +}); | ... | ... |
... | ... | @@ -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<T extends noosfero.RestModel> { |
80 | 80 | } |
81 | 81 | return { |
82 | 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 | 88 | protected buildResult(response: restangular.IResponse): noosfero.RestResult<T> { |
88 | 89 | return { |
89 | 90 | data: response.data, |
90 | - headers: response.headers | |
91 | + headers: response.headers, | |
92 | + status: response.status | |
91 | 93 | }; |
92 | 94 | }; |
93 | 95 | /** | ... | ... |
... | ... | @@ -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 | +}); | ... | ... |
... | ... | @@ -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 | +} | ... | ... |
... | ... | @@ -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 | +}); | ... | ... |
... | ... | @@ -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/environment.ts
... | ... | @@ -5,7 +5,7 @@ namespace noosfero { |
5 | 5 | * @name noofero.Environment |
6 | 6 | * @description |
7 | 7 | * A representation of a Noosfero Environment. |
8 | - */ | |
8 | + */ | |
9 | 9 | export interface Environment extends RestModel { |
10 | 10 | /** |
11 | 11 | * @ngdoc property |
... | ... | @@ -23,6 +23,21 @@ namespace noosfero { |
23 | 23 | * @returns {string} The Environment layout (e.g. default, rightbar) |
24 | 24 | */ |
25 | 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
... | ... | @@ -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 | 40 | return this.modalInstance; |
41 | 41 | } |
42 | 42 | }, |
43 | + registerService: { | |
44 | + createAccount: (user: noosfero.User) => { | |
45 | + return Promise.resolve({ status: 201 }); | |
46 | + } | |
47 | + }, | |
43 | 48 | authService: { |
44 | 49 | loginSuccess: { |
45 | 50 | event: Function, |
... | ... | @@ -145,6 +150,15 @@ export var mocks: any = { |
145 | 150 | return mocks.promiseResultTemplate({ |
146 | 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 | 164 | profileService: { |
... | ... | @@ -223,11 +237,16 @@ export var mocks: any = { |
223 | 237 | } |
224 | 238 | }, |
225 | 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 | 252 | $log: { |
... | ... | @@ -240,6 +259,7 @@ export var mocks: any = { |
240 | 259 | }, |
241 | 260 | notificationService: { |
242 | 261 | success: () => { }, |
243 | - confirmation: () => { } | |
262 | + confirmation: () => { }, | |
263 | + error: () => { } | |
244 | 264 | } |
245 | 265 | }; | ... | ... |
typings.json
... | ... | @@ -8,6 +8,7 @@ |
8 | 8 | "angular-mocks": "github:DefinitelyTyped/DefinitelyTyped/angularjs/angular-mocks.d.ts", |
9 | 9 | "angular-translate": "github:DefinitelyTyped/DefinitelyTyped/angular-translate/angular-translate.d.ts#19850bf86c876e0c2544842114878ece4664941a", |
10 | 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 | 12 | "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c", |
12 | 13 | "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#dd638012d63e069f2c99d06ef4dcc9616a943ee4", |
13 | 14 | "jquery": "github:DefinitelyTyped/DefinitelyTyped/jquery/jquery.d.ts#470954c4f427e0805a2d633636a7c6aa7170def8", | ... | ... |