Compare View

switch
from
...
to
 
Commits (26)
Showing 63 changed files   Show diff stats
README.md
... ... @@ -53,18 +53,31 @@ See some important folders bellow:
53 53  
54 54 ## Change skin
55 55  
56   -- Create a any scss file into your theme folder structure
  56 +- Create an any scss file into: `app/layout/skins/`
  57 + > **Suggestion:** Create a `sass` file partial. Something like: **`_mycustom.scss`**.
  58 +
57 59 - Extend your skin css class from `%skin-base` scss placeholder selector. Something like this:
  60 + > **Suggestion:** Use the prefix **`skin-`** to the css class
58 61  
59 62 ```sass
60   -.skin-custom {
  63 +.skin-mycustom {
61 64 @extend %skin-base
62 65 }
63 66 ```
64 67 - Configure application to use the new theme, e.g.:
65   -`npm config set angular-theme:skin custom-skin`
  68 +`npm config set angular-theme:skin skin-mycustom`
  69 +
  70 +**N.B.**
  71 +
  72 +1. The full name of the scss class should be used as the parameter for the command `npm config set angular-theme:`, like in _skin-mycustom_. DO NOT use the file name of the skin as the parameter.
  73 +
  74 +2. The skin's file should be the named as the scss class without the word `skin`, preceded by an underline. Otherwise, it will raise an error during `npm install`.
  75 +
  76 +Example: _mycustom.
  77 +
66 78  
67   -- Start the application with `npm start` scripts ou make a build
  79 +- Start the application with `npm start` scripts or make a build
  80 + > **PS:** If the configured skin is invalid, an error message is showed in the terminal.
68 81  
69 82 ## Development environment
70 83  
... ...
bower.json
... ... @@ -38,7 +38,8 @@
38 38 "angular-bind-html-compile": "^1.2.1",
39 39 "angular-click-outside": "^2.7.1",
40 40 "ng-ckeditor": "^0.2.1",
41   - "angular-bootstrap-toggle-switch": "^0.5.6"
  41 + "angular-bootstrap-toggle-switch": "^0.5.6",
  42 + "angular-tag-cloud": "^0.3.0"
