Commit 670cf3cf146eeef12575f3a265f8c905771a445a
1 parent
45978e40
Edit profile header and footer
Showing
12 changed files
with
208 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,69 @@ |
| 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 | + }); | |
| 43 | + } | |
| 44 | + | |
| 45 | + save() { | |
| 46 | + let profile: any = { id: this.profile.id }; | |
| 47 | + profile[this.attribute] = this.content; | |
| 48 | + this.profileService.update(profile).then(() => { | |
| 49 | + this.closeEdit(); | |
| 50 | + this.notificationService.success({ title: "profile.content.success.title", message: "profile.content.success.message" }); | |
| 51 | + }); | |
| 52 | + } | |
| 53 | + | |
| 54 | + preview() { | |
| 55 | + this.closeEdit(); | |
| 56 | + } | |
| 57 | + | |
| 58 | + cancel() { | |
| 59 | + this.content = this.originalContent; | |
| 60 | + this.closeEdit(); | |
| 61 | + } | |
| 62 | + | |
| 63 | + private closeEdit() { | |
| 64 | + if (this.modalInstance) { | |
| 65 | + this.modalInstance.close(); | |
| 66 | + this.modalInstance = null; | |
| 67 | + } | |
| 68 | + } | |
| 69 | +} | ... | ... |
| ... | ... | @@ -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 | } | ... | ... |