Commit 9eb40175e10f5e1b796de95c11dff721ae9ead7e

Authored by Victor Costa
2 parents 772e7865 fd340759

Merge branch 'comments' into ngforward

src/app/article/article-default-view-component.spec.ts
1   -
2 1 import {Input, provide, Component} from 'ng-forward';
3 2 import {ArticleViewComponent, ArticleDefaultViewComponent} from './article-default-view.component';
4 3  
5   -import {createComponentFromClass, quickCreateComponent} from "../../spec/helpers";
  4 +import * as helpers from "../../spec/helpers";
6 5  
7 6 // this htmlTemplate will be re-used between the container components in this spec file
8 7 const htmlTemplate: string = '<noosfero-article [article]="ctrl.article" [profile]="ctrl.profile"></noosfero-article>';
... ... @@ -21,15 +20,22 @@ describe(&quot;Components&quot;, () =&gt; {
21 20 it("renders the default component when no specific component is found", (done: Function) => {
22 21 // Creating a container component (ArticleContainerComponent) to include
23 22 // the component under test (ArticleView)
24   - @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [ArticleViewComponent] })
  23 + @Component({
  24 + selector: 'test-container-component',
  25 + template: htmlTemplate,
  26 + directives: [ArticleViewComponent],
  27 + providers: [
  28 + helpers.createProviderToValue('CommentService', helpers.mocks.commentService),
  29 + helpers.provideFilters("translateFilter"),
  30 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService)
  31 + ]
  32 + })
25 33 class ArticleContainerComponent {
26 34 article = { type: 'anyArticleType' };
27 35 profile = { name: 'profile-name' };
28   - constructor() {
29   - }
30 36 }
31 37  
32   - createComponentFromClass(ArticleContainerComponent).then((fixture) => {
  38 + helpers.createComponentFromClass(ArticleContainerComponent).then((fixture) => {
33 39 // and here we can inspect and run the test assertions
34 40  
35 41 // gets the children component of ArticleContainerComponent
... ... @@ -51,16 +57,23 @@ describe(&quot;Components&quot;, () =&gt; {
51 57  
52 58 // Creating a container component (ArticleContainerComponent) to include
53 59 // the component under test (ArticleView)
54   - @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [ArticleViewComponent] })
  60 + @Component({
  61 + selector: 'test-container-component',
  62 + template: htmlTemplate,
  63 + directives: [ArticleViewComponent],
  64 + providers: [
  65 + helpers.createProviderToValue('CommentService', helpers.mocks.commentService),
  66 + helpers.provideFilters("translateFilter"),
  67 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService)
  68 + ]
  69 + })
55 70 class ArticleContainerComponent {
56 71 article = { type: 'anyArticleType' };
57 72 profile = { name: 'profile-name' };
58   - constructor() {
59   - }
60 73 }
61 74  
62 75 // uses the TestComponentBuilder instance to initialize the component
63   - createComponentFromClass(ArticleContainerComponent).then((fixture) => {
  76 + helpers.createComponentFromClass(ArticleContainerComponent).then((fixture) => {
64 77 // and here we can inspect and run the test assertions
65 78 let articleView: ArticleViewComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
66 79  
... ... @@ -93,10 +106,8 @@ describe(&quot;Components&quot;, () =&gt; {
93 106 class CustomArticleType {
94 107 article = { type: 'TinyMceArticle' };
95 108 profile = { name: 'profile-name' };
96   - constructor() {
97   - }
98 109 }
99   - createComponentFromClass(CustomArticleType).then(fixture => {
  110 + helpers.createComponentFromClass(CustomArticleType).then(fixture => {
100 111 let myComponent: CustomArticleType = fixture.componentInstance;
101 112 expect(myComponent.article.type).toEqual("TinyMceArticle");
102 113 expect(fixture.debugElement.componentViewChildren[0].text()).toEqual("TinyMceArticle");
... ... @@ -105,4 +116,4 @@ describe(&quot;Components&quot;, () =&gt; {
105 116 });
106 117  
107 118 });
108   -});
109 119 \ No newline at end of file
  120 +});
... ...
src/app/article/article-default-view.component.ts
1 1 import { bundle, Input, Inject, Component, Directive } from 'ng-forward';
2 2 import {ArticleBlogComponent} from "./types/blog/blog.component";
  3 +import {CommentsComponent} from "./comment/comments.component";
3 4  
4 5 /**
5 6 * @ngdoc controller
... ... @@ -29,7 +30,7 @@ export class ArticleDefaultViewComponent {
29 30 @Component({
30 31 selector: 'noosfero-article',
31 32 template: 'not-used',
32   - directives: [ArticleDefaultViewComponent, ArticleBlogComponent]
  33 + directives: [ArticleDefaultViewComponent, ArticleBlogComponent, CommentsComponent]
33 34 })
34 35 @Inject("$element", "$scope", "$injector", "$compile")
35 36 export class ArticleViewComponent {
... ...
src/app/article/article.html
... ... @@ -20,4 +20,6 @@
20 20 <div class="page-body">
21 21 <div ng-bind-html="ctrl.article.body"></div>
22 22 </div>
  23 +
  24 + <comments [article]="ctrl.article"></comments>
23 25 </div>
... ...
src/app/article/comment/comment.component.ts 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +import { Input, Component } from 'ng-forward';
  2 +
  3 +@Component({
  4 + selector: 'comment',
  5 + templateUrl: 'app/article/comment/comment.html'
  6 +})
  7 +export class CommentComponent {
  8 +
  9 + @Input() comment: noosfero.Comment;
  10 + @Input() article: noosfero.Article;
  11 +
  12 + showReply: boolean = false;
  13 +
  14 + reply() {
  15 + this.showReply = true;
  16 + }
  17 +}
... ...
src/app/article/comment/comment.html 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +<div class="comment media">
  2 + <div class="media-left">
  3 + <a ui-sref="main.profile.home({profile: ctrl.comment.author.identifier})">
  4 + <noosfero-profile-image [profile]="ctrl.comment.author"></noosfero-profile-image>
  5 + </a>
  6 + </div>
  7 + <div class="media-body">
  8 + <div class="heading clearfix">
  9 + <a class="pull-left" ui-sref="main.profile.home({profile: ctrl.comment.author.identifier})">
  10 + <h4 class="media-heading">{{ctrl.comment.author.name}}</h4>
  11 + </a>
  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>
  19 + <div class="title">{{ctrl.comment.title}}</div>
  20 + <div class="body">{{ctrl.comment.body}}</div>
  21 + </div>
  22 +
  23 + <post-comment ng-if="ctrl.showReply" [article]="ctrl.article" [reply-of]="ctrl.comment"></post-comment>
  24 +
  25 +</div>
... ...
src/app/article/comment/comment.scss 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +.comments {
  2 + .comment {
  3 + margin: 20px;
  4 + .date {
  5 + @extend .text-muted;
  6 + @extend .small;
  7 + margin-left: 8px;
  8 + }
  9 + .title {
  10 + font-weight: bold;
  11 + }
  12 + .media-left {
  13 + min-width: 40px;
  14 + }
  15 + .media-body {
  16 + background-color: #F9F9F9;
  17 + padding: 10px;
  18 + }
  19 + noosfero-profile-image {
  20 + img {
  21 + height: 30px;
  22 + width: 30px;
  23 + max-width: 30px;
  24 + display: inline-block;
  25 + @extend .img-circle;
  26 + }
  27 + i {
  28 + font-size: 1.7em;
  29 + }
  30 + }
  31 + }
  32 +}
... ...
src/app/article/comment/comments.component.ts 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +import { Inject, Input, Component, provide } from 'ng-forward';
  2 +import { PostCommentComponent } from "./post-comment.component";
  3 +import { CommentService } from "../../../lib/ng-noosfero-api/http/comment.service";
  4 +import { CommentComponent } from "./comment.component";
  5 +
  6 +@Component({
  7 + selector: 'comments',
  8 + templateUrl: 'app/article/comment/comments.html',
  9 + directives: [PostCommentComponent, CommentComponent]
  10 +})
  11 +@Inject(CommentService, "$rootScope")
  12 +export class CommentsComponent {
  13 +
  14 + comments: noosfero.Comment[] = [];
  15 + @Input() article: noosfero.Article;
  16 +
  17 + constructor(private commentService: CommentService, private $rootScope: ng.IScope) {
  18 + $rootScope.$on("comment.received", (event: ng.IAngularEvent, comment: noosfero.Comment) => {
  19 + this.comments.push(comment);
  20 + });
  21 + }
  22 +
  23 + ngOnInit() {
  24 + this.commentService.getByArticle(this.article).then((result: noosfero.RestResult<noosfero.Comment[]>) => {
  25 + this.comments = result.data;
  26 + });
  27 + }
  28 +}
... ...
src/app/article/comment/comments.html 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<div class="comments">
  2 + <post-comment [article]="ctrl.article"></post-comment>
  3 +
  4 + <div class="comments-list">
  5 + <comment ng-repeat="comment in ctrl.comments" [comment]="comment" [article]="ctrl.article"></comment>
  6 + </div>
  7 +</div>
... ...
src/app/article/comment/post-comment.component.ts 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +import { Inject, Input, Component } from 'ng-forward';
  2 +import { CommentService } from "../../../lib/ng-noosfero-api/http/comment.service";
  3 +import { NotificationService } from "../../shared/services/notification.service";
  4 +
  5 +@Component({
  6 + selector: 'post-comment',
  7 + templateUrl: 'app/article/comment/post-comment.html'
  8 +})
  9 +@Inject(CommentService, NotificationService, "$rootScope")
  10 +export class PostCommentComponent {
  11 +
  12 + @Input() article: noosfero.Article;
  13 + comment: noosfero.Comment;
  14 +
  15 + @Input() replyOf: noosfero.Comment;
  16 +
  17 + constructor(private commentService: CommentService, private notificationService: NotificationService, private $rootScope: ng.IScope) { }
  18 +
  19 + save() {
  20 + if (this.replyOf) {
  21 + this.comment.reply_of_id = this.replyOf.id;
  22 + }
  23 + this.commentService.createInArticle(this.article, this.comment).then((result: noosfero.RestResult<noosfero.Comment>) => {
  24 + this.$rootScope.$emit("comment.received", result.data);
  25 + this.notificationService.success({ title: "Good job!", message: "Comment saved!" });
  26 + });
  27 + }
  28 +}
... ...
src/app/article/comment/post-comment.html 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +<form>
  2 + <div class="form-group">
  3 + <textarea class="form-control custom-control" rows="3" ng-model="ctrl.comment.body"></textarea>
  4 + </div>
  5 + <button type="submit" class="btn btn-default" ng-click="ctrl.save()">{{"comment.post" | translate}}</button>
  6 +</form>
... ...
src/languages/en.json
... ... @@ -22,5 +22,7 @@
22 22 "notification.error.default.title": "Oops...",
23 23 "notification.profile.not_found": "Page not found",
24 24 "notification.http_error.401.message": "Unauthorized",
25   - "notification.http_error.500.message": "Server error"
  25 + "notification.http_error.500.message": "Server error",
  26 + "comment.post": "Post",
  27 + "comment.reply": "reply"
26 28 }
... ...
src/languages/pt.json
... ... @@ -22,5 +22,7 @@
22 22 "notification.error.default.title": "Oops...",
23 23 "notification.profile.not_found": "Página não encontrada",
24 24 "notification.http_error.401.message": "Não autorizado",
25   - "notification.http_error.500.message": "Erro no servidor"
  25 + "notification.http_error.500.message": "Erro no servidor",
  26 + "comment.post": "Postar",
  27 + "comment.reply": "responder"
26 28 }
... ...
src/lib/ng-noosfero-api/http/comment.service.ts 0 → 100644
... ... @@ -0,0 +1,33 @@
  1 +import { Injectable, Inject } from "ng-forward";
  2 +import {RestangularService} from "./restangular_service";
  3 +import {ArticleService} from "./article.service";
  4 +
  5 +@Injectable()
  6 +@Inject("Restangular", "$q", "$log", ArticleService)
  7 +export class CommentService extends RestangularService<noosfero.Comment> {
  8 +
  9 + constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected articleService: ArticleService) {
  10 + super(Restangular, $q, $log);
  11 + }
  12 +
  13 + getResourcePath() {
  14 + return "comments";
  15 + }
  16 +
  17 + getDataKeys() {
  18 + return {
  19 + singular: 'comment',
  20 + plural: 'comments'
  21 + };
  22 + }
  23 +
  24 + getByArticle(article: noosfero.Article, params?: any): ng.IPromise<noosfero.RestResult<noosfero.Comment[]>> {
  25 + let articleElement = this.articleService.getElement(<number>article.id);
  26 + return this.list(articleElement);
  27 + }
  28 +
  29 + createInArticle(article: noosfero.Article, comment: noosfero.Comment): ng.IPromise<noosfero.RestResult<noosfero.Comment>> {
  30 + let articleElement = this.articleService.getElement(<number>article.id);
  31 + return this.create(comment, articleElement, null, { 'Content-Type': 'application/json' }, false);
  32 + }
  33 +}
... ...
src/lib/ng-noosfero-api/http/restangular_service.ts
... ... @@ -221,13 +221,17 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
221 221 * Creates a new Resource into the resource collection
222 222 * calls POST /resourcePath
223 223 */
224   - public create(obj: T, rootElement?: noosfero.RestModel, queryParams?: any, headers?: any): ng.IPromise<noosfero.RestResult<T>> {
  224 + public create(obj: T, rootElement?: noosfero.RestModel, queryParams?: any, headers?: any, isSub: boolean = true): ng.IPromise<noosfero.RestResult<T>> {
225 225 let deferred = this.$q.defer<noosfero.RestResult<T>>();
226 226  
227 227 let restRequest: ng.IPromise<noosfero.RestResult<T>>;
228 228  
229   - let data = <any>{ };
230   - data[this.getDataKeys().singular] = obj;
  229 + let data = <any>{};
  230 + if (isSub) {
  231 + data[this.getDataKeys().singular] = obj;
  232 + } else {
  233 + data = obj;
  234 + }
231 235  
232 236 if (rootElement) {
233 237 restRequest = rootElement.all(this.getResourcePath()).post(data, queryParams, headers);
... ...
src/lib/ng-noosfero-api/interfaces/comment.ts 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +namespace noosfero {
  2 + export interface Comment extends RestModel {
  3 + reply_of_id: number;
  4 + }
  5 +}
... ...
src/spec/mocks.ts
... ... @@ -73,6 +73,11 @@ export var mocks = {
73 73 },
74 74 instant: () => { }
75 75 },
  76 + commentService: {
  77 + getByArticle: (article: noosfero.Article) => {
  78 + return Promise.resolve();
  79 + }
  80 + },
76 81 sessionWithCurrentUser: (user: any) => {
77 82 return {
78 83 currentUser: () => { return user; }
... ... @@ -111,5 +116,8 @@ export var mocks = {
111 116 currentLanguage: () => { },
112 117 changeLanguage: (lang: string) => { },
113 118 translate: (text: string) => { return text; }
  119 + },
  120 + notificationService: {
  121 +
114 122 }
115 123 };
... ...