Compare View

switch
from
...
to
 
Commits (38)
Showing 89 changed files   Show diff stats
.gitignore
... ... @@ -14,3 +14,4 @@ typings
14 14 npm-debug.log
15 15 src/vendor.bundle.js*
16 16 .vagrant/
  17 +*.sw*
... ...
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/environment/environment.html
1 1 <div class="environment-container">
2 2 <div class="row">
3   - <noosfero-boxes ng-if="vm.boxes" [boxes]="vm.boxes" [owner]="vm.environment"></noosfero-boxes>
  3 + <noosfero-boxes ng-if="vm.boxes"
  4 + [layout]="vm.environment.layout_template"
  5 + [boxes]="vm.boxes"
  6 + [owner]="vm.environment">
  7 + </noosfero-boxes>
4 8 </div>
5 9 </div>
... ...
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 {
  14 + background-color: transparent;
  15 + border: 0;
  16 + }
  17 + }
  18 + .block-actions {
  19 + position: absolute;
  20 + z-index: 100;
  21 + right: 1px;
  22 + top: 1px;
9 23 }
10 24 }
... ...
src/app/layout/blocks/index.ts
1 1 /* Module Index Entry - generated using the script npm run generate-index */
2   -export * from "./block.component";
  2 +export * from "./block-content.component";
... ...
src/app/layout/blocks/main/main-block.component.ts
1 1 import {Component, Input} from 'ng-forward';
2   -import {BlockComponent} from '../block.component';
3 2  
4 3 @Component({
5 4 selector: 'noosfero-main-block',
... ...
src/app/layout/blocks/person-tags-plugin-interests/index.ts 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./person-tags-plugin-interests-block.component";
... ...
src/app/layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,56 @@
  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 {PersonTagsPluginInterestsBlockComponent} from './person-tags-plugin-interests-block.component';
  5 +import * as helpers from "./../../../../spec/helpers";
  6 +
  7 +const htmlTemplate: string = '<noosfero-person-tags-plugin-interests-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-person-tags-plugin-interests-block>';
  8 +
  9 +const tcb = new TestComponentBuilder();
  10 +
  11 +describe("Components", () => {
  12 + describe("Person Tags Interests Block Component", () => {
  13 +
  14 + let settingsObj = {};
  15 + let person = <noosfero.Person>{ name: "Person" };
  16 + let mockedService = {
  17 + getTags: (profile: noosfero.Profile): any => {
  18 + return Promise.resolve({ data: ['foo', 'bar'], headers: (name: string) => { return name; } });
  19 + }
  20 + };
  21 + beforeEach(angular.mock.module("templates"));
  22 +
  23 + let state = jasmine.createSpyObj("state", ["go"]);
  24 +
  25 +
  26 + function getProviders() {
  27 + return [
  28 + new Provider('$state', { useValue: state }),
  29 + new Provider('PersonService', {
  30 + useValue: mockedService
  31 + })
  32 + ].concat(provideFilters("truncateFilter", "stripTagsFilter"));
  33 + }
  34 + let componentClass: any = null;
  35 +
  36 + function getComponent() {
  37 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [PersonTagsPluginInterestsBlockComponent], providers: getProviders() })
  38 + class BlockContainerComponent {
  39 + block = { type: 'Block', settings: settingsObj };
  40 + owner = person;
  41 + constructor() {
  42 + }
  43 + }
  44 + return BlockContainerComponent;
  45 + }
  46 +
  47 + it("get tags from the person service", done => {
  48 + tcb.createAsync(getComponent()).then(fixture => {
  49 + let PersonTagsPluginInterestsBlock: PersonTagsPluginInterestsBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  50 + expect(PersonTagsPluginInterestsBlock.tags).toEqual(['foo', 'bar']);
  51 + done();
  52 + });
  53 + });
  54 +
  55 + });
  56 +});
