Browse Source

Content Handling

pull/1/head
Sebastian 9 years ago
parent
commit
238d147574
  1. 44
      src/Squidex/app/app.module.ts
  2. 4
      src/Squidex/app/features/content/declarations.ts
  3. 61
      src/Squidex/app/features/content/module.ts
  4. 16
      src/Squidex/app/features/content/pages/content-page.component.html
  5. 27
      src/Squidex/app/features/content/pages/content-page.component.ts
  6. 95
      src/Squidex/app/features/content/pages/content/content-page.component.html
  7. 0
      src/Squidex/app/features/content/pages/content/content-page.component.scss
  8. 62
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  9. 33
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  10. 7
      src/Squidex/app/features/content/pages/contents/contents-page.component.scss
  11. 39
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  12. 35
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
  13. 83
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.scss
  14. 76
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts
  15. 4
      src/Squidex/app/features/schemas/module.ts
  16. 4
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  17. 41
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  18. 2
      src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.html
  19. 6
      src/Squidex/app/framework/angular/name.pipe.spec.ts
  20. 21
      src/Squidex/app/framework/angular/name.pipe.ts
  21. 26
      src/Squidex/app/framework/module.ts
  22. 2
      src/Squidex/app/shared/declarations.ts
  23. 15
      src/Squidex/app/shared/guards/app-must-exist.guard.spec.ts
  24. 4
      src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts
  25. 4
      src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts
  26. 55
      src/Squidex/app/shared/guards/resolve-published-schema.guard.ts
  27. 55
      src/Squidex/app/shared/guards/resolve-schema.guard.ts
  28. 46
      src/Squidex/app/shared/module.ts
  29. 1
      src/Squidex/app/theme/_bootstrap.scss

44
src/Squidex/app/app.module.ts

@ -12,30 +12,11 @@ import { AppComponent } from './app.component';
import {
ApiUrlConfig,
AppClientsService,
AppContributorsService,
AppLanguagesService,
AppMustExistGuard,
AppsService,
AppsStoreService,
AuthService,
CurrencyConfig,
DecimalSeparatorConfig,
HistoryService,
LanguageService,
LocalStoreService,
MessageBus,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
NotificationService,
PanelService,
SchemasService,
SqxFrameworkModule,
SqxSharedModule,
TitlesConfig,
TitleService,
UsersProviderService,
UsersService
TitlesConfig
} from './shared';
import { SqxShellModule } from './shell';
@ -61,8 +42,8 @@ export function configCurrency() {
@NgModule({
imports: [
BrowserModule,
SqxFrameworkModule,
SqxSharedModule,
SqxFrameworkModule.forRoot(),
SqxSharedModule.forRoot(),
SqxShellModule,
routing
],
@ -70,25 +51,6 @@ export function configCurrency() {
AppComponent
],
providers: [
AppClientsService,
AppContributorsService,
AppLanguagesService,
AppsStoreService,
AppsService,
AppMustExistGuard,
AuthService,
HistoryService,
LanguageService,
LocalStoreService,
MessageBus,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
NotificationService,
PanelService,
SchemasService,
TitleService,
UsersProviderService,
UsersService,
{ provide: ApiUrlConfig, useFactory: configApiUrl },
{ provide: CurrencyConfig, useFactory: configCurrency },
{ provide: DecimalSeparatorConfig, useFactory: configDecimalSeparator },

4
src/Squidex/app/features/content/declarations.ts

@ -5,4 +5,6 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './pages/content-page.component';
export * from './pages/content/content-page.component';
export * from './pages/contents/contents-page.component';
export * from './pages/schemas/schemas-page.component';

61
src/Squidex/app/features/content/module.ts

@ -8,16 +8,67 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from 'shared';
import {
HistoryComponent,
ResolvePublishedSchemaGuard,
SqxFrameworkModule,
SqxSharedModule
} from 'shared';
import {
ContentPageComponent
ContentPageComponent,
ContentsPageComponent,
SchemasPageComponent
} from './declarations';
const routes: Routes = [
{
path: '',
component: ContentPageComponent
component: SchemasPageComponent,
children: [
{
path: ''
},
{
path: ':schemaName',
component: ContentsPageComponent,
resolve: {
schema: ResolvePublishedSchemaGuard
},
children: [
{
path: 'new',
component: ContentPageComponent,
resolve: {
schema: ResolvePublishedSchemaGuard
},
data: {
disableHistory: true
}
}, {
path: 'history',
component: HistoryComponent,
data: {
channel: 'contents.{schemaName}'
}
}, {
path: ':contentId',
component: ContentPageComponent,
resolve: {
schema: ResolvePublishedSchemaGuard
},
children: [
{
path: 'history',
component: HistoryComponent,
data: {
channel: 'contents.{schemaName}.{contentId}'
}
}
]
}
]
}]
}
];
@ -28,7 +79,9 @@ const routes: Routes = [
RouterModule.forChild(routes)
],
declarations: [
ContentPageComponent
ContentPageComponent,
ContentsPageComponent,
SchemasPageComponent
]
})
export class SqxFeatureContentModule { }

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

@ -1,16 +0,0 @@
<sqx-title message="{app} | Content" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">
<h3 class="panel-title">Content</h3>
<a class="panel-close" dashboardLink>
<i class="icon-close"></i>
</a>
</div>
<div class="panel-main">
<div class="panel-content">
</div>
</div>
</div>

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

@ -1,27 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component } from '@angular/core';
import {
AppComponentBase,
AppsStoreService,
NotificationService,
UsersProviderService
} from 'shared';
@Component({
selector: 'sqx-content-page',
styleUrls: ['./content-page.component.scss'],
templateUrl: './content-page.component.html'
})
export class ContentPageComponent extends AppComponentBase {
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService) {
super(apps, notifications, users);
}
}

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

