Commit afd487b014b35453e633b1e704e02b22635de372

Authored by Victor Costa
2 parents 9b3a27c9 34b11fee

Merge branch 'comment-paragraph-article' into 'master'

Make available comment paragraph discussion for creation and visualization

See merge request !15
Showing 36 changed files with 484 additions and 231 deletions   Show diff stats
src/app/article/article-default-view.component.ts
@@ -3,6 +3,7 @@ import {ArticleBlogComponent} from "./types/blog/blog.component"; @@ -3,6 +3,7 @@ import {ArticleBlogComponent} from "./types/blog/blog.component";
3 import {CommentsComponent} from "./comment/comments.component"; 3 import {CommentsComponent} from "./comment/comments.component";
4 import {MacroDirective} from "./macro/macro.directive"; 4 import {MacroDirective} from "./macro/macro.directive";
5 import {ArticleToolbarHotspotComponent} from "../hotspot/article-toolbar-hotspot.component"; 5 import {ArticleToolbarHotspotComponent} from "../hotspot/article-toolbar-hotspot.component";
  6 +import {ArticleContentHotspotComponent} from "../hotspot/article-content-hotspot.component";
6 7
7 /** 8 /**
8 * @ngdoc controller 9 * @ngdoc controller
@@ -33,7 +34,8 @@ export class ArticleDefaultViewComponent { @@ -33,7 +34,8 @@ export class ArticleDefaultViewComponent {
33 selector: 'noosfero-article', 34 selector: 'noosfero-article',
34 template: 'not-used', 35 template: 'not-used',
35 directives: [ArticleDefaultViewComponent, ArticleBlogComponent, 36 directives: [ArticleDefaultViewComponent, ArticleBlogComponent,
36 - CommentsComponent, MacroDirective, ArticleToolbarHotspotComponent] 37 + CommentsComponent, MacroDirective, ArticleToolbarHotspotComponent,
  38 + ArticleContentHotspotComponent]
37 }) 39 })
38 @Inject("$element", "$scope", "$injector", "$compile") 40 @Inject("$element", "$scope", "$injector", "$compile")
39 export class ArticleViewComponent { 41 export class ArticleViewComponent {
@@ -43,7 +45,8 @@ export class ArticleViewComponent { @@ -43,7 +45,8 @@ export class ArticleViewComponent {
43 directiveName: string; 45 directiveName: string;
44 46
45 ngOnInit() { 47 ngOnInit() {
46 - let specificDirective = 'noosfero' + this.article.type; 48 + let articleType = this.article.type.replace(/::/, '');
  49 + let specificDirective = 'noosfero' + articleType;
47 this.directiveName = "noosfero-default-article"; 50 this.directiveName = "noosfero-default-article";
48 if (this.$injector.has(specificDirective + 'Directive')) { 51 if (this.$injector.has(specificDirective + 'Directive')) {
49 this.directiveName = specificDirective.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); 52 this.directiveName = specificDirective.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
src/app/article/article.html
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 </span> 20 </span>
21 </div> 21 </div>
22 </div> 22 </div>
23 - 23 + <noosfero-hotspot-article-content [article]="ctrl.article"></noosfero-hotspot-article-content>
24 <div class="page-body"> 24 <div class="page-body">
25 <div bind-html-compile="ctrl.article.body"></div> 25 <div bind-html-compile="ctrl.article.body"></div>
26 </div> 26 </div>
src/app/article/basic-editor/basic-editor.component.spec.ts
@@ -1,84 +0,0 @@ @@ -1,84 +0,0 @@
1 -import {quickCreateComponent} from "../../../spec/helpers";  
2 -import {BasicEditorComponent} from "./basic-editor.component";  
3 -  
4 -  
5 -describe("Article BasicEditor", () => {  
6 -  
7 - let $rootScope: ng.IRootScopeService;  
8 - let $q: ng.IQService;  
9 - let articleServiceMock: any;  
10 - let profileServiceMock: any;  
11 - let $state: any;  
12 - let $stateParams: any;  
13 - let $window: any;  
14 - let profile = { id: 1 };  
15 - let notification: any;  
16 -  
17 -  
18 - beforeEach(inject((_$rootScope_: ng.IRootScopeService, _$q_: ng.IQService) => {  
19 - $rootScope = _$rootScope_;  
20 - $q = _$q_;  
21 - }));  
22 -  
23 - beforeEach(() => {  
24 - $window = jasmine.createSpyObj("$window", ["back"]);  
25 - $state = jasmine.createSpyObj("$state", ["go"]);  
26 - notification = jasmine.createSpyObj("notification", ["success"]);  
27 - profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["setCurrentProfileByIdentifier"]);  
28 - articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInParent", "updateArticle", "get"]);  
29 -  
30 - $stateParams = { profile: "profile" };  
31 -  
32 - let setCurrentProfileByIdentifierResponse = $q.defer();  
33 - setCurrentProfileByIdentifierResponse.resolve(profile);  
34 -  
35 - let articleCreate = $q.defer();  
36 - articleCreate.resolve({ data: { path: "path", profile: { identifier: "profile" } } });  
37 -  
38 - let articleGet = $q.defer();  
39 - articleGet.resolve({ data: { path: "parent-path", profile: { identifier: "profile" } } });  
40 -  
41 - profileServiceMock.setCurrentProfileByIdentifier = jasmine.createSpy("setCurrentProfileByIdentifier").and.returnValue(setCurrentProfileByIdentifierResponse.promise);  
42 - articleServiceMock.createInParent = jasmine.createSpy("createInParent").and.returnValue(articleCreate.promise);  
43 - articleServiceMock.updateArticle = jasmine.createSpy("updateArticle").and.returnValue(articleCreate.promise);  
44 - articleServiceMock.get = jasmine.createSpy("get").and.returnValue(articleGet.promise);  
45 - });  
46 -  
47 - it("create an article in the current profile when save", done => {  
48 - $stateParams['parent_id'] = 1;  
49 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);  
50 - component.save();  
51 - $rootScope.$apply();  
52 - expect(profileServiceMock.setCurrentProfileByIdentifier).toHaveBeenCalled();  
53 - expect(articleServiceMock.createInParent).toHaveBeenCalledWith(1, component.article);  
54 - done();  
55 - });  
56 -  
57 - it("got to the new article page and display an alert when saving sucessfully", done => {  
58 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);  
59 - component.save();  
60 - $rootScope.$apply();  
61 - expect($state.go).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "profile" });  
62 - expect(notification.success).toHaveBeenCalled();  
63 - done();  
64 - });  
65 -  
66 - it("go back when cancel article edition", done => {  
67 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);  
68 - $window.history = { back: jasmine.createSpy('back') };  
69 - component.cancel();  
70 - expect($window.history.back).toHaveBeenCalled();  
71 - done();  
72 - });  
73 -  
74 - it("edit existing article when save", done => {  
75 - $stateParams['parent_id'] = null;  
76 - $stateParams['id'] = 2;  
77 - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);  
78 - component.save();  
79 - $rootScope.$apply();  
80 - expect(articleServiceMock.updateArticle).toHaveBeenCalledWith(component.article);  
81 - done();  
82 - });  
83 -  
84 -});  
src/app/article/basic-editor/basic-editor.component.ts
@@ -1,69 +0,0 @@ @@ -1,69 +0,0 @@
1 -import {StateConfig, Component, Inject, provide} from 'ng-forward';  
2 -import {ArticleService} from "../../../lib/ng-noosfero-api/http/article.service";  
3 -import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service";  
4 -import {NotificationService} from "../../shared/services/notification.service.ts";  
5 -  
6 -@Component({  
7 - selector: 'article-basic-editor',  
8 - templateUrl: "app/article/basic-editor/basic-editor.html",  
9 - providers: [  
10 - provide('articleService', { useClass: ArticleService }),  
11 - provide('profileService', { useClass: ProfileService }),  
12 - provide('notification', { useClass: NotificationService })  
13 - ]  
14 -})  
15 -@Inject(ArticleService, ProfileService, "$state", NotificationService, "$stateParams", "$window")  
16 -export class BasicEditorComponent {  
17 -  
18 - article: noosfero.Article = <noosfero.Article>{ type: "TextArticle" };  
19 - parent: noosfero.Article = <noosfero.Article>{};  
20 -  
21 - id: number;  
22 - parentId: number;  
23 - profileIdentifier: string;  
24 -  
25 - constructor(private articleService: ArticleService,  
26 - private profileService: ProfileService,  
27 - private $state: ng.ui.IStateService,  
28 - private notification: NotificationService,  
29 - private $stateParams: ng.ui.IStateParamsService,  
30 - private $window: ng.IWindowService) {  
31 -  
32 - this.parentId = this.$stateParams['parent_id'];  
33 - this.profileIdentifier = this.$stateParams["profile"];  
34 - this.id = this.$stateParams['id'];  
35 -  
36 - if (this.parentId) {  
37 - this.articleService.get(this.parentId).then((result: noosfero.RestResult<noosfero.Article>) => {  
38 - this.parent = result.data;  
39 - });  
40 - }  
41 - if (this.id) {  
42 - this.articleService.get(this.id).then((result: noosfero.RestResult<noosfero.Article>) => {  
43 - this.article = result.data;  
44 - this.article.name = this.article.title; // FIXME  
45 - });  
46 - }  
47 - }  
48 -  
49 - save() {  
50 - this.profileService.setCurrentProfileByIdentifier(this.profileIdentifier).then((profile: noosfero.Profile) => {  
51 - if (this.id) {  
52 - return this.articleService.updateArticle(this.article);  
53 - } else {  
54 - return this.articleService.createInParent(this.parentId, this.article);  
55 - }  
56 - }).then((response: noosfero.RestResult<noosfero.Article>) => {  
57 - let article = (<noosfero.Article>response.data);  
58 - this.$state.go('main.profile.page', { page: article.path, profile: article.profile.identifier });  
59 - this.notification.success({ title: "article.basic_editor.success.title", message: "article.basic_editor.success.message" });  
60 - }).catch(() => {  
61 - this.notification.error({ message: "article.basic_editor.save.failed" });  
62 - });  
63 - }  
64 -  
65 - cancel() {  
66 - this.$window.history.back();  
67 - }  
68 -  
69 -}  
src/app/article/basic-editor/basic-editor.html
@@ -1,34 +0,0 @@ @@ -1,34 +0,0 @@
1 -<div class="basic-editor">  
2 - <div class="row">  
3 - <div class="col-md-1"></div>  
4 - <div class="col-md-8">  
5 - <form>  
6 - <div class="form-group">  
7 - <label for="titleInput">{{"article.basic_editor.title" | translate}}</label>  
8 - <input type="text" class="form-control" id="titleInput" placeholder="{{'article.basic_editor.title' | translate}}" ng-model="vm.article.name">  
9 - </div>  
10 - <div class="form-group">  
11 - <label for="bodyInput">{{"article.basic_editor.body" | translate}}</label>  
12 - <html-editor [(value)]="vm.article.body"></html-editor>  
13 - </div>  
14 - <button type="submit" class="btn btn-default" ng-click="vm.save()">{{"article.basic_editor.save" | translate}}</button>  
15 - <button type="button" class="btn btn-danger" ng-click="vm.cancel()">{{"article.basic_editor.cancel" | translate}}</button>  
16 - </form>  
17 - </div>  
18 - <div class="side-options">  
19 - <div class="visibility panel panel-default">  
20 - <div class="panel-heading">{{"article.basic_editor.visibility" | translate}}</div>  
21 - <div class="panel-body">  
22 - <div>  
23 - <input type="radio" ng-model="vm.article.published" ng-value="true">  
24 - <i class="fa fa-unlock fa-fw"></i> {{"article.basic_editor.visibility.public" | translate}}  
25 - </div>  
26 - <div>  
27 - <input type="radio" ng-model="vm.article.published" ng-value="false">  
28 - <i class="fa fa-lock fa-fw"></i> {{"article.basic_editor.visibility.private" | translate}}  
29 - </div>  
30 - </div>  
31 - </div>  
32 - </div>  
33 - </div>  
34 -</div>  
src/app/article/basic-editor/basic-editor.scss
@@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
1 -.basic-editor {  
2 - @extend .container-fluid;  
3 - padding: 0 1%;  
4 -  
5 - .side-options {  
6 - @extend .col-md-3;  
7 - margin-top: 25px;  
8 -  
9 - .visibility {  
10 - .panel-heading {  
11 - background-color: transparent;  
12 - font-weight: bold;  
13 - }  
14 - .panel-body {  
15 - i {  
16 - color: #A5A5A5;  
17 - }  
18 - }  
19 - }  
20 - }  
21 -}  
src/app/article/cms/article-editor/article-editor.component.ts 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +import {Component, Input, Inject} from 'ng-forward';
  2 +
  3 +@Component({
  4 + selector: 'article-editor',
  5 + template: "not-used"
  6 +})
  7 +@Inject("$element", "$scope", "$injector", "$compile")
  8 +export class ArticleEditorComponent {
  9 +
  10 + @Input() article: noosfero.Article;
  11 +
  12 + constructor(
  13 + private $element: any,
  14 + private $scope: ng.IScope,
  15 + private $injector: ng.auto.IInjectorService,
  16 + private $compile: ng.ICompileService) { }
  17 +
  18 + ngOnInit() {
  19 + let articleType = this.article.type.replace(/::/, '');
  20 + let specificDirective = `${articleType.charAt(0).toLowerCase()}${articleType.substring(1)}Editor`;
  21 + let directiveName = "article-basic-editor";
  22 + if (this.$injector.has(specificDirective + 'Directive')) {
  23 + directiveName = specificDirective.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  24 + }
  25 + this.$element.replaceWith(this.$compile('<' + directiveName + ' [article]="ctrl.article"></' + directiveName + '>')(this.$scope));
  26 + }
  27 +}
src/app/article/cms/basic-editor/basic-editor.component.ts 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +import {Component, Input} from 'ng-forward';
  2 +
  3 +@Component({
  4 + selector: 'article-basic-editor',
  5 + templateUrl: "app/article/cms/basic-editor/basic-editor.html"
  6 +})
  7 +export class BasicEditorComponent {
  8 +
  9 + @Input() article: noosfero.Article;
  10 +}
src/app/article/cms/basic-editor/basic-editor.html 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<form>
  2 + <div class="form-group">
  3 + <label for="titleInput">{{"article.basic_editor.title" | translate}}</label>
  4 + <input type="text" class="form-control" id="titleInput" placeholder="{{'article.basic_editor.title' | translate}}" ng-model="ctrl.article.name">
  5 + </div>
  6 + <div class="form-group">
  7 + <label for="bodyInput">{{"article.basic_editor.body" | translate}}</label>
  8 + <html-editor [(value)]="ctrl.article.body"></html-editor>
  9 + </div>
  10 +</form>
src/app/article/cms/basic-editor/basic-editor.scss 0 → 100644
src/app/article/cms/basic-options/basic-options.component.ts 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +import {Component, Input} from 'ng-forward';
  2 +
  3 +@Component({
  4 + selector: 'article-basic-options',
  5 + templateUrl: "app/article/cms/basic-options/basic-options.html"
  6 +})
  7 +export class BasicOptionsComponent {
  8 +
  9 + @Input() article: noosfero.Article;
  10 +
  11 +}
src/app/article/cms/basic-options/basic-options.html 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +<div class="side-options">
  2 + <div class="visibility panel panel-default">
  3 + <div class="panel-heading">{{"article.basic_editor.visibility" | translate}}</div>
  4 + <div class="panel-body">
  5 + <div>
  6 + <input type="radio" ng-model="ctrl.article.published" ng-value="true">
  7 + <i class="fa fa-unlock fa-fw"></i> {{"article.basic_editor.visibility.public" | translate}}
  8 + </div>
  9 + <div>
  10 + <input type="radio" ng-model="ctrl.article.published" ng-value="false">
  11 + <i class="fa fa-lock fa-fw"></i> {{"article.basic_editor.visibility.private" | translate}}
  12 + </div>
  13 + </div>
  14 + </div>
  15 +</div>
src/app/article/cms/basic-options/basic-options.scss 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +.side-options {
  2 + margin-top: 25px;
  3 +
  4 + .visibility {
  5 + .panel-heading {
  6 + background-color: transparent;
  7 + font-weight: bold;
  8 + }
  9 + .panel-body {
  10 + i {
  11 + color: #A5A5A5;
  12 + }
  13 + }
  14 + }
  15 +}
src/app/article/cms/cms.component.spec.ts 0 → 100644
@@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
  1 +import {quickCreateComponent} from "../../../spec/helpers";
  2 +import {CmsComponent} from "./cms.component";
  3 +
  4 +
  5 +describe("Article Cms", () => {
  6 +
  7 + let $rootScope: ng.IRootScopeService;
  8 + let $q: ng.IQService;
  9 + let articleServiceMock: any;
  10 + let profileServiceMock: any;
  11 + let $state: any;
  12 + let $stateParams: any;
  13 + let $window: any;
  14 + let profile = { id: 1 };
  15 + let notification: any;
  16 +
  17 +
  18 + beforeEach(inject((_$rootScope_: ng.IRootScopeService, _$q_: ng.IQService) => {
  19 + $rootScope = _$rootScope_;
  20 + $q = _$q_;
  21 + }));
  22 +
  23 + beforeEach(() => {
  24 + $window = jasmine.createSpyObj("$window", ["back"]);
  25 + $state = jasmine.createSpyObj("$state", ["go"]);
  26 + notification = jasmine.createSpyObj("notification", ["success"]);
  27 + profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["setCurrentProfileByIdentifier"]);
  28 + articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInParent", "updateArticle", "get"]);
  29 +
  30 + $stateParams = { profile: "profile" };
  31 +
  32 + let setCurrentProfileByIdentifierResponse = $q.defer();
  33 + setCurrentProfileByIdentifierResponse.resolve(profile);
  34 +
  35 + let articleCreate = $q.defer();
  36 + articleCreate.resolve({ data: { path: "path", profile: { identifier: "profile" } } });
  37 +
  38 + let articleGet = $q.defer();
  39 + articleGet.resolve({ data: { path: "parent-path", profile: { identifier: "profile" } } });
  40 +
  41 + profileServiceMock.setCurrentProfileByIdentifier = jasmine.createSpy("setCurrentProfileByIdentifier").and.returnValue(setCurrentProfileByIdentifierResponse.promise);
  42 + articleServiceMock.createInParent = jasmine.createSpy("createInParent").and.returnValue(articleCreate.promise);
  43 + articleServiceMock.updateArticle = jasmine.createSpy("updateArticle").and.returnValue(articleCreate.promise);
  44 + articleServiceMock.get = jasmine.createSpy("get").and.returnValue(articleGet.promise);
  45 + });
  46 +
  47 + it("create an article in the current profile when save", done => {
  48 + $stateParams['parent_id'] = 1;
  49 + let component: CmsComponent = new CmsComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  50 + component.save();
  51 + $rootScope.$apply();
  52 + expect(profileServiceMock.setCurrentProfileByIdentifier).toHaveBeenCalled();
  53 + expect(articleServiceMock.createInParent).toHaveBeenCalledWith(1, component.article);
  54 + done();
  55 + });
  56 +
  57 + it("got to the new article page and display an alert when saving sucessfully", done => {
  58 + let component: CmsComponent = new CmsComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  59 + component.save();
  60 + $rootScope.$apply();
  61 + expect($state.go).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "profile" });
  62 + expect(notification.success).toHaveBeenCalled();
  63 + done();
  64 + });
  65 +
  66 + it("go back when cancel article edition", done => {
  67 + let component: CmsComponent = new CmsComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  68 + $window.history = { back: jasmine.createSpy('back') };
  69 + component.cancel();
  70 + expect($window.history.back).toHaveBeenCalled();
  71 + done();
  72 + });
  73 +
  74 + it("edit existing article when save", done => {
  75 + $stateParams['parent_id'] = null;
  76 + $stateParams['id'] = 2;
  77 + let component: CmsComponent = new CmsComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  78 + component.save();
  79 + $rootScope.$apply();
  80 + expect(articleServiceMock.updateArticle).toHaveBeenCalledWith(component.article);
  81 + done();
  82 + });
  83 +
  84 +});
src/app/article/cms/cms.component.ts 0 → 100644
@@ -0,0 +1,74 @@ @@ -0,0 +1,74 @@
  1 +import {StateConfig, Component, Inject, provide} from 'ng-forward';
  2 +import {ArticleService} from "../../../lib/ng-noosfero-api/http/article.service";
  3 +import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service";
  4 +import {NotificationService} from "../../shared/services/notification.service.ts";
  5 +import {BasicOptionsComponent} from './basic-options/basic-options.component';
  6 +import {BasicEditorComponent} from './basic-editor/basic-editor.component';
  7 +import {ArticleEditorComponent} from './article-editor/article-editor.component';
  8 +
  9 +@Component({
  10 + selector: 'article-cms',
  11 + templateUrl: "app/article/cms/cms.html",
  12 + providers: [
  13 + provide('articleService', { useClass: ArticleService }),
  14 + provide('profileService', { useClass: ProfileService }),
  15 + provide('notification', { useClass: NotificationService })
  16 + ],
  17 + directives: [ArticleEditorComponent, BasicOptionsComponent, BasicEditorComponent]
  18 +})
  19 +@Inject(ArticleService, ProfileService, "$state", NotificationService, "$stateParams", "$window")
  20 +export class CmsComponent {
  21 +
  22 + article: noosfero.Article;
  23 + parent: noosfero.Article = <noosfero.Article>{};
  24 +
  25 + id: number;
  26 + parentId: number;
  27 + profileIdentifier: string;
  28 +
  29 + constructor(private articleService: ArticleService,
  30 + private profileService: ProfileService,
  31 + private $state: ng.ui.IStateService,
  32 + private notification: NotificationService,
  33 + private $stateParams: ng.ui.IStateParamsService,
  34 + private $window: ng.IWindowService) {
  35 +
  36 + this.parentId = this.$stateParams['parent_id'];
  37 + this.profileIdentifier = this.$stateParams["profile"];
  38 + this.id = this.$stateParams['id'];
  39 +
  40 + if (this.parentId) {
  41 + this.article = <noosfero.Article>{ type: this.$stateParams['type'] || "TextArticle", published: true };
  42 + this.articleService.get(this.parentId).then((result: noosfero.RestResult<noosfero.Article>) => {
  43 + this.parent = result.data;
  44 + });
  45 + }
  46 + if (this.id) {
  47 + this.articleService.get(this.id).then((result: noosfero.RestResult<noosfero.Article>) => {
  48 + this.article = result.data;
  49 + this.article.name = this.article.title; // FIXME
  50 + });
  51 + }
  52 + }
  53 +
  54 + save() {
  55 + this.profileService.setCurrentProfileByIdentifier(this.profileIdentifier).then((profile: noosfero.Profile) => {
  56 + if (this.id) {
  57 + return this.articleService.updateArticle(this.article);
  58 + } else {
  59 + return this.articleService.createInParent(this.parentId, this.article);
  60 + }
  61 + }).then((response: noosfero.RestResult<noosfero.Article>) => {
  62 + let article = (<noosfero.Article>response.data);
  63 + this.$state.go('main.profile.page', { page: article.path, profile: article.profile.identifier });
  64 + this.notification.success({ title: "article.basic_editor.success.title", message: "article.basic_editor.success.message" });
  65 + }).catch(() => {
  66 + this.notification.error({ message: "article.basic_editor.save.failed" });
  67 + });
  68 + }
  69 +
  70 + cancel() {
  71 + this.$window.history.back();
  72 + }
  73 +
  74 +}
src/app/article/cms/cms.html 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 +<div class="cms">
  2 + <div class="row">
  3 + <div class="col-md-1"></div>
  4 + <div class="col-md-8">
  5 + <article-editor ng-if="vm.article" [article]="vm.article"></article-editor>
  6 + </div>
  7 + <div class="col-md-3">
  8 + <article-basic-options ng-if="vm.article" [article]="vm.article"></article-basic-options>
  9 + </div>
  10 + </div>
  11 + <div class="row">
  12 + <div class="col-md-1"></div>
  13 + <div class="col-md-8">
  14 + <button type="submit" class="btn btn-default" ng-click="vm.save()">{{"article.basic_editor.save" | translate}}</button>
  15 + <button type="button" class="btn btn-danger" ng-click="vm.cancel()">{{"article.basic_editor.cancel" | translate}}</button>
  16 + </div>
  17 + </div>
  18 +</div>
src/app/article/cms/cms.scss 0 → 100644
@@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
  1 +.cms {
  2 + @extend .container-fluid;
  3 + padding: 0 1%;
  4 +}
src/app/article/comment/comment.html
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 <div class="title">{{ctrl.comment.title}}</div> 18 <div class="title">{{ctrl.comment.title}}</div>
19 <div class="body">{{ctrl.comment.body}}</div> 19 <div class="body">{{ctrl.comment.body}}</div>
20 <div class="actions" ng-if="ctrl.displayActions"> 20 <div class="actions" ng-if="ctrl.displayActions">
21 - <a href="#" (click)="ctrl.reply()" class="small text-muted"> 21 + <a href="#" (click)="ctrl.reply()" class="small text-muted" ng-if="ctrl.article.accept_comments">
22 {{"comment.reply" | translate}} 22 {{"comment.reply" | translate}}
23 </a> 23 </a>
24 </div> 24 </div>
src/app/article/comment/comments.scss 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +.comments {
  2 + border-top: 2px solid #F3F3F3;
  3 + .comments {
  4 + border-top: 0;
  5 + }
  6 +}
src/app/article/comment/post-comment/post-comment.component.spec.ts
@@ -7,6 +7,8 @@ const htmlTemplate: string = &#39;&lt;noosfero-post-comment [article]=&quot;ctrl.article&quot; [r @@ -7,6 +7,8 @@ const htmlTemplate: string = &#39;&lt;noosfero-post-comment [article]=&quot;ctrl.article&quot; [r
7 describe("Components", () => { 7 describe("Components", () => {
8 describe("Post Comment Component", () => { 8 describe("Post Comment Component", () => {
9 9
  10 + let properties = { article: { id: 1, accept_comments: true } };
  11 +
10 beforeEach(angular.mock.module("templates")); 12 beforeEach(angular.mock.module("templates"));
11 13
12 let commentService = jasmine.createSpyObj("commentService", ["createInArticle"]); 14 let commentService = jasmine.createSpyObj("commentService", ["createInArticle"]);
@@ -19,7 +21,7 @@ describe(&quot;Components&quot;, () =&gt; { @@ -19,7 +21,7 @@ describe(&quot;Components&quot;, () =&gt; {
19 21
20 @Component({ selector: 'test-container-component', directives: [PostCommentComponent], template: htmlTemplate, providers: providers }) 22 @Component({ selector: 'test-container-component', directives: [PostCommentComponent], template: htmlTemplate, providers: providers })
21 class ContainerComponent { 23 class ContainerComponent {
22 - article = { id: 1 }; 24 + article = properties['article'];
23 comment = { id: 2 }; 25 comment = { id: 2 };
24 } 26 }
25 27
@@ -30,6 +32,14 @@ describe(&quot;Components&quot;, () =&gt; { @@ -30,6 +32,14 @@ describe(&quot;Components&quot;, () =&gt; {
30 }); 32 });
31 }); 33 });
32 34
  35 + it("not render the post comment form when article doesn't accept comments", done => {
  36 + properties['article'].accept_comments = false;
  37 + helpers.createComponentFromClass(ContainerComponent).then(fixture => {
  38 + expect(fixture.debugElement.queryAll("form").length).toEqual(0);
  39 + done();
  40 + });
  41 + });
  42 +
33 it("emit an event when create comment", done => { 43 it("emit an event when create comment", done => {
34 helpers.createComponentFromClass(ContainerComponent).then(fixture => { 44 helpers.createComponentFromClass(ContainerComponent).then(fixture => {
35 let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance; 45 let component: PostCommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
src/app/article/comment/post-comment/post-comment.html
1 -<form class="clearfix post-comment"> 1 +<form class="clearfix post-comment" ng-if="ctrl.article.accept_comments">
2 <div class="form-group"> 2 <div class="form-group">
3 <div class="comment media"> 3 <div class="comment media">
4 <div class="media-left"> 4 <div class="media-left">
src/app/article/comment/post-comment/post-comment.scss
1 .comments { 1 .comments {
2 .post-comment { 2 .post-comment {
3 .media { 3 .media {
4 - border-top: 2px solid #F3F3F3;  
5 padding-top: 10px; 4 padding-top: 10px;
6 .media-left { 5 .media-left {
7 padding: 10px 0; 6 padding: 10px 0;
src/app/hotspot/article-content-hotspot.component.ts 0 → 100644
@@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
  1 +import {Component, Input, Inject} from "ng-forward";
  2 +import * as plugins from "../../plugins";
  3 +import {dasherize} from "ng-forward/cjs/util/helpers";
  4 +import {PluginHotspot} from "./plugin-hotspot";
  5 +
  6 +@Component({
  7 + selector: "noosfero-hotspot-article-content",
  8 + template: "<span></span>"
  9 +})
  10 +@Inject("$element", "$scope", "$compile")
  11 +export class ArticleContentHotspotComponent extends PluginHotspot {
  12 +
  13 + @Input() article: noosfero.Article;
  14 +
  15 + constructor(
  16 + private $element: any,
  17 + private $scope: ng.IScope,
  18 + private $compile: ng.ICompileService) {
  19 + super("article_extra_content");
  20 + }
  21 +
  22 + addHotspot(directiveName: string) {
  23 + this.$element.append(this.$compile('<' + directiveName + ' [article]="ctrl.article"></' + directiveName + '>')(this.$scope));
  24 + }
  25 +}
src/app/profile/profile.component.ts
1 import {StateConfig, Component, Inject, provide} from 'ng-forward'; 1 import {StateConfig, Component, Inject, provide} from 'ng-forward';
2 import {ProfileInfoComponent} from './info/profile-info.component'; 2 import {ProfileInfoComponent} from './info/profile-info.component';
3 import {ProfileHomeComponent} from './profile-home.component'; 3 import {ProfileHomeComponent} from './profile-home.component';
4 -import {BasicEditorComponent} from '../article/basic-editor/basic-editor.component'; 4 +import {BasicEditorComponent} from '../article/cms/basic-editor/basic-editor.component';
  5 +import {CmsComponent} from '../article/cms/cms.component';
5 import {ContentViewerComponent} from "../article/content-viewer/content-viewer.component"; 6 import {ContentViewerComponent} from "../article/content-viewer/content-viewer.component";
6 import {ContentViewerActionsComponent} from "../article/content-viewer/content-viewer-actions.component"; 7 import {ContentViewerActionsComponent} from "../article/content-viewer/content-viewer-actions.component";
7 import {ActivitiesComponent} from "./activities/activities.component"; 8 import {ActivitiesComponent} from "./activities/activities.component";
@@ -46,12 +47,12 @@ import {MyProfileComponent} from &quot;./myprofile.component&quot;; @@ -46,12 +47,12 @@ import {MyProfileComponent} from &quot;./myprofile.component&quot;;
46 }, 47 },
47 { 48 {
48 name: 'main.cms', 49 name: 'main.cms',
49 - url: "^/myprofile/:profile/cms?parent_id",  
50 - component: BasicEditorComponent, 50 + url: "^/myprofile/:profile/cms?parent_id&type",
  51 + component: CmsComponent,
51 views: { 52 views: {
52 "content": { 53 "content": {
53 - templateUrl: "app/article/basic-editor/basic-editor.html",  
54 - controller: BasicEditorComponent, 54 + templateUrl: "app/article/cms/cms.html",
  55 + controller: CmsComponent,
55 controllerAs: "vm" 56 controllerAs: "vm"
56 } 57 }
57 } 58 }
@@ -59,11 +60,11 @@ import {MyProfileComponent} from &quot;./myprofile.component&quot;; @@ -59,11 +60,11 @@ import {MyProfileComponent} from &quot;./myprofile.component&quot;;
59 { 60 {
60 name: 'main.cmsEdit', 61 name: 'main.cmsEdit',
61 url: "^/myprofile/:profile/cms/edit/:id", 62 url: "^/myprofile/:profile/cms/edit/:id",
62 - component: BasicEditorComponent, 63 + component: CmsComponent,
63 views: { 64 views: {
64 "content": { 65 "content": {
65 - templateUrl: "app/article/basic-editor/basic-editor.html",  
66 - controller: BasicEditorComponent, 66 + templateUrl: "app/article/cms/cms.html",
  67 + controller: CmsComponent,
67 controllerAs: "vm" 68 controllerAs: "vm"
68 } 69 }
69 } 70 }
src/lib/ng-noosfero-api/http/article.service.ts
@@ -27,7 +27,13 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; { @@ -27,7 +27,13 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
27 'Content-Type': 'application/json' 27 'Content-Type': 'application/json'
28 }; 28 };
29 let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article>>(); 29 let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article>>();
30 - let attributesToUpdate: any = { article: { name: article.name, body: article.body, published: article.published } }; 30 + // TODO dynamically copy the selected attributes to update
  31 + let attributesToUpdate: any = {
  32 + article: {
  33 + name: article.name, body: article.body, published: article.published,
  34 + start_date: article['start_date'], end_date: article['end_date']
  35 + }
  36 + };
31 let restRequest: ng.IPromise<noosfero.RestResult<noosfero.Article>> = this.getElement(article.id).customPOST(attributesToUpdate, null, null, headers); 37 let restRequest: ng.IPromise<noosfero.RestResult<noosfero.Article>> = this.getElement(article.id).customPOST(attributesToUpdate, null, null, headers);
32 restRequest.then(this.getHandleSuccessFunction(deferred)) 38 restRequest.then(this.getHandleSuccessFunction(deferred))
33 .catch(this.getHandleErrorFunction(deferred)); 39 .catch(this.getHandleErrorFunction(deferred));
src/lib/ng-noosfero-api/interfaces/article.ts
1 1
2 namespace noosfero { 2 namespace noosfero {
3 - export interface Article extends RestModel { 3 + export interface Article extends RestModel {
4 path: string; 4 path: string;
5 profile: Profile; 5 profile: Profile;
6 type: string; 6 type: string;
7 - parent: Article; 7 + parent: Article;
8 body: string; 8 body: string;
9 title: string; 9 title: string;
10 name: string; 10 name: string;
11 published: boolean; 11 published: boolean;
12 setting: any; 12 setting: any;
  13 + start_date: string;
  14 + end_date: string;
13 } 15 }
14 -}  
15 \ No newline at end of file 16 \ No newline at end of file
  17 +}
src/plugins/comment_paragraph/article/cms/discussion-editor/discussion-editor.component.ts 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +import {Component, Input, Inject} from 'ng-forward';
  2 +
  3 +@Component({
  4 + selector: 'comment-paragraph-plugin-discussion-editor',
  5 + templateUrl: "plugins/comment_paragraph/article/cms/discussion-editor/discussion-editor.html"
  6 +})
  7 +@Inject("$scope")
  8 +export class DiscussionEditorComponent {
  9 +
  10 + @Input() article: noosfero.Article;
  11 + start_date: Date;
  12 + end_date: Date;
  13 +
  14 + constructor(private $scope: ng.IScope) {
  15 + this.convertDate('start_date');
  16 + this.convertDate('end_date');
  17 + }
  18 +
  19 + convertDate(attributeName: string) {
  20 + this.$scope.$watch(() => {
  21 + return (<any>this)[attributeName];
  22 + }, () => {
  23 + if ((<any>this)[attributeName]) {
  24 + (<any>this.article)[attributeName] = (<any>this)[attributeName].toISOString();
  25 + }
  26 + });
  27 + }
  28 +
  29 + ngOnInit() {
  30 + if (this.article.start_date) {
  31 + this.start_date = new Date(this.article.start_date);
  32 + } else {
  33 + this.start_date = moment().toDate();
  34 + }
  35 + if (this.article.end_date) {
  36 + this.end_date = new Date(this.article.end_date);
  37 + }
  38 + }
  39 +}
src/plugins/comment_paragraph/article/cms/discussion-editor/discussion-editor.html 0 → 100644
@@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
  1 +<form>
  2 + <div class="form-group">
  3 + <label for="titleInput">{{"article.basic_editor.title" | translate}}</label>
  4 + <input type="text" class="form-control" id="titleInput" placeholder="{{'article.basic_editor.title' | translate}}" ng-model="ctrl.article.name">
  5 + </div>
  6 + <div class="form-group">
  7 +
  8 + <div class="form-inline">
  9 + <span class="start-date discussion-date">
  10 + <label for="startDateInput">{{"comment-paragraph-plugin.discussion.editor.start_date.label" | translate}}</label>
  11 + <input id="startDateInput" type="text" class="form-control" uib-datepicker-popup ng-model="ctrl.start_date"
  12 + is-open="startDateOpened" ng-required="true" close-text="Close" />
  13 + <span class="input-group-btn date-popup-button">
  14 + <button type="button" class="btn btn-default" ng-click="startDateOpened = true">
  15 + <i class="fa fa-calendar fa-fw"></i>
  16 + </button>
  17 + </span>
  18 + </span>
  19 + <span class="end-date discussion-date">
  20 + <label for="endDateInput">{{"comment-paragraph-plugin.discussion.editor.end_date.label" | translate}}</label>
  21 + <input id="endDateInput" type="text" class="form-control" uib-datepicker-popup ng-model="ctrl.end_date"
  22 + is-open="endDateOpened" ng-required="true" close-text="Close" />
  23 + <span class="date-popup-button">
  24 + <button type="button" class="btn btn-default" ng-click="endDateOpened = true">
  25 + <i class="fa fa-calendar fa-fw"></i>
  26 + </button>
  27 + </span>
  28 + </span>
  29 + </div>
  30 + </div>
  31 + <div class="form-group">
  32 + <label for="bodyInput">{{"article.basic_editor.body" | translate}}</label>
  33 + <html-editor [(value)]="ctrl.article.body"></html-editor>
  34 + </div>
  35 +</form>
src/plugins/comment_paragraph/article/cms/discussion-editor/discussion.scss 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +comment-paragraph-plugin-discussion-editor {
  2 + .discussion-date {
  3 + [uib-datepicker-popup-wrap] {
  4 + display: inline-block;
  5 + }
  6 + .date-popup-button {
  7 + @extend .input-group-btn;
  8 + display: inline-block;
  9 + }
  10 + }
  11 + .end-date {
  12 + margin-left: 60px;
  13 + }
  14 +}
src/plugins/comment_paragraph/hotspot/article-content/article-content.component.ts 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +import { Input, Inject, Component } from "ng-forward";
  2 +import {Hotspot} from "../../../../app/hotspot/hotspot.decorator";
  3 +
  4 +@Component({
  5 + selector: "comment-paragraph-article-content-hotspot",
  6 + templateUrl: "plugins/comment_paragraph/hotspot/article-content/article-content.html",
  7 +})
  8 +@Hotspot("article_extra_content")
  9 +export class CommentParagraphArticleContentHotspotComponent {
  10 +
  11 + @Input() article: noosfero.Article;
  12 +}
src/plugins/comment_paragraph/hotspot/article-content/article-content.html 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +<div class="discussion-header">
  2 + <span class="description">{{"comment-paragraph-plugin.discussion.header" | translate}}</span>
  3 + <span class="start-date date" ng-if="ctrl.article.start_date">
  4 + <span class="description">{{"comment-paragraph-plugin.discussion.editor.start_date.label" | translate}}</span>
  5 + <span class="value">{{ctrl.article.start_date | amDateFormat:'DD/MM/YYYY'}}</span>
  6 + </span>
  7 + <span class="end-date date" ng-if="ctrl.article.end_date">
  8 + <span class="description">{{"comment-paragraph-plugin.discussion.editor.end_date.label" | translate}}</span>
  9 + <span class="value">{{ctrl.article.end_date | amDateFormat:'DD/MM/YYYY'}}</span>
  10 + </span>
  11 +</div>
src/plugins/comment_paragraph/hotspot/article-content/article-content.scss 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +.discussion-header {
  2 + @extend .pull-right;
  3 + margin-bottom: 20px;
  4 + .date {
  5 + text-transform: lowercase;
  6 + font-size: 16px;
  7 + .description {
  8 + font-weight: bold;
  9 + color: #BFBFBF;
  10 + }
  11 + .value {
  12 + font-size: 15px;
  13 + color: #969696;
  14 + }
  15 + }
  16 + .description {
  17 + font-weight: bold;
  18 + color: #BFBFBF;
  19 + }
  20 +}
src/plugins/comment_paragraph/index.ts
1 import {AllowCommentComponent} from "./allow-comment/allow-comment.component"; 1 import {AllowCommentComponent} from "./allow-comment/allow-comment.component";
2 import {CommentParagraphArticleButtonHotspotComponent} from "./hotspot/comment-paragraph-article-button.component"; 2 import {CommentParagraphArticleButtonHotspotComponent} from "./hotspot/comment-paragraph-article-button.component";
3 import {CommentParagraphFormHotspotComponent} from "./hotspot/comment-paragraph-form.component"; 3 import {CommentParagraphFormHotspotComponent} from "./hotspot/comment-paragraph-form.component";
  4 +import {DiscussionEditorComponent} from "./article/cms/discussion-editor/discussion-editor.component";
  5 +import {CommentParagraphArticleContentHotspotComponent} from "./hotspot/article-content/article-content.component";
4 6
5 -export let mainComponents: any = [AllowCommentComponent];  
6 -export let hotspots: any = [CommentParagraphArticleButtonHotspotComponent, CommentParagraphFormHotspotComponent]; 7 +export let mainComponents: any = [AllowCommentComponent, DiscussionEditorComponent];
  8 +export let hotspots: any = [CommentParagraphArticleButtonHotspotComponent, CommentParagraphFormHotspotComponent, CommentParagraphArticleContentHotspotComponent];
src/plugins/comment_paragraph/languages/en.json
1 { 1 {
2 - "comment-paragraph-plugin.title": "Paragraph Comments" 2 + "comment-paragraph-plugin.title": "Paragraph Comments",
  3 + "comment-paragraph-plugin.discussion.editor.start_date.label": "From",
  4 + "comment-paragraph-plugin.discussion.editor.end_date.label": "To",
  5 + "comment-paragraph-plugin.discussion.header": "Open for comments"
3 } 6 }
src/plugins/comment_paragraph/languages/pt.json
1 { 1 {
2 - "comment-paragraph-plugin.title": "Comentários por Parágrafo" 2 + "comment-paragraph-plugin.title": "Comentários por Parágrafo",
  3 + "comment-paragraph-plugin.discussion.editor.start_date.label": "De",
  4 + "comment-paragraph-plugin.discussion.editor.end_date.label": "Até",
  5 + "comment-paragraph-plugin.discussion.header": "Aberto para comentários"
3 } 6 }
src/plugins/comment_paragraph/side-comments/side-comments.scss
1 comment-paragraph-side-comments { 1 comment-paragraph-side-comments {
2 .comments { 2 .comments {
  3 + min-height: 20px;
  4 + border-top: 0;
3 .comment { 5 .comment {
4 margin: 0; 6 margin: 0;
5 &.media { 7 &.media {