diff --git a/src/Squidex/app/features/administration/guards/user-must-exist.guard.ts b/src/Squidex/app/features/administration/guards/user-must-exist.guard.ts
index db67514f9..63fc2bba1 100644
--- a/src/Squidex/app/features/administration/guards/user-must-exist.guard.ts
+++ b/src/Squidex/app/features/administration/guards/user-must-exist.guard.ts
@@ -32,7 +32,7 @@ export class UserMustExistGuard implements CanActivate {
this.router.navigate(['/404']);
}
}),
- map(u => u !== null));
+ map(u => !!u));
return result;
}
diff --git a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
index 30161a18b..a8df38179 100644
--- a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
+++ b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
@@ -38,8 +38,7 @@ export class EventConsumersPageComponent implements OnDestroy, OnInit {
this.eventConsumersState.load(false, true).pipe(onErrorResumeNext()).subscribe();
this.timerSubscription =
- timer(2000, 2000).pipe(
- switchMap(x => this.eventConsumersState.load(true, true)), onErrorResumeNext())
+ timer(2000, 2000).pipe(switchMap(x => this.eventConsumersState.load(true, true)), onErrorResumeNext())
.subscribe();
}
diff --git a/src/Squidex/app/features/administration/pages/restore/restore-page.component.ts b/src/Squidex/app/features/administration/pages/restore/restore-page.component.ts
index fa8977ccf..32884a7d2 100644
--- a/src/Squidex/app/features/administration/pages/restore/restore-page.component.ts
+++ b/src/Squidex/app/features/administration/pages/restore/restore-page.component.ts
@@ -8,7 +8,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Subscription, timer } from 'rxjs';
-import { switchMap } from 'rxjs/operators';
+import { filter, switchMap } from 'rxjs/operators';
import {
AuthService,
@@ -43,11 +43,9 @@ export class RestorePageComponent implements OnDestroy, OnInit {
public ngOnInit() {
this.timerSubscription =
- timer(0, 2000).pipe(switchMap(() => this.backupsService.getRestore()))
+ timer(0, 2000).pipe(switchMap(() => this.backupsService.getRestore()), filter(x => !!x))
.subscribe(dto => {
- if (dto !== null) {
- this.restoreJob = dto;
- }
+ this.restoreJob = dto!;
});
}
diff --git a/src/Squidex/app/features/content/module.ts b/src/Squidex/app/features/content/module.ts
index 832b7c161..f08988fc9 100644
--- a/src/Squidex/app/features/content/module.ts
+++ b/src/Squidex/app/features/content/module.ts
@@ -15,6 +15,7 @@ import {
ContentMustExistGuard,
LoadLanguagesGuard,
SchemaMustExistPublishedGuard,
+ SchemaMustNotBeSingletonGuard,
SqxFrameworkModule,
SqxSharedModule,
UnsetContentGuard
@@ -52,12 +53,13 @@ const routes: Routes = [
{
path: '',
component: ContentsPageComponent,
+ canActivate: [SchemaMustNotBeSingletonGuard],
canDeactivate: [CanDeactivateGuard]
},
{
path: 'new',
component: ContentPageComponent,
- canActivate: [UnsetContentGuard],
+ canActivate: [SchemaMustNotBeSingletonGuard, UnsetContentGuard],
canDeactivate: [CanDeactivateGuard]
},
{
diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.ts b/src/Squidex/app/features/content/pages/content/content-page.component.ts
index d8466fdcd..53b9f47b9 100644
--- a/src/Squidex/app/features/content/pages/content/content-page.component.ts
+++ b/src/Squidex/app/features/content/pages/content/content-page.component.ts
@@ -8,7 +8,7 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, of, Subscription } from 'rxjs';
-import { filter, map, onErrorResumeNext, switchMap } from 'rxjs/operators';
+import { filter, onErrorResumeNext, switchMap } from 'rxjs/operators';
import { ContentVersionSelected } from './../messages';
@@ -88,19 +88,19 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
});
this.selectedSchemaSubscription =
- this.schemasState.selectedSchema.pipe(filter(s => !!s), map(s => s!))
+ this.schemasState.selectedSchema.pipe(filter(s => !!s))
.subscribe(schema => {
- this.schema = schema;
+ this.schema = schema!;
this.contentForm = new EditContentForm(this.schema, this.languages);
});
this.contentSubscription =
- this.contentsState.selectedContent.pipe(filter(c => !!c), map(c => c!))
+ this.contentsState.selectedContent.pipe(filter(c => !!c))
.subscribe(content => {
- this.content = content;
+ this.content = content!;
- this.loadContent(content.dataDraft);
+ this.loadContent(this.content.dataDraft);
});
this.contentVersionSelectedSubscription =
diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts
index 5f68b0ab2..b3a3acbd4 100644
--- a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts
+++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts
@@ -6,8 +6,9 @@
*/
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
-import { onErrorResumeNext, switchMap, tap } from 'rxjs/operators';
+import { filter, onErrorResumeNext, switchMap, takeUntil, tap } from 'rxjs/operators';
import {
AppLanguageDto,
@@ -17,6 +18,7 @@ import {
ImmutableArray,
LanguagesState,
ModalModel,
+ navigatedToOtherComponent,
Queries,
SchemaDetailsDto,
SchemasState,
@@ -59,6 +61,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public readonly contentsState: ContentsState,
private readonly languagesState: LanguagesState,
private readonly schemasState: SchemasState,
+ private readonly router: Router,
private readonly uiState: UIState
) {
}
@@ -70,8 +73,10 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public ngOnInit() {
+ const routeChanged = this.router.events.pipe(filter(navigatedToOtherComponent(this.router)));
+
this.selectedSchemaSubscription =
- this.schemasState.selectedSchema
+ this.schemasState.selectedSchema.pipe(takeUntil(routeChanged))
.subscribe(schema => {
this.resetSelection();
@@ -82,7 +87,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
});
this.contentsSubscription =
- this.contentsState.contents
+ this.contentsState.contents.pipe(takeUntil(routeChanged))
.subscribe(() => {
this.updateSelectionSummary();
});
@@ -100,7 +105,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public deleteSelected() {
- this.contentsState.deleteMany(this.select()).pipe(onErrorResumeNext()).subscribe();
+ this.contentsState.deleteMany(this.selectItems()).pipe(onErrorResumeNext()).subscribe();
}
public delete(content: ContentDto) {
@@ -112,7 +117,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public publishSelected() {
- this.changeContentItems(this.select(c => c.status !== 'Published'), 'Publish');
+ this.changeContentItems(this.selectItems(c => c.status !== 'Published'), 'Publish');
}
public unpublish(content: ContentDto) {
@@ -120,7 +125,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public unpublishSelected() {
- this.changeContentItems(this.select(c => c.status === 'Published'), 'Unpublish');
+ this.changeContentItems(this.selectItems(c => c.status === 'Published'), 'Unpublish');
}
public archive(content: ContentDto) {
@@ -128,15 +133,15 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public archiveSelected() {
- this.changeContentItems(this.select(), 'Archive');
+ this.changeContentItems(this.selectItems(), 'Archive');
}
public restore(content: ContentDto) {
this.changeContentItems([content], 'Restore');
}
- public restoreSelected(scheduled: boolean) {
- this.changeContentItems(this.select(), 'Restore');
+ public restoreSelected() {
+ this.changeContentItems(this.selectItems(), 'Restore');
}
public clone(content: ContentDto) {
@@ -185,12 +190,16 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.contentsState.search(query).pipe(onErrorResumeNext()).subscribe();
}
+ public selectLanguage(language: AppLanguageDto) {
+ this.language = language;
+ }
+
public isItemSelected(content: ContentDto): boolean {
return !!this.selectedItems[content.id];
}
- public selectLanguage(language: AppLanguageDto) {
- this.language = language;
+ private selectItems(predicate?: (content: ContentDto) => boolean) {
+ return this.contentsState.snapshot.contents.values.filter(c => this.selectedItems[c.id] && (!predicate || predicate(c)));
}
public selectItem(content: ContentDto, isSelected: boolean) {
@@ -215,10 +224,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
return content.id;
}
- private select(predicate?: (content: ContentDto) => boolean) {
- return this.contentsState.snapshot.contents.values.filter(c => this.selectedItems[c.id] && (!predicate || predicate(c)));
- }
-
private resetSelection() {
this.selectedItems = {};
diff --git a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
index 1903873f2..a270463cd 100644
--- a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
+++ b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
@@ -21,6 +21,7 @@
[name]="category"
[schemas]="schemas"
[schemasFilter]="schemasFilter.valueChanges | async"
+ [routeSingletonToContent]="true"
[isReadonly]="true">
diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts
index 0405d9426..ba2eaad1d 100644
--- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts
+++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts
@@ -82,7 +82,7 @@ export class ContentChangedTriggerComponent implements OnInit {
} else {
return null;
}
- }).filter(s => s !== null).map(s => s!)).sortByStringAsc(s => s.schema.name);
+ }).filter(s => !!s).map(s => s!)).sortByStringAsc(s => s.schema.name);
this.schemasToAdd =
this.schemas.filter(schema =>
diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
index 92ab4699e..81c86168f 100644
--- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
+++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
@@ -10,7 +10,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
-import { filter, map, onErrorResumeNext } from 'rxjs/operators';
+import { filter, onErrorResumeNext } from 'rxjs/operators';
import {
AppsState,
@@ -73,9 +73,9 @@ export class SchemaPageComponent implements OnDestroy, OnInit {
this.patternsState.load().pipe(onErrorResumeNext()).subscribe();
this.selectedSchemaSubscription =
- this.schemasState.selectedSchema.pipe(filter(s => !!s), map(s => s!))
+ this.schemasState.selectedSchema.pipe(filter(s => !!s))
.subscribe(schema => {
- this.schema = schema;
+ this.schema = schema!;
this.export();
});
diff --git a/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts b/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts
index 6dbc72276..85f2d2b18 100644
--- a/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts
+++ b/src/Squidex/app/framework/angular/forms/date-time-editor.component.ts
@@ -52,7 +52,7 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy,
}
public get hasValue() {
- return this.dateValue !== null;
+ return !!this.dateValue;
}
@ViewChild('dateInput')
diff --git a/src/Squidex/app/framework/angular/forms/stars.component.ts b/src/Squidex/app/framework/angular/forms/stars.component.ts
index 0b3b0c728..b3b17eb9c 100644
--- a/src/Squidex/app/framework/angular/forms/stars.component.ts
+++ b/src/Squidex/app/framework/angular/forms/stars.component.ts
@@ -88,7 +88,7 @@ export class StarsComponent implements ControlValueAccessor {
return false;
}
- if (this.value !== null) {
+ if (this.value) {
this.value = null;
this.stars = 0;
diff --git a/src/Squidex/app/framework/angular/image-source.directive.ts b/src/Squidex/app/framework/angular/image-source.directive.ts
index 2bc972977..ecb517ccc 100644
--- a/src/Squidex/app/framework/angular/image-source.directive.ts
+++ b/src/Squidex/app/framework/angular/image-source.directive.ts
@@ -96,7 +96,7 @@ export class ImageSourceDirective implements OnChanges, OnDestroy, OnInit, After
if (w > 0 && h > 0) {
let source = `${this.imageSource}&width=${w}&height=${h}&mode=Crop`;
- if (this.loadQuery !== null) {
+ if (this.loadQuery) {
source += `&q=${this.loadQuery}`;
}
diff --git a/src/Squidex/app/framework/angular/modals/modal-view.directive.ts b/src/Squidex/app/framework/angular/modals/modal-view.directive.ts
index 160488675..16a4834d3 100644
--- a/src/Squidex/app/framework/angular/modals/modal-view.directive.ts
+++ b/src/Squidex/app/framework/angular/modals/modal-view.directive.ts
@@ -71,7 +71,7 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
}
private update(isOpen: boolean) {
- if (isOpen === (this.renderedView !== null)) {
+ if (isOpen === (!!this.renderedView)) {
return;
}
diff --git a/src/Squidex/app/framework/angular/routers/router-utils.ts b/src/Squidex/app/framework/angular/routers/router-utils.ts
index 6dbfe0831..4dd16a7a8 100644
--- a/src/Squidex/app/framework/angular/routers/router-utils.ts
+++ b/src/Squidex/app/framework/angular/routers/router-utils.ts
@@ -5,7 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
-import { ActivatedRoute, ActivatedRouteSnapshot, Data, Params } from '@angular/router';
+import { ActivatedRoute, ActivatedRouteSnapshot, Data, Params, Router, RouterEvent, RouterStateSnapshot, RoutesRecognized } from '@angular/router';
+
+import { Types } from './../../utils/types';
export function allData(value: ActivatedRouteSnapshot | ActivatedRoute): Data {
let snapshot: ActivatedRouteSnapshot | null = value['snapshot'] || value;
@@ -40,4 +42,22 @@ export function allParams(value: ActivatedRouteSnapshot | ActivatedRoute): Param
}
return result;
+}
+
+export function childComponent(value: RouterStateSnapshot) {
+ let current = value.root;
+
+ while (true) {
+ if (current.firstChild) {
+ current = current.firstChild;
+ } else {
+ break;
+ }
+ }
+
+ return current.component;
+}
+
+export function navigatedToOtherComponent(router: Router) {
+ return (e: RouterEvent) => Types.is(e, RoutesRecognized) && childComponent(e.state) !== childComponent(router.routerState.snapshot);
}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/components/geolocation-editor.component.ts b/src/Squidex/app/shared/components/geolocation-editor.component.ts
index 7239840db..2b4709ce3 100644
--- a/src/Squidex/app/shared/components/geolocation-editor.component.ts
+++ b/src/Squidex/app/shared/components/geolocation-editor.component.ts
@@ -144,8 +144,9 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi
}
public updateValueByInput() {
- let updateMap = this.geolocationForm.controls['latitude'].value !== null &&
- this.geolocationForm.controls['longitude'].value !== null;
+ let updateMap =
+ !!this.geolocationForm.controls['latitude'].value &&
+ !!this.geolocationForm.controls['longitude'].value;
this.value = this.geolocationForm.value;
diff --git a/src/Squidex/app/shared/components/schema-category.component.html b/src/Squidex/app/shared/components/schema-category.component.html
index 570ae4798..f86bc7c42 100644
--- a/src/Squidex/app/shared/components/schema-category.component.html
+++ b/src/Squidex/app/shared/components/schema-category.component.html
@@ -16,7 +16,7 @@
-
-
+
{{schema.displayName}}
diff --git a/src/Squidex/app/shared/components/schema-category.component.ts b/src/Squidex/app/shared/components/schema-category.component.ts
index 9d8ee6831..b4838d9d0 100644
--- a/src/Squidex/app/shared/components/schema-category.component.ts
+++ b/src/Squidex/app/shared/components/schema-category.component.ts
@@ -36,6 +36,9 @@ export class SchemaCategoryComponent implements OnInit, OnChanges {
@Input()
public isReadonly: boolean;
+ @Input()
+ public routeSingletonToContent = false;
+
@Input()
public schemasFilter: string;
@@ -100,6 +103,10 @@ export class SchemaCategoryComponent implements OnInit, OnChanges {
this.schemasState.changeCategory(schema, this.name).pipe(onErrorResumeNext()).subscribe();
}
+ public schemaRoute(schema: SchemaDto) {
+ return schema.isSingleton && this.routeSingletonToContent ? [schema.name, schema.id] : [schema.name];
+ }
+
public trackBySchema(index: number, schema: SchemaDto) {
return schema.id;
}
diff --git a/src/Squidex/app/shared/guards/content-must-exist.guard.spec.ts b/src/Squidex/app/shared/guards/content-must-exist.guard.spec.ts
index 9bcadd2bf..96f0727b6 100644
--- a/src/Squidex/app/shared/guards/content-must-exist.guard.spec.ts
+++ b/src/Squidex/app/shared/guards/content-must-exist.guard.spec.ts
@@ -7,7 +7,7 @@
import { Router } from '@angular/router';
import { of } from 'rxjs';
-import { IMock, Mock, Times } from 'typemoq';
+import { IMock, It, Mock, Times } from 'typemoq';
import { ContentDto } from './../services/contents.service';
import { ContentsState } from './../state/contents.state';
@@ -42,7 +42,7 @@ describe('ContentMustExistGuard', () => {
expect(result!).toBeTruthy();
- contentsState.verify(x => x.select('123'), Times.once());
+ router.verify(x => x.navigate(It.isAny()), Times.never());
});
it('should load content and return false when not found', () => {
diff --git a/src/Squidex/app/shared/guards/content-must-exist.guard.ts b/src/Squidex/app/shared/guards/content-must-exist.guard.ts
index a96463a27..c295fdfa0 100644
--- a/src/Squidex/app/shared/guards/content-must-exist.guard.ts
+++ b/src/Squidex/app/shared/guards/content-must-exist.guard.ts
@@ -32,7 +32,7 @@ export class ContentMustExistGuard implements CanActivate {
this.router.navigate(['/404']);
}
}),
- map(u => u !== null));
+ map(u => !!u));
return result;
}
diff --git a/src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts b/src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts
index 7d49f0dc8..027ca52da 100644
--- a/src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts
+++ b/src/Squidex/app/shared/guards/must-be-authenticated.guard.spec.ts
@@ -7,7 +7,7 @@
import { Router } from '@angular/router';
import { of } from 'rxjs';
-import { IMock, Mock, Times } from 'typemoq';
+import { IMock, It, Mock, Times } from 'typemoq';
import { AuthService } from '@app/shared';
@@ -52,5 +52,7 @@ describe('MustBeAuthenticatedGuard', () => {
});
expect(result!).toBeTruthy();
+
+ router.verify(x => x.navigate(It.isAny()), Times.never());
});
});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts b/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts
index 303d67ef4..2bf342413 100644
--- a/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts
+++ b/src/Squidex/app/shared/guards/must-be-not-authenticated.guard.spec.ts
@@ -7,7 +7,7 @@
import { Router } from '@angular/router';
import { of } from 'rxjs';
-import { IMock, Mock, Times } from 'typemoq';
+import { IMock, It, Mock, Times } from 'typemoq';
import { AuthService } from '@app/shared';
@@ -52,5 +52,7 @@ describe('MustNotBeAuthenticatedGuard', () => {
});
expect(result!).toBeTruthy();
+
+ router.verify(x => x.navigate(It.isAny()), Times.never());
});
});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/guards/schema-must-exist-published.guard.spec.ts b/src/Squidex/app/shared/guards/schema-must-exist-published.guard.spec.ts
index 9013eae45..560ff9a86 100644
--- a/src/Squidex/app/shared/guards/schema-must-exist-published.guard.spec.ts
+++ b/src/Squidex/app/shared/guards/schema-must-exist-published.guard.spec.ts
@@ -5,9 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
-import { Router, RouterStateSnapshot } from '@angular/router';
+import { Router } from '@angular/router';
import { of } from 'rxjs';
-import { IMock, Mock, Times } from 'typemoq';
+import { IMock, It, Mock, Times } from 'typemoq';
import { SchemaDetailsDto } from './../services/schemas.service';
import { SchemasState } from './../state/schemas.state';
@@ -21,7 +21,6 @@ describe('SchemaMustExistPublishedGuard', () => {
};
let schemasState: IMock
;
- let state: RouterStateSnapshot = { url: 'current-url' };
let router: IMock;
let schemaGuard: SchemaMustExistPublishedGuard;
@@ -37,13 +36,13 @@ describe('SchemaMustExistPublishedGuard', () => {
let result: boolean;
- schemaGuard.canActivate(route, state).subscribe(x => {
+ schemaGuard.canActivate(route).subscribe(x => {
result = x;
}).unsubscribe();
expect(result!).toBeTruthy();
- schemasState.verify(x => x.select('123'), Times.once());
+ router.verify(x => x.navigate(It.isAny()), Times.never());
});
it('should load schema and return false when not found', () => {
@@ -52,7 +51,7 @@ describe('SchemaMustExistPublishedGuard', () => {
let result: boolean;
- schemaGuard.canActivate(route, state).subscribe(x => {
+ schemaGuard.canActivate(route).subscribe(x => {
result = x;
}).unsubscribe();
@@ -60,34 +59,4 @@ describe('SchemaMustExistPublishedGuard', () => {
router.verify(x => x.navigate(['/404']), Times.once());
});
-
- it('should load schema and return false when not found', () => {
- schemasState.setup(x => x.select('123'))
- .returns(() => of(null));
-
- let result: boolean;
-
- schemaGuard.canActivate(route, state).subscribe(x => {
- result = x;
- }).unsubscribe();
-
- expect(result!).toBeFalsy();
-
- router.verify(x => x.navigate(['/404']), Times.once());
- });
-
- it('should redirect to content when singleton', () => {
- schemasState.setup(x => x.select('123'))
- .returns(() => of({ isSingleton: true, id: 'schema-id' }));
-
- let result: boolean;
-
- schemaGuard.canActivate(route, state).subscribe(x => {
- result = x;
- }).unsubscribe();
-
- expect(result!).toBeFalsy();
-
- router.verify(x => x.navigate([state.url, 'schema-id']), Times.once());
- });
});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/guards/schema-must-exist-published.guard.ts b/src/Squidex/app/shared/guards/schema-must-exist-published.guard.ts
index c74df86d9..81e3c48a6 100644
--- a/src/Squidex/app/shared/guards/schema-must-exist-published.guard.ts
+++ b/src/Squidex/app/shared/guards/schema-must-exist-published.guard.ts
@@ -6,7 +6,7 @@
*/
import { Injectable } from '@angular/core';
-import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
+import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
@@ -22,7 +22,7 @@ export class SchemaMustExistPublishedGuard implements CanActivate {
) {
}
- public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ public canActivate(route: ActivatedRouteSnapshot): Observable {
const schemaName = allParams(route)['schemaName'];
const result =
@@ -31,12 +31,8 @@ export class SchemaMustExistPublishedGuard implements CanActivate {
if (!dto || !dto.isPublished) {
this.router.navigate(['/404']);
}
-
- if (dto && dto.isSingleton && state.url.indexOf(dto.id) < 0) {
- this.router.navigate([state.url, dto.id]);
- }
}),
- map(s => s !== null && s.isPublished));
+ map(s => !!s && s.isPublished));
return result;
}
diff --git a/src/Squidex/app/shared/guards/schema-must-exist.guard.spec.ts b/src/Squidex/app/shared/guards/schema-must-exist.guard.spec.ts
index efa0a15dd..2c517be7b 100644
--- a/src/Squidex/app/shared/guards/schema-must-exist.guard.spec.ts
+++ b/src/Squidex/app/shared/guards/schema-must-exist.guard.spec.ts
@@ -41,8 +41,6 @@ describe('SchemaMustExistGuard', () => {
}).unsubscribe();
expect(result!).toBeTruthy();
-
- schemasState.verify(x => x.select('123'), Times.once());
});
it('should load schema and return false when not found', () => {
diff --git a/src/Squidex/app/shared/guards/schema-must-exist.guard.ts b/src/Squidex/app/shared/guards/schema-must-exist.guard.ts
index bcdf3f632..8e12f86db 100644
--- a/src/Squidex/app/shared/guards/schema-must-exist.guard.ts
+++ b/src/Squidex/app/shared/guards/schema-must-exist.guard.ts
@@ -32,7 +32,7 @@ export class SchemaMustExistGuard implements CanActivate {
this.router.navigate(['/404']);
}
}),
- map(s => s !== null));
+ map(s => !!s));
return result;
}
diff --git a/src/Squidex/app/shared/guards/schema-must-not-be-singleton.guard.spec.ts b/src/Squidex/app/shared/guards/schema-must-not-be-singleton.guard.spec.ts
new file mode 100644
index 000000000..ba845da60
--- /dev/null
+++ b/src/Squidex/app/shared/guards/schema-must-not-be-singleton.guard.spec.ts
@@ -0,0 +1,105 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
+ */
+
+import { Router, RouterStateSnapshot, UrlSegment } from '@angular/router';
+import { of } from 'rxjs';
+import { IMock, It, Mock, Times } from 'typemoq';
+
+import { SchemaDetailsDto } from './../services/schemas.service';
+import { SchemasState } from './../state/schemas.state';
+import { SchemaMustNotBeSingletonGuard } from './schema-must-not-be-singleton.guard';
+
+describe('SchemaMustNotBeSingletonGuard', () => {
+ const route: any = {
+ params: {
+ schemaName: '123'
+ },
+ url: [
+ new UrlSegment('schemas', {}),
+ new UrlSegment('name', {}),
+ new UrlSegment('new', {})
+ ]
+ };
+
+ let schemasState: IMock;
+ let router: IMock;
+ let schemaGuard: SchemaMustNotBeSingletonGuard;
+
+ beforeEach(() => {
+ router = Mock.ofType();
+ schemasState = Mock.ofType();
+ schemaGuard = new SchemaMustNotBeSingletonGuard(schemasState.object, router.object);
+ });
+
+ it('should subscribe to schema and return true when not singleton', () => {
+ const state: RouterStateSnapshot = { url: 'schemas/name/' };
+
+ schemasState.setup(x => x.selectedSchema)
+ .returns(() => of({ id: '123', isSingleton: false }));
+
+ let result: boolean;
+
+ schemaGuard.canActivate(route, state).subscribe(x => {
+ result = x;
+ }).unsubscribe();
+
+ expect(result!).toBeTruthy();
+
+ router.verify(x => x.navigate(It.isAny()), Times.never());
+ });
+
+ it('should subscribe to schema and return false when not found', () => {
+ const state: RouterStateSnapshot = { url: 'schemas/name/' };
+
+ schemasState.setup(x => x.selectedSchema)
+ .returns(() => of(null));
+
+ let result: boolean;
+
+ schemaGuard.canActivate(route, state).subscribe(x => {
+ result = x;
+ }).unsubscribe();
+
+ expect(result!).toBeFalsy();
+
+ router.verify(x => x.navigate(It.isAny()), Times.never());
+ });
+
+ it('should redirect to content when singleton', () => {
+ const state: RouterStateSnapshot = { url: 'schemas/name/' };
+
+ schemasState.setup(x => x.selectedSchema)
+ .returns(() => of({ id: '123', isSingleton: true }));
+
+ let result: boolean;
+
+ schemaGuard.canActivate(route, state).subscribe(x => {
+ result = x;
+ }).unsubscribe();
+
+ expect(result!).toBeFalsy();
+
+ router.verify(x => x.navigate([state.url, '123']), Times.once());
+ });
+
+ it('should redirect to content when singleton on new page', () => {
+ const state: RouterStateSnapshot = { url: 'schemas/name/new/' };
+
+ schemasState.setup(x => x.selectedSchema)
+ .returns(() => of({ id: '123', isSingleton: true }));
+
+ let result: boolean;
+
+ schemaGuard.canActivate(route, state).subscribe(x => {
+ result = x;
+ }).unsubscribe();
+
+ expect(result!).toBeFalsy();
+
+ router.verify(x => x.navigate(['schemas/name/', '123']), Times.once());
+ });
+});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/guards/schema-must-not-be-singleton.guard.ts b/src/Squidex/app/shared/guards/schema-must-not-be-singleton.guard.ts
new file mode 100644
index 000000000..73624cd09
--- /dev/null
+++ b/src/Squidex/app/shared/guards/schema-must-not-be-singleton.guard.ts
@@ -0,0 +1,42 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
+ */
+
+import { Injectable } from '@angular/core';
+import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
+import { Observable } from 'rxjs';
+import { map, take, tap } from 'rxjs/operators';
+
+import { SchemasState } from './../state/schemas.state';
+
+@Injectable()
+export class SchemaMustNotBeSingletonGuard implements CanActivate {
+ constructor(
+ private readonly schemasState: SchemasState,
+ private readonly router: Router
+ ) {
+ }
+
+ public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ const result =
+ this.schemasState.selectedSchema.pipe(
+ take(1),
+ tap(dto => {
+ if (dto && dto.isSingleton) {
+ if (state.url.indexOf('/new') >= 0) {
+ const parentUrl = state.url.slice(0, state.url.indexOf(route.url[route.url.length - 1].path));
+
+ this.router.navigate([parentUrl, dto.id]);
+ } else {
+ this.router.navigate([state.url, dto.id]);
+ }
+ }
+ }),
+ map(s => !!s && !s.isSingleton));
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/internal.ts b/src/Squidex/app/shared/internal.ts
index 2a3e2ba5a..562a4393d 100644
--- a/src/Squidex/app/shared/internal.ts
+++ b/src/Squidex/app/shared/internal.ts
@@ -13,6 +13,7 @@ export * from './guards/must-be-authenticated.guard';
export * from './guards/must-be-not-authenticated.guard';
export * from './guards/schema-must-exist-published.guard';
export * from './guards/schema-must-exist.guard';
+export * from './guards/schema-must-not-be-singleton.guard';
export * from './guards/unset-app.guard';
export * from './guards/unset-content.guard';
diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts
index 49e44f77b..88e848859 100644
--- a/src/Squidex/app/shared/module.ts
+++ b/src/Squidex/app/shared/module.ts
@@ -64,6 +64,7 @@ import {
SchemaCategoryComponent,
SchemaMustExistGuard,
SchemaMustExistPublishedGuard,
+ SchemaMustNotBeSingletonGuard,
SchemasService,
SchemasState,
SearchFormComponent,
@@ -180,6 +181,7 @@ export class SqxSharedModule {
RulesState,
SchemaMustExistGuard,
SchemaMustExistPublishedGuard,
+ SchemaMustNotBeSingletonGuard,
SchemasService,
SchemasState,
UIService,
diff --git a/src/Squidex/app/shared/state/contents.forms.ts b/src/Squidex/app/shared/state/contents.forms.ts
index 348124935..0eba8de49 100644
--- a/src/Squidex/app/shared/state/contents.forms.ts
+++ b/src/Squidex/app/shared/state/contents.forms.ts
@@ -379,7 +379,7 @@ export class EditContentForm extends Form {
private addArrayItem(field: RootFieldDto, language: AppLanguageDto | null, partitionForm: FormArray) {
const itemForm = new FormGroup({});
- let isOptional = field.isLocalizable && language !== null && language.isOptional;
+ let isOptional = field.isLocalizable && !!language && language.isOptional;
for (let nested of field.nested) {
const nestedValidators = FieldValidatorsFactory.createValidators(nested, isOptional);
diff --git a/src/Squidex/app/shared/state/contents.state.ts b/src/Squidex/app/shared/state/contents.state.ts
index f220670bd..21961630b 100644
--- a/src/Squidex/app/shared/state/contents.state.ts
+++ b/src/Squidex/app/shared/state/contents.state.ts
@@ -273,7 +273,7 @@ export abstract class ContentsStateBase extends State {
}
public init(): Observable {
- this.next(s => ({ ...s, contentsPager: new Pager(0), contentsQuery: '', isArchive: false, isLoaded: false }));
+ this.next(s => ({ contents: ImmutableArray.of(), contentsPager: new Pager(0) }));
return this.loadInternal();
}
diff --git a/src/Squidex/app/shared/state/queries.spec.ts b/src/Squidex/app/shared/state/queries.spec.ts
index bac5abc65..8dc79c04f 100644
--- a/src/Squidex/app/shared/state/queries.spec.ts
+++ b/src/Squidex/app/shared/state/queries.spec.ts
@@ -68,12 +68,16 @@ describe('Queries', () => {
it('should forward add call to state', () => {
queries.add('key3', 'filter3');
+ expect(true).toBeTruthy();
+
uiState.verify(x => x.set('schemas.my-schema.queries.key3', 'filter3'), Times.once());
});
it('should forward remove call to state', () => {
queries.remove('key3');
+ expect(true).toBeTruthy();
+
uiState.verify(x => x.remove('schemas.my-schema.queries.key3'), Times.once());
});
});
\ No newline at end of file
diff --git a/tools/GenerateLanguages/GenerateLanguages.sln b/tools/GenerateLanguages/GenerateLanguages.sln
new file mode 100644
index 000000000..a0dd55f86
--- /dev/null
+++ b/tools/GenerateLanguages/GenerateLanguages.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27703.2042
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenerateLanguages", "GenerateLanguages.csproj", "{8421C72C-A305-4CDA-9413-715B4A095F56}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8421C72C-A305-4CDA-9413-715B4A095F56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8421C72C-A305-4CDA-9413-715B4A095F56}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8421C72C-A305-4CDA-9413-715B4A095F56}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8421C72C-A305-4CDA-9413-715B4A095F56}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {25A5F1D2-5109-48B1-B161-8F846145BEFB}
+ EndGlobalSection
+EndGlobal