@ -0,0 +1,95 @@
<sqx-title message="{app} | Content" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<form [formGroup]="contentForm" (ngSubmit)="saveSchema()">
<div class="panel panel-light" >
<div class="panel-header">
<div class="float-xs-right">
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
<h3 class="panel-title" *ngIf="isNewMode">New {{schema|displayName}}</h3>
<a class="panel-close" routerLink="../">
<i class="icon-close"></i>
</a>
</div>
<div class="panel-main">
<div class="panel-content">
<div *ngFor="let field of schema.fields">
<div class="table-items-row">
<label>{{field|displayName:'properties.label':'name'}}</label>
<div>
<div [ngSwitch]="field.properties.fieldType">
<div *ngSwitchCase="'number'">
<div [ngSwitch]="field.properties.editor">
<div *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControlName]="field.name">
</div>
<div *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name">
<option *ngFor="let value of field.properties.allowedValues">{{value}}</option>
</select>
</div>
<div *ngSwitchCase="'Radio'">
<label *ngFor="let value of field.properties.allowedValues">
<input type="radio" value="{{value}}" [formControlName]="field.name"> {{value}}
</label>
</div>
</div>
</div>
<div *ngSwitchCase="'string'">
<div [ngSwitch]="field.properties.editor">
<div *ngSwitchCase="'Input'">
<input class="form-control" type="number" [formControlName]="field.name">
</div>
<div *ngSwitchCase="'Dropdown'">
<select class="form-control" [formControlName]="field.name">
<option *ngFor="let value of field.properties.allowedValues">{{value}}</option>
</select>
</div>
<div *ngSwitchCase="'TextArea'">
<textarea class="form-control" [formControlName]="field.name"></textarea>
</div>
<div *ngSwitchCase="'Radio'">
<label *ngFor="let value of field.properties.allowedValues">
<input type="radio" value="{{value}}" [formControlName]="field.name"> {{value}}
</label>
</div>
</div>
</div>
<div *ngSwitchCase="'boolean'">
<div [ngSwitch]="field.properties.editor">
<div *ngSwitchCase="'Checkbox'">
<div class="form-check">
<input type="checkbox" [formControlName]="field.name">
</div>
</div>
<div *ngSwitchCase="'Toggle'">
</div>
</div>
</div>
</div>
</div>
<div class="form-hint" *ngIf="field.properties.hints && field.properties.hints.length > 0">
{{field.properties.hints}}
</div>
</div>
</div>
</div>
<div class="panel-sidebar" *ngIf="!isNewMode">
<div class="nav nav-pills nav-stacked nav-light">
<li class="nav-item">
<a class="nav-link" routerLink="history" routerLinkActive="active">
<i class="icon-time"></i>
</a>
</li>
</div>
</div>
</div>
</div>
</form>

