Commit 88c2b5831b8e61923ca9b3f07340b7906c591a3e

Authored by Victor Costa
1 parent 4000a698

Add button to remove comments from articles

src/app/article/comment/comment.component.spec.ts
@@ -18,7 +18,11 @@ describe("Components", () => { @@ -18,7 +18,11 @@ describe("Components", () => {
18 }); 18 });
19 19
20 function createComponent() { 20 function createComponent() {
21 - let providers = helpers.provideFilters("translateFilter"); 21 + let commentService = jasmine.createSpyObj("commentService", ["removeFromArticle"]);
  22 + let providers = [
  23 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService),
  24 + helpers.createProviderToValue("CommentService", commentService)
  25 + ].concat(helpers.provideFilters("translateFilter"));
22 26
23 @Component({ selector: 'test-container-component', directives: [CommentComponent], template: htmlTemplate, providers: providers }) 27 @Component({ selector: 'test-container-component', directives: [CommentComponent], template: htmlTemplate, providers: providers })
24 class ContainerComponent { 28 class ContainerComponent {
src/app/article/comment/comment.component.ts
1 import { Inject, Input, Component, Output, EventEmitter } from 'ng-forward'; 1 import { Inject, Input, Component, Output, EventEmitter } from 'ng-forward';
2 import { PostCommentComponent } from "./post-comment/post-comment.component"; 2 import { PostCommentComponent } from "./post-comment/post-comment.component";
  3 +import { CommentService } from "../../../lib/ng-noosfero-api/http/comment.service";
  4 +import { NotificationService } from "../../shared/services/notification.service";
3 5
4 @Component({ 6 @Component({
5 selector: 'noosfero-comment', 7 selector: 'noosfero-comment',
6 templateUrl: 'app/article/comment/comment.html' 8 templateUrl: 'app/article/comment/comment.html'
7 }) 9 })
  10 +@Inject(CommentService, NotificationService)
8 export class CommentComponent { 11 export class CommentComponent {
9 12
10 @Input() comment: noosfero.CommentViewModel; 13 @Input() comment: noosfero.CommentViewModel;
@@ -16,11 +19,23 @@ export class CommentComponent { @@ -16,11 +19,23 @@ export class CommentComponent {
16 return this.comment && this.comment.__show_reply === true; 19 return this.comment && this.comment.__show_reply === true;
17 } 20 }
18 21
19 - constructor() {  
20 - }  
21 - 22 + constructor(private commentService: CommentService,
  23 + private notificationService: NotificationService) { }
22 24
23 reply() { 25 reply() {
24 this.comment.__show_reply = !this.comment.__show_reply; 26 this.comment.__show_reply = !this.comment.__show_reply;
25 } 27 }
  28 +
  29 + allowRemove() {
  30 + return true;
  31 + }
  32 +
  33 + remove() {
  34 + this.notificationService.confirmation({ title: "comment.remove.confirmation.title", message: "comment.remove.confirmation.message" }, () => {
  35 + this.commentService.removeFromArticle(this.article, this.comment).then((result: noosfero.RestResult<noosfero.Comment>) => {
  36 + // FIXME send event
  37 + this.notificationService.success({ title: "comment.remove.success.title", message: "comment.remove.success.message" });
  38 + });
  39 + });
  40 + }
26 } 41 }
src/app/article/comment/comment.html
@@ -18,9 +18,14 @@ @@ -18,9 +18,14 @@
18 <div class="title">{{ctrl.comment.title}}</div> 18 <div class="title">{{ctrl.comment.title}}</div>
19 <div class="body">{{ctrl.comment.body}}</div> 19 <div class="body">{{ctrl.comment.body}}</div>
20 <div class="actions" ng-if="ctrl.displayActions"> 20 <div class="actions" ng-if="ctrl.displayActions">
21 - <a href="#" (click)="ctrl.reply()" class="small text-muted reply" ng-if="ctrl.article.accept_comments"> 21 + <a href="#" (click)="ctrl.reply()" class="action small text-muted reply" ng-if="ctrl.article.accept_comments">
  22 + <span class="bullet-separator">•</span>
22 {{"comment.reply" | translate}} 23 {{"comment.reply" | translate}}
23 </a> 24 </a>
  25 + <a href="#" (click)="ctrl.remove()" class="action small text-muted remove" ng-if="ctrl.allowRemove()">
  26 + <span class="bullet-separator">•</span>
  27 + {{"comment.remove" | translate}}
  28 + </a>
24 </div> 29 </div>
25 </div> 30 </div>
26 <noosfero-comments [show-form]="ctrl.showReply()" [article]="ctrl.article" [parent]="ctrl.comment" ng-if="ctrl.displayReplies"></noosfero-comments> 31 <noosfero-comments [show-form]="ctrl.showReply()" [article]="ctrl.article" [parent]="ctrl.comment" ng-if="ctrl.displayReplies"></noosfero-comments>
src/app/article/comment/comment.scss
@@ -10,6 +10,22 @@ @@ -10,6 +10,22 @@
10 .title { 10 .title {
11 font-weight: bold; 11 font-weight: bold;
12 } 12 }
  13 + .actions {
  14 + .action {
  15 + text-decoration: none;
  16 + &:first-child {
  17 + .bullet-separator {
  18 + display: none;
  19 + }
  20 + }
  21 + .bullet-separator {
  22 + font-size: 8px;
  23 + vertical-align: middle;
  24 + margin: 3px;
  25 + color: #B6C2CA;
  26 + }
  27 + }
  28 + }
13 .media-left { 29 .media-left {
14 min-width: 40px; 30 min-width: 40px;
15 } 31 }
src/app/shared/services/notification.service.spec.ts
@@ -20,10 +20,10 @@ describe(&quot;Components&quot;, () =&gt; { @@ -20,10 +20,10 @@ describe(&quot;Components&quot;, () =&gt; {
20 let component: NotificationService = new NotificationService(<any>helpers.mocks.$log, <any>sweetAlert, <any>helpers.mocks.translatorService); 20 let component: NotificationService = new NotificationService(<any>helpers.mocks.$log, <any>sweetAlert, <any>helpers.mocks.translatorService);
21 component.error({ message: "message", title: "title" }); 21 component.error({ message: "message", title: "title" });
22 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ 22 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
23 - text: "message",  
24 title: "title", 23 title: "title",
  24 + text: "message",
25 type: "error" 25 type: "error"
26 - })); 26 + }), null);
27 done(); 27 done();
28 }); 28 });
29 29
@@ -36,7 +36,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -36,7 +36,7 @@ describe(&quot;Components&quot;, () =&gt; {
36 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ 36 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
37 text: NotificationService.DEFAULT_ERROR_MESSAGE, 37 text: NotificationService.DEFAULT_ERROR_MESSAGE,
38 type: "error" 38 type: "error"
39 - })); 39 + }), null);
40 done(); 40 done();
41 }); 41 });
42 42
@@ -48,7 +48,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -48,7 +48,7 @@ describe(&quot;Components&quot;, () =&gt; {
48 component.success({ title: "title", message: "message", timer: 1000 }); 48 component.success({ title: "title", message: "message", timer: 1000 });
49 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ 49 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
50 type: "success" 50 type: "success"
51 - })); 51 + }), null);
52 done(); 52 done();
53 }); 53 });
54 54
@@ -60,7 +60,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -60,7 +60,7 @@ describe(&quot;Components&quot;, () =&gt; {
60 component.httpError(500, {}); 60 component.httpError(500, {});
61 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ 61 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
62 text: "notification.http_error.500.message" 62 text: "notification.http_error.500.message"
63 - })); 63 + }), null);
64 done(); 64 done();
65 }); 65 });
66 66
@@ -73,7 +73,22 @@ describe(&quot;Components&quot;, () =&gt; { @@ -73,7 +73,22 @@ describe(&quot;Components&quot;, () =&gt; {
73 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ 73 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
74 type: "success", 74 type: "success",
75 timer: NotificationService.DEFAULT_SUCCESS_TIMER 75 timer: NotificationService.DEFAULT_SUCCESS_TIMER
76 - })); 76 + }), null);
  77 + done();
  78 + });
  79 +
  80 + it("display a confirmation dialog when call confirmation method", done => {
  81 + let sweetAlert = jasmine.createSpyObj("sweetAlert", ["swal"]);
  82 + sweetAlert.swal = jasmine.createSpy("swal");
  83 +
  84 + let component: NotificationService = new NotificationService(<any>helpers.mocks.$log, <any>sweetAlert, <any>helpers.mocks.translatorService);
  85 + let func = () => { };
  86 + component.confirmation({ title: "title", message: "message" }, func);
  87 + expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
  88 + title: "title",
  89 + text: "message",
  90 + type: "warning"
  91 + }), jasmine.any(Function));
