@sumaris-net/ngx-components 21.4.3 → 21.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +26 -15
- package/doc/changelog.md +17 -3
- package/fesm2022/sumaris-net.ngx-components.mjs +822 -50
- package/fesm2022/sumaris-net.ngx-components.mjs.map +1 -1
- package/package.json +1 -1
- package/src/assets/i18n/en-US.json +21 -0
- package/src/assets/i18n/en.json +21 -1
- package/src/assets/i18n/fr.json +21 -1
- package/src/assets/manifest.json +1 -1
- package/types/sumaris-net.ngx-components.d.ts +273 -46
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Directive, inject, Pipe, Injectable, EventEmitter, Output, Optional, Inject, NgModule, forwardRef, ElementRef, booleanAttribute, ViewChildren, Input, ChangeDetectionStrategy, Component, HostListener, HostBinding, numberAttribute, ViewChild, isDevMode, ANIMATION_MODULE_TYPE, RendererStyleFlags2, DOCUMENT, TemplateRef, ContentChild, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, Self, ViewEncapsulation, Injector, provideAppInitializer, APP_INITIALIZER, DestroyRef, input, output, Renderer2, effect,
|
|
3
|
-
import { firstValueFrom, shareReplay, tap, of, timer, Subject, merge, delay, isObservable, from, Subscription, BehaviorSubject, fromEvent, noop as noop$a, Observable, forkJoin, timeout, defer, combineLatest, debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1, mergeMap as mergeMap$1, switchMap as switchMap$1,
|
|
2
|
+
import { InjectionToken, Directive, inject, Pipe, Injectable, EventEmitter, Output, Optional, Inject, NgModule, forwardRef, ElementRef, booleanAttribute, ViewChildren, Input, ChangeDetectionStrategy, Component, HostListener, HostBinding, numberAttribute, ViewChild, isDevMode, ANIMATION_MODULE_TYPE, RendererStyleFlags2, DOCUMENT, TemplateRef, ContentChild, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, Self, ViewEncapsulation, viewChild, signal, computed, Injector, provideAppInitializer, APP_INITIALIZER, DestroyRef, input, output, Renderer2, effect, SecurityContext, EnvironmentInjector, runInInjectionContext, viewChildren, model, untracked } from '@angular/core';
|
|
3
|
+
import { firstValueFrom, shareReplay, tap, of, timer, Subject, merge, delay, isObservable, from, Subscription, BehaviorSubject, fromEvent, EMPTY, noop as noop$a, Observable, forkJoin, timeout, defer, combineLatest, debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1, mergeMap as mergeMap$1, switchMap as switchMap$1, interval } from 'rxjs';
|
|
4
4
|
import { catchError, map, filter, takeUntil, first, switchMap, tap as tap$1, throttleTime, debounceTime, startWith, distinctUntilChanged, mergeMap, skip, finalize, distinctUntilKeyChanged, take } from 'rxjs/operators';
|
|
5
5
|
import * as cloneImported from 'clone';
|
|
6
6
|
import * as i2$3 from '@angular/common';
|
|
7
|
-
import { CommonModule, APP_BASE_HREF, LocationStrategy,
|
|
7
|
+
import { CommonModule, Location, APP_BASE_HREF, LocationStrategy, NgIf, AsyncPipe, JsonPipe } from '@angular/common';
|
|
8
8
|
import { CdkTableModule } from '@angular/cdk/table';
|
|
9
9
|
import * as i1$4 from '@angular/cdk/a11y';
|
|
10
10
|
import { A11yModule, FocusMonitor } from '@angular/cdk/a11y';
|
|
@@ -109,9 +109,9 @@ import { base64Decode, cryptoWaitReady, ed25519PairFromSeed, ed25519Sign, base64
|
|
|
109
109
|
import * as i4$5 from '@angular/cdk/drag-drop';
|
|
110
110
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
111
111
|
import * as i4$3 from 'ngx-markdown';
|
|
112
|
-
import { MarkdownModule, MarkedRenderer, MARKED_OPTIONS, SANITIZE } from 'ngx-markdown';
|
|
112
|
+
import { MarkdownModule, MarkedRenderer, MARKED_OPTIONS, MARKED_EXTENSIONS, SANITIZE } from 'ngx-markdown';
|
|
113
113
|
import * as i1$5 from '@angular/router';
|
|
114
|
-
import {
|
|
114
|
+
import { ActivatedRoute, Router, RouterModule, RouteReuseStrategy, NavigationEnd } from '@angular/router';
|
|
115
115
|
import { Drivers } from '@ionic/storage';
|
|
116
116
|
import * as LocalForage from 'localforage';
|
|
117
117
|
import * as CordovaSQLiteDriver from 'localforage-cordovasqlitedriver';
|
|
@@ -121,7 +121,7 @@ import * as i4$4 from 'ngx-jdenticon';
|
|
|
121
121
|
import { NgxJdenticonModule } from 'ngx-jdenticon';
|
|
122
122
|
import { __decorate, __param } from 'tslib';
|
|
123
123
|
import { Apollo, gql as gql$1, provideApollo } from 'apollo-angular';
|
|
124
|
-
import { ApolloLink, NetworkStatus, gql, InMemoryCache } from '@apollo/client/core';
|
|
124
|
+
import { ApolloLink, NetworkStatus, gql, InMemoryCache, DocumentTransform } from '@apollo/client/core';
|
|
125
125
|
import { trigger, transition, style, animate, state } from '@angular/animations';
|
|
126
126
|
import { Network } from '@capacitor/network';
|
|
127
127
|
import * as i3$6 from '@rx-angular/state';
|
|
@@ -130,6 +130,7 @@ import { SplashScreen } from '@capacitor/splash-screen';
|
|
|
130
130
|
import * as i4$2 from 'ionic-cache';
|
|
131
131
|
import { CacheModule } from 'ionic-cache';
|
|
132
132
|
import { getMainDefinition } from '@apollo/client/utilities';
|
|
133
|
+
import { visit, Kind } from 'graphql';
|
|
133
134
|
import { v4 } from 'uuid';
|
|
134
135
|
import * as LoggerLinkModule from 'apollo-link-logger';
|
|
135
136
|
import { scryptEncode } from '@polkadot/util-crypto/scrypt/encode';
|
|
@@ -155,6 +156,8 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
|
155
156
|
import { Geolocation } from '@capacitor/geolocation';
|
|
156
157
|
import * as i3$5 from '@e-is/ngx-material-table';
|
|
157
158
|
import { ValidatorService, TableDataSource, AsyncTableDataSource } from '@e-is/ngx-material-table';
|
|
159
|
+
import { HttpLink as HttpLink$1 } from '@apollo/client/link/http';
|
|
160
|
+
import { Defer20220824Handler } from '@apollo/client/incremental';
|
|
158
161
|
import { persistCacheSync } from 'apollo4-cache-persist';
|
|
159
162
|
import { HttpLink } from 'apollo-angular/http';
|
|
160
163
|
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
|
|
@@ -163,7 +166,6 @@ import { RetryLink } from '@apollo/client/link/retry';
|
|
|
163
166
|
import { createFragmentRegistry } from '@apollo/client/cache';
|
|
164
167
|
import { createOperation } from '@apollo/client/link/utils';
|
|
165
168
|
import { checkDocument, getOperationDefinition } from '@apollo/client/utilities/internal';
|
|
166
|
-
import { Kind } from 'graphql';
|
|
167
169
|
import 'moment-timezone';
|
|
168
170
|
|
|
169
171
|
const ENVIRONMENT = new InjectionToken('ENV');
|
|
@@ -203,6 +205,10 @@ class Environment {
|
|
|
203
205
|
offline;
|
|
204
206
|
// Apollo (graphQL) config
|
|
205
207
|
apolloFetchPolicy;
|
|
208
|
+
// Enable the GraphQL @defer directive (incremental delivery) - default: false
|
|
209
|
+
// - true: queries with @defer are sent with a fetch link, and the response is streamed (multipart/mixed). Requires a pod that supports @defer.
|
|
210
|
+
// - false: @defer directives are removed from queries (all fields are fetched in a single response)
|
|
211
|
+
enableGraphqlDefer;
|
|
206
212
|
// Default values
|
|
207
213
|
defaultLocale;
|
|
208
214
|
defaultLatLongFormat;
|
|
@@ -10895,8 +10901,9 @@ class MatAutocompleteField {
|
|
|
10895
10901
|
this._openedSubscription.unsubscribe();
|
|
10896
10902
|
this._subscription.remove(this._openedSubscription);
|
|
10897
10903
|
}
|
|
10898
|
-
|
|
10899
|
-
const
|
|
10904
|
+
// Listen every opening: the 'more items' option (that calls this function) is not recreated when the panel reopens
|
|
10905
|
+
const opened$ = merge(this.autocomplete._isOpen ? timer(0) : EMPTY, this.autocomplete.opened.pipe(debounceTime(200)));
|
|
10906
|
+
const scrollEnd$ = opened$.pipe(map(() => this.getAutocompletePanel()), filter(isNotNil), switchMap((panel) => fromScrollEndEvent(panel, threshold).pipe(takeUntil(this.autocomplete.closed))));
|
|
10900
10907
|
// Trigger fetch more, when end scroll reached
|
|
10901
10908
|
this._openedSubscription = scrollEnd$.subscribe(() => this._fetchMore$.emit());
|
|
10902
10909
|
// Register subscription
|
|
@@ -10911,8 +10918,9 @@ class MatAutocompleteField {
|
|
|
10911
10918
|
this._openedSubscription.unsubscribe();
|
|
10912
10919
|
this._subscription.remove(this._openedSubscription);
|
|
10913
10920
|
}
|
|
10914
|
-
|
|
10915
|
-
const
|
|
10921
|
+
// Listen every opening: the 'more items' option (that calls this function) is not recreated when the panel reopens
|
|
10922
|
+
const opened$ = merge(this.matSelect.panelOpen ? timer(0) : EMPTY, this.matSelect._openedStream.pipe(debounceTime(200)));
|
|
10923
|
+
const scrollEnd$ = opened$.pipe(map(() => this.getMatSelectPanel()), filter(isNotNil), switchMap((panel) => fromScrollEndEvent(panel, threshold).pipe(takeUntil(this.matSelect._closedStream))));
|
|
10916
10924
|
// Trigger fetch more, end end scroll reached
|
|
10917
10925
|
this._openedSubscription = scrollEnd$.pipe(filter(() => this._moreItemsCount > 0)).subscribe(() => this._fetchMore$.emit());
|
|
10918
10926
|
// Register subscription
|
|
@@ -14318,8 +14326,9 @@ class MatChipsField {
|
|
|
14318
14326
|
this._openedSubscription.unsubscribe();
|
|
14319
14327
|
this._subscription.remove(this._openedSubscription);
|
|
14320
14328
|
}
|
|
14321
|
-
|
|
14322
|
-
const
|
|
14329
|
+
// Listen every opening: the 'more items' option (that calls this function) is not recreated when the panel reopens
|
|
14330
|
+
const opened$ = merge(this.autocomplete._isOpen ? timer(0) : EMPTY, this.autocomplete.opened.pipe(debounceTime(200)));
|
|
14331
|
+
const scrollEnd$ = opened$.pipe(map((_) => this.getAutocompletePanel()), filter(isNotNil), switchMap((panel) => fromScrollEndEvent(panel, threshold).pipe(takeUntil(this.autocomplete.closed))));
|
|
14323
14332
|
// Trigger fetch more, end end scroll reached
|
|
14324
14333
|
this._openedSubscription = scrollEnd$.subscribe(() => this._fetchMore$.emit());
|
|
14325
14334
|
// Register subscription
|
|
@@ -17677,11 +17686,11 @@ class UploadFileComponent {
|
|
|
17677
17686
|
return waitFor(() => this.processingFilesCount === 0, opts);
|
|
17678
17687
|
}
|
|
17679
17688
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: UploadFileComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i2.TranslateService }], target: i0.ɵɵFactoryTarget.Component });
|
|
17680
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: UploadFileComponent, isStandalone: false, selector: "app-upload-file", inputs: { fileExtension: "fileExtension", fileExtensionsExcluded: "fileExtensionsExcluded", uniqueFile: "uniqueFile", instantUpload: "instantUpload", uploadFn: "uploadFn", deleteFn: "deleteFn", maxParallelUpload: "maxParallelUpload", maxFileSizeMb: "maxFileSizeMb", autoHideDropArea: "autoHideDropArea", showDropArea: "showDropArea", showThumbnails: "showThumbnails" }, outputs: { filesChange: "filesChange" }, viewQueries: [{ propertyName: "fileDropEl", first: true, predicate: ["fileDropRef"], descendants: true }], ngImport: i0, template: "@if (!showDropArea) {\n <!-- hidden file input (no drop area): used by browse() -->\n <input\n type=\"file\"\n #fileDropRef\n class=\"hidden-file-input\"\n hidden\n [multiple]=\"!uniqueFile\"\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n} @else if ((!uniqueFile && !autoHideDropArea) || files.length === 0) {\n <div class=\"container\" appDragAndDrop (fileDropped)=\"onFileDropped($event)\">\n <input\n type=\"file\"\n #fileDropRef\n id=\"fileDropRef\"\n [multiple]=\"!uniqueFile\"\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n <mat-icon style=\"font-size: xx-large\" color=\"accent\" class=\"text-size\">upload</mat-icon>\n <ion-label>\n <span\n [innerHTML]=\"\n 'FILE.UPLOAD.DRAG_AND_DROP' | translate: { extension: !fileExtension ? '' : ' (' + fileExtension + ')' }\n \"\n ></span>\n </ion-label>\n <ion-button color=\"tertiary\">{{ 'FILE.UPLOAD.BROWSE' | translate }}</ion-button>\n </div>\n}\n<!-- global progression (useful when many files are selected) -->\n@if (uploading && files.length > 1) {\n <div class=\"upload-summary\">\n <ion-label color=\"medium\">\n <small>{{ 'FILE.UPLOAD.DONE_COUNT' | translate: { count: doneFilesCount, total: files.length } }}</small>\n </ion-label>\n <ion-progress-bar [value]=\"doneFilesCount / files.length\" color=\"accent\"></ion-progress-bar>\n </div>\n}\n<div class=\"files-list\">\n <ion-list>\n @for (file of files; track file; let i = $index) {\n <ion-item>\n @if (showThumbnails && file.kind === 'image') {\n <!-- image thumbnail, or a skeleton until the image is ready -->\n <div slot=\"start\" class=\"file-thumbnail\">\n @if (file.error) {\n <mat-icon color=\"danger\">broken_image</mat-icon>\n } @else if (file.progress === 1 && file.response?.body) {\n <img [src]=\"file.response.body\" alt=\"\" />\n } @else {\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n }\n </div>\n } @else {\n <!-- icon of the file type (pdf, spreadsheet, document...) -->\n <div slot=\"start\" class=\"file-icon\" [class.file-thumbnail]=\"showThumbnails\" [attr.data-kind]=\"file.kind\">\n <mat-icon>{{ fileKindIcons[file.kind || 'other'] }}</mat-icon>\n </div>\n }\n <div class=\"single-file\">\n <ion-label>\n <h4 class=\"name\" [class.deleting]=\"file.deleting\">\n {{ file.name }}\n </h4>\n <p>\n {{ file.size | fileSize }}\n </p>\n </ion-label>\n @if (uploading && !file.error && !showThumbnails) {\n <ion-progress-bar\n [value]=\"file.progress || 0\"\n [type]=\"file.deleting || file.progress === -1 ? 'indeterminate' : 'determinate'\"\n ></ion-progress-bar>\n }\n @if (file.error) {\n <ion-text\n color=\"danger\"\n [innerHTML]=\"\n file.error\n | translate\n : {\n fileExtension: fileExtension,\n fileExtensionsExcluded: fileExtensionsExcluded.join(', '),\n maxFileSizeMb: maxFileSizeMb,\n }\n \"\n ></ion-text>\n }\n </div>\n <!-- remote from list (before importation) -->\n @if (!file.progress || file.error) {\n <button mat-icon-button slot=\"end\" (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n }\n <!-- delete file (remotely - after importation) -->\n @if (file.progress === 1 && !!deleteFn && !file.deleting) {\n <button mat-icon-button slot=\"end\" (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n }\n </ion-item>\n }\n </ion-list>\n</div>\n", styles: [".container{width:calc(100% - 3rem);min-height:200px;padding:2rem;text-align:center;border:dashed 1px #979797;position:relative;margin:1.5rem}.container input{opacity:0;position:absolute;z-index:2;width:100%;height:100%;top:0;left:0;cursor:pointer}.container ion-label{display:block;font-size:20px;font-weight:600;color:#38424c}.container label{display:inline-block;color:#fff;width:183px;height:44px;border-radius:21.5px;background-color:#db202f;padding:8px 16px}.fileover{border:solid 1px var(--ion-color-accent)}.upload-summary{padding:0 1.5rem .5rem}.upload-summary ion-progress-bar{margin-top:4px}.file-thumbnail{--thumbnail-size: 48px;width:var(--thumbnail-size);height:var(--thumbnail-size);flex-shrink:0;display:flex;align-items:center;justify-content:center;border-radius:4px;overflow:hidden}.file-thumbnail img{width:100%;height:100%;object-fit:cover}.file-thumbnail ion-skeleton-text{width:100%;height:100%;margin:0}.file-icon{display:flex;align-items:center;justify-content:center;color:var(--ion-color-medium)}.file-icon.file-thumbnail mat-icon{--icon-size: 36px;font-size:var(--icon-size);width:var(--icon-size);height:var(--icon-size)}.file-icon[data-kind=pdf]{color:#d93025}.file-icon[data-kind=document]{color:#2b579a}.file-icon[data-kind=spreadsheet]{color:#217346}.file-icon[data-kind=presentation]{color:#d24726}.file-icon[data-kind=archive]{color:#b8860b}.file-icon[data-kind=image]{color:var(--ion-color-tertiary)}.file-icon[data-kind=video],.file-icon[data-kind=audio]{color:#7b1fa2}.files-list{max-height:300px;overflow:auto}.files-list .single-file{display:flex;flex-direction:column;flex:1 1 auto}.files-list .single-file .name{word-break:break-all}.files-list .single-file .name.deleting{color:gray!important;font-style:italic!important}\n"], dependencies: [{ kind: "component", type: i2$2.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$2.IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$2.IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: i2$2.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$2.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i6$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: DragAndDropDirective, selector: "[appDragAndDrop]", outputs: ["fileDropped"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "pipe", type: FileSizePipe, name: "fileSize" }] });
|
|
17689
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: UploadFileComponent, isStandalone: false, selector: "app-upload-file", inputs: { fileExtension: "fileExtension", fileExtensionsExcluded: "fileExtensionsExcluded", uniqueFile: "uniqueFile", instantUpload: "instantUpload", uploadFn: "uploadFn", deleteFn: "deleteFn", maxParallelUpload: "maxParallelUpload", maxFileSizeMb: "maxFileSizeMb", autoHideDropArea: "autoHideDropArea", showDropArea: "showDropArea", showThumbnails: "showThumbnails" }, outputs: { filesChange: "filesChange" }, viewQueries: [{ propertyName: "fileDropEl", first: true, predicate: ["fileDropRef"], descendants: true }], ngImport: i0, template: "@if (!showDropArea) {\n <!-- hidden file input (no drop area): used by browse() -->\n <input\n type=\"file\"\n #fileDropRef\n class=\"hidden-file-input\"\n hidden\n [multiple]=\"!uniqueFile\"\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n} @else if ((!uniqueFile && !autoHideDropArea) || files.length === 0) {\n <div class=\"container\" appDragAndDrop (fileDropped)=\"onFileDropped($event)\">\n <input\n type=\"file\"\n #fileDropRef\n id=\"fileDropRef\"\n [multiple]=\"!uniqueFile\"\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n <mat-icon style=\"font-size: xx-large\" color=\"accent\" class=\"text-size\">upload</mat-icon>\n <ion-label>\n <span\n [innerHTML]=\"\n 'FILE.UPLOAD.DRAG_AND_DROP' | translate: { extension: !fileExtension ? '' : ' (' + fileExtension + ')' }\n \"\n ></span>\n </ion-label>\n <ion-button color=\"tertiary\">{{ 'FILE.UPLOAD.BROWSE' | translate }}</ion-button>\n </div>\n}\n<!-- global progression (useful when many files are selected) -->\n@if (uploading && files.length > 1) {\n <div class=\"upload-summary\">\n <ion-label color=\"medium\">\n <small>{{ 'FILE.UPLOAD.DONE_COUNT' | translate: { count: doneFilesCount, total: files.length } }}</small>\n </ion-label>\n <ion-progress-bar [value]=\"doneFilesCount / files.length\" color=\"accent\"></ion-progress-bar>\n </div>\n}\n<div class=\"files-list\">\n <ion-list>\n @for (file of files; track file; let i = $index) {\n <ion-item>\n @if (showThumbnails && file.kind === 'image') {\n <!-- image thumbnail, or a skeleton until the image is ready -->\n <div slot=\"start\" class=\"file-thumbnail\">\n @if (file.error) {\n <mat-icon color=\"danger\">broken_image</mat-icon>\n } @else if (file.progress === 1 && file.response?.body) {\n <img [src]=\"file.response.body\" alt=\"\" />\n } @else {\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n }\n </div>\n } @else {\n <!-- icon of the file type (pdf, spreadsheet, document...) -->\n <div slot=\"start\" class=\"file-icon\" [class.file-thumbnail]=\"showThumbnails\" [attr.data-kind]=\"file.kind\">\n <mat-icon>{{ fileKindIcons[file.kind || 'other'] }}</mat-icon>\n </div>\n }\n <div class=\"single-file\">\n <ion-label>\n <h4 class=\"name\" [class.deleting]=\"file.deleting\">\n {{ file.name }}\n </h4>\n <p>\n {{ file.size | fileSize }}\n </p>\n </ion-label>\n @if (uploading && !file.error && !showThumbnails) {\n <ion-progress-bar\n [value]=\"file.progress || 0\"\n [type]=\"file.deleting || file.progress === -1 ? 'indeterminate' : 'determinate'\"\n ></ion-progress-bar>\n }\n @if (file.error) {\n <ion-text\n color=\"danger\"\n [innerHTML]=\"\n file.error\n | translate\n : {\n fileExtension: fileExtension,\n fileExtensionsExcluded: fileExtensionsExcluded.join(', '),\n maxFileSizeMb: maxFileSizeMb,\n }\n \"\n ></ion-text>\n }\n </div>\n <!-- remote from list (before importation) -->\n @if (!file.progress || file.error) {\n <button mat-icon-button slot=\"end\" (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n }\n <!-- delete file (remotely - after importation) -->\n @if (file.progress === 1 && !!deleteFn && !file.deleting) {\n <button mat-icon-button slot=\"end\" (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n }\n </ion-item>\n }\n </ion-list>\n</div>\n", styles: [".container{width:calc(100% - 3rem);min-height:200px;padding:2rem;text-align:center;border:dashed 1px #979797;position:relative;margin:1.5rem}.container input{opacity:0;position:absolute;z-index:2;width:100%;height:100%;top:0;left:0;cursor:pointer}.container ion-label{display:block;font-size:20px;font-weight:600;color:#38424c}.container label{display:inline-block;color:#fff;width:183px;height:44px;border-radius:21.5px;background-color:#db202f;padding:8px 16px}.fileover{border:solid 1px var(--ion-color-accent)}.upload-summary{padding:0 1.5rem .5rem}.upload-summary ion-progress-bar{margin-top:4px}.file-thumbnail{--thumbnail-size: 48px;width:var(--thumbnail-size);height:var(--thumbnail-size);flex-shrink:0;display:flex;align-items:center;justify-content:center;border-radius:4px;overflow:hidden}.file-thumbnail img{width:100%;height:100%;object-fit:cover}.file-thumbnail ion-skeleton-text{width:100%;height:100%;margin:0}.file-icon{display:flex;align-items:center;justify-content:center;color:var(--ion-color-medium)}.file-icon.file-thumbnail mat-icon{--icon-size: 36px;font-size:var(--icon-size);width:var(--icon-size);height:var(--icon-size)}.file-icon[data-kind=pdf]{color:#d93025}.file-icon[data-kind=document]{color:#2b579a}.file-icon[data-kind=spreadsheet]{color:#217346}.file-icon[data-kind=presentation]{color:#d24726}.file-icon[data-kind=archive]{color:#b8860b}.file-icon[data-kind=image]{color:var(--ion-color-tertiary)}.file-icon[data-kind=video],.file-icon[data-kind=audio]{color:#7b1fa2}.files-list{max-height:var(--app-upload-file-list-max-height, 300px);overflow:auto}.files-list .single-file{display:flex;flex-direction:column;flex:1 1 auto}.files-list .single-file .name{word-break:break-all}.files-list .single-file .name.deleting{color:gray!important;font-style:italic!important}\n"], dependencies: [{ kind: "component", type: i2$2.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$2.IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$2.IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: i2$2.IonSkeletonText, selector: "ion-skeleton-text", inputs: ["animated"] }, { kind: "component", type: i2$2.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i6$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: DragAndDropDirective, selector: "[appDragAndDrop]", outputs: ["fileDropped"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "pipe", type: FileSizePipe, name: "fileSize" }] });
|
|
17681
17690
|
}
|
|
17682
17691
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: UploadFileComponent, decorators: [{
|
|
17683
17692
|
type: Component,
|
|
17684
|
-
args: [{ selector: 'app-upload-file', standalone: false, template: "@if (!showDropArea) {\n <!-- hidden file input (no drop area): used by browse() -->\n <input\n type=\"file\"\n #fileDropRef\n class=\"hidden-file-input\"\n hidden\n [multiple]=\"!uniqueFile\"\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n} @else if ((!uniqueFile && !autoHideDropArea) || files.length === 0) {\n <div class=\"container\" appDragAndDrop (fileDropped)=\"onFileDropped($event)\">\n <input\n type=\"file\"\n #fileDropRef\n id=\"fileDropRef\"\n [multiple]=\"!uniqueFile\"\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n <mat-icon style=\"font-size: xx-large\" color=\"accent\" class=\"text-size\">upload</mat-icon>\n <ion-label>\n <span\n [innerHTML]=\"\n 'FILE.UPLOAD.DRAG_AND_DROP' | translate: { extension: !fileExtension ? '' : ' (' + fileExtension + ')' }\n \"\n ></span>\n </ion-label>\n <ion-button color=\"tertiary\">{{ 'FILE.UPLOAD.BROWSE' | translate }}</ion-button>\n </div>\n}\n<!-- global progression (useful when many files are selected) -->\n@if (uploading && files.length > 1) {\n <div class=\"upload-summary\">\n <ion-label color=\"medium\">\n <small>{{ 'FILE.UPLOAD.DONE_COUNT' | translate: { count: doneFilesCount, total: files.length } }}</small>\n </ion-label>\n <ion-progress-bar [value]=\"doneFilesCount / files.length\" color=\"accent\"></ion-progress-bar>\n </div>\n}\n<div class=\"files-list\">\n <ion-list>\n @for (file of files; track file; let i = $index) {\n <ion-item>\n @if (showThumbnails && file.kind === 'image') {\n <!-- image thumbnail, or a skeleton until the image is ready -->\n <div slot=\"start\" class=\"file-thumbnail\">\n @if (file.error) {\n <mat-icon color=\"danger\">broken_image</mat-icon>\n } @else if (file.progress === 1 && file.response?.body) {\n <img [src]=\"file.response.body\" alt=\"\" />\n } @else {\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n }\n </div>\n } @else {\n <!-- icon of the file type (pdf, spreadsheet, document...) -->\n <div slot=\"start\" class=\"file-icon\" [class.file-thumbnail]=\"showThumbnails\" [attr.data-kind]=\"file.kind\">\n <mat-icon>{{ fileKindIcons[file.kind || 'other'] }}</mat-icon>\n </div>\n }\n <div class=\"single-file\">\n <ion-label>\n <h4 class=\"name\" [class.deleting]=\"file.deleting\">\n {{ file.name }}\n </h4>\n <p>\n {{ file.size | fileSize }}\n </p>\n </ion-label>\n @if (uploading && !file.error && !showThumbnails) {\n <ion-progress-bar\n [value]=\"file.progress || 0\"\n [type]=\"file.deleting || file.progress === -1 ? 'indeterminate' : 'determinate'\"\n ></ion-progress-bar>\n }\n @if (file.error) {\n <ion-text\n color=\"danger\"\n [innerHTML]=\"\n file.error\n | translate\n : {\n fileExtension: fileExtension,\n fileExtensionsExcluded: fileExtensionsExcluded.join(', '),\n maxFileSizeMb: maxFileSizeMb,\n }\n \"\n ></ion-text>\n }\n </div>\n <!-- remote from list (before importation) -->\n @if (!file.progress || file.error) {\n <button mat-icon-button slot=\"end\" (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n }\n <!-- delete file (remotely - after importation) -->\n @if (file.progress === 1 && !!deleteFn && !file.deleting) {\n <button mat-icon-button slot=\"end\" (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n }\n </ion-item>\n }\n </ion-list>\n</div>\n", styles: [".container{width:calc(100% - 3rem);min-height:200px;padding:2rem;text-align:center;border:dashed 1px #979797;position:relative;margin:1.5rem}.container input{opacity:0;position:absolute;z-index:2;width:100%;height:100%;top:0;left:0;cursor:pointer}.container ion-label{display:block;font-size:20px;font-weight:600;color:#38424c}.container label{display:inline-block;color:#fff;width:183px;height:44px;border-radius:21.5px;background-color:#db202f;padding:8px 16px}.fileover{border:solid 1px var(--ion-color-accent)}.upload-summary{padding:0 1.5rem .5rem}.upload-summary ion-progress-bar{margin-top:4px}.file-thumbnail{--thumbnail-size: 48px;width:var(--thumbnail-size);height:var(--thumbnail-size);flex-shrink:0;display:flex;align-items:center;justify-content:center;border-radius:4px;overflow:hidden}.file-thumbnail img{width:100%;height:100%;object-fit:cover}.file-thumbnail ion-skeleton-text{width:100%;height:100%;margin:0}.file-icon{display:flex;align-items:center;justify-content:center;color:var(--ion-color-medium)}.file-icon.file-thumbnail mat-icon{--icon-size: 36px;font-size:var(--icon-size);width:var(--icon-size);height:var(--icon-size)}.file-icon[data-kind=pdf]{color:#d93025}.file-icon[data-kind=document]{color:#2b579a}.file-icon[data-kind=spreadsheet]{color:#217346}.file-icon[data-kind=presentation]{color:#d24726}.file-icon[data-kind=archive]{color:#b8860b}.file-icon[data-kind=image]{color:var(--ion-color-tertiary)}.file-icon[data-kind=video],.file-icon[data-kind=audio]{color:#7b1fa2}.files-list{max-height:300px;overflow:auto}.files-list .single-file{display:flex;flex-direction:column;flex:1 1 auto}.files-list .single-file .name{word-break:break-all}.files-list .single-file .name.deleting{color:gray!important;font-style:italic!important}\n"] }]
|
|
17693
|
+
args: [{ selector: 'app-upload-file', standalone: false, template: "@if (!showDropArea) {\n <!-- hidden file input (no drop area): used by browse() -->\n <input\n type=\"file\"\n #fileDropRef\n class=\"hidden-file-input\"\n hidden\n [multiple]=\"!uniqueFile\"\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n} @else if ((!uniqueFile && !autoHideDropArea) || files.length === 0) {\n <div class=\"container\" appDragAndDrop (fileDropped)=\"onFileDropped($event)\">\n <input\n type=\"file\"\n #fileDropRef\n id=\"fileDropRef\"\n [multiple]=\"!uniqueFile\"\n [accept]=\"fileExtension\"\n (change)=\"fileBrowseHandler($any($event.target).files)\"\n />\n <mat-icon style=\"font-size: xx-large\" color=\"accent\" class=\"text-size\">upload</mat-icon>\n <ion-label>\n <span\n [innerHTML]=\"\n 'FILE.UPLOAD.DRAG_AND_DROP' | translate: { extension: !fileExtension ? '' : ' (' + fileExtension + ')' }\n \"\n ></span>\n </ion-label>\n <ion-button color=\"tertiary\">{{ 'FILE.UPLOAD.BROWSE' | translate }}</ion-button>\n </div>\n}\n<!-- global progression (useful when many files are selected) -->\n@if (uploading && files.length > 1) {\n <div class=\"upload-summary\">\n <ion-label color=\"medium\">\n <small>{{ 'FILE.UPLOAD.DONE_COUNT' | translate: { count: doneFilesCount, total: files.length } }}</small>\n </ion-label>\n <ion-progress-bar [value]=\"doneFilesCount / files.length\" color=\"accent\"></ion-progress-bar>\n </div>\n}\n<div class=\"files-list\">\n <ion-list>\n @for (file of files; track file; let i = $index) {\n <ion-item>\n @if (showThumbnails && file.kind === 'image') {\n <!-- image thumbnail, or a skeleton until the image is ready -->\n <div slot=\"start\" class=\"file-thumbnail\">\n @if (file.error) {\n <mat-icon color=\"danger\">broken_image</mat-icon>\n } @else if (file.progress === 1 && file.response?.body) {\n <img [src]=\"file.response.body\" alt=\"\" />\n } @else {\n <ion-skeleton-text [animated]=\"true\"></ion-skeleton-text>\n }\n </div>\n } @else {\n <!-- icon of the file type (pdf, spreadsheet, document...) -->\n <div slot=\"start\" class=\"file-icon\" [class.file-thumbnail]=\"showThumbnails\" [attr.data-kind]=\"file.kind\">\n <mat-icon>{{ fileKindIcons[file.kind || 'other'] }}</mat-icon>\n </div>\n }\n <div class=\"single-file\">\n <ion-label>\n <h4 class=\"name\" [class.deleting]=\"file.deleting\">\n {{ file.name }}\n </h4>\n <p>\n {{ file.size | fileSize }}\n </p>\n </ion-label>\n @if (uploading && !file.error && !showThumbnails) {\n <ion-progress-bar\n [value]=\"file.progress || 0\"\n [type]=\"file.deleting || file.progress === -1 ? 'indeterminate' : 'determinate'\"\n ></ion-progress-bar>\n }\n @if (file.error) {\n <ion-text\n color=\"danger\"\n [innerHTML]=\"\n file.error\n | translate\n : {\n fileExtension: fileExtension,\n fileExtensionsExcluded: fileExtensionsExcluded.join(', '),\n maxFileSizeMb: maxFileSizeMb,\n }\n \"\n ></ion-text>\n }\n </div>\n <!-- remote from list (before importation) -->\n @if (!file.progress || file.error) {\n <button mat-icon-button slot=\"end\" (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n }\n <!-- delete file (remotely - after importation) -->\n @if (file.progress === 1 && !!deleteFn && !file.deleting) {\n <button mat-icon-button slot=\"end\" (click)=\"deleteFile(i)\">\n <mat-icon>clear</mat-icon>\n </button>\n }\n </ion-item>\n }\n </ion-list>\n</div>\n", styles: [".container{width:calc(100% - 3rem);min-height:200px;padding:2rem;text-align:center;border:dashed 1px #979797;position:relative;margin:1.5rem}.container input{opacity:0;position:absolute;z-index:2;width:100%;height:100%;top:0;left:0;cursor:pointer}.container ion-label{display:block;font-size:20px;font-weight:600;color:#38424c}.container label{display:inline-block;color:#fff;width:183px;height:44px;border-radius:21.5px;background-color:#db202f;padding:8px 16px}.fileover{border:solid 1px var(--ion-color-accent)}.upload-summary{padding:0 1.5rem .5rem}.upload-summary ion-progress-bar{margin-top:4px}.file-thumbnail{--thumbnail-size: 48px;width:var(--thumbnail-size);height:var(--thumbnail-size);flex-shrink:0;display:flex;align-items:center;justify-content:center;border-radius:4px;overflow:hidden}.file-thumbnail img{width:100%;height:100%;object-fit:cover}.file-thumbnail ion-skeleton-text{width:100%;height:100%;margin:0}.file-icon{display:flex;align-items:center;justify-content:center;color:var(--ion-color-medium)}.file-icon.file-thumbnail mat-icon{--icon-size: 36px;font-size:var(--icon-size);width:var(--icon-size);height:var(--icon-size)}.file-icon[data-kind=pdf]{color:#d93025}.file-icon[data-kind=document]{color:#2b579a}.file-icon[data-kind=spreadsheet]{color:#217346}.file-icon[data-kind=presentation]{color:#d24726}.file-icon[data-kind=archive]{color:#b8860b}.file-icon[data-kind=image]{color:var(--ion-color-tertiary)}.file-icon[data-kind=video],.file-icon[data-kind=audio]{color:#7b1fa2}.files-list{max-height:var(--app-upload-file-list-max-height, 300px);overflow:auto}.files-list .single-file{display:flex;flex-direction:column;flex:1 1 auto}.files-list .single-file .name{word-break:break-all}.files-list .single-file .name.deleting{color:gray!important;font-style:italic!important}\n"] }]
|
|
17685
17694
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i2.TranslateService }], propDecorators: { fileDropEl: [{
|
|
17686
17695
|
type: ViewChild,
|
|
17687
17696
|
args: ['fileDropRef', { static: false }]
|
|
@@ -17843,35 +17852,218 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
17843
17852
|
}] } });
|
|
17844
17853
|
|
|
17845
17854
|
const APP_TESTING_PAGES = new InjectionToken('testingPages');
|
|
17855
|
+
const SEARCH_QUERY_PARAM = 'q';
|
|
17856
|
+
function normalize(value) {
|
|
17857
|
+
return removeDiacritics(value || '').toLowerCase();
|
|
17858
|
+
}
|
|
17859
|
+
/**
|
|
17860
|
+
* Split a text into parts, flagging parts that match one of the (normalized) tokens.
|
|
17861
|
+
* Assumes removeDiacritics() keeps the string length (true for latin accents).
|
|
17862
|
+
*/
|
|
17863
|
+
function splitByTokens(text, tokens) {
|
|
17864
|
+
if (!text)
|
|
17865
|
+
return [];
|
|
17866
|
+
if (!tokens.length)
|
|
17867
|
+
return [{ text, match: false }];
|
|
17868
|
+
const normalized = normalize(text);
|
|
17869
|
+
const matches = new Array(text.length).fill(false);
|
|
17870
|
+
for (const token of tokens) {
|
|
17871
|
+
let start = normalized.indexOf(token);
|
|
17872
|
+
while (start !== -1) {
|
|
17873
|
+
matches.fill(true, start, start + token.length);
|
|
17874
|
+
start = normalized.indexOf(token, start + token.length);
|
|
17875
|
+
}
|
|
17876
|
+
}
|
|
17877
|
+
const parts = [];
|
|
17878
|
+
for (let i = 0; i < text.length; i++) {
|
|
17879
|
+
const last = parts[parts.length - 1];
|
|
17880
|
+
if (last && last.match === matches[i])
|
|
17881
|
+
last.text += text[i];
|
|
17882
|
+
else
|
|
17883
|
+
parts.push({ text: text[i], match: matches[i] });
|
|
17884
|
+
}
|
|
17885
|
+
return parts;
|
|
17886
|
+
}
|
|
17846
17887
|
class SharedTestsPage {
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
17851
|
-
|
|
17852
|
-
|
|
17853
|
-
|
|
17854
|
-
|
|
17855
|
-
|
|
17856
|
-
|
|
17888
|
+
// Code under free license (GPL version 3)
|
|
17889
|
+
searchbar = viewChild(IonSearchbar, ...(ngDevMode ? [{ debugName: "searchbar" }] : /* istanbul ignore next */ []));
|
|
17890
|
+
searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
|
|
17891
|
+
activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
17892
|
+
showSearchbar = signal(false, ...(ngDevMode ? [{ debugName: "showSearchbar" }] : /* istanbul ignore next */ []));
|
|
17893
|
+
totalCount;
|
|
17894
|
+
initialSearchText; // Not bound to searchText(), to avoid overriding the input while typing
|
|
17895
|
+
sections = computed(() => this.filter(this.searchText()), ...(ngDevMode ? [{ debugName: "sections" }] : /* istanbul ignore next */ []));
|
|
17896
|
+
visibleCount = computed(() => this.sections().reduce((count, section) => count + section.items.length, 0), ...(ngDevMode ? [{ debugName: "visibleCount" }] : /* istanbul ignore next */ []));
|
|
17897
|
+
route = inject(ActivatedRoute);
|
|
17898
|
+
router = inject(Router);
|
|
17899
|
+
location = inject(Location);
|
|
17900
|
+
pages = inject(APP_TESTING_PAGES, { optional: true });
|
|
17901
|
+
constructor() {
|
|
17902
|
+
console.info('[testing] Loaded tests pages:', this.pages);
|
|
17903
|
+
this.totalCount = (this.pages || []).filter((p) => !p.divider && p.page).length;
|
|
17904
|
+
// Restore the search text (e.g. when going back from a test page)
|
|
17905
|
+
this.initialSearchText = this.route.snapshot.queryParamMap.get(SEARCH_QUERY_PARAM) || '';
|
|
17906
|
+
this.searchText.set(this.initialSearchText);
|
|
17907
|
+
this.showSearchbar.set(!!this.initialSearchText);
|
|
17908
|
+
}
|
|
17909
|
+
ionViewDidEnter() {
|
|
17910
|
+
// Focus the searchbar (if visible), to be able to continue typing
|
|
17911
|
+
if (this.showSearchbar())
|
|
17912
|
+
this.focusSearchbar();
|
|
17913
|
+
}
|
|
17914
|
+
onDocumentKeydown(domEvent) {
|
|
17915
|
+
const event = domEvent;
|
|
17916
|
+
// Open the searchbar on '/' or Ctrl+K, if not already typing in a field
|
|
17917
|
+
const target = event.target;
|
|
17918
|
+
const isTyping = target?.closest?.('input, textarea, [contenteditable="true"]');
|
|
17919
|
+
if ((event.key === '/' && !isTyping) || (event.key === 'k' && (event.ctrlKey || event.metaKey))) {
|
|
17920
|
+
event.preventDefault();
|
|
17921
|
+
this.openSearchbar();
|
|
17922
|
+
}
|
|
17923
|
+
}
|
|
17924
|
+
toggleSearchbar() {
|
|
17925
|
+
if (this.showSearchbar())
|
|
17926
|
+
this.closeSearchbar();
|
|
17927
|
+
else
|
|
17928
|
+
this.openSearchbar();
|
|
17929
|
+
}
|
|
17930
|
+
openSearchbar() {
|
|
17931
|
+
this.showSearchbar.set(true);
|
|
17932
|
+
this.focusSearchbar();
|
|
17933
|
+
}
|
|
17934
|
+
closeSearchbar() {
|
|
17935
|
+
// Reset the filter, to never hide pages without a visible searchbar
|
|
17936
|
+
if (this.searchText())
|
|
17937
|
+
this.onSearchInput('');
|
|
17938
|
+
this.initialSearchText = '';
|
|
17939
|
+
this.showSearchbar.set(false);
|
|
17940
|
+
}
|
|
17941
|
+
onSearchInput(value) {
|
|
17942
|
+
const searchText = value || '';
|
|
17943
|
+
this.searchText.set(searchText);
|
|
17944
|
+
this.activeIndex.set(0);
|
|
17945
|
+
// Keep the search text in the URL (restored on back navigation), without triggering a navigation
|
|
17946
|
+
const urlTree = this.router.createUrlTree([], {
|
|
17947
|
+
relativeTo: this.route,
|
|
17948
|
+
queryParams: { [SEARCH_QUERY_PARAM]: searchText.trim() || null },
|
|
17949
|
+
queryParamsHandling: 'merge',
|
|
17950
|
+
});
|
|
17951
|
+
this.location.replaceState(this.router.serializeUrl(urlTree));
|
|
17857
17952
|
}
|
|
17858
|
-
|
|
17953
|
+
onSearchKeydown(event) {
|
|
17954
|
+
const count = this.visibleCount();
|
|
17955
|
+
switch (event.key) {
|
|
17956
|
+
case 'ArrowDown':
|
|
17957
|
+
event.preventDefault();
|
|
17958
|
+
if (count)
|
|
17959
|
+
this.setActiveIndex((this.activeIndex() + 1) % count);
|
|
17960
|
+
break;
|
|
17961
|
+
case 'ArrowUp':
|
|
17962
|
+
event.preventDefault();
|
|
17963
|
+
if (count)
|
|
17964
|
+
this.setActiveIndex((this.activeIndex() - 1 + count) % count);
|
|
17965
|
+
break;
|
|
17966
|
+
case 'Enter': {
|
|
17967
|
+
event.preventDefault();
|
|
17968
|
+
const item = this.findItem(this.activeIndex());
|
|
17969
|
+
if (item)
|
|
17970
|
+
this.openPage(item.page, event);
|
|
17971
|
+
break;
|
|
17972
|
+
}
|
|
17973
|
+
case 'Escape':
|
|
17974
|
+
// Clear the search text, or close the searchbar if already empty
|
|
17975
|
+
event.preventDefault();
|
|
17976
|
+
if (this.searchText())
|
|
17977
|
+
this.clearSearch();
|
|
17978
|
+
else
|
|
17979
|
+
this.closeSearchbar();
|
|
17980
|
+
break;
|
|
17981
|
+
}
|
|
17982
|
+
}
|
|
17983
|
+
clearSearch() {
|
|
17984
|
+
const searchbar = this.searchbar();
|
|
17985
|
+
if (searchbar)
|
|
17986
|
+
searchbar.value = '';
|
|
17987
|
+
this.onSearchInput('');
|
|
17988
|
+
this.focusSearchbar();
|
|
17989
|
+
}
|
|
17990
|
+
focusSearchbar() {
|
|
17991
|
+
// Wait the searchbar to be rendered
|
|
17992
|
+
setTimeout(() => this.searchbar()?.setFocus(), 150);
|
|
17993
|
+
}
|
|
17994
|
+
async openPage(page, event) {
|
|
17995
|
+
// Open in a new tab, on Ctrl/Cmd + click (or Ctrl/Cmd + Enter)
|
|
17996
|
+
if (event && (event.ctrlKey || event.metaKey)) {
|
|
17997
|
+
const url = this.router.serializeUrl(this.router.createUrlTree([page]));
|
|
17998
|
+
window.open(url, '_blank');
|
|
17999
|
+
return false;
|
|
18000
|
+
}
|
|
17859
18001
|
return this.router.navigate([page], {
|
|
17860
18002
|
relativeTo: this.route,
|
|
17861
18003
|
});
|
|
17862
18004
|
}
|
|
17863
|
-
|
|
17864
|
-
|
|
18005
|
+
setActiveIndex(index) {
|
|
18006
|
+
this.activeIndex.set(index);
|
|
18007
|
+
// Scroll the active item into view
|
|
18008
|
+
setTimeout(() => document.querySelector('app-tests-page ion-item.active')?.scrollIntoView({ block: 'nearest' }));
|
|
18009
|
+
}
|
|
18010
|
+
findItem(index) {
|
|
18011
|
+
for (const section of this.sections()) {
|
|
18012
|
+
const item = section.items.find((i) => i.index === index);
|
|
18013
|
+
if (item)
|
|
18014
|
+
return item;
|
|
18015
|
+
}
|
|
18016
|
+
return undefined;
|
|
18017
|
+
}
|
|
18018
|
+
filter(searchText) {
|
|
18019
|
+
const tokens = normalize(searchText)
|
|
18020
|
+
.split(/\s+/)
|
|
18021
|
+
.filter((token) => token.length > 0);
|
|
18022
|
+
// Group pages by section (divider)
|
|
18023
|
+
const groups = [];
|
|
18024
|
+
for (const page of this.pages || []) {
|
|
18025
|
+
if (page.divider)
|
|
18026
|
+
groups.push({ label: page.label, pages: [] });
|
|
18027
|
+
else if (page.page) {
|
|
18028
|
+
if (!groups.length)
|
|
18029
|
+
groups.push({ pages: [] });
|
|
18030
|
+
groups[groups.length - 1].pages.push(page);
|
|
18031
|
+
}
|
|
18032
|
+
}
|
|
18033
|
+
// Filter: each token must match the item label, its path, or its section label
|
|
18034
|
+
let index = 0;
|
|
18035
|
+
const sections = [];
|
|
18036
|
+
for (const group of groups) {
|
|
18037
|
+
const sectionText = normalize(group.label);
|
|
18038
|
+
const items = group.pages
|
|
18039
|
+
.filter((p) => {
|
|
18040
|
+
if (!tokens.length)
|
|
18041
|
+
return true;
|
|
18042
|
+
const text = `${sectionText} ${normalize(p.label)} ${normalize(p.page)}`;
|
|
18043
|
+
return tokens.every((token) => text.includes(token));
|
|
18044
|
+
})
|
|
18045
|
+
.map((p) => ({
|
|
18046
|
+
index: index++,
|
|
18047
|
+
page: p.page,
|
|
18048
|
+
labelParts: splitByTokens(p.label, tokens),
|
|
18049
|
+
pageParts: splitByTokens(p.page, tokens),
|
|
18050
|
+
}));
|
|
18051
|
+
if (items.length) {
|
|
18052
|
+
sections.push({ labelParts: group.label ? splitByTokens(group.label, tokens) : undefined, items });
|
|
18053
|
+
}
|
|
18054
|
+
}
|
|
18055
|
+
return sections;
|
|
18056
|
+
}
|
|
18057
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: SharedTestsPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
18058
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: SharedTestsPage, isStandalone: false, selector: "app-tests-page", host: { listeners: { "document:keydown": "onDocumentKeydown($event)" } }, viewQueries: [{ propertyName: "searchbar", first: true, predicate: IonSearchbar, descendants: true, isSignal: true }], ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-menu-button></ion-menu-button>\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n @if (showSearchbar()) {\n <!-- Search bar (replace the title, when expanded) -->\n <ion-searchbar\n color=\"light\"\n [value]=\"initialSearchText\"\n [debounce]=\"0\"\n [placeholder]=\"'TESTING_PAGE.SEARCH_PLACEHOLDER' | translate\"\n showClearButton=\"always\"\n autocapitalize=\"off\"\n (ionInput)=\"onSearchInput($event.detail.value)\"\n (ionClear)=\"clearSearch()\"\n (keydown)=\"onSearchKeydown($event)\"\n ></ion-searchbar>\n } @else {\n <ion-title>{{ 'TESTING_PAGE.TITLE' | translate }}</ion-title>\n }\n\n <ion-buttons slot=\"end\">\n <!-- Toggle the search bar -->\n <ion-button\n [class.search-active]=\"showSearchbar()\"\n [title]=\"(showSearchbar() ? 'TESTING_PAGE.BTN_HIDE_SEARCH' : 'TESTING_PAGE.BTN_SHOW_SEARCH') | translate\"\n (click)=\"toggleSearchbar()\"\n >\n <ion-icon slot=\"icon-only\" name=\"search\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n\n <!-- Result count (under the search bar) -->\n @if (showSearchbar() && searchText().trim()) {\n <div class=\"search-count\">\n <ion-label [color]=\"visibleCount() === 0 ? 'danger' : 'primary100'\" class=\"ion-padding\">\n {{ (visibleCount() ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT') | translate: { count: visibleCount() } }}\n </ion-label>\n </div>\n }\n</ion-header>\n\n<ion-content>\n @if (sections().length) {\n <ion-list>\n @for (section of sections(); track $index) {\n <ion-item-group>\n @if (section.labelParts) {\n <ion-item-divider sticky>\n <ion-label>\n <ng-container *ngTemplateOutlet=\"textParts; context: { $implicit: section.labelParts }\"></ng-container>\n </ion-label>\n </ion-item-divider>\n }\n @for (item of section.items; track item.page) {\n <ion-item\n tappable\n class=\"text-1x\"\n [class.active]=\"searchText() && item.index === activeIndex()\"\n [title]=\"item.page\"\n (click)=\"openPage(item.page, $event)\"\n (auxclick)=\"$event.button === 1 && openPage(item.page, { ctrlKey: true })\"\n >\n <ion-label>\n <ng-container *ngTemplateOutlet=\"textParts; context: { $implicit: item.labelParts }\"></ng-container>\n </ion-label>\n <ion-note slot=\"end\" class=\"page-path ion-hide-sm-down\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { $implicit: item.pageParts }\"></ng-container>\n </ion-note>\n </ion-item>\n }\n </ion-item-group>\n }\n </ion-list>\n } @else if (searchText()) {\n <!-- No result: the message is displayed under the search bar -->\n <div class=\"ion-padding ion-text-center\">\n <ion-button fill=\"clear\" (click)=\"clearSearch()\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'TESTING_PAGE.BTN_CLEAR_SEARCH' | translate }}\n </ion-button>\n </div>\n }\n</ion-content>\n\n<ng-template #textParts let-parts>\n <!-- Keep on a single line, to avoid extra spaces around <mark> -->\n <!-- prettier-ignore -->\n @for (part of parts; track $index) {@if (part.match) {<mark>{{ part.text }}</mark>} @else {{{ part.text }}}}\n</ng-template>\n", styles: [":host ion-toolbar ion-searchbar{padding-top:4px;padding-bottom:4px}:host .search-count{display:flex;background:var(--ion-background-color, #fff);border-bottom:1px solid var(--ion-border-color, var(--ion-color-light-shade))}:host ion-button.search-active{--background: rgba(var(--ion-color-primary-contrast-rgb, 255, 255, 255), .2);--border-radius: 50%}:host ion-item.active{--background: var(--ion-color-light);box-shadow:inset 3px 0 0 var(--ion-color-primary)}:host .page-path{font-family:monospace;font-size:.8em}:host mark{background-color:var(--ion-color-warning-tint, #ffe187);color:inherit;padding:0;border-radius:2px}\n"], dependencies: [{ kind: "directive", type: i2$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$2.IonBackButton, selector: "ion-back-button" }, { kind: "component", type: i2$2.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$2.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$2.IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$2.IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonItemDivider, selector: "ion-item-divider", inputs: ["color", "mode", "sticky"] }, { kind: "component", type: i2$2.IonItemGroup, selector: "ion-item-group" }, { kind: "component", type: i2$2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$2.IonMenuButton, selector: "ion-menu-button", inputs: ["autoHide", "color", "disabled", "menu", "mode", "type"] }, { kind: "component", type: i2$2.IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: i2$2.IonSearchbar, selector: "ion-searchbar", inputs: ["animated", "autocomplete", "autocorrect", "cancelButtonIcon", "cancelButtonText", "clearIcon", "color", "debounce", "disabled", "enterkeyhint", "inputmode", "mode", "name", "placeholder", "searchIcon", "showCancelButton", "showClearButton", "spellcheck", "type", "value"] }, { kind: "component", type: i2$2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
17865
18059
|
}
|
|
17866
18060
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: SharedTestsPage, decorators: [{
|
|
17867
18061
|
type: Component,
|
|
17868
|
-
args: [{ selector: 'app-tests-page', standalone: false, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-menu-button></ion-menu-button>\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>
|
|
17869
|
-
}], ctorParameters: () => [
|
|
17870
|
-
|
|
17871
|
-
|
|
17872
|
-
|
|
17873
|
-
args: [APP_TESTING_PAGES]
|
|
17874
|
-
}] }] });
|
|
18062
|
+
args: [{ selector: 'app-tests-page', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-menu-button></ion-menu-button>\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n @if (showSearchbar()) {\n <!-- Search bar (replace the title, when expanded) -->\n <ion-searchbar\n color=\"light\"\n [value]=\"initialSearchText\"\n [debounce]=\"0\"\n [placeholder]=\"'TESTING_PAGE.SEARCH_PLACEHOLDER' | translate\"\n showClearButton=\"always\"\n autocapitalize=\"off\"\n (ionInput)=\"onSearchInput($event.detail.value)\"\n (ionClear)=\"clearSearch()\"\n (keydown)=\"onSearchKeydown($event)\"\n ></ion-searchbar>\n } @else {\n <ion-title>{{ 'TESTING_PAGE.TITLE' | translate }}</ion-title>\n }\n\n <ion-buttons slot=\"end\">\n <!-- Toggle the search bar -->\n <ion-button\n [class.search-active]=\"showSearchbar()\"\n [title]=\"(showSearchbar() ? 'TESTING_PAGE.BTN_HIDE_SEARCH' : 'TESTING_PAGE.BTN_SHOW_SEARCH') | translate\"\n (click)=\"toggleSearchbar()\"\n >\n <ion-icon slot=\"icon-only\" name=\"search\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n\n <!-- Result count (under the search bar) -->\n @if (showSearchbar() && searchText().trim()) {\n <div class=\"search-count\">\n <ion-label [color]=\"visibleCount() === 0 ? 'danger' : 'primary100'\" class=\"ion-padding\">\n {{ (visibleCount() ? 'COMMON.RESULT_COUNT' : 'COMMON.NO_RESULT') | translate: { count: visibleCount() } }}\n </ion-label>\n </div>\n }\n</ion-header>\n\n<ion-content>\n @if (sections().length) {\n <ion-list>\n @for (section of sections(); track $index) {\n <ion-item-group>\n @if (section.labelParts) {\n <ion-item-divider sticky>\n <ion-label>\n <ng-container *ngTemplateOutlet=\"textParts; context: { $implicit: section.labelParts }\"></ng-container>\n </ion-label>\n </ion-item-divider>\n }\n @for (item of section.items; track item.page) {\n <ion-item\n tappable\n class=\"text-1x\"\n [class.active]=\"searchText() && item.index === activeIndex()\"\n [title]=\"item.page\"\n (click)=\"openPage(item.page, $event)\"\n (auxclick)=\"$event.button === 1 && openPage(item.page, { ctrlKey: true })\"\n >\n <ion-label>\n <ng-container *ngTemplateOutlet=\"textParts; context: { $implicit: item.labelParts }\"></ng-container>\n </ion-label>\n <ion-note slot=\"end\" class=\"page-path ion-hide-sm-down\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { $implicit: item.pageParts }\"></ng-container>\n </ion-note>\n </ion-item>\n }\n </ion-item-group>\n }\n </ion-list>\n } @else if (searchText()) {\n <!-- No result: the message is displayed under the search bar -->\n <div class=\"ion-padding ion-text-center\">\n <ion-button fill=\"clear\" (click)=\"clearSearch()\">\n <ion-icon slot=\"start\" name=\"close\"></ion-icon>\n {{ 'TESTING_PAGE.BTN_CLEAR_SEARCH' | translate }}\n </ion-button>\n </div>\n }\n</ion-content>\n\n<ng-template #textParts let-parts>\n <!-- Keep on a single line, to avoid extra spaces around <mark> -->\n <!-- prettier-ignore -->\n @for (part of parts; track $index) {@if (part.match) {<mark>{{ part.text }}</mark>} @else {{{ part.text }}}}\n</ng-template>\n", styles: [":host ion-toolbar ion-searchbar{padding-top:4px;padding-bottom:4px}:host .search-count{display:flex;background:var(--ion-background-color, #fff);border-bottom:1px solid var(--ion-border-color, var(--ion-color-light-shade))}:host ion-button.search-active{--background: rgba(var(--ion-color-primary-contrast-rgb, 255, 255, 255), .2);--border-radius: 50%}:host ion-item.active{--background: var(--ion-color-light);box-shadow:inset 3px 0 0 var(--ion-color-primary)}:host .page-path{font-family:monospace;font-size:.8em}:host mark{background-color:var(--ion-color-warning-tint, #ffe187);color:inherit;padding:0;border-radius:2px}\n"] }]
|
|
18063
|
+
}], ctorParameters: () => [], propDecorators: { searchbar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => IonSearchbar), { isSignal: true }] }], onDocumentKeydown: [{
|
|
18064
|
+
type: HostListener,
|
|
18065
|
+
args: ['document:keydown', ['$event']]
|
|
18066
|
+
}] } });
|
|
17875
18067
|
|
|
17876
18068
|
const StorageDrivers = {
|
|
17877
18069
|
SQLLite: CordovaSQLiteDriver._driver,
|
|
@@ -22663,6 +22855,47 @@ function isSubscriptionOperation(operation) {
|
|
|
22663
22855
|
const def = getMainDefinition(operation.query);
|
|
22664
22856
|
return def.kind === 'OperationDefinition' && def.operation === 'subscription';
|
|
22665
22857
|
}
|
|
22858
|
+
const DEFER_DIRECTIVE_NAME = 'defer';
|
|
22859
|
+
/**
|
|
22860
|
+
* Return true if the document uses the @defer directive
|
|
22861
|
+
*/
|
|
22862
|
+
function hasDeferDirective(document) {
|
|
22863
|
+
if (!document)
|
|
22864
|
+
return false;
|
|
22865
|
+
let found = false;
|
|
22866
|
+
visit(document, {
|
|
22867
|
+
[Kind.DIRECTIVE]: (node) => {
|
|
22868
|
+
if (node.name.value === DEFER_DIRECTIVE_NAME)
|
|
22869
|
+
found = true;
|
|
22870
|
+
},
|
|
22871
|
+
});
|
|
22872
|
+
return found;
|
|
22873
|
+
}
|
|
22874
|
+
/**
|
|
22875
|
+
* Remove the @defer directives from a document (deferred fragments are then fetched with the rest of the query)
|
|
22876
|
+
*/
|
|
22877
|
+
function removeDeferDirectives(document) {
|
|
22878
|
+
if (!hasDeferDirective(document))
|
|
22879
|
+
return document;
|
|
22880
|
+
return visit(document, {
|
|
22881
|
+
[Kind.DIRECTIVE]: (node) => (node.name.value === DEFER_DIRECTIVE_NAME ? null : undefined),
|
|
22882
|
+
});
|
|
22883
|
+
}
|
|
22884
|
+
function isDeferredOperation(operation) {
|
|
22885
|
+
return hasDeferDirective(operation.query);
|
|
22886
|
+
}
|
|
22887
|
+
/**
|
|
22888
|
+
* Convert Angular HttpHeaders (used by the apollo-angular HttpLink) into a plain object, as expected by the Apollo fetch HttpLink
|
|
22889
|
+
*/
|
|
22890
|
+
const httpHeadersToRecordLink = new ApolloLink((operation, forward) => {
|
|
22891
|
+
const { headers } = operation.getContext();
|
|
22892
|
+
if (headers instanceof HttpHeaders) {
|
|
22893
|
+
const record = {};
|
|
22894
|
+
headers.keys().forEach((key) => (record[key] = headers.getAll(key).join(', ')));
|
|
22895
|
+
operation.setContext({ headers: record });
|
|
22896
|
+
}
|
|
22897
|
+
return forward(operation);
|
|
22898
|
+
});
|
|
22666
22899
|
const TRACKED_QUERIES_STORAGE_KEY = 'apollo-tracker-persist';
|
|
22667
22900
|
function createTrackerLink(opts) {
|
|
22668
22901
|
const trackedQueriesUpdated = new EventEmitter();
|
|
@@ -22777,6 +23010,12 @@ class GraphqlService extends StartableService {
|
|
|
22777
23010
|
get cache() {
|
|
22778
23011
|
return this.apollo.client.cache;
|
|
22779
23012
|
}
|
|
23013
|
+
/**
|
|
23014
|
+
* Is the @defer directive enabled (see environment.enableGraphqlDefer)? If not, @defer directives are removed from queries
|
|
23015
|
+
*/
|
|
23016
|
+
get deferEnabled() {
|
|
23017
|
+
return this.environment.enableGraphqlDefer === true;
|
|
23018
|
+
}
|
|
22780
23019
|
get defaultFetchPolicy() {
|
|
22781
23020
|
return this._defaultFetchPolicy;
|
|
22782
23021
|
}
|
|
@@ -22823,7 +23062,11 @@ class GraphqlService extends StartableService {
|
|
|
22823
23062
|
});
|
|
22824
23063
|
}
|
|
22825
23064
|
queryRefValuesChanges(queryRef, opts) {
|
|
22826
|
-
return queryRef.valueChanges.pipe(catchError(async (error) => this.toApolloError(error, opts.error)),
|
|
23065
|
+
return queryRef.valueChanges.pipe(catchError(async (error) => this.toApolloError(error, opts.error)),
|
|
23066
|
+
// Emit complete results, errors, and partial results of a @defer query (streaming)
|
|
23067
|
+
filter((value) => value.networkStatus === NetworkStatus.ready ||
|
|
23068
|
+
value.networkStatus === NetworkStatus.error ||
|
|
23069
|
+
value.networkStatus === NetworkStatus.streaming), map(({ data, error }) => {
|
|
22827
23070
|
if (error) {
|
|
22828
23071
|
throw error;
|
|
22829
23072
|
}
|
|
@@ -33534,6 +33777,11 @@ class GitlabUtils {
|
|
|
33534
33777
|
}
|
|
33535
33778
|
}
|
|
33536
33779
|
|
|
33780
|
+
/** Default placeholder inserted inside wrap markers, when the selection is empty. */
|
|
33781
|
+
const WRAP_PLACEHOLDER = 'text';
|
|
33782
|
+
/** Default placeholders inserted by insertLink, when the selection is empty. */
|
|
33783
|
+
const LINK_PLACEHOLDER = 'link';
|
|
33784
|
+
const LINK_URL_PLACEHOLDER = 'url';
|
|
33537
33785
|
class MarkdownUtils {
|
|
33538
33786
|
static HTML_ENTITIES_MAP = {
|
|
33539
33787
|
''': "'", // Single quote
|
|
@@ -33608,6 +33856,128 @@ class MarkdownUtils {
|
|
|
33608
33856
|
return UrlUtils.getRootUrl(url);
|
|
33609
33857
|
}
|
|
33610
33858
|
}
|
|
33859
|
+
/** Markdown heading markers, longest first (used to replace an existing heading level). */
|
|
33860
|
+
const HEADING_MARKERS = ['###### ', '##### ', '#### ', '### ', '## ', '# '];
|
|
33861
|
+
/**
|
|
33862
|
+
* Wraps (or unwraps when already wrapped) the textarea selection with the given markdown markers.
|
|
33863
|
+
* Pure function: the caller is responsible for applying the result to the textarea and the control value.
|
|
33864
|
+
*
|
|
33865
|
+
* @param state current text and selection
|
|
33866
|
+
* @param prefix left marker (e.g. '**' for bold, '==' for highlight)
|
|
33867
|
+
* @param suffix right marker, defaults to prefix
|
|
33868
|
+
* @param placeholder text inserted when the selection is empty
|
|
33869
|
+
*/
|
|
33870
|
+
function wrapSelection(state, prefix, suffix = prefix, placeholder = WRAP_PLACEHOLDER) {
|
|
33871
|
+
const text = state?.text ?? '';
|
|
33872
|
+
let start = state?.selectionStart ?? 0;
|
|
33873
|
+
let end = state?.selectionEnd ?? 0;
|
|
33874
|
+
// Toggle off: selection directly surrounded by markers -> remove them
|
|
33875
|
+
if (start !== end && text.substring(Math.max(0, start - prefix.length), start) === prefix && text.substring(end, end + suffix.length) === suffix) {
|
|
33876
|
+
const before = text.substring(0, Math.max(0, start - prefix.length));
|
|
33877
|
+
const selection = text.substring(start, end);
|
|
33878
|
+
const after = text.substring(end + suffix.length);
|
|
33879
|
+
return {
|
|
33880
|
+
text: before + selection + after,
|
|
33881
|
+
selectionStart: start - prefix.length,
|
|
33882
|
+
selectionEnd: end - prefix.length,
|
|
33883
|
+
};
|
|
33884
|
+
}
|
|
33885
|
+
// Toggle off: selection itself contains the markers -> unwrap them
|
|
33886
|
+
let innerStart = start;
|
|
33887
|
+
let innerEnd = end;
|
|
33888
|
+
if (text.substring(start, Math.min(end, start + prefix.length)) === prefix && text.substring(Math.max(start, end - suffix.length), end) === suffix) {
|
|
33889
|
+
innerStart += prefix.length;
|
|
33890
|
+
innerEnd -= suffix.length;
|
|
33891
|
+
}
|
|
33892
|
+
const isEmptySelection = start === end;
|
|
33893
|
+
// When the markers are inside the selection (toggle off), they are removed:
|
|
33894
|
+
// - content before/after the selection keeps its original positions;
|
|
33895
|
+
// - the content starts at the original selection start.
|
|
33896
|
+
const didUnwrap = innerStart !== start;
|
|
33897
|
+
const before = text.substring(0, didUnwrap ? start : innerStart);
|
|
33898
|
+
const selection = text.substring(innerStart, innerEnd) || placeholder;
|
|
33899
|
+
// The trailing content continues after the original selection end (markers, if any, were inside it)
|
|
33900
|
+
const after = text.substring(end);
|
|
33901
|
+
// Caret right after the prefix marker; the selected text stays selected when wrapping existing text
|
|
33902
|
+
const selectionStart = didUnwrap ? start : start + prefix.length;
|
|
33903
|
+
const selectionEnd = didUnwrap ? start + selection.length : isEmptySelection ? selectionStart : selectionStart + selection.length;
|
|
33904
|
+
// Toggle off: the markers were inside the selection, just remove them
|
|
33905
|
+
const nextText = didUnwrap ? before + selection + after : before + prefix + selection + suffix + after;
|
|
33906
|
+
return {
|
|
33907
|
+
text: nextText,
|
|
33908
|
+
selectionStart,
|
|
33909
|
+
selectionEnd,
|
|
33910
|
+
};
|
|
33911
|
+
}
|
|
33912
|
+
/**
|
|
33913
|
+
* Toggles a line marker (e.g. '# ' for heading 1) on every involved line of the selection.
|
|
33914
|
+
* When all involved lines already have the marker, it is removed; another heading marker is replaced.
|
|
33915
|
+
*/
|
|
33916
|
+
function toggleLinePrefix(state, marker) {
|
|
33917
|
+
const text = state?.text ?? '';
|
|
33918
|
+
let start = state?.selectionStart ?? 0;
|
|
33919
|
+
let end = state?.selectionEnd ?? 0;
|
|
33920
|
+
// Line boundaries covering the selection
|
|
33921
|
+
const lineStart = text.lastIndexOf('\n', Math.max(0, start - 1)) + 1;
|
|
33922
|
+
const nextNewLine = text.indexOf('\n', end);
|
|
33923
|
+
const lineEnd = nextNewLine === -1 ? text.length : nextNewLine;
|
|
33924
|
+
const block = text.substring(lineStart, lineEnd);
|
|
33925
|
+
const lines = block.length ? block.split('\n') : [''];
|
|
33926
|
+
if (lines.every((line) => line.startsWith(marker))) {
|
|
33927
|
+
// Toggle off: remove the marker on each involved line
|
|
33928
|
+
return {
|
|
33929
|
+
text: text.substring(0, lineStart) + lines.map((line) => line.substring(marker.length)).join('\n') + text.substring(lineEnd),
|
|
33930
|
+
selectionStart: lineStart,
|
|
33931
|
+
selectionEnd: lineEnd - marker.length * lines.length,
|
|
33932
|
+
};
|
|
33933
|
+
}
|
|
33934
|
+
// Add (or replace an existing heading marker) on each involved line
|
|
33935
|
+
const replaceExisting = HEADING_MARKERS.filter((m) => m !== marker);
|
|
33936
|
+
const prefixed = lines
|
|
33937
|
+
.map((line) => {
|
|
33938
|
+
const existing = replaceExisting.find((m) => line.startsWith(m));
|
|
33939
|
+
return existing ? marker + line.substring(existing.length) : marker + line;
|
|
33940
|
+
})
|
|
33941
|
+
.join('\n');
|
|
33942
|
+
const diff = prefixed.length - block.length;
|
|
33943
|
+
return {
|
|
33944
|
+
text: text.substring(0, lineStart) + prefixed + text.substring(lineEnd),
|
|
33945
|
+
// Empty line(s): place the caret after the inserted marker; otherwise select the prefixed block
|
|
33946
|
+
selectionStart: block.length ? lineStart : lineStart + diff,
|
|
33947
|
+
selectionEnd: lineEnd + diff,
|
|
33948
|
+
};
|
|
33949
|
+
}
|
|
33950
|
+
/**
|
|
33951
|
+
* Inserts a markdown link: turns the selected text into a link label, or inserts a template
|
|
33952
|
+
* with the given placeholders when the selection is empty. The label stays selected afterwards.
|
|
33953
|
+
*/
|
|
33954
|
+
function insertLink(state, opts) {
|
|
33955
|
+
const text = state?.text ?? '';
|
|
33956
|
+
const start = Math.min(state?.selectionStart ?? 0, text.length);
|
|
33957
|
+
const end = Math.min(state?.selectionEnd ?? 0, text.length);
|
|
33958
|
+
const url = opts?.url ?? LINK_URL_PLACEHOLDER;
|
|
33959
|
+
const label = start === end ? (opts?.label ?? LINK_PLACEHOLDER) : text.substring(start, end);
|
|
33960
|
+
const link = `[${label}](${url})`;
|
|
33961
|
+
const before = text.substring(0, start);
|
|
33962
|
+
const after = text.substring(end);
|
|
33963
|
+
let labelStart;
|
|
33964
|
+
let labelEnd;
|
|
33965
|
+
if (start === end) {
|
|
33966
|
+
// Empty selection: select the default label, so the user can type over it
|
|
33967
|
+
labelStart = before.length + 1;
|
|
33968
|
+
labelEnd = labelStart + label.length;
|
|
33969
|
+
}
|
|
33970
|
+
else {
|
|
33971
|
+
// Reuse the existing text as label: keep it selected
|
|
33972
|
+
labelStart = start + 1;
|
|
33973
|
+
labelEnd = labelStart + label.length;
|
|
33974
|
+
}
|
|
33975
|
+
return {
|
|
33976
|
+
text: before + link + after,
|
|
33977
|
+
selectionStart: labelStart,
|
|
33978
|
+
selectionEnd: labelEnd,
|
|
33979
|
+
};
|
|
33980
|
+
}
|
|
33611
33981
|
|
|
33612
33982
|
class MarkdownService {
|
|
33613
33983
|
route = inject(ActivatedRoute);
|
|
@@ -34712,6 +35082,148 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
34712
35082
|
}]
|
|
34713
35083
|
}] });
|
|
34714
35084
|
|
|
35085
|
+
/**
|
|
35086
|
+
* Marked inline extension rendering `==highlighted text==` as `<mark>` elements.
|
|
35087
|
+
*
|
|
35088
|
+
* The highlight syntax has no GFM standard: this extension completes the renderer
|
|
35089
|
+
* used by ngx-markdown, so composed messages (feed, inbox) render the same way as they are previewed.
|
|
35090
|
+
*
|
|
35091
|
+
* Registered through `SharedMarkdownModule.forRoot()` (`markedExtensions` option),
|
|
35092
|
+
* and testable without Angular by applying it on a `Marked` instance.
|
|
35093
|
+
*/
|
|
35094
|
+
const compositionMarkdownExtensions = [
|
|
35095
|
+
{
|
|
35096
|
+
extensions: [
|
|
35097
|
+
{
|
|
35098
|
+
name: 'highlight',
|
|
35099
|
+
level: 'inline',
|
|
35100
|
+
start(src) {
|
|
35101
|
+
const index = src.indexOf('==');
|
|
35102
|
+
return index === -1 ? undefined : index;
|
|
35103
|
+
},
|
|
35104
|
+
// Same-line content only, and no unbalanced side (e.g. 'a == b' stays as-is)
|
|
35105
|
+
tokenizer(src) {
|
|
35106
|
+
const match = /^==(?!\s)([^=\n]+?)(?<!\s)==/.exec(src);
|
|
35107
|
+
if (!match) {
|
|
35108
|
+
return undefined;
|
|
35109
|
+
}
|
|
35110
|
+
return {
|
|
35111
|
+
type: 'highlight',
|
|
35112
|
+
raw: match[0],
|
|
35113
|
+
text: match[1],
|
|
35114
|
+
// Keep nested inline syntaxes renderable (e.g. '==a **bold** b==')
|
|
35115
|
+
tokens: this.lexer.inlineTokens(match[1]),
|
|
35116
|
+
};
|
|
35117
|
+
},
|
|
35118
|
+
renderer(token) {
|
|
35119
|
+
return `<mark>${this.parser.parseInline(token.tokens ?? token.text)}</mark>`;
|
|
35120
|
+
},
|
|
35121
|
+
},
|
|
35122
|
+
],
|
|
35123
|
+
},
|
|
35124
|
+
];
|
|
35125
|
+
|
|
35126
|
+
/**
|
|
35127
|
+
* Markdown composition toolbar: a row of buttons that insert markdown syntax
|
|
35128
|
+
* at the caret of the linked textarea (bold, underline, strikethrough, highlight, headings, bullet list, link, attachment).
|
|
35129
|
+
*
|
|
35130
|
+
* Purely presentational towards the text: the component emits the new text through
|
|
35131
|
+
* {@link textChange}, so the host remains owner of the form control value.
|
|
35132
|
+
* The caret and selection are restored on the textarea after each action.
|
|
35133
|
+
*/
|
|
35134
|
+
class MarkdownToolbarComponent {
|
|
35135
|
+
/** The textarea to edit (native element). */
|
|
35136
|
+
textarea;
|
|
35137
|
+
/** Disable all buttons (e.g. when the host form is disabled). */
|
|
35138
|
+
disabled = false;
|
|
35139
|
+
/** Disable the edition buttons, but not the preview toggle (e.g. when the preview replaces the text). */
|
|
35140
|
+
editDisabled = false;
|
|
35141
|
+
/** Show the attachment (paper clip) button. */
|
|
35142
|
+
canAttach = false;
|
|
35143
|
+
/** Reflects the current preview state (highlight the toggle button when active). */
|
|
35144
|
+
preview = false;
|
|
35145
|
+
/** Emits the new text value after each insertion. */
|
|
35146
|
+
textChange = new EventEmitter();
|
|
35147
|
+
/** Emits when the attachment button is clicked. */
|
|
35148
|
+
attachClick = new EventEmitter();
|
|
35149
|
+
/** Emits when the preview toggle button is clicked. */
|
|
35150
|
+
previewToggle = new EventEmitter();
|
|
35151
|
+
bold() {
|
|
35152
|
+
this.apply(wrapSelection(this.currentState(), '**', '**'));
|
|
35153
|
+
}
|
|
35154
|
+
/** Underline: no markdown syntax exists, so use the inline HTML `<u>` tag (rendered by marked, and portable) */
|
|
35155
|
+
underline() {
|
|
35156
|
+
this.apply(wrapSelection(this.currentState(), '<u>', '</u>'));
|
|
35157
|
+
}
|
|
35158
|
+
/** Strikethrough (GFM syntax) */
|
|
35159
|
+
strikethrough() {
|
|
35160
|
+
this.apply(wrapSelection(this.currentState(), '~~', '~~'));
|
|
35161
|
+
}
|
|
35162
|
+
highlight() {
|
|
35163
|
+
this.apply(wrapSelection(this.currentState(), '==', '=='));
|
|
35164
|
+
}
|
|
35165
|
+
heading1() {
|
|
35166
|
+
this.apply(toggleLinePrefix(this.currentState(), '# '));
|
|
35167
|
+
}
|
|
35168
|
+
heading2() {
|
|
35169
|
+
this.apply(toggleLinePrefix(this.currentState(), '## '));
|
|
35170
|
+
}
|
|
35171
|
+
/** Bullet list: toggle the '- ' marker on each selected line */
|
|
35172
|
+
bulletList() {
|
|
35173
|
+
this.apply(toggleLinePrefix(this.currentState(), '- '));
|
|
35174
|
+
}
|
|
35175
|
+
link() {
|
|
35176
|
+
this.apply(insertLink(this.currentState()));
|
|
35177
|
+
}
|
|
35178
|
+
onAttachClick(event) {
|
|
35179
|
+
this.attachClick.emit(event);
|
|
35180
|
+
}
|
|
35181
|
+
togglePreview() {
|
|
35182
|
+
this.previewToggle.emit();
|
|
35183
|
+
}
|
|
35184
|
+
currentState() {
|
|
35185
|
+
const el = this.textarea;
|
|
35186
|
+
return {
|
|
35187
|
+
text: el?.value ?? '',
|
|
35188
|
+
selectionStart: el?.selectionStart ?? 0,
|
|
35189
|
+
selectionEnd: el?.selectionEnd ?? 0,
|
|
35190
|
+
};
|
|
35191
|
+
}
|
|
35192
|
+
apply(next) {
|
|
35193
|
+
if (!this.textarea) {
|
|
35194
|
+
return;
|
|
35195
|
+
}
|
|
35196
|
+
// Update the textarea value first, then the host's control value (synchronous),
|
|
35197
|
+
// then restore the caret, as updating the control may re-write the textarea value.
|
|
35198
|
+
this.textarea.value = next.text;
|
|
35199
|
+
this.textChange.emit(next.text);
|
|
35200
|
+
this.textarea.focus();
|
|
35201
|
+
this.textarea.setSelectionRange(next.selectionStart, next.selectionEnd);
|
|
35202
|
+
}
|
|
35203
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MarkdownToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
35204
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MarkdownToolbarComponent, isStandalone: false, selector: "app-markdown-toolbar", inputs: { textarea: "textarea", disabled: "disabled", editDisabled: "editDisabled", canAttach: "canAttach", preview: "preview" }, outputs: { textChange: "textChange", attachClick: "attachClick", previewToggle: "previewToggle" }, ngImport: i0, template: "<ion-toolbar role=\"toolbar\">\n <ion-buttons slot=\"start\">\n <!-- Bold -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"bold()\"\n [title]=\"'MARKDOWN.TOOLBAR.BOLD' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_bold</mat-icon>\n </ion-button>\n\n <!-- Underline (<u>text</u>) -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"underline()\"\n [title]=\"'MARKDOWN.TOOLBAR.UNDERLINE' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_underlined</mat-icon>\n </ion-button>\n\n <!-- Strikethrough (~~text~~) -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"strikethrough()\"\n [title]=\"'MARKDOWN.TOOLBAR.STRIKETHROUGH' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_strikethrough</mat-icon>\n </ion-button>\n\n <!-- Highlight (==text==) -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"highlight()\"\n [title]=\"'MARKDOWN.TOOLBAR.HIGHLIGHT' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_color_text</mat-icon>\n </ion-button>\n\n <!-- Heading 1 -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"heading1()\"\n [title]=\"'MARKDOWN.TOOLBAR.HEADING1' | translate\"\n tabindex=\"-1\"\n >\n H1\n </ion-button>\n\n <!-- Heading 2 -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"heading2()\"\n [title]=\"'MARKDOWN.TOOLBAR.HEADING2' | translate\"\n tabindex=\"-1\"\n >\n H2\n </ion-button>\n\n <!-- Bullet list (- item) -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"bulletList()\"\n [title]=\"'MARKDOWN.TOOLBAR.BULLET_LIST' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_list_bulleted</mat-icon>\n </ion-button>\n\n <!-- Link -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"link()\"\n [title]=\"'MARKDOWN.TOOLBAR.LINK' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">link</mat-icon>\n </ion-button>\n\n <!-- Attachment (paper clip) -->\n @if (canAttach) {\n <ion-button\n data-testid=\"attach-button\"\n [disabled]=\"disabled || editDisabled\"\n (click)=\"onAttachClick($event)\"\n [title]=\"'MARKDOWN.TOOLBAR.ATTACHMENT' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">attach_file</mat-icon>\n </ion-button>\n }\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- Preview toggle -->\n <ion-button\n data-testid=\"preview-button\"\n [disabled]=\"disabled\"\n [color]=\"preview ? 'primary' : undefined\"\n [class.active]=\"preview\"\n (click)=\"togglePreview()\"\n [title]=\"'MARKDOWN.TOOLBAR.PREVIEW' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">{{ preview ? 'visibility_off' : 'visibility' }}</mat-icon>\n </ion-button>\n </ion-buttons>\n</ion-toolbar>\n", styles: [":host{display:block}ion-toolbar{--min-height: 40px;--background: transparent}\n"], dependencies: [{ kind: "component", type: i2$2.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$2.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i6$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
35205
|
+
}
|
|
35206
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MarkdownToolbarComponent, decorators: [{
|
|
35207
|
+
type: Component,
|
|
35208
|
+
args: [{ selector: 'app-markdown-toolbar', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ion-toolbar role=\"toolbar\">\n <ion-buttons slot=\"start\">\n <!-- Bold -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"bold()\"\n [title]=\"'MARKDOWN.TOOLBAR.BOLD' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_bold</mat-icon>\n </ion-button>\n\n <!-- Underline (<u>text</u>) -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"underline()\"\n [title]=\"'MARKDOWN.TOOLBAR.UNDERLINE' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_underlined</mat-icon>\n </ion-button>\n\n <!-- Strikethrough (~~text~~) -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"strikethrough()\"\n [title]=\"'MARKDOWN.TOOLBAR.STRIKETHROUGH' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_strikethrough</mat-icon>\n </ion-button>\n\n <!-- Highlight (==text==) -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"highlight()\"\n [title]=\"'MARKDOWN.TOOLBAR.HIGHLIGHT' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_color_text</mat-icon>\n </ion-button>\n\n <!-- Heading 1 -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"heading1()\"\n [title]=\"'MARKDOWN.TOOLBAR.HEADING1' | translate\"\n tabindex=\"-1\"\n >\n H1\n </ion-button>\n\n <!-- Heading 2 -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"heading2()\"\n [title]=\"'MARKDOWN.TOOLBAR.HEADING2' | translate\"\n tabindex=\"-1\"\n >\n H2\n </ion-button>\n\n <!-- Bullet list (- item) -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"bulletList()\"\n [title]=\"'MARKDOWN.TOOLBAR.BULLET_LIST' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">format_list_bulleted</mat-icon>\n </ion-button>\n\n <!-- Link -->\n <ion-button\n [disabled]=\"disabled || editDisabled\"\n (click)=\"link()\"\n [title]=\"'MARKDOWN.TOOLBAR.LINK' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">link</mat-icon>\n </ion-button>\n\n <!-- Attachment (paper clip) -->\n @if (canAttach) {\n <ion-button\n data-testid=\"attach-button\"\n [disabled]=\"disabled || editDisabled\"\n (click)=\"onAttachClick($event)\"\n [title]=\"'MARKDOWN.TOOLBAR.ATTACHMENT' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">attach_file</mat-icon>\n </ion-button>\n }\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- Preview toggle -->\n <ion-button\n data-testid=\"preview-button\"\n [disabled]=\"disabled\"\n [color]=\"preview ? 'primary' : undefined\"\n [class.active]=\"preview\"\n (click)=\"togglePreview()\"\n [title]=\"'MARKDOWN.TOOLBAR.PREVIEW' | translate\"\n tabindex=\"-1\"\n >\n <mat-icon slot=\"icon-only\">{{ preview ? 'visibility_off' : 'visibility' }}</mat-icon>\n </ion-button>\n </ion-buttons>\n</ion-toolbar>\n", styles: [":host{display:block}ion-toolbar{--min-height: 40px;--background: transparent}\n"] }]
|
|
35209
|
+
}], propDecorators: { textarea: [{
|
|
35210
|
+
type: Input
|
|
35211
|
+
}], disabled: [{
|
|
35212
|
+
type: Input
|
|
35213
|
+
}], editDisabled: [{
|
|
35214
|
+
type: Input
|
|
35215
|
+
}], canAttach: [{
|
|
35216
|
+
type: Input
|
|
35217
|
+
}], preview: [{
|
|
35218
|
+
type: Input
|
|
35219
|
+
}], textChange: [{
|
|
35220
|
+
type: Output
|
|
35221
|
+
}], attachClick: [{
|
|
35222
|
+
type: Output
|
|
35223
|
+
}], previewToggle: [{
|
|
35224
|
+
type: Output
|
|
35225
|
+
}] } });
|
|
35226
|
+
|
|
34715
35227
|
// function that returns `MarkedOptions` with renderer override
|
|
34716
35228
|
class SharedMarkdownModule {
|
|
34717
35229
|
static forRoot() {
|
|
@@ -34726,6 +35238,12 @@ class SharedMarkdownModule {
|
|
|
34726
35238
|
provide: SANITIZE,
|
|
34727
35239
|
useValue: SecurityContext.NONE,
|
|
34728
35240
|
},
|
|
35241
|
+
// Composition extensions (e.g. ==highlight==), used by the markdown toolbar
|
|
35242
|
+
markedExtensions: compositionMarkdownExtensions.map((extension) => ({
|
|
35243
|
+
provide: MARKED_EXTENSIONS,
|
|
35244
|
+
useValue: extension,
|
|
35245
|
+
multi: true,
|
|
35246
|
+
})),
|
|
34729
35247
|
markedOptions: {
|
|
34730
35248
|
provide: MARKED_OPTIONS,
|
|
34731
35249
|
deps: [MarkdownService],
|
|
@@ -34750,15 +35268,18 @@ class SharedMarkdownModule {
|
|
|
34750
35268
|
// Component
|
|
34751
35269
|
AppMarkdownContent,
|
|
34752
35270
|
AppMarkdownModal,
|
|
35271
|
+
MarkdownToolbarComponent,
|
|
34753
35272
|
// Directive
|
|
34754
35273
|
MarkdownDirective], imports: [CommonModule,
|
|
34755
35274
|
SharedIonicModule,
|
|
34756
|
-
TranslatePipe,
|
|
35275
|
+
TranslatePipe,
|
|
35276
|
+
SharedMaterialModule, i4$3.MarkdownModule,
|
|
34757
35277
|
// App module
|
|
34758
35278
|
SharedToolbarModule,
|
|
34759
|
-
SharedDebugModule], exports: [MarkdownModule, AppMarkdownContent, MarkdownDirective] });
|
|
35279
|
+
SharedDebugModule], exports: [MarkdownModule, AppMarkdownContent, MarkdownDirective, MarkdownToolbarComponent] });
|
|
34760
35280
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: SharedMarkdownModule, providers: [provideChildTranslateService()], imports: [CommonModule,
|
|
34761
35281
|
SharedIonicModule,
|
|
35282
|
+
SharedMaterialModule,
|
|
34762
35283
|
MarkdownModule.forChild(),
|
|
34763
35284
|
// App module
|
|
34764
35285
|
SharedToolbarModule,
|
|
@@ -34771,6 +35292,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
34771
35292
|
CommonModule,
|
|
34772
35293
|
SharedIonicModule,
|
|
34773
35294
|
TranslatePipe,
|
|
35295
|
+
SharedMaterialModule,
|
|
34774
35296
|
MarkdownModule.forChild(),
|
|
34775
35297
|
// App module
|
|
34776
35298
|
SharedToolbarModule,
|
|
@@ -34780,10 +35302,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
34780
35302
|
// Component
|
|
34781
35303
|
AppMarkdownContent,
|
|
34782
35304
|
AppMarkdownModal,
|
|
35305
|
+
MarkdownToolbarComponent,
|
|
34783
35306
|
// Directive
|
|
34784
35307
|
MarkdownDirective,
|
|
34785
35308
|
],
|
|
34786
|
-
exports: [MarkdownModule, AppMarkdownContent, MarkdownDirective],
|
|
35309
|
+
exports: [MarkdownModule, AppMarkdownContent, MarkdownDirective, MarkdownToolbarComponent],
|
|
34787
35310
|
providers: [provideChildTranslateService()],
|
|
34788
35311
|
}]
|
|
34789
35312
|
}] });
|
|
@@ -39529,6 +40052,7 @@ class MessageForm extends AppForm {
|
|
|
39529
40052
|
/**
|
|
39530
40053
|
* Allow the user to attach files to the message.
|
|
39531
40054
|
* When enabled, {@link uploadFn} and {@link deleteFn} should also be provided (see `UploadFileComponent`).
|
|
40055
|
+
* Also used by the markdown toolbar to show its attachment (paper clip) button.
|
|
39532
40056
|
*/
|
|
39533
40057
|
canAttach = false;
|
|
39534
40058
|
/** Upload callback, called for each added file (usually a call to the REST endpoint `POST api/upload`) */
|
|
@@ -39537,7 +40061,18 @@ class MessageForm extends AppForm {
|
|
|
39537
40061
|
deleteFn;
|
|
39538
40062
|
/** Max size of an attached file, in megabytes (default: 20 Mo) */
|
|
39539
40063
|
maxAttachmentSizeMb = 20;
|
|
40064
|
+
/**
|
|
40065
|
+
* Layout of the body preview: side by side with the body text (2 columns), or replacing the text (e.g. on small screens).
|
|
40066
|
+
* The edition toolbar buttons are disabled when the preview replaces the text.
|
|
40067
|
+
*/
|
|
40068
|
+
bodyPreviewSideBySide = false;
|
|
39540
40069
|
searchRecipients = new EventEmitter();
|
|
40070
|
+
/** Emits when the attachment button is clicked (after opening the file browser) */
|
|
40071
|
+
attach = new EventEmitter();
|
|
40072
|
+
/** Emits when the body preview is toggled (e.g. to let the parent modal grow, if side by side) */
|
|
40073
|
+
bodyPreviewChange = new EventEmitter();
|
|
40074
|
+
/** Whether the markdown preview of the body is displayed. */
|
|
40075
|
+
bodyPreview = false;
|
|
39541
40076
|
attachmentsUpload;
|
|
39542
40077
|
types = MessageTypeList;
|
|
39543
40078
|
/** Existing attachments of the message (already stored by the pod, e.g. when editing a feed item) */
|
|
@@ -39592,6 +40127,18 @@ class MessageForm extends AppForm {
|
|
|
39592
40127
|
isSamePerson(o1, o2) {
|
|
39593
40128
|
return EntityUtils.equals(o1, o2, 'id');
|
|
39594
40129
|
}
|
|
40130
|
+
/**
|
|
40131
|
+
* New body value, emitted by the markdown toolbar after an insertion at the caret.
|
|
40132
|
+
* The textarea value (and its caret) has already been updated by the toolbar.
|
|
40133
|
+
*/
|
|
40134
|
+
onBodyTextChange(value) {
|
|
40135
|
+
this.form.controls.body.setValue(value);
|
|
40136
|
+
}
|
|
40137
|
+
toggleBodyPreview() {
|
|
40138
|
+
this.bodyPreview = !this.bodyPreview;
|
|
40139
|
+
this.bodyPreviewChange.emit(this.bodyPreview);
|
|
40140
|
+
this.cd.markForCheck();
|
|
40141
|
+
}
|
|
39595
40142
|
setValue(data, opts) {
|
|
39596
40143
|
this.existingAttachments = (data?.attachments || []).filter((a) => isNotNil(a?.id));
|
|
39597
40144
|
this.uploadedAttachments = [];
|
|
@@ -39613,9 +40160,13 @@ class MessageForm extends AppForm {
|
|
|
39613
40160
|
/**
|
|
39614
40161
|
* Open the file browser, to add an attachment
|
|
39615
40162
|
*/
|
|
40163
|
+
/**
|
|
40164
|
+
* Open the file browser (called by the markdown toolbar attachment button), then emit {@link attach}
|
|
40165
|
+
*/
|
|
39616
40166
|
addAttachment(event) {
|
|
39617
40167
|
event?.preventDefault();
|
|
39618
40168
|
this.attachmentsUpload?.browse();
|
|
40169
|
+
this.attach.emit(event);
|
|
39619
40170
|
}
|
|
39620
40171
|
/**
|
|
39621
40172
|
* Highlight the form when a file is dragged over it
|
|
@@ -39733,11 +40284,11 @@ class MessageForm extends AppForm {
|
|
|
39733
40284
|
this.cd.markForCheck();
|
|
39734
40285
|
}
|
|
39735
40286
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageForm, deps: [{ token: i0.Injector }, { token: i1$2.UntypedFormBuilder }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
39736
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MessageForm, isStandalone: false, selector: "app-message-form", inputs: { mobile: "mobile", suggestFn: "suggestFn", subjectMinLength: "subjectMinLength", subjectMaxLength: "subjectMaxLength", bodyMaxLength: "bodyMaxLength", bodyAutoHeight: "bodyAutoHeight", canSelectType: "canSelectType", canRecipientFilter: "canRecipientFilter", recipientFilterCount: "recipientFilterCount", canSearchRecipients: "canSearchRecipients", canAttach: ["canAttach", "canAttach", toBoolean], uploadFn: "uploadFn", deleteFn: "deleteFn", maxAttachmentSizeMb: "maxAttachmentSizeMb" }, outputs: { searchRecipients: "searchRecipients" }, host: { listeners: { "dragover": "onDragOver($event)", "dragleave": "onDragLeave($event)", "drop": "onDrop($event)" }, properties: { "class.file-over": "this.fileOver" } }, viewQueries: [{ propertyName: "attachmentsUpload", first: true, predicate: ["attachmentsUpload"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<!-- debug -->\n@if (debug) {\n <ng-container *ngTemplateOutlet=\"debugPanel\"></ng-container>\n}\n\n<form [formGroup]=\"form\" class=\"form-container\">\n <!-- type -->\n @if (canSelectType) {\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.TYPE' | translate }}</mat-label>\n <mat-select formControlName=\"type\">\n @for (item of types; track item.id) {\n @if (canRecipientFilter || item.id !== 'FEED') {\n <mat-option [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Recipients -->\n @if ((form | formGetValue: 'type') !== 'FEED') {\n <mat-chips-field\n class=\"ion-flex-auto\"\n formControlName=\"recipients\"\n chipColor=\"accent\"\n [placeholder]=\"'SOCIAL.MESSAGE.RECIPIENTS' | translate\"\n [config]=\"autocompleteFields.recipients\"\n [equals]=\"isSamePerson\"\n [style.flex-grow]=\"'1'\"\n >\n <!-- Fix #2090 (Lot 7): search button inside the field frame (as suffix), using the 'list' icon\n (ellipsis-horizontal is not the expected icon, see GitLab #2090) -->\n @if (canSearchRecipients) {\n <button\n matSuffix\n mat-icon-button\n type=\"button\"\n tabindex=\"-1\"\n (click)=\"searchRecipients.emit($event)\"\n [title]=\"'COMMON.BTN_SEARCH' | translate\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n </mat-chips-field>\n } @else {\n <mat-form-field>\n <input matInput hidden formControlName=\"recipients\" type=\"text\" />\n <mat-chip-grid>\n <mat-chip-row>\n {{ 'SOCIAL.MESSAGE.RECIPIENT_FILTER_COUNT' | translate: { count: recipientFilterCount } }}\n </mat-chip-row>\n </mat-chip-grid>\n </mat-form-field>\n }\n\n <!-- Subject -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.SUBJECT' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"subject\" autocomplete=\"off\" required />\n @if (form.controls.subject.hasError('required')) {\n <mat-error>{{ 'ERROR.FIELD_REQUIRED' | translate }}</mat-error>\n }\n @if (form.controls.subject.hasError('minlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MIN_LENGTH_COMPACT' | translate }}\n </mat-error>\n }\n @if (form.controls.subject.hasError('maxlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MAX_LENGTH_COMPACT' | translate }}\n </mat-error>\n }\n @if (subjectMaxLength) {\n <mat-hint align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.subject.value?.length || 0, max: subjectMaxLength }\n }}\n </mat-hint>\n }\n </mat-form-field>\n\n <!-- Body -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.BODY_HELP' | translate }}</mat-label>\n <textarea\n matInput\n type=\"text\"\n formControlName=\"body\"\n [class.fixed-height]=\"!bodyAutoHeight\"\n [cdkTextareaAutosize]=\"bodyAutoHeight\"\n (keydown.control.enter)=\"doSubmit($event)\"\n ></textarea>\n @if (form.controls.body.hasError('maxlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MAX_LENGTH' | translate: form.controls.body.errors.maxlength }}\n </mat-error>\n }\n @if (bodyMaxLength) {\n <mat-hint align=\"end\">\n {{ 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.body.value?.length || 0, max: bodyMaxLength } }}\n </mat-hint>\n }\n </mat-form-field>\n\n <!-- Attachments -->\n @if (canAttach) {\n <div class=\"message-attachments\">\n <ion-button\n class=\"add-attachment-button\"\n fill=\"clear\"\n size=\"small\"\n [disabled]=\"disabled\"\n (click)=\"addAttachment($event)\"\n >\n <mat-icon slot=\"start\">attach_file</mat-icon>\n <ion-label>{{ 'SOCIAL.MESSAGE.ATTACHMENTS.BTN_ADD' | translate }}</ion-label>\n </ion-button>\n\n <!-- existing attachments (already stored by the pod) -->\n @if (existingAttachments | isNotEmptyArray) {\n <ion-list class=\"existing-attachments\" lines=\"none\">\n @for (attachment of existingAttachments; track attachment.id) {\n <ion-item class=\"existing-attachment\">\n <mat-icon slot=\"start\">{{ attachment | map: getAttachmentIcon }}</mat-icon>\n <ion-label>\n <h4>{{ attachment | map: getAttachmentName }}</h4>\n @if (attachment.size) {\n <p>{{ attachment.size | fileSize }}</p>\n }\n </ion-label>\n <button\n mat-icon-button\n slot=\"end\"\n class=\"remove-attachment\"\n [disabled]=\"disabled\"\n [title]=\"'SOCIAL.MESSAGE.ATTACHMENTS.BTN_REMOVE' | translate\"\n (click)=\"removeExistingAttachment($event, attachment)\"\n >\n <mat-icon>clear</mat-icon>\n </button>\n </ion-item>\n }\n </ion-list>\n }\n\n <!-- files list only (no drop area: files are added by the button above, or dropped anywhere in the form) -->\n <app-upload-file\n #attachmentsUpload\n [showDropArea]=\"false\"\n [instantUpload]=\"true\"\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [maxFileSizeMb]=\"maxAttachmentSizeMb\"\n (filesChange)=\"onAttachmentsChange($event)\"\n ></app-upload-file>\n @if (form.controls.attachments.getError('uploading')) {\n <ion-note color=\"danger\">\n {{ 'SOCIAL.MESSAGE.ATTACHMENTS.UPLOADING_ERROR' | translate }}\n </ion-note>\n }\n </div>\n }\n</form>\n\n<ng-template #debugPanel>\n <app-debug title=\"Message form\">\n <ion-grid>\n <ion-row>\n <ion-col>\n ready: {{ readySubject | async }}\n <br />\n loading: {{ loadingSubject | async }}\n <br />\n enabled: {{ enabled }}\n <br />\n dirty: {{ dirty }}\n <br />\n valid: {{ valid }}\n <br />\n <br />\n error: {{ _form | formError | json }}\n <br />\n recipients: {{ _form | formGetValue: 'recipients' | personToString }}\n <br />\n recipientFilter: {{ _form | formGetValue: 'recipientFilter' | json }}\n </ion-col>\n </ion-row>\n </ion-grid>\n </app-debug>\n</ng-template>\n", styles: ["textarea.fixed-height{height:11.5em}textarea{min-height:11.5em}.message-attachments{margin-top:8px}:host.file-over{display:block;outline:2px dashed var(--ion-color-accent);outline-offset:4px}\n"], dependencies: [{ kind: "directive", type: i2$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$2.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$2.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$2.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$2.IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$2.IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: i2$2.IonRow, selector: "ion-row" }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i1$3.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "component", type: i6$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i9$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i9$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: MatChipsField, selector: "mat-chips-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "placeholder", "suggestFn", "required", "mobile", "readonly", "clearable", "debounceTime", "displaySeparator", "displayWith", "displayAttributes", "displayColumnSizes", "displayColumnNames", "highlightAccent", "showAllOnFocus", "showPanelOnFocus", "autofocus", "config", "i18nPrefix", "noResultMessage", "panelClass", "panelWidth", "disableRipple", "matAutocompletePosition", "itemSize", "fetchMoreThreshold", "suggestLengthThreshold", "showLoadingSpinner", "chipColor", "debug", "applyImplicitValue", "dropButtonTitle", "clearButtonTitle", "trimSearchText", "hideRequiredMarker", "colSizes", "separatorKeysCodes", "showChips", "appearance", "subscriptSizing", "class", "filter", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keyup.enter"] }, { kind: "directive", type: AutoResizeDirective, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMaxRows", "cdkAutosizeMinRows"] }, { kind: "component", type: UploadFileComponent, selector: "app-upload-file", inputs: ["fileExtension", "fileExtensionsExcluded", "uniqueFile", "instantUpload", "uploadFn", "deleteFn", "maxParallelUpload", "maxFileSizeMb", "autoHideDropArea", "showDropArea", "showThumbnails"], outputs: ["filesChange"] }, { kind: "component", type: i11$1.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "component", type: i11$1.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "pipe", type: i2$3.AsyncPipe, name: "async" }, { kind: "pipe", type: i2$3.JsonPipe, name: "json" }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "pipe", type: FileSizePipe, name: "fileSize" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: FormErrorPipe, name: "formError" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }, { kind: "pipe", type: MapPipe, name: "map" }, { kind: "pipe", type: PersonToStringPipe, name: "personToString" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
40287
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MessageForm, isStandalone: false, selector: "app-message-form", inputs: { mobile: "mobile", suggestFn: "suggestFn", subjectMinLength: "subjectMinLength", subjectMaxLength: "subjectMaxLength", bodyMaxLength: "bodyMaxLength", bodyAutoHeight: "bodyAutoHeight", canSelectType: "canSelectType", canRecipientFilter: "canRecipientFilter", recipientFilterCount: "recipientFilterCount", canSearchRecipients: "canSearchRecipients", canAttach: ["canAttach", "canAttach", toBoolean], uploadFn: "uploadFn", deleteFn: "deleteFn", maxAttachmentSizeMb: "maxAttachmentSizeMb", bodyPreviewSideBySide: ["bodyPreviewSideBySide", "bodyPreviewSideBySide", toBoolean] }, outputs: { searchRecipients: "searchRecipients", attach: "attach", bodyPreviewChange: "bodyPreviewChange" }, host: { listeners: { "dragover": "onDragOver($event)", "dragleave": "onDragLeave($event)", "drop": "onDrop($event)" }, properties: { "class.file-over": "this.fileOver" } }, viewQueries: [{ propertyName: "attachmentsUpload", first: true, predicate: ["attachmentsUpload"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<!-- debug -->\n@if (debug) {\n <ng-container *ngTemplateOutlet=\"debugPanel\"></ng-container>\n}\n\n<form [formGroup]=\"form\" class=\"form-container\">\n <!-- type -->\n @if (canSelectType) {\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.TYPE' | translate }}</mat-label>\n <mat-select formControlName=\"type\">\n @for (item of types; track item.id) {\n @if (canRecipientFilter || item.id !== 'FEED') {\n <mat-option [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Recipients -->\n @if ((form | formGetValue: 'type') !== 'FEED') {\n <mat-chips-field\n class=\"ion-flex-auto\"\n formControlName=\"recipients\"\n chipColor=\"accent\"\n [placeholder]=\"'SOCIAL.MESSAGE.RECIPIENTS' | translate\"\n [config]=\"autocompleteFields.recipients\"\n [equals]=\"isSamePerson\"\n [style.flex-grow]=\"'1'\"\n >\n <!-- Fix #2090 (Lot 7): search button inside the field frame (as suffix), using the 'list' icon\n (ellipsis-horizontal is not the expected icon, see GitLab #2090) -->\n @if (canSearchRecipients) {\n <button\n matSuffix\n mat-icon-button\n type=\"button\"\n tabindex=\"-1\"\n (click)=\"searchRecipients.emit($event)\"\n [title]=\"'COMMON.BTN_SEARCH' | translate\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n </mat-chips-field>\n } @else {\n <mat-form-field>\n <input matInput hidden formControlName=\"recipients\" type=\"text\" />\n <mat-chip-grid>\n <mat-chip-row>\n {{ 'SOCIAL.MESSAGE.RECIPIENT_FILTER_COUNT' | translate: { count: recipientFilterCount } }}\n </mat-chip-row>\n </mat-chip-grid>\n </mat-form-field>\n }\n\n <!-- Subject -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.SUBJECT' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"subject\" autocomplete=\"off\" required />\n @if (form.controls.subject.hasError('required')) {\n <mat-error>{{ 'ERROR.FIELD_REQUIRED' | translate }}</mat-error>\n }\n @if (form.controls.subject.hasError('minlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MIN_LENGTH_COMPACT' | translate }}\n </mat-error>\n }\n @if (form.controls.subject.hasError('maxlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MAX_LENGTH_COMPACT' | translate }}\n </mat-error>\n }\n @if (subjectMaxLength) {\n <mat-hint align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.subject.value?.length || 0, max: subjectMaxLength }\n }}\n </mat-hint>\n }\n </mat-form-field>\n\n <!-- Body -->\n <!-- Markdown composition toolbar (bold, highlight, headings, link, attachment) -->\n <app-markdown-toolbar\n [textarea]=\"bodyTextarea\"\n [canAttach]=\"canAttach\"\n [disabled]=\"disabled\"\n (attachClick)=\"addAttachment($event)\"\n (textChange)=\"onBodyTextChange($event)\"\n [preview]=\"bodyPreview\"\n [editDisabled]=\"bodyPreview && !bodyPreviewSideBySide\"\n (previewToggle)=\"toggleBodyPreview()\"\n ></app-markdown-toolbar>\n <!-- Body text and preview: side by side (2 columns), or the preview replaces the text -->\n <ion-row class=\"ion-no-padding\">\n <ion-col\n class=\"ion-no-padding\"\n [size]=\"bodyPreview && bodyPreviewSideBySide ? 6 : 12\"\n [hidden]=\"bodyPreview && !bodyPreviewSideBySide\"\n >\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.BODY_HELP' | translate }}</mat-label>\n <textarea\n #bodyTextarea\n matInput\n type=\"text\"\n formControlName=\"body\"\n [class.fixed-height]=\"!bodyAutoHeight\"\n [cdkTextareaAutosize]=\"bodyAutoHeight\"\n (keydown.control.enter)=\"doSubmit($event)\"\n ></textarea>\n @if (form.controls.body.hasError('maxlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MAX_LENGTH' | translate: form.controls.body.errors.maxlength }}\n </mat-error>\n }\n @if (bodyMaxLength) {\n <mat-hint align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.body.value?.length || 0, max: bodyMaxLength }\n }}\n </mat-hint>\n }\n </mat-form-field>\n </ion-col>\n\n <!-- Body preview (markdown rendering, toggled by the toolbar) -->\n @if (bodyPreview) {\n <ion-col\n class=\"ion-no-padding\"\n [size]=\"bodyPreviewSideBySide ? 6 : 12\"\n [class.ion-padding-start]=\"bodyPreviewSideBySide\"\n >\n <div class=\"body-preview\" data-testid=\"body-preview\">\n <markdown [data]=\"form.controls.body.value || ''\" emoji></markdown>\n </div>\n </ion-col>\n }\n </ion-row>\n\n <!-- Attachments -->\n @if (canAttach) {\n <div class=\"message-attachments\">\n <!-- existing attachments (already stored by the pod) -->\n @if (existingAttachments | isNotEmptyArray) {\n <ion-list class=\"existing-attachments\" lines=\"none\">\n @for (attachment of existingAttachments; track attachment.id) {\n <ion-item class=\"existing-attachment\">\n <mat-icon slot=\"start\">{{ attachment | map: getAttachmentIcon }}</mat-icon>\n <ion-label>\n <h4>{{ attachment | map: getAttachmentName }}</h4>\n @if (attachment.size) {\n <p>{{ attachment.size | fileSize }}</p>\n }\n </ion-label>\n <button\n mat-icon-button\n slot=\"end\"\n class=\"remove-attachment\"\n [disabled]=\"disabled\"\n [title]=\"'SOCIAL.MESSAGE.ATTACHMENTS.BTN_REMOVE' | translate\"\n (click)=\"removeExistingAttachment($event, attachment)\"\n >\n <mat-icon>clear</mat-icon>\n </button>\n </ion-item>\n }\n </ion-list>\n }\n\n <!-- files list only (no drop area: files are added by the toolbar attachment button, or dropped anywhere in the form) -->\n <app-upload-file\n #attachmentsUpload\n [showDropArea]=\"false\"\n [instantUpload]=\"true\"\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [maxFileSizeMb]=\"maxAttachmentSizeMb\"\n (filesChange)=\"onAttachmentsChange($event)\"\n ></app-upload-file>\n @if (form.controls.attachments.getError('uploading')) {\n <ion-note color=\"danger\">\n {{ 'SOCIAL.MESSAGE.ATTACHMENTS.UPLOADING_ERROR' | translate }}\n </ion-note>\n }\n </div>\n }\n</form>\n\n<ng-template #debugPanel>\n <app-debug title=\"Message form\">\n <ion-grid>\n <ion-row>\n <ion-col>\n ready: {{ readySubject | async }}\n <br />\n loading: {{ loadingSubject | async }}\n <br />\n enabled: {{ enabled }}\n <br />\n dirty: {{ dirty }}\n <br />\n valid: {{ valid }}\n <br />\n <br />\n error: {{ _form | formError | json }}\n <br />\n recipients: {{ _form | formGetValue: 'recipients' | personToString }}\n <br />\n recipientFilter: {{ _form | formGetValue: 'recipientFilter' | json }}\n </ion-col>\n </ion-row>\n </ion-grid>\n </app-debug>\n</ng-template>\n", styles: ["textarea.fixed-height{height:11.5em}textarea{min-height:11.5em}app-markdown-toolbar{display:block;margin-bottom:4px}.body-preview{box-sizing:border-box;height:calc(100% - 22px);min-height:calc(11.5em + 32px);padding:0 1em;overflow:auto;border:1px dashed var(--ion-item-border-color, #c8cdd4);border-radius:4px}.body-preview>markdown{display:block}.message-attachments{margin-top:8px;--app-upload-file-list-max-height: none}:host.file-over{display:block;outline:2px dashed var(--ion-color-accent);outline-offset:4px}\n"], dependencies: [{ kind: "directive", type: i2$3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$2.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$2.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$2.IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$2.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$2.IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: i2$2.IonRow, selector: "ion-row" }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i4$3.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i1$3.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "component", type: i6$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i9$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i9$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: MatChipsField, selector: "mat-chips-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "placeholder", "suggestFn", "required", "mobile", "readonly", "clearable", "debounceTime", "displaySeparator", "displayWith", "displayAttributes", "displayColumnSizes", "displayColumnNames", "highlightAccent", "showAllOnFocus", "showPanelOnFocus", "autofocus", "config", "i18nPrefix", "noResultMessage", "panelClass", "panelWidth", "disableRipple", "matAutocompletePosition", "itemSize", "fetchMoreThreshold", "suggestLengthThreshold", "showLoadingSpinner", "chipColor", "debug", "applyImplicitValue", "dropButtonTitle", "clearButtonTitle", "trimSearchText", "hideRequiredMarker", "colSizes", "separatorKeysCodes", "showChips", "appearance", "subscriptSizing", "class", "filter", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keyup.enter"] }, { kind: "directive", type: AutoResizeDirective, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMaxRows", "cdkAutosizeMinRows"] }, { kind: "component", type: UploadFileComponent, selector: "app-upload-file", inputs: ["fileExtension", "fileExtensionsExcluded", "uniqueFile", "instantUpload", "uploadFn", "deleteFn", "maxParallelUpload", "maxFileSizeMb", "autoHideDropArea", "showDropArea", "showThumbnails"], outputs: ["filesChange"] }, { kind: "component", type: i11$1.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "component", type: i11$1.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "directive", type: MarkdownDirective, selector: "markdown,[markdown]" }, { kind: "component", type: MarkdownToolbarComponent, selector: "app-markdown-toolbar", inputs: ["textarea", "disabled", "editDisabled", "canAttach", "preview"], outputs: ["textChange", "attachClick", "previewToggle"] }, { kind: "pipe", type: i2$3.AsyncPipe, name: "async" }, { kind: "pipe", type: i2$3.JsonPipe, name: "json" }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "pipe", type: FileSizePipe, name: "fileSize" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: FormErrorPipe, name: "formError" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }, { kind: "pipe", type: MapPipe, name: "map" }, { kind: "pipe", type: PersonToStringPipe, name: "personToString" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
39737
40288
|
}
|
|
39738
40289
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageForm, decorators: [{
|
|
39739
40290
|
type: Component,
|
|
39740
|
-
args: [{ selector: 'app-message-form', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<!-- debug -->\n@if (debug) {\n <ng-container *ngTemplateOutlet=\"debugPanel\"></ng-container>\n}\n\n<form [formGroup]=\"form\" class=\"form-container\">\n <!-- type -->\n @if (canSelectType) {\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.TYPE' | translate }}</mat-label>\n <mat-select formControlName=\"type\">\n @for (item of types; track item.id) {\n @if (canRecipientFilter || item.id !== 'FEED') {\n <mat-option [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Recipients -->\n @if ((form | formGetValue: 'type') !== 'FEED') {\n <mat-chips-field\n class=\"ion-flex-auto\"\n formControlName=\"recipients\"\n chipColor=\"accent\"\n [placeholder]=\"'SOCIAL.MESSAGE.RECIPIENTS' | translate\"\n [config]=\"autocompleteFields.recipients\"\n [equals]=\"isSamePerson\"\n [style.flex-grow]=\"'1'\"\n >\n <!-- Fix #2090 (Lot 7): search button inside the field frame (as suffix), using the 'list' icon\n (ellipsis-horizontal is not the expected icon, see GitLab #2090) -->\n @if (canSearchRecipients) {\n <button\n matSuffix\n mat-icon-button\n type=\"button\"\n tabindex=\"-1\"\n (click)=\"searchRecipients.emit($event)\"\n [title]=\"'COMMON.BTN_SEARCH' | translate\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n </mat-chips-field>\n } @else {\n <mat-form-field>\n <input matInput hidden formControlName=\"recipients\" type=\"text\" />\n <mat-chip-grid>\n <mat-chip-row>\n {{ 'SOCIAL.MESSAGE.RECIPIENT_FILTER_COUNT' | translate: { count: recipientFilterCount } }}\n </mat-chip-row>\n </mat-chip-grid>\n </mat-form-field>\n }\n\n <!-- Subject -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.SUBJECT' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"subject\" autocomplete=\"off\" required />\n @if (form.controls.subject.hasError('required')) {\n <mat-error>{{ 'ERROR.FIELD_REQUIRED' | translate }}</mat-error>\n }\n @if (form.controls.subject.hasError('minlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MIN_LENGTH_COMPACT' | translate }}\n </mat-error>\n }\n @if (form.controls.subject.hasError('maxlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MAX_LENGTH_COMPACT' | translate }}\n </mat-error>\n }\n @if (subjectMaxLength) {\n <mat-hint align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.subject.value?.length || 0, max: subjectMaxLength }\n }}\n </mat-hint>\n }\n </mat-form-field>\n\n <!-- Body -->\n <mat-form-field>\n
|
|
40291
|
+
args: [{ selector: 'app-message-form', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<!-- debug -->\n@if (debug) {\n <ng-container *ngTemplateOutlet=\"debugPanel\"></ng-container>\n}\n\n<form [formGroup]=\"form\" class=\"form-container\">\n <!-- type -->\n @if (canSelectType) {\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.TYPE' | translate }}</mat-label>\n <mat-select formControlName=\"type\">\n @for (item of types; track item.id) {\n @if (canRecipientFilter || item.id !== 'FEED') {\n <mat-option [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Recipients -->\n @if ((form | formGetValue: 'type') !== 'FEED') {\n <mat-chips-field\n class=\"ion-flex-auto\"\n formControlName=\"recipients\"\n chipColor=\"accent\"\n [placeholder]=\"'SOCIAL.MESSAGE.RECIPIENTS' | translate\"\n [config]=\"autocompleteFields.recipients\"\n [equals]=\"isSamePerson\"\n [style.flex-grow]=\"'1'\"\n >\n <!-- Fix #2090 (Lot 7): search button inside the field frame (as suffix), using the 'list' icon\n (ellipsis-horizontal is not the expected icon, see GitLab #2090) -->\n @if (canSearchRecipients) {\n <button\n matSuffix\n mat-icon-button\n type=\"button\"\n tabindex=\"-1\"\n (click)=\"searchRecipients.emit($event)\"\n [title]=\"'COMMON.BTN_SEARCH' | translate\"\n >\n <mat-icon>list</mat-icon>\n </button>\n }\n </mat-chips-field>\n } @else {\n <mat-form-field>\n <input matInput hidden formControlName=\"recipients\" type=\"text\" />\n <mat-chip-grid>\n <mat-chip-row>\n {{ 'SOCIAL.MESSAGE.RECIPIENT_FILTER_COUNT' | translate: { count: recipientFilterCount } }}\n </mat-chip-row>\n </mat-chip-grid>\n </mat-form-field>\n }\n\n <!-- Subject -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.SUBJECT' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"subject\" autocomplete=\"off\" required />\n @if (form.controls.subject.hasError('required')) {\n <mat-error>{{ 'ERROR.FIELD_REQUIRED' | translate }}</mat-error>\n }\n @if (form.controls.subject.hasError('minlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MIN_LENGTH_COMPACT' | translate }}\n </mat-error>\n }\n @if (form.controls.subject.hasError('maxlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MAX_LENGTH_COMPACT' | translate }}\n </mat-error>\n }\n @if (subjectMaxLength) {\n <mat-hint align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.subject.value?.length || 0, max: subjectMaxLength }\n }}\n </mat-hint>\n }\n </mat-form-field>\n\n <!-- Body -->\n <!-- Markdown composition toolbar (bold, highlight, headings, link, attachment) -->\n <app-markdown-toolbar\n [textarea]=\"bodyTextarea\"\n [canAttach]=\"canAttach\"\n [disabled]=\"disabled\"\n (attachClick)=\"addAttachment($event)\"\n (textChange)=\"onBodyTextChange($event)\"\n [preview]=\"bodyPreview\"\n [editDisabled]=\"bodyPreview && !bodyPreviewSideBySide\"\n (previewToggle)=\"toggleBodyPreview()\"\n ></app-markdown-toolbar>\n <!-- Body text and preview: side by side (2 columns), or the preview replaces the text -->\n <ion-row class=\"ion-no-padding\">\n <ion-col\n class=\"ion-no-padding\"\n [size]=\"bodyPreview && bodyPreviewSideBySide ? 6 : 12\"\n [hidden]=\"bodyPreview && !bodyPreviewSideBySide\"\n >\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.BODY_HELP' | translate }}</mat-label>\n <textarea\n #bodyTextarea\n matInput\n type=\"text\"\n formControlName=\"body\"\n [class.fixed-height]=\"!bodyAutoHeight\"\n [cdkTextareaAutosize]=\"bodyAutoHeight\"\n (keydown.control.enter)=\"doSubmit($event)\"\n ></textarea>\n @if (form.controls.body.hasError('maxlength')) {\n <mat-error>\n {{ 'ERROR.FIELD_MAX_LENGTH' | translate: form.controls.body.errors.maxlength }}\n </mat-error>\n }\n @if (bodyMaxLength) {\n <mat-hint align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.body.value?.length || 0, max: bodyMaxLength }\n }}\n </mat-hint>\n }\n </mat-form-field>\n </ion-col>\n\n <!-- Body preview (markdown rendering, toggled by the toolbar) -->\n @if (bodyPreview) {\n <ion-col\n class=\"ion-no-padding\"\n [size]=\"bodyPreviewSideBySide ? 6 : 12\"\n [class.ion-padding-start]=\"bodyPreviewSideBySide\"\n >\n <div class=\"body-preview\" data-testid=\"body-preview\">\n <markdown [data]=\"form.controls.body.value || ''\" emoji></markdown>\n </div>\n </ion-col>\n }\n </ion-row>\n\n <!-- Attachments -->\n @if (canAttach) {\n <div class=\"message-attachments\">\n <!-- existing attachments (already stored by the pod) -->\n @if (existingAttachments | isNotEmptyArray) {\n <ion-list class=\"existing-attachments\" lines=\"none\">\n @for (attachment of existingAttachments; track attachment.id) {\n <ion-item class=\"existing-attachment\">\n <mat-icon slot=\"start\">{{ attachment | map: getAttachmentIcon }}</mat-icon>\n <ion-label>\n <h4>{{ attachment | map: getAttachmentName }}</h4>\n @if (attachment.size) {\n <p>{{ attachment.size | fileSize }}</p>\n }\n </ion-label>\n <button\n mat-icon-button\n slot=\"end\"\n class=\"remove-attachment\"\n [disabled]=\"disabled\"\n [title]=\"'SOCIAL.MESSAGE.ATTACHMENTS.BTN_REMOVE' | translate\"\n (click)=\"removeExistingAttachment($event, attachment)\"\n >\n <mat-icon>clear</mat-icon>\n </button>\n </ion-item>\n }\n </ion-list>\n }\n\n <!-- files list only (no drop area: files are added by the toolbar attachment button, or dropped anywhere in the form) -->\n <app-upload-file\n #attachmentsUpload\n [showDropArea]=\"false\"\n [instantUpload]=\"true\"\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [maxFileSizeMb]=\"maxAttachmentSizeMb\"\n (filesChange)=\"onAttachmentsChange($event)\"\n ></app-upload-file>\n @if (form.controls.attachments.getError('uploading')) {\n <ion-note color=\"danger\">\n {{ 'SOCIAL.MESSAGE.ATTACHMENTS.UPLOADING_ERROR' | translate }}\n </ion-note>\n }\n </div>\n }\n</form>\n\n<ng-template #debugPanel>\n <app-debug title=\"Message form\">\n <ion-grid>\n <ion-row>\n <ion-col>\n ready: {{ readySubject | async }}\n <br />\n loading: {{ loadingSubject | async }}\n <br />\n enabled: {{ enabled }}\n <br />\n dirty: {{ dirty }}\n <br />\n valid: {{ valid }}\n <br />\n <br />\n error: {{ _form | formError | json }}\n <br />\n recipients: {{ _form | formGetValue: 'recipients' | personToString }}\n <br />\n recipientFilter: {{ _form | formGetValue: 'recipientFilter' | json }}\n </ion-col>\n </ion-row>\n </ion-grid>\n </app-debug>\n</ng-template>\n", styles: ["textarea.fixed-height{height:11.5em}textarea{min-height:11.5em}app-markdown-toolbar{display:block;margin-bottom:4px}.body-preview{box-sizing:border-box;height:calc(100% - 22px);min-height:calc(11.5em + 32px);padding:0 1em;overflow:auto;border:1px dashed var(--ion-item-border-color, #c8cdd4);border-radius:4px}.body-preview>markdown{display:block}.message-attachments{margin-top:8px;--app-upload-file-list-max-height: none}:host.file-over{display:block;outline:2px dashed var(--ion-color-accent);outline-offset:4px}\n"] }]
|
|
39741
40292
|
}], ctorParameters: () => [{ type: i0.Injector }, { type: i1$2.UntypedFormBuilder }, { type: i0.ChangeDetectorRef }], propDecorators: { mobile: [{
|
|
39742
40293
|
type: Input
|
|
39743
40294
|
}], suggestFn: [{
|
|
@@ -39767,8 +40318,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
39767
40318
|
type: Input
|
|
39768
40319
|
}], maxAttachmentSizeMb: [{
|
|
39769
40320
|
type: Input
|
|
40321
|
+
}], bodyPreviewSideBySide: [{
|
|
40322
|
+
type: Input,
|
|
40323
|
+
args: [{ transform: toBoolean }]
|
|
39770
40324
|
}], searchRecipients: [{
|
|
39771
40325
|
type: Output
|
|
40326
|
+
}], attach: [{
|
|
40327
|
+
type: Output
|
|
40328
|
+
}], bodyPreviewChange: [{
|
|
40329
|
+
type: Output
|
|
39772
40330
|
}], attachmentsUpload: [{
|
|
39773
40331
|
type: ViewChild,
|
|
39774
40332
|
args: ['attachmentsUpload']
|
|
@@ -39802,6 +40360,12 @@ class MessageModal {
|
|
|
39802
40360
|
accountService;
|
|
39803
40361
|
cd;
|
|
39804
40362
|
userController;
|
|
40363
|
+
/** Margin (in px) kept between the modal and the window borders, when the modal grows */
|
|
40364
|
+
static FIT_HEIGHT_MARGIN = 50;
|
|
40365
|
+
/** Min window width (in px) to display the body preview side by side with the text (2 columns), in a wider modal */
|
|
40366
|
+
static PREVIEW_SIDE_BY_SIDE_MIN_WINDOW_WIDTH = 1000;
|
|
40367
|
+
/** Max modal width (in px), when the body preview is displayed side by side */
|
|
40368
|
+
static PREVIEW_SIDE_BY_SIDE_MAX_WIDTH = 1200;
|
|
39805
40369
|
mobile;
|
|
39806
40370
|
debug = false;
|
|
39807
40371
|
title = 'SOCIAL.MESSAGE.NEW.TITLE';
|
|
@@ -39816,6 +40380,14 @@ class MessageModal {
|
|
|
39816
40380
|
deleteFn;
|
|
39817
40381
|
maxAttachmentSizeMb = 20;
|
|
39818
40382
|
form;
|
|
40383
|
+
elementRef = inject(ElementRef);
|
|
40384
|
+
/** Initial (default) height of the modal, used as the min height when fitting the content */
|
|
40385
|
+
initialHeight;
|
|
40386
|
+
/** Attachments count, at the last fit (used to allow the modal to shrink only when an attachment has been removed) */
|
|
40387
|
+
fittedAttachmentsCount = 0;
|
|
40388
|
+
resizeObserver;
|
|
40389
|
+
/** Display the body preview side by side with the text (if enough space), or replacing the text */
|
|
40390
|
+
bodyPreviewSideBySide = false;
|
|
39819
40391
|
constructor(settings, viewCtrl, accountService, cd, userController) {
|
|
39820
40392
|
this.settings = settings;
|
|
39821
40393
|
this.viewCtrl = viewCtrl;
|
|
@@ -39837,8 +40409,13 @@ class MessageModal {
|
|
|
39837
40409
|
}
|
|
39838
40410
|
this.form.markAsLoaded();
|
|
39839
40411
|
this.form.enable();
|
|
40412
|
+
// Grow the modal when attachments are added (or shrink back when removed)
|
|
40413
|
+
this.observeAttachmentsResize();
|
|
39840
40414
|
});
|
|
39841
40415
|
}
|
|
40416
|
+
ngOnDestroy() {
|
|
40417
|
+
this.resizeObserver?.disconnect();
|
|
40418
|
+
}
|
|
39842
40419
|
cancel() {
|
|
39843
40420
|
this.viewCtrl.dismiss();
|
|
39844
40421
|
}
|
|
@@ -39904,12 +40481,77 @@ class MessageModal {
|
|
|
39904
40481
|
markAsLoaded(opts) {
|
|
39905
40482
|
this.form.markAsLoaded(opts);
|
|
39906
40483
|
}
|
|
40484
|
+
/**
|
|
40485
|
+
* Observe the attachments area: when its size changes, fit the modal height to its content
|
|
40486
|
+
*/
|
|
40487
|
+
observeAttachmentsResize() {
|
|
40488
|
+
if (this.mobile || typeof ResizeObserver === 'undefined')
|
|
40489
|
+
return; // Skip on mobile (full screen modal)
|
|
40490
|
+
const attachmentsElement = this.elementRef.nativeElement.querySelector('.message-attachments');
|
|
40491
|
+
if (!attachmentsElement)
|
|
40492
|
+
return; // No attachments allowed
|
|
40493
|
+
this.resizeObserver = new ResizeObserver(() => this.fitHeightToContent());
|
|
40494
|
+
this.resizeObserver.observe(attachmentsElement);
|
|
40495
|
+
}
|
|
40496
|
+
/**
|
|
40497
|
+
* Fit the modal height to its content (to avoid any scroll), between its initial height,
|
|
40498
|
+
* and the window height (minus a margin). The modal never shrinks below its initial height.
|
|
40499
|
+
*
|
|
40500
|
+
* To avoid any flickering, the modal shrinks only when an attachment has been removed (or if `allowShrink`): the upload progress
|
|
40501
|
+
* (global and per file progress bars) makes the content grow temporarily, so the reached height is kept after the upload.
|
|
40502
|
+
*/
|
|
40503
|
+
fitHeightToContent(opts) {
|
|
40504
|
+
const page = this.elementRef.nativeElement; // The modal page (.ion-page), filling the modal
|
|
40505
|
+
const modal = page.closest('ion-modal');
|
|
40506
|
+
const content = page.querySelector('ion-content');
|
|
40507
|
+
const formElement = page.querySelector('app-message-form');
|
|
40508
|
+
if (!modal || !content || !formElement)
|
|
40509
|
+
return;
|
|
40510
|
+
const currentHeight = page.offsetHeight;
|
|
40511
|
+
this.initialHeight = this.initialHeight ?? currentHeight;
|
|
40512
|
+
const maxHeight = window.innerHeight - MessageModal.FIT_HEIGHT_MARGIN;
|
|
40513
|
+
if (this.initialHeight >= maxHeight)
|
|
40514
|
+
return; // Already full height: nothing to do
|
|
40515
|
+
// Required height = current height + content overflow (positive) or free space (negative)
|
|
40516
|
+
const contentPaddingBottom = parseFloat(getComputedStyle(content).getPropertyValue('--padding-bottom')) || 0;
|
|
40517
|
+
const delta = formElement.getBoundingClientRect().bottom + contentPaddingBottom - content.getBoundingClientRect().bottom;
|
|
40518
|
+
let height = Math.round(Math.max(this.initialHeight, Math.min(currentHeight + delta, maxHeight)));
|
|
40519
|
+
// Shrink only if an attachment has been removed
|
|
40520
|
+
const attachmentsCount = page.querySelectorAll('.message-attachments ion-item').length;
|
|
40521
|
+
if (height < currentHeight && !opts?.allowShrink && attachmentsCount >= this.fittedAttachmentsCount) {
|
|
40522
|
+
height = currentHeight;
|
|
40523
|
+
}
|
|
40524
|
+
this.fittedAttachmentsCount = attachmentsCount;
|
|
40525
|
+
if (Math.abs(height - currentHeight) >= 1) {
|
|
40526
|
+
modal.style.setProperty('--height', `${height}px`);
|
|
40527
|
+
}
|
|
40528
|
+
}
|
|
40529
|
+
/**
|
|
40530
|
+
* When the body preview is toggled: if enough space, widen the modal to display the preview side by side with the text;
|
|
40531
|
+
* otherwise (e.g. mobile, or small window), the preview replaces the text
|
|
40532
|
+
*/
|
|
40533
|
+
onBodyPreviewChange(preview) {
|
|
40534
|
+
const modal = this.elementRef.nativeElement.closest('ion-modal');
|
|
40535
|
+
if (preview) {
|
|
40536
|
+
this.bodyPreviewSideBySide = !this.mobile && window.innerWidth >= MessageModal.PREVIEW_SIDE_BY_SIDE_MIN_WINDOW_WIDTH;
|
|
40537
|
+
}
|
|
40538
|
+
if (preview && this.bodyPreviewSideBySide) {
|
|
40539
|
+
modal?.style.setProperty('--width', `min(${MessageModal.PREVIEW_SIDE_BY_SIDE_MAX_WIDTH}px, calc(100% - ${MessageModal.FIT_HEIGHT_MARGIN * 2}px))`);
|
|
40540
|
+
}
|
|
40541
|
+
else {
|
|
40542
|
+
modal?.style.removeProperty('--width');
|
|
40543
|
+
}
|
|
40544
|
+
this.markForCheck();
|
|
40545
|
+
// Fit the height to the new content (after the rendering)
|
|
40546
|
+
if (!this.mobile)
|
|
40547
|
+
setTimeout(() => this.fitHeightToContent({ allowShrink: true }));
|
|
40548
|
+
}
|
|
39907
40549
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageModal, deps: [{ token: LocalSettingsService }, { token: i2$2.ModalController }, { token: AccountService }, { token: i0.ChangeDetectorRef }, { token: USER_CONTROLLER, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
39908
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MessageModal, isStandalone: false, selector: "app-message-modal", inputs: { debug: ["debug", "debug", booleanAttribute], title: "title", suggestFn: "suggestFn", data: "data", canSelectType: ["canSelectType", "canSelectType", booleanAttribute], canRecipientFilter: ["canRecipientFilter", "canRecipientFilter", booleanAttribute], canSearchRecipients: ["canSearchRecipients", "canSearchRecipients", booleanAttribute], recipientFilterCount: ["recipientFilterCount", "recipientFilterCount", booleanAttribute], canAttach: ["canAttach", "canAttach", booleanAttribute], uploadFn: "uploadFn", deleteFn: "deleteFn", maxAttachmentSizeMb: "maxAttachmentSizeMb" }, viewQueries: [{ propertyName: "form", first: true, predicate: ["form"], descendants: true, static: true }], ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-buttons slot=\"start\">\n <ion-button class=\"back-button\" (click)=\"cancel()\" visible-xs visible-sm visible-mobile>\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title>\n {{ title | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button\n class=\"back-button\"\n (click)=\"doSubmit()\"\n [disabled]=\"!form.valid\"\n visible-xs\n visible-sm\n visible-mobile\n >\n <ion-icon slot=\"icon-only\" name=\"checkmark\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (form.error) {\n <ion-item visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\"><span [innerHTML]=\"form.error | translate\"></span></ion-label>\n </ion-item>\n }\n\n <app-message-form\n #form\n (onSubmit)=\"doSubmit()\"\n (onCancel)=\"cancel()\"\n [canSelectType]=\"canSelectType\"\n [canRecipientFilter]=\"canRecipientFilter\"\n [recipientFilterCount]=\"recipientFilterCount\"\n [suggestFn]=\"suggestFn\"\n [canSearchRecipients]=\"canSearchRecipients\"\n [canAttach]=\"canAttach\"\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [maxAttachmentSizeMb]=\"maxAttachmentSizeMb\"\n (searchRecipients)=\"openSelectUsers($event)\"\n [debug]=\"debug\"\n ></app-message-form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <!-- buttons -->\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-text>{{ 'COMMON.BTN_CANCEL' | translate }}</ion-text>\n </ion-button>\n\n <ion-button\n [fill]=\"form.invalid ? 'clear' : 'solid'\"\n [disabled]=\"form.loading || form.invalid\"\n (keyup.enter)=\"doSubmit()\"\n (click)=\"doSubmit()\"\n color=\"tertiary\"\n >\n <ion-text>{{ 'COMMON.BTN_SEND' | translate }}</ion-text>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", dependencies: [{ kind: "component", type: i2$2.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$2.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$2.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$2.IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$2.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$2.IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$2.IonRow, selector: "ion-row" }, { kind: "component", type: i2$2.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: MessageForm, selector: "app-message-form", inputs: ["mobile", "suggestFn", "subjectMinLength", "subjectMaxLength", "bodyMaxLength", "bodyAutoHeight", "canSelectType", "canRecipientFilter", "recipientFilterCount", "canSearchRecipients", "canAttach", "uploadFn", "deleteFn", "maxAttachmentSizeMb"], outputs: ["searchRecipients"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
40550
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MessageModal, isStandalone: false, selector: "app-message-modal", inputs: { debug: ["debug", "debug", booleanAttribute], title: "title", suggestFn: "suggestFn", data: "data", canSelectType: ["canSelectType", "canSelectType", booleanAttribute], canRecipientFilter: ["canRecipientFilter", "canRecipientFilter", booleanAttribute], canSearchRecipients: ["canSearchRecipients", "canSearchRecipients", booleanAttribute], recipientFilterCount: ["recipientFilterCount", "recipientFilterCount", booleanAttribute], canAttach: ["canAttach", "canAttach", booleanAttribute], uploadFn: "uploadFn", deleteFn: "deleteFn", maxAttachmentSizeMb: "maxAttachmentSizeMb" }, viewQueries: [{ propertyName: "form", first: true, predicate: ["form"], descendants: true, static: true }], ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-buttons slot=\"start\">\n <ion-button class=\"back-button\" (click)=\"cancel()\" visible-xs visible-sm visible-mobile>\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title>\n {{ title | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button\n class=\"back-button\"\n (click)=\"doSubmit()\"\n [disabled]=\"!form.valid\"\n visible-xs\n visible-sm\n visible-mobile\n >\n <ion-icon slot=\"icon-only\" name=\"checkmark\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (form.error) {\n <ion-item visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\"><span [innerHTML]=\"form.error | translate\"></span></ion-label>\n </ion-item>\n }\n\n <app-message-form\n #form\n (onSubmit)=\"doSubmit()\"\n (onCancel)=\"cancel()\"\n [canSelectType]=\"canSelectType\"\n [canRecipientFilter]=\"canRecipientFilter\"\n [recipientFilterCount]=\"recipientFilterCount\"\n [suggestFn]=\"suggestFn\"\n [canSearchRecipients]=\"canSearchRecipients\"\n [canAttach]=\"canAttach\"\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [maxAttachmentSizeMb]=\"maxAttachmentSizeMb\"\n [bodyPreviewSideBySide]=\"bodyPreviewSideBySide\"\n (bodyPreviewChange)=\"onBodyPreviewChange($event)\"\n (searchRecipients)=\"openSelectUsers($event)\"\n [debug]=\"debug\"\n ></app-message-form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <!-- buttons -->\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-text>{{ 'COMMON.BTN_CANCEL' | translate }}</ion-text>\n </ion-button>\n\n <ion-button\n [fill]=\"form.invalid ? 'clear' : 'solid'\"\n [disabled]=\"form.loading || form.invalid\"\n (keyup.enter)=\"doSubmit()\"\n (click)=\"doSubmit()\"\n color=\"tertiary\"\n >\n <ion-text>{{ 'COMMON.BTN_SEND' | translate }}</ion-text>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", dependencies: [{ kind: "component", type: i2$2.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$2.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$2.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$2.IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$2.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$2.IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$2.IonRow, selector: "ion-row" }, { kind: "component", type: i2$2.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: MessageForm, selector: "app-message-form", inputs: ["mobile", "suggestFn", "subjectMinLength", "subjectMaxLength", "bodyMaxLength", "bodyAutoHeight", "canSelectType", "canRecipientFilter", "recipientFilterCount", "canSearchRecipients", "canAttach", "uploadFn", "deleteFn", "maxAttachmentSizeMb", "bodyPreviewSideBySide"], outputs: ["searchRecipients", "attach", "bodyPreviewChange"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
39909
40551
|
}
|
|
39910
40552
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageModal, decorators: [{
|
|
39911
40553
|
type: Component,
|
|
39912
|
-
args: [{ selector: 'app-message-modal', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-buttons slot=\"start\">\n <ion-button class=\"back-button\" (click)=\"cancel()\" visible-xs visible-sm visible-mobile>\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title>\n {{ title | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button\n class=\"back-button\"\n (click)=\"doSubmit()\"\n [disabled]=\"!form.valid\"\n visible-xs\n visible-sm\n visible-mobile\n >\n <ion-icon slot=\"icon-only\" name=\"checkmark\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (form.error) {\n <ion-item visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\"><span [innerHTML]=\"form.error | translate\"></span></ion-label>\n </ion-item>\n }\n\n <app-message-form\n #form\n (onSubmit)=\"doSubmit()\"\n (onCancel)=\"cancel()\"\n [canSelectType]=\"canSelectType\"\n [canRecipientFilter]=\"canRecipientFilter\"\n [recipientFilterCount]=\"recipientFilterCount\"\n [suggestFn]=\"suggestFn\"\n [canSearchRecipients]=\"canSearchRecipients\"\n [canAttach]=\"canAttach\"\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [maxAttachmentSizeMb]=\"maxAttachmentSizeMb\"\n (searchRecipients)=\"openSelectUsers($event)\"\n [debug]=\"debug\"\n ></app-message-form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <!-- buttons -->\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-text>{{ 'COMMON.BTN_CANCEL' | translate }}</ion-text>\n </ion-button>\n\n <ion-button\n [fill]=\"form.invalid ? 'clear' : 'solid'\"\n [disabled]=\"form.loading || form.invalid\"\n (keyup.enter)=\"doSubmit()\"\n (click)=\"doSubmit()\"\n color=\"tertiary\"\n >\n <ion-text>{{ 'COMMON.BTN_SEND' | translate }}</ion-text>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n" }]
|
|
40554
|
+
args: [{ selector: 'app-message-modal', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-buttons slot=\"start\">\n <ion-button class=\"back-button\" (click)=\"cancel()\" visible-xs visible-sm visible-mobile>\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title>\n {{ title | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button\n class=\"back-button\"\n (click)=\"doSubmit()\"\n [disabled]=\"!form.valid\"\n visible-xs\n visible-sm\n visible-mobile\n >\n <ion-icon slot=\"icon-only\" name=\"checkmark\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n @if (form.error) {\n <ion-item visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\"><span [innerHTML]=\"form.error | translate\"></span></ion-label>\n </ion-item>\n }\n\n <app-message-form\n #form\n (onSubmit)=\"doSubmit()\"\n (onCancel)=\"cancel()\"\n [canSelectType]=\"canSelectType\"\n [canRecipientFilter]=\"canRecipientFilter\"\n [recipientFilterCount]=\"recipientFilterCount\"\n [suggestFn]=\"suggestFn\"\n [canSearchRecipients]=\"canSearchRecipients\"\n [canAttach]=\"canAttach\"\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [maxAttachmentSizeMb]=\"maxAttachmentSizeMb\"\n [bodyPreviewSideBySide]=\"bodyPreviewSideBySide\"\n (bodyPreviewChange)=\"onBodyPreviewChange($event)\"\n (searchRecipients)=\"openSelectUsers($event)\"\n [debug]=\"debug\"\n ></app-message-form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <!-- buttons -->\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-text>{{ 'COMMON.BTN_CANCEL' | translate }}</ion-text>\n </ion-button>\n\n <ion-button\n [fill]=\"form.invalid ? 'clear' : 'solid'\"\n [disabled]=\"form.loading || form.invalid\"\n (keyup.enter)=\"doSubmit()\"\n (click)=\"doSubmit()\"\n color=\"tertiary\"\n >\n <ion-text>{{ 'COMMON.BTN_SEND' | translate }}</ion-text>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n" }]
|
|
39913
40555
|
}], ctorParameters: () => [{ type: LocalSettingsService }, { type: i2$2.ModalController }, { type: AccountService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
|
|
39914
40556
|
type: Optional
|
|
39915
40557
|
}, {
|
|
@@ -45174,14 +45816,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
45174
45816
|
|
|
45175
45817
|
class MessageModule {
|
|
45176
45818
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
45177
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.22", ngImport: i0, type: MessageModule, declarations: [MessageModal, MessageForm], imports: [CommonModule, SharedModule, CorePipesModule, MatChipsModule, SharedDebugModule], exports: [MessageModal, MessageForm] });
|
|
45178
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageModule, providers: [provideChildTranslateService(), { provide: USER_CONTROLLER, useExisting: UserController }], imports: [CommonModule, SharedModule, CorePipesModule, MatChipsModule, SharedDebugModule] });
|
|
45819
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.22", ngImport: i0, type: MessageModule, declarations: [MessageModal, MessageForm], imports: [CommonModule, SharedModule, CorePipesModule, MatChipsModule, SharedDebugModule, SharedMarkdownModule], exports: [MessageModal, MessageForm] });
|
|
45820
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageModule, providers: [provideChildTranslateService(), { provide: USER_CONTROLLER, useExisting: UserController }], imports: [CommonModule, SharedModule, CorePipesModule, MatChipsModule, SharedDebugModule, SharedMarkdownModule] });
|
|
45179
45821
|
}
|
|
45180
45822
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageModule, decorators: [{
|
|
45181
45823
|
type: NgModule,
|
|
45182
45824
|
args: [{
|
|
45183
45825
|
// Do NOT import CoreModule here (circular dependency: CoreModule > home > FeedModule > MessageModule)
|
|
45184
|
-
imports: [CommonModule, SharedModule, CorePipesModule, MatChipsModule, SharedDebugModule],
|
|
45826
|
+
imports: [CommonModule, SharedModule, CorePipesModule, MatChipsModule, SharedDebugModule, SharedMarkdownModule],
|
|
45185
45827
|
declarations: [MessageModal, MessageForm],
|
|
45186
45828
|
exports: [MessageModal, MessageForm],
|
|
45187
45829
|
providers: [provideChildTranslateService(), { provide: USER_CONTROLLER, useExisting: UserController }],
|
|
@@ -52163,6 +52805,18 @@ function createApolloClientOptions() {
|
|
|
52163
52805
|
const httpLinkInstance = httpLink.create({
|
|
52164
52806
|
uri: (o) => $url.value,
|
|
52165
52807
|
});
|
|
52808
|
+
// Queries link
|
|
52809
|
+
let queryLink = ApolloLink.from([retryLink, authLink, httpLinkInstance]);
|
|
52810
|
+
// Enable @defer (incremental delivery): send queries with @defer using the Apollo fetch link,
|
|
52811
|
+
// because the apollo-angular HttpLink (Angular HttpClient) cannot read a streamed multipart/mixed response
|
|
52812
|
+
const enableDefer = environment.enableGraphqlDefer === true;
|
|
52813
|
+
if (enableDefer) {
|
|
52814
|
+
console.info('[graphql] Enable @defer directive (incremental delivery)');
|
|
52815
|
+
const fetchHttpLink = new HttpLink$1({
|
|
52816
|
+
uri: () => $url.value,
|
|
52817
|
+
});
|
|
52818
|
+
queryLink = ApolloLink.split(isDeferredOperation, ApolloLink.from([retryLink, authLink, httpHeadersToRecordLink, fetchHttpLink]), queryLink);
|
|
52819
|
+
}
|
|
52166
52820
|
// Cache
|
|
52167
52821
|
const cache = new InMemoryCache({
|
|
52168
52822
|
typePolicies,
|
|
@@ -52218,7 +52872,12 @@ function createApolloClientOptions() {
|
|
|
52218
52872
|
// Handle subscriptions
|
|
52219
52873
|
isSubscriptionOperation, wsLink,
|
|
52220
52874
|
// Handle queries
|
|
52221
|
-
|
|
52875
|
+
queryLink)),
|
|
52876
|
+
// @defer support
|
|
52877
|
+
...(enableDefer
|
|
52878
|
+
? { incrementalHandler: new Defer20220824Handler() }
|
|
52879
|
+
: // Remove @defer directives, when disabled
|
|
52880
|
+
{ documentTransform: new DocumentTransform(removeDeferDirectives) }),
|
|
52222
52881
|
// For @apollo/client < 3.11
|
|
52223
52882
|
//connectToDevTools: !environment.production,
|
|
52224
52883
|
// For @apollo/client 3.11+
|
|
@@ -58800,12 +59459,117 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
58800
59459
|
}]
|
|
58801
59460
|
}] });
|
|
58802
59461
|
|
|
59462
|
+
// Free license (GPL version 3)
|
|
59463
|
+
// Fake recipients, used by the autocomplete field (no pod needed)
|
|
59464
|
+
const FAKE_PERSONS = [
|
|
59465
|
+
{ id: 1, firstName: 'Demo', lastName: 'Admin', department: { id: 1, label: 'FROM-NORD', name: 'FROM Nord' } },
|
|
59466
|
+
{ id: 2, firstName: 'Demo', lastName: 'Observer', department: { id: 1, label: 'FROM-NORD', name: 'FROM Nord' } },
|
|
59467
|
+
{ id: 3, firstName: 'Charles', lastName: 'Dupond', department: { id: 2, label: 'IFREMER', name: 'Ifremer' } },
|
|
59468
|
+
].map(Person.fromObject);
|
|
59469
|
+
const SAMPLE_BODY = `# Title
|
|
59470
|
+
|
|
59471
|
+
Some **bold**, <u>underlined</u>, ~~strikethrough~~ text, and a ==highlighted== word.
|
|
59472
|
+
|
|
59473
|
+
## Sub title
|
|
59474
|
+
|
|
59475
|
+
- see [the docs](https://gitlab.ifremer.fr/sih-public/sumaris/ngx-sumaris-components)
|
|
59476
|
+
`;
|
|
59477
|
+
class MessageFormTestingPage {
|
|
59478
|
+
modalCtrl;
|
|
59479
|
+
cd;
|
|
59480
|
+
form;
|
|
59481
|
+
debug = false;
|
|
59482
|
+
canSelectType = true;
|
|
59483
|
+
canAttach = true;
|
|
59484
|
+
bodyPreviewSideBySide = false;
|
|
59485
|
+
modalResult;
|
|
59486
|
+
suggestFn = async (value) => {
|
|
59487
|
+
const search = typeof value === 'string' ? value.toLowerCase() : '';
|
|
59488
|
+
return FAKE_PERSONS.filter((p) => !search || `${p.lastName} ${p.firstName}`.toLowerCase().includes(search));
|
|
59489
|
+
};
|
|
59490
|
+
/** Simulate an upload (with progression) */
|
|
59491
|
+
uploadFn = (file) => {
|
|
59492
|
+
const steps = 5;
|
|
59493
|
+
return timer(0, 100).pipe(take(steps + 1), map((i) => i < steps
|
|
59494
|
+
? { type: HttpEventType.UploadProgress, total: file.size, loaded: (file.size * i) / steps }
|
|
59495
|
+
: new HttpResponse({ body: { finalName: file.name } })));
|
|
59496
|
+
};
|
|
59497
|
+
deleteFn = async (_file) => {
|
|
59498
|
+
await sleep(500);
|
|
59499
|
+
return true;
|
|
59500
|
+
};
|
|
59501
|
+
constructor(modalCtrl, cd) {
|
|
59502
|
+
this.modalCtrl = modalCtrl;
|
|
59503
|
+
this.cd = cd;
|
|
59504
|
+
}
|
|
59505
|
+
ngAfterViewInit() {
|
|
59506
|
+
setTimeout(() => this.reset());
|
|
59507
|
+
}
|
|
59508
|
+
async reset(withSample) {
|
|
59509
|
+
this.form.markAsReady({ emitEvent: false });
|
|
59510
|
+
await this.form.setValue(Message.fromObject({
|
|
59511
|
+
type: MessageTypes.INBOX_MESSAGE,
|
|
59512
|
+
recipients: [FAKE_PERSONS[0]],
|
|
59513
|
+
subject: withSample ? 'Markdown sample' : null,
|
|
59514
|
+
body: withSample ? SAMPLE_BODY : null,
|
|
59515
|
+
}));
|
|
59516
|
+
this.form.markAsLoaded();
|
|
59517
|
+
this.form.enable();
|
|
59518
|
+
this.cd.markForCheck();
|
|
59519
|
+
}
|
|
59520
|
+
async openModal() {
|
|
59521
|
+
const modal = await this.modalCtrl.create({
|
|
59522
|
+
component: MessageModal,
|
|
59523
|
+
componentProps: {
|
|
59524
|
+
suggestFn: this.suggestFn,
|
|
59525
|
+
canSelectType: this.canSelectType,
|
|
59526
|
+
canAttach: this.canAttach,
|
|
59527
|
+
uploadFn: this.uploadFn,
|
|
59528
|
+
deleteFn: this.deleteFn,
|
|
59529
|
+
debug: this.debug,
|
|
59530
|
+
data: Message.fromObject({ recipients: [FAKE_PERSONS[0]] }),
|
|
59531
|
+
},
|
|
59532
|
+
keyboardClose: true,
|
|
59533
|
+
});
|
|
59534
|
+
await modal.present();
|
|
59535
|
+
const { data } = await modal.onDidDismiss();
|
|
59536
|
+
console.info('[message-form-testing] Modal result:', data);
|
|
59537
|
+
this.modalResult = data;
|
|
59538
|
+
this.cd.markForCheck();
|
|
59539
|
+
}
|
|
59540
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageFormTestingPage, deps: [{ token: i2$2.ModalController }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
59541
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MessageFormTestingPage, isStandalone: false, selector: "app-message-form-testing", viewQueries: [{ propertyName: "form", first: true, predicate: ["form"], descendants: true, static: true }], ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n <ion-title>Message form test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <ion-grid class=\"form-container ion-padding-top\">\n <!-- options -->\n <ion-row>\n <ion-col>\n <mat-checkbox [(ngModel)]=\"canSelectType\">canSelectType</mat-checkbox>\n <mat-checkbox [(ngModel)]=\"canAttach\">canAttach</mat-checkbox>\n <mat-checkbox [(ngModel)]=\"bodyPreviewSideBySide\">bodyPreviewSideBySide</mat-checkbox>\n <mat-checkbox [(ngModel)]=\"debug\">debug</mat-checkbox>\n </ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"outline\" (click)=\"reset()\">Reset</ion-button>\n <ion-button fill=\"outline\" (click)=\"reset(true)\">Markdown sample</ion-button>\n <ion-button (click)=\"openModal()\">Open in a modal</ion-button>\n </ion-col>\n </ion-row>\n\n <!-- inline form -->\n <ion-row>\n <ion-col size=\"12\" size-lg=\"6\">\n <ion-card>\n <ion-card-content>\n <app-message-form\n #form\n [suggestFn]=\"suggestFn\"\n [canSelectType]=\"canSelectType\"\n [canAttach]=\"canAttach\"\n [bodyPreviewSideBySide]=\"bodyPreviewSideBySide\"\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [debug]=\"debug\"\n ></app-message-form>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- form value -->\n <ion-col size=\"12\" size-lg=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-subtitle>Form value (valid: {{ form.valid }})</ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <pre>{{ form.form?.value | json }}</pre>\n </ion-card-content>\n </ion-card>\n\n @if (modalResult) {\n <ion-card>\n <ion-card-header>\n <ion-card-subtitle>Modal result</ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <pre>{{ modalResult | json }}</pre>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n </ion-row>\n </ion-grid>\n</ion-content>\n", dependencies: [{ kind: "component", type: i2$2.IonBackButton, selector: "ion-back-button" }, { kind: "component", type: i2$2.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$2.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$2.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$2.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$2.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2$2.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i2$2.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$2.IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$2.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$2.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$2.IonRow, selector: "ion-row" }, { kind: "component", type: i2$2.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$2.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: MessageForm, selector: "app-message-form", inputs: ["mobile", "suggestFn", "subjectMinLength", "subjectMaxLength", "bodyMaxLength", "bodyAutoHeight", "canSelectType", "canRecipientFilter", "recipientFilterCount", "canSearchRecipients", "canAttach", "uploadFn", "deleteFn", "maxAttachmentSizeMb", "bodyPreviewSideBySide"], outputs: ["searchRecipients", "attach", "bodyPreviewChange"] }, { kind: "pipe", type: i2$3.JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
59542
|
+
}
|
|
59543
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageFormTestingPage, decorators: [{
|
|
59544
|
+
type: Component,
|
|
59545
|
+
args: [{ selector: 'app-message-form-testing', changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n <ion-title>Message form test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <ion-grid class=\"form-container ion-padding-top\">\n <!-- options -->\n <ion-row>\n <ion-col>\n <mat-checkbox [(ngModel)]=\"canSelectType\">canSelectType</mat-checkbox>\n <mat-checkbox [(ngModel)]=\"canAttach\">canAttach</mat-checkbox>\n <mat-checkbox [(ngModel)]=\"bodyPreviewSideBySide\">bodyPreviewSideBySide</mat-checkbox>\n <mat-checkbox [(ngModel)]=\"debug\">debug</mat-checkbox>\n </ion-col>\n <ion-col size=\"auto\">\n <ion-button fill=\"outline\" (click)=\"reset()\">Reset</ion-button>\n <ion-button fill=\"outline\" (click)=\"reset(true)\">Markdown sample</ion-button>\n <ion-button (click)=\"openModal()\">Open in a modal</ion-button>\n </ion-col>\n </ion-row>\n\n <!-- inline form -->\n <ion-row>\n <ion-col size=\"12\" size-lg=\"6\">\n <ion-card>\n <ion-card-content>\n <app-message-form\n #form\n [suggestFn]=\"suggestFn\"\n [canSelectType]=\"canSelectType\"\n [canAttach]=\"canAttach\"\n [bodyPreviewSideBySide]=\"bodyPreviewSideBySide\"\n [uploadFn]=\"uploadFn\"\n [deleteFn]=\"deleteFn\"\n [debug]=\"debug\"\n ></app-message-form>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <!-- form value -->\n <ion-col size=\"12\" size-lg=\"6\">\n <ion-card>\n <ion-card-header>\n <ion-card-subtitle>Form value (valid: {{ form.valid }})</ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <pre>{{ form.form?.value | json }}</pre>\n </ion-card-content>\n </ion-card>\n\n @if (modalResult) {\n <ion-card>\n <ion-card-header>\n <ion-card-subtitle>Modal result</ion-card-subtitle>\n </ion-card-header>\n <ion-card-content>\n <pre>{{ modalResult | json }}</pre>\n </ion-card-content>\n </ion-card>\n }\n </ion-col>\n </ion-row>\n </ion-grid>\n</ion-content>\n" }]
|
|
59546
|
+
}], ctorParameters: () => [{ type: i2$2.ModalController }, { type: i0.ChangeDetectorRef }], propDecorators: { form: [{
|
|
59547
|
+
type: ViewChild,
|
|
59548
|
+
args: ['form', { static: true }]
|
|
59549
|
+
}] } });
|
|
59550
|
+
|
|
59551
|
+
class MessageFormTestingModule {
|
|
59552
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageFormTestingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
59553
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.22", ngImport: i0, type: MessageFormTestingModule, declarations: [MessageFormTestingPage], imports: [SharedModule, FormsModule, MessageModule], exports: [MessageFormTestingPage] });
|
|
59554
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageFormTestingModule, providers: [provideChildTranslateService()], imports: [SharedModule, FormsModule, MessageModule] });
|
|
59555
|
+
}
|
|
59556
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MessageFormTestingModule, decorators: [{
|
|
59557
|
+
type: NgModule,
|
|
59558
|
+
args: [{
|
|
59559
|
+
imports: [SharedModule, FormsModule, MessageModule],
|
|
59560
|
+
declarations: [MessageFormTestingPage],
|
|
59561
|
+
exports: [MessageFormTestingPage],
|
|
59562
|
+
providers: [provideChildTranslateService()],
|
|
59563
|
+
}]
|
|
59564
|
+
}] });
|
|
59565
|
+
|
|
58803
59566
|
// Testing pages, declared outside the testing module, to be imported without loading the (lazy) testing module
|
|
58804
59567
|
const SOCIAL_TESTING_PAGES = [
|
|
58805
59568
|
{ label: 'Social components', divider: true },
|
|
58806
59569
|
{ label: 'Job progression', page: '/testing/social/job-progression' },
|
|
58807
59570
|
{ label: 'User event', page: '/testing/social/user-event' },
|
|
58808
59571
|
{ label: 'Feed', page: '/testing/social/feed' },
|
|
59572
|
+
{ label: 'Message form', page: '/testing/social/message-form' },
|
|
58809
59573
|
];
|
|
58810
59574
|
|
|
58811
59575
|
const routes = [
|
|
@@ -58824,6 +59588,11 @@ const routes = [
|
|
|
58824
59588
|
pathMatch: 'full',
|
|
58825
59589
|
component: FeedTestingPage,
|
|
58826
59590
|
},
|
|
59591
|
+
{
|
|
59592
|
+
path: 'message-form',
|
|
59593
|
+
pathMatch: 'full',
|
|
59594
|
+
component: MessageFormTestingPage,
|
|
59595
|
+
},
|
|
58827
59596
|
{
|
|
58828
59597
|
path: 'feed/page',
|
|
58829
59598
|
pathMatch: 'full',
|
|
@@ -58836,13 +59605,15 @@ class SocialTestingModule {
|
|
|
58836
59605
|
// Sub modules
|
|
58837
59606
|
JobTestingModule,
|
|
58838
59607
|
UserEventTestingModule,
|
|
58839
|
-
FeedTestingModule
|
|
59608
|
+
FeedTestingModule,
|
|
59609
|
+
MessageFormTestingModule] });
|
|
58840
59610
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: SocialTestingModule, providers: [provideChildTranslateService()], imports: [CommonModule,
|
|
58841
59611
|
RouterModule.forChild(routes),
|
|
58842
59612
|
// Sub modules
|
|
58843
59613
|
JobTestingModule,
|
|
58844
59614
|
UserEventTestingModule,
|
|
58845
|
-
FeedTestingModule
|
|
59615
|
+
FeedTestingModule,
|
|
59616
|
+
MessageFormTestingModule] });
|
|
58846
59617
|
}
|
|
58847
59618
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: SocialTestingModule, decorators: [{
|
|
58848
59619
|
type: NgModule,
|
|
@@ -58854,6 +59625,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
58854
59625
|
JobTestingModule,
|
|
58855
59626
|
UserEventTestingModule,
|
|
58856
59627
|
FeedTestingModule,
|
|
59628
|
+
MessageFormTestingModule,
|
|
58857
59629
|
],
|
|
58858
59630
|
providers: [provideChildTranslateService()],
|
|
58859
59631
|
}]
|
|
@@ -58894,5 +59666,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
58894
59666
|
* Generated bundle index. Do not edit.
|
|
58895
59667
|
*/
|
|
58896
59668
|
|
|
58897
|
-
export { ADMIN_ICONS, APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_ACCOUNT_SERVICE, APP_ACCOUNT_SERVICE_OPTIONS, APP_CELL_SELECTION_SERVICE_CONFIG_TOKEN, APP_CELL_SELECTION_SERVICE_TOKEN, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FEED_SERVICE, APP_FILE_TRANSFER_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_SERVICE, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_HOME_CONFIG, APP_HOME_TOOLBAR_BUTTONS, APP_HOTKEYS_CONFIG, APP_INPUT_FIELD, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_MESSAGE_SERVICE_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PERSON_SERVICE, APP_PERSON_SERVICE_OPTIONS, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_SHOW_TOOLTIP, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TABLE_OPTIONS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractNamedFilterService, AbstractPersonService, AbstractSelectionModelPipe, AbstractTableSelectionPipe, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, AccountUtils, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppChangePasswordModule, AppChangePasswordPage, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormContainer, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppHomePageModule, AppIconComponent, AppIconModule, AppIconSelectorField, AppIconSelectorModal, AppIconSelectorModule, AppImageGalleryComponent, AppImageGallerySlideshowComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMarkdownContent, AppMarkdownModal, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppPropertiesUtils, AppPropertyUtils, AppRegisterModule, AppResetPasswordModal, AppRowField, AppSelectPeerModule, AppSelectUsersModal, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextFormModule, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppWebSocket, AppendQueryParamsPipePipe, ArrayDistinctPipe, ArrayFilterPipe, ArrayFindByPropertyPipe, ArrayFindPipe, ArrayFirstPipe, ArrayFormTestPage, ArrayIncludesPipe, ArrayJoinPipe, ArrayLastPipe, ArrayLengthPipe, ArrayMapPipe, ArrayPluckPipe, ArraySlicePipe, ArraySortPipe, AsAnyPipe, AsArrayPipe, AsBooleanPipe, AsFloatLabelTypePipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoResizeDirective, AutoTitleDirective, AutoTooltipDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, BadgeNumberPipe, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, BooleanFormatPipe, BooleanTestPage, CORE_CONFIG_OPTIONS, CORE_ICONS, CORE_TESTING_PAGES, CapitalizePipe, CellIdentifierDirective, CellSelectionDirective, CellSelectionService, CellValueChangeListener, ChangeCaseToUnderscorePipe, ChangePasswordForm, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigFragments, ConfigService, Configuration, CoreModule, CorePipesModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_ISO_PATTERNS, DATE_MATCH_REGEXP, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_JOIN_ARRAY_VALUES_SEPARATOR, DEFAULT_JOIN_PROPERTIES_SEPARATOR, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateFromPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DisplayWithPipe, DragAndDropDirective, DurationPipe, DurationTestPage, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, EMPTY_PLACEHOLDER_CHAR_REGEXP_GLOBAL, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EmptyMissingTranslationHandler, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EnvironmentHttpLoader, EnvironmentLoader, ErrorCodes, EvenPipe, FILE_KIND_ICONS, FeedAttachmentUtils, FeedDirective, FeedModule, FeedPage, FeedService, FeedsComponent, FileAttachmentComponent, FileAttachmentPreviewModal, FileAttachmentUtils, FileDownloadService, FileResponse, FileService, FileSizePipe, FileTypeUtils, FilesUtils, FirstFalsePipe, FirstPipe, FirstTruePipe, FormArrayAtControlPipe, FormArrayAtGroupPipe, FormArrayHelper, FormArrayTestModule, FormButtonsBarComponent, FormButtonsBarToken, FormErrorPipe, FormErrorTranslatePipe, FormErrorTranslator, FormFieldDefinitionUtils, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetNamePipe, FormGetPipe, FormGetValuePipe, GalleryTestPage, GeolocationUtils, GitlabUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, HttpUtils, IMAGE_DEFAULTS, ION_ICONS, IPosition, IconSelectorTestPage, IconSelectorTestingModule, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImageSizes, ImagesUtils, InMemoryEntitiesService, InputFieldTestPage, IsAllSelectedPipe, IsEmptySelectionPipe, IsLoginAccountPipe, IsMultipleSelectionPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotAllSelectedPipe, IsNotEmptySelectionPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, IsSelectedPipe, IsSingleSelectionPipe, IsValidDatePipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonFeedUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_PATTERNS, LAT_LONG_PATTERN_MAX_DECIMALS, LAT_LONG_VALUE_MAX_DECIMALS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, ListFormTestPage, ListFormTestingModule, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MASKS, MASK_RANGES, MAT_FORM_FIELD_DEFAULT_APPEARANCE, MAT_FORM_FIELD_DEFAULT_SUBSCRIPT_SIZING, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapPipe, MapToPipe, MapValuesPipe, MarkdownDirective, MarkdownService, MarkdownTestPage, MarkdownTestingModule, MarkdownUtils, MaskitoPlaceholderPipe, MaskitoTestPage, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBadgeTestPage, MatBooleanField, MatChipsField, MatColorPipe, MatCommonTestPage, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatLatLongFieldInput, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialAutocompleteFooterDirective, MaterialAutocompleteHeaderDirective, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItem, MenuItems, MenuOptions, MenuService, MenuTestingModule, MenuTestingPage, Message, MessageAttachment, MessageAttachmentUtils, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NETWORK_DEFAULT_CONNECTION_TIMEOUT, NamedFilter, NamedFilterFilter, NamedFilterSelector, NamedFilterSelectorTestingModule, NamedFilterSelectorTestingPage, NativeWebSocket, NavActionsColumnComponent, NestedTableTestPage, NetworkService, NetworkUtils, NewTokenForm, NewTokenModal, NgInitDirective, NgVarDirective, NoHtmlPipe, NotEmptyArrayPipe, NumberFormatPipe, ObservableTestPage, OddPipe, OtherMenuTestingPage, PEER_URL_REGEXP, PLUS_PLACEHOLDER_CHAR_REGEXP_GLOBAL, PRINT_ID_QUERY_PARAM, PRINT_LOADING_STORAGE_KEY_PREFIX, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFilterAdditionalFields, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, PrintService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialToStringPipe, ReferentialUtils, ReferentialValidatorService, ReferentialsToStringPipe, RegExpUtils, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, RoundPipe, RxStateComputed, RxStateModule, RxStateOutput, RxStateProperty, RxStateRegister, RxStateSelect, SCRYPT_PARAMS, SETTINGS_COMPACT_ROWS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_ICONS, SHARED_IONIC_COMPONENTS, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_CONFIG_OPTIONS, SOCIAL_ICONS, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SPACE_PLACEHOLDER_CHAR_REGEXP_GLOBAL, STARTUP_DATA_STORAGE_KEY, SafeHtmlPipe, SafeStylePipe, SelectPeerModal, SelectionLengthPipe, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFileAttachmentModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedIonicModule, SharedMarkdownModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedNamedFilterModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, SplitArrayInChunksPipe, StartableService, StartupService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageExplorerTestingModule, StorageExplorerTestingRoutingModule, StorageService, StorageServiceWrapper, StrIncludesPipe, StrLengthPipe, StrReplacePipe, SubMenuTabDirective, SwipeTestPage, TABLE_SETTINGS_ENUM, TOOLBAR_HEADER_ID, TRACKED_QUERIES_STORAGE_KEY, Table2TestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextFormTestingModule, TextFormTestingPage, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ThrottledClickDirective, TimeFormatPipe, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, TreeItemEntityUtils, TruncHtmlPipe, TruncTextPipe, TruncateHtmlPipe, USER_CONTROLLER, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UrlUtils, UserController, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventNotificationModal, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, ValueFormatPipe, VersionUtils, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayResize, arraySize, asInputElement, assignSkipUndefined, base64ArrayBuffer, booleanToString, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, collectByPropertyPath, compareValues, compareValuesDesc, compareVersionNumbers, composeComparators, computeDecimalDegrees, computeDecimalPart, copyEntity2Form, createApolloClientOptions, createAppStartupInitializer, createPromiseEvent, createPromiseEventEmitter, createTestingImageDataUrl, createTrackerLink, decorateWithTakeUntil, deepMergeSkipUndefined, departmentToString, departmentsToString, disableAndClearControl, disableAndClearControls, disableControl, disableControls, emitPromiseEvent, enableControl, enableControls, enableRxStateProdMode, entityToString, equals, equalsOrNil, escapeRegExp, expansionAnimation, fadeInAnimation, fadeInOutAnimation, fadeInSlowAnimation, filterFalse, filterFormErrors, filterFormErrorsByPath, filterFormErrorsByPrefix, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatLong, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorLuminance, getColorShade, getColorTint, getControlFromPath, getFocusableInputElements, getFormControlName, getFormErrors, getFormValueFromEntity, getInputRangeFromCaretIndex, getInputSelectionRangesFromMask, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, getRandomImageWithCredit, getUserAgent, hasFocusWithin, hexToRgb, hexToRgbArray, initArrayControlsFromValues, initializeSharedModule, interpolateString, intersectArrays, isAndroid, isAppInputField, isAppTextInputField, isBlankString, isCapacitor, isChrome, isControlHasInput, isEdge, isEmptyArray, isEntityService, isFirefox, isFocusableElement, isIOS, isInputElement, isInstanceOf, isInt, isIpad, isLightColor, isMacOS, isMobile, isMutationOperation, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilObject, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isOnFieldMode, isPrint, isProgressEvent, isPromise, isResponseEvent, isSafari, isSameVersion, isStartableService, isSubscriptionOperation, isTouchUi, isValidVersion, isVersionCompatible, isWebAnimationsSupported, isWindows, joinProperties, joinPropertiesPath, lastArrayValue, logFormErrors, loggerLink, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, maskitoAutoSelectByMaskPattern, maskitoPrefixPlugin, matchMedia, matchUpperCase, mergeLoadResult, mergeObjectsWithoutUndefined, mixHex, moveInputCaretToSeparator, newArray, noHtml, noTrailingSlash, notNilOrDefault, nullIfNilOrBlank, nullIfUndefined, numberOrNilAttribute, numberToString, parseBooleanText, parseLatLongText, parseLatitudeOrLongitude, parseVersion, propertiesPathComparator, propertyComparator, propertyPathComparator, provideAccountService, provideAllIonicIcons, provideIonicIcons, provideIonicIconsInitializer, providePersonService, referentialToString, referentialsToString, registerIonicIcons, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, restoreTrackedQueries, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputContentFromEvent, selectInputRange, setCalculatedValue, setControlEnabled, setControlRequired, setControlsEnabled, setFormErrors, setPropertyByPath, setTabIndex, sleep, slideDownAnimation, slideInAnimation, slideInOutAnimation, slideUpDownAnimation, sort, splitArrayInChunks, splitById, splitByProperty, splitDegreesToDDArray, splitDegreesToDDMMArray, splitDegreesToDDMMSSArray, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toLoadData, toLoadResult, toNotNil, toNumber, trimEmptyToNull, truncateHtml, typeTextIntoInput, uncapitalizeFirstLetter, undefinedIfNull, underscoreToChangeCase, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
|
|
59669
|
+
export { ADMIN_ICONS, APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_ACCOUNT_SERVICE, APP_ACCOUNT_SERVICE_OPTIONS, APP_CELL_SELECTION_SERVICE_CONFIG_TOKEN, APP_CELL_SELECTION_SERVICE_TOKEN, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FEED_SERVICE, APP_FILE_TRANSFER_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_SERVICE, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_HOME_CONFIG, APP_HOME_TOOLBAR_BUTTONS, APP_HOTKEYS_CONFIG, APP_INPUT_FIELD, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_MESSAGE_SERVICE_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PERSON_SERVICE, APP_PERSON_SERVICE_OPTIONS, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_SHOW_TOOLTIP, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TABLE_OPTIONS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractNamedFilterService, AbstractPersonService, AbstractSelectionModelPipe, AbstractTableSelectionPipe, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, AccountUtils, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppChangePasswordModule, AppChangePasswordPage, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormContainer, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppHomePageModule, AppIconComponent, AppIconModule, AppIconSelectorField, AppIconSelectorModal, AppIconSelectorModule, AppImageGalleryComponent, AppImageGallerySlideshowComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMarkdownContent, AppMarkdownModal, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppPropertiesUtils, AppPropertyUtils, AppRegisterModule, AppResetPasswordModal, AppRowField, AppSelectPeerModule, AppSelectUsersModal, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextFormModule, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppWebSocket, AppendQueryParamsPipePipe, ArrayDistinctPipe, ArrayFilterPipe, ArrayFindByPropertyPipe, ArrayFindPipe, ArrayFirstPipe, ArrayFormTestPage, ArrayIncludesPipe, ArrayJoinPipe, ArrayLastPipe, ArrayLengthPipe, ArrayMapPipe, ArrayPluckPipe, ArraySlicePipe, ArraySortPipe, AsAnyPipe, AsArrayPipe, AsBooleanPipe, AsFloatLabelTypePipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoResizeDirective, AutoTitleDirective, AutoTooltipDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, BadgeNumberPipe, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, BooleanFormatPipe, BooleanTestPage, CORE_CONFIG_OPTIONS, CORE_ICONS, CORE_TESTING_PAGES, CapitalizePipe, CellIdentifierDirective, CellSelectionDirective, CellSelectionService, CellValueChangeListener, ChangeCaseToUnderscorePipe, ChangePasswordForm, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigFragments, ConfigService, Configuration, CoreModule, CorePipesModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_ISO_PATTERNS, DATE_MATCH_REGEXP, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_JOIN_ARRAY_VALUES_SEPARATOR, DEFAULT_JOIN_PROPERTIES_SEPARATOR, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DEFER_DIRECTIVE_NAME, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateFromPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DisplayWithPipe, DragAndDropDirective, DurationPipe, DurationTestPage, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, EMPTY_PLACEHOLDER_CHAR_REGEXP_GLOBAL, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EmptyMissingTranslationHandler, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EnvironmentHttpLoader, EnvironmentLoader, ErrorCodes, EvenPipe, FILE_KIND_ICONS, FeedAttachmentUtils, FeedDirective, FeedModule, FeedPage, FeedService, FeedsComponent, FileAttachmentComponent, FileAttachmentPreviewModal, FileAttachmentUtils, FileDownloadService, FileResponse, FileService, FileSizePipe, FileTypeUtils, FilesUtils, FirstFalsePipe, FirstPipe, FirstTruePipe, FormArrayAtControlPipe, FormArrayAtGroupPipe, FormArrayHelper, FormArrayTestModule, FormButtonsBarComponent, FormButtonsBarToken, FormErrorPipe, FormErrorTranslatePipe, FormErrorTranslator, FormFieldDefinitionUtils, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetNamePipe, FormGetPipe, FormGetValuePipe, GalleryTestPage, GeolocationUtils, GitlabUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, HttpUtils, IMAGE_DEFAULTS, ION_ICONS, IPosition, IconSelectorTestPage, IconSelectorTestingModule, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImageSizes, ImagesUtils, InMemoryEntitiesService, InputFieldTestPage, IsAllSelectedPipe, IsEmptySelectionPipe, IsLoginAccountPipe, IsMultipleSelectionPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotAllSelectedPipe, IsNotEmptySelectionPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, IsSelectedPipe, IsSingleSelectionPipe, IsValidDatePipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonFeedUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_PATTERNS, LAT_LONG_PATTERN_MAX_DECIMALS, LAT_LONG_VALUE_MAX_DECIMALS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, ListFormTestPage, ListFormTestingModule, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MASKS, MASK_RANGES, MAT_FORM_FIELD_DEFAULT_APPEARANCE, MAT_FORM_FIELD_DEFAULT_SUBSCRIPT_SIZING, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapPipe, MapToPipe, MapValuesPipe, MarkdownDirective, MarkdownService, MarkdownTestPage, MarkdownTestingModule, MarkdownToolbarComponent, MarkdownUtils, MaskitoPlaceholderPipe, MaskitoTestPage, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBadgeTestPage, MatBooleanField, MatChipsField, MatColorPipe, MatCommonTestPage, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatLatLongFieldInput, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialAutocompleteFooterDirective, MaterialAutocompleteHeaderDirective, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItem, MenuItems, MenuOptions, MenuService, MenuTestingModule, MenuTestingPage, Message, MessageAttachment, MessageAttachmentUtils, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NETWORK_DEFAULT_CONNECTION_TIMEOUT, NamedFilter, NamedFilterFilter, NamedFilterSelector, NamedFilterSelectorTestingModule, NamedFilterSelectorTestingPage, NativeWebSocket, NavActionsColumnComponent, NestedTableTestPage, NetworkService, NetworkUtils, NewTokenForm, NewTokenModal, NgInitDirective, NgVarDirective, NoHtmlPipe, NotEmptyArrayPipe, NumberFormatPipe, ObservableTestPage, OddPipe, OtherMenuTestingPage, PEER_URL_REGEXP, PLUS_PLACEHOLDER_CHAR_REGEXP_GLOBAL, PRINT_ID_QUERY_PARAM, PRINT_LOADING_STORAGE_KEY_PREFIX, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFilterAdditionalFields, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, PrintService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialToStringPipe, ReferentialUtils, ReferentialValidatorService, ReferentialsToStringPipe, RegExpUtils, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, RoundPipe, RxStateComputed, RxStateModule, RxStateOutput, RxStateProperty, RxStateRegister, RxStateSelect, SCRYPT_PARAMS, SETTINGS_COMPACT_ROWS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_ICONS, SHARED_IONIC_COMPONENTS, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_CONFIG_OPTIONS, SOCIAL_ICONS, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SPACE_PLACEHOLDER_CHAR_REGEXP_GLOBAL, STARTUP_DATA_STORAGE_KEY, SafeHtmlPipe, SafeStylePipe, SelectPeerModal, SelectionLengthPipe, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFileAttachmentModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedIonicModule, SharedMarkdownModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedNamedFilterModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, SplitArrayInChunksPipe, StartableService, StartupService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageExplorerTestingModule, StorageExplorerTestingRoutingModule, StorageService, StorageServiceWrapper, StrIncludesPipe, StrLengthPipe, StrReplacePipe, SubMenuTabDirective, SwipeTestPage, TABLE_SETTINGS_ENUM, TOOLBAR_HEADER_ID, TRACKED_QUERIES_STORAGE_KEY, Table2TestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextFormTestingModule, TextFormTestingPage, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ThrottledClickDirective, TimeFormatPipe, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, TreeItemEntityUtils, TruncHtmlPipe, TruncTextPipe, TruncateHtmlPipe, USER_CONTROLLER, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UrlUtils, UserController, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventNotificationModal, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, ValueFormatPipe, VersionUtils, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayResize, arraySize, asInputElement, assignSkipUndefined, base64ArrayBuffer, booleanToString, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, collectByPropertyPath, compareValues, compareValuesDesc, compareVersionNumbers, composeComparators, compositionMarkdownExtensions, computeDecimalDegrees, computeDecimalPart, copyEntity2Form, createApolloClientOptions, createAppStartupInitializer, createPromiseEvent, createPromiseEventEmitter, createTestingImageDataUrl, createTrackerLink, decorateWithTakeUntil, deepMergeSkipUndefined, departmentToString, departmentsToString, disableAndClearControl, disableAndClearControls, disableControl, disableControls, emitPromiseEvent, enableControl, enableControls, enableRxStateProdMode, entityToString, equals, equalsOrNil, escapeRegExp, expansionAnimation, fadeInAnimation, fadeInOutAnimation, fadeInSlowAnimation, filterFalse, filterFormErrors, filterFormErrorsByPath, filterFormErrorsByPrefix, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatLong, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorLuminance, getColorShade, getColorTint, getControlFromPath, getFocusableInputElements, getFormControlName, getFormErrors, getFormValueFromEntity, getInputRangeFromCaretIndex, getInputSelectionRangesFromMask, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, getRandomImageWithCredit, getUserAgent, hasDeferDirective, hasFocusWithin, hexToRgb, hexToRgbArray, httpHeadersToRecordLink, initArrayControlsFromValues, initializeSharedModule, insertLink, interpolateString, intersectArrays, isAndroid, isAppInputField, isAppTextInputField, isBlankString, isCapacitor, isChrome, isControlHasInput, isDeferredOperation, isEdge, isEmptyArray, isEntityService, isFirefox, isFocusableElement, isIOS, isInputElement, isInstanceOf, isInt, isIpad, isLightColor, isMacOS, isMobile, isMutationOperation, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilObject, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isOnFieldMode, isPrint, isProgressEvent, isPromise, isResponseEvent, isSafari, isSameVersion, isStartableService, isSubscriptionOperation, isTouchUi, isValidVersion, isVersionCompatible, isWebAnimationsSupported, isWindows, joinProperties, joinPropertiesPath, lastArrayValue, logFormErrors, loggerLink, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, maskitoAutoSelectByMaskPattern, maskitoPrefixPlugin, matchMedia, matchUpperCase, mergeLoadResult, mergeObjectsWithoutUndefined, mixHex, moveInputCaretToSeparator, newArray, noHtml, noTrailingSlash, notNilOrDefault, nullIfNilOrBlank, nullIfUndefined, numberOrNilAttribute, numberToString, parseBooleanText, parseLatLongText, parseLatitudeOrLongitude, parseVersion, propertiesPathComparator, propertyComparator, propertyPathComparator, provideAccountService, provideAllIonicIcons, provideIonicIcons, provideIonicIconsInitializer, providePersonService, referentialToString, referentialsToString, registerIonicIcons, remove, removeAll, removeDeferDirectives, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, restoreTrackedQueries, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputContentFromEvent, selectInputRange, setCalculatedValue, setControlEnabled, setControlRequired, setControlsEnabled, setFormErrors, setPropertyByPath, setTabIndex, sleep, slideDownAnimation, slideInAnimation, slideInOutAnimation, slideUpDownAnimation, sort, splitArrayInChunks, splitById, splitByProperty, splitDegreesToDDArray, splitDegreesToDDMMArray, splitDegreesToDDMMSSArray, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toLoadData, toLoadResult, toNotNil, toNumber, toggleLinePrefix, trimEmptyToNull, truncateHtml, typeTextIntoInput, uncapitalizeFirstLetter, undefinedIfNull, underscoreToChangeCase, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending, wrapSelection };
|
|
58898
59670
|
//# sourceMappingURL=sumaris-net.ngx-components.mjs.map
|