... ...
src/app/layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.component.ts 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +import {Component, Inject, Input} from "ng-forward";
  2 +import {PersonService} from "./../../../../lib/ng-noosfero-api/http/person.service";
  3 +import {Arrays} from "./../../../../lib/util/arrays";
  4 +
  5 +@Component({
  6 + selector: "noosfero-person-tags-plugin-interests-block",
  7 + templateUrl: 'app/layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.html'
  8 +})
  9 +@Inject(PersonService, "$state")
  10 +export class PersonTagsPluginInterestsBlockComponent {
  11 +
  12 + @Input() block: any;
  13 + @Input() owner: any;
  14 +
  15 + profile: any;
  16 + tags: any;
  17 +
  18 + constructor(private personService: PersonService, private $state: any) { }
  19 +
  20 + ngOnInit() {
  21 + this.profile = this.owner;
  22 + this.tags = [];
  23 + this.personService.getTags(this.owner).then((result: noosfero.RestResult<any>) => {
  24 + this.tags = result.data;
  25 + });
  26 + }
  27 +}
... ...
src/app/layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.html 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<ul class="person-tags-plugin-interests">
  2 + <li ng-repeat="tag in ctrl.tags">{{tag}}</li>
  3 +</ul>
... ...
src/app/layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.scss 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +.person-tags-plugin-interests {
  2 + padding: 0;
  3 + margin: 0;
  4 +
  5 + li {
  6 + list-style: none;
  7 + float: left;
  8 + padding: 5px;
  9 + margin: 5px;
  10 + text-transform: lowercase;
  11 + color: #fff;
  12 + background-color: #1E96D0;
  13 + }
  14 +}
... ...
src/app/layout/boxes/box.html
1   -<div ng-class="{'col-md-2-5': box.position!=1, 'col-md-7': box.position==1}">
2   - <div ng-repeat="block in box.blocks | displayBlocks:ctrl.isHomepage:ctrl.currentUser | orderBy: 'position'" class="panel panel-default block {{block.type | lowercase}}" >
  1 +<div ng-class="box.position | setBoxLayout:ctrl.layout">
  2 +
  3 + <div ng-repeat="block in box.blocks | orderBy: 'position'" class="panel panel-default block {{block.type | lowercase}}" >
3 4 <div class="panel-heading" ng-show="block.title">
4 5 <h3 class="panel-title">{{block.title}}</h3>
5 6 </div>
... ...
src/app/layout/boxes/boxes.component.spec.ts
... ... @@ -48,40 +48,4 @@ describe(&quot;Boxes Component&quot;, () =&gt; {
48 48 expect(helper.find('div.col-md-7').length).toEqual(1);
49 49 expect(helper.find('div.col-md-2-5').length).toEqual(1);
50 50 });
51   -
52   - it("check the boxes order", () => {
53   - expect(helper.component.boxesOrder(properties['boxes'][0])).toEqual(1);
54   - expect(helper.component.boxesOrder(properties['boxes'][1])).toEqual(0);
55   - });
56   -
57   - it("set isHomepage as false by default", () => {
58   - expect(helper.component.isHomepage).toBeFalsy();
59   - });
60   -
61   - it("set isHomepage as true when in profile home page", () => {
62   - state.current = { name: "main.profile.home" };
63   - helper.component.ngOnInit();
64   - expect(helper.component.isHomepage).toBeTruthy();
65   - });
66   -
67   - it("set isHomepage as true when in profile info page", () => {
68   - state.current = { name: "main.profile.info" };
69   - helper.component.ngOnInit();
70   - expect(helper.component.isHomepage).toBeTruthy();
71   - });
72   -
73   - it("set isHomepage as true when in profile page", () => {
74   - state.current = { name: "main.profile.page" };
75   - state.params = { page: "/page" };
76   - (<noosfero.Profile>helper.component.owner).homepage = '/page';
77   - helper.component.ngOnInit();
78   - expect(helper.component.isHomepage).toBeTruthy();
79   - });
80   -
81   - it("set isHomepage as true when in environment home page", () => {
82   - state.current = { name: "main.environment.home" };
83   - helper.component.owner = <noosfero.Environment>{};
84   - helper.component.ngOnInit();
85   - expect(helper.component.isHomepage).toBeTruthy();
86   - });
87 51 });
... ...
src/app/layout/boxes/boxes.component.ts
1 1 import {Input, Inject, Component} from 'ng-forward';
2   -import {SessionService, AuthService, AuthEvents} from "../../login";
3   -import {DisplayBlocks} from "./display-blocks.filter";
  2 +import {DisplayBoxes} from "./display-boxes.filter";
  3 +import {SetBoxLayout} from "./set-box-layout.filter";