77 done(); 92 done();
78 }); 93 });
79 }); 94 });
src/app/shared/services/notification.service.ts
@@ -36,15 +36,23 @@ export class NotificationService { @@ -36,15 +36,23 @@ export class NotificationService {
36 this.showMessage({ title: title, text: message, timer: timer }); 36 this.showMessage({ title: title, text: message, timer: timer });
37 } 37 }
38 38
39 - private showMessage({title, text, type = "success", timer = null, showConfirmButton = true}) { 39 + confirmation({title, message, showCancelButton = true, type = "warning"}, confirmationFunction: Function) {
  40 + this.showMessage({ title: title, text: message, showCancelButton: showCancelButton, type: type, closeOnConfirm: false }, confirmationFunction);
  41 + }
  42 +
  43 + private showMessage({title, text, type = "success", timer = null, showConfirmButton = true, showCancelButton = false, closeOnConfirm = true}, confirmationFunction: Function = null) {
40 this.$log.debug("Notification message:", title, text, type, this.translatorService.currentLanguage()); 44 this.$log.debug("Notification message:", title, text, type, this.translatorService.currentLanguage());
41 this.SweetAlert.swal({ 45 this.SweetAlert.swal({
42 title: this.translatorService.translate(title), 46 title: this.translatorService.translate(title),
43 text: this.translatorService.translate(text), 47 text: this.translatorService.translate(text),
44 type: type, 48 type: type,
45 timer: timer, 49 timer: timer,
46 - showConfirmButton: showConfirmButton  
47 - }); 50 + showConfirmButton: showConfirmButton,
  51 + showCancelButton: showCancelButton,
  52 + closeOnConfirm: closeOnConfirm
  53 + }, confirmationFunction ? (isConfirm: boolean) => {
  54 + if (isConfirm) confirmationFunction();
  55 + } : null);
48 } 56 }
49 57
50 } 58 }
src/languages/en.json
@@ -35,7 +35,12 @@ @@ -35,7 +35,12 @@
35 "comment.pagination.more": "More", 35 "comment.pagination.more": "More",
36 "comment.post.success.title": "Good job!", 36 "comment.post.success.title": "Good job!",
37 "comment.post.success.message": "Comment saved!", 37 "comment.post.success.message": "Comment saved!",
  38 + "comment.remove.success.title": "Good job!",
  39 + "comment.remove.success.message": "Comment removed!",
  40 + "comment.remove.confirmation.title": "Are you sure?",
  41 + "comment.remove.confirmation.message": "You will not be able to recover this comment!",
