Commit 2d503248cbc7843562768702281cf4759c363093
1 parent
45978e40
Exists in
master
and in
19 other branches
Edit profile header and footer
Showing
12 changed files
with
209 additions
and
5 deletions
Show diff stats
src/app/main/main.component.ts
... | ... | @@ -16,6 +16,7 @@ import {RecentDocumentsBlockComponent} from "../layout/blocks/recent-documents/r |
16 | 16 | import {ProfileImageBlockComponent} from "../layout/blocks/profile-image/profile-image-block.component"; |
17 | 17 | import {RawHTMLBlockComponent} from "../layout/blocks/raw-html/raw-html-block.component"; |
18 | 18 | import {StatisticsBlockComponent} from "../layout/blocks/statistics/statistics-block.component"; |
19 | +import {CustomContentComponent} from "../profile/custom-content/custom-content.component"; | |
19 | 20 | |
20 | 21 | import {MembersBlockComponent} from "../layout/blocks/members/members-block.component"; |
21 | 22 | import {CommunitiesBlockComponent} from "../layout/blocks/communities/communities-block.component"; |
... | ... | @@ -99,7 +100,7 @@ export class EnvironmentContent { |
99 | 100 | LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, |
100 | 101 | MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, |
101 | 102 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, |
102 | - LoginBlockComponent | |
103 | + LoginBlockComponent, CustomContentComponent | |
103 | 104 | ].concat(plugins.mainComponents).concat(plugins.hotspots), |
104 | 105 | |
105 | 106 | providers: [AuthService, SessionService, NotificationService, BodyStateClassesService] | ... | ... |
src/app/profile/custom-content/custom-content.component.spec.ts
0 → 100644
... | ... | @@ -0,0 +1,77 @@ |
1 | +import {CustomContentComponent} from './custom-content.component'; | |
2 | +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper'; | |
3 | +import * as helpers from "../../../spec/helpers"; | |
4 | + | |
5 | +const htmlTemplate: string = '<custom-content [attribute]="\'custom_footer\'" [profile]="ctrl.profile"></custom-content>'; | |
6 | + | |
7 | +describe("Components", () => { | |
8 | + describe("Custom Content Component", () => { | |
9 | + | |
10 | + let helper: ComponentTestHelper<CustomContentComponent>; | |
11 | + beforeEach(angular.mock.module("templates")); | |
12 | + beforeEach(angular.mock.module("ngSanitize")); | |
13 | + | |
14 | + beforeEach((done) => { | |
15 | + let profileService = jasmine.createSpyObj("profileService", ["update"]); | |
16 | + let notificationService = jasmine.createSpyObj("notificationService", ["success"]); | |
17 | + let properties = { profile: { custom_footer: "footer" } }; | |
18 | + let cls = createClass({ | |
19 | + template: htmlTemplate, | |
20 | + directives: [CustomContentComponent], | |
21 | + properties: properties, | |
22 | + providers: [ | |
23 | + helpers.createProviderToValue("$uibModal", helpers.mocks.$modal), | |
24 | + helpers.createProviderToValue("ProfileService", profileService), | |
25 | + helpers.createProviderToValue("NotificationService", notificationService) | |
26 | + ] | |
27 | + }); | |
28 | + helper = new ComponentTestHelper<CustomContentComponent>(cls, done); | |
29 | + }); | |
30 | + | |
31 | + it("set modal instance when open edit modal", () => { | |
32 | + helper.component['$uibModal'].open = jasmine.createSpy("open"); | |
33 | + helper.component.openEdit(); | |
34 | + expect(helper.component['$uibModal'].open).toHaveBeenCalled(); | |
35 | + expect(helper.component.originalContent).toEqual(helper.component.content); | |
36 | + }); | |
37 | + | |
38 | + it("restore original content when cancelled", () => { | |
39 | + helper.component.openEdit(); | |
40 | + helper.component.content = "modified"; | |
41 | + helper.component.cancel(); | |
42 | + expect(helper.component.content).toEqual(helper.component.originalContent); | |
43 | + }); | |
44 | + | |
45 | + it("keep modified content when click on preview", () => { | |
46 | + helper.component.openEdit(); | |
47 | + helper.component.content = "modified"; | |
48 | + helper.component.preview(); | |
49 | + expect(helper.component.content).toEqual("modified"); | |
50 | + }); | |
51 | + | |
52 | + it("not override original content when cancelled openEdit again", () => { | |
53 | + helper.component.openEdit(); | |
54 | + helper.component.content = "modified"; | |
55 | + helper.component.openEdit(); | |
56 | + expect(helper.component.originalContent).toEqual("footer"); | |
57 | + }); | |
58 | + | |
59 | + it("reset modal instance when close edit modal", () => { | |
60 | + let modalInstance = jasmine.createSpyObj("modalInstance", ["close"]); | |
61 | + helper.component['$uibModal'].open = jasmine.createSpy("open").and.returnValue(modalInstance); | |
62 | + helper.component.openEdit(); | |
63 | + expect(helper.component['$uibModal'].open).toHaveBeenCalled(); | |
64 | + helper.component.cancel(); | |
65 | + expect(modalInstance.close).toHaveBeenCalled(); | |
66 | + expect(helper.component['modalInstance']).toBeNull(); | |
67 | + }); | |
68 | + | |
69 | + it("call profile service to update profile when save", () => { | |
70 | + helper.component['profileService'].update = jasmine.createSpy("update").and.returnValue({ | |
71 | + then: (func: Function) => { func(); } | |
72 | + }); | |
73 | + helper.component.save(); | |
74 | + expect(helper.component['notificationService'].success).toHaveBeenCalled(); | |
75 | + }); | |
76 | + }); | |
77 | +}); | ... | ... |
src/app/profile/custom-content/custom-content.component.ts
0 → 100644
... | ... | @@ -0,0 +1,70 @@ |
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 | + | |
5 | +@Component({ | |
6 | + selector: 'custom-content', | |
7 | + templateUrl: "app/profile/custom-content/custom-content.html", | |
8 | +}) | |
9 | +@Inject("$uibModal", "$scope", ProfileService, NotificationService) | |
10 | +export class CustomContentComponent { | |
11 | + | |
12 | + @Input() attribute: string; | |
13 | + @Input() profile: noosfero.Profile; | |
14 | + @Input() label: string; | |
15 | + | |
16 | + content: string; | |
17 | + originalContent: string; | |
18 | + private modalInstance: any = null; | |
19 | + | |
20 | + constructor(private $uibModal: any, | |
21 | + private $scope: ng.IScope, | |
22 | + private profileService: ProfileService, | |
23 | + private notificationService: NotificationService) { } | |
24 | + | |
25 | + ngOnInit() { | |
26 | + this.$scope.$watch(() => { | |
27 | + return this.profile ? (<any>this.profile)[this.attribute] : null; | |
28 | + }, () => { | |
29 | + if (this.profile) this.content = (<any>this.profile)[this.attribute]; | |
30 | + }); | |
31 | + } | |
32 | + | |
33 | + openEdit() { | |
34 | + if (!this.originalContent) this.originalContent = this.content; | |
35 | + this.modalInstance = this.$uibModal.open({ | |
36 | + templateUrl: 'app/profile/custom-content/edit-content.html', | |
37 | + size: 'lg', | |
38 | + controller: CustomContentComponent, | |
39 | + controllerAs: 'modal', | |
40 | + bindToController: true, | |
41 | + scope: this.$scope, | |
42 | + backdrop: 'static' | |
43 | + }); | |
44 | + } | |
45 | + | |
46 | + save() { | |
47 | + let profile: any = { id: this.profile.id }; | |
48 | + profile[this.attribute] = this.content; | |
49 | + this.profileService.update(profile).then(() => { | |
50 | + this.closeEdit(); | |
51 | + this.notificationService.success({ title: "profile.content.success.title", message: "profile.content.success.message" }); | |
52 | + }); | |
53 | + } | |
54 | + | |
55 | + preview() { | |
56 | + this.closeEdit(); | |
57 | + } | |
58 | + | |
59 | + cancel() { | |
60 | + this.content = this.originalContent; | |
61 | + this.closeEdit(); | |
62 | + } | |
63 | + | |
64 | + private closeEdit() { | |
65 | + if (this.modalInstance) { | |
66 | + this.modalInstance.close(); | |
67 | + this.modalInstance = null; | |
68 | + } | |
69 | + } | |
70 | +} | ... | ... |
... | ... | @@ -0,0 +1,6 @@ |
1 | +<div class="custom-content"> | |
2 | + <div class="actions"> | |
3 | + <button type="submit" class="btn btn-xs btn-default" ng-click="ctrl.openEdit()"><i class="fa fa-edit fa-fw"></i> {{ctrl.label | translate}}</button> | |
4 | + </div> | |
5 | + <div class="content" ng-bind-html="ctrl.content"></div> | |
6 | +</div> | ... | ... |
... | ... | @@ -0,0 +1,7 @@ |
1 | +<div class="edit-content"> | |
2 | + <h3>{{"custom_content.title" | translate}}</h3> | |
3 | + <html-editor [(value)]="ctrl.content"></html-editor> | |
4 | + <button type="submit" class="btn btn-default" ng-click="ctrl.save()">Save</button> | |
5 | + <button type="submit" class="btn btn-warning" ng-click="ctrl.preview()">Preview</button> | |
6 | + <button type="submit" class="btn btn-danger" ng-click="ctrl.cancel()">Cancel</button> | |
7 | +</div> | ... | ... |
src/app/profile/profile.html
1 | 1 | <div class="profile-container"> |
2 | - <div class="profile-header" ng-bind-html="vm.profile.custom_header"></div> | |
2 | + <custom-content class="profile-header" [label]="'profile.custom_header.label'" [attribute]="'custom_header'" [profile]="vm.profile"></custom-content> | |
3 | 3 | <div class="row"> |
4 | 4 | <noosfero-boxes ng-if="vm.boxes" [boxes]="vm.boxes" [owner]="vm.profile"></noosfero-boxes> |
5 | 5 | </div> |
6 | - <div class="profile-footer" ng-bind-html="vm.profile.custom_footer"></div> | |
6 | + <custom-content class="profile-footer" [label]="'profile.custom_footer.label'" [attribute]="'custom_footer'" [profile]="vm.profile"></custom-content> | |
7 | 7 | </div> | ... | ... |
src/languages/en.json
... | ... | @@ -66,5 +66,10 @@ |
66 | 66 | "statistics.categories": "Categories", |
67 | 67 | "statistics.tags": "Tags", |
68 | 68 | "statistics.comments": "Comments", |
69 | - "statistics.hits": "Hits" | |
69 | + "statistics.hits": "Hits", | |
70 | + "profile.content.success.title": "Good job!", | |
71 | + "profile.content.success.message": "Profile saved!", | |
72 | + "custom_content.title": "Edit content", | |
73 | + "profile.custom_header.label": "Header", | |
74 | + "profile.custom_footer.label": "Footer" | |
70 | 75 | } | ... | ... |
src/languages/pt.json
... | ... | @@ -63,5 +63,10 @@ |
63 | 63 | "statistics.categories": "Categorias", |
64 | 64 | "statistics.tags": "Tags", |
65 | 65 | "statistics.comments": "Comentários", |
66 | - "statistics.hits": "Acessos" | |
66 | + "statistics.hits": "Acessos", | |
67 | + "profile.content.success.title": "Bom trabalho!", | |
68 | + "profile.content.success.message": "Perfil salvo!", | |
69 | + "custom_content.title": "Editar conteúdo", | |
70 | + "profile.custom_header.label": "Cabeçalho", | |
71 | + "profile.custom_footer.label": "Rodapé" | |
67 | 72 | } | ... | ... |
src/lib/ng-noosfero-api/http/profile.service.spec.ts
... | ... | @@ -101,6 +101,16 @@ describe("Services", () => { |
101 | 101 | }); |
102 | 102 | $httpBackend.flush(); |
103 | 103 | }); |
104 | + | |
105 | + it("should update the profile attributes", (done) => { | |
106 | + let profileId = 1; | |
107 | + $httpBackend.expectPOST(`/api/v1/profiles/${profileId}`).respond(200, { profile: { custom_header: "something" } }); | |
108 | + profileService.update(<any>{ id: profileId, custom_header: "something" }).then((response: restangular.IResponse) => { | |
109 | + expect(response.data.profile.custom_header).toEqual("something"); | |
110 | + done(); | |
111 | + }); | |
112 | + $httpBackend.flush(); | |
113 | + }); | |
104 | 114 | }); |
105 | 115 | |
106 | 116 | ... | ... |
src/lib/ng-noosfero-api/http/profile.service.ts
... | ... | @@ -60,4 +60,8 @@ export class ProfileService { |
60 | 60 | return this.restangular.one('profiles', profileId); |
61 | 61 | } |
62 | 62 | |
63 | + update(profile: noosfero.Profile) { | |
64 | + let headers = { 'Content-Type': 'application/json' }; | |
65 | + return this.get(profile.id).customPOST({ profile: profile }, null, null, headers); | |
66 | + } | |
63 | 67 | } | ... | ... |
src/lib/ng-noosfero-api/interfaces/profile.ts
... | ... | @@ -62,5 +62,21 @@ namespace noosfero { |
62 | 62 | * @returns {string} The Profile homepage |
63 | 63 | */ |
64 | 64 | homepage: string; |
65 | + | |
66 | + /** | |
67 | + * @ngdoc property | |
68 | + * @name custom_header | |
69 | + * @propertyOf noofero.Profile | |
70 | + * @returns {string} The Profile header | |
71 | + */ | |
72 | + custom_header: string; | |
73 | + | |
74 | + /** | |
75 | + * @ngdoc property | |
76 | + * @name custom_footer | |
77 | + * @propertyOf noofero.Profile | |
78 | + * @returns {string} The Profile footer | |
79 | + */ | |
80 | + custom_footer: string; | |
65 | 81 | } |
66 | 82 | } | ... | ... |