4 4  
5 5 @Component({
6 6 selector: "noosfero-boxes",
7 7 templateUrl: "app/layout/boxes/boxes.html",
8   - directives: [DisplayBlocks]
  8 + directives: [DisplayBoxes, SetBoxLayout]
9 9 })
10   -@Inject("SessionService", 'AuthService', "$state", "$rootScope")
11 10 export class BoxesComponent {
12 11  
13 12 @Input() boxes: noosfero.Box[];
14 13 @Input() owner: noosfero.Profile | noosfero.Environment;
  14 + @Input() layout: string;
15 15  
16   - currentUser: noosfero.User;
17   - isHomepage = true;
18   -
19   - constructor(private session: SessionService,
20   - private authService: AuthService,
21   - private $state: ng.ui.IStateService,
22   - private $rootScope: ng.IRootScopeService) {
23   -
24   - this.currentUser = this.session.currentUser();
25   - this.authService.subscribe(AuthEvents[AuthEvents.loginSuccess], () => {
26   - this.currentUser = this.session.currentUser();
27   - this.verifyHomepage();
28   - });
29   - this.authService.subscribe(AuthEvents[AuthEvents.logoutSuccess], () => {
30   - this.currentUser = this.session.currentUser();
31   - this.verifyHomepage();
32   - });
33   - this.$rootScope.$on("$stateChangeSuccess", (event: ng.IAngularEvent, toState: ng.ui.IState) => {
34   - this.verifyHomepage();
35   - });
36   - }
37   -
38   - ngOnInit() {
39   - this.verifyHomepage();
40   - }
41   -
42   - boxesOrder(box: noosfero.Box) {
43   - if (box.position === 2) return 0;
44   - return box.position;
45   - }
46   -
47   - private verifyHomepage() {
48   - if (this.owner && ["Profile", "Community", "Person"].indexOf((<any>this.owner)['type']) >= 0) {
49   - let profile = <noosfero.Profile>this.owner;
50   - this.isHomepage = this.$state.current.name === "main.profile.home";
51   - if (profile.homepage) {
52   - this.isHomepage = this.isHomepage ||
53   - (this.$state.current.name === "main.profile.page" && profile.homepage === this.$state.params['page']);
54   - } else {
55   - this.isHomepage = this.isHomepage || this.$state.current.name === "main.profile.info";
56   - }
57   - } else {
58   - this.isHomepage = this.$state.current.name === "main.environment.home";
59   - }
60   - }
61 16 }
... ...
src/app/layout/boxes/boxes.html
1   -<ng-include ng-repeat="box in ctrl.boxes | orderBy: ctrl.boxesOrder" src="'app/layout/boxes/box.html'"></ng-include>
  1 +<ng-include ng-repeat="box in ctrl.boxes | displayBoxes:ctrl.layout" src="'app/layout/boxes/box.html'"></ng-include>
