Commit ae05af8ea8ebccc08f887ca224869c8e1d7afb13

Authored by Caio Almeida
2 parents 2841288e 6c67e997

Fixing conflict

Showing 76 changed files with 1511 additions and 419 deletions   Show diff stats
README.md
... ... @@ -50,3 +50,31 @@ See some important folders bellow:
50 50 1. Create an app folder inside custom-theme and add your custom scss files
51 51 1. Put the templates that you want to override in the same structure from the main application source, e.g.:
52 52 `src/app/profile/profile.html` will be overriden by `themes/custom-theme/app/profile/profile.html`
  53 +
  54 +## Change skin
  55 +
  56 +- Create a any scss file into your theme folder structure
  57 +- Extend your skin css class from `%skin-base` scss placeholder selector. Something like this:
  58 +
  59 +```sass
  60 +.skin-custom {
  61 + @extend %skin-base
  62 +}
  63 +```
  64 +- Configure application to use the new theme, e.g.:
  65 +`npm config set angular-theme:skin custom-skin`
  66 +
  67 +- Start the application with `npm start` scripts ou make a build
  68 +
  69 +## Development environment
  70 +
  71 +## Known Issues
  72 +
  73 +### Message Translation: angular-i18n
  74 +
  75 + - Plural Interpolation only working when current language is En (English)
  76 +
  77 + `Plural Function not found for locale`
  78 +
  79 + For some reason the messageformat installed on bower_component directory was an older version. Removing the bower_components directory
  80 +and runing `bower install` solved the problem
... ...
bower.json
... ... @@ -35,9 +35,10 @@
35 35 "angular-i18n": "^1.5.0",
36 36 "angular-load": "^0.4.1",
37 37 "angular-translate-interpolation-messageformat": "^2.10.0",
38   - "angular-bind-html-compile": "^1.2.1",
39   - "angular-click-outside": "^2.7.1",
40   - "ng-ckeditor": "^0.2.1"
  38 + "angular-bind-html-compile": "^1.2.1",
  39 + "angular-click-outside": "^2.7.1",
  40 + "ng-ckeditor": "^0.2.1",
  41 + "angular-bootstrap-toggle-switch": "^0.5.6"
