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,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("Components", () => { | @@ -20,10 +20,10 @@ describe("Components", () => { | ||
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("Components", () => { | @@ -36,7 +36,7 @@ describe("Components", () => { | ||
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("Components", () => { | @@ -48,7 +48,7 @@ describe("Components", () => { | ||
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("Components", () => { | @@ -60,7 +60,7 @@ describe("Components", () => { | ||
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("Components", () => { | @@ -73,7 +73,22 @@ describe("Components", () => { | ||
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("Services", () => { | @@ -40,6 +40,17 @@ describe("Services", () => { | ||
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<noosfero.Comment> { | @@ -31,4 +31,9 @@ export class CommentService extends RestangularService<noosfero.Comment> { | ||
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("Components", () => { | @@ -57,7 +57,7 @@ describe("Components", () => { | ||
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; |