... ...
src/app/layout/boxes/boxes.scss
1 1 .col-md-2-5 {
2 2 @extend .col-md-3;
3   - @media (min-width: 920px) {
  3 + @media (min-width: 992px) {
4 4 width: 20.83%;
5 5 }
6 6 }
... ...
src/app/layout/boxes/display-boxes.filter.ts 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +import {Pipe, Inject} from "ng-forward";
  2 +import {TranslatorService} from "../../shared/services/translator.service";
  3 +
  4 +@Pipe("displayBoxes")
  5 +@Inject(TranslatorService)
  6 +export class DisplayBoxes {
  7 +
  8 + constructor(private translatorService: TranslatorService) { }
  9 +
  10 + transform(boxes: noosfero.Box[], layout: string) {
  11 + let function_str: string = "visible_on" + layout;
  12 + let valid_boxes: number[] = [];
  13 + let selected: noosfero.Box[] = [];
  14 + boxes = boxes || [];
  15 +
  16 + if(layout == "rightbar") {
  17 + valid_boxes = this.visible_on_right_bar();
  18 + }else {
  19 + valid_boxes = this.visible_on_default();
  20 + }
  21 +
  22 + for (let box of boxes) {
  23 + if(valid_boxes.indexOf(box.position) != -1){
  24 + selected.push(box);
  25 + }
  26 + }
  27 + return selected;
  28 + }
  29 +
  30 + private visible_on_default() {
  31 + return [1,2,3];
  32 + }
  33 +
  34 + private visible_on_right_bar() {
  35 + return [1,3];
  36 + }
  37 +
  38 +}
  39 +
... ...
src/app/layout/boxes/set-box-layout.filter.ts 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +import {Pipe, Inject} from "ng-forward";
  2 +import {TranslatorService} from "../../shared/services/translator.service";
  3 +
  4 +@Pipe("setBoxLayout")
  5 +@Inject(TranslatorService)
  6 +export class SetBoxLayout {
  7 +
  8 + constructor(private translatorService: TranslatorService) { }
  9 +
  10 + transform(pos: number, layout: string) {
  11 + console.log(layout)
  12 + if(layout == "rightbar") {
  13 + return this.right_bar(pos);
  14 + }else {
  15 + return this.default(pos);
  16 + }
  17 + }
  18 +
  19 + private default(position: number) {
  20 + if(position == 1) {
  21 + return "col-md-6 col-md-push-3";
  22 + }else if(position == 2){
  23 + return "col-md-3 col-md-pull-6";
  24 + }else {
  25 + return "col-md-3";
  26 + }
  27 + }
  28 +
  29 + private right_bar(position: number) {
  30 + if(position == 1) {
  31 + return "col-sm-6 col-sm-push-2";
  32 + }else{
  33 + return "col-sm-3 col-sm-push-2";
  34 + }
  35 + }
  36 +
  37 +}
... ...
src/app/layout/navbar/navbar.html
... ... @@ -26,23 +26,29 @@
26 26 <span ng-bind="ctrl.currentUser.person.name"></span> <b class="caret"></b>
27 27 </a>
28 28 <ul class="dropdown-menu" uib-dropdown-menu>
29   - <li>
30   - <a ui-sref="main.profile.info({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-user"></i> {{"navbar.profile" | translate}}</a>
31   - </li>
32   - <li>
33   - <a target="_self" ui-sref="main.profile.settings({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-gear"></i> {{"navbar.settings" | translate}}</a>
34   - </li>
35   - <li class="divider"></li>
36   - <li>
37   - <a href="#" ng-click="ctrl.logout()"><i class="fa fa-fw fa-power-off"></i> {{"navbar.logout" | translate}}</a>
38   - </li>
  29 + <li>
  30 + <a ui-sref="main.profile.info({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-user"></i> {{"navbar.profile" | translate}}</a>
  31 + </li>
  32 + <li>
  33 + <a target="_self" ui-sref="main.profile.settings({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-gear"></i> {{"navbar.settings" | translate}}</a>
  34 + </li>
  35 + <li class="divider"></li>
  36 + <li>
  37 + <a href="#" ng-click="ctrl.logout()"><i class="fa fa-fw fa-power-off"></i> {{"navbar.logout" | translate}}</a>
  38 + </li>
39 39 </ul>
40 40 </li>
41 41 </ul>
  42 +
42 43 <ul class="nav navbar-nav navbar-right">
43 44 <language-selector class="nav navbar-nav navbar-right"></language-selector>
44 45 </ul>
45 46 <div ui-view="actions"></div>
  47 + <div class="nav navbar-nav search navbar-right">
  48 + <search-form></search-form>
  49 + </div>
46 50 </div>
47 51 </div>
48 52 </nav>
  53 +<div ui-view="toolbar">
  54 +</div>
49 55 \ No newline at end of file
... ...
src/app/layout/navbar/navbar.scss
... ... @@ -34,6 +34,16 @@
34 34 text-align: center;
35 35 }
36 36  
  37 + .search {
  38 + .search-input {
  39 + height: 45px;
  40 + margin-top: 10px;
  41 + }
  42 + .input-group-btn {
  43 + padding-top: 10px;
  44 + }
  45 + }
  46 +
