Commit 1894afc0521954895e6b817afa85c8c4a51e8d5e

Authored by Michel Felipe
2 parents 0e6cfa00 5f9ef2ae

Merge branch 'create-article' into 'master'

Basic form to create and edit articles

See merge request !11
bower.json
... ... @@ -34,7 +34,9 @@
34 34 "angular-dynamic-locale": "^0.1.30",
35 35 "angular-i18n": "^1.5.0",
36 36 "angular-load": "^0.4.1",
37   - "angular-translate-interpolation-messageformat": "^2.10.0"
  37 + "angular-translate-interpolation-messageformat": "^2.10.0",
  38 + "ng-ckeditor": "^0.2.1",
  39 + "ckeditor": "^4.5.8"
38 40 },
39 41 "devDependencies": {
40 42 "angular-mocks": "~1.5.0"
... ...
src/app/article/article.html
... ... @@ -4,6 +4,9 @@
4 4 </div>
5 5  
6 6 <div class="sub-header clearfix">
  7 + <a href="#" class="btn btn-default btn-xs" ui-sref="main.cmsEdit({profile: ctrl.profile.identifier, id: ctrl.article.id})">
  8 + <i class="fa fa-pencil-square-o fa-fw fa-lg"></i> {{"article.actions.edit" | translate}}
  9 + </a>
7 10 <div class="page-info pull-right small text-muted">
8 11 <span class="time">
9 12 <i class="fa fa-clock-o"></i> <span am-time-ago="ctrl.article.created_at | dateFormat"></span>
... ...
src/app/article/basic-editor.component.spec.ts
... ... @@ -1,55 +0,0 @@
1   -import {quickCreateComponent} from "../../spec/helpers";
2   -import {BasicEditorComponent} from "./basic-editor.component";
3   -
4   -
5   -describe("Article BasicEditor", () => {
6   -
7   - let $rootScope: ng.IRootScopeService;
8   - let $q: ng.IQService;
9   - let articleServiceMock: any;
10   - let profileServiceMock: any;
11   - let $state: any;
12   - let profile = { id: 1 };
13   - let notification: any;
14   -
15   -
16   - beforeEach(inject((_$rootScope_: ng.IRootScopeService, _$q_: ng.IQService) => {
17   - $rootScope = _$rootScope_;
18   - $q = _$q_;
19   - }));
20   -
21   - beforeEach(() => {
22   - $state = jasmine.createSpyObj("$state", ["transitionTo"]);
23   - notification = jasmine.createSpyObj("notification", ["success"]);
24   - profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["getCurrentProfile"]);
25   - articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInProfile"]);
26   -
27   - let getCurrentProfileResponse = $q.defer();
28   - getCurrentProfileResponse.resolve(profile);
29   -
30   - let articleCreate = $q.defer();
31   - articleCreate.resolve({ data: { path: "path", profile: { identifier: "profile" } } });
32   -
33   - profileServiceMock.getCurrentProfile = jasmine.createSpy("getCurrentProfile").and.returnValue(getCurrentProfileResponse.promise);
34   - articleServiceMock.createInProfile = jasmine.createSpy("createInProfile").and.returnValue(articleCreate.promise);
35   - });
36   -
37   - it("create an article in the current profile when save", done => {
38   - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification);
39   - component.save();
40   - $rootScope.$apply();
41   - expect(profileServiceMock.getCurrentProfile).toHaveBeenCalled();
42   - expect(articleServiceMock.createInProfile).toHaveBeenCalledWith(profile, component.article);
43   - done();
44   - });
45   -
46   - it("got to the new article page and display an alert when saving sucessfully", done => {
47   - let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification);
48   - component.save();
49   - $rootScope.$apply();
50   - expect($state.transitionTo).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "profile" });
51   - expect(notification.success).toHaveBeenCalled();
52   - done();
53   - });
54   -
55   -});
src/app/article/basic-editor.component.ts
... ... @@ -1,35 +0,0 @@
1   -import {StateConfig, Component, Inject, provide} from 'ng-forward';
2   -import {ArticleService} from "../../lib/ng-noosfero-api/http/article.service";
3   -import {ProfileService} from "../../lib/ng-noosfero-api/http/profile.service";
4   -import {NotificationService} from "../shared/services/notification.service.ts";
5   -
6   -@Component({
7   - selector: 'article-basic-editor',
8   - templateUrl: "app/article/basic-editor.html",
9   - providers: [
10   - provide('articleService', { useClass: ArticleService }),
11   - provide('profileService', { useClass: ProfileService }),
12   - provide('notification', { useClass: NotificationService })
13   - ]
14   -})
15   -@Inject(ArticleService, ProfileService, "$state", NotificationService)
16   -export class BasicEditorComponent {
17   -
18   - article: noosfero.Article = <noosfero.Article>{};
19   -
20   - constructor(private articleService: ArticleService,
21   - private profileService: ProfileService,
22   - private $state: ng.ui.IStateService,
23   - private notification: NotificationService) { }
24   -
25   - save() {
26   - this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
27   - return this.articleService.createInProfile(profile, this.article);
28   - }).then((response: noosfero.RestResult<noosfero.Article>) => {
29   - let article = (<noosfero.Article>response.data);
30   - this.$state.transitionTo('main.profile.page', { page: article.path, profile: article.profile.identifier });
31   - this.notification.success({ title: "Good job!", message: "Article saved!" });
32   - });
33   - }
34   -
35   -}
src/app/article/basic-editor.html
... ... @@ -1,11 +0,0 @@
1   -<form>
2   - <div class="form-group">
3   - <label for="titleInput">Title</label>
4   - <input type="text" class="form-control" id="titleInput" placeholder="title" ng-model="vm.article.name">
5   - </div>
6   - <div class="form-group">
7   - <label for="bodyInput">Text</label>
8   - <textarea class="form-control" id="bodyInput" rows="10" ng-model="vm.article.body"></textarea>
9   - </div>
10   - <button type="submit" class="btn btn-default" ng-click="vm.save()">Save</button>
11   -</form>
src/app/article/basic-editor/basic-editor.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,84 @@
  1 +import {quickCreateComponent} from "../../../spec/helpers";
  2 +import {BasicEditorComponent} from "./basic-editor.component";
  3 +
  4 +
  5 +describe("Article BasicEditor", () => {
  6 +
  7 + let $rootScope: ng.IRootScopeService;
  8 + let $q: ng.IQService;
  9 + let articleServiceMock: any;
  10 + let profileServiceMock: any;
  11 + let $state: any;
  12 + let $stateParams: any;
  13 + let $window: any;
  14 + let profile = { id: 1 };
  15 + let notification: any;
  16 +
  17 +
  18 + beforeEach(inject((_$rootScope_: ng.IRootScopeService, _$q_: ng.IQService) => {
  19 + $rootScope = _$rootScope_;
  20 + $q = _$q_;
  21 + }));
  22 +
  23 + beforeEach(() => {
  24 + $window = jasmine.createSpyObj("$window", ["back"]);
  25 + $state = jasmine.createSpyObj("$state", ["go"]);
  26 + notification = jasmine.createSpyObj("notification", ["success"]);
  27 + profileServiceMock = jasmine.createSpyObj("profileServiceMock", ["setCurrentProfileByIdentifier"]);
  28 + articleServiceMock = jasmine.createSpyObj("articleServiceMock", ["createInParent", "updateArticle", "get"]);
  29 +
  30 + $stateParams = { profile: "profile" };
  31 +
  32 + let setCurrentProfileByIdentifierResponse = $q.defer();
  33 + setCurrentProfileByIdentifierResponse.resolve(profile);
  34 +
  35 + let articleCreate = $q.defer();
  36 + articleCreate.resolve({ data: { path: "path", profile: { identifier: "profile" } } });
  37 +
  38 + let articleGet = $q.defer();
  39 + articleGet.resolve({ data: { path: "parent-path", profile: { identifier: "profile" } } });
  40 +
  41 + profileServiceMock.setCurrentProfileByIdentifier = jasmine.createSpy("setCurrentProfileByIdentifier").and.returnValue(setCurrentProfileByIdentifierResponse.promise);
  42 + articleServiceMock.createInParent = jasmine.createSpy("createInParent").and.returnValue(articleCreate.promise);
  43 + articleServiceMock.updateArticle = jasmine.createSpy("updateArticle").and.returnValue(articleCreate.promise);
  44 + articleServiceMock.get = jasmine.createSpy("get").and.returnValue(articleGet.promise);
  45 + });
  46 +
  47 + it("create an article in the current profile when save", done => {
  48 + $stateParams['parent_id'] = 1;
  49 + let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  50 + component.save();
  51 + $rootScope.$apply();
  52 + expect(profileServiceMock.setCurrentProfileByIdentifier).toHaveBeenCalled();
  53 + expect(articleServiceMock.createInParent).toHaveBeenCalledWith(1, component.article);
  54 + done();
  55 + });
  56 +
  57 + it("got to the new article page and display an alert when saving sucessfully", done => {
  58 + let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  59 + component.save();
  60 + $rootScope.$apply();
  61 + expect($state.go).toHaveBeenCalledWith("main.profile.page", { page: "path", profile: "profile" });
  62 + expect(notification.success).toHaveBeenCalled();
  63 + done();
  64 + });
  65 +
  66 + it("go back when cancel article edition", done => {
  67 + let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  68 + $window.history = { back: jasmine.createSpy('back') };
  69 + component.cancel();
  70 + expect($window.history.back).toHaveBeenCalled();
  71 + done();
  72 + });
  73 +
  74 + it("edit existing article when save", done => {
  75 + $stateParams['parent_id'] = null;
  76 + $stateParams['id'] = 2;
  77 + let component: BasicEditorComponent = new BasicEditorComponent(articleServiceMock, profileServiceMock, $state, notification, $stateParams, $window);
  78 + component.save();
  79 + $rootScope.$apply();
  80 + expect(articleServiceMock.updateArticle).toHaveBeenCalledWith(component.article);
  81 + done();
  82 + });
  83 +
  84 +});
