Compare View

switch
from
...
to
 
Commits (42)
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 });
... ...
src/app/account/index.ts 0 → 100644
... ... @@ -0,0 +1 @@
  1 +export * from "./register.component";
... ...
src/app/account/register-component.html 0 → 100644
... ... @@ -0,0 +1,77 @@
  1 +<div class="register-page">
  2 + <div class="welcome-message">
  3 + <h1>{{"account.register.welcomeMessageTitle" | translate }}</h1>
  4 + <div class="environment-signup-intro" ng-bind-html="ctrl.environment.signup_intro"></div>
  5 + </div>
  6 + <form name="signupForm">
  7 + <div class="row">
  8 + <div class="col-md-12 register-field">
  9 + <div class="input-group">
  10 + <span class="input-group-addon"><i class="fa fa-male"></i><i class="fa fa-female"></i></span>
  11 + <input type="text" class="form-control" id="name" name="fullName" placeholder="{{'account.register.fullNameLabel' | translate }}" ng-model="ctrl.account.name">
  12 + </div>
  13 + </div>
  14 +
  15 + <div class="form-group col-md-12 register-field" ng-class="ctrl.isInvalid(signupForm.username)">
  16 + <div class="input-group">
  17 + <span class="input-group-addon" style="font-weight: bold;"><i class="fa fa-globe"></i>&nbsp;{{ ctrl.environment.host }}</span>
  18 + <input type="text" id="username" name="username" class="form-control" placeholder="{{'account.register.usernameLabel' | translate }}" ng-model="ctrl.account.login" required>
  19 + </div>
  20 + <div class="help-block" ng-show="signupForm.username.$touched" ng-messages="signupForm.username.$error">
  21 + <ul class="list-unstyled">
  22 + <li ng-messages-include="languages/messages.html"></li>
  23 + </ul>
  24 + </div>
  25 + </div>
  26 +
  27 + <div class="form-group col-md-12 register-field" ng-class="ctrl.isInvalid(signupForm.email)">
  28 + <div class="input-group">
  29 + <span class="input-group-addon"><i class="fa fa-envelope"></i></span>
  30 + <input type="email" class="form-control" id="email" name="email" placeholder="{{'account.register.emailLabel' | translate }}" ng-model="ctrl.account.email" required>
  31 + </div>
  32 + <div class="help-block" ng-show="signupForm.email.$touched" ng-messages="signupForm.email.$error">
  33 + <ul class="list-unstyled">
  34 + <li ng-messages-include="languages/messages.html"></li>
  35 + </ul>
  36 + </div>
  37 + </div>
  38 +
  39 + <div class="form-group col-md-6 register-field" ng-class="ctrl.isInvalid(signupForm.password)">
  40 + <div class="input-group">
  41 + <span class="input-group-addon"><i class="fa fa-lock"></i></span>
  42 + <input type="password" class="form-control" id="password" name="password" placeholder="{{'account.register.passwordLabel' | translate }}" ng-model="ctrl.account.password" required>
  43 + </div>
  44 + <div class="help-block" ng-show="signupForm.password.$touched" ng-messages="signupForm.password.$error">
  45 + <ul class="list-unstyled">
  46 + <li ng-messages-include="languages/messages.html"></li>
  47 + </ul>
  48 + </div>
  49 + </div>
  50 +
  51 + <div class="form-group col-md-6 register-field" ng-class="ctrl.isInvalid(signupForm.passwordConfirm)">
  52 + <div class="input-group">
  53 + <span class="input-group-addon"><i class="fa fa-unlock-alt"></i></span>
  54 + <input type="password" class="form-control" id="passwordConfirm" name="passwordConfirm" match-password="password" placeholder="{{'account.register.passwordConfirmationLabel' | translate}}" ng-model="ctrl.account.passwordConfirmation" required>
  55 + </div>
  56 + <div class="help-block" ng-show="signupForm.passwordConfirm.$touched" ng-messages="signupForm.passwordConfirm.$error">
  57 + <ul class="list-unstyled">
  58 + <li ng-messages-include="languages/messages.html"></li>
  59 + <li ng-message="passwordMatch" translate="messages.invalid.passwordMatch"></li>
  60 + </ul>
  61 + </div>
  62 + </div>
  63 +
  64 + <div class="col-md-12">
  65 + <p class="terms-info">{{"account.register.accountCreatingMessage" | translate}} <a (click)="ctrl.openTerms()" href="#">{{"account.register.termsOfUseMessage" | translate}}</a>.</p>
  66 + </div>
  67 +
  68 + <div class="col-md-12">
  69 + <button type="submit" class="btn btn-default" ng-disabled="signupForm.$invalid" ng-click="ctrl.signup()">{{"account.register.signupMessage" | translate}}</button>
  70 + </div>
  71 +
  72 + </div>
  73 + </form>
  74 +
  75 + <p class="already-registered-message">{{"account.register.haveAccountMessage" | translate}}</p>
  76 +
  77 +</div>
