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 import {Input, provide, Component} from 'ng-forward'; 1 import {Input, provide, Component} from 'ng-forward';
3 import {ArticleViewComponent, ArticleDefaultViewComponent} from './article-default-view.component'; 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 // this htmlTemplate will be re-used between the container components in this spec file 6 // this htmlTemplate will be re-used between the container components in this spec file
8 const htmlTemplate: string = '<noosfero-article [article]="ctrl.article" [profile]="ctrl.profile"></noosfero-article>'; 7 const htmlTemplate: string = '<noosfero-article [article]="ctrl.article" [profile]="ctrl.profile"></noosfero-article>';
@@ -21,15 +20,22 @@ describe(&quot;Components&quot;, () =&gt; { @@ -21,15 +20,22 @@ describe(&quot;Components&quot;, () =&gt; {
21 it("renders the default component when no specific component is found", (done: Function) => { 20 it("renders the default component when no specific component is found", (done: Function) => {
22 // Creating a container component (ArticleContainerComponent) to include 21 // Creating a container component (ArticleContainerComponent) to include
23 // the component under test (ArticleView) 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 class ArticleContainerComponent { 33 class ArticleContainerComponent {
26 article = { type: 'anyArticleType' }; 34 article = { type: 'anyArticleType' };
27 profile = { name: 'profile-name' }; 35 profile = { name: 'profile-name' };
28 - constructor() {  
29 - }  
30 } 36 }
31 37
32 - createComponentFromClass(ArticleContainerComponent).then((fixture) => { 38 + helpers.createComponentFromClass(ArticleContainerComponent).then((fixture) => {
33 // and here we can inspect and run the test assertions 39 // and here we can inspect and run the test assertions
34 40
35 // gets the children component of ArticleContainerComponent 41 // gets the children component of ArticleContainerComponent
@@ -51,16 +57,23 @@ describe(&quot;Components&quot;, () =&gt; { @@ -51,16 +57,23 @@ describe(&quot;Components&quot;, () =&gt; {
51 57
52 // Creating a container component (ArticleContainerComponent) to include 58 // Creating a container component (ArticleContainerComponent) to include
53 // the component under test (ArticleView) 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 class ArticleContainerComponent { 70 class ArticleContainerComponent {
56 article = { type: 'anyArticleType' }; 71 article = { type: 'anyArticleType' };
57 profile = { name: 'profile-name' }; 72 profile = { name: 'profile-name' };
58 - constructor() {  
59 - }  
60 } 73 }
61 74
62 // uses the TestComponentBuilder instance to initialize the component 75 // uses the TestComponentBuilder instance to initialize the component
63 - createComponentFromClass(ArticleContainerComponent).then((fixture) => { 76 + helpers.createComponentFromClass(ArticleContainerComponent).then((fixture) => {
64 // and here we can inspect and run the test assertions 77 // and here we can inspect and run the test assertions
65 let articleView: ArticleViewComponent = fixture.debugElement.componentViewChildren[0].componentInstance; 78 let articleView: ArticleViewComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
66 79
@@ -93,10 +106,8 @@ describe(&quot;Components&quot;, () =&gt; { @@ -93,10 +106,8 @@ describe(&quot;Components&quot;, () =&gt; {
93 class CustomArticleType { 106 class CustomArticleType {
94 article = { type: 'TinyMceArticle' }; 107 article = { type: 'TinyMceArticle' };
95 profile = { name: 'profile-name' }; 108 profile = { name: 'profile-name' };
96 - constructor() {  
97 - }  
98 } 109 }
99 - createComponentFromClass(CustomArticleType).then(fixture => { 110 + helpers.createComponentFromClass(CustomArticleType).then(fixture => {
100 let myComponent: CustomArticleType = fixture.componentInstance; 111 let myComponent: CustomArticleType = fixture.componentInstance;
101 expect(myComponent.article.type).toEqual("TinyMceArticle"); 112 expect(myComponent.article.type).toEqual("TinyMceArticle");
102 expect(fixture.debugElement.componentViewChildren[0].text()).toEqual("TinyMceArticle"); 113 expect(fixture.debugElement.componentViewChildren[0].text()).toEqual("TinyMceArticle");
@@ -105,4 +116,4 @@ describe(&quot;Components&quot;, () =&gt; { @@ -105,4 +116,4 @@ describe(&quot;Components&quot;, () =&gt; {
105 }); 116 });
106 117
107 }); 118 });
108 -});  
109 \ No newline at end of file 119 \ No newline at end of file
  120 +});
src/app/article/article-default-view.component.ts
1 import { bundle, Input, Inject, Component, Directive } from 'ng-forward'; 1 import { bundle, Input, Inject, Component, Directive } from 'ng-forward';
2 import {ArticleBlogComponent} from "./types/blog/blog.component"; 2 import {ArticleBlogComponent} from "./types/blog/blog.component";
  3 +import {CommentsComponent} from "./comment/comments.component";
3 4
4 /** 5 /**
5 * @ngdoc controller 6 * @ngdoc controller
@@ -29,7 +30,7 @@ export class ArticleDefaultViewComponent { @@ -29,7 +30,7 @@ export class ArticleDefaultViewComponent {
29 @Component({ 30 @Component({
30 selector: 'noosfero-article', 31 selector: 'noosfero-article',
31 template: 'not-used', 32 template: 'not-used',
32 - directives: [ArticleDefaultViewComponent, ArticleBlogComponent] 33 + directives: [ArticleDefaultViewComponent, ArticleBlogComponent, CommentsComponent]
33 }) 34 })
34 @Inject("$element", "$scope", "$injector", "$compile") 35 @Inject("$element", "$scope", "$injector", "$compile")
35 export class ArticleViewComponent { 36 export class ArticleViewComponent {
src/app/article/article.html
@@ -20,4 +20,6 @@ @@ -20,4 +20,6 @@
20 <div class="page-body"> 20 <div class="page-body">
21 <div ng-bind-html="ctrl.article.body"></div> 21 <div ng-bind-html="ctrl.article.body"></div>
22 </div> 22 </div>
  23 +
  24 + <comments [article]="ctrl.article"></comments>
23 </div> 25 </div>
src/app/article/comment/comment.component.ts 0 → 100644
@@ -0,0 +1,17 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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,5 +22,7 @@
22 "notification.error.default.title": "Oops...", 22 "notification.error.default.title": "Oops...",
23 "notification.profile.not_found": "Page not found", 23 "notification.profile.not_found": "Page not found",
24 "notification.http_error.401.message": "Unauthorized", 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,5 +22,7 @@
22 "notification.error.default.title": "Oops...", 22 "notification.error.default.title": "Oops...",
23 "notification.profile.not_found": "Página não encontrada", 23 "notification.profile.not_found": "Página não encontrada",
24 "notification.http_error.401.message": "Não autorizado", 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 @@ @@ -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,13 +221,17 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
221 * Creates a new Resource into the resource collection 221 * Creates a new Resource into the resource collection
222 * calls POST /resourcePath 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 let deferred = this.$q.defer<noosfero.RestResult<T>>(); 225 let deferred = this.$q.defer<noosfero.RestResult<T>>();
226 226
227 let restRequest: ng.IPromise<noosfero.RestResult<T>>; 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 if (rootElement) { 236 if (rootElement) {
233 restRequest = rootElement.all(this.getResourcePath()).post(data, queryParams, headers); 237 restRequest = rootElement.all(this.getResourcePath()).post(data, queryParams, headers);
src/lib/ng-noosfero-api/interfaces/comment.ts 0 → 100644
@@ -0,0 +1,5 @@ @@ -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,6 +73,11 @@ export var mocks = {
73 }, 73 },
74 instant: () => { } 74 instant: () => { }
75 }, 75 },
  76 + commentService: {
  77 + getByArticle: (article: noosfero.Article) => {
  78 + return Promise.resolve();
  79 + }
  80 + },
76 sessionWithCurrentUser: (user: any) => { 81 sessionWithCurrentUser: (user: any) => {
77 return { 82 return {
78 currentUser: () => { return user; } 83 currentUser: () => { return user; }
@@ -111,5 +116,8 @@ export var mocks = { @@ -111,5 +116,8 @@ export var mocks = {
111 currentLanguage: () => { }, 116 currentLanguage: () => { },
112 changeLanguage: (lang: string) => { }, 117 changeLanguage: (lang: string) => { },
113 translate: (text: string) => { return text; } 118 translate: (text: string) => { return text; }
  119 + },
  120 + notificationService: {
  121 +
114 } 122 }
115 }; 123 };