0
src/Squidex/app/features/content/pages/content-page.component.scss → src/Squidex/app/features/content/pages/content/content-page.component.scss

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

@ -0,0 +1,62 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component } from '@angular/core';
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import {
AppComponentBase,
AppsStoreService,
NotificationService,
SchemaDetailsDto,
UsersProviderService
} from 'shared';
@Component({
selector: 'sqx-content-page',
styleUrls: ['./content-page.component.scss'],
templateUrl: './content-page.component.html'
})
export class ContentPageComponent extends AppComponentBase {
public schema: SchemaDetailsDto;
public contentForm: FormGroup;
public isNewMode = false;
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly route: ActivatedRoute
) {
super(apps, notifications, users);
}
public ngOnInit() {
this.route.params.map(p => p['contentId']).subscribe(contentId => {
this.isNewMode = !contentId;
});
this.route.data.map(p => p['schema']).subscribe((schema: SchemaDetailsDto) => {
this.schema = schema;
this.setupForm(schema);
});
}
private setupForm(schema: SchemaDetailsDto) {
const controls: { [key: string]: AbstractControl } = {};
for (const field of schema.fields) {
const formControl = new FormControl();
controls[field.name] = formControl;
}
this.contentForm = new FormGroup(controls);
}
}

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

@ -0,0 +1,33 @@
<sqx-title message="{app} | Contents" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">
<div class="float-xs-right">
<a class="btn btn-success" [routerLink]="['new']">
<i class="icon-plus"></i> New
</a>
</div>
<h3 class="panel-title">{{schema|displayName}} Contents</h3>
<a class="panel-close" routerLink="../">
<i class="icon-close"></i>
</a>
</div>
<div class="panel-main">
<div class="panel-content">
</div>
<div class="panel-sidebar">
<div class="nav nav-pills nav-stacked nav-light">
<li class="nav-item">
<a class="nav-link" routerLink="history" routerLinkActive="active">
<i class="icon-time"></i>
</a>
</li>
</div>
</div>
</div>
</div>
<router-outlet></router-outlet>

7
src/Squidex/app/features/content/pages/contents/contents-page.component.scss

@ -0,0 +1,7 @@
@import '_vars';
@import '_mixins';
.panel {
min-width: 600px;
max-width: 600px;
}

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

@ -0,0 +1,39 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import {
AppComponentBase,
AppsStoreService,
NotificationService,
SchemaDetailsDto,
UsersProviderService
} from 'shared';
@Component({
selector: 'sqx-contents-page',
styleUrls: ['./contents-page.component.scss'],
templateUrl: './contents-page.component.html'
})
export class ContentsPageComponent extends AppComponentBase implements OnInit {
public schema: SchemaDetailsDto;
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly route: ActivatedRoute
) {
super(apps, notifications, users);
}
public ngOnInit() {
this.route.data.map(p => p['schema']).subscribe(schema => {
this.schema = schema;
});
}
}

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