... ...
src/app/account/register-terms.html 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<div class="modal-header">
  2 + <h3 class="modal-title">Register terms</h3>
  3 +</div>
  4 +<div class="modal-body modal-body-overflow" ng-bind-html="ctrl.environment.terms_of_use"></div>
  5 +<div class="modal-footer">
  6 + <button class="btn btn-primary" type="button" (click)="vm.closeTerms()">OK</button>
  7 +</div>
... ...
src/app/account/register.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,52 @@
  1 +import { ComponentTestHelper, createClass } from "../../spec/component-test-helper";
  2 +import * as helpers from "../../spec/helpers";
  3 +import { RegisterComponent } from "./register.component";
  4 +// import {RegisterService} from "../../lib/ng-noosfero-api/http/register.service"
  5 +
  6 +
  7 +describe("Register Component", () => {
  8 + const htmlTemplate: string = '<noosfero-register></noosfero-register>';
  9 +
  10 + let helper: ComponentTestHelper<RegisterComponent>;
  11 + let registerService = helpers.mocks.registerService;
  12 + let stateService = jasmine.createSpyObj("$state", ["transitionTo"]);
  13 + let notificationService = helpers.mocks.notificationService;
  14 + notificationService.success = jasmine.createSpy('success');
  15 + notificationService.error = jasmine.createSpy('error');
  16 +
  17 +
  18 + let account: any = {
  19 + id: 1,
  20 + login: 'test',
  21 + email: 'test@email.com',
  22 + password: 'xxx',
  23 + passwordConfirmation: 'xxx'
  24 + };
  25 +
  26 + beforeEach(() => {
  27 + angular.mock.module('templates');
  28 + angular.mock.module('ngSanitize');
  29 + angular.mock.module('ngMessages');
  30 + angular.mock.module('ngPassword');
  31 + });
  32 +
  33 + beforeEach((done) => {
  34 + let cls = createClass({
  35 + template: htmlTemplate,
  36 + directives: [RegisterComponent],
  37 + providers: [
  38 + helpers.createProviderToValue('$state', stateService),
  39 + helpers.createProviderToValue('$uibModal', helpers.mocks.$modal),
  40 + helpers.createProviderToValue('RegisterService', registerService),
  41 + helpers.createProviderToValue('NotificationService', notificationService),
  42 + helpers.createProviderToValue('EnvironmentService', helpers.mocks.environmentService)
  43 + ]
  44 + });
  45 + helper = new ComponentTestHelper<RegisterComponent>(cls, done);
  46 + });
  47 +
  48 + it('register page was rendered', () => {
  49 + expect(helper.debugElement.query('div.register-page').length).toEqual(1);
  50 + });
  51 +
  52 +});
