Browse Source

Feature/angular performance (#806)

* Improve performance by moving all animations to nested components.

* Get rid of animations.

* Another improvement.

* Expand once.
pull/808/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
65482456c4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      frontend/app/features/administration/administration-area.component.html
  2. 4
      frontend/app/features/administration/module.ts
  3. 4
      frontend/app/features/apps/pages/app.component.html
  4. 5
      frontend/app/features/apps/pages/app.component.ts
  5. 12
      frontend/app/features/apps/pages/onboarding-dialog.component.html
  6. 8
      frontend/app/features/content/pages/content/content-history-page.component.html
  7. 5
      frontend/app/features/content/pages/content/content-history-page.component.ts
  8. 4
      frontend/app/features/content/pages/content/content-page.component.html
  9. 5
      frontend/app/features/content/pages/content/content-page.component.ts
  10. 2
      frontend/app/features/content/pages/content/editor/content-editor.component.html
  11. 2
      frontend/app/features/content/pages/content/editor/content-section.component.html
  12. 4
      frontend/app/features/content/pages/content/editor/field-copy-button.component.html
  13. 5
      frontend/app/features/content/pages/content/editor/field-copy-button.component.ts
  14. 4
      frontend/app/features/content/pages/contents/contents-page.component.html
  15. 5
      frontend/app/features/content/pages/contents/contents-page.component.ts
  16. 4
      frontend/app/features/content/shared/forms/array-editor.component.html
  17. 21
      frontend/app/features/content/shared/forms/array-editor.component.ts
  18. 6
      frontend/app/features/content/shared/forms/array-item.component.html
  19. 18
      frontend/app/features/content/shared/forms/array-item.component.ts
  20. 2
      frontend/app/features/content/shared/forms/component-section.component.html
  21. 4
      frontend/app/features/content/shared/forms/component.component.html
  22. 5
      frontend/app/features/content/shared/forms/component.component.ts
  23. 2
      frontend/app/features/content/shared/forms/field-editor.component.html
  24. 4
      frontend/app/features/content/shared/list/content.component.html
  25. 5
      frontend/app/features/content/shared/list/content.component.ts
  26. 4
      frontend/app/features/content/shared/preview-button.component.html
  27. 5
      frontend/app/features/content/shared/preview-button.component.ts
  28. 5
      frontend/app/features/content/shared/references/reference-dropdown.component.html
  29. 5
      frontend/app/features/dashboard/pages/cards/api-calls-card.component.ts
  30. 5
      frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.ts
  31. 5
      frontend/app/features/dashboard/pages/cards/api-card.component.ts
  32. 5
      frontend/app/features/dashboard/pages/cards/api-performance-card.component.ts
  33. 5
      frontend/app/features/dashboard/pages/cards/api-traffic-card.component.ts
  34. 5
      frontend/app/features/dashboard/pages/cards/api-traffic-summary-card.component.ts
  35. 5
      frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.ts
  36. 5
      frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.ts
  37. 5
      frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.ts
  38. 5
      frontend/app/features/dashboard/pages/cards/content-summary-card.component.ts
  39. 5
      frontend/app/features/dashboard/pages/cards/github-card.component.ts
  40. 5
      frontend/app/features/dashboard/pages/cards/history-card.component.ts
  41. 5
      frontend/app/features/dashboard/pages/cards/iframe-card.component.ts
  42. 5
      frontend/app/features/dashboard/pages/cards/schema-card.component.ts
  43. 5
      frontend/app/features/dashboard/pages/cards/support-card.component.ts
  44. 4
      frontend/app/features/dashboard/pages/dashboard-config.component.html
  45. 5
      frontend/app/features/dashboard/pages/dashboard-config.component.ts
  46. 2
      frontend/app/features/dashboard/pages/dashboard-page.component.html
  47. 2
      frontend/app/features/rules/pages/rule/rule-page.component.html
  48. 4
      frontend/app/features/rules/pages/rules/rule.component.html
  49. 5
      frontend/app/features/rules/pages/rules/rule.component.ts
  50. 2
      frontend/app/features/rules/pages/rules/rules-page.component.html
  51. 2
      frontend/app/features/rules/shared/actions/generic-action.component.html
  52. 4
      frontend/app/features/schemas/pages/schema/fields/field.component.html
  53. 5
      frontend/app/features/schemas/pages/schema/fields/field.component.ts
  54. 2
      frontend/app/features/schemas/pages/schema/fields/types/date-time-ui.component.html
  55. 4
      frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html
  56. 5
      frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts
  57. 4
      frontend/app/features/schemas/pages/schema/schema-page.component.html
  58. 5
      frontend/app/features/schemas/pages/schema/schema-page.component.ts
  59. 8
      frontend/app/features/settings/pages/clients/client-connect-form.component.html
  60. 24
      frontend/app/framework/angular/animations.ts
  61. 1
      frontend/app/framework/angular/dropdown-menu.component.html
  62. 0
      frontend/app/framework/angular/dropdown-menu.component.scss
  63. 37
      frontend/app/framework/angular/dropdown-menu.component.ts
  64. 7
      frontend/app/framework/angular/forms/control-errors-messages.component.html
  65. 4
      frontend/app/framework/angular/forms/control-errors-messages.component.scss
  66. 23
      frontend/app/framework/angular/forms/control-errors-messages.component.ts
  67. 8
      frontend/app/framework/angular/forms/control-errors.component.html
  68. 9
      frontend/app/framework/angular/forms/control-errors.component.ts
  69. 6
      frontend/app/framework/angular/forms/editors/autocomplete.component.html
  70. 5
      frontend/app/framework/angular/forms/editors/autocomplete.component.ts
  71. 4
      frontend/app/framework/angular/forms/editors/dropdown.component.html
  72. 5
      frontend/app/framework/angular/forms/editors/localized-input.component.ts
  73. 10
      frontend/app/framework/angular/forms/editors/tag-editor.component.html
  74. 31
      frontend/app/framework/angular/forms/editors/tag-editor.component.ts
  75. 2
      frontend/app/framework/angular/forms/form-error.component.html
  76. 4
      frontend/app/framework/angular/language-selector.component.html
  77. 5
      frontend/app/framework/angular/language-selector.component.ts
  78. 5
      frontend/app/framework/angular/list-view.component.ts
  79. 68
      frontend/app/framework/angular/markdown.directive.ts
  80. 4
      frontend/app/framework/angular/modals/dialog-renderer.component.html
  81. 3
      frontend/app/framework/declarations.ts
  82. 7
      frontend/app/framework/module.ts
  83. 4
      frontend/app/shared/components/assets/asset-folder.component.html
  84. 5
      frontend/app/shared/components/assets/asset-folder.component.ts
  85. 4
      frontend/app/shared/components/assets/asset-uploader.component.html
  86. 5
      frontend/app/shared/components/assets/asset-uploader.component.ts
  87. 2
      frontend/app/shared/components/comments/comment.component.html
  88. 5
      frontend/app/shared/components/schema-category.component.ts
  89. 2
      frontend/app/shared/components/search/search-form.component.html
  90. 4
      frontend/app/shell/pages/internal/apps-menu.component.html
  91. 5
      frontend/app/shell/pages/internal/apps-menu.component.ts
  92. 4
      frontend/app/shell/pages/internal/notifications-menu.component.html
  93. 5
      frontend/app/shell/pages/internal/notifications-menu.component.ts
  94. 4
      frontend/app/shell/pages/internal/profile-menu.component.html
  95. 5
      frontend/app/shell/pages/internal/profile-menu.component.ts

10
frontend/app/features/administration/administration-area.component.html

@ -2,16 +2,16 @@
<div class="sidebar">
<ul class="nav nav-panel flex-column">
<li class="nav-item" *ngIf="uiState.canReadEvents | async">
<a class="nav-link" routerLink="event-consumers" routerLinkActive="active">
<i class="nav-icon icon-time"></i> <div class="nav-text">{{ 'common.consumers' | sqxTranslate }}</div>
</a>
</li>
<li class="nav-item" *ngIf="uiState.canReadUsers | async">
<a class="nav-link" routerLink="users" routerLinkActive="active">
<i class="nav-icon icon-user-o"></i> <div class="nav-text">{{ 'common.users' | sqxTranslate }}</div>
</a>
</li>
<li class="nav-item" *ngIf="uiState.canReadEvents | async">
<a class="nav-link" routerLink="event-consumers" routerLinkActive="active">
<i class="nav-icon icon-time"></i> <div class="nav-text">{{ 'common.consumers' | sqxTranslate }}</div>
</a>
</li>
<li class="nav-item" *ngIf="uiState.canRestore | async">
<a class="nav-link" routerLink="restore" routerLinkActive="active">
<i class="nav-icon icon-backup"></i> <div class="nav-text">{{ 'common.restore' | sqxTranslate }}</div>

4
frontend/app/features/administration/module.ts

@ -41,6 +41,10 @@ const routes: Routes = [
},
],
},
{
path: '',
redirectTo: 'users',
},
],
},
],

4
frontend/app/features/apps/pages/app.component.html

@ -29,7 +29,7 @@
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true">
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="leave.emit(app)"
confirmTitle="i18n:apps.leaveConfirmTitle"
@ -37,7 +37,7 @@
confirmRememberKey="leaveApp">
{{ 'apps.leave' | sqxTranslate }}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</div>
</div>