37 47 @media (max-width: $break-sm-max) {
38 48 .navbar-toggle .fa-bars{
39 49 font-size: 14pt;
... ...
src/app/layout/navbar/navbar.ts
... ... @@ -4,11 +4,13 @@ import {SessionService, AuthService, AuthController, AuthEvents} from &quot;./../../l
4 4 import {EnvironmentService} from "./../../../lib/ng-noosfero-api/http/environment.service";
5 5 import {SidebarNotificationService} from "../sidebar/sidebar.notification.service";
6 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';
7 9  
8 10 @Component({
9 11 selector: "acme-navbar",
10 12 templateUrl: "app/layout/navbar/navbar.html",
11   - directives: [LanguageSelectorComponent],
  13 + directives: [LanguageSelectorComponent, DesignModeTogglerComponent, BootstrapSwitcherComponent],
12 14 providers: [AuthService, SessionService, SidebarNotificationService, EnvironmentService]
13 15 })
14 16 @Inject("$uibModal", AuthService, "SessionService", "$state", SidebarNotificationService, BodyStateClassesService, EnvironmentService)
... ... @@ -18,7 +20,6 @@ export class Navbar {
18 20 private modalInstance: any = null;
19 21 public showHamburger: boolean = false;
20 22 public currentEnvironment: noosfero.Environment = <any>{ name: '' };
21   -
22 23 /**
23 24 *
24 25 */
... ...
src/app/layout/scss/_layout.scss
... ... @@ -35,3 +35,16 @@
35 35 padding: 0 20px 20px 20px;
36 36 }
37 37 }
  38 +
  39 +ul.nav > li.toggler-container {
  40 + position: relative;
  41 + padding-top: 12px;
  42 +}
  43 +
  44 +.noosfero-main-toolbar {
  45 + padding: 5px;
  46 + @include make-row();
  47 + margin-left: 0px;
  48 + margin-right: 0px;
  49 + background-color: #edecec;
  50 +}
... ...
src/app/layout/scss/skins/_whbl.scss
... ... @@ -3,7 +3,7 @@ $whbl-sidebar-linkColor: #484848;
3 3 $whbl-primary-color: #1E96D0; //blue
4 4 $whbl-font-color: #16191c;
5 5  
6   -.skin-whbl {
  6 +%skin-base {
7 7 #header-navbar {
8 8 background-color: $whbl-primary-color;
9 9 }
... ... @@ -180,27 +180,19 @@ $whbl-font-color: #16191c;
180 180 }
181 181 .pagination {
182 182 > li {
183   - > a,
184   - > span,
185   - > a:hover,
186   - > span:hover,
187   - > a:focus,
188   - > span:focus,
189   - > a:active,
190   - > span:active {
191   - color: $whbl-primary-color;
  183 + &.active {
  184 + a {
  185 + background-color: $whbl-primary-color;
  186 + color: #FFF;
  187 + }
192 188 }
193   - }
194   - > .active {
195   - > a,
196   - > span,
197   - > a:hover,
198   - > span:hover,
199   - > a:focus,
200   - > span:focus {
201   - background-color: $whbl-primary-color;
202   - border-color: $whbl-primary-color;
203   - color: #fff;
  189 + > a {
  190 + &:hover {
  191 + background-color: $whbl-primary-color;
  192 + color: #FFF;
  193 + }
  194 + background-color: #FFF;
  195 + color: $whbl-primary-color;
204 196 }
205 197 }
206 198 }
... ... @@ -289,6 +281,31 @@ $whbl-font-color: #16191c;
289 281 .pace .pace-progress {
290 282 background-color: #fff;
291 283 }
  284 +
  285 + noosfero-design-toggler .ats-switch .knob i {
  286 + color: #999999;
  287 + }
  288 +
  289 + .ats-switch .knob {
  290 + padding-right: 15px;
  291 + }
  292 +
  293 + .ats-switch .glyphicon {
  294 + top: 2px;
  295 + }
  296 +
  297 + .ats-switch .switch-left {
  298 + background-color: #41b941;
  299 + font-weight: bold;
  300 + color: white;
  301 + }
  302 +
  303 + .ats-switch .switch-right {
  304 + background-color: #ce3b3b;
  305 + font-weight: bold;
  306 + color: white;
  307 + }
  308 +
292 309 }
293 310 .rtl.skin-whbl #content-wrapper {
294 311 border-left: 0;
... ... @@ -296,7 +313,7 @@ $whbl-font-color: #16191c;
296 313 }
297 314  
298 315 @media (max-width: $break-sm-max) {
299   - .skin-whbl {
  316 + %skin-base {
300 317 #logo.navbar-brand > img.normal-logo.logo-white {
301 318 display: block;
302 319 }
... ... @@ -308,3 +325,7 @@ $whbl-font-color: #16191c;
308 325 }
309 326 }
310 327 }
  328 +
  329 +.skin-whbl {
  330 + @extend %skin-base;
  331 +}
