Commit 638280755f7b228312ab3daddf04b8e3e8f1aa05

Authored by Caio Almeida
2 parents 07ad018c 45978e40

Merge branch 'master' into tags-block

Showing 125 changed files with 1743 additions and 537 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 125 files displayed.

gulp/watch.js
... ... @@ -15,11 +15,13 @@ gulp.task('watch', ['inject'], function () {
15 15  
16 16 gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject-reload']);
17 17  
18   - gulp.watch([
19   - path.join(conf.paths.src, '/app/**/*.css'),
20   - path.join(conf.paths.src, '/app/**/*.scss'),
21   - path.join(conf.paths.src, conf.paths.plugins, '/**/*.scss')
22   - ], function(event) {
  18 + var stylePaths = [path.join(conf.paths.src, conf.paths.plugins, '/**/*.scss')];
  19 + conf.paths.allSources.forEach(function(src) {
  20 + stylePaths.push(path.join(src, '/app/**/*.css'));
  21 + stylePaths.push(path.join(src, '/app/**/*.scss'));
  22 + });
  23 +
  24 + gulp.watch(stylePaths, function(event) {
23 25 if(isOnlyChange(event)) {
24 26 gulp.start('styles-reload');
25 27 } else {
... ...
karma.conf.js
... ... @@ -126,6 +126,10 @@ module.exports = function (config) {
126 126 };
127 127  
128 128  
  129 + if(config.grep) {
  130 + configuration.client = { args: ['--grep', config.grep] };
  131 + }
  132 +
129 133 if (coverage) {
130 134  
131 135 /*configuration.webpack = {
... ...
src/app/article/article-default-view-component.spec.ts
1 1 import {Input, provide, Component} from 'ng-forward';
2 2 import {ArticleViewComponent, ArticleDefaultViewComponent} from './article-default-view.component';
  3 +import {ComponentTestHelper, createClass} from './../../spec/component-test-helper';
3 4  
4 5 import * as helpers from "../../spec/helpers";
5 6  
... ... @@ -9,113 +10,82 @@ const htmlTemplate: string = '<noosfero-article [article]="ctrl.article" [profil
9 10  
10 11 describe("Components", () => {
11 12  
12   - describe("ArticleView Component", () => {
13   -
14   - // the karma preprocessor html2js transform the templates html into js files which put
15   - // the templates to the templateCache into the module templates
16   - // we need to load the module templates here as the template for the
17   - // component Noosfero ArtileView will be load on our tests
18   - beforeEach(angular.mock.module("templates"));
19   -
20   - it("renders the default component when no specific component is found", (done: Function) => {
21   - // Creating a container component (ArticleContainerComponent) to include
22   - // the component under test (ArticleView)
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   - helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({}))
32   - ]
33   - })
34   - class ArticleContainerComponent {
35   - article = { type: 'anyArticleType' };
36   - profile = { name: 'profile-name' };
  13 + // the karma preprocessor html2js transform the templates html into js files which put
  14 + // the templates to the templateCache into the module templates
  15 + // we need to load the module templates here as the template for the
  16 + // component Noosfero ArtileView will be load on our tests
  17 + beforeEach(angular.mock.module("templates"));
  18 +
  19 + describe("Article Default View Component", () => {
  20 + let helper: ComponentTestHelper<ArticleDefaultViewComponent>;
  21 + const defaultViewTemplate: string = '<noosfero-default-article [article]="ctrl.article" [profile]="ctrl.profile"></noosfero-default-article>';
  22 + let articleService: any = helpers.mocks.articleService;
  23 + let article = <noosfero.Article>{
  24 + id: 1,
  25 + profile: {
  26 + identifier: "1"
37 27 }
38   -
39   - helpers.createComponentFromClass(ArticleContainerComponent).then((fixture) => {
40   - // and here we can inspect and run the test assertions
41   -
42   - // gets the children component of ArticleContainerComponent
43   - let articleView: ArticleViewComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
44   -
45   - // and checks if the article View rendered was the Default Article View
46   - expect(articleView.constructor.prototype).toEqual(ArticleDefaultViewComponent.prototype);
47   -
48   - // done needs to be called (it isn't really needed, as we can read in
49   - // here (https://github.com/ngUpgraders/ng-forward/blob/master/API.md#createasync)
50   - // because createAsync in ng-forward is not really async, but as the intention
51   - // here is write tests in angular 2 ways, this is recommended
52   - done();
  28 + };
  29 + let state = <ng.ui.IStateService>jasmine.createSpyObj("state", ["go", "transitionTo"]);
  30 + let providers = [
  31 + provide('$state', { useValue: state }),
  32 + provide('ArticleService', { useValue: articleService })
  33 + ].concat(helpers.provideFilters("translateFilter"));
  34 +
  35 + /**
  36 + * The beforeEach procedure will initialize the helper and parse
  37 + * the component according to the given providers. Unfortunetly, in
  38 + * this mode, the providers and properties given to the construtor
  39 + * can't be overriden.
  40 + */
  41 + beforeEach((done) => {
  42 + // Create the component bed for the test. Optionally, this could be done
  43 + // in each test if one needs customization of these parameters per test
  44 + let cls = createClass({
  45 + template: defaultViewTemplate,
  46 + directives: [ArticleDefaultViewComponent],
  47 + providers: providers,
  48 + properties: {
  49 + article: article
  50 + }
53 51 });
54   -
  52 + helper = new ComponentTestHelper<ArticleDefaultViewComponent>(cls, done);
55 53 });
56 54  
57   - it("receives the article and profile as inputs", (done: Function) => {
58   -
59   - // Creating a container component (ArticleContainerComponent) to include
60   - // the component under test (ArticleView)
61   - @Component({
62   - selector: 'test-container-component',
63   - template: htmlTemplate,
64   - directives: [ArticleViewComponent],
65   - providers: [
66   - helpers.createProviderToValue('CommentService', helpers.mocks.commentService),
67   - helpers.provideFilters("translateFilter"),
68   - helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService),
69   - helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({}))
70   - ]
71   - })
72   - class ArticleContainerComponent {
73   - article = { type: 'anyArticleType' };
74   - profile = { name: 'profile-name' };
75   - }
76   -
77   - // uses the TestComponentBuilder instance to initialize the component
78   - helpers.createComponentFromClass(ArticleContainerComponent).then((fixture) => {
79   - // and here we can inspect and run the test assertions
80   - let articleView: ArticleViewComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  55 + function getArticle() {
  56 + return this.article;
  57 + }
81 58  
82   - // assure the article object inside the ArticleView matches
83   - // the provided through the parent component
84   - expect(articleView.article.type).toEqual("anyArticleType");
85   - expect(articleView.profile.name).toEqual("profile-name");
86   -
87   - // done needs to be called (it isn't really needed, as we can read in
88   - // here (https://github.com/ngUpgraders/ng-forward/blob/master/API.md#createasync)
89   - // because createAsync in ng-forward is not really async, but as the intention
90   - // here is write tests in angular 2 ways, this is recommended
91   - done();
92   - });
  59 + it("it should delete article when delete is activated", () => {
  60 + expect(helper.component.article).toEqual(article);
  61 + // Spy the state service
  62 + doDeleteArticle();
  63 + expect(state.transitionTo).toHaveBeenCalled();
93 64 });
94 65  
95   -
96   - it("renders a article view which matches to the article type", done => {
97   - // NoosferoTinyMceArticle component created to check if it will be used
98   - // when a article with type 'TinyMceArticle' is provided to the noosfero-article (ArticleView)
99   - // *** Important *** - the selector is what ng-forward uses to define the name of the directive provider
100   - @Component({ selector: 'noosfero-tiny-mce-article', template: "<h1>TinyMceArticle</h1>" })
101   - class TinyMceArticleView {
102   - @Input() article: any;
103   - @Input() profile: any;
104   - }
105   -
106   - // Creating a container component (ArticleContainerComponent) to include our NoosferoTinyMceArticle
107   - @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [ArticleViewComponent, TinyMceArticleView] })
108   - class CustomArticleType {
109   - article = { type: 'TinyMceArticle' };
110   - profile = { name: 'profile-name' };
111   - }
112   - helpers.createComponentFromClass(CustomArticleType).then(fixture => {
113   - let myComponent: CustomArticleType = fixture.componentInstance;
114   - expect(myComponent.article.type).toEqual("TinyMceArticle");
115   - expect(fixture.debugElement.componentViewChildren[0].text()).toEqual("TinyMceArticle");
116   - done();
  66 + /**
  67 + * Execute the delete method on the target component
  68 + */
  69 + function doDeleteArticle() {
  70 + // Create a mock for the ArticleService removeArticle method
  71 + spyOn(helper.component.articleService, 'removeArticle').and.callFake(function(param: noosfero.Article) {
  72 + return {
  73 + catch: () => {}
  74 + };
117 75 });
118   - });
119   -
  76 + helper.component.delete();
  77 + expect(articleService.removeArticle).toHaveBeenCalled();
  78 + // After the component delete method execution, fire the
  79 + // ArticleEvent.removed event
  80 + simulateRemovedEvent();
  81 + }
  82 +
  83 + /**
  84 + * Simulate the ArticleService ArticleEvent.removed event
  85 + */
  86 + function simulateRemovedEvent() {
  87 + helper.component.articleService["notifyArticleRemovedListeners"](article);
  88 + }
120 89 });
  90 +
121 91 });
... ...
src/app/article/article-default-view.component.ts
... ... @@ -4,6 +4,7 @@ import {CommentsComponent} from &quot;./comment/comments.component&quot;;
4 4 import {MacroDirective} from "./macro/macro.directive";
5 5 import {ArticleToolbarHotspotComponent} from "../hotspot/article-toolbar-hotspot.component";
6 6 import {ArticleContentHotspotComponent} from "../hotspot/article-content-hotspot.component";
  7 +import {ArticleService} from "./../../lib/ng-noosfero-api/http/article.service";
7 8  
8 9 /**
9 10 * @ngdoc controller
... ... @@ -16,11 +17,29 @@ import {ArticleContentHotspotComponent} from &quot;../hotspot/article-content-hotspot
16 17 selector: 'noosfero-default-article',
17 18 templateUrl: 'app/article/article.html'
18 19 })
  20 +@Inject("$state", ArticleService)
19 21 export class ArticleDefaultViewComponent {
20 22  
21 23 @Input() article: noosfero.Article;
22 24 @Input() profile: noosfero.Profile;
23 25  
  26 + constructor(private $state: ng.ui.IStateService, public articleService: ArticleService) {
  27 + // Subscribe to the Article Removed Event
  28 + this.articleService.subscribeToArticleRemoved((article: noosfero.Article) => {
  29 + if (this.article.parent) {
  30 + this.$state.transitionTo('main.profile.page', { page: this.article.parent.path, profile: this.article.profile.identifier });
  31 + } else {
  32 + this.$state.transitionTo('main.profile.info', { profile: this.article.profile.identifier });
  33 + }
  34 + });
  35 + }
  36 +
  37 + delete() {
  38 + this.articleService.removeArticle(this.article).catch((cause: any) => {
  39 + throw new Error(`Problem removing the article: ${cause}`);
  40 + });
  41 + }
  42 +
24 43 }
25 44  
26 45 /**
... ... @@ -60,4 +79,5 @@ export class ArticleViewComponent {
60 79 private $injector: ng.auto.IInjectorService,
61 80 private $compile: ng.ICompileService) {
62 81 }
  82 +
63 83 }
... ...
src/app/article/article-view-component.spec.ts 0 → 100644
... ... @@ -0,0 +1,124 @@
  1 +import {Input, provide, Component} from 'ng-forward';
  2 +import {ArticleViewComponent, ArticleDefaultViewComponent} from './article-default-view.component';
  3 +
  4 +import * as helpers from "../../spec/helpers";
  5 +
  6 +// this htmlTemplate will be re-used between the container components in this spec file
  7 +const htmlTemplate: string = '<noosfero-article [article]="ctrl.article" [profile]="ctrl.profile"></noosfero-article>';
  8 +
  9 +
  10 +describe("Components", () => {
  11 +
  12 + // the karma preprocessor html2js transform the templates html into js files which put
  13 + // the templates to the templateCache into the module templates
  14 + // we need to load the module templates here as the template for the
  15 + // component Noosfero ArtileView will be load on our tests
  16 + beforeEach(angular.mock.module("templates"));
  17 +
  18 + describe("ArticleView Component", () => {
  19 + let state = <ng.ui.IStateService>jasmine.createSpyObj("state", ["go", "transitionTo"]);
  20 + it("renders the default component when no specific component is found", (done: Function) => {
  21 + // Creating a container component (ArticleContainerComponent) to include
  22 + // the component under test (ArticleView)
  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 + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})),
  32 + helpers.createProviderToValue('ArticleService', helpers.mocks.articleService),
  33 + helpers.createProviderToValue('$state', state)
  34 + ]
  35 + })
  36 + class ArticleContainerComponent {
  37 + article = { type: 'anyArticleType' };
  38 + profile = { name: 'profile-name' };
  39 + }
  40 +
  41 + helpers.createComponentFromClass(ArticleContainerComponent).then((fixture) => {
  42 + // and here we can inspect and run the test assertions
  43 +
  44 + // gets the children component of ArticleContainerComponent
  45 + let articleView: ArticleViewComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  46 +
  47 + // and checks if the article View rendered was the Default Article View
  48 + expect(articleView.constructor.prototype).toEqual(ArticleDefaultViewComponent.prototype);
  49 +
  50 + // done needs to be called (it isn't really needed, as we can read in
  51 + // here (https://github.com/ngUpgraders/ng-forward/blob/master/API.md#createasync)
  52 + // because createAsync in ng-forward is not really async, but as the intention
  53 + // here is write tests in angular 2 ways, this is recommended
  54 + done();
  55 + });
  56 + });
  57 +
  58 + it("receives the article and profile as inputs", (done: Function) => {
  59 +
  60 + // Creating a container component (ArticleContainerComponent) to include
  61 + // the component under test (ArticleView)
  62 + @Component({
  63 + selector: 'test-container-component',
  64 + template: htmlTemplate,
  65 + directives: [ArticleViewComponent],
  66 + providers: [
  67 + helpers.createProviderToValue('CommentService', helpers.mocks.commentService),
  68 + helpers.provideFilters("translateFilter"),
  69 + helpers.createProviderToValue('NotificationService', helpers.mocks.notificationService),
  70 + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})),
  71 + helpers.createProviderToValue('ArticleService', helpers.mocks.articleService),
  72 + helpers.createProviderToValue('$state', state)
  73 + ]
  74 + })
  75 + class ArticleContainerComponent {
  76 + article = { type: 'anyArticleType' };
  77 + profile = { name: 'profile-name' };
  78 + }
  79 +
  80 + // uses the TestComponentBuilder instance to initialize the component
  81 + helpers.createComponentFromClass(ArticleContainerComponent).then((fixture) => {
  82 + // and here we can inspect and run the test assertions
  83 + let articleView: ArticleViewComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  84 +
  85 + // assure the article object inside the ArticleView matches
  86 + // the provided through the parent component
  87 + expect(articleView.article.type).toEqual("anyArticleType");
  88 + expect(articleView.profile.name).toEqual("profile-name");
  89 +
  90 + // done needs to be called (it isn't really needed, as we can read in
  91 + // here (https://github.com/ngUpgraders/ng-forward/blob/master/API.md#createasync)
  92 + // because createAsync in ng-forward is not really async, but as the intention
  93 + // here is write tests in angular 2 ways, this is recommended
  94 + done();
  95 + });
  96 + });
  97 +
  98 +
  99 + it("renders a article view which matches to the article type", done => {
  100 + // NoosferoTinyMceArticle component created to check if it will be used
  101 + // when a article with type 'TinyMceArticle' is provided to the noosfero-article (ArticleView)
  102 + // *** Important *** - the selector is what ng-forward uses to define the name of the directive provider
  103 + @Component({ selector: 'noosfero-tiny-mce-article', template: "<h1>TinyMceArticle</h1>" })
  104 + class TinyMceArticleView {
  105 + @Input() article: any;
  106 + @Input() profile: any;
  107 + }
  108 +
  109 + // Creating a container component (ArticleContainerComponent) to include our NoosferoTinyMceArticle
  110 + @Component({ selector: 'test-container-component', template: htmlTemplate, directives: [ArticleViewComponent, TinyMceArticleView] })
  111 + class CustomArticleType {
  112 + article = { type: 'TinyMceArticle' };
  113 + profile = { name: 'profile-name' };
  114 + }
  115 + helpers.createComponentFromClass(CustomArticleType).then(fixture => {
  116 + let myComponent: CustomArticleType = fixture.componentInstance;
  117 + expect(myComponent.article.type).toEqual("TinyMceArticle");
  118 + expect(fixture.debugElement.componentViewChildren[0].text()).toEqual("TinyMceArticle");
  119 + done();
  120 + });
  121 + });
  122 +
  123 + });
  124 +});
... ...
src/app/article/article.html
... ... @@ -7,6 +7,9 @@
7 7 <a href="#" class="btn btn-default btn-xs" ui-sref="main.cmsEdit({profile: ctrl.profile.identifier, id: ctrl.article.id})">
8 8 <i class="fa fa-pencil-square-o fa-fw fa-lg"></i> {{"article.actions.edit" | translate}}
9 9 </a>
  10 + <a href="#" class="btn btn-default btn-xs" ng-click="ctrl.delete()">
  11 + <i class="fa fa-trash-o fa-fw fa-lg" ng-click="ctrl.delete()"></i> {{"article.actions.delete" | translate}}
  12 + </a>
10 13 <noosfero-hotspot-article-toolbar [article]="ctrl.article"></noosfero-hotspot-article-toolbar>
11 14 <div class="page-info pull-right small text-muted">
12 15 <span class="time">
... ...
src/app/article/cms/article-editor/article-editor.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +import {ArticleEditorComponent} from './article-editor.component';
  2 +import {BasicEditorComponent} from "../basic-editor/basic-editor.component";
  3 +import {ComponentTestHelper, createClass} from '../../../../spec/component-test-helper';
  4 +import * as helpers from "../../../../spec/helpers";
  5 +
  6 +const htmlTemplate: string = '<article-editor [article]="ctrl.article"></article-editor>';
  7 +
  8 +describe("Components", () => {
  9 + describe("Article Editor Component", () => {
  10 +
  11 + let helper: ComponentTestHelper<ArticleEditorComponent>;
  12 + beforeEach(angular.mock.module("templates"));
  13 +
  14 + beforeEach((done) => {
  15 + let properties = { article: { type: "TextArticle" } };
  16 + let cls = createClass({
  17 + template: htmlTemplate,
  18 + directives: [ArticleEditorComponent, BasicEditorComponent],
  19 + properties: properties
  20 + });
  21 + helper = new ComponentTestHelper<ArticleEditorComponent>(cls, done);
  22 + });
  23 +
  24 + it("replace element with article basic editor when type is TextArticle", () => {
  25 + expect(helper.find("article-basic-editor").length).toEqual(1);
  26 + });
  27 + });
  28 +});
... ...
src/app/article/cms/article-editor/article-editor.component.ts
... ... @@ -16,10 +16,10 @@ export class ArticleEditorComponent {
16 16 private $compile: ng.ICompileService) { }
17 17  
18 18 ngOnInit() {
19   - let articleType = this.article.type.replace(/::/, '');
  19 + let articleType = this.article && this.article.type ? this.article.type.replace(/::/, '') : "TextArticle";
20 20 let specificDirective = `${articleType.charAt(0).toLowerCase()}${articleType.substring(1)}Editor`;
21 21 let directiveName = "article-basic-editor";
22   - if (this.$injector.has(specificDirective + 'Directive')) {
  22 + if (specificDirective !== "articleEditor" && this.$injector.has(specificDirective + 'Directive')) {
23 23 directiveName = specificDirective.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
24 24 }
25 25 this.$element.replaceWith(this.$compile('<' + directiveName + ' [article]="ctrl.article"></' + directiveName + '>')(this.$scope));
... ...
src/app/article/comment/comment.component.spec.ts
... ... @@ -7,16 +7,28 @@ const htmlTemplate: string = &#39;&lt;noosfero-comment [article]=&quot;ctrl.article&quot; [commen
7 7 describe("Components", () => {
8 8 describe("Comment Component", () => {
9 9  
10   - beforeEach(angular.mock.module("templates"));
  10 + let properties: any;
  11 + let notificationService = helpers.mocks.notificationService;
  12 + let commentService = jasmine.createSpyObj("commentService", ["removeFromArticle"]);
11 13  
  14 + beforeEach(angular.mock.module("templates"));
  15 + beforeEach(() => {
  16 + properties = {
  17 + article: { id: 1, accept_comments: true },
  18 + comment: { title: "title", body: "body" }
  19 + };
  20 + });
12 21  
13 22 function createComponent() {
14   - let providers = helpers.provideFilters("translateFilter");
  23 + let providers = [
  24 + helpers.createProviderToValue('NotificationService', notificationService),
  25 + helpers.createProviderToValue("CommentService", commentService)
  26 + ].concat(helpers.provideFilters("translateFilter"));
15 27  
16 28 @Component({ selector: 'test-container-component', directives: [CommentComponent], template: htmlTemplate, providers: providers })
17 29 class ContainerComponent {
18   - article = { id: 1 };
19   - comment = { title: "title", body: "body" };
  30 + article = properties['article'];
  31 + comment = properties['comment'];
20 32 }
21 33 return helpers.createComponentFromClass(ContainerComponent);
22 34 }
... ... @@ -52,5 +64,51 @@ describe(&quot;Components&quot;, () =&gt; {
52 64 done();
53 65 });
54 66 });
  67 +
  68 + it("display reply button", done => {
  69 + createComponent().then(fixture => {
  70 + expect(fixture.debugElement.queryAll(".comment .actions .reply").length).toEqual(1);
  71 + done();
  72 + });
  73 + });
  74 +
  75 + it("not display reply button when accept_comments is false", done => {
  76 + properties['article']['accept_comments'] = false;
  77 + createComponent().then(fixture => {
  78 + expect(fixture.debugElement.queryAll(".comment .actions .reply").length).toEqual(0);
  79 + done();
  80 + });
  81 + });
  82 +
  83 + it("does not show the Remove button if user is not allowed to remove", done => {
  84 + createComponent().then(fixture => {
  85 + let component: CommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  86 + component.allowRemove = () => false;
  87 + fixture.detectChanges();
  88 + expect(fixture.debugElement.queryAll("a.action.remove").length).toEqual(0);
  89 + done();
  90 + });
  91 + });
  92 +
  93 + it("shows the Remove button if user is allowed to remove", done => {
  94 + createComponent().then(fixture => {
  95 + let component: CommentComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  96 + component.allowRemove = () => true;
  97 + fixture.detectChanges();
  98 + expect(fixture.debugElement.queryAll("a.action.remove").length).toEqual(1);
  99 + done();
  100 + });
  101 + });
  102 +
  103 + it("call comment service to remove comment", done => {
  104 + notificationService.confirmation = (params: any, func: Function) => { func(); };
  105 + commentService.removeFromArticle = jasmine.createSpy("removeFromArticle").and.returnValue(Promise.resolve());
  106 + createComponent().then(fixture => {
  107 + let component = fixture.debugElement.componentViewChildren[0].componentInstance;
  108 + component.remove();
  109 + expect(commentService.removeFromArticle).toHaveBeenCalled();
  110 + done();
  111 + });
  112 + });
55 113 });
56 114 });
... ...
src/app/article/comment/comment.component.ts
1 1 import { Inject, Input, Component, Output, EventEmitter } from 'ng-forward';
2 2 import { PostCommentComponent } from "./post-comment/post-comment.component";
  3 +import { CommentService } from "../../../lib/ng-noosfero-api/http/comment.service";
  4 +import { NotificationService } from "../../shared/services/notification.service";
3 5  
4 6 @Component({
5 7 selector: 'noosfero-comment',
  8 + outputs: ['commentRemoved'],
6 9 templateUrl: 'app/article/comment/comment.html'
7 10 })
  11 +@Inject(CommentService, NotificationService)
8 12 export class CommentComponent {
9 13  
10 14 @Input() comment: noosfero.CommentViewModel;
11 15 @Input() article: noosfero.Article;
12 16 @Input() displayActions = true;
13 17 @Input() displayReplies = true;
  18 + @Output() commentRemoved: EventEmitter<Comment> = new EventEmitter<Comment>();
14 19  
15 20 showReply() {
16 21 return this.comment && this.comment.__show_reply === true;
17 22 }
18 23  
19   - constructor() {
20   - }
21   -
  24 + constructor(private commentService: CommentService,
  25 + private notificationService: NotificationService) { }
22 26  
23 27 reply() {
24 28 this.comment.__show_reply = !this.comment.__show_reply;
25 29 }
  30 +
  31 + allowRemove() {
  32 + return true;
  33 + }
  34 +
  35 + remove() {
  36 + this.notificationService.confirmation({ title: "comment.remove.confirmation.title", message: "comment.remove.confirmation.message" }, () => {
  37 + this.commentService.removeFromArticle(this.article, this.comment).then((result: noosfero.RestResult<noosfero.Comment>) => {
  38 + this.commentRemoved.next(this.comment);
  39 + this.notificationService.success({ title: "comment.remove.success.title", message: "comment.remove.success.message" });
  40 + });
  41 + });
  42 + }
26 43 }
... ...
src/app/article/comment/comment.html
... ... @@ -18,9 +18,14 @@
18 18 <div class="title">{{ctrl.comment.title}}</div>
19 19 <div class="body">{{ctrl.comment.body}}</div>
20 20 <div class="actions" ng-if="ctrl.displayActions">
21   - <a href="#" (click)="ctrl.reply()" class="small text-muted" ng-if="ctrl.article.accept_comments">
  21 + <a href="#" (click)="ctrl.reply()" class="action small text-muted reply" ng-if="ctrl.article.accept_comments">
  22 + <span class="bullet-separator">•</span>
22 23 {{"comment.reply" | translate}}
23 24 </a>
  25 + <a href="#" (click)="ctrl.remove()" class="action small text-muted remove" ng-if="ctrl.allowRemove()">
  26 + <span class="bullet-separator">•</span>
  27 + {{"comment.remove" | translate}}
  28 + </a>
24 29 </div>
25 30 </div>
26 31 <noosfero-comments [show-form]="ctrl.showReply()" [article]="ctrl.article" [parent]="ctrl.comment" ng-if="ctrl.displayReplies"></noosfero-comments>
... ...
src/app/article/comment/comment.scss
... ... @@ -10,6 +10,22 @@
10 10 .title {
11 11 font-weight: bold;
12 12 }
  13 + .actions {
  14 + .action {
  15 + text-decoration: none;
  16 + &:first-child {
  17 + .bullet-separator {
  18 + display: none;
  19 + }
  20 + }
  21 + .bullet-separator {
  22 + font-size: 8px;
  23 + vertical-align: middle;
  24 + margin: 3px;
  25 + color: #B6C2CA;
  26 + }
  27 + }
  28 + }
13 29 .media-left {
14 30 min-width: 40px;
15 31 }
... ...
src/app/article/comment/comments.component.spec.ts
... ... @@ -83,5 +83,26 @@ describe(&quot;Components&quot;, () =&gt; {
83 83 });
84 84 });
85 85  
  86 + it("remove comment when calling commentRemoved", done => {
  87 + createComponent().then(fixture => {
  88 + let component: CommentsComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  89 + let comment = { id: 1 };
  90 + component.comments = <any>[comment];
  91 + component.commentRemoved(<any>comment);
  92 + expect(component.comments).toEqual([]);
  93 + done();
  94 + });
  95 + });
  96 +
  97 + it("do nothing when call commentRemoved with a comment that doesn't belongs to the comments list", done => {
  98 + createComponent().then(fixture => {
  99 + let component: CommentsComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
  100 + let comment = { id: 1 };
  101 + component.comments = <any>[comment];
  102 + component.commentRemoved(<any>{ id: 2 });
  103 + expect(component.comments).toEqual([comment]);
  104 + done();
  105 + });
  106 + });
86 107 });
87 108 });
... ...
src/app/article/comment/comments.component.ts
... ... @@ -37,6 +37,13 @@ export class CommentsComponent {
37 37 this.resetShowReply();
38 38 }
39 39  
  40 + commentRemoved(comment: noosfero.Comment): void {
  41 + let index = this.comments.indexOf(comment, 0);
  42 + if (index >= 0) {
  43 + this.comments.splice(index, 1);
  44 + }
  45 + }
  46 +
40 47 private resetShowReply() {
41 48 this.comments.forEach((comment: noosfero.CommentViewModel) => {
42 49 comment.__show_reply = false;
... ...
src/app/article/comment/comments.html
... ... @@ -2,7 +2,7 @@
2 2 <noosfero-post-comment (comment-saved)="ctrl.commentAdded($event.detail)" ng-if="ctrl.showForm" [article]="ctrl.article" [parent]="ctrl.parent" [comment]="ctrl.newComment"></noosfero-post-comment>
3 3  
4 4 <div class="comments-list">
5   - <noosfero-comment ng-repeat="comment in ctrl.comments | orderBy: 'created_at':true" [comment]="comment" [article]="ctrl.article"></noosfero-comment>
  5 + <noosfero-comment (comment-removed)="ctrl.commentRemoved($event.detail)" ng-repeat="comment in ctrl.comments | orderBy: 'created_at':true" [comment]="comment" [article]="ctrl.article"></noosfero-comment>
6 6 </div>
7 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>
8 8 </div>
... ...
src/app/article/content-viewer/navbar-actions.html
1 1 <ul class="nav navbar-nav">
2   - <li ng-show="vm.profile">
3   - <a ng-show="vm.parentId" href="#" role="button" ui-sref="main.cms({profile: vm.profile.identifier, parent_id: vm.parentId})">
4   - <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}}
5   - </a>
6   - </li>
7   - <li ng-show="vm.profile">
8   - <a href="#" role="button" ui-sref="main.cms({profile: vm.profile.identifier, parent_id: vm.parentId, type: 'CommentParagraphPlugin::Discussion'})">
9   - <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}}
10   - </a>
11   - </li>
  2 + <li class="dropdown profile-menu" uib-dropdown>
  3 + <a class="btn dropdown-toggle" data-toggle="dropdown" uib-dropdown-toggle>
  4 + {{"navbar.content_viewer_actions.new_item" | translate}}
  5 + <i class="fa fa-caret-down"></i>
  6 + </a>
  7 + <ul class="dropdown-menu" uib-dropdown-menu ng-show="vm.profile">
  8 + <li ng-show="vm.parentId">
  9 + <a href="#" ui-sref="main.cms({profile: vm.profile.identifier, parent_id: vm.parentId})">
  10 + <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}}
  11 + </a>
  12 + </li>
  13 + <li>
  14 + <a href="#" ui-sref="main.cms({profile: vm.profile.identifier, parent_id: vm.parentId, type: 'CommentParagraphPlugin::Discussion'})">
  15 + <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_discussion" | translate}}
  16 + </a>
  17 + </li>
  18 + </ul>
  19 + </li>
  20 +
12 21 </ul>
  22 +
... ...
src/app/article/content-viewer/navbar-actions.spec.ts 0 → 100644
... ... @@ -0,0 +1,53 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Provider} from 'ng-forward';
  3 +import {ComponentTestHelper, createClass} from "./../../../spec/component-test-helper";
  4 +import {providers} from 'ng-forward/cjs/testing/providers';
  5 +import {ContentViewerActionsComponent} from '././content-viewer-actions.component';
  6 +import * as helpers from "../../../spec/helpers";
  7 +
  8 +const htmlTemplate: string = '<content-viewer-actions [article]="ctrl.article" [profile]="ctrl.profile"></content-viewer-actions>';
  9 +
  10 +describe("Components", () => {
  11 +
  12 + describe("Content Viewer Actions Component", () => {
  13 + let serviceMock = {
  14 + getEnvironmentPeople: (filters: any): any => {
  15 + return Promise.resolve([{ identifier: "person1" }]);
  16 + }
  17 + };
  18 + let providers = [
  19 + new Provider('ArticleService', { useValue: helpers.mocks.articleService }),
  20 + new Provider('ProfileService', { useValue: helpers.mocks.profileService })
  21 + ];
  22 +
  23 + let helper: ComponentTestHelper<ContentViewerActionsComponent>;
  24 +
  25 + beforeEach(angular.mock.module("templates"));
  26 +
  27 + /**
  28 + * The beforeEach procedure will initialize the helper and parse
  29 + * the component according to the given providers. Unfortunetly, in
  30 + * this mode, the providers and properties given to the construtor
  31 + * can't be overriden.
  32 + */
  33 + beforeEach((done) => {
  34 + // Create the component bed for the test. Optionally, this could be done
  35 + // in each test if one needs customization of these parameters per test
  36 + let cls = createClass({
  37 + template: htmlTemplate,
  38 + directives: [ContentViewerActionsComponent],
  39 + providers: providers,
  40 + properties: {}
  41 + });
  42 + helper = new ComponentTestHelper<ContentViewerActionsComponent>(cls, done);
  43 + });
  44 +
  45 + it("render the actions new item menu", () => {
  46 + expect(helper.all("a[class|='btn dropdown-toggle']")[0]).not.toBeNull();
  47 + });
  48 +
  49 + it("render two menu item actions", () => {
  50 + expect(helper.all("ul")[1].find("li").length).toBe(2);
  51 + });
  52 + });
  53 +});
