diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs index 2785eceba..531507e37 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs @@ -14,6 +14,8 @@ namespace Squidex.Domain.Apps.Core.Schemas { public bool? DefaultValue { get; set; } + public bool InlineEditable { get; set; } + public BooleanFieldEditor Editor { get; set; } public override T Accept(IFieldPropertiesVisitor visitor) diff --git a/src/Squidex/app/app.routes.ts b/src/Squidex/app/app.routes.ts index 00674bff9..c6ca613b6 100644 --- a/src/Squidex/app/app.routes.ts +++ b/src/Squidex/app/app.routes.ts @@ -6,7 +6,7 @@ */ import { ModuleWithProviders } from '@angular/core'; -import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; +import { RouterModule, Routes } from '@angular/router'; import { AppAreaComponent, @@ -96,4 +96,4 @@ export const routes: Routes = [ } ]; -export const routing: ModuleWithProviders = RouterModule.forRoot(routes, { useHash: false, preloadingStrategy: PreloadAllModules }); \ No newline at end of file +export const routing: ModuleWithProviders = RouterModule.forRoot(routes, { useHash: false }); \ No newline at end of file diff --git a/src/Squidex/app/features/content/pages/content/content-field.component.html b/src/Squidex/app/features/content/pages/content/content-field.component.html index 7a05664f4..44784c00c 100644 --- a/src/Squidex/app/features/content/pages/content/content-field.component.html +++ b/src/Squidex/app/features/content/pages/content/content-field.component.html @@ -6,7 +6,7 @@ Disabled
-
+
@@ -24,7 +24,7 @@
- +
@@ -48,13 +48,13 @@
- +
- +
- +
@@ -106,7 +106,7 @@
- +
diff --git a/src/Squidex/app/features/content/pages/content/content-field.component.ts b/src/Squidex/app/features/content/pages/content/content-field.component.ts index 4152da206..b3f36db2a 100644 --- a/src/Squidex/app/features/content/pages/content/content-field.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-field.component.ts @@ -7,9 +7,9 @@ import { Component, Input, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; import { AppLanguageDto, FieldDto } from 'shared'; -import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'sqx-content-field', @@ -17,10 +17,6 @@ import { ActivatedRoute, Router } from '@angular/router'; templateUrl: './content-field.component.html' }) export class ContentFieldComponent implements OnInit { - constructor(private readonly router: Router, private readonly route: ActivatedRoute) { - } - private masterLanguageCode: string; - @Input() public field: FieldDto; @@ -33,35 +29,38 @@ export class ContentFieldComponent implements OnInit { @Input() public contentFormSubmitted: boolean; - public fieldPartitions: string[]; - public fieldPartition: string; + public selectedFormControl: string; + public selectedLanguage: AppLanguageDto; - public selectLanguage(language: AppLanguageDto) { - this.fieldPartition = language.iso2Code; + constructor( + private readonly router: Router, + private readonly route: ActivatedRoute + ) { } public ngOnInit() { - this.masterLanguageCode = this.languages.find(l => l.isMaster)!.iso2Code; - if (this.field.isDisabled) { this.fieldForm.disable(); } - if (this.field.partitioning === 'language') { - this.fieldPartitions = this.languages.map(t => t.iso2Code); - this.fieldPartition = this.fieldPartitions[0]; + const masterLanguage = this.languages.find(l => l.isMaster)!; + + if (this.field.isLocalizable) { + this.selectedFormControl = masterLanguage.iso2Code; } else { - this.fieldPartitions = ['iv']; - this.fieldPartition = 'iv'; + this.selectedFormControl = 'iv'; } + + this.selectedLanguage = masterLanguage; } - public assetPluginClicked() { - this.router.navigate(['assets'], { relativeTo: this.route }); + public selectLanguage(language: AppLanguageDto) { + this.selectedFormControl = language.iso2Code; + this.selectedLanguage = language; } - public selectFieldLanguage(partition: string) { - return partition === 'iv' ? this.masterLanguageCode : partition; + public assetPluginClicked() { + this.router.navigate(['assets'], { relativeTo: this.route }); } } 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 9ba93a9e9..c67137e86 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 @@ -40,6 +40,7 @@ import { export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, OnInit { private contentStatusChangedSubscription: Subscription; private contentDeletedSubscription: Subscription; + private contentUpdatedSubscription: Subscription; private contentVersionSelectedSubscription: Subscription; public schema: SchemaDetailsDto; @@ -62,6 +63,7 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, public ngOnDestroy() { this.contentVersionSelectedSubscription.unsubscribe(); this.contentStatusChangedSubscription.unsubscribe(); + this.contentUpdatedSubscription.unsubscribe(); this.contentDeletedSubscription.unsubscribe(); } @@ -80,6 +82,14 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, } }); + this.contentUpdatedSubscription = + this.ctx.bus.of(ContentUpdated) + .subscribe(message => { + if (this.content && message.content.id === this.content.id) { + this.reloadContentForm(message.content); + } + }); + this.contentStatusChangedSubscription = this.ctx.bus.of(ContentStatusChanged) .subscribe(message => { @@ -102,9 +112,7 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, this.ctx.route.data.map(d => d.content) .subscribe((content: ContentDto) => { - this.content = content; - - this.populateContentForm(); + this.reloadContentForm(content); }); } @@ -122,7 +130,7 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, this.contentOld = null; this.emitContentUpdated(this.content); - this.populateContentForm(); + this.reloadContentForm(this.content); } } @@ -159,13 +167,13 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, } else { this.contentsService.putContent(this.ctx.appName, this.schema.name, this.content.id, requestDto, this.content.version) .subscribe(dto => { - this.content = this.content.update(dto.payload, this.ctx.userToken, dto.version); + const content = this.content.update(dto.payload, this.ctx.userToken, dto.version); this.ctx.notifyInfo('Content saved successfully.'); this.emitContentUpdated(this.content); this.enableContentForm(); - this.populateContentForm(); + this.reloadContentForm(content); }, error => { this.ctx.notifyError(error); @@ -187,12 +195,9 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, this.contentOld = null; } - this.content = this.content.setData(dto); - this.ctx.notifyInfo('Content version loaded successfully.'); - this.emitContentUpdated(this.content); - this.populateContentForm(); + this.reloadContentForm(this.content.setData(dto)); }, error => { this.ctx.notifyError(error); }); @@ -235,12 +240,12 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, for (const field of schema.fields) { const group = new FormGroup({}); - if (field.partitioning === 'language') { + if (field.isLocalizable) { for (let language of this.languages) { - group.addControl(language.iso2Code, new FormControl(undefined, field.createValidators(language.isOptional))); + group.setControl(language.iso2Code, new FormControl(undefined, field.createValidators(language.isOptional))); } } else { - group.addControl('iv', new FormControl(undefined, field.createValidators(false))); + group.setControl('iv', new FormControl(undefined, field.createValidators(false))); } controls[field.name] = group; @@ -249,7 +254,8 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy, this.contentForm = new FormGroup(controls); } - private populateContentForm() { + private reloadContentForm(content: ContentDto) { + this.content = content; this.contentForm.markAsPristine(); this.isNewMode = !this.content; diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.html b/src/Squidex/app/features/content/pages/contents/contents-page.component.html index f00ddc40a..a52a910c7 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.html +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.html @@ -124,7 +124,7 @@ + (deleting)="deleteContent(content)" + (saved)="onContentSaved(content, $event)"> @@ -141,7 +142,7 @@ 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 689d817ec..217265983 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 @@ -27,7 +27,8 @@ import { ImmutableArray, ModalView, Pager, - SchemaDetailsDto + SchemaDetailsDto, + Versioned } from 'shared'; @Component({ @@ -203,7 +204,9 @@ export class ContentsPageComponent implements OnDestroy, OnInit { DateTime.parseISO_UTC(dueTime) : null; - this.contentItems = this.contentItems.replaceBy('id', content.changeStatus(status, dt, this.ctx.userToken, dto.version)); + content = content.changeStatus(status, dt, this.ctx.userToken, dto.version); + + this.contentItems = this.contentItems.replaceBy('id', content); this.emitContentStatusChanged(content); } @@ -240,6 +243,14 @@ export class ContentsPageComponent implements OnDestroy, OnInit { }); } + public onContentSaved(content: ContentDto, update: Versioned) { + content = content.update(update.payload, this.ctx.userToken, update.version); + + this.contentItems = this.contentItems.replaceBy('id', content); + + this.emitContentUpdated(content); + } + public load(showInfo = false) { this.contentsService.getContents(this.ctx.appName, this.schema.name, this.contentsPager.pageSize, this.contentsPager.skip, this.contentsQuery, undefined, this.isArchive) .finally(() => { @@ -342,6 +353,10 @@ export class ContentsPageComponent implements OnDestroy, OnInit { 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)); } diff --git a/src/Squidex/app/features/content/shared/content-item.component.html b/src/Squidex/app/features/content/shared/content-item.component.html index 051ed7162..c19c1d417 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.html +++ b/src/Squidex/app/features/content/shared/content-item.component.html @@ -1,15 +1,61 @@ - - + - - - {{value}} - + + +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+ {{values[i]}} +
- + @@ -28,10 +74,23 @@ {{content.lastModified | sqxFromNow}} - + + + + + + + + + - + +
- + diff --git a/src/Squidex/app/features/content/shared/content-item.component.scss b/src/Squidex/app/features/content/shared/content-item.component.scss index d87c980ec..a9239ad91 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.scss +++ b/src/Squidex/app/features/content/shared/content-item.component.scss @@ -1,6 +1,10 @@ @import '_vars'; @import '_mixins'; +.truncate { + @include truncate; +} + .content-status { & { vertical-align: middle; diff --git a/src/Squidex/app/features/content/shared/content-item.component.ts b/src/Squidex/app/features/content/shared/content-item.component.ts index d5f667beb..a2fb8aea0 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.ts +++ b/src/Squidex/app/features/content/shared/content-item.component.ts @@ -5,15 +5,20 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; import { AppContext, + AppLanguageDto, ContentDto, + ContentsService, fadeAnimation, FieldDto, ModalView, - SchemaDto + SchemaDto, + Types, + Versioned } from 'shared'; /* tslint:disable:component-selector */ @@ -27,8 +32,7 @@ import { ], animations: [ fadeAnimation - ], - changeDetection: ChangeDetectionStrategy.OnPush + ] }) export class ContentItemComponent implements OnInit, OnChanges { @Output() @@ -46,6 +50,9 @@ export class ContentItemComponent implements OnInit, OnChanges { @Output() public deleting = new EventEmitter(); + @Output() + public saved = new EventEmitter>(); + @Output() public selectedChange = new EventEmitter(); @@ -53,10 +60,7 @@ export class ContentItemComponent implements OnInit, OnChanges { public selected = false; @Input() - public columnWidth: number; - - @Input() - public languageCode: string; + public language: AppLanguageDto; @Input() public schemaFields: FieldDto[]; @@ -73,11 +77,15 @@ export class ContentItemComponent implements OnInit, OnChanges { @Input('sqxContent') public content: ContentDto; + public formSubmitted = false; + public form: FormGroup = new FormGroup({}); + public dropdown = new ModalView(false, true); public values: any[] = []; - constructor(public readonly ctx: AppContext + constructor(public readonly ctx: AppContext, + private readonly contentsService: ContentsService ) { } @@ -86,31 +94,96 @@ export class ContentItemComponent implements OnInit, OnChanges { } public ngOnInit() { + for (let field of this.schemaFields) { + if (field.properties['inlineEditable']) { + this.form.setControl(field.name, new FormControl(undefined, field.createValidators(this.language.isOptional))); + } + } + this.updateValues(); } + public shouldStop(event: Event) { + if (this.form.dirty) { + event.stopPropagation(); + event.stopImmediatePropagation(); + } + } + + public save() { + this.formSubmitted = true; + + if (this.form.dirty && this.form.valid) { + this.form.disable(); + + const request = {}; + + for (let field of this.schemaFields) { + if (field.properties['inlineEditable']) { + const value = this.form.controls[field.name].value; + + if (field.partitioning === 'invariant') { + request[field.name] = { iv: value }; + } else { + request[field.name] = { [this.language.iso2Code]: value }; + } + } + } + + this.contentsService.patchContent(this.ctx.appName, this.schema.name, this.content.id, request, this.content.version) + .finally(() => { + this.form.enable(); + }) + .subscribe(dto => { + this.form.markAsPristine(); + + this.emitSaved(dto); + }, error => { + this.ctx.notifyError(error); + }); + } + } + + private emitSaved(data: Versioned) { + this.saved.emit(data); + } + private updateValues() { this.values = []; if (this.schemaFields) { for (let field of this.schemaFields) { - this.values.push(this.getValue(field)); + const value = this.getRawValue(field); + + if (Types.isUndefined(value)) { + this.values.push(''); + } else { + this.values.push(field.formatValue(value)); + } + + if (this.form) { + const formControl = this.form.controls[field.name]; + + if (formControl) { + formControl.setValue(value); + } + } } } } - private getValue(field: FieldDto): any { + private getRawValue(field: FieldDto): any { const contentField = this.content.data[field.name]; if (contentField) { if (field.partitioning === 'language') { - return field.formatValue(contentField[this.languageCode]); + return contentField[this.language.iso2Code]; } else { - return field.formatValue(contentField['iv']); + return contentField['iv']; } - } else { - return ''; } + + return undefined; } } diff --git a/src/Squidex/app/features/content/shared/references-editor.component.html b/src/Squidex/app/features/content/shared/references-editor.component.html index 7e2a68453..07147915e 100644 --- a/src/Squidex/app/features/content/shared/references-editor.component.html +++ b/src/Squidex/app/features/content/shared/references-editor.component.html @@ -13,7 +13,7 @@ +
{{message}} diff --git a/src/Squidex/app/framework/angular/control-errors.component.ts b/src/Squidex/app/framework/angular/control-errors.component.ts index 9f1f9c972..d37f51b3f 100644 --- a/src/Squidex/app/framework/angular/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/control-errors.component.ts @@ -5,8 +5,9 @@ * Copyright (c) Sebastian Stehle. All rights r vbeserved */ -import { ChangeDetectionStrategy, Component, Host, Input, OnChanges, Optional } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Host, Input, OnChanges, OnDestroy, Optional } from '@angular/core'; import { AbstractControl, FormGroupDirective } from '@angular/forms'; +import { Observable, Subscription } from 'rxjs'; import { fadeAnimation } from './animations'; @@ -33,9 +34,11 @@ const DEFAULT_ERRORS: { [key: string]: string } = { ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ControlErrorsComponent implements OnChanges { +export class ControlErrorsComponent implements OnChanges, OnDestroy { private displayFieldName: string; private control: AbstractControl; + private controlSubscription: Subscription | null = null; + private originalMarkAsTouched: any; @Input() public for: string; @@ -52,17 +55,74 @@ export class ControlErrorsComponent implements OnChanges { @Input() public submitOnly = false; - public get errorMessages(): string[] | null { - if (!this.control) { - return null; + public errorMessages: string[] = []; + + constructor( + @Optional() @Host() private readonly formGroupDirective: FormGroupDirective, + private readonly changeDetector: ChangeDetectorRef + ) { + if (!this.formGroupDirective) { + throw new Error('control-errors must be used with a parent formGroup directive'); } + } - if (this.control.invalid && ((this.control.touched && !this.submitOnly) || this.submitted) && this.control.errors) { - const errors: string[] = []; + public ngOnDestroy() { + this.unsubscribe(); + } + + public ngOnChanges() { + if (this.fieldName) { + this.displayFieldName = this.fieldName; + } else if (this.for) { + this.displayFieldName = this.for.substr(0, 1).toUpperCase() + this.for.substr(1); + } + + const control = this.formGroupDirective.form.controls[this.for]; + + if (this.control !== control) { + this.unsubscribe(); + + this.control = control; + + if (control) { + const self = this; + + this.controlSubscription = + Observable.merge(control.valueChanges, control.statusChanges) + .subscribe(() => { + this.createMessages(); + }); + + this.originalMarkAsTouched = this.control.markAsTouched; + + this.control['markAsTouched'] = function () { + self.originalMarkAsTouched.apply(this, arguments); + + self.createMessages(); + }; + } + } + this.createMessages(); + } + + private unsubscribe() { + if (this.controlSubscription) { + this.controlSubscription.unsubscribe(); + } + + if (this.control && this.originalMarkAsTouched) { + this.control['markAsTouched'] = this.originalMarkAsTouched; + } + } + + private createMessages() { + const errors: string[] = []; + + if (this.control.invalid && ((this.control.touched && !this.submitOnly) || this.submitted) && this.control.errors) { for (let key in this.control.errors) { if (this.control.errors.hasOwnProperty(key)) { - let message = (this.errors ? this.errors[key] : null) || DEFAULT_ERRORS[key]; + let message = (this.errors ? this.errors[key] : null) || DEFAULT_ERRORS[key.toLowerCase()]; if (!message) { continue; @@ -81,28 +141,10 @@ export class ControlErrorsComponent implements OnChanges { errors.push(message); } } - - return errors.length > 0 ? errors : null; } - return null; - } - - constructor( - @Optional() @Host() private readonly formGroupDirective: FormGroupDirective - ) { - if (!this.formGroupDirective) { - throw new Error('control-errors must be used with a parent formGroup directive'); - } - } - - public ngOnChanges() { - if (this.fieldName) { - this.displayFieldName = this.fieldName; - } else if (this.for) { - this.displayFieldName = this.for.substr(0, 1).toUpperCase() + this.for.substr(1); - } + this.errorMessages = errors; - this.control = this.formGroupDirective.form.controls[this.for]; + this.changeDetector.detectChanges(); } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/http-extensions-impl.ts b/src/Squidex/app/framework/angular/http-extensions-impl.ts index 3b96117ec..f99f383b7 100644 --- a/src/Squidex/app/framework/angular/http-extensions-impl.ts +++ b/src/Squidex/app/framework/angular/http-extensions-impl.ts @@ -87,6 +87,12 @@ export module HTTP { return handleVersion(http.put(url, body, { observe: 'response', headers }), version); } + export function patchVersioned(http: HttpClient, url: string, body: any, version?: Version): Observable>> { + const headers = createHeaders(version); + + return handleVersion(http.request('PATCH', url, { body, observe: 'response', headers }), version); + } + export function deleteVersioned(http: HttpClient, url: string, version?: Version): Observable>> { const headers = createHeaders(version); diff --git a/src/Squidex/app/framework/angular/modal-view.directive.ts b/src/Squidex/app/framework/angular/modal-view.directive.ts index a8df8263b..fea01ae0b 100644 --- a/src/Squidex/app/framework/angular/modal-view.directive.ts +++ b/src/Squidex/app/framework/angular/modal-view.directive.ts @@ -21,7 +21,7 @@ export class ModalViewDirective implements OnChanges, OnDestroy { private renderedView: EmbeddedViewRef | null = null; @Input('sqxModalView') - public modalView: ModalView; + public modalView: ModalView | any; @Input('sqxModalViewOnRoot') public placeOnRoot = false; @@ -40,7 +40,9 @@ export class ModalViewDirective implements OnChanges, OnDestroy { public ngOnDestroy() { this.stopListening(); - this.modalView.hide(); + if (this.modalView instanceof ModalView) { + this.modalView.hide(); + } } public ngOnChanges(changes: SimpleChanges) { @@ -53,36 +55,42 @@ export class ModalViewDirective implements OnChanges, OnDestroy { this.subscription = null; } - if (this.modalView) { + if (this.modalView instanceof ModalView) { this.subscription = this.modalView.isOpen.subscribe(isOpen => { - if (isOpen === (this.renderedView !== null)) { - return; - } + this.update(isOpen); + }); + } else { + this.update(!!this.modalView); + } + } - if (isOpen && !this.renderedView) { - if (this.placeOnRoot) { - this.renderedView = this.rootContainer.createEmbeddedView(this.templateRef); - } else { - this.renderedView = this.viewContainer.createEmbeddedView(this.templateRef); - } - this.renderer.setElementStyle(this.renderedView.rootNodes[0], 'display', 'block'); - - setTimeout(() => { - this.startListening(); - }); - } else if (!isOpen && this.renderedView) { - this.renderedView = null; - - if (this.placeOnRoot) { - this.rootContainer.clear(); - } else { - this.viewContainer.clear(); - } + private update(isOpen: boolean) { + if (isOpen === (this.renderedView !== null)) { + return; + } - this.stopListening(); - } + if (isOpen && !this.renderedView) { + if (this.placeOnRoot) { + this.renderedView = this.rootContainer.createEmbeddedView(this.templateRef); + } else { + this.renderedView = this.viewContainer.createEmbeddedView(this.templateRef); + } + this.renderer.setElementStyle(this.renderedView.rootNodes[0], 'display', 'block'); + + setTimeout(() => { + this.startListening(); }); + } else if (!isOpen && this.renderedView) { + this.renderedView = null; + + if (this.placeOnRoot) { + this.rootContainer.clear(); + } else { + this.viewContainer.clear(); + } + + this.stopListening(); } } diff --git a/src/Squidex/app/framework/utils/string-helper.ts b/src/Squidex/app/framework/utils/string-helper.ts index 06cd2cee8..778da4c2a 100644 --- a/src/Squidex/app/framework/utils/string-helper.ts +++ b/src/Squidex/app/framework/utils/string-helper.ts @@ -6,7 +6,7 @@ */ export module StringHelper { - export function firstNonEmpty(...values: string[]) { + export function firstNonEmpty(...values: (string | undefined | null)[]) { for (let value of values) { if (value) { value = value.trim(); diff --git a/src/Squidex/app/shared/components/language-selector.component.html b/src/Squidex/app/shared/components/language-selector.component.html index 880e57172..466bc2802 100644 --- a/src/Squidex/app/shared/components/language-selector.component.html +++ b/src/Squidex/app/shared/components/language-selector.component.html @@ -1,11 +1,11 @@
-