38 "comment.reply": "reply", 42 "comment.reply": "reply",
  43 + "comment.remove": "remove",
39 "article.actions.edit": "Edit", 44 "article.actions.edit": "Edit",
40 "article.actions.read_more": "Read More", 45 "article.actions.read_more": "Read More",
41 "article.basic_editor.title": "Title", 46 "article.basic_editor.title": "Title",
src/languages/pt.json
@@ -35,7 +35,12 @@ @@ -35,7 +35,12 @@
35 "comment.pagination.more": "Mais", 35 "comment.pagination.more": "Mais",
36 "comment.post.success.title": "Bom trabalho!", 36 "comment.post.success.title": "Bom trabalho!",
37 "comment.post.success.message": "Comentário salvo com sucesso!", 37 "comment.post.success.message": "Comentário salvo com sucesso!",
  38 + "comment.remove.success.title": "Bom trabalho!",
  39 + "comment.remove.success.message": "Comentário removido com sucesso!",
  40 + "comment.remove.confirmation.title": "Tem certeza?",
  41 + "comment.remove.confirmation.message": "Você não poderá recuperar o comentário removido!",
38 "comment.reply": "responder", 42 "comment.reply": "responder",
  43 + "comment.remove": "remover",
39 "article.actions.edit": "Editar", 44 "article.actions.edit": "Editar",
40 "article.actions.read_more": "Ler mais", 45 "article.actions.read_more": "Ler mais",
41 "article.basic_editor.title": "Título", 46 "article.basic_editor.title": "Título",
src/lib/ng-noosfero-api/http/comment.service.spec.ts
@@ -40,6 +40,17 @@ describe(&quot;Services&quot;, () =&gt; { @@ -40,6 +40,17 @@ describe(&quot;Services&quot;, () =&gt; {
40 }); 40 });
41 $httpBackend.flush(); 41 $httpBackend.flush();
42 }); 42 });
  43 +
  44 + it("should remove a comment from an article", (done) => {
  45 + let articleId = 1;
  46 + let comment: noosfero.Comment = <any>{ id: 2 };
  47 + $httpBackend.expectDELETE(`/api/v1/articles/${articleId}/comments/${comment.id}`).respond(200, { comment: { id: 2 } });
  48 + commentService.removeFromArticle(<noosfero.Article>{ id: articleId }, comment).then((result: noosfero.RestResult<noosfero.Comment>) => {
  49 + expect(result.data).toEqual({ id: 2 });
  50 + done();
  51 + });
  52 + $httpBackend.flush();
  53 + });
