Compare View
Commits (38)
-
…ith the query value in the current query param. changed the search.component to have a search text box to show then current query and allow the user query for new terms.
-
…designModeToggler Component
-
The use of bootstrap to start the application broke the startup process.
-
Export comments Incorporado o botão de exportar comentários. See merge request !40
-
* theme-skin-yellow: New npm config 'skin' to allow sass by theme skins
-
* fix-invisible-block-title: fix invisible block title
-
- Added support only to 'rightbar' and 'default' @TODO - Develop all layouts Signed-off-by: Tallys Martins <tallysmartins@gmail.com>
Showing
89 changed files
Show diff stats
.gitignore
README.md
@@ -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 |
bower.json
@@ -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" |
gulp/conf.js
@@ -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 | }); |
package.json
@@ -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", |
@@ -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 |
@@ -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 = " <i class='glyphicon glyphicon-wrench'></i> "; | ||
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 |
@@ -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 "../../../lib/ng-noosfero-api/http/profile.service" | @@ -14,7 +14,7 @@ import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service" | ||
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 'ng-forward'; | @@ -2,6 +2,7 @@ import {StateConfig, Component, Inject, provide} from 'ng-forward'; | ||
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 "./environment-home.component"; | @@ -29,6 +30,18 @@ import {EnvironmentHomeComponent} from "./environment-home.component"; | ||
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 "./login/auth-events"; | @@ -6,7 +6,7 @@ import {AuthEvents} from "./login/auth-events"; | ||
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) { |
@@ -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 | +}); |
@@ -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 | +} |
@@ -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.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 | } |
@@ -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
src/app/layout/blocks/main/main-block.component.ts
src/app/layout/blocks/person-tags-plugin-interests/index.ts
0 → 100644
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
src/app/layout/blocks/person-tags-plugin-interests/person-tags-plugin-interests-block.scss
0 → 100644
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("Boxes Component", () => { | @@ -48,40 +48,4 @@ describe("Boxes Component", () => { | ||
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
src/app/layout/boxes/boxes.scss
@@ -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 | + |
@@ -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 "./../../l | @@ -4,11 +4,13 @@ import {SessionService, AuthService, AuthController, AuthEvents} from "./../../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 "./../../login/auth.service"; | @@ -4,7 +4,7 @@ import {AuthService} from "./../../login/auth.service"; | ||
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("BodyStateClasses Service", () => { | @@ -19,10 +19,13 @@ describe("BodyStateClasses Service", () => { | ||
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("BodyStateClasses Service", () => { | @@ -168,4 +171,30 @@ describe("BodyStateClasses Service", () => { | ||
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 "../../login/auth-events"; | @@ -3,6 +3,7 @@ import {AuthEvents} from "../../login/auth-events"; | ||
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 "./../article/article-default-view.component" | @@ -6,6 +6,7 @@ import {ArticleViewComponent} from "./../article/article-default-view.component" | ||
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 "../layout/blocks/recent-documents/r | @@ -16,6 +17,7 @@ import {RecentDocumentsBlockComponent} from "../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 "../layout/sidebar/sidebar.component"; | @@ -40,6 +42,8 @@ import {SidebarComponent} from "../layout/sidebar/sidebar.component"; | ||
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 'ng-forward'; | @@ -2,27 +2,32 @@ import {Component, Input, Inject} from 'ng-forward'; | ||
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> |
@@ -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 "../../lib/ng-noosfero-api/http/profile.service"; | @@ -10,7 +10,7 @@ import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service"; | ||
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 "./profile-actions.component"; | @@ -42,6 +42,11 @@ import {ProfileActionsComponent} from "./profile-actions.component"; | ||
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 "./profile-actions.component"; | @@ -54,6 +59,11 @@ import {ProfileActionsComponent} from "./profile-actions.component"; | ||
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 "./profile-actions.component"; | @@ -106,6 +116,11 @@ import {ProfileActionsComponent} from "./profile-actions.component"; | ||
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/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 | +}); |
@@ -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 | +} |
@@ -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> |
@@ -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 | +}); |
@@ -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 | +} |
@@ -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> |
@@ -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/permission/permission.directive.ts
@@ -6,7 +6,7 @@ import {Directive, Inject, Input} from "ng-forward"; | @@ -6,7 +6,7 @@ import {Directive, Inject, Input} from "ng-forward"; | ||
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("Services", () => { | @@ -23,7 +23,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -32,7 +32,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -42,7 +42,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -52,7 +52,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -62,7 +62,7 @@ describe("Services", () => { | ||
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("Services", () => { | @@ -71,14 +71,25 @@ describe("Services", () => { | ||
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<noosfero.Article> { | @@ -118,6 +118,11 @@ export class ArticleService extends RestangularService<noosfero.Article> { | ||
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("Services", () => { | @@ -29,6 +29,16 @@ describe("Services", () => { | ||
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<noosfero.Block> { | @@ -47,4 +47,12 @@ export class BlockService extends RestangularService<noosfero.Block> { | ||
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<noosfero.Person> { | @@ -20,4 +21,11 @@ export class PersonService extends RestangularService<noosfero.Person> { | ||
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<T extends noosfero.RestModel> { | @@ -79,7 +79,7 @@ export abstract class RestangularService<T extends noosfero.RestModel> { | ||
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<T extends noosfero.RestModel> { | @@ -289,7 +289,7 @@ export abstract class RestangularService<T extends noosfero.RestModel> { | ||
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
src/lib/ng-noosfero-api/interfaces/block.ts
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
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 | +} |