... ...
src/app/account/register.component.ts 0 → 100644
... ... @@ -0,0 +1,66 @@
  1 +import { Inject, Input, Component, Output, EventEmitter, provide } from 'ng-forward';
  2 +import { RegisterService } from "./../../lib/ng-noosfero-api/http/register.service";
  3 +import { NotificationService } from "./../shared/services/notification.service";
  4 +import { EnvironmentService } from "../../lib/ng-noosfero-api/http/environment.service";
  5 +import { RegisterController } from "./register.controller";
  6 +import { IModalComponent } from "../shared/components/interfaces";
  7 +
  8 +@Component({
  9 + selector: 'noosfero-register',
  10 + templateUrl: 'app/account/register-component.html',
  11 + providers: [
  12 + provide('registerService', { useClass: RegisterService })
  13 + ]
  14 +})
  15 +
  16 +@Inject('$state', '$uibModal', '$scope', RegisterService, NotificationService, EnvironmentService)
  17 +export class RegisterComponent {
  18 + @Input() account: any;
  19 + environment: noosfero.Environment;
  20 +
  21 + modalInstance: ng.ui.bootstrap.IModalServiceInstance;
  22 +
  23 + constructor(
  24 + private $state: ng.ui.IStateService,
  25 + private $uibModal: ng.ui.bootstrap.IModalService,
  26 + private $scope: ng.IScope,
  27 + public registerService: RegisterService,
  28 + private notificationService: NotificationService,
  29 + private environmentService: EnvironmentService
  30 + ) {
  31 + this.account = {};
  32 + this.environment = environmentService.getCurrentEnvironment();
  33 + }
  34 +
  35 + signup() {
  36 + if (this.account.password === this.account.passwordConfirmation) {
  37 + this.registerService.createAccount(this.account).then((response) => {
  38 +
  39 + if (response.status === 201) {
  40 + this.$state.transitionTo('main.environment');
  41 + this.notificationService.success({ title: "account.register.success.title", message: "account.register.success.message" });
  42 + } else {
  43 + throw new Error('Invalid attributes');
  44 + }
  45 + });
  46 + } else {
  47 + this.notificationService.error({ message: "account.register.passwordConfirmation.failed" });
  48 + }
  49 + }
  50 +
  51 + isInvalid(field: any): any {
  52 + return { 'has-error': field['$touched'] && field['$invalid'] };
  53 + }
  54 +
  55 + openTerms() {
  56 +
  57 + this.modalInstance = this.$uibModal.open({
  58 + templateUrl: 'app/account/register-terms.html',
  59 + size: 'lg',
  60 + controller: RegisterController,
  61 + controllerAs: 'vm',
  62 + bindToController: true,
  63 + scope: this.$scope
  64 + });
  65 + }
  66 +}
... ...
src/app/account/register.controller.ts 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +import { Input } from "ng-forward";
  2 +import { IModalComponent } from "../shared/components/interfaces";
  3 +
  4 +export class RegisterController {
  5 +
  6 + static $inject = ["$log", "$stateParams", "$scope"];
  7 + ctrl: IModalComponent;
  8 +
  9 + constructor(
  10 + private $log: ng.ILogService,
  11 + private $stateParams: any
  12 + ) { }
  13 +
  14 + closeTerms() {
  15 + this.ctrl.modalInstance.dismiss('ok');
  16 + }
  17 +}
... ...
src/app/account/register.html 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +<div class="row">
  2 + <div class="col-md-3"></div>
  3 +
  4 + <!-- component at center of page -->
  5 + <div class="col-md-6">
  6 + <noosfero-register></noosfero-register>
  7 + </div>
  8 +
  9 + <div class="col-md-3"></div>
  10 +</div>
... ...
src/app/account/scss/register.scss 0 → 100644
... ... @@ -0,0 +1,44 @@
  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(&#39;noosfero.init&#39;, [&#39;noosfero.templates.app&#39;, &#39;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 +);
... ...
src/app/known-events.ts 0 → 100644
... ... @@ -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(&quot;Components&quot;, () =&gt; {
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(&quot;Components&quot;, () =&gt; {
42 52 });
43 53 });
44 54  
  55 + it("display button to join community", (done: Function) => {
  56 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  57 + let elProfile = fixture.debugElement.componentViewChildren[0];
  58 + expect(elProfile.query('.actions .join').length).toEqual(1);
  59 + done();
  60 + });
  61 + });
  62 +
  63 + it("display button to leave community", (done: Function) => {
  64 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  65 + let elProfile = fixture.debugElement.componentViewChildren[0];
  66 + elProfile.componentInstance['isMember'] = true;
  67 + fixture.detectChanges();
  68 + expect(elProfile.query('.actions .leave').length).toEqual(1);
  69 + done();
  70 + });
  71 + });
  72 +
  73 + it("join community", (done: Function) => {
  74 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  75 + let elProfile = fixture.debugElement.componentViewChildren[0];
  76 + profileService.addMember = jasmine.createSpy("addMember").and.returnValue(Promise.resolve({ data: {} }));
  77 + elProfile.componentInstance.join();
  78 + expect(profileService.addMember).toHaveBeenCalled();
  79 + done();
  80 + });
  81 + });
  82 +
  83 + it("leave community", (done: Function) => {
  84 + helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {
  85 + let elProfile = fixture.debugElement.componentViewChildren[0];
  86 + profileService.removeMember = jasmine.createSpy("removeMember").and.returnValue(Promise.resolve({ data: {} }));
  87 + elProfile.componentInstance.leave();
  88 + expect(profileService.removeMember).toHaveBeenCalled();
  89 + done();
  90 + });
  91 + });