... ...
src/app/article/basic-editor/basic-editor.component.ts 0 → 100644
... ... @@ -0,0 +1,69 @@
  1 +import {StateConfig, Component, Inject, provide} from 'ng-forward';
  2 +import {ArticleService} from "../../../lib/ng-noosfero-api/http/article.service";
  3 +import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service";
  4 +import {NotificationService} from "../../shared/services/notification.service.ts";
  5 +
  6 +@Component({
  7 + selector: 'article-basic-editor',
  8 + templateUrl: "app/article/basic-editor/basic-editor.html",
  9 + providers: [
  10 + provide('articleService', { useClass: ArticleService }),
  11 + provide('profileService', { useClass: ProfileService }),
  12 + provide('notification', { useClass: NotificationService })
  13 + ]
  14 +})
  15 +@Inject(ArticleService, ProfileService, "$state", NotificationService, "$stateParams", "$window")
  16 +export class BasicEditorComponent {
  17 +
  18 + article: noosfero.Article = <noosfero.Article>{ type: "TextArticle" };
  19 + parent: noosfero.Article = <noosfero.Article>{};
  20 +
  21 + id: number;
  22 + parentId: number;
  23 + profileIdentifier: string;
  24 +
  25 + constructor(private articleService: ArticleService,
  26 + private profileService: ProfileService,
  27 + private $state: ng.ui.IStateService,
  28 + private notification: NotificationService,
  29 + private $stateParams: ng.ui.IStateParamsService,
  30 + private $window: ng.IWindowService) {
  31 +
  32 + this.parentId = this.$stateParams['parent_id'];
  33 + this.profileIdentifier = this.$stateParams["profile"];
  34 + this.id = this.$stateParams['id'];
  35 +
  36 + if (this.parentId) {
  37 + this.articleService.get(this.parentId).then((result: noosfero.RestResult<noosfero.Article>) => {
  38 + this.parent = result.data;
  39 + });
  40 + }
  41 + if (this.id) {
  42 + this.articleService.get(this.id).then((result: noosfero.RestResult<noosfero.Article>) => {
  43 + this.article = result.data;
  44 + this.article.name = this.article.title; // FIXME
  45 + });
  46 + }
  47 + }
  48 +
  49 + save() {
  50 + this.profileService.setCurrentProfileByIdentifier(this.profileIdentifier).then((profile: noosfero.Profile) => {
  51 + if (this.id) {
  52 + return this.articleService.updateArticle(this.article);
  53 + } else {
  54 + return this.articleService.createInParent(this.parentId, this.article);
  55 + }
  56 + }).then((response: noosfero.RestResult<noosfero.Article>) => {
  57 + let article = (<noosfero.Article>response.data);
  58 + this.$state.go('main.profile.page', { page: article.path, profile: article.profile.identifier });
  59 + this.notification.success({ title: "article.basic_editor.success.title", message: "article.basic_editor.success.message" });
  60 + }).catch(() => {
  61 + this.notification.error({ message: "article.basic_editor.save.failed" });
  62 + });
  63 + }
  64 +
  65 + cancel() {
  66 + this.$window.history.back();
  67 + }
  68 +
  69 +}
