Commit 2f5bbc4fccacff192ec721d69f1b8b7bb0aa5f4e
Exists in
staging
Merge branch 'master' into staging
Showing
86 changed files
with
1323 additions
and
323 deletions
Show diff stats
README.md
... | ... | @@ -50,3 +50,31 @@ See some important folders bellow: |
50 | 50 | 1. Create an app folder inside custom-theme and add your custom scss files |
51 | 51 | 1. Put the templates that you want to override in the same structure from the main application source, e.g.: |
52 | 52 | `src/app/profile/profile.html` will be overriden by `themes/custom-theme/app/profile/profile.html` |
53 | + | |
54 | +## Change skin | |
55 | + | |
56 | +- Create a any scss file into your theme folder structure | |
57 | +- Extend your skin css class from `%skin-base` scss placeholder selector. Something like this: | |
58 | + | |
59 | +```sass | |
60 | +.skin-custom { | |
61 | + @extend %skin-base | |
62 | +} | |
63 | +``` | |
64 | +- Configure application to use the new theme, e.g.: | |
65 | +`npm config set angular-theme:skin custom-skin` | |
66 | + | |
67 | +- Start the application with `npm start` scripts ou make a build | |
68 | + | |
69 | +## Development environment | |
70 | + | |
71 | +## Known Issues | |
72 | + | |
73 | +### Message Translation: angular-i18n | |
74 | + | |
75 | + - Plural Interpolation only working when current language is En (English) | |
76 | + | |
77 | + `Plural Function not found for locale` | |
78 | + | |
79 | + For some reason the messageformat installed on bower_component directory was an older version. Removing the bower_components directory | |
80 | +and runing `bower install` solved the problem | ... | ... |
bower.json
... | ... | @@ -35,9 +35,10 @@ |
35 | 35 | "angular-i18n": "^1.5.0", |
36 | 36 | "angular-load": "^0.4.1", |
37 | 37 | "angular-translate-interpolation-messageformat": "^2.10.0", |
38 | - "angular-bind-html-compile": "^1.2.1", | |
39 | - "angular-click-outside": "^2.7.1", | |
40 | - "ng-ckeditor": "^0.2.1" | |
38 | + "angular-bind-html-compile": "^1.2.1", | |
39 | + "angular-click-outside": "^2.7.1", | |
40 | + "ng-ckeditor": "^0.2.1", | |
41 | + "angular-bootstrap-toggle-switch": "^0.5.6" | |
41 | 42 | }, |
42 | 43 | "devDependencies": { |
43 | 44 | "angular-mocks": "~1.5.0" |
... | ... | @@ -46,9 +47,7 @@ |
46 | 47 | "ng-ckeditor": { |
47 | 48 | "main": [ |
48 | 49 | "ng-ckeditor.js", |
49 | - "libs/ckeditor/lang", | |
50 | - "libs/ckeditor/ckeditor.js", | |
51 | - "libs/ckeditor/config.js" | |
50 | + "libs/ckeditor/ckeditor.js" | |
52 | 51 | ] |
53 | 52 | }, |
54 | 53 | "bootstrap-sass": { | ... | ... |
gulp/build.js
... | ... | @@ -30,7 +30,8 @@ gulp.task('partials', function () { |
30 | 30 | quotes: true |
31 | 31 | })) |
32 | 32 | .pipe($.angularTemplatecache('templateCacheHtml-'+partialPath+'.js', { |
33 | - module: 'noosferoApp', | |
33 | + module: 'noosfero.templates.' + partialPath, | |
34 | + standalone: true, | |
34 | 35 | root: partialPath |
35 | 36 | })) |
36 | 37 | .pipe(gulp.dest(conf.paths.tmp + '/partials/'))); |
... | ... | @@ -81,7 +82,6 @@ gulp.task('html', ['inject', 'partials'], function () { |
81 | 82 | .pipe($.revReplace({prefix: noosferoThemePrefix})) |
82 | 83 | .pipe(htmlFilter) |
83 | 84 | .pipe($.replace('/bower_components/ng-ckeditor/libs/ckeditor/', noosferoThemePrefix + 'ng-ckeditor/libs/ckeditor/')) |
84 | - .pipe($.replace('/bower_components/ng-ckeditor/ng-ckeditor.min.js', noosferoThemePrefix + 'ng-ckeditor/ng-ckeditor.min.js')) | |
85 | 85 | .pipe($.minifyHtml({ |
86 | 86 | empty: true, |
87 | 87 | spare: true, |
... | ... | @@ -103,7 +103,7 @@ gulp.task('fonts', function () { |
103 | 103 | }); |
104 | 104 | |
105 | 105 | gulp.task('ckeditor', function () { |
106 | - conf.wiredep.exclude.push(/ckeditor/); // exclude ckeditor from build to improve performance | |
106 | + conf.wiredep.exclude.push(/bower_components\/ng-ckeditor\/libs\/ckeditor/); // exclude ckeditor from build to improve performance | |
107 | 107 | return gulp.src(['bower_components/ng-ckeditor/**/*']).pipe(gulp.dest(path.join(conf.paths.dist, '/ng-ckeditor'))); |
108 | 108 | }); |
109 | 109 | ... | ... |
gulp/conf.js
... | ... | @@ -27,6 +27,10 @@ exports.configTheme = function(theme) { |
27 | 27 | exports.paths.theme = theme || "angular-default"; |
28 | 28 | exports.paths.allSources = [exports.paths.src, path.join(exports.paths.themes, exports.paths.theme)]; |
29 | 29 | exports.paths.dist = path.join("dist", exports.paths.theme); |
30 | + | |
31 | + if(argv.skin) { | |
32 | + exports.paths.skin = argv.skin; | |
33 | + } | |
30 | 34 | } |
31 | 35 | exports.configTheme(argv.theme); |
32 | 36 | ... | ... |
gulp/inject.js
... | ... | @@ -3,6 +3,8 @@ |
3 | 3 | var path = require('path'); |
4 | 4 | var gulp = require('gulp'); |
5 | 5 | var conf = require('./conf'); |
6 | +var map = require('map-stream'); | |
7 | +var transform = require('vinyl-transform'); | |
6 | 8 | |
7 | 9 | var $ = require('gulp-load-plugins')(); |
8 | 10 | |
... | ... | @@ -40,3 +42,34 @@ gulp.task('inject', ['scripts', 'styles'], function () { |
40 | 42 | .pipe(wiredep(_.extend({}, conf.wiredep))) |
41 | 43 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve'))); |
42 | 44 | }); |
45 | + | |
46 | +/** | |
47 | +* Replace the default theme skin to a npm config | |
48 | +* | |
49 | +* Uses "vinyl-transform" + "map-stream" to open the | |
50 | +* js file and rewrite the source file into the same | |
51 | +* destination | |
52 | +* | |
53 | +* @see https://www.npmjs.com/package/vinyl-transform | |
54 | +* @see https://www.npmjs.com/package/map-stream | |
55 | +*/ | |
56 | +gulp.task('inject-skin', function () { | |
57 | + | |
58 | + if(conf.paths.skin) { | |
59 | + | |
60 | + $.util.log('Configured theme skin:', conf.paths.skin); | |
61 | + | |
62 | + var replaceSkin = transform(function(filename) { | |
63 | + return map(function(file, next) { | |
64 | + var contents = file.toString(); | |
65 | + contents = contents.replace('skin-whbl', conf.paths.skin); | |
66 | + return next(null, contents); | |
67 | + }); | |
68 | + }); | |
69 | + | |
70 | + gulp.src(path.join(conf.paths.src,'./noosfero.js')) | |
71 | + .pipe(replaceSkin) | |
72 | + .pipe(gulp.dest(conf.paths.src)); | |
73 | + } | |
74 | + | |
75 | +}); | ... | ... |
gulp/scripts.js
... | ... | @@ -9,7 +9,7 @@ var browserSync = require('browser-sync'); |
9 | 9 | var $ = require('gulp-load-plugins')(); |
10 | 10 | |
11 | 11 | |
12 | -gulp.task('scripts-reload', function() { | |
12 | +gulp.task('scripts-reload', ['inject-skin'], function() { | |
13 | 13 | return buildScripts() |
14 | 14 | .pipe(browserSync.stream()); |
15 | 15 | }); | ... | ... |
package.json
... | ... | @@ -8,10 +8,11 @@ |
8 | 8 | "ng-forward": "0.0.1-alpha.12" |
9 | 9 | }, |
10 | 10 | "config": { |
11 | - "theme": "angular-default" | |
11 | + "theme": "angular-default", | |
12 | + "skin": "skin-whbl" | |
12 | 13 | }, |
13 | 14 | "scripts": { |
14 | - "build": "webpack; gulp clean && gulp --theme=$npm_package_config_theme build", | |
15 | + "build": "webpack; gulp clean && gulp --theme=$npm_package_config_theme --skin=$npm_package_config_skin build", | |
15 | 16 | "build-all": "gulp clean && for d in ./themes/* ; do (gulp --theme=`basename $d` build); done", |
16 | 17 | "webpack": "webpack", |
17 | 18 | "karma": "concurrently \"webpack -w\" \"karma start\"", |
... | ... | @@ -23,7 +24,7 @@ |
23 | 24 | "test": "webpack -w --test", |
24 | 25 | "jenkins": "webpack && karma start --single-run", |
25 | 26 | "postinstall": "bower install; typings install; cd dev-scripts; typings install; cd ../; npm run fix-jqlite", |
26 | - "start": "concurrently \"webpack -w\" \"gulp --theme=$npm_package_config_theme serve\"", | |
27 | + "start": "concurrently \"webpack -w\" \"gulp --theme=$npm_package_config_theme --skin=$npm_package_config_skin serve\"", | |
27 | 28 | "generate-indexes": "ts-node --project ./dev-scripts ./dev-scripts/generate-index-modules.ts", |
28 | 29 | "fix-jqlite": "ts-node --project ./dev-scripts dev-scripts/fix-jqlite.ts" |
29 | 30 | }, |
... | ... | @@ -46,8 +47,8 @@ |
46 | 47 | "gulp-eslint": "~1.0.0", |
47 | 48 | "gulp-filter": "~3.0.1", |
48 | 49 | "gulp-flatten": "~0.2.0", |
49 | - "gulp-insert": "^0.5.0", | |
50 | 50 | "gulp-inject": "~3.0.0", |
51 | + "gulp-insert": "^0.5.0", | |
51 | 52 | "gulp-load-plugins": "~0.10.0", |
52 | 53 | "gulp-merge-json": "^0.4.0", |
53 | 54 | "gulp-minify-css": "~1.2.1", |
... | ... | @@ -81,6 +82,7 @@ |
81 | 82 | "karma-webpack": "^1.7.0", |
82 | 83 | "lodash": "~3.10.1", |
83 | 84 | "main-bower-files": "~2.9.0", |
85 | + "map-stream": "0.0.6", | |
84 | 86 | "merge-stream": "^1.0.0", |
85 | 87 | "on-build-webpack": "^0.1.0", |
86 | 88 | "phantomjs": "~1.9.18", |
... | ... | @@ -96,6 +98,7 @@ |
96 | 98 | "typescript": "^1.8.10", |
97 | 99 | "typings": "^0.6.8", |
98 | 100 | "uglify-save-license": "~0.4.1", |
101 | + "vinyl-transform": "^1.0.0", | |
99 | 102 | "webpack": "^1.12.14", |
100 | 103 | "wiredep": "~2.2.2", |
101 | 104 | "wrench": "~1.5.8", | ... | ... |
... | ... | @@ -0,0 +1,31 @@ |
1 | +import {DesignModeService} from './designMode.service'; | |
2 | + | |
3 | +describe('DesignMode Service', () => { | |
4 | + let service: DesignModeService; | |
5 | + | |
6 | + beforeEach(() => { | |
7 | + service = new DesignModeService(); | |
8 | + }); | |
9 | + | |
10 | + it('has the designModeOn equals false as default', () => { | |
11 | + expect(service.isInDesignMode()).toBeFalsy(); | |
12 | + }); | |
13 | + | |
14 | + it('allows set the designMode value', () => { | |
15 | + spyOn(service.onToggle, 'next').and.stub(); | |
16 | + service.setInDesignMode(true); | |
17 | + expect(service.isInDesignMode).toBeTruthy(); | |
18 | + }); | |
19 | + | |
20 | + it('emits the onToggle event when changing the designModeOn property', () => { | |
21 | + spyOn(service.onToggle, 'next').and.stub(); | |
22 | + service.setInDesignMode(true); | |
23 | + expect(service.onToggle.next).toHaveBeenCalled(); | |
24 | + }); | |
25 | + | |
26 | + it('does not emit onToggle event when there is no change on designModeOn property', () => { | |
27 | + spyOn(service.onToggle, 'next').and.stub(); | |
28 | + service.setInDesignMode(false); | |
29 | + expect(service.onToggle.next).not.toHaveBeenCalled(); | |
30 | + }); | |
31 | +}); | |
0 | 32 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,23 @@ |
1 | +import {Component, Injectable, Output, EventEmitter} from 'ng-forward'; | |
2 | +import {BodyStateClassesService} from '../../layout/services/body-state-classes.service'; | |
3 | + | |
4 | +@Injectable() | |
5 | +export class DesignModeService { | |
6 | + @Output() onToggle: EventEmitter<boolean> = new EventEmitter<boolean>(); | |
7 | + | |
8 | + private designModeOn: boolean = false; | |
9 | + | |
10 | + isInDesignMode(): boolean { | |
11 | + return this.designModeOn; | |
12 | + } | |
13 | + | |
14 | + setInDesignMode(value: boolean) { | |
15 | + if (this.designModeOn !== value) { | |
16 | + this.designModeOn = value; | |
17 | + this.onToggle.next(this.designModeOn); | |
18 | + } | |
19 | + } | |
20 | + | |
21 | + constructor() { | |
22 | + } | |
23 | +} | |
0 | 24 | \ No newline at end of file | ... | ... |
src/app/admin/layout-edit/designModeToggler.component.spec.ts
0 → 100644
... | ... | @@ -0,0 +1,59 @@ |
1 | +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper'; | |
2 | +import * as helpers from '../../../spec/helpers'; | |
3 | +import {DesignModeTogglerComponent} from './designModeToggler.component'; | |
4 | +import {DesignModeService} from './designMode.service'; | |
5 | + | |
6 | +describe('DesignModeToggler Component', () => { | |
7 | + const htmlTemplate: string = '<noosfero-design-toggler></noosfero-design-toggler>'; | |
8 | + | |
9 | + let helper: ComponentTestHelper<DesignModeTogglerComponent>; | |
10 | + beforeEach(() => { | |
11 | + angular.mock.module('templates'); | |
12 | + angular.mock.module('ngSanitize'); | |
13 | + angular.mock.module('toggle-switch'); | |
14 | + }); | |
15 | + | |
16 | + let designModeService: DesignModeService; | |
17 | + beforeEach((done) => { | |
18 | + designModeService = new DesignModeService(); | |
19 | + let cls = createClass({ | |
20 | + template: htmlTemplate, | |
21 | + directives: [DesignModeTogglerComponent], | |
22 | + providers: [ | |
23 | + helpers.createProviderToValue('DesignModeService', designModeService) | |
24 | + ] | |
25 | + }); | |
26 | + helper = new ComponentTestHelper<DesignModeTogglerComponent>(cls, done); | |
27 | + }); | |
28 | + | |
29 | + it('changes css classes representing the switch is on or off', () => { | |
30 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeTruthy(); | |
31 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeFalsy(); | |
32 | + helper.component.inDesignMode = true; | |
33 | + helper.detectChanges(); | |
34 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-on')).toBeTruthy(); | |
35 | + expect(helper.debugElement.query('div.switch-animate').hasClass('switch-off')).toBeFalsy(); | |
36 | + }); | |
37 | + | |
38 | + it('emits event with value "true" when changing inDesignMode to On', (done) => { | |
39 | + designModeService.onToggle.subscribe((designModeOn: boolean) => { | |
40 | + expect(designModeOn).toBeTruthy(); | |
41 | + done(); | |
42 | + }); | |
43 | + helper.component.inDesignMode = true; | |
44 | + helper.detectChanges(); | |
45 | + }); | |
46 | + | |
47 | + it('emits events with value "false" when changing inDesignMode to Off', (done) => { | |
48 | + helper.component.inDesignMode = true; | |
49 | + helper.detectChanges(); | |
50 | + | |
51 | + designModeService.onToggle.subscribe((designModeOn: boolean) => { | |
52 | + expect(designModeOn).toBeFalsy(); | |
53 | + done(); | |
54 | + }); | |
55 | + | |
56 | + helper.component.inDesignMode = false; | |
57 | + helper.detectChanges(); | |
58 | + }); | |
59 | +}); | |
0 | 60 | \ No newline at end of file | ... | ... |
src/app/admin/layout-edit/designModeToggler.component.ts
0 → 100644
... | ... | @@ -0,0 +1,24 @@ |
1 | +import {Component, Inject} from 'ng-forward'; | |
2 | +import {DesignModeService} from './designMode.service'; | |
3 | +@Component({ | |
4 | + selector: 'noosfero-design-toggler', | |
5 | + templateUrl: 'app/admin/layout-edit/designModeToggler.html' | |
6 | +}) | |
7 | +@Inject(DesignModeService) | |
8 | +export class DesignModeTogglerComponent { | |
9 | + | |
10 | + icon: string = " <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 | 25 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,8 @@ |
1 | +<toggle-switch | |
2 | + html="true" | |
3 | + ng-model="ctrl.inDesignMode" | |
4 | + on-label="{{'designMode.toggle.ON' | translate}}" | |
5 | + off-label="{{'designMode.toggle.OFF' | translate}}" | |
6 | + class="switch-small" | |
7 | + knob-label="{{ ctrl.icon + ('designMode.label' | translate) }}"> | |
8 | +</toggle-switch> | |
0 | 9 | \ No newline at end of file | ... | ... |
src/app/article/article-default-view-component.spec.ts
... | ... | @@ -65,16 +65,36 @@ describe("Components", () => { |
65 | 65 | expect(state.transitionTo).toHaveBeenCalled(); |
66 | 66 | }); |
67 | 67 | |
68 | + it("hide button to delete article when user doesn't have permission", () => { | |
69 | + expect(helper.find(".article-toolbar .delete-article").attr('style')).toEqual("display: none; "); | |
70 | + }); | |
71 | + | |
72 | + it("hide button to edit article when user doesn't have permission", () => { | |
73 | + expect(helper.find(".article-toolbar .edit-article").attr('style')).toEqual("display: none; "); | |
74 | + }); | |
75 | + | |
76 | + it("show button to edit article when user has permission", () => { | |
77 | + (<any>helper.component['article'])['permissions'] = ['allow_edit']; | |
78 | + helper.detectChanges(); | |
79 | + expect(helper.find(".article-toolbar .edit-article").attr('style')).toEqual(''); | |
80 | + }); | |
81 | + | |
82 | + it("show button to delete article when user has permission", () => { | |
83 | + (<any>helper.component['article'])['permissions'] = ['allow_delete']; | |
84 | + helper.detectChanges(); | |
85 | + expect(helper.find(".article-toolbar .delete-article").attr('style')).toEqual(''); | |
86 | + }); | |
87 | + | |
68 | 88 | /** |
69 | 89 | * Execute the delete method on the target component |
70 | 90 | */ |
71 | 91 | function doDeleteArticle() { |
72 | 92 | // Create a mock for the notification service confirmation |
73 | - spyOn(helper.component.notificationService, 'confirmation').and.callFake(function (params: Function) { | |
93 | + spyOn(helper.component.notificationService, 'confirmation').and.callFake(function(params: Function) { | |
74 | 94 | |
75 | 95 | }); |
76 | 96 | // Create a mock for the ArticleService removeArticle method |
77 | - spyOn(helper.component.articleService, 'remove').and.callFake(function (param: noosfero.Article) { | |
97 | + spyOn(helper.component.articleService, 'remove').and.callFake(function(param: noosfero.Article) { | |
78 | 98 | |
79 | 99 | return { |
80 | 100 | catch: () => { } | ... | ... |
src/app/article/article-default-view.component.ts
... | ... | @@ -6,6 +6,7 @@ import {ArticleToolbarHotspotComponent} from "../hotspot/article-toolbar-hotspot |
6 | 6 | import {ArticleContentHotspotComponent} from "../hotspot/article-content-hotspot.component"; |
7 | 7 | import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service"; |
8 | 8 | import { NotificationService } from "./../shared/services/notification.service"; |
9 | +import {PermissionDirective} from '../shared/components/permission/permission.directive'; | |
9 | 10 | |
10 | 11 | /** |
11 | 12 | * @ngdoc controller |
... | ... | @@ -16,7 +17,8 @@ import { NotificationService } from "./../shared/services/notification.service"; |
16 | 17 | */ |
17 | 18 | @Component({ |
18 | 19 | selector: 'noosfero-default-article', |
19 | - templateUrl: 'app/article/article.html' | |
20 | + templateUrl: 'app/article/article.html', | |
21 | + directives: [PermissionDirective] | |
20 | 22 | }) |
21 | 23 | @Inject("$state", ArticleService, NotificationService) |
22 | 24 | export class ArticleDefaultViewComponent { | ... | ... |
src/app/article/article.html
... | ... | @@ -4,13 +4,15 @@ |
4 | 4 | </div> |
5 | 5 | |
6 | 6 | <div class="sub-header clearfix"> |
7 | - <a href="#" class="btn btn-default btn-xs" ui-sref="main.cmsEdit({profile: ctrl.profile.identifier, id: ctrl.article.id})"> | |
8 | - <i class="fa fa-pencil-square-o fa-fw fa-lg"></i> {{"article.actions.edit" | translate}} | |
9 | - </a> | |
10 | - <a href="#" class="btn btn-default btn-xs" ng-click="ctrl.delete()"> | |
11 | - <i class="fa fa-trash-o fa-fw fa-lg" ng-click="ctrl.delete()"></i> {{"article.actions.delete" | translate}} | |
12 | - </a> | |
13 | - <noosfero-hotspot-article-toolbar [article]="ctrl.article"></noosfero-hotspot-article-toolbar> | |
7 | + <div class="article-toolbar"> | |
8 | + <a href="#" permission="ctrl.article.permissions" permission-action="allow_edit" class="btn btn-default btn-xs edit-article" ui-sref="main.cmsEdit({profile: ctrl.profile.identifier, id: ctrl.article.id})"> | |
9 | + <i class="fa fa-pencil-square-o fa-fw fa-lg"></i> {{"article.actions.edit" | translate}} | |
10 | + </a> | |
11 | + <a href="#" permission="ctrl.article.permissions" permission-action="allow_delete" class="btn btn-default btn-xs delete-article" ng-click="ctrl.delete()"> | |
12 | + <i class="fa fa-trash-o fa-fw fa-lg" ng-click="ctrl.delete()"></i> {{"article.actions.delete" | translate}} | |
13 | + </a> | |
14 | + <noosfero-hotspot-article-toolbar [article]="ctrl.article"></noosfero-hotspot-article-toolbar> | |
15 | + </div> | |
14 | 16 | <div class="page-info pull-right small text-muted"> |
15 | 17 | <span class="time"> |
16 | 18 | <i class="fa fa-clock-o"></i> <span am-time-ago="ctrl.article.created_at | dateFormat"></span> | ... | ... |
src/app/article/comment/comments.component.spec.ts
... | ... | @@ -19,7 +19,6 @@ describe("Components", () => { |
19 | 19 | |
20 | 20 | let properties = { article: { id: 1 }, parent: <any>null }; |
21 | 21 | function createComponent() { |
22 | - // postCommentEventService = jasmine.createSpyObj("postCommentEventService", ["subscribe"]); | |
23 | 22 | let providers = [ |
24 | 23 | helpers.createProviderToValue('CommentService', commentService), |
25 | 24 | helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService), | ... | ... |
src/app/article/comment/comments.component.ts
... | ... | @@ -9,7 +9,7 @@ import { CommentComponent } from "./comment.component"; |
9 | 9 | directives: [PostCommentComponent, CommentComponent], |
10 | 10 | outputs: ['commentAdded'] |
11 | 11 | }) |
12 | -@Inject(CommentService, "$element") | |
12 | +@Inject(CommentService, "$scope") | |
13 | 13 | export class CommentsComponent { |
14 | 14 | |
15 | 15 | comments: noosfero.CommentViewModel[] = []; |
... | ... | @@ -32,9 +32,22 @@ export class CommentsComponent { |
32 | 32 | } |
33 | 33 | } |
34 | 34 | |
35 | - commentAdded(comment: noosfero.Comment): void { | |
35 | + commentAdded(comment: noosfero.CommentViewModel): void { | |
36 | + comment.__show_reply = false; | |
37 | + if (comment.reply_of) { | |
38 | + this.comments.forEach((commentOnList) => { | |
39 | + if (commentOnList.id === comment.reply_of.id) { | |
40 | + if (commentOnList.replies) { | |
41 | + commentOnList.replies.push(comment); | |
42 | + } else { | |
43 | + commentOnList.replies = [comment]; | |
44 | + } | |
45 | + } | |
46 | + }); | |
47 | + } | |
36 | 48 | this.comments.push(comment); |
37 | 49 | this.resetShowReply(); |
50 | + this.$scope.$apply(); | |
38 | 51 | } |
39 | 52 | |
40 | 53 | commentRemoved(comment: noosfero.Comment): void { |
... | ... | @@ -51,6 +64,7 @@ export class CommentsComponent { |
51 | 64 | if (this.parent) { |
52 | 65 | this.parent.__show_reply = false; |
53 | 66 | } |
67 | + | |
54 | 68 | } |
55 | 69 | |
56 | 70 | loadComments() { | ... | ... |
src/app/article/content-viewer/content-viewer.component.ts
... | ... | @@ -14,7 +14,7 @@ import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service" |
14 | 14 | provide('profileService', { useClass: ProfileService }) |
15 | 15 | ] |
16 | 16 | }) |
17 | -@Inject(ArticleService, ProfileService, "$log", "$stateParams") | |
17 | +@Inject(ArticleService, ProfileService, "$stateParams") | |
18 | 18 | export class ContentViewerComponent { |
19 | 19 | |
20 | 20 | @Input() |
... | ... | @@ -23,7 +23,10 @@ export class ContentViewerComponent { |
23 | 23 | @Input() |
24 | 24 | profile: noosfero.Profile = null; |
25 | 25 | |
26 | - constructor(private articleService: ArticleService, private profileService: ProfileService, private $log: ng.ILogService, private $stateParams: angular.ui.IStateParamsService) { | |
26 | + constructor( | |
27 | + private articleService: ArticleService, | |
28 | + private profileService: ProfileService, | |
29 | + private $stateParams: angular.ui.IStateParamsService) { | |
27 | 30 | this.activate(); |
28 | 31 | } |
29 | 32 | ... | ... |
src/app/article/content-viewer/navbar-actions.html
1 | -<ul class="nav navbar-nav"> | |
1 | +<ul class="nav navbar-nav" permission="vm.profile.permissions" permission-action="allow_edit"> | |
2 | 2 | <li class="dropdown profile-menu" uib-dropdown> |
3 | 3 | <a class="btn dropdown-toggle" data-toggle="dropdown" uib-dropdown-toggle> |
4 | 4 | {{"navbar.content_viewer_actions.new_item" | translate}} |
5 | 5 | <i class="fa fa-caret-down"></i> |
6 | 6 | </a> |
7 | 7 | <ul class="dropdown-menu" uib-dropdown-menu ng-show="vm.profile"> |
8 | - <li ng-show="vm.parentId"> | |
8 | + <li> | |
9 | 9 | <a href="#" ui-sref="main.cms({profile: vm.profile.identifier, parent_id: vm.parentId})"> |
10 | 10 | <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}} |
11 | 11 | </a> |
... | ... | @@ -17,6 +17,4 @@ |
17 | 17 | </li> |
18 | 18 | </ul> |
19 | 19 | </li> |
20 | - | |
21 | 20 | </ul> |
22 | - | ... | ... |
src/app/article/types/blog/blog.component.ts
src/app/article/types/blog/blog.html
... | ... | @@ -17,8 +17,8 @@ |
17 | 17 | </div> |
18 | 18 | </div> |
19 | 19 | |
20 | - <pagination ng-model="ctrl.currentPage" total-items="ctrl.totalPosts" class="pagination-sm center-block" | |
20 | + <uib-pagination ng-model="ctrl.currentPage" total-items="ctrl.totalPosts" class="pagination-sm center-block" | |
21 | 21 | boundary-links="true" items-per-page="ctrl.perPage" ng-change="ctrl.loadPage()" |
22 | 22 | first-text="«" last-text="»" previous-text="‹" next-text="›"> |
23 | - </pagination> | |
23 | + </uib-pagination> | |
24 | 24 | </div> | ... | ... |
src/app/environment/environment-home.component.ts
... | ... | @@ -22,7 +22,7 @@ export class EnvironmentHomeComponent { |
22 | 22 | environment: noosfero.Environment; |
23 | 23 | |
24 | 24 | constructor(private environmentService: EnvironmentService, private $sce: ng.ISCEService) { |
25 | - environmentService.getByIdentifier("default").then((result: noosfero.Environment) => { | |
25 | + environmentService.get().then((result: noosfero.Environment) => { | |
26 | 26 | this.environment = result; |
27 | 27 | }); |
28 | 28 | } | ... | ... |
src/app/environment/environment.component.spec.ts
... | ... | @@ -9,6 +9,7 @@ describe("Components", () => { |
9 | 9 | let environmentServiceMock: any; |
10 | 10 | let notificationMock: any; |
11 | 11 | let $state: any; |
12 | + let defaultEnvironment = <any> {id: 1, name: 'Noosfero' }; | |
12 | 13 | |
13 | 14 | beforeEach(inject((_$rootScope_: ng.IRootScopeService, _$q_: ng.IQService) => { |
14 | 15 | $rootScope = _$rootScope_; |
... | ... | @@ -17,44 +18,40 @@ describe("Components", () => { |
17 | 18 | |
18 | 19 | beforeEach(() => { |
19 | 20 | $state = jasmine.createSpyObj("$state", ["transitionTo"]); |
20 | - environmentServiceMock = jasmine.createSpyObj("environmentServiceMock", ["getByIdentifier", "getBoxes"]); | |
21 | + environmentServiceMock = jasmine.createSpyObj("environmentServiceMock", ["get", "getBoxes"]); | |
21 | 22 | notificationMock = jasmine.createSpyObj("notificationMock", ["error"]); |
22 | 23 | |
23 | - let environmentResponse = $q.defer(); | |
24 | - environmentResponse.resolve({ id: 1 }); | |
25 | 24 | let getBoxesResponse = $q.defer(); |
26 | 25 | getBoxesResponse.resolve({ data: { boxes: [{ id: 2 }] } }); |
27 | 26 | |
28 | - environmentServiceMock.getByIdentifier = jasmine.createSpy('getByIdentifier').and.returnValue(environmentResponse.promise); | |
29 | 27 | environmentServiceMock.getBoxes = jasmine.createSpy("getBoxes").and.returnValue(getBoxesResponse.promise); |
30 | 28 | }); |
31 | 29 | |
32 | 30 | it("get the default environment", done => { |
33 | - let component: EnvironmentComponent = new EnvironmentComponent(environmentServiceMock, $state, notificationMock); | |
31 | + let component: EnvironmentComponent = new EnvironmentComponent(environmentServiceMock, $state, notificationMock, defaultEnvironment); | |
34 | 32 | $rootScope.$apply(); |
35 | - expect(component.environment).toEqual({ id: 1 }); | |
33 | + expect(component.environment).toEqual({ id: 1, name: 'Noosfero' }); | |
36 | 34 | done(); |
37 | 35 | }); |
38 | 36 | |
39 | 37 | it("get the environment boxes", done => { |
40 | - let component: EnvironmentComponent = new EnvironmentComponent(environmentServiceMock, $state, notificationMock); | |
38 | + let component: EnvironmentComponent = new EnvironmentComponent(environmentServiceMock, $state, notificationMock, defaultEnvironment); | |
41 | 39 | $rootScope.$apply(); |
42 | 40 | expect(environmentServiceMock.getBoxes).toHaveBeenCalled(); |
43 | 41 | expect(component.boxes).toEqual({ data: { boxes: [{ id: 2 }] } }); |
44 | 42 | done(); |
45 | 43 | }); |
46 | 44 | |
47 | - it("display notification error when the environment wasn't found", done => { | |
45 | + it("display notification error when does not find boxes to the environment", done => { | |
48 | 46 | let environmentResponse = $q.defer(); |
49 | 47 | environmentResponse.reject(); |
50 | 48 | |
51 | - environmentServiceMock.getByIdentifier = jasmine.createSpy('getByIdentifier').and.returnValue(environmentResponse.promise); | |
49 | + environmentServiceMock.getBoxes = jasmine.createSpy('getBoxes').and.returnValue(environmentResponse.promise); | |
52 | 50 | |
53 | - let component: EnvironmentComponent = new EnvironmentComponent(environmentServiceMock, $state, notificationMock); | |
51 | + let component: EnvironmentComponent = new EnvironmentComponent(environmentServiceMock, $state, notificationMock, defaultEnvironment); | |
54 | 52 | $rootScope.$apply(); |
55 | 53 | |
56 | 54 | expect(notificationMock.error).toHaveBeenCalled(); |
57 | - expect(component.environment).toBeUndefined(); | |
58 | 55 | done(); |
59 | 56 | }); |
60 | 57 | ... | ... |
src/app/environment/environment.component.ts
... | ... | @@ -2,6 +2,7 @@ import {StateConfig, Component, Inject, provide} from 'ng-forward'; |
2 | 2 | import {EnvironmentService} from "../../lib/ng-noosfero-api/http/environment.service"; |
3 | 3 | import {NotificationService} from "../shared/services/notification.service"; |
4 | 4 | import {EnvironmentHomeComponent} from "./environment-home.component"; |
5 | +import {SearchComponent} from "../search/search.component"; | |
5 | 6 | |
6 | 7 | /** |
7 | 8 | * @ngdoc controller |
... | ... | @@ -29,23 +30,37 @@ import {EnvironmentHomeComponent} from "./environment-home.component"; |
29 | 30 | controllerAs: "vm" |
30 | 31 | } |
31 | 32 | } |
33 | + }, | |
34 | + { | |
35 | + url: '^/search?query', | |
36 | + component: SearchComponent, | |
37 | + name: 'main.environment.search', | |
38 | + views: { | |
39 | + "mainBlockContent": { | |
40 | + templateUrl: "app/search/search.html", | |
41 | + controller: SearchComponent, | |
42 | + controllerAs: "ctrl" | |
43 | + } | |
44 | + } | |
32 | 45 | } |
33 | 46 | ]) |
34 | -@Inject(EnvironmentService, "$state") | |
47 | +@Inject(EnvironmentService, "$state", "currentEnvironment") | |
35 | 48 | export class EnvironmentComponent { |
36 | 49 | |
37 | 50 | boxes: noosfero.Box[]; |
38 | 51 | environment: noosfero.Environment; |
39 | 52 | |
40 | - constructor(environmentService: EnvironmentService, $state: ng.ui.IStateService, notificationService: NotificationService) { | |
41 | - let boxesPromisse = environmentService.getByIdentifier("default").then((environment: noosfero.Environment) => { | |
42 | - this.environment = environment; | |
43 | - return environmentService.getBoxes(this.environment.id); | |
44 | - }).then((boxes: noosfero.Box[]) => { | |
45 | - this.boxes = boxes; | |
46 | - }).catch(() => { | |
47 | - $state.transitionTo('main'); | |
48 | - notificationService.error({ message: "notification.environment.not_found" }); | |
49 | - }); | |
53 | + constructor(private environmentService: EnvironmentService, private $state: ng.ui.IStateService, private notificationService: NotificationService, currentEnvironment: noosfero.Environment) { | |
54 | + this.environment = currentEnvironment; | |
55 | + | |
56 | + this.environmentService.getBoxes(this.environment.id) | |
57 | + .then((boxes: noosfero.Box[]) => { | |
58 | + this.boxes = boxes; | |
59 | + }).catch(() => { | |
60 | + this.$state.transitionTo('main'); | |
61 | + this.notificationService.error({ message: "notification.environment.not_found" }); | |
62 | + }); | |
63 | + | |
50 | 64 | } |
65 | + | |
51 | 66 | } | ... | ... |
src/app/index.module.ts
... | ... | @@ -1,97 +0,0 @@ |
1 | - | |
2 | -/** | |
3 | - * @ngdoc service | |
4 | - * @name NoosferoApp | |
5 | - * @description | |
6 | - * The main NoosferoApp module class. It provide helper static methods used by | |
7 | - * the module to initialize the application. | |
8 | - */ | |
9 | -export class NoosferoApp { | |
10 | - | |
11 | - /** | |
12 | - * @ngdoc property | |
13 | - * @name appName | |
14 | - * @propertyOf NoosferoApp | |
15 | - * @returns {string} the name of this application ('noosferoApp') | |
16 | - */ | |
17 | - static appName: string = "noosferoApp"; | |
18 | - | |
19 | - /** | |
20 | - * @ngdoc property | |
21 | - * @name angularModule | |
22 | - * @propertyOf NoosferoApp | |
23 | - * @returns {any} all the modules installed for this application | |
24 | - */ | |
25 | - static angularModule: any; | |
26 | - | |
27 | - /** | |
28 | - * @ngdoc method | |
29 | - * @name addConfig | |
30 | - * @methodOf NoosferoApp | |
31 | - * @param {Function} configFunc the configuration function to add | |
32 | - * @descprition adds a configuration function to | |
33 | - * the {@link NoosferoApp#angularModule} | |
34 | - */ | |
35 | - static addConfig(configFunc: Function) { | |
36 | - NoosferoApp.angularModule.config(configFunc); | |
37 | - } | |
38 | - | |
39 | - /** | |
40 | - * @ngdoc method | |
41 | - * @name addConstants | |
42 | - * @methodOf NoosferoApp | |
43 | - * @param {string} constantName the constant name | |
44 | - * @param {any} value the constant value | |
45 | - * @description adds a constant to the {@link NoosferoApp#angularModule} | |
46 | - */ | |
47 | - static addConstants(constantName: string, value: any) { | |
48 | - NoosferoApp.angularModule.constant(constantName, value); | |
49 | - } | |
50 | - | |
51 | - /** | |
52 | - * @ngdoc method | |
53 | - * @name addService | |
54 | - * @methodOf NoosferoApp | |
55 | - * @param {string} serviceName the service name | |
56 | - * @param {any} value the service value | |
57 | - * @description adds a service to the {@link NoosferoApp#angularModule} | |
58 | - */ | |
59 | - static addService(serviceName: string, value: any) { | |
60 | - NoosferoApp.angularModule.service(serviceName, value); | |
61 | - } | |
62 | - | |
63 | - /** | |
64 | - * @ngdoc method | |
65 | - * @name addFactory | |
66 | - * @methodOf NoosferoApp | |
67 | - * @param {string} factoryName the factory name | |
68 | - * @param {any} value the factory value | |
69 | - * @description adds a factory to the {@link NoosferoApp#angularModule} | |
70 | - */ | |
71 | - static addFactory(factoryName: string, value: any) { | |
72 | - NoosferoApp.angularModule.factory(factoryName, value); | |
73 | - } | |
74 | - | |
75 | - /** | |
76 | - * @ngdoc method | |
77 | - * @name addController | |
78 | - * @methodOf NoosferoApp | |
79 | - * @param {string} controllerName the controller name | |
80 | - * @param {any} value the controller value | |
81 | - * @description adds a controller to the {@link NoosferoApp#angularModule} | |
82 | - */ | |
83 | - static addController(controllerName: string, value: any) { | |
84 | - NoosferoApp.angularModule.controller(controllerName, value); | |
85 | - } | |
86 | - | |
87 | - /** | |
88 | - * @ngdoc method | |
89 | - * @name run | |
90 | - * @methodOf NoosferoApp | |
91 | - * @param {Function} runFunction the function to execute | |
92 | - * @description runs a function using the {@link NoosferoApp#angularModule} | |
93 | - */ | |
94 | - static run(runFunction: Function) { | |
95 | - NoosferoApp.angularModule.run(runFunction); | |
96 | - } | |
97 | -} |
src/app/index.ts
1 | -import {NoosferoApp} from "./index.module"; | |
1 | +import {bootstrap} from "ng-forward"; | |
2 | 2 | import {noosferoModuleConfig} from "./index.config"; |
3 | 3 | import {noosferoAngularRunBlock} from "./index.run"; |
4 | - | |
5 | 4 | import {MainComponent} from "./main/main.component"; |
6 | -import {bootstrap, bundle} from "ng-forward"; | |
7 | - | |
8 | 5 | import {AuthEvents} from "./login/auth-events"; |
9 | -import {AuthController} from "./login/auth.controller"; | |
10 | - | |
11 | -import {Navbar} from "./layout/navbar/navbar"; | |
12 | 6 | |
13 | 7 | declare var moment: any; |
14 | 8 | |
15 | -let noosferoApp: any = bundle("noosferoApp", MainComponent, ["ngAnimate", "ngCookies", "ngStorage", "ngTouch", | |
16 | - "ngSanitize", "ngMessages", "ngAria", "restangular", | |
17 | - "ui.router", "ui.bootstrap", "toastr", "ngCkeditor", | |
18 | - "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid", | |
19 | - "angular-timeline", "duScroll", "oitozero.ngSweetAlert", | |
20 | - "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad", | |
21 | - "angular-click-outside"]).publish(); | |
22 | - | |
23 | -NoosferoApp.angularModule = noosferoApp; | |
24 | - | |
25 | - | |
26 | -NoosferoApp.addConstants("moment", moment); | |
27 | -NoosferoApp.addConstants("AuthEvents", AuthEvents); | |
28 | - | |
29 | -NoosferoApp.addConfig(noosferoModuleConfig); | |
30 | -NoosferoApp.run(noosferoAngularRunBlock); | |
9 | +// FIXME see a better way to declare template modules for dev mode | |
10 | +try { | |
11 | + angular.module('noosfero.templates.app'); | |
12 | +} catch (error) { | |
13 | + angular.module('noosfero.templates.app', []); | |
14 | +} | |
15 | +try { | |
16 | + angular.module('noosfero.templates.plugins'); | |
17 | +} catch (error) { | |
18 | + angular.module('noosfero.templates.plugins', []); | |
19 | +} | |
20 | +angular.module('noosfero.init', ['noosfero.templates.app', 'noosfero.templates.plugins']). | |
21 | + config(noosferoModuleConfig). | |
22 | + run(noosferoAngularRunBlock). | |
23 | + constant("moment", moment). | |
24 | + constant("AuthEvents", AuthEvents); | |
25 | +bootstrap(MainComponent); | ... | ... |
src/app/layout/blocks/main/main-block.component.spec.ts
1 | 1 | import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder'; |
2 | 2 | import {Input, provide, Component, StateConfig} from 'ng-forward'; |
3 | - | |
4 | 3 | import {MainBlockComponent} from './main-block.component'; |
5 | -import {NoosferoApp} from '../../../index.module'; | |
4 | + | |
6 | 5 | |
7 | 6 | const tcb = new TestComponentBuilder(); |
8 | 7 | |
... | ... | @@ -37,4 +36,4 @@ describe("Components", () => { |
37 | 36 | }); |
38 | 37 | }); |
39 | 38 | }); |
40 | -}); | |
41 | 39 | \ No newline at end of file |
40 | +}); | ... | ... |
src/app/layout/navbar/navbar.html
1 | 1 | <nav class="navbar navbar-static-top navbar-inverse"> |
2 | 2 | <div class="container-fluid"> |
3 | 3 | <div class="navbar-header"> |
4 | - <button type="button" class="navbar-toggle collapsed" (click)="ctrl.toggleCollapse()" ng-show="ctrl.showHamburguer"> | |
4 | + <button type="button" class="navbar-toggle collapsed" (click)="ctrl.toggleCollapse()" ng-show="ctrl.showHamburger"> | |
5 | 5 | <span class="sr-only">{{"navbar.toggle_menu" | translate}}</span> |
6 | 6 | <i class="fa fa-bars"></i> |
7 | 7 | </button> |
8 | 8 | <a class="navbar-brand" ui-sref="main.environment.home"> |
9 | 9 | <span class="noosfero-logo"> </span> |
10 | - <span class="noosfero-name">{{"noosfero.name" | translate}}</span> | |
10 | + <span class="noosfero-name">{{ ctrl.currentEnvironment.name }}</span> | |
11 | 11 | </a> |
12 | 12 | </div> |
13 | 13 | |
... | ... | @@ -26,23 +26,29 @@ |
26 | 26 | <span ng-bind="ctrl.currentUser.person.name"></span> <b class="caret"></b> |
27 | 27 | </a> |
28 | 28 | <ul class="dropdown-menu" uib-dropdown-menu> |
29 | - <li> | |
30 | - <a ui-sref="main.profile.info({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-user"></i> {{"navbar.profile" | translate}}</a> | |
31 | - </li> | |
32 | - <li> | |
33 | - <a target="_self" ui-sref="main.profile.settings({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-gear"></i> {{"navbar.settings" | translate}}</a> | |
34 | - </li> | |
35 | - <li class="divider"></li> | |
36 | - <li> | |
37 | - <a href="#" ng-click="ctrl.logout()"><i class="fa fa-fw fa-power-off"></i> {{"navbar.logout" | translate}}</a> | |
38 | - </li> | |
29 | + <li> | |
30 | + <a ui-sref="main.profile.info({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-user"></i> {{"navbar.profile" | translate}}</a> | |
31 | + </li> | |
32 | + <li> | |
33 | + <a target="_self" ui-sref="main.profile.settings({profile: ctrl.currentUser.person.identifier})"><i class="fa fa-fw fa-gear"></i> {{"navbar.settings" | translate}}</a> | |
34 | + </li> | |
35 | + <li class="divider"></li> | |
36 | + <li> | |
37 | + <a href="#" ng-click="ctrl.logout()"><i class="fa fa-fw fa-power-off"></i> {{"navbar.logout" | translate}}</a> | |
38 | + </li> | |
39 | 39 | </ul> |
40 | 40 | </li> |
41 | 41 | </ul> |
42 | + | |
42 | 43 | <ul class="nav navbar-nav navbar-right"> |
43 | 44 | <language-selector class="nav navbar-nav navbar-right"></language-selector> |
44 | 45 | </ul> |
45 | 46 | <div ui-view="actions"></div> |
47 | + <div class="nav navbar-nav search navbar-right"> | |
48 | + <search-form></search-form> | |
49 | + </div> | |
46 | 50 | </div> |
47 | 51 | </div> |
48 | 52 | </nav> |
53 | +<div ui-view="toolbar"> | |
54 | +</div> | |
49 | 55 | \ No newline at end of file | ... | ... |
src/app/layout/navbar/navbar.scss
... | ... | @@ -34,6 +34,16 @@ |
34 | 34 | text-align: center; |
35 | 35 | } |
36 | 36 | |
37 | + .search { | |
38 | + .search-input { | |
39 | + height: 45px; | |
40 | + margin-top: 10px; | |
41 | + } | |
42 | + .input-group-btn { | |
43 | + padding-top: 10px; | |
44 | + } | |
45 | + } | |
46 | + | |
37 | 47 | @media (max-width: $break-sm-max) { |
38 | 48 | .navbar-toggle .fa-bars{ |
39 | 49 | font-size: 14pt; | ... | ... |
src/app/layout/navbar/navbar.spec.ts
... | ... | @@ -69,6 +69,11 @@ describe("Components", () => { |
69 | 69 | AuthEvents |
70 | 70 | } |
71 | 71 | }), |
72 | + provide('EnvironmentService', { | |
73 | + useValue: { | |
74 | + getCurrentEnvironment: () => { return { id: 1, name: 'Nosofero' }; } | |
75 | + } | |
76 | + }), | |
72 | 77 | provide('TranslatorService', { |
73 | 78 | useValue: helpers.mocks.translatorService |
74 | 79 | }) | ... | ... |
src/app/layout/navbar/navbar.ts
1 | 1 | import {Component, Inject, EventEmitter, Input} from "ng-forward"; |
2 | 2 | import {LanguageSelectorComponent} from "../language-selector/language-selector.component"; |
3 | 3 | import {SessionService, AuthService, AuthController, AuthEvents} from "./../../login"; |
4 | +import {EnvironmentService} from "./../../../lib/ng-noosfero-api/http/environment.service"; | |
4 | 5 | import {SidebarNotificationService} from "../sidebar/sidebar.notification.service"; |
5 | 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'; | |
6 | 9 | |
7 | 10 | @Component({ |
8 | 11 | selector: "acme-navbar", |
9 | 12 | templateUrl: "app/layout/navbar/navbar.html", |
10 | - directives: [LanguageSelectorComponent], | |
11 | - providers: [AuthService, SessionService, SidebarNotificationService] | |
13 | + directives: [LanguageSelectorComponent, DesignModeTogglerComponent, BootstrapSwitcherComponent], | |
14 | + providers: [AuthService, SessionService, SidebarNotificationService, EnvironmentService] | |
12 | 15 | }) |
13 | -@Inject("$uibModal", AuthService, "SessionService", "$state", SidebarNotificationService, BodyStateClassesService) | |
16 | +@Inject("$uibModal", AuthService, "SessionService", "$state", SidebarNotificationService, BodyStateClassesService, EnvironmentService) | |
14 | 17 | export class Navbar { |
15 | 18 | |
16 | 19 | private currentUser: noosfero.User; |
17 | 20 | private modalInstance: any = null; |
18 | - | |
19 | - public showHamburguer: boolean = false; | |
20 | - | |
21 | + public showHamburger: boolean = false; | |
22 | + public currentEnvironment: noosfero.Environment = <any>{ name: '' }; | |
21 | 23 | /** |
22 | 24 | * |
23 | 25 | */ |
... | ... | @@ -27,11 +29,13 @@ export class Navbar { |
27 | 29 | private session: SessionService, |
28 | 30 | private $state: ng.ui.IStateService, |
29 | 31 | private sidebarNotificationService: SidebarNotificationService, |
30 | - private bodyStateService: BodyStateClassesService | |
32 | + private bodyStateService: BodyStateClassesService, | |
33 | + private environmentService: EnvironmentService | |
31 | 34 | ) { |
32 | 35 | this.currentUser = this.session.currentUser(); |
36 | + this.currentEnvironment = environmentService.getCurrentEnvironment(); | |
33 | 37 | |
34 | - this.showHamburguer = this.authService.isAuthenticated(); | |
38 | + this.showHamburger = this.authService.isAuthenticated(); | |
35 | 39 | this.bodyStateService.addContentClass(!this.sidebarNotificationService.sidebarVisible); |
36 | 40 | |
37 | 41 | this.authService.subscribe(AuthEvents[AuthEvents.loginSuccess], () => { |
... | ... | @@ -41,7 +45,7 @@ export class Navbar { |
41 | 45 | } |
42 | 46 | |
43 | 47 | this.currentUser = this.session.currentUser(); |
44 | - this.showHamburguer = true; | |
48 | + this.showHamburger = true; | |
45 | 49 | |
46 | 50 | this.$state.go(this.$state.current, {}, { reload: true }); // TODO move to auth |
47 | 51 | }); | ... | ... |
src/app/layout/scss/_layout.scss
... | ... | @@ -35,3 +35,24 @@ |
35 | 35 | padding: 0 20px 20px 20px; |
36 | 36 | } |
37 | 37 | } |
38 | + | |
39 | + | |
40 | +body.noosfero-design-on { | |
41 | + | |
42 | + div.content-wrapper { | |
43 | + opacity: 0.5; | |
44 | + } | |
45 | +} | |
46 | + | |
47 | +ul.nav > li.toggler-container { | |
48 | + position: relative; | |
49 | + padding-top: 12px; | |
50 | +} | |
51 | + | |
52 | +.noosfero-main-toolbar { | |
53 | + padding: 5px; | |
54 | + @include make-row(); | |
55 | + margin-left: 0px; | |
56 | + margin-right: 0px; | |
57 | + background-color: #edecec; | |
58 | +} | |
38 | 59 | \ No newline at end of file | ... | ... |
src/app/layout/scss/skins/_whbl.scss
... | ... | @@ -3,7 +3,7 @@ $whbl-sidebar-linkColor: #484848; |
3 | 3 | $whbl-primary-color: #1E96D0; //blue |
4 | 4 | $whbl-font-color: #16191c; |
5 | 5 | |
6 | -.skin-whbl { | |
6 | +%skin-base { | |
7 | 7 | #header-navbar { |
8 | 8 | background-color: $whbl-primary-color; |
9 | 9 | } |
... | ... | @@ -180,27 +180,19 @@ $whbl-font-color: #16191c; |
180 | 180 | } |
181 | 181 | .pagination { |
182 | 182 | > li { |
183 | - > a, | |
184 | - > span, | |
185 | - > a:hover, | |
186 | - > span:hover, | |
187 | - > a:focus, | |
188 | - > span:focus, | |
189 | - > a:active, | |
190 | - > span:active { | |
191 | - color: $whbl-primary-color; | |
183 | + &.active { | |
184 | + a { | |
185 | + background-color: $whbl-primary-color; | |
186 | + color: #FFF; | |
187 | + } | |
192 | 188 | } |
193 | - } | |
194 | - > .active { | |
195 | - > a, | |
196 | - > span, | |
197 | - > a:hover, | |
198 | - > span:hover, | |
199 | - > a:focus, | |
200 | - > span:focus { | |
201 | - background-color: $whbl-primary-color; | |
202 | - border-color: $whbl-primary-color; | |
203 | - color: #fff; | |
189 | + > a { | |
190 | + &:hover { | |
191 | + background-color: $whbl-primary-color; | |
192 | + color: #FFF; | |
193 | + } | |
194 | + background-color: #FFF; | |
195 | + color: $whbl-primary-color; | |
204 | 196 | } |
205 | 197 | } |
206 | 198 | } |
... | ... | @@ -289,6 +281,31 @@ $whbl-font-color: #16191c; |
289 | 281 | .pace .pace-progress { |
290 | 282 | background-color: #fff; |
291 | 283 | } |
284 | + | |
285 | + noosfero-design-toggler .ats-switch .knob i { | |
286 | + color: #999999; | |
287 | + } | |
288 | + | |
289 | + .ats-switch .knob { | |
290 | + padding-right: 15px; | |
291 | + } | |
292 | + | |
293 | + .ats-switch .glyphicon { | |
294 | + top: 2px; | |
295 | + } | |
296 | + | |
297 | + .ats-switch .switch-left { | |
298 | + background-color: #41b941; | |
299 | + font-weight: bold; | |
300 | + color: white; | |
301 | + } | |
302 | + | |
303 | + .ats-switch .switch-right { | |
304 | + background-color: #ce3b3b; | |
305 | + font-weight: bold; | |
306 | + color: white; | |
307 | + } | |
308 | + | |
292 | 309 | } |
293 | 310 | .rtl.skin-whbl #content-wrapper { |
294 | 311 | border-left: 0; |
... | ... | @@ -296,7 +313,7 @@ $whbl-font-color: #16191c; |
296 | 313 | } |
297 | 314 | |
298 | 315 | @media (max-width: $break-sm-max) { |
299 | - .skin-whbl { | |
316 | + %skin-base { | |
300 | 317 | #logo.navbar-brand > img.normal-logo.logo-white { |
301 | 318 | display: block; |
302 | 319 | } |
... | ... | @@ -308,3 +325,7 @@ $whbl-font-color: #16191c; |
308 | 325 | } |
309 | 326 | } |
310 | 327 | } |
328 | + | |
329 | +.skin-whbl { | |
330 | + @extend %skin-base; | |
331 | +} | ... | ... |
src/app/layout/services/body-state-classes.service.spec.ts
... | ... | @@ -4,7 +4,7 @@ import {AuthService} from "./../../login/auth.service"; |
4 | 4 | import {AuthEvents} from "./../../login/auth-events"; |
5 | 5 | |
6 | 6 | import {EventEmitter} from 'ng-forward'; |
7 | - | |
7 | +import {DesignModeService} from './../../admin/layout-edit/designMode.service'; | |
8 | 8 | |
9 | 9 | describe("BodyStateClasses Service", () => { |
10 | 10 | |
... | ... | @@ -19,10 +19,13 @@ describe("BodyStateClasses Service", () => { |
19 | 19 | }, |
20 | 20 | authService: any = helpers.mocks.authService, |
21 | 21 | bodyEl: { className: string }, |
22 | - bodyElJq: any; | |
22 | + bodyElJq: any, | |
23 | + designModeService = new DesignModeService(); | |
24 | + | |
25 | + | |
23 | 26 | |
24 | 27 | let getService = (): BodyStateClassesService => { |
25 | - return new BodyStateClassesService($rootScope, $document, $state, authService); | |
28 | + return new BodyStateClassesService($rootScope, $document, $state, authService, designModeService); | |
26 | 29 | }; |
27 | 30 | |
28 | 31 | beforeEach(() => { |
... | ... | @@ -168,4 +171,30 @@ describe("BodyStateClasses Service", () => { |
168 | 171 | |
169 | 172 | expect(contentWrapperMock.removeClass).toHaveBeenCalledWith(BodyStateClassesService.CONTENT_WRAPPER_FULL); |
170 | 173 | }); |
174 | + | |
175 | + it("should add the class noosfero-design-on when designMode is changed to true", () => { | |
176 | + let fnOnToggle: Function = null; | |
177 | + designModeService.onToggle = <any> { | |
178 | + subscribe: (fn: Function) => { | |
179 | + fnOnToggle = fn; | |
180 | + }, | |
181 | + next: (value: boolean) => { | |
182 | + fnOnToggle.apply(designModeService, [value]); | |
183 | + } | |
184 | + }; | |
185 | + | |
186 | + let service = getService(); | |
187 | + | |
188 | + bodyElJq.addClass = jasmine.createSpy("addClass"); | |
189 | + bodyElJq.removeClass = jasmine.createSpy("removeClass"); | |
190 | + service["bodyElement"] = bodyElJq; | |
191 | + | |
192 | + service.start(); | |
193 | + | |
194 | + debugger; | |
195 | + designModeService.setInDesignMode(true); | |
196 | + | |
197 | + | |
198 | + expect(bodyElJq.addClass).toHaveBeenCalledWith(BodyStateClassesService.DESIGN_MODE_ON_CLASSNAME); | |
199 | + }); | |
171 | 200 | }); | ... | ... |
src/app/layout/services/body-state-classes.service.ts
... | ... | @@ -3,6 +3,7 @@ import {AuthEvents} from "../../login/auth-events"; |
3 | 3 | import {AuthService} from "./../../login/auth.service"; |
4 | 4 | import {HtmlUtils} from "../html-utils"; |
5 | 5 | import {INgForwardJQuery} from 'ng-forward/cjs/util/jqlite-extensions'; |
6 | +import {DesignModeService} from './../../admin/layout-edit/designMode.service'; | |
6 | 7 | |
7 | 8 | export interface StartParams { |
8 | 9 | skin?: string; |
... | ... | @@ -22,12 +23,13 @@ export interface StartParams { |
22 | 23 | * - full-content |
23 | 24 | */ |
24 | 25 | @Injectable() |
25 | -@Inject("$rootScope", "$document", "$state", AuthService) | |
26 | +@Inject("$rootScope", "$document", "$state", AuthService, DesignModeService) | |
26 | 27 | export class BodyStateClassesService { |
27 | 28 | |
28 | 29 | private started: boolean = false; |
29 | 30 | private skin: string; |
30 | 31 | |
32 | + public static get DESIGN_MODE_ON_CLASSNAME(): string { return "noosfero-design-on"; } | |
31 | 33 | public static get USER_LOGGED_CLASSNAME(): string { return "noosfero-user-logged"; } |
32 | 34 | public static get ROUTE_STATE_CLASSNAME_PREFIX(): string { return "noosfero-route-"; } |
33 | 35 | public static get CONTENT_WRAPPER_FULL(): string { return "full-content"; } |
... | ... | @@ -38,16 +40,16 @@ export class BodyStateClassesService { |
38 | 40 | private $rootScope: ng.IRootScopeService, |
39 | 41 | private $document: ng.IDocumentService, |
40 | 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 | 48 | start(config?: StartParams) { |
47 | 49 | if (!this.started) { |
48 | 50 | this.setupUserLoggedClassToggle(); |
49 | 51 | this.setupStateClassToggle(); |
50 | - | |
52 | + this.setupDesignModeClassToggle(); | |
51 | 53 | if (config) { |
52 | 54 | this.setThemeSkin(config.skin); |
53 | 55 | } |
... | ... | @@ -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 | 105 | private setupStateClassToggle() { |
94 | 106 | let bodyElement = this.getBodyElement(); |
95 | 107 | bodyElement.addClass(BodyStateClassesService.ROUTE_STATE_CLASSNAME_PREFIX + this.$state.current.name); | ... | ... |
src/app/login/auth.service.spec.ts
... | ... | @@ -17,7 +17,7 @@ describe("Services", () => { |
17 | 17 | let $rootScope: ng.IRootScopeService; |
18 | 18 | let user: noosfero.User; |
19 | 19 | |
20 | - beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => { | |
20 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | |
21 | 21 | $translateProvider.translations('en', {}); |
22 | 22 | })); |
23 | 23 | ... | ... |
... | ... | @@ -0,0 +1,86 @@ |
1 | +import { provide, Component, componentStore, bundleStore } from "ng-forward"; | |
2 | +import {MainComponent} from "./main.component"; | |
3 | +import {TestComponentBuilder, ComponentFixture} from "ng-forward/cjs/testing/test-component-builder"; | |
4 | + | |
5 | +import {quickCreateComponent} from "../../spec/helpers"; | |
6 | +import {getAngularServiceFactory} from "../../spec/helpers"; | |
7 | + | |
8 | +describe("MainComponent", function() { | |
9 | + | |
10 | + let localFixture: ComponentFixture; | |
11 | + let $state: angular.ui.IStateService; | |
12 | + let $q: ng.IQService; | |
13 | + let $httpBackend: ng.IHttpBackendService; | |
14 | + let authService: any = jasmine.createSpyObj("authService", ["loginFromCookie", "isAuthenticated", "subscribe"]); | |
15 | + let environmentService: any = jasmine.createSpyObj("environmentService", ["get", "getCurrentEnvironment"]); | |
16 | + | |
17 | + beforeEach(angular.mock.module("ui.router")); | |
18 | + beforeEach(angular.mock.module("templates")); | |
19 | + beforeEach(angular.mock.module("pascalprecht.translate", ($translateProvider: angular.translate.ITranslateProvider) => { | |
20 | + $translateProvider.translations('en', {}); | |
21 | + })); | |
22 | + | |
23 | + @Component({ | |
24 | + selector: "parent", | |
25 | + template: "<main></main>", | |
26 | + directives: [MainComponent], | |
27 | + providers: [ | |
28 | + provide("AuthService", | |
29 | + { | |
30 | + useValue: authService | |
31 | + }), | |
32 | + | |
33 | + provide("EnvironmentService", | |
34 | + { | |
35 | + useValue: environmentService | |
36 | + }), | |
37 | + ] | |
38 | + }) | |
39 | + class MainComponentParent { | |
40 | + constructor() { | |
41 | + | |
42 | + } | |
43 | + } | |
44 | + | |
45 | + beforeEach(() => { | |
46 | + authService.loginFromCookie = jasmine.createSpy("loginFromCookie").and.returnValue({ id: 1, name: "user1" }); | |
47 | + environmentService.get = jasmine.createSpy("get").and.returnValue({ id: 1, name: "Noosfero Default Environment" }); | |
48 | + }); | |
49 | + | |
50 | + it("renders the main component only when the login and environment were resolved", (done) => { | |
51 | + quickCreateComponent({ directives: [MainComponentParent], template: "<parent></parent>" }) | |
52 | + .then((fixture) => { | |
53 | + fixture.debugElement.getLocal("$httpBackend").expectGET("/api/v1/environments/1/boxes").respond(200, {}); | |
54 | + localFixture = fixture; | |
55 | + // get the $state service to navigate between routes | |
56 | + $state = fixture.debugElement.getLocal("$state"); | |
57 | + // navigates to the environment home | |
58 | + $state.go("main.environment.home"); | |
59 | + localFixture.detectChanges(); | |
60 | + // after changes were detected it checks the current $state route | |
61 | + expect($state.current.name).toEqual("main.environment.home"); | |
62 | + done(); | |
63 | + }); | |
64 | + | |
65 | + }); | |
66 | + | |
67 | + it("does not render the main component when get error loading the environment", (done) => { | |
68 | + quickCreateComponent({ directives: [MainComponentParent], template: "<parent></parent>" }) | |
69 | + .then((fixture) => { | |
70 | + localFixture = fixture; | |
71 | + // get the $state service to navigate between routes | |
72 | + $state = fixture.debugElement.getLocal("$state"); | |
73 | + // get the $q service to create a rejected promise | |
74 | + $q = fixture.debugElement.getLocal("$q"); | |
75 | + // mock the environmentService to force a rejected promise | |
76 | + environmentService.get = jasmine.createSpy("get").and.returnValue($q.reject("Error simulated")); | |
77 | + // tries to navigate to the environment home | |
78 | + $state.go("main.environment.home"); | |
79 | + localFixture.detectChanges(); | |
80 | + // after the changes were detected the state remains '' because the environment could not be loaded | |
81 | + expect($state.current.name).toEqual(""); | |
82 | + done(); | |
83 | + }); | |
84 | + | |
85 | + }); | |
86 | +}); | ... | ... |
src/app/main/main.component.ts
... | ... | @@ -28,7 +28,7 @@ import {DateFormat} from "../shared/pipes/date-format.filter"; |
28 | 28 | |
29 | 29 | import {AuthService} from "../login/auth.service"; |
30 | 30 | import {SessionService} from "../login/session.service"; |
31 | - | |
31 | +import {EnvironmentService} from "./../../lib/ng-noosfero-api/http/environment.service"; | |
32 | 32 | import {NotificationService} from "../shared/services/notification.service"; |
33 | 33 | |
34 | 34 | import {BodyStateClassesService} from "./../layout/services/body-state-classes.service"; |
... | ... | @@ -39,7 +39,9 @@ import {SidebarComponent} from "../layout/sidebar/sidebar.component"; |
39 | 39 | |
40 | 40 | import {MainBlockComponent} from "../layout/blocks/main/main-block.component"; |
41 | 41 | import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component"; |
42 | - | |
42 | +import {PermissionDirective} from "../shared/components/permission/permission.directive"; | |
43 | +import {SearchComponent} from "../search/search.component"; | |
44 | +import {SearchFormComponent} from "../search/search-form/search-form.component"; | |
43 | 45 | |
44 | 46 | /** |
45 | 47 | * @ngdoc controller |
... | ... | @@ -93,17 +95,23 @@ export class EnvironmentContent { |
93 | 95 | */ |
94 | 96 | @Component({ |
95 | 97 | selector: 'main', |
96 | - template: '<div ng-view></div>', | |
98 | + template: '<ui-view></ui-view>', | |
97 | 99 | directives: [ |
98 | 100 | ArticleBlogComponent, ArticleViewComponent, BoxesComponent, BlockComponent, |
99 | 101 | EnvironmentComponent, PeopleBlockComponent, DisplayContentBlockComponent, |
100 | - LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, | |
102 | + LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, ProfileComponent, | |
101 | 103 | MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, |
102 | 104 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent, |
103 | - LoginBlockComponent, CustomContentComponent | |
105 | + LoginBlockComponent, CustomContentComponent, PermissionDirective, SearchFormComponent, SearchComponent | |
104 | 106 | ].concat(plugins.mainComponents).concat(plugins.hotspots), |
105 | - | |
106 | - providers: [AuthService, SessionService, NotificationService, BodyStateClassesService] | |
107 | + providers: [AuthService, SessionService, NotificationService, BodyStateClassesService, | |
108 | + "ngAnimate", "ngCookies", "ngStorage", "ngTouch", | |
109 | + "ngSanitize", "ngMessages", "ngAria", "restangular", | |
110 | + "ui.router", "ui.bootstrap", "toastr", "ngCkeditor", | |
111 | + "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid", | |
112 | + "angular-timeline", "duScroll", "oitozero.ngSweetAlert", | |
113 | + "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad", | |
114 | + "angular-click-outside", "toggle-switch", "noosfero.init"] | |
107 | 115 | }) |
108 | 116 | @StateConfig([ |
109 | 117 | { |
... | ... | @@ -114,6 +122,9 @@ export class EnvironmentContent { |
114 | 122 | resolve: { |
115 | 123 | currentUser: function(AuthService: AuthService) { |
116 | 124 | return AuthService.loginFromCookie(); |
125 | + }, | |
126 | + currentEnvironment: function(EnvironmentService: EnvironmentService) { | |
127 | + return EnvironmentService.get(); | |
117 | 128 | } |
118 | 129 | } |
119 | 130 | }, |
... | ... | @@ -144,5 +155,4 @@ export class EnvironmentContent { |
144 | 155 | } |
145 | 156 | } |
146 | 157 | ]) |
147 | -export class MainComponent { | |
148 | -} | |
158 | +export class MainComponent { } | ... | ... |
src/app/profile/custom-content/custom-content.component.spec.ts
... | ... | @@ -73,5 +73,16 @@ describe("Components", () => { |
73 | 73 | helper.component.save(); |
74 | 74 | expect(helper.component['notificationService'].success).toHaveBeenCalled(); |
75 | 75 | }); |
76 | + | |
77 | + it("hide button to edit content when user doesn't have the permission", () => { | |
78 | + helper.detectChanges(); | |
79 | + expect(helper.find(".actions").attr('style')).toEqual('display: none; '); | |
80 | + }); | |
81 | + | |
82 | + it("show button to edit content when user has the permission", () => { | |
83 | + (<any>helper.component['profile'])['permissions'] = ['allow_edit']; | |
84 | + helper.detectChanges(); | |
85 | + expect(helper.find(".actions").attr('style')).toEqual(''); | |
86 | + }); | |
76 | 87 | }); |
77 | 88 | }); | ... | ... |
src/app/profile/custom-content/custom-content.component.ts
1 | 1 | import {Component, Input, Inject} from 'ng-forward'; |
2 | 2 | import {ProfileService} from '../../../lib/ng-noosfero-api/http/profile.service'; |
3 | 3 | import {NotificationService} from '../../shared/services/notification.service'; |
4 | +import {PermissionDirective} from '../../shared/components/permission/permission.directive'; | |
4 | 5 | |
5 | 6 | @Component({ |
6 | 7 | selector: 'custom-content', |
7 | 8 | templateUrl: "app/profile/custom-content/custom-content.html", |
9 | + directives: [PermissionDirective] | |
8 | 10 | }) |
9 | 11 | @Inject("$uibModal", "$scope", ProfileService, NotificationService) |
10 | 12 | export class CustomContentComponent { | ... | ... |
src/app/profile/custom-content/custom-content.html
1 | 1 | <div class="custom-content"> |
2 | - <div class="actions"> | |
2 | + <div class="actions" permission="ctrl.profile.permissions" permission-action="allow_edit"> | |
3 | 3 | <button type="submit" class="btn btn-xs btn-default" ng-click="ctrl.openEdit()"><i class="fa fa-edit fa-fw"></i> {{ctrl.label | translate}}</button> |
4 | 4 | </div> |
5 | 5 | <div class="content" ng-bind-html="ctrl.content"></div> | ... | ... |
... | ... | @@ -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 | 10 | import {NotificationService} from "../shared/services/notification.service"; |
11 | 11 | import {MyProfileComponent} from "./myprofile.component"; |
12 | 12 | import {ProfileActionsComponent} from "./profile-actions.component"; |
13 | - | |
13 | +import {ProfileToolbarComponent} from "./profile-toolbar.component"; | |
14 | 14 | /** |
15 | 15 | * @ngdoc controller |
16 | 16 | * @name profile.Profile |
... | ... | @@ -42,6 +42,11 @@ import {ProfileActionsComponent} from "./profile-actions.component"; |
42 | 42 | templateUrl: "app/profile/navbar-actions.html", |
43 | 43 | controller: ProfileActionsComponent, |
44 | 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 | 59 | templateUrl: "app/profile/navbar-actions.html", |
55 | 60 | controller: ProfileActionsComponent, |
56 | 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 | 116 | templateUrl: "app/article/content-viewer/navbar-actions.html", |
107 | 117 | controller: ContentViewerActionsComponent, |
108 | 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/search/search-form/search-form.component.spec.ts
0 → 100644
... | ... | @@ -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"]); | |
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 @@ |
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 @@ |
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,38 @@ |
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 | + | |
16 | + beforeEach(angular.mock.module("templates")); | |
17 | + | |
18 | + beforeEach((done) => { | |
19 | + let cls = createClass({ | |
20 | + template: htmlTemplate, | |
21 | + directives: [SearchComponent], | |
22 | + providers: [ | |
23 | + helpers.createProviderToValue("$stateParams", stateParams), | |
24 | + helpers.createProviderToValue("ArticleService", articleService) | |
25 | + ].concat(helpers.provideFilters("truncateFilter", "stripTagsFilter")) | |
26 | + }); | |
27 | + helper = new ComponentTestHelper<SearchComponent>(cls, done); | |
28 | + }); | |
29 | + | |
30 | + it("load first page with search results", () => { | |
31 | + expect(articleService.search).toHaveBeenCalledWith({ query: 'query', per_page: 10, page: 0 }); | |
32 | + }); | |
33 | + | |
34 | + it("display search results", () => { | |
35 | + expect(helper.all(".result").length).toEqual(1); | |
36 | + }); | |
37 | + }); | |
38 | +}); | ... | ... |
... | ... | @@ -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 @@ |
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 @@ |
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 | 49 | \ No newline at end of file | ... | ... |
src/app/shared/components/bootstrap-switcher/bootstrap-switcher.component.ts
0 → 100644
... | ... | @@ -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 | 52 | \ No newline at end of file | ... | ... |
src/app/shared/components/permission/permission.directive.spec.ts
0 → 100644
... | ... | @@ -0,0 +1,27 @@ |
1 | +import {Input, provide, Component} from 'ng-forward'; | |
2 | +import {PermissionDirective} from "./permission.directive"; | |
3 | +import * as helpers from "../../../../spec/helpers"; | |
4 | + | |
5 | +const htmlTemplate: string = '<div permission="ctrl.permissions" permission-action="action"></div>'; | |
6 | + | |
7 | +describe("Permission directive", () => { | |
8 | + | |
9 | + let element = jasmine.createSpyObj("$element", ["css"]); | |
10 | + let scope = jasmine.createSpyObj("$scope", ["$watch", "$eval"]); | |
11 | + scope.$watch = (param: string, f: Function) => { f(); }; | |
12 | + scope.$eval = (param: any) => { return param; }; | |
13 | + | |
14 | + it("hide element when there is no permission action in the permissions array", (done: Function) => { | |
15 | + let attrs: any = { permission: [], permissionAction: 'action' }; | |
16 | + let directive = new PermissionDirective(<any>attrs, scope, element); | |
17 | + expect(element.css).toHaveBeenCalledWith('display', 'none'); | |
18 | + done(); | |
19 | + }); | |
20 | + | |
21 | + it("show element when the permission action exists in the permissions array", (done: Function) => { | |
22 | + let attrs = { permission: ['action'], permissionAction: 'action' }; | |
23 | + let directive = new PermissionDirective(<any>attrs, scope, element); | |
24 | + expect(element.css).toHaveBeenCalledWith('display', ''); | |
25 | + done(); | |
26 | + }); | |
27 | +}); | ... | ... |
src/app/shared/components/permission/permission.directive.ts
0 → 100644
... | ... | @@ -0,0 +1,20 @@ |
1 | +import {Directive, Inject, Input} from "ng-forward"; | |
2 | + | |
3 | +@Directive({ | |
4 | + selector: '[permission]' | |
5 | +}) | |
6 | +@Inject('$attrs', '$scope', '$element') | |
7 | +export class PermissionDirective { | |
8 | + | |
9 | + constructor($attrs: ng.IAttributes, $scope: ng.IScope, $element: ng.IAugmentedJQuery) { | |
10 | + $scope.$watch($attrs['permission'], () => { | |
11 | + let permissions = $scope.$eval($attrs['permission']); | |
12 | + let permissionAction = $attrs['permissionAction']; | |
13 | + if (!permissions || permissions.indexOf(permissionAction) < 0) { | |
14 | + $element.css("display", "none"); | |
15 | + } else { | |
16 | + $element.css("display", ""); | |
17 | + } | |
18 | + }); | |
19 | + } | |
20 | +} | ... | ... |
src/index.html
1 | 1 | <!doctype html> |
2 | -<html ng-app="noosferoApp"> | |
2 | +<html> | |
3 | 3 | <head> |
4 | 4 | <base href="/"> |
5 | 5 | <meta charset="utf-8"> |
... | ... | @@ -25,11 +25,12 @@ |
25 | 25 | <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> |
26 | 26 | <![endif]--> |
27 | 27 | |
28 | - <div ui-view></div> | |
28 | + <main></main> | |
29 | 29 | |
30 | 30 | <script> |
31 | 31 | CKEDITOR_BASEPATH='/bower_components/ng-ckeditor/libs/ckeditor/'; |
32 | 32 | </script> |
33 | + <script src="/bower_components/ng-ckeditor/libs/ckeditor/ckeditor.js"></script> | |
33 | 34 | |
34 | 35 | <!-- build:js(src) scripts/vendor.js --> |
35 | 36 | <!-- bower:js --> |
... | ... | @@ -38,15 +39,12 @@ |
38 | 39 | <!-- endbuild --> |
39 | 40 | |
40 | 41 | <!-- build:js({.tmp/serve,.tmp/partials,src}) scripts/app.js --> |
41 | - <script src="commons.js"></script> | |
42 | - <script src="vendor.bundle.js"></script> | |
43 | - <script src="noosfero.js"></script> | |
44 | 42 | <!-- inject:partials --> |
45 | 43 | <!-- angular templates will be automatically converted in js and inserted here --> |
46 | 44 | <!-- endinject --> |
45 | + <script src="commons.js"></script> | |
46 | + <script src="vendor.bundle.js"></script> | |
47 | + <script src="noosfero.js"></script> | |
47 | 48 | <!-- endbuild --> |
48 | - | |
49 | - <script src="/bower_components/ng-ckeditor/libs/ckeditor/ckeditor.js"></script> | |
50 | - <script src="/bower_components/ng-ckeditor/ng-ckeditor.min.js"></script> | |
51 | 49 | </body> |
52 | 50 | </html> | ... | ... |
src/languages/en.json
... | ... | @@ -27,7 +27,6 @@ |
27 | 27 | "navbar.content_viewer_actions.new_item": "New Item", |
28 | 28 | "navbar.profile_actions.new_item": "New Item", |
29 | 29 | "navbar.content_viewer_actions.new_post": "New Post", |
30 | - "//TODO": "Create a way to load plugin translatios - Move plugins translations to the plugins translations files", | |
31 | 30 | "navbar.content_viewer_actions.new_discussion": "New Discussion", |
32 | 31 | "navbar.profile_actions.new_discussion": "New Discussion", |
33 | 32 | "notification.error.default.message": "Something went wrong!", |
... | ... | @@ -75,5 +74,11 @@ |
75 | 74 | "profile.content.success.message": "Profile saved!", |
76 | 75 | "custom_content.title": "Edit content", |
77 | 76 | "profile.custom_header.label": "Header", |
78 | - "profile.custom_footer.label": "Footer" | |
77 | + "profile.custom_footer.label": "Footer", | |
78 | + "designMode.label": "In Design", | |
79 | + "designMode.toggle.ON": "ON", | |
80 | + "designMode.toggle.OFF": "OFF", | |
81 | + "search.results.summary": "{results, plural, one{result} other{# results}}", | |
82 | + "search.results.query.label": "Search for:", | |
83 | + "search.results.query.placeholder": "Search" | |
79 | 84 | } | ... | ... |
src/languages/pt.json
... | ... | @@ -25,8 +25,10 @@ |
25 | 25 | "auth.form.password": "Senha", |
26 | 26 | "auth.form.login_button": "Login", |
27 | 27 | "navbar.content_viewer_actions.new_item": "Novo Item", |
28 | + "navbar.profile_actions.new_item": "Novo Item", | |
28 | 29 | "navbar.content_viewer_actions.new_post": "Novo Artigo", |
29 | 30 | "navbar.content_viewer_actions.new_discussion": "Nova Discussão", |
31 | + "navbar.profile_actions.new_discussion": "Nova Discussão", | |
30 | 32 | "notification.error.default.message": "Algo deu errado!", |
31 | 33 | "notification.error.default.title": "Oops...", |
32 | 34 | "notification.profile.not_found": "Página não encontrada", |
... | ... | @@ -56,7 +58,7 @@ |
56 | 58 | "article.remove.success.title": "Bom trabalho!", |
57 | 59 | "article.remove.success.message": "Artigo removido!", |
58 | 60 | "article.remove.confirmation.title": "Tem certeza?", |
59 | - "article.remove.confirmation.message": "Não será possível recuperar este artigo!", | |
61 | + "article.remove.confirmation.message": "Não será possível recuperar este artigo!", | |
60 | 62 | "article.basic_editor.visibility": "Visibilidade", |
61 | 63 | "article.basic_editor.visibility.public": "Público", |
62 | 64 | "article.basic_editor.visibility.private": "Privado", |
... | ... | @@ -72,5 +74,11 @@ |
72 | 74 | "profile.content.success.message": "Perfil salvo!", |
73 | 75 | "custom_content.title": "Editar conteúdo", |
74 | 76 | "profile.custom_header.label": "Cabeçalho", |
75 | - "profile.custom_footer.label": "Rodapé" | |
77 | + "profile.custom_footer.label": "Rodapé", | |
78 | + "designMode.label": "Modo de Edição", | |
79 | + "designMode.toggle.ON": "Ligado", | |
80 | + "designMode.toggle.OFF": "Desligado", | |
81 | + "search.results.summary": "{results, plural, one{# resultado} other{# resultados}}", | |
82 | + "search.results.query.label": "Buscar:", | |
83 | + "search.results.query.placeholder": "Informe aqui sua busca" | |
76 | 84 | } | ... | ... |
src/lib/ng-noosfero-api/http/article.service.spec.ts
... | ... | @@ -8,7 +8,7 @@ describe("Services", () => { |
8 | 8 | let $httpBackend: ng.IHttpBackendService; |
9 | 9 | let articleService: ArticleService; |
10 | 10 | |
11 | - beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => { | |
11 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | |
12 | 12 | $translateProvider.translations('en', {}); |
13 | 13 | })); |
14 | 14 | |
... | ... | @@ -23,7 +23,7 @@ describe("Services", () => { |
23 | 23 | it("should remove article", (done) => { |
24 | 24 | let articleId = 1; |
25 | 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 | 27 | $httpBackend.flush(); |
28 | 28 | $httpBackend.verifyNoOutstandingExpectation(); |
29 | 29 | done(); |
... | ... | @@ -32,7 +32,7 @@ describe("Services", () => { |
32 | 32 | it("should return article children", (done) => { |
33 | 33 | let articleId = 1; |
34 | 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 | 36 | expect(result.data).toEqual([{ name: "article1" }]); |
37 | 37 | done(); |
38 | 38 | }); |
... | ... | @@ -42,7 +42,7 @@ describe("Services", () => { |
42 | 42 | it("should get articles by profile", (done) => { |
43 | 43 | let profileId = 1; |
44 | 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 | 46 | expect(result.data).toEqual([{ name: "article1" }]); |
47 | 47 | done(); |
48 | 48 | }); |
... | ... | @@ -52,7 +52,7 @@ describe("Services", () => { |
52 | 52 | it("should get articles by profile with additional filters", (done) => { |
53 | 53 | let profileId = 1; |
54 | 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 | 56 | expect(result.data).toEqual([{ name: "article1" }]); |
57 | 57 | done(); |
58 | 58 | }); |
... | ... | @@ -62,7 +62,7 @@ describe("Services", () => { |
62 | 62 | it("should get article children with additional filters", (done) => { |
63 | 63 | let articleId = 1; |
64 | 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 | 66 | expect(result.data).toEqual([{ name: "article1" }]); |
67 | 67 | done(); |
68 | 68 | }); |
... | ... | @@ -71,14 +71,25 @@ describe("Services", () => { |
71 | 71 | |
72 | 72 | it("should create an article in a profile", (done) => { |
73 | 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 | 77 | expect(result.data).toEqual({ id: 2 }); |
78 | 78 | done(); |
79 | 79 | }); |
80 | 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 | 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
... | ... | @@ -8,7 +8,7 @@ describe("Services", () => { |
8 | 8 | let $httpBackend: ng.IHttpBackendService; |
9 | 9 | let blockService: BlockService; |
10 | 10 | |
11 | - beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => { | |
11 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | |
12 | 12 | $translateProvider.translations('en', {}); |
13 | 13 | })); |
14 | 14 | ... | ... |
src/lib/ng-noosfero-api/http/comment.service.spec.ts
... | ... | @@ -8,7 +8,7 @@ describe("Services", () => { |
8 | 8 | let $httpBackend: ng.IHttpBackendService; |
9 | 9 | let commentService: CommentService; |
10 | 10 | |
11 | - beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => { | |
11 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | |
12 | 12 | $translateProvider.translations('en', {}); |
13 | 13 | })); |
14 | 14 | ... | ... |
src/lib/ng-noosfero-api/http/community.service.spec.ts
... | ... | @@ -8,7 +8,7 @@ describe("Services", () => { |
8 | 8 | let $httpBackend: ng.IHttpBackendService; |
9 | 9 | let communityService: CommunityService; |
10 | 10 | |
11 | - beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => { | |
11 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | |
12 | 12 | $translateProvider.translations('en', {}); |
13 | 13 | })); |
14 | 14 | ... | ... |
src/lib/ng-noosfero-api/http/environment.service.ts
... | ... | @@ -4,12 +4,17 @@ import { Injectable, Inject } from "ng-forward"; |
4 | 4 | @Inject("Restangular", "$q") |
5 | 5 | export class EnvironmentService { |
6 | 6 | |
7 | - private _currentEnvironmentPromise: ng.IDeferred<noosfero.Environment>; | |
8 | 7 | |
8 | + private currentEnvironment: noosfero.Environment = null; | |
9 | 9 | constructor(private restangular: restangular.IService, private $q: ng.IQService) { |
10 | 10 | |
11 | 11 | } |
12 | 12 | |
13 | + getCurrentEnvironment(): noosfero.Environment { | |
14 | + return this.currentEnvironment; | |
15 | + } | |
16 | + | |
17 | + | |
13 | 18 | getEnvironmentPeople(params: any): ng.IPromise<noosfero.Person[]> { |
14 | 19 | let p = this.restangular.one('people').get(params); |
15 | 20 | let deferred = this.$q.defer<noosfero.Person[]>(); |
... | ... | @@ -18,10 +23,19 @@ export class EnvironmentService { |
18 | 23 | return deferred.promise; |
19 | 24 | } |
20 | 25 | |
21 | - getByIdentifier(identifier: string): ng.IPromise<noosfero.Environment> { | |
26 | + get(identifier: string = 'default'): ng.IPromise<noosfero.Environment> { | |
22 | 27 | let p = this.restangular.one('environment').customGET(identifier); |
23 | 28 | let deferred = this.$q.defer<noosfero.Environment>(); |
24 | - p.then(this.getHandleSuccessFunction<noosfero.Environment>(deferred)); | |
29 | + if (identifier === 'default') { | |
30 | + p.then((response) => { | |
31 | + let data = this.restangular.stripRestangular(response.data); | |
32 | + this.currentEnvironment = data; | |
33 | + this.getHandleSuccessFunction<noosfero.Environment>(deferred).bind(this)(response); | |
34 | + }); | |
35 | + } else { | |
36 | + p.then(this.getHandleSuccessFunction<noosfero.Environment>(deferred)); | |
37 | + } | |
38 | + | |
25 | 39 | p.catch(this.getHandleErrorFunction<noosfero.Environment>(deferred)); |
26 | 40 | return deferred.promise; |
27 | 41 | } |
... | ... | @@ -36,8 +50,8 @@ export class EnvironmentService { |
36 | 50 | |
37 | 51 | /** TODO - Please, use the base class RestangularService |
38 | 52 | * (description) |
39 | - * | |
40 | - * @template T | |
53 | + * | |
54 | + * @template T_currentEnvironmentPromise | |
41 | 55 | * @param {ng.IDeferred<T>} deferred (description) |
42 | 56 | * @returns {(response: restangular.IResponse) => void} (description) |
43 | 57 | */ |
... | ... | @@ -45,7 +59,7 @@ export class EnvironmentService { |
45 | 59 | let self = this; |
46 | 60 | /** |
47 | 61 | * (description) |
48 | - * | |
62 | + * | |
49 | 63 | * @param {restangular.IResponse} response (description) |
50 | 64 | */ |
51 | 65 | let errorFunction = (response: restangular.IResponse): void => { |
... | ... | @@ -62,7 +76,7 @@ export class EnvironmentService { |
62 | 76 | |
63 | 77 | /** |
64 | 78 | * (description) |
65 | - * | |
79 | + * | |
66 | 80 | * @param {restangular.IResponse} response (description) |
67 | 81 | */ |
68 | 82 | let successFunction = (response: restangular.IResponse): void => { |
... | ... | @@ -80,7 +94,7 @@ export class EnvironmentService { |
80 | 94 | |
81 | 95 | /** |
82 | 96 | * (description) |
83 | - * | |
97 | + * | |
84 | 98 | * @param {restangular.IResponse} response (description) |
85 | 99 | */ |
86 | 100 | let successFunction = (response: restangular.IResponse): void => { | ... | ... |
src/lib/ng-noosfero-api/http/profile.service.spec.ts
... | ... | @@ -8,7 +8,7 @@ describe("Services", () => { |
8 | 8 | let profileService: ProfileService; |
9 | 9 | let $rootScope: ng.IRootScopeService; |
10 | 10 | |
11 | - beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => { | |
11 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | |
12 | 12 | $translateProvider.translations('en', {}); |
13 | 13 | })); |
14 | 14 | ... | ... |
src/lib/ng-noosfero-api/http/profile.service.ts
... | ... | @@ -22,7 +22,7 @@ export class ProfileService { |
22 | 22 | this._currentProfilePromise.resolve(profile); |
23 | 23 | } |
24 | 24 | |
25 | - setCurrentProfileByIdentifier(identifier: string) { | |
25 | + setCurrentProfileByIdentifier(identifier: string): ng.IPromise<noosfero.Profile> { | |
26 | 26 | this.resetCurrentProfile(); |
27 | 27 | return this.getByIdentifier(identifier).then((profile: noosfero.Profile) => { |
28 | 28 | this.setCurrentProfile(profile); | ... | ... |
src/lib/ng-noosfero-api/http/restangular_service.spec.ts
... | ... | @@ -40,7 +40,7 @@ describe("Restangular Service - base Class", () => { |
40 | 40 | let objectRestService: ObjectRestService; |
41 | 41 | let rootObjectRestService: RootObjectRestService; |
42 | 42 | |
43 | - beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => { | |
43 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | |
44 | 44 | $translateProvider.translations('en', {}); |
45 | 45 | })); |
46 | 46 | ... | ... |
src/lib/ng-noosfero-api/http/restangular_service.ts
... | ... | @@ -223,7 +223,7 @@ export abstract class RestangularService<T extends noosfero.RestModel> { |
223 | 223 | restRequest = restangularObj.remove(queryParams, headers); |
224 | 224 | |
225 | 225 | restRequest |
226 | - .then(this.getHandleSuccessFunction(deferred, this.modelRemovedEventEmitter)) | |
226 | + .then(this.getHandleSuccessFunction(deferred, this.modelRemovedEventEmitter, obj)) | |
227 | 227 | .catch(this.getHandleErrorFunction(deferred)); |
228 | 228 | |
229 | 229 | return deferred.promise; |
... | ... | @@ -311,7 +311,7 @@ export abstract class RestangularService<T extends noosfero.RestModel> { |
311 | 311 | } |
312 | 312 | |
313 | 313 | /** HANDLERS */ |
314 | - protected getHandleSuccessFunction<C>(deferred: ng.IDeferred<noosfero.RestResult<C | T | any>>, successEmitter: EventEmitter<T> = null): (response: restangular.IResponse) => void { | |
314 | + protected getHandleSuccessFunction<C>(deferred: ng.IDeferred<noosfero.RestResult<C | T | any>>, successEmitter: EventEmitter<T> = null, currentModel: T = null): (response: restangular.IResponse) => void { | |
315 | 315 | let self = this; |
316 | 316 | |
317 | 317 | /** |
... | ... | @@ -328,7 +328,11 @@ export abstract class RestangularService<T extends noosfero.RestModel> { |
328 | 328 | deferred.resolve(resultModel); |
329 | 329 | // emits the event if a successEmiter was provided in the successEmitter parameter |
330 | 330 | if (successEmitter !== null) { |
331 | - successEmitter.next(resultModel); | |
331 | + if (successEmitter !== this.modelRemovedEventEmitter) { | |
332 | + successEmitter.next(resultModel.data); | |
333 | + } else { | |
334 | + successEmitter.next(currentModel !== null ? currentModel : resultModel.data); | |
335 | + } | |
332 | 336 | } |
333 | 337 | }; |
334 | 338 | return successFunction; | ... | ... |
src/lib/ng-noosfero-api/interfaces/article.ts
src/lib/ng-noosfero-api/interfaces/comment.ts
src/lib/ng-noosfero-api/interfaces/modelWithPermissions.ts
0 → 100644
src/lib/ng-noosfero-api/interfaces/profile.ts
src/plugins/comment_paragraph/allow-comment/allow-comment.component.spec.ts
... | ... | @@ -3,7 +3,6 @@ import {ComponentTestHelper, createClass} from '../../../spec/component-test-hel |
3 | 3 | import * as helpers from "../../../spec/helpers"; |
4 | 4 | import {Provider} from 'ng-forward'; |
5 | 5 | import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; |
6 | - | |
7 | 6 | let htmlTemplate = '<comment-paragraph-plugin-allow-comment [content]="ctrl.content" [paragraph-uuid]="ctrl.paragraphUuid" [article]="ctrl.article"></comment-paragraph-plugin-allow-comment>'; |
8 | 7 | |
9 | 8 | describe("Components", () => { |
... | ... | @@ -23,6 +22,7 @@ describe("Components", () => { |
23 | 22 | }; |
24 | 23 | |
25 | 24 | let providers = [ |
25 | + new Provider('CommentService', { useValue: helpers.mocks.commentService } ), | |
26 | 26 | new Provider('CommentParagraphService', { useValue: serviceMock }), |
27 | 27 | new Provider('CommentParagraphEventService', { useValue: eventServiceMock }) |
28 | 28 | ]; | ... | ... |
src/plugins/comment_paragraph/allow-comment/allow-comment.component.ts
... | ... | @@ -2,24 +2,27 @@ import {Component, Input, Inject} from "ng-forward"; |
2 | 2 | import {SideCommentsComponent} from "../side-comments/side-comments.component"; |
3 | 3 | import {CommentParagraphEventService} from "../events/comment-paragraph-event.service"; |
4 | 4 | import {CommentParagraphService} from "../http/comment-paragraph.service"; |
5 | +import {CommentService} from "./../../../lib/ng-noosfero-api/http/comment.service"; | |
5 | 6 | |
6 | 7 | @Component({ |
7 | 8 | selector: "comment-paragraph-plugin-allow-comment", |
8 | 9 | templateUrl: "plugins/comment_paragraph/allow-comment/allow-comment.html", |
9 | 10 | directives: [SideCommentsComponent] |
10 | 11 | }) |
11 | -@Inject("$scope", CommentParagraphEventService, CommentParagraphService) | |
12 | +@Inject("$scope", CommentParagraphEventService, CommentParagraphService, CommentService) | |
12 | 13 | export class AllowCommentComponent { |
13 | 14 | |
14 | 15 | @Input() content: string; |
15 | 16 | @Input() paragraphUuid: string; |
16 | 17 | @Input() article: noosfero.Article; |
17 | - commentsCount: number; | |
18 | + commentsCount: number = 0; | |
18 | 19 | display = false; |
19 | 20 | |
20 | 21 | constructor(private $scope: ng.IScope, |
21 | 22 | private commentParagraphEventService: CommentParagraphEventService, |
22 | - private commentParagraphService: CommentParagraphService) { } | |
23 | + private commentParagraphService: CommentParagraphService, | |
24 | + private commentService: CommentService | |
25 | + ) { } | |
23 | 26 | |
24 | 27 | ngOnInit() { |
25 | 28 | this.commentParagraphEventService.subscribeToggleCommentParagraph((article: noosfero.Article) => { |
... | ... | @@ -27,7 +30,19 @@ export class AllowCommentComponent { |
27 | 30 | this.$scope.$apply(); |
28 | 31 | }); |
29 | 32 | this.commentParagraphService.commentParagraphCount(this.article, this.paragraphUuid).then((count: number) => { |
30 | - this.commentsCount = count; | |
33 | + this.commentsCount = count ? count : 0; | |
34 | + }); | |
35 | + | |
36 | + this.commentService.subscribeToModelAdded((comment: noosfero.CommentParagraph) => { | |
37 | + if (comment.paragraph_uuid === this.paragraphUuid) { | |
38 | + this.commentsCount += 1; | |
39 | + }; | |
40 | + }); | |
41 | + | |
42 | + this.commentService.subscribeToModelRemoved((comment: noosfero.CommentParagraph) => { | |
43 | + if (comment.paragraph_uuid === this.paragraphUuid) { | |
44 | + this.commentsCount -= (comment.replies) ? 1 + comment.replies.length : 1; | |
45 | + }; | |
31 | 46 | }); |
32 | 47 | } |
33 | 48 | ... | ... |
src/plugins/comment_paragraph/hotspot/export-comment-button.component.spec.ts
0 → 100644
... | ... | @@ -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 @@ |
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 @@ |
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/http/comment-paragraph.service.spec.ts
... | ... | @@ -8,7 +8,7 @@ describe("Services", () => { |
8 | 8 | let $httpBackend: ng.IHttpBackendService; |
9 | 9 | let commentParagraphService: CommentParagraphService; |
10 | 10 | |
11 | - beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => { | |
11 | + beforeEach(angular.mock.module("main", ($translateProvider: angular.translate.ITranslateProvider) => { | |
12 | 12 | $translateProvider.translations('en', {}); |
13 | 13 | })); |
14 | 14 | ... | ... |
src/plugins/comment_paragraph/index.ts
1 | 1 | import {AllowCommentComponent} from "./allow-comment/allow-comment.component"; |
2 | +import {ExportCommentButtonHotspotComponent} from "./hotspot/export-comment-button.component"; | |
2 | 3 | import {CommentParagraphFormHotspotComponent} from "./hotspot/comment-paragraph-form.component"; |
3 | 4 | import {DiscussionEditorComponent} from "./article/cms/discussion-editor/discussion-editor.component"; |
4 | 5 | import {CommentParagraphArticleContentHotspotComponent} from "./hotspot/article-content/article-content.component"; |
5 | 6 | import {DiscussionBlockComponent} from "./block/discussion/discussion-block.component"; |
6 | 7 | |
7 | 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 | 2 | "comment-paragraph-plugin.title": "Paragraph Comments", |
3 | + "comment-paragraph-plugin.export": "Export Comments", | |
3 | 4 | "comment-paragraph-plugin.discussion.editor.start_date.label": "From", |
4 | 5 | "comment-paragraph-plugin.discussion.editor.end_date.label": "To", |
5 | 6 | "comment-paragraph-plugin.discussion.header": "Open for comments", | ... | ... |
src/plugins/comment_paragraph/languages/pt.json
1 | 1 | { |
2 | 2 | "comment-paragraph-plugin.title": "Comentários por Parágrafo", |
3 | + "comment-paragraph-plugin.export": "Exportar Comentários", | |
3 | 4 | "comment-paragraph-plugin.discussion.editor.start_date.label": "De", |
4 | 5 | "comment-paragraph-plugin.discussion.editor.end_date.label": "Até", |
5 | 6 | "comment-paragraph-plugin.discussion.header": "Aberto para comentários", | ... | ... |
src/plugins/comment_paragraph/models/comment_paragraph.ts
0 → 100644
src/spec/mocks.ts
... | ... | @@ -85,24 +85,24 @@ export var mocks: any = { |
85 | 85 | mocks.articleService.articleAddedFn = fn; |
86 | 86 | }, |
87 | 87 | modelRemovedEventEmitter: |
88 | - { | |
89 | - subscribe: (fn: Function) => { | |
90 | - mocks.articleService.articleRemovedFn = fn; | |
91 | - }, | |
92 | - next: (param: any) => { | |
93 | - mocks.articleService.articleRemovedFn(param); | |
94 | - } | |
88 | + { | |
89 | + subscribe: (fn: Function) => { | |
90 | + mocks.articleService.articleRemovedFn = fn; | |
91 | + }, | |
92 | + next: (param: any) => { | |
93 | + mocks.articleService.articleRemovedFn(param); | |
95 | 94 | } |
95 | + } | |
96 | 96 | , |
97 | 97 | modelAddedEventEmitter: |
98 | - { | |
99 | - subscribe: (fn: Function) => { | |
100 | - mocks.articleService.articleAddedFn = fn; | |
101 | - }, | |
102 | - next: (param: any) => { | |
103 | - mocks.articleService.articleAddedFn(param); | |
104 | - } | |
98 | + { | |
99 | + subscribe: (fn: Function) => { | |
100 | + mocks.articleService.articleAddedFn = fn; | |
101 | + }, | |
102 | + next: (param: any) => { | |
103 | + mocks.articleService.articleAddedFn(param); | |
105 | 104 | } |
105 | + } | |
106 | 106 | , |
107 | 107 | remove: (article: noosfero.Article) => { |
108 | 108 | return { |
... | ... | @@ -156,6 +156,34 @@ export var mocks: any = { |
156 | 156 | instant: () => { } |
157 | 157 | }, |
158 | 158 | commentService: { |
159 | + commentRemovedFn: null, | |
160 | + commentAddedFn: null, | |
161 | + subscribeToModelRemoved: (fn: Function) => { | |
162 | + mocks.commentService.commentRemovedFn = fn; | |
163 | + }, | |
164 | + subscribeToModelAdded: (fn: Function) => { | |
165 | + mocks.commentService.commentAddedFn = fn; | |
166 | + }, | |
167 | + modelRemovedEventEmitter: | |
168 | + { | |
169 | + subscribe: (fn: Function) => { | |
170 | + mocks.commentService.commentRemovedFn = fn; | |
171 | + }, | |
172 | + next: (param: any) => { | |
173 | + mocks.commentService.commentRemovedFn(param); | |
174 | + } | |
175 | + } | |
176 | + , | |
177 | + modelAddedEventEmitter: | |
178 | + { | |
179 | + subscribe: (fn: Function) => { | |
180 | + mocks.articleService.commentAddedFn = fn; | |
181 | + }, | |
182 | + next: (param: any) => { | |
183 | + mocks.articleService.commentAddedFn(param); | |
184 | + } | |
185 | + } | |
186 | + , | |
159 | 187 | getByArticle: (article: noosfero.Article) => { |
160 | 188 | return Promise.resolve({ data: {} }); |
161 | 189 | } | ... | ... |
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 | + | |
1 | 16 | .block-head-with-icon { |
2 | 17 | padding: 15px 15px 15px 55px; |
3 | 18 | } |
4 | 19 | |
20 | +.content-wrapper .block .panel-heading, | |
21 | +.content-wrapper .side-options .panel-heading { | |
22 | + @extend %panel-head-theme; | |
23 | +} | |
24 | + | |
5 | 25 | .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 | 26 | |
21 | 27 | &.membersblock { |
22 | 28 | .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 | 13 | .navbar { |
2 | 14 | min-height: 123px; |
3 | 15 | background-color: #f9c404; | ... | ... |
themes/angular-participa-consulta/app/participa-consulta.scss
1 | +$selected-color: #f6c445; | |
2 | + | |
1 | 3 | @font-face { |
2 | 4 | font-family: 'Ubuntu'; |
3 | 5 | font-weight: 300; |
... | ... | @@ -19,9 +21,51 @@ |
19 | 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 | 71 | .profile-header, .profile-footer{ |
... | ... | @@ -33,3 +77,8 @@ |
33 | 77 | background-color: #7E7E7E; |
34 | 78 | } |
35 | 79 | } |
80 | + | |
81 | +.ats-switch { | |
82 | + border: 0px; | |
83 | + border-color: transparent; | |
84 | +} | ... | ... |