41 42 },
42 43 "devDependencies": {
43 44 "angular-mocks": "~1.5.0"
... ...
gulp/conf.js
... ... @@ -27,6 +27,10 @@ exports.configTheme = function(theme) {
27 27 exports.paths.theme = theme || "angular-default";
28 28 exports.paths.allSources = [exports.paths.src, path.join(exports.paths.themes, exports.paths.theme)];
29 29 exports.paths.dist = path.join("dist", exports.paths.theme);
  30 +
  31 + if(argv.skin) {
  32 + exports.paths.skin = argv.skin;
  33 + }
30 34 }
31 35 exports.configTheme(argv.theme);
32 36  
... ...
gulp/inject.js
... ... @@ -3,6 +3,8 @@
3 3 var path = require('path');
4 4 var gulp = require('gulp');
5 5 var conf = require('./conf');
  6 +var map = require('map-stream');
  7 +var transform = require('vinyl-transform');
6 8  
7 9 var $ = require('gulp-load-plugins')();
8 10  
... ... @@ -40,3 +42,34 @@ gulp.task('inject', ['scripts', 'styles'], function () {
40 42 .pipe(wiredep(_.extend({}, conf.wiredep)))
41 43 .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve')));
42 44 });
  45 +
  46 +/**
  47 +* Replace the default theme skin to a npm config
  48 +*
  49 +* Uses "vinyl-transform" + "map-stream" to open the
  50 +* js file and rewrite the source file into the same
  51 +* destination
  52 +*
  53 +* @see https://www.npmjs.com/package/vinyl-transform
  54 +* @see https://www.npmjs.com/package/map-stream
  55 +*/
  56 +gulp.task('inject-skin', function () {
  57 +
  58 + if(conf.paths.skin) {
  59 +
  60 + $.util.log('Configured theme skin:', conf.paths.skin);
  61 +
  62 + var replaceSkin = transform(function(filename) {
  63 + return map(function(file, next) {
  64 + var contents = file.toString();
  65 + contents = contents.replace('skin-whbl', conf.paths.skin);
  66 + return next(null, contents);
  67 + });
  68 + });
  69 +
  70 + gulp.src(path.join(conf.paths.src,'./noosfero.js'))
  71 + .pipe(replaceSkin)
  72 + .pipe(gulp.dest(conf.paths.src));
  73 + }
  74 +
  75 +});
... ...
gulp/scripts.js
... ... @@ -9,7 +9,7 @@ var browserSync = require('browser-sync');
9 9 var $ = require('gulp-load-plugins')();
10 10  
11 11  
12   -gulp.task('scripts-reload', function() {
  12 +gulp.task('scripts-reload', ['inject-skin'], function() {
13 13 return buildScripts()
14 14 .pipe(browserSync.stream());
15 15 });
... ...
package.json
... ... @@ -8,10 +8,11 @@
8 8 "ng-forward": "0.0.1-alpha.12"
9 9 },
10 10 "config": {
11   - "theme": "angular-default"
  11 + "theme": "angular-default",
  12 + "skin": "skin-whbl"
12 13 },
13 14 "scripts": {
14   - "build": "webpack; gulp clean && gulp --theme=$npm_package_config_theme build",
  15 + "build": "webpack; gulp clean && gulp --theme=$npm_package_config_theme --skin=$npm_package_config_skin build",
15 16 "build-all": "gulp clean && for d in ./themes/* ; do (gulp --theme=`basename $d` build); done",
16 17 "webpack": "webpack",
17 18 "karma": "concurrently \"webpack -w\" \"karma start\"",
... ... @@ -23,7 +24,7 @@
23 24 "test": "webpack -w --test",
24 25 "jenkins": "webpack && karma start --single-run",
25 26 "postinstall": "bower install; typings install; cd dev-scripts; typings install; cd ../; npm run fix-jqlite",
26   - "start": "concurrently \"webpack -w\" \"gulp --theme=$npm_package_config_theme serve\"",
  27 + "start": "concurrently \"webpack -w\" \"gulp --theme=$npm_package_config_theme --skin=$npm_package_config_skin serve\"",
27 28 "generate-indexes": "ts-node --project ./dev-scripts ./dev-scripts/generate-index-modules.ts",
28 29 "fix-jqlite": "ts-node --project ./dev-scripts dev-scripts/fix-jqlite.ts"
29 30 },
... ... @@ -46,8 +47,8 @@
46 47 "gulp-eslint": "~1.0.0",
47 48 "gulp-filter": "~3.0.1",
48 49 "gulp-flatten": "~0.2.0",
49   - "gulp-insert": "^0.5.0",
50 50 "gulp-inject": "~3.0.0",
  51 + "gulp-insert": "^0.5.0",
51 52 "gulp-load-plugins": "~0.10.0",
52 53 "gulp-merge-json": "^0.4.0",
53 54 "gulp-minify-css": "~1.2.1",
... ... @@ -81,6 +82,7 @@
81 82 "karma-webpack": "^1.7.0",
82 83 "lodash": "~3.10.1",
83 84 "main-bower-files": "~2.9.0",
  85 + "map-stream": "0.0.6",
84 86 "merge-stream": "^1.0.0",
85 87 "on-build-webpack": "^0.1.0",
86 88 "phantomjs": "~1.9.18",
... ... @@ -96,6 +98,7 @@
96 98 "typescript": "^1.8.10",
97 99 "typings": "^0.6.8",
98 100 "uglify-save-license": "~0.4.1",
  101 + "vinyl-transform": "^1.0.0",
99 102 "webpack": "^1.12.14",
100 103 "wiredep": "~2.2.2",
101 104 "wrench": "~1.5.8",
... ...
src/app/admin/layout-edit/designMode.service.spec.ts 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +import {DesignModeService} from './designMode.service';
  2 +
  3 +describe('DesignMode Service', () => {
  4 + let service: DesignModeService;
  5 +
  6 + beforeEach(() => {
  7 + service = new DesignModeService();
  8 + });
  9 +
  10 + it('has the designModeOn equals false as default', () => {
  11 + expect(service.isInDesignMode()).toBeFalsy();
  12 + });
  13 +
  14 + it('allows set the designMode value', () => {
  15 + spyOn(service.onToggle, 'next').and.stub();
  16 + service.setInDesignMode(true);
  17 + expect(service.isInDesignMode).toBeTruthy();
  18 + });
  19 +
  20 + it('emits the onToggle event when changing the designModeOn property', () => {
  21 + spyOn(service.onToggle, 'next').and.stub();
  22 + service.setInDesignMode(true);
  23 + expect(service.onToggle.next).toHaveBeenCalled();
  24 + });
  25 +
  26 + it('does not emit onToggle event when there is no change on designModeOn property', () => {
  27 + spyOn(service.onToggle, 'next').and.stub();
  28 + service.setInDesignMode(false);
  29 + expect(service.onToggle.next).not.toHaveBeenCalled();
  30 + });
  31 +});
0 32 \ No newline at end of file
... ...
src/app/admin/layout-edit/designMode.service.ts 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +import {Injectable, Output, EventEmitter} from 'ng-forward';
  2 +
  3 +@Injectable()
  4 +export class DesignModeService {
  5 + @Output() onToggle: EventEmitter<boolean> = new EventEmitter<boolean>();
  6 +
  7 + private designModeOn: boolean = false;
  8 +
  9 + isInDesignMode(): boolean {
  10 + return this.designModeOn;
  11 + }
  12 +
  13 + setInDesignMode(value: boolean) {
  14 + if (this.designModeOn !== value) {
  15 + this.designModeOn = value;
  16 + this.onToggle.next(this.designModeOn);
  17 + }
  18 + }
  19 +
  20 + constructor() {
  21 + }
  22 +}
... ...
src/app/admin/layout-edit/designModeToggler.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,59 @@
  1 +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
  2 +import * as helpers from '../../../spec/helpers';
  3 +import {DesignModeTogglerComponent} from './designModeToggler.component';
  4 +import {DesignModeService} from './designMode.service';
  5 +
  6 +describe('DesignModeToggler Component', () => {
  7 + const htmlTemplate: string = '<noosfero-design-toggler></noosfero-design-toggler>';
  8 +
  9 + let helper: ComponentTestHelper<DesignModeTogglerComponent>;
  10 + beforeEach(() => {
  11 + angular.mock.module('templates');
  12 + angular.mock.module('ngSanitize');
  13 + angular.mock.module('toggle-switch');
  14 + });
  15 +
  16 + let designModeService: DesignModeService;
  17 + beforeEach((done) => {
  18 + designModeService = new DesignModeService();
  19 + let cls = createClass({
  20 + template: htmlTemplate,
  21 + directives: [DesignModeTogglerComponent],
  22 + providers: [
  23 + helpers.createProviderToValue('DesignModeService', designModeService)
  24 + ]
  25 + });
  26 + helper = new ComponentTestHelper<DesignModeTogglerComponent>(cls, done);
  27 + });
  28 +
  29 + it('changes css classes representing the switch is on or off', () => {
  30 + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeTruthy();
  31 + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeFalsy();
  32 + helper.component.inDesignMode = true;
  33 + helper.detectChanges();
  34 + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeTruthy();
  35 + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeFalsy();
  36 + });
  37 +
  38 + it('emits event with value "true" when changing inDesignMode to On', (done) => {
  39 + designModeService.onToggle.subscribe((designModeOn: boolean) => {
  40 + expect(designModeOn).toBeTruthy();
  41 + done();
  42 + });
  43 + helper.component.inDesignMode = true;
  44 + helper.detectChanges();
  45 + });
  46 +
  47 + it('emits events with value "false" when changing inDesignMode to Off', (done) => {
  48 + helper.component.inDesignMode = true;
  49 + helper.detectChanges();
  50 +
  51 + designModeService.onToggle.subscribe((designModeOn: boolean) => {
  52 + expect(designModeOn).toBeFalsy();
  53 + done();
  54 + });
  55 +
  56 + helper.component.inDesignMode = false;
  57 + helper.detectChanges();
  58 + });
  59 +});
0 60 \ No newline at end of file
... ...
src/app/admin/layout-edit/designModeToggler.component.ts 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +import {Component, Inject} from 'ng-forward';
  2 +import {DesignModeService} from './designMode.service';
  3 +@Component({
  4 + selector: 'noosfero-design-toggler',
  5 + templateUrl: 'app/admin/layout-edit/designModeToggler.html'
  6 +})
  7 +@Inject(DesignModeService)
  8 +export class DesignModeTogglerComponent {
  9 +
  10 + icon: string = "&nbsp;<i class='glyphicon glyphicon-wrench'></i>&nbsp;";
  11 +
  12 + constructor(private designModeService: DesignModeService) {
  13 + }
  14 +
  15 + private _inDesignMode: boolean = false;
  16 +
  17 + get inDesignMode(): boolean {
  18 + return this.designModeService.isInDesignMode();
  19 + };
  20 +
  21 + set inDesignMode(value: boolean) {
  22 + this.designModeService.setInDesignMode(value);
  23 + };
  24 +}
0 25 \ No newline at end of file
... ...
src/app/admin/layout-edit/designModeToggler.html 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +<toggle-switch
  2 + html="true"
  3 + ng-model="ctrl.inDesignMode"
  4 + on-label="{{'designMode.toggle.ON' | translate}}"
  5 + off-label="{{'designMode.toggle.OFF' | translate}}"
  6 + class="switch-small"
  7 + knob-label="{{ ctrl.icon + ('designMode.label' | translate) }}">
  8 +</toggle-switch>
0 9 \ No newline at end of file
... ...
src/app/article/content-viewer/content-viewer.component.ts
... ... @@ -14,7 +14,7 @@ import {ProfileService} from &quot;../../../lib/ng-noosfero-api/http/profile.service&quot;
14 14 provide('profileService', { useClass: ProfileService })
15 15 ]
16 16 })
17   -@Inject(ArticleService, ProfileService, "$log", "$stateParams")
  17 +@Inject(ArticleService, ProfileService, "$stateParams")
