Browse Source

Inspect tab for content.

pull/756/head
Sebastian 4 years ago
parent
commit
f2cc9ea715
  1. 4
      backend/i18n/frontend_en.json
  2. 4
      backend/i18n/frontend_it.json
  3. 4
      backend/i18n/frontend_nl.json
  4. 4
      backend/i18n/frontend_zh.json
  5. 4
      backend/i18n/source/frontend_en.json
  6. 14
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  7. 11
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs
  8. 2
      frontend/app-config/webpack.config.js
  9. 3
      frontend/app/features/administration/guards/user-must-exist.guard.ts
  10. 1
      frontend/app/features/content/declarations.ts
  11. 6
      frontend/app/features/content/module.ts
  12. 136
      frontend/app/features/content/pages/content/content-page.component.html
  13. 13
      frontend/app/features/content/pages/content/content-page.component.scss
  14. 23
      frontend/app/features/content/pages/content/inspecting/content-inspection.component.html
  15. 28
      frontend/app/features/content/pages/content/inspecting/content-inspection.component.scss
  16. 72
      frontend/app/features/content/pages/content/inspecting/content-inspection.component.ts
  17. 42
      frontend/app/framework/angular/http/http-extensions.ts
  18. 29
      frontend/app/shared/services/contents.service.ts

4
backend/i18n/frontend_en.json

@ -403,6 +403,7 @@
"contents.componentsNoSchema": "Add at least one schema to add components.",
"contents.contentNotValid": "Content element not valid, please check the field with the red bar on the left in all languages (if localizable).",
"contents.contentTab.editor": "Editor",
"contents.contentTab.inspect": "Inspect",
"contents.contentTab.references": "References",
"contents.contentTab.referencing": "Referencing",
"contents.create": "New",
@ -425,6 +426,9 @@
"contents.draftStatus": "New Version",
"contents.editPageTitle": "Edit Content",
"contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.",
"contents.inspectContent": "Content",
"contents.inspectData": "Data",
"contents.inspectFlatData": "Flat Data",
"contents.invariantFieldDescription": "The '{fieldName}' field of the content item.",
"contents.languageModeAll": "All Languages",
"contents.languageModeSingle": "Single Language",

4
backend/i18n/frontend_it.json

@ -403,6 +403,7 @@
"contents.componentsNoSchema": "Add at least one schema to add components.",
"contents.contentNotValid": "Un elemento del contenuto non è valido, verifica il campo con la barra rossa per tutte le lingue impostate (se presenti).",
"contents.contentTab.editor": "Editor",
"contents.contentTab.inspect": "Inspect",
"contents.contentTab.references": "Collegato a",
"contents.contentTab.referencing": "Collegato da",
"contents.create": "Nuovo",
@ -425,6 +426,9 @@
"contents.draftStatus": "Nuova versione",
"contents.editPageTitle": "Modifica contenuto",
"contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.",
"contents.inspectContent": "Content",
"contents.inspectData": "Data",
"contents.inspectFlatData": "Flat Data",
"contents.invariantFieldDescription": "Il campo '{fieldName}' del contenuto.",
"contents.languageModeAll": "Tutte le lingue",
"contents.languageModeSingle": "Una sola lingua",

4
backend/i18n/frontend_nl.json

@ -403,6 +403,7 @@
"contents.componentsNoSchema": "Add at least one schema to add components.",
"contents.contentNotValid": "Inhoudselement niet geldig, controleer het veld met de rode balk aan de linkerkant in alle talen (indien lokaliseerbaar).",
"contents.contentTab.editor": "Editor",
"contents.contentTab.inspect": "Inspect",
"contents.contentTab.references": "References",
"contents.contentTab.referencing": "Referencing",
"contents.create": "Nieuw",
@ -425,6 +426,9 @@
"contents.draftStatus": "Nieuwe versie",
"contents.editPageTitle": "Inhoud bewerken",
"contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.",
"contents.inspectContent": "Content",
"contents.inspectData": "Data",
"contents.inspectFlatData": "Flat Data",
"contents.invariantFieldDescription": "Het veld '{fieldName}' van het inhoudsitem.",
"contents.languageModeAll": "Alle talen",
"contents.languageModeSingle": "Enkele taal",

