Commit 12f1379fee7191aa0a0289d4cbfadfa8592e9b77
Exists in
master
and in
26 other branches
Merge branch 'comment-paragraph' into 'master'
Frontend for comment paragraph plugin See merge request !7
Showing
52 changed files
with
1055 additions
and
78 deletions
Show diff stats
bower.json
@@ -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 | }, |
gulp/build.js
@@ -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('clean-docs', [], function() { | @@ -124,6 +131,10 @@ gulp.task('clean-docs', [], 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('noosfero', ['html'], function () { | @@ -136,4 +147,4 @@ gulp.task('noosfero', ['html'], 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']); |
gulp/conf.js
@@ -15,11 +15,13 @@ var path = require('path'); | @@ -15,11 +15,13 @@ var path = require('path'); | ||
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"; |
@@ -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 |
gulp/watch.js
@@ -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 | ||
@@ -14,13 +15,11 @@ gulp.task('watch', ['inject'], function () { | @@ -14,13 +15,11 @@ gulp.task('watch', ['inject'], function () { | ||
14 | 15 | ||
15 | gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject-reload']); | 16 | gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject-reload']); |
16 | 17 | ||
17 | - var stylePaths = []; | ||
18 | - conf.paths.allSources.forEach(function(src) { | ||
19 | - stylePaths.push(path.join(src, '/app/**/*.css')); | ||
20 | - stylePaths.push(path.join(src, '/app/**/*.scss')); | ||
21 | - }); | ||
22 | - | ||
23 | - gulp.watch(stylePaths, function(event) { | 18 | + gulp.watch([ |
19 | + path.join(conf.paths.src, '/app/**/*.css'), | ||
20 | + path.join(conf.paths.src, '/app/**/*.scss'), | ||
21 | + path.join(conf.paths.src, conf.paths.plugins, '/**/*.scss') | ||
22 | + ], function(event) { | ||
24 | if(isOnlyChange(event)) { | 23 | if(isOnlyChange(event)) { |
25 | gulp.start('styles-reload'); | 24 | gulp.start('styles-reload'); |
26 | } else { | 25 | } else { |
@@ -36,9 +35,14 @@ gulp.task('watch', ['inject'], function () { | @@ -36,9 +35,14 @@ gulp.task('watch', ['inject'], function () { | ||
36 | } | 35 | } |
37 | }); | 36 | }); |
38 | 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 | + | ||
39 | var watchPaths = []; | 42 | var watchPaths = []; |
40 | conf.paths.allSources.forEach(function(src) { | 43 | conf.paths.allSources.forEach(function(src) { |
41 | 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')); | ||
42 | }); | 46 | }); |
43 | gulp.watch(watchPaths, function(event) { | 47 | gulp.watch(watchPaths, function(event) { |
44 | browserSync.reload(event.path); | 48 | browserSync.reload(event.path); |
karma.conf.js
@@ -49,7 +49,7 @@ var _ = require('lodash'); | @@ -49,7 +49,7 @@ var _ = require('lodash'); | ||
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"); |
package.json
@@ -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"; | ||
4 | 6 | ||
5 | /** | 7 | /** |
6 | * @ngdoc controller | 8 | * @ngdoc controller |
@@ -30,7 +32,8 @@ export class ArticleDefaultViewComponent { | @@ -30,7 +32,8 @@ export class ArticleDefaultViewComponent { | ||
30 | @Component({ | 32 | @Component({ |
31 | selector: 'noosfero-article', | 33 | selector: 'noosfero-article', |
32 | template: 'not-used', | 34 | template: 'not-used', |
33 | - directives: [ArticleDefaultViewComponent, ArticleBlogComponent, CommentsComponent] | 35 | + directives: [ArticleDefaultViewComponent, ArticleBlogComponent, |
36 | + CommentsComponent, MacroDirective, ArticleToolbarHotspotComponent] | ||
34 | }) | 37 | }) |
35 | @Inject("$element", "$scope", "$injector", "$compile") | 38 | @Inject("$element", "$scope", "$injector", "$compile") |
36 | export class ArticleViewComponent { | 39 | export class ArticleViewComponent { |
@@ -53,6 +56,5 @@ export class ArticleViewComponent { | @@ -53,6 +56,5 @@ export class ArticleViewComponent { | ||
53 | private $scope: ng.IScope, | 56 | private $scope: ng.IScope, |
54 | private $injector: ng.auto.IInjectorService, | 57 | private $injector: ng.auto.IInjectorService, |
55 | private $compile: ng.ICompileService) { | 58 | private $compile: ng.ICompileService) { |
56 | - | ||
57 | } | 59 | } |
58 | } | 60 | } |
src/app/article/article.html
@@ -4,9 +4,7 @@ | @@ -4,9 +4,7 @@ | ||
4 | </div> | 4 | </div> |
5 | 5 | ||
6 | <div class="sub-header clearfix"> | 6 | <div class="sub-header clearfix"> |
7 | - <a href="#" class="btn btn-default btn-xs" ui-sref="main.cmsEdit({profile: ctrl.profile.identifier, id: ctrl.article.id})"> | ||
8 | - <i class="fa fa-pencil-square-o fa-fw fa-lg"></i> {{"article.actions.edit" | translate}} | ||
9 | - </a> | 7 | + <noosfero-hotspot-article-toolbar [article]="ctrl.article"></noosfero-hotspot-article-toolbar> |
10 | <div class="page-info pull-right small text-muted"> | 8 | <div class="page-info pull-right small text-muted"> |
11 | <span class="time"> | 9 | <span class="time"> |
12 | <i class="fa fa-clock-o"></i> <span am-time-ago="ctrl.article.created_at | dateFormat"></span> | 10 | <i class="fa fa-clock-o"></i> <span am-time-ago="ctrl.article.created_at | dateFormat"></span> |
@@ -21,7 +19,7 @@ | @@ -21,7 +19,7 @@ | ||
21 | </div> | 19 | </div> |
22 | 20 | ||
23 | <div class="page-body"> | 21 | <div class="page-body"> |
24 | - <div ng-bind-html="ctrl.article.body"></div> | 22 | + <div bind-html-compile="ctrl.article.body"></div> |
25 | </div> | 23 | </div> |
26 | 24 | ||
27 | <noosfero-comments [article]="ctrl.article"></noosfero-comments> | 25 | <noosfero-comments [article]="ctrl.article"></noosfero-comments> |
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"> | ||
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"; |
5 | +import { PostCommentEventService } from "./post-comment/post-comment-event.service"; | ||
8 | 6 | ||
9 | @Component({ | 7 | @Component({ |
10 | selector: 'noosfero-comments', | 8 | selector: 'noosfero-comments', |
11 | templateUrl: 'app/article/comment/comments.html', | 9 | templateUrl: 'app/article/comment/comments.html', |
12 | - directives: [PostCommentComponent, CommentComponent], | ||
13 | - outputs: ['commentAdded'] | 10 | + directives: [PostCommentComponent, CommentComponent] |
14 | }) | 11 | }) |
15 | -@Inject(CommentService, "$element") | 12 | +@Inject(CommentService, PostCommentEventService, "$scope") |
16 | export class CommentsComponent { | 13 | export class CommentsComponent { |
17 | 14 | ||
18 | - comments: noosfero.CommentViewModel[] = []; | 15 | + comments: noosfero.Comment[] = []; |
19 | @Input() showForm = true; | 16 | @Input() showForm = true; |
20 | @Input() article: noosfero.Article; | 17 | @Input() article: noosfero.Article; |
21 | - @Input() parent: noosfero.CommentViewModel; | ||
22 | - | 18 | + @Input() parent: noosfero.Comment; |
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 postCommentEventService: PostCommentEventService, private $scope: ng.IScope) { } | ||
28 | 26 | ||
29 | ngOnInit() { | 27 | ngOnInit() { |
30 | if (this.parent) { | 28 | if (this.parent) { |
@@ -32,20 +30,13 @@ export class CommentsComponent { | @@ -32,20 +30,13 @@ export class CommentsComponent { | ||
32 | } else { | 30 | } else { |
33 | this.loadNextPage(); | 31 | this.loadNextPage(); |
34 | } | 32 | } |
35 | - } | ||
36 | - | ||
37 | - commentAdded(comment: noosfero.Comment): void { | ||
38 | - this.comments.push(comment); | ||
39 | - this.resetShowReply(); | ||
40 | - } | ||
41 | - | ||
42 | - private resetShowReply() { | ||
43 | - this.comments.forEach((comment: noosfero.CommentViewModel) => { | ||
44 | - comment.__show_reply = false; | 33 | + this.postCommentEventService.subscribe((comment: noosfero.Comment) => { |
34 | + if ((!this.parent && !comment.reply_of) || (comment.reply_of && this.parent && comment.reply_of.id === this.parent.id)) { | ||
35 | + if (!this.comments) this.comments = []; | ||
36 | + this.comments.push(comment); | ||
37 | + this.$scope.$apply(); | ||
38 | + } | ||
45 | }); | 39 | }); |
46 | - if (this.parent) { | ||
47 | - this.parent.__show_reply = false; | ||
48 | - } | ||
49 | } | 40 | } |
50 | 41 | ||
51 | loadComments() { | 42 | loadComments() { |
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 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/post-comment/post-comment.component.ts
1 | -import { Inject, Input, Output, EventEmitter, Component } from 'ng-forward'; | 1 | +import { Inject, Input, Component } from 'ng-forward'; |
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 { PostCommentEventService } from "./post-comment-event.service"; | ||
6 | +import { CommentFormHotspotComponent } from "../../../hotspot/comment-form-hotspot.component"; | ||
5 | 7 | ||
6 | @Component({ | 8 | @Component({ |
7 | selector: 'noosfero-post-comment', | 9 | selector: 'noosfero-post-comment', |
8 | templateUrl: 'app/article/comment/post-comment/post-comment.html', | 10 | templateUrl: 'app/article/comment/post-comment/post-comment.html', |
9 | - outputs: ['commentSaved'] | 11 | + directives: [CommentFormHotspotComponent] |
10 | }) | 12 | }) |
11 | -@Inject(CommentService, NotificationService, SessionService) | 13 | +@Inject(CommentService, NotificationService, SessionService, PostCommentEventService) |
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>(); | ||
17 | - | ||
18 | - comment = <noosfero.Comment>{}; | 20 | + @Input() comment = <noosfero.Comment>{}; |
19 | private currentUser: noosfero.User; | 21 | private currentUser: noosfero.User; |
20 | 22 | ||
21 | constructor(private commentService: CommentService, | 23 | constructor(private commentService: CommentService, |
22 | private notificationService: NotificationService, | 24 | private notificationService: NotificationService, |
23 | - private session: SessionService) { | 25 | + private session: SessionService, |
26 | + private postCommentEventService: PostCommentEventService) { | ||
24 | this.currentUser = this.session.currentUser(); | 27 | this.currentUser = this.session.currentUser(); |
25 | } | 28 | } |
26 | 29 | ||
@@ -29,7 +32,7 @@ export class PostCommentComponent { | @@ -29,7 +32,7 @@ export class PostCommentComponent { | ||
29 | this.comment.reply_of_id = this.parent.id; | 32 | this.comment.reply_of_id = this.parent.id; |
30 | } | 33 | } |
31 | this.commentService.createInArticle(this.article, this.comment).then((result: noosfero.RestResult<noosfero.Comment>) => { | 34 | this.commentService.createInArticle(this.article, this.comment).then((result: noosfero.RestResult<noosfero.Comment>) => { |
32 | - this.commentSaved.next(result.data); | 35 | + this.postCommentEventService.emit(result.data); |
33 | this.comment.body = ""; | 36 | this.comment.body = ""; |
34 | this.notificationService.success({ title: "comment.post.success.title", message: "comment.post.success.message" }); | 37 | this.notificationService.success({ title: "comment.post.success.title", message: "comment.post.success.message" }); |
35 | }); | 38 | }); |
src/app/article/comment/post-comment/post-comment.html
@@ -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> |
@@ -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 | +}); |
@@ -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, '"'); | ||
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 | +} |
@@ -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 | +} |
@@ -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 | +} |
@@ -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.ts
@@ -15,9 +15,10 @@ declare var moment: any; | @@ -15,9 +15,10 @@ 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 |
src/app/main/main.component.ts
1 | +import * as plugins from "../../plugins"; | ||
1 | import {bundle, Component, StateConfig, Inject} from "ng-forward"; | 2 | import {bundle, Component, StateConfig, Inject} from "ng-forward"; |
2 | import {ArticleBlogComponent} from "./../article/types/blog/blog.component"; | 3 | import {ArticleBlogComponent} from "./../article/types/blog/blog.component"; |
3 | 4 | ||
@@ -93,7 +94,7 @@ export class EnvironmentContent { | @@ -93,7 +94,7 @@ export class EnvironmentContent { | ||
93 | LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, | 94 | LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent, |
94 | MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, | 95 | MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent, |
95 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent | 96 | MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent |
96 | - ], | 97 | + ].concat(plugins.mainComponents).concat(plugins.hotspots), |
97 | providers: [AuthService, SessionService, NotificationService, BodyStateClassesService] | 98 | providers: [AuthService, SessionService, NotificationService, BodyStateClassesService] |
98 | }) | 99 | }) |
99 | @StateConfig([ | 100 | @StateConfig([ |
src/lib/ng-noosfero-api/http/restangular_service.spec.ts
@@ -207,4 +207,27 @@ describe("Restangular Service - base Class", () => { | @@ -207,4 +207,27 @@ describe("Restangular Service - base Class", () => { | ||
207 | $httpBackend.flush(); | 207 | $httpBackend.flush(); |
208 | }); | 208 | }); |
209 | 209 | ||
210 | -}); | ||
211 | \ No newline at end of file | 210 | \ No newline at end of file |
211 | + it("post('customPath', rootObject) calls POST /rootObjects/1/customPath", (done) => { | ||
212 | + let rootObj: RootObjectModel = rootObjectRestService.getElement(1); | ||
213 | + $httpBackend.expectPOST("/api/v1/rootObjects/1/customPath").respond(201, { object: { attr: 1, rootId: 1 } }); | ||
214 | + objectRestService.post('customPath', rootObj).then((result: noosfero.RestResult<ObjectModel>) => { | ||
215 | + expect(result.data).toBeDefined(); | ||
216 | + expect((<any>result.data).attr).toEqual(1); | ||
217 | + expect((<any>result.data).rootId).toEqual(1); | ||
218 | + done(); | ||
219 | + }); | ||
220 | + $httpBackend.flush(); | ||
221 | + }); | ||
222 | + | ||
223 | + it("post('customPath') calls POST /objects/customPath", (done) => { | ||
224 | + $httpBackend.expectPOST("/api/v1/objects/customPath").respond(201, { object: { attr: 1, rootId: 1 } }); | ||
225 | + objectRestService.post('customPath').then((result: noosfero.RestResult<ObjectModel>) => { | ||
226 | + expect(result.data).toBeDefined(); | ||
227 | + expect((<any>result.data).attr).toEqual(1); | ||
228 | + expect((<any>result.data).rootId).toEqual(1); | ||
229 | + done(); | ||
230 | + }); | ||
231 | + $httpBackend.flush(); | ||
232 | + }); | ||
233 | + | ||
234 | +}); |
src/lib/ng-noosfero-api/http/restangular_service.ts
@@ -261,6 +261,22 @@ export abstract class RestangularService<T extends noosfero.RestModel> { | @@ -261,6 +261,22 @@ export abstract class RestangularService<T extends noosfero.RestModel> { | ||
261 | return deferred.promise; | 261 | return deferred.promise; |
262 | } | 262 | } |
263 | 263 | ||
264 | + | ||
265 | + public post(path: string, rootElement?: restangular.IElement, data?: any, headers?: any): ng.IPromise<noosfero.RestResult<T>> { | ||
266 | + let deferred = this.$q.defer<noosfero.RestResult<T>>(); | ||
267 | + let restRequest: ng.IPromise<any>; | ||
268 | + | ||
269 | + if (rootElement) { | ||
270 | + restRequest = rootElement.customPOST(data, path, headers); | ||
271 | + } else { | ||
272 | + restRequest = this.baseResource.customPOST(data, path, headers); | ||
273 | + } | ||
274 | + restRequest | ||
275 | + .then(this.getHandleSuccessFunction(deferred)) | ||
276 | + .catch(this.getHandleErrorFunction(deferred)); | ||
277 | + return deferred.promise; | ||
278 | + } | ||
279 | + | ||
264 | /** | 280 | /** |
265 | * Returns a Restangular IElement representing the | 281 | * Returns a Restangular IElement representing the |
266 | */ | 282 | */ |
src/lib/ng-noosfero-api/interfaces/article.ts
1 | 1 | ||
2 | namespace noosfero { | 2 | namespace noosfero { |
3 | - export interface Article extends RestModel { | 3 | + export interface Article extends RestModel { |
4 | path: string; | 4 | path: string; |
5 | profile: Profile; | 5 | profile: Profile; |
6 | type: string; | 6 | type: string; |
7 | - parent: Article; | 7 | + parent: Article; |
8 | body: string; | 8 | body: string; |
9 | title: string; | 9 | title: string; |
10 | name: string; | 10 | name: string; |
11 | published: boolean; | 11 | published: boolean; |
12 | + setting: any; | ||
12 | } | 13 | } |
13 | -} | 14 | -} |
15 | +} | ||
14 | \ No newline at end of file | 16 | \ No newline at end of file |
src/plugins/comment_paragraph/allow-comment/allow-comment.component.spec.ts
0 → 100644
@@ -0,0 +1,78 @@ | @@ -0,0 +1,78 @@ | ||
1 | +import {AllowCommentComponent} from "./allow-comment.component"; | ||
2 | +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper'; | ||
3 | +import * as helpers from "../../../spec/helpers"; | ||
4 | +import {Provider} from 'ng-forward'; | ||
5 | +import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; | ||
6 | + | ||
7 | +let htmlTemplate = '<comment-paragraph-plugin-allow-comment [content]="ctrl.content" [paragraph-uuid]="ctrl.paragraphUuid" [article]="ctrl.article"></comment-paragraph-plugin-allow-comment>'; | ||
8 | + | ||
9 | +describe("Components", () => { | ||
10 | + describe("Allow Comment Component", () => { | ||
11 | + | ||
12 | + let serviceMock = { | ||
13 | + commentParagraphCount: () => { | ||
14 | + return Promise.resolve(5); | ||
15 | + } | ||
16 | + }; | ||
17 | + let functionToggleCommentParagraph: Function; | ||
18 | + let eventServiceMock = { | ||
19 | + // toggleCommentParagraph | ||
20 | + subscribeToggleCommentParagraph: (fn: Function) => { | ||
21 | + functionToggleCommentParagraph = fn; | ||
22 | + } | ||
23 | + }; | ||
24 | + | ||
25 | + let providers = [ | ||
26 | + new Provider('CommentParagraphService', { useValue: serviceMock }), | ||
27 | + new Provider('CommentParagraphEventService', { useValue: eventServiceMock }) | ||
28 | + ]; | ||
29 | + let helper: ComponentTestHelper; | ||
30 | + | ||
31 | + beforeEach(angular.mock.module("templates")); | ||
32 | + | ||
33 | + beforeEach((done) => { | ||
34 | + let cls = createClass({ | ||
35 | + template: htmlTemplate, | ||
36 | + directives: [AllowCommentComponent], | ||
37 | + providers: providers, | ||
38 | + properties: { | ||
39 | + content: "", | ||
40 | + paragraphUuid: "uuid", | ||
41 | + article: { | ||
42 | + setting: { | ||
43 | + comment_paragraph_plugin_activate: true | ||
44 | + } | ||
45 | + } | ||
46 | + } | ||
47 | + }); | ||
48 | + helper = new ComponentTestHelper(cls, done); | ||
49 | + }); | ||
50 | + | ||
51 | + it('update comments count', () => { | ||
52 | + expect(helper.component.commentsCount).toEqual(5); | ||
53 | + }); | ||
54 | + | ||
55 | + it('display paragraph content', () => { | ||
56 | + expect(helper.all(".paragraph .paragraph-content").length).toEqual(1); | ||
57 | + }); | ||
58 | + | ||
59 | + it('display button to side comments', () => { | ||
60 | + expect(helper.all(".paragraph .actions a").length).toEqual(1); | ||
61 | + }); | ||
62 | + | ||
63 | + it('set display to true when click in show paragraph', () => { | ||
64 | + helper.component.showParagraphComments(); | ||
65 | + expect(helper.component.display).toBeTruthy(); | ||
66 | + }); | ||
67 | + | ||
68 | + it('set display to false when click in hide paragraph', () => { | ||
69 | + helper.component.hideParagraphComments(); | ||
70 | + expect(helper.component.display).toBeFalsy(); | ||
71 | + }); | ||
72 | + | ||
73 | + it('update article when receive a toogle paragraph event', () => { | ||
74 | + functionToggleCommentParagraph({ id: 2 }); | ||
75 | + expect(helper.component.article.id).toEqual(2); | ||
76 | + }); | ||
77 | + }); | ||
78 | +}); |
src/plugins/comment_paragraph/allow-comment/allow-comment.component.ts
0 → 100644
@@ -0,0 +1,45 @@ | @@ -0,0 +1,45 @@ | ||
1 | +import {Component, Input, Inject} from "ng-forward"; | ||
2 | +import {SideCommentsComponent} from "../side-comments/side-comments.component"; | ||
3 | +import {CommentParagraphEventService} from "../events/comment-paragraph-event.service"; | ||
4 | +import {CommentParagraphService} from "../http/comment-paragraph.service"; | ||
5 | + | ||
6 | +@Component({ | ||
7 | + selector: "comment-paragraph-plugin-allow-comment", | ||
8 | + templateUrl: "plugins/comment_paragraph/allow-comment/allow-comment.html", | ||
9 | + directives: [SideCommentsComponent] | ||
10 | +}) | ||
11 | +@Inject("$scope", CommentParagraphEventService, CommentParagraphService) | ||
12 | +export class AllowCommentComponent { | ||
13 | + | ||
14 | + @Input() content: string; | ||
15 | + @Input() paragraphUuid: string; | ||
16 | + @Input() article: noosfero.Article; | ||
17 | + commentsCount: number; | ||
18 | + display = false; | ||
19 | + | ||
20 | + constructor(private $scope: ng.IScope, | ||
21 | + private commentParagraphEventService: CommentParagraphEventService, | ||
22 | + private commentParagraphService: CommentParagraphService) { } | ||
23 | + | ||
24 | + ngOnInit() { | ||
25 | + this.commentParagraphEventService.subscribeToggleCommentParagraph((article: noosfero.Article) => { | ||
26 | + this.article = article; | ||
27 | + this.$scope.$apply(); | ||
28 | + }); | ||
29 | + this.commentParagraphService.commentParagraphCount(this.article, this.paragraphUuid).then((count: number) => { | ||
30 | + this.commentsCount = count; | ||
31 | + }); | ||
32 | + } | ||
33 | + | ||
34 | + isActivated() { | ||
35 | + return this.article && this.article.setting && this.article.setting.comment_paragraph_plugin_activate; | ||
36 | + } | ||
37 | + | ||
38 | + showParagraphComments() { | ||
39 | + this.display = true; | ||
40 | + } | ||
41 | + | ||
42 | + hideParagraphComments() { | ||
43 | + this.display = false; | ||
44 | + } | ||
45 | +} |
src/plugins/comment_paragraph/allow-comment/allow-comment.html
0 → 100644
@@ -0,0 +1,12 @@ | @@ -0,0 +1,12 @@ | ||
1 | +<div class="paragraph" ng-class="{'active' : ctrl.display}"> | ||
2 | + <div class="paragraph-content" ng-bind-html="ctrl.content" ng-class="{'active' : ctrl.display}"></div> | ||
3 | + <div ng-if="ctrl.isActivated()" class="actions"> | ||
4 | + <a href="#" popover-placement="right-top" popover-trigger="none" | ||
5 | + uib-popover-template="'plugins/comment_paragraph/allow-comment/popover.html'" | ||
6 | + (click)="ctrl.showParagraphComments()" popover-is-open="ctrl.display"> | ||
7 | + <div class="arrow_box" ng-class="{'active' : ctrl.display}"> | ||
8 | + <span class="count">{{ctrl.commentsCount > 0 ? ctrl.commentsCount : '+'}}</span> | ||
9 | + </div> | ||
10 | + </a> | ||
11 | + </div> | ||
12 | +</div> |
src/plugins/comment_paragraph/allow-comment/allow-comment.scss
0 → 100644
@@ -0,0 +1,62 @@ | @@ -0,0 +1,62 @@ | ||
1 | +$balloon-selected-color: #50BF68; | ||
2 | +$balloon-color: #c4c4c4; | ||
3 | + | ||
4 | +comment-paragraph-plugin-allow-comment { | ||
5 | + .paragraph { | ||
6 | + width: 100%; | ||
7 | + &.active { | ||
8 | + width: 80%; | ||
9 | + } | ||
10 | + .popover { | ||
11 | + &.right > .arrow { | ||
12 | + } | ||
13 | + } | ||
14 | + .paragraph-content { | ||
15 | + width: 95%; | ||
16 | + display: inline-block; | ||
17 | + } | ||
18 | + .actions { | ||
19 | + width: 3%; | ||
20 | + display: inline-block; | ||
21 | + vertical-align: top; | ||
22 | + margin-left: 10px; | ||
23 | + .popover { | ||
24 | + width: 100%; | ||
25 | + max-width: 330px; | ||
26 | + } | ||
27 | + .count { | ||
28 | + font-size: 14px; | ||
29 | + font-weight: bold; | ||
30 | + color: white; | ||
31 | + text-align: center; | ||
32 | + width: 100%; | ||
33 | + display: inline-block; | ||
34 | + } | ||
35 | + .arrow_box { | ||
36 | + position: relative; | ||
37 | + background: $balloon-color; | ||
38 | + margin-top: 5px; | ||
39 | + width: 25px; | ||
40 | + border-radius: 2px; | ||
41 | + &:after { | ||
42 | + top: 100%; | ||
43 | + left: 50%; | ||
44 | + border: solid transparent; | ||
45 | + content: " "; | ||
46 | + position: absolute; | ||
47 | + pointer-events: none; | ||
48 | + border-color: rgba(196, 196, 196, 0); | ||
49 | + border-top-color: $balloon-color; | ||
50 | + border-width: 6px; | ||
51 | + margin-left: -6px; | ||
52 | + } | ||
53 | + &:hover, &.active { | ||
54 | + background: $balloon-selected-color; | ||
55 | + &:after { | ||
56 | + border-top-color: $balloon-selected-color; | ||
57 | + } | ||
58 | + } | ||
59 | + } | ||
60 | + } | ||
61 | + } | ||
62 | +} |
src/plugins/comment_paragraph/allow-comment/popover.html
0 → 100644
@@ -0,0 +1 @@ | @@ -0,0 +1 @@ | ||
1 | +<comment-paragraph-side-comments id="side-comments-{{ctrl.paragraphUuid}}" click-outside="ctrl.hideParagraphComments()" [article]="ctrl.article" [paragraph-uuid]="ctrl.paragraphUuid"></comment-paragraph-side-comments> |
src/plugins/comment_paragraph/events/comment-paragraph-event.service.spec.ts
0 → 100644
@@ -0,0 +1,28 @@ | @@ -0,0 +1,28 @@ | ||
1 | +import {CommentParagraphEventService} from "./comment-paragraph-event.service"; | ||
2 | +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper'; | ||
3 | +import * as helpers from "../../../spec/helpers"; | ||
4 | +import {Provider} from 'ng-forward'; | ||
5 | +import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; | ||
6 | + | ||
7 | +describe("Services", () => { | ||
8 | + describe("Comment Paragraph Event Service", () => { | ||
9 | + let eventService: CommentParagraphEventService; | ||
10 | + | ||
11 | + beforeEach(() => { | ||
12 | + eventService = new CommentParagraphEventService(); | ||
13 | + eventService['toggleCommentParagraphEmitter'] = jasmine.createSpyObj("toggleCommentParagraphEmitter", ["next", "subscribe"]); | ||
14 | + }); | ||
15 | + | ||
16 | + it('subscribe to toggle comment paragraph event', () => { | ||
17 | + eventService['toggleCommentParagraphEmitter'].subscribe = jasmine.createSpy("subscribe"); | ||
18 | + eventService.subscribeToggleCommentParagraph(() => { }); | ||
19 | + expect(eventService['toggleCommentParagraphEmitter'].subscribe).toHaveBeenCalled(); | ||
20 | + }); | ||
21 | + | ||
22 | + it('emit event when toggle comment paragraph', () => { | ||
23 | + eventService['toggleCommentParagraphEmitter'].subscribe = jasmine.createSpy("next"); | ||
24 | + eventService.toggleCommentParagraph(<noosfero.Article>{}); | ||
25 | + expect(eventService['toggleCommentParagraphEmitter'].next).toHaveBeenCalled(); | ||
26 | + }); | ||
27 | + }); | ||
28 | +}); |
src/plugins/comment_paragraph/events/comment-paragraph-event.service.ts
0 → 100644
@@ -0,0 +1,19 @@ | @@ -0,0 +1,19 @@ | ||
1 | +import {Injectable, EventEmitter} from "ng-forward"; | ||
2 | + | ||
3 | +@Injectable() | ||
4 | +export class CommentParagraphEventService { | ||
5 | + | ||
6 | + private toggleCommentParagraphEmitter: EventEmitter<noosfero.Article>; | ||
7 | + | ||
8 | + constructor() { | ||
9 | + this.toggleCommentParagraphEmitter = new EventEmitter(); | ||
10 | + } | ||
11 | + | ||
12 | + toggleCommentParagraph(article: noosfero.Article) { | ||
13 | + this.toggleCommentParagraphEmitter.next(article); | ||
14 | + } | ||
15 | + | ||
16 | + subscribeToggleCommentParagraph(fn: (article: noosfero.Article) => void) { | ||
17 | + this.toggleCommentParagraphEmitter.subscribe(fn); | ||
18 | + } | ||
19 | +} |
src/plugins/comment_paragraph/hotspot/comment-paragraph-article-button.component.spec.ts
0 → 100644
@@ -0,0 +1,85 @@ | @@ -0,0 +1,85 @@ | ||
1 | +import {CommentParagraphArticleButtonHotspotComponent} from "./comment-paragraph-article-button.component"; | ||
2 | +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper'; | ||
3 | +import * as helpers from "../../../spec/helpers"; | ||
4 | +import {Provider} from 'ng-forward'; | ||
5 | +import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; | ||
6 | + | ||
7 | +let htmlTemplate = '<comment-paragraph-article-button-hotspot [article]="ctrl.article"></comment-paragraph-article-button-hotspot>'; | ||
8 | + | ||
9 | +describe("Components", () => { | ||
10 | + describe("Comment Paragraph Article Button Hotspot Component", () => { | ||
11 | + | ||
12 | + let serviceMock = jasmine.createSpyObj("CommentParagraphService", ["deactivateCommentParagraph", "activateCommentParagraph"]); | ||
13 | + let eventServiceMock = jasmine.createSpyObj("CommentParagraphEventService", ["toggleCommentParagraph"]); | ||
14 | + | ||
15 | + let providers = [ | ||
16 | + new Provider('CommentParagraphService', { useValue: serviceMock }), | ||
17 | + new Provider('CommentParagraphEventService', { useValue: eventServiceMock }) | ||
18 | + ].concat(helpers.provideFilters('translateFilter')); | ||
19 | + let helper: ComponentTestHelper; | ||
20 | + | ||
21 | + beforeEach(angular.mock.module("templates")); | ||
22 | + | ||
23 | + beforeEach((done) => { | ||
24 | + let cls = createClass({ | ||
25 | + template: htmlTemplate, | ||
26 | + directives: [CommentParagraphArticleButtonHotspotComponent], | ||
27 | + providers: providers, | ||
28 | + properties: { | ||
29 | + article: {} | ||
30 | + } | ||
31 | + }); | ||
32 | + helper = new ComponentTestHelper(cls, done); | ||
33 | + }); | ||
34 | + | ||
35 | + it('emit event when deactivate comment paragraph in an article', () => { | ||
36 | + serviceMock.deactivateCommentParagraph = jasmine.createSpy("deactivateCommentParagraph").and.returnValue( | ||
37 | + { then: (fn: Function) => { fn({ data: {} }); } } | ||
38 | + ); | ||
39 | + eventServiceMock.toggleCommentParagraph = jasmine.createSpy("toggleCommentParagraph"); | ||
40 | + helper.component.deactivateCommentParagraph(); | ||
41 | + | ||
42 | + expect(serviceMock.deactivateCommentParagraph).toHaveBeenCalled(); | ||
43 | + expect(eventServiceMock.toggleCommentParagraph).toHaveBeenCalled(); | ||
44 | + }); | ||
45 | + | ||
46 | + it('emit event when activate comment paragraph in an article', () => { | ||
47 | + serviceMock.activateCommentParagraph = jasmine.createSpy("activateCommentParagraph").and.returnValue( | ||
48 | + { then: (fn: Function) => { fn({ data: {} }); } } | ||
49 | + ); | ||
50 | + eventServiceMock.toggleCommentParagraph = jasmine.createSpy("toggleCommentParagraph"); | ||
51 | + helper.component.activateCommentParagraph(); | ||
52 | + | ||
53 | + expect(serviceMock.activateCommentParagraph).toHaveBeenCalled(); | ||
54 | + expect(eventServiceMock.toggleCommentParagraph).toHaveBeenCalled(); | ||
55 | + }); | ||
56 | + | ||
57 | + it('return true when comment paragraph is active', () => { | ||
58 | + helper.component.article = { setting: { comment_paragraph_plugin_activate: true } }; | ||
59 | + helper.detectChanges(); | ||
60 | + expect(helper.component.isActivated()).toBeTruthy(); | ||
61 | + }); | ||
62 | + | ||
63 | + it('return false when comment paragraph is not active', () => { | ||
64 | + expect(helper.component.isActivated()).toBeFalsy(); | ||
65 | + }); | ||
66 | + | ||
67 | + it('return false when article has no setting attribute', () => { | ||
68 | + helper.component.article = {}; | ||
69 | + helper.detectChanges(); | ||
70 | + expect(helper.component.isActivated()).toBeFalsy(); | ||
71 | + }); | ||
72 | + | ||
73 | + it('display activate button when comment paragraph is not active', () => { | ||
74 | + expect(helper.all('.comment-paragraph-activate').length).toEqual(1); | ||
75 | + expect(helper.all('.comment-paragraph-deactivate').length).toEqual(0); | ||
76 | + }); | ||
77 | + | ||
78 | + it('display deactivate button when comment paragraph is active', () => { | ||
79 | + helper.component.article = { setting: { comment_paragraph_plugin_activate: true } }; | ||
80 | + helper.detectChanges(); | ||
81 | + expect(helper.all('.comment-paragraph-deactivate').length).toEqual(1); | ||
82 | + expect(helper.all('.comment-paragraph-activate').length).toEqual(0); | ||
83 | + }); | ||
84 | + }); | ||
85 | +}); |
src/plugins/comment_paragraph/hotspot/comment-paragraph-article-button.component.ts
0 → 100644
@@ -0,0 +1,38 @@ | @@ -0,0 +1,38 @@ | ||
1 | +import { Input, Inject, Component } from "ng-forward"; | ||
2 | +import {Hotspot} from "../../../app/hotspot/hotspot.decorator"; | ||
3 | +import {CommentParagraphService} from "../http/comment-paragraph.service"; | ||
4 | +import {CommentParagraphEventService} from "../events/comment-paragraph-event.service"; | ||
5 | + | ||
6 | +@Component({ | ||
7 | + selector: "comment-paragraph-article-button-hotspot", | ||
8 | + templateUrl: "plugins/comment_paragraph/hotspot/comment-paragraph-article-button.html", | ||
9 | +}) | ||
10 | +@Inject("$scope", CommentParagraphService, CommentParagraphEventService) | ||
11 | +@Hotspot("article_extra_toolbar_buttons") | ||
12 | +export class CommentParagraphArticleButtonHotspotComponent { | ||
13 | + | ||
14 | + @Input() article: noosfero.Article; | ||
15 | + | ||
16 | + constructor(private $scope: ng.IScope, | ||
17 | + private commentParagraphService: CommentParagraphService, | ||
18 | + private commentParagraphEventService: CommentParagraphEventService) { } | ||
19 | + | ||
20 | + deactivateCommentParagraph() { | ||
21 | + this.toggleCommentParagraph(this.commentParagraphService.deactivateCommentParagraph(this.article)); | ||
22 | + } | ||
23 | + | ||
24 | + activateCommentParagraph() { | ||
25 | + this.toggleCommentParagraph(this.commentParagraphService.activateCommentParagraph(this.article)); | ||
26 | + } | ||
27 | + | ||
28 | + isActivated() { | ||
29 | + return this.article && this.article.setting && this.article.setting.comment_paragraph_plugin_activate; | ||
30 | + } | ||
31 | + | ||
32 | + private toggleCommentParagraph(promise: ng.IPromise<noosfero.RestResult<noosfero.Article>>) { | ||
33 | + promise.then((result: noosfero.RestResult<noosfero.Article>) => { | ||
34 | + this.article = result.data; | ||
35 | + this.commentParagraphEventService.toggleCommentParagraph(this.article); | ||
36 | + }); | ||
37 | + } | ||
38 | +} |
src/plugins/comment_paragraph/hotspot/comment-paragraph-article-button.html
0 → 100644
@@ -0,0 +1,8 @@ | @@ -0,0 +1,8 @@ | ||
1 | +<a href='#' class="btn btn-default btn-xs comment-paragraph-activate" (click)="ctrl.activateCommentParagraph()" | ||
2 | + ng-if="!ctrl.article.setting.comment_paragraph_plugin_activate"> | ||
3 | + <i class="fa fa-fw fa-plus"></i> {{"comment-paragraph-plugin.title" | translate}} | ||
4 | +</a> | ||
5 | +<a href='#' class="btn btn-default btn-xs comment-paragraph-deactivate" (click)="ctrl.deactivateCommentParagraph()" | ||
6 | + ng-if="ctrl.article.setting.comment_paragraph_plugin_activate"> | ||
7 | + <i class="fa fa-fw fa-minus"></i> {{"comment-paragraph-plugin.title" | translate}} | ||
8 | +</a> |
src/plugins/comment_paragraph/hotspot/comment-paragraph-form.component.ts
0 → 100644
@@ -0,0 +1,26 @@ | @@ -0,0 +1,26 @@ | ||
1 | +import { Inject, Input, Component } from "ng-forward"; | ||
2 | +import {Hotspot} from "../../../app/hotspot/hotspot.decorator"; | ||
3 | + | ||
4 | +@Component({ | ||
5 | + selector: "comment-paragraph-form-hotspot", | ||
6 | + template: "<span></span>", | ||
7 | +}) | ||
8 | +@Hotspot("comment_form_extra_contents") | ||
9 | +@Inject("$scope") | ||
10 | +export class CommentParagraphFormHotspotComponent { | ||
11 | + | ||
12 | + @Input() comment: noosfero.Comment; | ||
13 | + @Input() parent: noosfero.Comment; | ||
14 | + | ||
15 | + constructor(private $scope: ng.IScope) { } | ||
16 | + | ||
17 | + ngOnInit() { | ||
18 | + this.$scope.$watch(() => { | ||
19 | + return this.parent; | ||
20 | + }, () => { | ||
21 | + if (this.parent && (<any>this.parent).paragraph_uuid) { | ||
22 | + (<any>this.comment).paragraph_uuid = (<any>this.parent).paragraph_uuid; | ||
23 | + } | ||
24 | + }); | ||
25 | + } | ||
26 | +} |
src/plugins/comment_paragraph/hotspot/comment-paragraph-from.component.spec.ts
0 → 100644
@@ -0,0 +1,33 @@ | @@ -0,0 +1,33 @@ | ||
1 | +import {CommentParagraphFormHotspotComponent} from "./comment-paragraph-form.component"; | ||
2 | +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper'; | ||
3 | +import * as helpers from "../../../spec/helpers"; | ||
4 | +import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; | ||
5 | + | ||
6 | +let htmlTemplate = '<comment-paragraph-form-hotspot [comment]="ctrl.comment" [parent]="ctrl.parent"></comment-paragraph-form-hotspot>'; | ||
7 | + | ||
8 | +describe("Components", () => { | ||
9 | + describe("Comment Paragraph Form Hotspot Component", () => { | ||
10 | + | ||
11 | + let helper: ComponentTestHelper; | ||
12 | + | ||
13 | + beforeEach(angular.mock.module("templates")); | ||
14 | + | ||
15 | + beforeEach((done) => { | ||
16 | + let cls = createClass({ | ||
17 | + template: htmlTemplate, | ||
18 | + directives: [CommentParagraphFormHotspotComponent], | ||
19 | + providers: [], | ||
20 | + properties: { | ||
21 | + comment: {} | ||
22 | + } | ||
23 | + }); | ||
24 | + helper = new ComponentTestHelper(cls, done); | ||
25 | + }); | ||
26 | + | ||
27 | + it('set paragraph uuid when parent has it setted', () => { | ||
28 | + helper.component.parent = { paragraph_uuid: 'uuid' }; | ||
29 | + helper.detectChanges(); | ||
30 | + expect((<any>helper.component.comment).paragraph_uuid).toEqual('uuid'); | ||
31 | + }); | ||
32 | + }); | ||
33 | +}); |
src/plugins/comment_paragraph/http/comment-paragraph.service.spec.ts
0 → 100644
@@ -0,0 +1,90 @@ | @@ -0,0 +1,90 @@ | ||
1 | +import {CommentParagraphService} from "./comment-paragraph.service"; | ||
2 | + | ||
3 | + | ||
4 | +describe("Services", () => { | ||
5 | + | ||
6 | + describe("Comment Paragraph Service", () => { | ||
7 | + | ||
8 | + let $httpBackend: ng.IHttpBackendService; | ||
9 | + let commentParagraphService: CommentParagraphService; | ||
10 | + | ||
11 | + beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => { | ||
12 | + $translateProvider.translations('en', {}); | ||
13 | + })); | ||
14 | + | ||
15 | + beforeEach(inject((_$httpBackend_: ng.IHttpBackendService, _CommentParagraphService_: CommentParagraphService) => { | ||
16 | + $httpBackend = _$httpBackend_; | ||
17 | + commentParagraphService = _CommentParagraphService_; | ||
18 | + })); | ||
19 | + | ||
20 | + | ||
21 | + describe("Succesfull requests", () => { | ||
22 | + | ||
23 | + it("should return paragraph comments by article", (done) => { | ||
24 | + let articleId = 1; | ||
25 | + $httpBackend.expectGET(`/api/v1/articles/${articleId}/comment_paragraph_plugin/comments?without_reply=true`).respond(200, { comments: [{ body: "comment1" }] }); | ||
26 | + commentParagraphService.getByArticle(<noosfero.Article>{ id: articleId }).then((result: noosfero.RestResult<noosfero.Comment[]>) => { | ||
27 | + expect(result.data).toEqual([{ body: "comment1" }]); | ||
28 | + done(); | ||
29 | + }); | ||
30 | + $httpBackend.flush(); | ||
31 | + }); | ||
32 | + | ||
33 | + it("should create a paragraph comment", (done) => { | ||
34 | + let articleId = 1; | ||
35 | + $httpBackend.expectPOST(`/api/v1/articles/${articleId}/comment_paragraph_plugin/comments`).respond(200, { comment: { body: "comment1" } }); | ||
36 | + commentParagraphService.createInArticle(<noosfero.Article>{ id: articleId }, <noosfero.Comment>{ body: "comment1" }).then((result: noosfero.RestResult<noosfero.Comment>) => { | ||
37 | + expect(result.data).toEqual({ body: "comment1" }); | ||
38 | + done(); | ||
39 | + }); | ||
40 | + $httpBackend.flush(); | ||
41 | + }); | ||
42 | + | ||
43 | + it("activate paragraph comments for an article", (done) => { | ||
44 | + let articleId = 1; | ||
45 | + $httpBackend.expectPOST(`/api/v1/articles/${articleId}/comment_paragraph_plugin/activate`).respond(200, { article: { title: "article1" } }); | ||
46 | + commentParagraphService.activateCommentParagraph(<noosfero.Article>{ id: articleId }).then((result: noosfero.RestResult<noosfero.Article>) => { | ||
47 | + expect(result.data).toEqual({ title: "article1" }); | ||
48 | + done(); | ||
49 | + }); | ||
50 | + $httpBackend.flush(); | ||
51 | + }); | ||
52 | + | ||
53 | + it("deactivate paragraph comments for an article", (done) => { | ||
54 | + let articleId = 1; | ||
55 | + $httpBackend.expectPOST(`/api/v1/articles/${articleId}/comment_paragraph_plugin/deactivate`).respond(200, { article: { title: "article1" } }); | ||
56 | + commentParagraphService.deactivateCommentParagraph(<noosfero.Article>{ id: articleId }).then((result: noosfero.RestResult<noosfero.Article>) => { | ||
57 | + expect(result.data).toEqual({ title: "article1" }); | ||
58 | + done(); | ||
59 | + }); | ||
60 | + $httpBackend.flush(); | ||
61 | + }); | ||
62 | + | ||
63 | + it("return counts for paragraph comments", (done) => { | ||
64 | + let articleId = 1; | ||
65 | + $httpBackend.expectGET(`/api/v1/articles/${articleId}/comment_paragraph_plugin/comments/count`).respond(200, { '1': 5, '2': 6 }); | ||
66 | + commentParagraphService.commentParagraphCount(<noosfero.Article>{ id: articleId }, '1').then((count: number) => { | ||
67 | + expect(count).toEqual(5); | ||
68 | + done(); | ||
69 | + }); | ||
70 | + $httpBackend.flush(); | ||
71 | + }); | ||
72 | + | ||
73 | + it("reset promise when comment paragraph counts fails", (done) => { | ||
74 | + let articleId = 1; | ||
75 | + commentParagraphService['articleService'] = jasmine.createSpyObj("articleService", ["getElement"]); | ||
76 | + commentParagraphService['articleService'].getElement = jasmine.createSpy("getElement").and.returnValue( | ||
77 | + { | ||
78 | + customGET: (path: string) => { | ||
79 | + return Promise.reject({}); | ||
80 | + } | ||
81 | + } | ||
82 | + ); | ||
83 | + commentParagraphService.commentParagraphCount(<noosfero.Article>{ id: articleId }, '1').catch(() => { | ||
84 | + expect(commentParagraphService['commentParagraphCountsPromise']).toBeNull(); | ||
85 | + done(); | ||
86 | + }); | ||
87 | + }); | ||
88 | + }); | ||
89 | + }); | ||
90 | +}); |
src/plugins/comment_paragraph/http/comment-paragraph.service.ts
0 → 100644
@@ -0,0 +1,64 @@ | @@ -0,0 +1,64 @@ | ||
1 | +import { Injectable, Inject } from "ng-forward"; | ||
2 | +import {RestangularService} from "../../../lib/ng-noosfero-api/http/restangular_service"; | ||
3 | +import {ArticleService} from "../../../lib/ng-noosfero-api/http/article.service"; | ||
4 | + | ||
5 | +@Injectable() | ||
6 | +@Inject("Restangular", "$q", "$log", ArticleService) | ||
7 | +export class CommentParagraphService extends RestangularService<noosfero.Comment> { | ||
8 | + | ||
9 | + private commentParagraphCountsPromise: ng.IPromise<any>; | ||
10 | + | ||
11 | + constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected articleService: ArticleService) { | ||
12 | + super(Restangular, $q, $log); | ||
13 | + } | ||
14 | + | ||
15 | + getResourcePath() { | ||
16 | + return "comment_paragraph_plugin/comments"; | ||
17 | + } | ||
18 | + | ||
19 | + getDataKeys() { | ||
20 | + return { | ||
21 | + singular: 'comment', | ||
22 | + plural: 'comments' | ||
23 | + }; | ||
24 | + } | ||
25 | + | ||
26 | + getByArticle(article: noosfero.Article, params: any = {}): ng.IPromise<noosfero.RestResult<noosfero.Comment[]>> { | ||
27 | + params['without_reply'] = true; | ||
28 | + let articleElement = this.articleService.getElement(<number>article.id); | ||
29 | + return this.list(articleElement, params); | ||
30 | + } | ||
31 | + | ||
32 | + createInArticle(article: noosfero.Article, comment: noosfero.Comment): ng.IPromise<noosfero.RestResult<noosfero.Comment>> { | ||
33 | + let articleElement = this.articleService.getElement(<number>article.id); | ||
34 | + return this.create(comment, articleElement, null, { 'Content-Type': 'application/json' }, false); | ||
35 | + } | ||
36 | + | ||
37 | + activateCommentParagraph(article: noosfero.Article) { | ||
38 | + let articleElement = this.articleService.getElement(<number>article.id); | ||
39 | + return this.articleService.post("comment_paragraph_plugin/activate", articleElement); | ||
40 | + } | ||
41 | + | ||
42 | + deactivateCommentParagraph(article: noosfero.Article) { | ||
43 | + let articleElement = this.articleService.getElement(<number>article.id); | ||
44 | + return this.articleService.post("comment_paragraph_plugin/deactivate", articleElement); | ||
45 | + } | ||
46 | + | ||
47 | + commentParagraphCount(article: noosfero.Article, paragraphUuid: string) { | ||
48 | + return this.commentParagraphCounts(article).then((counts: any) => { | ||
49 | + return counts[paragraphUuid]; | ||
50 | + }); | ||
51 | + } | ||
52 | + | ||
53 | + private commentParagraphCounts(article: noosfero.Article) { | ||
54 | + if (!this.commentParagraphCountsPromise) { | ||
55 | + let articleElement = this.articleService.getElement(<number>article.id); | ||
56 | + this.commentParagraphCountsPromise = articleElement.customGET("comment_paragraph_plugin/comments/count").then((response: restangular.IResponse) => { | ||
57 | + return response.data; | ||
58 | + }).catch(() => { | ||
59 | + this.commentParagraphCountsPromise = null; | ||
60 | + }); | ||
61 | + } | ||
62 | + return this.commentParagraphCountsPromise; | ||
63 | + } | ||
64 | +} |
@@ -0,0 +1,6 @@ | @@ -0,0 +1,6 @@ | ||
1 | +import {AllowCommentComponent} from "./allow-comment/allow-comment.component"; | ||
2 | +import {CommentParagraphArticleButtonHotspotComponent} from "./hotspot/comment-paragraph-article-button.component"; | ||
3 | +import {CommentParagraphFormHotspotComponent} from "./hotspot/comment-paragraph-form.component"; | ||
4 | + | ||
5 | +export let mainComponents: any = [AllowCommentComponent]; | ||
6 | +export let hotspots: any = [CommentParagraphArticleButtonHotspotComponent, CommentParagraphFormHotspotComponent]; |
src/plugins/comment_paragraph/side-comments/side-comments.component.spec.ts
0 → 100644
@@ -0,0 +1,50 @@ | @@ -0,0 +1,50 @@ | ||
1 | +import {SideCommentsComponent} from "./side-comments.component"; | ||
2 | +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper'; | ||
3 | +import * as helpers from "../../../spec/helpers"; | ||
4 | +import {Provider} from 'ng-forward'; | ||
5 | +import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder'; | ||
6 | + | ||
7 | +let htmlTemplate = '<comment-paragraph-side-comments [article]="ctrl.article" [paragraph-uuid]="ctrl.paragraphUuid"></comment-paragraph-side-comments>'; | ||
8 | + | ||
9 | +describe("Components", () => { | ||
10 | + describe("Side Comments Component", () => { | ||
11 | + | ||
12 | + let serviceMock = jasmine.createSpyObj("CommentParagraphService", ["getByArticle"]); | ||
13 | + serviceMock.getByArticle = jasmine.createSpy("getByArticle").and.returnValue(Promise.resolve({ data: {} })); | ||
14 | + | ||
15 | + let commentServiceMock = {}; | ||
16 | + let postCommentEventService = jasmine.createSpyObj("postCommentEventService", ["emit", "subscribe"]); | ||
17 | + postCommentEventService.subscribe = jasmine.createSpy("subscribe"); | ||
18 | + | ||
19 | + let providers = [ | ||
20 | + new Provider('CommentParagraphService', { useValue: serviceMock }), | ||
21 | + new Provider('CommentService', { useValue: commentServiceMock }), | ||
22 | + new Provider('PostCommentEventService', { useValue: postCommentEventService }) | ||
23 | + ]; | ||
24 | + let helper: ComponentTestHelper; | ||
25 | + | ||
26 | + beforeEach(angular.mock.module("templates")); | ||
27 | + | ||
28 | + beforeEach((done) => { | ||
29 | + let cls = createClass({ | ||
30 | + template: htmlTemplate, | ||
31 | + directives: [SideCommentsComponent], | ||
32 | + providers: providers, | ||
33 | + properties: { | ||
34 | + paragraphUuid: "uuid", | ||
35 | + article: {} | ||
36 | + } | ||
37 | + }); | ||
38 | + helper = new ComponentTestHelper(cls, done); | ||
39 | + }); | ||
40 | + | ||
41 | + it('call service to load paragraph comments', () => { | ||
42 | + helper.component.loadComments(); | ||
43 | + expect(serviceMock.getByArticle).toHaveBeenCalled(); | ||
44 | + }); | ||
45 | + | ||
46 | + it('set paragraph uuid in new comment object', () => { | ||
47 | + expect(helper.component.newComment['paragraph_uuid']).toEqual('uuid'); | ||
48 | + }); | ||
49 | + }); | ||
50 | +}); |
src/plugins/comment_paragraph/side-comments/side-comments.component.ts
0 → 100644
@@ -0,0 +1,32 @@ | @@ -0,0 +1,32 @@ | ||
1 | +import {Component, Inject, Input, Output} from "ng-forward"; | ||
2 | +import {CommentsComponent} from "../../../app/article/comment/comments.component"; | ||
3 | +import {CommentService} from "../../../lib/ng-noosfero-api/http/comment.service"; | ||
4 | +import {CommentParagraphService} from "../http/comment-paragraph.service"; | ||
5 | +import { PostCommentEventService } from "../../../app/article/comment/post-comment/post-comment-event.service"; | ||
6 | + | ||
7 | +@Component({ | ||
8 | + selector: "comment-paragraph-side-comments", | ||
9 | + templateUrl: 'app/article/comment/comments.html', | ||
10 | +}) | ||
11 | +@Inject(CommentService, PostCommentEventService, "$scope", CommentParagraphService) | ||
12 | +export class SideCommentsComponent extends CommentsComponent { | ||
13 | + | ||
14 | + @Input() article: noosfero.Article; | ||
15 | + @Input() paragraphUuid: string; | ||
16 | + | ||
17 | + constructor(commentService: CommentService, | ||
18 | + postCommentEventService: PostCommentEventService, | ||
19 | + $scope: ng.IScope, | ||
20 | + private commentParagraphService: CommentParagraphService) { | ||
21 | + super(commentService, postCommentEventService, $scope); | ||
22 | + } | ||
23 | + | ||
24 | + ngOnInit() { | ||
25 | + super.ngOnInit(); | ||
26 | + (<any>this.newComment).paragraph_uuid = this.paragraphUuid; | ||
27 | + } | ||
28 | + | ||
29 | + loadComments() { | ||
30 | + return this.commentParagraphService.getByArticle(this.article, { page: this.page, per_page: this.perPage, paragraph_uuid: this.paragraphUuid }); | ||
31 | + } | ||
32 | +} |
src/plugins/comment_paragraph/side-comments/side-comments.scss
0 → 100644
@@ -0,0 +1,7 @@ | @@ -0,0 +1,7 @@ | ||
1 | +import * as commentParagraph from "./comment_paragraph"; | ||
2 | + | ||
3 | +export let mainComponents: any = []; | ||
4 | +mainComponents = mainComponents.concat(commentParagraph.mainComponents); | ||
5 | + | ||
6 | +export let hotspots: any = []; | ||
7 | +hotspots = hotspots.concat(commentParagraph.hotspots); |
src/spec/component-test-helper.ts
@@ -68,6 +68,8 @@ export class ComponentTestHelper<T extends any> { | @@ -68,6 +68,8 @@ export class ComponentTestHelper<T extends any> { | ||
68 | this.init(done); | 68 | this.init(done); |
69 | } | 69 | } |
70 | 70 | ||
71 | + fixture: any; | ||
72 | + | ||
71 | /** | 73 | /** |
72 | * @ngdoc method | 74 | * @ngdoc method |
73 | * @name init | 75 | * @name init |
@@ -79,7 +81,8 @@ export class ComponentTestHelper<T extends any> { | @@ -79,7 +81,8 @@ export class ComponentTestHelper<T extends any> { | ||
79 | let promisse = this.tcb.createAsync(this.mockComponent) as Promise<ComponentFixture>; | 81 | let promisse = this.tcb.createAsync(this.mockComponent) as Promise<ComponentFixture>; |
80 | return promisse.then((fixture: any) => { | 82 | return promisse.then((fixture: any) => { |
81 | // Fire all angular events and parsing | 83 | // Fire all angular events and parsing |
82 | - fixture.detectChanges(); | 84 | + this.fixture = fixture; |
85 | + this.detectChanges(); | ||
83 | // The main debug element | 86 | // The main debug element |
84 | this.debugElement = fixture.debugElement; | 87 | this.debugElement = fixture.debugElement; |
85 | this.component = <T>this.debugElement.componentViewChildren[0].componentInstance; | 88 | this.component = <T>this.debugElement.componentViewChildren[0].componentInstance; |
@@ -96,6 +99,17 @@ export class ComponentTestHelper<T extends any> { | @@ -96,6 +99,17 @@ export class ComponentTestHelper<T extends any> { | ||
96 | 99 | ||
97 | /** | 100 | /** |
98 | * @ngdoc method | 101 | * @ngdoc method |
102 | + * @name detectChanges | ||
103 | + * @methodOf spec.ComponentTestHelper | ||
104 | + * @description | ||
105 | + * Detect changes in component | ||
106 | + */ | ||
107 | + detectChanges() { | ||
108 | + this.fixture.detectChanges(); | ||
109 | + } | ||
110 | + | ||
111 | + /** | ||
112 | + * @ngdoc method | ||
99 | * @name all | 113 | * @name all |
100 | * @methodOf spec.ComponentTestHelper | 114 | * @methodOf spec.ComponentTestHelper |
101 | * @description | 115 | * @description |