42 43 },
43 44 "devDependencies": {
44 45 "angular-mocks": "~1.5.0"
... ...
gulp/conf.js
... ... @@ -9,6 +9,7 @@
9 9 var argv = require('minimist')(process.argv.slice(2));
10 10 var gutil = require('gulp-util');
11 11 var path = require('path');
  12 +var fs = require('fs');
12 13  
13 14 /**
14 15 * The main paths of your project handle these with care
... ... @@ -23,12 +24,70 @@ exports.paths = {
23 24 themes: 'themes',
24 25 languages: 'languages'
25 26 };
  27 +
  28 +/**
  29 +* Check if theme folder exists on "themes" directory
  30 +*
  31 +* @param path The string relative path of the theme
  32 +*/
  33 +exports.themeExists = function (path) {
  34 + try {
  35 + fs.statSync(path);
  36 + } catch (e) {
  37 + throw new Error('The theme "'+exports.paths.theme+ ' on path "'+path+'" was not found');
  38 + }
  39 +};
  40 +
  41 +/**
  42 +* Check if skin file exists on "{theme}/app/layout/skins" directory
  43 +*
  44 +* @param skin The skin name passed by arg to gulp task
  45 +*/
  46 +exports.skinExists = function (skin) {
  47 +
  48 + var skinPath, prefixPath = '';
  49 + var skinFile = skin+'.scss';
  50 + if (/skin-/.test(skin)) {
  51 + skinFile = skin.replace('skin-','_')+'.scss';
  52 + }
  53 +
  54 + if (/-default$/.test(exports.paths.theme)) {
  55 + prefixPath = exports.paths.src;
  56 + }else {
  57 + prefixPath = path.join(exports.paths.themes, exports.paths.theme);
  58 + }
  59 +
  60 + skinPath = path.join(prefixPath, '/app/layout/scss/skins/', skinFile);
  61 +
  62 + try {
  63 + fs.statSync(skinPath);
  64 + } catch(e) {
  65 + throw new Error('The skin file "'+skinPath+'" was not found');
  66 + }
  67 +
  68 + var content = fs.readFileSync(skinPath, {encoding: 'utf8'});
  69 + if(content.search(skin) == -1) {
  70 + throw new Error('The skin css selector ".'+skin+'" was not found in "'+skinPath+'" file');
  71 + }else if (content.search('@extend %skin-base') == -1) {
  72 + throw new Error('The skin css selector ".'+skin+'" needs inherit from %skin-base sass placeholder');
  73 + }
  74 +
  75 +};
  76 +
26 77 exports.configTheme = function(theme) {
  78 +
27 79 exports.paths.theme = theme || "angular-default";
28   - exports.paths.allSources = [exports.paths.src, path.join(exports.paths.themes, exports.paths.theme)];
  80 + var themePath = path.join(exports.paths.themes, exports.paths.theme);
  81 +
  82 + exports.paths.allSources = [exports.paths.src, themePath];
  83 +
  84 +
  85 + exports.themeExists(themePath);
29 86 exports.paths.dist = path.join("dist", exports.paths.theme);
30 87  
31 88 if(argv.skin) {
  89 + exports.skinExists(argv.skin);
  90 +
32 91 exports.paths.skin = argv.skin;
33 92 }
34 93 }
... ...
gulp/styles.js
... ... @@ -10,6 +10,7 @@ var $ = require('gulp-load-plugins')();
10 10  
11 11 var wiredep = require('wiredep').stream;
12 12 var _ = require('lodash');
  13 +var importCss = require('gulp-import-css');
13 14  
14 15 gulp.task('styles-reload', ['styles'], function() {
15 16 return buildStyles()
... ... @@ -55,5 +56,6 @@ var buildStyles = function() {
55 56 .pipe($.sass(sassOptions)).on('error', conf.errorHandler('Sass'))
56 57 .pipe($.autoprefixer()).on('error', conf.errorHandler('Autoprefixer'))
57 58 .pipe($.sourcemaps.write())
  59 + .pipe(importCss())
58 60 .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve/app/')));
59 61 };
... ...
package.json
... ... @@ -47,6 +47,7 @@
47 47 "gulp-eslint": "~1.0.0",
48 48 "gulp-filter": "~3.0.1",
49 49 "gulp-flatten": "~0.2.0",
  50 + "gulp-import-css": "^0.1.2",
50 51 "gulp-inject": "~3.0.0",
51 52 "gulp-insert": "^0.5.0",
52 53 "gulp-load-plugins": "~0.10.0",
... ...
src/app/admin/layout-edit/designMode.service.spec.ts
1 1 import {DesignModeService} from './designMode.service';
  2 +import {INoosferoLocalStorage} from "./../../shared/models/interfaces";
2 3  
3 4 describe('DesignMode Service', () => {
4 5 let service: DesignModeService;
5   -
  6 + let $localStorage = <INoosferoLocalStorage>{ currentUser: null, settings: { designMode: false } };
6 7 beforeEach(() => {
7   - service = new DesignModeService();
  8 + service = new DesignModeService($localStorage);
8 9 });
9 10  
10 11 it('has the designModeOn equals false as default', () => {
... ... @@ -18,14 +19,16 @@ describe(&#39;DesignMode Service&#39;, () =&gt; {
18 19 });
19 20  
20 21 it('emits the onToggle event when changing the designModeOn property', () => {
  22 + service.setInDesignMode(false);
21 23 spyOn(service.onToggle, 'next').and.stub();
22 24 service.setInDesignMode(true);
23 25 expect(service.onToggle.next).toHaveBeenCalled();
24 26 });
25 27  
26 28 it('does not emit onToggle event when there is no change on designModeOn property', () => {
  29 + service.setInDesignMode(false);
27 30 spyOn(service.onToggle, 'next').and.stub();
28 31 service.setInDesignMode(false);
29 32 expect(service.onToggle.next).not.toHaveBeenCalled();
30 33 });
31   -});
32 34 \ No newline at end of file
  35 +});
... ...
src/app/admin/layout-edit/designMode.service.ts
1   -import {Injectable, Output, EventEmitter} from 'ng-forward';
  1 +import {Injectable, Output, EventEmitter, Inject} from 'ng-forward';
  2 +import {INoosferoLocalStorage} from "./../../shared/models/interfaces";
2 3  
3 4 @Injectable()
  5 +@Inject("$localStorage")
4 6 export class DesignModeService {
5   - @Output() onToggle: EventEmitter<boolean> = new EventEmitter<boolean>();
6 7  
7   - private designModeOn: boolean = false;
  8 + @Output() onToggle: EventEmitter<boolean> = new EventEmitter<boolean>();
8 9  
9 10 isInDesignMode(): boolean {
10   - return this.designModeOn;
  11 + return this.$localStorage.settings.designModeOn;
  12 + }
  13 +
  14 + destroy() {
  15 + delete this.$localStorage.settings;
  16 + this.$localStorage.settings = {};
11 17 }
12 18  
13 19 setInDesignMode(value: boolean) {
14   - if (this.designModeOn !== value) {
15   - this.designModeOn = value;
16   - this.onToggle.next(this.designModeOn);
  20 + if (this.$localStorage.settings.designModeOn !== value) {
  21 + this.$localStorage.settings.designModeOn = value;
  22 + this.onToggle.next(value);
17 23 }
18 24 }
19 25  
20   - constructor() {
  26 + constructor(private $localStorage: INoosferoLocalStorage) {
  27 + if (!this.$localStorage.settings) {
  28 + this.$localStorage.settings = {};
  29 + }
21 30 }
22 31 }
... ...
src/app/admin/layout-edit/designModeToggler.component.spec.ts
... ... @@ -2,6 +2,7 @@ import {ComponentTestHelper, createClass} from &#39;../../../spec/component-test-hel
2 2 import * as helpers from '../../../spec/helpers';
3 3 import {DesignModeTogglerComponent} from './designModeToggler.component';
4 4 import {DesignModeService} from './designMode.service';
  5 +import {INoosferoLocalStorage} from "./../../shared/models/interfaces";
5 6  
6 7 describe('DesignModeToggler Component', () => {
7 8 const htmlTemplate: string = '<noosfero-design-toggler></noosfero-design-toggler>';
... ... @@ -14,13 +15,15 @@ describe(&#39;DesignModeToggler Component&#39;, () =&gt; {
14 15 });
15 16  
16 17 let designModeService: DesignModeService;
  18 + let $localStorage = <INoosferoLocalStorage>{ currentUser: null, settings: { designMode: false } };
17 19 beforeEach((done) => {
18   - designModeService = new DesignModeService();
  20 + designModeService = new DesignModeService($localStorage);
19 21 let cls = createClass({
20 22 template: htmlTemplate,
21 23 directives: [DesignModeTogglerComponent],
22 24 providers: [
23   - helpers.createProviderToValue('DesignModeService', designModeService)
  25 + helpers.createProviderToValue('DesignModeService', designModeService),
  26 + helpers.createProviderToValue('AuthService', helpers.mocks.authService),
24 27 ]
25 28 });
26 29 helper = new ComponentTestHelper<DesignModeTogglerComponent>(cls, done);
... ... @@ -36,6 +39,7 @@ describe(&#39;DesignModeToggler Component&#39;, () =&gt; {
36 39 });
37 40  
38 41 it('emits event with value "true" when changing inDesignMode to On', (done) => {
  42 + designModeService.setInDesignMode(false);
39 43 designModeService.onToggle.subscribe((designModeOn: boolean) => {
40 44 expect(designModeOn).toBeTruthy();
41 45 done();
... ...
src/app/admin/layout-edit/designModeToggler.component.ts
1 1 import {Component, Inject} from 'ng-forward';
2 2 import {DesignModeService} from './designMode.service';
  3 +import {AuthService, AuthEvents} from '../../login';
  4 +
3 5 @Component({
4 6 selector: 'noosfero-design-toggler',
5 7 templateUrl: 'app/admin/layout-edit/designModeToggler.html'
6 8 })
7   -@Inject(DesignModeService)
  9 +@Inject(DesignModeService, AuthService)
8 10 export class DesignModeTogglerComponent {
9 11  
10 12 icon: string = "&nbsp;<i class='glyphicon glyphicon-wrench'></i>&nbsp;";
11 13  
12   - constructor(private designModeService: DesignModeService) {
  14 + constructor(private designModeService: DesignModeService, private authService: AuthService) {
  15 + this.authService.subscribe(AuthEvents[AuthEvents.logoutSuccess], () => {
  16 + this.designModeService.destroy();
  17 + });
13 18 }
14 19  
15 20 private _inDesignMode: boolean = false;
... ... @@ -21,4 +26,4 @@ export class DesignModeTogglerComponent {
21 26 set inDesignMode(value: boolean) {
22 27 this.designModeService.setInDesignMode(value);
23 28 };
24   -}
25 29 \ No newline at end of file
  30 +}
... ...
src/app/environment/environment.html
1 1 <div class="environment-container">
2 2 <div class="row">
3   - <noosfero-boxes ng-if="vm.boxes" [boxes]="vm.boxes" [owner]="vm.environment"></noosfero-boxes>
  3 + <noosfero-boxes ng-if="vm.boxes"
  4 + [layout]="vm.environment.layout_template"
  5 + [boxes]="vm.boxes"
  6 + [owner]="vm.environment">
  7 + </noosfero-boxes>
4 8 </div>
5 9 </div>
... ...
src/app/index.scss
... ... @@ -82,3 +82,4 @@ h1, h2, h3, h4, h5 {
82 82 @import "layout/scss/layout";
83 83 @import "layout/scss/sidebar";
84 84 @import "layout/scss/tables";
  85 +@import "layout/scss/forms";
... ...
src/app/layout/blocks/block.component.spec.ts
... ... @@ -2,6 +2,7 @@ import {Component} from &#39;ng-forward&#39;;
2 2 import {BlockComponent} from './block.component';
3 3 import * as helpers from "../../../spec/helpers";
4 4 import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
  5 +import {DesignModeService} from '../../admin/layout-edit/designMode.service';
5 6  
6 7 const htmlTemplate: string = '<noosfero-block [block]="ctrl.block" [owner]="ctrl.profile"></noosfero-block>';
7 8  
... ... @@ -32,12 +33,12 @@ describe(&quot;Boxes Component&quot;, () =&gt; {
32 33 helpers.createProviderToValue('TranslatorService', translatorService),
33 34 helpers.createProviderToValue('$uibModal', helpers.mocks.$modal),
34 35 helpers.createProviderToValue('BlockService', blockService),
35   - helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService)
  36 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService),
  37 + helpers.createProviderToValue('DesignModeService', helpers.mocks.designModeService)
36 38 ]
37 39 });
38 40 helper = new ComponentTestHelper<BlockComponent>(cls, done);
39 41 });
40   -
41 42 let translatorService = jasmine.createSpyObj("translatorService", ["currentLanguage"]);
42 43 let blockService = jasmine.createSpyObj("blockService", ["update"]);
43 44 let state = jasmine.createSpyObj("state", ["current"]);
... ...
src/app/layout/blocks/block.component.ts
... ... @@ -11,7 +11,8 @@ import { DesignModeService } from &quot;../../admin/layout-edit/designMode.service&quot;;
11 11 templateUrl: 'app/layout/blocks/block.html',
12 12 directives: [BlockEditionComponent]
13 13 })
14   -@Inject("$uibModal", "$scope", "$state", "$rootScope", BlockService, NotificationService, AuthService, SessionService, TranslatorService, DesignModeService)
  14 +@Inject("$uibModal", "$scope", "$state", "$rootScope", BlockService, NotificationService,
  15 + AuthService, SessionService, TranslatorService, DesignModeService)
15 16 export class BlockComponent {
16 17  
17 18 @Input() block: noosfero.Block;
... ...
src/app/layout/blocks/block.html
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">
  1 +<div ng-show="ctrl.canDisplay() || ctrl.inEditMode() || ctrl.designMode" ng-class="{'invisible-block': !ctrl.canDisplay()}" class="noosfero-block" ng-mouseover="displayActions = true" ng-mouseleave="displayActions = false">
2 2 <div ng-show="displayActions" class="actions block-actions" permission="ctrl.block.permissions" permission-action="allow_edit">
3 3 <button type="submit" class="btn btn-xs btn-default" ng-click="ctrl.openEdit()"><i class="fa fa-edit fa-fw"></i></button>
4 4 </div>
... ...
src/app/layout/blocks/login-block/login-block.html
... ... @@ -15,13 +15,21 @@
15 15 <div class="logged-user-info" ng-show="!ctrl.currentUser">
16 16 <form>
17 17 <div class="form-group">
18   - <label for="exampleInputEmail1">{{"auth.form.login" | translate}}</label>
19   - <input type="text" class="form-control" id="exampleInputEmail1" placeholder="Login / Email" ng-model="ctrl.credentials.username">
  18 + <label for="email">{{"auth.form.login" | translate}}</label>
  19 + <input type="text" class="form-control" id="email" placeholder="{{'auth.form.login' | translate}}" ng-model="ctrl.credentials.username">
20 20 </div>
21 21 <div class="form-group">
22   - <label for="exampleInputPassword1">{{"auth.form.password" | translate}}</label>
23   - <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password" ng-model="ctrl.credentials.password">
  22 + <label for="passwd">{{"auth.form.password" | translate}}</label>
  23 + <input type="password" class="form-control" id="passwd" placeholder="{{'auth.form.password' | translate}}" ng-model="ctrl.credentials.password">
  24 + </div>
  25 + <div class="form-inline">
  26 + <div class="checkbox-nice">
  27 + <input type="checkbox" id="keep-logged">
  28 + <label for="keep-logged">
  29 + {{"auth.form.keepLoggedIn" | translate}}
  30 + </label>
  31 + </div>
24 32 </div>
25 33 <button type="submit" class="btn btn-default" ng-click="ctrl.login()">{{"auth.form.login_button" | translate}}</button>
26 34 </form>
27   -</div>
28 35 \ No newline at end of file
  36 +</div>
... ...
src/app/layout/blocks/tags/index.ts 0 → 100644
... ... @@ -0,0 +1 @@
  1 +export * from "./tags-block.component";
... ...
src/app/layout/blocks/tags/tags-block.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,55 @@
  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 {TagsBlockComponent} from './tags-block.component';
  5 +
  6 +const htmlTemplate: string = '<noosfero-tags-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-tags-block>';
  7 +
  8 +const tcb = new TestComponentBuilder();
  9 +
  10 +describe("Components", () => {
  11 + describe("Tags Block Component", () => {
  12 +
  13 + let settingsObj = {};
  14 + let mockedEnvironmentService = {
  15 + getTags: (): any => {
  16 + return Promise.resolve({ foo: 10, bar: 20 });
  17 + }
  18 + };
  19 + let profile = { name: 'profile-name' };
  20 + beforeEach(angular.mock.module("templates"));
  21 +
  22 + let state = jasmine.createSpyObj("state", ["go"]);
  23 +
  24 +
  25 + function getProviders() {
  26 + return [
  27 + new Provider('$state', { useValue: state }),
  28 + new Provider('EnvironmentService', {
  29 + useValue: mockedEnvironmentService
  30 + }),
  31 + ].concat(provideFilters("truncateFilter", "stripTagsFilter"));
  32 + }
  33 + let componentClass: any = null;
  34 +
  35 + function getComponent() {
  36 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [TagsBlockComponent], providers: getProviders() })
  37 + class BlockContainerComponent {
  38 + block = { type: 'Block', settings: settingsObj };
  39 + owner = profile;
  40 + constructor() {
  41 + }
  42 + }
  43 + return BlockContainerComponent;
  44 + }
  45 +
  46 +
  47 + it("get tags from the environment service", done => {
  48 + tcb.createAsync(getComponent()).then(fixture => {
  49 + let tagsBlock: TagsBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  50 + expect(tagsBlock.tags).toEqual([{ text: "foo", weight: '10', link: '/tag/foo' }, { text: "bar", weight: '20', link: '/tag/bar' }]);
  51 + done();
  52 + });
  53 + });
  54 + });
  55 +});
... ...
src/app/layout/blocks/tags/tags-block.component.ts 0 → 100644
... ... @@ -0,0 +1,45 @@
  1 +import {Component, Inject, Input} from "ng-forward";
  2 +import {EnvironmentService} from "../../../../lib/ng-noosfero-api/http/environment.service";
  3 +
  4 +@Component({
  5 + selector: "noosfero-tags-block",
  6 + templateUrl: 'app/layout/blocks/tags/tags-block.html'
  7 +})
  8 +@Inject(EnvironmentService, "$state")
  9 +export class TagsBlockComponent {
  10 +
  11 + @Input() block: any;
  12 + @Input() owner: any;
  13 +
  14 + profile: any;
  15 + tags: any;
  16 + tagsLoaded: boolean = false;
  17 +
  18 + constructor(private environmentService: EnvironmentService, private $state: any) {
  19 + this.loadTags();
  20 + }
  21 +
  22 + loadTags() {
  23 + this.tags = [];
  24 + let tag = '';
  25 + let tags: any = [];
  26 + let that = this;
  27 +
  28 + this.environmentService.getTags()
  29 + .then((result: any) => {
  30 + for (tag in result) {
  31 + if (result.hasOwnProperty(tag)) {
  32 + let size: number = result[tag];
  33 + tags.push({ text: tag.toString(), weight: size.toString(), link: '/tag/' + tag });
  34 + }
  35 + }
  36 +
  37 + that.tagsLoaded = true;
  38 + that.tags = tags.slice();
  39 + });
  40 + }
  41 +
  42 + ngOnInit() {
  43 + this.profile = this.owner;
  44 + }
  45 +}
... ...
src/app/layout/blocks/tags/tags-block.html 0 → 100644
... ... @@ -0,0 +1 @@
  1 +<ng-tag-cloud cloud-width="200" cloud-height="150" cloud-data="ctrl.tags"></ng-tag-cloud>
... ...
src/app/layout/blocks/tags/tags-block.scss 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +@import url('../../bower_components/angular-tag-cloud/src/css/ng-tag-cloud.css');
  2 +
  3 +div.ng-tag-cloud span {
  4 + &.w10, &.w8, &.w9 {
  5 + color: #1E96D0;
  6 + }
  7 +}
... ...
src/app/layout/boxes/box.html
1   -<div ng-class="{'col-md-2-5': box.position!=1, 'col-md-7': box.position==1}">
2   - <noosfero-block ng-repeat="block in box.blocks | orderBy: 'position'" [block]="block" [owner]="ctrl.owner"></noosfero-block>
  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}}" >
  4 + <div class="panel-heading" ng-show="block.title">
  5 + <h3 class="panel-title">{{block.title}}</h3>
  6 + </div>
  7 + <div class="panel-body {{block.type | lowercase}}" >
  8 + <noosfero-block [block]="block" [owner]="ctrl.owner"></noosfero-block>
  9 + </div>
  10 + </div>
3 11 </div>
... ...
src/app/layout/boxes/boxes.component.spec.ts
... ... @@ -45,12 +45,7 @@ describe(&quot;Boxes Component&quot;, () =&gt; {
45 45 state.current = { name: "" };
46 46  
47 47 it("renders boxes into a container", () => {
48   - expect(helper.find('div.col-md-7').length).toEqual(1);
49   - expect(helper.find('div.col-md-2-5').length).toEqual(1);
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);
  48 + expect(helper.find('div.col-md-6').length).toEqual(1);
  49 + expect(helper.find('div.col-md-3').length).toEqual(1);
55 50 });
56 51 });
... ...
src/app/layout/boxes/boxes.component.ts
1   -import {Input, Component} from 'ng-forward';
  1 +import {Input, Inject, Component} from 'ng-forward';
  2 +import {DisplayBoxes} from "./display-boxes.filter";
  3 +import {SetBoxLayout} from "./set-box-layout.filter";
2 4  
3 5 @Component({
4 6 selector: "noosfero-boxes",
5   - templateUrl: "app/layout/boxes/boxes.html"
  7 + templateUrl: "app/layout/boxes/boxes.html",
  8 + directives: [DisplayBoxes, SetBoxLayout]
6 9 })
7 10 export class BoxesComponent {
8 11  
9 12 @Input() boxes: noosfero.Box[];
10 13 @Input() owner: noosfero.Profile | noosfero.Environment;
  14 + @Input() layout: string;
11 15  
12   - boxesOrder(box: noosfero.Box) {
13   - if (box.position === 2) return 0;
14   - return box.position;
15   - }
16 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/display-boxes.filter.spec.ts 0 → 100644
... ... @@ -0,0 +1,51 @@
  1 +import {DisplayBoxes} from './display-boxes.filter';
  2 +
  3 +describe("Boxes Filters", () => {
  4 + describe("Display Boxes Filter", () => {
  5 +
  6 + let boxes: noosfero.Box[] = [
  7 + {id: 1, position: 1 },
  8 + {id: 2, position: 2 },
  9 + {id: 3, position: 3 },
  10 + {id: 4, position: 4 }
  11 + ];
  12 +
  13 + let expected_on_default: noosfero.Box[] = [
  14 + {id: 1, position: 1 },
  15 + {id: 2, position: 2 },
  16 + {id: 3, position: 3 },
  17 + ];
  18 +
  19 + let expected_on_rightbar: noosfero.Box[] = [
  20 + {id: 1, position: 1 },
  21 + {id: 3, position: 3 },
  22 + ];
  23 +
  24 + it("filter boxes when layout is set to default", done => {
  25 + let filter = new DisplayBoxes();
  26 +
  27 + let filtered_boxes: noosfero.Box[] = filter.transform(boxes, "default");
  28 + expect(filtered_boxes.length).toEqual(3);
  29 + expect(filtered_boxes).toEqual(expected_on_default);
  30 + done();
  31 + });
  32 +
  33 + it("filter boxes when layout is set to rightbar", done => {
  34 + let filter = new DisplayBoxes();
  35 +
  36 + let filtered_boxes: noosfero.Box[] = filter.transform(boxes, "rightbar");
  37 + expect(filtered_boxes.length).toEqual(2);
  38 + expect(filtered_boxes).toEqual(expected_on_rightbar);
  39 + done();
  40 + });
  41 +
  42 + it("filter boxes with default layout when invalid layout is given", done => {
  43 + let filter = new DisplayBoxes();
  44 +
  45 + let filtered_boxes: noosfero.Box[] = filter.transform(boxes, "");
  46 + expect(filtered_boxes.length).toEqual(3);
  47 + expect(filtered_boxes).toEqual(expected_on_default);
  48 + done();
  49 + });
  50 + });
  51 +});
... ...
src/app/layout/boxes/display-boxes.filter.ts 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +import {Pipe, Inject} from "ng-forward";
  2 +import {TranslatorService} from "../../shared/services/translator.service";
  3 +
  4 +@Pipe("displayBoxes")
  5 +@Inject(TranslatorService)
  6 +export class DisplayBoxes {
  7 +
  8 + transform(boxes: noosfero.Box[], layout: string) {
  9 + let function_str: string = "visible_on" + layout;
  10 + let valid_boxes: number[] = [];
  11 + let selected: noosfero.Box[] = [];
  12 + boxes = boxes || [];
  13 +
  14 + if (layout === "rightbar") {
  15 + valid_boxes = this.visible_on_right_bar();
  16 + }else {
  17 + valid_boxes = this.visible_on_default();
  18 + }
  19 +
  20 + for (let box of boxes) {
  21 + if (valid_boxes.indexOf(box.position) !== -1) {
  22 + selected.push(box);
  23 + }
  24 + }
  25 + return selected;
  26 + }
  27 +
  28 + private visible_on_default() {
  29 + return [1, 2, 3];
  30 + }
  31 +
  32 + private visible_on_right_bar() {
  33 + return [1, 3];
  34 + }
  35 +
  36 +}
  37 +
... ...
src/app/layout/boxes/set-box-layout.filter.spec.ts 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +import {SetBoxLayout} from './set-box-layout.filter';
  2 +
  3 +describe("Boxes Filters", () => {
  4 + describe("Set Box Layout Filter", () => {
  5 + let box_layout_filter = new SetBoxLayout();
  6 + describe("When layout is set to default", () => {
  7 + it("return style when box position is 1 ", done => {
  8 + expect(box_layout_filter.transform(1, "default")).toEqual("col-md-6 col-md-push-3");
  9 + done();
  10 + });
  11 + it("return style when box position is 2", done => {
  12 + expect(box_layout_filter.transform(2, "default")).toEqual("col-md-3 col-md-pull-6");
  13 + done();
  14 + });
  15 + it("return style when any other position is given", done => {
  16 + expect(box_layout_filter.transform(null, "default")).toEqual("col-md-3");
  17 + expect(box_layout_filter.transform(3, "default")).toEqual("col-md-3");
  18 + expect(box_layout_filter.transform(99, "default")).toEqual("col-md-3");
  19 + done();
  20 + });
  21 + });
  22 +
  23 + describe("When layout is set to right_bar", () => {
  24 + it("return style when box position is 1 ", done => {
  25 + expect(box_layout_filter.transform(1, "rightbar")).toEqual("col-sm-6 col-sm-push-2");
  26 + done();
  27 + });
  28 + it("return style when box other position is given", done => {
  29 + expect(box_layout_filter.transform(2, "rightbar")).toEqual("col-sm-3 col-sm-push-2");
  30 + done();
  31 + });
  32 + });
  33 + });
  34 +});
... ...
src/app/layout/boxes/set-box-layout.filter.ts 0 → 100644
... ... @@ -0,0 +1,34 @@
  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 + transform(pos: number, layout: string) {
  9 + if (layout === "rightbar") {
  10 + return this.right_bar(pos);
  11 + }else {
  12 + return this.default(pos);
  13 + }
  14 + }
  15 +
  16 + private default(position: number) {
  17 + if (position === 1) {
  18 + return "col-md-6 col-md-push-3";
  19 + }else if (position === 2) {
  20 + return "col-md-3 col-md-pull-6";
  21 + }else {
  22 + return "col-md-3";
  23 + }
  24 + }
  25 +
  26 + private right_bar(position: number) {
  27 + if (position === 1) {
  28 + return "col-sm-6 col-sm-push-2";
  29 + }else {
  30 + return "col-sm-3 col-sm-push-2";
  31 + }
  32 + }
  33 +
  34 +}
... ...
src/app/layout/language-selector/language-selector.html
1   -<li class="dropdown profile-menu" uib-dropdown>
  1 +<li class="dropdown profile-menu btn-nav" uib-dropdown>
2 2 <a href="#" class="dropdown-toggle" aria-expanded="false" uib-dropdown-toggle>
3 3 <span>{{"language.selector" | translate}}</span> <b class="caret"></b>
4 4 </a>
... ...
src/app/layout/navbar/navbar.html
... ... @@ -16,7 +16,7 @@
16 16 </ul>
17 17  
18 18 <ul class="nav navbar-nav navbar-right">
19   - <li ng-show="!ctrl.currentUser">
  19 + <li class="btn-nav" ng-show="!ctrl.currentUser">
20 20 <a ng-href="#" ng-click="ctrl.openLogin()">{{"navbar.login" | translate}}</a>
21 21 </li>
22 22  
... ... @@ -51,4 +51,4 @@
51 51 </div>
52 52 </nav>
53 53 <div ui-view="toolbar">
54   -</div>
55 54 \ No newline at end of file
  55 +</div>
... ...
src/app/layout/navbar/navbar.scss
... ... @@ -50,4 +50,10 @@
50 50 }
51 51 }
52 52 }
  53 +
  54 + .btn-nav {
  55 + border-radius: 0px;
  56 + height: 50px;
  57 + border: none;
  58 + }