... ...
src/app/article/basic-editor/basic-editor.html 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +<div class="basic-editor">
  2 + <div class="row">
  3 + <div class="col-md-1"></div>
  4 + <div class="col-md-8">
  5 + <form>
  6 + <div class="form-group">
  7 + <label for="titleInput">{{"article.basic_editor.title" | translate}}</label>
  8 + <input type="text" class="form-control" id="titleInput" placeholder="{{'article.basic_editor.title' | translate}}" ng-model="vm.article.name">
  9 + </div>
  10 + <div class="form-group">
  11 + <label for="bodyInput">{{"article.basic_editor.body" | translate}}</label>
  12 + <html-editor [(value)]="vm.article.body"></html-editor>
  13 + </div>
  14 + <button type="submit" class="btn btn-default" ng-click="vm.save()">{{"article.basic_editor.save" | translate}}</button>
  15 + <button type="button" class="btn btn-danger" ng-click="vm.cancel()">{{"article.basic_editor.cancel" | translate}}</button>
  16 + </form>
  17 + </div>
  18 + <div class="side-options">
  19 + <div class="visibility panel panel-default">
  20 + <div class="panel-heading">{{"article.basic_editor.visibility" | translate}}</div>
  21 + <div class="panel-body">
  22 + <div>
  23 + <input type="radio" ng-model="vm.article.published" ng-value="true">
  24 + <i class="fa fa-unlock fa-fw"></i> {{"article.basic_editor.visibility.public" | translate}}
  25 + </div>
  26 + <div>
  27 + <input type="radio" ng-model="vm.article.published" ng-value="false">
  28 + <i class="fa fa-lock fa-fw"></i> {{"article.basic_editor.visibility.private" | translate}}
  29 + </div>
  30 + </div>
  31 + </div>
  32 + </div>
  33 + </div>
  34 +</div>