4
backend/i18n/frontend_zh.json

@ -403,6 +403,7 @@
"contents.componentsNoSchema": "添加至少一个Schemas来添加组件。",
"contents.contentNotValid": "内容元素无效,请用所有语言(如果可本地化)检查左侧带有红色条的字段。",
"contents.contentTab.editor": "编辑器",
"contents.contentTab.inspect": "Inspect",
"contents.contentTab.references": "参考资料",
"contents.contentTab.referencing": "引用",
"contents.create": "新建",
@ -425,6 +426,9 @@
"contents.draftStatus": "新版本",
"contents.editPageTitle": "编辑内容",
"contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.",
"contents.inspectContent": "Content",
"contents.inspectData": "Data",
"contents.inspectFlatData": "Flat Data",
"contents.invariantFieldDescription": "内容项的 '{fieldName}' 字段。",
"contents.languageModeAll": "所有语言",
"contents.languageModeSingle": "单一语言",

4
backend/i18n/source/frontend_en.json

@ -403,6 +403,7 @@
"contents.componentsNoSchema": "Add at least one schema to add components.",
"contents.contentNotValid": "Content element not valid, please check the field with the red bar on the left in all languages (if localizable).",
"contents.contentTab.editor": "Editor",
"contents.contentTab.inspect": "Inspect",
"contents.contentTab.references": "References",
"contents.contentTab.referencing": "Referencing",
"contents.create": "New",
@ -425,6 +426,9 @@
"contents.draftStatus": "New Version",
"contents.editPageTitle": "Edit Content",
"contents.idPlaceholder": "Define a custom ID or leave empty to let Squidex generate one.",
"contents.inspectContent": "Content",
"contents.inspectData": "Data",
"contents.inspectFlatData": "Flat Data",
"contents.invariantFieldDescription": "The '{fieldName}' field of the content item.",
"contents.languageModeAll": "All Languages",
"contents.languageModeSingle": "Single Language",

14
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -203,10 +203,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return;
}
var skipReason = SkipReason.None;
if (!triggerHandler.Trigger(typed, context))
{
jobs.Add(JobResult.ConditionDoesNotMatch);
return;
skipReason = SkipReason.ConditionDoesNotMatch;
}
await foreach (var enrichedEvent in triggerHandler.CreateEnrichedEventsAsync(typed, context, ct))
@ -222,17 +223,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules
if (!triggerHandler.Trigger(enrichedEvent, context))
{
if (jobs.Count == 0)
{
jobs.Add(JobResult.ConditionDoesNotMatch);
}
continue;
skipReason = SkipReason.ConditionDoesNotMatch;
}
var job = await CreateJobAsync(actionHandler, enrichedEvent, context, now);
jobs.Add(job);
jobs.Add(job with { SkipReason = skipReason });
}
catch (Exception ex)
{

11
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs

@ -411,6 +411,8 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{
var context = Rule();
var enrichedEvent = new EnrichedContentEvent { AppId = appId };
var @event = Envelope.Create(new ContentCreated());
A.CallTo(() => ruleTriggerHandler.Handles(@event.Payload))
@ -419,12 +421,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleTriggerHandler.Trigger(MatchPayload(@event), context))
.Returns(false);
var (_, _, reason) = await sut.CreateJobsAsync(@event, context).SingleAsync();
A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(MatchPayload(@event), context, default))
.Returns(new List<EnrichedEvent> { enrichedEvent }.ToAsyncEnumerable());
Assert.Equal(SkipReason.ConditionDoesNotMatch, reason);
var (job, _, reason) = await sut.CreateJobsAsync(@event, context).SingleAsync();
A.CallTo(() => ruleTriggerHandler.CreateEnrichedEventsAsync(A<Envelope<AppEvent>>._, A<RuleContext>._, default))
.MustNotHaveHappened();
Assert.Equal(SkipReason.ConditionDoesNotMatch, reason);
Assert.NotNull(job);
}
[Fact]

