Compare View

switch
from
...
to
 
Commits (38)
Showing 89 changed files   Show diff stats
@@ -14,3 +14,4 @@ typings @@ -14,3 +14,4 @@ typings
14 npm-debug.log 14 npm-debug.log
15 src/vendor.bundle.js* 15 src/vendor.bundle.js*
16 .vagrant/ 16 .vagrant/
  17 +*.sw*
@@ -50,3 +50,31 @@ See some important folders bellow: @@ -50,3 +50,31 @@ See some important folders bellow:
50 1. Create an app folder inside custom-theme and add your custom scss files 50 1. Create an app folder inside custom-theme and add your custom scss files
51 1. Put the templates that you want to override in the same structure from the main application source, e.g.: 51 1. Put the templates that you want to override in the same structure from the main application source, e.g.:
52 `src/app/profile/profile.html` will be overriden by `themes/custom-theme/app/profile/profile.html` 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
@@ -35,9 +35,10 @@ @@ -35,9 +35,10 @@
35 "angular-i18n": "^1.5.0", 35 "angular-i18n": "^1.5.0",
36 "angular-load": "^0.4.1", 36 "angular-load": "^0.4.1",
37 "angular-translate-interpolation-messageformat": "^2.10.0", 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 "devDependencies": { 43 "devDependencies": {
43 "angular-mocks": "~1.5.0" 44 "angular-mocks": "~1.5.0"
@@ -27,6 +27,10 @@ exports.configTheme = function(theme) { @@ -27,6 +27,10 @@ exports.configTheme = function(theme) {
27 exports.paths.theme = theme || "angular-default"; 27 exports.paths.theme = theme || "angular-default";
28 exports.paths.allSources = [exports.paths.src, path.join(exports.paths.themes, exports.paths.theme)]; 28 exports.paths.allSources = [exports.paths.src, path.join(exports.paths.themes, exports.paths.theme)];
29 exports.paths.dist = path.join("dist", exports.paths.theme); 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 exports.configTheme(argv.theme); 35 exports.configTheme(argv.theme);
32 36
gulp/inject.js
@@ -3,6 +3,8 @@ @@ -3,6 +3,8 @@
3 var path = require('path'); 3 var path = require('path');
4 var gulp = require('gulp'); 4 var gulp = require('gulp');
5 var conf = require('./conf'); 5 var conf = require('./conf');
  6 +var map = require('map-stream');
  7 +var transform = require('vinyl-transform');
6 8
7 var $ = require('gulp-load-plugins')(); 9 var $ = require('gulp-load-plugins')();
8 10
@@ -40,3 +42,34 @@ gulp.task('inject', ['scripts', 'styles'], function () { @@ -40,3 +42,34 @@ gulp.task('inject', ['scripts', 'styles'], function () {
40 .pipe(wiredep(_.extend({}, conf.wiredep))) 42 .pipe(wiredep(_.extend({}, conf.wiredep)))
41 .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve'))); 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,7 +9,7 @@ var browserSync = require('browser-sync');
9 var $ = require('gulp-load-plugins')(); 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 return buildScripts() 13 return buildScripts()
14 .pipe(browserSync.stream()); 14 .pipe(browserSync.stream());
15 }); 15 });
@@ -8,10 +8,11 @@ @@ -8,10 +8,11 @@
8 "ng-forward": "0.0.1-alpha.12" 8 "ng-forward": "0.0.1-alpha.12"
9 }, 9 },
10 "config": { 10 "config": {
11 - "theme": "angular-default" 11 + "theme": "angular-default",
  12 + "skin": "skin-whbl"
12 }, 13 },
13 "scripts": { 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 "build-all": "gulp clean && for d in ./themes/* ; do (gulp --theme=`basename $d` build); done", 16 "build-all": "gulp clean && for d in ./themes/* ; do (gulp --theme=`basename $d` build); done",
16 "webpack": "webpack", 17 "webpack": "webpack",
17 "karma": "concurrently \"webpack -w\" \"karma start\"", 18 "karma": "concurrently \"webpack -w\" \"karma start\"",
@@ -23,7 +24,7 @@ @@ -23,7 +24,7 @@
23 "test": "webpack -w --test", 24 "test": "webpack -w --test",
24 "jenkins": "webpack && karma start --single-run", 25 "jenkins": "webpack && karma start --single-run",
25 "postinstall": "bower install; typings install; cd dev-scripts; typings install; cd ../; npm run fix-jqlite", 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 "generate-indexes": "ts-node --project ./dev-scripts ./dev-scripts/generate-index-modules.ts", 28 "generate-indexes": "ts-node --project ./dev-scripts ./dev-scripts/generate-index-modules.ts",
28 "fix-jqlite": "ts-node --project ./dev-scripts dev-scripts/fix-jqlite.ts" 29 "fix-jqlite": "ts-node --project ./dev-scripts dev-scripts/fix-jqlite.ts"
29 }, 30 },
@@ -46,8 +47,8 @@ @@ -46,8 +47,8 @@
46 "gulp-eslint": "~1.0.0", 47 "gulp-eslint": "~1.0.0",
47 "gulp-filter": "~3.0.1", 48 "gulp-filter": "~3.0.1",
48 "gulp-flatten": "~0.2.0", 49 "gulp-flatten": "~0.2.0",
49 - "gulp-insert": "^0.5.0",  
50 "gulp-inject": "~3.0.0", 50 "gulp-inject": "~3.0.0",
  51 + "gulp-insert": "^0.5.0",
51 "gulp-load-plugins": "~0.10.0", 52 "gulp-load-plugins": "~0.10.0",
52 "gulp-merge-json": "^0.4.0", 53 "gulp-merge-json": "^0.4.0",
53 "gulp-minify-css": "~1.2.1", 54 "gulp-minify-css": "~1.2.1",
@@ -81,6 +82,7 @@ @@ -81,6 +82,7 @@
81 "karma-webpack": "^1.7.0", 82 "karma-webpack": "^1.7.0",
82 "lodash": "~3.10.1", 83 "lodash": "~3.10.1",
83 "main-bower-files": "~2.9.0", 84 "main-bower-files": "~2.9.0",
  85 + "map-stream": "0.0.6",
84 "merge-stream": "^1.0.0", 86 "merge-stream": "^1.0.0",
85 "on-build-webpack": "^0.1.0", 87 "on-build-webpack": "^0.1.0",
86 "phantomjs": "~1.9.18", 88 "phantomjs": "~1.9.18",
@@ -96,6 +98,7 @@ @@ -96,6 +98,7 @@
96 "typescript": "^1.8.10", 98 "typescript": "^1.8.10",
97 "typings": "^0.6.8", 99 "typings": "^0.6.8",
98 "uglify-save-license": "~0.4.1", 100 "uglify-save-license": "~0.4.1",
  101 + "vinyl-transform": "^1.0.0",
99 "webpack": "^1.12.14", 102 "webpack": "^1.12.14",
100 "wiredep": "~2.2.2", 103 "wiredep": "~2.2.2",
101 "wrench": "~1.5.8", 104 "wrench": "~1.5.8",
src/app/admin/layout-edit/designMode.service.spec.ts 0 → 100644
@@ -0,0 +1,31 @@ @@ -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 \ No newline at end of file 32 \ No newline at end of file
src/app/admin/layout-edit/designMode.service.ts 0 → 100644
@@ -0,0 +1,22 @@ @@ -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 @@ @@ -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 \ No newline at end of file 60 \ No newline at end of file
src/app/admin/layout-edit/designModeToggler.component.ts 0 → 100644
@@ -0,0 +1,24 @@ @@ -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 \ No newline at end of file 25 \ No newline at end of file
src/app/admin/layout-edit/designModeToggler.html 0 → 100644
@@ -0,0 +1,8 @@ @@ -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 \ No newline at end of file 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,7 +14,7 @@ import {ProfileService} from &quot;../../../lib/ng-noosfero-api/http/profile.service&quot;
14 provide('profileService', { useClass: ProfileService }) 14 provide('profileService', { useClass: ProfileService })
15 ] 15 ]
16 }) 16 })
17 -@Inject(ArticleService, ProfileService, "$log", "$stateParams") 17 +@Inject(ArticleService, ProfileService, "$stateParams")
18 export class ContentViewerComponent { 18 export class ContentViewerComponent {
19 19
20 @Input() 20 @Input()
@@ -23,7 +23,10 @@ export class ContentViewerComponent { @@ -23,7 +23,10 @@ export class ContentViewerComponent {
23 @Input() 23 @Input()
24 profile: noosfero.Profile = null; 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 this.activate(); 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,6 +2,7 @@ import {StateConfig, Component, Inject, provide} from &#39;ng-forward&#39;;
2 import {EnvironmentService} from "../../lib/ng-noosfero-api/http/environment.service"; 2 import {EnvironmentService} from "../../lib/ng-noosfero-api/http/environment.service";
3 import {NotificationService} from "../shared/services/notification.service"; 3 import {NotificationService} from "../shared/services/notification.service";
4 import {EnvironmentHomeComponent} from "./environment-home.component"; 4 import {EnvironmentHomeComponent} from "./environment-home.component";
  5 +import {SearchComponent} from "../search/search.component";
5 6
6 /** 7 /**
7 * @ngdoc controller 8 * @ngdoc controller
@@ -29,6 +30,18 @@ import {EnvironmentHomeComponent} from &quot;./environment-home.component&quot;; @@ -29,6 +30,18 @@ import {EnvironmentHomeComponent} from &quot;./environment-home.component&quot;;
29 controllerAs: "vm" 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 @Inject(EnvironmentService, "$state", "currentEnvironment") 47 @Inject(EnvironmentService, "$state", "currentEnvironment")
src/app/environment/environment.html
1 <div class="environment-container"> 1 <div class="environment-container">
2 <div class="row"> 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 </div> 8 </div>
5 </div> 9 </div>
src/app/index.ts
@@ -6,7 +6,7 @@ import {AuthEvents} from &quot;./login/auth-events&quot;; @@ -6,7 +6,7 @@ import {AuthEvents} from &quot;./login/auth-events&quot;;
6 6
7 declare var moment: any; 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 try { 10 try {
11 angular.module('noosfero.templates.app'); 11 angular.module('noosfero.templates.app');
12 } catch (error) { 12 } catch (error) {
src/app/layout/blocks/block-content.component.spec.ts 0 → 100644
@@ -0,0 +1,91 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 import {BlockComponent} from './block.component'; 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 \ No newline at end of file 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 @Component({ 9 @Component({
4 selector: 'noosfero-block', 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 export class BlockComponent { 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 ngOnInit() { 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 @@ @@ -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 /* Module Index Entry - generated using the script npm run generate-index */ 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 import {Component, Input} from 'ng-forward'; 1 import {Component, Input} from 'ng-forward';
2 -import {BlockComponent} from '../block.component';  
3 2
4 @Component({ 3 @Component({
5 selector: 'noosfero-main-block', 4 selector: 'noosfero-main-block',
src/app/layout/blocks/person-tags-plugin-interests/index.ts 0 → 100644
@@ -0,0 +1,2 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 <div class="panel-heading" ng-show="block.title"> 4 <div class="panel-heading" ng-show="block.title">
4 <h3 class="panel-title">{{block.title}}</h3> 5 <h3 class="panel-title">{{block.title}}</h3>
5 </div> 6 </div>
src/app/layout/boxes/boxes.component.spec.ts
@@ -48,40 +48,4 @@ describe(&quot;Boxes Component&quot;, () =&gt; { @@ -48,40 +48,4 @@ describe(&quot;Boxes Component&quot;, () =&gt; {
48 expect(helper.find('div.col-md-7').length).toEqual(1); 48 expect(helper.find('div.col-md-7').length).toEqual(1);
49 expect(helper.find('div.col-md-2-5').length).toEqual(1); 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 import {Input, Inject, Component} from 'ng-forward'; 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 @Component({ 5 @Component({
6 selector: "noosfero-boxes", 6 selector: "noosfero-boxes",
7 templateUrl: "app/layout/boxes/boxes.html", 7 templateUrl: "app/layout/boxes/boxes.html",
8 - directives: [DisplayBlocks] 8 + directives: [DisplayBoxes, SetBoxLayout]
9 }) 9 })
10 -@Inject("SessionService", 'AuthService', "$state", "$rootScope")  
11 export class BoxesComponent { 10 export class BoxesComponent {
12 11
13 @Input() boxes: noosfero.Box[]; 12 @Input() boxes: noosfero.Box[];
14 @Input() owner: noosfero.Profile | noosfero.Environment; 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 .col-md-2-5 { 1 .col-md-2-5 {
2 @extend .col-md-3; 2 @extend .col-md-3;
3 - @media (min-width: 920px) { 3 + @media (min-width: 992px) {
4 width: 20.83%; 4 width: 20.83%;
5 } 5 }
6 } 6 }
src/app/layout/boxes/display-boxes.filter.ts 0 → 100644
@@ -0,0 +1,39 @@ @@ -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 @@ @@ -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,23 +26,29 @@
26 <span ng-bind="ctrl.currentUser.person.name"></span> <b class="caret"></b> 26 <span ng-bind="ctrl.currentUser.person.name"></span> <b class="caret"></b>
27 </a> 27 </a>
28 <ul class="dropdown-menu" uib-dropdown-menu> 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 </ul> 39 </ul>
40 </li> 40 </li>
41 </ul> 41 </ul>
  42 +
42 <ul class="nav navbar-nav navbar-right"> 43 <ul class="nav navbar-nav navbar-right">
43 <language-selector class="nav navbar-nav navbar-right"></language-selector> 44 <language-selector class="nav navbar-nav navbar-right"></language-selector>
44 </ul> 45 </ul>
45 <div ui-view="actions"></div> 46 <div ui-view="actions"></div>
  47 + <div class="nav navbar-nav search navbar-right">
  48 + <search-form></search-form>
  49 + </div>
46 </div> 50 </div>
47 </div> 51 </div>
48 </nav> 52 </nav>
  53 +<div ui-view="toolbar">
  54 +</div>
49 \ No newline at end of file 55 \ No newline at end of file
src/app/layout/navbar/navbar.scss
@@ -34,6 +34,16 @@ @@ -34,6 +34,16 @@
34 text-align: center; 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 @media (max-width: $break-sm-max) { 47 @media (max-width: $break-sm-max) {
38 .navbar-toggle .fa-bars{ 48 .navbar-toggle .fa-bars{
39 font-size: 14pt; 49 font-size: 14pt;
src/app/layout/navbar/navbar.ts
@@ -4,11 +4,13 @@ import {SessionService, AuthService, AuthController, AuthEvents} from &quot;./../../l @@ -4,11 +4,13 @@ import {SessionService, AuthService, AuthController, AuthEvents} from &quot;./../../l
4 import {EnvironmentService} from "./../../../lib/ng-noosfero-api/http/environment.service"; 4 import {EnvironmentService} from "./../../../lib/ng-noosfero-api/http/environment.service";
5 import {SidebarNotificationService} from "../sidebar/sidebar.notification.service"; 5 import {SidebarNotificationService} from "../sidebar/sidebar.notification.service";
6 import {BodyStateClassesService} from '../services/body-state-classes.service'; 6 import {BodyStateClassesService} from '../services/body-state-classes.service';
  7 +import {DesignModeTogglerComponent} from './../../admin/layout-edit/designModeToggler.component';
  8 +import {BootstrapSwitcherComponent, BootstrapSwitcherItem} from './../../shared/components/bootstrap-switcher/bootstrap-switcher.component';
7 9
8 @Component({ 10 @Component({
9 selector: "acme-navbar", 11 selector: "acme-navbar",
10 templateUrl: "app/layout/navbar/navbar.html", 12 templateUrl: "app/layout/navbar/navbar.html",
11 - directives: [LanguageSelectorComponent], 13 + directives: [LanguageSelectorComponent, DesignModeTogglerComponent, BootstrapSwitcherComponent],
12 providers: [AuthService, SessionService, SidebarNotificationService, EnvironmentService] 14 providers: [AuthService, SessionService, SidebarNotificationService, EnvironmentService]
13 }) 15 })
14 @Inject("$uibModal", AuthService, "SessionService", "$state", SidebarNotificationService, BodyStateClassesService, EnvironmentService) 16 @Inject("$uibModal", AuthService, "SessionService", "$state", SidebarNotificationService, BodyStateClassesService, EnvironmentService)
@@ -18,7 +20,6 @@ export class Navbar { @@ -18,7 +20,6 @@ export class Navbar {
18 private modalInstance: any = null; 20 private modalInstance: any = null;
19 public showHamburger: boolean = false; 21 public showHamburger: boolean = false;
20 public currentEnvironment: noosfero.Environment = <any>{ name: '' }; 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,3 +35,16 @@
35 padding: 0 20px 20px 20px; 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,7 +3,7 @@ $whbl-sidebar-linkColor: #484848;
3 $whbl-primary-color: #1E96D0; //blue 3 $whbl-primary-color: #1E96D0; //blue
4 $whbl-font-color: #16191c; 4 $whbl-font-color: #16191c;
5 5
6 -.skin-whbl { 6 +%skin-base {
7 #header-navbar { 7 #header-navbar {
8 background-color: $whbl-primary-color; 8 background-color: $whbl-primary-color;
9 } 9 }
@@ -180,27 +180,19 @@ $whbl-font-color: #16191c; @@ -180,27 +180,19 @@ $whbl-font-color: #16191c;
180 } 180 }
181 .pagination { 181 .pagination {
182 > li { 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,6 +281,31 @@ $whbl-font-color: #16191c;
289 .pace .pace-progress { 281 .pace .pace-progress {
290 background-color: #fff; 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 .rtl.skin-whbl #content-wrapper { 310 .rtl.skin-whbl #content-wrapper {
294 border-left: 0; 311 border-left: 0;
@@ -296,7 +313,7 @@ $whbl-font-color: #16191c; @@ -296,7 +313,7 @@ $whbl-font-color: #16191c;
296 } 313 }
297 314
298 @media (max-width: $break-sm-max) { 315 @media (max-width: $break-sm-max) {
299 - .skin-whbl { 316 + %skin-base {
300 #logo.navbar-brand > img.normal-logo.logo-white { 317 #logo.navbar-brand > img.normal-logo.logo-white {
301 display: block; 318 display: block;
302 } 319 }
@@ -308,3 +325,7 @@ $whbl-font-color: #16191c; @@ -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,7 +4,7 @@ import {AuthService} from &quot;./../../login/auth.service&quot;;
4 import {AuthEvents} from "./../../login/auth-events"; 4 import {AuthEvents} from "./../../login/auth-events";
5 5
6 import {EventEmitter} from 'ng-forward'; 6 import {EventEmitter} from 'ng-forward';
7 - 7 +import {DesignModeService} from './../../admin/layout-edit/designMode.service';
8 8
9 describe("BodyStateClasses Service", () => { 9 describe("BodyStateClasses Service", () => {
10 10
@@ -19,10 +19,13 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; { @@ -19,10 +19,13 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
19 }, 19 },
20 authService: any = helpers.mocks.authService, 20 authService: any = helpers.mocks.authService,
21 bodyEl: { className: string }, 21 bodyEl: { className: string },
22 - bodyElJq: any; 22 + bodyElJq: any,
  23 + designModeService = new DesignModeService();
  24 +
  25 +
23 26
24 let getService = (): BodyStateClassesService => { 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 beforeEach(() => { 31 beforeEach(() => {
@@ -168,4 +171,30 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; { @@ -168,4 +171,30 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
168 171
169 expect(contentWrapperMock.removeClass).toHaveBeenCalledWith(BodyStateClassesService.CONTENT_WRAPPER_FULL); 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;; @@ -3,6 +3,7 @@ import {AuthEvents} from &quot;../../login/auth-events&quot;;
3 import {AuthService} from "./../../login/auth.service"; 3 import {AuthService} from "./../../login/auth.service";
4 import {HtmlUtils} from "../html-utils"; 4 import {HtmlUtils} from "../html-utils";
5 import {INgForwardJQuery} from 'ng-forward/cjs/util/jqlite-extensions'; 5 import {INgForwardJQuery} from 'ng-forward/cjs/util/jqlite-extensions';
  6 +import {DesignModeService} from './../../admin/layout-edit/designMode.service';
6 7
7 export interface StartParams { 8 export interface StartParams {
8 skin?: string; 9 skin?: string;
@@ -22,12 +23,13 @@ export interface StartParams { @@ -22,12 +23,13 @@ export interface StartParams {
22 * - full-content 23 * - full-content
23 */ 24 */
24 @Injectable() 25 @Injectable()
25 -@Inject("$rootScope", "$document", "$state", AuthService) 26 +@Inject("$rootScope", "$document", "$state", AuthService, DesignModeService)
26 export class BodyStateClassesService { 27 export class BodyStateClassesService {
27 28
28 private started: boolean = false; 29 private started: boolean = false;
29 private skin: string; 30 private skin: string;
30 31
  32 + public static get DESIGN_MODE_ON_CLASSNAME(): string { return "noosfero-design-on"; }
31 public static get USER_LOGGED_CLASSNAME(): string { return "noosfero-user-logged"; } 33 public static get USER_LOGGED_CLASSNAME(): string { return "noosfero-user-logged"; }
32 public static get ROUTE_STATE_CLASSNAME_PREFIX(): string { return "noosfero-route-"; } 34 public static get ROUTE_STATE_CLASSNAME_PREFIX(): string { return "noosfero-route-"; }
33 public static get CONTENT_WRAPPER_FULL(): string { return "full-content"; } 35 public static get CONTENT_WRAPPER_FULL(): string { return "full-content"; }
@@ -38,16 +40,16 @@ export class BodyStateClassesService { @@ -38,16 +40,16 @@ export class BodyStateClassesService {
38 private $rootScope: ng.IRootScopeService, 40 private $rootScope: ng.IRootScopeService,
39 private $document: ng.IDocumentService, 41 private $document: ng.IDocumentService,
40 private $state: ng.ui.IStateService, 42 private $state: ng.ui.IStateService,
41 - private authService: AuthService 43 + private authService: AuthService,
  44 + private designModeService: DesignModeService
42 ) { 45 ) {
43 -  
44 } 46 }
45 47
46 start(config?: StartParams) { 48 start(config?: StartParams) {
47 if (!this.started) { 49 if (!this.started) {
48 this.setupUserLoggedClassToggle(); 50 this.setupUserLoggedClassToggle();
49 this.setupStateClassToggle(); 51 this.setupStateClassToggle();
50 - 52 + this.setupDesignModeClassToggle();
51 if (config) { 53 if (config) {
52 this.setThemeSkin(config.skin); 54 this.setThemeSkin(config.skin);
53 } 55 }
@@ -87,9 +89,19 @@ export class BodyStateClassesService { @@ -87,9 +89,19 @@ export class BodyStateClassesService {
87 } 89 }
88 90
89 /** 91 /**
90 - * Setup the initial class name on body element indicating the current route  
91 - * and adds event handler to swith this class when the current page/state changes 92 + * setup the listeners to the desigModeService to add class on the Body Element
  93 + * indicating the user activated the designMode
92 */ 94 */
  95 + private setupDesignModeClassToggle() {
  96 + this.designModeService.onToggle.subscribe((designOn: boolean) => {
  97 + if (designOn) {
  98 + this.getBodyElement().addClass(BodyStateClassesService.DESIGN_MODE_ON_CLASSNAME);
  99 + } else {
  100 + this.getBodyElement().removeClass(BodyStateClassesService.DESIGN_MODE_ON_CLASSNAME);
  101 + }
  102 + });
  103 + }
  104 +
93 private setupStateClassToggle() { 105 private setupStateClassToggle() {
94 let bodyElement = this.getBodyElement(); 106 let bodyElement = this.getBodyElement();
95 bodyElement.addClass(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + this.$state.current.name); 107 bodyElement.addClass(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + this.$state.current.name);
src/app/main/main.component.ts
@@ -6,6 +6,7 @@ import {ArticleViewComponent} from &quot;./../article/article-default-view.component&quot; @@ -6,6 +6,7 @@ import {ArticleViewComponent} from &quot;./../article/article-default-view.component&quot;
6 6
7 import {ProfileComponent} from "../profile/profile.component"; 7 import {ProfileComponent} from "../profile/profile.component";
8 import {BoxesComponent} from "../layout/boxes/boxes.component"; 8 import {BoxesComponent} from "../layout/boxes/boxes.component";
  9 +import {BlockContentComponent} from "../layout/blocks/block-content.component";
9 import {BlockComponent} from "../layout/blocks/block.component"; 10 import {BlockComponent} from "../layout/blocks/block.component";
10 import {EnvironmentComponent} from "../environment/environment.component"; 11 import {EnvironmentComponent} from "../environment/environment.component";
11 import {EnvironmentHomeComponent} from "../environment/environment-home.component"; 12 import {EnvironmentHomeComponent} from "../environment/environment-home.component";
@@ -16,6 +17,7 @@ import {RecentDocumentsBlockComponent} from &quot;../layout/blocks/recent-documents/r @@ -16,6 +17,7 @@ import {RecentDocumentsBlockComponent} from &quot;../layout/blocks/recent-documents/r
16 import {ProfileImageBlockComponent} from "../layout/blocks/profile-image/profile-image-block.component"; 17 import {ProfileImageBlockComponent} from "../layout/blocks/profile-image/profile-image-block.component";
17 import {RawHTMLBlockComponent} from "../layout/blocks/raw-html/raw-html-block.component"; 18 import {RawHTMLBlockComponent} from "../layout/blocks/raw-html/raw-html-block.component";
18 import {StatisticsBlockComponent} from "../layout/blocks/statistics/statistics-block.component"; 19 import {StatisticsBlockComponent} from "../layout/blocks/statistics/statistics-block.component";
  20 +import {PersonTagsPluginInterestsBlockComponent} from "../layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.component";
19 import {CustomContentComponent} from "../profile/custom-content/custom-content.component"; 21 import {CustomContentComponent} from "../profile/custom-content/custom-content.component";
20 22
21 import {MembersBlockComponent} from "../layout/blocks/members/members-block.component"; 23 import {MembersBlockComponent} from "../layout/blocks/members/members-block.component";
@@ -40,6 +42,8 @@ import {SidebarComponent} from &quot;../layout/sidebar/sidebar.component&quot;; @@ -40,6 +42,8 @@ import {SidebarComponent} from &quot;../layout/sidebar/sidebar.component&quot;;
40 import {MainBlockComponent} from "../layout/blocks/main/main-block.component"; 42 import {MainBlockComponent} from "../layout/blocks/main/main-block.component";
41 import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component"; 43 import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component";
42 import {PermissionDirective} from "../shared/components/permission/permission.directive"; 44 import {PermissionDirective} from "../shared/components/permission/permission.directive";
  45 +import {SearchComponent} from "../search/search.component";
  46 +import {SearchFormComponent} from "../search/search-form/search-form.component";
43 47
44 /** 48 /**
45 * @ngdoc controller 49 * @ngdoc controller
@@ -82,7 +86,7 @@ export class EnvironmentContent { @@ -82,7 +86,7 @@ export class EnvironmentContent {
82 * @name main.Main 86 * @name main.Main
83 * @requires AuthService, Session, Notification, ArticleBlog, ArticleView, Boxes, Block, LinkListBlock, 87 * @requires AuthService, Session, Notification, ArticleBlog, ArticleView, Boxes, Block, LinkListBlock,
84 * MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock, 88 * MainBlock, RecentDocumentsBlock, Navbar, ProfileImageBlock, MembersBlock,
85 - * NoosferoTemplate, DateFormat, RawHTMLBlock 89 + * NoosferoTemplate, DateFormat, RawHTMLBlock, PersonTagsPluginInterestsBlock
86 * @description 90 * @description
87 * The Main controller for the Noosfero Angular Theme application. 91 * The Main controller for the Noosfero Angular Theme application.
88 * 92 *
@@ -95,12 +99,13 @@ export class EnvironmentContent { @@ -95,12 +99,13 @@ export class EnvironmentContent {
95 selector: 'main', 99 selector: 'main',
96 template: '<ui-view></ui-view>', 100 template: '<ui-view></ui-view>',
97 directives: [ 101 directives: [
98 - ArticleBlogComponent, ArticleViewComponent, BoxesComponent, BlockComponent, 102 + ArticleBlogComponent, ArticleViewComponent, BoxesComponent, BlockContentComponent,
99 EnvironmentComponent, PeopleBlockComponent, DisplayContentBlockComponent, 103 EnvironmentComponent, PeopleBlockComponent, DisplayContentBlockComponent,
100 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, ProfileComponent, 104 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, ProfileComponent,
101 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, 105 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
102 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, 106 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
103 - LoginBlockComponent, CustomContentComponent, PermissionDirective 107 + LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent,
  108 + PersonTagsPluginInterestsBlockComponent, BlockComponent
104 ].concat(plugins.mainComponents).concat(plugins.hotspots), 109 ].concat(plugins.mainComponents).concat(plugins.hotspots),
105 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, 110 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService,
106 "ngAnimate", "ngCookies", "ngStorage", "ngTouch", 111 "ngAnimate", "ngCookies", "ngStorage", "ngTouch",
@@ -109,7 +114,7 @@ export class EnvironmentContent { @@ -109,7 +114,7 @@ export class EnvironmentContent {
109 "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid", 114 "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid",
110 "angular-timeline", "duScroll", "oitozero.ngSweetAlert", 115 "angular-timeline", "duScroll", "oitozero.ngSweetAlert",
111 "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad", 116 "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad",
112 - "angular-click-outside", "noosfero.init"] 117 + "angular-click-outside", "toggle-switch", "noosfero.init"]
113 }) 118 })
114 @StateConfig([ 119 @StateConfig([
115 { 120 {
src/app/profile/custom-content/custom-content.component.ts
@@ -2,27 +2,32 @@ import {Component, Input, Inject} from &#39;ng-forward&#39;; @@ -2,27 +2,32 @@ import {Component, Input, Inject} from &#39;ng-forward&#39;;
2 import {ProfileService} from '../../../lib/ng-noosfero-api/http/profile.service'; 2 import {ProfileService} from '../../../lib/ng-noosfero-api/http/profile.service';
3 import {NotificationService} from '../../shared/services/notification.service'; 3 import {NotificationService} from '../../shared/services/notification.service';
4 import {PermissionDirective} from '../../shared/components/permission/permission.directive'; 4 import {PermissionDirective} from '../../shared/components/permission/permission.directive';
  5 +import {DesignModeService} from '../../admin/layout-edit/designMode.service';
5 6
6 @Component({ 7 @Component({
7 selector: 'custom-content', 8 selector: 'custom-content',
8 templateUrl: "app/profile/custom-content/custom-content.html", 9 templateUrl: "app/profile/custom-content/custom-content.html",
9 directives: [PermissionDirective] 10 directives: [PermissionDirective]
10 }) 11 })
11 -@Inject("$uibModal", "$scope", ProfileService, NotificationService) 12 +@Inject("$uibModal", "$scope", ProfileService, NotificationService, DesignModeService)
12 export class CustomContentComponent { 13 export class CustomContentComponent {
13 14
  15 + static $inject = ["DesignModeService"]; // @Inject doesn't works with uibModal.open
  16 +
14 @Input() attribute: string; 17 @Input() attribute: string;
15 @Input() profile: noosfero.Profile; 18 @Input() profile: noosfero.Profile;
16 @Input() label: string; 19 @Input() label: string;
17 20
18 content: string; 21 content: string;
19 originalContent: string; 22 originalContent: string;
  23 + editionMode = false;
20 private modalInstance: any = null; 24 private modalInstance: any = null;
21 25
22 constructor(private $uibModal: any, 26 constructor(private $uibModal: any,
23 private $scope: ng.IScope, 27 private $scope: ng.IScope,
24 private profileService: ProfileService, 28 private profileService: ProfileService,
25 - private notificationService: NotificationService) { } 29 + private notificationService: NotificationService,
  30 + private designModeService: DesignModeService) { }
26 31
27 ngOnInit() { 32 ngOnInit() {
28 this.$scope.$watch(() => { 33 this.$scope.$watch(() => {
@@ -30,6 +35,9 @@ export class CustomContentComponent { @@ -30,6 +35,9 @@ export class CustomContentComponent {
30 }, () => { 35 }, () => {
31 if (this.profile) this.content = (<any>this.profile)[this.attribute]; 36 if (this.profile) this.content = (<any>this.profile)[this.attribute];
32 }); 37 });
  38 + this.designModeService.onToggle.subscribe((designModeOn: boolean) => {
  39 + this.editionMode = designModeOn;
  40 + });
33 } 41 }
34 42
35 openEdit() { 43 openEdit() {
src/app/profile/custom-content/custom-content.html
1 <div class="custom-content"> 1 <div class="custom-content">
2 - <div class="actions" permission="ctrl.profile.permissions" permission-action="allow_edit"> 2 + <div class="actions" permission="ctrl.profile.permissions" permission-action="allow_edit" ng-show="ctrl.editionMode">
3 <button type="submit" class="btn btn-xs btn-default" ng-click="ctrl.openEdit()"><i class="fa fa-edit fa-fw"></i> {{ctrl.label | translate}}</button> 3 <button type="submit" class="btn btn-xs btn-default" ng-click="ctrl.openEdit()"><i class="fa fa-edit fa-fw"></i> {{ctrl.label | translate}}</button>
4 </div> 4 </div>
5 <div class="content" ng-bind-html="ctrl.content"></div> 5 <div class="content" ng-bind-html="ctrl.content"></div>
src/app/profile/profile-toolbar.component.ts 0 → 100644
@@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
  1 +import {Component, Inject, provide} from "ng-forward";
  2 +import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service";
  3 +
  4 +@Component({
  5 + selector: "profile-toolbar",
  6 + templateUrl: "app/profile/toolbar.html",
  7 + providers: [
  8 + provide('profileService', { useClass: ProfileService })
  9 + ]
  10 +})
  11 +@Inject(ProfileService)
  12 +export class ProfileToolbarComponent {
  13 + profile: noosfero.Profile;
  14 + parentId: number;
  15 +
  16 + constructor(profileService: ProfileService) {
  17 + profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
  18 + this.profile = profile;
  19 + });
  20 + }
  21 +}
src/app/profile/profile.component.ts
@@ -10,7 +10,7 @@ import {ProfileService} from &quot;../../lib/ng-noosfero-api/http/profile.service&quot;; @@ -10,7 +10,7 @@ import {ProfileService} from &quot;../../lib/ng-noosfero-api/http/profile.service&quot;;
10 import {NotificationService} from "../shared/services/notification.service"; 10 import {NotificationService} from "../shared/services/notification.service";
11 import {MyProfileComponent} from "./myprofile.component"; 11 import {MyProfileComponent} from "./myprofile.component";
12 import {ProfileActionsComponent} from "./profile-actions.component"; 12 import {ProfileActionsComponent} from "./profile-actions.component";
13 - 13 +import {ProfileToolbarComponent} from "./profile-toolbar.component";
14 /** 14 /**
15 * @ngdoc controller 15 * @ngdoc controller
16 * @name profile.Profile 16 * @name profile.Profile
@@ -42,6 +42,11 @@ import {ProfileActionsComponent} from &quot;./profile-actions.component&quot;; @@ -42,6 +42,11 @@ import {ProfileActionsComponent} from &quot;./profile-actions.component&quot;;
42 templateUrl: "app/profile/navbar-actions.html", 42 templateUrl: "app/profile/navbar-actions.html",
43 controller: ProfileActionsComponent, 43 controller: ProfileActionsComponent,
44 controllerAs: "vm" 44 controllerAs: "vm"
  45 + },
  46 + "toolbar@main": {
  47 + templateUrl: "app/profile/toolbar.html",
  48 + controller: ProfileToolbarComponent,
  49 + controllerAs: "vm"
45 } 50 }
46 } 51 }
47 }, 52 },
@@ -54,6 +59,11 @@ import {ProfileActionsComponent} from &quot;./profile-actions.component&quot;; @@ -54,6 +59,11 @@ import {ProfileActionsComponent} from &quot;./profile-actions.component&quot;;
54 templateUrl: "app/profile/navbar-actions.html", 59 templateUrl: "app/profile/navbar-actions.html",
55 controller: ProfileActionsComponent, 60 controller: ProfileActionsComponent,
56 controllerAs: "vm" 61 controllerAs: "vm"
  62 + },
  63 + "toolbar@main": {
  64 + templateUrl: "app/profile/toolbar.html",
  65 + controller: ProfileToolbarComponent,
  66 + controllerAs: "vm"
57 } 67 }
58 } 68 }
59 }, 69 },
@@ -106,6 +116,11 @@ import {ProfileActionsComponent} from &quot;./profile-actions.component&quot;; @@ -106,6 +116,11 @@ import {ProfileActionsComponent} from &quot;./profile-actions.component&quot;;
106 templateUrl: "app/article/content-viewer/navbar-actions.html", 116 templateUrl: "app/article/content-viewer/navbar-actions.html",
107 controller: ContentViewerActionsComponent, 117 controller: ContentViewerActionsComponent,
108 controllerAs: "vm" 118 controllerAs: "vm"
  119 + },
  120 + "toolbar@main": {
  121 + templateUrl: "app/profile/toolbar.html",
  122 + controller: ProfileToolbarComponent,
  123 + controllerAs: "vm"
109 } 124 }
110 } 125 }
111 } 126 }
src/app/profile/profile.html
1 <div class="profile-container"> 1 <div class="profile-container">
2 - <custom-content class="profile-header" [label]="'profile.custom_header.label'" [attribute]="'custom_header'" [profile]="vm.profile"></custom-content>  
3 - <div class="row">  
4 - <noosfero-boxes ng-if="vm.boxes" [boxes]="vm.boxes" [owner]="vm.profile"></noosfero-boxes>  
5 - </div> 2 + <custom-content class="profile-header"
  3 + [label]="'profile.custom_header.label'"
  4 + [attribute]="'custom_header'"
  5 + [profile]="vm.profile">
  6 + </custom-content>
  7 + <div class="row" ui-view="profile-info"></div>
  8 + <noosfero-boxes ng-if="vm.boxes"
  9 + [layout]="vm.profile.layout_template"
  10 + [boxes]="vm.boxes"
  11 + [owner]="vm.profile" class="row">
  12 + </noosfero-boxes>
6 <custom-content class="profile-footer" [label]="'profile.custom_footer.label'" [attribute]="'custom_footer'" [profile]="vm.profile"></custom-content> 13 <custom-content class="profile-footer" [label]="'profile.custom_footer.label'" [attribute]="'custom_footer'" [profile]="vm.profile"></custom-content>
7 </div> 14 </div>
src/app/profile/toolbar.html 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +<div class="noosfero-main-toolbar" permission="vm.profile.permissions" permission-action="allow_edit">
  2 + <noosfero-design-toggler class="pull-right"></noosfero-design-toggler>
  3 +
  4 +</div>
0 \ No newline at end of file 5 \ No newline at end of file
src/app/search/search-form/search-form.component.spec.ts 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +import {ComponentTestHelper, createClass} from "../../../spec/component-test-helper";
  2 +import {SearchFormComponent} from "./search-form.component";
  3 +import * as helpers from "../../../spec/helpers";
  4 +
  5 +const htmlTemplate: string = '<search-form></search-form>';
  6 +
  7 +describe("Components", () => {
  8 + describe("Search Form Component", () => {
  9 +
  10 + let helper: ComponentTestHelper<SearchFormComponent>;
  11 + let stateMock = jasmine.createSpyObj("$state", ["go", "params", "current"]);
  12 +
  13 + beforeEach(angular.mock.module("templates"));
  14 +
  15 + beforeEach((done) => {
  16 + let cls = createClass({
  17 + template: htmlTemplate,
  18 + directives: [SearchFormComponent],
  19 + providers: [helpers.createProviderToValue("$state", stateMock)]
  20 + });
  21 + helper = new ComponentTestHelper<SearchFormComponent>(cls, done);
  22 + });
  23 +
  24 + it("render a input for search query", () => {
  25 + expect(helper.find(".search-input").length).toEqual(1);
  26 + });
  27 +
  28 + it("go to search page when click on search button", () => {
  29 + helper.component.query = 'query';
  30 + helper.component.search();
  31 + expect(stateMock.go).toHaveBeenCalledWith('main.environment.search', { query: 'query' });
  32 + });
  33 + });
  34 +});
src/app/search/search-form/search-form.component.ts 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +import {Component, Inject} from "ng-forward";
  2 +
  3 +@Component({
  4 + selector: 'search-form',
  5 + templateUrl: 'app/search/search-form/search-form.html'
  6 +})
  7 +@Inject("$state")
  8 +export class SearchFormComponent {
  9 +
  10 + query: string;
  11 +
  12 + constructor(private $state: ng.ui.IStateService) {
  13 + }
  14 +
  15 + ngOnInit() {
  16 + this.query = this.$state.params['query'];
  17 + }
  18 +
  19 + search() {
  20 + this.$state.go('main.environment.search', { query: this.query });
  21 + }
  22 +
  23 + isSearchPage() {
  24 + return "main.environment.search" === this.$state.current.name;
  25 + }
  26 +}
src/app/search/search-form/search-form.html 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +<form class="navbar-form search-form" role="search" ng-if="!ctrl.isSearchPage()">
  2 + <div class="input-group">
  3 + <input type="text" class="search-input form-control" placeholder="Search" name="q" ng-model="ctrl.query">
  4 + <div class="input-group-btn">
  5 + <button class="btn btn-default" type="submit" (click)="ctrl.search()"><i class="fa fa-search fa-fw"></i></button>
  6 + </div>
  7 + </div>
  8 +</form>
src/app/search/search.component.spec.ts 0 → 100644
@@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
  1 +import {ComponentTestHelper, createClass} from "../../spec/component-test-helper";
  2 +import {SearchComponent} from "./search.component";
  3 +import * as helpers from "../../spec/helpers";
  4 +
  5 +const htmlTemplate: string = '<search></search>';
  6 +
  7 +describe("Components", () => {
  8 + describe("Search Component", () => {
  9 +
  10 + let helper: ComponentTestHelper<SearchComponent>;
  11 + let stateParams = { query: 'query' };
  12 + let articleService = jasmine.createSpyObj("ArticleService", ["search"]);
  13 + let result = Promise.resolve({ data: [{ id: 1 }], headers: (param: string) => { return 1; } });
  14 + articleService.search = jasmine.createSpy("search").and.returnValue(result);
  15 + let stateMock = jasmine.createSpyObj("$state", ["go"]);
  16 +
  17 + beforeEach(angular.mock.module("templates"));
  18 +
  19 + beforeEach((done) => {
  20 + let cls = createClass({
  21 + template: htmlTemplate,
  22 + directives: [SearchComponent],
  23 + providers: [
  24 + helpers.createProviderToValue("$stateParams", stateParams),
  25 + helpers.createProviderToValue("ArticleService", articleService),
  26 + helpers.createProviderToValue("$state", stateMock),
  27 + ].concat(helpers.provideFilters("truncateFilter", "stripTagsFilter"))
  28 + });
  29 + helper = new ComponentTestHelper<SearchComponent>(cls, done);
  30 + });
  31 +
  32 + it("load first page with search results", () => {
  33 + expect(articleService.search).toHaveBeenCalledWith({ query: 'query', per_page: 10, page: 0 });
  34 + });
  35 +
  36 + it("display search results", () => {
  37 + expect(helper.all(".result").length).toEqual(1);
  38 + });
  39 + });
  40 +});
src/app/search/search.component.ts 0 → 100644
@@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
  1 +import {Component, Inject} from "ng-forward";
  2 +import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service";
  3 +
  4 +import {SearchFormComponent} from "./search-form/search-form.component";
  5 +
  6 +@Component({
  7 + selector: 'search',
  8 + templateUrl: 'app/search/search.html',
  9 + directives: [SearchFormComponent]
  10 +})
  11 +@Inject(ArticleService, "$stateParams", "$state")
  12 +export class SearchComponent {
  13 +
  14 + articles: noosfero.Article[];
  15 + query: string;
  16 + totalResults = 0;
  17 + perPage = 10;
  18 + currentPage: number = 0;
  19 +
  20 + constructor(private articleService: ArticleService, private $stateParams: ng.ui.IStateParamsService, private $state: ng.ui.IStateService) {
  21 + this.query = this.$stateParams['query'];
  22 + this.loadPage();
  23 + }
  24 +
  25 + search() {
  26 + this.$state.go('main.environment.search', { query: this.query });
  27 + }
  28 +
  29 + loadPage() {
  30 + let filters = {
  31 + query: this.query,
  32 + per_page: this.perPage,
  33 + page: this.currentPage
  34 + };
  35 + this.articleService.search(filters).then((result: noosfero.RestResult<noosfero.Article[]>) => {
  36 + this.totalResults = <number>result.headers("total");
  37 + this.articles = result.data;
  38 + });
  39 + }
  40 +}
src/app/search/search.html 0 → 100644
@@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
  1 +<form ng-submit="ctrl.search()">
  2 +<label for="query" ng-bind-html="'search.results.query.label' | translate"></label>
  3 +<input id="query" placeholder="{{'search.results.query.placeholder' | translate}}" type="search" class="search-box-title" ng-model="ctrl.query">
  4 +</form>
  5 +<div class="search-results">
  6 + <div class="summary">
  7 + {{"search.results.summary" | translate:{results: ctrl.totalResults}:"messageformat"}}
  8 + </div>
  9 + <div ng-repeat="article in ctrl.articles | orderBy: 'created_at':true" class="result">
  10 + <a class="title" ui-sref="main.profile.page({profile: article.profile.identifier, page: article.path})">
  11 + <h4 ng-bind="article.title"></h4>
  12 + </a>
  13 + <div class="info">
  14 + <a class="profile" ui-sref="main.profile.home({profile: article.profile.identifier})">
  15 + {{article.profile.name}}
  16 + </a>
  17 + <span class="bullet-separator">•</span>
  18 + <span class="time">
  19 + <span am-time-ago="article.created_at | dateFormat"></span>
  20 + </span>
  21 + </div>
  22 + <div class="post-lead" ng-bind-html="article.body | stripTags | truncate: 250: '...': true"></div>
  23 + </div>
  24 + <uib-pagination ng-model="ctrl.currentPage" total-items="ctrl.totalResults" class="pagination-sm center-block"
  25 + boundary-links="true" items-per-page="ctrl.perPage" ng-change="ctrl.loadPage()"
  26 + first-text="«" last-text="»" previous-text="‹" next-text="›">
  27 + </uib-pagination>
  28 +</div>
src/app/search/search.scss 0 → 100644
@@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
  1 +.search-results {
  2 + .summary {
  3 + color: #bbbbbb;
  4 + font-size: 13px;
  5 + border-top: 1px solid #ececec;
  6 + }
  7 + .result {
  8 + margin: 25px 0;
  9 +
  10 + .title {
  11 + h4 {
  12 + margin: 0;
  13 + }
  14 + }
  15 + .info {
  16 + .profile {
  17 + color: #6e9e7b;
  18 + }
  19 + .time {
  20 + color: #6e9e7b;
  21 + font-size: 12px;
  22 + }
  23 + .bullet-separator {
  24 + margin: 0 2px;
  25 + font-size: 10px;
  26 + color: #afd6ba;
  27 + }
  28 + }
  29 + }
  30 +}
  31 +
  32 +.search-box-title {
  33 + border: 0;
  34 + border-bottom: 1px solid rgba(0,0,0,.15);
  35 + border-radius: 0;
  36 + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Open Sans","Helvetica Neue",sans-serif;
  37 + letter-spacing: 0;
  38 + font-weight: 300;
  39 + font-style: normal;
  40 + font-size: 50px;
  41 + height: 80px;
  42 + padding: 0;
  43 + width: 100%;
  44 +}
  45 +
  46 +.search-box-title:focus {
  47 + outline: none;
  48 +}
0 \ No newline at end of file 49 \ No newline at end of file
src/app/shared/components/bootstrap-switcher/bootstrap-switcher.component.ts 0 → 100644
@@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
  1 +import {Component, Input, Output, EventEmitter} from 'ng-forward';
  2 +
  3 +
  4 +export interface BootstrapSwitcherItem {
  5 + value: any;
  6 + label: string;
  7 +}
  8 +@Component({
  9 + selector: 'noosfero-bootstrap-switcher',
  10 + template: `
  11 + <span class="switcher-label" ng-bind="ctrl.label | translate"></span>
  12 + <div class="btn-group switcher">
  13 + <button ng-repeat="option in ctrl.options track by $index"
  14 + (click)="ctrl.switcherClick(option)"
  15 + ng-class="ctrl.getCssClassForItem(option)"
  16 + class="btn btn-xs" ng-bind="option.label | translate">
  17 + </button>
  18 + </div>
  19 + `,
  20 + inputs: ['activeClass', 'defaultClass', 'label', 'options', 'defaultOption'],
  21 + outputs: ['onSwitch']
  22 +})
  23 +export class BootstrapSwitcherComponent {
  24 + @Input() activeClass: string = 'active btn-danger';
  25 + @Input() defaultClass: string = 'btn-default';
  26 + @Input() label: string;
  27 + @Input() options: BootstrapSwitcherItem[];
  28 + @Input() defaultOption: BootstrapSwitcherItem;
  29 + @Output() onSwitch: EventEmitter<BootstrapSwitcherItem> = new EventEmitter<BootstrapSwitcherItem>();
  30 +
  31 + selectedOption: BootstrapSwitcherItem = null;
  32 +
  33 + constructor() { }
  34 +
  35 + ngOnInit() {
  36 + this.selectedOption = this.defaultOption;
  37 + }
  38 +
  39 + isSelectedOption(value: BootstrapSwitcherItem): boolean {
  40 + return this.selectedOption === value;
  41 + }
  42 +
  43 + getCssClassForItem(value: BootstrapSwitcherItem): string {
  44 + return this.isSelectedOption(value) ? this.activeClass : this.defaultClass;
  45 + }
  46 +
  47 + switcherClick(value: BootstrapSwitcherItem) {
  48 + this.selectedOption = value;
  49 + this.onSwitch.next(value);
  50 + }
  51 +}
0 \ No newline at end of file 52 \ No newline at end of file
src/app/shared/components/interfaces.ts 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +
  2 +
  3 +export interface IComponentWithPermissions {
  4 + permissions: () => string[];
  5 +}
0 \ No newline at end of file 6 \ No newline at end of file
src/app/shared/components/permission/permission.directive.ts
@@ -6,7 +6,7 @@ import {Directive, Inject, Input} from &quot;ng-forward&quot;; @@ -6,7 +6,7 @@ import {Directive, Inject, Input} from &quot;ng-forward&quot;;
6 @Inject('$attrs', '$scope', '$element') 6 @Inject('$attrs', '$scope', '$element')
7 export class PermissionDirective { 7 export class PermissionDirective {
8 8
9 - constructor($attrs: ng.IAttributes, $scope: ng.IScope, $element: any) { 9 + constructor($attrs: ng.IAttributes, $scope: ng.IScope, $element: ng.IAugmentedJQuery) {
10 $scope.$watch($attrs['permission'], () => { 10 $scope.$watch($attrs['permission'], () => {
11 let permissions = $scope.$eval($attrs['permission']); 11 let permissions = $scope.$eval($attrs['permission']);
12 let permissionAction = $attrs['permissionAction']; 12 let permissionAction = $attrs['permissionAction'];
src/languages/en.json
@@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
6 "navbar.logout": "Log Out", 6 "navbar.logout": "Log Out",
7 "navbar.login": "Login", 7 "navbar.login": "Login",
8 "navbar.toggle_menu": "Toggle navigation", 8 "navbar.toggle_menu": "Toggle navigation",
  9 + "language.all": "All languages",
9 "language.en": "English", 10 "language.en": "English",
10 "language.pt": "Portuguese", 11 "language.pt": "Portuguese",
11 "language.selector": "Language", 12 "language.selector": "Language",
@@ -74,5 +75,26 @@ @@ -74,5 +75,26 @@
74 "profile.content.success.message": "Profile saved!", 75 "profile.content.success.message": "Profile saved!",
75 "custom_content.title": "Edit content", 76 "custom_content.title": "Edit content",
76 "profile.custom_header.label": "Header", 77 "profile.custom_header.label": "Header",
77 - "profile.custom_footer.label": "Footer" 78 + "profile.custom_footer.label": "Footer",
  79 + "designMode.label": "In Design",
  80 + "designMode.toggle.ON": "ON",
  81 + "designMode.toggle.OFF": "OFF",
  82 + "search.results.summary": "{results, plural, one{result} other{# results}}",
  83 + "search.results.query.label": "Search for:",
  84 + "search.results.query.placeholder": "Search",
  85 + "block.edit": "Edit",
  86 + "block.edition.title": "Edit Block",
  87 + "block.edition.success.title": "Good job!",
  88 + "block.edition.success.message": "Block saved!",
  89 + "block.edition.display.label": "Display this block:",
  90 + "block.edition.title.label": "Custom title for this block:",
  91 + "block.edition.display.always": "In all pages",
  92 + "block.edition.display.home_page_only": "Only in the homepage",
  93 + "block.edition.display.except_home_page": "In all pages, except in the homepage",
  94 + "block.edition.display.never": "Don't display",
  95 + "block.edition.display_user.label": "Display to users:",
  96 + "block.edition.display_user.all": "All users",
  97 + "block.edition.display_user.logged": "Logged",
  98 + "block.edition.display_user.not_logged": "Not logged",
  99 + "block.edition.language.label": "Show for:"
78 } 100 }
src/languages/pt.json
@@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
6 "navbar.logout": "Sair", 6 "navbar.logout": "Sair",
7 "navbar.login": "Login", 7 "navbar.login": "Login",
8 "navbar.toggle_menu": "Abrir Menu", 8 "navbar.toggle_menu": "Abrir Menu",
  9 + "language.all": "Todos os idiomas",
9 "language.en": "Inglês", 10 "language.en": "Inglês",
10 "language.pt": "Português", 11 "language.pt": "Português",
11 "language.selector": "Idioma", 12 "language.selector": "Idioma",
@@ -58,7 +59,7 @@ @@ -58,7 +59,7 @@
58 "article.remove.success.title": "Bom trabalho!", 59 "article.remove.success.title": "Bom trabalho!",
59 "article.remove.success.message": "Artigo removido!", 60 "article.remove.success.message": "Artigo removido!",
60 "article.remove.confirmation.title": "Tem certeza?", 61 "article.remove.confirmation.title": "Tem certeza?",
61 - "article.remove.confirmation.message": "Não será possível recuperar este artigo!", 62 + "article.remove.confirmation.message": "Não será possível recuperar este artigo!",
62 "article.basic_editor.visibility": "Visibilidade", 63 "article.basic_editor.visibility": "Visibilidade",
63 "article.basic_editor.visibility.public": "Público", 64 "article.basic_editor.visibility.public": "Público",
64 "article.basic_editor.visibility.private": "Privado", 65 "article.basic_editor.visibility.private": "Privado",
@@ -74,5 +75,26 @@ @@ -74,5 +75,26 @@
74 "profile.content.success.message": "Perfil salvo!", 75 "profile.content.success.message": "Perfil salvo!",
75 "custom_content.title": "Editar conteúdo", 76 "custom_content.title": "Editar conteúdo",
76 "profile.custom_header.label": "Cabeçalho", 77 "profile.custom_header.label": "Cabeçalho",
77 - "profile.custom_footer.label": "Rodapé" 78 + "profile.custom_footer.label": "Rodapé",
  79 + "designMode.label": "Modo de Edição",
  80 + "designMode.toggle.ON": "Ligado",
  81 + "designMode.toggle.OFF": "Desligado",
  82 + "search.results.summary": "{results, plural, one{# resultado} other{# resultados}}",
  83 + "search.results.query.label": "Buscar:",
  84 + "search.results.query.placeholder": "Informe aqui sua busca",
  85 + "block.edit": "Editar",
  86 + "block.edition.title": "Editar Bloco",
  87 + "block.edition.success.title": "Bom trabalho!",
  88 + "block.edition.success.message": "Bloco salvo com sucesso!",
  89 + "block.edition.display.label": "Exibir este bloco:",
  90 + "block.edition.title.label": "Título personalizado do bloco:",
  91 + "block.edition.display.always": "Em todas as páginas",
  92 + "block.edition.display.home_page_only": "Apenas na página inicial",
  93 + "block.edition.display.except_home_page": "Em todas as páginas, exceto na inicial",
  94 + "block.edition.display.never": "Não exibir",
  95 + "block.edition.display_user.label": "Exibir para usuários:",
  96 + "block.edition.display_user.all": "Todos os usuários",
  97 + "block.edition.display_user.logged": "Logados",
  98 + "block.edition.display_user.not_logged": "Não logados",
  99 + "block.edition.language.label": "Exibir para:"
78 } 100 }
src/lib/ng-noosfero-api/http/article.service.spec.ts
@@ -23,7 +23,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -23,7 +23,7 @@ describe(&quot;Services&quot;, () =&gt; {
23 it("should remove article", (done) => { 23 it("should remove article", (done) => {
24 let articleId = 1; 24 let articleId = 1;
25 $httpBackend.expectDELETE(`/api/v1/articles/${articleId}`).respond(200, { success: "true" }); 25 $httpBackend.expectDELETE(`/api/v1/articles/${articleId}`).respond(200, { success: "true" });
26 - articleService.remove(<noosfero.Article>{id: articleId}); 26 + articleService.remove(<noosfero.Article>{ id: articleId });
27 $httpBackend.flush(); 27 $httpBackend.flush();
28 $httpBackend.verifyNoOutstandingExpectation(); 28 $httpBackend.verifyNoOutstandingExpectation();
29 done(); 29 done();
@@ -32,7 +32,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -32,7 +32,7 @@ describe(&quot;Services&quot;, () =&gt; {
32 it("should return article children", (done) => { 32 it("should return article children", (done) => {
33 let articleId = 1; 33 let articleId = 1;
34 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children`).respond(200, { articles: [{ name: "article1" }] }); 34 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children`).respond(200, { articles: [{ name: "article1" }] });
35 - articleService.getChildren(<noosfero.Article>{id: articleId}).then((result: noosfero.RestResult<noosfero.Article[]>) => { 35 + articleService.getChildren(<noosfero.Article>{ id: articleId }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
36 expect(result.data).toEqual([{ name: "article1" }]); 36 expect(result.data).toEqual([{ name: "article1" }]);
37 done(); 37 done();
38 }); 38 });
@@ -42,7 +42,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -42,7 +42,7 @@ describe(&quot;Services&quot;, () =&gt; {
42 it("should get articles by profile", (done) => { 42 it("should get articles by profile", (done) => {
43 let profileId = 1; 43 let profileId = 1;
44 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles`).respond(200, { articles: [{ name: "article1" }] }); 44 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles`).respond(200, { articles: [{ name: "article1" }] });
45 - articleService.getByProfile(<noosfero.Profile>{id: profileId}).then((result: noosfero.RestResult<noosfero.Article[]>) => { 45 + articleService.getByProfile(<noosfero.Profile>{ id: profileId }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
46 expect(result.data).toEqual([{ name: "article1" }]); 46 expect(result.data).toEqual([{ name: "article1" }]);
47 done(); 47 done();
48 }); 48 });
@@ -52,7 +52,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -52,7 +52,7 @@ describe(&quot;Services&quot;, () =&gt; {
52 it("should get articles by profile with additional filters", (done) => { 52 it("should get articles by profile with additional filters", (done) => {
53 let profileId = 1; 53 let profileId = 1;
54 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles?path=test`).respond(200, { articles: [{ name: "article1" }] }); 54 $httpBackend.expectGET(`/api/v1/profiles/${profileId}/articles?path=test`).respond(200, { articles: [{ name: "article1" }] });
55 - articleService.getByProfile(<noosfero.Profile>{id: profileId}, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => { 55 + articleService.getByProfile(<noosfero.Profile>{ id: profileId }, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
56 expect(result.data).toEqual([{ name: "article1" }]); 56 expect(result.data).toEqual([{ name: "article1" }]);
57 done(); 57 done();
58 }); 58 });
@@ -62,7 +62,7 @@ describe(&quot;Services&quot;, () =&gt; { @@ -62,7 +62,7 @@ describe(&quot;Services&quot;, () =&gt; {
62 it("should get article children with additional filters", (done) => { 62 it("should get article children with additional filters", (done) => {
63 let articleId = 1; 63 let articleId = 1;
64 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children?path=test`).respond(200, { articles: [{ name: "article1" }] }); 64 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children?path=test`).respond(200, { articles: [{ name: "article1" }] });
65 - articleService.getChildren(<noosfero.Article>{id: articleId}, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => { 65 + articleService.getChildren(<noosfero.Article>{ id: articleId }, { path: 'test' }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
66 expect(result.data).toEqual([{ name: "article1" }]); 66 expect(result.data).toEqual([{ name: "article1" }]);
67 done(); 67 done();
68 }); 68 });
@@ -71,14 +71,25 @@ describe(&quot;Services&quot;, () =&gt; { @@ -71,14 +71,25 @@ describe(&quot;Services&quot;, () =&gt; {
71 71
72 it("should create an article in a profile", (done) => { 72 it("should create an article in a profile", (done) => {
73 let profileId = 1; 73 let profileId = 1;
74 - let article: noosfero.Article = <any>{ id: null};  
75 - $httpBackend.expectPOST(`/api/v1/profiles/${profileId}/articles`, { article: article }).respond(200, {article: { id: 2 }});  
76 - articleService.createInProfile(<noosfero.Profile>{id: profileId}, article).then((result: noosfero.RestResult<noosfero.Article>) => { 74 + let article: noosfero.Article = <any>{ id: null };
  75 + $httpBackend.expectPOST(`/api/v1/profiles/${profileId}/articles`, { article: article }).respond(200, { article: { id: 2 } });
  76 + articleService.createInProfile(<noosfero.Profile>{ id: profileId }, article).then((result: noosfero.RestResult<noosfero.Article>) => {
77 expect(result.data).toEqual({ id: 2 }); 77 expect(result.data).toEqual({ id: 2 });
78 done(); 78 done();
79 }); 79 });
80 $httpBackend.flush(); 80 $httpBackend.flush();
81 }); 81 });
  82 +
  83 + it("should search for articles in environment", (done) => {
  84 + let profileId = 1;
  85 + let article: noosfero.Article = <any>{ id: null };
  86 + $httpBackend.expectGET(`/api/v1/search/article?query=query`).respond(200, { articles: [{ id: 2 }] });
  87 + articleService.search({ query: 'query' }).then((result: noosfero.RestResult<noosfero.Article[]>) => {
  88 + expect(result.data).toEqual([{ id: 2 }]);
  89 + done();
  90 + });
  91 + $httpBackend.flush();
  92 + });
82 }); 93 });
83 94
84 95
src/lib/ng-noosfero-api/http/article.service.ts
@@ -118,6 +118,11 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; { @@ -118,6 +118,11 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
118 return this.listSubElements(<noosfero.Article>articleElement, "children", params); 118 return this.listSubElements(<noosfero.Article>articleElement, "children", params);
119 } 119 }
120 120
  121 + search(params: any): ng.IPromise<noosfero.RestResult<noosfero.Article[]>> {
  122 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article[]>>();
  123 + let restRequest = this.restangularService.all("search").customGET('article', params);
  124 + restRequest.then(this.getHandleSuccessFunction(deferred)).catch(this.getHandleErrorFunction(deferred));
  125 + return deferred.promise;
  126 + }
121 127
122 } 128 }
123 -  
src/lib/ng-noosfero-api/http/block.service.spec.ts
@@ -29,6 +29,16 @@ describe(&quot;Services&quot;, () =&gt; { @@ -29,6 +29,16 @@ describe(&quot;Services&quot;, () =&gt; {
29 }); 29 });
30 $httpBackend.flush(); 30 $httpBackend.flush();
31 }); 31 });
  32 +
  33 + it("update block settings", (done) => {
  34 + let blockId = 1;
  35 + $httpBackend.expectPOST(`/api/v1/blocks/${blockId}`).respond(200, { block: { id: blockId } });
  36 + blockService.update(<any>{ id: blockId, display: 'never' }).then((result: noosfero.RestResult<noosfero.Block>) => {
  37 + expect(result.data).toEqual({ id: blockId });
  38 + done();
  39 + });
  40 + $httpBackend.flush();
  41 + });
32 }); 42 });
33 43
34 44
src/lib/ng-noosfero-api/http/block.service.ts
@@ -47,4 +47,12 @@ export class BlockService extends RestangularService&lt;noosfero.Block&gt; { @@ -47,4 +47,12 @@ export class BlockService extends RestangularService&lt;noosfero.Block&gt; {
47 return deferred.promise; 47 return deferred.promise;
48 } 48 }
49 49
  50 + update(block: noosfero.Block) {
  51 + let element = this.getElement(block.id);
  52 + let headers = {
  53 + 'Content-Type': 'application/json'
  54 + };
  55 + return this.post(null, element, { block: block }, headers);
  56 + }
  57 +
50 } 58 }
src/lib/ng-noosfero-api/http/person.service.ts
1 import { Injectable, Inject } from "ng-forward"; 1 import { Injectable, Inject } from "ng-forward";
2 import {RestangularService} from "./restangular_service"; 2 import {RestangularService} from "./restangular_service";
  3 +import {ProfileService} from "./profile.service";
3 4
4 @Injectable() 5 @Injectable()
5 -@Inject("Restangular", "$q", "$log") 6 +@Inject("Restangular", "$q", "$log", ProfileService)
6 export class PersonService extends RestangularService<noosfero.Person> { 7 export class PersonService extends RestangularService<noosfero.Person> {
7 8
8 - constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService) { 9 + constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected profileService: ProfileService) {
9 super(Restangular, $q, $log); 10 super(Restangular, $q, $log);
10 } 11 }
11 12
@@ -20,4 +21,11 @@ export class PersonService extends RestangularService&lt;noosfero.Person&gt; { @@ -20,4 +21,11 @@ export class PersonService extends RestangularService&lt;noosfero.Person&gt; {
20 }; 21 };
21 } 22 }
22 23
  24 + getTags(profile: noosfero.Profile): ng.IPromise<noosfero.RestResult<any>> {
  25 + let p = this.getElement(<number>profile.id).customGET('tags');
  26 + let deferred = this.$q.defer<noosfero.RestResult<any>>();
  27 + p.then(this.getHandleSuccessFunction<noosfero.RestResult<any>>(deferred));
  28 + p.catch(this.getHandleErrorFunction<noosfero.RestResult<any>>(deferred));
  29 + return deferred.promise;
  30 + }
23 } 31 }
src/lib/ng-noosfero-api/http/profile.service.ts
@@ -22,7 +22,7 @@ export class ProfileService { @@ -22,7 +22,7 @@ export class ProfileService {
22 this._currentProfilePromise.resolve(profile); 22 this._currentProfilePromise.resolve(profile);
23 } 23 }
24 24
25 - setCurrentProfileByIdentifier(identifier: string) { 25 + setCurrentProfileByIdentifier(identifier: string): ng.IPromise<noosfero.Profile> {
26 this.resetCurrentProfile(); 26 this.resetCurrentProfile();
27 return this.getByIdentifier(identifier).then((profile: noosfero.Profile) => { 27 return this.getByIdentifier(identifier).then((profile: noosfero.Profile) => {
28 this.setCurrentProfile(profile); 28 this.setCurrentProfile(profile);
src/lib/ng-noosfero-api/http/restangular_service.ts
@@ -79,7 +79,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -79,7 +79,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
79 } 79 }
80 } 80 }
81 return { 81 return {
82 - data: response.data[dataKey], 82 + data: (response.data[dataKey] || response.data),
83 headers: response.headers 83 headers: response.headers
84 }; 84 };
85 }; 85 };
@@ -289,7 +289,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; { @@ -289,7 +289,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
289 let restRequest: ng.IPromise<any>; 289 let restRequest: ng.IPromise<any>;
290 290
291 if (rootElement) { 291 if (rootElement) {
292 - restRequest = rootElement.customPOST(data, path, headers); 292 + restRequest = rootElement.customPOST(data, path, null, headers);
293 } else { 293 } else {
294 restRequest = this.baseResource.customPOST(data, path, headers); 294 restRequest = this.baseResource.customPOST(data, path, headers);
295 } 295 }
src/lib/ng-noosfero-api/interfaces/article.ts
@@ -14,5 +14,7 @@ namespace noosfero { @@ -14,5 +14,7 @@ namespace noosfero {
14 start_date: string; 14 start_date: string;
15 end_date: string; 15 end_date: string;
16 accept_comments: boolean; 16 accept_comments: boolean;
  17 +
  18 + permissions: string[];
17 } 19 }
18 } 20 }
src/lib/ng-noosfero-api/interfaces/block.ts
@@ -5,5 +5,6 @@ namespace noosfero { @@ -5,5 +5,6 @@ namespace noosfero {
5 limit: number; 5 limit: number;
6 api_content: any; 6 api_content: any;
7 hide: boolean; 7 hide: boolean;
  8 + title: string;
8 } 9 }
9 } 10 }
src/lib/ng-noosfero-api/interfaces/environment.ts
@@ -15,5 +15,13 @@ namespace noosfero { @@ -15,5 +15,13 @@ namespace noosfero {
15 */ 15 */
16 id: number; 16 id: number;
17 settings: any 17 settings: any
  18 +
  19 + /**
  20 + * @ngdoc property
  21 + * @name layout_template
  22 + * @propertyOf noofero.Environment
  23 + * @returns {string} The Environment layout (e.g. default, rightbar)
  24 + */
  25 + layout_template: string;
18 } 26 }
19 -}  
20 \ No newline at end of file 27 \ No newline at end of file
  28 +}
src/lib/ng-noosfero-api/interfaces/modelWithPermissions.ts 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +namespace noosfero {
  2 + export interface ModelWithPermissions {
  3 + permissions: string[];
  4 + }
  5 +}
0 \ No newline at end of file 6 \ No newline at end of file
src/lib/ng-noosfero-api/interfaces/profile.ts
@@ -78,5 +78,15 @@ namespace noosfero { @@ -78,5 +78,15 @@ namespace noosfero {
78 * @returns {string} The Profile footer 78 * @returns {string} The Profile footer
79 */ 79 */
80 custom_footer: string; 80 custom_footer: string;
  81 +
  82 + permissions: string[];
  83 +
  84 + /**
  85 + * @ngdoc property
  86 + * @name layout_template
  87 + * @propertyOf noofero.Profile
  88 + * @returns {string} The Profile layout template (e.g.: "rightbar", "default")
  89 + */
  90 + layout_template: string;
81 } 91 }
82 } 92 }
src/plugins/comment_paragraph/hotspot/export-comment-button.component.spec.ts 0 → 100644
@@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
  1 +import {ExportCommentButtonHotspotComponent} from "./export-comment-button.component";
  2 +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
  3 +import * as helpers from "../../../spec/helpers";
  4 +import {Provider} from 'ng-forward';
  5 +import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder';
  6 +import {PermissionDirective} from '../../../app/shared/components/permission/permission.directive';
  7 +
  8 +let htmlTemplate = '<export-comment-button-hotspot [article]="ctrl.article"></export-comment-button-hotspot>';
  9 +
  10 +describe("Components", () => {
  11 + describe("Export Comment Button Hotspot Component", () => {
  12 +
  13 + let serviceMock = jasmine.createSpyObj("CommentParagraphService", ["getArticle"]);
  14 +
  15 + let providers = [new Provider('CommentParagraphService', { useValue: serviceMock })]
  16 + .concat(helpers.provideFilters('translateFilter'));
  17 + let helper: ComponentTestHelper<ExportCommentButtonHotspotComponent>;
  18 +
  19 + beforeEach(angular.mock.module("templates"));
  20 +
  21 + beforeEach((done) => {
  22 + let cls = createClass({
  23 + template: htmlTemplate,
  24 + directives: [ExportCommentButtonHotspotComponent, PermissionDirective],
  25 + providers: providers,
  26 + properties: {
  27 + article: {}
  28 + }
  29 + });
  30 + helper = new ComponentTestHelper<ExportCommentButtonHotspotComponent>(cls, done);
  31 + });
  32 +
  33 + it('return true when comment paragraph is active', () => {
  34 + helper.component.article = <noosfero.Article>{ setting: { comment_paragraph_plugin_activate: true } };
  35 + helper.detectChanges();
  36 + expect(helper.component.isActivated()).toBeTruthy();
  37 + expect(helper.all('.export-comment-button').length).toEqual(1);
  38 + });
  39 +
  40 + it('return false when comment paragraph is not active', () => {
  41 + expect(helper.component.isActivated()).toBeFalsy();
  42 + expect(helper.all('.export-comment-button').length).toEqual(0);
  43 + });
  44 +
  45 + it('return false when article has no setting attribute', () => {
  46 + helper.component.article = <noosfero.Article>{};
  47 + helper.detectChanges();
  48 + expect(helper.component.isActivated()).toBeFalsy();
  49 + expect(helper.all('.export-comment-button').length).toEqual(0);
  50 + });
  51 +
  52 + it('not display export comment button when user does not have permission', () => {
  53 + helper.component.article = <noosfero.Article>{ setting: { comment_paragraph_plugin_activate: true } };
  54 + helper.detectChanges();
  55 + expect(helper.find('.export-comment-button').attr('style')).toEqual("display: none; ");
  56 + });
  57 + });
  58 +});
src/plugins/comment_paragraph/hotspot/export-comment-button.component.ts 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +import { Input, Inject, Component } from "ng-forward";
  2 +import {Hotspot} from "../../../app/hotspot/hotspot.decorator";
  3 +import {CommentParagraphService} from "../http/comment-paragraph.service";
  4 +
  5 +@Component({
  6 + selector: "export-comment-button-hotspot",
  7 + templateUrl: "plugins/comment_paragraph/hotspot/export-comment-button.html",
  8 +})
  9 +@Inject(CommentParagraphService)
  10 +@Hotspot("article_extra_toolbar_buttons")
  11 +export class ExportCommentButtonHotspotComponent {
  12 +
  13 + @Input() article: noosfero.Article;
  14 + exportCommentPath: any;
  15 +
  16 + constructor(private commentParagraphService: CommentParagraphService) { }
  17 +
  18 + isActivated() {
  19 + this.exportCommentPath = ["/api/v1/articles/", this.article.id, "/comment_paragraph_plugin/export"].join("");
  20 + return this.article && this.article.setting && this.article.setting.comment_paragraph_plugin_activate;
  21 + }
  22 +
  23 +}
src/plugins/comment_paragraph/hotspot/export-comment-button.html 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +<a href='{{ctrl.exportCommentPath}}' target="_self"
  2 + class="btn btn-default btn-xs export-comment-button" ng-if="ctrl.isActivated()"
  3 + permission="ctrl.article.permissions" permission-action="allow_edit">
  4 + <i class="fa fa-fw fa-download"></i> {{"comment-paragraph-plugin.export" | translate}}
  5 +</a>
src/plugins/comment_paragraph/index.ts
1 import {AllowCommentComponent} from "./allow-comment/allow-comment.component"; 1 import {AllowCommentComponent} from "./allow-comment/allow-comment.component";
  2 +import {ExportCommentButtonHotspotComponent} from "./hotspot/export-comment-button.component";
2 import {CommentParagraphFormHotspotComponent} from "./hotspot/comment-paragraph-form.component"; 3 import {CommentParagraphFormHotspotComponent} from "./hotspot/comment-paragraph-form.component";
3 import {DiscussionEditorComponent} from "./article/cms/discussion-editor/discussion-editor.component"; 4 import {DiscussionEditorComponent} from "./article/cms/discussion-editor/discussion-editor.component";
4 import {CommentParagraphArticleContentHotspotComponent} from "./hotspot/article-content/article-content.component"; 5 import {CommentParagraphArticleContentHotspotComponent} from "./hotspot/article-content/article-content.component";
5 import {DiscussionBlockComponent} from "./block/discussion/discussion-block.component"; 6 import {DiscussionBlockComponent} from "./block/discussion/discussion-block.component";
6 7
7 export let mainComponents: any = [AllowCommentComponent, DiscussionEditorComponent, DiscussionBlockComponent]; 8 export let mainComponents: any = [AllowCommentComponent, DiscussionEditorComponent, DiscussionBlockComponent];
8 -export let hotspots: any = [CommentParagraphFormHotspotComponent, CommentParagraphArticleContentHotspotComponent]; 9 +export let hotspots: any = [ExportCommentButtonHotspotComponent, CommentParagraphFormHotspotComponent, CommentParagraphArticleContentHotspotComponent];
src/plugins/comment_paragraph/languages/en.json
1 { 1 {
2 "comment-paragraph-plugin.title": "Paragraph Comments", 2 "comment-paragraph-plugin.title": "Paragraph Comments",
  3 + "comment-paragraph-plugin.export": "Export Comments",
3 "comment-paragraph-plugin.discussion.editor.start_date.label": "From", 4 "comment-paragraph-plugin.discussion.editor.start_date.label": "From",
4 "comment-paragraph-plugin.discussion.editor.end_date.label": "To", 5 "comment-paragraph-plugin.discussion.editor.end_date.label": "To",
5 "comment-paragraph-plugin.discussion.header": "Open for comments", 6 "comment-paragraph-plugin.discussion.header": "Open for comments",
src/plugins/comment_paragraph/languages/pt.json
1 { 1 {
2 "comment-paragraph-plugin.title": "Comentários por Parágrafo", 2 "comment-paragraph-plugin.title": "Comentários por Parágrafo",
  3 + "comment-paragraph-plugin.export": "Exportar Comentários",
3 "comment-paragraph-plugin.discussion.editor.start_date.label": "De", 4 "comment-paragraph-plugin.discussion.editor.start_date.label": "De",
4 "comment-paragraph-plugin.discussion.editor.end_date.label": "Até", 5 "comment-paragraph-plugin.discussion.editor.end_date.label": "Até",
5 "comment-paragraph-plugin.discussion.header": "Aberto para comentários", 6 "comment-paragraph-plugin.discussion.header": "Aberto para comentários",
themes/angular-participa-consulta/app/blocks.scss
  1 +%panel-head-theme {
  2 + border: none;
  3 + border-radius: 3px 3px 0 0;
  4 + font-family: "Ubuntu Medium";
  5 + font-size: 15px;
  6 + font-variant: normal;
  7 + font-weight: normal;
  8 + line-height: 30px;
  9 + padding: 15px 15px 15px 15px;
  10 + text-transform: capitalize;
  11 + background-size: 30px 30px;
  12 + background: #DAE1C4 !important;
  13 + color: #4F9CAC;
  14 +}
  15 +
  16 +.panel-title {
  17 + color: #4F9CAC;
  18 +}
  19 +
1 .block-head-with-icon { 20 .block-head-with-icon {
2 padding: 15px 15px 15px 55px; 21 padding: 15px 15px 15px 55px;
3 } 22 }
4 23
  24 +.content-wrapper .block .panel-heading,
  25 +.content-wrapper .side-options .panel-heading {
  26 + @extend %panel-head-theme;
  27 +}
  28 +
5 .content-wrapper .block { 29 .content-wrapper .block {
6 - .panel-heading {  
7 - border: none;  
8 - border-radius: 3px 3px 0 0;  
9 - font-family: "Ubuntu Medium";  
10 - font-size: 15px;  
11 - font-variant: normal;  
12 - font-weight: normal;  
13 - line-height: 30px;  
14 - padding: 15px 15px 15px 15px;  
15 - text-transform: capitalize;  
16 - background-size: 30px 30px;  
17 - background: #DAE1C4;  
18 - color: #4F9CAC;  
19 - }  
20 30
21 &.membersblock { 31 &.membersblock {
22 .panel-heading { 32 .panel-heading {
themes/angular-participa-consulta/app/navbar.scss
  1 +.skin-yellow {
  2 + .navbar-inverse .navbar-toggle {
  3 + border-color: #D49F18;
  4 + }
  5 +
  6 + .container-fluid .navbar-header .navbar-toggle {
  7 + &:hover, &:focus {
  8 + background-color: #f5b025;
  9 + }
  10 + }
  11 +}
  12 +
1 .navbar { 13 .navbar {
2 min-height: 123px; 14 min-height: 123px;
3 background-color: #f9c404; 15 background-color: #f9c404;
themes/angular-participa-consulta/app/participa-consulta.scss
  1 +$selected-color: #f6c445;
  2 +
1 @font-face { 3 @font-face {
2 font-family: 'Ubuntu'; 4 font-family: 'Ubuntu';
3 font-weight: 300; 5 font-weight: 300;
@@ -19,9 +21,51 @@ @@ -19,9 +21,51 @@
19 src: url('../assets/fonts/participa-consulta/Ubuntu-RI.ttf'); 21 src: url('../assets/fonts/participa-consulta/Ubuntu-RI.ttf');
20 } 22 }
21 23
22 -.skin-whbl .notifications-list .item-footer {  
23 - background: #DAE1C4;  
24 - color: #4F9CAC; 24 +.skin-yellow {
  25 + @extend %skin-base;
  26 +
  27 + .notifications-list .item-footer {
  28 + background: #DAE1C4;
  29 + color: #4F9CAC;
  30 + }
  31 +
  32 + .navbar-nav .open > a,
  33 + .navbar-nav .open > a:hover,
  34 + .navbar-nav .open > a:focus {
  35 + background-color: $selected-color;
  36 + color: #000;
  37 + }
  38 +
  39 + .nav .open > a,
  40 + .nav .open > a:hover,
  41 + .nav .open > a:focus {
  42 + border: none;
  43 + }
  44 +
  45 + .dropdown-menu {
  46 + li > a:hover {
  47 + color: #555;
  48 + background-color: #F7F7F7;
  49 + }
  50 +
  51 + .active > a,
  52 + .active > a:hover,
  53 + .active > a:focus {
  54 + color: #000;
  55 + background-color: #CCC;
  56 + }
  57 + }
  58 +
  59 + .nav .caret {
  60 + border-bottom-color: #fff !important;
  61 + border-top-color: #fff !important;
  62 + }
  63 +
  64 + .nav .open .caret {
  65 + border-bottom-color: #000 !important;
  66 + border-top-color: #000 !important;
  67 + }
  68 +
25 } 69 }
26 70
27 .profile-header, .profile-footer{ 71 .profile-header, .profile-footer{
@@ -33,3 +77,8 @@ @@ -33,3 +77,8 @@
33 background-color: #7E7E7E; 77 background-color: #7E7E7E;
34 } 78 }
35 } 79 }
  80 +
  81 +.ats-switch {
  82 + border: 0px;
  83 + border-color: transparent;
  84 +}