Browse Source

1) When saving a content don't autoclose the edit content pane. Closes #23

2) Improve Save Buttons. Closes #22
pull/65/head
Sebastian Stehle 9 years ago
parent
commit
25d5a42fa6
  1. 2
      src/Squidex.Write/Contents/Commands/CreateContent.cs
  2. 5
      src/Squidex.Write/Contents/ContentDomainObject.cs
  3. 4
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  4. 1
      src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs
  5. 19
      src/Squidex/app/features/content/pages/content/content-page.component.html
  6. 70
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  7. 30
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  8. 17
      src/Squidex/app/features/content/pages/messages.ts
  9. 12
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  10. 2
      src/Squidex/app/shared/components/component-base.ts
  11. 4
      src/Squidex/app/shared/services/contents.service.spec.ts
  12. 4
      src/Squidex/app/shared/services/contents.service.ts
  13. 12
      tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs

2
src/Squidex.Write/Contents/Commands/CreateContent.cs

@ -12,6 +12,8 @@ namespace Squidex.Write.Contents.Commands
{ {
public class CreateContent : ContentDataCommand public class CreateContent : ContentDataCommand
{ {
public bool Publish { get; set; }
public CreateContent() public CreateContent()
{ {
ContentId = Guid.NewGuid(); ContentId = Guid.NewGuid();

5
src/Squidex.Write/Contents/ContentDomainObject.cs

@ -75,6 +75,11 @@ namespace Squidex.Write.Contents
RaiseEvent(SimpleMapper.Map(command, new ContentCreated())); RaiseEvent(SimpleMapper.Map(command, new ContentCreated()));
if (command.Publish)
{
RaiseEvent(SimpleMapper.Map(command, new ContentPublished()));
}
return this; return this;
} }

4
src/Squidex/Controllers/ContentApi/ContentsController.cs

@ -115,9 +115,9 @@ namespace Squidex.Controllers.ContentApi
[HttpPost] [HttpPost]
[Route("content/{app}/{name}/")] [Route("content/{app}/{name}/")]
public async Task<IActionResult> PostContent([FromBody] ContentData request) public async Task<IActionResult> PostContent([FromBody] ContentData request, [FromQuery] bool publish = false)
{ {
var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned() }; var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned(), Publish = publish };
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);

1
src/Squidex/Controllers/ContentApi/Generator/SchemasSwaggerGenerator.cs

@ -266,6 +266,7 @@ namespace Squidex.Controllers.ContentApi.Generator
var responseSchema = CreateContentSchema(schemaName, schemaIdentifier, dataSchema); var responseSchema = CreateContentSchema(schemaName, schemaIdentifier, dataSchema);
operation.AddBodyParameter(dataSchema, "data", schemaBodyDescription); operation.AddBodyParameter(dataSchema, "data", schemaBodyDescription);
operation.AddQueryParameter("publish", JsonObjectType.Boolean, "Set to true to autopublish content.");
operation.AddResponse("201", $"{schemaName} created.", responseSchema); operation.AddResponse("201", $"{schemaName} created.", responseSchema);
}); });
} }

19
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -1,13 +1,24 @@
<sqx-title message="{app} | Content" parameter1="app" value1="{{appName() | async}}"></sqx-title> <sqx-title message="{app} | Content" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<form [formGroup]="contentForm" (ngSubmit)="saveContent()"> <form [formGroup]="contentForm" (ngSubmit)="saveAndPublish()">
<sqx-panel panelWidth="53rem"> <sqx-panel panelWidth="53rem">
<div class="panel-header"> <div class="panel-header">
<div class="panel-title-row"> <div class="panel-title-row">
<div class="float-right"> <div class="float-right">
<button type="submit" class="btn btn-primary"> <span *ngIf="isNewMode">
Save <button type="button" class="btn btn-default" (click)="saveAsDraft()">
</button> Save as Draft
</button>
<button type="submit" class="btn btn-primary">
Save and Publish
</button>
</span>
<span *ngIf="!isNewMode">
<button type="submit" class="btn btn-primary">
Save
</button>
</span>
</div> </div>
<h3 class="panel-title" *ngIf="isNewMode"> <h3 class="panel-title" *ngIf="isNewMode">