45 92 });
46 93 });
... ...
src/app/layout/blocks/profile-image/profile-image-block.component.ts
1 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
... ... @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./profile-images-plugin-profile-images-block.component";
... ...
src/app/layout/blocks/profile-images-plugin-profile-images/profile-images-plugin-profile-images-block.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,65 @@
  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
... ... @@ -0,0 +1,3 @@
  1 +<img ng-src="{{activity.params.first_image}}" ng-attr-alt="{{activity.params.name}}" class="event-image" />
  2 +<p class="event-description"><b>{{activity.params.name}}</b><br />{{activity.params.lead | stripTags}}</p>
  3 +<br style="clear: both;" />
... ...
src/app/layout/blocks/recent-activities-plugin-activities/recent-activities-plugin-activities-block.component.ts
... ... @@ -18,7 +18,12 @@ export class RecentActivitiesPluginActivitiesBlockComponent {
18 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>&nbsp;<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 &#39;./../../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
... ... @@ -2,6 +2,10 @@ label {
2 2 cursor: pointer;
3 3 }
4 4  
  5 +.has-error .help-block {
  6 + font-weight: bold;
  7 +}
  8 +
5 9 .checkbox-nice {
6 10 position: relative;
7 11 padding-left: 15px;
... ...
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
... ... @@ -23,4 +23,4 @@ export class SessionService {
23 23 return this.$localStorage.currentUser;
24 24 };
25 25  
26   -}
27 26 \ No newline at end of file
  27 +}
... ...
src/app/main/main.component.spec.ts
... ... @@ -4,8 +4,9 @@ import {TestComponentBuilder, ComponentFixture} from &quot;ng-forward/cjs/testing/tes
4 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(&quot;MainComponent&quot;, 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(&quot;MainComponent&quot;, 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 &quot;../search/search-form/search-form.component&quot;;
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 = &#39;&lt;noosfero-activity [activity]=&quot;ctrl.activity&quot;&gt;&lt;/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(&quot;Components&quot;, () =&gt; {
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 });
... ...
src/app/profile/activities/activity/leave_scrap.html 0 → 100644
... ... @@ -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(&quot;Components&quot;, () =&gt; {
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 &quot;ng-forward&quot;;
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 +});
... ...
src/app/profile/image/profile-image-editor.component.ts 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +import {StateConfig, Component, Input, Output, Inject, provide} from 'ng-forward';
  2 +import {TranslateProfile} from "../../shared/pipes/translate-profile.filter";
  3 +import {PersonService} from "../../../lib/ng-noosfero-api/http/person.service";
  4 +
  5 +export class ProfileImageEditorComponent {
  6 +
  7 + activities: any;
  8 + croppedDataUrl: string;
  9 + static $inject = ["picFile", "profile", "personService", "$uibModalInstance"];
  10 +
  11 + constructor(public picFile: any, public profile: noosfero.Profile, public personService: PersonService,
  12 + public $uibModalInstance: any) {
  13 + }
  14 +
  15 + uploadImage(dataUrl: any, name: any) {
  16 + let base64_image_json = this.getBase64ImageJson(dataUrl, name);
  17 + this.personService.uploadImage(this.profile, base64_image_json).then( (result: any) => {
  18 + this.$uibModalInstance.close(name);
  19 + });
  20 + }
  21 +
  22 + getBase64ImageJson(dataUrl: any, name: any) {
  23 + let data = this.getData(dataUrl);
  24 + let image_name = this.getImageName(name);
  25 + return {
  26 + tempfile: data,
  27 + filename: image_name,
  28 + type: this.picFile.type
  29 + };
  30 + }
  31 +
  32 + getImageName(name: any) {
  33 + return this.profile.name + "_" + name;
  34 + }
  35 +
  36 + getData(dataUrl: any) {
  37 + return dataUrl.substring(dataUrl.indexOf('base64,') + 7);
  38 + }
  39 +
  40 + cancel() {
  41 + this.$uibModalInstance.close();
  42 + }
  43 +}
... ...
src/app/profile/image/profile-image-editor.html 0 → 100644
... ... @@ -0,0 +1,24 @@
  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>
... ...
src/app/profile/info/profile-info.scss 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +.profile-info .upload-camera-container {
  2 + top: 55%;
  3 + left: 39px;
  4 +}
  5