Commit 88c2b5831b8e61923ca9b3f07340b7906c591a3e
1 parent
4000a698
Exists in
master
and in
26 other branches
Add button to remove comments from articles
Showing
13 changed files
with
106 additions
and
17 deletions
Show diff stats
src/app/article/comment/comment.component.spec.ts
... | ... | @@ -18,7 +18,11 @@ describe("Components", () => { |
18 | 18 | }); |
19 | 19 | |
20 | 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 | 27 | @Component({ selector: 'test-container-component', directives: [CommentComponent], template: htmlTemplate, providers: providers }) |
24 | 28 | class ContainerComponent { | ... | ... |
src/app/article/comment/comment.component.ts
1 | 1 | import { Inject, Input, Component, Output, EventEmitter } from 'ng-forward'; |
2 | 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 | 6 | @Component({ |
5 | 7 | selector: 'noosfero-comment', |
6 | 8 | templateUrl: 'app/article/comment/comment.html' |
7 | 9 | }) |
10 | +@Inject(CommentService, NotificationService) | |
8 | 11 | export class CommentComponent { |
9 | 12 | |
10 | 13 | @Input() comment: noosfero.CommentViewModel; |
... | ... | @@ -16,11 +19,23 @@ export class CommentComponent { |
16 | 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 | 25 | reply() { |
24 | 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 | 18 | <div class="title">{{ctrl.comment.title}}</div> |
19 | 19 | <div class="body">{{ctrl.comment.body}}</div> |
20 | 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 | 23 | {{"comment.reply" | translate}} |
23 | 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 | 29 | </div> |
25 | 30 | </div> |
26 | 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 | 10 | .title { |
11 | 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 | 29 | .media-left { |
14 | 30 | min-width: 40px; |
15 | 31 | } | ... | ... |
src/app/shared/services/notification.service.spec.ts
... | ... | @@ -20,10 +20,10 @@ describe("Components", () => { |
20 | 20 | let component: NotificationService = new NotificationService(<any>helpers.mocks.$log, <any>sweetAlert, <any>helpers.mocks.translatorService); |
21 | 21 | component.error({ message: "message", title: "title" }); |
22 | 22 | expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ |
23 | - text: "message", | |
24 | 23 | title: "title", |
24 | + text: "message", | |
25 | 25 | type: "error" |
26 | - })); | |
26 | + }), null); | |
27 | 27 | done(); |
28 | 28 | }); |
29 | 29 | |
... | ... | @@ -36,7 +36,7 @@ describe("Components", () => { |
36 | 36 | expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ |
37 | 37 | text: NotificationService.DEFAULT_ERROR_MESSAGE, |
38 | 38 | type: "error" |
39 | - })); | |
39 | + }), null); | |
40 | 40 | done(); |
41 | 41 | }); |
42 | 42 | |
... | ... | @@ -48,7 +48,7 @@ describe("Components", () => { |
48 | 48 | component.success({ title: "title", message: "message", timer: 1000 }); |
49 | 49 | expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ |
50 | 50 | type: "success" |
51 | - })); | |
51 | + }), null); | |
52 | 52 | done(); |
53 | 53 | }); |
54 | 54 | |
... | ... | @@ -60,7 +60,7 @@ describe("Components", () => { |
60 | 60 | component.httpError(500, {}); |
61 | 61 | expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ |
62 | 62 | text: "notification.http_error.500.message" |
63 | - })); | |
63 | + }), null); | |
64 | 64 | done(); |
65 | 65 | }); |
66 | 66 | |
... | ... | @@ -73,7 +73,22 @@ describe("Components", () => { |
73 | 73 | expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({ |
74 | 74 | type: "success", |
75 | 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 | 92 | done(); |
78 | 93 | }); |
79 | 94 | }); | ... | ... |
src/app/shared/services/notification.service.ts
... | ... | @@ -36,15 +36,23 @@ export class NotificationService { |
36 | 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 | 44 | this.$log.debug("Notification message:", title, text, type, this.translatorService.currentLanguage()); |
41 | 45 | this.SweetAlert.swal({ |
42 | 46 | title: this.translatorService.translate(title), |
43 | 47 | text: this.translatorService.translate(text), |
44 | 48 | type: type, |
45 | 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 | 35 | "comment.pagination.more": "More", |
36 | 36 | "comment.post.success.title": "Good job!", |
37 | 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 | 42 | "comment.reply": "reply", |
43 | + "comment.remove": "remove", | |
39 | 44 | "article.actions.edit": "Edit", |
40 | 45 | "article.actions.read_more": "Read More", |
41 | 46 | "article.basic_editor.title": "Title", | ... | ... |
src/languages/pt.json
... | ... | @@ -35,7 +35,12 @@ |
35 | 35 | "comment.pagination.more": "Mais", |
36 | 36 | "comment.post.success.title": "Bom trabalho!", |
37 | 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 | 42 | "comment.reply": "responder", |
43 | + "comment.remove": "remover", | |
39 | 44 | "article.actions.edit": "Editar", |
40 | 45 | "article.actions.read_more": "Ler mais", |
41 | 46 | "article.basic_editor.title": "Título", | ... | ... |
src/lib/ng-noosfero-api/http/comment.service.spec.ts
... | ... | @@ -40,6 +40,17 @@ describe("Services", () => { |
40 | 40 | }); |
41 | 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<noosfero.Comment> { |
31 | 31 | let articleElement = this.articleService.getElement(<number>article.id); |
32 | 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("Components", () => { |
57 | 57 | }); |
58 | 58 | |
59 | 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 | 63 | it('set display to true when click in show paragraph', () => { | ... | ... |
src/plugins/comment_paragraph/allow-comment/allow-comment.html
1 | 1 | <div class="paragraph" ng-class="{'active' : ctrl.display}"> |
2 | 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 | 4 | <a href="#" popover-placement="right-top" popover-trigger="none" |
5 | 5 | uib-popover-template="'plugins/comment_paragraph/allow-comment/popover.html'" |
6 | 6 | (click)="ctrl.showParagraphComments()" popover-is-open="ctrl.display"> | ... | ... |
src/plugins/comment_paragraph/allow-comment/allow-comment.scss