5
frontend/app/features/apps/pages/app.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { AppDto, fadeAnimation, ModalModel } from '@app/shared';
import { AppDto, ModalModel } from '@app/shared';
@Component({
selector: 'sqx-app[app]',
styleUrls: ['./app.component.scss'],
templateUrl: './app.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {

12
frontend/app/features/apps/pages/onboarding-dialog.component.html

@ -8,7 +8,7 @@
<div @slide class="onboarding-enter-leave">
<h1>{{ 'tour.welcome' | sqxTranslate }} <span class="header-focus">{{ 'tour.welcomeProduct' | sqxTranslate }}</span></h1>
<div [innerHTML]="'tour.step0Text' | sqxTranslate | sqxMarkdown"></div>
<div [innerHTML]="'tour.step0Text' | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
<button (click)="next()" class="btn btn-success">{{ 'tour.step0Next' | sqxTranslate }}</button>
</div>
@ -19,7 +19,7 @@
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text" [innerHTML]="'tour.step1Text' | sqxTranslate | sqxMarkdown"></div>
<div class="onboarding-text" [innerHTML]="'tour.step1Text' | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
</div>
<div class="col col-image">
<img src="./images/onboarding-step1.png">
@ -37,7 +37,7 @@
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text" [innerHTML]="'tour.step2Text' | sqxTranslate | sqxMarkdown"></div>
<div class="onboarding-text" [innerHTML]="'tour.step2Text' | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
</div>
<div class="col col-image">
<img src="./images/onboarding-step2.png">
@ -55,7 +55,7 @@
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text" [innerHTML]="'tour.step3Text' | sqxTranslate | sqxMarkdown"></div>
<div class="onboarding-text" [innerHTML]="'tour.step3Text' | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
</div>
<div class="col col-image">
<img src="./images/onboarding-step3.png">
@ -73,7 +73,7 @@
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text" [innerHTML]="'tour.step4Text' | sqxTranslate | sqxMarkdown"></div>
<div class="onboarding-text" [innerHTML]="'tour.step4Text' | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
</div>
<div class="col col-image">
<img src="./images/onboarding-step4.png">
@ -91,7 +91,7 @@
<div @slide class="onboarding-enter-leave">
<h1>{{ 'tour.step5Title' | sqxTranslate }}</h1>
<div [innerHTML]="'tour.step5Text' | sqxTranslate | sqxMarkdown"></div>
<div [innerHTML]="'tour.step5Text' | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
<div>
<a class="btn btn-success" href="https://support.squidex.io" sqxExternalLink>

8
frontend/app/features/content/pages/content/content-history-page.component.html

@ -32,7 +32,7 @@
</button>
<ng-container *sqxModal="dropdownNew;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true">
<ng-container *ngIf="content.statusUpdates.length > 0">
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="changeStatus(info.status)">
{{ 'common.statusChangeTo' | sqxTranslate }} <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}}
@ -58,7 +58,7 @@
confirmRememberKey="deleteContent">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-template>
</div>
@ -77,7 +77,7 @@
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true">
<ng-container *ngIf="content.statusUpdates.length > 0">
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="changeStatus(info.status)">
{{ 'common.statusChangeTo' | sqxTranslate }}
@ -109,7 +109,7 @@
confirmRememberKey="deleteContent">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</div>

5
frontend/app/features/content/pages/content/content-history-page.component.ts