18 18 export class ContentViewerComponent {
19 19  
20 20 @Input()
... ... @@ -23,7 +23,10 @@ export class ContentViewerComponent {
23 23 @Input()
24 24 profile: noosfero.Profile = null;
25 25  
26   - constructor(private articleService: ArticleService, private profileService: ProfileService, private $log: ng.ILogService, private $stateParams: angular.ui.IStateParamsService) {
  26 + constructor(
  27 + private articleService: ArticleService,
  28 + private profileService: ProfileService,
  29 + private $stateParams: angular.ui.IStateParamsService) {
27 30 this.activate();
28 31 }
29 32  
... ...
src/app/environment/environment.component.ts
... ... @@ -2,6 +2,7 @@ import {StateConfig, Component, Inject, provide} from &#39;ng-forward&#39;;
2 2 import {EnvironmentService} from "../../lib/ng-noosfero-api/http/environment.service";
3 3 import {NotificationService} from "../shared/services/notification.service";
4 4 import {EnvironmentHomeComponent} from "./environment-home.component";
  5 +import {SearchComponent} from "../search/search.component";
5 6  
6 7 /**
7 8 * @ngdoc controller
... ... @@ -29,6 +30,18 @@ import {EnvironmentHomeComponent} from &quot;./environment-home.component&quot;;
29 30 controllerAs: "vm"
30 31 }
31 32 }
  33 + },
  34 + {
  35 + url: '^/search?query',
  36 + component: SearchComponent,
  37 + name: 'main.environment.search',
  38 + views: {
  39 + "mainBlockContent": {
  40 + templateUrl: "app/search/search.html",
  41 + controller: SearchComponent,
  42 + controllerAs: "ctrl"
  43 + }
  44 + }
32 45 }
33 46 ])
34 47 @Inject(EnvironmentService, "$state", "currentEnvironment")
... ...
src/app/index.ts
... ... @@ -6,7 +6,7 @@ import {AuthEvents} from &quot;./login/auth-events&quot;;
6 6  
7 7 declare var moment: any;
8 8  
9   -//FIXME see a better way to declare template modules for dev mode
  9 +// FIXME see a better way to declare template modules for dev mode
10 10 try {
11 11 angular.module('noosfero.templates.app');
12 12 } catch (error) {
... ...
src/app/layout/blocks/block-content.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,91 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Input, provide, Component} from 'ng-forward';
  3 +
  4 +import {BlockContentComponent} from './block-content.component';
  5 +
  6 +const tcb = new TestComponentBuilder();
  7 +
  8 +const htmlTemplate: string = '<noosfero-block-content [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-block-content>';
  9 +
  10 +describe("Components", () => {
  11 + describe("Block Component", () => {
  12 +
  13 + // the karma preprocessor html2js transform the templates html into js files which put
  14 + // the templates to the templateCache into the module templates
  15 + // we need to load the module templates here as the template for the
  16 + // component Block will be load on our tests
  17 + beforeEach(angular.mock.module("templates"));
  18 +
  19 + it("receives the block and the owner as inputs", done => {
  20 +
  21 + // Creating a container component (BlockContainerComponent) to include
  22 + // the component under test (Block)
  23 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [BlockContentComponent] })
  24 + class BlockContainerComponent {
  25 + block = { type: 'Block' };
  26 + owner = { name: 'profile-name' };
  27 + constructor() {
  28 + }
  29 + }
  30 +
  31 + // uses the TestComponentBuilder instance to initialize the component
  32 + tcb
  33 + .createAsync(BlockContainerComponent).then(fixture => {
  34 + // and here we can inspect and run the test assertions
  35 + let myComponent: BlockContentComponent = fixture.componentInstance;
  36 +
  37 + // assure the block object inside the Block matches
  38 + // the provided through the parent component
  39 + expect(myComponent.block.type).toEqual("Block");
  40 + expect(myComponent.owner.name).toEqual("profile-name");
  41 + done();
  42 + });
  43 + });
  44 +
  45 +
  46 + it("renders a component which matches to the block type", done => {
  47 + // CustomBlock component created to check if it will be used
  48 + // when a block with type 'CustomBlock' is provided to the noosfero-block (Block)
  49 + // *** Important *** - the selector is what ng-forward uses to define the name of the directive provider
  50 + @Component({ selector: 'noosfero-custom-block', template: "<h1>My Custom Block</h1>" })
  51 + class CustomBlock {
  52 + @Input() block: any;
  53 + @Input() owner: any;
  54 + }
  55 +
  56 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [BlockContentComponent, CustomBlock] })
  57 + class CustomBlockType {
  58 + block = { type: 'CustomBlock' };
  59 + owner = { name: 'profile-name' };
  60 + constructor() {
  61 + }
  62 + }
  63 + tcb
  64 + .createAsync(CustomBlockType).then(fixture => {
  65 + let myComponent: CustomBlockType = fixture.componentInstance;
  66 + expect(myComponent.block.type).toEqual("CustomBlock");
  67 + expect(fixture.debugElement.componentViewChildren[0].text()).toEqual("My Custom Block");
  68 + done();
  69 + });
  70 + });
  71 +
  72 +
  73 + it("renders the default block when hasn't defined a block type", done => {
  74 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [BlockContentComponent] })
  75 + class CustomBlockType {
  76 + block: any = { type: null };
  77 + owner: any = { name: 'profile-name' };
  78 + constructor() {
  79 + }
  80 + }
  81 + tcb
  82 + .createAsync(CustomBlockType).then(fixture => {
  83 + let myComponent: CustomBlockType = fixture.componentInstance;
  84 + expect(myComponent.block.type).toBeNull();
  85 + expect(!!fixture.debugElement.nativeElement.querySelector("noosfero-default-block")).toBeTruthy();
  86 + done();
  87 + });
  88 + });
  89 +
  90 + });
  91 +});
... ...
src/app/layout/blocks/block-content.component.ts 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +import { Input, Inject, Component } from 'ng-forward';
  2 +
  3 +@Component({
  4 + selector: 'noosfero-block-content',
  5 + template: '<div></div>'
  6 +})
  7 +@Inject("$element", "$scope", "$injector", "$compile")
  8 +export class BlockContentComponent {
  9 +
  10 + @Input() block: any;
  11 + @Input() owner: any;
  12 +
  13 + ngOnInit() {
  14 + let blockName = (this.block && this.block.type) ? this.block.type.replace(/::/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() : "default-block";
  15 + this.$element.replaceWith(this.$compile('<noosfero-' + blockName + ' [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-' + blockName + '>')(this.$scope));
  16 + }
  17 +
  18 + constructor(private $element: any, private $scope: ng.IScope, private $injector: ng.auto.IInjectorService, private $compile: ng.ICompileService) {
  19 + }
  20 +}
... ...
src/app/layout/blocks/block-edition/block-edition.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +import {Component} from 'ng-forward';
  2 +import {BlockEditionComponent} from './block-edition.component';
  3 +import * as helpers from "../../../../spec/helpers";
  4 +import {ComponentTestHelper, createClass} from '../../../../spec/component-test-helper';
  5 +
  6 +const htmlTemplate: string = '<noosfero-block-edition></noosfero-block-edition>';
  7 +
  8 +describe("Boxes Component", () => {
  9 +
  10 + let helper: ComponentTestHelper<BlockEditionComponent>;
  11 + let translatorService = {
  12 + availableLanguages: { 'en': 'English', 'pt': 'Portuguese' }
  13 + };
  14 +
  15 + beforeEach(() => {
  16 + angular.mock.module("templates");
  17 + });
  18 +
  19 + beforeEach((done) => {
  20 + let cls = createClass({
  21 + template: htmlTemplate,
  22 + directives: [BlockEditionComponent],
  23 + providers: [
  24 + helpers.createProviderToValue('TranslatorService', translatorService)
  25 + ]
  26 + });
  27 + helper = new ComponentTestHelper<BlockEditionComponent>(cls, done);
  28 + });
  29 +
  30 + it("get available languages from translator service", () => {
  31 + expect(helper.component.languageOptions).toEqual(['all', 'en', 'pt']);
  32 + });
  33 +
  34 +});
... ...
src/app/layout/blocks/block-edition/block-edition.component.ts 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +import { Input, Inject, Component } from 'ng-forward';
  2 +import { TranslatorService } from "../../../shared/services/translator.service";
  3 +
  4 +@Component({
  5 + selector: 'noosfero-block-edition',
  6 + templateUrl: 'app/layout/blocks/block-edition/block-edition.html'
  7 +})
  8 +@Inject(TranslatorService)
  9 +export class BlockEditionComponent {
  10 +
  11 + static $inject = ["TranslatorService"]; // @Inject doesn't works with uibModal.open
  12 +
  13 + displayOptions: any;
  14 + displayUserOptions: any;
  15 + languageOptions: any;
  16 +
  17 + constructor(private translatorService: TranslatorService) {
  18 + this.displayOptions = ["always", "home_page_only", "except_home_page", "never"];
  19 + this.displayUserOptions = ["all", "logged", "not_logged"];
  20 + this.languageOptions = ["all"].concat(Object.keys(translatorService.availableLanguages));
  21 + }
  22 +}
... ...
src/app/layout/blocks/block-edition/block-edition.html 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +<div class="edit-block">
  2 + <h3>{{"block.edition.title" | translate}}</h3>
  3 +
  4 + <form class="options">
  5 + <div class="title block-option">
  6 + <label for="titleInput">{{"block.edition.title.label" | translate}}</label>
  7 + <input type="text" id="titleInput" ng-model="ctrl.block.title" class="block-input">
  8 + </div>
  9 + <div class="display block-option">
  10 + <label for="displayInput">{{"block.edition.display.label" | translate}}</label>
  11 + <select id="displayInput" ng-model="ctrl.block.settings.display" class="block-input">
  12 + <option ng-repeat="option in modal.displayOptions" value="{{option}}">{{"block.edition.display." + option | translate}}</option>
  13 + </select>
  14 + </div>
  15 + <div class="displayUser block-option">
  16 + <label for="displayUserInput">{{"block.edition.display_user.label" | translate}}</label>
  17 + <select id="displayUserInput" ng-model="ctrl.block.settings.display_user" class="block-input">
  18 + <option ng-repeat="option in modal.displayUserOptions" value="{{option}}">{{"block.edition.display_user." + option | translate}}</option>
  19 + </select>
  20 + </div>
  21 + <div class="language block-option">
  22 + <label for="languageInput">{{"block.edition.language.label" | translate}}</label>
  23 + <select id="languageInput" ng-model="ctrl.block.settings.language" class="block-input">
  24 + <option ng-repeat="option in modal.languageOptions" value="{{option}}">{{"language." + option | translate}}</option>
  25 + </select>
  26 + </div>
  27 + </form>
  28 +
  29 + <div class="actions">
  30 + <button type="submit" class="btn btn-default" ng-click="ctrl.save()">Save</button>
  31 + <button type="submit" class="btn btn-warning" ng-click="ctrl.preview()">Preview</button>
  32 + <button type="submit" class="btn btn-danger" ng-click="ctrl.cancel()">Cancel</button>
  33 + </div>
  34 +</div>
... ...
src/app/layout/blocks/block-edition/block-edition.scss 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +.edit-block {
  2 + margin: 20px;
  3 +
  4 + .options {
  5 + margin-bottom: 20px;
  6 +
  7 + .block-option {
  8 + @extend .form-group;
  9 + .block-input {
  10 + @extend .form-control;
  11 + }
  12 + }
  13 + }
  14 +}
... ...
src/app/layout/blocks/block.component.spec.ts
1   -import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
2   -import {Input, provide, Component} from 'ng-forward';
3   -
  1 +import {Component} from 'ng-forward';
4 2 import {BlockComponent} from './block.component';
  3 +import * as helpers from "../../../spec/helpers";
  4 +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
5 5  
6   -const tcb = new TestComponentBuilder();
7   -
8   -const htmlTemplate: string = '<noosfero-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-block>';
9   -
10   -describe("Components", () => {
11   - describe("Block Component", () => {
12   -
13   - // the karma preprocessor html2js transform the templates html into js files which put
14   - // the templates to the templateCache into the module templates
15   - // we need to load the module templates here as the template for the
16   - // component Block will be load on our tests
17   - beforeEach(angular.mock.module("templates"));
18   -
19   - it("receives the block and the owner as inputs", done => {
20   -
21   - // Creating a container component (BlockContainerComponent) to include
22   - // the component under test (Block)
23   - @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [BlockComponent] })
24   - class BlockContainerComponent {
25   - block = { type: 'Block' };
26   - owner = { name: 'profile-name' };
27   - constructor() {
28   - }
29   - }
30   -
31   - // uses the TestComponentBuilder instance to initialize the component
32   - tcb
33   - .createAsync(BlockContainerComponent).then(fixture => {
34   - // and here we can inspect and run the test assertions
35   - let myComponent: BlockComponent = fixture.componentInstance;
36   -
37   - // assure the block object inside the Block matches
38   - // the provided through the parent component
39   - expect(myComponent.block.type).toEqual("Block");
40   - expect(myComponent.owner.name).toEqual("profile-name");
41   - done();
42   - });
43   - });
  6 +const htmlTemplate: string = '<noosfero-block [block]="ctrl.block" [owner]="ctrl.profile"></noosfero-block>';
44 7  
  8 +describe("Boxes Component", () => {
45 9  
46   - it("renders a component which matches to the block type", done => {
47   - // CustomBlock component created to check if it will be used
48   - // when a block with type 'CustomBlock' is provided to the noosfero-block (Block)
49   - // *** Important *** - the selector is what ng-forward uses to define the name of the directive provider
50   - @Component({ selector: 'noosfero-custom-block', template: "<h1>My Custom Block</h1>" })
51   - class CustomBlock {
52   - @Input() block: any;
53   - @Input() owner: any;
54   - }
55   -
56   - @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [BlockComponent, CustomBlock] })
57   - class CustomBlockType {
58   - block = { type: 'CustomBlock' };
59   - owner = { name: 'profile-name' };
60   - constructor() {
61   - }
62   - }
63   - tcb
64   - .createAsync(CustomBlockType).then(fixture => {
65   - let myComponent: CustomBlockType = fixture.componentInstance;
66   - expect(myComponent.block.type).toEqual("CustomBlock");
67   - expect(fixture.debugElement.componentViewChildren[0].text()).toEqual("My Custom Block");
68   - done();
69   - });
  10 + let helper: ComponentTestHelper<BlockComponent>;
  11 + beforeEach(() => {
  12 + angular.mock.module("templates");
  13 + });
  14 +
  15 + let properties = {
  16 + block: { id: 1 },
  17 + owner: {
  18 + id: 1,
  19 + identifier: 'profile-name',
  20 + type: 'Person'
  21 + }
  22 + };
  23 + beforeEach((done) => {
  24 + let cls = createClass({
  25 + template: htmlTemplate,
  26 + directives: [BlockComponent],
  27 + properties: properties,
  28 + providers: [
  29 + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})),
  30 + helpers.createProviderToValue('AuthService', helpers.mocks.authService),
  31 + helpers.createProviderToValue('$state', state),
  32 + helpers.createProviderToValue('TranslatorService', translatorService),
  33 + helpers.createProviderToValue('$uibModal', helpers.mocks.$modal),
  34 + helpers.createProviderToValue('BlockService', blockService),
  35 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService)
  36 + ]
70 37 });
  38 + helper = new ComponentTestHelper<BlockComponent>(cls, done);
  39 + });
71 40  
  41 + let translatorService = jasmine.createSpyObj("translatorService", ["currentLanguage"]);
  42 + let blockService = jasmine.createSpyObj("blockService", ["update"]);
  43 + let state = jasmine.createSpyObj("state", ["current"]);
  44 + state.current = { name: "" };
72 45  
73   - it("renders the default block when hasn't defined a block type", done => {
74   - @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [BlockComponent] })
75   - class CustomBlockType {
76   - block: any = { type: null };
77   - owner: any = { name: 'profile-name' };
78   - constructor() {
79   - }
80   - }
81   - tcb
82   - .createAsync(CustomBlockType).then(fixture => {
83   - let myComponent: CustomBlockType = fixture.componentInstance;
84   - expect(myComponent.block.type).toBeNull();
85   - expect(!!fixture.debugElement.nativeElement.querySelector("noosfero-default-block")).toBeTruthy();
86   - done();
87   - });
88   - });
  46 + it("set isHomepage as false by default", () => {
  47 + expect(helper.component.isHomepage).toBeFalsy();
  48 + });
  49 +
  50 + it("set isHomepage as true when in profile home page", () => {
  51 + state.current = { name: "main.profile.home" };
  52 + helper.component.ngOnInit();
  53 + expect(helper.component.isHomepage).toBeTruthy();
  54 + });
  55 +
  56 + it("set isHomepage as true when in profile info page", () => {
  57 + state.current = { name: "main.profile.info" };
  58 + helper.component.ngOnInit();
  59 + expect(helper.component.isHomepage).toBeTruthy();
  60 + });
  61 +
  62 + it("set isHomepage as true when in profile page", () => {
  63 + state.current = { name: "main.profile.page" };
  64 + state.params = { page: "/page" };
  65 + (<noosfero.Profile>helper.component.owner).homepage = '/page';
  66 + helper.component.ngOnInit();
  67 + expect(helper.component.isHomepage).toBeTruthy();
  68 + });
  69 +
  70 + it("set isHomepage as true when in environment home page", () => {
  71 + state.current = { name: "main.environment.home" };
  72 + helper.component.owner = <noosfero.Environment>{};
  73 + helper.component.ngOnInit();
  74 + expect(helper.component.isHomepage).toBeTruthy();
  75 + });
  76 +
  77 + it("return true in canDisplay when no display option is setted", () => {
  78 + helper.component.block = <any>{};
  79 + expect(helper.component.canDisplay()).toEqual(true);
  80 + });
  81 +
  82 + it("return false in canDisplay for an invisible block", () => {
  83 + helper.component.block = <any>{ settings: { display: "never" } };
  84 + expect(helper.component.canDisplay()).toEqual(false);
  85 + });
  86 +
  87 + it("return false in canDisplay with except_home_page in homepage", () => {
  88 + helper.component.block = <any>{ settings: { display_user: "except_home_page" } };
  89 + expect(helper.component.canDisplay()).toEqual(false);
  90 + });
  91 +
  92 + it("return false in canDisplay with home_page_only outside homepage", () => {
  93 + helper.component.block = <any>{ settings: { display_user: "home_page_only" } };
  94 + expect(helper.component.canDisplay()).toEqual(false);
  95 + });
  96 +
  97 + it("return true in canDisplay when display_user is all for logged user", () => {
  98 + helper.component.block = <any>{ settings: { display_user: "all" } };
  99 + expect(helper.component.canDisplay()).toEqual(true);
  100 + });
  101 +
  102 + it("return true in canDisplay when display_user is all for not logged user", () => {
  103 + helper.component.currentUser = null;
  104 + helper.component.block = <any>{ settings: { display_user: "all" } };
  105 + expect(helper.component.canDisplay()).toEqual(true);
  106 + });
89 107  
  108 + it("return false in canDisplay when display_user is logged for not logged user", () => {
  109 + helper.component.currentUser = null;
  110 + helper.component.block = <any>{ settings: { display_user: "logged" } };
  111 + expect(helper.component.canDisplay()).toEqual(false);
90 112 });
91   -});
92 113 \ No newline at end of file
  114 +
  115 + it("return false in canDisplay when display_user is not_logged for logged user", () => {
  116 + helper.component.block = <any>{ settings: { display_user: "not_logged" } };
  117 + expect(helper.component.canDisplay()).toEqual(false);
  118 + });
  119 +
  120 + it("return false in canDisplay when current language is not equal to language in block settings", () => {
  121 + helper.component['translatorService'].currentLanguage = jasmine.createSpy("currentLanguage").and.returnValue("pt");
  122 + helper.component.block = <any>{ settings: { language: "en" } };
  123 + expect(helper.component.canDisplay()).toEqual(false);
  124 + });
  125 +
  126 + it("return false in canDisplay when hide is true", () => {
  127 + helper.component.block = <any>{ id: 1, hide: true };
  128 + expect(helper.component.canDisplay()).toEqual(false);
  129 + });
  130 +
  131 + it("return true in canDisplay when hide is not true", () => {
  132 + helper.component.block = <any>{ id: 1, hide: false };
  133 + expect(helper.component.canDisplay()).toEqual(true);
  134 + });
  135 +
  136 +});
... ...
src/app/layout/blocks/block.component.ts
1   -import { Input, Inject, Component } from 'ng-forward';
  1 +import { Input, Component, Inject } from 'ng-forward';
  2 +import { BlockEditionComponent } from './block-edition/block-edition.component';
  3 +import { BlockService } from '../../../lib/ng-noosfero-api/http/block.service';
  4 +import { NotificationService } from '../../shared/services/notification.service';
  5 +import { AuthService, SessionService, AuthEvents } from "../../login";
  6 +import { TranslatorService } from "../../shared/services/translator.service";
  7 +import { DesignModeService } from "../../admin/layout-edit/designMode.service";
2 8  
3 9 @Component({
4 10 selector: 'noosfero-block',
5   - template: '<div></div>'
  11 + templateUrl: 'app/layout/blocks/block.html',
  12 + directives: [BlockEditionComponent]
6 13 })
7   -@Inject("$element", "$scope", "$injector", "$compile")
  14 +@Inject("$uibModal", "$scope", "$state", "$rootScope", BlockService, NotificationService, AuthService, SessionService, TranslatorService, DesignModeService)
8 15 export class BlockComponent {
9 16  
10   - @Input() block: any;
11   - @Input() owner: any;
  17 + @Input() block: noosfero.Block;
  18 + @Input() owner: noosfero.Profile | noosfero.Environment;
  19 +
  20 + private modalInstance: any = null;
  21 + originalBlock: noosfero.Block;
  22 +
  23 + currentUser: noosfero.User;
  24 + isHomepage = true;
  25 + editionMode = false;
  26 + designMode = false;
  27 +
  28 + constructor(private $uibModal: any,
  29 + private $scope: ng.IScope,
  30 + private $state: ng.ui.IStateService,
  31 + private $rootScope: ng.IRootScopeService,
  32 + private blockService: BlockService,
  33 + private notificationService: NotificationService,
  34 + private authService: AuthService,
  35 + private session: SessionService,
  36 + private translatorService: TranslatorService,
  37 + private designModeService: DesignModeService) {
  38 +
  39 + this.currentUser = this.session.currentUser();
  40 + this.authService.subscribe(AuthEvents[AuthEvents.loginSuccess], () => {
  41 + this.currentUser = this.session.currentUser();
  42 + this.verifyHomepage();
  43 + });
  44 + this.authService.subscribe(AuthEvents[AuthEvents.logoutSuccess], () => {
  45 + this.currentUser = this.session.currentUser();
  46 + this.verifyHomepage();
  47 + });
  48 + this.$rootScope.$on("$stateChangeSuccess", (event: ng.IAngularEvent, toState: ng.ui.IState) => {
  49 + this.verifyHomepage();
  50 + });
  51 + this.designModeService.onToggle.subscribe((designModeOn: boolean) => {
  52 + this.editionMode = designModeOn;
  53 + this.designMode = designModeOn;
  54 + this.$scope.$apply();
  55 + });
  56 + }
12 57  
13 58 ngOnInit() {
14   - let blockName = (this.block && this.block.type) ? this.block.type.replace(/::/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() : "default-block";
15   - this.$element.replaceWith(this.$compile('<noosfero-' + blockName + ' [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-' + blockName + '>')(this.$scope));
  59 + this.verifyHomepage();
  60 + }
  61 +
  62 + openEdit() {
  63 + this.editionMode = true;
  64 + if (!this.originalBlock) this.originalBlock = JSON.parse(JSON.stringify(this.block)); // deep copy of block data
  65 + this.modalInstance = this.$uibModal.open({
  66 + templateUrl: 'app/layout/blocks/block-edition/block-edition.html',
  67 + size: 'lg',
  68 + controller: BlockEditionComponent,
  69 + controllerAs: 'modal',
  70 + bindToController: true,
  71 + scope: this.$scope
  72 + });
  73 + }
  74 +
  75 + save() {
  76 + this.editionMode = false;
  77 + this.blockService.update(this.attributesToUpdate()).then(() => {
  78 + this.closeEdit();
  79 + this.notificationService.success({ title: "block.edition.success.title", message: "block.edition.success.message" });
  80 + });
  81 + }
  82 +
  83 + preview() {
  84 + this.closeEdit();
  85 + }
  86 +
  87 + cancel() {
  88 + this.editionMode = false;
  89 + this.block = this.originalBlock;
  90 + this.closeEdit();
16 91 }
17 92  
18   - constructor(private $element: any, private $scope: ng.IScope, private $injector: ng.auto.IInjectorService, private $compile: ng.ICompileService) {
  93 + canDisplay() {
  94 + return this.visible() && this.displayToUser() &&
  95 + this.displayOnLanguage(this.translatorService.currentLanguage()) &&
  96 + !this.block.hide;
19 97 }
  98 +
  99 + protected visible() {
  100 + let display = this.block.settings ? (<any>this.block.settings)['display'] : null;
  101 + return !display || ((this.isHomepage ? display !== "except_home_page" : display !== "home_page_only") && display !== "never");
  102 + }
  103 +
  104 + protected displayToUser() {
  105 + let displayUser = this.block.settings ? (<any>this.block.settings)['display_user'] : null;
  106 + return !displayUser || displayUser === "all" ||
  107 + (this.currentUser ? displayUser === "logged" : displayUser === "not_logged");
  108 + }
  109 +
  110 + protected displayOnLanguage(language: string) {
  111 + let displayLanguage = this.block.settings ? (<any>this.block.settings)['language'] : null;
  112 + return !displayLanguage || displayLanguage === "all" ||
  113 + language === displayLanguage;
  114 + }
  115 +
  116 + protected attributesToUpdate() {
  117 + return <any>{
  118 + id: this.block.id,
  119 + display: (<any>this.block.settings).display,
  120 + title: this.block.title,
  121 + display_user: (<any>this.block.settings).display_user,
  122 + language: (<any>this.block.settings).language
  123 + };
  124 + }
  125 +
  126 + protected verifyHomepage() {
  127 + if (this.owner && ["Profile", "Community", "Person"].indexOf((<any>this.owner)['type']) >= 0) {
  128 + let profile = <noosfero.Profile>this.owner;
  129 + this.isHomepage = this.$state.current.name === "main.profile.home";
  130 + if (profile.homepage) {
  131 + this.isHomepage = this.isHomepage ||
  132 + (this.$state.current.name === "main.profile.page" && profile.homepage === this.$state.params['page']);
  133 + } else {
  134 + this.isHomepage = this.isHomepage || this.$state.current.name === "main.profile.info";
  135 + }
  136 + } else {
  137 + this.isHomepage = this.$state.current.name === "main.environment.home";
  138 + }
  139 + }
  140 +
  141 + private closeEdit() {
  142 + if (this.modalInstance) {
  143 + this.modalInstance.close();
  144 + this.modalInstance = null;
  145 + }
  146 + }
  147 +
20 148 }
... ...
src/app/layout/blocks/block.html 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +<div ng-show="ctrl.canDisplay() || ctrl.editionMode || ctrl.designMode" ng-class="{'invisible-block': !ctrl.canDisplay()}" class="noosfero-block" ng-mouseover="displayActions = true" ng-mouseleave="displayActions = false">
  2 + <div ng-show="displayActions" class="actions block-actions" permission="ctrl.block.permissions" permission-action="allow_edit">
  3 + <button type="submit" class="btn btn-xs btn-default" ng-click="ctrl.openEdit()"><i class="fa fa-edit fa-fw"></i></button>
  4 + </div>
  5 + <div class="panel panel-default block {{ctrl.block.type | lowercase}}" >
  6 + <div class="panel-heading" ng-show="ctrl.block.title">
  7 + <h3 class="panel-title">{{ctrl.block.title}}</h3>
  8 + </div>
  9 + <div class="panel-body {{ctrl.block.type | lowercase}}" >
  10 + <noosfero-block-content [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-block-content>
  11 + </div>
  12 + </div>
  13 +</div>
... ...
src/app/layout/blocks/block.scss
1   -.block {
2   - .panel-title {
3   - font-size: 15px;
4   - font-weight: bold;
  1 +.noosfero-block {
  2 + position: relative;
  3 + &.invisible-block {
  4 + .block {
  5 + opacity: 0.4;
  6 + }
5 7 }
6   - .panel-heading {
7   - background-color: transparent;
8   - border: 0;
  8 + .block {
  9 + .panel-title {
  10 + font-size: 15px;
  11 + font-weight: bold;
  12 + }
  13 + .panel-heading {