From 5fa176d941d5fbd7549704ce0b2baa7d5b0270b9 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 4 Feb 2019 18:30:29 +0100 Subject: [PATCH] Started with diff view. --- .../event-consumers-page.component.ts | 2 +- .../pages/restore/restore-page.component.ts | 2 +- .../pages/users/user-page.component.ts | 2 +- .../pages/graphql/graphql-page.component.html | 2 +- .../assets/pages/assets-page.component.html | 2 +- .../app/features/content/declarations.ts | 1 + src/Squidex/app/features/content/module.ts | 2 + .../content/content-field.component.html | 113 ++++-- .../content/content-field.component.scss | 37 +- .../pages/content/content-field.component.ts | 55 ++- .../pages/content/content-page.component.html | 3 +- .../pages/content/content-page.component.ts | 32 +- .../content/field-languages.component.ts | 55 +++ .../contents-filters-page.component.ts | 2 +- .../contents/contents-page.component.html | 2 +- .../pages/contents/contents-page.component.ts | 6 +- .../shared/assets-editor.component.scss | 3 +- .../shared/field-editor.component.html | 2 +- .../pages/dashboard-page.component.ts | 10 +- .../pages/schema/schema-page.component.ts | 5 +- .../pages/schema/types/number-ui.component.ts | 4 +- .../pages/schema/types/string-ui.component.ts | 4 +- .../types/string-validation.component.ts | 2 +- .../pages/schemas/schemas-page.component.ts | 4 +- .../pages/backups/backups-page.component.ts | 2 +- .../languages/languages-page.component.ts | 2 +- .../angular/ignore-scrollbar.directive.ts | 4 +- .../angular/image-source.directive.ts | 2 +- .../angular/modals/modal-dialog.component.ts | 2 +- .../angular/modals/modal-target.directive.ts | 6 +- .../angular/modals/modal-view.directive.ts | 37 +- .../app/framework/angular/panel.component.ts | 4 + .../angular/routers/parent-link.directive.ts | 2 +- .../framework/angular/stateful.component.ts | 4 +- .../angular/user-report.component.ts | 2 +- .../app/framework/utils/version.spec.ts | 7 + src/Squidex/app/framework/utils/version.ts | 12 + .../shared/components/comments.component.ts | 2 +- .../shared/components/permission.directive.ts | 4 +- .../app/shared/services/contents.service.ts | 4 +- .../app/shared/state/contents.state.ts | 3 +- .../pages/internal/internal-area.component.ts | 2 +- .../app/theme/icomoon/demo-files/demo.css | 4 +- src/Squidex/app/theme/icomoon/demo.html | 374 +++++++++--------- .../app/theme/icomoon/fonts/icomoon.eot | Bin 28088 -> 28144 bytes .../app/theme/icomoon/fonts/icomoon.svg | 1 + .../app/theme/icomoon/fonts/icomoon.ttf | Bin 27924 -> 27980 bytes .../app/theme/icomoon/fonts/icomoon.woff | Bin 28000 -> 28056 bytes src/Squidex/app/theme/icomoon/selection.json | 2 +- src/Squidex/app/theme/icomoon/style.css | 67 ++-- 50 files changed, 563 insertions(+), 338 deletions(-) create mode 100644 src/Squidex/app/features/content/pages/content/field-languages.component.ts diff --git a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts index ffde74647..2cae59e9e 100644 --- a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts +++ b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts @@ -32,7 +32,7 @@ export class EventConsumersPageComponent extends ResourceOwner implements OnInit public ngOnInit() { this.eventConsumersState.load().pipe(onErrorResumeNext()).subscribe(); - this.takeOver(timer(2000, 2000).pipe(switchMap(() => this.eventConsumersState.load(true, true)))); + this.own(timer(2000, 2000).pipe(switchMap(() => this.eventConsumersState.load(true, true)))); } public reload() { diff --git a/src/Squidex/app/features/administration/pages/restore/restore-page.component.ts b/src/Squidex/app/features/administration/pages/restore/restore-page.component.ts index 27c25e171..c0f6304ab 100644 --- a/src/Squidex/app/features/administration/pages/restore/restore-page.component.ts +++ b/src/Squidex/app/features/administration/pages/restore/restore-page.component.ts @@ -38,7 +38,7 @@ export class RestorePageComponent extends ResourceOwner implements OnInit { } public ngOnInit() { - this.takeOver( + this.own( timer(0, 2000).pipe(switchMap(() => this.backupsService.getRestore().pipe(onErrorResumeNext()))) .subscribe(job => { if (job) { diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.ts b/src/Squidex/app/features/administration/pages/users/user-page.component.ts index 59750b200..e009b6115 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.ts @@ -35,7 +35,7 @@ export class UserPageComponent extends ResourceOwner implements OnInit { } public ngOnInit() { - this.takeOver( + this.own( this.usersState.selectedUser .subscribe(selectedUser => { this.user = selectedUser!; diff --git a/src/Squidex/app/features/api/pages/graphql/graphql-page.component.html b/src/Squidex/app/features/api/pages/graphql/graphql-page.component.html index 74f0d3054..d762911d5 100644 --- a/src/Squidex/app/features/api/pages/graphql/graphql-page.component.html +++ b/src/Squidex/app/features/api/pages/graphql/graphql-page.component.html @@ -1,5 +1,5 @@ - +
\ No newline at end of file diff --git a/src/Squidex/app/features/assets/pages/assets-page.component.html b/src/Squidex/app/features/assets/pages/assets-page.component.html index 36e9e5e3d..675175803 100644 --- a/src/Squidex/app/features/assets/pages/assets-page.component.html +++ b/src/Squidex/app/features/assets/pages/assets-page.component.html @@ -1,6 +1,6 @@ - + Assets diff --git a/src/Squidex/app/features/content/declarations.ts b/src/Squidex/app/features/content/declarations.ts index f94dfd517..2ebc03da9 100644 --- a/src/Squidex/app/features/content/declarations.ts +++ b/src/Squidex/app/features/content/declarations.ts @@ -9,6 +9,7 @@ export * from './pages/comments/comments-page.component'; export * from './pages/content/content-field.component'; export * from './pages/content/content-history-page.component'; export * from './pages/content/content-page.component'; +export * from './pages/content/field-languages.component'; export * from './pages/contents/contents-filters-page.component'; export * from './pages/contents/contents-page.component'; export * from './pages/schemas/schemas-page.component'; diff --git a/src/Squidex/app/features/content/module.ts b/src/Squidex/app/features/content/module.ts index 1f9af170a..a2f9b17b6 100644 --- a/src/Squidex/app/features/content/module.ts +++ b/src/Squidex/app/features/content/module.ts @@ -36,6 +36,7 @@ import { ContentStatusComponent, DueTimeSelectorComponent, FieldEditorComponent, + FieldLanguagesComponent, PreviewButtonComponent, ReferencesEditorComponent, SchemasPageComponent @@ -119,6 +120,7 @@ const routes: Routes = [ ContentsSelectorComponent, DueTimeSelectorComponent, FieldEditorComponent, + FieldLanguagesComponent, PreviewButtonComponent, ReferencesEditorComponent, SchemasPageComponent 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 2dd8dd959..2d4ea03b5 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 @@ -1,42 +1,79 @@ -
-
- - - - - - - - Please remember to check all languages when you see validation errors. - - +
+
+
+
+ + +
+ + +
+ + +
+
+ + + + + +
- -
- - -
-
+
+ - - - - +
+
+ + +
+ + +
+ + +
+
+ + + + + +
+
diff --git a/src/Squidex/app/features/content/pages/content/content-field.component.scss b/src/Squidex/app/features/content/pages/content/content-field.component.scss index 9c13f51a7..821dd5465 100644 --- a/src/Squidex/app/features/content/pages/content/content-field.component.scss +++ b/src/Squidex/app/features/content/pages/content/content-field.component.scss @@ -11,8 +11,26 @@ @include absolute(.7rem, 1.25rem, auto, auto); } -.invalid { - border-left-color: $color-theme-error; +.row { + margin-left: -1.5rem; + margin-right: -1.5rem; +} + +.col-12 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.col-6 { + & { + padding-left: 1.5rem; + padding-right: .5rem; + } + + &.col-right { + padding-left: .5rem; + padding-right: 1.5rem; + } } .field { @@ -20,9 +38,24 @@ color: $color-theme-error; } + &-invalid { + border-left-color: $color-theme-error; + } + &-disabled { color: $color-border-dark; font-size: .8rem; font-weight: normal; } + + &-copy { + @include absolute(1rem, auto, auto, -1rem); + z-index: 1000; + } +} + +.compare { + padding: .5rem 0; + border: 0; + border-bottom: 1px solid $color-border; } \ No newline at end of file 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 cf176d979..955e75ba2 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 @@ -36,6 +36,9 @@ export class ContentFieldComponent implements DoCheck, OnChanges { @Input() public fieldForm: FormGroup; + @Input() + public fieldFormCompare?: FormGroup; + @Input() public schema: SchemaDto; @@ -49,6 +52,8 @@ export class ContentFieldComponent implements DoCheck, OnChanges { public languageChange = new EventEmitter(); public selectedFormControl: AbstractControl; + public selectedFormControlCompare?: AbstractControl; + public showAllControls = false; public isInvalid: Observable; @@ -68,20 +73,24 @@ export class ContentFieldComponent implements DoCheck, OnChanges { } } - public toggleShowAll() { - this.showAllControls = !this.showAllControls; + public changeShowAllControls(value: boolean) { + this.showAllControls = value; this.localStore.setBoolean(this.configKey(), this.showAllControls); } - public ngDoCheck() { - let control: AbstractControl; - - if (this.field.isLocalizable) { - control = this.fieldForm.controls[this.language.iso2Code]; - } else { - control = this.fieldForm.controls[fieldInvariant]; + public copy() { + if (this.selectedFormControlCompare && this.fieldFormCompare) { + if (this.showAllControls) { + this.fieldForm.setValue(this.fieldFormCompare.value); + } else { + this.selectedFormControl.setValue(this.selectedFormControlCompare.value); + } } + } + + public ngDoCheck() { + const control = this.findControl(this.fieldForm); if (this.selectedFormControl !== control) { if (this.selectedFormControl && Types.isFunction(this.selectedFormControl['_clearChangeFns'])) { @@ -90,6 +99,34 @@ export class ContentFieldComponent implements DoCheck, OnChanges { this.selectedFormControl = control; } + + if (this.fieldFormCompare) { + const controlCompare = this.findControl(this.fieldFormCompare); + + if (this.selectedFormControlCompare !== controlCompare) { + if (this.selectedFormControlCompare && Types.isFunction(this.selectedFormControlCompare['_clearChangeFns'])) { + this.selectedFormControlCompare['_clearChangeFns'](); + } + + this.selectedFormControlCompare = controlCompare; + } + } + } + + private findControl(form: FormGroup) { + if (this.field.isLocalizable) { + return form.controls[this.language.iso2Code]; + } else { + return form.controls[fieldInvariant]; + } + } + + public prefix(language: AppLanguageDto) { + return `(${language.iso2Code}`; + } + + public trackByLanguage(index: number, language: AppLanguageDto) { + return language.iso2Code; } private configKey() { diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.html b/src/Squidex/app/features/content/pages/content/content-page.component.html index bef584f93..f92e5b7ac 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.html +++ b/src/Squidex/app/features/content/pages/content/content-page.component.html @@ -1,7 +1,7 @@
- + @@ -116,6 +116,7 @@ [form]="contentForm" [field]="field" [fieldForm]="contentForm.form.get(field.name)" + [fieldFormCompare]="contentFormCompare?.form.get(field.name)" [schema]="schema" [languages]="languages" [(language)]="language"> 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 6508507e2..2e3d726ca 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 @@ -47,6 +47,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD public content: ContentDto; public contentVersion: Version | null; public contentForm: EditContentForm; + public contentFormCompare: EditContentForm | null = null; public dropdown = new ModalModel(); @@ -70,14 +71,14 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD } public ngOnInit() { - this.takeOver( + this.own( this.languagesState.languages .subscribe(languages => { this.languages = languages.map(x => x.language); this.language = this.languages.at(0); })); - this.takeOver( + this.own( this.schemasState.selectedSchema .subscribe(schema => { if (schema) { @@ -87,7 +88,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD } })); - this.takeOver( + this.own( this.contentsState.selectedContent .subscribe(content => { if (content) { @@ -97,7 +98,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD } })); - this.takeOver( + this.own( this.messageBus.of(ContentVersionSelected) .subscribe(message => { this.loadVersion(message.version); @@ -208,26 +209,25 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD .subscribe(); } - private loadVersion(version: Version) { - if (this.content) { + private loadVersion(version: Version | null) { + if (!this.content || version === null || version.eq(this.content.version)) { + this.contentFormCompare = null; + this.contentVersion = null; + } else { this.contentsState.loadVersion(this.content, version) .subscribe(dto => { - if (this.content.version.value !== version.value) { - this.contentVersion = version; - } else { - this.contentVersion = null; + if (this.contentFormCompare === null) { + this.contentFormCompare = new EditContentForm(this.schema, this.languages); + this.contentFormCompare.form.disable(); } - this.loadContent(dto); + this.contentFormCompare.load(dto.payload); + this.contentVersion = version; }); } } public showLatest() { - if (this.contentVersion) { - this.contentVersion = null; - - this.loadContent(this.content.dataDraft); - } + this.loadVersion(null); } } \ No newline at end of file diff --git a/src/Squidex/app/features/content/pages/content/field-languages.component.ts b/src/Squidex/app/features/content/pages/content/field-languages.component.ts new file mode 100644 index 000000000..47246437f --- /dev/null +++ b/src/Squidex/app/features/content/pages/content/field-languages.component.ts @@ -0,0 +1,55 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +import { + AppLanguageDto, + ImmutableArray, + RootFieldDto +} from '@app/shared'; + +@Component({ + selector: 'sqx-field-languages', + template: ` + + + + + + + + + Please remember to check all languages when you see validation errors. + + + ` +}) +export class FieldLanguagesComponent { + @Input() + public field: RootFieldDto; + + @Input() + public showAllControls: boolean; + + @Input() + public language: AppLanguageDto; + + @Input() + public languages: ImmutableArray; + + @Output() + public languageChange = new EventEmitter(); + + @Output() + public showAllControlsChange = new EventEmitter(); +} \ No newline at end of file diff --git a/src/Squidex/app/features/content/pages/contents/contents-filters-page.component.ts b/src/Squidex/app/features/content/pages/contents/contents-filters-page.component.ts index 26f45801d..3b4756846 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-filters-page.component.ts +++ b/src/Squidex/app/features/content/pages/contents/contents-filters-page.component.ts @@ -33,7 +33,7 @@ export class ContentsFiltersPageComponent extends ResourceOwner implements OnIni } public ngOnInit() { - this.takeOver( + this.own( this.schemasState.selectedSchema .subscribe(schema => { if (schema) { 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 7d7e8067d..87950b60a 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 @@ -1,6 +1,6 @@ - + Archive 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 3bbca2169..622a182a5 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 @@ -61,7 +61,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit { } public ngOnInit() { - this.takeOver( + this.own( this.schemasState.selectedSchema .subscribe(schema => { this.resetSelection(); @@ -72,13 +72,13 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit { this.contentsState.init().pipe(onErrorResumeNext()).subscribe(); })); - this.takeOver( + this.own( this.contentsState.contents .subscribe(() => { this.updateSelectionSummary(); })); - this.takeOver( + this.own( this.languagesState.languages .subscribe(languages => { this.languages = languages.map(x => x.language); diff --git a/src/Squidex/app/features/content/shared/assets-editor.component.scss b/src/Squidex/app/features/content/shared/assets-editor.component.scss index 498a0bcc2..c3cdd384a 100644 --- a/src/Squidex/app/features/content/shared/assets-editor.component.scss +++ b/src/Squidex/app/features/content/shared/assets-editor.component.scss @@ -44,8 +44,7 @@ & { @include transition(border-color .4s ease); @include border-radius; - @include flex-box; - @include truncate; + @include truncate-nowidth; border: 2px dashed darken($color-border, 10%); font-weight: normal; text-align: center; diff --git a/src/Squidex/app/features/content/shared/field-editor.component.html b/src/Squidex/app/features/content/shared/field-editor.component.html index 5bcf53a3c..a50fe912e 100644 --- a/src/Squidex/app/features/content/shared/field-editor.component.html +++ b/src/Squidex/app/features/content/shared/field-editor.component.html @@ -5,7 +5,7 @@ Disabled - +
diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts index 020574acb..031d5d1b7 100644 --- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts +++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts @@ -106,7 +106,7 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit { } public ngOnInit() { - this.takeOver( + this.own( this.app.pipe( switchMap(app => this.usagesService.getTodayStorage(app.name))) .subscribe(dto => { @@ -114,7 +114,7 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit { this.assetsMax = dto.maxAllowed; })); - this.takeOver( + this.own( this.app.pipe( switchMap(app => this.usagesService.getMonthCalls(app.name))) .subscribe(dto => { @@ -122,14 +122,14 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit { this.callsMax = dto.maxAllowed; })); - this.takeOver( + this.own( this.app.pipe( switchMap(app => this.historyService.getHistory(app.name, ''))) .subscribe(dto => { this.history = dto; })); - this.takeOver( + this.own( this.app.pipe( switchMap(app => this.usagesService.getStorageUsages(app.name, DateTime.today().addDays(-20), DateTime.today()))) .subscribe(dtos => { @@ -166,7 +166,7 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit { }; })); - this.takeOver( + this.own( this.app.pipe( switchMap(app => this.usagesService.getCallsUsages(app.name, DateTime.today().addDays(-20), DateTime.today()))) .subscribe(dtos => { diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts index ba66a2d37..3d09b375e 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts @@ -68,7 +68,7 @@ export class SchemaPageComponent extends ResourceOwner implements OnDestroy, OnI public ngOnInit() { this.patternsState.load().pipe(onErrorResumeNext()).subscribe(); - this.takeOver( + this.own( this.schemasState.selectedSchema .subscribe(schema => { if (schema) { @@ -92,8 +92,7 @@ export class SchemaPageComponent extends ResourceOwner implements OnDestroy, OnI } public trackByField(index: number, field: FieldDto) { - const a = this; - return field.fieldId + a.schema.id; + return field.fieldId + this.schema.id; } public deleteSchema() { diff --git a/src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts index 306edee1c..88c7949c7 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/number-ui.component.ts @@ -57,14 +57,14 @@ export class NumberUIComponent extends ResourceOwner implements OnInit { this.editForm.controls['editor'].valueChanges.pipe( startWith(this.properties.editor), map(x => !(x && (x === 'Input' || x === 'Dropdown')))); - this.takeOver( + this.own( this.hideAllowedValues.subscribe(isSelection => { if (isSelection) { this.editForm.controls['allowedValues'].setValue(undefined); } })); - this.takeOver( + this.own( this.hideInlineEditable.subscribe(isSelection => { if (isSelection) { this.editForm.controls['inlineEditable'].setValue(false); diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts index 8e1ad06dd..a731e095e 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-ui.component.ts @@ -54,14 +54,14 @@ export class StringUIComponent extends ResourceOwner implements OnInit { this.editForm.controls['editor'].valueChanges.pipe( startWith(this.properties.editor), map(x => !(x && (x === 'Input' || x === 'Dropdown' || x === 'Slug')))); - this.takeOver( + this.own( this.hideAllowedValues.subscribe(isSelection => { if (isSelection) { this.editForm.controls['allowedValues'].setValue(undefined); } })); - this.takeOver( + this.own( this.hideInlineEditable.subscribe(isSelection => { if (isSelection) { this.editForm.controls['inlineEditable'].setValue(false); diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts index ed7cd48e4..4cfd545e8 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts @@ -82,7 +82,7 @@ export class StringValidationComponent extends ResourceOwner implements OnDestro this.showPatternMessage = this.editForm.controls['pattern'].value && this.editForm.controls['pattern'].value.trim().length > 0; - this.takeOver( + this.own( this.editForm.controls['pattern'].valueChanges .subscribe((value: string) => { if (!value || value.length === 0) { diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts index 62bc3ec20..cf0ac2093 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts @@ -47,7 +47,7 @@ export class SchemasPageComponent extends ResourceOwner implements OnInit { } public ngOnInit() { - this.takeOver( + this.own( this.messageBus.of(SchemaCloning) .subscribe(m => { this.import = m.schema; @@ -55,7 +55,7 @@ export class SchemasPageComponent extends ResourceOwner implements OnInit { this.addSchemaDialog.show(); })); - this.takeOver( + this.own( this.route.params.pipe(map(q => q['showDialog'])) .subscribe(showDialog => { if (showDialog) { diff --git a/src/Squidex/app/features/settings/pages/backups/backups-page.component.ts b/src/Squidex/app/features/settings/pages/backups/backups-page.component.ts index 3ae10a231..a4474ce19 100644 --- a/src/Squidex/app/features/settings/pages/backups/backups-page.component.ts +++ b/src/Squidex/app/features/settings/pages/backups/backups-page.component.ts @@ -32,7 +32,7 @@ export class BackupsPageComponent extends ResourceOwner implements OnInit { public ngOnInit() { this.backupsState.load().pipe(onErrorResumeNext()).subscribe(); - this.takeOver( + this.own( timer(3000, 3000).pipe(switchMap(() => this.backupsState.load(true, true).pipe(onErrorResumeNext()))) .subscribe()); } diff --git a/src/Squidex/app/features/settings/pages/languages/languages-page.component.ts b/src/Squidex/app/features/settings/pages/languages/languages-page.component.ts index a56c5b648..31634d509 100644 --- a/src/Squidex/app/features/settings/pages/languages/languages-page.component.ts +++ b/src/Squidex/app/features/settings/pages/languages/languages-page.component.ts @@ -34,7 +34,7 @@ export class LanguagesPageComponent extends ResourceOwner implements OnInit { } public ngOnInit() { - this.takeOver( + this.own( this.languagesState.newLanguages .subscribe(languages => { if (languages.length > 0) { diff --git a/src/Squidex/app/framework/angular/ignore-scrollbar.directive.ts b/src/Squidex/app/framework/angular/ignore-scrollbar.directive.ts index 8de848e47..c9d1ec641 100644 --- a/src/Squidex/app/framework/angular/ignore-scrollbar.directive.ts +++ b/src/Squidex/app/framework/angular/ignore-scrollbar.directive.ts @@ -29,12 +29,12 @@ export class IgnoreScrollbarDirective extends ResourceOwner implements OnInit, A this.parent = this.renderer.parentNode(this.element.nativeElement); } - this.takeOver( + this.own( this.renderer.listen(this.element.nativeElement, 'resize', () => { this.reposition(); })); - this.takeOver(timer(100, 100).subscribe(() => this.reposition)); + this.own(timer(100, 100).subscribe(() => this.reposition)); } public ngAfterViewInit() { diff --git a/src/Squidex/app/framework/angular/image-source.directive.ts b/src/Squidex/app/framework/angular/image-source.directive.ts index 7b45bfd4f..ad4022b12 100644 --- a/src/Squidex/app/framework/angular/image-source.directive.ts +++ b/src/Squidex/app/framework/angular/image-source.directive.ts @@ -50,7 +50,7 @@ export class ImageSourceDirective extends ResourceOwner implements OnChanges, On this.parent = this.renderer.parentNode(this.element.nativeElement); } - this.takeOver( + this.own( this.renderer.listen(this.parent, 'resize', () => { this.resize(); })); diff --git a/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts b/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts index cfa494a2f..e845e9bd2 100644 --- a/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts +++ b/src/Squidex/app/framework/angular/modals/modal-dialog.component.ts @@ -21,7 +21,7 @@ interface State { animations: [ fadeAnimation ], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.Default }) export class ModalDialogComponent extends StatefulComponent implements AfterViewInit { @Input() diff --git a/src/Squidex/app/framework/angular/modals/modal-target.directive.ts b/src/Squidex/app/framework/angular/modals/modal-target.directive.ts index f0d1dc027..ab63bab3f 100644 --- a/src/Squidex/app/framework/angular/modals/modal-target.directive.ts +++ b/src/Squidex/app/framework/angular/modals/modal-target.directive.ts @@ -49,17 +49,17 @@ export class ModalTargetDirective extends ResourceOwner implements AfterViewInit if (this.target) { this.targetElement = this.target; - this.takeOver( + this.own( this.renderer.listen(this.targetElement, 'resize', () => { this.updatePosition(); })); - this.takeOver( + this.own( this.renderer.listen(this.element.nativeElement, 'resize', () => { this.updatePosition(); })); - this.takeOver(timer(100, 100).subscribe(() => this.updatePosition())); + this.own(timer(100, 100).subscribe(() => this.updatePosition())); } } diff --git a/src/Squidex/app/framework/angular/modals/modal-view.directive.ts b/src/Squidex/app/framework/angular/modals/modal-view.directive.ts index 7da0dfbed..16a4834d3 100644 --- a/src/Squidex/app/framework/angular/modals/modal-view.directive.ts +++ b/src/Squidex/app/framework/angular/modals/modal-view.directive.ts @@ -6,19 +6,22 @@ */ import { Directive, EmbeddedViewRef, Input, OnChanges, OnDestroy, Renderer2, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core'; +import { Subscription } from 'rxjs'; import { DialogModel, ModalModel, - ResourceOwner, Types } from '@app/framework/internal'; import { RootViewComponent } from './root-view.component'; + @Directive({ selector: '[sqxModalView]' }) -export class ModalViewDirective extends ResourceOwner implements OnChanges, OnDestroy { +export class ModalViewDirective implements OnChanges, OnDestroy { + private subscription: Subscription | null = null; + private documentClickListener: Function | null = null; private renderedView: EmbeddedViewRef | null = null; @Input('sqxModalView') @@ -39,11 +42,11 @@ export class ModalViewDirective extends ResourceOwner implements OnChanges, OnDe private readonly viewContainer: ViewContainerRef, private readonly rootView: RootViewComponent ) { - super(); } public ngOnDestroy() { - super.ngOnDestroy(); + this.unsubscribeToModal(); + this.unsubscribeToClick(); if (Types.is(this.modalView, DialogModel) || Types.is(this.modalView, ModalModel)) { this.modalView.hide(); @@ -55,13 +58,13 @@ export class ModalViewDirective extends ResourceOwner implements OnChanges, OnDe return; } - super.ngOnDestroy(); + this.unsubscribeToModal(); if (Types.is(this.modalView, DialogModel) || Types.is(this.modalView, ModalModel)) { - this.takeOver( + this.subscription = this.modalView.isOpen.subscribe(isOpen => { this.update(isOpen); - })); + }); } else { this.update(!!this.modalView); } @@ -92,7 +95,7 @@ export class ModalViewDirective extends ResourceOwner implements OnChanges, OnDe this.renderedView = null; - super.ngOnDestroy(); + this.unsubscribeToClick(); } } @@ -105,7 +108,7 @@ export class ModalViewDirective extends ResourceOwner implements OnChanges, OnDe return; } - this.takeOver( + this.documentClickListener = this.renderer.listen('document', 'click', (event: MouseEvent) => { if (!event.target || this.renderedView === null) { return; @@ -133,6 +136,20 @@ export class ModalViewDirective extends ResourceOwner implements OnChanges, OnDe return; } } - })); + }); + } + + private unsubscribeToModal() { + if (this.subscription) { + this.subscription.unsubscribe(); + this.subscription = null; + } + } + + private unsubscribeToClick() { + if (this.documentClickListener) { + this.documentClickListener(); + this.documentClickListener = null; + } } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/panel.component.ts b/src/Squidex/app/framework/angular/panel.component.ts index e29d14327..bc03b2379 100644 --- a/src/Squidex/app/framework/angular/panel.component.ts +++ b/src/Squidex/app/framework/angular/panel.component.ts @@ -31,6 +31,9 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { @Input() public desiredWidth = '10rem'; + @Input() + public minWidth?: string; + @Input() public isBlank = false; @@ -84,6 +87,7 @@ export class PanelComponent implements AfterViewInit, OnDestroy, OnInit { this.styleWidth = size; this.renderer.setStyle(this.panel.nativeElement, 'width', size); + this.renderer.setStyle(this.panel.nativeElement, 'minWidth', this.minWidth); this.renderWidth = this.panel.nativeElement.offsetWidth; } } diff --git a/src/Squidex/app/framework/angular/routers/parent-link.directive.ts b/src/Squidex/app/framework/angular/routers/parent-link.directive.ts index be0798db4..71611a6b6 100644 --- a/src/Squidex/app/framework/angular/routers/parent-link.directive.ts +++ b/src/Squidex/app/framework/angular/routers/parent-link.directive.ts @@ -29,7 +29,7 @@ export class ParentLinkDirective extends ResourceOwner implements OnDestroy, OnI } public ngOnInit() { - this.takeOver( + this.own( this.route.url.subscribe(() => { this.url = this.isLazyLoaded ? this.router.createUrlTree(['.'], { relativeTo: this.route.parent!.parent }).toString() : diff --git a/src/Squidex/app/framework/angular/stateful.component.ts b/src/Squidex/app/framework/angular/stateful.component.ts index a88edd1dc..161ada74f 100644 --- a/src/Squidex/app/framework/angular/stateful.component.ts +++ b/src/Squidex/app/framework/angular/stateful.component.ts @@ -19,7 +19,7 @@ declare type UnsubscribeFunction = () => void; export class ResourceOwner implements OnDestroy { private subscriptions: (Subscription | UnsubscribeFunction)[] = []; - public takeOver(subscription: Subscription | UnsubscribeFunction | Observable) { + public own(subscription: Subscription | UnsubscribeFunction | Observable) { if (subscription) { if (Types.isFunction(subscription['subscribe'])) { const observable = >subscription; @@ -66,7 +66,7 @@ export abstract class StatefulComponent extends State implements OnD } public takeOver(subscription: Subscription | UnsubscribeFunction | Observable) { - this.subscriptions.takeOver(subscription); + this.subscriptions.own(subscription); } } diff --git a/src/Squidex/app/framework/angular/user-report.component.ts b/src/Squidex/app/framework/angular/user-report.component.ts index 260ee44b7..a67a7bfd3 100644 --- a/src/Squidex/app/framework/angular/user-report.component.ts +++ b/src/Squidex/app/framework/angular/user-report.component.ts @@ -32,7 +32,7 @@ export class UserReportComponent extends ResourceOwner implements OnDestroy, OnI window['_urq'] = window['_urq'] || []; window['_urq'].push(['initSite', this.config.siteId]); - this.takeOver( + this.own( timer(4000).subscribe(() => { this.resourceLoader.loadScript('https://cdn.userreport.com/userreport.js'); })); diff --git a/src/Squidex/app/framework/utils/version.spec.ts b/src/Squidex/app/framework/utils/version.spec.ts index 4af85456d..20603d4d3 100644 --- a/src/Squidex/app/framework/utils/version.spec.ts +++ b/src/Squidex/app/framework/utils/version.spec.ts @@ -13,6 +13,13 @@ describe('Version', () => { expect(version.value).toBe('1.0'); }); + + it('should ignore prefix for equal comparison', () => { + expect(new Version('2').eq(new Version('2'))).toBeTruthy(); + expect(new Version('2').eq(new Version('W/2'))).toBeTruthy(); + expect(new Version('W/2').eq(new Version('2'))).toBeTruthy(); + expect(new Version('W/2').eq(new Version('W/2'))).toBeTruthy(); + }); }); describe('Versioned', () => { diff --git a/src/Squidex/app/framework/utils/version.ts b/src/Squidex/app/framework/utils/version.ts index 92016cd90..e7bf99dd6 100644 --- a/src/Squidex/app/framework/utils/version.ts +++ b/src/Squidex/app/framework/utils/version.ts @@ -10,6 +10,18 @@ export class Version { public readonly value: string ) { } + + public eq(other: Version) { + return other && other.trimmed() === this.trimmed(); + } + + private trimmed(): string { + if (this.value.startsWith('W/')) { + return this.value.substr(2); + } else { + return this.value; + } + } } export class Versioned { diff --git a/src/Squidex/app/shared/components/comments.component.ts b/src/Squidex/app/shared/components/comments.component.ts index 35208dcaf..6ef6be86e 100644 --- a/src/Squidex/app/shared/components/comments.component.ts +++ b/src/Squidex/app/shared/components/comments.component.ts @@ -50,7 +50,7 @@ export class CommentsComponent extends ResourceOwner implements OnDestroy, OnIni public ngOnInit() { this.state = new CommentsState(this.appsState, this.commentsId, this.commentsService, this.dialogs); - this.takeOver( + this.own( timer(0, 4000).pipe(switchMap(() => this.state.load().pipe(onErrorResumeNext()))) .subscribe()); } diff --git a/src/Squidex/app/shared/components/permission.directive.ts b/src/Squidex/app/shared/components/permission.directive.ts index 21c674141..7f91d02c1 100644 --- a/src/Squidex/app/shared/components/permission.directive.ts +++ b/src/Squidex/app/shared/components/permission.directive.ts @@ -44,14 +44,14 @@ export class PermissionDirective extends ResourceOwner implements OnChanges, OnI } public ngOnInit() { - this.takeOver( + this.own( this.appsState.selectedApp.subscribe(app => { if (app && !this.app) { this.update(app, this.schemasState.snapshot.selectedSchema); } })); - this.takeOver( + this.own( this.schemasState.selectedSchema.subscribe(schema => { if (schema && !this.schema) { this.update(this.appsState.snapshot.selectedApp, schema); diff --git a/src/Squidex/app/shared/services/contents.service.ts b/src/Squidex/app/shared/services/contents.service.ts index 05bb55fd8..33036df7a 100644 --- a/src/Squidex/app/shared/services/contents.service.ts +++ b/src/Squidex/app/shared/services/contents.service.ts @@ -162,12 +162,12 @@ export class ContentsService { pretifyError('Failed to load content. Please reload.')); } - public getVersionData(appName: string, schemaName: string, id: string, version: Version): Observable { + public getVersionData(appName: string, schemaName: string, id: string, version: Version): Observable> { const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${version.value}`); return HTTP.getVersioned(this.http, url).pipe( map(response => { - return response.payload.body; + return new Versioned(response.version, response.payload.body); }), pretifyError('Failed to load data. Please reload.')); } diff --git a/src/Squidex/app/shared/state/contents.state.ts b/src/Squidex/app/shared/state/contents.state.ts index 41612be52..d7a0f4e75 100644 --- a/src/Squidex/app/shared/state/contents.state.ts +++ b/src/Squidex/app/shared/state/contents.state.ts @@ -301,7 +301,8 @@ export abstract class ContentsStateBase extends State { } public loadVersion(content: ContentDto, version: Version): Observable> { - return this.contentsService.getVersionData(this.appName, this.schemaName, content.id, version).pipe(notify(this.dialogs)); + return this.contentsService.getVersionData(this.appName, this.schemaName, content.id, version).pipe( + notify(this.dialogs)); } private get appName() { diff --git a/src/Squidex/app/shell/pages/internal/internal-area.component.ts b/src/Squidex/app/shell/pages/internal/internal-area.component.ts index d92da4bb6..243160d02 100644 --- a/src/Squidex/app/shell/pages/internal/internal-area.component.ts +++ b/src/Squidex/app/shell/pages/internal/internal-area.component.ts @@ -34,7 +34,7 @@ export class InternalAreaComponent extends ResourceOwner implements OnDestroy, O } public ngOnInit() { - this.takeOver( + this.own( this.route.queryParams.subscribe(params => { const successMessage = params['successMessage']; diff --git a/src/Squidex/app/theme/icomoon/demo-files/demo.css b/src/Squidex/app/theme/icomoon/demo-files/demo.css index 1d2e3901d..8aefb78fe 100644 --- a/src/Squidex/app/theme/icomoon/demo-files/demo.css +++ b/src/Squidex/app/theme/icomoon/demo-files/demo.css @@ -147,10 +147,10 @@ p { font-size: 16px; } .fs1 { - font-size: 28px; + font-size: 24px; } .fs2 { - font-size: 24px; + font-size: 28px; } .fs3 { font-size: 24px; diff --git a/src/Squidex/app/theme/icomoon/demo.html b/src/Squidex/app/theme/icomoon/demo.html index 903707ae3..46df0bc2a 100644 --- a/src/Squidex/app/theme/icomoon/demo.html +++ b/src/Squidex/app/theme/icomoon/demo.html @@ -9,11 +9,174 @@
-

Font Name: icomoon (Glyphs: 113)

+

Font Name: icomoon (Glyphs: 114)

-

Grid Size: 14

+

Grid Size: 24

+
+
+ + + + icon-arrow_back +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-external-link +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-minus-square +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-plus-square +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-drag2 +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-comments +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-backup +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-support +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-control-RichText +
+
+ + +
+
+ liga: + +
+
+
+ + + + icon-download +
+
+ + +
+
+ liga: + +
+
+
+
+

Grid Size: 14

+
@@ -29,7 +192,7 @@
-
+
@@ -45,7 +208,7 @@
-
+
@@ -61,7 +224,7 @@
-
+
@@ -77,7 +240,7 @@
-
+
@@ -93,7 +256,7 @@
-
+
@@ -109,7 +272,7 @@
-
+
@@ -125,7 +288,7 @@
-
+
@@ -141,7 +304,7 @@
-
+
@@ -157,7 +320,7 @@
-
+
@@ -173,7 +336,7 @@
-
+
@@ -189,7 +352,7 @@
-
+
@@ -205,7 +368,7 @@
-
+
@@ -221,7 +384,7 @@
-
+
@@ -237,7 +400,7 @@
-
+
@@ -253,7 +416,7 @@
-
+
@@ -269,7 +432,7 @@
-
+
@@ -285,7 +448,7 @@
-
+
@@ -301,7 +464,7 @@
-
+
@@ -317,7 +480,7 @@
-
+
@@ -333,7 +496,7 @@
-
+
@@ -349,7 +512,7 @@
-
+
@@ -365,7 +528,7 @@
-
+
@@ -381,7 +544,7 @@
-
+
@@ -397,7 +560,7 @@
-
+
@@ -413,7 +576,7 @@
-
+
@@ -429,7 +592,7 @@
-
+
@@ -445,7 +608,7 @@
-
+
@@ -461,7 +624,7 @@
-
+
@@ -477,7 +640,7 @@
-
+
@@ -493,7 +656,7 @@
-
+
@@ -510,153 +673,6 @@
-
-

Grid Size: 24

-
-
- - - - icon-external-link -
-
- - -
-
- liga: - -
-
-
-
- - - - icon-minus-square -
-
- - -
-
- liga: - -
-
-
-
- - - - icon-plus-square -
-
- - -
-
- liga: - -
-
-
-
- - - - icon-drag2 -
-
- - -
-
- liga: - -
-
-
-
- - - - icon-comments -
-
- - -
-
- liga: - -
-
-
-
- - - - icon-backup -
-
- - -
-
- liga: - -
-
-
-
- - - - icon-support -
-
- - -
-
- liga: - -
-
-
-
- - - - icon-control-RichText -
-
- - -
-
- liga: - -
-
-
-
- - - - icon-download -
-
- - -
-
- liga: - -
-
-

Grid Size: 16

diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.eot b/src/Squidex/app/theme/icomoon/fonts/icomoon.eot index d55d2effe93776ddc19f8dd4dcbcf8c79ee1eb57..839014719a992379097d14cb201f476cfdccac94 100644 GIT binary patch delta 318 zcmdmSoAJYKM%E9x3=BRKSsz! zBQ-HaG^$aVfgvM-fq}s+11P}B#99vI&j9jOGIC2QcKFM<0r`7?dT!+8CnsvB2ms}> zrU2#ND03JyBLfFepQxfZJ13(!J9F6IAMb4btzrzb zdB=D)O$UM!+ qxN7o`tWQ#ghm10eRg5!$Dj0#dj4^^i45XTYY4XBc;myK1OBn%r6k2Nl delta 246 zcmexxn{mf&M%Ep<3=AR@SZ~f&3hx`nH1n;t~dCpbSIK7mz#yGxOwR#vaDp z$sZV%Syu>T@~_@3!Bm*Md1q=cvrOPsL)&undXe8n-elm bGfvjee$QhRV9aA2z`zKUFWsz{bBGZDr7cEk diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg index f36479998..1a34a42a8 100644 --- a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg +++ b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg @@ -117,6 +117,7 @@ + diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf b/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf index f977e9c805ecc85b2277e27797d7d4acf0d592e6..bc9a86121d1926e4463ee0ee0466358cadb13876 100644 GIT binary patch delta 316 zcmbPoi}B1Y#tDk`p0|}47#L+37#MQW6N?Lgv;dIb0i-$7b1KtLb*)2f6LSABSs>Dn$&fh?RB|r^r1^LA#49q|&hFqPAJ0>&cP4;0_ zW?d>#dk#W`JkgQKq jhKGzYj8%*?fGQY)xQsD^K@6mtfoU>huJGpk?4^tVv_Mv7 delta 248 zcmX?ei*d>=#tDk`9=GKg7#L+37#MQW6N?Lgv;dIb0i-$7b1Ku88?w6r`4boz(iUW- zCZ>qoSS857kYNCnH_HGDa5Ax$0r?p~zDh=JNyQd_={Z1t4^YpQoc!d(9}5J77#Ok= zfbws06DtZBdKjY^7_xnUe1*Kk+*FB~UYx&y0y#hpZ3X$oB@E0!DTbUc6L(Bz%$@AR zsLZ-TAd`Rf<`Txj* Ych-9zqX1(b;{XOmpnU1(uIxjM0AG?tu>b%7 diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.woff b/src/Squidex/app/theme/icomoon/fonts/icomoon.woff index 23d0a69c9e9b7bf861101edf7b19d353103df0e8..0061d0bad706a86e5c12f93290645bf2199258ca 100644 GIT binary patch delta 344 zcmaEGi*d$nMv-!VH#Y`G1|Z0t!N3iseI|<7)_dMoN>3~wLBxYq=JDV6KEFW z4G`YpFXNVzpA1x&^#G{m9SCcu2+YV$tSHc7V8{kqz@Pxey^K+LiMgpju@69vZ6G|; zi}QCuesKxV-4zr6%x27+JcCi0b)`TS|C-GQ7z>m285veFgffRQGlCq?D5@yV&dDgw z&K&ml$2*&Us~E#<-Z5Sc`C!8s24n(F-E5E=!YngqQkZ2tzs*+$ZWf@$3=B8w|X5v delta 308 zcmbPnoAJRdMv-!VH#Y`G1|Y~yVBiMRB9j|)#V2a3)qC8QPfsi^U|?Xp0+ir@VuAFW z$~2(Z9R`N91t7fKkligKH8F*OA)^MU#tei-Zmbf_015)dwgCAmAk4|cT9%PpQUMgZ z0Wp*D$`*g=IXU^sKy_JbfNI`=@Q($8LAi+)1v(52*)xCw3SivB7?qcpn+g=$0o2?E z!ZW=%e;4EzmjK=FF!9f9#@xv>7?oL92xRiF-h6