Commit b9abd2b869a1c1b804344ed3d057ba7682ad51e4

Authored by Leandro Santos
2 parents 960cb3cb 5e8f7a89

Merge branch 'master' into staging

Showing 193 changed files with 4023 additions and 1233 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 193 files displayed.

@@ -35,6 +35,8 @@ @@ -35,6 +35,8 @@
35 "angular-i18n": "^1.5.0", 35 "angular-i18n": "^1.5.0",
36 "angular-load": "^0.4.1", 36 "angular-load": "^0.4.1",
37 "angular-translate-interpolation-messageformat": "^2.10.0", 37 "angular-translate-interpolation-messageformat": "^2.10.0",
  38 + "angular-bind-html-compile": "^1.2.1",
  39 + "angular-click-outside": "^2.7.1",
38 "ng-ckeditor": "^0.2.1", 40 "ng-ckeditor": "^0.2.1",
39 "ckeditor": "^4.5.8" 41 "ckeditor": "^4.5.8"
40 }, 42 },
@@ -6,6 +6,7 @@ var rename = require('gulp-rename'); @@ -6,6 +6,7 @@ var rename = require('gulp-rename');
6 var insert = require('gulp-insert'); 6 var insert = require('gulp-insert');
7 var merge = require('merge-stream'); 7 var merge = require('merge-stream');
8 var conf = require('./conf'); 8 var conf = require('./conf');
  9 +var languages = require('./languages');