53 59 }
... ...
src/app/layout/navbar/navbar.spec.ts
... ... @@ -9,6 +9,8 @@ import {SessionService, AuthService, AuthController, AuthEvents} from &quot;./../../l
9 9  
10 10 import events from 'ng-forward/cjs/events/events';
11 11  
  12 +import {DesignModeService} from '../../admin/layout-edit/designMode.service';
  13 +
12 14 describe("Components", () => {
13 15  
14 16 describe("Navbar Component", () => {
... ... @@ -22,6 +24,7 @@ describe(&quot;Components&quot;, () =&gt; {
22 24 let authService: any;
23 25 let stateService: any;
24 26 let sessionService: SessionService;
  27 + let designModeService: DesignModeService;
25 28  
26 29 let provideFunc = provide;
27 30  
... ... @@ -37,6 +40,7 @@ describe(&quot;Components&quot;, () =&gt; {
37 40 authService = helpers.mocks.authService;
38 41 stateService = jasmine.createSpyObj("$state", ["go"]);
39 42 sessionService = <any>helpers.mocks.sessionWithCurrentUser(user);
  43 + designModeService = helpers.mocks.designModeService;
40 44 });
41 45  
42 46  
... ... @@ -76,6 +80,9 @@ describe(&quot;Components&quot;, () =&gt; {
76 80 }),
77 81 provide('TranslatorService', {
78 82 useValue: helpers.mocks.translatorService
  83 + }),
  84 + provide('DesignModeService', {
  85 + useValue: helpers.mocks.designModeService
79 86 })
80 87 ].concat(helpers.provideFilters("translateFilter")),
81 88 directives: [Navbar],
... ...
src/app/layout/scss/_forms.scss 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +label {
  2 + cursor: pointer;
  3 +}
  4 +
  5 +.checkbox-nice {
  6 + position: relative;
  7 + padding-left: 15px;
  8 +
  9 + input[type=checkbox] {
  10 + visibility: hidden;
  11 + }
  12 + label {
  13 + padding-top: 3px;
  14 + }
  15 + &.checkbox-inline > label {
  16 + margin-left: 16px;
  17 + }
  18 + label:before {
  19 + @include checkbox-mark(22px, 22px, 1px, 1px, #ffffff, 2px solid $main-bg-color);
  20 + @include border-radius(3px);
  21 + }
  22 + label:after {
  23 + @include checkbox-mark(12px, 7px, 7px, 6px, transparent, 3px solid #000);
  24 + @include opacity(0);
  25 +
  26 + border-top: none;
  27 + border-right: none;
  28 + transform: rotate(-45deg);
  29 + }
  30 + label:hover::after {
  31 + @include opacity(0.3);
  32 + }
  33 + input[type=checkbox]:checked + label:after {
  34 + @include opacity(1);
  35 + }
  36 +}
... ...
src/app/layout/scss/_mixins.scss
... ... @@ -3,3 +3,48 @@
3 3 border-radius: $radius;
4 4 background-clip: padding-box; /* stops bg color from leaking outside the border: */
5 5 }
  6 +
  7 +@mixin checkbox-mark($width, $height, $top, $left, $bg, $border, $content: "", $cursor: pointer, $position: absolute) {
  8 + width: $width;
  9 + height: $height;
  10 + cursor: $cursor;
  11 + position: $position;
  12 + left: $left;
  13 + top: $top;
  14 + background: $bg;
  15 + content: $content;
  16 + border: $border;
  17 +}
  18 +
  19 +@mixin opacity($opacity) {
  20 + /* Netscape */
  21 + -moz-opacity: $opacity;
  22 +
  23 + /* Safari 1.x */
  24 + -khtml-opacity: $opacity;
  25 +
  26 + /* Good browsers */
  27 + opacity: $opacity;
  28 +}
  29 +
  30 +@mixin transition($args...) {
  31 +
  32 + $duration: 300ms;
  33 + $keys: keywords($args);
  34 + @if map-has-key($keys, duration) {
  35 + $duration: map-get($keys, duration);
  36 + }
  37 +
  38 + -webkit-transition: $args;
  39 + -o-transition: $args;
  40 + transition: $args;
  41 + -webkit-transition-duration: $duration;
  42 + transition-duration: $duration;
  43 +}
  44 +
  45 +@mixin outline($style: none, $width: 0px) {
  46 + &:focus {
  47 + outline-style: $style;
  48 + outline-width: $width;
  49 + }
  50 +}
... ...
src/app/layout/scss/skins/_whbl.scss
... ... @@ -7,6 +7,15 @@ $whbl-font-color: #16191c;
7 7 #header-navbar {
8 8 background-color: $whbl-primary-color;
9 9 }
  10 +
  11 + .navbar {
  12 + .btn-nav {
  13 + &:hover {
  14 + background-color: #1b86ba;
  15 + }
  16 + }
  17 + }
  18 +
10 19 .navbar > .container .navbar-brand {
11 20 background-color: transparent;
12 21 width: 221px;
... ... @@ -306,6 +315,13 @@ $whbl-font-color: #16191c;
306 315 color: white;
307 316 }
308 317  
  318 + /* Form overrides */
  319 + .checkbox-nice {
  320 + label:after {
  321 + border-color: $whbl-primary-color;
  322 + }
  323 + }
  324 +
309 325 }
310 326 .rtl.skin-whbl #content-wrapper {
311 327 border-left: 0;
... ...
src/app/layout/services/body-state-classes.service.spec.ts
... ... @@ -5,11 +5,13 @@ import {AuthEvents} from &quot;./../../login/auth-events&quot;;
5 5  
6 6 import {EventEmitter} from 'ng-forward';
7 7 import {DesignModeService} from './../../admin/layout-edit/designMode.service';
  8 +import {INoosferoLocalStorage} from "./../../shared/models/interfaces";
8 9  
9 10 describe("BodyStateClasses Service", () => {
10 11  
11 12 let currentStateName = "main";
12 13 let bodyStateClasseService: BodyStateClassesService;
  14 + let $localStorage = <INoosferoLocalStorage>{ currentUser: null, settings: { designMode: false } };
13 15 let $rootScope: ng.IRootScopeService = <any>{},
14 16 $document: ng.IDocumentService = <any>{},
15 17 $state: ng.ui.IStateService = <any>{
... ... @@ -20,7 +22,7 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
20 22 authService: any = helpers.mocks.authService,
21 23 bodyEl: { className: string },
22 24 bodyElJq: any,
23   - designModeService = new DesignModeService();
  25 + designModeService = new DesignModeService($localStorage);
24 26  
25 27  
26 28  
... ... @@ -174,7 +176,7 @@ describe(&quot;BodyStateClasses Service&quot;, () =&gt; {
174 176  
175 177 it("should add the class noosfero-design-on when designMode is changed to true", () => {
176 178 let fnOnToggle: Function = null;
177   - designModeService.onToggle = <any> {
  179 + designModeService.onToggle = <any>{
178 180 subscribe: (fn: Function) => {
179 181 fnOnToggle = fn;
180 182 },
... ...
src/app/login/login.html
... ... @@ -4,13 +4,21 @@
4 4 <div class="modal-body">
5 5 <form>
6 6 <div class="form-group">
7   - <label for="exampleInputEmail1">{{"auth.form.login" | translate}}</label>
8   - <input type="text" class="form-control" id="exampleInputEmail1" placeholder="Login / Email" ng-model="vm.credentials.username">
  7 + <label for="email">{{"auth.form.login" | translate}}</label>
  8 + <input type="text" class="form-control" id="email" placeholder="{{'auth.form.login' | translate}}" ng-model="vm.credentials.username">
9 9 </div>
10 10 <div class="form-group">
11   - <label for="exampleInputPassword1">{{"auth.form.password" | translate}}</label>
12   - <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password" ng-model="vm.credentials.password">
  11 + <label for="passwd">{{"auth.form.password" | translate}}</label>
  12 + <input type="password" class="form-control" id="passwd" placeholder="{{'auth.form.password' | translate}}" ng-model="vm.credentials.password">
13 13 </div>
14   - <button type="submit" class="btn btn-default" ng-click="vm.login()">{{"auth.form.login_button" | translate}}</button>
  14 + <div class="form-inline">
  15 + <div class="checkbox-nice">
  16 + <input type="checkbox" id="keep-logged">
  17 + <label for="keep-logged">
  18 + {{"auth.form.keepLoggedIn" | translate}}
  19 + </label>
  20 + </div>
  21 + </div>
  22 + <button type="submit" class="btn btn-default btn-block" ng-click="vm.login()">{{"auth.form.login_button" | translate}}</button>
15 23 </form>
16 24 </div>
... ...
src/app/login/login.scss 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +.modal-content {
  2 + padding: 40px;
  3 + .modal-header {
  4 + border-bottom: 0;
  5 + text-align: center;
  6 + }
  7 + form {
  8 + margin-bottom: 30px;
  9 + button {
  10 + margin-top: 20px;
  11 + text-transform: uppercase;
  12 + }
  13 + }
  14 +}
  15 +
  16 +.strike {
  17 + display: block;
  18 + text-align: center;
  19 + overflow: hidden;
  20 + white-space: nowrap;
  21 + padding: 25px 0;
  22 + > span {
  23 + position: relative;
  24 + display: inline-block;
  25 + text-transform: uppercase;
  26 + }
  27 + > span:before, > span:after {
  28 + content: "";
  29 + position: absolute;
  30 + top: 50%;
  31 + width: 9999px;
  32 + height: 1px;
  33 + background: black;
  34 + }
  35 + > span:before {
  36 + right: 100%;
  37 + margin-right: 15px;
  38 + }
  39 + > span:after {
  40 + left: 100%;
  41 + margin-left: 15px;
  42 + }
  43 +}
... ...
src/app/login/session.service.spec.ts
... ... @@ -13,7 +13,7 @@ describe(&quot;Services&quot;, () =&gt; {
13 13 let $log: any;
14 14  
15 15 beforeEach(() => {
16   - $localStorage = <INoosferoLocalStorage>{ currentUser: null };
  16 + $localStorage = <INoosferoLocalStorage>{ currentUser: null, settings: null };
17 17 $log = jasmine.createSpyObj('$log', ['debug']);
18 18 });
19 19  
... ...
src/app/login/session.service.ts
... ... @@ -16,6 +16,7 @@ export class SessionService {
16 16  
17 17 destroy() {
18 18 delete this.$localStorage.currentUser;
  19 + delete this.$localStorage.settings;
19 20 };
20 21  
21 22 currentUser(): noosfero.User {
... ...
src/app/main/main.component.ts
... ... @@ -18,6 +18,7 @@ import {ProfileImageBlockComponent} from &quot;../layout/blocks/profile-image/profile
18 18 import {RawHTMLBlockComponent} from "../layout/blocks/raw-html/raw-html-block.component";
19 19 import {StatisticsBlockComponent} from "../layout/blocks/statistics/statistics-block.component";
20 20 import {PersonTagsPluginInterestsBlockComponent} from "../layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.component";
  21 +import {TagsBlockComponent} from "../layout/blocks/tags/tags-block.component";
21 22 import {CustomContentComponent} from "../profile/custom-content/custom-content.component";
22 23  
23 24 import {MembersBlockComponent} from "../layout/blocks/members/members-block.component";
... ... @@ -105,7 +106,7 @@ export class EnvironmentContent {
105 106 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
106 107 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
107 108 LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent,
108   - PersonTagsPluginInterestsBlockComponent, BlockComponent
  109 + PersonTagsPluginInterestsBlockComponent, TagsBlockComponent, BlockComponent
109 110 ].concat(plugins.mainComponents).concat(plugins.hotspots),
110 111 providers: [AuthService, SessionService, NotificationService, BodyStateClassesService,
111 112 "ngAnimate", "ngCookies", "ngStorage", "ngTouch",
... ... @@ -114,7 +115,7 @@ export class EnvironmentContent {
114 115 "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid",
115 116 "angular-timeline", "duScroll", "oitozero.ngSweetAlert",
116 117 "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad",
117   - "angular-click-outside", "toggle-switch", "noosfero.init"]
  118 + "angular-click-outside", "toggle-switch", "ngTagCloud", "noosfero.init"]
118 119 })
119 120 @StateConfig([
120 121 {
... ...
src/app/profile/custom-content/custom-content.component.spec.ts
1 1 import {CustomContentComponent} from './custom-content.component';
2 2 import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
3 3 import * as helpers from "../../../spec/helpers";
  4 +import {DesignModeService} from '../../admin/layout-edit/designMode.service';
4 5  
5 6 const htmlTemplate: string = '<custom-content [attribute]="\'custom_footer\'" [profile]="ctrl.profile"></custom-content>';
6 7  
... ... @@ -14,6 +15,7 @@ describe(&quot;Components&quot;, () =&gt; {
14 15 beforeEach((done) => {
15 16 let profileService = jasmine.createSpyObj("profileService", ["update"]);
16 17 let notificationService = jasmine.createSpyObj("notificationService", ["success"]);
  18 + let designModeService = { isInDesignMode: () => { return true; }};
17 19 let properties = { profile: { custom_footer: "footer" } };
18 20 let cls = createClass({
19 21 template: htmlTemplate,
... ... @@ -22,7 +24,8 @@ describe(&quot;Components&quot;, () =&gt; {
22 24 providers: [
23 25 helpers.createProviderToValue("$uibModal", helpers.mocks.$modal),
24 26 helpers.createProviderToValue("ProfileService", profileService),
25   - helpers.createProviderToValue("NotificationService", notificationService)
  27 + helpers.createProviderToValue("NotificationService", notificationService),
  28 + helpers.createProviderToValue("DesignModeService", designModeService)
26 29 ]
27 30 });
28 31 helper = new ComponentTestHelper<CustomContentComponent>(cls, done);
... ...
src/app/profile/custom-content/custom-content.component.ts
... ... @@ -12,7 +12,8 @@ import {DesignModeService} from &#39;../../admin/layout-edit/designMode.service&#39;;
12 12 @Inject("$uibModal", "$scope", ProfileService, NotificationService, DesignModeService)
13 13 export class CustomContentComponent {
14 14  
15   - static $inject = ["DesignModeService"]; // @Inject doesn't works with uibModal.open
  15 + // @Inject doesn't works with uibModal.open
  16 + static $inject = ["DesignModeService"];
16 17  
17 18 @Input() attribute: string;
18 19 @Input() profile: noosfero.Profile;
... ... @@ -20,7 +21,6 @@ export class CustomContentComponent {
20 21  
21 22 content: string;
22 23 originalContent: string;
23   - editionMode = false;
24 24 private modalInstance: any = null;
25 25  
26 26 constructor(private $uibModal: any,
... ... @@ -35,9 +35,10 @@ export class CustomContentComponent {
35 35 }, () => {
36 36 if (this.profile) this.content = (<any>this.profile)[this.attribute];
37 37 });
38   - this.designModeService.onToggle.subscribe((designModeOn: boolean) => {
39   - this.editionMode = designModeOn;
40   - });
  38 + }
  39 +
  40 + inEditMode() {
  41 + return this.designModeService.isInDesignMode();
41 42 }
42 43  
43 44 openEdit() {
... ...
src/app/profile/custom-content/custom-content.html
1 1 <div class="custom-content">
2   - <div class="actions" permission="ctrl.profile.permissions" permission-action="allow_edit" ng-show="ctrl.editionMode">
  2 + <div class="actions" permission="ctrl.profile.permissions" permission-action="allow_edit" ng-show="ctrl.inEditMode()">
3 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 4 </div>
5 5 <div class="content" ng-bind-html="ctrl.content"></div>
... ...
src/app/profile/profile.html
1 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 13 <custom-content class="profile-footer" [label]="'profile.custom_footer.label'" [attribute]="'custom_footer'" [profile]="vm.profile"></custom-content>
7 14 </div>
... ...
src/app/search/search-form/search-form.component.spec.ts
... ... @@ -21,8 +21,8 @@ describe(&quot;Components&quot;, () =&gt; {
21 21 helper = new ComponentTestHelper<SearchFormComponent>(cls, done);
22 22 });
23 23  
24   - it("render a input for search query", () => {
25   - expect(helper.find(".search-input").length).toEqual(1);
  24 + it("render a button that open a search query field", () => {
  25 + expect(helper.find(".btn-search-nav").length).toEqual(1);
26 26 });
27 27  
28 28 it("go to search page when click on search button", () => {
... ...
src/app/search/search-form/search-form.html
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>
  1 +<a class="btn btn-nav" ng-click="showSearch = !showSearch">
  2 + <i class="fa fa-search btn-search-nav" aria-hidden="true"></i>
  3 +</a>
  4 +<form class="navbar-form search-form" role="search">
  5 + <div class="ng-scope" ng-class="{'top-search-wrap': !showSearch, 'top-search-toggled': showSearch}">
  6 + <div class="tsw-inner">
  7 + <i id="top-search-close" class="fa fa-chevron-left" aria-hidden="true" ng-click="showSearch = false"></i>
  8 + <input type="text" placeholder="{{ 'search.label' | translate }}" name="q" ng-model="ctrl.query">
  9 + <i class="fa fa-search btn-search" aria-hidden="true" (click)="ctrl.search()"></i>
  10 + <button type="submit" (click)="ctrl.search()"></i>
6 11 </div>
7 12 </div>
8 13 </form>
... ...
src/app/search/search-form/search-form.scss 0 → 100644
... ... @@ -0,0 +1,107 @@
  1 +/**
  2 +* TODO: Remove the temporary reimport mixins
  3 +* Maybe the cause for this is in gulp/styles.js
  4 +* task on compile sass order.
  5 +*/
  6 +@import "../../layout/scss/mixins";
  7 +
  8 +@mixin search-wrap-btn {
  9 + @include border-radius(2px 0px 0px 2px);
  10 + @content;
  11 + font-size: 23px;
  12 + font-style: normal;
  13 + width: 45px;
  14 + text-align: center;
  15 + cursor: pointer;
  16 + height: 40px;
  17 + padding-top: 9px;
  18 +
  19 + background-color: #e3e3e3;
  20 + &:hover {
  21 + background-color: #D1D1D1;
  22 + }
  23 +
  24 + @include outline(none);
  25 +}
  26 +
  27 +.top-search-wrap {
  28 + @include opacity(0);
  29 + @include transition(all);
  30 +
  31 + position: absolute;
  32 + top: -65px;
  33 + left: 0;
  34 + width: 100%;
  35 + height: 70px;
  36 + background: #fff;
  37 + z-index: 10;
  38 +
  39 + .tsw-inner {
  40 + position: relative;
  41 + padding: 15px;
  42 + max-width: 700px;
  43 + display: block;
  44 + margin: 0 auto;
  45 + }
  46 +
  47 + #top-search-close {
  48 + @include border-radius(2px 0px 0px 2px);
  49 + @include search-wrap-btn {
  50 + position: absolute;
  51 + top: 15px;
  52 + left: 15px;
  53 + }
  54 +
  55 + @include outline(none);
  56 + }
  57 +
  58 + .btn-search {
  59 + display: inline;
  60 + float: right;
  61 +
  62 + @include search-wrap-btn {
  63 + position: relative;
  64 + top: -40px;
  65 + left: 0px;
  66 + }
  67 +
  68 + }
  69 +
  70 + input[type="text"] {
  71 + @include border-radius(2px);
  72 + border: 0;
  73 + height: 40px;
  74 + padding: 0 10px 0 55px;
  75 + font-size: 18px;
  76 + width: 500px;
  77 + background-color: #efefef;
  78 + width: 100%;
  79 +
  80 + @include outline(none);
  81 + }
  82 +
  83 +}
  84 +
  85 +.top-search-toggled {
  86 + @extend .top-search-wrap;
  87 + @include opacity(1);
  88 + position: fixed;
  89 + top: 0;
  90 + box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.3);
  91 +}
  92 +
  93 +.btn-nav {
  94 + .btn-search-nav {
  95 + font-size: 25px;
  96 + color: #FFF;
  97 + cursor: pointer;
  98 + padding: 1px;
  99 + }
  100 +}
  101 +
  102 +
  103 +.search-form {
  104 + button[type="submit"] {
  105 + visibility: hidden;
  106 + }
  107 +}
... ...
src/app/search/search.component.ts
... ... @@ -23,7 +23,7 @@ export class SearchComponent {
23 23 }
24 24  
25 25 search() {
26   - this.$state.go('main.environment.search', { query: this.query });
  26 + this.$state.go('main.environment.search', { query: this.query });
27 27 }
28 28  
29 29 loadPage() {
... ...
src/app/search/search.html
1 1 <form ng-submit="ctrl.search()">
2 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">
  3 +<h3 id="query" class="search-box-title">{{ctrl.query}}</h3>
4 4 </form>
5 5 <div class="search-results">
6 6 <div class="summary">
... ...
src/app/search/search.scss
... ... @@ -2,7 +2,6 @@
2 2 .summary {
3 3 color: #bbbbbb;
4 4 font-size: 13px;
5   - border-top: 1px solid #ececec;
6 5 }
7 6 .result {
8 7 margin: 25px 0;
... ... @@ -45,4 +44,4 @@
45 44  
46 45 .search-box-title:focus {
47 46 outline: none;
48   -}
49 47 \ No newline at end of file
  48 +}
... ...
src/app/shared/models/interfaces.ts
... ... @@ -8,4 +8,5 @@ export interface UserResponse {
8 8  
9 9 export interface INoosferoLocalStorage extends angular.storage.ILocalStorageService {
10 10 currentUser: noosfero.User;
  11 + settings: any;
11 12 }
... ...
src/languages/en.json
... ... @@ -21,9 +21,10 @@
21 21 "activities.create_article.description": "has published on",
22 22 "activities.add_member_in_community.description": "has joined the community",
23 23 "activities.new_friendship.description": "has made {friends, plural, one{one new friend} other{# new friends}}:",
24   - "auth.title": "Login",
25   - "auth.form.login": "Login / Email address",
  24 + "auth.title": "Great to have you back!",
  25 + "auth.form.login": "Username or Email address",
26 26 "auth.form.password": "Password",
  27 + "auth.form.keepLoggedIn": "Keep me logged in",
27 28 "auth.form.login_button": "Login",
28 29 "navbar.content_viewer_actions.new_item": "New Item",
29 30 "navbar.profile_actions.new_item": "New Item",
... ... @@ -80,8 +81,8 @@
80 81 "designMode.toggle.ON": "ON",
81 82 "designMode.toggle.OFF": "OFF",
82 83 "search.results.summary": "{results, plural, one{result} other{# results}}",
83   - "search.results.query.label": "Search for:",
84   - "search.results.query.placeholder": "Search",
  84 + "search.results.query.label": "Search therm:",
  85 + "search.label": "Search",
85 86 "block.edit": "Edit",
86 87 "block.edition.title": "Edit Block",
87 88 "block.edition.success.title": "Good job!",
... ...
src/languages/pt.json
... ... @@ -21,9 +21,10 @@
21 21 "activities.create_article.description": "publicou em",
22 22 "activities.add_member_in_community.description": "entrou na comunidade",
23 23 "activities.new_friendship.description": "fez {friends, plural, one{um novo amigo} other{# novos amigos}}:",
24   - "auth.title": "Login",
25   - "auth.form.login": "Login / Email",
  24 + "auth.title": "Legal ter você de volta!",
  25 + "auth.form.login": "Nome de usuário ou Email",
26 26 "auth.form.password": "Senha",
  27 + "auth.form.keepLoggedIn": "Continuar logado",
27 28 "auth.form.login_button": "Login",
28 29 "navbar.content_viewer_actions.new_item": "Novo Item",
29 30 "navbar.profile_actions.new_item": "Novo Item",
... ... @@ -80,8 +81,8 @@
80 81 "designMode.toggle.ON": "Ligado",
81 82 "designMode.toggle.OFF": "Desligado",
82 83 "search.results.summary": "{results, plural, one{# resultado} other{# resultados}}",
83   - "search.results.query.label": "Buscar:",
84   - "search.results.query.placeholder": "Informe aqui sua busca",
  84 + "search.results.query.label": "Termo da busca:",
  85 + "search.label": "Pesquisar",
85 86 "block.edit": "Editar",
86 87 "block.edition.title": "Editar Bloco",
87 88 "block.edition.success.title": "Bom trabalho!",
... ...
src/lib/ng-noosfero-api/http/environment.service.ts
... ... @@ -68,6 +68,14 @@ export class EnvironmentService {
68 68 return errorFunction;
69 69 }
70 70  
  71 + getTags(): ng.IPromise<{}> {
  72 + let p = this.restangular.one('environment').customGET('tags');
  73 + let deferred = this.$q.defer<{}>();
  74 + p.then(this.getHandleSuccessFunction<{}>(deferred));
  75 + p.catch(this.getHandleErrorFunction<{}>(deferred));
  76 + return deferred.promise;
  77 + }
  78 +
71 79 /**
72 80 * TODO - use restangular service as base class, and this will not be necessary here anymore
73 81 */
... ...
src/lib/ng-noosfero-api/interfaces/environment.ts
... ... @@ -15,5 +15,13 @@ namespace noosfero {
15 15 */
16 16 id: number;
17 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 27 \ No newline at end of file
  28 +}
... ...
src/lib/ng-noosfero-api/interfaces/profile.ts
... ... @@ -80,5 +80,13 @@ namespace noosfero {
80 80 custom_footer: string;
81 81  
82 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;
83 91 }
84 92 }
... ...
src/plugins/comment_paragraph/hotspot/export-comment-button.scss 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +.export-comment-button {
  2 + margin-left: 1px;
  3 +}
  4 +
... ...
src/spec/mocks.ts
... ... @@ -193,6 +193,17 @@ export var mocks: any = {
193 193 currentUser: () => { return user; }
194 194 };
195 195 },
  196 + designModeService: {
  197 + modeFn: null,
  198 + onToggle: {
  199 + subscribe: (fn: Function) => {
  200 + mocks.designModeService.modeFn = fn;
  201 + },
  202 + next: (param: any) => {
  203 + mocks.designModeService.modeFn(param);
  204 + }
  205 + }
  206 + },
196 207 $translate: {
197 208 use: (lang?: string) => {
198 209 return lang ? Promise.resolve(lang) : "en";
... ...
themes/angular-participa-consulta/README.md 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +# Angular Participa Consulta Theme
  2 +
  3 +
  4 +## Getting started
  5 +
  6 +Run these commands to set the proper theme and skin
  7 +
  8 +`npm config set angular-theme:theme angular-participa-consulta`
  9 +
  10 +`npm config set angular-theme:skin skin-yellow`
... ...
themes/angular-participa-consulta/app/layout/scss/skins/_yellow.scss 0 → 100644
... ... @@ -0,0 +1,64 @@
  1 +.skin-yellow {
  2 + @extend %skin-base;
  3 +
  4 + .notifications-list .item-footer {
  5 + background: #DAE1C4;
  6 + color: #4F9CAC;
  7 + }
  8 +
  9 + .navbar-nav .open > a,
  10 + .navbar-nav .open > a:hover,
  11 + .navbar-nav .open > a:focus {
  12 + background-color: $selected-color;
  13 + color: #000;
  14 + }
  15 +
  16 + .nav .open > a,
  17 + .nav .open > a:hover,
  18 + .nav .open > a:focus {
  19 + border: none;
  20 + }
  21 +
  22 + .dropdown-menu {
  23 + li > a:hover {
  24 + color: #555;
  25 + background-color: #F7F7F7;
  26 + }
  27 +
  28 + .active > a,
  29 + .active > a:hover,
  30 + .active > a:focus {
  31 + color: #000;
  32 + background-color: #CCC;
  33 + }
  34 + }
  35 +
  36 + .nav .caret {
  37 + border-bottom-color: #fff !important;
  38 + border-top-color: #fff !important;
  39 + }
  40 +
  41 + .nav .open .caret {
  42 + border-bottom-color: #000 !important;
  43 + border-top-color: #000 !important;
  44 + }
  45 +
  46 + .navbar-inverse .navbar-toggle {
  47 + border-color: #D49F18;
  48 + }
  49 +
  50 + .container-fluid .navbar-header .navbar-toggle {
  51 + &:hover, &:focus {
  52 + background-color: #f5b025;
  53 + }
  54 + }
  55 +
  56 + .navbar {
  57 + .navbar-nav .btn-nav {
  58 + &:hover {
  59 + background-color: $selected-color;
  60 + }
  61 + }
  62 + }
  63 +
  64 +}
... ...
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   -
13 1 .navbar {
14 2 min-height: 123px;
15 3 background-color: #f9c404;
... ...
themes/angular-participa-consulta/app/participa-consulta.scss
... ... @@ -21,53 +21,6 @@ $selected-color: #f6c445;
21 21 src: url('../assets/fonts/participa-consulta/Ubuntu-RI.ttf');
22 22 }
23 23  
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   -
69   -}
70   -
71 24 .profile-header, .profile-footer{
72 25 text-align: center;
73 26 }
... ...