Browse Source

Merge pull request #272 from Squidex/feature-content-selector

Feature content selector
pull/282/head
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
c945d32d9b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Squidex/app/features/content/declarations.ts
  2. 37
      src/Squidex/app/features/content/module.ts
  3. 2
      src/Squidex/app/features/content/pages/content/content-history.component.html
  4. 6
      src/Squidex/app/features/content/pages/content/content-page.component.html
  5. 73
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  6. 67
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  7. 16
      src/Squidex/app/features/content/pages/contents/contents-page.component.scss
  8. 83
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  9. 76
      src/Squidex/app/features/content/pages/contents/search-form.component.html
  10. 20
      src/Squidex/app/features/content/pages/contents/search-form.component.scss
  11. 29
      src/Squidex/app/features/content/pages/contents/search-form.component.ts
  12. 30
      src/Squidex/app/features/content/pages/messages.ts
  13. 4
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
  14. 60
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts
  15. 2
      src/Squidex/app/features/content/shared/content-item.component.html
  16. 66
      src/Squidex/app/features/content/shared/contents-selector.component.html
  17. 10
      src/Squidex/app/features/content/shared/contents-selector.component.scss
  18. 145
      src/Squidex/app/features/content/shared/contents-selector.component.ts
  19. 19
      src/Squidex/app/features/content/shared/references-editor.component.html
  20. 2
      src/Squidex/app/features/content/shared/references-editor.component.scss
  21. 44
      src/Squidex/app/features/content/shared/references-editor.component.ts
  22. 2
      src/Squidex/app/features/schemas/declarations.ts
  23. 2
      src/Squidex/app/features/schemas/module.ts
  24. 8
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html
  25. 4
      src/Squidex/app/framework/angular/modals/modal-dialog.component.html
  26. 6
      src/Squidex/app/framework/angular/modals/modal-dialog.component.ts
  27. 45
      src/Squidex/app/framework/angular/panel-container.directive.ts
  28. 2
      src/Squidex/app/framework/angular/panel.component.html
  29. 30
      src/Squidex/app/framework/angular/panel.component.ts
  30. 38
      src/Squidex/app/shared/guards/schema-must-exist-published.guard.ts
  31. 6
      src/Squidex/app/shared/guards/schema-must-exist.guard.ts
  32. 2
      src/Squidex/app/shared/internal.ts
  33. 4
      src/Squidex/app/shared/module.ts
  34. 4
      src/Squidex/app/shared/state/schemas.state.ts

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

@ -11,6 +11,8 @@ export * from './pages/content/content-page.component';
export * from './pages/contents/contents-page.component';
export * from './pages/contents/search-form.component';
export * from './pages/schemas/schemas-page.component';
export * from './shared/assets-editor.component';
export * from './shared/content-item.component';
export * from './shared/contents-selector.component';
export * from './shared/references-editor.component';

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