9 10
10 var themeName = conf.paths.theme.replace('-', ' '); 11 var themeName = conf.paths.theme.replace('-', ' ');
11 themeName = themeName.charAt(0).toUpperCase() + themeName.slice(1); 12 themeName = themeName.charAt(0).toUpperCase() + themeName.slice(1);
@@ -16,25 +17,31 @@ var $ = require('gulp-load-plugins')({ @@ -16,25 +17,31 @@ var $ = require('gulp-load-plugins')({
16 }); 17 });
17 18
18 gulp.task('partials', function () { 19 gulp.task('partials', function () {
19 - var srcPaths = [path.join(conf.paths.tmp, '/serve/app/**/*.html')];  
20 - conf.paths.allSources.forEach(function(src) {  
21 - srcPaths.push(path.join(src, '/app/**/*.html')); 20 + var merged = merge();
  21 + ['app', conf.paths.plugins].forEach(function(partialPath) {
  22 + var srcPaths = [path.join(conf.paths.tmp, '/serve/app/**/*.html')];
  23 + conf.paths.allSources.forEach(function(src) {
  24 + srcPaths.push(path.join(src, partialPath, '/**/*.html'));
  25 + });
  26 + merged.add(gulp.src(srcPaths)
  27 + .pipe($.minifyHtml({
  28 + empty: true,
  29 + spare: true,
  30 + quotes: true
  31 + }))
  32 + .pipe($.angularTemplatecache('templateCacheHtml-'+partialPath+'.js', {
  33 + module: 'noosferoApp',
  34 + root: partialPath
  35 + }))
  36 + .pipe(gulp.dest(conf.paths.tmp + '/partials/')));
22 }); 37 });
23 - return gulp.src(srcPaths)  
24 - .pipe($.minifyHtml({  
25 - empty: true,  
26 - spare: true,  
27 - quotes: true  
28 - }))  
29 - .pipe($.angularTemplatecache('templateCacheHtml.js', {  
30 - module: 'noosferoApp',  
31 - root: 'app'  
32 - }))  
33 - .pipe(gulp.dest(conf.paths.tmp + '/partials/')); 38 + return merged;
34 }); 39 });
35 40
36 gulp.task('html', ['inject', 'partials'], function () { 41 gulp.task('html', ['inject', 'partials'], function () {
37 - var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false }); 42 + var partialsInjectFile = gulp.src([
  43 + path.join(conf.paths.tmp, '/partials/templateCacheHtml-app.js'),
  44 + path.join(conf.paths.tmp, '/partials/templateCacheHtml-plugins.js')], { read: false });
38 var partialsInjectOptions = { 45 var partialsInjectOptions = {
39 starttag: '<!-- inject:partials -->', 46 starttag: '<!-- inject:partials -->',
40 ignorePath: path.join(conf.paths.tmp, '/partials'), 47 ignorePath: path.join(conf.paths.tmp, '/partials'),
@@ -124,6 +131,10 @@ gulp.task(&#39;clean-docs&#39;, [], function() { @@ -124,6 +131,10 @@ gulp.task(&#39;clean-docs&#39;, [], function() {
124 return $.del([path.join(conf.paths.docs, '/')]); 131 return $.del([path.join(conf.paths.docs, '/')]);
125 }); 132 });
126 133
  134 +gulp.task('plugin-languages', ['locale'], function() {
  135 + return languages.pluginLanguages(conf.paths.dist);
  136 +});
  137 +
127 gulp.task('noosfero', ['html'], function () { 138 gulp.task('noosfero', ['html'], function () {
128 var layouts = gulp.src('layouts/**/*') 139 var layouts = gulp.src('layouts/**/*')
129 .pipe(gulp.dest(path.join(conf.paths.dist, "layouts"))); 140 .pipe(gulp.dest(path.join(conf.paths.dist, "layouts")));
@@ -136,4 +147,4 @@ gulp.task(&#39;noosfero&#39;, [&#39;html&#39;], function () { @@ -136,4 +147,4 @@ gulp.task(&#39;noosfero&#39;, [&#39;html&#39;], function () {
136 return merge(layouts, theme, index); 147 return merge(layouts, theme, index);
137 }); 148 });
138 149
139 -gulp.task('build', ['html', 'fonts', 'other', 'locale', 'noosfero']); 150 +gulp.task('build', ['html', 'fonts', 'other', 'locale', 'plugin-languages', 'noosfero']);
@@ -15,11 +15,13 @@ var path = require(&#39;path&#39;); @@ -15,11 +15,13 @@ var path = require(&#39;path&#39;);
15 */ 15 */
16 exports.paths = { 16 exports.paths = {
17 src: 'src', 17 src: 'src',
  18 + plugins: 'plugins',
18 dist: 'dist', 19 dist: 'dist',
19 tmp: '.tmp', 20 tmp: '.tmp',
20 e2e: 'e2e', 21 e2e: 'e2e',
21 docs: 'docs', 22 docs: 'docs',
22 - themes: 'themes' 23 + themes: 'themes',
  24 + languages: 'languages'
23 }; 25 };
24 exports.configTheme = function(theme) { 26 exports.configTheme = function(theme) {
25 exports.paths.theme = theme || "angular-default"; 27 exports.paths.theme = theme || "angular-default";
gulp/languages.js 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +'use strict';
  2 +
  3 +var path = require('path');
  4 +var gulp = require('gulp');
  5 +var merge = require('merge-stream');
  6 +var conf = require('./conf');
  7 +var mergeJson = require('gulp-merge-json');
  8 +var glob = require("glob");
  9 +
  10 +exports.pluginLanguages = function(dest) {
  11 + var merged = merge();
  12 + glob(path.join(conf.paths.src, conf.paths.languages, "*.json"), function (er, files) {
  13 + files.forEach(function(file) {
  14 + merged.add(exports.pluginLanguage(file, dest));
  15 + });
  16 + });
  17 + return merged;
  18 +}
  19 +
  20 +exports.pluginLanguage = function(file, dest) {
  21 + var language = file.split('/').pop().replace('\.json','');
  22 + return gulp.src(path.join(conf.paths.src, '**', conf.paths.languages, language+'.json'))
  23 + .pipe(mergeJson(path.join(conf.paths.languages, language+'.json')))
  24 + .pipe(gulp.dest(dest))
  25 +}
  26 +
  27 +gulp.task('serve-languages', function() {
  28 + return exports.pluginLanguages(path.join(conf.paths.tmp, '/serve'));
  29 +});
gulp/server.js
@@ -45,7 +45,7 @@ browserSync.use(browserSyncSpa({ @@ -45,7 +45,7 @@ browserSync.use(browserSyncSpa({
45 selector: '[ng-app]'// Only needed for angular apps 45 selector: '[ng-app]'// Only needed for angular apps
46 })); 46 }));
47 47
48 -gulp.task('serve', ['watch'], function () { 48 +gulp.task('serve', ['serve-languages', 'watch'], function () {
49 var srcPaths = [path.join(conf.paths.tmp, '/serve')]; 49 var srcPaths = [path.join(conf.paths.tmp, '/serve')];
50 conf.paths.allSources.reverse().forEach(function(src) { 50 conf.paths.allSources.reverse().forEach(function(src) {
51 srcPaths.push(src); 51 srcPaths.push(src);
gulp/styles.js
@@ -31,6 +31,7 @@ var buildStyles = function() { @@ -31,6 +31,7 @@ var buildStyles = function() {
31 ]; 31 ];
32 conf.paths.allSources.forEach(function(src) { 32 conf.paths.allSources.forEach(function(src) {
33 srcPaths.push(path.join(src, '/app/**/*.scss')); 33 srcPaths.push(path.join(src, '/app/**/*.scss'));
  34 + srcPaths.push(path.join(src, conf.paths.plugins, '/**/*.scss'));
34 }); 35 });
35 var injectFiles = gulp.src(srcPaths, { read: false }); 36 var injectFiles = gulp.src(srcPaths, { read: false });
36 37
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 var path = require('path'); 3 var path = require('path');
4 var gulp = require('gulp'); 4 var gulp = require('gulp');
5 var conf = require('./conf'); 5 var conf = require('./conf');
  6 +var languages = require('./languages');
6 7
7 var browserSync = require('browser-sync'); 8 var browserSync = require('browser-sync');
8 9
@@ -16,7 +17,8 @@ gulp.task(&#39;watch&#39;, [&#39;inject&#39;], function () { @@ -16,7 +17,8 @@ gulp.task(&#39;watch&#39;, [&#39;inject&#39;], function () {
16 17
17 gulp.watch([ 18 gulp.watch([
18 path.join(conf.paths.src, '/app/**/*.css'), 19 path.join(conf.paths.src, '/app/**/*.css'),
19 - path.join(conf.paths.src, '/app/**/*.scss') 20 + path.join(conf.paths.src, '/app/**/*.scss'),
  21 + path.join(conf.paths.src, conf.paths.plugins, '/**/*.scss')
20 ], function(event) { 22 ], function(event) {
21 if(isOnlyChange(event)) { 23 if(isOnlyChange(event)) {
22 gulp.start('styles-reload'); 24 gulp.start('styles-reload');
@@ -33,9 +35,14 @@ gulp.task(&#39;watch&#39;, [&#39;inject&#39;], function () { @@ -33,9 +35,14 @@ gulp.task(&#39;watch&#39;, [&#39;inject&#39;], function () {
33 } 35 }
34 }); 36 });
35 37
  38 + gulp.watch(path.join(conf.paths.src, '**', conf.paths.languages, '*.json'), function(event) {
  39 + languages.pluginLanguage(event.path, path.join(conf.paths.tmp, '/serve'));
  40 + });
  41 +
36 var watchPaths = []; 42 var watchPaths = [];
37 conf.paths.allSources.forEach(function(src) { 43 conf.paths.allSources.forEach(function(src) {
38 watchPaths.push(path.join(src, '/app/**/*.html')); 44 watchPaths.push(path.join(src, '/app/**/*.html'));
  45 + watchPaths.push(path.join(src, conf.paths.plugins, '/**/*.html'));
39 }); 46 });
40 gulp.watch(watchPaths, function(event) { 47 gulp.watch(watchPaths, function(event) {
41 browserSync.reload(event.path); 48 browserSync.reload(event.path);
@@ -49,7 +49,7 @@ var _ = require(&#39;lodash&#39;); @@ -49,7 +49,7 @@ var _ = require(&#39;lodash&#39;);
49 var wiredep = require('wiredep'); 49 var wiredep = require('wiredep');
50 50
51 var pathSrcHtml = [ 51 var pathSrcHtml = [
52 - path.join('./src/app/**/*.html') 52 + path.join('./src/**/*.html')
53 ]; 53 ];
54 54
55 var glob = require("glob"); 55 var glob = require("glob");
@@ -49,6 +49,7 @@ @@ -49,6 +49,7 @@
49 "gulp-insert": "^0.5.0", 49 "gulp-insert": "^0.5.0",
50 "gulp-inject": "~3.0.0", 50 "gulp-inject": "~3.0.0",
51 "gulp-load-plugins": "~0.10.0", 51 "gulp-load-plugins": "~0.10.0",
  52 + "gulp-merge-json": "^0.4.0",
52 "gulp-minify-css": "~1.2.1", 53 "gulp-minify-css": "~1.2.1",
53 "gulp-minify-html": "~1.0.4", 54 "gulp-minify-html": "~1.0.4",
54 "gulp-ng-annotate": "~1.1.0", 55 "gulp-ng-annotate": "~1.1.0",
src/app/article/article-default-view.component.ts
1 import { bundle, Input, Inject, Component, Directive } from 'ng-forward'; 1 import { bundle, Input, Inject, Component, Directive } from 'ng-forward';
2 import {ArticleBlogComponent} from "./types/blog/blog.component"; 2 import {ArticleBlogComponent} from "./types/blog/blog.component";
3 import {CommentsComponent} from "./comment/comments.component"; 3 import {CommentsComponent} from "./comment/comments.component";
  4 +import {MacroDirective} from "./macro/macro.directive";
  5 +import {ArticleToolbarHotspotComponent} from "../hotspot/article-toolbar-hotspot.component";
  6 +import {ArticleContentHotspotComponent} from "../hotspot/article-content-hotspot.component";
4 7
5 /** 8 /**
6 * @ngdoc controller 9 * @ngdoc controller
@@ -30,7 +33,9 @@ export class ArticleDefaultViewComponent { @@ -30,7 +33,9 @@ export class ArticleDefaultViewComponent {
30 @Component({ 33 @Component({
31 selector: 'noosfero-article', 34 selector: 'noosfero-article',
32 template: 'not-used', 35 template: 'not-used',
33 - directives: [ArticleDefaultViewComponent, ArticleBlogComponent, CommentsComponent] 36 + directives: [ArticleDefaultViewComponent, ArticleBlogComponent,
  37 + CommentsComponent, MacroDirective, ArticleToolbarHotspotComponent,
  38 + ArticleContentHotspotComponent]
34 }) 39 })
35 @Inject("$element", "$scope", "$injector", "$compile") 40 @Inject("$element", "$scope", "$injector", "$compile")
36 export class ArticleViewComponent { 41 export class ArticleViewComponent {
@@ -40,7 +45,8 @@ export class ArticleViewComponent { @@ -40,7 +45,8 @@ export class ArticleViewComponent {
40 directiveName: string; 45 directiveName: string;
41 46
42 ngOnInit() { 47 ngOnInit() {
43 - let specificDirective = 'noosfero' + this.article.type; 48 + let articleType = this.article.type.replace(/::/, '');
  49 + let specificDirective = 'noosfero' + articleType;
44 this.directiveName = "noosfero-default-article"; 50 this.directiveName = "noosfero-default-article";
45 if (this.$injector.has(specificDirective + 'Directive')) { 51 if (this.$injector.has(specificDirective + 'Directive')) {
46 this.directiveName = specificDirective.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); 52 this.directiveName = specificDirective.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
@@ -53,6 +59,5 @@ export class ArticleViewComponent { @@ -53,6 +59,5 @@ export class ArticleViewComponent {
53 private $scope: ng.IScope, 59 private $scope: ng.IScope,
54 private $injector: ng.auto.IInjectorService, 60 private $injector: ng.auto.IInjectorService,
55 private $compile: ng.ICompileService) { 61 private $compile: ng.ICompileService) {
56 -  
57 } 62 }
58 } 63 }
src/app/article/article.html
@@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
7 <a href="#" class="btn btn-default btn-xs" ui-sref="main.cmsEdit({profile: ctrl.profile.identifier, id: ctrl.article.id})"> 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}} 8 <i class="fa fa-pencil-square-o fa-fw fa-lg"></i> {{"article.actions.edit" | translate}}
9 </a> 9 </a>
  10 + <noosfero-hotspot-article-toolbar [article]="ctrl.article"></noosfero-hotspot-article-toolbar>
10 <div class="page-info pull-right small text-muted"> 11 <div class="page-info pull-right small text-muted">
11 <span class="time"> 12 <span class="time">
12 <i class="fa fa-clock-o"></i> <span am-time-ago="ctrl.article.created_at | dateFormat"></span> 13 <i class="fa fa-clock-o"></i> <span am-time-ago="ctrl.article.created_at | dateFormat"></span>
@@ -19,9 +20,9 @@ @@ -19,9 +20,9 @@
19 </span> 20 </span>
20 </div> 21 </div>
21 </div> 22 </div>
22 - 23 + <noosfero-hotspot-article-content [article]="ctrl.article"></noosfero-hotspot-article-content>
23 <div class="page-body"> 24 <div class="page-body">
24 - <div ng-bind-html="ctrl.article.body"></div> 25 + <div bind-html-compile="ctrl.article.body"></div>
25 </div> 26 </div>
26 27
27 <noosfero-comments [article]="ctrl.article"></noosfero-comments> 28 <noosfero-comments [article]="ctrl.article"></noosfero-comments>
src/app/article/basic-editor/basic-editor.component.spec.ts
@@ -1,84 +0,0 @@ @@ -1,84 +0,0 @@
1 -import {quickCreateComponent} from "../../../spec/helpers";  
2 -import {BasicEditorComponent} from "./basic-editor.component";  
3 -  
4 -  
5 -describe("Article BasicEditor", () => {  
6 -  
7 - let $rootScope: ng.IRootScopeService;  
8 - let $q: ng.IQService;  
9 - let articleServiceMock: any;  
10 - let profileServiceMock: any;  
11 - let $state: any;  
12 - let $stateParams: any;  
13 - let $window: any;  
14 - let profile = { id: 1 };  
15 - let notification: any;  
16 -  
17 -  
18 - beforeEach(inject((_$rootScope_: ng.IRootScopeService, _$q_: ng.IQService) => {  
19 - $rootScope = _$rootScope_;  
20 - $q = _$q_;  
21 - }));  
22 -  
23 - beforeEach(() => {  
24 - $window = jasmine.createSpyObj("$window", ["back"]);  
25 - $state = jasmine.createSpyObj("$state", ["go"]);  
26 - notification = jasmine.createSpyObj("notification", ["success"]);  
27 - profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["setCurrentProfileByIdentifier"]);  
28 - articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInParent", "updateArticle", "get"]);  
29 -  
30 - $stateParams = { profile: "profile" };  
31 -  
32 - let setCurrentProfileByIdentifierResponse = $q.defer();  
33 - setCurrentProfileByIdentifierResponse.resolve(profile);  
34 -  
35 - let articleCreate = $q.defer();  
36 - articleCreate.resolve({ data: { path: "path", profile: { identifier: "profile" } } });  
37 -  
38 - let articleGet = $q.defer();  
39 - articleGet.resolve({ data: { path: "parent-path", profile: { identifier: "profile" } } });  
40 -  
41 - profileServiceMock.setCurrentProfileByIdentifier = jasmine.createSpy("setCurrentProfileByIdentifier").and.returnValue(setCurrentProfileByIdentifierResponse.promise);  
42 - articleServiceMock.createInParent = jasmine.createSpy("createInParent").and.returnValue(articleCreate.promise);  
43 - articleServiceMock.updateArticle = jasmine.createSpy("updateArticle").and.returnValue(articleCreate.promise);  
44 - articleServiceMock.get = jasmine.createSpy("get").and.returnValue(articleGet.promise);  
45 - });  
46 -  
47 - it("create an article in the current profile when save", done => {  
48 - $stateParams['parent_id'] = 1;  
49 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);  
50 - component.save();  
51 - $rootScope.$apply();  
52 - expect(profileServiceMock.setCurrentProfileByIdentifier).toHaveBeenCalled();  
53 - expect(articleServiceMock.createInParent).toHaveBeenCalledWith(1, component.article);  
54 - done();  
55 - });  
56 -  
57 - it("got to the new article page and display an alert when saving sucessfully", done => {  
58 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);  
59 - component.save();  
60 - $rootScope.$apply();  
61 - expect($state.go).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "profile" });  
62 - expect(notification.success).toHaveBeenCalled();  
63 - done();  
64 - });  
65 -  
66 - it("go back when cancel article edition", done => {  
67 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);  
68 - $window.history = { back: jasmine.createSpy('back') };  
69 - component.cancel();  
70 - expect($window.history.back).toHaveBeenCalled();  
71 - done();  
72 - });  
73 -  
74 - it("edit existing article when save", done => {  
75 - $stateParams['parent_id'] = null;  
76 - $stateParams['id'] = 2;  
77 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);  
78 - component.save();  
79 - $rootScope.$apply();  
80 - expect(articleServiceMock.updateArticle).toHaveBeenCalledWith(component.article);  
81 - done();  
82 - });  
83 -  
84 -});  
src/app/article/basic-editor/basic-editor.component.ts
@@ -1,69 +0,0 @@ @@ -1,69 +0,0 @@
1 -import {StateConfig, Component, Inject, provide} from 'ng-forward';  
2 -import {ArticleService} from "../../../lib/ng-noosfero-api/http/article.service";  
3 -import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service";  
4 -import {NotificationService} from "../../shared/services/notification.service.ts";  
5 -  
6 -@Component({  
7 - selector: 'article-basic-editor',  
8 - templateUrl: "app/article/basic-editor/basic-editor.html",  
9 - providers: [  
10 - provide('articleService', { useClass: ArticleService }),  
11 - provide('profileService', { useClass: ProfileService }),  
12 - provide('notification', { useClass: NotificationService })  
13 - ]  
14 -})  
15 -@Inject(ArticleService, ProfileService, "$state", NotificationService, "$stateParams", "$window")  
16 -export class BasicEditorComponent {  
17 -  
18 - article: noosfero.Article = <noosfero.Article>{ type: "TextArticle" };  
19 - parent: noosfero.Article = <noosfero.Article>{};  
20 -  
21 - id: number;  
22 - parentId: number;  
23 - profileIdentifier: string;  
24 -  
25 - constructor(private articleService: ArticleService,  
26 - private profileService: ProfileService,  
27 - private $state: ng.ui.IStateService,  
28 - private notification: NotificationService,  
29 - private $stateParams: ng.ui.IStateParamsService,  
30 - private $window: ng.IWindowService) {  
31 -  
32 - this.parentId = this.$stateParams['parent_id'];  
33 - this.profileIdentifier = this.$stateParams["profile"];  
34 - this.id = this.$stateParams['id'];  
35 -  
36 - if (this.parentId) {  
37 - this.articleService.get(this.parentId).then((result: noosfero.RestResult<noosfero.Article>) => {  
38 - this.parent = result.data;  
39 - });  
40 - }  
41 - if (this.id) {  
42 - this.articleService.get(this.id).then((result: noosfero.RestResult<noosfero.Article>) => {  
43 - this.article = result.data;  
44 - this.article.name = this.article.title; // FIXME  
45 - });  
46 - }  
47 - }  
48 -  
49 - save() {  
50 - this.profileService.setCurrentProfileByIdentifier(this.profileIdentifier).then((profile: noosfero.Profile) => {  
51 - if (this.id) {  
52 - return this.articleService.updateArticle(this.article);  
53 - } else {  
54 - return this.articleService.createInParent(this.parentId, this.article);  
55 - }  
56 - }).then((response: noosfero.RestResult<noosfero.Article>) => {  
57 - let article = (<noosfero.Article>response.data);  
58 - this.$state.go('main.profile.page', { page: article.path, profile: article.profile.identifier });  
59 - this.notification.success({ title: "article.basic_editor.success.title", message: "article.basic_editor.success.message" });  
60 - }).catch(() => {  
61 - this.notification.error({ message: "article.basic_editor.save.failed" });  
62 - });  
63 - }  
64 -  
65 - cancel() {  
66 - this.$window.history.back();  
67 - }  
68 -  
69 -}  
src/app/article/basic-editor/basic-editor.html
@@ -1,34 +0,0 @@ @@ -1,34 +0,0 @@
1 -<div class="basic-editor">  
2 - <div class="row">  
3 - <div class="col-md-1"></div>  
4 - <div class="col-md-8">  
5 - <form>  
6 - <div class="form-group">  
7 - <label for="titleInput">{{"article.basic_editor.title" | translate}}</label>  
8 - <input type="text" class="form-control" id="titleInput" placeholder="{{'article.basic_editor.title' | translate}}" ng-model="vm.article.name">  
9 - </div>  
10 - <div class="form-group">  
11 - <label for="bodyInput">{{"article.basic_editor.body" | translate}}</label>  
12 - <html-editor [(value)]="vm.article.body"></html-editor>  
13 - </div>  
14 - <button type="submit" class="btn btn-default" ng-click="vm.save()">{{"article.basic_editor.save" | translate}}</button>  
15 - <button type="button" class="btn btn-danger" ng-click="vm.cancel()">{{"article.basic_editor.cancel" | translate}}</button>  
16 - </form>  
17 - </div>  
18 - <div class="side-options">  
19 - <div class="visibility panel panel-default">  
20 - <div class="panel-heading">{{"article.basic_editor.visibility" | translate}}</div>  
21 - <div class="panel-body">  
22 - <div>  
23 - <input type="radio" ng-model="vm.article.published" ng-value="true">  
24 - <i class="fa fa-unlock fa-fw"></i> {{"article.basic_editor.visibility.public" | translate}}  
25 - </div>  
26 - <div>  
27 - <input type="radio" ng-model="vm.article.published" ng-value="false">  
28 - <i class="fa fa-lock fa-fw"></i> {{"article.basic_editor.visibility.private" | translate}}  
29 - </div>  
30 - </div>  
31 - </div>  
32 - </div>  
33 - </div>  
34 -</div>  
src/app/article/basic-editor/basic-editor.scss
@@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
1 -.basic-editor {  
2 - @extend .container-fluid;  
3 - padding: 0 1%;  
4 -  
5 - .side-options {  
6 - @extend .col-md-3;  
7 - margin-top: 25px;  
8 -  
9 - .visibility {  
10 - .panel-heading {  
11 - background-color: transparent;  
12 - font-weight: bold;  
13 - }  
14 - .panel-body {  
15 - i {  
16 - color: #A5A5A5;  
17 - }  
18 - }  
19 - }  
20 - }  
21 -}  
src/app/article/cms/article-editor/article-editor.component.ts 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +import {Component, Input, Inject} from 'ng-forward';
  2 +
  3 +@Component({
  4 + selector: 'article-editor',
  5 + template: "not-used"
  6 +})
  7 +@Inject("$element", "$scope", "$injector", "$compile")
  8 +export class ArticleEditorComponent {
  9 +
  10 + @Input() article: noosfero.Article;
  11 +
  12 + constructor(
  13 + private $element: any,
  14 + private $scope: ng.IScope,
  15 + private $injector: ng.auto.IInjectorService,
  16 + private $compile: ng.ICompileService) { }
  17 +
  18 + ngOnInit() {
  19 + let articleType = this.article.type.replace(/::/, '');
  20 + let specificDirective = `${articleType.charAt(0).toLowerCase()}${articleType.substring(1)}Editor`;
  21 + let directiveName = "article-basic-editor";
  22 + if (this.$injector.has(specificDirective + 'Directive')) {
  23 + directiveName = specificDirective.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  24 + }
  25 + this.$element.replaceWith(this.$compile('<' + directiveName + ' [article]="ctrl.article"></' + directiveName + '>')(this.$scope));
  26 + }
  27 +}
src/app/article/cms/basic-editor/basic-editor.component.ts 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +import {Component, Input} from 'ng-forward';
  2 +
  3 +@Component({
  4 + selector: 'article-basic-editor',
  5 + templateUrl: "app/article/cms/basic-editor/basic-editor.html"
  6 +})
  7 +export class BasicEditorComponent {
  8 +
  9 + @Input() article: noosfero.Article;
  10 +}
src/app/article/cms/basic-editor/basic-editor.html 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<form>
  2 + <div class="form-group">
  3 + <label for="titleInput">{{"article.basic_editor.title" | translate}}</label>
  4 + <input type="text" class="form-control" id="titleInput" placeholder="{{'article.basic_editor.title' | translate}}" ng-model="ctrl.article.name">
  5 + </div>
  6 + <div class="form-group">
  7 + <label for="bodyInput">{{"article.basic_editor.body" | translate}}</label>
  8 + <html-editor [(value)]="ctrl.article.body"></html-editor>
  9 + </div>
  10 +</form>
src/app/article/cms/basic-editor/basic-editor.scss 0 → 100644
src/app/article/cms/basic-options/basic-options.component.ts 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +import {Component, Input} from 'ng-forward';
  2 +
  3 +@Component({
  4 + selector: 'article-basic-options',
  5 + templateUrl: "app/article/cms/basic-options/basic-options.html"
  6 +})
  7 +export class BasicOptionsComponent {
  8 +
  9 + @Input() article: noosfero.Article;
  10 +
  11 +}
src/app/article/cms/basic-options/basic-options.html 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +<div class="side-options">
  2 + <div class="visibility panel panel-default">
  3 + <div class="panel-heading">{{"article.basic_editor.visibility" | translate}}</div>
  4 + <div class="panel-body">
  5 + <div>
  6 + <input type="radio" ng-model="ctrl.article.published" ng-value="true">
  7 + <i class="fa fa-unlock fa-fw"></i> {{"article.basic_editor.visibility.public" | translate}}
  8 + </div>
  9 + <div>
  10 + <input type="radio" ng-model="ctrl.article.published" ng-value="false">
  11 + <i class="fa fa-lock fa-fw"></i> {{"article.basic_editor.visibility.private" | translate}}
  12 + </div>
  13 + </div>
  14 + </div>
  15 +</div>
src/app/article/cms/basic-options/basic-options.scss 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +.side-options {
  2 + margin-top: 25px;
  3 +
  4 + .visibility {
  5 + .panel-heading {
  6 + background-color: transparent;
  7 + font-weight: bold;
  8 + }
  9 + .panel-body {
  10 + i {
  11 + color: #A5A5A5;
  12 + }
  13 + }
  14 + }
  15 +}
src/app/article/cms/cms.component.spec.ts 0 → 100644
@@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
  1 +import {quickCreateComponent} from "../../../spec/helpers";
  2 +import {CmsComponent} from "./cms.component";
  3 +
  4 +
  5 +describe("Article Cms", () => {
  6 +
  7 + let $rootScope: ng.IRootScopeService;
  8 + let $q: ng.IQService;
  9 + let articleServiceMock: any;
  10 + let profileServiceMock: any;
  11 + let $state: any;
  12 + let $stateParams: any;
  13 + let $window: any;
  14 + let profile = { id: 1 };
  15 + let notification: any;
  16 +
  17 +
  18 + beforeEach(inject((_$rootScope_: ng.IRootScopeService, _$q_: ng.IQService) => {
  19 + $rootScope = _$rootScope_;
  20 + $q = _$q_;
  21 + }));
  22 +
  23 + beforeEach(() => {
  24 + $window = jasmine.createSpyObj("$window", ["back"]);
  25 + $state = jasmine.createSpyObj("$state", ["go"]);
  26 + notification = jasmine.createSpyObj("notification", ["success"]);
  27 + profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["setCurrentProfileByIdentifier"]);
  28 + articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInParent", "updateArticle", "get"]);
  29 +
  30 + $stateParams = { profile: "profile" };
  31 +
  32 + let setCurrentProfileByIdentifierResponse = $q.defer();
  33 + setCurrentProfileByIdentifierResponse.resolve(profile);
  34 +
  35 + let articleCreate = $q.defer();
  36 + articleCreate.resolve({ data: { path: "path", profile: { identifier: "profile" } } });
  37 +
  38 + let articleGet = $q.defer();
  39 + articleGet.resolve({ data: { path: "parent-path", profile: { identifier: "profile" } } });
  40 +
  41 + profileServiceMock.setCurrentProfileByIdentifier = jasmine.createSpy("setCurrentProfileByIdentifier").and.returnValue(setCurrentProfileByIdentifierResponse.promise);
  42 + articleServiceMock.createInParent = jasmine.createSpy("createInParent").and.returnValue(articleCreate.promise);
  43 + articleServiceMock.updateArticle = jasmine.createSpy("updateArticle").and.returnValue(articleCreate.promise);
  44 + articleServiceMock.get = jasmine.createSpy("get").and.returnValue(articleGet.promise);
  45 + });
  46 +
  47 + it("create an article in the current profile when save", done => {
  48 + $stateParams['parent_id'] = 1;
  49 + let component: CmsComponent = new CmsComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  50 + component.save();
  51 + $rootScope.$apply();
  52 + expect(profileServiceMock.setCurrentProfileByIdentifier).toHaveBeenCalled();
  53 + expect(articleServiceMock.createInParent).toHaveBeenCalledWith(1, component.article);
  54 + done();
  55 + });
  56 +
  57 + it("got to the new article page and display an alert when saving sucessfully", done => {
  58 + let component: CmsComponent = new CmsComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  59 + component.save();
  60 + $rootScope.$apply();
  61 + expect($state.go).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "profile" });
  62 + expect(notification.success).toHaveBeenCalled();
  63 + done();
  64 + });
  65 +
  66 + it("go back when cancel article edition", done => {
  67 + let component: CmsComponent = new CmsComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  68 + $window.history = { back: jasmine.createSpy('back') };
  69 + component.cancel();
  70 + expect($window.history.back).toHaveBeenCalled();
  71 + done();
  72 + });
  73 +
  74 + it("edit existing article when save", done => {
  75 + $stateParams['parent_id'] = null;
  76 + $stateParams['id'] = 2;
  77 + let component: CmsComponent = new CmsComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  78 + component.save();
  79 + $rootScope.$apply();
  80 + expect(articleServiceMock.updateArticle).toHaveBeenCalledWith(component.article);
  81 + done();
  82 + });
  83 +
  84 +});
src/app/article/cms/cms.component.ts 0 → 100644
@@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
  1 +import {StateConfig, Component, Inject, provide} from 'ng-forward';
  2 +import {ArticleService} from "../../../lib/ng-noosfero-api/http/article.service";
  3 +import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service";
  4 +import {NotificationService} from "../../shared/services/notification.service.ts";
  5 +import {BasicOptionsComponent} from './basic-options/basic-options.component';
  6 +import {BasicEditorComponent} from './basic-editor/basic-editor.component';
  7 +import {ArticleEditorComponent} from './article-editor/article-editor.component';
  8 +
  9 +@Component({
  10 + selector: 'article-cms',
  11 + templateUrl: "app/article/cms/cms.html",
  12 + providers: [
  13 + provide('articleService', { useClass: ArticleService }),
  14 + provide('profileService', { useClass: ProfileService }),
  15 + provide('notification', { useClass: NotificationService })
  16 + ],
  17 + directives: [ArticleEditorComponent, BasicOptionsComponent, BasicEditorComponent]
  18 +})
  19 +@Inject(ArticleService, ProfileService, "$state", NotificationService, "$stateParams", "$window")
  20 +export class CmsComponent {
  21 +
  22 + article: noosfero.Article;
  23 + parent: noosfero.Article = <noosfero.Article>{};
  24 +
  25 + id: number;
  26 + parentId: number;
  27 + profileIdentifier: string;
  28 +
  29 + constructor(private articleService: ArticleService,
  30 + private profileService: ProfileService,
  31 + private $state: ng.ui.IStateService,
  32 + private notification: NotificationService,
  33 + private $stateParams: ng.ui.IStateParamsService,
  34 + private $window: ng.IWindowService) {
  35 +
  36 + this.parentId = this.$stateParams['parent_id'];
  37 + this.profileIdentifier = this.$stateParams["profile"];
  38 + this.id = this.$stateParams['id'];
  39 +
  40 + if (this.parentId) {
  41 + this.articleService.get(this.parentId).then((result: noosfero.RestResult<noosfero.Article>) => {
  42 + this.parent = result.data;
  43 + });
  44 + }
  45 + if (this.id) {
  46 + this.articleService.get(this.id).then((result: noosfero.RestResult<noosfero.Article>) => {
  47 + this.article = result.data;
  48 + this.article.name = this.article.title; // FIXME
  49 + });
  50 + } else {
  51 + this.article = <noosfero.Article>{ type: this.$stateParams['type'] || "TextArticle", published: true };
  52 + }
  53 + }
  54 +
  55 + save() {
  56 + this.profileService.setCurrentProfileByIdentifier(this.profileIdentifier).then((profile: noosfero.Profile) => {
  57 + if (this.id) {
  58 + return this.articleService.updateArticle(this.article);
  59 + } else if (this.parentId) {
  60 + return this.articleService.createInParent(this.parentId, this.article);
  61 + } else {
  62 + return this.articleService.createInProfile(profile, this.article);
  63 + }
  64 + }).then((response: noosfero.RestResult<noosfero.Article>) => {
  65 + let article = (<noosfero.Article>response.data);
  66 + this.$state.go('main.profile.page', { page: article.path, profile: article.profile.identifier });
  67 + this.notification.success({ title: "article.basic_editor.success.title", message: "article.basic_editor.success.message" });
  68 + }).catch(() => {
  69 + this.notification.error({ message: "article.basic_editor.save.failed" });
  70 + });
  71 + }
  72 +
  73 + cancel() {
  74 + this.$window.history.back();
  75 + }
  76 +
  77 +}
src/app/article/cms/cms.html 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 +<div class="cms">
  2 + <div class="row">
  3 + <div class="col-md-1"></div>
  4 + <div class="col-md-8">
  5 + <article-editor ng-if="vm.article" [article]="vm.article"></article-editor>
  6 + </div>
  7 + <div class="col-md-3">
  8 + <article-basic-options ng-if="vm.article" [article]="vm.article"></article-basic-options>
  9 + </div>
  10 + </div>
  11 + <div class="row">
  12 + <div class="col-md-1"></div>
  13 + <div class="col-md-8">
  14 + <button type="submit" class="btn btn-default" ng-click="vm.save()">{{"article.basic_editor.save" | translate}}</button>
  15 + <button type="button" class="btn btn-danger" ng-click="vm.cancel()">{{"article.basic_editor.cancel" | translate}}</button>
  16 + </div>
  17 + </div>
  18 +</div>
src/app/article/cms/cms.scss 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +.cms {
  2 + @extend .container-fluid;
  3 + padding: 0 1%;
  4 +}
src/app/article/comment/comment-reply-tooltip.html 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +<div class="reply-tooltip">
  2 + <noosfero-comment [comment]="ctrl.comment.reply_of" [article]="ctrl.article" [display-actions]="false" [display-replies]="false"></noosfero-comment>
  3 +</div>
src/app/article/comment/comment.component.ts
@@ -9,6 +9,8 @@ export class CommentComponent { @@ -9,6 +9,8 @@ export class CommentComponent {
9 9
10 @Input() comment: noosfero.CommentViewModel; 10 @Input() comment: noosfero.CommentViewModel;
11 @Input() article: noosfero.Article; 11 @Input() article: noosfero.Article;
  12 + @Input() displayActions = true;
  13 + @Input() displayReplies = true;
12 14
13 showReply() { 15 showReply() {
14 return this.comment && this.comment.__show_reply === true; 16 return this.comment && this.comment.__show_reply === true;
src/app/article/comment/comment.html
@@ -9,13 +9,19 @@ @@ -9,13 +9,19 @@
9 <a class="pull-left" ui-sref="main.profile.home({profile: ctrl.comment.author.identifier})"> 9 <a class="pull-left" ui-sref="main.profile.home({profile: ctrl.comment.author.identifier})">
10 <h4 class="media-heading">{{ctrl.comment.author.name}}</h4> 10 <h4 class="media-heading">{{ctrl.comment.author.name}}</h4>
11 </a> 11 </a>
  12 + <span class="reply-of" ng-if="ctrl.comment.reply_of" uib-tooltip-template="'app/article/comment/comment-reply-tooltip.html'">
  13 + <i class="fa fa-fw fa-mail-forward"></i>
  14 + <span class="author">{{ctrl.comment.reply_of.author.name}}</span>
  15 + </span>
12 <span class="date" am-time-ago="ctrl.comment.created_at | dateFormat"></span> 16 <span class="date" am-time-ago="ctrl.comment.created_at | dateFormat"></span>
13 </div> 17 </div>
14 <div class="title">{{ctrl.comment.title}}</div> 18 <div class="title">{{ctrl.comment.title}}</div>
15 <div class="body">{{ctrl.comment.body}}</div> 19 <div class="body">{{ctrl.comment.body}}</div>
16 - <a href="#" (click)="ctrl.reply()" class="small text-muted">  
17 - {{"comment.reply" | translate}}  
18 - </a> 20 + <div class="actions" ng-if="ctrl.displayActions">
  21 + <a href="#" (click)="ctrl.reply()" class="small text-muted" ng-if="ctrl.article.accept_comments">
  22 + {{"comment.reply" | translate}}
  23 + </a>
  24 + </div>
19 </div> 25 </div>
20 - <noosfero-comments [show-form]="ctrl.showReply()" [article]="ctrl.article" [parent]="ctrl.comment"></noosfero-comments> 26 + <noosfero-comments [show-form]="ctrl.showReply()" [article]="ctrl.article" [parent]="ctrl.comment" ng-if="ctrl.displayReplies"></noosfero-comments>
21 </div> 27 </div>
src/app/article/comment/comment.scss
@@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
5 @extend .text-muted; 5 @extend .text-muted;
6 @extend .small; 6 @extend .small;
7 margin-left: 8px; 7 margin-left: 8px;
  8 + font-size: 12px;
8 } 9 }
9 .title { 10 .title {
10 font-weight: bold; 11 font-weight: bold;
@@ -13,7 +14,18 @@ @@ -13,7 +14,18 @@
13 min-width: 40px; 14 min-width: 40px;
14 } 15 }
15 .media-body { 16 .media-body {
16 - padding: 0 10px 10px 10px; 17 + padding: 0 10px 10px 0;
  18 + .reply-of {
  19 + font-size: 12px;
  20 + color: #B5B5B5;
  21 + margin-left: 5px;
  22 + i {
  23 + font-size: 10px;
  24 + }
  25 + }
  26 + h4 {
  27 + font-size: 16px;
  28 + }
17 } 29 }
18 noosfero-profile-image { 30 noosfero-profile-image {
19 img { 31 img {
@@ -27,8 +39,24 @@ @@ -27,8 +39,24 @@
27 font-size: 1.7em; 39 font-size: 1.7em;
28 } 40 }
29 } 41 }
  42 + // Limit identation of replies
30 .comments { 43 .comments {
31 margin-left: 30px; 44 margin-left: 30px;
  45 + .comments .comments {
  46 + margin-left: 0px;
  47 + .comment {
  48 + margin-left: 0px;
  49 + }
  50 + }
  51 + }
  52 + .tooltip-inner {
  53 + max-width: 350px;
  54 + text-align: left;
  55 + .reply-tooltip {
  56 + .comment {
  57 + margin: 5px;
  58 + }
  59 + }
32 } 60 }
33 } 61 }
34 } 62 }
src/app/article/comment/comments.component.ts
1 -import { Inject, Input, Output, Component, provide, EventEmitter } from 'ng-forward';  
2 -import {INgForwardJQuery} from "ng-forward/cjs/util/jqlite-extensions";  
3 -  
4 - 1 +import { Inject, Input, Component, provide } from 'ng-forward';
5 import { PostCommentComponent } from "./post-comment/post-comment.component"; 2 import { PostCommentComponent } from "./post-comment/post-comment.component";
6 import { CommentService } from "../../../lib/ng-noosfero-api/http/comment.service"; 3 import { CommentService } from "../../../lib/ng-noosfero-api/http/comment.service";
7 import { CommentComponent } from "./comment.component"; 4 import { CommentComponent } from "./comment.component";
@@ -19,12 +16,13 @@ export class CommentsComponent { @@ -19,12 +16,13 @@ export class CommentsComponent {
19 @Input() showForm = true; 16 @Input() showForm = true;
20 @Input() article: noosfero.Article; 17 @Input() article: noosfero.Article;
21 @Input() parent: noosfero.CommentViewModel; 18 @Input() parent: noosfero.CommentViewModel;
22 -  
23 protected page = 1; 19 protected page = 1;
24 protected perPage = 5; 20 protected perPage = 5;
25 protected total = 0; 21 protected total = 0;
26 22
27 - constructor(protected commentService: CommentService) { } 23 + newComment = <noosfero.Comment>{};
  24 +
  25 + constructor(protected commentService: CommentService, private $scope: ng.IScope) { }
28 26
29 ngOnInit() { 27 ngOnInit() {
30 if (this.parent) { 28 if (this.parent) {
@@ -43,7 +41,7 @@ export class CommentsComponent { @@ -43,7 +41,7 @@ export class CommentsComponent {
43 this.comments.forEach((comment: noosfero.CommentViewModel) => { 41 this.comments.forEach((comment: noosfero.CommentViewModel) => {
44 comment.__show_reply = false; 42 comment.__show_reply = false;
45 }); 43 });
46 - if (this.parent) { 44 + if (this.parent) {
47 this.parent.__show_reply = false; 45 this.parent.__show_reply = false;
48 } 46 }
49 } 47 }
src/app/article/comment/comments.html
1 <div class="comments"> 1 <div class="comments">
2 - <noosfero-post-comment (comment-saved)="ctrl.commentAdded($event.detail)" ng-if="ctrl.showForm" [article]="ctrl.article" [parent]="ctrl.parent"></noosfero-post-comment> 2 + <noosfero-post-comment (comment-saved)="ctrl.commentAdded($event.detail)" ng-if="ctrl.showForm" [article]="ctrl.article" [parent]="ctrl.parent" [comment]="ctrl.newComment"></noosfero-post-comment>
3 3
4 <div class="comments-list"> 4 <div class="comments-list">
5 <noosfero-comment ng-repeat="comment in ctrl.comments | orderBy: 'created_at':true" [comment]="comment" [article]="ctrl.article"></noosfero-comment> 5 <noosfero-comment ng-repeat="comment in ctrl.comments | orderBy: 'created_at':true" [comment]="comment" [article]="ctrl.article"></noosfero-comment>
src/app/article/comment/comments.scss 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +.comments {
  2 + border-top: 2px solid #F3F3F3;
  3 + .comments {
  4 + border-top: 0;
  5 + }
  6 +}
src/app/article/comment/post-comment/post-comment.component.spec.ts
@@ -7,6 +7,8 @@ const htmlTemplate: string = &#39;&lt;noosfero-post-comment [article]=&quot;ctrl.article&quot; [r @@ -7,6 +7,8 @@ const htmlTemplate: string = &#39;&lt;noosfero-post-comment [article]=&quot;ctrl.article&quot; [r
7 describe("Components", () => { 7 describe("Components", () => {
8 describe("Post Comment Component", () => { 8 describe("Post Comment Component", () => {
9 9
  10 + let properties = { article: { id: 1, accept_comments: true } };
  11 +
10 beforeEach(angular.mock.module("templates")); 12 beforeEach(angular.mock.module("templates"));
11 13
12 let commentService = jasmine.createSpyObj("commentService", ["createInArticle"]); 14 let commentService = jasmine.createSpyObj("commentService", ["createInArticle"]);
@@ -19,7 +21,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -19,7 +21,7 @@ describe(&quot;Components&quot;, () =&gt; {
19 21
20 @Component({ selector: 'test-container-component', directives: [PostCommentComponent], template: htmlTemplate, providers: providers }) 22 @Component({ selector: 'test-container-component', directives: [PostCommentComponent], template: htmlTemplate, providers: providers })
21 class ContainerComponent { 23 class ContainerComponent {
22 - article = { id: 1 }; 24 + article = properties['article'];
23 comment = { id: 2 }; 25 comment = { id: 2 };
24 } 26 }
25 27
@@ -30,6 +32,14 @@ describe(&quot;Components&quot;, () =&gt; { @@ -30,6 +32,14 @@ describe(&quot;Components&quot;, () =&gt; {
30 }); 32 });
31 }); 33 });
32 34
  35 + it("not render the post comment form when article doesn't accept comments", done => {
  36 + properties['article'].accept_comments = false;
  37 + helpers.createComponentFromClass(ContainerComponent).then(fixture => {
  38 + expect(fixture.debugElement.queryAll("form").length).toEqual(0);
  39 + done();
  40 + });
  41 + });
  42 +
33 it("emit an event when create comment", done => { 43 it("emit an event when create comment", done => {
34 helpers.createComponentFromClass(ContainerComponent).then(fixture => { 44 helpers.createComponentFromClass(ContainerComponent).then(fixture => {
35 let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance; 45 let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
src/app/article/comment/post-comment/post-comment.component.ts
@@ -2,20 +2,23 @@ import { Inject, Input, Output, EventEmitter, Component } from &#39;ng-forward&#39;; @@ -2,20 +2,23 @@ import { Inject, Input, Output, EventEmitter, Component } from &#39;ng-forward&#39;;
2 import { CommentService } from "../../../../lib/ng-noosfero-api/http/comment.service"; 2 import { CommentService } from "../../../../lib/ng-noosfero-api/http/comment.service";
3 import { NotificationService } from "../../../shared/services/notification.service"; 3 import { NotificationService } from "../../../shared/services/notification.service";
4 import { SessionService } from "../../../login"; 4 import { SessionService } from "../../../login";
  5 +import { CommentFormHotspotComponent } from "../../../hotspot/comment-form-hotspot.component";
5 6
6 @Component({ 7 @Component({
7 selector: 'noosfero-post-comment', 8 selector: 'noosfero-post-comment',
8 templateUrl: 'app/article/comment/post-comment/post-comment.html', 9 templateUrl: 'app/article/comment/post-comment/post-comment.html',
9 - outputs: ['commentSaved'] 10 + outputs: ['commentSaved'],
  11 + directives: [CommentFormHotspotComponent]
10 }) 12 })
11 @Inject(CommentService, NotificationService, SessionService) 13 @Inject(CommentService, NotificationService, SessionService)
12 export class PostCommentComponent { 14 export class PostCommentComponent {
13 15
  16 + public static EVENT_COMMENT_RECEIVED = "comment.received";
  17 +
14 @Input() article: noosfero.Article; 18 @Input() article: noosfero.Article;
15 @Input() parent: noosfero.Comment; 19 @Input() parent: noosfero.Comment;
16 @Output() commentSaved: EventEmitter<Comment> = new EventEmitter<Comment>(); 20 @Output() commentSaved: EventEmitter<Comment> = new EventEmitter<Comment>();
17 -  
18 - comment = <noosfero.Comment>{}; 21 + @Input() comment = <noosfero.Comment>{};
19 private currentUser: noosfero.User; 22 private currentUser: noosfero.User;
20 23
21 constructor(private commentService: CommentService, 24 constructor(private commentService: CommentService,
src/app/article/comment/post-comment/post-comment.html
1 -<form class="clearfix post-comment"> 1 +<form class="clearfix post-comment" ng-if="ctrl.article.accept_comments">
2 <div class="form-group"> 2 <div class="form-group">
3 <div class="comment media"> 3 <div class="comment media">
4 <div class="media-left"> 4 <div class="media-left">
@@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
8 </div> 8 </div>
9 <div class="media-body"> 9 <div class="media-body">
10 <textarea class="form-control custom-control" rows="1" ng-model="ctrl.comment.body" placeholder="{{'comment.post.placeholder' | translate}}"></textarea> 10 <textarea class="form-control custom-control" rows="1" ng-model="ctrl.comment.body" placeholder="{{'comment.post.placeholder' | translate}}"></textarea>
  11 + <noosfero-hotspot-comment-form [comment]="ctrl.comment" [parent]="ctrl.parent"></noosfero-hotspot-comment-form>
11 <button ng-show="ctrl.comment.body" type="submit" class="btn btn-default pull-right ng-hide" ng-click="ctrl.save()">{{"comment.post" | translate}}</button> 12 <button ng-show="ctrl.comment.body" type="submit" class="btn btn-default pull-right ng-hide" ng-click="ctrl.save()">{{"comment.post" | translate}}</button>
12 </div> 13 </div>
13 </div> 14 </div>
src/app/article/comment/post-comment/post-comment.scss
1 .comments { 1 .comments {
2 .post-comment { 2 .post-comment {
3 .media { 3 .media {
4 - border-top: 2px solid #F3F3F3;  
5 padding-top: 10px; 4 padding-top: 10px;
6 .media-left { 5 .media-left {
7 padding: 10px 0; 6 padding: 10px 0;
src/app/article/content-viewer/navbar-actions.html
@@ -4,4 +4,9 @@ @@ -4,4 +4,9 @@
4 <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}} 4 <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}}
5 </a> 5 </a>
6 </li> 6 </li>
  7 + <li ng-show="vm.profile">
  8 + <a href="#" role="button" ui-sref="main.cms({profile: vm.profile.identifier, parent_id: vm.parentId, type: 'CommentParagraphPlugin::Discussion'})">
  9 + <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}}
  10 + </a>
  11 + </li>
7 </ul> 12 </ul>
src/app/article/macro/macro.directive.spec.ts 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +import {Input, provide, Component} from 'ng-forward';
  2 +import {MacroDirective} from "./macro.directive";
  3 +
  4 +import * as helpers from "../../../spec/helpers";
  5 +
  6 +const htmlTemplate: string = '<div data-macro="macro_component" data-macro-custom="custom"></div>';
  7 +
  8 +describe("Directives", () => {
  9 +
  10 + describe("Macro directive", () => {
  11 + it("renders a macro component using the name passed in data-macro", (done: Function) => {
  12 + helpers.quickCreateComponent({ template: htmlTemplate, directives: [MacroDirective] }).then((fixture) => {
  13 + expect(fixture.debugElement.queryAll('macro-component').length).toEqual(1);
  14 + done();
  15 + });
  16 + });
  17 +
  18 + it("extract custom attributes from macro", (done: Function) => {
  19 + helpers.quickCreateComponent({ template: htmlTemplate, directives: [MacroDirective] }).then((fixture) => {
  20 + expect(fixture.debugElement.query('macro-component').attr("custom")).toEqual("custom");
  21 + done();
  22 + });
  23 + });
  24 + });
  25 +});
src/app/article/macro/macro.directive.ts 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +import {Directive, Inject} from "ng-forward";
  2 +
  3 +@Directive({
  4 + selector: '[macro]',
  5 + providers: []
  6 +})
  7 +@Inject('$element', '$scope', '$compile')
  8 +export class MacroDirective {
  9 +
  10 + private macroPrefix = "data-macro";
  11 +
  12 + constructor(private $element: any, private $scope: ng.IScope, private $compile: ng.ICompileService) {
  13 + let macro = $element[0].attributes[this.macroPrefix].value;
  14 + let componentName = this.normalizeName(macro);
  15 + let content = $element.html().replace(/"/g, '&quot;');
  16 + let customAttributes = this.extractCustomAttributes($element[0].attributes);
  17 + $element.replaceWith($compile(`<${componentName} [article]="ctrl.article" content="${content}" ${customAttributes}></${componentName}>`)($scope));
  18 + }
  19 +
  20 + extractCustomAttributes(attributes: any) {
  21 + let customAttributes = "";
  22 + for (let attr of attributes) {
  23 + if (attr.name.startsWith(this.macroPrefix + '-')) {
  24 + let name = this.normalizeName(attr.name.replace(this.macroPrefix + '-', ''));
  25 + customAttributes += ` ${name}='${attr.value}'`;
  26 + }
  27 + }
  28 + return customAttributes;
  29 + }
  30 +
  31 + normalizeName(name: string) {
  32 + return name.replace(/[_\/]/g, '-').toLowerCase();
  33 + }
  34 +}
src/app/hotspot/article-content-hotspot.component.ts 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +import {Component, Input, Inject} from "ng-forward";
  2 +import * as plugins from "../../plugins";
  3 +import {dasherize} from "ng-forward/cjs/util/helpers";
  4 +import {PluginHotspot} from "./plugin-hotspot";
  5 +
  6 +@Component({
  7 + selector: "noosfero-hotspot-article-content",
  8 + template: "<span></span>"
  9 +})
  10 +@Inject("$element", "$scope", "$compile")
  11 +export class ArticleContentHotspotComponent extends PluginHotspot {
  12 +
  13 + @Input() article: noosfero.Article;
  14 +
  15 + constructor(
  16 + private $element: any,
  17 + private $scope: ng.IScope,
  18 + private $compile: ng.ICompileService) {
  19 + super("article_extra_content");
  20 + }
  21 +
  22 + addHotspot(directiveName: string) {
  23 + this.$element.append(this.$compile('<' + directiveName + ' [article]="ctrl.article"></' + directiveName + '>')(this.$scope));
  24 + }
  25 +}
src/app/hotspot/article-toolbar-hotspot.component.ts 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +import {Component, Input, Inject} from "ng-forward";
  2 +import * as plugins from "../../plugins";
  3 +import {dasherize} from "ng-forward/cjs/util/helpers";
  4 +import {PluginHotspot} from "./plugin-hotspot";
  5 +
  6 +@Component({
  7 + selector: "noosfero-hotspot-article-toolbar",
  8 + template: "<span></span>"
  9 +})
  10 +@Inject("$element", "$scope", "$compile")
  11 +export class ArticleToolbarHotspotComponent extends PluginHotspot {
  12 +
  13 + @Input() article: noosfero.Article;
  14 +
  15 + constructor(
  16 + private $element: any,
  17 + private $scope: ng.IScope,
  18 + private $compile: ng.ICompileService) {
  19 + super("article_extra_toolbar_buttons");
  20 + }
  21 +
  22 + addHotspot(directiveName: string) {
  23 + this.$element.append(this.$compile('<' + directiveName + ' [article]="ctrl.article"></' + directiveName + '>')(this.$scope));
  24 + }
  25 +}
src/app/hotspot/comment-form-hotspot.component.ts 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +import {Component, Input, Inject} from "ng-forward";
  2 +import * as plugins from "../../plugins";
  3 +import {dasherize} from "ng-forward/cjs/util/helpers";
  4 +import {PluginHotspot} from "./plugin-hotspot";
  5 +
  6 +@Component({
  7 + selector: "noosfero-hotspot-comment-form",
  8 + template: "<span></span>"
  9 +})
  10 +@Inject("$element", "$scope", "$compile")
  11 +export class CommentFormHotspotComponent extends PluginHotspot {
  12 +
  13 + @Input() comment: noosfero.Comment;
  14 + @Input() parent: noosfero.Comment;
  15 +
  16 + constructor(
  17 + private $element: any,
  18 + private $scope: ng.IScope,
  19 + private $compile: ng.ICompileService) {
  20 + super("comment_form_extra_contents");
  21 + }
  22 +
  23 + addHotspot(directiveName: string) {
  24 + this.$element.append(this.$compile('<' + directiveName + ' [comment]="ctrl.comment" [parent]="ctrl.parent"></' + directiveName + '>')(this.$scope));
  25 + }
  26 +}
src/app/hotspot/hotspot.decorator.ts 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +export function Hotspot(hotspotName: string) {
  2 + return (target: any) => {
  3 + target['hotspot'] = hotspotName;
  4 + };
  5 +}
src/app/hotspot/plugin-hotspot.ts 0 → 100644
@@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
  1 +import {Component, Input, Inject} from "ng-forward";
  2 +import * as plugins from "../../plugins";
  3 +import {dasherize} from "ng-forward/cjs/util/helpers";
  4 +
  5 +export abstract class PluginHotspot {
  6 +
  7 + constructor(protected hotspot: string) { }
  8 +
  9 + ngOnInit() {
  10 + for (let component of plugins.hotspots) {
  11 + if (component.hotspot === this.hotspot) {
  12 + let directiveName = dasherize(component.name.replace('Component', ''));
  13 + this.addHotspot(directiveName);
  14 + }
  15 + }
  16 + }
  17 +
  18 + abstract addHotspot(directiveName: string): any;
  19 +}
src/app/index.scss
@@ -28,12 +28,16 @@ $primary-color-dark: #0288d1; @@ -28,12 +28,16 @@ $primary-color-dark: #0288d1;
28 $default-bg-hover-color: #f8f8f8; 28 $default-bg-hover-color: #f8f8f8;
29 $red-color: #e84e40; 29 $red-color: #e84e40;
30 $red-color-dark: #dd191d; 30 $red-color-dark: #dd191d;
  31 +$green-color: #8bc34a;
  32 +$green-color-dark: #689f38;
31 33
32 //GRID - media queries breakpoints 34 //GRID - media queries breakpoints
33 $break-xxs-min: 420px; 35 $break-xxs-min: 420px;
34 $break-xs-min: 768px; 36 $break-xs-min: 768px;
  37 +$break-sm-min: 992px;
35 38
36 $break-xxs-max: ($break-xxs-min - 1); 39 $break-xxs-max: ($break-xxs-min - 1);
  40 +$break-sm-max: ($break-sm-min - 1);
37 $break-xs-max: ($break-xs-min - 1); 41 $break-xs-max: ($break-xs-min - 1);
38 42
39 43
@@ -76,4 +80,5 @@ h1, h2, h3, h4, h5 { @@ -76,4 +80,5 @@ h1, h2, h3, h4, h5 {
76 @import "layout/scss/mixins"; 80 @import "layout/scss/mixins";
77 @import "layout/scss/bootstrap-overrides"; 81 @import "layout/scss/bootstrap-overrides";
78 @import "layout/scss/layout"; 82 @import "layout/scss/layout";
  83 +@import "layout/scss/sidebar";
79 @import "layout/scss/tables"; 84 @import "layout/scss/tables";
src/app/index.ts
@@ -5,7 +5,7 @@ import {noosferoAngularRunBlock} from &quot;./index.run&quot;; @@ -5,7 +5,7 @@ import {noosferoAngularRunBlock} from &quot;./index.run&quot;;
5 import {MainComponent} from "./main/main.component"; 5 import {MainComponent} from "./main/main.component";
6 import {bootstrap, bundle} from "ng-forward"; 6 import {bootstrap, bundle} from "ng-forward";
7 7
8 -import {AUTH_EVENTS} from "./login/auth-events"; 8 +import {AuthEvents} from "./login/auth-events";
9 import {AuthController} from "./login/auth.controller"; 9 import {AuthController} from "./login/auth.controller";
10 10
11 import {Navbar} from "./layout/navbar/navbar"; 11 import {Navbar} from "./layout/navbar/navbar";
@@ -15,15 +15,16 @@ declare var moment: any; @@ -15,15 +15,16 @@ declare var moment: any;
15 let noosferoApp: any = bundle("noosferoApp", MainComponent, ["ngAnimate", "ngCookies", "ngStorage", "ngTouch", 15 let noosferoApp: any = bundle("noosferoApp", MainComponent, ["ngAnimate", "ngCookies", "ngStorage", "ngTouch",
16 "ngSanitize", "ngMessages", "ngAria", "restangular", 16 "ngSanitize", "ngMessages", "ngAria", "restangular",
17 "ui.router", "ui.bootstrap", "toastr", "ngCkeditor", 17 "ui.router", "ui.bootstrap", "toastr", "ngCkeditor",
18 - "angularMoment", "angular.filter", "akoenig.deckgrid", 18 + "angular-bind-html-compile", "angularMoment", "angular.filter", "akoenig.deckgrid",
19 "angular-timeline", "duScroll", "oitozero.ngSweetAlert", 19 "angular-timeline", "duScroll", "oitozero.ngSweetAlert",
20 - "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad"]).publish(); 20 + "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad",
  21 + "angular-click-outside"]).publish();
21 22
22 NoosferoApp.angularModule = noosferoApp; 23 NoosferoApp.angularModule = noosferoApp;
23 24
24 25
25 NoosferoApp.addConstants("moment", moment); 26 NoosferoApp.addConstants("moment", moment);
26 -NoosferoApp.addConstants("AUTH_EVENTS", AUTH_EVENTS); 27 +NoosferoApp.addConstants("AuthEvents", AuthEvents);
27 28
28 NoosferoApp.addConfig(noosferoModuleConfig); 29 NoosferoApp.addConfig(noosferoModuleConfig);
29 NoosferoApp.run(noosferoAngularRunBlock); 30 NoosferoApp.run(noosferoAngularRunBlock);
src/app/layout/blocks/block.component.ts
@@ -11,7 +11,7 @@ export class BlockComponent { @@ -11,7 +11,7 @@ export class BlockComponent {
11 @Input() owner: any; 11 @Input() owner: any;
12 12
13 ngOnInit() { 13 ngOnInit() {
14 - let blockName = (this.block && this.block.type) ? this.block.type.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() : "default-block"; 14 + let blockName = (this.block && this.block.type) ? this.block.type.replace(/::/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() : "default-block";
15 this.$element.replaceWith(this.$compile('<noosfero-' + blockName + ' [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-' + blockName + '>')(this.$scope)); 15 this.$element.replaceWith(this.$compile('<noosfero-' + blockName + ' [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-' + blockName + '>')(this.$scope));
16 } 16 }
17 17
src/app/layout/blocks/communities-block/communities-block.component.spec.ts
@@ -1,51 +0,0 @@ @@ -1,51 +0,0 @@
1 -import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';  
2 -import {Provider, Input, provide, Component} from 'ng-forward';  
3 -  
4 -import {CommunitiesBlockComponent} from './communities-block.component';  
5 -  
6 -const htmlTemplate: string = '<noosfero-communities-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-communities-block>';  
7 -  
8 -const tcb = new TestComponentBuilder();  
9 -  
10 -describe("Components", () => {  
11 - describe("Communities Block Component", () => {  
12 -  
13 - beforeEach(angular.mock.module("templates"));  
14 -  
15 - let state = jasmine.createSpyObj("state", ["go"]);  
16 - let providers = [  
17 - new Provider('truncateFilter', { useValue: () => { } }),  
18 - new Provider('stripTagsFilter', { useValue: () => { } }),  
19 - new Provider('$state', { useValue: state }),  
20 - new Provider('CommunityService', {  
21 - useValue: {  
22 - getByOwner: (owner: any, params: any): any => {  
23 - return Promise.resolve({ data: [{ identifier: "community1" }] });  
24 - }  
25 - }  
26 - }),  
27 - ];  
28 - @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [CommunitiesBlockComponent], providers: providers })  
29 - class BlockContainerComponent {  
30 - block = { type: 'Block', settings: {} };  
31 - owner = { name: 'profile-name' };  
32 - }  
33 -  
34 - it("get communities", done => {  
35 - tcb.createAsync(BlockContainerComponent).then(fixture => {  
36 - let block: CommunitiesBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;  
37 - expect(block.profiles).toEqual([{ identifier: "community1" }]);  
38 - done();  
39 - });  
40 - });  
41 -  
42 - it("render the profile image for each community", done => {  
43 - tcb.createAsync(BlockContainerComponent).then(fixture => {  
44 - fixture.debugElement.getLocal("$rootScope").$apply();  
45 - expect(fixture.debugElement.queryAll("noosfero-profile-image").length).toEqual(1);  
46 - done();  
47 - });  
48 - });  
49 -  
50 - });  
51 -});  
src/app/layout/blocks/communities-block/communities-block.component.ts
@@ -1,24 +0,0 @@ @@ -1,24 +0,0 @@
1 -import {Input, Inject, Component} from "ng-forward";  
2 -import {CommunityService} from "../../../../lib/ng-noosfero-api/http/community.service";  
3 -  
4 -@Component({  
5 - selector: "noosfero-communities-block",  
6 - templateUrl: 'app/layout/blocks/communities-block/communities-block.html',  
7 -})  
8 -@Inject(CommunityService)  
9 -export class CommunitiesBlockComponent {  
10 -  
11 - @Input() block: noosfero.Block;  
12 - @Input() owner: noosfero.Profile;  
13 -  
14 - profiles: any = [];  
15 -  
16 - constructor(private communityService: CommunityService) { }  
17 -  
18 - ngOnInit() {  
19 - let limit: number = ((this.block && this.block.settings) ? this.block.settings.limit : null) || 5;  
20 - this.communityService.getByOwner(this.owner, { limit: limit }).then((result: noosfero.RestResult<noosfero.Community[]>) => {  
21 - this.profiles = result.data;  
22 - });  
23 - }  
24 -}  
src/app/layout/blocks/communities-block/communities-block.html
@@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
1 -<div class="communities-block">  
2 - <a ng-repeat="profile in ctrl.profiles" ui-sref="main.profile.home({profile: profile.identifier})" class="profile">  
3 - <noosfero-profile-image [profile]="profile"></noosfero-profile-image>  
4 - </a>  
5 -</div>  
src/app/layout/blocks/communities-block/communities-block.scss
@@ -1,16 +0,0 @@ @@ -1,16 +0,0 @@
1 -.communities-block {  
2 - .profile {  
3 - margin: 10px;  
4 - img, i.profile-image {  
5 - width: 60px;  
6 - }  
7 - img {  
8 - display: inline-block;  
9 - vertical-align: top;  
10 - }  
11 - i.profile-image {  
12 - text-align: center;  
13 - font-size: 4.5em;  
14 - }  
15 - }  
16 -}  
src/app/layout/blocks/communities/communities-block.component.spec.ts 0 → 100644
@@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Provider, Input, provide, Component} from 'ng-forward';
  3 +
  4 +import {CommunitiesBlockComponent} from './communities-block.component';
  5 +
  6 +const htmlTemplate: string = '<noosfero-communities-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-communities-block>';
  7 +
  8 +const tcb = new TestComponentBuilder();
  9 +
  10 +describe("Components", () => {
  11 + describe("Communities Block Component", () => {
  12 +
  13 + beforeEach(angular.mock.module("templates"));
  14 +
  15 + let state = jasmine.createSpyObj("state", ["go"]);
  16 + let providers = [
  17 + new Provider('truncateFilter', { useValue: () => { } }),
  18 + new Provider('stripTagsFilter', { useValue: () => { } }),
  19 + new Provider('$state', { useValue: state }),
  20 + new Provider('CommunityService', {
  21 + useValue: {
  22 + getByOwner: (owner: any, params: any): any => {
  23 + return Promise.resolve({ data: [{ identifier: "community1" }] });
  24 + }
  25 + }
  26 + }),
  27 + ];
  28 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [CommunitiesBlockComponent], providers: providers })
  29 + class BlockContainerComponent {
  30 + block = { type: 'Block', settings: {} };
  31 + owner = { name: 'profile-name' };
  32 + }
  33 +
  34 + it("get communities", done => {
  35 + tcb.createAsync(BlockContainerComponent).then(fixture => {
  36 + let block: CommunitiesBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  37 + expect(block.profiles).toEqual([{ identifier: "community1" }]);
  38 + done();
  39 + });
  40 + });
  41 +
  42 + it("render the profile image for each community", done => {
  43 + tcb.createAsync(BlockContainerComponent).then(fixture => {
  44 + fixture.debugElement.getLocal("$rootScope").$apply();
  45 + expect(fixture.debugElement.queryAll("noosfero-profile-image").length).toEqual(1);
  46 + done();
  47 + });
  48 + });
  49 +
  50 + });
  51 +});
src/app/layout/blocks/communities/communities-block.component.ts 0 → 100644
@@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
  1 +import {Input, Inject, Component} from "ng-forward";
  2 +import {CommunityService} from "../../../../lib/ng-noosfero-api/http/community.service";
  3 +
  4 +@Component({
  5 + selector: "noosfero-communities-block",
  6 + templateUrl: 'app/layout/blocks/communities/communities-block.html',
  7 +})
  8 +@Inject(CommunityService)
  9 +export class CommunitiesBlockComponent {
  10 +
  11 + @Input() block: noosfero.Block;
  12 + @Input() owner: noosfero.Profile;
  13 +
  14 + profiles: any = [];
  15 +
  16 + constructor(private communityService: CommunityService) { }
  17 +
  18 + ngOnInit() {
  19 + let limit: number = ((this.block && this.block.settings) ? this.block.settings.limit : null) || 5;
  20 + this.communityService.getByOwner(this.owner, { limit: limit }).then((result: noosfero.RestResult<noosfero.Community[]>) => {
  21 + this.profiles = result.data;
  22 + });
  23 + }
  24 +}
src/app/layout/blocks/communities/communities-block.html 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +<div class="communities-block">
  2 + <a ng-repeat="profile in ctrl.profiles" ui-sref="main.profile.home({profile: profile.identifier})" class="profile">
  3 + <noosfero-profile-image [profile]="profile"></noosfero-profile-image>
  4 + </a>
  5 +</div>
src/app/layout/blocks/communities/communities-block.scss 0 → 100644
@@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
  1 +.communities-block {
  2 + .profile {
  3 + margin: 10px;
  4 + img, i.profile-image {
  5 + width: 60px;
  6 + }
  7 + img {
  8 + display: inline-block;
  9 + vertical-align: top;
  10 + }
  11 + i.profile-image {
  12 + text-align: center;
  13 + font-size: 4.5em;
  14 + }
  15 + }
  16 +}
src/app/layout/blocks/communities/index.ts 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./communities-block.component";
src/app/layout/blocks/link-list/index.ts
1 /* Module Index Entry - generated using the script npm run generate-index */ 1 /* Module Index Entry - generated using the script npm run generate-index */
2 -export * from "./link-list.component"; 2 +export * from "./link-list-block.component";
src/app/layout/blocks/link-list/link-list-block.component.spec.ts 0 → 100644
@@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Pipe, Input, provide, Component} from 'ng-forward';
  3 +import {provideFilters} from '../../../../spec/helpers';
  4 +
  5 +import {LinkListBlockComponent} from './link-list-block.component';
  6 +
  7 +const tcb = new TestComponentBuilder();
  8 +
  9 +const htmlTemplate: string = '<noosfero-link-list-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-link-list-block>';
  10 +
  11 +
  12 +describe("Components", () => {
  13 +
  14 + describe("Link List Block Component", () => {
  15 +
  16 + beforeEach(angular.mock.module("templates"));
  17 +
  18 + it("receives the block and the owner as inputs", done => {
  19 +
  20 + // Creating a container component (BlockContainerComponent) to include
  21 + // the component under test (Block)
  22 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [LinkListBlockComponent] })
  23 + class BlockContainerComponent {
  24 + block = { type: 'Block' };
  25 + owner = { name: 'profile-name' };
  26 + constructor() {
  27 + }
  28 + }
  29 +
  30 + // uses the TestComponentBuilder instance to initialize the component
  31 + // .overrideView(LinkListBlock, { template: 'asdasdasd', pipes: [NoosferoTemplate] })
  32 + tcb.createAsync(BlockContainerComponent).then(fixture => {
  33 + // and here we can inspect and run the test assertions
  34 + let myComponent: LinkListBlockComponent = fixture.componentInstance;
  35 +
  36 + // assure the block object inside the Block matches
  37 + // the provided through the parent component
  38 + expect(myComponent.block.type).toEqual("Block");
  39 + expect(myComponent.owner.name).toEqual("profile-name");
  40 + done();
  41 + });
  42 + });
  43 +
  44 +
  45 + it("display links stored in block settings", done => {
  46 +
  47 + @Component({
  48 + selector: 'test-container-component',
  49 + template: htmlTemplate,
  50 + directives: [LinkListBlockComponent],
  51 + providers: provideFilters("noosferoTemplateFilter")
  52 + })
  53 + class CustomBlockType {
  54 + block: any = { settings: { links: [{ name: 'link1', address: 'address1' }, { name: 'link2', address: 'address2' }] } };
  55 + owner: any = { name: 'profile-name' };
  56 + }
  57 + tcb.createAsync(CustomBlockType).then(fixture => {
  58 + expect(fixture.debugElement.queryAll(".link-list-block a").length).toEqual(2);
  59 + done();
  60 + });
  61 + });
  62 +
  63 + });
  64 +
  65 +});
src/app/layout/blocks/link-list/link-list-block.component.ts 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +import {Component, Input} from "ng-forward";
  2 +
  3 +@Component({
  4 + selector: "noosfero-link-list-block",
  5 + templateUrl: "app/layout/blocks/link-list/link-list-block.html"
  6 +})
  7 +export class LinkListBlockComponent {
  8 +
  9 + @Input() block: any;
  10 + @Input() owner: any;
  11 +
  12 + links: any;
  13 +
  14 + ngOnInit() {
  15 + if (this.block && this.block.settings) {
  16 + this.links = this.block.settings.links;
  17 + }
  18 + }
  19 +
  20 +}
src/app/layout/blocks/link-list/link-list-block.html 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +<div class="link-list-block">
  2 + <div ng-repeat="link in ctrl.links">
  3 + <a ng-href="{{link.address | noosferoTemplate:{profile: ctrl.owner.identifier} }}">
  4 + <i class="fa fa-fw icon-{{link.icon}}"></i> <span>{{link.name}}</span>
  5 + </a>
  6 + </div>
  7 +</div>
src/app/layout/blocks/link-list/link-list-block.scss 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +.icon-event {
  2 + @extend .fa-calendar;
  3 +}
  4 +.icon-photos {
  5 + @extend .fa-photo;
  6 +}
  7 +.icon-edit {
  8 + @extend .fa-edit;
  9 +}
  10 +.icon-ok {
  11 + @extend .fa-check;
  12 +}
  13 +.icon-send {
  14 + @extend .fa-send-o;
  15 +}
  16 +.icon-menu-people {
  17 + @extend .fa-user;
  18 +}
  19 +.icon-forum {
  20 + @extend .fa-users;
  21 +}
  22 +.icon-new {
  23 + @extend .fa-file-o;
  24 +}
  25 +.icon-save {
  26 + @extend .fa-save;
  27 +}
  28 +
  29 +.link-list-block {
  30 + a i {
  31 + line-height: 25px;
  32 + color: #949494;
  33 + }
  34 +}
src/app/layout/blocks/link-list/link-list.component.spec.ts
@@ -1,65 +0,0 @@ @@ -1,65 +0,0 @@
1 -import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';  
2 -import {Pipe, Input, provide, Component} from 'ng-forward';  
3 -import {provideFilters} from '../../../../spec/helpers';  
4 -  
5 -import {LinkListBlockComponent} from './link-list.component';  
6 -  
7 -const tcb = new TestComponentBuilder();  
8 -  
9 -const htmlTemplate: string = '<noosfero-link-list-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-link-list-block>';  
10 -  
11 -  
12 -describe("Components", () => {  
13 -  
14 - describe("Link List Block Component", () => {  
15 -  
16 - beforeEach(angular.mock.module("templates"));  
17 -  
18 - it("receives the block and the owner as inputs", done => {  
19 -  
20 - // Creating a container component (BlockContainerComponent) to include  
21 - // the component under test (Block)  
22 - @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [LinkListBlockComponent] })  
23 - class BlockContainerComponent {  
24 - block = { type: 'Block' };  
25 - owner = { name: 'profile-name' };  
26 - constructor() {  
27 - }  
28 - }  
29 -  
30 - // uses the TestComponentBuilder instance to initialize the component  
31 - // .overrideView(LinkListBlock, { template: 'asdasdasd', pipes: [NoosferoTemplate] })  
32 - tcb.createAsync(BlockContainerComponent).then(fixture => {  
33 - // and here we can inspect and run the test assertions  
34 - let myComponent: LinkListBlockComponent = fixture.componentInstance;  
35 -  
36 - // assure the block object inside the Block matches  
37 - // the provided through the parent component  
38 - expect(myComponent.block.type).toEqual("Block");  
39 - expect(myComponent.owner.name).toEqual("profile-name");  
40 - done();  
41 - });  
42 - });  
43 -  
44 -  
45 - it("display links stored in block settings", done => {  
46 -  
47 - @Component({  
48 - selector: 'test-container-component',  
49 - template: htmlTemplate,  
50 - directives: [LinkListBlockComponent],  
51 - providers: provideFilters("noosferoTemplateFilter")  
52 - })  
53 - class CustomBlockType {  
54 - block: any = { settings: { links: [{ name: 'link1', address: 'address1' }, { name: 'link2', address: 'address2' }] } };  
55 - owner: any = { name: 'profile-name' };  
56 - }  
57 - tcb.createAsync(CustomBlockType).then(fixture => {  
58 - expect(fixture.debugElement.queryAll(".link-list-block a").length).toEqual(2);  
59 - done();  
60 - });  
61 - });  
62 -  
63 - });  
64 -  
65 -});  
66 \ No newline at end of file 0 \ No newline at end of file
src/app/layout/blocks/link-list/link-list.component.ts
@@ -1,20 +0,0 @@ @@ -1,20 +0,0 @@
1 -import {Component, Input} from "ng-forward";  
2 -  
3 -@Component({  
4 - selector: "noosfero-link-list-block",  
5 - templateUrl: "app/layout/blocks/link-list/link-list.html"  
6 -})  
7 -export class LinkListBlockComponent {  
8 -  
9 - @Input() block: any;  
10 - @Input() owner: any;  
11 -  
12 - links: any;  
13 -  
14 - ngOnInit() {  
15 - if (this.block && this.block.settings) {  
16 - this.links = this.block.settings.links;  
17 - }  
18 - }  
19 -  
20 -}  
src/app/layout/blocks/link-list/link-list.html
@@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
1 -<div class="link-list-block">  
2 - <div ng-repeat="link in ctrl.links">  
3 - <a ng-href="{{link.address | noosferoTemplate:{profile: ctrl.owner.identifier} }}">  
4 - <i class="fa fa-fw icon-{{link.icon}}"></i> <span>{{link.name}}</span>  
5 - </a>  
6 - </div>  
7 -</div>  
src/app/layout/blocks/link-list/link-list.scss
@@ -1,34 +0,0 @@ @@ -1,34 +0,0 @@
1 -.icon-event {  
2 - @extend .fa-calendar;  
3 -}  
4 -.icon-photos {  
5 - @extend .fa-photo;  
6 -}  
7 -.icon-edit {  
8 - @extend .fa-edit;  
9 -}  
10 -.icon-ok {  
11 - @extend .fa-check;  
12 -}  
13 -.icon-send {  
14 - @extend .fa-send-o;  
15 -}  
16 -.icon-menu-people {  
17 - @extend .fa-user;  
18 -}  
19 -.icon-forum {  
20 - @extend .fa-users;  
21 -}  
22 -.icon-new {  
23 - @extend .fa-file-o;  
24 -}  
25 -.icon-save {  
26 - @extend .fa-save;  
27 -}  
28 -  
29 -.link-list-block {  
30 - a i {  
31 - line-height: 25px;  
32 - color: #949494;  
33 - }  
34 -}  
src/app/layout/blocks/main-block/index.ts
@@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
1 -/* Module Index Entry - generated using the script npm run generate-index */  
2 -export * from "./main-block.component";  
src/app/layout/blocks/main-block/main-block.component.spec.ts
@@ -1,40 +0,0 @@ @@ -1,40 +0,0 @@
1 -import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';  
2 -import {Input, provide, Component, StateConfig} from 'ng-forward';  
3 -  
4 -import {MainBlockComponent} from './main-block.component';  
5 -import {NoosferoApp} from '../../../index.module';  
6 -  
7 -const tcb = new TestComponentBuilder();  
8 -  
9 -const htmlTemplate: string = '<noosfero-main-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-main-block>';  
10 -  
11 -describe("Components", () => {  
12 - describe("Main Block Component", () => {  
13 -  
14 - // the karma preprocessor html2js transform the templates html into js files which put  
15 - // the templates to the templateCache into the module templates  
16 - // we need to load the module templates here as the template for the  
17 - // component Block will be load on our tests  
18 - beforeEach(angular.mock.module("templates"));  
19 -  
20 - it("check if the main block has a tag with ui-view attribute", done => {  
21 -  
22 - // Creating a container component (BlockContainerComponent) to include  
23 - // the component under test (Block)  
24 - @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [MainBlockComponent] })  
25 - class BlockContainerComponent {  
26 - }  
27 -  
28 - // uses the TestComponentBuilder instance to initialize the component  
29 - tcb.createAsync(BlockContainerComponent).then(fixture => {  
30 - // and here we can inspect and run the test assertions  
31 - // let myComponent: MainBlockComponent = fixture.componentInstance;  
32 -  
33 - // assure the block object inside the Block matches  
34 - // the provided through the parent component  
35 - expect(fixture.debugElement.queryAll('[ui-view="mainBlockContent"]').length).toEqual(1);  
36 - done();  
37 - });  
38 - });  
39 - });  
40 -});  
41 \ No newline at end of file 0 \ No newline at end of file
src/app/layout/blocks/main-block/main-block.component.ts
@@ -1,10 +0,0 @@ @@ -1,10 +0,0 @@
1 -import {Component, Input} from 'ng-forward';  
2 -import {BlockComponent} from '../block.component';  
3 -  
4 -@Component({  
5 - selector: 'noosfero-main-block',  
6 - templateUrl: 'app/layout/blocks/main-block/main-block.html'  
7 -})  
8 -export class MainBlockComponent {  
9 -  
10 -}  
src/app/layout/blocks/main-block/main-block.html
@@ -1 +0,0 @@ @@ -1 +0,0 @@
1 -<div ui-view="mainBlockContent" autoscroll></div>  
src/app/layout/blocks/main/index.ts 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./main-block.component";
src/app/layout/blocks/main/main-block.component.spec.ts 0 → 100644
@@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Input, provide, Component, StateConfig} from 'ng-forward';
  3 +
  4 +import {MainBlockComponent} from './main-block.component';
  5 +import {NoosferoApp} from '../../../index.module';
  6 +
  7 +const tcb = new TestComponentBuilder();
  8 +
  9 +const htmlTemplate: string = '<noosfero-main-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-main-block>';
  10 +
  11 +describe("Components", () => {
  12 + describe("Main Block Component", () => {
  13 +
  14 + // the karma preprocessor html2js transform the templates html into js files which put
  15 + // the templates to the templateCache into the module templates
  16 + // we need to load the module templates here as the template for the
  17 + // component Block will be load on our tests
  18 + beforeEach(angular.mock.module("templates"));
  19 +
  20 + it("check if the main block has a tag with ui-view attribute", done => {
  21 +
  22 + // Creating a container component (BlockContainerComponent) to include
  23 + // the component under test (Block)
  24 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [MainBlockComponent] })
  25 + class BlockContainerComponent {
  26 + }
  27 +
  28 + // uses the TestComponentBuilder instance to initialize the component
  29 + tcb.createAsync(BlockContainerComponent).then(fixture => {
  30 + // and here we can inspect and run the test assertions
  31 + // let myComponent: MainBlockComponent = fixture.componentInstance;
  32 +
  33 + // assure the block object inside the Block matches
  34 + // the provided through the parent component
  35 + expect(fixture.debugElement.queryAll('[ui-view="mainBlockContent"]').length).toEqual(1);
  36 + done();
  37 + });
  38 + });
  39 + });
  40 +});
0 \ No newline at end of file 41 \ No newline at end of file
src/app/layout/blocks/main/main-block.component.ts 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +import {Component, Input} from 'ng-forward';
  2 +import {BlockComponent} from '../block.component';
  3 +
  4 +@Component({
  5 + selector: 'noosfero-main-block',
  6 + templateUrl: 'app/layout/blocks/main/main-block.html'
  7 +})
  8 +export class MainBlockComponent {
  9 +
  10 +}
src/app/layout/blocks/main/main-block.html 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<div ui-view="mainBlockContent" autoscroll></div>
src/app/layout/blocks/members-block/index.ts
@@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
1 -/* Module Index Entry - generated using the script npm run generate-index */  
2 -export * from "./members-block.component";  
src/app/layout/blocks/members-block/members-block.component.spec.ts
@@ -1,49 +0,0 @@ @@ -1,49 +0,0 @@
1 -import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';  
2 -import {Provider, Input, provide, Component} from 'ng-forward';  
3 -import {MembersBlockComponent} from './members-block.component';  
4 -import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';  
5 -  
6 -const htmlTemplate: string = '<noosfero-members-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-members-block>';  
7 -  
8 -const tcb = new TestComponentBuilder();  
9 -  
10 -describe("Components", () => {  
11 - describe("Members Block Component", () => {  
12 -  
13 - let helper: ComponentTestHelper<MembersBlockComponent>;  
14 -  
15 - let providers = [  
16 - new Provider('ProfileService', {  
17 - useValue: {  
18 - getProfileMembers: (profileId: number, filters: any): any => {  
19 - return Promise.resolve({ data: { people: [{ identifier: "person1" }] } });  
20 - }  
21 - }  
22 - }),  
23 - ];  
24 -  
25 - beforeEach(angular.mock.module("templates"));  
26 -  
27 - beforeEach((done) => {  
28 - // Custom properties for the component  
29 - let properties = { owner: { id: 1 } };  
30 - // Create the component bed for the test.  
31 - let cls = createClass({  
32 - template: htmlTemplate,  
33 - directives: [MembersBlockComponent],  
34 - providers: providers,  
35 - properties: properties  
36 - });  
37 - helper = new ComponentTestHelper<MembersBlockComponent>(cls, done);  
38 - });  
39 -  
40 - it("get members of the block owner", () => {  
41 - expect(helper.component.members[0].identifier).toEqual("person1");  
42 - });  
43 -  
44 - it("render the profile image for each member", () => {  
45 - expect(helper.all("noosfero-profile-image").length).toEqual(1);  
46 - });  
47 -  
48 - });  
49 -});  
src/app/layout/blocks/members-block/members-block.component.ts
@@ -1,25 +0,0 @@ @@ -1,25 +0,0 @@
1 -import {Input, Inject, Component} from "ng-forward";  
2 -import {ProfileService} from "../../../../lib/ng-noosfero-api/http/profile.service";  
3 -  
4 -@Component({  
5 - selector: "noosfero-members-block",  
6 - templateUrl: 'app/layout/blocks/members-block/members-block.html',  
7 -})  
8 -@Inject(ProfileService)  
9 -export class MembersBlockComponent {  
10 -  
11 - @Input() block: noosfero.Block;  
12 - @Input() owner: noosfero.Profile;  
13 -  
14 - members: any = [];  
15 -  
16 - constructor(private profileService: ProfileService) {  
17 -  
18 - }  
19 -  
20 - ngOnInit() {  
21 - this.profileService.getProfileMembers(this.owner.id, { per_page: 6 }).then((response: any) => {  
22 - this.members = response.data.people;  
23 - });  
24 - }  
25 -}  
src/app/layout/blocks/members-block/members-block.html
@@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
1 -<div class="members-block">  
2 - <a ng-repeat="member in ctrl.members" ui-sref="main.profile.home({profile: member.identifier})" class="member">  
3 - <noosfero-profile-image [profile]="member"></noosfero-profile-image>  
4 - </a>  
5 -</div>  
src/app/layout/blocks/members-block/members-block.scss
@@ -1,17 +0,0 @@ @@ -1,17 +0,0 @@
1 -.members-block {  
2 - .member {  
3 - img, i.profile-image {  
4 - width: 60px;  
5 - }  
6 - img {  
7 - display: inline-block;  
8 - vertical-align: top;  
9 - }  
10 - i.profile-image {  
11 - text-align: center;  
12 - background-color: #889DB1;  
13 - color: #F1F1F1;  
14 - font-size: 4.5em;  
15 - }  
16 - }  
17 -}  
src/app/layout/blocks/members/index.ts 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./members-block.component";
src/app/layout/blocks/members/members-block.component.spec.ts 0 → 100644
@@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Provider, Input, provide, Component} from 'ng-forward';
  3 +import {MembersBlockComponent} from './members-block.component';
  4 +import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
  5 +
  6 +const htmlTemplate: string = '<noosfero-members-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-members-block>';
  7 +
  8 +const tcb = new TestComponentBuilder();
  9 +
  10 +describe("Components", () => {
  11 + describe("Members Block Component", () => {
  12 +
  13 + let helper: ComponentTestHelper<MembersBlockComponent>;
  14 +
  15 + let providers = [
  16 + new Provider('ProfileService', {
  17 + useValue: {
  18 + getProfileMembers: (profileId: number, filters: any): any => {
  19 + return Promise.resolve({ data: { people: [{ identifier: "person1" }] } });
  20 + }
  21 + }
  22 + }),
  23 + ];
  24 +
  25 + beforeEach(angular.mock.module("templates"));
  26 +
  27 + beforeEach((done) => {
  28 + // Custom properties for the component
  29 + let properties = { owner: { id: 1 } };
  30 + // Create the component bed for the test.
  31 + let cls = createClass({
  32 + template: htmlTemplate,
  33 + directives: [MembersBlockComponent],
  34 + providers: providers,
  35 + properties: properties
  36 + });
  37 + helper = new ComponentTestHelper<MembersBlockComponent>(cls, done);
  38 + });
  39 +
  40 + it("get members of the block owner", () => {
  41 + expect(helper.component.members[0].identifier).toEqual("person1");
  42 + });
  43 +
  44 + it("render the profile image for each member", () => {
  45 + expect(helper.all("noosfero-profile-image").length).toEqual(1);
  46 + });
  47 +
  48 + });
  49 +});
src/app/layout/blocks/members/members-block.component.ts 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +import {Input, Inject, Component} from "ng-forward";
  2 +import {ProfileService} from "../../../../lib/ng-noosfero-api/http/profile.service";
  3 +
  4 +@Component({
  5 + selector: "noosfero-members-block",
  6 + templateUrl: 'app/layout/blocks/members/members-block.html',
  7 +})
  8 +@Inject(ProfileService)
  9 +export class MembersBlockComponent {
  10 +
  11 + @Input() block: noosfero.Block;
  12 + @Input() owner: noosfero.Profile;
  13 +
  14 + members: any = [];
  15 +
  16 + constructor(private profileService: ProfileService) {
  17 +
  18 + }
  19 +
  20 + ngOnInit() {
  21 + this.profileService.getProfileMembers(this.owner.id, { per_page: 6 }).then((response: any) => {
  22 + this.members = response.data.people;
  23 + });
  24 + }
  25 +}
src/app/layout/blocks/members/members-block.html 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +<div class="members-block">
  2 + <a ng-repeat="member in ctrl.members" ui-sref="main.profile.home({profile: member.identifier})" class="member">
  3 + <noosfero-profile-image [profile]="member"></noosfero-profile-image>
  4 + </a>
  5 +</div>
src/app/layout/blocks/members/members-block.scss 0 → 100644
@@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
  1 +.members-block {
  2 + .member {
  3 + img, i.profile-image {
  4 + width: 60px;
  5 + }
  6 + img {
  7 + display: inline-block;
  8 + vertical-align: top;
  9 + }
  10 + i.profile-image {
  11 + text-align: center;
  12 + background-color: #889DB1;
  13 + color: #F1F1F1;
  14 + font-size: 4.5em;
  15 + }
  16 + }
  17 +}
src/app/layout/blocks/people-block/index.ts
@@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
1 -/* Module Index Entry - generated using the script npm run generate-index */  
2 -export * from "./people-block.component";  
src/app/layout/blocks/people-block/people-block.component.spec.ts
@@ -1,72 +0,0 @@ @@ -1,72 +0,0 @@
1 -import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';  
2 -import {Provider} from 'ng-forward';  
3 -import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';  
4 -import {providers} from 'ng-forward/cjs/testing/providers';  
5 -import {PeopleBlockComponent} from './people-block.component';  
6 -  
7 -const htmlTemplate: string = '<noosfero-people-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-people-block>';  
8 -  
9 -describe("Components", () => {  
10 -  
11 - describe("People Block Component", () => {  
12 - let serviceMock = {  
13 - getEnvironmentPeople: (filters: any): any => {  
14 - return Promise.resolve([{ identifier: "person1" }]);  
15 - }  
16 - };  
17 - let providers = [new Provider('EnvironmentService', { useValue: serviceMock })];  
18 -  
19 - let helper: ComponentTestHelper<PeopleBlockComponent>;  
20 -  
21 - beforeEach(angular.mock.module("templates"));  
22 -  
23 - /**  
24 - * The beforeEach procedure will initialize the helper and parse  
25 - * the component according to the given providers. Unfortunetly, in  
26 - * this mode, the providers and properties given to the construtor  
27 - * can't be overriden.  
28 - */  
29 - beforeEach((done) => {  
30 - // Create the component bed for the test. Optionally, this could be done  
31 - // in each test if one needs customization of these parameters per test  
32 - let cls = createClass({  
33 - template: htmlTemplate,  
34 - directives: [PeopleBlockComponent],  
35 - providers: providers,  
36 - properties: {}  
37 - });  
38 - helper = new ComponentTestHelper<PeopleBlockComponent>(cls, done);  
39 - });  
40 -  
41 - /**  
42 - * By default the helper will have the component, with all properties  
43 - * ready to be used. Here the mock provider 'EnvironmentService' will  
44 - * return the given array with one person.  
45 - */  
46 - it("get block with one people", () => {  
47 - expect(helper.component.people[0].identifier).toEqual("person1");  
48 - });  
49 -  
50 - /**  
51 - * There are helper functions to access the JQuery DOM like this.  
52 - */  
53 - it("render the profile image for each person", () => {  
54 - expect(helper.all("noosfero-profile-image").length).toEqual(1);  
55 - });  
56 -  
57 - /**  
58 - * The main debugElement element is also available  
59 - */  
60 - it("render the main noosfero people block", () => {  
61 - expect(helper.debugElement.children().length).toEqual(1, "The people-block should have a div children");  
62 - });  
63 -  
64 - /**  
65 - * Just another example of a JQuery DOM helper function  
66 - */  
67 - it("render the noosfero people block div", () => {  
68 - let div = helper.findChildren("noosfero-people-block", "div");  
69 - expect(div.className).toBe('people-block', "The class should be people-block");  
70 - });  
71 - });  
72 -});  
src/app/layout/blocks/people-block/people-block.component.ts
@@ -1,26 +0,0 @@ @@ -1,26 +0,0 @@
1 -import {Input, Inject, Component} from "ng-forward";  
2 -import {EnvironmentService} from "../../../../lib/ng-noosfero-api/http/environment.service";  
3 -  
4 -@Component({  
5 - selector: "noosfero-people-block",  
6 - templateUrl: 'app/layout/blocks/people-block/people-block.html',  
7 -})  
8 -@Inject(EnvironmentService)  
9 -export class PeopleBlockComponent {  
10 -  
11 - @Input() block: noosfero.Block;  
12 - @Input() owner: noosfero.Environment;  
13 - private type: string = "people";  
14 -  
15 - people: noosfero.Person[] = [];  
16 -  
17 - constructor(private environmentService: EnvironmentService) {  
18 - }  
19 -  
20 - ngOnInit() {  
21 - this.environmentService.getEnvironmentPeople({ limit: '6' }).then((people: noosfero.Person[]) => {  
22 - this.people = people;  
23 - });  
24 - }  
25 -  
26 -}  
src/app/layout/blocks/people-block/people-block.html
@@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
1 -<div class="{{ctrl.type}}-block">  
2 - <a ng-repeat="person in ctrl.people" ui-sref="main.profile.home({profile: person.identifier})" class="person">  
3 - <noosfero-profile-image [profile]="person"></noosfero-profile-image>  
4 - </a>  
5 -</div>  
src/app/layout/blocks/people-block/people-block.scss
@@ -1,17 +0,0 @@ @@ -1,17 +0,0 @@
1 -.members-block {  
2 - .member {  
3 - img, i.profile-image {  
4 - width: 60px;  
5 - }  
6 - img {  
7 - display: inline-block;  
8 - vertical-align: top;  
9 - }  
10 - i.profile-image {  
11 - text-align: center;  
12 - background-color: #889DB1;  
13 - color: #F1F1F1;  
14 - font-size: 4.5em;  
15 - }  
16 - }  
17 -}  
src/app/layout/blocks/people/index.ts 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./people-block.component";
src/app/layout/blocks/people/people-block.component.spec.ts 0 → 100644
@@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Provider} from 'ng-forward';
  3 +import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
  4 +import {providers} from 'ng-forward/cjs/testing/providers';
  5 +import {PeopleBlockComponent} from './people-block.component';
  6 +
  7 +const htmlTemplate: string = '<noosfero-people-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-people-block>';
  8 +
  9 +describe("Components", () => {
  10 +
  11 + describe("People Block Component", () => {
  12 + let serviceMock = {
  13 + getEnvironmentPeople: (filters: any): any => {
  14 + return Promise.resolve([{ identifier: "person1" }]);
  15 + }
  16 + };
  17 + let providers = [new Provider('EnvironmentService', { useValue: serviceMock })];
  18 +
  19 + let helper: ComponentTestHelper<PeopleBlockComponent>;
  20 +
  21 + beforeEach(angular.mock.module("templates"));
  22 +
  23 + /**
  24 + * The beforeEach procedure will initialize the helper and parse
  25 + * the component according to the given providers. Unfortunetly, in
  26 + * this mode, the providers and properties given to the construtor
  27 + * can't be overriden.
  28 + */
  29 + beforeEach((done) => {
  30 + // Create the component bed for the test. Optionally, this could be done
  31 + // in each test if one needs customization of these parameters per test
  32 + let cls = createClass({
  33 + template: htmlTemplate,
  34 + directives: [PeopleBlockComponent],
  35 + providers: providers,
  36 + properties: {}
  37 + });
  38 + helper = new ComponentTestHelper<PeopleBlockComponent>(cls, done);
  39 + });
  40 +
  41 + /**
  42 + * By default the helper will have the component, with all properties
  43 + * ready to be used. Here the mock provider 'EnvironmentService' will
  44 + * return the given array with one person.
  45 + */
  46 + it("get block with one people", () => {
  47 + expect(helper.component.people[0].identifier).toEqual("person1");
  48 + });
  49 +
  50 + /**
  51 + * There are helper functions to access the JQuery DOM like this.
  52 + */
  53 + it("render the profile image for each person", () => {
  54 + expect(helper.all("noosfero-profile-image").length).toEqual(1);
  55 + });
  56 +
  57 + /**
  58 + * The main debugElement element is also available
  59 + */
  60 + it("render the main noosfero people block", () => {
  61 + expect(helper.debugElement.children().length).toEqual(1, "The people-block should have a div children");
  62 + });
  63 +
  64 + /**
  65 + * Just another example of a JQuery DOM helper function
  66 + */
  67 + it("render the noosfero people block div", () => {
  68 + let div = helper.findChildren("noosfero-people-block", "div");
  69 + expect(div.className).toBe('people-block', "The class should be people-block");
  70 + });
  71 + });
  72 +});
src/app/layout/blocks/people/people-block.component.ts 0 → 100644
@@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
  1 +import {Input, Inject, Component} from "ng-forward";
  2 +import {EnvironmentService} from "../../../../lib/ng-noosfero-api/http/environment.service";
  3 +
  4 +@Component({
  5 + selector: "noosfero-people-block",
  6 + templateUrl: 'app/layout/blocks/people/people-block.html',
  7 +})
  8 +@Inject(EnvironmentService)
  9 +export class PeopleBlockComponent {
  10 +
  11 + @Input() block: noosfero.Block;
  12 + @Input() owner: noosfero.Environment;
  13 + private type: string = "people";
  14 +
  15 + people: noosfero.Person[] = [];
  16 +
  17 + constructor(private environmentService: EnvironmentService) {
  18 + }
  19 +
  20 + ngOnInit() {
  21 + this.environmentService.getEnvironmentPeople({ limit: '6' }).then((people: noosfero.Person[]) => {
  22 + this.people = people;
  23 + });
  24 + }
  25 +
  26 +}
src/app/layout/blocks/people/people-block.html 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +<div class="{{ctrl.type}}-block">
  2 + <a ng-repeat="person in ctrl.people" ui-sref="main.profile.home({profile: person.identifier})" class="person">
  3 + <noosfero-profile-image [profile]="person"></noosfero-profile-image>
  4 + </a>
  5 +</div>
src/app/layout/blocks/people/people-block.scss 0 → 100644
@@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
  1 +.members-block {
  2 + .member {
  3 + img, i.profile-image {
  4 + width: 60px;
  5 + }
  6 + img {
  7 + display: inline-block;
  8 + vertical-align: top;
  9 + }
  10 + i.profile-image {
  11 + text-align: center;
  12 + background-color: #889DB1;
  13 + color: #F1F1F1;
  14 + font-size: 4.5em;
  15 + }
  16 + }
  17 +}
src/app/layout/blocks/profile-image-block/index.ts
@@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
1 -/* Module Index Entry - generated using the script npm run generate-index */  
2 -export * from "./profile-image-block.component";  
src/app/layout/blocks/profile-image-block/profile-image-block.component.spec.ts
@@ -1,46 +0,0 @@ @@ -1,46 +0,0 @@
1 -import {TestComponentBuilder, ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder';  
2 -import {Pipe, Input, provide, Component} from 'ng-forward';  
3 -  
4 -import {ProfileImageBlockComponent} from './profile-image-block.component';  
5 -  
6 -import * as helpers from "./../../../../spec/helpers";  
7 -  
8 -const tcb = new TestComponentBuilder();  
9 -  
10 -const htmlTemplate: string = '<noosfero-profile-image-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-profile-image-block>';  
11 -  
12 -describe("Components", () => {  
13 -  
14 - describe("Profile Image Block Component", () => {  
15 -  
16 - beforeEach(angular.mock.module("templates"));  
17 -  
18 - @Component({  
19 - selector: 'test-container-component',  
20 - template: htmlTemplate,  
21 - directives: [ProfileImageBlockComponent],  
22 - providers: helpers.provideFilters("translateFilter")  
23 - })  
24 - class BlockContainerComponent {  
25 - block = { type: 'Block' };  
26 - owner = { name: 'profile-name' };  
27 - constructor() {  
28 - }  
29 - }  
30 -  
31 - it("show image if present", () => {  
32 - helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {  
33 - let elProfile = fixture.debugElement.componentViewChildren[0];  
34 - expect(elProfile.query('div.profile-image-block').length).toEqual(1);  
35 - });  
36 - });  
37 -  
38 - it("has link to the profile", () => {  
39 - helpers.tcb.createAsync(BlockContainerComponent).then(fixture => {  
40 - let elProfile = fixture.debugElement.componentViewChildren[0];  
41 - expect(elProfile.query('a.settings-link').length).toEqual(1);  
42 - });  
43 - });  
44 -  
45 - });  
46 -});  
src/app/layout/blocks/profile-image-block/profile-image-block.component.ts
@@ -1,14 +0,0 @@ @@ -1,14 +0,0 @@
1 -import {Inject, Input, Component} from "ng-forward";  
2 -import {ProfileImageComponent} from "./../../../profile/image/image.component";  
3 -  
4 -@Component({  
5 - selector: "noosfero-profile-image-block",  
6 - templateUrl: 'app/layout/blocks/profile-image-block/profile-image-block.html',  
7 - directives: [ProfileImageComponent]  
8 -})  
9 -export class ProfileImageBlockComponent {  
10 -  
11 - @Input() block: noosfero.Block;  
12 - @Input() owner: noosfero.Profile;  
13 -  
14 -}  
src/app/layout/blocks/profile-image-block/profile-image-block.html
@@ -1,6 +0,0 @@ @@ -1,6 +0,0 @@
1 -<div class="center-block text-center profile-image-block">  
2 - <a ui-sref="main.profile.info({profile: ctrl.owner.identifier})">  
3 - <noosfero-profile-image [profile]="ctrl.owner"></noosfero-profile-image>  
4 - </a>  
5 - <a class="settings-link" target="_self" ui-sref="main.profile.settings({profile: ctrl.owner.identifier})">{{"blocks.profile_image.control_panel" | translate}}</a>  
6 -</div>  
src/app/layout/blocks/profile-image-block/profile-image-block.scss
@@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
1 -.profile-image-block {  
2 - .settings-link {  
3 - display: block;  
4 - }  
5 -}  
src/app/layout/blocks/profile-image/index.ts 0 → 100644
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./profile-image-block.component";