... ...
src/app/environment/environment.html
1 1 <div class="environment-container">
2 2 <div class="row">
3   - <noosfero-boxes [boxes]="vm.boxes" [owner]="vm.environment"></noosfero-boxes>
  3 + <noosfero-boxes ng-if="vm.boxes" [boxes]="vm.boxes" [owner]="vm.environment"></noosfero-boxes>
4 4 </div>
5 5 </div>
... ...
src/app/layout/blocks/display-content/display-content-block.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,75 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Provider, provide} from 'ng-forward';
  3 +import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
  4 +import {providers} from 'ng-forward/cjs/testing/providers';
  5 +import {DisplayContentBlockComponent} from './display-content-block.component';
  6 +import * as helpers from './../../../../spec/helpers';
  7 +
  8 +const htmlTemplate: string = '<noosfero-display-content-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-display-content-block>';
  9 +
  10 +describe("Components", () => {
  11 +
  12 + describe("Display Content Block Component", () => {
  13 + let state = jasmine.createSpyObj("state", ["go"]);
  14 + let providers = [
  15 + provide('ArticleService', {
  16 + useValue: helpers.mocks.articleService
  17 + }),
  18 + provide('$state', { useValue: state })
  19 + ].concat(helpers.provideFilters("translateFilter"));
  20 +
  21 + let sections: noosfero.Section[] = [
  22 + { value: 'abstract', checked: 'abstract'},
  23 + { value: 'title', checked: 'title' }
  24 + ];
  25 + let settings: noosfero.Settings = {
  26 + limit: 6,
  27 + sections: sections
  28 + };
  29 +
  30 + let helper: ComponentTestHelper<DisplayContentBlockComponent>;
  31 +
  32 + beforeEach(angular.mock.module("templates"));
  33 +
  34 + /**
  35 + * The beforeEach procedure will initialize the helper and parse
  36 + * the component according to the given providers. Unfortunetly, in
  37 + * this mode, the providers and properties given to the construtor
  38 + * can't be overriden.
  39 + */
  40 + beforeEach((done) => {
  41 + // Create the component bed for the test. Optionally, this could be done
  42 + // in each test if one needs customization of these parameters per test
  43 + let cls = createClass({
  44 + template: htmlTemplate,
  45 + directives: [DisplayContentBlockComponent],
  46 + providers: providers,
  47 + properties: {
  48 + block: {
  49 + settings: settings
  50 + }
  51 + }
  52 + });
  53 + helper = new ComponentTestHelper<DisplayContentBlockComponent>(cls, done);
  54 + });
  55 +
  56 + it("verify settings is injected", () => {
  57 + expect(helper.component.block).not.toBeNull;
  58 + expect(helper.component.block.settings).not.toBeNull;
  59 + expect(helper.component.block.settings.limit).toEqual(6);
  60 + expect(helper.component.block.settings.sections.length).toEqual(3);
  61 + });
  62 +
  63 + it("verify abstract is displayed", () => {
  64 + expect(helper.all("div[ng-bind-html|='article.abstract']")[0]).not.toBeNull;
  65 + });
  66 +
  67 + it("verify title is displayed", () => {
  68 + expect(helper.all("div > h5")[0]).not.toBeNull;
  69 + });
  70 +
  71 + it("verify body is not displayed", () => {
  72 + expect(helper.all("div[ng-bind-html|='article.body']")[0]).toBeNull;
  73 + });
  74 + });
  75 +});