... ...
src/app/layout/services/body-state-classes.service.spec.ts
... ... @@ -4,7 +4,7 @@ import {AuthService} from &quot;./../../login/auth.service&quot;;
4 4 import {AuthEvents} from "./../../login/auth-events";
5 5  
6 6 import {EventEmitter} from 'ng-forward';
7   -
  7 +import {DesignModeService} from './../../admin/layout-edit/designMode.service';
8 8  
9 9 describe("BodyStateClasses Service", () => {
10 10  
... ... @@ -19,10 +19,13 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
19 19 },
20 20 authService: any = helpers.mocks.authService,
21 21 bodyEl: { className: string },
22   - bodyElJq: any;
  22 + bodyElJq: any,
  23 + designModeService = new DesignModeService();
  24 +
  25 +
23 26  
24 27 let getService = (): BodyStateClassesService => {
25   - return new BodyStateClassesService($rootScope, $document, $state, authService);
  28 + return new BodyStateClassesService($rootScope, $document, $state, authService, designModeService);
26 29 };
27 30  
28 31 beforeEach(() => {
... ... @@ -168,4 +171,30 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
168 171  
169 172 expect(contentWrapperMock.removeClass).toHaveBeenCalledWith(BodyStateClassesService.CONTENT_WRAPPER_FULL);
170 173 });
  174 +
  175 + it("should add the class noosfero-design-on when designMode is changed to true", () => {
  176 + let fnOnToggle: Function = null;
  177 + designModeService.onToggle = <any> {
  178 + subscribe: (fn: Function) => {
  179 + fnOnToggle = fn;
  180 + },
  181 + next: (value: boolean) => {
  182 + fnOnToggle.apply(designModeService, [value]);
  183 + }
  184 + };
  185 +
  186 + let service = getService();
  187 +
  188 + bodyElJq.addClass = jasmine.createSpy("addClass");
  189 + bodyElJq.removeClass = jasmine.createSpy("removeClass");
  190 + service["bodyElement"] = bodyElJq;
  191 +
  192 + service.start();
  193 +
  194 + debugger;
  195 + designModeService.setInDesignMode(true);
  196 +
  197 +
  198 + expect(bodyElJq.addClass).toHaveBeenCalledWith(BodyStateClassesService.DESIGN_MODE_ON_CLASSNAME);
  199 + });
171 200 });
... ...
src/app/layout/services/body-state-classes.service.ts
... ... @@ -3,6 +3,7 @@ import {AuthEvents} from &quot;../../login/auth-events&quot;;