70
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -6,6 +6,7 @@
*/ */
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { AbstractControl, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { AbstractControl, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@ -13,7 +14,9 @@ import { Subscription } from 'rxjs';
import { import {
ContentCreated, ContentCreated,
ContentDeleted, ContentDeleted,
ContentUpdated ContentPublished,
ContentUpdated,
ContentUnpublished
} from './../messages'; } from './../messages';
import { import {
@ -38,7 +41,9 @@ import {
templateUrl: './content-page.component.html' templateUrl: './content-page.component.html'
}) })
export class ContentPageComponent extends AppComponentBase implements OnDestroy, OnInit { export class ContentPageComponent extends AppComponentBase implements OnDestroy, OnInit {
private messageSubscription: Subscription; private contentDeletedSubscription: Subscription;
private contentPublishedSubscription: Subscription;
private contentUnpublishedSubscription: Subscription;
private version: Version = new Version(''); private version: Version = new Version('');
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
@ -48,12 +53,14 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
public contentData: any = null; public contentData: any = null;
public contentId: string; public contentId: string;
public isPublished = false;
public isNewMode = true; public isNewMode = true;
public languages: AppLanguageDto[] = []; public languages: AppLanguageDto[] = [];
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly contentsService: ContentsService, private readonly contentsService: ContentsService,
private readonly location: Location,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly router: Router, private readonly router: Router,
private readonly messageBus: MessageBus private readonly messageBus: MessageBus
@ -62,11 +69,13 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
} }
public ngOnDestroy() { public ngOnDestroy() {
this.messageSubscription.unsubscribe(); this.contentDeletedSubscription.unsubscribe();
this.contentPublishedSubscription.unsubscribe();
this.contentUnpublishedSubscription.unsubscribe();
} }
public ngOnInit() { public ngOnInit() {
this.messageSubscription = this.contentDeletedSubscription =
this.messageBus.of(ContentDeleted) this.messageBus.of(ContentDeleted)
.subscribe(message => { .subscribe(message => {
if (message.id === this.contentId) { if (message.id === this.contentId) {
@ -74,6 +83,22 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
} }
}); });
this.contentPublishedSubscription =
this.messageBus.of(ContentPublished)
.subscribe(message => {
if (message.id === this.contentId) {
this.isPublished = true;
}
});
this.contentUnpublishedSubscription =
this.messageBus.of(ContentUnpublished)
.subscribe(message => {
if (message.id === this.contentId) {
this.isPublished = false;
}
});
this.route.parent.data.map(p => p['appLanguages']) this.route.parent.data.map(p => p['appLanguages'])
.subscribe((languages: AppLanguageDto[]) => { .subscribe((languages: AppLanguageDto[]) => {
this.languages = languages; this.languages = languages;
@ -90,7 +115,15 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
}); });
} }
public saveContent() { public saveAndPublish() {
this.saveContent(true);
}
public saveAsDraft() {
this.saveContent(false);
}
private saveContent(publish: boolean) {
this.contentFormSubmitted = true; this.contentFormSubmitted = true;
if (this.contentForm.valid) { if (this.contentForm.valid) {
@ -100,11 +133,17 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
if (this.isNewMode) { if (this.isNewMode) {
this.appName() this.appName()
.switchMap(app => this.contentsService.postContent(app, this.schema.name, data, this.version)) .switchMap(app => this.contentsService.postContent(app, this.schema.name, data, publish, this.version))
.subscribe(created => { .subscribe(created => {
this.messageBus.publish(new ContentCreated(created.id, created.data, this.version.value)); this.contentId = created.id;
this.router.navigate(['../'], { relativeTo: this.route }); this.messageBus.publish(new ContentCreated(created.id, created.data, this.version.value, publish));
this.enable();
this.finishCreation();
this.updateUrl();
this.notifyInfo('Content created successfully.');
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
this.enable(); this.enable();
@ -115,7 +154,9 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
.subscribe(() => { .subscribe(() => {
this.messageBus.publish(new ContentUpdated(this.contentId, data, this.version.value)); this.messageBus.publish(new ContentUpdated(this.contentId, data, this.version.value));
this.router.navigate(['../'], { relativeTo: this.route }); this.enable();
this.notifyInfo('Content saved successfully.');
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
this.enable(); this.enable();
@ -124,6 +165,16 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
} }
} }
private finishCreation() {
this.isNewMode = false;
}
private updateUrl() {
const newUrl = this.router.createUrlTree(['../', this.contentId], { relativeTo: this.route, replaceUrl: true });
this.location.replaceState(newUrl.toString());
}
private enable() { private enable() {
for (const field of this.schema.fields.filter(f => !f.isDisabled)) { for (const field of this.schema.fields.filter(f => !f.isDisabled)) {
const fieldForm = this.contentForm.controls[field.name]; const fieldForm = this.contentForm.controls[field.name];
@ -191,6 +242,7 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
} else { } else {
this.contentData = content.data; this.contentData = content.data;
this.contentId = content.id; this.contentId = content.id;
this.isPublished = content.isPublished;
this.isNewMode = false; this.isNewMode = false;
} }

30
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -13,7 +13,9 @@ import { Subscription } from 'rxjs';
import { import {
ContentCreated, ContentCreated,
ContentDeleted, ContentDeleted,
ContentUpdated ContentPublished,
ContentUpdated,
ContentUnpublished
} from './../messages'; } from './../messages';
import { import {
@ -40,8 +42,8 @@ import {
templateUrl: './contents-page.component.html' templateUrl: './contents-page.component.html'
}) })
export class ContentsPageComponent extends AppComponentBase implements OnDestroy, OnInit { export class ContentsPageComponent extends AppComponentBase implements OnDestroy, OnInit {
private messageCreatedSubscription: Subscription; private contentCreatedSubscription: Subscription;
private messageUpdatedSubscription: Subscription; private contentUpdatedSubscription: Subscription;
public schema: SchemaDetailsDto; public schema: SchemaDetailsDto;
@ -68,19 +70,19 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
} }
public ngOnDestroy() { public ngOnDestroy() {
this.messageCreatedSubscription.unsubscribe(); this.contentCreatedSubscription.unsubscribe();
this.messageUpdatedSubscription.unsubscribe(); this.contentUpdatedSubscription.unsubscribe();
} }
public ngOnInit() { public ngOnInit() {
this.messageCreatedSubscription = this.contentCreatedSubscription =
this.messageBus.of(ContentCreated) this.messageBus.of(ContentCreated)
.subscribe(message => { .subscribe(message => {
this.contentItems = this.contentItems.pushFront(this.createContent(message.id, message.data, message.version)); this.contentItems = this.contentItems.pushFront(this.createContent(message.id, message.data, message.version, message.isPublished));
this.contentsPager = this.contentsPager.incrementCount(); this.contentsPager = this.contentsPager.incrementCount();
}); });
this.messageUpdatedSubscription = this.contentUpdatedSubscription =
this.messageBus.of(ContentUpdated) this.messageBus.of(ContentUpdated)
.subscribe(message => { .subscribe(message => {
this.updateContents(message.id, undefined, message.data, message.version); this.updateContents(message.id, undefined, message.data, message.version);
@ -120,6 +122,8 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
.switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id, content.version)) .switchMap(app => this.contentsService.publishContent(app, this.schema.name, content.id, content.version))
.subscribe(() => { .subscribe(() => {
this.updateContents(content.id, true, content.data, content.version.value); this.updateContents(content.id, true, content.data, content.version.value);
this.messageBus.publish(new ContentPublished(content.id));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -130,6 +134,8 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
.switchMap(app => this.contentsService.unpublishContent(app, this.schema.name, content.id, content.version)) .switchMap(app => this.contentsService.unpublishContent(app, this.schema.name, content.id, content.version))
.subscribe(() => { .subscribe(() => {
this.updateContents(content.id, false, content.data, content.version.value); this.updateContents(content.id, false, content.data, content.version.value);
this.messageBus.publish(new ContentUnpublished(content.id));
}, error => { }, error => {
this.notifyError(error); this.notifyError(error);
}); });
@ -188,12 +194,13 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.contentItems = this.contentItems.replaceAll(x => x.id === id, c => this.updateContent(c, p === undefined ? c.isPublished : p, data, version)); this.contentItems = this.contentItems.replaceAll(x => x.id === id, c => this.updateContent(c, p === undefined ? c.isPublished : p, data, version));
} }
private createContent(id: string, data: any, version: string): ContentDto { private createContent(id: string, data: any, version: string, isPublished: boolean): ContentDto {
const me = `subject:${this.authService.user!.id}`; const me = `subject:${this.authService.user!.id}`;
const newContent = const newContent =
new ContentDto( new ContentDto(
id, false, id,
isPublished,
me, me, me, me,
DateTime.now(), DateTime.now(),
DateTime.now(), DateTime.now(),
@ -208,7 +215,8 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
const newContent = const newContent =
new ContentDto( new ContentDto(
content.id, isPublished, content.id,
isPublished,
content.createdBy, me, content.createdBy, me,
content.created, DateTime.now(), content.created, DateTime.now(),
data, data,

17
src/Squidex/app/features/content/pages/messages.ts

@ -9,7 +9,8 @@ export class ContentCreated {
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly data: any, public readonly data: any,
public readonly version: string public readonly version: string,
public readonly isPublished: boolean
) { ) {
} }
} }
@ -23,6 +24,20 @@ export class ContentUpdated {
} }
} }
export class ContentPublished {
constructor(
public readonly id: string
) {
}
}
export class ContentUnpublished {
constructor(
public readonly id: string
) {
}
}
export class ContentDeleted { export class ContentDeleted {
constructor( constructor(
public readonly id: string public readonly id: string

12
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts

@ -37,8 +37,8 @@ import { SchemaDeleted, SchemaUpdated } from './../messages';
] ]
}) })
export class SchemasPageComponent extends AppComponentBase implements OnDestroy, OnInit { export class SchemasPageComponent extends AppComponentBase implements OnDestroy, OnInit {
private messageUpdatedSubscription: Subscription; private schemaUpdatedSubscription: Subscription;
private messageDeletedSubscription: Subscription; private schemaDeletedSubscription: Subscription;
public addSchemaDialog = new ModalView(); public addSchemaDialog = new ModalView();
@ -57,8 +57,8 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
} }
public ngOnDestroy() { public ngOnDestroy() {
this.messageUpdatedSubscription.unsubscribe(); this.schemaUpdatedSubscription.unsubscribe();
this.messageDeletedSubscription.unsubscribe(); this.schemaDeletedSubscription.unsubscribe();
} }
public ngOnInit() { public ngOnInit() {
@ -76,13 +76,13 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
} }
}); });
this.messageUpdatedSubscription = this.schemaUpdatedSubscription =
this.messageBus.of(SchemaUpdated) this.messageBus.of(SchemaUpdated)
.subscribe(m => { .subscribe(m => {
this.updateSchemas(this.schemas.map(s => s.name === m.name ? updateSchema(s, this.authService, m) : s)); this.updateSchemas(this.schemas.map(s => s.name === m.name ? updateSchema(s, this.authService, m) : s));
}); });
this.messageDeletedSubscription = this.schemaDeletedSubscription =
this.messageBus.of(SchemaDeleted) this.messageBus.of(SchemaDeleted)
.subscribe(m => { .subscribe(m => {
this.updateSchemas(this.schemas.filter(s => s.name !== m.name)); this.updateSchemas(this.schemas.filter(s => s.name !== m.name));

2
src/Squidex/app/shared/components/component-base.ts

@ -76,6 +76,6 @@ export abstract class ComponentBase {
} }
protected notifyInfo(error: string) { protected notifyInfo(error: string) {
this.notifications.notify(Notification.error(error)); this.notifications.notify(Notification.info(error));
} }
} }

4
src/Squidex/app/shared/services/contents.service.spec.ts

@ -169,7 +169,7 @@ describe('ContentsService', () => {
it('should make post request to create content', () => { it('should make post request to create content', () => {
const dto = {}; const dto = {};
authService.setup(x => x.authPost('http://service/p/api/content/my-app/my-schema', dto, version)) authService.setup(x => x.authPost('http://service/p/api/content/my-app/my-schema?publish=true', dto, version))
.returns(() => Observable.of( .returns(() => Observable.of(
new Response( new Response(
new ResponseOptions({ new ResponseOptions({
@ -190,7 +190,7 @@ describe('ContentsService', () => {
let content: ContentDto | null = null; let content: ContentDto | null = null;
contentsService.postContent('my-app', 'my-schema', dto, version).subscribe(result => { contentsService.postContent('my-app', 'my-schema', dto, true, version).subscribe(result => {
content = result; content = result;
}); });

4
src/Squidex/app/shared/services/contents.service.ts

@ -110,8 +110,8 @@ export class ContentsService {
.catchError('Failed to load content. Please reload.'); .catchError('Failed to load content. Please reload.');
} }
public postContent(appName: string, schemaName: string, dto: any, version: Version): Observable<ContentDto> { public postContent(appName: string, schemaName: string, dto: any, publish: boolean, version: Version): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}`); const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?publish=${publish}`);
return this.authService.authPost(url, dto, version) return this.authService.authPost(url, dto, version)
.map(response => response.json()) .map(response => response.json())

12
tests/Squidex.Write.Tests/Contents/ContentDomainObjectTests.cs

@ -72,6 +72,18 @@ namespace Squidex.Write.Contents
); );
} }
[Fact]
public void Create_should_also_publish_if_set_to_true()
{
sut.Create(CreateContentCommand(new CreateContent { Data = data, Publish = true }));
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateContentEvent(new ContentCreated { Data = data }),
CreateContentEvent(new ContentPublished())
);
}
[Fact] [Fact]
public void Update_should_throw_if_not_created() public void Update_should_throw_if_not_created()
{ {

Loading…
Cancel
Save