... ...
src/app/layout/blocks/display-content/display-content-block.component.ts 0 → 100644
... ... @@ -0,0 +1,54 @@
  1 +import {Input, Inject, Component} from "ng-forward";
  2 +import {ArticleService} from "../../../../lib/ng-noosfero-api/http/article.service";
  3 +
  4 +@Component({
  5 + selector: "noosfero-display-content-block",
  6 + templateUrl: 'app/layout/blocks/display-content/display-content-block.html',
  7 +})
  8 +@Inject(ArticleService, "$state")
  9 +export class DisplayContentBlockComponent {
  10 +
  11 + @Input() block: noosfero.Block;
  12 + @Input() owner: noosfero.Profile;
  13 +
  14 + profile: noosfero.Profile;
  15 + articles: noosfero.Article[];
  16 + sections: noosfero.Section[];
  17 +
  18 + documentsLoaded: boolean = false;
  19 +
  20 + constructor(private articleService: ArticleService, private $state: ng.ui.IStateService) {}
  21 +
  22 + ngOnInit() {
  23 + this.profile = this.owner;
  24 + let limit = ((this.block && this.block.settings) ? this.block.settings.limit : null) || 5;
  25 + this.articleService.getByProfile(this.profile, { content_type: 'TinyMceArticle', per_page: limit })
  26 + .then((result: noosfero.RestResult<noosfero.Article[]>) => {
  27 + this.articles = <noosfero.Article[]>result.data;
  28 + this.sections = this.block.settings.sections;
  29 + // Add sections not defined by Noosfero API
  30 + this.addDefaultSections();
  31 + this.documentsLoaded = true;
  32 + });
  33 + }
  34 +
  35 + /**
  36 + * This configuration doesn't exists on Noosfero. Statically typing here.
  37 + */
  38 + private addDefaultSections() {
  39 + let author: noosfero.Section = <noosfero.Section>{ value: 'author', checked: 'author' };
  40 + this.sections.push(author);
  41 + }
  42 +
  43 + /**
  44 + * Returns whether a settings section should be displayed.
  45 + *
  46 + */
  47 + private display(section_name: string): boolean {
  48 + let section: noosfero.Section = this.sections.find( function(section: noosfero.Section) {
  49 + return section.value === section_name;
  50 + });
  51 + return section !== undefined && section.checked !== undefined;
  52 + }
  53 +
  54 +}
... ...
src/app/layout/blocks/display-content/display-content-block.html 0 → 100644
... ... @@ -0,0 +1,46 @@
  1 +<div class="{{ctrl.type}}-block">
  2 + <div ng-repeat="article in ctrl.articles" ui-sref="main.profile.page({profile: ctrl.profile.identifier, page: article.path})"" class="article">
  3 + <!-- Article Title -->
  4 + <div class="page-header" ng-if="ctrl.display('title')">
  5 + <h5 class="title media-heading" ng-bind="article.title"></h3>
  6 + </div>
  7 +
  8 + <div class="sub-header clearfix">
  9 + <!-- Article Abstract and Read More Link -->
  10 + <div class="post-lead" ng-if="ctrl.display('abstract')">
  11 + <div ng-bind-html="article.abstract"></div>
  12 + <a href="#" ui-sref="main.profile.page({profile: ctrl.profile.identifier, page: article.path})">
  13 + <i class="fa fa-pencil-square-o fa-fw fa-lg"></i> {{"article.actions.read_more" | translate}}
  14 + </a>
  15 + </div>
  16 + <div class="page-info pull-right small text-muted" ng-if="ctrl.display('publish_date')">
  17 + <!-- Article Published Date -->
  18 + <span class="time">
  19 + <i class="fa fa-clock-o"></i> <span am-time-ago="article.created_at | dateFormat"></span>
  20 + </span>
  21 + <!-- Article Author -->
  22 + <span class="author" ng-if="ctrl.display('author')">
  23 + <i class="fa fa-user"></i>
  24 + <a ui-sref="main.profile.home({profile: article.author.identifier})" ng-if="article.author">
  25 + <span class="author-name" ng-bind="article.author.name"></span>
  26 + </a>
  27 + </span>
  28 + </div>
  29 + </div>
  30 +
  31 + <div class="post-lead">
  32 + <!-- Article Image -->
  33 + <img ng-show="ctrl.display('image')" ng-src="{{article.image.url}}" class="img-responsive article-image">
  34 + <!-- Article Body -->
  35 + <div ng-bind-html="article.body" ng-show="ctrl.display('body')"></div>
  36 + </div>
  37 +
  38 + <!-- Article Tags -->
  39 + <div ng-if="ctrl.display('tags')" class="post-lead">
  40 + <div class="label" ng-repeat="tag in article.tag_list">
  41 + <span class="badge" ng-bind="tag"></span>
  42 + </div>
  43 + </div>
  44 +
  45 + </div>
  46 +</div>
... ...
src/app/layout/blocks/display-content/display-content-block.scss 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +.members-block {
  2 + .member {
  3 + img, i.profile-image {
  4 + width: 60px;
  5 + }
  6 + img {
  7 + display: inline-block;
  8 + vertical-align: top;
  9 + }
  10 + i.profile-image {
  11 + text-align: center;
  12 + background-color: #889DB1;
  13 + color: #F1F1F1;
  14 + font-size: 4.5em;
  15 + }
  16 + }
  17 +}