... ...
src/app/article/basic-editor/basic-editor.scss 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +.basic-editor {
  2 + @extend .container-fluid;
  3 + padding: 0 1%;
  4 +
  5 + .side-options {
  6 + @extend .col-md-3;
  7 + margin-top: 25px;
  8 +
  9 + .visibility {
  10 + .panel-heading {
  11 + background-color: transparent;
  12 + font-weight: bold;
  13 + }
  14 + .panel-body {
  15 + i {
  16 + color: #A5A5A5;
  17 + }
  18 + }
  19 + }
  20 + }
  21 +}
... ...
src/app/article/content-viewer/content-viewer-actions.component.spec.ts
1   -import {providers} from 'ng-forward/cjs/testing/providers';
2   -
3 1 import {Input, Component, provide} from 'ng-forward';
4 2  
5 3 import * as helpers from "../../../spec/helpers";
6   -
7   -import {ComponentFixture} from 'ng-forward/cjs/testing/test-component-builder';
  4 +import {ComponentTestHelper, createClass} from '../../../spec/component-test-helper';
8 5 import {ContentViewerActionsComponent} from './content-viewer-actions.component';
9 6  
10 7 // this htmlTemplate will be re-used between the container components in this spec file
... ... @@ -12,56 +9,57 @@ const htmlTemplate: string = &#39;&lt;content-viewer-actions [article]=&quot;ctrl.article&quot; [
12 9  
13 10 describe('Content Viewer Actions Component', () => {
14 11  
15   - beforeEach(() => {
  12 + let helper: ComponentTestHelper<ContentViewerActionsComponent>;
16 13  
17   - angular.mock.module("templates");
  14 + beforeEach(angular.mock.module("templates"));
18 15  
19   - providers((provide: any) => {
20   - return <any>[
21   - provide('ProfileService', {
22   - useValue: helpers.mocks.profileService
23   - })
24   - ];
25   - });
26   - });
  16 + let providers = [
  17 + provide('ProfileService', {
  18 + useValue: helpers.mocks.profileService
  19 + }),
  20 + provide('ArticleService', {
  21 + useValue: helpers.mocks.articleService
  22 + })
  23 + ].concat(helpers.provideFilters("translateFilter"));
27 24  
28   - let buildComponent = (): Promise<ComponentFixture> => {
29   - return helpers.quickCreateComponent({
30   - providers: [
31   - helpers.provideEmptyObjects('Restangular'),
32   - helpers.provideFilters('translateFilter')
33   - ],
  25 + beforeEach((done) => {
  26 + let cls = createClass({
  27 + template: htmlTemplate,
34 28 directives: [ContentViewerActionsComponent],
35   - template: htmlTemplate
  29 + providers: providers
36 30 });
37   - };
  31 + helper = new ComponentTestHelper<ContentViewerActionsComponent>(cls, done);
  32 + });
38 33  
39   - it('renders content viewer actions directive', (done: Function) => {
40   - buildComponent().then((fixture: ComponentFixture) => {
41   - expect(fixture.debugElement.query('content-viewer-actions').length).toEqual(1);
  34 + it('renders content viewer actions directive', () => {
  35 + expect(helper.all("content-viewer-actions").length).toEqual(1);
  36 + });
42 37  
43   - done();
44   - });
  38 + it('return article parent as container when it is not a folder', () => {
  39 + let article = <noosfero.Article>({ id: 1, type: 'TextArticle', parent: { id: 2 } });
  40 + expect(helper.component.getArticleContainer(article)).toEqual(2);
  41 + });
  42 +
  43 + it('return article as container when it is a folder', () => {
  44 + let article = <noosfero.Article>({ id: 1, type: 'Folder' });
  45 + expect(helper.component.getArticleContainer(article)).toEqual(1);
45 46 });
46 47  
47   - it('check if profile was loaded', (done: Function) => {
  48 + it('return article as container when it is a blog', () => {
  49 + let article = <noosfero.Article>({ id: 1, type: 'Blog' });
  50 + expect(helper.component.getArticleContainer(article)).toEqual(1);
  51 + });
  52 +
  53 + it('check if profile was loaded', () => {
48 54 let profile: any = {
49 55 id: 1,
50 56 identifier: 'the-profile-test',
51 57 type: 'Person'
52 58 };
53   -
54 59 helpers.mocks.profileService.getCurrentProfile = () => {
55 60 return helpers.mocks.promiseResultTemplate(profile);
56 61 };
57   -
58   - buildComponent().then((fixture: ComponentFixture) => {
59   - let contentViewerComp: ContentViewerActionsComponent = fixture.debugElement.componentViewChildren[0].componentInstance;
60   -
61   - expect(contentViewerComp.profile).toEqual(jasmine.objectContaining(profile));
62   -
63   - done();
64   - });
  62 + let component = new ContentViewerActionsComponent(<any>helpers.mocks.profileService, <any>helpers.mocks.articleService);
  63 + expect(component.profile).toEqual(jasmine.objectContaining(profile));
65 64 });
66   -
67 65 });
... ...
src/app/article/content-viewer/content-viewer-actions.component.ts
1 1 import {Component, Inject, provide} from "ng-forward";
2 2 import {ProfileService} from "../../../lib/ng-noosfero-api/http/profile.service";
  3 +import {ArticleService} from "../../../lib/ng-noosfero-api/http/article.service";
3 4  
4 5 @Component({
5 6 selector: "content-viewer-actions",
6 7 templateUrl: "app/article/content-viewer/navbar-actions.html",
7   - providers: [provide('profileService', { useClass: ProfileService })]
  8 + providers: [
  9 + provide('profileService', { useClass: ProfileService }),
  10 + provide('articleService', { useClass: ArticleService })
  11 + ]
8 12 })
9   -@Inject(ProfileService)
  13 +@Inject(ProfileService, ArticleService)
10 14 export class ContentViewerActionsComponent {
11 15  
12 16 article: noosfero.Article;
13 17 profile: noosfero.Profile;
  18 + parentId: number;
14 19  
15   - constructor(profileService: ProfileService) {
  20 + constructor(profileService: ProfileService, articleService: ArticleService) {
16 21 profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
17 22 this.profile = profile;
  23 + return articleService.getCurrent();
  24 + }).then((article: noosfero.Article) => {
  25 + this.article = article;
  26 + this.parentId = this.getArticleContainer(article);
18 27 });
19 28 }
  29 +
  30 + getArticleContainer(article: noosfero.Article) {
  31 + // FIXME get folder types from api
  32 + if (article.type === "Blog" || article.type === "Folder") {
  33 + return article.id;
  34 + } else if (article.parent) {
  35 + return article.parent.id;
  36 + }
  37 + }
20 38 }
... ...
src/app/article/content-viewer/content-viewer.component.ts
... ... @@ -28,11 +28,12 @@ export class ContentViewerComponent {
28 28 }
29 29  
30 30 activate() {
31   - this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
  31 + this.profileService.getCurrentProfile().then((profile: noosfero.Profile) => {
32 32 this.profile = profile;
33 33 return this.articleService.getArticleByProfileAndPath(this.profile, this.$stateParams["page"]);
34 34 }).then((result: noosfero.RestResult<any>) => {
35 35 this.article = <noosfero.Article>result.data;
  36 + this.articleService.setCurrent(this.article);
36 37 });
37 38 }
38 39 }
... ...
src/app/article/content-viewer/navbar-actions.html
1 1 <ul class="nav navbar-nav">
2 2 <li ng-show="vm.profile">
3   - <a href="#" role="button" ui-sref="main.profile.cms({profile: vm.profile.identifier})">
  3 + <a ng-show="vm.parentId" href="#" role="button" ui-sref="main.cms({profile: vm.profile.identifier, parent_id: vm.parentId})">
4 4 <i class="fa fa-file fa-fw fa-lg"></i> {{"navbar.content_viewer_actions.new_post" | translate}}
5 5 </a>
6 6 </li>
... ...
src/app/article/index.ts
1 1 /* Module Index Entry - generated using the script npm run generate-index */
2 2 export * from "./article-default-view.component";
3   -export * from "./basic-editor.component";
... ...
src/app/index.ts
... ... @@ -14,7 +14,7 @@ declare var moment: any;
14 14  
15 15 let noosferoApp: any = bundle("noosferoApp", MainComponent, ["ngAnimate", "ngCookies", "ngStorage", "ngTouch",
16 16 "ngSanitize", "ngMessages", "ngAria", "restangular",
17   - "ui.router", "ui.bootstrap", "toastr",
  17 + "ui.router", "ui.bootstrap", "toastr", "ngCkeditor",
18 18 "angularMoment", "angular.filter", "akoenig.deckgrid",
19 19 "angular-timeline", "duScroll", "oitozero.ngSweetAlert",
20 20 "pascalprecht.translate", "tmh.dynamicLocale", "angularLoad"]).publish();
... ...
src/app/main/main.component.ts
... ... @@ -29,6 +29,7 @@ import {BodyStateClassesService} from &quot;./../layout/services/body-state-classes.s
29 29 import {Navbar} from "../layout/navbar/navbar";
30 30  
31 31 import {MainBlockComponent} from "../layout/blocks/main-block/main-block.component";
  32 +import {HtmlEditorComponent} from "../shared/components/html-editor/html-editor.component";
32 33  
33 34  
34 35 /**
... ... @@ -82,7 +83,7 @@ export class EnvironmentContent {
82 83 directives: [
83 84 ArticleBlogComponent, ArticleViewComponent, BoxesComponent, BlockComponent,
84 85 EnvironmentComponent, PeopleBlockComponent,
85   - LinkListBlockComponent, CommunitiesBlockComponent,
  86 + LinkListBlockComponent, CommunitiesBlockComponent, HtmlEditorComponent,
86 87 MainBlockComponent, RecentDocumentsBlockComponent, Navbar, ProfileImageBlockComponent,
87 88 MembersBlockComponent, NoosferoTemplate, DateFormat, RawHTMLBlockComponent
88 89 ],
... ...
src/app/profile/profile.component.ts
1 1 import {StateConfig, Component, Inject, provide} from 'ng-forward';
2 2 import {ProfileInfoComponent} from './info/profile-info.component';
3 3 import {ProfileHomeComponent} from './profile-home.component';
4   -import {BasicEditorComponent} from '../article/basic-editor.component';
  4 +import {BasicEditorComponent} from '../article/basic-editor/basic-editor.component';
5 5 import {ContentViewerComponent} from "../article/content-viewer/content-viewer.component";
6 6 import {ContentViewerActionsComponent} from "../article/content-viewer/content-viewer-actions.component";
7 7 import {ActivitiesComponent} from "./activities/activities.component";
... ... @@ -45,12 +45,24 @@ import {MyProfileComponent} from &quot;./myprofile.component&quot;;
45 45 component: MyProfileComponent
46 46 },
47 47 {
48   - name: 'main.profile.cms',
49   - url: "^/myprofile/:profile/cms",
  48 + name: 'main.cms',
  49 + url: "^/myprofile/:profile/cms?parent_id",
50 50 component: BasicEditorComponent,
51 51 views: {
52   - "mainBlockContent": {
53   - templateUrl: "app/article/basic-editor.html",
  52 + "content": {
  53 + templateUrl: "app/article/basic-editor/basic-editor.html",
  54 + controller: BasicEditorComponent,
  55 + controllerAs: "vm"
  56 + }
  57 + }
  58 + },
  59 + {
  60 + name: 'main.cmsEdit',
  61 + url: "^/myprofile/:profile/cms/edit/:id",
  62 + component: BasicEditorComponent,
  63 + views: {
  64 + "content": {
  65 + templateUrl: "app/article/basic-editor/basic-editor.html",
54 66 controller: BasicEditorComponent,
55 67 controllerAs: "vm"
56 68 }
... ... @@ -98,7 +110,7 @@ export class ProfileComponent {
98 110 }).then((response: restangular.IResponse) => {
99 111 this.boxes = response.data.boxes;
100 112 }).catch(() => {
101   - $state.transitionTo('main');
  113 + $state.transitionTo('main.environment.home');
102 114 notificationService.error({ message: "notification.profile.not_found" });
103 115 });
104 116 }
... ...
src/app/shared/components/html-editor/html-editor.component.spec.ts 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +import {ComponentTestHelper, createClass} from './../../../../spec/component-test-helper';
  2 +import {HtmlEditorComponent} from "./html-editor.component";
  3 +
  4 +const htmlTemplate: string = '<html-editor [(value)]="ctrl.value" [options]="ctrl.options"></html-editor>';
  5 +
  6 +describe("Components", () => {
  7 + describe("Html Editor Component", () => {
  8 +
  9 + let helper: ComponentTestHelper<HtmlEditorComponent>;
  10 + beforeEach(angular.mock.module("templates"));
  11 +
  12 + beforeEach((done) => {
  13 + let properties = { value: "value" };
  14 + let cls = createClass({
  15 + template: htmlTemplate,
  16 + directives: [HtmlEditorComponent],
  17 + properties: properties
  18 + });
  19 + helper = new ComponentTestHelper<HtmlEditorComponent>(cls, done);
  20 + });
  21 +
  22 + it("render a textarea", () => {
  23 + expect(helper.find("textarea").length).toEqual(1);
  24 + });
  25 + });
  26 +});
... ...
src/app/shared/components/html-editor/html-editor.component.ts 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +import {Component, Input} from "ng-forward";
  2 +
  3 +@Component({
  4 + selector: 'html-editor',
  5 + templateUrl: "app/shared/components/html-editor/html-editor.html",
  6 +})
  7 +export class HtmlEditorComponent {
  8 +
  9 + @Input() options: any = {};
  10 + @Input() value: any;
  11 +}
... ...
src/app/shared/components/html-editor/html-editor.html 0 → 100644
... ... @@ -0,0 +1 @@
  1 +<textarea ckeditor="ctrl.options" class="form-control" ng-model="ctrl.value"></textarea>
... ...
src/app/shared/services/notification.service.ts
... ... @@ -25,7 +25,7 @@ export class NotificationService {
25 25  
26 26 httpError(status: number, data: any): boolean {
27 27 this.error({ message: `notification.http_error.${status}.message` });
28   - return true; // return true to indicate that the error was already handled
  28 + return false; // return true to indicate that the error was already handled
29 29 }
30 30  
31 31 success({
... ...
src/languages/en.json
... ... @@ -35,5 +35,16 @@
35 35 "comment.pagination.more": "More",
36 36 "comment.post.success.title": "Good job!",
37 37 "comment.post.success.message": "Comment saved!",
38   - "comment.reply": "reply"
  38 + "comment.reply": "reply",
  39 + "article.actions.edit": "Edit",
  40 + "article.basic_editor.title": "Title",
  41 + "article.basic_editor.body": "Body",
  42 + "article.basic_editor.save": "Save",
  43 + "article.basic_editor.save.failed": "This article could not be saved",
  44 + "article.basic_editor.cancel": "Cancel",
  45 + "article.basic_editor.success.title": "Good job!",
  46 + "article.basic_editor.success.message": "Article saved!",
  47 + "article.basic_editor.visibility": "Visibility",
  48 + "article.basic_editor.visibility.public": "Public",
  49 + "article.basic_editor.visibility.private": "Private"
39 50 }
... ...
src/languages/pt.json
... ... @@ -35,5 +35,16 @@
35 35 "comment.pagination.more": "Mais",
36 36 "comment.post.success.title": "Bom trabalho!",
37 37 "comment.post.success.message": "Comentário salvo com sucesso!",
38   - "comment.reply": "responder"
  38 + "comment.reply": "responder",
  39 + "article.actions.edit": "Editar",
  40 + "article.basic_editor.title": "Título",
  41 + "article.basic_editor.body": "Corpo",
  42 + "article.basic_editor.save": "Salvar",
  43 + "article.basic_editor.save.failed": "O artigo não pode ser salvo",
  44 + "article.basic_editor.cancel": "Cancelar",
  45 + "article.basic_editor.success.title": "Bom trabalho!",
  46 + "article.basic_editor.success.message": "Artigo salvo com sucesso!",
  47 + "article.basic_editor.visibility": "Visibilidade",
  48 + "article.basic_editor.visibility.public": "Público",
  49 + "article.basic_editor.visibility.private": "Privado"
39 50 }
... ...
src/lib/ng-noosfero-api/http/article.service.ts
... ... @@ -22,6 +22,17 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
22 22 };
23 23 }
24 24  
  25 + updateArticle(article: noosfero.Article) {
  26 + let headers = {
  27 + 'Content-Type': 'application/json'
  28 + };
  29 + let deferred = this.$q.defer<noosfero.RestResult<noosfero.Article>>();
  30 + let attributesToUpdate: any = { article: { name: article.name, body: article.body, published: article.published } };
  31 + let restRequest: ng.IPromise<noosfero.RestResult<noosfero.Article>> = this.getElement(article.id).customPOST(attributesToUpdate, null, null, headers);
  32 + restRequest.then(this.getHandleSuccessFunction(deferred))
  33 + .catch(this.getHandleErrorFunction(deferred));
  34 + return deferred.promise;
  35 + }
25 36  
26 37 createInProfile(profile: noosfero.Profile, article: noosfero.Article): ng.IPromise<noosfero.RestResult<noosfero.Article>> {
27 38 let profileElement = this.profileService.get(<number>profile.id);
... ... @@ -32,6 +43,14 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
32 43 return this.create(article, <noosfero.RestModel>profileElement, null, headers);
33 44 }
34 45  
  46 + createInParent(parentId: number, article: noosfero.Article): ng.IPromise<noosfero.RestResult<noosfero.Article>> {
  47 + let headers = {
  48 + 'Content-Type': 'application/json'
  49 + };
  50 +
  51 + let parent = this.getElement(parentId);
  52 + return this.create(article, parent, null, headers, true, "children");
  53 + }
35 54  
36 55 getAsCollectionChildrenOf<C>(rootElement: noosfero.Environment | noosfero.Article | noosfero.Profile, path: string, queryParams?: any, headers?: any): restangular.ICollectionPromise<C> {
37 56 return rootElement.getList<C>(path, queryParams, headers);
... ... @@ -77,5 +96,4 @@ export class ArticleService extends RestangularService&lt;noosfero.Article&gt; {
77 96 }
78 97  
79 98  
80   -
81 99 }
... ...
src/lib/ng-noosfero-api/http/restangular_service.ts
... ... @@ -11,6 +11,8 @@
11 11 export abstract class RestangularService<T extends noosfero.RestModel> {
12 12  
13 13 private baseResource: restangular.IElement;
  14 + private currentPromise: ng.IDeferred<T>;
  15 +
14 16 /**
15 17 * Creates an instance of RestangularService.
16 18 *
... ... @@ -20,6 +22,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
20 22 */
21 23 constructor(protected restangularService: restangular.IService, protected $q: ng.IQService, protected $log: ng.ILogService) {
22 24 this.baseResource = restangularService.all(this.getResourcePath());
  25 + this.resetCurrent();
23 26 // TODO
24 27 // this.restangularService.setResponseInterceptor((data, operation, what, url, response, deferred) => {
25 28 // let transformedData: any = data;
... ... @@ -32,6 +35,18 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
32 35 // });
33 36 }
34 37  
  38 + public resetCurrent() {
  39 + this.currentPromise = this.$q.defer();
  40 + }
  41 +
  42 + public getCurrent(): ng.IPromise<T> {
  43 + return this.currentPromise.promise;
  44 + }
  45 +
  46 + public setCurrent(object: T) {
  47 + this.currentPromise.resolve(object);
  48 + }
  49 +
35 50 protected extractData(response: restangular.IResponse): noosfero.RestResult<T> {
36 51 let dataKey: string;
37 52 if (response.data && this.getDataKeys()) {
... ... @@ -221,7 +236,7 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
221 236 * Creates a new Resource into the resource collection
222 237 * calls POST /resourcePath
223 238 */
224   - public create(obj: T, rootElement?: noosfero.RestModel, queryParams?: any, headers?: any, isSub: boolean = true): ng.IPromise<noosfero.RestResult<T>> {
  239 + public create(obj: T, rootElement?: noosfero.RestModel, queryParams?: any, headers?: any, isSub: boolean = true, path?: string): ng.IPromise<noosfero.RestResult<T>> {
225 240 let deferred = this.$q.defer<noosfero.RestResult<T>>();
226 241  
227 242 let restRequest: ng.IPromise<noosfero.RestResult<T>>;
... ... @@ -233,8 +248,9 @@ export abstract class RestangularService&lt;T extends noosfero.RestModel&gt; {
233 248 data = obj;
234 249 }
235 250  
  251 + let subpath = path || this.getResourcePath();
236 252 if (rootElement) {
237   - restRequest = rootElement.all(this.getResourcePath()).post(data, queryParams, headers);
  253 + restRequest = rootElement.all(subpath).post(data, queryParams, headers);
238 254 } else {
239 255 restRequest = this.baseResource.post(data, queryParams, headers);
240 256 }
... ...
src/lib/ng-noosfero-api/interfaces/article.ts
1 1  
2 2 namespace noosfero {
3   - export interface Article extends RestModel {
  3 + export interface Article extends RestModel {
4 4 path: string;
5 5 profile: Profile;
6 6 type: string;
  7 + parent: Article;
  8 + body: string;
  9 + title: string;
  10 + name: string;
  11 + published: boolean;
7 12 }
8   -}
9 13 \ No newline at end of file
  14 +}
... ...
src/spec/mocks.ts
1 1 const DEBUG = false;
2 2  
3   -let log = (message: string, ...args: any[]) => {
  3 +let log = (message: string, ...args: any[]) => {
4 4 if (DEBUG) {
5 5 console.log(message);
6 6 }
... ... @@ -70,7 +70,9 @@ export var mocks = {
70 70 return {
71 71 then: (func?: Function) => { if (func) func(); }
72 72 };
73   - }
  73 + },
  74 + setCurrent: (article: noosfero.Article) => { },
  75 + getCurrent: () => { return Promise.resolve({}); }
74 76 },
75 77 environmentService: {
76 78 getEnvironmentPeople: (params: any) => {
... ...