43 }); 54 });
44 55
45 56
src/lib/ng-noosfero-api/http/comment.service.ts
@@ -31,4 +31,9 @@ export class CommentService extends RestangularService&lt;noosfero.Comment&gt; { @@ -31,4 +31,9 @@ export class CommentService extends RestangularService&lt;noosfero.Comment&gt; {
31 let articleElement = this.articleService.getElement(<number>article.id); 31 let articleElement = this.articleService.getElement(<number>article.id);
32 return this.create(comment, articleElement, null, { 'Content-Type': 'application/json' }, false); 32 return this.create(comment, articleElement, null, { 'Content-Type': 'application/json' }, false);
33 } 33 }
  34 +
  35 + removeFromArticle(article: noosfero.Article, comment: noosfero.Comment): ng.IPromise<noosfero.RestResult<noosfero.Comment>> {
  36 + let articleElement = this.articleService.getElement(<number>article.id);
  37 + return this.remove(comment, articleElement);
  38 + }
34 } 39 }
src/plugins/comment_paragraph/allow-comment/allow-comment.component.spec.ts
@@ -57,7 +57,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -57,7 +57,7 @@ describe(&quot;Components&quot;, () =&gt; {
57 }); 57 });
58 58
59 it('display button to side comments', () => { 59 it('display button to side comments', () => {
60 - expect(helper.all(".paragraph .actions a").length).toEqual(1); 60 + expect(helper.all(".paragraph .paragraph-actions a").length).toEqual(1);
61 }); 61 });
62 62
63 it('set display to true when click in show paragraph', () => { 63 it('set display to true when click in show paragraph', () => {
src/plugins/comment_paragraph/allow-comment/allow-comment.html
1 <div class="paragraph" ng-class="{'active' : ctrl.display}"> 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> 2 <div class="paragraph-content" ng-bind-html="ctrl.content" ng-class="{'active' : ctrl.display}"></div>
3 - <div ng-if="ctrl.isActivated()" class="actions"> 3 + <div ng-if="ctrl.isActivated()" class="paragraph-actions">
4 <a href="#" popover-placement="right-top" popover-trigger="none" 4 <a href="#" popover-placement="right-top" popover-trigger="none"
5 uib-popover-template="'plugins/comment_paragraph/allow-comment/popover.html'" 5 uib-popover-template="'plugins/comment_paragraph/allow-comment/popover.html'"
6 (click)="ctrl.showParagraphComments()" popover-is-open="ctrl.display"> 6 (click)="ctrl.showParagraphComments()" popover-is-open="ctrl.display">
src/plugins/comment_paragraph/allow-comment/allow-comment.scss
@@ -15,7 +15,7 @@ comment-paragraph-plugin-allow-comment { @@ -15,7 +15,7 @@ comment-paragraph-plugin-allow-comment {
15 width: 95%; 15 width: 95%;
16 display: inline-block; 16 display: inline-block;
17 } 17 }
18 - .actions { 18 + .paragraph-actions {
19 width: 3%; 19 width: 3%;
20 display: inline-block; 20 display: inline-block;
21 vertical-align: top; 21 vertical-align: top;