Merge Request #4

Merged
noosfero-themes/angular-theme!4
Created by Victor Costa

Add comments to articles

Closes #14

Assignee: None
Milestone: 2016.04

Merged by Ábner Oliveira

Source branch has been removed
Commits (10)
2 participants
src/app/article/article-default-view-component.spec.ts
... ... @@ -27,7 +27,8 @@ describe("Components", () => {
27 27 providers: [
28 28 helpers.createProviderToValue('CommentService', helpers.mocks.commentService),
29 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 34 class ArticleContainerComponent {
... ... @@ -64,7 +65,8 @@ describe("Components", () => {
64 65 providers: [
65 66 helpers.createProviderToValue('CommentService', helpers.mocks.commentService),
66 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 72 class ArticleContainerComponent {
... ...
src/app/article/comment/comment.component.spec.ts
... ... @@ -2,6 +2,7 @@ import {Provider, provide, Component} from 'ng-forward';
2 2 import * as helpers from "../../../spec/helpers";
3 3  
4 4 import {CommentComponent} from './comment.component';
  5 +import {PostCommentComponent} from './post-comment/post-comment.component';
5 6  
6 7 const htmlTemplate: string = '<noosfero-comment [article]="ctrl.article" [comment]="ctrl.comment"></noosfero-comment>';
7 8  
... ... @@ -10,35 +11,56 @@ describe(&quot;Components&quot;, () =&gt; {
10 11  
11 12 beforeEach(angular.mock.module("templates"));
12 13  
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" };
  14 + let postCommentEventService = jasmine.createSpyObj("postCommentEventService", ["subscribe"]);
  15 +
  16 + function createComponent() {
  17 + let providers = [
  18 + new Provider('PostCommentEventService', { useValue: postCommentEventService })
  19 + ].concat(helpers.provideFilters("translateFilter"));
  20 +
  21 + @Component({ selector: 'test-container-component', directives: [CommentComponent], template: htmlTemplate, providers: providers })
  22 + class ContainerComponent {
  23 + article = { id: 1 };
  24 + comment = { title: "title", body: "body" };
  25 + }
  26 + return helpers.createComponentFromClass(ContainerComponent);
17 27 }
18 28  
19 29 it("render a comment", done => {
20   - helpers.createComponentFromClass(ContainerComponent).then(fixture => {
  30 + createComponent().then(fixture => {
21 31 expect(fixture.debugElement.queryAll(".comment").length).toEqual(1);
22 32 done();
23 33 });
24 34 });
25 35  
26 36 it("not render a post comment tag in the beginning", done => {
27   - helpers.createComponentFromClass(ContainerComponent).then(fixture => {
  37 + createComponent().then(fixture => {
28 38 expect(fixture.debugElement.queryAll("noosfero-post-comment").length).toEqual(0);
29 39 done();
30 40 });
31 41 });
32 42  
33   - it("render a post comment tag when click in reply", done => {
34   - helpers.createComponentFromClass(ContainerComponent).then(fixture => {
  43 + it("set show reply to true when click reply", done => {
  44 + createComponent().then(fixture => {
35 45 let component: CommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
36 46 component.reply();
37   - fixture.debugElement.getLocal("$rootScope").$apply();
38   - expect(fixture.debugElement.queryAll("noosfero-post-comment").length).toEqual(1);
  47 + expect(component.showReply).toBeTruthy(1);
39 48 done();
40 49 });
41 50 });
42 51  
  52 + it("close form when receive a reply", done => {
  53 + let func: Function;
  54 + postCommentEventService.subscribe = (fn: Function) => {
  55 + func = fn;
  56 + };
  57 + createComponent().then(fixture => {
  58 + let component = fixture.debugElement.componentViewChildren[0];
  59 + component.componentInstance.showReply = true;
  60 + func(<noosfero.Comment>{});
  61 + expect(component.componentInstance.showReply).toEqual(false);
  62 + done();
  63 + });
  64 + });
43 65 });
44 66 });
... ...
src/app/article/comment/comment.component.ts
1   -import { Input, Component } from 'ng-forward';
  1 +import { Inject, Input, Component } from 'ng-forward';
  2 +import { PostCommentComponent } from "./post-comment/post-comment.component";
  3 +import { PostCommentEventService } from "./post-comment/post-comment-event.service";
2 4  
3 5 @Component({
4 6 selector: 'noosfero-comment',
5 7 templateUrl: 'app/article/comment/comment.html'
6 8 })
  9 +@Inject(PostCommentEventService, "$scope")
7 10 export class CommentComponent {
8 11  
9 12 @Input() comment: noosfero.Comment;
... ... @@ -11,7 +14,14 @@ export class CommentComponent {
11 14  
12 15 showReply: boolean = false;
13 16  
  17 + constructor(postCommentEventService: PostCommentEventService, private $scope: ng.IScope) {
  18 + postCommentEventService.subscribe((comment: noosfero.Comment) => {
  19 + this.showReply = false;
  20 + this.$scope.$apply();
  21 + });
  22 + }
  23 +
14 24 reply() {
15   - this.showReply = true;
  25 + this.showReply = !this.showReply;
16 26 }
17 27 }
... ...
src/app/article/comment/comment.html
... ... @@ -10,16 +10,12 @@
10 10 <h4 class="media-heading">{{ctrl.comment.author.name}}</h4>
11 11 </a>
12 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 13 </div>
19 14 <div class="title">{{ctrl.comment.title}}</div>
20 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 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 21 </div>
... ...
src/app/article/comment/comment.scss
... ... @@ -13,8 +13,7 @@
13 13 min-width: 40px;
14 14 }
15 15 .media-body {
16   - background-color: #F9F9F9;
17   - padding: 10px;
  16 + padding: 0 10px 10px 10px;
18 17 }
19 18 noosfero-profile-image {
20 19 img {
... ... @@ -28,5 +27,8 @@
28 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,78 @@ describe(&quot;Components&quot;, () =&gt; {
17 17 commentService.getByArticle = jasmine.createSpy("getByArticle")
18 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 emitEvent: Function;
  21 + let postCommentEventService = {
  22 + subscribe: (fn: Function) => {
  23 + emitEvent = fn;
  24 + }
  25 + };
  26 +
  27 + let properties = { article: { id: 1 }, parent: <any>null };
  28 + function createComponent() {
  29 + // postCommentEventService = jasmine.createSpyObj("postCommentEventService", ["subscribe"]);
  30 + let providers = [
  31 + helpers.createProviderToValue('CommentService', commentService),
  32 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService),
  33 + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})),
  34 + new Provider('PostCommentEventService', { useValue: postCommentEventService })
  35 + ].concat(helpers.provideFilters("translateFilter"));
  36 +
  37 + return helpers.quickCreateComponent({
  38 + providers: providers,
  39 + directives: [CommentsComponent],
  40 + template: htmlTemplate,
  41 + properties: properties
  42 + });
28 43 }
29 44  
  45 +
30 46 it("render comments associated to an article", done => {
31   - helpers.createComponentFromClass(ContainerComponent).then(fixture => {
  47 + createComponent().then(fixture => {
32 48 expect(fixture.debugElement.queryAll("noosfero-comment").length).toEqual(2);
33 49 done();
34 50 });
35 51 });
36 52  
37 53 it("render a post comment tag", done => {
38   - helpers.createComponentFromClass(ContainerComponent).then(fixture => {
  54 + createComponent().then(fixture => {
39 55 expect(fixture.debugElement.queryAll("noosfero-post-comment").length).toEqual(1);
40 56 done();
41 57 });
42 58 });
43 59  
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();
  60 + it("update comments list when receive an reply", done => {
  61 + properties.parent = { id: 3 };
  62 + createComponent().then(fixture => {
  63 + emitEvent(<noosfero.Comment>{ id: 1, reply_of: { id: 3 } });
48 64 expect(fixture.debugElement.queryAll("noosfero-comment").length).toEqual(3);
49 65 done();
50 66 });
51 67 });
52 68  
  69 + it("load comments for next page", done => {
  70 + createComponent().then(fixture => {
  71 + let headers = jasmine.createSpy("headers").and.returnValue(3);
  72 + commentService.getByArticle = jasmine.createSpy("getByArticle")
  73 + .and.returnValue(helpers.mocks.promiseResultTemplate({ data: { id: 4 }, headers: headers }));
  74 + let component: CommentsComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  75 + component.loadNextPage();
  76 + expect(component['page']).toEqual(3);
  77 + expect(component.comments.length).toEqual(3);
  78 + expect(component['total']).toEqual(3);
  79 + done();
  80 + });
  81 + });
  82 +
  83 + it("not display more when there is no more comments to load", done => {
  84 + createComponent().then(fixture => {
  85 + let component: CommentsComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  86 + component['total'] = 0;
  87 + component.parent = null;
  88 + expect(component.displayMore()).toBeFalsy();
  89 + done();
  90 + });
  91 + });
  92 +
53 93 });
54 94 });
... ...
src/app/article/comment/comments.component.ts
... ... @@ -2,27 +2,55 @@ import { Inject, Input, Component, provide } from &#39;ng-forward&#39;;
2 2 import { PostCommentComponent } from "./post-comment/post-comment.component";
3 3 import { CommentService } from "../../../lib/ng-noosfero-api/http/comment.service";
4 4 import { CommentComponent } from "./comment.component";
  5 +import { PostCommentEventService } from "./post-comment/post-comment-event.service";
5 6  
6 7 @Component({
7 8 selector: 'noosfero-comments',
8 9 templateUrl: 'app/article/comment/comments.html',
9 10 directives: [PostCommentComponent, CommentComponent]
10 11 })
11   -@Inject(CommentService, "$rootScope")
  12 +@Inject(CommentService, PostCommentEventService, "$scope")
12 13 export class CommentsComponent {
13 14  
14 15 comments: noosfero.Comment[] = [];
  16 + @Input() showForm = true;
15 17 @Input() article: noosfero.Article;
  18 + @Input() parent: noosfero.Comment;
  19 + protected page = 1;
  20 + protected perPage = 5;
  21 + protected total = 0;
16 22  
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);
  23 + constructor(protected commentService: CommentService, private postCommentEventService: PostCommentEventService, private $scope: ng.IScope) { }
  24 +
  25 + ngOnInit() {
  26 + if (this.parent) {
  27 + this.comments = this.parent.replies;
  28 + } else {
  29 + this.loadNextPage();
  30 + }
  31 + this.postCommentEventService.subscribe((comment: noosfero.Comment) => {
  32 + if ((!this.parent && !comment.reply_of) || (comment.reply_of && this.parent && comment.reply_of.id === this.parent.id)) {
  33 + if (!this.comments) this.comments = [];
  34 + this.comments.push(comment);
  35 + this.$scope.$apply();
  36 + }
20 37 });
21 38 }
22 39  
23   - ngOnInit() {
24   - this.commentService.getByArticle(this.article).then((result: noosfero.RestResult<noosfero.Comment[]>) => {
25   - this.comments = result.data;
  40 + loadComments() {
  41 + return this.commentService.getByArticle(this.article, { page: this.page, per_page: this.perPage });
  42 + }
  43 +
  44 + loadNextPage() {
  45 + this.loadComments().then((result: noosfero.RestResult<noosfero.Comment[]>) => {
  46 + this.comments = this.comments.concat(result.data);
  47 + this.total = result.headers ? result.headers("total") : this.comments.length;
  48 + this.page++;
26 49 });
27 50 }
  51 +
  52 + displayMore() {
  53 + let pages = Math.ceil(this.total / this.perPage);
  54 + return !this.parent && pages >= this.page;
  55 + }
28 56 }
... ...
src/app/article/comment/comments.html
1 1 <div class="comments">
2   - <noosfero-post-comment [article]="ctrl.article"></noosfero-post-comment>
  2 + <noosfero-post-comment ng-if="ctrl.showForm" [article]="ctrl.article" [parent]="ctrl.parent"></noosfero-post-comment>
3 3  
4 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 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>
2
7 8 </div>
... ...
src/app/article/comment/post-comment/post-comment-event.service.spec.ts 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +import {PostCommentEventService} from "./post-comment-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: PostCommentEventService;
  10 +
  11 + beforeEach(() => {
  12 + eventService = new PostCommentEventService();
  13 + eventService['eventEmitter'] = jasmine.createSpyObj("eventEmitter", ["next", "subscribe"]);
  14 + });
  15 +
  16 + it('subscribe to post comment event', () => {
  17 + eventService.subscribe(() => { });
  18 + expect(eventService['eventEmitter'].subscribe).toHaveBeenCalled();
  19 + });
  20 +
  21 + it('emit event when post comment', () => {
  22 + eventService.emit(<noosfero.Comment>{});
  23 + expect(eventService['eventEmitter'].next).toHaveBeenCalled();
  24 + });
  25 + });
  26 +});
... ...
src/app/article/comment/post-comment/post-comment-event.service.ts 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +import {Injectable, EventEmitter} from "ng-forward";
  2 +
  3 +@Injectable()
  4 +export class PostCommentEventService {
  5 +
  6 + private eventEmitter: EventEmitter<noosfero.Comment>;
  7 +
  8 + constructor() {
  9 + this.eventEmitter = new EventEmitter();
  10 + }
  11 +
  12 + emit(comment: noosfero.Comment) {
  13 + this.eventEmitter.next(comment);
  14 + }
  15 +
  16 + subscribe(fn: (comment: noosfero.Comment) => void) {
  17 + this.eventEmitter.subscribe(fn);
  18 + }
  19 +}
... ...
src/app/article/comment/post-comment/post-comment.component.spec.ts
... ... @@ -11,9 +11,13 @@ describe(&quot;Components&quot;, () =&gt; {
11 11 beforeEach(angular.mock.module("templates"));
12 12  
13 13 let commentService = jasmine.createSpyObj("commentService", ["createInArticle"]);
  14 + let postCommentEventService = jasmine.createSpyObj("postCommentEventService", ["emit"]);
  15 + let user = {};
14 16 let providers = [
15 17 new Provider('CommentService', { useValue: commentService }),
16   - new Provider('NotificationService', { useValue: helpers.mocks.notificationService })
  18 + new Provider('NotificationService', { useValue: helpers.mocks.notificationService }),
  19 + new Provider('SessionService', { useValue: helpers.mocks.sessionWithCurrentUser(user) }),
  20 + new Provider('PostCommentEventService', { useValue: postCommentEventService })
17 21 ].concat(helpers.provideFilters("translateFilter"));
18 22  
19 23 @Component({ selector: 'test-container-component', directives: [PostCommentComponent], template: htmlTemplate, providers: providers })
... ... @@ -33,9 +37,8 @@ describe(&quot;Components&quot;, () =&gt; {
33 37 helpers.createComponentFromClass(ContainerComponent).then(fixture => {
34 38 let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
35 39 commentService.createInArticle = jasmine.createSpy("createInArticle").and.returnValue(helpers.mocks.promiseResultTemplate({ data: {} }));
36   - component["$rootScope"].$emit = jasmine.createSpy("$emit");
37 40 component.save();
38   - expect(component["$rootScope"].$emit).toHaveBeenCalledWith(PostCommentComponent.EVENT_COMMENT_RECEIVED, jasmine.any(Object));
  41 + expect(postCommentEventService.emit).toHaveBeenCalled();
39 42 done();
40 43 });
41 44 });
... ... @@ -55,9 +58,9 @@ describe(&quot;Components&quot;, () =&gt; {
55 58 helpers.createComponentFromClass(ContainerComponent).then(fixture => {
56 59 let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
57 60 component.comment = <any>{ reply_of_id: null };
58   - component.replyOf = <any>{ id: 10 };
  61 + component.parent = <any>{ id: 10 };
59 62 component.save();
60   - expect(component.comment.reply_of_id).toEqual(component.replyOf.id);
  63 + expect(component.comment.reply_of_id).toEqual(component.parent.id);
61 64 done();
62 65 });
63 66 });
... ...
src/app/article/comment/post-comment/post-comment.component.ts
1 1 import { Inject, Input, Component } from 'ng-forward';
2 2 import { CommentService } from "../../../../lib/ng-noosfero-api/http/comment.service";
3 3 import { NotificationService } from "../../../shared/services/notification.service";
  4 +import { SessionService } from "../../../login";
  5 +import { PostCommentEventService } from "./post-comment-event.service";
4 6  
5 7 @Component({
6 8 selector: 'noosfero-post-comment',
7 9 templateUrl: 'app/article/comment/post-comment/post-comment.html'
8 10 })
9   -@Inject(CommentService, NotificationService, "$rootScope")
  11 +@Inject(CommentService, NotificationService, SessionService, PostCommentEventService)
10 12 export class PostCommentComponent {
11 13  
12 14 public static EVENT_COMMENT_RECEIVED = "comment.received";
13 15  
14 16 @Input() article: noosfero.Article;
15   - @Input() replyOf: noosfero.Comment;
  17 + @Input() parent: noosfero.Comment;
16 18  
17   - comment: noosfero.Comment;
  19 + comment = <noosfero.Comment>{};
  20 + private currentUser: noosfero.User;
18 21  
19   - constructor(private commentService: CommentService, private notificationService: NotificationService, private $rootScope: ng.IScope) { }
  22 + constructor(private commentService: CommentService,
  23 + private notificationService: NotificationService,
  24 + private session: SessionService,
  25 + private postCommentEventService: PostCommentEventService) {
  26 + this.currentUser = this.session.currentUser();
  27 + }
20 28  
21 29 save() {
22   - if (this.replyOf && this.comment) {
23   - this.comment.reply_of_id = this.replyOf.id;
  30 + if (this.parent && this.comment) {
  31 + this.comment.reply_of_id = this.parent.id;
24 32 }
25 33 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!" });
  34 + this.postCommentEventService.emit(result.data);
  35 + this.comment.body = "";
  36 + this.notificationService.success({ title: "comment.post.success.title", message: "comment.post.success.message" });
28 37 });
29 38 }
30 39 }
... ...
src/app/article/comment/post-comment/post-comment.html
1   -<form>
  1 +<form class="clearfix post-comment">
2 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 14 </div>
5   - <button type="submit" class="btn btn-default" ng-click="ctrl.save()">{{"comment.post" | translate}}</button>
6 15 </form>
... ...
src/app/article/comment/post-comment/post-comment.scss 0 → 100644
... ... @@ -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 {
1
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira @abner

    I liked the animation here. But maybe would be better provide this animation at the default theme instead. Would be hard to override this on themes.

    Choose File ...   File name...
    Cancel
  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 20 title = NotificationService.DEFAULT_ERROR_TITLE,
21 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 26 httpError(status: number, data: any): boolean {
... ... @@ -39,11 +33,17 @@ export class NotificationService {
39 33 message,
40 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 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 31 "notification.http_error.401.message": "Unauthorized",
32 32 "notification.http_error.500.message": "Server error",
33 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 38 "comment.reply": "reply"
35 39 }
... ...
src/languages/pt.json
... ... @@ -31,5 +31,9 @@
31 31 "notification.http_error.401.message": "Não autorizado",
32 32 "notification.http_error.500.message": "Erro no servidor",
33 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 38 "comment.reply": "responder"
35 39 }
... ...
src/lib/ng-noosfero-api/http/comment.service.spec.ts
... ... @@ -22,8 +22,8 @@ describe(&quot;Services&quot;, () =&gt; {
22 22  
23 23 it("should return comments by article", (done) => {
24 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 27 expect(result.data).toEqual([{ name: "comment1" }]);
28 28 done();
29 29 });
... ... @@ -32,9 +32,9 @@ describe(&quot;Services&quot;, () =&gt; {
32 32  
33 33 it("should create a comment in an article", (done) => {
34 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 38 expect(result.data).toEqual({ id: 2 });
39 39 done();
40 40 });
... ...
src/lib/ng-noosfero-api/http/comment.service.ts
... ... @@ -21,9 +21,10 @@ export class CommentService extends RestangularService&lt;noosfero.Comment&gt; {
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 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 30 createInArticle(article: noosfero.Article, comment: noosfero.Comment): ng.IPromise<noosfero.RestResult<noosfero.Comment>> {
... ...
src/lib/ng-noosfero-api/interfaces/comment.ts
1 1 namespace noosfero {
2 2 export interface Comment extends RestModel {
3 3 reply_of_id: number;
  4 + reply_of: Comment;
  5 + replies: Comment[];
  6 + body: string;
4 7 }
5 8 }
... ...
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Milestone changed to 2016.04

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 1 new commit:

    • 8202814b - Refactor comments component to permit extensions
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 1 new commit:

    • 06a2fc8d - Translate success message of post comment
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 10 new commits:

    • 556724ef - Fixed home link
    • f0b9c7a0 - Display replies of comments
    • 8b4048cc - Improve layout of post comment component
    • 20a7654b - Toggle reply form when clicked from comment component
    • 734399ac - Sort comments by creation date
    • b4c8800b - Fix layout of comments replies
    • cb5558ce - Add pagination button for comment listing
    • 3e93892b - Hide more comments button when there is no more pages to load
    • de7418b4 - Refactor comments component to permit extensions
    • 95a63439 - Translate success message of post comment
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 13 new commits:

    • efa64a13 - Fix helpers.ts type problems and added conditional to show profile custom-fields table
    • 6d10324a - People block refactoring. Created component-test-helper
    • 458f2ccd - Review Modifications
    • 7bab2b41 - Merge branch 'people-block' into 'master'
    • 986f9d29 - Display replies of comments
    • 89d0707b - Improve layout of post comment component
    • 50ef7e70 - Toggle reply form when clicked from comment component
    • d78752ef - Sort comments by creation date
    • db57fad4 - Fix layout of comments replies
    • 34b31510 - Add pagination button for comment listing
    • 07583059 - Hide more comments button when there is no more pages to load
    • 37a2a450 - Refactor comments component to permit extensions
    • 5d2ebeec - Translate success message of post comment
    Choose File ...   File name...
    Cancel
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira @abner

    Reassigned to @abner

    Choose File ...   File name...
    Cancel
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira @abner (Edited )

    I would like to deliver some feedback about the comment system implemented:

    1 - The "More" button is showing even when there isn't more comments avaiable;

    2 - I think would be better minimize the identation to represent replies, maybe to just one level and use some text+icon to represent the comment is a reply. I think the comments would have anchors and so the reply text+icon would link to the comment it's replying.

    3 - I realy liked the Post Comment Button Animation. :D

    Choose File ...   File name...
    Cancel
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira started a discussion on the outdated diff
    last updated by Ábner Oliveira
    src/app/article/comment/comment.component.ts
    11 13  
    12 14 showReply: boolean = false;
    13 15  
      16 + constructor(private $scope: ng.IScope) {
      17 + $scope.$on(PostCommentComponent.EVENT_COMMENT_RECEIVED, (event: ng.IAngularEvent, comment: noosfero.Comment) => {
    1
    • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
      Ábner Oliveira @abner

      We started to use the EventEmmiter and Services to events propagation. So this app will be more aligned with the Angular 2 Way of work with events. So i think we should change to use that approach instead of the $scope.broadcast

      Choose File ...   File name...
      Cancel
    0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira started a discussion on the diff
    last updated by Victor Costa
    src/app/article/comment/comments.html
    1 1 <div class="comments">
    2   - <noosfero-post-comment [article]="ctrl.article"></noosfero-post-comment>
      2 + <noosfero-post-comment ng-if="ctrl.showForm" [article]="ctrl.article" [parent]="ctrl.parent"></noosfero-post-comment>
    3 3  
    4 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 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>
    2
    0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira started a discussion on the diff
    last updated by Ábner Oliveira
    src/app/article/comment/post-comment/post-comment.scss 0 → 100644
      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 {
    1
    • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
      Ábner Oliveira @abner

      I liked the animation here. But maybe would be better provide this animation at the default theme instead. Would be hard to override this on themes.

      Choose File ...   File name...
      Cancel
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira @abner

    Reassigned to @vfcosta

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 16 new commits:

    • 5c75ac60 - Adding a Vagrantfile
    • 697684e6 - fix date format for general locales
    • 50d18589 - change README install instructions
    • dce2b87d - Cleaning up unneeded files
    • f9896e22 - Refactoring build task
    • d80617bf - Update README.md with new build instructions
    • a5c48faf - Merge branch 'dev-fixes' into 'master'
    • a944f06a - Display replies of comments
    • 4b876c39 - Improve layout of post comment component
    • 2ebd4ad0 - Toggle reply form when clicked from comment component
    • 6911e123 - Sort comments by creation date
    • 1d6363e0 - Fix layout of comments replies
    • 6e2ef9ed - Add pagination button for comment listing
    • 16b1a6fa - Hide more comments button when there is no more pages to load
    • d247ad88 - Refactor comments component to permit extensions
    • 845f879b - Translate success message of post comment
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 1 new commit:

    • 6061cf6e - Refactor post comment event to use EventEmitter
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Added 1 new commit:

    • 132b5d04 - Refactor post comment event to use EventEmitter
    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    @abner the identation will be fixed in comment-paragraph branch

    Choose File ...   File name...
    Cancel
  • 4a20548511a65cfccc863520b70c3ee9?s=40&d=identicon
    Victor Costa @vfcosta

    Assignee removed

    Choose File ...   File name...
    Cancel
  • 0deafa1501ec8dd687ee70f90488d592?s=40&d=identicon
    Ábner Oliveira @abner

    o commit 00882cc3edbc26721fd392b5a314c0a24bee977a não apareceu aqui no MergeRequest

    Choose File ...   File name...
    Cancel