2
frontend/app-config/webpack.config.js

@ -53,7 +53,7 @@ module.exports = function calculateConfig(env) {
const configFile = isTests ? 'tsconfig.spec.json' : 'tsconfig.app.json';
// eslint-disable-next-line no-console
console.log(`Use ${configFile}, Production: ${isProduction}`);
console.log(`Use ${configFile}, Production: ${!!isProduction}`);
const config = {
mode: isProduction ? 'production' : 'development',

3
frontend/app/features/administration/guards/user-must-exist.guard.ts

@ -9,8 +9,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { UsersState } from '@app/features/administration/internal';
import { allParams } from '@app/framework';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { Observable, map, tap } from 'rxjs';
@Injectable()
export class UserMustExistGuard implements CanActivate {

1
frontend/app/features/content/declarations.ts

@ -14,6 +14,7 @@ export * from './pages/content/editor/content-editor.component';
export * from './pages/content/editor/content-field.component';
export * from './pages/content/editor/content-section.component';
export * from './pages/content/editor/field-languages.component';
export * from './pages/content/inspecting/content-inspection.component';
export * from './pages/content/references/content-references.component';
export * from './pages/contents/contents-filters-page.component';
export * from './pages/contents/contents-page.component';

6
frontend/app/features/content/module.ts

@ -10,8 +10,7 @@ import { RouterModule, Routes } from '@angular/router';
import { CanDeactivateGuard, ContentMustExistGuard, LoadLanguagesGuard, LoadSchemasGuard, SchemaMustExistPublishedGuard, SchemaMustNotBeSingletonGuard, SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { ScrollingModule as ScrollingModuleExperimental } from '@angular/cdk-experimental/scrolling';
import { ArrayEditorComponent, ArrayItemComponent, AssetsEditorComponent, CommentsPageComponent, ComponentComponent, ComponentSectionComponent, ContentComponent, ContentCreatorComponent, ContentEditorComponent, ContentEventComponent, ContentExtensionComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentPageComponent, ContentReferencesComponent, ContentSectionComponent, ContentsFiltersPageComponent, ContentsPageComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldEditorComponent, FieldLanguagesComponent, IFrameEditorComponent, PreviewButtonComponent, ReferenceItemComponent, ReferencesEditorComponent, SchemasPageComponent, SidebarPageComponent, StockPhotoEditorComponent } from './declarations';
import { CalendarPageComponent } from './pages/calendar/calendar-page.component';
import { ArrayEditorComponent, ArrayItemComponent, AssetsEditorComponent, CalendarPageComponent, CommentsPageComponent, ComponentComponent, ComponentSectionComponent, ContentComponent, ContentCreatorComponent, ContentEditorComponent, ContentEventComponent, ContentExtensionComponent, ContentFieldComponent, ContentHistoryPageComponent, ContentInspectionComponent, ContentPageComponent, ContentReferencesComponent, ContentSectionComponent, ContentsFiltersPageComponent, ContentsPageComponent, CustomViewEditorComponent, DueTimeSelectorComponent, FieldEditorComponent, FieldLanguagesComponent, IFrameEditorComponent, PreviewButtonComponent, ReferenceItemComponent, ReferencesEditorComponent, SchemasPageComponent, SidebarPageComponent, StockPhotoEditorComponent } from './declarations';
const routes: Routes = [
{
@ -95,14 +94,15 @@ const routes: Routes = [
CalendarPageComponent,
CommentsPageComponent,
ComponentComponent,
ContentCreatorComponent,
ComponentSectionComponent,
ContentComponent,
ContentCreatorComponent,
ContentEditorComponent,
ContentEventComponent,
ContentExtensionComponent,
ContentFieldComponent,
ContentHistoryPageComponent,
ContentInspectionComponent,
ContentPageComponent,
ContentReferencesComponent,
ContentSectionComponent,

136
frontend/app/features/content/pages/content/content-page.component.html

@ -17,12 +17,8 @@
<sqx-title message="i18n:contents.createPageTitle"></sqx-title>
</ng-template>
</div>
</ng-container>
<ng-container menu>
<ng-container *ngIf="content">
<ul class="nav nav-tabs2" *ngIf="contentTab | async; let tab">
<ul class="nav nav-tabs2" *ngIf="content && contentTab | async; let tab">
<li class="nav-item">
<a class="nav-link" [routerLink]="[]" [queryParams]="{ tab: 'editor' }" [class.active]="tab === 'editor'">
{{ 'contents.contentTab.editor' | sqxTranslate }}
@ -38,71 +34,91 @@
{{ 'contents.contentTab.referencing' | sqxTranslate }}
</a>
</li>
<li>
<a class="nav-link" [routerLink]="[]" [queryParams]="{ tab: 'inspect' }" [class.active]="tab === 'inspect'">
{{ 'contents.contentTab.inspect' | sqxTranslate }}
</a>
</li>
<li *ngIf="schema.properties.contentEditorUrl">
<a class="nav-link" [routerLink]="[]" [queryParams]="{ tab: 'extension' }" [class.active]="tab === 'extension'">
{{ 'common.extension' | sqxTranslate }}
</a>
</li>
</ul>
</ng-container>
</div>
</ng-container>
<ng-container menu>
<ng-container *ngIf="content; else noContentMenu">
<sqx-notifo topic="apps/{{contentsState.appId}}/schemas/{{schema?.name}}/contents/{{content.id}}"></sqx-notifo>
<ng-container *ngIf="contentTab | async; let tab">
<ng-container *ngIf="tab === 'references' || tab === 'referencing'; else defaultHeader">
<button type="button" class="btn btn-primary ms-2" (click)="publish()">
{{ 'contents.publishAll' | sqxTranslate }}
</button>
<button type="button" class="btn btn-primary ms-2" (click)="validate()">
{{ 'contents.validate' | sqxTranslate }}
</button>
<div class="menu">
<ng-container *ngIf="content; else noContentMenu">
<sqx-notifo topic="apps/{{contentsState.appId}}/schemas/{{schema?.name}}/contents/{{content.id}}"></sqx-notifo>
<ng-container *ngIf="languages.length > 1">
<sqx-language-selector class="languages-buttons" [(language)]="language" [languages]="languages"></sqx-language-selector>
</ng-container>
<ng-template #defaultHeader>
<sqx-preview-button [schema]="schema" [content]="content" [confirm]="confirmPreview"></sqx-preview-button>
<ng-container *ngIf="content?.canDelete">
<button type="button" class="btn btn-outline-secondary ms-2" (click)="dropdown.toggle()" #buttonOptions>
<i class="icon-dots"></i>
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade>
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="delete()"
confirmTitle="i18n:contents.deleteConfirmTitle"
confirmText="i18n:contents.deleteConfirmText"
confirmRememberKey="deleteContent">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
<ng-container *ngIf="contentTab | async; let tab">
<ng-container *ngIf="tab === 'references' || tab === 'referencing'; else defaultHeader">
<ng-container *ngIf="content?.canDelete">
<button type="button" class="btn btn-outline-secondary ms-2" (click)="dropdown.toggle()" #buttonOptions>
<i class="icon-dots"></i>
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade>
<a class="dropdown-item" (click)="publish()">
{{ 'contents.publishAll' | sqxTranslate }}
</a>
<a class="dropdown-item" (click)="validate()">
{{ 'contents.validate' | sqxTranslate }}
</a>
</div>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngIf="content?.canUpdate">
<button type="submit" class="btn btn-primary ms-2" title="i18n:common.saveShortcut" shortcut="CTRL + SHIFT + S">
{{ 'common.save' | sqxTranslate }}
</button>
</ng-container>
</ng-template>
<ng-template #defaultHeader>
<sqx-preview-button [schema]="schema" [content]="content" [confirm]="confirmPreview"></sqx-preview-button>
<ng-container *ngIf="content?.canDelete">
<button type="button" class="btn btn-outline-secondary ms-2" (click)="dropdown.toggle()" #buttonOptions>
<i class="icon-dots"></i>
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade>
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="delete()"
confirmTitle="i18n:contents.deleteConfirmTitle"
confirmText="i18n:contents.deleteConfirmText"
confirmRememberKey="deleteContent">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</ng-container>
</ng-container>
<ng-container *ngIf="content?.canUpdate">
<button type="submit" class="btn btn-primary ms-2" title="i18n:common.saveShortcut" shortcut="CTRL + SHIFT + S">
{{ 'common.save' | sqxTranslate }}
</button>
</ng-container>
</ng-template>
</ng-container>
</ng-container>
</ng-container>
<ng-template #noContentMenu>
<button type="button" class="btn btn-primary" (click)="save()" *ngIf="contentsState.canCreate | async">
{{ 'common.save' | sqxTranslate }}
</button>
<ng-template #noContentMenu>
<button type="button" class="btn btn-primary ms-2" (click)="save()" *ngIf="contentsState.canCreate | async">
{{ 'common.save' | sqxTranslate }}
</button>
<button type="submit" class="btn btn-success ms-2" title="i18n:common.saveShortcut" shortcut="CTRL + SHIFT + S" *ngIf="contentsState.canCreateAndPublish | async">
{{ 'contents.saveAndPublish' | sqxTranslate }}
</button>
</ng-template>
<button type="submit" class="btn btn-success ms-2" title="i18n:common.saveShortcut" shortcut="CTRL + SHIFT + S" *ngIf="contentsState.canCreateAndPublish | async">
{{ 'contents.saveAndPublish' | sqxTranslate }}
</button>
</ng-template>
<sqx-form-error [bubble]="true" [closeable]="true" [error]="contentForm.error | async"></sqx-form-error>
<sqx-form-error [bubble]="true" [closeable]="true" [error]="contentForm.error | async"></sqx-form-error>
</div>
</ng-container>
<ng-container>
@ -122,6 +138,14 @@
[languages]="languages">
</sqx-content-references>
</ng-container>
<ng-container *ngSwitchCase="'inspect'">
<sqx-content-inspection
[appName]="contentsState.appName"
[language]="language"
[languages]="languages"
[content]="content">
</sqx-content-inspection>
</ng-container>
<ng-container *ngSwitchCase="'extension'">
<sqx-content-extension mode="referencing" *ngIf="schema.properties.contentEditorUrl && content"
[content]="content"
@ -147,14 +171,14 @@
<ng-template #noContentEditor>
<sqx-content-editor
[(language)]="language"
[(contentId)]="contentId"
[isNew]="!content"
[contentForm]="contentForm"
[contentFormCompare]="contentFormCompare"
[contentVersion]="contentVersion"
[formContext]="formContext"
[languages]="languages"
[schema]="schema">
[schema]="schema"
[(contentId)]="contentId">
</sqx-content-editor>
</ng-template>
</ng-container>

13
frontend/app/features/content/pages/content/content-page.component.scss

@ -8,8 +8,21 @@
.nav-tabs2 {
@include absolute(auto, auto, 0, 5rem);
flex-wrap: nowrap;
a {
padding-bottom: 1.5rem;
}
}
.menu {
@include absolute(0, 0, 0);
align-items: center;
background: white;
border-radius: 0;
border: 0;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
padding: $panel-padding;
}

23
frontend/app/features/content/pages/content/inspecting/content-inspection.component.html

@ -0,0 +1,23 @@
<div class="inner-menu">
<ul class="nav nav-tabs2" *ngIf="mode | async; let currentMode">
<li class="nav-item">
<a class="nav-link" [class.active]="currentMode === 'Content'" (click)="setMode('Content')">
{{ 'contents.inspectContent' | sqxTranslate }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" [class.active]="currentMode === 'Data'" (click)="setMode('Data')">
{{ 'contents.inspectData' | sqxTranslate }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" [class.active]="currentMode === 'FlatData'" (click)="setMode('FlatData')">
{{ 'contents.inspectFlatData' | sqxTranslate }}
</a>
</li>
</ul>
</div>
<div class="inner-main">
<sqx-code-editor [borderless]="true" [ngModel]="actualData | async" valueMode="Json"></sqx-code-editor>
</div>

28
frontend/app/features/content/pages/content/inspecting/content-inspection.component.scss

@ -0,0 +1,28 @@
:host,
.inner-main {
display: flex;
flex-direction: column;
flex-grow: 1;
}
:host ::ng-deep {
.editor {
@include absolute(0, 0, 0, 0);
}
}
.inner-menu,
.inner-main {
position: relative;
}
.inner-menu {
border-bottom: 1px solid $color-border;
border-radius: 0;
height: 4rem;
}
.nav {
@include absolute(null, null, 0, 2rem);
}

72
frontend/app/features/content/pages/content/inspecting/content-inspection.component.ts

@ -0,0 +1,72 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { AppLanguageDto, ContentDto, ContentsService } from '@app/shared';
import { BehaviorSubject, combineLatest, of } from 'rxjs';
import { filter, map, switchMap } from 'rxjs/operators';
type Mode = 'Content' | 'Data' | 'FlatData';
@Component({
selector: 'sqx-content-inspection[appName][content][language][languages]',
styleUrls: ['./content-inspection.component.scss'],
templateUrl: './content-inspection.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContentInspectionComponent implements OnChanges {
private languageChanges$ = new BehaviorSubject<AppLanguageDto | null>(null);
@Input()
public appName: string;
@Input()
public language: AppLanguageDto;
@Input()
public languages: ReadonlyArray<AppLanguageDto>;
@Input()
public content: ContentDto;
public mode = new BehaviorSubject<Mode>('Content');
public actualData =
combineLatest([
this.languageChanges$,
this.mode,
]).pipe(
filter(x => !!x[0]),
switchMap(([language, mode]) => {
if (mode === 'Content') {
return of(this.content);
} else if (mode === 'Data') {
return of(this.content.data);
} else {
return this.contentsService.getContent(this.appName,
this.content.schemaName,
this.content.id,
language?.iso2Code).pipe(
map(x => x.data));
}
}));
constructor(
private readonly contentsService: ContentsService,
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['language']) {
this.languageChanges$.next(this.language);
}
}
public setMode(mode: Mode) {
this.mode.next(mode);
}
}

42
frontend/app/framework/angular/http/http-extensions.ts

@ -14,43 +14,49 @@ import { catchError, map } from 'rxjs/operators';
export module HTTP {
export function upload<T = any>(http: HttpClient, method: string, url: string, file: Blob, version?: Version): Observable<HttpEvent<T>> {
const req = new HttpRequest(method, url, getFormData(file), { headers: createHeaders(version), reportProgress: true });
const req = new HttpRequest(method, url, getFormData(file), { headers: createHeaders(version, undefined), reportProgress: true });
return http.request<T>(req);
}
export function getVersioned<T = any>(http: HttpClient, url: string, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
export function getVersioned<T = any>(http: HttpClient, url: string,
version?: Version, customHeaders?: HttpHeaders): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version, customHeaders);
return handleVersion(http.get<T>(url, { observe: 'response', headers }));
}
export function postVersioned<T = any>(http: HttpClient, url: string, body: any, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
export function postVersioned<T = any>(http: HttpClient, url: string, body: any,
version?: Version, customHeaders?: HttpHeaders): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version, customHeaders);
return handleVersion(http.post<T>(url, body, { observe: 'response', headers }));
}
export function putVersioned<T = any>(http: HttpClient, url: string, body: any, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
export function putVersioned<T = any>(http: HttpClient, url: string, body: any,
version?: Version, customHeaders?: HttpHeaders): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version, customHeaders);
return handleVersion(http.put<T>(url, body, { observe: 'response', headers }));
}
export function patchVersioned<T = any>(http: HttpClient, url: string, body: any, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
export function patchVersioned<T = any>(http: HttpClient, url: string, body: any,
version?: Version, customHeaders?: HttpHeaders): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version, customHeaders);
return handleVersion(http.request<T>('PATCH', url, { body, observe: 'response', headers }));
}
export function deleteVersioned<T = any>(http: HttpClient, url: string, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
export function deleteVersioned<T = any>(http: HttpClient, url: string,
version?: Version, customHeaders?: HttpHeaders): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version, customHeaders);
return handleVersion(http.delete<T>(url, { observe: 'response', headers }));
}
export function requestVersioned<T = any>(http: HttpClient, method: string, url: string, version?: Version, body?: any): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
export function requestVersioned<T = any>(http: HttpClient, method: string, url: string,
version?: Version, body?: any, customHeaders?: HttpHeaders): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version, customHeaders);
return handleVersion(http.request<T>(method, url, { observe: 'response', headers, body }));
}
@ -63,12 +69,14 @@ export module HTTP {
return formData;
}
function createHeaders(version?: Version): HttpHeaders {
function createHeaders(version: Version | undefined, customHeaders: HttpHeaders | undefined): HttpHeaders {
customHeaders ||= new HttpHeaders();
if (version && version.value && version.value.length > 0) {
return new HttpHeaders().set('If-Match', version.value);
} else {
return new HttpHeaders();
return customHeaders.set('If-Match', version.value);
}
return customHeaders;
}
function handleVersion<T>(httpRequest: Observable<HttpResponse<T>>): Observable<Versioned<HttpResponse<T>>> {

29
frontend/app/shared/services/contents.service.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { AnalyticsService, ApiUrlConfig, DateTime, ErrorDto, hasAnyLink, HTTP, mapVersioned, pretifyError, Resource, ResourceLinks, ResultSet, Version, Versioned } from '@app/framework';
import { Observable } from 'rxjs';
@ -165,6 +165,23 @@ export class ContentsService {
pretifyError('i18n:contents.loadFailed'));
}
public getContent(appName: string, schemaName: string, id: string, language?: string): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
let headers = new HttpHeaders();
if (language) {
headers = headers.set('X-Flatten', '1');
headers = headers.set('X-Languages', language);
}
return HTTP.getVersioned(this.http, url, undefined, headers).pipe(
map(({ payload }) => {
return parseContent(payload.body);
}),
pretifyError('i18n:contents.loadContentFailed'));
}
public getAllContents(appName: string, q: ContentsByIds | ContentsBySchedule): Observable<ContentsDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}`);
@ -177,16 +194,6 @@ export class ContentsService {
pretifyError('i18n:contents.loadFailed'));
}
public getContent(appName: string, schemaName: string, id: string): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
return HTTP.getVersioned(this.http, url).pipe(
map(({ payload }) => {
return parseContent(payload.body);
}),
pretifyError('i18n:contents.loadContentFailed'));
}
public getContentReferences(appName: string, schemaName: string, id: string, q?: ContentsByQuery): Observable<ContentsDto> {
const { fullQuery } = buildQuery(q);

Loading…
Cancel
Save