Commit 45e136413c812b5d1d447f75930635a6260d5621
Exists in
master
and in
28 other branches
Merge branch 'comments'
Showing
18 changed files
with
237 additions
and
92 deletions
Show diff stats
src/app/article/article-default-view-component.spec.ts
| @@ -27,7 +27,8 @@ describe("Components", () => { | @@ -27,7 +27,8 @@ describe("Components", () => { | ||
| 27 | providers: [ | 27 | providers: [ |
| 28 | helpers.createProviderToValue('CommentService', helpers.mocks.commentService), | 28 | helpers.createProviderToValue('CommentService', helpers.mocks.commentService), |
| 29 | helpers.provideFilters("translateFilter"), | 29 | helpers.provideFilters("translateFilter"), |
| 30 | - helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService) | 30 | + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService), |
| 31 | + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})) | ||
| 31 | ] | 32 | ] |
| 32 | }) | 33 | }) |
| 33 | class ArticleContainerComponent { | 34 | class ArticleContainerComponent { |
| @@ -64,7 +65,8 @@ describe("Components", () => { | @@ -64,7 +65,8 @@ describe("Components", () => { | ||
| 64 | providers: [ | 65 | providers: [ |
| 65 | helpers.createProviderToValue('CommentService', helpers.mocks.commentService), | 66 | helpers.createProviderToValue('CommentService', helpers.mocks.commentService), |
| 66 | helpers.provideFilters("translateFilter"), | 67 | helpers.provideFilters("translateFilter"), |
| 67 | - helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService) | 68 | + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService), |
| 69 | + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})) | ||
| 68 | ] | 70 | ] |
| 69 | }) | 71 | }) |
| 70 | class ArticleContainerComponent { | 72 | class ArticleContainerComponent { |
src/app/article/comment/comment.component.spec.ts
| 1 | import {Provider, provide, Component} from 'ng-forward'; | 1 | import {Provider, provide, Component} from 'ng-forward'; |
| 2 | import * as helpers from "../../../spec/helpers"; | 2 | import * as helpers from "../../../spec/helpers"; |
| 3 | - | ||
| 4 | import {CommentComponent} from './comment.component'; | 3 | import {CommentComponent} from './comment.component'; |
| 5 | 4 | ||
| 6 | const htmlTemplate: string = '<noosfero-comment [article]="ctrl.article" [comment]="ctrl.comment"></noosfero-comment>'; | 5 | const htmlTemplate: string = '<noosfero-comment [article]="ctrl.article" [comment]="ctrl.comment"></noosfero-comment>'; |
| @@ -10,35 +9,48 @@ describe("Components", () => { | @@ -10,35 +9,48 @@ describe("Components", () => { | ||
| 10 | 9 | ||
| 11 | beforeEach(angular.mock.module("templates")); | 10 | beforeEach(angular.mock.module("templates")); |
| 12 | 11 | ||
| 13 | - @Component({ selector: 'test-container-component', directives: [CommentComponent], template: htmlTemplate, providers: helpers.provideFilters("translateFilter") }) | ||
| 14 | - class ContainerComponent { | ||
| 15 | - article = { id: 1 }; | ||
| 16 | - comment = { title: "title", body: "body" }; | 12 | + |
| 13 | + function createComponent() { | ||
| 14 | + let providers = helpers.provideFilters("translateFilter"); | ||
| 15 | + | ||
| 16 | + @Component({ selector: 'test-container-component', directives: [CommentComponent], template: htmlTemplate, providers: providers }) | ||
| 17 | + class ContainerComponent { | ||
| 18 | + article = { id: 1 }; | ||
| 19 | + comment = { title: "title", body: "body" }; | ||
| 20 | + } | ||
| 21 | + return helpers.createComponentFromClass(ContainerComponent); | ||
| 17 | } | 22 | } |
| 18 | 23 | ||
| 19 | it("render a comment", done => { | 24 | it("render a comment", done => { |
| 20 | - helpers.createComponentFromClass(ContainerComponent).then(fixture => { | 25 | + createComponent().then(fixture => { |
| 21 | expect(fixture.debugElement.queryAll(".comment").length).toEqual(1); | 26 | expect(fixture.debugElement.queryAll(".comment").length).toEqual(1); |
| 22 | done(); | 27 | done(); |
| 23 | }); | 28 | }); |
| 24 | }); | 29 | }); |
| 25 | 30 | ||
| 26 | it("not render a post comment tag in the beginning", done => { | 31 | it("not render a post comment tag in the beginning", done => { |
| 27 | - helpers.createComponentFromClass(ContainerComponent).then(fixture => { | 32 | + createComponent().then(fixture => { |
| 28 | expect(fixture.debugElement.queryAll("noosfero-post-comment").length).toEqual(0); | 33 | expect(fixture.debugElement.queryAll("noosfero-post-comment").length).toEqual(0); |
| 29 | done(); | 34 | done(); |
| 30 | }); | 35 | }); |
| 31 | }); | 36 | }); |
| 32 | 37 | ||
| 33 | - it("render a post comment tag when click in reply", done => { | ||
| 34 | - helpers.createComponentFromClass(ContainerComponent).then(fixture => { | 38 | + it("set show reply to true when click reply", done => { |
| 39 | + createComponent().then(fixture => { | ||
| 35 | let component: CommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance; | 40 | let component: CommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance; |
| 36 | component.reply(); | 41 | component.reply(); |
| 37 | - fixture.debugElement.getLocal("$rootScope").$apply(); | ||
| 38 | - expect(fixture.debugElement.queryAll("noosfero-post-comment").length).toEqual(1); | 42 | + expect(component.showReply()).toBeTruthy("Reply was expected to be true"); |
| 39 | done(); | 43 | done(); |
| 40 | }); | 44 | }); |
| 41 | }); | 45 | }); |
| 42 | 46 | ||
| 47 | + it("show reply relies on current comment __showReply attribute", done => { | ||
| 48 | + createComponent().then(fixture => { | ||
| 49 | + let component = fixture.debugElement.componentViewChildren[0]; | ||
| 50 | + component.componentInstance.comment.__showReply = false; | ||
| 51 | + expect(component.componentInstance.showReply()).toEqual(false); | ||
| 52 | + done(); | ||
| 53 | + }); | ||
| 54 | + }); | ||
| 43 | }); | 55 | }); |
| 44 | }); | 56 | }); |
src/app/article/comment/comment.component.ts
| 1 | -import { Input, Component } from 'ng-forward'; | 1 | +import { Inject, Input, Component, Output, EventEmitter } from 'ng-forward'; |
| 2 | +import { PostCommentComponent } from "./post-comment/post-comment.component"; | ||
| 2 | 3 | ||
| 3 | @Component({ | 4 | @Component({ |
| 4 | selector: 'noosfero-comment', | 5 | selector: 'noosfero-comment', |
| @@ -6,12 +7,18 @@ import { Input, Component } from 'ng-forward'; | @@ -6,12 +7,18 @@ import { Input, Component } from 'ng-forward'; | ||
| 6 | }) | 7 | }) |
| 7 | export class CommentComponent { | 8 | export class CommentComponent { |
| 8 | 9 | ||
| 9 | - @Input() comment: noosfero.Comment; | 10 | + @Input() comment: noosfero.CommentViewModel; |
| 10 | @Input() article: noosfero.Article; | 11 | @Input() article: noosfero.Article; |
| 11 | 12 | ||
| 12 | - showReply: boolean = false; | 13 | + showReply() { |
| 14 | + return this.comment && this.comment.__show_reply === true; | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + constructor() { | ||
| 18 | + } | ||
| 19 | + | ||
| 13 | 20 | ||
| 14 | reply() { | 21 | reply() { |
| 15 | - this.showReply = true; | 22 | + this.comment.__show_reply = !this.comment.__show_reply; |
| 16 | } | 23 | } |
| 17 | } | 24 | } |
src/app/article/comment/comment.html
| @@ -10,16 +10,12 @@ | @@ -10,16 +10,12 @@ | ||
| 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="date" am-time-ago="ctrl.comment.created_at | dateFormat"></span> | 12 | <span class="date" am-time-ago="ctrl.comment.created_at | dateFormat"></span> |
| 13 | - <a href="#" (click)="ctrl.reply()"> | ||
| 14 | - <span class="pull-right small text-muted"> | ||
| 15 | - {{"comment.reply" | translate}} | ||
| 16 | - </span> | ||
| 17 | - </a> | ||
| 18 | </div> | 13 | </div> |
| 19 | <div class="title">{{ctrl.comment.title}}</div> | 14 | <div class="title">{{ctrl.comment.title}}</div> |
| 20 | <div class="body">{{ctrl.comment.body}}</div> | 15 | <div class="body">{{ctrl.comment.body}}</div> |
| 16 | + <a href="#" (click)="ctrl.reply()" class="small text-muted"> | ||
| 17 | + {{"comment.reply" | translate}} | ||
| 18 | + </a> | ||
| 21 | </div> | 19 | </div> |
| 22 | - | ||
| 23 | - <noosfero-post-comment ng-if="ctrl.showReply" [article]="ctrl.article" [reply-of]="ctrl.comment"></noosfero-post-comment> | ||
| 24 | - | 20 | + <noosfero-comments [show-form]="ctrl.showReply()" [article]="ctrl.article" [parent]="ctrl.comment"></noosfero-comments> |
| 25 | </div> | 21 | </div> |
src/app/article/comment/comment.scss
| @@ -13,8 +13,7 @@ | @@ -13,8 +13,7 @@ | ||
| 13 | min-width: 40px; | 13 | min-width: 40px; |
| 14 | } | 14 | } |
| 15 | .media-body { | 15 | .media-body { |
| 16 | - background-color: #F9F9F9; | ||
| 17 | - padding: 10px; | 16 | + padding: 0 10px 10px 10px; |
| 18 | } | 17 | } |
| 19 | noosfero-profile-image { | 18 | noosfero-profile-image { |
| 20 | img { | 19 | img { |
| @@ -28,5 +27,8 @@ | @@ -28,5 +27,8 @@ | ||
| 28 | font-size: 1.7em; | 27 | font-size: 1.7em; |
| 29 | } | 28 | } |
| 30 | } | 29 | } |
| 30 | + .comments { | ||
| 31 | + margin-left: 30px; | ||
| 32 | + } | ||
| 31 | } | 33 | } |
| 32 | } | 34 | } |
src/app/article/comment/comments.component.spec.ts
| @@ -17,38 +17,71 @@ describe("Components", () => { | @@ -17,38 +17,71 @@ describe("Components", () => { | ||
| 17 | commentService.getByArticle = jasmine.createSpy("getByArticle") | 17 | commentService.getByArticle = jasmine.createSpy("getByArticle") |
| 18 | .and.returnValue(helpers.mocks.promiseResultTemplate({ data: comments })); | 18 | .and.returnValue(helpers.mocks.promiseResultTemplate({ data: comments })); |
| 19 | 19 | ||
| 20 | - let providers = [ | ||
| 21 | - new Provider('CommentService', { useValue: commentService }), | ||
| 22 | - new Provider('NotificationService', { useValue: helpers.mocks.notificationService }) | ||
| 23 | - ].concat(helpers.provideFilters("translateFilter")); | ||
| 24 | - | ||
| 25 | - @Component({ selector: 'test-container-component', directives: [CommentsComponent], template: htmlTemplate, providers: providers }) | ||
| 26 | - class ContainerComponent { | ||
| 27 | - article = { id: 1 }; | 20 | + let properties = { article: { id: 1 }, parent: <any>null }; |
| 21 | + function createComponent() { | ||
| 22 | + // postCommentEventService = jasmine.createSpyObj("postCommentEventService", ["subscribe"]); | ||
| 23 | + let providers = [ | ||
| 24 | + helpers.createProviderToValue('CommentService', commentService), | ||
| 25 | + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService), | ||
| 26 | + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})) | ||
| 27 | + ].concat(helpers.provideFilters("translateFilter")); | ||
| 28 | + | ||
| 29 | + return helpers.quickCreateComponent({ | ||
| 30 | + providers: providers, | ||
| 31 | + directives: [CommentsComponent], | ||
| 32 | + template: htmlTemplate, | ||
| 33 | + properties: properties | ||
| 34 | + }); | ||
| 28 | } | 35 | } |
| 29 | 36 | ||
| 37 | + | ||
| 30 | it("render comments associated to an article", done => { | 38 | it("render comments associated to an article", done => { |
| 31 | - helpers.createComponentFromClass(ContainerComponent).then(fixture => { | 39 | + createComponent().then(fixture => { |
| 32 | expect(fixture.debugElement.queryAll("noosfero-comment").length).toEqual(2); | 40 | expect(fixture.debugElement.queryAll("noosfero-comment").length).toEqual(2); |
| 33 | done(); | 41 | done(); |
| 34 | }); | 42 | }); |
| 35 | }); | 43 | }); |
| 36 | 44 | ||
| 37 | it("render a post comment tag", done => { | 45 | it("render a post comment tag", done => { |
| 38 | - helpers.createComponentFromClass(ContainerComponent).then(fixture => { | 46 | + createComponent().then(fixture => { |
| 39 | expect(fixture.debugElement.queryAll("noosfero-post-comment").length).toEqual(1); | 47 | expect(fixture.debugElement.queryAll("noosfero-post-comment").length).toEqual(1); |
| 40 | done(); | 48 | done(); |
| 41 | }); | 49 | }); |
| 42 | }); | 50 | }); |
| 43 | 51 | ||
| 44 | - it("update comments list when receive an event", done => { | ||
| 45 | - helpers.createComponentFromClass(ContainerComponent).then(fixture => { | ||
| 46 | - fixture.debugElement.getLocal("$rootScope").$emit(PostCommentComponent.EVENT_COMMENT_RECEIVED, { id: 1 }); | ||
| 47 | - fixture.debugElement.getLocal("$rootScope").$apply(); | 52 | + it("update comments list when receive an reply", done => { |
| 53 | + properties.parent = { id: 3 }; | ||
| 54 | + createComponent().then(fixture => { | ||
| 55 | + (<CommentsComponent>fixture.debugElement.componentViewChildren[0].componentInstance).commentAdded(<noosfero.Comment>{ id: 1, reply_of: { id: 3 } }); | ||
| 56 | + fixture.detectChanges(); | ||
| 48 | expect(fixture.debugElement.queryAll("noosfero-comment").length).toEqual(3); | 57 | expect(fixture.debugElement.queryAll("noosfero-comment").length).toEqual(3); |
| 49 | done(); | 58 | done(); |
| 50 | }); | 59 | }); |
| 51 | }); | 60 | }); |
| 52 | 61 | ||
| 62 | + it("load comments for next page", done => { | ||
| 63 | + createComponent().then(fixture => { | ||
| 64 | + let headers = jasmine.createSpy("headers").and.returnValue(3); | ||
| 65 | + commentService.getByArticle = jasmine.createSpy("getByArticle") | ||
| 66 | + .and.returnValue(helpers.mocks.promiseResultTemplate({ data: { id: 4 }, headers: headers })); | ||
| 67 | + let component: CommentsComponent = fixture.debugElement.componentViewChildren[0].componentInstance; | ||
| 68 | + component.loadNextPage(); | ||
| 69 | + expect(component['page']).toEqual(3); | ||
| 70 | + expect(component.comments.length).toEqual(3); | ||
| 71 | + expect(component['total']).toEqual(3); | ||
| 72 | + done(); | ||
| 73 | + }); | ||
| 74 | + }); | ||
| 75 | + | ||
| 76 | + it("not display more when there is no more comments to load", done => { | ||
| 77 | + createComponent().then(fixture => { | ||
| 78 | + let component: CommentsComponent = fixture.debugElement.componentViewChildren[0].componentInstance; | ||
| 79 | + component['total'] = 0; | ||
| 80 | + component.parent = null; | ||
| 81 | + expect(component.displayMore()).toBeFalsy(); | ||
| 82 | + done(); | ||
| 83 | + }); | ||
| 84 | + }); | ||
| 85 | + | ||
| 53 | }); | 86 | }); |
| 54 | }); | 87 | }); |
src/app/article/comment/comments.component.ts
| 1 | -import { Inject, Input, Component, provide } from 'ng-forward'; | 1 | +import { Inject, Input, Output, Component, provide, EventEmitter } from 'ng-forward'; |
| 2 | +import {INgForwardJQuery} from "ng-forward/cjs/util/jqlite-extensions"; | ||
| 3 | + | ||
| 4 | + | ||
| 2 | import { PostCommentComponent } from "./post-comment/post-comment.component"; | 5 | import { PostCommentComponent } from "./post-comment/post-comment.component"; |
| 3 | import { CommentService } from "../../../lib/ng-noosfero-api/http/comment.service"; | 6 | import { CommentService } from "../../../lib/ng-noosfero-api/http/comment.service"; |
| 4 | import { CommentComponent } from "./comment.component"; | 7 | import { CommentComponent } from "./comment.component"; |
| @@ -6,23 +9,59 @@ import { CommentComponent } from "./comment.component"; | @@ -6,23 +9,59 @@ import { CommentComponent } from "./comment.component"; | ||
| 6 | @Component({ | 9 | @Component({ |
| 7 | selector: 'noosfero-comments', | 10 | selector: 'noosfero-comments', |
| 8 | templateUrl: 'app/article/comment/comments.html', | 11 | templateUrl: 'app/article/comment/comments.html', |
| 9 | - directives: [PostCommentComponent, CommentComponent] | 12 | + directives: [PostCommentComponent, CommentComponent], |
| 13 | + outputs: ['commentAdded'] | ||
| 10 | }) | 14 | }) |
| 11 | -@Inject(CommentService, "$rootScope") | 15 | +@Inject(CommentService, "$element") |
| 12 | export class CommentsComponent { | 16 | export class CommentsComponent { |
| 13 | 17 | ||
| 14 | - comments: noosfero.Comment[] = []; | 18 | + comments: noosfero.CommentViewModel[] = []; |
| 19 | + @Input() showForm = true; | ||
| 15 | @Input() article: noosfero.Article; | 20 | @Input() article: noosfero.Article; |
| 21 | + @Input() parent: noosfero.CommentViewModel; | ||
| 22 | + | ||
| 23 | + protected page = 1; | ||
| 24 | + protected perPage = 5; | ||
| 25 | + protected total = 0; | ||
| 26 | + | ||
| 27 | + constructor(protected commentService: CommentService) { } | ||
| 28 | + | ||
| 29 | + ngOnInit() { | ||
| 30 | + if (this.parent) { | ||
| 31 | + this.comments = this.parent.replies; | ||
| 32 | + } else { | ||
| 33 | + this.loadNextPage(); | ||
| 34 | + } | ||
| 35 | + } | ||
| 16 | 36 | ||
| 17 | - constructor(private commentService: CommentService, private $rootScope: ng.IScope) { | ||
| 18 | - $rootScope.$on(PostCommentComponent.EVENT_COMMENT_RECEIVED, (event: ng.IAngularEvent, comment: noosfero.Comment) => { | ||
| 19 | - this.comments.push(comment); | 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; | ||
| 20 | }); | 45 | }); |
| 46 | + if (this.parent) { | ||
| 47 | + this.parent.__show_reply = false; | ||
| 48 | + } | ||
| 21 | } | 49 | } |
| 22 | 50 | ||
| 23 | - ngOnInit() { | ||
| 24 | - this.commentService.getByArticle(this.article).then((result: noosfero.RestResult<noosfero.Comment[]>) => { | ||
| 25 | - this.comments = result.data; | 51 | + loadComments() { |
| 52 | + return this.commentService.getByArticle(this.article, { page: this.page, per_page: this.perPage }); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + loadNextPage() { | ||
| 56 | + this.loadComments().then((result: noosfero.RestResult<noosfero.Comment[]>) => { | ||
| 57 | + this.comments = this.comments.concat(result.data); | ||
| 58 | + this.total = result.headers ? result.headers("total") : this.comments.length; | ||
| 59 | + this.page++; | ||
| 26 | }); | 60 | }); |
| 27 | } | 61 | } |
| 62 | + | ||
| 63 | + displayMore() { | ||
| 64 | + let pages = Math.ceil(this.total / this.perPage); | ||
| 65 | + return !this.parent && pages >= this.page; | ||
| 66 | + } | ||
| 28 | } | 67 | } |
src/app/article/comment/comments.html
| 1 | <div class="comments"> | 1 | <div class="comments"> |
| 2 | - <noosfero-post-comment [article]="ctrl.article"></noosfero-post-comment> | 2 | + <noosfero-post-comment (comment-saved)="ctrl.commentAdded($event.detail)" ng-if="ctrl.showForm" [article]="ctrl.article" [parent]="ctrl.parent"></noosfero-post-comment> |
| 3 | 3 | ||
| 4 | <div class="comments-list"> | 4 | <div class="comments-list"> |
| 5 | - <noosfero-comment ng-repeat="comment in ctrl.comments" [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> |
| 6 | </div> | 6 | </div> |
| 7 | + <button type="button" ng-if="ctrl.displayMore()" class="more-comments btn btn-default btn-block" ng-click="ctrl.loadNextPage()">{{"comment.pagination.more" | translate}}</button> | ||
| 7 | </div> | 8 | </div> |
src/app/article/comment/post-comment/post-comment.component.spec.ts
| 1 | import {Provider, provide, Component} from 'ng-forward'; | 1 | import {Provider, provide, Component} from 'ng-forward'; |
| 2 | import * as helpers from "../../../../spec/helpers"; | 2 | import * as helpers from "../../../../spec/helpers"; |
| 3 | - | ||
| 4 | import {PostCommentComponent} from './post-comment.component'; | 3 | import {PostCommentComponent} from './post-comment.component'; |
| 5 | 4 | ||
| 6 | const htmlTemplate: string = '<noosfero-post-comment [article]="ctrl.article" [reply-of]="ctrl.comment"></noosfero-post-comment>'; | 5 | const htmlTemplate: string = '<noosfero-post-comment [article]="ctrl.article" [reply-of]="ctrl.comment"></noosfero-post-comment>'; |
| @@ -11,9 +10,11 @@ describe("Components", () => { | @@ -11,9 +10,11 @@ describe("Components", () => { | ||
| 11 | beforeEach(angular.mock.module("templates")); | 10 | beforeEach(angular.mock.module("templates")); |
| 12 | 11 | ||
| 13 | let commentService = jasmine.createSpyObj("commentService", ["createInArticle"]); | 12 | let commentService = jasmine.createSpyObj("commentService", ["createInArticle"]); |
| 13 | + let user = {}; | ||
| 14 | let providers = [ | 14 | let providers = [ |
| 15 | new Provider('CommentService', { useValue: commentService }), | 15 | new Provider('CommentService', { useValue: commentService }), |
| 16 | - new Provider('NotificationService', { useValue: helpers.mocks.notificationService }) | 16 | + new Provider('NotificationService', { useValue: helpers.mocks.notificationService }), |
| 17 | + new Provider('SessionService', { useValue: helpers.mocks.sessionWithCurrentUser(user) }) | ||
| 17 | ].concat(helpers.provideFilters("translateFilter")); | 18 | ].concat(helpers.provideFilters("translateFilter")); |
| 18 | 19 | ||
| 19 | @Component({ selector: 'test-container-component', directives: [PostCommentComponent], template: htmlTemplate, providers: providers }) | 20 | @Component({ selector: 'test-container-component', directives: [PostCommentComponent], template: htmlTemplate, providers: providers }) |
| @@ -32,10 +33,10 @@ describe("Components", () => { | @@ -32,10 +33,10 @@ describe("Components", () => { | ||
| 32 | it("emit an event when create comment", done => { | 33 | it("emit an event when create comment", done => { |
| 33 | helpers.createComponentFromClass(ContainerComponent).then(fixture => { | 34 | helpers.createComponentFromClass(ContainerComponent).then(fixture => { |
| 34 | let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance; | 35 | let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance; |
| 36 | + component.commentSaved.next = jasmine.createSpy("next"); | ||
| 35 | commentService.createInArticle = jasmine.createSpy("createInArticle").and.returnValue(helpers.mocks.promiseResultTemplate({ data: {} })); | 37 | commentService.createInArticle = jasmine.createSpy("createInArticle").and.returnValue(helpers.mocks.promiseResultTemplate({ data: {} })); |
| 36 | - component["$rootScope"].$emit = jasmine.createSpy("$emit"); | ||
| 37 | component.save(); | 38 | component.save(); |
| 38 | - expect(component["$rootScope"].$emit).toHaveBeenCalledWith(PostCommentComponent.EVENT_COMMENT_RECEIVED, jasmine.any(Object)); | 39 | + expect(component.commentSaved.next).toHaveBeenCalled(); |
| 39 | done(); | 40 | done(); |
| 40 | }); | 41 | }); |
| 41 | }); | 42 | }); |
| @@ -55,9 +56,9 @@ describe("Components", () => { | @@ -55,9 +56,9 @@ describe("Components", () => { | ||
| 55 | helpers.createComponentFromClass(ContainerComponent).then(fixture => { | 56 | helpers.createComponentFromClass(ContainerComponent).then(fixture => { |
| 56 | let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance; | 57 | let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance; |
| 57 | component.comment = <any>{ reply_of_id: null }; | 58 | component.comment = <any>{ reply_of_id: null }; |
| 58 | - component.replyOf = <any>{ id: 10 }; | 59 | + component.parent = <any>{ id: 10 }; |
| 59 | component.save(); | 60 | component.save(); |
| 60 | - expect(component.comment.reply_of_id).toEqual(component.replyOf.id); | 61 | + expect(component.comment.reply_of_id).toEqual(component.parent.id); |
| 61 | done(); | 62 | done(); |
| 62 | }); | 63 | }); |
| 63 | }); | 64 | }); |
src/app/article/comment/post-comment/post-comment.component.ts
| 1 | -import { Inject, Input, Component } from 'ng-forward'; | 1 | +import { Inject, Input, Output, EventEmitter, 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 | 5 | ||
| 5 | @Component({ | 6 | @Component({ |
| 6 | selector: 'noosfero-post-comment', | 7 | selector: 'noosfero-post-comment', |
| 7 | - templateUrl: 'app/article/comment/post-comment/post-comment.html' | 8 | + templateUrl: 'app/article/comment/post-comment/post-comment.html', |
| 9 | + outputs: ['commentSaved'] | ||
| 8 | }) | 10 | }) |
| 9 | -@Inject(CommentService, NotificationService, "$rootScope") | 11 | +@Inject(CommentService, NotificationService, SessionService) |
| 10 | export class PostCommentComponent { | 12 | export class PostCommentComponent { |
| 11 | 13 | ||
| 12 | - public static EVENT_COMMENT_RECEIVED = "comment.received"; | ||
| 13 | - | ||
| 14 | @Input() article: noosfero.Article; | 14 | @Input() article: noosfero.Article; |
| 15 | - @Input() replyOf: noosfero.Comment; | 15 | + @Input() parent: noosfero.Comment; |
| 16 | + @Output() commentSaved: EventEmitter<Comment> = new EventEmitter<Comment>(); | ||
| 16 | 17 | ||
| 17 | - comment: noosfero.Comment; | 18 | + comment = <noosfero.Comment>{}; |
| 19 | + private currentUser: noosfero.User; | ||
| 18 | 20 | ||
| 19 | - constructor(private commentService: CommentService, private notificationService: NotificationService, private $rootScope: ng.IScope) { } | 21 | + constructor(private commentService: CommentService, |
| 22 | + private notificationService: NotificationService, | ||
| 23 | + private session: SessionService) { | ||
| 24 | + this.currentUser = this.session.currentUser(); | ||
| 25 | + } | ||
| 20 | 26 | ||
| 21 | save() { | 27 | save() { |
| 22 | - if (this.replyOf && this.comment) { | ||
| 23 | - this.comment.reply_of_id = this.replyOf.id; | 28 | + if (this.parent && this.comment) { |
| 29 | + this.comment.reply_of_id = this.parent.id; | ||
| 24 | } | 30 | } |
| 25 | this.commentService.createInArticle(this.article, this.comment).then((result: noosfero.RestResult<noosfero.Comment>) => { | 31 | this.commentService.createInArticle(this.article, this.comment).then((result: noosfero.RestResult<noosfero.Comment>) => { |
| 26 | - this.$rootScope.$emit(PostCommentComponent.EVENT_COMMENT_RECEIVED, result.data); | ||
| 27 | - this.notificationService.success({ title: "Good job!", message: "Comment saved!" }); | 32 | + this.commentSaved.next(result.data); |
| 33 | + this.comment.body = ""; | ||
| 34 | + this.notificationService.success({ title: "comment.post.success.title", message: "comment.post.success.message" }); | ||
| 28 | }); | 35 | }); |
| 29 | } | 36 | } |
| 30 | } | 37 | } |
src/app/article/comment/post-comment/post-comment.html
| 1 | -<form> | 1 | +<form class="clearfix post-comment"> |
| 2 | <div class="form-group"> | 2 | <div class="form-group"> |
| 3 | - <textarea class="form-control custom-control" rows="3" ng-model="ctrl.comment.body"></textarea> | 3 | + <div class="comment media"> |
| 4 | + <div class="media-left"> | ||
| 5 | + <a ui-sref="main.profile.home({profile: ctrl.currentUser.person.identifier})"> | ||
| 6 | + <noosfero-profile-image [profile]="ctrl.currentUser.person"></noosfero-profile-image> | ||
| 7 | + </a> | ||
| 8 | + </div> | ||
| 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> | ||
| 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 | + </div> | ||
| 13 | + </div> | ||
| 4 | </div> | 14 | </div> |
| 5 | - <button type="submit" class="btn btn-default" ng-click="ctrl.save()">{{"comment.post" | translate}}</button> | ||
| 6 | </form> | 15 | </form> |
| @@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
| 1 | +.comments { | ||
| 2 | + .post-comment { | ||
| 3 | + .media { | ||
| 4 | + border-top: 2px solid #F3F3F3; | ||
| 5 | + padding-top: 10px; | ||
| 6 | + .media-left { | ||
| 7 | + padding: 10px 0; | ||
| 8 | + } | ||
| 9 | + button { | ||
| 10 | + margin-top: 10px; | ||
| 11 | + &.ng-hide-add { | ||
| 12 | + animation: 0.5s lightSpeedOut ease; | ||
| 13 | + } | ||
| 14 | + &.ng-hide-remove { | ||
| 15 | + animation: 0.5s lightSpeedIn ease; | ||
| 16 | + } | ||
| 17 | + } | ||
| 18 | + } | ||
| 19 | + } | ||
| 20 | +} |
src/app/shared/services/notification.service.ts
| @@ -20,13 +20,7 @@ export class NotificationService { | @@ -20,13 +20,7 @@ export class NotificationService { | ||
| 20 | title = NotificationService.DEFAULT_ERROR_TITLE, | 20 | title = NotificationService.DEFAULT_ERROR_TITLE, |
| 21 | showConfirmButton = true | 21 | showConfirmButton = true |
| 22 | } = {}) { | 22 | } = {}) { |
| 23 | - this.$log.debug("Notification error:", title, message, this.translatorService.currentLanguage()); | ||
| 24 | - this.SweetAlert.swal({ | ||
| 25 | - title: this.translatorService.translate(title), | ||
| 26 | - text: this.translatorService.translate(message), | ||
| 27 | - type: "error", | ||
| 28 | - showConfirmButton: showConfirmButton | ||
| 29 | - }); | 23 | + this.showMessage({ title: title, text: message, showConfirmButton: showConfirmButton, type: "error" }); |
| 30 | } | 24 | } |
| 31 | 25 | ||
| 32 | httpError(status: number, data: any): boolean { | 26 | httpError(status: number, data: any): boolean { |
| @@ -39,11 +33,17 @@ export class NotificationService { | @@ -39,11 +33,17 @@ export class NotificationService { | ||
| 39 | message, | 33 | message, |
| 40 | timer = NotificationService.DEFAULT_SUCCESS_TIMER | 34 | timer = NotificationService.DEFAULT_SUCCESS_TIMER |
| 41 | }) { | 35 | }) { |
| 36 | + this.showMessage({ title: title, text: message, timer: timer }); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + private showMessage({title, text, type = "success", timer = null, showConfirmButton = true}) { | ||
| 40 | + this.$log.debug("Notification message:", title, text, type, this.translatorService.currentLanguage()); | ||
| 42 | this.SweetAlert.swal({ | 41 | this.SweetAlert.swal({ |
| 43 | - title: title, | ||
| 44 | - text: message, | ||
| 45 | - type: "success", | ||
| 46 | - timer: timer | 42 | + title: this.translatorService.translate(title), |
| 43 | + text: this.translatorService.translate(text), | ||
| 44 | + type: type, | ||
| 45 | + timer: timer, | ||
| 46 | + showConfirmButton: showConfirmButton | ||
| 47 | }); | 47 | }); |
| 48 | } | 48 | } |
| 49 | 49 |
src/languages/en.json
| @@ -31,5 +31,9 @@ | @@ -31,5 +31,9 @@ | ||
| 31 | "notification.http_error.401.message": "Unauthorized", | 31 | "notification.http_error.401.message": "Unauthorized", |
| 32 | "notification.http_error.500.message": "Server error", | 32 | "notification.http_error.500.message": "Server error", |
| 33 | "comment.post": "Post a comment", | 33 | "comment.post": "Post a comment", |
| 34 | + "comment.post.placeholder": "Join the discussion...", | ||
| 35 | + "comment.pagination.more": "More", | ||
| 36 | + "comment.post.success.title": "Good job!", | ||
| 37 | + "comment.post.success.message": "Comment saved!", | ||
| 34 | "comment.reply": "reply" | 38 | "comment.reply": "reply" |
| 35 | } | 39 | } |
src/languages/pt.json
| @@ -31,5 +31,9 @@ | @@ -31,5 +31,9 @@ | ||
| 31 | "notification.http_error.401.message": "Não autorizado", | 31 | "notification.http_error.401.message": "Não autorizado", |
| 32 | "notification.http_error.500.message": "Erro no servidor", | 32 | "notification.http_error.500.message": "Erro no servidor", |
| 33 | "comment.post": "Commentar", | 33 | "comment.post": "Commentar", |
| 34 | + "comment.post.placeholder": "Participe da discussão...", | ||
| 35 | + "comment.pagination.more": "Mais", | ||
| 36 | + "comment.post.success.title": "Bom trabalho!", | ||
| 37 | + "comment.post.success.message": "Comentário salvo com sucesso!", | ||
| 34 | "comment.reply": "responder" | 38 | "comment.reply": "responder" |
| 35 | } | 39 | } |
src/lib/ng-noosfero-api/http/comment.service.spec.ts
| @@ -22,8 +22,8 @@ describe("Services", () => { | @@ -22,8 +22,8 @@ describe("Services", () => { | ||
| 22 | 22 | ||
| 23 | it("should return comments by article", (done) => { | 23 | it("should return comments by article", (done) => { |
| 24 | let articleId = 1; | 24 | let articleId = 1; |
| 25 | - $httpBackend.expectGET(`/api/v1/articles/${articleId}/comments`).respond(200, { comments: [{ name: "comment1" }] }); | ||
| 26 | - commentService.getByArticle(<noosfero.Article>{id: articleId}).then((result: noosfero.RestResult<noosfero.Comment[]>) => { | 25 | + $httpBackend.expectGET(`/api/v1/articles/${articleId}/comments?without_reply=true`).respond(200, { comments: [{ name: "comment1" }] }); |
| 26 | + commentService.getByArticle(<noosfero.Article>{ id: articleId }).then((result: noosfero.RestResult<noosfero.Comment[]>) => { | ||
| 27 | expect(result.data).toEqual([{ name: "comment1" }]); | 27 | expect(result.data).toEqual([{ name: "comment1" }]); |
| 28 | done(); | 28 | done(); |
| 29 | }); | 29 | }); |
| @@ -32,9 +32,9 @@ describe("Services", () => { | @@ -32,9 +32,9 @@ describe("Services", () => { | ||
| 32 | 32 | ||
| 33 | it("should create a comment in an article", (done) => { | 33 | it("should create a comment in an article", (done) => { |
| 34 | let articleId = 1; | 34 | let articleId = 1; |
| 35 | - let comment: noosfero.Comment = <any>{ id: null}; | ||
| 36 | - $httpBackend.expectPOST(`/api/v1/articles/${articleId}/comments`, comment ).respond(200, {comment: { id: 2 }}); | ||
| 37 | - commentService.createInArticle(<noosfero.Article>{id: articleId}, comment).then((result: noosfero.RestResult<noosfero.Comment>) => { | 35 | + let comment: noosfero.Comment = <any>{ id: null }; |
| 36 | + $httpBackend.expectPOST(`/api/v1/articles/${articleId}/comments`, comment).respond(200, { comment: { id: 2 } }); | ||
| 37 | + commentService.createInArticle(<noosfero.Article>{ id: articleId }, comment).then((result: noosfero.RestResult<noosfero.Comment>) => { | ||
| 38 | expect(result.data).toEqual({ id: 2 }); | 38 | expect(result.data).toEqual({ id: 2 }); |
| 39 | done(); | 39 | done(); |
| 40 | }); | 40 | }); |
src/lib/ng-noosfero-api/http/comment.service.ts
| @@ -21,9 +21,10 @@ export class CommentService extends RestangularService<noosfero.Comment> { | @@ -21,9 +21,10 @@ export class CommentService extends RestangularService<noosfero.Comment> { | ||
| 21 | }; | 21 | }; |
| 22 | } | 22 | } |
| 23 | 23 | ||
| 24 | - getByArticle(article: noosfero.Article, params?: any): ng.IPromise<noosfero.RestResult<noosfero.Comment[]>> { | 24 | + getByArticle(article: noosfero.Article, params: any = {}): ng.IPromise<noosfero.RestResult<noosfero.Comment[]>> { |
| 25 | + params['without_reply'] = true; | ||
| 25 | let articleElement = this.articleService.getElement(<number>article.id); | 26 | let articleElement = this.articleService.getElement(<number>article.id); |
| 26 | - return this.list(articleElement); | 27 | + return this.list(articleElement, params); |
| 27 | } | 28 | } |
| 28 | 29 | ||
| 29 | createInArticle(article: noosfero.Article, comment: noosfero.Comment): ng.IPromise<noosfero.RestResult<noosfero.Comment>> { | 30 | createInArticle(article: noosfero.Article, comment: noosfero.Comment): ng.IPromise<noosfero.RestResult<noosfero.Comment>> { |
src/lib/ng-noosfero-api/interfaces/comment.ts
| 1 | namespace noosfero { | 1 | namespace noosfero { |
| 2 | export interface Comment extends RestModel { | 2 | export interface Comment extends RestModel { |
| 3 | reply_of_id: number; | 3 | reply_of_id: number; |
| 4 | + reply_of: Comment; | ||
| 5 | + replies: Comment[]; | ||
| 6 | + body: string; | ||
| 7 | + } | ||
| 8 | + | ||
| 9 | + export interface CommentViewModel extends Comment { | ||
| 10 | + __show_reply?: boolean; | ||
| 4 | } | 11 | } |
| 5 | } | 12 | } |