@ -13,7 +13,7 @@ import {
CanDeactivateGuard,
ResolveAppLanguagesGuard,
ResolveContentGuard,
ResolvePublishedSchemaGuard,
SchemaMustExistPublishedGuard,
SqxFrameworkModule,
SqxSharedModule
} from '@app/shared';
@ -25,6 +25,7 @@ import {
ContentPageComponent,
ContentItemComponent,
ContentsPageComponent,
ContentsSelectorComponent,
ReferencesEditorComponent,
SchemasPageComponent,
SearchFormComponent
@ -40,27 +41,20 @@ const routes: Routes = [
},
{
path: ':schemaName',
component: ContentsPageComponent,
canActivate: [SchemaMustExistPublishedGuard],
resolve: {
schema: ResolvePublishedSchemaGuard, appLanguages: ResolveAppLanguagesGuard
appLanguages: ResolveAppLanguagesGuard
},
children: [
{
path: '',
component: ContentsPageComponent,
canDeactivate: [CanDeactivateGuard]
},
{
path: 'new',
component: ContentPageComponent,
canDeactivate: [CanDeactivateGuard],
children: [
{
path: 'references/:schemaName/:language',
component: ContentsPageComponent,
data: {
isReadOnly: true
},
resolve: {
schema: ResolvePublishedSchemaGuard
}
}
]
canDeactivate: [CanDeactivateGuard]
},
{
path: ':contentId',
@ -76,16 +70,6 @@ const routes: Routes = [
data: {
channel: 'contents.{contentId}'
}
},
{
path: 'references/:schemaName/:language',
component: ContentsPageComponent,
data: {
isReadOnly: true
},
resolve: {
schema: ResolvePublishedSchemaGuard
}
}
]
}
@ -108,6 +92,7 @@ const routes: Routes = [
ContentItemComponent,
ContentPageComponent,
ContentsPageComponent,
ContentsSelectorComponent,
ReferencesEditorComponent,
SchemasPageComponent,
SearchFormComponent

2
src/Squidex/app/features/content/pages/content/content-history.component.html

@ -1,4 +1,4 @@
<sqx-panel desiredWidth="16rem" isBlank="true">
<sqx-panel desiredWidth="16rem" isBlank="true" [isLazyLoaded]="false">
<ng-container title>
Activity
</ng-container>

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

@ -1,8 +1,12 @@
<sqx-title message="{app} | {schema} | Content" parameter1="app" parameter2="schema" [value1]="ctx.appName" [value2]="schema?.displayName"></sqx-title>
<form [formGroup]="contentForm" (ngSubmit)="saveAndPublish()">
<sqx-panel desiredWidth="53rem" showSidebar="true">
<sqx-panel desiredWidth="*" showSidebar="true">
<ng-container title>
<a class="btn btn-link" (click)="back()">
<i class="icon-angle-left"></i>
</a>
<ng-container *ngIf="isNewMode">
New Content
</ng-container>

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

@ -10,13 +10,7 @@ import { AbstractControl, FormControl, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import { Observable, Subscription } from 'rxjs';
import {
ContentCreated,
ContentRemoved,
ContentStatusChanged,
ContentUpdated,
ContentVersionSelected
} from './../messages';
import { ContentVersionSelected } from './../messages';
import {
AppContext,
@ -27,6 +21,7 @@ import {
ContentsService,
fieldInvariant,
SchemaDetailsDto,
SchemasState,
Version
} from '@app/shared';
@ -39,10 +34,8 @@ import {
]
})
export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, OnInit {
private contentStatusChangedSubscription: Subscription;
private contentDeletedSubscription: Subscription;
private contentUpdatedSubscription: Subscription;
private contentVersionSelectedSubscription: Subscription;
private selectedSchemaSubscription: Subscription;
public schema: SchemaDetailsDto;
@ -57,15 +50,14 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
constructor(public readonly ctx: AppContext,
private readonly contentsService: ContentsService,
private readonly router: Router
private readonly router: Router,
private readonly schemasState: SchemasState
) {
}
public ngOnDestroy() {
this.contentVersionSelectedSubscription.unsubscribe();
this.contentStatusChangedSubscription.unsubscribe();
this.contentUpdatedSubscription.unsubscribe();
this.contentDeletedSubscription.unsubscribe();
this.selectedSchemaSubscription.unsubscribe();
}
public ngOnInit() {
@ -75,41 +67,15 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
this.loadVersion(message.version);
});
this.contentDeletedSubscription =
this.ctx.bus.of(ContentRemoved)
.subscribe(message => {
if (this.content && message.content.id === this.content.id) {
this.router.navigate(['../'], { relativeTo: this.ctx.route });
}
});
this.contentUpdatedSubscription =
this.ctx.bus.of(ContentUpdated)
.subscribe(message => {
if (this.content && message.content.id === this.content.id) {
this.reloadContentForm(message.content);
}
});
this.selectedSchemaSubscription =
this.schemasState.selectedSchema
.subscribe(schema => {
const routeData = allData(this.ctx.route);
this.contentStatusChangedSubscription =
this.ctx.bus.of(ContentStatusChanged)
.subscribe(message => {
if (this.content && message.content.id === this.content.id) {
this.content =
this.content.changeStatus(
message.content.scheduledTo || message.content.status,
message.content.scheduledAt,
message.content.lastModifiedBy,
message.content.version,
message.content.lastModified);
}
this.setupLanguages(routeData);
this.setupContentForm(schema!);
});
const routeData = allData(this.ctx.route);
this.setupLanguages(routeData);
this.setupContentForm(routeData.schema);
this.ctx.route.data.map(d => d.content)
.subscribe((content: ContentDto) => {
this.reloadContentForm(content);
@ -129,7 +95,6 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
this.content = this.contentOld;
this.contentOld = null;
this.emitContentUpdated(this.content);
this.reloadContentForm(this.content);
}
}
@ -157,7 +122,6 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
this.ctx.notifyInfo('Content created successfully.');
this.emitContentCreated(this.content);
this.back();
}, error => {
this.ctx.notifyError(error);
@ -171,7 +135,6 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
this.ctx.notifyInfo('Content saved successfully.');
this.emitContentUpdated(content);
this.enableContentForm();
this.reloadContentForm(content);
}, error => {
@ -204,16 +167,8 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
}
}
private back() {
this.router.navigate(['../'], { relativeTo: this.ctx.route, replaceUrl: true });
}
private emitContentCreated(content: ContentDto) {
this.ctx.bus.emit(new ContentCreated(content));
}
private emitContentUpdated(content: ContentDto) {
this.ctx.bus.emit(new ContentUpdated(content));
public back() {
this.router.navigate([this.schema.name], { relativeTo: this.ctx.route.parent!.parent, replaceUrl: true });
}
private disableContentForm() {

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

@ -1,16 +1,14 @@
<sqx-title message="{app} | {schema} | Contents" parameter1="app" parameter2="schema" [value1]="ctx.appName" [value2]="schema?.displayName"></sqx-title>
<sqx-panel [desiredWidth]="isReadOnly ? '40rem' : '60rem'" contentClass="grid">
<sqx-panel desiredWidth="*" contentClass="grid">
<ng-container title>
<ng-container *ngIf="!isReadOnly && !isArchive">
Contents
</ng-container>
<ng-container *ngIf="isArchive">
<ng-container *ngIf="isArchive; else noArchive">
Archive
</ng-container>
<ng-container *ngIf="isReadOnly">
References
</ng-container>
<ng-template #noArchive>
Contents
</ng-template>
</ng-container>
<ng-container menu>
@ -19,40 +17,14 @@
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="load(true)"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+g" (trigger)="newButton.click()" *ngIf="!isReadOnly"></sqx-shortcut>
<form class="form-inline" (ngSubmit)="search()">
<input class="form-control form-control-expandable" #inputFind [formControl]="contentsFilter" placeholder="Search for content" />
<a class="expand-search" (click)="searchModal.toggle()" #archive>
<i class="icon-caret-down"></i>
</a>
</form>
<sqx-onboarding-tooltip id="contentArchive" [for]="archive" position="bottomRight" after="60000">
Click this icon to show the advanced search menu and to show the archive!
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="contentFind" [for]="inputFind" position="bottomRight" after="120000">
Search for content using full text search over all fields and languages!
</sqx-onboarding-tooltip>
<div class="dropdown-menu" *sqxModalView="searchModal" [sqxModalTarget]="inputFind">
<sqx-search-form
[canArchive]="!isReadOnly"
(queryChanged)="contentsFilter.setValue($event, { emitEvent: false })"
[query]="contentsFilter.value"
(archivedChanged)="updateArchive($event)"
[archived]="isArchive">
</sqx-search-form>
</div>
<ng-container *ngIf="!isReadOnly && languages.length > 1">
<sqx-search-form (queryChanged)="search($event)" [query]="contentsQuery" enableShortcut="true"></sqx-search-form>
<ng-container *ngIf="languages.length > 1">
<sqx-language-selector class="languages-buttons" (selectedLanguageChanged)="selectLanguage($event)" [languages]="languages"></sqx-language-selector>
</ng-container>
<button *ngIf="!isReadOnly" class="btn btn-success" #newButton routerLink="new" title="New Content (CTRL + SHIFT + G)">
<button class="btn btn-success" #newButton routerLink="new" title="New Content (CTRL + SHIFT + G)">
<i class="icon-plus"></i> New
</button>
</ng-container>
@ -111,13 +83,13 @@
<div class="grid-content">
<div sqxIgnoreScrollbar>
<table class="table table-items table-fixed" *ngIf="contentItems" >
<tbody *ngIf="!isReadOnly">
<table class="table table-items table-fixed" *ngIf="contentItems">
<tbody>
<ng-template ngFor let-content [ngForOf]="contentItems" [ngForTrackBy]="trackByContent">
<tr [sqxContent]="content" [routerLink]="[content.id]" routerLinkActive="active"
[language]="languageSelected"
[schemaFields]="contentFields"
[schema]="schema"
[schemaFields]="contentFields"
[selected]="isItemSelected(content)"
(selectedChange)="selectItem(content, $event)"
(unpublishing)="unpublishContent(content)"
@ -129,17 +101,6 @@
<tr class="spacer"></tr>
</ng-template>
</tbody>
<tbody *ngIf="isReadOnly">
<ng-template ngFor let-content [ngForOf]="contentItems">
<tr [sqxContent]="content" dnd-draggable [dragData]="dropData(content)"
[language]="languageSelected"
[schemaFields]="contentFields"
[schema]="schema"
isReadOnly="true"></tr>
<tr class="spacer"></tr>
</ng-template>
</tbody>
</table>
</div>
</div>
@ -150,8 +111,6 @@
</ng-container>
</sqx-panel>
<router-outlet></router-outlet>
<ng-container *sqxModalView="dueTimeDialog;onRoot:true">
<sqx-modal-dialog (close)="cancelStatusChange()">
<ng-container title>

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

@ -9,22 +9,6 @@
font-size: .8rem;
}
.form-inline {
position: relative;
}
.form-control-expandable {
padding-right: 1.5rem;
}
.expand-search {
@include absolute(8px, 8px, auto, auto);
color: $color-border-dark !important;
font-size: .9rem;
font-weight: normal;
cursor: pointer !important;
}
.selection {
background: $color-table-footer;
border-bottom: 2px solid $color-border;

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

@ -6,16 +6,8 @@
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable, Subscription } from 'rxjs';
import {
ContentCreated,
ContentRemoved,
ContentStatusChanged,
ContentUpdated
} from './../messages';
import {
allData,
AppContext,
@ -27,6 +19,7 @@ import {
ImmutableArray,
ModalView,
Pager,
SchemasState,
SchemaDetailsDto,
Versioned
} from '@app/shared';
@ -40,8 +33,7 @@ import {
]
})
export class ContentsPageComponent implements OnDestroy, OnInit {
private contentCreatedSubscription: Subscription;
private contentUpdatedSubscription: Subscription;
private selectedSchemaSubscription: Subscription;
public schema: SchemaDetailsDto;
@ -49,7 +41,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public contentItems: ImmutableArray<ContentDto>;
public contentFields: FieldDto[];
public contentsFilter = new FormControl();
public contentsQuery = '';
public contentsPager = new Pager(0);
@ -70,58 +61,31 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public languageParameter: string;
public isAllSelected = false;
public isReadOnly = false;
public isArchive = false;
constructor(public readonly ctx: AppContext,
private readonly contentsService: ContentsService
private readonly contentsService: ContentsService,
private readonly schemasState: SchemasState
) {
}
public ngOnDestroy() {
this.contentCreatedSubscription.unsubscribe();
this.contentUpdatedSubscription.unsubscribe();
this.selectedSchemaSubscription.unsubscribe();
}
public ngOnInit() {
this.contentCreatedSubscription =
this.ctx.bus.of(ContentCreated)
.subscribe(message => {
this.contentItems = this.contentItems.pushFront(message.content);
this.contentsPager = this.contentsPager.incrementCount();
});
this.selectedSchemaSubscription =
this.schemasState.selectedSchema
.subscribe(schema => {
this.schema = schema!;
this.contentUpdatedSubscription =
this.ctx.bus.of(ContentUpdated)
.subscribe(message => {
this.contentItems = this.contentItems.replaceBy('id', message.content, (o, n) => o.update(n.data, n.lastModifiedBy, n.version, n.lastModified));
this.resetContents();
this.load();
});
const routeData = allData(this.ctx.route);
this.languages = routeData.appLanguages;
this.ctx.route.data.map(p => p.isReadOnly)
.subscribe(isReadOnly => {
this.isReadOnly = isReadOnly;
});
this.ctx.route.params.map(p => p.language)
.subscribe(language => {
this.languageSelected = this.languages.find(l => l.iso2Code === language) || this.languages.find(l => l.isMaster) || this.languages[0];
});
this.ctx.route.data.map(d => d.schema)
.subscribe(schema => {
this.schema = schema;
this.resetContents();
this.load();
});
}
public dropData(content: ContentDto) {
return { content, schemaId: this.schema.id };
}
public publishContent(content: ContentDto) {
@ -207,8 +171,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
content = content.changeStatus(status, dt, this.ctx.userToken, dto.version);
this.contentItems = this.contentItems.replaceBy('id', content);
this.emitContentStatusChanged(content);
}
});
}
@ -233,9 +195,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public deleteContentItem(content: ContentDto): Observable<any> {
return this.contentsService.deleteContent(this.ctx.appName, this.schema.name, content.id, content.version)
.do(() => {
this.emitContentRemoved(content);
})
.catch(error => {
this.ctx.notifyError(error);
@ -247,8 +206,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
content = content.update(update.payload, this.ctx.userToken, update.version);
this.contentItems = this.contentItems.replaceBy('id', content);
this.emitContentUpdated(content);
}
public load(showInfo = false) {
@ -271,7 +228,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public updateArchive(isArchive: boolean) {
this.contentsQuery = this.contentsFilter.value;
this.contentsPager = new Pager(0);
this.isArchive = isArchive;
@ -281,8 +237,8 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.load();
}
public search() {
this.contentsQuery = this.contentsFilter.value;
public search(query: string) {
this.contentsQuery = query;
this.contentsPager = new Pager(0);
this.load();
@ -349,18 +305,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.languageSelected = language;
}
private emitContentStatusChanged(content: ContentDto) {
this.ctx.bus.emit(new ContentStatusChanged(content));
}
private emitContentUpdated(content: ContentDto) {
this.ctx.bus.emit(new ContentUpdated(content));
}
private emitContentRemoved(content: ContentDto) {
this.ctx.bus.emit(new ContentRemoved(content));
}
public trackByContent(content: ContentDto): string {
return content.id;
}
@ -368,7 +312,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
private resetContents() {
this.contentItems = ImmutableArray.empty<ContentDto>();
this.contentsQuery = '';
this.contentsFilter.setValue('');
this.contentsPager = new Pager(0);
this.selectedItems = {};

76
src/Squidex/app/features/content/pages/contents/search-form.component.html

@ -1,38 +1,60 @@
<div class="form-horizontal">
<div [formGroup]="searchForm">
<div class="form-group row">
<label class="col col-2 col-form-label" for="odataSearch">Text</label>
<ng-container *ngIf="enableShortcut">
<sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
</ng-container>
<div class="col col-10">
<input type="input" class="form-control" id="search" (blur)="updateQuery()" formControlName="odataSearch" placeholder="Fulltext search" />
<form class="form-inline search-form" (ngSubmit)="search()">
<input class="form-control form-control-expandable" #inputFind [formControl]="contentsFilter" placeholder="Search for content" />
<a class="expand-search" (click)="searchModal.toggle()" #archive>
<i class="icon-caret-down"></i>
</a>
</form>
<sqx-onboarding-tooltip id="contentArchive" [for]="archive" position="bottomRight" after="60000">
Click this icon to show the advanced search menu and to show the archive!
</sqx-onboarding-tooltip>
<sqx-onboarding-tooltip id="contentFind" [for]="inputFind" position="bottomRight" after="120000">
Search for content using full text search over all fields and languages!
</sqx-onboarding-tooltip>
<div class="dropdown-menu" *sqxModalView="searchModal" [sqxModalTarget]="inputFind">
<div class="form-horizontal">
<div [formGroup]="searchForm">
<div class="form-group row">
<label class="col col-2 col-form-label" for="odataSearch">Text</label>
<div class="col col-10">
<input type="input" class="form-control" id="search" (blur)="updateQuery()" formControlName="odataSearch" placeholder="Fulltext search" />
</div>
</div>
</div>
<div class="form-group row">
<label class="col col-2 col-form-label" for="filter">Filter</label>
<div class="form-group row">
<label class="col col-2 col-form-label" for="filter">Filter</label>
<div class="col col-10">
<input type="input" class="form-control" id="filter" (blur)="updateQuery()" formControlName="odataFilter" placeholder="data/[MY_FIELD]/iv eq 100" />
<div class="col col-10">
<input type="input" class="form-control" id="filter" (blur)="updateQuery()" formControlName="odataFilter" placeholder="data/[MY_FIELD]/iv eq 100" />
</div>
</div>
</div>
<div class="form-group row">
<label class="col col-2 col-form-label" for="orderBy">Order</label>
<div class="col col-10">
<input type="input" class="form-control" id="orderBy" (blur)="updateQuery()" formControlName="odataOrderBy" placeholder="data/[MY_FIELD]/iv desc" />
<div class="form-group row">
<label class="col col-2 col-form-label" for="orderBy">Order</label>
<div class="col col-10">
<input type="input" class="form-control" id="orderBy" (blur)="updateQuery()" formControlName="odataOrderBy" placeholder="data/[MY_FIELD]/iv desc" />
</div>
</div>
</div>
</div>
<div class="form-check" *ngIf="canArchive">
<input class="form-check-input" type="checkbox" id="archivedItems" [ngModel]="archived" (ngModelChange)="archivedChanged.emit($event)" />
<label class="form-check-label" for="archivedItems">
Archived items
</label>
</div>
<div class="form-check" *ngIf="canArchive">
<input class="form-check-input" type="checkbox" id="archivedItems" [ngModel]="archived" (ngModelChange)="archivedChanged.emit($event)" />
<label class="form-check-label" for="archivedItems">
Archived items
</label>
</div>
<div class="link">
Read more about filtering in the <a href="https://docs.squidex.io/04-guides/02-api.html" target="_blank">Documentation</a>.
<div class="link">
Read more about filtering in the <a href="https://docs.squidex.io/04-guides/02-api.html" target="_blank">Documentation</a>.
</div>
</div>
</div>

20
src/Squidex/app/features/content/pages/contents/search-form.component.scss

@ -1,6 +1,18 @@
@import '_vars';
@import '_mixins';
.search-form {
display: inline-block;
}
.form-inline {
position: relative;
}
.form-control-expandable {
padding-right: 1.5rem;
}
.form-horizontal {
padding: 1rem 1.5rem;
min-width: 25rem;
@ -14,6 +26,14 @@
text-align: right;
}
.expand-search {
@include absolute(8px, 8px, auto, auto);
color: $color-border-dark !important;
font-size: .9rem;
font-weight: normal;
cursor: pointer !important;
}
.link {
margin-top: 1.5rem;
font-size: .8rem;

29
src/Squidex/app/features/content/pages/contents/search-form.component.ts

@ -6,7 +6,9 @@
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { FormBuilder, FormControl } from '@angular/forms';
import { ModalView } from '@app/shared';
@Component({
selector: 'sqx-search-form',
@ -15,8 +17,6 @@ import { FormBuilder } from '@angular/forms';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SearchFormComponent implements OnChanges {
private queryValue = '';
@Input()
public query = '';
@ -32,6 +32,12 @@ export class SearchFormComponent implements OnChanges {
@Input()
public canArchive = true;
@Input()
public enableShortcut = false;
public contentsFilter = new FormControl();
public searchModal = new ModalView();
public searchForm =
this.formBuilder.group({
odataOrderBy: '',
@ -44,8 +50,18 @@ export class SearchFormComponent implements OnChanges {
) {
}
public search() {
this.invalidate(this.contentsFilter.value);
this.queryChanged.emit(this.contentsFilter.value);
}
public ngOnChanges() {
if (this.query === this.queryValue) {
this.invalidate(this.query);
}
private invalidate(query: string) {
if (query === this.contentsFilter.value) {
return;
}
@ -81,7 +97,7 @@ export class SearchFormComponent implements OnChanges {
odataOrderBy
}, { emitEvent: false });
this.queryValue = this.query;
this.contentsFilter.setValue(this.query);
}
public updateQuery() {
@ -112,8 +128,9 @@ export class SearchFormComponent implements OnChanges {
}
if (query !== this.query) {
this.queryValue = query;
this.queryChanged.emit(query);
}
this.contentsFilter.setValue(query);
}
}

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

@ -5,39 +5,9 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ContentDto } from '@app/shared';
export class ContentCreated {
constructor(
public readonly content: ContentDto
) {
}
}
export class ContentUpdated {
constructor(
public readonly content: ContentDto
) {
}
}
export class ContentRemoved {
constructor(
public readonly content: ContentDto
) {
}
}
export class ContentVersionSelected {
constructor(
public readonly version: number
) {
}
}
export class ContentStatusChanged {
constructor(
public readonly content: ContentDto
) {
}
}

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Schemas" parameter1="app" [value1]="ctx.appName"></sqx-title>
<sqx-title message="{app} | Schemas" parameter1="app" [value1]="appsState.appName"></sqx-title>
<sqx-panel theme="dark" desiredWidth="16rem" showSecondHeader="true">
<ng-container title>
@ -17,7 +17,7 @@
<ng-container content>
<ul class="nav nav-panel nav-dark nav-dark-bordered flex-column">
<li class="nav-item" *ngFor="let schema of schemasFiltered | async">
<li class="nav-item" *ngFor="let schema of schemasFiltered | async; trackBy: trackBySchema">
<a class="nav-link" [routerLink]="schema.name" routerLinkActive="active">{{schema.displayName}} <i class="icon-angle-right"></i></a>
</li>
</ul>

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

@ -5,65 +5,45 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import {
AppContext,
AppsState,
SchemaDto,
SchemasService
SchemasState
} from '@app/shared';
@Component({
selector: 'sqx-schemas-page',
styleUrls: ['./schemas-page.component.scss'],
templateUrl: './schemas-page.component.html',
providers: [
AppContext
]
templateUrl: './schemas-page.component.html'
})
export class SchemasPageComponent {
export class SchemasPageComponent implements OnInit {
public schemasFilter = new FormControl();
public schemasFiltered =
this.schemasFilter.valueChanges
.startWith(null)
.distinctUntilChanged()
.debounceTime(300)
.combineLatest(this.loadSchemas(),
(query, schemas) => {
this.schemasFilter.setValue(query);
schemas = schemas.filter(t => t.isPublished);
this.schemasState.publishedSchemas
.combineLatest(this.schemasFilter.valueChanges.startWith(''),
(schemas, query) => {
if (query && query.length > 0) {
schemas = schemas.filter(t => t.name.indexOf(query) >= 0);
}
return schemas;
}).map(schemas => {
return schemas.sort((a, b) => {
if (a.name < b.name) {
return -1;
return schemas.filter(t => t.name.indexOf(query) >= 0);
} else {
return schemas;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
});
constructor(public readonly ctx: AppContext,
private readonly schemasService: SchemasService
constructor(
public readonly appsState: AppsState,
private readonly schemasState: SchemasState
) {
}
private loadSchemas(): Observable<SchemaDto[]> {
return this.schemasService.getSchemas(this.ctx.appName)
.catch(error => {
this.ctx.notifyError(error);
return [];
});
public ngOnInit() {
this.schemasState.loadSchemas().subscribe();
}
public trackBySchema(index: number, schema: SchemaDto) {
return schema.id;
}
}

2
src/Squidex/app/features/content/shared/content-item.component.html

@ -1,4 +1,4 @@
<td class="cell-select" *ngIf="!isReadOnly" (click)="shouldStop($event)">
<td class="cell-select" *ngIf="!isReference" (click)="shouldStop($event)">
<input type="checkbox" class="form-control"
[ngModel]="selected"
(ngModelChange)="selectedChange.emit($event);"

66
src/Squidex/app/features/content/shared/contents-selector.component.html

@ -0,0 +1,66 @@
<sqx-modal-dialog (close)="complete()" large="true" fullHeight="true" contentClass="grid">
<ng-container title>
Select contents
</ng-container>
<ng-container tabs>
<div class="text-right">
<button class="btn btn-link btn-secondary" (click)="load(true)">
<i class="icon-reset"></i> Refresh
</button>
<sqx-search-form (queryChanged)="search($event)" [canArchive]="false"></sqx-search-form>
</div>
</ng-container>
<ng-container content>
<div class="grid-header">
<table class="table table-items table-fixed" *ngIf="contentItems">
<thead>
<tr>
<th class="cell-select">
<input type="checkbox" class="form-control" [ngModel]="isAllSelected" (ngModelChange)="selectAll($event)" />
</th>
<th class="cell-auto" *ngFor="let field of schemaFields">
<span class="field">{{field.displayName}}</span>
</th>
<th class="cell-time">
Updated
</th>
<th class="cell-user">
By
</th>
</tr>
</thead>
</table>
</div>
<div class="grid-content">
<div sqxIgnoreScrollbar>
<table class="table table-items table-fixed" *ngIf="contentItems">
<tbody>
<ng-template ngFor let-content [ngForOf]="contentItems" [ngForTrackBy]="trackByContent">
<tr [sqxContent]="content"
[selected]="isItemSelected(content)"
(selectedChange)="onContentSelected(content)"
[language]="languageSelected"
[schemaFields]="schemaFields"
[schema]="schema"
isReadOnly="true"></tr>
<tr class="spacer"></tr>
</ng-template>
</tbody>
</table>
</div>
</div>
<div class="grid-footer">
<sqx-pager [pager]="contentsPager"></sqx-pager>
</div>
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="complete()">Cancel</button>
<button type="submit" class="float-right btn btn-success" (click)="select()" [disabled]="selectionCount === 0">Link selected contents ({{selectionCount}})</button>
</ng-container>
</sqx-modal-dialog>

10
src/Squidex/app/features/content/shared/contents-selector.component.scss

@ -0,0 +1,10 @@
@import '_vars';
@import '_mixins';
:host /deep/ .modal-body {
background: $color-background;
}
:host /deep/ .modal-tabs {
background: $color-dark-foreground;
}

145
src/Squidex/app/features/content/shared/contents-selector.component.ts

@ -0,0 +1,145 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import {
AppsState,
ContentDto,
ContentsService,
DialogService,
FieldDto,
ImmutableArray,
ModalView,
Pager,
SchemaDetailsDto,
LanguageDto
} from '@app/shared';
@Component({
selector: 'sqx-contents-selector',
styleUrls: ['./contents-selector.component.scss'],
templateUrl: './contents-selector.component.html'
})
export class ContentsSelectorComponent implements OnInit {
@Input()
public language: LanguageDto[];
@Input()
public schema: SchemaDetailsDto;
@Input()
public schemaFields: FieldDto[];
@Output()
public selected = new EventEmitter<ContentDto[]>();
public searchModal = new ModalView();
public contentItems: ImmutableArray<ContentDto>;
public contentsQuery = '';
public contentsPager = new Pager(0);
public selectedItems: { [id: string]: ContentDto; } = {};
public selectionCount = 0;
public isAllSelected = false;
constructor(
private readonly appsState: AppsState,
private readonly contentsService: ContentsService,
private readonly dialogs: DialogService
) {
}
public ngOnInit() {
this.load();
}
public load(showInfo = false) {
this.contentsService.getContents(this.appsState.appName, this.schema.name, this.contentsPager.pageSize, this.contentsPager.skip, this.contentsQuery, undefined, false)
.finally(() => {
this.selectedItems = {};
this.updateSelectionSummary();
})
.subscribe(dtos => {
this.contentItems = ImmutableArray.of(dtos.items);
this.contentsPager = this.contentsPager.setCount(dtos.total);
if (showInfo) {
this.dialogs.notifyInfo('Contents reloaded.');
}
}, error => {
this.dialogs.notifyError(error);
});
}
public search(query: string) {
this.contentsQuery = query;
this.contentsPager = new Pager(0);
this.load();
}
public goNext() {
this.contentsPager = this.contentsPager.goNext();
this.load();
}
public goPrev() {
this.contentsPager = this.contentsPager.goPrev();
this.load();
}
public isItemSelected(content: ContentDto) {
return this.selectedItems[content.id];
}
public complete() {
this.selected.emit([]);
}
public select() {
this.selected.emit(Object.values(this.selectedItems));
}
public selectAll(isSelected: boolean) {
this.selectedItems = {};
if (isSelected) {
for (let content of this.contentItems.values) {
this.selectedItems[content.id] = content;
}
}
this.updateSelectionSummary();
}
public onContentSelected(content: ContentDto) {
if (this.selectedItems[content.id]) {
delete this.selectedItems[content.id];
} else {
this.selectedItems[content.id] = content;
}
this.updateSelectionSummary();
}
private updateSelectionSummary() {
this.selectionCount = Object.keys(this.selectedItems).length;
this.isAllSelected = this.selectionCount === this.contentItems.length;
}
public trackByContent(content: ContentDto): string {
return content.id;
}
}

19
src/Squidex/app/features/content/shared/references-editor.component.html

@ -1,7 +1,7 @@
<div class="references-container" [class.disabled]="isDisabled">
<div class="drop-area-container" *ngIf="schema && !isDisabled">
<div class="drop-area" dnd-droppable (onDropSuccess)="onContentDropped($event.dragData.content)" [allowDrop]="canDrop()" [routerLink]="['references', schemaId, languageCode]">
Drop content here to add a reference.
<div class="drop-area-container" *ngIf="schema">
<div class="drop-area" (click)="showModal()">
Click here to link a content.
</div>
</div>
@ -14,8 +14,8 @@
<ng-template ngFor let-content let-i="index" [ngForOf]="contentItems">
<tr [sqxContent]="content" dnd-sortable [sortableIndex]="i" (sqxSorted)="onContentsSorted($event)"
[language]="language"
[schemaFields]="contentFields"
[schema]="schema"
[schemaFields]="schemaFields"
(deleting)="onContentRemoving(content)"
isReadOnly="true"
isReference="true"></tr>
@ -23,4 +23,13 @@
</ng-template>
</tbody>
</table>
</div>
</div>
<ng-container *sqxModalView="isModalVisibible;onRoot:true;closeAuto:false">
<sqx-contents-selector
[language]="language"
[schema]="schema"
[schemaFields]="schemaFields"
(selected)="onContentsSelected($event)">
</sqx-contents-selector>
</ng-container>

2
src/Squidex/app/features/content/shared/references-editor.component.scss

@ -32,7 +32,7 @@
font-size: 1.2rem;
font-weight: normal;
text-align: center;
padding: 2rem;
padding: 1rem;
cursor: pointer;
color: darken($color-border, 30%);
}

44
src/Squidex/app/features/content/shared/references-editor.component.ts

@ -11,8 +11,8 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
AppContext,
AppLanguageDto,
AppsState,
ContentDto,
ContentsService,
FieldDto,
@ -32,7 +32,6 @@ export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
styleUrls: ['./references-editor.component.scss'],
templateUrl: './references-editor.component.html',
providers: [
AppContext,
SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR
]
})
@ -46,15 +45,18 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit {
@Input()
public language: AppLanguageDto;
public isModalVisibible = false;
public schema: SchemaDetailsDto;
public schemaFields: FieldDto[];
public contentItems = ImmutableArray.empty<ContentDto>();
public contentFields: FieldDto[];
public isDisabled = false;
public isInvalidSchema = false;
constructor(public readonly ctx: AppContext,
constructor(
private readonly appsState: AppsState,
private readonly contentsService: ContentsService,
private readonly schemasService: SchemasService
) {
@ -65,7 +67,7 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit {
return;
}
this.schemasService.getSchema(this.ctx.appName, this.schemaId)
this.schemasService.getSchema(this.appsState.appName, this.schemaId)
.subscribe(dto => {
this.schema = dto;
@ -81,7 +83,7 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit {
if (Types.isArrayOfString(value) && value.length > 0) {
const contentIds: string[] = value;
this.contentsService.getContents(this.ctx.appName, this.schemaId, 10000, 0, undefined, contentIds)
this.contentsService.getContents(this.appsState.appName, this.schemaId, 10000, 0, undefined, contentIds)
.subscribe(dtos => {
this.contentItems = ImmutableArray.of(contentIds.map(id => dtos.items.find(c => c.id === id)).filter(r => !!r).map(r => r!));
});
@ -100,20 +102,24 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit {
this.callTouched = fn;
}
public canDrop() {
const component = this;
public showModal() {
this.isModalVisibible = true;
}
return (dragData: any) => {
return dragData.content instanceof ContentDto && dragData.schemaId === component.schemaId && !component.contentItems.find(c => c.id === dragData.content.id);
};
public hideModal() {
this.isModalVisibible = false;
}
public onContentDropped(content: ContentDto) {
if (content) {
this.contentItems = this.contentItems.pushFront(content);
public onContentsSelected(contents: ContentDto[]) {
for (let content of contents) {
this.contentItems = this.contentItems.push(content);
}
if (contents.length > 0) {
this.updateValue();
}
this.hideModal();
}
public onContentRemoving(content: ContentDto) {
@ -144,14 +150,14 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit {
}
private loadFields() {
this.contentFields = this.schema.fields.filter(x => x.properties.isListField);
this.schemaFields = this.schema.fields.filter(x => x.properties.isListField);
if (this.contentFields.length === 0 && this.schema.fields.length > 0) {
this.contentFields = [this.schema.fields[0]];
if (this.schemaFields.length === 0 && this.schema.fields.length > 0) {
this.schemaFields = [this.schema.fields[0]];
}
if (this.contentFields.length === 0) {
this.contentFields = [<any>{}];
if (this.schemaFields.length === 0) {
this.schemaFields = [<any>{}];
}
}
}

2
src/Squidex/app/features/schemas/declarations.ts

@ -5,8 +5,6 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
export * from './guards/schema-must-exist.guard';
export * from './pages/schema/types/assets-ui.component';
export * from './pages/schema/types/assets-validation.component';
export * from './pages/schema/types/boolean-ui.component';

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

@ -10,6 +10,7 @@ import { RouterModule, Routes } from '@angular/router';
import { DndModule } from 'ng2-dnd';
import {
SchemaMustExistGuard,
SqxFrameworkModule,
SqxSharedModule
} from '@app/shared';
@ -36,7 +37,6 @@ import {
ReferencesValidationComponent,
SchemaEditFormComponent,
SchemaFormComponent,
SchemaMustExistGuard,
SchemaPageComponent,
SchemasPageComponent,
SchemaScriptsFormComponent,

8
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html

@ -28,12 +28,12 @@
</div>
<div class="form-group">
<button class="btn btn-sm btn-link" (click)="toggleImport()" [class.hidden]="showImport">
<a class="btn btn-sm btn-link" (click)="toggleImport()" [class.hidden]="showImport">
Import schema
</button>
<button class="btn btn-sm btn-link" (click)="toggleImport()" [class.hidden]="!showImport">
</a>
<a class="btn btn-sm btn-link" (click)="toggleImport()" [class.hidden]="!showImport">
Hide
</button>
</a>
<sqx-json-editor *ngIf="showImport" formControlName="import"></sqx-json-editor>
</div>

4
src/Squidex/app/framework/angular/modals/modal-dialog.component.html

@ -13,11 +13,11 @@
</button>
</div>
<div class="modal-tabs clearfix" #tabsElement [hidden]="!showTabs">
<div class="modal-tabs {{tabsClass}} clearfix" #tabsElement [hidden]="!showTabs">
<ng-content select=[tabs]></ng-content>
</div>
<div class="modal-body">
<div class="modal-body {{contentClass}}">
<ng-content select=[content]></ng-content>
</div>

6
src/Squidex/app/framework/angular/modals/modal-dialog.component.ts

@ -31,6 +31,12 @@ export class ModalDialogComponent implements AfterViewInit {
@Input()
public fullHeight = false;
@Input()
public tabsClass = '';
@Input()
public contentClass = '';
@Output()
public close = new EventEmitter();

45
src/Squidex/app/framework/angular/panel-container.directive.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Directive, ElementRef, HostListener, Renderer } from '@angular/core';
import { AfterViewInit, Directive, ElementRef, HostListener } from '@angular/core';
import { PanelComponent } from './panel.component';
@ -17,8 +17,7 @@ export class PanelContainerDirective implements AfterViewInit {
private containerWidth = 0;
constructor(
private readonly element: ElementRef,
private readonly renderer: Renderer
private readonly element: ElementRef
) {
}
@ -48,28 +47,38 @@ export class PanelContainerDirective implements AfterViewInit {
this.containerWidth = this.element.nativeElement.getBoundingClientRect().width;
}
let currentPosition = 0;
let currentLayer = this.panels.length * 10;
const panels = this.panels;
const last = this.panels[this.panels.length - 1];
let currentSize = 0;
let panelsWidthSpread = 0;
for (let panel of this.panels) {
const panelRoot = panel.panel.nativeElement;
for (let panel of panels) {
if (panel.desiredWidth !== '*') {
const layoutWidth = panel.desiredWidth;
let layoutWidth = '';
panel.measure(layoutWidth);
if (panel.desiredWidth === '*' && panel === last) {
layoutWidth = (this.containerWidth - currentPosition) + 'px';
currentSize += panel.renderWidth;
} else {
layoutWidth = panel.desiredWidth;
panelsWidthSpread++;
}
}
for (let panel of panels) {
if (panel.desiredWidth === '*') {
const layoutWidth = (this.containerWidth - currentSize) / panelsWidthSpread;
panel.measure(layoutWidth + 'px');
currentSize += panel.renderWidth;
}
}
let currentPosition = 0;
let currentLayer = panels.length * 10;
this.renderer.setElementStyle(panelRoot, 'top', '0px');
this.renderer.setElementStyle(panelRoot, 'left', currentPosition + 'px');
this.renderer.setElementStyle(panelRoot, 'width', layoutWidth);
this.renderer.setElementStyle(panelRoot, 'bottom', '0px');
this.renderer.setElementStyle(panelRoot, 'position', 'absolute');
this.renderer.setElementStyle(panelRoot, 'z-index', currentLayer.toString());
for (let panel of panels) {
panel.arrange(currentPosition + 'px', currentLayer.toString());
currentPosition += panel.renderWidth;
currentLayer -= 10;

2
src/Squidex/app/framework/angular/panel.component.html

@ -13,7 +13,7 @@
</h3>
</div>
<a class="panel-close" sqxParentLink isLazyLoaded="true" *ngIf="showClose">
<a class="panel-close" sqxParentLink [isLazyLoaded]="isLazyLoaded" *ngIf="showClose">
<i class="icon-close"></i>
</a>

30
src/Squidex/app/framework/angular/panel.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, Renderer, ViewChild } from '@angular/core';
import { slideRightAnimation } from './animations';
@ -20,6 +20,8 @@ import { PanelContainerDirective } from './panel-container.directive';
]
})
export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
private styleWidth: string;
public renderWidth = 0;
@Input()
@ -34,6 +36,9 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
@Input()
public isFullSize = false;
@Input()
public isLazyLoaded = true;
@Input()
public showScrollbar = false;
@ -53,7 +58,8 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
public panel: ElementRef;
constructor(
private readonly container: PanelContainerDirective
private readonly container: PanelContainerDirective,
private readonly renderer: Renderer
) {
}
@ -66,8 +72,24 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit {
}
public ngAfterViewInit() {
this.renderWidth = this.panel.nativeElement.getBoundingClientRect().width;
this.container.invalidate();
}
public measure(size: string) {
if (this.styleWidth !== size) {
this.styleWidth = size;
this.renderer.setElementStyle(this.panel.nativeElement, 'width', size);
this.renderWidth = this.panel.nativeElement.getBoundingClientRect().width;
}
}
public arrange(left: any, layer: any) {
this.renderer.setElementStyle(this.panel.nativeElement, 'top', '0px');
this.renderer.setElementStyle(this.panel.nativeElement, 'left', left);
this.renderer.setElementStyle(this.panel.nativeElement, 'bottom', '0px');
this.renderer.setElementStyle(this.panel.nativeElement, 'position', 'absolute');
this.renderer.setElementStyle(this.panel.nativeElement, 'z-index', layer);
}
}

38
src/Squidex/app/shared/guards/schema-must-exist-published.guard.ts

@ -0,0 +1,38 @@
/*
* 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 { allParams } from '@app/framework';
import { SchemasState } from './../state/schemas.state';
@Injectable()
export class SchemaMustExistPublishedGuard implements CanActivate {
constructor(
private readonly schemasState: SchemasState,
private readonly router: Router
) {
}
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const schemaName = allParams(route)['schemaName'];
const result =
this.schemasState.selectSchema(schemaName)
.do(dto => {
if (!dto || !dto.isPublished) {
this.router.navigate(['/404']);
}
})
.map(s => s !== null && s.isPublished);
return result;
}
}

6
src/Squidex/app/features/schemas/guards/schema-must-exist.guard.ts → src/Squidex/app/shared/guards/schema-must-exist.guard.ts

@ -9,7 +9,9 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { allParams, SchemasState } from '@app/shared';
import { allParams } from '@app/framework';
import { SchemasState } from './../state/schemas.state';
@Injectable()
export class SchemaMustExistGuard implements CanActivate {
@ -29,7 +31,7 @@ export class SchemaMustExistGuard implements CanActivate {
this.router.navigate(['/404']);
}
})
.map(u => u !== null);
.map(s => s !== null);
return result;
}

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

@ -13,6 +13,8 @@ export * from './guards/resolve-app-languages.guard';
export * from './guards/resolve-content.guard';
export * from './guards/resolve-published-schema.guard';
export * from './guards/resolve-schema.guard';
export * from './guards/schema-must-exist-published.guard';
export * from './guards/schema-must-exist.guard';
export * from './guards/unset-app.guard';
export * from './interceptors/auth.interceptor';

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

@ -51,6 +51,8 @@ import {
ResolveContentGuard,
ResolvePublishedSchemaGuard,
ResolveSchemaGuard,
SchemaMustExistGuard,
SchemaMustExistPublishedGuard,
SchemasService,
RulesService,
UIService,
@ -151,6 +153,8 @@ export class SqxSharedModule {
ResolvePublishedSchemaGuard,
ResolveSchemaGuard,
RulesService,
SchemaMustExistGuard,
SchemaMustExistPublishedGuard,
SchemasService,
SchemasState,
UIService,

4
src/Squidex/app/shared/state/schemas.state.ts

@ -156,6 +156,10 @@ export class SchemasState extends State<Snapshot> {
this.changes.map(s => s.schemas)
.distinctUntilChanged();
public publishedSchemas =
this.changes.map(s => s.schemas.filter(x => x.isPublished))
.distinctUntilChanged();
public get schemaName() {
return this.snapshot.selectedSchema!.name;
}

Loading…
Cancel
Save