... ...
src/app/layout/blocks/display-content/index.ts 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./display-content-block.component";
... ...
src/app/layout/blocks/recent-documents/recent-documents-block.component.spec.ts
... ... @@ -11,9 +11,9 @@ describe(&quot;Components&quot;, () =&gt; {
11 11 describe("Recent Documents Block Component", () => {
12 12  
13 13 let settingsObj = {};
14   - let mockedArticleService = {
15   - getByProfile: (profile: noosfero.Profile, filters: any): any => {
16   - return Promise.resolve({ data: [{ name: "article1" }], headers: (name: string) => { return name; } });
  14 + let mockedBlockService = {
  15 + getApiContent: (block: noosfero.Block): any => {
  16 + return Promise.resolve({ articles: [{ name: "article1" }], headers: (name: string) => { return name; } });
17 17 }
18 18 };
19 19 let profile = { name: 'profile-name' };
... ... @@ -25,8 +25,8 @@ describe(&quot;Components&quot;, () =&gt; {
25 25 function getProviders() {
26 26 return [
27 27 new Provider('$state', { useValue: state }),
28   - new Provider('ArticleService', {
29   - useValue: mockedArticleService
  28 + new Provider('BlockService', {
  29 + useValue: mockedBlockService
30 30 }),
31 31 ].concat(provideFilters("truncateFilter", "stripTagsFilter"));
32 32 }
... ... @@ -44,7 +44,7 @@ describe(&quot;Components&quot;, () =&gt; {
44 44 }
45 45  
46 46  
47   - it("get recent documents from the article service", done => {
  47 + it("get recent documents from the block service", done => {
48 48 tcb.createAsync(getComponent()).then(fixture => {
49 49 let recentDocumentsBlock: RecentDocumentsBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
50 50 expect(recentDocumentsBlock.documents).toEqual([{ name: "article1" }]);
... ... @@ -61,20 +61,5 @@ describe(&quot;Components&quot;, () =&gt; {
61 61 });
62 62 });
63 63  
64   - it("it uses default limit 5 if not defined on block", done => {
65   - settingsObj = null;
66   - mockedArticleService = jasmine.createSpyObj("mockedArticleService", ["getByProfile"]);
67   - (<any>mockedArticleService).mocked = true;
68   - let thenMocked = jasmine.createSpy("then");
69   - mockedArticleService.getByProfile = jasmine.createSpy("getByProfile").and.returnValue({then: thenMocked});
70   - let getByProfileFunct = mockedArticleService.getByProfile;
71   - tcb.createAsync(getComponent()).then(fixture => {
72   - let recentDocumentsBlock: RecentDocumentsBlockComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
73   - recentDocumentsBlock.openDocument({ path: "path", profile: { identifier: "identifier" } });
74   - expect(getByProfileFunct).toHaveBeenCalledWith(profile, { content_type: 'TinyMceArticle', per_page: 5 });
75   - done();
76   - });
77   - });
78   -
79 64 });
80 65 });
... ...
src/app/layout/blocks/recent-documents/recent-documents-block.component.ts
1 1 import {Component, Inject, Input} from "ng-forward";
2   -import {ArticleService} from "../../../../lib/ng-noosfero-api/http/article.service";
  2 +import {BlockService} from "../../../../lib/ng-noosfero-api/http/block.service";
3 3  
4 4 @Component({
5 5 selector: "noosfero-recent-documents-block",
6 6 templateUrl: 'app/layout/blocks/recent-documents/recent-documents-block.html'
7 7 })
8   -@Inject(ArticleService, "$state")
  8 +@Inject(BlockService, "$state")
9 9 export class RecentDocumentsBlockComponent {
10 10  
11 11 @Input() block: any;
... ... @@ -13,23 +13,15 @@ export class RecentDocumentsBlockComponent {
13 13  
14 14 profile: any;
15 15 documents: any;
16   -
17 16 documentsLoaded: boolean = false;
18 17  
19   - constructor(private articleService: ArticleService, private $state: any) {
20   - }
  18 + constructor(private blockService: BlockService, private $state: any) { }
21 19  
22 20 ngOnInit() {
23 21 this.profile = this.owner;
24 22 this.documents = [];
25   -
26   - let limit = ((this.block && this.block.settings) ? this.block.settings.limit : null) || 5;
27   - // FIXME get all text articles
28   - // FIXME make the getByProfile a generic method where we tell the type passing a class TinyMceArticle
29   - // and the promise should be of type TinyMceArticle[], per example
30   - this.articleService.getByProfile(this.profile, { content_type: 'TinyMceArticle', per_page: limit })
31   - .then((result: noosfero.RestResult<noosfero.Article[]>) => {
32   - this.documents = <noosfero.Article[]>result.data;
  23 + this.blockService.getApiContent(this.block).then((content: any) => {
  24 + this.documents = content.articles;
33 25 this.documentsLoaded = true;
34 26 });
35 27 }
... ...
src/app/layout/boxes/box.html
1 1 <div ng-class="{'col-md-2-5': box.position!=1, 'col-md-7': box.position==1}">
2   - <div ng-repeat="block in box.blocks | orderBy: 'position'" class="panel panel-default block {{block.type | lowercase}}" >
  2 + <div ng-repeat="block in box.blocks | displayBlocks:ctrl.isHomepage:ctrl.currentUser | orderBy: 'position'" class="panel panel-default block {{block.type | lowercase}}" >
3 3 <div class="panel-heading" ng-show="block.title">
4 4 <h3 class="panel-title">{{block.title}}</h3>
5 5 </div>
6   - <div class="panel-body">
  6 + <div class="panel-body {{block.type | lowercase}}" >
7 7 <noosfero-block [block]="block" [owner]="ctrl.owner"></noosfero-block>
8 8 </div>
9 9 </div>
... ...
src/app/layout/boxes/boxes.component.spec.ts
1 1 import {Component} from 'ng-forward';
2   -
3 2 import {BoxesComponent} from './boxes.component';
4   -
5   -import {
6   - createComponentFromClass,
7   - quickCreateComponent,
8   - provideEmptyObjects,
9   - createProviderToValue,
10   - provideFilters
11   -} from "../../../spec/helpers";
  3 +import * as helpers from "../../../spec/helpers";
  4 +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
12 5  
13 6 // this htmlTemplate will be re-used between the container components in this spec file
14 7 const htmlTemplate: string = '<noosfero-boxes [boxes]="ctrl.boxes" [owner]="ctrl.profile"></noosfero-blog>';
... ... @@ -16,49 +9,79 @@ const htmlTemplate: string = &#39;&lt;noosfero-boxes [boxes]=&quot;ctrl.boxes&quot; [owner]=&quot;ctrl
16 9  
17 10 describe("Boxes Component", () => {
18 11  
  12 + let helper: ComponentTestHelper<BoxesComponent>;
19 13 beforeEach(() => {
20 14 angular.mock.module("templates");
21 15 });
22 16  
23   - @Component({
24   - selector: 'test-container-component',
25   - template: htmlTemplate,
26   - directives: [BoxesComponent],
27   - providers: []
28   - })
29   - class BoxesContainerComponent {
30   - boxes: noosfero.Box[] = [
  17 + let properties = {
  18 + boxes: [
31 19 { id: 1, position: 1 },
32 20 { id: 2, position: 2 }
33   - ];
34   -
35   - owner: noosfero.Profile = <noosfero.Profile> {
  21 + ],
  22 + owner: {
36 23 id: 1,
37 24 identifier: 'profile-name',
38 25 type: 'Person'
39   - };
40   - }
  26 + }
  27 + };
  28 + beforeEach((done) => {
  29 + let cls = createClass({
  30 + template: htmlTemplate,
  31 + directives: [BoxesComponent],
  32 + properties: properties,
  33 + providers: [
  34 + helpers.createProviderToValue('SessionService', helpers.mocks.sessionWithCurrentUser({})),
  35 + helpers.createProviderToValue('AuthService', helpers.mocks.authService),
  36 + helpers.createProviderToValue('$state', state),
  37 + helpers.createProviderToValue('TranslatorService', translatorService)
  38 + ]
  39 + });
  40 + helper = new ComponentTestHelper<BoxesComponent>(cls, done);
  41 + });
41 42  
42   - it("renders boxes into a container", (done: Function) => {
43   - createComponentFromClass(BoxesContainerComponent).then((fixture) => {
44   - let boxesHtml = fixture.debugElement;
45   - expect(boxesHtml.query('div.col-md-7').length).toEqual(1);
46   - expect(boxesHtml.query('div.col-md-2-5').length).toEqual(1);
  43 + let translatorService = jasmine.createSpyObj("translatorService", ["currentLanguage"]);
  44 + let state = jasmine.createSpyObj("state", ["current"]);
  45 + state.current = { name: "" };
47 46  
48   - done();
49   - });
  47 + it("renders boxes into a container", () => {
  48 + expect(helper.find('div.col-md-7').length).toEqual(1);
  49 + expect(helper.find('div.col-md-2-5').length).toEqual(1);
  50 + });
  51 +
  52 + it("check the boxes order", () => {
  53 + expect(helper.component.boxesOrder(properties['boxes'][0])).toEqual(1);
  54 + expect(helper.component.boxesOrder(properties['boxes'][1])).toEqual(0);
50 55 });
51 56  
52   - it("check the boxes order", (done: Function) => {
53   - createComponentFromClass(BoxesContainerComponent).then((fixture) => {
  57 + it("set isHomepage as false by default", () => {
  58 + expect(helper.component.isHomepage).toBeFalsy();
  59 + });
54 60  
55   - let boxesComponent: BoxesComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
56   - let boxesContainer: BoxesContainerComponent = fixture.componentInstance;
  61 + it("set isHomepage as true when in profile home page", () => {
  62 + state.current = { name: "main.profile.home" };
  63 + helper.component.ngOnInit();
  64 + expect(helper.component.isHomepage).toBeTruthy();
  65 + });
57 66  
58   - expect(boxesComponent.boxesOrder(boxesContainer.boxes[0])).toEqual(1);
59   - expect(boxesComponent.boxesOrder(boxesContainer.boxes[1])).toEqual(0);
  67 + it("set isHomepage as true when in profile info page", () => {
  68 + state.current = { name: "main.profile.info" };
  69 + helper.component.ngOnInit();
  70 + expect(helper.component.isHomepage).toBeTruthy();
  71 + });
60 72  
61   - done();
62   - });
  73 + it("set isHomepage as true when in profile page", () => {
  74 + state.current = { name: "main.profile.page" };
  75 + state.params = { page: "/page" };
  76 + (<noosfero.Profile>helper.component.owner).homepage = '/page';
  77 + helper.component.ngOnInit();
  78 + expect(helper.component.isHomepage).toBeTruthy();
  79 + });
  80 +
  81 + it("set isHomepage as true when in environment home page", () => {
  82 + state.current = { name: "main.environment.home" };
  83 + helper.component.owner = <noosfero.Environment>{};
  84 + helper.component.ngOnInit();
  85 + expect(helper.component.isHomepage).toBeTruthy();
63 86 });
64 87 });
... ...
src/app/layout/boxes/boxes.component.ts
1 1 import {Input, Inject, Component} from 'ng-forward';
  2 +import {SessionService, AuthService, AuthEvents} from "../../login";
  3 +import {DisplayBlocks} from "./display-blocks.filter";
2 4  
3 5 @Component({
4 6 selector: "noosfero-boxes",
5   - templateUrl: "app/layout/boxes/boxes.html"
  7 + templateUrl: "app/layout/boxes/boxes.html",
  8 + directives: [DisplayBlocks]
6 9 })
  10 +@Inject("SessionService", 'AuthService', "$state", "$rootScope")
7 11 export class BoxesComponent {
8 12  
9 13 @Input() boxes: noosfero.Box[];
10   - @Input() owner: noosfero.Profile;
  14 + @Input() owner: noosfero.Profile | noosfero.Environment;
  15 +
  16 + currentUser: noosfero.User;
  17 + isHomepage = true;
  18 +
  19 + constructor(private session: SessionService,
  20 + private authService: AuthService,
  21 + private $state: ng.ui.IStateService,
  22 + private $rootScope: ng.IRootScopeService) {
  23 +
  24 + this.currentUser = this.session.currentUser();
  25 + this.authService.subscribe(AuthEvents[AuthEvents.loginSuccess], () => {
  26 + this.currentUser = this.session.currentUser();
  27 + this.verifyHomepage();
  28 + });
  29 + this.authService.subscribe(AuthEvents[AuthEvents.logoutSuccess], () => {
  30 + this.currentUser = this.session.currentUser();
  31 + this.verifyHomepage();
  32 + });
  33 + this.$rootScope.$on("$stateChangeSuccess", (event: ng.IAngularEvent, toState: ng.ui.IState) => {
  34 + this.verifyHomepage();
  35 + });
  36 + }
  37 +
  38 + ngOnInit() {
  39 + this.verifyHomepage();
  40 + }
11 41  
12 42 boxesOrder(box: noosfero.Box) {
13 43 if (box.position === 2) return 0;
14 44 return box.position;
15 45 }
  46 +
  47 + private verifyHomepage() {
  48 + if (this.owner && ["Profile", "Community", "Person"].indexOf((<any>this.owner)['type']) >= 0) {
  49 + let profile = <noosfero.Profile>this.owner;
  50 + this.isHomepage = this.$state.current.name === "main.profile.home";
  51 + if (profile.homepage) {
  52 + this.isHomepage = this.isHomepage ||
  53 + (this.$state.current.name === "main.profile.page" && profile.homepage === this.$state.params['page']);
  54 + } else {
  55 + this.isHomepage = this.isHomepage || this.$state.current.name === "main.profile.info";
  56 + }
  57 + } else {
  58 + this.isHomepage = this.$state.current.name === "main.environment.home";
  59 + }
  60 + }
16 61 }
... ...
src/app/layout/boxes/display-blocks.filter.spec.ts 0 → 100644
... ... @@ -0,0 +1,100 @@
  1 +import {quickCreateComponent} from "../../../spec/helpers";
  2 +import {DisplayBlocks} from './display-blocks.filter';
  3 +
  4 +describe("Filters", () => {
  5 + describe("Display Blocks Filter", () => {
  6 +
  7 + let translatorService = jasmine.createSpyObj("translatorService", ["currentLanguage"]);
  8 +
  9 + it("not fail when blocks is null", done => {
  10 + let filter = new DisplayBlocks(translatorService);
  11 + expect(filter.transform(null, true, <noosfero.User>{})).toEqual([]);
  12 + done();
  13 + });
  14 +
  15 + it("return blocks when no setting is passed", done => {
  16 + let blocks = [{}];
  17 + let filter = new DisplayBlocks(translatorService);
  18 + expect(filter.transform(<any>blocks, true, <noosfero.User>{})).toEqual(blocks);
  19 + done();
  20 + });
  21 +
  22 + it("return blocks when no display is passed", done => {
  23 + let blocks = [{ setting: {} }];
  24 + let filter = new DisplayBlocks(translatorService);
  25 + expect(filter.transform(<any>blocks, true, <noosfero.User>{})).toEqual(blocks);
  26 + done();
  27 + });
  28 +
  29 + it("filter invisible blocks", done => {
  30 + let blocks = [{ settings: { display: "never" } }];
  31 + let filter = new DisplayBlocks(translatorService);
  32 + expect(filter.transform(<any>blocks, true, <noosfero.User>{})).toEqual([]);
  33 + done();
  34 + });
  35 +
  36 + it("filter blocks with except_home_page in homepage", done => {
  37 + let blocks = [{ settings: { display: "except_home_page" } }];
  38 + let filter = new DisplayBlocks(translatorService);
  39 + expect(filter.transform(<any>blocks, true, <noosfero.User>{})).toEqual([]);
  40 + done();
  41 + });
  42 +
  43 + it("filter blocks with home_page_only outside homepage", done => {
  44 + let blocks = [{ settings: { display: "home_page_only" } }];
  45 + let filter = new DisplayBlocks(translatorService);
  46 + expect(filter.transform(<any>blocks, false, <noosfero.User>{})).toEqual([]);
  47 + done();
  48 + });
  49 +
  50 + it("show all blocks when display_user is all for logged user", done => {
  51 + let blocks = [{ settings: { display_user: "all" } }];
  52 + let filter = new DisplayBlocks(translatorService);
  53 + expect(filter.transform(<any>blocks, true, <noosfero.User>{})).toEqual(blocks);
  54 + done();
  55 + });
  56 +
  57 + it("show all blocks when display_user is all for not logged user", done => {
  58 + let blocks = [{ settings: { display_user: "all" } }];
  59 + let filter = new DisplayBlocks(translatorService);
  60 + expect(filter.transform(<any>blocks, true, null)).toEqual(blocks);
  61 + done();
  62 + });
  63 +
  64 + it("filter blocks when display_user is logged for not logged user", done => {
  65 + let blocks = [{ settings: { display_user: "logged" } }];
  66 + let filter = new DisplayBlocks(translatorService);
  67 + expect(filter.transform(<any>blocks, true, null)).toEqual([]);
  68 + done();
  69 + });
  70 +
  71 + it("filter blocks when display_user is not_logged for logged user", done => {
  72 + let blocks = [{ settings: { display_user: "not_logged" } }];
  73 + let filter = new DisplayBlocks(translatorService);
  74 + expect(filter.transform(<any>blocks, true, <noosfero.User>{})).toEqual([]);
  75 + done();
  76 + });
  77 +
  78 + it("filter blocks with different language", done => {
  79 + let blocks = [{ settings: { language: "en" } }];
  80 + translatorService.currentLanguage = jasmine.createSpy("currentLanguage").and.returnValue("pt");
  81 + let filter = new DisplayBlocks(translatorService);
  82 + expect(filter.transform(<any>blocks, true, <noosfero.User>{})).toEqual([]);
  83 + done();
  84 + });
  85 +
  86 + it("filter blocks when hide is true", done => {
  87 + let blocks = [{ hide: true }];
  88 + let filter = new DisplayBlocks(translatorService);
  89 + expect(filter.transform(<any>blocks, true, null)).toEqual([]);
  90 + done();
  91 + });
  92 +
  93 + it("not filter blocks when hide is not true", done => {
  94 + let blocks = [{ id: 1, hide: false }, { id: 2 }];
  95 + let filter = new DisplayBlocks(translatorService);
  96 + expect(filter.transform(<any>blocks, true, null)).toEqual(blocks);
  97 + done();
  98 + });
  99 + });
  100 +});
... ...
src/app/layout/boxes/display-blocks.filter.ts 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +import {Pipe, Inject} from "ng-forward";
  2 +import {TranslatorService} from "../../shared/services/translator.service";
  3 +
  4 +@Pipe("displayBlocks")
  5 +@Inject(TranslatorService)
  6 +export class DisplayBlocks {
  7 +
  8 + constructor(private translatorService: TranslatorService) { }
  9 +
  10 + transform(blocks: noosfero.Block[], isHomepage: boolean, currentUser: noosfero.User) {
  11 + let selected: noosfero.Block[] = [];
  12 + blocks = blocks || [];
  13 + for (let block of blocks) {
  14 + if (this.visible(block, isHomepage) && this.displayToUser(block, currentUser) &&
  15 + this.displayOnLanguage(block, this.translatorService.currentLanguage())
  16 + && !block.hide) {
  17 + selected.push(block);
  18 + }
  19 + }
  20 + return selected;
  21 + }
  22 +
  23 + private visible(block: noosfero.Block, isHomepage: boolean) {
  24 + let display = block.settings ? (<any>block.settings)['display'] : null;
  25 + return !display || ((isHomepage ? display !== "except_home_page" : display !== "home_page_only") && display !== "never");
  26 + }
  27 +
  28 + private displayToUser(block: noosfero.Block, currentUser: noosfero.User) {
  29 + let displayUser = block.settings ? (<any>block.settings)['display_user'] : null;
  30 + return !displayUser || displayUser === "all" ||
  31 + (currentUser ? displayUser === "logged" : displayUser === "not_logged");
  32 + }
  33 +
  34 + private displayOnLanguage(block: noosfero.Block, language: string) {
  35 + let displayLanguage = block.settings ? (<any>block.settings)['language'] : null;
  36 + return !displayLanguage || displayLanguage === "all" ||
  37 + language === displayLanguage;
  38 + }
  39 +}
... ...
src/app/layout/navbar/navbar.scss
... ... @@ -7,7 +7,8 @@
7 7 .navbar-brand {
8 8 .noosfero-logo {
9 9 background: url("../assets/images/logo-noosfero.png") no-repeat;
10   - padding: 0px 62px 64px 15px;
  10 + padding: 0px 62px 28px 15px;
  11 + background-size: 46px;
11 12 }
12 13 }
13 14  
... ...
src/app/layout/services/body-state-classes.service.ts
1 1 import {Directive, Inject, Injectable} from "ng-forward";
2   -import {AuthEvents} from "./../../login/auth-events";
  2 +import {AuthEvents} from "../../login/auth-events";
3 3 import {AuthService} from "./../../login/auth.service";
4 4 import {HtmlUtils} from "../html-utils";
5 5 import {INgForwardJQuery} from 'ng-forward/cjs/util/jqlite-extensions';
... ...
src/app/main/main.component.ts
... ... @@ -10,15 +10,16 @@ import {BlockComponent} from &quot;../layout/blocks/block.component&quot;;
10 10 import {EnvironmentComponent} from "../environment/environment.component";
11 11 import {EnvironmentHomeComponent} from "../environment/environment-home.component";
12 12 import {PeopleBlockComponent} from "../layout/blocks/people/people-block.component";
13   -import {LinkListBlockComponent} from "./../layout/blocks/link-list/link-list-block.component";
  13 +import {DisplayContentBlockComponent} from "../layout/blocks/display-content/display-content-block.component";
  14 +import {LinkListBlockComponent} from "../layout/blocks/link-list/link-list-block.component";
14 15 import {RecentDocumentsBlockComponent} from "../layout/blocks/recent-documents/recent-documents-block.component";
15 16 import {ProfileImageBlockComponent} from "../layout/blocks/profile-image/profile-image-block.component";
16 17 import {RawHTMLBlockComponent} from "../layout/blocks/raw-html/raw-html-block.component";
17 18 import {StatisticsBlockComponent} from "../layout/blocks/statistics/statistics-block.component";
18 19 import {TagsBlockComponent} from "../layout/blocks/tags/tags-block.component";
19 20  
20   -import {MembersBlockComponent} from "./../layout/blocks/members/members-block.component";
21   -import {CommunitiesBlockComponent} from "./../layout/blocks/communities/communities-block.component";
  21 +import {MembersBlockComponent} from "../layout/blocks/members/members-block.component";
  22 +import {CommunitiesBlockComponent} from "../layout/blocks/communities/communities-block.component";
22 23  
23 24 import {LoginBlockComponent} from "../layout/blocks/login-block/login-block.component";
24 25  
... ... @@ -95,7 +96,7 @@ export class EnvironmentContent {
95 96 template: '<div ng-view></div>',
96 97 directives: [
97 98 ArticleBlogComponent, ArticleViewComponent, BoxesComponent, BlockComponent,
98   - EnvironmentComponent, PeopleBlockComponent,
  99 + EnvironmentComponent, PeopleBlockComponent, DisplayContentBlockComponent,
99 100 LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent,
100 101 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, SidebarComponent, ProfileImageBlockComponent,
101 102 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent, StatisticsBlockComponent,
... ...
src/app/profile/navbar-actions.html 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +<ul class="nav navbar-nav">
  2 + <li class="dropdown profile-menu" uib-dropdown>
  3 + <a class="btn dropdown-toggle" data-toggle="dropdown" uib-dropdown-toggle>
  4 + {{"navbar.profile_actions.new_item" | translate}}
  5 + <i class="fa fa-caret-down"></i>
  6 + </a>
  7 + <ul class="dropdown-menu" uib-dropdown-menu ng-show="vm.profile">
  8 + <!-- FIXED HERE BUT SHOULD BE A HOTSPOT TO INCLUDE LINKS FROM THE PLUGIN -->
  9 + <li>
  10 + <a href="#" ui-sref="main.cms({profile: vm.profile.identifier, parent_id: null, type: 'CommentParagraphPlugin::Discussion'})">
  11 + <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.profile_actions.new_discussion" | translate}}
  12 + </a>
  13 + </li>
  14 + </ul>
  15 + </li>
  16 +
  17 +</ul>
  18 +
... ...
src/app/profile/profile-actions.component.ts 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +import {Component, Inject, provide} from "ng-forward";
  2 +import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service";
  3 +
  4 +@Component({
  5 + selector: "profile-actions",
  6 + templateUrl: "app/article/content-viewer/navbar-actions.html",
  7 + providers: [
  8 + provide('profileService', { useClass: ProfileService })
  9 + ]
  10 +})
  11 +@Inject(ProfileService)
  12 +export class ProfileActionsComponent {
  13 + profile: noosfero.Profile;
  14 + parentId: number;
  15 +
  16 + constructor(profileService: ProfileService) {
  17 + profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
  18 + this.profile = profile;
  19 + });
  20 + }
  21 +}
... ...
src/app/profile/profile-home.component.ts
... ... @@ -18,8 +18,10 @@ export class ProfileHomeComponent {
18 18 return profileService.getHomePage(<number>this.profile.id, { fields: 'path' });
19 19 }).then((response: restangular.IResponse) => {
20 20 if (response.data.article) {
  21 + this.profile.homepage = response.data.article.path;
21 22 $state.transitionTo('main.profile.page', { page: response.data.article.path, profile: this.profile.identifier }, { location: false });
22 23 } else {
  24 + this.profile.homepage = null;
23 25 $state.transitionTo('main.profile.info', { profile: this.profile.identifier }, { location: false });
24 26 }
25 27 });
... ...
src/app/profile/profile.component.ts
... ... @@ -9,7 +9,7 @@ import {ActivitiesComponent} from &quot;./activities/activities.component&quot;;
9 9 import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service";
10 10 import {NotificationService} from "../shared/services/notification.service";
11 11 import {MyProfileComponent} from "./myprofile.component";
12   -
  12 +import {ProfileActionsComponent} from "./profile-actions.component";
13 13  
14 14 /**
15 15 * @ngdoc controller
... ... @@ -37,13 +37,25 @@ import {MyProfileComponent} from &quot;./myprofile.component&quot;;
37 37 templateUrl: "app/profile/info/profile-info.html",
38 38 controller: ProfileInfoComponent,
39 39 controllerAs: "vm"
  40 + },
  41 + "actions@main": {
  42 + templateUrl: "app/profile/navbar-actions.html",
  43 + controller: ProfileActionsComponent,
  44 + controllerAs: "vm"
40 45 }
41 46 }
42 47 },
43 48 {
44 49 name: 'main.profile.settings',
45 50 url: "^/myprofile/:profile",
46   - component: MyProfileComponent
  51 + component: MyProfileComponent,
  52 + views: {
  53 + "actions@main": {
  54 + templateUrl: "app/profile/navbar-actions.html",
  55 + controller: ProfileActionsComponent,
  56 + controllerAs: "vm"
  57 + }
  58 + }
47 59 },
48 60 {
49 61 name: 'main.cms',
... ...
src/app/profile/profile.html
1 1 <div class="profile-container">
2 2 <div class="profile-header" ng-bind-html="vm.profile.custom_header"></div>
3 3 <div class="row">
4   - <noosfero-boxes [boxes]="vm.boxes" [owner]="vm.profile"></noosfero-boxes>
  4 + <noosfero-boxes ng-if="vm.boxes" [boxes]="vm.boxes" [owner]="vm.profile"></noosfero-boxes>
5 5 </div>
6 6 <div class="profile-footer" ng-bind-html="vm.profile.custom_footer"></div>
7 7 </div>
... ...
src/app/shared/models/interfaces.ts
... ... @@ -6,7 +6,6 @@ export interface UserResponse {
6 6 user: noosfero.User;
7 7 }
8 8  
9   -
10 9 export interface INoosferoLocalStorage extends angular.storage.ILocalStorageService {
11 10 currentUser: noosfero.User;
12 11 }
... ...
src/app/shared/services/notification.service.spec.ts
... ... @@ -20,10 +20,10 @@ describe(&quot;Components&quot;, () =&gt; {
20 20 let component: NotificationService = new NotificationService(<any>helpers.mocks.$log, <any>sweetAlert, <any>helpers.mocks.translatorService);
21 21 component.error({ message: "message", title: "title" });
22 22 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
23   - text: "message",
24 23 title: "title",
  24 + text: "message",
25 25 type: "error"
26   - }));
  26 + }), null);
27 27 done();
28 28 });
29 29  
... ... @@ -36,7 +36,7 @@ describe(&quot;Components&quot;, () =&gt; {
36 36 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
37 37 text: NotificationService.DEFAULT_ERROR_MESSAGE,
38 38 type: "error"
39   - }));
  39 + }), null);
40 40 done();
41 41 });
42 42  
... ... @@ -48,7 +48,7 @@ describe(&quot;Components&quot;, () =&gt; {
48 48 component.success({ title: "title", message: "message", timer: 1000 });
49 49 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
50 50 type: "success"
51   - }));
  51 + }), null);
52 52 done();
53 53 });
54 54  
... ... @@ -60,7 +60,7 @@ describe(&quot;Components&quot;, () =&gt; {
60 60 component.httpError(500, {});
61 61 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
62 62 text: "notification.http_error.500.message"
63   - }));
  63 + }), null);
64 64 done();
65 65 });
66 66  
... ... @@ -73,7 +73,22 @@ describe(&quot;Components&quot;, () =&gt; {
73 73 expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
74 74 type: "success",
75 75 timer: NotificationService.DEFAULT_SUCCESS_TIMER
76   - }));
  76 + }), null);
  77 + done();
  78 + });
  79 +
  80 + it("display a confirmation dialog when call confirmation method", done => {
  81 + let sweetAlert = jasmine.createSpyObj("sweetAlert", ["swal"]);
  82 + sweetAlert.swal = jasmine.createSpy("swal");
  83 +
  84 + let component: NotificationService = new NotificationService(<any>helpers.mocks.$log, <any>sweetAlert, <any>helpers.mocks.translatorService);
  85 + let func = () => { };
  86 + component.confirmation({ title: "title", message: "message" }, func);
  87 + expect(sweetAlert.swal).toHaveBeenCalledWith(jasmine.objectContaining({
  88 + title: "title",
  89 + text: "message",
  90 + type: "warning"
  91 + }), jasmine.any(Function));
77 92 done();
78 93 });
79 94 });
... ...
src/app/shared/services/notification.service.ts
... ... @@ -36,15 +36,23 @@ export class NotificationService {
36 36 this.showMessage({ title: title, text: message, timer: timer });
37 37 }
38 38  
39   - private showMessage({title, text, type = "success", timer = null, showConfirmButton = true}) {
  39 + confirmation({title, message, showCancelButton = true, type = "warning"}, confirmationFunction: Function) {
  40 + this.showMessage({ title: title, text: message, showCancelButton: showCancelButton, type: type, closeOnConfirm: false }, confirmationFunction);
  41 + }
  42 +
  43 + private showMessage({title, text, type = "success", timer = null, showConfirmButton = true, showCancelButton = false, closeOnConfirm = true}, confirmationFunction: Function = null) {
40 44 this.$log.debug("Notification message:", title, text, type, this.translatorService.currentLanguage());
41 45 this.SweetAlert.swal({
42 46 title: this.translatorService.translate(title),
43 47 text: this.translatorService.translate(text),
44 48 type: type,
45 49 timer: timer,
46   - showConfirmButton: showConfirmButton
47   - });
  50 + showConfirmButton: showConfirmButton,
  51 + showCancelButton: showCancelButton,
  52 + closeOnConfirm: closeOnConfirm
  53 + }, confirmationFunction ? (isConfirm: boolean) => {
  54 + if (isConfirm) confirmationFunction();
  55 + } : null);
48 56 }
49 57  
50 58 }
... ...
src/app/shared/utils/arrays.ts 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +export class ArrayUtils {
  2 +
  3 + /**
  4 + * Returns if two arrays are equal
  5 + */
  6 + static arraysEqual(a: any, b: any): boolean {
  7 + if (a === b) return true;
  8 + if (a == null || b == null) return false;
  9 + if (a.length !== b.length) return false;
  10 +
  11 + // If you don't care about the order of the elements inside
  12 + // the array, you should sort both arrays here.
  13 + for (let i = 0; i < a.length; ++i) {
  14 + if (a[i] !== b[i]) return false;
  15 + }
  16 + return true;
  17 + }
  18 +
  19 +}
0 20 \ No newline at end of file
... ...
src/app/shared/utils/hashmap.ts 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +export class HashMap<K extends EqualsObject, V> {
  2 + private values: Array<HashItem<K, V>> = new Array();
  3 +
  4 + get(key: K): V {
  5 + let found = this.values.find( function(value: HashItem<K, V>) {
  6 + return value.key.equals(key);
  7 + });
  8 + if (found) {
  9 + return found.value;
  10 + }
  11 + return null;
  12 + }
  13 +
  14 + put(key: K, value: V): void {
  15 + this.values.push(new HashItem(key, value));
  16 + }
  17 +
  18 + clear() {
  19 + this.values = new Array();
  20 + }
  21 +}
  22 +
  23 +export class HashItem<K, V> {
  24 + key: K;
  25 + value: V;
  26 + constructor(key: K, value: V) {
  27 + this.key = key;
  28 + this.value = value;
  29 + }
  30 +}
  31 +
  32 +export abstract class EqualsObject {
  33 +
  34 + abstract equals(other: any): boolean;
  35 +
  36 +}
0 37 \ No newline at end of file
... ...
src/app/shared/utils/index.ts 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +/* Module Index Entry - generated using the script npm run generate-index */
  2 +export * from "./hashmap";
  3 +export * from "./arrays";
... ...
src/languages/en.json
... ... @@ -24,7 +24,12 @@
24 24 "auth.form.login": "Login / Email address",
25 25 "auth.form.password": "Password",
26 26 "auth.form.login_button": "Login",
  27 + "navbar.content_viewer_actions.new_item": "New Item",
  28 + "navbar.profile_actions.new_item": "New Item",
27 29 "navbar.content_viewer_actions.new_post": "New Post",
  30 + "//TODO": "Create a way to load plugin translatios - Move plugins translations to the plugins translations files",
  31 + "navbar.content_viewer_actions.new_discussion": "New Discussion",
  32 + "navbar.profile_actions.new_discussion": "New Discussion",
28 33 "notification.error.default.message": "Something went wrong!",
29 34 "notification.error.default.title": "Oops...",
30 35 "notification.profile.not_found": "Page not found",
... ... @@ -35,8 +40,15 @@
35 40 "comment.pagination.more": "More",
36 41 "comment.post.success.title": "Good job!",
37 42 "comment.post.success.message": "Comment saved!",
  43 + "comment.remove.success.title": "Good job!",
  44 + "comment.remove.success.message": "Comment removed!",
  45 + "comment.remove.confirmation.title": "Are you sure?",
  46 + "comment.remove.confirmation.message": "You will not be able to recover this comment!",
38 47 "comment.reply": "reply",
  48 + "comment.remove": "remove",
39 49 "article.actions.edit": "Edit",
  50 + "article.actions.delete": "Delete",
  51 + "article.actions.read_more": "Read More",
40 52 "article.basic_editor.title": "Title",
41 53 "article.basic_editor.body": "Body",
42 54 "article.basic_editor.save": "Save",
... ...
src/languages/pt.json
... ... @@ -24,7 +24,9 @@
24 24 "auth.form.login": "Login / Email",
25 25 "auth.form.password": "Senha",
26 26 "auth.form.login_button": "Login",
  27 + "navbar.content_viewer_actions.new_item": "Novo Item",
27 28 "navbar.content_viewer_actions.new_post": "Novo Artigo",
  29 + "navbar.content_viewer_actions.new_discussion": "Nova Discussão",
28 30 "notification.error.default.message": "Algo deu errado!",
29 31 "notification.error.default.title": "Oops...",
30 32 "notification.profile.not_found": "Página não encontrada",
... ... @@ -35,8 +37,15 @@
35 37 "comment.pagination.more": "Mais",
36 38 "comment.post.success.title": "Bom trabalho!",
37 39 "comment.post.success.message": "Comentário salvo com sucesso!",
  40 + "comment.remove.success.title": "Bom trabalho!",
  41 + "comment.remove.success.message": "Comentário removido com sucesso!",
  42 + "comment.remove.confirmation.title": "Tem certeza?",
  43 + "comment.remove.confirmation.message": "Você não poderá recuperar o comentário removido!",
38 44 "comment.reply": "responder",
  45 + "comment.remove": "remover",
39 46 "article.actions.edit": "Editar",
  47 + "article.actions.delete": "Excluir",
  48 + "article.actions.read_more": "Ler mais",
40 49 "article.basic_editor.title": "Título",
41 50 "article.basic_editor.body": "Corpo",
42 51 "article.basic_editor.save": "Salvar",
... ...
src/lib/ng-noosfero-api/http/article.service.spec.ts
... ... @@ -20,6 +20,15 @@ describe(&quot;Services&quot;, () =&gt; {
20 20  
21 21 describe("Succesfull requests", () => {
22 22  
  23 + it("should remove article", (done) => {
  24 + let articleId = 1;
  25 + $httpBackend.expectDELETE(`/api/v1/articles/${articleId}`).respond(200, { success: "true" });
  26 + articleService.removeArticle(<noosfero.Article>{id: articleId});
  27 + $httpBackend.flush();
  28 + $httpBackend.verifyNoOutstandingExpectation();
  29 + done();
  30 + });
  31 +
23 32 it("should return article children", (done) => {
24 33 let articleId = 1;
25 34 $httpBackend.expectGET(`/api/v1/articles/${articleId}/children`).respond(200, { articles: [{ name: "article1" }] });
... ...
src/lib/ng-noosfero-api/http/article.service.ts
1   -import { Injectable, Inject } from "ng-forward";
  1 +import { Injectable, Inject, EventEmitter } from "ng-forward";
2 2 import {RestangularService} from "./restangular_service";
3 3 import {ProfileService} from "./profile.service";
  4 +import {NoosferoRootScope} from "./../../../app/shared/models/interfaces";
4 5  
5 6 @Injectable()
6 7 @Inject("Restangular", "$q", "$log", ProfileService)
7   -
8 8 export class ArticleService extends RestangularService<noosfero.Article> {
9 9  
  10 + private articleRemoved: EventEmitter<noosfero.Article> = new EventEmitter<noosfero.Article>();
  11 +
10 12 constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService, protected profileService: ProfileService) {
11 13 super(Restangular, $q, $log);
12 14 }
... ... @@ -22,6 +24,31 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
22 24 };
23 25 }
24 26  
  27 + removeArticle(article: noosfero.Article) {
  28 + let restRequest: ng.IPromise<noosfero.RestResult<noosfero.Article>> = this.remove(article);
  29 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article>>();
  30 + restRequest.then((result: any) => {
  31 + this.notifyArticleRemovedListeners(article);
  32 + }).catch(this.getHandleErrorFunction(deferred));
  33 + return deferred.promise;
  34 + }
  35 +
  36 + /**
  37 + * Notify listeners that this article has been removed
  38 + */
  39 + private notifyArticleRemovedListeners(article: noosfero.Article) {
  40 + // let listener = this.events.get(this.removed);
  41 + // listener.next(article);
  42 + this.articleRemoved.next(article);
  43 + }
  44 +
  45 + /**
  46 + * subscribes to the ArticleRemoved event emitter
  47 + */
  48 + subscribeToArticleRemoved(fn: Function) {
  49 + this.articleRemoved.subscribe(fn);
  50 + }
  51 +
25 52 updateArticle(article: noosfero.Article) {
26 53 let headers = {
27 54 'Content-Type': 'application/json'
... ... @@ -103,3 +130,4 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
103 130  
104 131  
105 132 }
  133 +
... ...
src/lib/ng-noosfero-api/http/block.service.spec.ts 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +import {BlockService} from "./block.service";
  2 +
  3 +
  4 +describe("Services", () => {
  5 +
  6 + describe("Block Service", () => {
  7 +
  8 + let $httpBackend: ng.IHttpBackendService;
  9 + let blockService: BlockService;
  10 +
  11 + beforeEach(angular.mock.module("noosferoApp", ($translateProvider: angular.translate.ITranslateProvider) => {
  12 + $translateProvider.translations('en', {});
  13 + }));
  14 +
  15 + beforeEach(inject((_$httpBackend_: ng.IHttpBackendService, _BlockService_: BlockService) => {
  16 + $httpBackend = _$httpBackend_;
  17 + blockService = _BlockService_;
  18 + }));
  19 +
  20 +
  21 + describe("Succesfull requests", () => {
  22 +
  23 + it("should return api content of a block", (done) => {
  24 + let blockId = 1;
  25 + $httpBackend.expectGET(`/api/v1/blocks/${blockId}`).respond(200, { block: { api_content: [{ name: "article1" }] } });
  26 + blockService.getApiContent(<noosfero.Block>{ id: blockId }).then((content: any) => {
  27 + expect(content).toEqual([{ name: "article1" }]);
  28 + done();
  29 + });
  30 + $httpBackend.flush();
  31 + });
  32 + });
  33 +
  34 +
  35 + });
  36 +});
... ...
src/lib/ng-noosfero-api/http/block.service.ts 0 → 100644
... ... @@ -0,0 +1,40 @@
  1 +import { Injectable, Inject } from "ng-forward";
  2 +import {RestangularService} from "./restangular_service";
  3 +import {ProfileService} from "./profile.service";
  4 +
  5 +@Injectable()
  6 +@Inject("Restangular", "$q", "$log")
  7 +export class BlockService extends RestangularService<noosfero.Block> {
  8 +
  9 + constructor(Restangular: restangular.IService, $q: ng.IQService, $log: ng.ILogService) {
  10 + super(Restangular, $q, $log);
  11 + }
  12 +
  13 + getResourcePath() {
  14 + return "blocks";
  15 + }
  16 +
  17 + getDataKeys() {
  18 + return {
  19 + singular: 'block',
  20 + plural: 'blocks'
  21 + };
  22 + }
  23 +
  24 + getApiContent(block: noosfero.Block) {
  25 + let apiContentPromise = this.$q.defer();
  26 + if (block) {
  27 + if (block.api_content) {
  28 + apiContentPromise.resolve(block.api_content);
  29 + } else {
  30 + this.get(block.id)
  31 + .then((result: noosfero.RestResult<noosfero.Block>) => {
  32 + block = result.data;
  33 + apiContentPromise.resolve(block.api_content);
  34 + });
  35 + }
  36 + }
  37 + return apiContentPromise.promise;
  38 + }
  39 +
  40 +}
... ...
src/lib/ng-noosfero-api/http/comment.service.spec.ts
... ... @@ -40,6 +40,17 @@ describe(&quot;Services&quot;, () =&gt; {
40 40 });
41 41 $httpBackend.flush();
42 42 });
  43 +
  44 + it("should remove a comment from an article", (done) => {
  45 + let articleId = 1;
  46 + let comment: noosfero.Comment = <any>{ id: 2 };
  47 + $httpBackend.expectDELETE(`/api/v1/articles/${articleId}/comments/${comment.id}`).respond(200, { comment: { id: 2 } });
  48 + commentService.removeFromArticle(<noosfero.Article>{ id: articleId }, comment).then((result: noosfero.RestResult<noosfero.Comment>) => {
  49 + expect(result.data).toEqual({ id: 2 });
  50 + done();
  51 + });
  52 + $httpBackend.flush();
  53 + });
43 54 });
44 55  
45 56  
... ...
src/lib/ng-noosfero-api/http/comment.service.ts
... ... @@ -31,4 +31,9 @@ export class CommentService extends RestangularService&lt;noosfero.Comment&gt; {
31 31 let articleElement = this.articleService.getElement(<number>article.id);
32 32 return this.create(comment, articleElement, null, { 'Content-Type': 'application/json' }, false);
33 33 }
  34 +
  35 + removeFromArticle(article: noosfero.Article, comment: noosfero.Comment): ng.IPromise<noosfero.RestResult<noosfero.Comment>> {
  36 + let articleElement = this.articleService.getElement(<number>article.id);
  37 + return this.remove(comment, articleElement);
  38 + }
34 39 }
... ...
src/lib/ng-noosfero-api/interfaces/article.ts
1 1  
2 2 namespace noosfero {
3 3 export interface Article extends RestModel {
  4 + abstract: string;
4 5 path: string;
5 6 profile: Profile;
6 7 type: string;
... ... @@ -12,5 +13,6 @@ namespace noosfero {
12 13 setting: any;
13 14 start_date: string;
14 15 end_date: string;
  16 + accept_comments: boolean;
15 17 }
16 18 }
... ...
src/lib/ng-noosfero-api/interfaces/block.ts
1 1 namespace noosfero {
2   - export interface Block {
  2 + export interface Block extends RestModel {
3 3 id: number;
4   - settings: any;
  4 + settings: Settings;
  5 + limit: number;
  6 + api_content: any;
  7 + hide: boolean;
5 8 }
6 9 }
... ...
src/lib/ng-noosfero-api/interfaces/profile.ts
... ... @@ -22,13 +22,13 @@ namespace noosfero {
22 22 * @returns {string} The unque identifier for the Profile
23 23 */
24 24 identifier: string;
25   -
  25 +
26 26 /**
27 27 * @ngdoc property
28 28 * @name created_at
29 29 * @propertyOf noofero.Profile
30 30 * @returns {string} The timestamp this object was created
31   - */
  31 + */
32 32 created_at: string;
33 33  
34 34 /**
... ... @@ -54,5 +54,13 @@ namespace noosfero {
54 54 * @returns {string} A key => value custom fields data of Profile (e.g.: "{'Address':'Street A, Number 102...'}")
55 55 */
56 56 additional_data?: any;
  57 +
  58 + /**
  59 + * @ngdoc property
  60 + * @name homepage
  61 + * @propertyOf noofero.Profile
  62 + * @returns {string} The Profile homepage
  63 + */
  64 + homepage: string;
57 65 }
58 66 }
... ...
src/lib/ng-noosfero-api/interfaces/section.ts 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +namespace noosfero {
  2 + /**
  3 + * @ngdoc interface
  4 + * @name noosfero.Section
  5 + * @description
  6 + * Represents a block settings section. A Section has a value property,
  7 + * which represents the Section name, and an optinally checked property which
  8 + * has the same value as the value property indicating that this property is
  9 + * selected in the block configuration.
  10 + */
  11 + export interface Section {
  12 +
  13 + value: string;
  14 + checked: string;
  15 + }
  16 +}
0 17 \ No newline at end of file
... ...
src/lib/ng-noosfero-api/interfaces/settings.ts 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +namespace noosfero {
  2 + /**
  3 + * Represents a noosfero block settings.
  4 + */
  5 + export interface Settings {
  6 + sections: noosfero.Section[];
  7 + limit: number;
  8 + }
  9 +}
0 10 \ No newline at end of file
... ...
src/plugins/comment_paragraph/allow-comment/allow-comment.component.spec.ts
... ... @@ -57,7 +57,7 @@ describe(&quot;Components&quot;, () =&gt; {
57 57 });
58 58  
59 59 it('display button to side comments', () => {
60   - expect(helper.all(".paragraph .actions a").length).toEqual(1);
  60 + expect(helper.all(".paragraph .paragraph-actions a").length).toEqual(1);
61 61 });
62 62  
63 63 it('set display to true when click in show paragraph', () => {
... ... @@ -74,5 +74,17 @@ describe(&quot;Components&quot;, () =&gt; {
74 74 functionToggleCommentParagraph({ id: 2 });
75 75 expect(helper.component.article.id).toEqual(2);
76 76 });
  77 +
  78 + it('not display button to side comments when comments was closed and there is no comment paragraph', () => {
  79 + helper.component.article.accept_comments = false;
  80 + helper.component.commentsCount = 0;
  81 + expect(helper.component.isActivated()).toBeFalsy();
  82 + });
  83 +
  84 + it('display button to side comments when comments was closed and there is some comments to display', () => {
  85 + helper.component.article.accept_comments = false;
  86 + helper.component.commentsCount = 2;
  87 + expect(helper.component.isActivated()).toBeTruthy();
  88 + });
77 89 });
78 90 });
... ...
src/plugins/comment_paragraph/allow-comment/allow-comment.component.ts
... ... @@ -32,7 +32,9 @@ export class AllowCommentComponent {
32 32 }
33 33  
34 34 isActivated() {
35   - return this.article && this.article.setting && this.article.setting.comment_paragraph_plugin_activate;
  35 + return this.article && this.article.setting &&
  36 + this.article.setting.comment_paragraph_plugin_activate &&
  37 + (this.article.accept_comments || this.commentsCount > 0);
36 38 }
37 39  
38 40 showParagraphComments() {
... ...
src/plugins/comment_paragraph/allow-comment/allow-comment.html
1 1 <div class="paragraph" ng-class="{'active' : ctrl.display}">
2 2 <div class="paragraph-content" ng-bind-html="ctrl.content" ng-class="{'active' : ctrl.display}"></div>
3   - <div ng-if="ctrl.isActivated()" class="actions">
  3 + <div ng-if="ctrl.isActivated()" class="paragraph-actions">
4 4 <a href="#" popover-placement="right-top" popover-trigger="none"
5 5 uib-popover-template="'plugins/comment_paragraph/allow-comment/popover.html'"
6 6 (click)="ctrl.showParagraphComments()" popover-is-open="ctrl.display">
... ...
src/plugins/comment_paragraph/allow-comment/allow-comment.scss
... ... @@ -15,7 +15,7 @@ comment-paragraph-plugin-allow-comment {
15 15 width: 95%;
16 16 display: inline-block;
17 17 }
18   - .actions {
  18 + .paragraph-actions {
19 19 width: 3%;
20 20 display: inline-block;
21 21 vertical-align: top;
... ...
src/plugins/comment_paragraph/article/cms/discussion-editor/discussion-editor.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +import {DiscussionEditorComponent} from './discussion-editor.component';
  2 +import {ComponentTestHelper, createClass} from './../../../../../spec/component-test-helper';
  3 +
  4 +const htmlTemplate: string = '<comment-paragraph-plugin-discussion-editor [article]="ctrl.article"></comment-paragraph-plugin-discussion-editor>';
  5 +
  6 +describe("Components", () => {
  7 + describe("Discussion Editor Component", () => {
  8 +
  9 + let helper: ComponentTestHelper<DiscussionEditorComponent>;
  10 + beforeEach(angular.mock.module("templates"));
  11 +
  12 + beforeEach((done) => {
  13 + let properties = { article: {} };
  14 + let cls = createClass({
  15 + template: htmlTemplate,
  16 + directives: [DiscussionEditorComponent],
  17 + properties: properties
  18 + });
  19 + helper = new ComponentTestHelper<DiscussionEditorComponent>(cls, done);
  20 + });
  21 +
  22 + it("set start_date as article start_date when it was defined", () => {
  23 + let article = {start_date: new Date()};
  24 + helper.changeProperties({article: article});
  25 + helper.component.ngOnInit();
  26 + expect(helper.component.start_date.getTime()).toEqual(article.start_date.getTime());
  27 + });
  28 +
  29 + it("set start_date as current date when it was not defined", () => {
  30 + helper.changeProperties({article: {}});
  31 + helper.component.ngOnInit();
  32 + expect(helper.component.start_date.getTime()).toBeDefined();
  33 + });
  34 +
  35 + it("set end_date as article end_date when it was defined", () => {
  36 + let article = {end_date: new Date()};
  37 + helper.changeProperties({article: article});
  38 + helper.component.ngOnInit();
  39 + expect(helper.component.end_date.getTime()).toEqual(article.end_date.getTime());
  40 + });
  41 + });
  42 +});
... ...
src/plugins/comment_paragraph/block/discussion/discussion-block.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +import {TestComponentBuilder} from 'ng-forward/cjs/testing/test-component-builder';
  2 +import {Provider, Input, provide, Component} from 'ng-forward';
  3 +import {provideFilters} from '../../../../spec/helpers';
  4 +import {DiscussionBlockComponent} from './discussion-block.component';
  5 +import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
  6 +
  7 +const htmlTemplate: string = '<noosfero-comment-paragraph-plugin-discussion-block [block]="ctrl.block" [owner]="ctrl.owner"></noosfero-comment-paragraph-plugin-discussion-block>';
  8 +
  9 +const tcb = new TestComponentBuilder();
  10 +
  11 +describe("Components", () => {
  12 + describe("Discussion Block Component", () => {
  13 +
  14 + let helper: ComponentTestHelper<DiscussionBlockComponent>;
  15 + let settingsObj = {};
  16 + let mockedBlockService = {
  17 + getApiContent: (content: any): any => {
  18 + return Promise.resolve({ articles: [{ name: "article1" }], headers: (name: string) => { return name; } });
  19 + }
  20 + };
  21 + let profile = { name: 'profile-name' };
  22 +
  23 + let state = jasmine.createSpyObj("state", ["go"]);
  24 +
  25 + let providers = [
  26 + new Provider('$state', { useValue: state }),
  27 + new Provider('BlockService', {
  28 + useValue: mockedBlockService
  29 + }),
  30 + ].concat(provideFilters("truncateFilter", "stripTagsFilter", "translateFilter", "amDateFormatFilter"));
  31 +
  32 + beforeEach(angular.mock.module("templates"));
  33 +
  34 + beforeEach((done) => {
  35 + let cls = createClass({
  36 + template: htmlTemplate,
  37 + directives: [DiscussionBlockComponent],
  38 + providers: providers,
  39 + properties: { block: {} }
  40 + });
  41 + helper = new ComponentTestHelper<DiscussionBlockComponent>(cls, done);
  42 + });
  43 +
  44 + it("get discussions from the block service", () => {
  45 + expect(helper.component.documents).toEqual([{ name: "article1" }]);
  46 + expect(helper.component.block.hide).toEqual(false);
  47 + });
  48 +
  49 + it("go to article page when open a document", () => {
  50 + let block = helper.component;
  51 + block.openDocument({ path: "path", profile: { identifier: "identifier" } });
  52 + expect(state.go).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "identifier" });
  53 + });
  54 + });
  55 +});
... ...
src/plugins/comment_paragraph/block/discussion/discussion-block.component.ts
1 1 import {Component, Inject, Input} from "ng-forward";
2   -import {ArticleService} from "../../../../lib/ng-noosfero-api/http/article.service";
  2 +import {BlockService} from "../../../../lib/ng-noosfero-api/http/block.service";
3 3  
4 4 @Component({
5 5 selector: "noosfero-comment-paragraph-plugin-discussion-block",
6 6 templateUrl: 'plugins/comment_paragraph/block/discussion/discussion-block.html'
7 7 })
8   -@Inject(ArticleService, "$state")
  8 +@Inject(BlockService, "$state")
9 9 export class DiscussionBlockComponent {
10 10  
11 11 @Input() block: any;
12 12 @Input() owner: any;
13 13  
14   - profile: any;
15   - documents: any;
  14 + profile: noosfero.Profile;
  15 + documents: Array<noosfero.Article>;
16 16  
17   - documentsLoaded: boolean = false;
18   -
19   - constructor(private articleService: ArticleService, private $state: any) { }
  17 + constructor(private blockService: BlockService, private $state: any) { }
20 18  
21 19 ngOnInit() {
22 20 this.profile = this.owner;
23   - this.documents = [];
24   -
25   - let limit = ((this.block && this.block.settings) ? this.block.settings.limit : null) || 50;
26   - let params: any = { content_type: 'CommentParagraphPlugin::Discussion', per_page: limit, order: 'start_date DESC' };
27   - let now = new Date().toISOString();
28   - switch (this.block.settings['discussion_status']) {
29   - case 0:
30   - params['from_start_date'] = now;
31   - break;
32   - case 1:
33   - params['until_start_date'] = now;
34   - params['from_end_date'] = now;
35   - break;
36   - case 2:
37   - params['until_end_date'] = now;
38   - break;
39   - }
40   - console.log(this.block.settings['discussion_status']);
41   - this.articleService.getByProfile(this.profile, params)
42   - .then((result: noosfero.RestResult<noosfero.Article[]>) => {
43   - this.documents = <noosfero.Article[]>result.data;
44   - this.documentsLoaded = true;
45   - });
  21 + this.blockService.getApiContent(this.block).then((content: any) => {
  22 + this.documents = content.articles;
  23 + this.block.hide = !this.documents || this.documents.length === 0;
  24 + });
46 25 }
47 26  
48 27 openDocument(article: any) {
... ...
src/plugins/comment_paragraph/hotspot/article-content/article-content.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,110 @@
  1 +import {CommentParagraphArticleContentHotspotComponent} from './article-content.component';
  2 +import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
  3 +
  4 +const htmlTemplate: string = '<comment-paragraph-article-content-hotspot [article]="ctrl.article"></comment-paragraph-article-content-hotspot>';
  5 +
  6 +describe("Components", () => {
  7 + describe("Article Content Hotspot Component", () => {
  8 +
  9 + let helper: ComponentTestHelper<CommentParagraphArticleContentHotspotComponent>;
  10 + beforeEach(angular.mock.module("templates"));
  11 +
  12 + beforeEach((done) => {
  13 + let properties = { article: {} };
  14 + let cls = createClass({
  15 + template: htmlTemplate,
  16 + directives: [CommentParagraphArticleContentHotspotComponent],
  17 + properties: properties
  18 + });
  19 + helper = new ComponentTestHelper<CommentParagraphArticleContentHotspotComponent>(cls, done);
  20 + });
  21 +
  22 + it("return false in isDiscussion when no type was specified", () => {
  23 + expect(helper.component.isDiscussion()).toBeFalsy();
  24 + });
  25 +
  26 + it("return false in isDiscussion when other type was specified", () => {
  27 + helper.changeProperties({ article: { type: "TextArticle" } });
  28 + expect(helper.component.isDiscussion()).toBeFalsy();
  29 + });
  30 +
  31 + it("return true in isDiscussion when discussion type was specified", () => {
  32 + helper.changeProperties({ article: { type: "CommentParagraphPlugin::Discussion" } });
  33 + expect(helper.component.isDiscussion()).toBeTruthy();
  34 + });
  35 +
  36 + it("return true in notOpened when start date is after today", () => {
  37 + let date = new Date();
  38 + date.setDate(date.getDate() + 1);
  39 + helper.changeProperties({ article: { start_date: date.toISOString() } });
  40 + expect(helper.component.notOpened()).toBeTruthy();
  41 + expect(helper.component.available()).toBeFalsy();
  42 + expect(helper.component.closed()).toBeFalsy();
  43 + });
  44 +
  45 + it("return false in notOpened when start date is before today", () => {
  46 + let date = new Date();
  47 + date.setDate(date.getDate() - 1);
  48 + helper.changeProperties({ article: { start_date: date.toISOString() } });
  49 + expect(helper.component.notOpened()).toBeFalsy();
  50 + });
  51 +
  52 + it("return false in notOpened when start date is null", () => {
  53 + helper.changeProperties({ article: { start_date: null } });
  54 + expect(helper.component.notOpened()).toBeFalsy();
  55 + });
  56 +
  57 + it("return true in closed when end date is before today", () => {
  58 + let date = new Date();
  59 + date.setDate(date.getDate() - 1);
  60 + helper.changeProperties({ article: { end_date: date.toISOString() } });
  61 + expect(helper.component.closed()).toBeTruthy();
  62 + expect(helper.component.available()).toBeFalsy();
  63 + expect(helper.component.notOpened()).toBeFalsy();
  64 + });
  65 +
  66 + it("return false in closed when start date is after today", () => {
  67 + let date = new Date();
  68 + date.setDate(date.getDate() + 1);
  69 + helper.changeProperties({ article: { end_date: date.toISOString() } });
  70 + expect(helper.component.closed()).toBeFalsy();
  71 + });
  72 +
  73 + it("return false in closed when end date is null", () => {
  74 + helper.changeProperties({ article: { start_date: null } });
  75 + expect(helper.component.closed()).toBeFalsy();
  76 + });
  77 +
  78 + it("return true in available when start date is before today and end date is after", () => {
  79 + let date = new Date();
  80 + date.setDate(date.getDate() - 1);
  81 + let startDate = date.toISOString();
  82 + date.setDate(date.getDate() + 3);
  83 + let endDate = date.toISOString();
  84 + helper.changeProperties({ article: { start_date: startDate, end_date: endDate } });
  85 + expect(helper.component.available()).toBeTruthy();
  86 + expect(helper.component.closed()).toBeFalsy();
  87 + expect(helper.component.notOpened()).toBeFalsy();
  88 + });
  89 +
  90 + it("return true in available when start date is before today and end date is null", () => {
  91 + let date = new Date();
  92 + date.setDate(date.getDate() - 1);
  93 + let startDate = date.toISOString();
  94 + helper.changeProperties({ article: { start_date: startDate, end_date: null } });
  95 + expect(helper.component.available()).toBeTruthy();
  96 + expect(helper.component.closed()).toBeFalsy();
  97 + expect(helper.component.notOpened()).toBeFalsy();
  98 + });
  99 +
  100 + it("return true in available when start date is null and end date is after today", () => {
  101 + let date = new Date();
  102 + date.setDate(date.getDate() + 3);
  103 + let endDate = date.toISOString();
  104 + helper.changeProperties({ article: { start_date: null, end_date: endDate } });
  105 + expect(helper.component.available()).toBeTruthy();
  106 + expect(helper.component.closed()).toBeFalsy();
  107 + expect(helper.component.notOpened()).toBeFalsy();
  108 + });
  109 + });
  110 +});
... ...
src/plugins/comment_paragraph/hotspot/article-content/article-content.component.ts
... ... @@ -13,4 +13,20 @@ export class CommentParagraphArticleContentHotspotComponent {
13 13 isDiscussion() {
14 14 return this.article.type === "CommentParagraphPlugin::Discussion";
15 15 }
  16 +
  17 + notOpened() {
  18 + let now = new Date();
  19 + return !!this.article.start_date && new Date(this.article.start_date) > now;
  20 + }
  21 +
  22 + available() {
  23 + let now = new Date();
  24 + return (!this.article.start_date || new Date(this.article.start_date) <= now) &&
  25 + (!this.article.end_date || new Date(this.article.end_date) >= now);
  26 + }
  27 +
  28 + closed() {
  29 + let now = new Date();
  30 + return !!this.article.end_date && new Date(this.article.end_date) < now;
  31 + }
16 32 }
... ...
src/plugins/comment_paragraph/hotspot/article-content/article-content.html
1 1 <div class="discussion-header" ng-if="ctrl.isDiscussion()">
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>
  2 + <div class="icon">
  3 + <i class="fa fa-calendar fa-fw fa-lg"></i>
  4 + </div>
  5 + <div class="period">
  6 + <div ng-if="ctrl.notOpened()" class="description not-opened">
  7 + {{"comment-paragraph-plugin.discussion.notOpened.header" | translate:{date: (ctrl.article.start_date | dateFormat | amTimeAgo)} }}
  8 + </div>
  9 + <div ng-if="ctrl.available()" class="description available">
  10 + <div ng-if="ctrl.article.end_date" class="with-end-date">
  11 + {{"comment-paragraph-plugin.discussion.available.header" | translate:{date: (ctrl.article.end_date | dateFormat | amTimeAgo)} }}
  12 + </div>
  13 + <div ng-if="!ctrl.article.end_date" class="without-end-date">
  14 + {{"comment-paragraph-plugin.discussion.available.without-end.header" | translate}}
  15 + </div>
  16 + </div>
  17 + <div ng-if="ctrl.closed()" class="description closed">
  18 + {{"comment-paragraph-plugin.discussion.closed.header" | translate:{date: (ctrl.article.end_date | dateFormat | amTimeAgo)} }}
  19 + </div>
  20 + </div>
11 21 </div>
... ...
src/plugins/comment_paragraph/hotspot/article-content/article-content.scss
1 1 .discussion-header {
2 2 @extend .pull-right;
3 3 margin-bottom: 20px;
4   - .date {
5   - text-transform: lowercase;
6   - font-size: 16px;
  4 + .period {
  5 + @extend .pull-right;
7 6 .description {
8 7 font-weight: bold;
9   - color: #BFBFBF;
10   - }
11   - .value {
12   - font-size: 15px;
13   - color: #969696;
  8 + color: #A2A2A2;
14 9 }
15 10 }
16   - .description {
17   - font-weight: bold;
18   - color: #BFBFBF;
  11 + .icon {
  12 + @extend .pull-right;
  13 + margin-left: 8px;
19 14 }
20 15 }
... ...
src/plugins/comment_paragraph/hotspot/comment-paragraph-article-button.component.spec.ts
... ... @@ -1,85 +0,0 @@
1   -import {CommentParagraphArticleButtonHotspotComponent} from "./comment-paragraph-article-button.component";
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   -let htmlTemplate = '<comment-paragraph-article-button-hotspot [article]="ctrl.article"></comment-paragraph-article-button-hotspot>';
8   -
9   -describe("Components", () => {
10   - describe("Comment Paragraph Article Button Hotspot Component", () => {
11   -
12   - let serviceMock = jasmine.createSpyObj("CommentParagraphService", ["deactivateCommentParagraph", "activateCommentParagraph"]);
13   - let eventServiceMock = jasmine.createSpyObj("CommentParagraphEventService", ["toggleCommentParagraph"]);
14   -
15   - let providers = [
16   - new Provider('CommentParagraphService', { useValue: serviceMock }),
17   - new Provider('CommentParagraphEventService', { useValue: eventServiceMock })
18   - ].concat(helpers.provideFilters('translateFilter'));
19   - let helper: ComponentTestHelper<CommentParagraphArticleButtonHotspotComponent>;
20   -
21   - beforeEach(angular.mock.module("templates"));
22   -
23   - beforeEach((done) => {
24   - let cls = createClass({
25   - template: htmlTemplate,
26   - directives: [CommentParagraphArticleButtonHotspotComponent],
27   - providers: providers,
28   - properties: {
29   - article: {}
30   - }
31   - });
32   - helper = new ComponentTestHelper<CommentParagraphArticleButtonHotspotComponent>(cls, done);
33   - });
34   -
35   - it('emit event when deactivate comment paragraph in an article', () => {
36   - serviceMock.deactivateCommentParagraph = jasmine.createSpy("deactivateCommentParagraph").and.returnValue(
37   - { then: (fn: Function) => { fn({ data: {} }); } }
38   - );
39   - eventServiceMock.toggleCommentParagraph = jasmine.createSpy("toggleCommentParagraph");
40   - helper.component.deactivateCommentParagraph();
41   -
42   - expect(serviceMock.deactivateCommentParagraph).toHaveBeenCalled();
43   - expect(eventServiceMock.toggleCommentParagraph).toHaveBeenCalled();
44   - });
45   -
46   - it('emit event when activate comment paragraph in an article', () => {
47   - serviceMock.activateCommentParagraph = jasmine.createSpy("activateCommentParagraph").and.returnValue(
48   - { then: (fn: Function) => { fn({ data: {} }); } }
49   - );
50   - eventServiceMock.toggleCommentParagraph = jasmine.createSpy("toggleCommentParagraph");
51   - helper.component.activateCommentParagraph();
52   -
53   - expect(serviceMock.activateCommentParagraph).toHaveBeenCalled();
54   - expect(eventServiceMock.toggleCommentParagraph).toHaveBeenCalled();
55   - });
56   -
57   - it('return true when comment paragraph is active', () => {
58   - helper.component.article = <noosfero.Article>{ setting: { comment_paragraph_plugin_activate: true } };
59   - helper.detectChanges();
60   - expect(helper.component.isActivated()).toBeTruthy();
61   - });
62   -
63   - it('return false when comment paragraph is not active', () => {
64   - expect(helper.component.isActivated()).toBeFalsy();
65   - });
66   -
67   - it('return false when article has no setting attribute', () => {
68   - helper.component.article = <noosfero.Article>{};
69   - helper.detectChanges();
70   - expect(helper.component.isActivated()).toBeFalsy();
71   - });
72   -
73   - it('display activate button when comment paragraph is not active', () => {
74   - expect(helper.all('.comment-paragraph-activate').length).toEqual(1);
75   - expect(helper.all('.comment-paragraph-deactivate').length).toEqual(0);
76   - });
77   -
78   - it('display deactivate button when comment paragraph is active', () => {
79   - helper.component.article = <noosfero.Article>{ setting: { comment_paragraph_plugin_activate: true } };
80   - helper.detectChanges();
81   - expect(helper.all('.comment-paragraph-deactivate').length).toEqual(1);
82   - expect(helper.all('.comment-paragraph-activate').length).toEqual(0);
83   - });
84   - });
85   -});
src/plugins/comment_paragraph/hotspot/comment-paragraph-article-button.component.ts
... ... @@ -1,38 +0,0 @@
1   -import { Input, Inject, Component } from "ng-forward";
2   -import {Hotspot} from "../../../app/hotspot/hotspot.decorator";
3   -import {CommentParagraphService} from "../http/comment-paragraph.service";
4   -import {CommentParagraphEventService} from "../events/comment-paragraph-event.service";
5   -
6   -@Component({
7   - selector: "comment-paragraph-article-button-hotspot",
8   - templateUrl: "plugins/comment_paragraph/hotspot/comment-paragraph-article-button.html",
9   -})
10   -@Inject("$scope", CommentParagraphService, CommentParagraphEventService)
11   -@Hotspot("article_extra_toolbar_buttons")
12   -export class CommentParagraphArticleButtonHotspotComponent {
13   -
14   - @Input() article: noosfero.Article;
15   -
16   - constructor(private $scope: ng.IScope,
17   - private commentParagraphService: CommentParagraphService,
18   - private commentParagraphEventService: CommentParagraphEventService) { }
19   -
20   - deactivateCommentParagraph() {
21   - this.toggleCommentParagraph(this.commentParagraphService.deactivateCommentParagraph(this.article));
22   - }
23   -
24   - activateCommentParagraph() {
25   - this.toggleCommentParagraph(this.commentParagraphService.activateCommentParagraph(this.article));
26   - }
27   -
28   - isActivated() {
29   - return this.article && this.article.setting && this.article.setting.comment_paragraph_plugin_activate;
30   - }
31   -
32   - private toggleCommentParagraph(promise: ng.IPromise<noosfero.RestResult<noosfero.Article>>) {
33   - promise.then((result: noosfero.RestResult<noosfero.Article>) => {
34   - this.article = result.data;
35   - this.commentParagraphEventService.toggleCommentParagraph(this.article);
36   - });
37   - }
38   -}
src/plugins/comment_paragraph/hotspot/comment-paragraph-article-button.html
... ... @@ -1,8 +0,0 @@
1   -<a href='#' class="btn btn-default btn-xs comment-paragraph-activate" (click)="ctrl.activateCommentParagraph()"
2   - ng-if="!ctrl.article.setting.comment_paragraph_plugin_activate">
3   - <i class="fa fa-fw fa-plus"></i> {{"comment-paragraph-plugin.title" | translate}}
4   -</a>
5   -<a href='#' class="btn btn-default btn-xs comment-paragraph-deactivate" (click)="ctrl.deactivateCommentParagraph()"
6   - ng-if="ctrl.article.setting.comment_paragraph_plugin_activate">
7   - <i class="fa fa-fw fa-minus"></i> {{"comment-paragraph-plugin.title" | translate}}
8   -</a>
src/plugins/comment_paragraph/index.ts
1 1 import {AllowCommentComponent} from "./allow-comment/allow-comment.component";
2   -import {CommentParagraphArticleButtonHotspotComponent} from "./hotspot/comment-paragraph-article-button.component";
3 2 import {CommentParagraphFormHotspotComponent} from "./hotspot/comment-paragraph-form.component";
4 3 import {DiscussionEditorComponent} from "./article/cms/discussion-editor/discussion-editor.component";
5 4 import {CommentParagraphArticleContentHotspotComponent} from "./hotspot/article-content/article-content.component";
6 5 import {DiscussionBlockComponent} from "./block/discussion/discussion-block.component";
7 6  
8 7 export let mainComponents: any = [AllowCommentComponent, DiscussionEditorComponent, DiscussionBlockComponent];
9   -export let hotspots: any = [CommentParagraphArticleButtonHotspotComponent, CommentParagraphFormHotspotComponent, CommentParagraphArticleContentHotspotComponent];
  8 +export let hotspots: any = [CommentParagraphFormHotspotComponent, CommentParagraphArticleContentHotspotComponent];
... ...
src/plugins/comment_paragraph/languages/en.json
... ... @@ -2,5 +2,9 @@
2 2 "comment-paragraph-plugin.title": "Paragraph Comments",
3 3 "comment-paragraph-plugin.discussion.editor.start_date.label": "From",
4 4 "comment-paragraph-plugin.discussion.editor.end_date.label": "To",
5   - "comment-paragraph-plugin.discussion.header": "Open for comments"
  5 + "comment-paragraph-plugin.discussion.header": "Open for comments",
  6 + "comment-paragraph-plugin.discussion.notOpened.header": "Discussion will start {{date}}",
  7 + "comment-paragraph-plugin.discussion.available.header": "Discussion will end {{date}}",
  8 + "comment-paragraph-plugin.discussion.available.without-end.header": "Discussion opened",
  9 + "comment-paragraph-plugin.discussion.closed.header": "Discussion finished {{date}}"
6 10 }
... ...
src/plugins/comment_paragraph/languages/pt.json
... ... @@ -2,5 +2,9 @@
2 2 "comment-paragraph-plugin.title": "Comentários por Parágrafo",
3 3 "comment-paragraph-plugin.discussion.editor.start_date.label": "De",
4 4 "comment-paragraph-plugin.discussion.editor.end_date.label": "Até",
5   - "comment-paragraph-plugin.discussion.header": "Aberto para comentários"
  5 + "comment-paragraph-plugin.discussion.header": "Aberto para comentários",
  6 + "comment-paragraph-plugin.discussion.notOpened.header": "Discussão iniciará {{date}}",
  7 + "comment-paragraph-plugin.discussion.available.header": "Discussão terminará {{date}}",
  8 + "comment-paragraph-plugin.discussion.available.without-end.header": "Discussão aberta",
  9 + "comment-paragraph-plugin.discussion.closed.header": "Discussão finalizada {{date}}"
6 10 }
... ...
src/spec/component-test-helper.ts
... ... @@ -88,16 +88,23 @@ export class ComponentTestHelper&lt;T extends any&gt; {
88 88 this.debugElement = fixture.debugElement;
89 89 this.component = <T>this.debugElement.componentViewChildren[0].componentInstance;
90 90 let mockObj = new this.mockComponent();
91   - Object.keys(mockObj).forEach((key: any) => {
92   - (<any>this.component)[key] = <any>mockObj[key];
93   - });
94   -
  91 + if (this.component) {
  92 + Object.keys(mockObj).forEach((key: any) => {
  93 + (<any>this.component)[key] = <any>mockObj[key];
  94 + });
  95 + }
95 96 }).then(() => {
96 97 // Force the resolution of components and sync
97 98 done();
98 99 });
99 100 }
100 101  
  102 + changeProperties(properties: any) {
  103 + Object.keys(properties).forEach((key: any) => {
  104 + this.component[key] = properties[key];
  105 + });
  106 + }
  107 +
101 108 /**
102 109 * @ngdoc method
103 110 * @name detectChanges
... ...
src/spec/mocks.ts
... ... @@ -76,6 +76,32 @@ export var mocks: any = {
76 76 isAuthenticated: () => { }
77 77 },
78 78 articleService: {
  79 + articleRemovedFn: null,
  80 + subscribeToArticleRemoved: (fn: Function) => {
  81 + mocks.articleService.articleRemovedFn = fn;
  82 + },
  83 + articleRemoved:
  84 + {
  85 + subscribe: (fn: Function) => {
  86 + mocks.articleService.articleRemovedFn = fn;
  87 + },
  88 + next: (param: any) => {
  89 + mocks.articleService.articleRemovedFn(param);
  90 + }
  91 + }
  92 + ,
  93 + removeArticle: (article: noosfero.Article) => {
  94 + return {
  95 + catch: (func?: Function) => {
  96 + }
  97 + };
  98 + },
  99 + notifyArticleRemovedListeners: (article: noosfero.Article) => {
  100 + mocks.articleService.articleRemoved.next(article);
  101 + },
  102 + subscribe: (eventType: any, fn: Function) => {
  103 + mocks.articleService.articleRemoved.subscribe(fn);
  104 + },
79 105 getByProfile: (profileId: number, params?: any) => {
80 106 return {
81 107 then: (func?: Function) => {
... ...
themes/angular-participa-consulta/app/blocks.scss 0 → 100644
... ... @@ -0,0 +1,70 @@
  1 +.block-head-with-icon {
  2 + padding: 15px 15px 15px 55px;
  3 +}
  4 +
  5 +.content-wrapper .block {
  6 + .panel-heading {
  7 + border: none;
  8 + border-radius: 3px 3px 0 0;
  9 + font-family: "Ubuntu Medium";
  10 + font-size: 15px;
  11 + font-variant: normal;
  12 + font-weight: normal;
  13 + line-height: 30px;
  14 + padding: 15px 15px 15px 15px;
  15 + text-transform: capitalize;
  16 + background-size: 30px 30px;
  17 + background: #DAE1C4;
  18 + color: #4F9CAC;
  19 + }
  20 +
  21 + &.membersblock {
  22 + .panel-heading {
  23 + @extend .block-head-with-icon;
  24 + background: #DAE1C4 url("../assets/icons/participa-consulta/pessoas.png") no-repeat 15px center;
  25 + color: #4F9CAC;
  26 + }
  27 + }
  28 +
  29 + &.statisticsblock {
  30 + .panel-heading {
  31 + @extend .block-head-with-icon;
  32 + background: #69677C url("../assets/icons/participa-consulta/indicadores.png") no-repeat 15px center;
  33 + color: #DAE1C4;
  34 + }
  35 + }
  36 +}
  37 +
  38 +.col-md-7 {
  39 + .panel-body.linklistblock {
  40 + padding: 0px;
  41 + }
  42 +
  43 + .link-list-block {
  44 + font-family: "Ubuntu";
  45 + font-size: 12px;
  46 + background: #69677C;
  47 + border-radius: 0 0 3px 3px;
  48 + padding: 4px 10px;
  49 + position: relative;
  50 +
  51 + div, a {
  52 + display: inline-block;
  53 + background: #69677C;
  54 + border-radius: 0;
  55 + color: #FFF;
  56 + font-family: "Ubuntu Medium";
  57 + font-size: 16px;
  58 + margin: 0;
  59 + padding-right: 15px;
  60 + font-weight: bold;
  61 + }
  62 +
  63 + div:first-child {
  64 + padding: 15px 5px 15px 50px;
  65 + background: #69677C url("../assets/icons/participa-consulta/home.png") no-repeat 7px center;
  66 + color: #FFF;
  67 + background-size: 30px 30px;
  68 + }
  69 + }
  70 +}
... ...
themes/angular-participa-consulta/app/navbar.scss 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +.navbar {
  2 + min-height: 123px;
  3 + background-color: #f9c404;
  4 + background-image: -moz-radial-gradient(center, ellipse cover, #fcdd4e 1%, #f9c404 100%);
  5 + background-image: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(1%, #fcdd4e), color-stop(100%, #f9c404));
  6 + background-image: -webkit-radial-gradient(center, ellipse cover, #fcdd4e 1%, #f9c404 100%);
  7 + background-image: -o-radial-gradient(center, ellipse cover, #fcdd4e 1%, #f9c404 100%);
  8 + background-image: -ms-radial-gradient(center, ellipse cover, #fcdd4e 1%, #f9c404 100%);
  9 + background-image: radial-gradient(ellipse at center, #fcdd4e 1%, #f9c404 100%);
  10 +
  11 + .container-fluid {
  12 + .navbar-brand {
  13 + .noosfero-logo {
  14 + display: none;
  15 + }
  16 + .noosfero-name {
  17 + color: #03316f;
  18 + font-size: 40px;
  19 + font-weight: 800;
  20 + line-height: 1em;
  21 + letter-spacing: -0.05em;
  22 + }
  23 + }
  24 + }
  25 +}
... ...
themes/angular-participa-consulta/app/participa-consulta.scss 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +@font-face {
  2 + font-family: 'Ubuntu';
  3 + font-weight: 300;
  4 + font-style: normal;
  5 + src: url('../assets/fonts/participa-consulta/Ubuntu-R.ttf');
  6 +}
  7 +
  8 +@font-face {
  9 + font-family: 'Ubuntu Medium';
  10 + font-weight: 300;
  11 + font-style: normal;
  12 + src: url('../assets/fonts/participa-consulta/Ubuntu-M.ttf');
  13 +}
  14 +
  15 +@font-face {
  16 + font-family: 'Ubuntu';
  17 + font-weight: 300;
  18 + font-style: italic;
  19 + src: url('../assets/fonts/participa-consulta/Ubuntu-RI.ttf');
  20 +}
  21 +
  22 +.skin-whbl .notifications-list .item-footer {
  23 + background: #DAE1C4;
  24 + color: #4F9CAC;
  25 +}
  26 +
  27 +.profile-header, .profile-footer{
  28 + text-align: center;
  29 +}
  30 +
  31 +.container-fluid .navbar-header .navbar-toggle{
  32 + &:hover, &:focus {
  33 + background-color: #7E7E7E;
  34 + }
  35 +}
... ...
themes/angular-participa-consulta/assets/fonts/participa-consulta/Ubuntu-B.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/Ubuntu-BI.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/Ubuntu-C.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/Ubuntu-L.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/Ubuntu-LI.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/Ubuntu-M.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/Ubuntu-MI.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/Ubuntu-R.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/Ubuntu-RI.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/UbuntuMono-B.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/UbuntuMono-BI.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/UbuntuMono-R.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/fonts/participa-consulta/UbuntuMono-RI.ttf 0 → 100644
No preview for this file type
themes/angular-participa-consulta/assets/icons/participa-consulta/agenda-contagem.png 0 → 100644

921 Bytes

themes/angular-participa-consulta/assets/icons/participa-consulta/balao.png 0 → 100644

528 Bytes

themes/angular-participa-consulta/assets/icons/participa-consulta/balao_claro.png 0 → 100644

517 Bytes

themes/angular-participa-consulta/assets/icons/participa-consulta/closed.png 0 → 100644

577 Bytes

themes/angular-participa-consulta/assets/icons/participa-consulta/como-participar.png 0 → 100644

1.14 KB

themes/angular-participa-consulta/assets/icons/participa-consulta/home.png 0 → 100644

777 Bytes

themes/angular-participa-consulta/assets/icons/participa-consulta/icon-attend.png 0 → 100644

1.32 KB

themes/angular-participa-consulta/assets/icons/participa-consulta/indicadores.png 0 → 100644

1004 Bytes