@ -6,7 +6,7 @@
*/
import { Component, OnInit, ViewChild } from '@angular/core';
import { AppsState, ContentDto, ContentsState, defined, fadeAnimation, HistoryEventDto, HistoryService, ModalModel, ResourceOwner, SchemasState, switchSafe } from '@app/shared';
import { AppsState, ContentDto, ContentsState, defined, HistoryEventDto, HistoryService, ModalModel, ResourceOwner, SchemasState, switchSafe } from '@app/shared';
import { Observable, timer } from 'rxjs';
import { map } from 'rxjs/operators';
import { DueTimeSelectorComponent } from './../../shared/due-time-selector.component';
@ -16,9 +16,6 @@ import { ContentPageComponent } from './content-page.component';
selector: 'sqx-history',
styleUrls: ['./content-history-page.component.scss'],
templateUrl: './content-history-page.component.html',
animations: [
fadeAnimation,
],
})
export class ContentHistoryPageComponent extends ResourceOwner implements OnInit {
@ViewChild('dueTimeSelector', { static: false })

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

@ -65,7 +65,7 @@
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true">
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="delete()"
confirmTitle="i18n:contents.deleteConfirmTitle"
@ -73,7 +73,7 @@
confirmRememberKey="deleteContent">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>

5
frontend/app/features/content/pages/content/content-page.component.ts

@ -7,7 +7,7 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApiUrlConfig, AppLanguageDto, AppsState, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, ContentDto, ResolveContents, ContentsState, defined, DialogService, EditContentForm, fadeAnimation, LanguagesState, ModalModel, ResourceOwner, SchemaDto, SchemasState, TempService, ToolbarService, Types, Version, ResolveAssets } from '@app/shared';
import { ApiUrlConfig, AppLanguageDto, AppsState, AuthService, AutoSaveKey, AutoSaveService, CanComponentDeactivate, ContentDto, ResolveContents, ContentsState, defined, DialogService, EditContentForm, LanguagesState, ModalModel, ResourceOwner, SchemaDto, SchemasState, TempService, ToolbarService, Types, Version, ResolveAssets } from '@app/shared';
import { Observable, of } from 'rxjs';
import { filter, map, tap } from 'rxjs/operators';
@ -15,9 +15,6 @@ import { filter, map, tap } from 'rxjs/operators';
selector: 'sqx-content-page',
styleUrls: ['./content-page.component.scss'],
templateUrl: './content-page.component.html',
animations: [
fadeAnimation,
],
providers: [
ResolveAssets,
ResolveContents,

2
frontend/app/features/content/pages/content/editor/content-editor.component.html

@ -7,7 +7,7 @@
<a (click)="loadLatest.emit()">{{ 'contents.viewLatest' | sqxTranslate }}</a>
</div>
<div [innerHTML]="'contents.versionViewing' | sqxTranslate: { version: contentVersion } | sqxMarkdownInline"></div>
<div [innerHTML]="'contents.versionViewing' | sqxTranslate: { version: contentVersion } | sqxMarkdownInline | sqxSafeHtml"></div>
</div>
<div *ngIf="isNew">

2
frontend/app/features/content/pages/content/editor/content-section.component.html

@ -10,7 +10,7 @@
<h3>{{separator.displayName}}</h3>
<sqx-form-hint *ngIf="separator.properties.hints && separator.properties.hints.length > 0">
<span [innerHTML]="separator.properties.hints | sqxMarkdownInline"></span>
<span [sqxMarkdown]="separator.properties.hints" [optional]="true" [inline]="true"></span>
</sqx-form-hint>
</div>
</div>

4
frontend/app/features/content/pages/content/editor/field-copy-button.component.html

@ -4,7 +4,7 @@
</button>
<ng-container *sqxModal="dropdown">
<div class="dropdown-menu" [sqxAnchoredTo]="button" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="button" [scrollY]="true">
<div class="section d-flex justify-content-end">
<button type="button" class="btn btn-primary" (click)="copy()" tabindex="-1">
{{ 'common.copy' | sqxTranslate }}
@ -32,6 +32,6 @@
<sqx-checkbox-group [(ngModel)]="copyTargets" [values]="languageCodes" layout="Multiline"></sqx-checkbox-group>
</div>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>

5
frontend/app/features/content/pages/content/editor/field-copy-button.component.ts

@ -6,15 +6,12 @@
*/
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { AppLanguageDto, fadeAnimation, FieldForm, ModalModel } from '@app/shared';
import { AppLanguageDto, FieldForm, ModalModel } from '@app/shared';
@Component({
selector: 'sqx-field-copy-button[formModel][languages]',
styleUrls: ['./field-copy-button.component.scss'],
templateUrl: './field-copy-button.component.html',
animations: [
fadeAnimation,
],
})
export class FieldCopyButtonComponent implements OnChanges {
@Input()

4
frontend/app/features/content/pages/contents/contents-page.component.html

@ -62,13 +62,13 @@
</button>
<ng-container *sqxModal="tableViewModal">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonSettings" [scrollY]="true" @fade position="bottom-right">
<sqx-dropdown-menu [sqxAnchoredTo]="buttonSettings" [scrollY]="true" position="bottom-right">
<sqx-custom-view-editor
[allFields]="tableView.allFields"
(fieldNamesChange)="tableView.updateFields($event)"
[fieldNames]="$any(tableView.listFieldNames | async)">
</sqx-custom-view-editor>
</div>
</sqx-dropdown-menu>
</ng-container>
</div>
</ng-container>

5
frontend/app/features/content/pages/contents/contents-page.component.ts

@ -9,7 +9,7 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { AppLanguageDto, AppsState, ContentDto, ContentsState, ContributorsState, defined, fadeAnimation, LanguagesState, ModalModel, Queries, Query, queryModelFromSchema, QuerySynchronizer, ResourceOwner, Router2State, SchemaDto, SchemasState, switchSafe, TableFields, TempService, UIState } from '@app/shared';
import { AppLanguageDto, AppsState, ContentDto, ContentsState, ContributorsState, defined, LanguagesState, ModalModel, Queries, Query, queryModelFromSchema, QuerySynchronizer, ResourceOwner, Router2State, SchemaDto, SchemasState, switchSafe, TableFields, TempService, UIState } from '@app/shared';
import { combineLatest } from 'rxjs';
import { distinctUntilChanged, map, switchMap, take, tap } from 'rxjs/operators';
import { DueTimeSelectorComponent } from './../../shared/due-time-selector.component';
@ -21,9 +21,6 @@ import { DueTimeSelectorComponent } from './../../shared/due-time-selector.compo
providers: [
Router2State,
],
animations: [
fadeAnimation,
],
})
export class ContentsPageComponent extends ResourceOwner implements OnInit {
@ViewChild('dueTimeSelector', { static: false })

4
frontend/app/features/content/shared/forms/array-editor.component.html

@ -74,11 +74,11 @@
</button>
<ng-container *sqxModal="schemasDropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonSelect" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonSelect" [scrollY]="true">
<a class="dropdown-item" *ngFor="let schema of schemasList" (click)="addComponent(schema)">
{{schema.displayName}}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>
<ng-container *ngIf="schemasList.length === 1">

21
frontend/app/features/content/shared/forms/array-editor.component.ts

@ -6,8 +6,8 @@
*/
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { ChangeDetectionStrategy, Component, Input, OnChanges, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { AppLanguageDto, ComponentsFieldPropertiesDto, disabled$, EditContentForm, fadeAnimation, FieldArrayForm, LocalStoreService, ModalModel, ObjectFormBase, SchemaDto, Settings, sorted, Types } from '@app/shared';
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { AppLanguageDto, ComponentsFieldPropertiesDto, disabled$, EditContentForm, FieldArrayForm, LocalStoreService, ModalModel, ObjectFormBase, SchemaDto, Settings, sorted, Types } from '@app/shared';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ArrayItemComponent } from './array-item.component';
@ -17,11 +17,8 @@ import { ArrayItemComponent } from './array-item.component';
styleUrls: ['./array-editor.component.scss'],
templateUrl: './array-editor.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
fadeAnimation,
],
})
export class ArrayEditorComponent implements OnChanges {
export class ArrayEditorComponent implements OnChanges, OnInit {
@Input()
public form: EditContentForm;
@ -65,6 +62,10 @@ export class ArrayEditorComponent implements OnChanges {
) {
}
public ngOnInit() {
this.isCollapsedInitial = this.formLevel > 0;
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['formModel']) {
const maxItems = this.formModel.field.properties['maxItems'] || Number.MAX_VALUE;
@ -84,7 +85,9 @@ export class ArrayEditorComponent implements OnChanges {
return disabled || items.length >= maxItems;
}));
this.isCollapsedInitial = this.formLevel > 0 || this.localStore.getBoolean(this.expandedKey());
if (this.formLevel === 0) {
this.isCollapsedInitial = this.localStore.getBoolean(this.expandedKey());
}
}
}
@ -125,16 +128,20 @@ export class ArrayEditorComponent implements OnChanges {
child.collapse();
});
if (this.formLevel === 0) {
this.localStore.setBoolean(this.expandedKey(), true);
}
}
public expandAll() {
this.children.forEach(child => {
child.expand();
});
if (this.formLevel === 0) {
this.localStore.setBoolean(this.expandedKey(), false);
}
}
private reset() {
this.children.forEach(child => {

6
frontend/app/features/content/shared/forms/array-item.component.html

@ -23,10 +23,10 @@
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="moveBottom()" title="i18n:contents.arrayMoveBottom">
<i class="icon-caret-bottom"></i>
</button>
<button type="button" class="btn btn-text-secondary" [class.hidden]="!snapshot.isCollapsed" (click)="expand()" title="i18n:contents.arrayExpandItem">
<button type="button" class="btn btn-text-secondary" [class.hidden]="snapshot.isExpanded" (click)="expand()" title="i18n:contents.arrayExpandItem">
<i class="icon-plus-square"></i>
</button>
<button type="button" class="btn btn-text-secondary" [class.hidden]="snapshot.isCollapsed" (click)="collapse()" title="i18n:contents.arrayCollapseItem">
<button type="button" class="btn btn-text-secondary" [class.hidden]="!snapshot.isExpanded" (click)="collapse()" title="i18n:contents.arrayCollapseItem">
<i class="icon-minus-square"></i>
</button>
</div>
@ -42,7 +42,7 @@
</div>
</div>
<div class="card-body" [class.hidden]="snapshot.isCollapsed">
<div class="card-body" *ngIf="snapshot.isExpandedOnce" [class.hidden]="!snapshot.isExpanded">
<div class="form-group" *ngFor="let section of formModel.fieldSectionsChanges | async">
<sqx-component-section
[canUnset]="canUnset"

18
frontend/app/features/content/shared/forms/array-item.component.ts

@ -12,8 +12,11 @@ import { map } from 'rxjs/operators';
import { ComponentSectionComponent } from './component-section.component';
interface State {
// The when the section is collapsed.
isCollapsed: boolean;
// True when the item is expanded.
isExpanded: boolean;
// True when the item is expanded at least once.
isExpandedOnce: boolean;
}
@Component({
@ -80,12 +83,15 @@ export class ArrayItemComponent extends StatefulComponent<State> implements OnCh
constructor(changeDetector: ChangeDetectorRef,
) {
super(changeDetector, {
isCollapsed: false,
isExpanded: false,
isExpandedOnce: false,
});
}
public ngOnInit() {
this.next({ isCollapsed: this.isCollapsedInitial });
if (!this.isCollapsedInitial) {
this.expand();
}
}
public ngOnChanges(changes: SimpleChanges) {
@ -103,11 +109,11 @@ export class ArrayItemComponent extends StatefulComponent<State> implements OnCh
}
public collapse() {
this.next({ isCollapsed: true });
this.next({ isExpanded: false });
}
public expand() {
this.next({ isCollapsed: false });
this.next({ isExpanded: true, isExpandedOnce: true });
}
public moveTop() {

2
frontend/app/features/content/shared/forms/component-section.component.html

@ -3,7 +3,7 @@
<h3>{{separator!.displayName}}</h3>
<sqx-form-hint *ngIf="separator.properties.hints && separator.properties.hints.length > 0">
<span [innerHTML]="separator.properties.hints | sqxMarkdownInline"></span>
<span [sqxMarkdown]="separator.properties.hints" [optional]="true" [inline]="true"></span>
</sqx-form-hint>
</div>

4
frontend/app/features/content/shared/forms/component.component.html

@ -24,11 +24,11 @@
</button>
<ng-container *sqxModal="schemasDropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonSelect" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonSelect" [scrollY]="true">
<a class="dropdown-item" *ngFor="let schema of schemasList" (click)="setSchema(schema)">
{{schema.displayName}}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>
<ng-container *ngIf="schemasList.length === 1">

5
frontend/app/features/content/shared/forms/component.component.ts

@ -6,7 +6,7 @@
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { AppLanguageDto, ComponentFieldPropertiesDto, ComponentForm, EditContentForm, fadeAnimation, FieldDto, FieldSection, ModalModel, ResourceOwner, SchemaDto, Types } from '@app/shared';
import { AppLanguageDto, ComponentFieldPropertiesDto, ComponentForm, EditContentForm, FieldDto, FieldSection, ModalModel, ResourceOwner, SchemaDto, Types } from '@app/shared';
import { ComponentSectionComponent } from './component-section.component';
@Component({
@ -14,9 +14,6 @@ import { ComponentSectionComponent } from './component-section.component';
styleUrls: ['./component.component.scss'],
templateUrl: './component.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [
fadeAnimation,
],
})
export class ComponentComponent extends ResourceOwner implements OnChanges {
@Input()

2
frontend/app/features/content/shared/forms/field-editor.component.html

@ -225,6 +225,6 @@
</div>
<sqx-form-hint *ngIf="field.properties.hints && field.properties.hints.length > 0">
<span [innerHTML]="field.properties.hints | sqxMarkdownInline"></span>
<span [sqxMarkdown]="field.properties.hints" [optional]="true" [inline]="true"></span>
</sqx-form-hint>
</div>

4
frontend/app/features/content/shared/list/content.component.html

@ -27,7 +27,7 @@
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" [scrollY]="true" position="bottom-left" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true" position="bottom-left">
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="statusChange.emit(info.status)">
{{ 'common.statusChangeTo' | sqxTranslate }}
@ -50,7 +50,7 @@
confirmRememberKey="deleteContent">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</td>

5
frontend/app/features/content/shared/list/content.component.ts

@ -6,7 +6,7 @@
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { AppLanguageDto, ContentDto, ContentListFieldComponent, ContentsState, fadeAnimation, ModalModel, PatchContentForm, RootFieldDto, TableField, Types } from '@app/shared';
import { AppLanguageDto, ContentDto, ContentListFieldComponent, ContentsState, ModalModel, PatchContentForm, RootFieldDto, TableField, Types } from '@app/shared';
/* tslint:disable: component-selector */
@ -14,9 +14,6 @@ import { AppLanguageDto, ContentDto, ContentListFieldComponent, ContentsState, f
selector: '[sqxContent][language][listFields]',
styleUrls: ['./content.component.scss'],
templateUrl: './content.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContentComponent implements OnChanges {

4
frontend/app/features/content/shared/preview-button.component.html

@ -10,9 +10,9 @@
<button type="button" class="btn btn-secondary dropdown-toggle" (click)="dropdown.toggle()"></button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonGroup" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonGroup" [scrollY]="true">
<a *ngFor="let name of snapshot.previewNamesMore" class="dropdown-item" (click)="follow(name)">{{name}}</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</div>
</div>

5
frontend/app/features/content/shared/preview-button.component.ts

@ -6,7 +6,7 @@
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ContentDto, fadeAnimation, interpolate, LocalStoreService, ModalModel, SchemaDto, Settings, StatefulComponent } from '@app/shared';
import { ContentDto, interpolate, LocalStoreService, ModalModel, SchemaDto, Settings, StatefulComponent } from '@app/shared';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
@ -22,9 +22,6 @@ interface State {
selector: 'sqx-preview-button[content][schema]',
styleUrls: ['./preview-button.component.scss'],
templateUrl: './preview-button.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PreviewButtonComponent extends StatefulComponent<State> implements OnInit {

5
frontend/app/features/content/shared/references/reference-dropdown.component.html

@ -1,5 +1,8 @@
<sqx-dropdown [formControl]="control" [items]="snapshot.contentNames" valueProperty="id" (open)="onOpened()">
<ng-template let-content="$implicit" let-context="context">
<span class="truncate" [innerHTML]="content.name | sqxHighlight:context"></span>
<span class="truncate" [innerHTML]="content.name | sqxHighlight:context | sqxSafeHtml"></span>
</ng-template>
<ng-template let-content="$implicit" let-context="context">
<span class="truncate">{{content.name}}</span>
</ng-template>
</sqx-dropdown>

5
frontend/app/features/dashboard/pages/cards/api-calls-card.component.ts

@ -6,16 +6,13 @@
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { AppDto, CallsUsageDto, fadeAnimation, UsagesService } from '@app/shared';
import { AppDto, CallsUsageDto, UsagesService } from '@app/shared';
import { ChartHelpers, ChartOptions } from './shared';
@Component({
selector: 'sqx-api-calls-card[app][usage]',
styleUrls: ['./api-calls-card.component.scss'],
templateUrl: './api-calls-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ApiCallsCardComponent implements OnChanges {

5
frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { AppDto, CallsUsageDto, fadeAnimation } from '@app/shared';
import { AppDto, CallsUsageDto } from '@app/shared';
@Component({
selector: 'sqx-api-calls-summary-card[app][usage]',
styleUrls: ['./api-calls-summary-card.component.scss'],
templateUrl: './api-calls-summary-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ApiCallsSummaryCardComponent implements OnChanges {

5
frontend/app/features/dashboard/pages/cards/api-card.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { AppDto, fadeAnimation } from '@app/shared';
import { AppDto } from '@app/shared';
@Component({
selector: 'sqx-api-card[app]',
styleUrls: ['./api-card.component.scss'],
templateUrl: './api-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ApiCardComponent {

5
frontend/app/features/dashboard/pages/cards/api-performance-card.component.ts

@ -6,16 +6,13 @@
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { AppDto, CallsUsageDto, fadeAnimation } from '@app/shared';
import { AppDto, CallsUsageDto } from '@app/shared';
import { ChartHelpers, ChartOptions } from './shared';
@Component({
selector: 'sqx-api-performance-card[app][usage]',
styleUrls: ['./api-performance-card.component.scss'],
templateUrl: './api-performance-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ApiPerformanceCardComponent implements OnChanges {

5
frontend/app/features/dashboard/pages/cards/api-traffic-card.component.ts

@ -6,16 +6,13 @@
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { AppDto, CallsUsageDto, fadeAnimation } from '@app/shared';
import { AppDto, CallsUsageDto } from '@app/shared';
import { ChartHelpers, ChartOptions } from './shared';
@Component({
selector: 'sqx-api-traffic-card[app][usage]',
styleUrls: ['./api-traffic-card.component.scss'],
templateUrl: './api-traffic-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ApiTrafficCardComponent implements OnChanges {

5
frontend/app/features/dashboard/pages/cards/api-traffic-summary-card.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { AppDto, CallsUsageDto, fadeAnimation } from '@app/shared';
import { AppDto, CallsUsageDto } from '@app/shared';
@Component({
selector: 'sqx-api-traffic-summary-card[app][usage]',
styleUrls: ['./api-traffic-summary-card.component.scss'],
templateUrl: './api-traffic-summary-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ApiTrafficSummaryCardComponent implements OnChanges {

5
frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.ts

@ -6,16 +6,13 @@
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { AppDto, fadeAnimation, StorageUsagePerDateDto } from '@app/shared';
import { AppDto, StorageUsagePerDateDto } from '@app/shared';
import { ChartHelpers, ChartOptions } from './shared';
@Component({
selector: 'sqx-asset-uploads-count-card[app][usage]',
styleUrls: ['./asset-uploads-count-card.component.scss'],
templateUrl: './asset-uploads-count-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AssetUploadsCountCardComponent implements OnChanges {

5
frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.ts

@ -6,16 +6,13 @@
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { AppDto, fadeAnimation, StorageUsagePerDateDto } from '@app/shared';
import { AppDto, StorageUsagePerDateDto } from '@app/shared';
import { ChartHelpers, ChartOptions } from './shared';
@Component({
selector: 'sqx-asset-uploads-size-card[app][usage]',
styleUrls: ['./asset-uploads-size-card.component.scss'],
templateUrl: './asset-uploads-size-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AssetUploadsSizeCardComponent implements OnChanges {

5
frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { AppDto, CurrentStorageDto, fadeAnimation } from '@app/shared';
import { AppDto, CurrentStorageDto } from '@app/shared';
@Component({
selector: 'sqx-asset-uploads-size-summary-card[app][usage]',
styleUrls: ['./asset-uploads-size-summary-card.component.scss'],
templateUrl: './asset-uploads-size-summary-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AssetUploadsSizeSummaryCardComponent implements OnChanges {

5
frontend/app/features/dashboard/pages/cards/content-summary-card.component.ts

@ -6,7 +6,7 @@
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { AppDto, ContentsService, fadeAnimation, StatefulComponent, Types } from '@app/shared';
import { AppDto, ContentsService, StatefulComponent, Types } from '@app/shared';
interface State {
// The number of items.
@ -17,9 +17,6 @@ interface State {
selector: 'sqx-content-summary-card',
styleUrls: ['./content-summary-card.component.scss'],
templateUrl: './content-summary-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContentSummaryCardComponent extends StatefulComponent<State> implements OnInit {

5
frontend/app/features/dashboard/pages/cards/github-card.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { AppDto, fadeAnimation } from '@app/shared';
import { AppDto } from '@app/shared';
@Component({
selector: 'sqx-github-card[app]',
styleUrls: ['./github-card.component.scss'],
templateUrl: './github-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GithubCardComponent {

5
frontend/app/features/dashboard/pages/cards/history-card.component.ts

@ -6,16 +6,13 @@
*/
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { AppDto, fadeAnimation, HistoryEventDto, HistoryService } from '@app/shared';
import { AppDto, HistoryEventDto, HistoryService } from '@app/shared';
import { Observable } from 'rxjs';
@Component({
selector: 'sqx-history-card[app]',
styleUrls: ['./history-card.component.scss'],
templateUrl: './history-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HistoryCardComponent implements OnChanges {

5
frontend/app/features/dashboard/pages/cards/iframe-card.component.ts

@ -6,15 +6,12 @@
*/
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, ViewChild } from '@angular/core';
import { AppDto, fadeAnimation } from '@app/shared';
import { AppDto } from '@app/shared';
@Component({
selector: 'sqx-iframe-card[app]',
styleUrls: ['./iframe-card.component.scss'],
templateUrl: './iframe-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class IFrameCardComponent implements AfterViewInit {

5
frontend/app/features/dashboard/pages/cards/schema-card.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { AppDto, fadeAnimation } from '@app/shared';
import { AppDto } from '@app/shared';
@Component({
selector: 'sqx-schema-card[app]',
styleUrls: ['./schema-card.component.scss'],
templateUrl: './schema-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SchemaCardComponent {

5
frontend/app/features/dashboard/pages/cards/support-card.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { AppDto, fadeAnimation } from '@app/shared';
import { AppDto } from '@app/shared';
@Component({
selector: 'sqx-support-card[app]',
styleUrls: ['./support-card.component.scss'],
templateUrl: './support-card.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SupportCardComponent {

4
frontend/app/features/dashboard/pages/dashboard-config.component.html

@ -4,7 +4,7 @@
</button>
<ng-container *sqxModal="dropdownModal">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonSettings" [scrollY]="true" @fade position="bottom-right">
<sqx-dropdown-menu [sqxAnchoredTo]="buttonSettings" [scrollY]="true" position="bottom-right">
<div class="dropdown-item" *ngFor="let item of configOptions">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="field_{{item.type}}" [ngModel]="isSelected(item)" (ngModelChange)="addOrRemove(item)">
@ -30,7 +30,7 @@
confirmRememberKey="resetConfig">
{{ 'common.reset' | sqxTranslate }}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>

5
frontend/app/features/dashboard/pages/dashboard-config.component.ts

@ -6,7 +6,7 @@
*/
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { AppDto, AppsState, AuthService, DialogModel, DialogService, fadeAnimation, LocalizerService, ModalModel, Types, UIState } from '@app/shared';
import { AppDto, AppsState, AuthService, DialogModel, DialogService, LocalizerService, ModalModel, Types, UIState } from '@app/shared';
import { GridsterItem } from 'angular-gridster2';
import { take } from 'rxjs/operators';
@ -14,9 +14,6 @@ import { take } from 'rxjs/operators';
selector: 'sqx-dashboard-config',
styleUrls: ['./dashboard-config.component.scss'],
templateUrl: './dashboard-config.component.html',
animations: [
fadeAnimation,
],
})
export class DashboardConfigComponent implements OnChanges {
@Input()

2
frontend/app/features/dashboard/pages/dashboard-page.component.html

@ -5,7 +5,7 @@
<div class="dashboard-summary" *ngIf="!isScrolled" @fade>
<h1 class="dashboard-title">{{ 'dashboard.welcomeTitle' | sqxTranslate: { user: user } }}</h1>
<div class="subtext" [innerHTML]="'dashboard.welcomeText' | sqxTranslate: { app: app.displayName } | sqxMarkdown"></div>
<div class="subtext" [innerHTML]="'dashboard.welcomeText' | sqxTranslate: { app: app.displayName } | sqxMarkdown | sqxSafeHtml"></div>
</div>
<gridster [options]="gridOptions" #grid>

2
frontend/app/features/rules/pages/rule/rule-page.component.html

@ -167,7 +167,7 @@
</a>
<sqx-onboarding-tooltip helpId="help" [for]="helpLink" position="left-top" [after]="180000">
<span [innerHTML]="'common.helpTour' | sqxTranslate | sqxMarkdownInline"></span>
<span [innerHTML]="'common.helpTour' | sqxTranslate | sqxMarkdownInline | sqxSafeHtml"></span>
</sqx-onboarding-tooltip>
</div>
</ng-container>

4
frontend/app/features/rules/pages/rules/rule.component.html

@ -16,7 +16,7 @@
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" [scrollY]="true" position="bottom-right" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true" position="bottom-right">
<a class="dropdown-item" *ngIf="rule.canUpdate" [routerLink]="rule.id">
{{ 'common.edit' | sqxTranslate }}
</a>
@ -58,7 +58,7 @@
confirmRememberKey="deleteRule">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</div>
</div>

5
frontend/app/features/rules/pages/rules/rule.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ActionsDto, fadeAnimation, ModalModel, RuleDto, RulesState, TriggersDto } from '@app/shared';
import { ActionsDto, ModalModel, RuleDto, RulesState, TriggersDto } from '@app/shared';
@Component({
selector: 'sqx-rule[rule][ruleActions][ruleTriggers]',
styleUrls: ['./rule.component.scss'],
templateUrl: './rule.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RuleComponent {

2
frontend/app/features/rules/pages/rules/rules-page.component.html

@ -52,7 +52,7 @@
</a>
<sqx-onboarding-tooltip helpId="help" [for]="helpLink" position="left-top" [after]="180000">
<span [innerHTML]="'common.helpTour' | sqxTranslate | sqxMarkdownInline"></span>
<span [innerHTML]="'common.helpTour' | sqxTranslate | sqxMarkdownInline | sqxSafeHtml"></span>
</sqx-onboarding-tooltip>
</div>
</ng-container>

2
frontend/app/features/rules/shared/actions/generic-action.component.html

@ -37,7 +37,7 @@
</ng-container>
<sqx-form-hint>
<span [innerHTML]="property.description | sqxMarkdownInline"></span>
<span [sqxMarkdown]="property.description" [inline]="true"></span>
<div *ngIf="property.isFormattable">
You can use advanced formatting: <a href="https://docs.squidex.io/concepts/rules#3-formatting" sqxExternalLink>Documentation</a>

4
frontend/app/features/schemas/pages/schema/fields/field.component.html

@ -46,7 +46,7 @@
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true">
<ng-container *ngIf="field.properties.isContentField">
<a class="dropdown-item" (click)="enableField()" *ngIf="field.canEnable">
{{ 'schemas.field.enable' | sqxTranslate }}
@ -85,7 +85,7 @@
{{ 'common.delete' | sqxTranslate }}
</a>
</ng-container>
</div>
</sqx-dropdown-menu>
</ng-container>
</div>
</div>

5
frontend/app/features/schemas/pages/schema/fields/field.component.ts

@ -7,15 +7,12 @@
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { AppSettingsDto, createProperties, DialogModel, EditFieldForm, fadeAnimation, LanguageDto, ModalModel, NestedFieldDto, RootFieldDto, SchemaDto, SchemasState, sorted } from '@app/shared';
import { AppSettingsDto, createProperties, DialogModel, EditFieldForm, LanguageDto, ModalModel, NestedFieldDto, RootFieldDto, SchemaDto, SchemasState, sorted } from '@app/shared';
@Component({
selector: 'sqx-field[field][languages][schema][settings]',
styleUrls: ['./field.component.scss'],
templateUrl: './field.component.html',
animations: [
fadeAnimation,
],
})
export class FieldComponent implements OnChanges {
@Input()

2
frontend/app/features/schemas/pages/schema/fields/types/date-time-ui.component.html

@ -17,7 +17,7 @@
<input type="text" class="form-control" id="{{field.fieldId}}_fieldFormat" maxlength="100" formControlName="format">
<sqx-form-hint>
<span [innerHTML]="'schemas.fieldTypes.dateTime.formatHint' | sqxTranslate | sqxMarkdownInline"></span>
<span [innerHTML]="'schemas.fieldTypes.dateTime.formatHint' | sqxTranslate | sqxMarkdownInline | sqxSafeHtml"></span>
</sqx-form-hint>
</div>
</div>

4
frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html

@ -36,14 +36,14 @@
<ng-container *ngIf="settings.patterns.length > 0 && (showPatternSuggestions | async)">
<ng-container *sqxModal="patternsModal">
<div class="control-dropdown" [sqxAnchoredTo]="inputPattern" [scrollY]="true" position="bottom-left" @fade>
<sqx-dropdown-menu class="control-dropdown" [sqxAnchoredTo]="inputPattern" [scrollY]="true" position="bottom-left">
<h4>{{ 'schemas.fieldTypes.string.suggestions' | sqxTranslate }}</h4>
<div *ngFor="let pattern of settings.patterns" class="control-dropdown-item control-dropdown-item-selectable" (mousedown)="setPattern(pattern)">
<div class="truncate">{{pattern.name}}</div>
<div class="truncate text-muted">{{pattern.regex}}</div>
</div>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>

5
frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts

@ -7,16 +7,13 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { AppSettingsDto, fadeAnimation, FieldDto, hasNoValue$, hasValue$, LanguageDto, ModalModel, PatternDto, ResourceOwner, RootFieldDto, SchemaDto, StringFieldPropertiesDto, STRING_CONTENT_TYPES, Types, value$ } from '@app/shared';
import { AppSettingsDto, FieldDto, hasNoValue$, hasValue$, LanguageDto, ModalModel, PatternDto, ResourceOwner, RootFieldDto, SchemaDto, StringFieldPropertiesDto, STRING_CONTENT_TYPES, Types, value$ } from '@app/shared';
import { Observable } from 'rxjs';
@Component({
selector: 'sqx-string-validation[field][fieldForm][properties][schema]',
styleUrls: ['string-validation.component.scss'],
templateUrl: 'string-validation.component.html',
animations: [
fadeAnimation,
],
})
export class StringValidationComponent extends ResourceOwner implements OnChanges {
public readonly contentTypes = STRING_CONTENT_TYPES;

4
frontend/app/features/schemas/pages/schema/schema-page.component.html

@ -46,7 +46,7 @@
</button>
<ng-container *sqxModal="editOptionsDropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true">
<ng-container *ngIf="schemasState.canCreate">
<div class="dropdown-divider"></div>
@ -66,7 +66,7 @@
{{ 'common.delete' | sqxTranslate }}
</a>
</ng-container>
</div>
</sqx-dropdown-menu>
</ng-container>
<sqx-onboarding-tooltip helpId="history" [for]="buttonOptions" position="bottom-right" [after]="60000">

5
frontend/app/features/schemas/pages/schema/schema-page.component.ts

@ -7,7 +7,7 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { defined, fadeAnimation, MessageBus, ModalModel, ResourceOwner, SchemaDto, SchemasState } from '@app/shared';
import { defined, MessageBus, ModalModel, ResourceOwner, SchemaDto, SchemasState } from '@app/shared';
import { map } from 'rxjs/operators';
import { SchemaCloning } from './../messages';
@ -15,9 +15,6 @@ import { SchemaCloning } from './../messages';
selector: 'sqx-schema-page',
styleUrls: ['./schema-page.component.scss'],
templateUrl: './schema-page.component.html',
animations: [
fadeAnimation,
],
})
export class SchemaPageComponent extends ResourceOwner implements OnInit {
public readonly exact = { exact: true };

8
frontend/app/features/settings/pages/clients/client-connect-form.component.html

@ -34,7 +34,7 @@
<h3>{{ 'clients.connectWizard.step1Title' | sqxTranslate }}</h3>
<sqx-form-hint>
<span [innerHTML]="'clients.connectWizard.postManDocs' | sqxTranslate | sqxMarkdownInline"></span>
<span [innerHTML]="'clients.connectWizard.postManDocs' | sqxTranslate | sqxMarkdownInline | sqxSafeHtml"></span>
</sqx-form-hint>
<div class="section">
@ -97,7 +97,7 @@
<div class="section step">
<h5><span class="badge rounded-pill badge-dark">1</span> {{ 'clients.connectWizard.cliStep1' | sqxTranslate }}</h5>
<div [innerHTML]="'clients.connectWizard.cliStep1Download' | sqxTranslate | sqxMarkdown"></div>
<div [innerHTML]="'clients.connectWizard.cliStep1Download' | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
<sqx-form-hint>
{{ 'clients.connectWizard.cliStep1Hint' | sqxTranslate }}
@ -105,7 +105,7 @@
</div>
<div class="section step">
<h5><span class="badge rounded-pill badge-dark">2</span> <span [innerHTML]="'clients.connectWizard.cliStep2' | sqxTranslate | sqxMarkdownInline"></span></h5>
<h5><span class="badge rounded-pill badge-dark">2</span> <span [innerHTML]="'clients.connectWizard.cliStep2' | sqxTranslate | sqxMarkdownInline | sqxSafeHtml"></span></h5>
</div>
<div class="section step">
@ -132,7 +132,7 @@
<div class="section step">
<h5><span class="badge rounded-pill badge-dark">1</span> {{ 'clients.connectWizard.sdkStep1' | sqxTranslate }}</h5>
<div [innerHTML]="'clients.connectWizard.sdkStep1Download' | sqxTranslate | sqxMarkdown"></div>
<div [innerHTML]="'clients.connectWizard.sdkStep1Download' | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
<p>
<sqx-code>dotnet add package Squidex.ClientLibrary</sqx-code>

24
frontend/app/framework/angular/animations.ts

@ -76,30 +76,6 @@ export function buildFadeAnimation(name = 'fade', timing = '150ms'): AnimationTr
);
}
export function buildHeightAnimation(name = 'height', timing = '200ms'): AnimationTriggerMetadata {
return trigger(
name, [
transition(':enter', [
style({ height: '0px' }),
animate(timing, style({ height: '*' })),
]),
transition(':leave', [
style({ height: '*' }),
animate(timing, style({ height: '0px' })),
]),
state('true',
style({ height: '*' }),
),
state('false',
style({ height: '0px' }),
),
transition('1 => 0', animate(timing)),
transition('0 => 1', animate(timing)),
],
);
}
export const fadeAnimation = buildFadeAnimation();
export const heightAnimation = buildHeightAnimation();
export const slideAnimation = buildSlideAnimation();
export const slideRightAnimation = buildSlideRightAnimation();

1
frontend/app/framework/angular/dropdown-menu.component.html

@ -0,0 +1 @@
<ng-content></ng-content>

0
frontend/app/framework/angular/dropdown-menu.component.scss

37
frontend/app/framework/angular/dropdown-menu.component.ts

@ -0,0 +1,37 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component, ElementRef, HostBinding } from '@angular/core';
import { fadeAnimation } from './animations';
@Component({
selector: 'sqx-dropdown-menu',
styleUrls: ['./dropdown-menu.component.scss'],
templateUrl: './dropdown-menu.component.html',
host: {
class: 'dropdown-menu',
},
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DropdownMenuComponent {
@HostBinding('@fade')
public get fade() {
return true;
}
public get nativeElement() {
return this.element.nativeElement;
}
constructor(
private readonly element: ElementRef,
) {
}
}

7
frontend/app/framework/angular/forms/control-errors-messages.component.html

@ -0,0 +1,7 @@
<div class="errors-container" *ngIf="errorMessages.length > 0" @fade>
<div class="errors">
<ng-container *ngFor="let message of errorMessages">
{{message}}
</ng-container>
</div>
</div>

4
frontend/app/framework/angular/forms/control-errors-messages.component.scss

@ -0,0 +1,4 @@
:host {
@include force-width(100%);
align-self: flex-start;
}

23
frontend/app/framework/angular/forms/control-errors-messages.component.ts

@ -0,0 +1,23 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights r vbeserved
*/
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { fadeAnimation } from '@app/framework/internal';
@Component({
selector: 'sqx-errors-messages',
styleUrls: ['./control-errors-messages.component.scss'],
templateUrl: './control-errors-messages.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ControlErrorsMessagesComponent {
@Input()
public errorMessages: ReadonlyArray<string>;
}

8
frontend/app/framework/angular/forms/control-errors.component.html

@ -1,7 +1 @@
<div class="errors-container" *ngIf="snapshot.errorMessages.length > 0" @fade>
<div class="errors">
<ng-container *ngFor="let message of snapshot.errorMessages">
{{message}}
</ng-container>
</div>
</div>
<sqx-errors-messages [errorMessages]="snapshot.errorMessages" *ngIf="snapshot.errorMessages.length > 0"></sqx-errors-messages>

9
frontend/app/framework/angular/forms/control-errors.component.ts

@ -7,7 +7,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Host, Input, OnChanges, OnDestroy, Optional } from '@angular/core';
import { AbstractControl, FormArray, FormGroupDirective } from '@angular/forms';
import { fadeAnimation, LocalizerService, StatefulComponent, Types } from '@app/framework/internal';
import { LocalizerService, StatefulComponent, Types } from '@app/framework/internal';
import { merge } from 'rxjs';
import { touchedChange$ } from './forms-helper';
import { formatError } from './error-formatting';
@ -21,9 +21,6 @@ interface State {
selector: 'sqx-control-errors[for]',
styleUrls: ['./control-errors.component.scss'],
templateUrl: './control-errors.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ControlErrorsComponent extends StatefulComponent<State> implements OnChanges, OnDestroy {
@ -115,6 +112,10 @@ export class ControlErrorsComponent extends StatefulComponent<State> implements
}
}
if (errorMessages.length === 0 && this.snapshot.errorMessages.length === 0) {
return;
}
this.next({ errorMessages });
}
}

6
frontend/app/framework/angular/forms/editors/autocomplete.component.html

@ -16,18 +16,18 @@
</div>
<ng-container *sqxModal="snapshot.suggestedItems.length > 0">
<div class="control-dropdown" [sqxAnchoredTo]="input" [scrollY]="true" [style.width]="dropdownWidth" [position]="dropdownPosition" #container @fade>
<sqx-dropdown-menu class="control-dropdown" [sqxAnchoredTo]="input" [scrollY]="true" [style.width]="dropdownWidth" [position]="dropdownPosition" #container>
<div *ngFor="let item of snapshot.suggestedItems; let i = index" class="control-dropdown-item control-dropdown-item-selectable"
[class.active]="i === snapshot.suggestedIndex"
(mousedown)="selectItem(item)"
(mouseover)="selectIndex(i)"
[sqxScrollActive]="i === snapshot.suggestedIndex"
[sqxScrollContainer]="container">
[sqxScrollContainer]="$any(container)">
<ng-container *ngIf="!itemTemplate">{{item}}</ng-container>
<ng-template *ngIf="itemTemplate" [sqxTemplateWrapper]="itemTemplate" [item]="item" [index]="i"></ng-template>
</div>
</div>
</sqx-dropdown-menu>
</ng-container>
</div>

5
frontend/app/framework/angular/forms/editors/autocomplete.component.ts

@ -7,7 +7,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ElementRef, forwardRef, Input, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fadeAnimation, Keys, StatefulControlComponent, Types } from '@app/framework/internal';
import { Keys, StatefulControlComponent, Types } from '@app/framework/internal';
import { RelativePosition } from '@app/shared';
import { Observable, of } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged, finalize, map, switchMap, tap } from 'rxjs/operators';
@ -43,9 +43,6 @@ const NO_EMIT = { emitEvent: false };
providers: [
SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR,
],
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AutocompleteComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements OnInit, OnDestroy {

4
frontend/app/framework/angular/forms/editors/dropdown.component.html

@ -15,7 +15,7 @@
<div class="items-container">
<ng-container *sqxModal="dropdown">
<div class="control-dropdown" [sqxAnchoredTo]="input" [scrollY]="true" position="bottom-left">
<sqx-dropdown-menu [sqxAnchoredTo]="input" [scrollY]="true" position="bottom-left">
<div *ngIf="canSearch" class="search-form">
<input class="form-control search" [formControl]="queryInput" placeholder="{{ 'contributors.search' | sqxTranslate }}" (keydown)="onKeyDown($event)" sqxFocusOnInit>
</div>
@ -32,6 +32,6 @@
<ng-template *ngIf="templateItem" [sqxTemplateWrapper]="templateItem" [item]="item" [index]="i" [context]="snapshot.query"></ng-template>
</div>
</div>
</div>
</sqx-dropdown-menu>
</ng-container>
</div>

5
frontend/app/framework/angular/forms/editors/localized-input.component.ts

@ -7,7 +7,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { fadeAnimation, ModalModel, StatefulControlComponent, Types } from '@app/framework/internal';
import { ModalModel, StatefulControlComponent, Types } from '@app/framework/internal';
import { Language } from './../../language-selector.component';
export const SQX_LOCALIZED_INPUT_CONTROL_VALUE_ACCESSOR: any = {
@ -28,9 +28,6 @@ interface State {
providers: [
SQX_LOCALIZED_INPUT_CONTROL_VALUE_ACCESSOR,
],
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LocalizedInputComponent extends StatefulControlComponent<State, { [key: string]: any }> {

10
frontend/app/framework/angular/forms/editors/tag-editor.component.html

@ -29,22 +29,22 @@
</div>
<ng-container *sqxModal="snapshot.suggestedItems.length > 0">
<div class="control-dropdown" [sqxAnchoredTo]="form" [scrollY]="true" position="bottom-left" #container @fade>
<sqx-dropdown-menu class="control-dropdown" [sqxAnchoredTo]="form" [scrollY]="true" position="bottom-left" #container>
<div *ngFor="let item of snapshot.suggestedItems; let i = index" class="control-dropdown-item control-dropdown-item-selectable"
[class.active]="i === snapshot.suggestedIndex"
[class.separated]="separated"
(mousedown)="selectValue(item)"
(mouseover)="selectIndex(i)"
[sqxScrollActive]="i === snapshot.suggestedIndex"
[sqxScrollContainer]="container">
[sqxScrollContainer]="container.nativeElement">
<ng-container>{{item}}</ng-container>
</div>
</div>
</sqx-dropdown-menu>
</ng-container>
<ng-container *ngIf="allowOpen || suggestionsSorted.length > 0">
<ng-container *sqxModal="suggestionsModal">
<div class="control-dropdown suggestions-dropdown" [sqxAnchoredTo]="form" position="bottom-left" @fade>
<sqx-dropdown-menu class="control-dropdown suggestions-dropdown" [sqxAnchoredTo]="form" position="bottom-left">
<div class="row">
<div class=" col-6" *ngFor="let item of suggestionsSorted; let i = index">
<div class="form-check form-check">
@ -59,7 +59,7 @@
</div>
</div>
</div>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>
</div>

31
frontend/app/framework/angular/forms/editors/tag-editor.component.ts

@ -7,7 +7,7 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fadeAnimation, getTagValues, Keys, ModalModel, StatefulControlComponent, StringConverter, TagValue, Types } from '@app/framework/internal';
import { getTagValues, Keys, ModalModel, StatefulControlComponent, StringConverter, TagValue, Types } from '@app/framework/internal';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
export const SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
@ -37,13 +37,11 @@ interface State {
providers: [
SQX_TAG_EDITOR_CONTROL_VALUE_ACCESSOR,
],
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TagEditorComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements AfterViewInit, OnChanges, OnInit {
private latestValue: any;
private latestInput: string;
@ViewChild('form', { static: false })
public formElement: ElementRef<HTMLElement>;
@ -104,6 +102,17 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
@Input()
public set suggestions(value: ReadonlyArray<string | TagValue> | undefined | null) {
this.suggestionsSorted = getTagValues(value);
if (this.addInput.value) {
const query = this.addInput.value;
const items = this.suggestionsSorted.filter(s => s.lowerCaseName.indexOf(query) >= 0 && !this.snapshot.items.find(x => x.id === s.id));
this.next({
suggestedIndex: -1,
suggestedItems: items || [],
});
}
}
public suggestionsSorted: ReadonlyArray<TagValue> = [];
@ -126,7 +135,7 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
public ngOnChanges(changes: SimpleChanges) {
if (changes['converter']) {
this.writeValue(this.latestValue);
this.writeValue(this.latestValue, true);
}
}
@ -146,7 +155,11 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
tap(query => {
if (!query) {
this.resetAutocompletion();
} else if (!this.latestInput) {
this.open.emit();
}
this.latestInput = query;
}),
distinctUntilChanged(),
map(query => {
@ -158,19 +171,21 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
return [];
}
}))
.subscribe(items => {
.subscribe(suggestedItems => {
this.next({
suggestedIndex: -1,
suggestedItems: items || [],
suggestedItems,
});
}));
}
public writeValue(obj: any) {
public writeValue(obj: any, noForm = false) {
this.latestValue = obj;
if (!noForm) {
this.resetForm();
this.resetSize();
}
const items: any[] = [];

2
frontend/app/framework/angular/forms/form-error.component.html

@ -5,7 +5,7 @@
<i class="icon-close"></i>
</span>
<div [innerHTML]="error | sqxTranslate | sqxMarkdown"></div>
<div [innerHTML]="error | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
</div>
</div>
</ng-container>

4
frontend/app/framework/angular/language-selector.component.html

@ -10,7 +10,7 @@
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="button" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="button" [scrollY]="true">
<table>
<tbody>
<tr class="dropdown-item" *ngFor="let supported of languages; trackBy: trackByLanguage" [class.active]="supported === language" (click)="selectLanguage(supported)">
@ -19,6 +19,6 @@
</tr>
</tbody>
</table>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>

5
frontend/app/framework/angular/language-selector.component.ts

@ -6,7 +6,7 @@
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { fadeAnimation, ModalModel } from '@app/framework/internal';
import { ModalModel } from '@app/framework/internal';
export interface Language { iso2Code: string; englishName: string; isMasterLanguage?: boolean }
@ -14,9 +14,6 @@ export interface Language { iso2Code: string; englishName: string; isMasterLangu
selector: 'sqx-language-selector[language][languages]',
styleUrls: ['./language-selector.component.scss'],
templateUrl: './language-selector.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LanguageSelectorComponent implements OnChanges, OnInit {

5
frontend/app/framework/angular/list-view.component.ts

@ -6,7 +6,7 @@
*/
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostBinding, Input, Renderer2, ViewChild } from '@angular/core';
import { fadeAnimation, StatefulComponent } from '@app/framework/internal';
import { StatefulComponent } from '@app/framework/internal';
interface State {
// True when loading.
@ -17,9 +17,6 @@ interface State {
selector: 'sqx-list-view',
styleUrls: ['./list-view.component.scss'],
templateUrl: './list-view.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.Default,
})
export class ListViewComponent extends StatefulComponent<State> implements AfterViewInit {

68
frontend/app/framework/angular/markdown.directive.ts

@ -0,0 +1,68 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Directive, ElementRef, Input, OnChanges, Renderer2 } from '@angular/core';
import marked from 'marked';
const RENDERER_DEFAULT = new marked.Renderer();
const RENDERER_INLINE = new marked.Renderer();
RENDERER_DEFAULT.link = (href, _, text) => {
if (href && href.startsWith('mailto')) {
return text;
} else {
return `<a href="${href}" target="_blank", rel="noopener">${text} <i class="icon-external-link"></i></a>`;
}
};
RENDERER_INLINE.paragraph = (text) => {
return text;
};
RENDERER_INLINE.link = RENDERER_DEFAULT.link;
@Directive({
selector: '[sqxMarkdown]',
})
export class MarkdownDirective implements OnChanges {
@Input('sqxMarkdown')
public markdown: string;
@Input()
public inline = true;
@Input()
public optional = false;
constructor(
private readonly element: ElementRef,
private readonly renderer: Renderer2,
) {
}
public ngOnChanges() {
let html = '';
const markdown = this.markdown;
if (!markdown) {
html = markdown;
} else if (this.optional && markdown.indexOf('!') !== 0) {
html = markdown;
} else if (this.markdown) {
const renderer = this.inline ? RENDERER_INLINE : RENDERER_DEFAULT;
html = marked(this.markdown, { renderer });
}
if (!html || html === this.markdown || html.indexOf('<') < 0) {
this.renderer.setProperty(this.element.nativeElement, 'textContent', html);
} else {
this.renderer.setProperty(this.element.nativeElement, 'innerHTML', html);
}
}
}

4
frontend/app/framework/angular/modals/dialog-renderer.component.html

@ -7,7 +7,7 @@
</ng-container>
<ng-container content>
<span [innerHTML]="request.text | sqxTranslate | sqxMarkdown"></span>
<span [innerHTML]="request.text | sqxTranslate | sqxMarkdown | sqxSafeHtml"></span>
<div class="form-check mt-4" *ngIf="request.canRemember">
<input class="form-check-input" type="checkbox" id="remember" [(ngModel)]="request.remember">
@ -35,7 +35,7 @@
<button type="button" class="btn-close" data-dismiss="alert" (close)="close(notification)"></button>
<div class="shadow"></div>
<div class="shadowed" [innerHTML]="notification.message | sqxTranslate | sqxMarkdown"></div>
<div class="shadowed" [innerHTML]="notification.message | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
</div>
</div>

3
frontend/app/framework/declarations.ts

@ -7,9 +7,11 @@
export * from './angular/avatar.component';
export * from './angular/code.component';
export * from './angular/dropdown-menu.component';
export * from './angular/external-link.directive';
export * from './angular/forms/confirm-click.directive';
export * from './angular/forms/control-errors.component';
export * from './angular/forms/control-errors-messages.component';
export * from './angular/forms/copy.directive';
export * from './angular/forms/editable-title.component';
export * from './angular/forms/editors/autocomplete.component';
@ -45,6 +47,7 @@ export * from './angular/language-selector.component';
export * from './angular/layout-container.directive';
export * from './angular/layout.component';
export * from './angular/list-view.component';
export * from './angular/markdown.directive';
export * from './angular/modals/dialog-renderer.component';
export * from './angular/modals/modal-dialog.component';
export * from './angular/modals/modal-placement.directive';

7
frontend/app/framework/module.ts

@ -11,7 +11,7 @@ import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { ColorPickerModule } from 'ngx-color-picker';
import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, ConfirmClickDirective, ControlErrorsComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, HighlightPipe, HoverBackgroundDirective, ImageSourceDirective, IndeterminateValueDirective, ISODatePipe, KeysPipe, KNumberPipe, LanguageSelectorComponent, LayoutComponent, LayoutContainerDirective, LightenPipe, ListViewComponent, LoadingInterceptor, LoadingService, LocalizedInputComponent, LocalStoreService, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MoneyPipe, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeResourceUrlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutDirective, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, StopDragDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, ToolbarComponent, TooltipDirective, TransformInputDirective, TranslatePipe, VideoPlayerComponent } from './declarations';
import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, ConfirmClickDirective, ControlErrorsComponent, ControlErrorsMessagesComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DropdownMenuComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, HighlightPipe, HoverBackgroundDirective, ImageSourceDirective, IndeterminateValueDirective, ISODatePipe, KeysPipe, KNumberPipe, LanguageSelectorComponent, LayoutComponent, LayoutContainerDirective, LightenPipe, ListViewComponent, LoadingInterceptor, LoadingService, LocalizedInputComponent, LocalStoreService, MarkdownDirective, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MoneyPipe, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeResourceUrlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutDirective, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, StopDragDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, ToolbarComponent, TooltipDirective, TransformInputDirective, TranslatePipe, VideoPlayerComponent } from './declarations';
@NgModule({
imports: [
@ -30,6 +30,7 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc
ColorPickerComponent,
ConfirmClickDirective,
ControlErrorsComponent,
ControlErrorsMessagesComponent,
CopyDirective,
DarkenPipe,
DatePipe,
@ -39,6 +40,7 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc
DialogRendererComponent,
DisplayNamePipe,
DropdownComponent,
DropdownMenuComponent,
DurationPipe,
EditableTitleComponent,
ExternalLinkDirective,
@ -63,6 +65,7 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc
LightenPipe,
ListViewComponent,
LocalizedInputComponent,
MarkdownDirective,
MarkdownInlinePipe,
MarkdownPipe,
ModalDialogComponent,
@ -121,6 +124,7 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc
DialogRendererComponent,
DisplayNamePipe,
DropdownComponent,
DropdownMenuComponent,
DurationPipe,
EditableTitleComponent,
ExternalLinkDirective,
@ -146,6 +150,7 @@ import { AnalyticsService, AutocompleteComponent, AvatarComponent, CachingInterc
LightenPipe,
ListViewComponent,
LocalizedInputComponent,
MarkdownDirective,
MarkdownInlinePipe,
MarkdownPipe,
ModalDialogComponent,

4
frontend/app/shared/components/assets/asset-folder.component.html

@ -14,7 +14,7 @@
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" [scrollY]="true" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true">
<ng-container *ngIf="canUpdate">
<a class="dropdown-item" (click)="editDialog.show()">
{{ 'common.rename' | sqxTranslate }}
@ -30,7 +30,7 @@
confirmRememberKey="deleteAssetFolder">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>
</div>

5
frontend/app/shared/components/assets/asset-folder.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { AssetFolderDto, AssetPathItem, DialogModel, fadeAnimation, ModalModel, Types } from '@app/shared/internal';
import { AssetFolderDto, AssetPathItem, DialogModel, ModalModel, Types } from '@app/shared/internal';
@Component({
selector: 'sqx-asset-folder',
styleUrls: ['./asset-folder.component.scss'],
templateUrl: './asset-folder.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AssetFolderComponent {

4
frontend/app/shared/components/assets/asset-uploader.component.html

@ -9,7 +9,7 @@
</span>
<ng-container *sqxModal="modalMenu;onRoot:false">
<div class="dropdown-menu container" (sqxDropFile)="addFiles($event)" @fade>
<sqx-dropdown-menu class="container" (sqxDropFile)="addFiles($event)">
<div class="uploads">
<small class="uploads-empty text-muted" *ngIf="uploads.length === 0">
{{ 'assets.uploaderUploadHere' | sqxTranslate }}
@ -46,7 +46,7 @@
</div>
</div>
</div>
</div>
</sqx-dropdown-menu>
</ng-container>
</li>
</ul>

5
frontend/app/shared/components/assets/asset-uploader.component.ts

@ -6,15 +6,12 @@
*/
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { AppsState, AssetsState, AssetUploaderState, fadeAnimation, ModalModel, Upload } from '@app/shared/internal';
import { AppsState, AssetsState, AssetUploaderState, ModalModel, Upload } from '@app/shared/internal';
@Component({
selector: 'sqx-asset-uploader',
styleUrls: ['./asset-uploader.component.scss'],
templateUrl: './asset-uploader.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AssetUploaderComponent {

2
frontend/app/shared/components/comments/comment.component.html

@ -10,7 +10,7 @@
<div class="user-ref">{{comment.user | sqxUserNameRef}}</div>
</div>
<div [innerHTML]="comment.text | sqxMarkdown"></div>
<div [innerHTML]="comment.text | sqxMarkdown | sqxSafeHtml"></div>
<div class="comment-created text-muted">
<ng-container *ngIf="canFollow && comment.url">

5
frontend/app/shared/components/schema-category.component.ts

@ -7,7 +7,7 @@
import { CdkDragDrop, CdkDragStart } from '@angular/cdk/drag-drop';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { fadeAnimation, LocalStoreService, SchemaCategory, SchemaDto, SchemasState } from '@app/shared/internal';
import { LocalStoreService, SchemaCategory, SchemaDto, SchemasState } from '@app/shared/internal';
const ITEM_HEIGHT = 2.5;
@ -15,9 +15,6 @@ const ITEM_HEIGHT = 2.5;
selector: 'sqx-schema-category[schemaCategory]',
styleUrls: ['./schema-category.component.scss'],
templateUrl: './schema-category.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SchemaCategoryComponent implements OnChanges {

2
frontend/app/shared/components/search/search-form.component.html

@ -81,7 +81,7 @@
[languages]="languages">
</sqx-query>
<div class="link" [innerHTML]="'search.help' | sqxTranslate | sqxMarkdown"></div>
<div class="link" [innerHTML]="'search.help' | sqxTranslate | sqxMarkdown | sqxSafeHtml"></div>
</div>
</ng-container>

4
frontend/app/shell/pages/internal/apps-menu.component.html

@ -12,7 +12,7 @@
<ng-container *ngIf="appsState.apps | async; let apps">
<ng-container *sqxModal="appsMenu;closeAlways:true;onRoot:false">
<div class="dropdown-menu" [sqxAnchoredTo]="button" [scrollY]="true" position="bottom-left" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="button" [scrollY]="true" position="bottom-left">
<a class="dropdown-item" routerLink="/app">
<div class="row g-0 align-items-center">
<div class="col">
@ -43,7 +43,7 @@
</button>
</div>
</ng-container>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>
</li>

5
frontend/app/shell/pages/internal/apps-menu.component.ts

@ -9,7 +9,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AppDto, AppsState, DialogModel, fadeAnimation, ModalModel, Title, TitleService, UIState } from '@app/shared';
import { AppDto, AppsState, DialogModel, ModalModel, Title, TitleService, UIState } from '@app/shared';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@ -17,9 +17,6 @@ import { map } from 'rxjs/operators';
selector: 'sqx-apps-menu',
styleUrls: ['./apps-menu.component.scss'],
templateUrl: './apps-menu.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppsMenuComponent {

4
frontend/app/shell/pages/internal/notifications-menu.component.html

@ -11,7 +11,7 @@
</li>
<ng-container *sqxModal="modalMenu;onRoot:false">
<div class="dropdown-menu" [scrollTop]="scrollMe.scrollHeight" [sqxAnchoredTo]="button" [offset]="10" @fade #scrollMe>
<sqx-dropdown-menu [scrollTop]="scrollMe.nativeElement.scrollHeight" [sqxAnchoredTo]="button" [offset]="10" #scrollMe>
<ng-container *ngIf="commentsState.comments | async; let comments">
<small class="text-muted" *ngIf="comments.length === 0">
{{ 'notifications.empty' | sqxTranslate}}
@ -26,7 +26,7 @@
[userToken]="userToken">
</sqx-comment>
</ng-container>
</div>
</sqx-dropdown-menu>
</ng-container>
</ng-container>
</ul>

5
frontend/app/shell/pages/internal/notifications-menu.component.ts

@ -8,7 +8,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { timer } from 'rxjs';
import { onErrorResumeNext, switchMap, tap } from 'rxjs/operators';
import { AuthService, CommentDto, CommentsService, CommentsState, DialogService, fadeAnimation, LocalStoreService, ModalModel, ResourceOwner, UIOptions } from '@app/shared';
import { AuthService, CommentDto, CommentsService, CommentsState, DialogService, LocalStoreService, ModalModel, ResourceOwner, UIOptions } from '@app/shared';
const CONFIG_KEY = 'notifications.version';
@ -16,9 +16,6 @@ const CONFIG_KEY = 'notifications.version';
selector: 'sqx-notifications-menu',
styleUrls: ['./notifications-menu.component.scss'],
templateUrl: './notifications-menu.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NotificationsMenuComponent extends ResourceOwner implements OnInit {

4
frontend/app/shell/pages/internal/profile-menu.component.html

@ -9,7 +9,7 @@
</ul>
<ng-container *sqxModal="modalMenu;onRoot:false;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="button" [scrollY]="true" [offset]="10" @fade>
<sqx-dropdown-menu [sqxAnchoredTo]="button" [scrollY]="true" [offset]="10">
<a class="dropdown-item dropdown-info" [sqxPopupLink]="snapshot.profileUrl">
<div>{{ 'profile.userEmail' | sqxTranslate }}</div>
@ -43,5 +43,5 @@
<a class="dropdown-item" (click)="logout()" sqxExternalLink>
{{ 'common.logout' | sqxTranslate }}
</a>
</div>
</sqx-dropdown-menu>
</ng-container>

5
frontend/app/shell/pages/internal/profile-menu.component.ts

@ -6,7 +6,7 @@
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ApiUrlConfig, AuthService, Cookies, fadeAnimation, ModalModel, StatefulComponent, UILanguages, UIOptions, UIState } from '@app/shared';
import { ApiUrlConfig, AuthService, Cookies, ModalModel, StatefulComponent, UILanguages, UIOptions, UIState } from '@app/shared';
interface State {
// The display name of the user.
@ -29,9 +29,6 @@ interface State {
selector: 'sqx-profile-menu',
styleUrls: ['./profile-menu.component.scss'],
templateUrl: './profile-menu.component.html',
animations: [
fadeAnimation,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProfileMenuComponent extends StatefulComponent<State> implements OnInit {

Loading…
Cancel
Save