@ -0,0 +1,35 @@
<sqx-title message="{app} | Schemas" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-dark">
<div class="panel-header">
<div>
<h3 class="panel-title">Schemas</h3>
<a class="panel-close" dashboardLink>
<i class="icon-close"></i>
</a>
</div>
<div class="subheader">
<div class="search-form">
<input class="form-control form-control-dark" [formControl]="schemasFilter" placeholder="Search for schemas..." />
<i class="icon-search"></i>
</div>
</div>
</div>
<div class="panel-main">
<div class="panel-content">
<div class="schemas">
<div class="schema" *ngFor="let schema of schemasFiltered | async" [routerLink]="[schema.name]" routerLinkActive="active">
<div class="schema-inner">
<span class="schema-name">{{schema|displayName}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<router-outlet></router-outlet>

83
src/Squidex/app/features/content/pages/schemas/schemas-page.component.scss

@ -0,0 +1,83 @@
@import '_vars';
@import '_mixins';
.panel {
min-width: 280px;
max-width: 280px;
}
.panel-header {
min-height: 120px;
max-height: 120px;
}
.subheader {
@include flex-box;
@include flex-flow(row);
margin-top: 2rem;
margin-right: -40px;
}
.search-form {
& {
@include flex-grow(1);
position: relative;
}
.form-control {
padding-left: 50px;
}
.icon-search {
@include absolute(10px, auto, auto, 12px);
color: $color-dark-foreground-selected;
font-size: 1.3rem;
font-weight: lighter;
}
}
.schemas {
margin-left: -$panel-padding;
margin-right: -$panel-padding;
}
.schema {
& {
padding-left: $panel-padding;
padding-right: $panel-padding;
}
&:hover,
&.active {
& {
background: $color-dark-background-selected;
}
& + .schema > .schema-inner {
border-color: transparent;
}
.schema-inner {
border-color: transparent;
}
}
&-inner {
padding-top: 1rem;
padding-bottom: 1rem;
border-top: 1px solid darken($color-dark-foreground, 10%);
}
&-name {
@include truncate;
color: $color-dark-foreground-selected;
font-size: 1rem;
font-weight: normal;
}
&:first-child {
.schema-inner {
border: 0;
}
}
}

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

@ -0,0 +1,76 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { BehaviorSubject, Observable } from 'rxjs';
import {
AppComponentBase,
AppsStoreService,
ImmutableArray,
NotificationService,
SchemaDto,
SchemasService,
UsersProviderService
} from 'shared';
@Component({
selector: 'sqx-schemas-page',
styleUrls: ['./schemas-page.component.scss'],
templateUrl: './schemas-page.component.html'
})
export class SchemasPageComponent extends AppComponentBase implements OnInit {
public schemas = new BehaviorSubject(ImmutableArray.empty<SchemaDto>());
public schemasFilter = new FormControl();
public schemasFiltered =
Observable.of(null)
.merge(this.schemasFilter.valueChanges.debounceTime(100))
.combineLatest(this.schemas,
(query, schemas) => {
schemas = schemas.filter(t => t.isPublished);
if (query && query.length > 0) {
schemas = schemas.filter(t => t.name.indexOf(query) >= 0);
}
schemas =
schemas.sort((a, b) => {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
return schemas;
});
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService,
private readonly schemasService: SchemasService
) {
super(apps, notifications, users);
}
public ngOnInit() {
this.load();
}
public load() {
this.appName()
.switchMap(app => this.schemasService.getSchemas(app).retry(2))
.subscribe(dtos => {
this.schemas.next(ImmutableArray.of(dtos));
}, error => {
this.notifyError(error);
});
}
}

4
src/Squidex/app/features/schemas/module.ts

@ -10,6 +10,7 @@ import { RouterModule, Routes } from '@angular/router';
import {
HistoryComponent,
ResolveSchemaGuard,
SqxFrameworkModule,
SqxSharedModule
} from 'shared';
@ -39,6 +40,9 @@ const routes: Routes = [
{
path: ':schemaName',
component: SchemaPageComponent,
resolve: {
schema: ResolveSchemaGuard
},
children: [
{
path: 'history',

4
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -16,8 +16,8 @@
<h3 class="panel-title">
{{schemaProperties|displayName}} <i class="schema-edit icon-pencil" (click)="editSchemaDialog.show()"></i>
</h3>
</h3>
<a class="panel-close" routerLink="../">
<i class="icon-close"></i>
</a>

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

@ -5,10 +5,9 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import {
AddFieldDto,
@ -22,6 +21,7 @@ import {
MessageBus,
ModalView,
NotificationService,
SchemaDetailsDto,
SchemasService,
UpdateFieldDto,
UsersProviderService
@ -38,9 +38,7 @@ import { SchemaUpdated } from './../messages';
fadeAnimation
]
})
export class SchemaPageComponent extends AppComponentBase implements OnDestroy, OnInit {
private routerSubscription: Subscription;
export class SchemaPageComponent extends AppComponentBase implements OnInit {
public fieldTypes: string[] = [
'string',
'number',
@ -78,30 +76,13 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
super(apps, notifications, users);
}
public ngOnDestroy() {
this.routerSubscription.unsubscribe();
}
public ngOnInit() {
this.routerSubscription =
this.route.params.map(p => p['schemaName']).subscribe(name => {
this.schemaName = name;
this.reset();
this.load();
});
}
public load() {
this.appName()
.switchMap(app => this.schemasService.getSchema(app, this.schemaName)).retry(2)
.subscribe(dto => {
this.schemaFields = ImmutableArray.of(dto.fields);
this.schemaProperties = new SchemaPropertiesDto(dto.name, dto.label, dto.hints);
this.isPublished = dto.isPublished;
}, error => {
this.notifyError(error);
});
this.route.data.map(p => p['schema']).subscribe((schema: SchemaDetailsDto) => {
this.schemaName = schema.name;
this.schemaFields = ImmutableArray.of(schema.fields);
this.schemaProperties = new SchemaPropertiesDto(schema.name, schema.label, schema.hints);
this.isPublished = schema.isPublished;
});
}
public publish() {
@ -222,10 +203,6 @@ export class SchemaPageComponent extends AppComponentBase implements OnDestroy,
}
}
private reset() {
this.schemaFields = ImmutableArray.empty<FieldDto>();
}
public onSchemaSaved(properties: SchemaPropertiesDto) {
this.updateProperties(properties);

2
src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.html

@ -45,7 +45,7 @@
</label>
</div>
</div>
<div class="form-group row">
<div class="form-group row" [class.hide]="hideAllowedValues | async">
<label for="field-allowed-values" class="col-xs-3 col-form-label">Allowed Values</label>
<div class="col-xs-6">

6
src/Squidex/app/framework/angular/name.pipe.spec.ts

@ -15,6 +15,12 @@ describe('DisplayNamePipe', () => {
expect(pipe.transform(undefined)).toBe('');
});
it('should return value from nested object', () => {
const pipe = new DisplayNamePipe();
expect(pipe.transform({ propertes: { label: 'name' } }, 'properties.label')).toBe('name');
});
it('should return label if value is valid', () => {
const pipe = new DisplayNamePipe();

21
src/Squidex/app/framework/angular/name.pipe.ts

@ -18,6 +18,25 @@ export class DisplayNamePipe {
return '';
}
return StringHelper.firstNonEmpty(value[field1], value[field2]);
return StringHelper.firstNonEmpty(this.valueOf(value, field1), this.valueOf(value, field2));
}
private valueOf(o: any, s: string): any {
s = s.replace(/\[(\w+)\]/g, '.$1');
s = s.replace(/^\./, '');
const parts = s.split('.');
for (let i = 0, n = parts.length; i < n; ++i) {
let k = parts[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
}
}

26
src/Squidex/app/framework/module.ts

@ -6,13 +6,14 @@
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import {
AutocompleteComponent,
ClipboardService,
CloakDirective,
CopyDirective,
DayOfWeekPipe,
@ -22,17 +23,23 @@ import {
FocusOnChangeDirective,
FocusOnInitDirective,
FromNowPipe,
LocalStoreService,
MessageBus,
ModalViewDirective,
MoneyPipe,
MonthPipe,
NotificationService,
PanelContainerDirective,
PanelDirective,
PanelService,
ScrollActiveDirective,
ShortcutComponent,
ShortcutService,
ShortDatePipe,
ShortTimePipe,
SliderComponent,
TagEditorComponent,
TitleService,
TitleComponent,
UserReportComponent
} from './declarations';
@ -101,4 +108,19 @@ import {
ReactiveFormsModule
]
})
export class SqxFrameworkModule { }
export class SqxFrameworkModule {
public static forRoot(): ModuleWithProviders {
return {
ngModule: SqxFrameworkModule,
providers: [
ClipboardService,
LocalStoreService,
MessageBus,
NotificationService,
PanelService,
ShortcutService,
TitleService
]
};
}
}

2
src/Squidex/app/shared/declarations.ts

@ -12,6 +12,8 @@ export * from './components/history.component';
export * from './guards/app-must-exist.guard';
export * from './guards/must-be-authenticated.guard';
export * from './guards/must-be-not-authenticated.guard';
export * from './guards/resolve-published-schema.guard';
export * from './guards/resolve-schema.guard';
export * from './services/app-contributors.service';
export * from './services/app-clients.service';

15
src/Squidex/app/shared/guards/app-must-exist.guard.spec.ts

@ -22,11 +22,12 @@ describe('AppMustExistGuard', () => {
it('should navigate to 404 page if app is not found', (done) => {
appsStore.setup(x => x.selectApp('my-app'))
.returns(() => Promise.resolve(false));
const router = new RouterMockup();
const route = <any> { params: { appName: 'my-app' } };
const guard = new AppMustExistGuard(appsStore.object, <any>router);
guard.canActivate(<any> { params: { appName: 'my-app' } }, null)
guard.canActivate(route, null)
.then(result => {
expect(result).toBeFalsy();
expect(router.lastNavigation).toEqual(['/404']);
@ -38,11 +39,12 @@ describe('AppMustExistGuard', () => {
it('should navigate to 404 page if app loading fails', (done) => {
appsStore.setup(x => x.selectApp('my-app'))
.returns(() => Promise.reject<boolean>('error'));
const router = new RouterMockup();
const route = <any> { params: { appName: 'my-app' } };
const guard = new AppMustExistGuard(appsStore.object, <any>router);
guard.canActivate(<any> { params: { appName: 'my-app' } }, null)
guard.canActivate(route, null)
.then(result => {
expect(result).toBeFalsy();
expect(router.lastNavigation).toEqual(['/404']);
@ -54,11 +56,12 @@ describe('AppMustExistGuard', () => {
it('should return true if app is found', (done) => {
appsStore.setup(x => x.selectApp('my-app'))
.returns(() => Promise.resolve(true));
const router = new RouterMockup();
const route = <any> { params: { appName: 'my-app' } };
const guard = new AppMustExistGuard(appsStore.object, <any>router);
guard.canActivate(<any> { params: { appName: 'my-app' } }, null)
guard.canActivate(route, null)
.then(result => {
expect(result).toBeTruthy();
expect(router.lastNavigation).toBeUndefined();

4
src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts

@ -22,8 +22,8 @@ describe('MustBeAuthenticatedGuard', () => {
it('should navigate to default page if not authenticated', (done) => {
authService.setup(x => x.checkLogin())
.returns(() => Promise.resolve(false));
const router = new RouterMockup();
const guard = new MustBeAuthenticatedGuard(authService.object, <any>router);
guard.canActivate(null, null)
@ -38,8 +38,8 @@ describe('MustBeAuthenticatedGuard', () => {
it('should return true if authenticated', (done) => {
authService.setup(x => x.checkLogin())
.returns(() => Promise.resolve(true));
const router = new RouterMockup();
const guard = new MustBeAuthenticatedGuard(authService.object, <any>router);
guard.canActivate(null, null)

4
src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts

@ -22,8 +22,8 @@ describe('MustBeNotAuthenticatedGuard', () => {
it('should navigate to app page if authenticated', (done) => {
authService.setup(x => x.checkLogin())
.returns(() => Promise.resolve(true));
const router = new RouterMockup();
const guard = new MustBeNotAuthenticatedGuard(authService.object, <any>router);
guard.canActivate(null, null)
@ -38,8 +38,8 @@ describe('MustBeNotAuthenticatedGuard', () => {
it('should return true if not authenticated', (done) => {
authService.setup(x => x.checkLogin())
.returns(() => Promise.resolve(false));
const router = new RouterMockup();
const guard = new MustBeNotAuthenticatedGuard(authService.object, <any>router);
guard.canActivate(null, null)

55
src/Squidex/app/shared/guards/resolve-published-schema.guard.ts

@ -0,0 +1,55 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { SchemaDetailsDto, SchemasService } from './../services/schemas.service';
@Injectable()
export class ResolvePublishedSchemaGuard implements Resolve<SchemaDetailsDto> {
constructor(
private readonly schemasService: SchemasService,
private readonly router: Router
) {
}
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<SchemaDetailsDto> {
const appName = this.findParameter(route, 'appName');
const schemaName = this.findParameter(route, 'schemaName');
const result =
this.schemasService.getSchema(appName, schemaName).toPromise()
.then(dto => {
if (!dto || !dto.isPublished) {
this.router.navigate(['/404']);
}
return dto;
}).catch(() => {
this.router.navigate(['/404']);
});
return result;
}
private findParameter(route: ActivatedRouteSnapshot, name: string) {
let result: string;
while (route) {
result = route.params[name];
if (result) {
break;
}
route = route.parent;
}
return result;
}
}

55
src/Squidex/app/shared/guards/resolve-schema.guard.ts

@ -0,0 +1,55 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { SchemaDetailsDto, SchemasService } from './../services/schemas.service';
@Injectable()
export class ResolveSchemaGuard implements Resolve<SchemaDetailsDto> {
constructor(
private readonly schemasService: SchemasService,
private readonly router: Router
) {
}
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<SchemaDetailsDto> {
const appName = this.findParameter(route, 'appName');
const schemaName = this.findParameter(route, 'schemaName');
const result =
this.schemasService.getSchema(appName, schemaName).toPromise()
.then(dto => {
if (!dto) {
this.router.navigate(['/404']);
}
return dto;
}).catch(() => {
this.router.navigate(['/404']);
});
return result;
}
private findParameter(route: ActivatedRouteSnapshot, name: string) {
let result: string;
while (route) {
result = route.params[name];
if (result) {
break;
}
route = route.parent;
}
return result;
}
}

46
src/Squidex/app/shared/module.ts

@ -5,14 +5,30 @@
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { NgModule } from '@angular/core';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { SqxFrameworkModule } from 'framework';
import {
AppFormComponent,
AppClientsService,
AppContributorsService,
AppLanguagesService,
AppsStoreService,
AppsService,
AppMustExistGuard,
AuthService,
DashboardLinkDirective,
HistoryComponent
HistoryComponent,
HistoryService,
LanguageService,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
ResolvePublishedSchemaGuard,
ResolveSchemaGuard,
SchemasService,
UsersProviderService,
UsersService
} from './declarations';
@NgModule({
@ -30,4 +46,28 @@ import {
HistoryComponent
]
})
export class SqxSharedModule { }
export class SqxSharedModule {
public static forRoot(): ModuleWithProviders {
return {
ngModule: SqxSharedModule,
providers: [
AppClientsService,
AppContributorsService,
AppLanguagesService,
AppsStoreService,
AppsService,
AppMustExistGuard,
AuthService,
HistoryService,
LanguageService,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
ResolvePublishedSchemaGuard,
ResolveSchemaGuard,
SchemasService,
UsersProviderService,
UsersService
]
};
}
}

1
src/Squidex/app/theme/_bootstrap.scss

@ -161,6 +161,7 @@ select {
.form-hint {
& {
font-size: .8rem;
color: lighten($color-text, 30%);
}
p {

Loading…
Cancel
Save