Commit 6e1dcd9ec9338c287383f16cb6f1c5ed90e12173

Authored by Carlos Purificação
2 parents 149cf6c3 2d503248

Merge branch 'edit-header-footer' into 'master'

Edit profile header and footer

See merge request !26
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 +}
... ...
src/app/profile/custom-content/custom-content.html 0 → 100644
... ... @@ -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>
... ...
src/app/profile/custom-content/edit-content.html 0 → 100644
... ... @@ -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/custom-content/edit-content.scss 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +.edit-content {
  2 + margin: 20px;
  3 +}
... ...
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(&quot;Services&quot;, () =&gt; {
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 }
... ...